blob: 04b301f1b32ad0d64272f3ce68fd918985258738 [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 Yang71e39412019-09-24 09:26:46 +080028from __future__ import print_function
Tammo Spalink01e11722012-07-24 10:17:54 -070029
Yilin Yangffbf2172020-03-23 10:06:53 +080030import argparse
31import copy
Hung-Te Line89b6302016-02-04 12:23:02 +080032import logging
Yilin Yangffbf2172020-03-23 10:06:53 +080033import pprint
Hung-Te Linc83105f2011-03-16 18:38:04 +080034import struct
Hung-Te Linc052ff32013-06-20 10:48:17 +080035import sys
Tammo Spalink01e11722012-07-24 10:17:54 -070036
Hung-Te Linc83105f2011-03-16 18:38:04 +080037
38# constants imported from lib/fmap.h
Yilin Yangffbf2172020-03-23 10:06:53 +080039FMAP_SIGNATURE = b'__FMAP__'
Hung-Te Linc83105f2011-03-16 18:38:04 +080040FMAP_VER_MAJOR = 1
41FMAP_VER_MINOR_MIN = 0
42FMAP_VER_MINOR_MAX = 1
43FMAP_STRLEN = 32
Hung-Te Line89b6302016-02-04 12:23:02 +080044FMAP_SEARCH_STRIDE = 4
Hung-Te Linc83105f2011-03-16 18:38:04 +080045
46FMAP_FLAGS = {
47 'FMAP_AREA_STATIC': 1 << 0,
48 'FMAP_AREA_COMPRESSED': 1 << 1,
Yilin Yangffbf2172020-03-23 10:06:53 +080049 'FMAP_AREA_RO': 1 << 2,
50 'FMAP_AREA_PRESERVE': 1 << 3,
Hung-Te Linc83105f2011-03-16 18:38:04 +080051}
52
53FMAP_HEADER_NAMES = (
54 'signature',
55 'ver_major',
56 'ver_minor',
57 'base',
58 'size',
59 'name',
60 'nareas',
61)
62
63FMAP_AREA_NAMES = (
64 'offset',
65 'size',
66 'name',
67 'flags',
68)
69
Tammo Spalink01e11722012-07-24 10:17:54 -070070
Hung-Te Linc83105f2011-03-16 18:38:04 +080071# format string
Hung-Te Lin56b18402015-01-16 14:52:30 +080072FMAP_HEADER_FORMAT = '<8sBBQI%dsH' % (FMAP_STRLEN)
73FMAP_AREA_FORMAT = '<II%dsH' % (FMAP_STRLEN)
Hung-Te Linc83105f2011-03-16 18:38:04 +080074
75
76def _fmap_decode_header(blob, offset):
Yilin Yangffbf2172020-03-23 10:06:53 +080077 """(internal) Decodes a FMAP header from blob by offset"""
Hung-Te Linc83105f2011-03-16 18:38:04 +080078 header = {}
79 for (name, value) in zip(FMAP_HEADER_NAMES,
80 struct.unpack_from(FMAP_HEADER_FORMAT,
81 blob,
82 offset)):
83 header[name] = value
84
85 if header['signature'] != FMAP_SIGNATURE:
86 raise struct.error('Invalid signature')
87 if (header['ver_major'] != FMAP_VER_MAJOR or
88 header['ver_minor'] < FMAP_VER_MINOR_MIN or
89 header['ver_minor'] > FMAP_VER_MINOR_MAX):
90 raise struct.error('Incompatible version')
91
92 # convert null-terminated names
Yilin Yangffbf2172020-03-23 10:06:53 +080093 header['name'] = header['name'].strip(b'\x00')
94
95 # In Python 2, binary==string, so we don't need to convert.
96 if sys.version_info.major >= 3:
97 # Do the decode after verifying it to avoid decode errors due to corruption.
98 for name in FMAP_HEADER_NAMES:
99 if hasattr(header[name], 'decode'):
100 header[name] = header[name].decode('utf-8')
101
Hung-Te Linc83105f2011-03-16 18:38:04 +0800102 return (header, struct.calcsize(FMAP_HEADER_FORMAT))
103
104
105def _fmap_decode_area(blob, offset):
Yilin Yangffbf2172020-03-23 10:06:53 +0800106 """(internal) Decodes a FMAP area record from blob by offset"""
Hung-Te Linc83105f2011-03-16 18:38:04 +0800107 area = {}
108 for (name, value) in zip(FMAP_AREA_NAMES,
109 struct.unpack_from(FMAP_AREA_FORMAT, blob, offset)):
110 area[name] = value
111 # convert null-terminated names
Yilin Yangffbf2172020-03-23 10:06:53 +0800112 area['name'] = area['name'].strip(b'\x00')
Hung-Te Linc83105f2011-03-16 18:38:04 +0800113 # add a (readonly) readable FLAGS
114 area['FLAGS'] = _fmap_decode_area_flags(area['flags'])
Yilin Yangffbf2172020-03-23 10:06:53 +0800115
116 # In Python 2, binary==string, so we don't need to convert.
117 if sys.version_info.major >= 3:
118 for name in FMAP_AREA_NAMES:
119 if hasattr(area[name], 'decode'):
120 area[name] = area[name].decode('utf-8')
121
Hung-Te Linc83105f2011-03-16 18:38:04 +0800122 return (area, struct.calcsize(FMAP_AREA_FORMAT))
123
124
125def _fmap_decode_area_flags(area_flags):
Yilin Yangffbf2172020-03-23 10:06:53 +0800126 """(internal) Decodes a FMAP flags property"""
127 # Since FMAP_FLAGS is a dict with arbitrary ordering, sort the list so the
128 # output is stable. Also sorting is nicer for humans.
129 return tuple(sorted(x for x in FMAP_FLAGS if area_flags & FMAP_FLAGS[x]))
Hung-Te Linc83105f2011-03-16 18:38:04 +0800130
131
Hung-Te Line89b6302016-02-04 12:23:02 +0800132def _fmap_check_name(fmap, name):
133 """Checks if the FMAP structure has correct name.
134
135 Args:
136 fmap: A decoded FMAP structure.
137 name: A string to specify expected FMAP name.
138
139 Raises:
140 struct.error if the name does not match.
141 """
142 if fmap['name'] != name:
143 raise struct.error('Incorrect FMAP (found: "%s", expected: "%s")' %
144 (fmap['name'], name))
145
146
147def _fmap_search_header(blob, fmap_name=None):
148 """Searches FMAP headers in given blob.
149
150 Uses same logic from vboot_reference/host/lib/fmap.c.
151
152 Args:
153 blob: A string containing FMAP data.
154 fmap_name: A string to specify target FMAP name.
155
156 Returns:
157 A tuple of (fmap, size, offset).
158 """
159 lim = len(blob) - struct.calcsize(FMAP_HEADER_FORMAT)
160 align = FMAP_SEARCH_STRIDE
161
162 # Search large alignments before small ones to find "right" FMAP.
163 while align <= lim:
164 align *= 2
165
166 while align >= FMAP_SEARCH_STRIDE:
Yilin Yangffbf2172020-03-23 10:06:53 +0800167 for offset in range(align, lim + 1, align * 2):
Hung-Te Line89b6302016-02-04 12:23:02 +0800168 if not blob.startswith(FMAP_SIGNATURE, offset):
169 continue
170 try:
171 (fmap, size) = _fmap_decode_header(blob, offset)
172 if fmap_name is not None:
173 _fmap_check_name(fmap, fmap_name)
174 return (fmap, size, offset)
175 except struct.error as e:
176 # Search for next FMAP candidate.
177 logging.debug('Continue searching FMAP due to exception %r', e)
Yilin Yanga4f5f102020-01-03 10:27:42 +0800178 align //= 2
Hung-Te Line89b6302016-02-04 12:23:02 +0800179 raise struct.error('No valid FMAP signatures.')
180
181
Mary Ruthven56761952016-01-14 13:07:35 -0800182def fmap_decode(blob, offset=None, fmap_name=None):
Yilin Yangffbf2172020-03-23 10:06:53 +0800183 """Decodes a blob to FMAP dictionary object.
Hung-Te Linc83105f2011-03-16 18:38:04 +0800184
Yilin Yangffbf2172020-03-23 10:06:53 +0800185 Args:
Hung-Te Linc83105f2011-03-16 18:38:04 +0800186 blob: a binary data containing FMAP structure.
187 offset: starting offset of FMAP. When omitted, fmap_decode will search in
188 the blob.
Yilin Yangffbf2172020-03-23 10:06:53 +0800189 fmap_name: A string to specify target FMAP name.
Hung-Te Linc83105f2011-03-16 18:38:04 +0800190 """
191 fmap = {}
Hung-Te Line89b6302016-02-04 12:23:02 +0800192
Hung-Te Lin8a346442013-06-20 10:49:22 +0800193 if offset is None:
Hung-Te Line89b6302016-02-04 12:23:02 +0800194 (fmap, size, offset) = _fmap_search_header(blob, fmap_name)
Hung-Te Lin8a346442013-06-20 10:49:22 +0800195 else:
196 (fmap, size) = _fmap_decode_header(blob, offset)
Hung-Te Line89b6302016-02-04 12:23:02 +0800197 if fmap_name is not None:
198 _fmap_check_name(fmap, fmap_name)
Hung-Te Linc83105f2011-03-16 18:38:04 +0800199 fmap['areas'] = []
200 offset = offset + size
Tammo Spalink01e11722012-07-24 10:17:54 -0700201 for _ in range(fmap['nareas']):
Hung-Te Linc83105f2011-03-16 18:38:04 +0800202 (area, size) = _fmap_decode_area(blob, offset)
203 offset = offset + size
204 fmap['areas'].append(area)
205 return fmap
206
207
208def _fmap_encode_header(obj):
Yilin Yangffbf2172020-03-23 10:06:53 +0800209 """(internal) Encodes a FMAP header"""
210 # Convert strings to bytes.
211 obj = copy.deepcopy(obj)
212 for name in FMAP_HEADER_NAMES:
213 if hasattr(obj[name], 'encode'):
214 obj[name] = obj[name].encode('utf-8')
215
Hung-Te Linc83105f2011-03-16 18:38:04 +0800216 values = [obj[name] for name in FMAP_HEADER_NAMES]
217 return struct.pack(FMAP_HEADER_FORMAT, *values)
218
219
220def _fmap_encode_area(obj):
Yilin Yangffbf2172020-03-23 10:06:53 +0800221 """(internal) Encodes a FMAP area entry"""
222 # Convert strings to bytes.
223 obj = copy.deepcopy(obj)
224 for name in FMAP_AREA_NAMES:
225 if hasattr(obj[name], 'encode'):
226 obj[name] = obj[name].encode('utf-8')
227
Hung-Te Linc83105f2011-03-16 18:38:04 +0800228 values = [obj[name] for name in FMAP_AREA_NAMES]
229 return struct.pack(FMAP_AREA_FORMAT, *values)
230
231
232def fmap_encode(obj):
Yilin Yangffbf2172020-03-23 10:06:53 +0800233 """Encodes a FMAP dictionary object to blob.
Hung-Te Linc83105f2011-03-16 18:38:04 +0800234
Yilin Yangffbf2172020-03-23 10:06:53 +0800235 Args
Hung-Te Linc83105f2011-03-16 18:38:04 +0800236 obj: a FMAP dictionary object.
237 """
238 # fix up values
239 obj['nareas'] = len(obj['areas'])
240 # TODO(hungte) re-assign signature / version?
241 blob = _fmap_encode_header(obj)
242 for area in obj['areas']:
243 blob = blob + _fmap_encode_area(area)
244 return blob
245
246
Fei Shaobd07c9a2020-06-15 19:04:50 +0800247class FirmwareImage:
Hung-Te Lin14f084f2018-04-11 23:21:20 +0800248 """Provides access to firmware image via FMAP sections."""
249
250 def __init__(self, image_source):
251 self._image = image_source
252 self._fmap = fmap_decode(self._image)
253 self._areas = dict(
254 (entry['name'], [entry['offset'], entry['size']])
255 for entry in self._fmap['areas'])
256
257 def get_size(self):
258 """Returns the size of associate firmware image."""
259 return len(self._image)
260
261 def has_section(self, name):
262 """Returns if specified section is available in image."""
263 return name in self._areas
264
265 def get_section_area(self, name):
266 """Returns the area (offset, size) information of given section."""
267 if not self.has_section(name):
268 raise ValueError('get_section_area: invalid section: %s' % name)
269 return self._areas[name]
270
271 def get_section(self, name):
272 """Returns the content of specified section."""
273 area = self.get_section_area(name)
274 return self._image[area[0]:(area[0] + area[1])]
275
276 def get_section_offset(self, name):
277 area = self.get_section_area(name)
278 return self._image[area[0]:(area[0] + area[1])]
279
280 def put_section(self, name, value):
281 """Updates content of specified section in image."""
282 area = self.get_section_area(name)
283 if len(value) != area[1]:
284 raise ValueError('Value size (%d) does not fit into section (%s, %d)' %
285 (len(value), name, area[1]))
286 self._image = (self._image[0:area[0]] +
287 value +
288 self._image[(area[0] + area[1]):])
289 return True
290
291 def get_fmap_blob(self):
292 """Returns the re-encoded fmap blob from firmware image."""
293 return fmap_encode(self._fmap)
294
295
Yilin Yangffbf2172020-03-23 10:06:53 +0800296def get_parser():
297 """Return a command line parser."""
298 parser = argparse.ArgumentParser(
299 description=__doc__,
300 formatter_class=argparse.RawTextHelpFormatter)
301 parser.add_argument('file', help='The file to decode & print.')
302 parser.add_argument('--raw', action='store_true',
303 help='Dump the object output for scripts.')
304 return parser
Drew Davenportc4e868e2017-05-26 09:35:03 -0600305
Yilin Yangffbf2172020-03-23 10:06:53 +0800306
307def main(argv):
308 """Decode FMAP from supplied file and print."""
309 parser = get_parser()
310 opts = parser.parse_args(argv)
311
312 if not opts.raw:
313 print('Decoding FMAP from: %s' % opts.file)
314 blob = open(opts.file, 'rb').read()
Hung-Te Linc83105f2011-03-16 18:38:04 +0800315 obj = fmap_decode(blob)
Yilin Yangffbf2172020-03-23 10:06:53 +0800316 if opts.raw:
317 print(obj)
318 else:
319 pp = pprint.PrettyPrinter(indent=2)
320 pp.pprint(obj)
Tammo Spalink01e11722012-07-24 10:17:54 -0700321
322
323if __name__ == '__main__':
Yilin Yangffbf2172020-03-23 10:06:53 +0800324 sys.exit(main(sys.argv[1:]))