blob: 96263a2b542b3bb03630eb07725f0441d1b038f4 [file] [log] [blame]
Yilin Yang19da6932019-12-10 13:39:28 +08001#!/usr/bin/env python3
Hung-Te Lin1990b742017-08-09 17:34:57 +08002# Copyright 2010 The Chromium OS Authors. All rights reserved.
Hung-Te Linc83105f2011-03-16 18:38:04 +08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
Drew Davenportc4e868e2017-05-26 09:35:03 -06005"""
6This module provides basic encode and decode functionality to the flashrom
7memory map (FMAP) structure.
8
9WARNING: This module has been copied from third_party/flashmap/fmap.py (see
10crbug/726356 for background). Please make modifications to this file there
11first and then copy changes to this file.
Hung-Te Linc83105f2011-03-16 18:38:04 +080012
13Usage:
14 (decode)
15 obj = fmap_decode(blob)
16 print obj
17
18 (encode)
19 blob = fmap_encode(obj)
20 open('output.bin', 'w').write(blob)
21
22 The object returned by fmap_decode is a dictionary with names defined in
23 fmap.h. A special property 'FLAGS' is provided as a readable and read-only
24 tuple of decoded area flags.
25"""
26
Yilin Yang71e39412019-09-24 09:26:46 +080027from __future__ import print_function
Tammo Spalink01e11722012-07-24 10:17:54 -070028
Hung-Te Line89b6302016-02-04 12:23:02 +080029import logging
Hung-Te Linc83105f2011-03-16 18:38:04 +080030import struct
Hung-Te Linc052ff32013-06-20 10:48:17 +080031import sys
Tammo Spalink01e11722012-07-24 10:17:54 -070032
Yilin Yange6639682019-10-03 12:49:21 +080033from six.moves import xrange
Hung-Te Linc83105f2011-03-16 18:38:04 +080034
35# constants imported from lib/fmap.h
Hung-Te Lin56b18402015-01-16 14:52:30 +080036FMAP_SIGNATURE = '__FMAP__'
Hung-Te Linc83105f2011-03-16 18:38:04 +080037FMAP_VER_MAJOR = 1
38FMAP_VER_MINOR_MIN = 0
39FMAP_VER_MINOR_MAX = 1
40FMAP_STRLEN = 32
Hung-Te Line89b6302016-02-04 12:23:02 +080041FMAP_SEARCH_STRIDE = 4
Hung-Te Linc83105f2011-03-16 18:38:04 +080042
43FMAP_FLAGS = {
44 'FMAP_AREA_STATIC': 1 << 0,
45 'FMAP_AREA_COMPRESSED': 1 << 1,
46}
47
48FMAP_HEADER_NAMES = (
49 'signature',
50 'ver_major',
51 'ver_minor',
52 'base',
53 'size',
54 'name',
55 'nareas',
56)
57
58FMAP_AREA_NAMES = (
59 'offset',
60 'size',
61 'name',
62 'flags',
63)
64
Tammo Spalink01e11722012-07-24 10:17:54 -070065
Hung-Te Linc83105f2011-03-16 18:38:04 +080066# format string
Hung-Te Lin56b18402015-01-16 14:52:30 +080067FMAP_HEADER_FORMAT = '<8sBBQI%dsH' % (FMAP_STRLEN)
68FMAP_AREA_FORMAT = '<II%dsH' % (FMAP_STRLEN)
Hung-Te Linc83105f2011-03-16 18:38:04 +080069
70
71def _fmap_decode_header(blob, offset):
72 """ (internal) Decodes a FMAP header from blob by offset"""
73 header = {}
74 for (name, value) in zip(FMAP_HEADER_NAMES,
75 struct.unpack_from(FMAP_HEADER_FORMAT,
76 blob,
77 offset)):
78 header[name] = value
79
80 if header['signature'] != FMAP_SIGNATURE:
81 raise struct.error('Invalid signature')
82 if (header['ver_major'] != FMAP_VER_MAJOR or
83 header['ver_minor'] < FMAP_VER_MINOR_MIN or
84 header['ver_minor'] > FMAP_VER_MINOR_MAX):
85 raise struct.error('Incompatible version')
86
87 # convert null-terminated names
88 header['name'] = header['name'].strip(chr(0))
89 return (header, struct.calcsize(FMAP_HEADER_FORMAT))
90
91
92def _fmap_decode_area(blob, offset):
93 """ (internal) Decodes a FMAP area record from blob by offset """
94 area = {}
95 for (name, value) in zip(FMAP_AREA_NAMES,
96 struct.unpack_from(FMAP_AREA_FORMAT, blob, offset)):
97 area[name] = value
98 # convert null-terminated names
99 area['name'] = area['name'].strip(chr(0))
100 # add a (readonly) readable FLAGS
101 area['FLAGS'] = _fmap_decode_area_flags(area['flags'])
102 return (area, struct.calcsize(FMAP_AREA_FORMAT))
103
104
105def _fmap_decode_area_flags(area_flags):
106 """ (internal) Decodes a FMAP flags property """
107 return tuple([name for name in FMAP_FLAGS if area_flags & FMAP_FLAGS[name]])
108
109
Hung-Te Line89b6302016-02-04 12:23:02 +0800110def _fmap_check_name(fmap, name):
111 """Checks if the FMAP structure has correct name.
112
113 Args:
114 fmap: A decoded FMAP structure.
115 name: A string to specify expected FMAP name.
116
117 Raises:
118 struct.error if the name does not match.
119 """
120 if fmap['name'] != name:
121 raise struct.error('Incorrect FMAP (found: "%s", expected: "%s")' %
122 (fmap['name'], name))
123
124
125def _fmap_search_header(blob, fmap_name=None):
126 """Searches FMAP headers in given blob.
127
128 Uses same logic from vboot_reference/host/lib/fmap.c.
129
130 Args:
131 blob: A string containing FMAP data.
132 fmap_name: A string to specify target FMAP name.
133
134 Returns:
135 A tuple of (fmap, size, offset).
136 """
137 lim = len(blob) - struct.calcsize(FMAP_HEADER_FORMAT)
138 align = FMAP_SEARCH_STRIDE
139
140 # Search large alignments before small ones to find "right" FMAP.
141 while align <= lim:
142 align *= 2
143
144 while align >= FMAP_SEARCH_STRIDE:
145 for offset in xrange(align, lim + 1, align * 2):
146 if not blob.startswith(FMAP_SIGNATURE, offset):
147 continue
148 try:
149 (fmap, size) = _fmap_decode_header(blob, offset)
150 if fmap_name is not None:
151 _fmap_check_name(fmap, fmap_name)
152 return (fmap, size, offset)
153 except struct.error as e:
154 # Search for next FMAP candidate.
155 logging.debug('Continue searching FMAP due to exception %r', e)
Yilin Yanga4f5f102020-01-03 10:27:42 +0800156 align //= 2
Hung-Te Line89b6302016-02-04 12:23:02 +0800157 raise struct.error('No valid FMAP signatures.')
158
159
Mary Ruthven56761952016-01-14 13:07:35 -0800160def fmap_decode(blob, offset=None, fmap_name=None):
Hung-Te Linc83105f2011-03-16 18:38:04 +0800161 """ Decodes a blob to FMAP dictionary object.
162
163 Arguments:
164 blob: a binary data containing FMAP structure.
165 offset: starting offset of FMAP. When omitted, fmap_decode will search in
166 the blob.
167 """
168 fmap = {}
Hung-Te Line89b6302016-02-04 12:23:02 +0800169
Hung-Te Lin8a346442013-06-20 10:49:22 +0800170 if offset is None:
Hung-Te Line89b6302016-02-04 12:23:02 +0800171 (fmap, size, offset) = _fmap_search_header(blob, fmap_name)
Hung-Te Lin8a346442013-06-20 10:49:22 +0800172 else:
173 (fmap, size) = _fmap_decode_header(blob, offset)
Hung-Te Line89b6302016-02-04 12:23:02 +0800174 if fmap_name is not None:
175 _fmap_check_name(fmap, fmap_name)
Hung-Te Linc83105f2011-03-16 18:38:04 +0800176 fmap['areas'] = []
177 offset = offset + size
Tammo Spalink01e11722012-07-24 10:17:54 -0700178 for _ in range(fmap['nareas']):
Hung-Te Linc83105f2011-03-16 18:38:04 +0800179 (area, size) = _fmap_decode_area(blob, offset)
180 offset = offset + size
181 fmap['areas'].append(area)
182 return fmap
183
184
185def _fmap_encode_header(obj):
186 """ (internal) Encodes a FMAP header """
187 values = [obj[name] for name in FMAP_HEADER_NAMES]
188 return struct.pack(FMAP_HEADER_FORMAT, *values)
189
190
191def _fmap_encode_area(obj):
192 """ (internal) Encodes a FMAP area entry """
193 values = [obj[name] for name in FMAP_AREA_NAMES]
194 return struct.pack(FMAP_AREA_FORMAT, *values)
195
196
197def fmap_encode(obj):
198 """ Encodes a FMAP dictionary object to blob.
199
200 Arguments
201 obj: a FMAP dictionary object.
202 """
203 # fix up values
204 obj['nareas'] = len(obj['areas'])
205 # TODO(hungte) re-assign signature / version?
206 blob = _fmap_encode_header(obj)
207 for area in obj['areas']:
208 blob = blob + _fmap_encode_area(area)
209 return blob
210
211
Hung-Te Lin14f084f2018-04-11 23:21:20 +0800212class FirmwareImage(object):
213 """Provides access to firmware image via FMAP sections."""
214
215 def __init__(self, image_source):
216 self._image = image_source
217 self._fmap = fmap_decode(self._image)
218 self._areas = dict(
219 (entry['name'], [entry['offset'], entry['size']])
220 for entry in self._fmap['areas'])
221
222 def get_size(self):
223 """Returns the size of associate firmware image."""
224 return len(self._image)
225
226 def has_section(self, name):
227 """Returns if specified section is available in image."""
228 return name in self._areas
229
230 def get_section_area(self, name):
231 """Returns the area (offset, size) information of given section."""
232 if not self.has_section(name):
233 raise ValueError('get_section_area: invalid section: %s' % name)
234 return self._areas[name]
235
236 def get_section(self, name):
237 """Returns the content of specified section."""
238 area = self.get_section_area(name)
239 return self._image[area[0]:(area[0] + area[1])]
240
241 def get_section_offset(self, name):
242 area = self.get_section_area(name)
243 return self._image[area[0]:(area[0] + area[1])]
244
245 def put_section(self, name, value):
246 """Updates content of specified section in image."""
247 area = self.get_section_area(name)
248 if len(value) != area[1]:
249 raise ValueError('Value size (%d) does not fit into section (%s, %d)' %
250 (len(value), name, area[1]))
251 self._image = (self._image[0:area[0]] +
252 value +
253 self._image[(area[0] + area[1]):])
254 return True
255
256 def get_fmap_blob(self):
257 """Returns the re-encoded fmap blob from firmware image."""
258 return fmap_encode(self._fmap)
259
260
Tammo Spalink01e11722012-07-24 10:17:54 -0700261def main():
Drew Davenportc4e868e2017-05-26 09:35:03 -0600262 """Decode FMAP from supplied file and print."""
263 if len(sys.argv) < 2:
Yilin Yang71e39412019-09-24 09:26:46 +0800264 print('Usage: fmap.py <file>')
Drew Davenportc4e868e2017-05-26 09:35:03 -0600265 sys.exit(1)
266
267 filename = sys.argv[1]
Yilin Yang71e39412019-09-24 09:26:46 +0800268 print('Decoding FMAP from: %s' % filename)
Hung-Te Linc052ff32013-06-20 10:48:17 +0800269 blob = open(filename).read()
Hung-Te Linc83105f2011-03-16 18:38:04 +0800270 obj = fmap_decode(blob)
Yilin Yang71e39412019-09-24 09:26:46 +0800271 print(obj)
Tammo Spalink01e11722012-07-24 10:17:54 -0700272
273
274if __name__ == '__main__':
275 main()