blob: 04021f56900a51506a937f93654003026a8604d5 [file] [log] [blame]
Yilin Yang19da6932019-12-10 13:39:28 +08001#!/usr/bin/env python3
Yilin Yangffbf2172020-03-23 10:06:53 +08002# -*- coding: utf-8 -*-
Hung-Te Lin1990b742017-08-09 17:34:57 +08003# Copyright 2010 The Chromium OS Authors. All rights reserved.
Hung-Te Linc83105f2011-03-16 18:38:04 +08004# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
Drew Davenportc4e868e2017-05-26 09:35:03 -06006"""
7This module provides basic encode and decode functionality to the flashrom
8memory map (FMAP) structure.
9
10WARNING: This module has been copied from third_party/flashmap/fmap.py (see
11crbug/726356 for background). Please make modifications to this file there
12first and then copy changes to this file.
Hung-Te Linc83105f2011-03-16 18:38:04 +080013
14Usage:
15 (decode)
16 obj = fmap_decode(blob)
17 print obj
18
19 (encode)
20 blob = fmap_encode(obj)
21 open('output.bin', 'w').write(blob)
22
23 The object returned by fmap_decode is a dictionary with names defined in
24 fmap.h. A special property 'FLAGS' is provided as a readable and read-only
25 tuple of decoded area flags.
26"""
27
Yilin Yangffbf2172020-03-23 10:06:53 +080028import argparse
29import copy
Hung-Te Line89b6302016-02-04 12:23:02 +080030import logging
Yilin Yangffbf2172020-03-23 10:06:53 +080031import pprint
Hung-Te Linc83105f2011-03-16 18:38:04 +080032import struct
Hung-Te Linc052ff32013-06-20 10:48:17 +080033import sys
Tammo Spalink01e11722012-07-24 10:17:54 -070034
Hung-Te Linc83105f2011-03-16 18:38:04 +080035
36# constants imported from lib/fmap.h
Yilin Yangffbf2172020-03-23 10:06:53 +080037FMAP_SIGNATURE = b'__FMAP__'
Hung-Te Linc83105f2011-03-16 18:38:04 +080038FMAP_VER_MAJOR = 1
39FMAP_VER_MINOR_MIN = 0
40FMAP_VER_MINOR_MAX = 1
41FMAP_STRLEN = 32
Hung-Te Line89b6302016-02-04 12:23:02 +080042FMAP_SEARCH_STRIDE = 4
Hung-Te Linc83105f2011-03-16 18:38:04 +080043
44FMAP_FLAGS = {
45 'FMAP_AREA_STATIC': 1 << 0,
46 'FMAP_AREA_COMPRESSED': 1 << 1,
Yilin Yangffbf2172020-03-23 10:06:53 +080047 'FMAP_AREA_RO': 1 << 2,
48 'FMAP_AREA_PRESERVE': 1 << 3,
Hung-Te Linc83105f2011-03-16 18:38:04 +080049}
50
51FMAP_HEADER_NAMES = (
52 'signature',
53 'ver_major',
54 'ver_minor',
55 'base',
56 'size',
57 'name',
58 'nareas',
59)
60
61FMAP_AREA_NAMES = (
62 'offset',
63 'size',
64 'name',
65 'flags',
66)
67
Tammo Spalink01e11722012-07-24 10:17:54 -070068
Hung-Te Linc83105f2011-03-16 18:38:04 +080069# format string
Hung-Te Lin56b18402015-01-16 14:52:30 +080070FMAP_HEADER_FORMAT = '<8sBBQI%dsH' % (FMAP_STRLEN)
71FMAP_AREA_FORMAT = '<II%dsH' % (FMAP_STRLEN)
Hung-Te Linc83105f2011-03-16 18:38:04 +080072
73
74def _fmap_decode_header(blob, offset):
Yilin Yangffbf2172020-03-23 10:06:53 +080075 """(internal) Decodes a FMAP header from blob by offset"""
Hung-Te Linc83105f2011-03-16 18:38:04 +080076 header = {}
77 for (name, value) in zip(FMAP_HEADER_NAMES,
78 struct.unpack_from(FMAP_HEADER_FORMAT,
79 blob,
80 offset)):
81 header[name] = value
82
83 if header['signature'] != FMAP_SIGNATURE:
84 raise struct.error('Invalid signature')
85 if (header['ver_major'] != FMAP_VER_MAJOR or
86 header['ver_minor'] < FMAP_VER_MINOR_MIN or
87 header['ver_minor'] > FMAP_VER_MINOR_MAX):
88 raise struct.error('Incompatible version')
89
90 # convert null-terminated names
Yilin Yangffbf2172020-03-23 10:06:53 +080091 header['name'] = header['name'].strip(b'\x00')
92
93 # In Python 2, binary==string, so we don't need to convert.
94 if sys.version_info.major >= 3:
95 # Do the decode after verifying it to avoid decode errors due to corruption.
96 for name in FMAP_HEADER_NAMES:
97 if hasattr(header[name], 'decode'):
98 header[name] = header[name].decode('utf-8')
99
Hung-Te Linc83105f2011-03-16 18:38:04 +0800100 return (header, struct.calcsize(FMAP_HEADER_FORMAT))
101
102
103def _fmap_decode_area(blob, offset):
Yilin Yangffbf2172020-03-23 10:06:53 +0800104 """(internal) Decodes a FMAP area record from blob by offset"""
Hung-Te Linc83105f2011-03-16 18:38:04 +0800105 area = {}
106 for (name, value) in zip(FMAP_AREA_NAMES,
107 struct.unpack_from(FMAP_AREA_FORMAT, blob, offset)):
108 area[name] = value
109 # convert null-terminated names
Yilin Yangffbf2172020-03-23 10:06:53 +0800110 area['name'] = area['name'].strip(b'\x00')
Hung-Te Linc83105f2011-03-16 18:38:04 +0800111 # add a (readonly) readable FLAGS
112 area['FLAGS'] = _fmap_decode_area_flags(area['flags'])
Yilin Yangffbf2172020-03-23 10:06:53 +0800113
114 # In Python 2, binary==string, so we don't need to convert.
115 if sys.version_info.major >= 3:
116 for name in FMAP_AREA_NAMES:
117 if hasattr(area[name], 'decode'):
118 area[name] = area[name].decode('utf-8')
119
Hung-Te Linc83105f2011-03-16 18:38:04 +0800120 return (area, struct.calcsize(FMAP_AREA_FORMAT))
121
122
123def _fmap_decode_area_flags(area_flags):
Yilin Yangffbf2172020-03-23 10:06:53 +0800124 """(internal) Decodes a FMAP flags property"""
125 # Since FMAP_FLAGS is a dict with arbitrary ordering, sort the list so the
126 # output is stable. Also sorting is nicer for humans.
127 return tuple(sorted(x for x in FMAP_FLAGS if area_flags & FMAP_FLAGS[x]))
Hung-Te Linc83105f2011-03-16 18:38:04 +0800128
129
Hung-Te Line89b6302016-02-04 12:23:02 +0800130def _fmap_check_name(fmap, name):
131 """Checks if the FMAP structure has correct name.
132
133 Args:
134 fmap: A decoded FMAP structure.
135 name: A string to specify expected FMAP name.
136
137 Raises:
138 struct.error if the name does not match.
139 """
140 if fmap['name'] != name:
141 raise struct.error('Incorrect FMAP (found: "%s", expected: "%s")' %
142 (fmap['name'], name))
143
144
145def _fmap_search_header(blob, fmap_name=None):
146 """Searches FMAP headers in given blob.
147
148 Uses same logic from vboot_reference/host/lib/fmap.c.
149
150 Args:
151 blob: A string containing FMAP data.
152 fmap_name: A string to specify target FMAP name.
153
154 Returns:
155 A tuple of (fmap, size, offset).
156 """
157 lim = len(blob) - struct.calcsize(FMAP_HEADER_FORMAT)
158 align = FMAP_SEARCH_STRIDE
159
160 # Search large alignments before small ones to find "right" FMAP.
161 while align <= lim:
162 align *= 2
163
164 while align >= FMAP_SEARCH_STRIDE:
Yilin Yangffbf2172020-03-23 10:06:53 +0800165 for offset in range(align, lim + 1, align * 2):
Hung-Te Line89b6302016-02-04 12:23:02 +0800166 if not blob.startswith(FMAP_SIGNATURE, offset):
167 continue
168 try:
169 (fmap, size) = _fmap_decode_header(blob, offset)
170 if fmap_name is not None:
171 _fmap_check_name(fmap, fmap_name)
172 return (fmap, size, offset)
173 except struct.error as e:
174 # Search for next FMAP candidate.
175 logging.debug('Continue searching FMAP due to exception %r', e)
Yilin Yanga4f5f102020-01-03 10:27:42 +0800176 align //= 2
Hung-Te Line89b6302016-02-04 12:23:02 +0800177 raise struct.error('No valid FMAP signatures.')
178
179
Mary Ruthven56761952016-01-14 13:07:35 -0800180def fmap_decode(blob, offset=None, fmap_name=None):
Yilin Yangffbf2172020-03-23 10:06:53 +0800181 """Decodes a blob to FMAP dictionary object.
Hung-Te Linc83105f2011-03-16 18:38:04 +0800182
Yilin Yangffbf2172020-03-23 10:06:53 +0800183 Args:
Hung-Te Linc83105f2011-03-16 18:38:04 +0800184 blob: a binary data containing FMAP structure.
185 offset: starting offset of FMAP. When omitted, fmap_decode will search in
186 the blob.
Yilin Yangffbf2172020-03-23 10:06:53 +0800187 fmap_name: A string to specify target FMAP name.
Hung-Te Linc83105f2011-03-16 18:38:04 +0800188 """
189 fmap = {}
Hung-Te Line89b6302016-02-04 12:23:02 +0800190
Hung-Te Lin8a346442013-06-20 10:49:22 +0800191 if offset is None:
Hung-Te Line89b6302016-02-04 12:23:02 +0800192 (fmap, size, offset) = _fmap_search_header(blob, fmap_name)
Hung-Te Lin8a346442013-06-20 10:49:22 +0800193 else:
194 (fmap, size) = _fmap_decode_header(blob, offset)
Hung-Te Line89b6302016-02-04 12:23:02 +0800195 if fmap_name is not None:
196 _fmap_check_name(fmap, fmap_name)
Hung-Te Linc83105f2011-03-16 18:38:04 +0800197 fmap['areas'] = []
198 offset = offset + size
Tammo Spalink01e11722012-07-24 10:17:54 -0700199 for _ in range(fmap['nareas']):
Hung-Te Linc83105f2011-03-16 18:38:04 +0800200 (area, size) = _fmap_decode_area(blob, offset)
201 offset = offset + size
202 fmap['areas'].append(area)
203 return fmap
204
205
206def _fmap_encode_header(obj):
Yilin Yangffbf2172020-03-23 10:06:53 +0800207 """(internal) Encodes a FMAP header"""
208 # Convert strings to bytes.
209 obj = copy.deepcopy(obj)
210 for name in FMAP_HEADER_NAMES:
211 if hasattr(obj[name], 'encode'):
212 obj[name] = obj[name].encode('utf-8')
213
Hung-Te Linc83105f2011-03-16 18:38:04 +0800214 values = [obj[name] for name in FMAP_HEADER_NAMES]
215 return struct.pack(FMAP_HEADER_FORMAT, *values)
216
217
218def _fmap_encode_area(obj):
Yilin Yangffbf2172020-03-23 10:06:53 +0800219 """(internal) Encodes a FMAP area entry"""
220 # Convert strings to bytes.
221 obj = copy.deepcopy(obj)
222 for name in FMAP_AREA_NAMES:
223 if hasattr(obj[name], 'encode'):
224 obj[name] = obj[name].encode('utf-8')
225
Hung-Te Linc83105f2011-03-16 18:38:04 +0800226 values = [obj[name] for name in FMAP_AREA_NAMES]
227 return struct.pack(FMAP_AREA_FORMAT, *values)
228
229
230def fmap_encode(obj):
Yilin Yangffbf2172020-03-23 10:06:53 +0800231 """Encodes a FMAP dictionary object to blob.
Hung-Te Linc83105f2011-03-16 18:38:04 +0800232
Yilin Yangffbf2172020-03-23 10:06:53 +0800233 Args
Hung-Te Linc83105f2011-03-16 18:38:04 +0800234 obj: a FMAP dictionary object.
235 """
236 # fix up values
237 obj['nareas'] = len(obj['areas'])
238 # TODO(hungte) re-assign signature / version?
239 blob = _fmap_encode_header(obj)
240 for area in obj['areas']:
241 blob = blob + _fmap_encode_area(area)
242 return blob
243
244
Fei Shaobd07c9a2020-06-15 19:04:50 +0800245class FirmwareImage:
Hung-Te Lin14f084f2018-04-11 23:21:20 +0800246 """Provides access to firmware image via FMAP sections."""
247
248 def __init__(self, image_source):
249 self._image = image_source
250 self._fmap = fmap_decode(self._image)
251 self._areas = dict(
252 (entry['name'], [entry['offset'], entry['size']])
253 for entry in self._fmap['areas'])
254
Cheng-Han Yang24d42d92020-08-09 04:53:53 +0800255 def get_blob(self):
256 """Returns the raw firmware blob."""
257 return self._image
258
Hung-Te Lin14f084f2018-04-11 23:21:20 +0800259 def get_size(self):
260 """Returns the size of associate firmware image."""
261 return len(self._image)
262
263 def has_section(self, name):
264 """Returns if specified section is available in image."""
265 return name in self._areas
266
267 def get_section_area(self, name):
268 """Returns the area (offset, size) information of given section."""
269 if not self.has_section(name):
270 raise ValueError('get_section_area: invalid section: %s' % name)
271 return self._areas[name]
272
273 def get_section(self, name):
274 """Returns the content of specified section."""
275 area = self.get_section_area(name)
276 return self._image[area[0]:(area[0] + area[1])]
277
278 def get_section_offset(self, name):
279 area = self.get_section_area(name)
280 return self._image[area[0]:(area[0] + area[1])]
281
282 def put_section(self, name, value):
283 """Updates content of specified section in image."""
284 area = self.get_section_area(name)
285 if len(value) != area[1]:
286 raise ValueError('Value size (%d) does not fit into section (%s, %d)' %
287 (len(value), name, area[1]))
288 self._image = (self._image[0:area[0]] +
289 value +
290 self._image[(area[0] + area[1]):])
291 return True
292
293 def get_fmap_blob(self):
294 """Returns the re-encoded fmap blob from firmware image."""
295 return fmap_encode(self._fmap)
296
297
Yilin Yangffbf2172020-03-23 10:06:53 +0800298def get_parser():
299 """Return a command line parser."""
300 parser = argparse.ArgumentParser(
301 description=__doc__,
302 formatter_class=argparse.RawTextHelpFormatter)
303 parser.add_argument('file', help='The file to decode & print.')
304 parser.add_argument('--raw', action='store_true',
305 help='Dump the object output for scripts.')
306 return parser
Drew Davenportc4e868e2017-05-26 09:35:03 -0600307
Yilin Yangffbf2172020-03-23 10:06:53 +0800308
309def main(argv):
310 """Decode FMAP from supplied file and print."""
311 parser = get_parser()
312 opts = parser.parse_args(argv)
313
314 if not opts.raw:
315 print('Decoding FMAP from: %s' % opts.file)
316 blob = open(opts.file, 'rb').read()
Hung-Te Linc83105f2011-03-16 18:38:04 +0800317 obj = fmap_decode(blob)
Yilin Yangffbf2172020-03-23 10:06:53 +0800318 if opts.raw:
319 print(obj)
320 else:
321 pp = pprint.PrettyPrinter(indent=2)
322 pp.pprint(obj)
Tammo Spalink01e11722012-07-24 10:17:54 -0700323
324
325if __name__ == '__main__':
Yilin Yangffbf2172020-03-23 10:06:53 +0800326 sys.exit(main(sys.argv[1:]))