| #!/usr/bin/env python2.6 |
| |
| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import os |
| import re |
| import struct |
| import subprocess |
| import sys |
| import tempfile |
| |
| # TODO(clchiou): Rewrite this part after official flashmap implementation is |
| # pulled into Chromium OS code base |
| |
| # constants imported from lib/fmap.h |
| FMAP_SIGNATURE = "__FMAP__" |
| FMAP_VER_MAJOR = 1 |
| FMAP_VER_MINOR = 0 |
| FMAP_STRLEN = 32 |
| |
| FMAP_AREA_STATIC = 1 << 0 |
| FMAP_AREA_COMPRESSED = 1 << 1 |
| FMAP_AREA_RO = 1 << 2 |
| |
| FMAP_HEADER_FORMAT = "<8sBBQI%dsH" % (FMAP_STRLEN) |
| FMAP_AREA_FORMAT = "<II%dsH" % (FMAP_STRLEN) |
| |
| FMAP_HEADER_NAMES = ( |
| 'signature', |
| 'ver_major', |
| 'ver_minor', |
| 'base', |
| 'size', |
| 'name', |
| 'nareas', |
| ) |
| |
| FMAP_AREA_NAMES = ( |
| 'offset', |
| 'size', |
| 'name', |
| 'flags', |
| ) |
| |
| RE_ASSIGNMENT = re.compile(r'^(\w+)=(.*)$') |
| |
| VERBOSE = False |
| |
| |
| class ConfigError(Exception): |
| pass |
| |
| |
| class PackError(Exception): |
| pass |
| |
| |
| class Entry(dict): |
| |
| @staticmethod |
| def _CheckFields(kwargs, fields): |
| for f in fields: |
| if f not in kwargs: |
| raise ConfigError('Entry: missing required field: %s' % f) |
| |
| def __init__(self, **kwargs): |
| Entry._CheckFields(kwargs, ('offset', 'length', 'name')) |
| super(Entry, self).__init__(kwargs) |
| |
| def __getattr__(self, name): |
| return self[name] |
| |
| def IsOverlapped(self, entry): |
| return (entry.offset <= self.offset < entry.offset + entry.length or |
| self.offset <= entry.offset < self.offset + self.length) |
| |
| def Pack(self, firmware_image, entries): |
| raise PackError('class Entry does not implement Pack()') |
| |
| |
| class EntryFmap(Entry): |
| |
| def __init__(self, **kwargs): |
| Entry._CheckFields(kwargs, ('ver_major', 'ver_minor', 'base', 'size')) |
| super(EntryFmap, self).__init__(**kwargs) |
| |
| def Pack(self, firmware_image, entries): |
| # prepare header areas |
| areas = [] |
| for e in entries: |
| if isinstance(e, EntryFmapArea): |
| areas.append(dict((name, e[name] if name != 'size' else e['length']) |
| for name in FMAP_AREA_NAMES)) |
| |
| # prepare header |
| obj = {'areas':areas} |
| for name in FMAP_HEADER_NAMES: |
| if name == 'nareas': |
| v = len(areas) |
| elif name == 'signature': |
| v = FMAP_SIGNATURE |
| else: |
| v = self[name] |
| obj[name] = v |
| |
| blob = fmap_encode(obj) |
| |
| if len(blob) > self.length: |
| raise PackError('fmap too large: %d > %d' % (len(blob), self.length)) |
| |
| firmware_image.seek(self.offset) |
| firmware_image.write(blob) |
| |
| |
| class EntryFmapArea(Entry): |
| |
| def __init__(self, **kwargs): |
| Entry._CheckFields(kwargs, ('flags',)) |
| super(EntryFmapArea, self).__init__(**kwargs) |
| |
| def Pack(self, firmware_image, entries): |
| pass |
| |
| |
| class EntryBlob(EntryFmapArea): |
| |
| def __init__(self, **kwargs): |
| Entry._CheckFields(kwargs, ('path',)) |
| super(EntryBlob, self).__init__(**kwargs) |
| |
| def Pack(self, firmware_image, entries): |
| size = os.stat(self.path).st_size |
| if size > self.length: |
| raise PackError('blob too large: %s: %d > %d' % |
| (self.path, size, self.length)) |
| if size == 0: # special case for files like /dev/zero |
| size = self.length |
| with open(self.path, 'rb') as blob_image: |
| firmware_image.seek(self.offset) |
| firmware_image.write(blob_image.read(size)) |
| |
| |
| class EntryKeyBlock(EntryFmapArea): |
| |
| stdout = None |
| stderr = None |
| |
| def __init__(self, **kwargs): |
| Entry._CheckFields(kwargs, |
| ('keyblock', 'signprivate', 'version', 'fv', 'kernelkey')) |
| super(EntryKeyBlock, self).__init__(**kwargs) |
| if VERBOSE: |
| EntryKeyBlock.stdout = sys.stdout |
| EntryKeyBlock.stderr = sys.stderr |
| |
| def Pack(self, firmware_image, entries): |
| fd, path = tempfile.mkstemp() |
| try: |
| args = [ |
| 'vbutil_firmware', |
| '--vblock', path, |
| '--keyblock', self.keyblock, |
| '--signprivate', self.signprivate, |
| '--version', '%d' % self.version, |
| '--fv', self.fv, |
| '--kernelkey', self.kernelkey, |
| ] |
| _Info('run: %s' % ' '.join(args)) |
| proc = subprocess.Popen(args, |
| stdout=EntryKeyBlock.stdout, stderr=EntryKeyBlock.stderr) |
| proc.wait() |
| if proc.returncode != 0: |
| raise PackError('cannot make key block: vbutil_firmware returns %d' % |
| proc.returncode) |
| |
| size = os.stat(path).st_size |
| if size > self.length: |
| raise PackError('key block too large: %d > %d' % (size, self.length)) |
| |
| with open(path, 'rb') as keyblock_image: |
| firmware_image.seek(self.offset) |
| firmware_image.write(keyblock_image.read()) |
| finally: |
| os.unlink(path) |
| |
| |
| # TODO(clchiou): Keep fmap_encode interface compatible with official's flashmap |
| # implementation, and remove it after it is pulled in. |
| def fmap_encode(obj): |
| def _FormatBlob(format, names, obj): |
| return struct.pack(format, *(obj[name] for name in names)) |
| obj['nareas'] = len(obj['areas']) |
| blob = _FormatBlob(FMAP_HEADER_FORMAT, FMAP_HEADER_NAMES, obj) |
| for area in obj['areas']: |
| blob = blob + _FormatBlob(FMAP_AREA_FORMAT, FMAP_AREA_NAMES, area) |
| return blob |
| |
| |
| def parse_assignment(stmt): |
| m = RE_ASSIGNMENT.match(stmt) |
| if m is None: |
| raise ConfigError('illegal statement: %s' % repr(stmt)) |
| return (m.group(1), parse_value(m.group(2))) |
| |
| |
| def parse_value(expr): |
| if ((expr.startswith('"') and expr.endswith('"')) or |
| (expr.startswith("'") and expr.endswith("'"))): |
| return expr[1:-1] # if it is quoted, always interpreted as string literals |
| try: |
| return int(expr, 0) |
| except ValueError: |
| return expr # if not a number, interpret as string literals |
| |
| |
| def pack_firmware_image(entries, output_path, image_size): |
| entries = sorted(entries, key=lambda e: e.offset) |
| for e1, e2 in zip(entries, entries[1:]): |
| # Allow overlap between "pure" fmap areas, but not any of its subclasses |
| # Here we exploit the fact that Entry is a new-style class |
| if (e1.IsOverlapped(e2) and |
| type(e1) is not EntryFmapArea and type(e2) is not EntryFmapArea): |
| raise PackError('overlapped entries: [%08x:%08x], [%08x:%08x]' % |
| (e1.offset, e1.offset + e1.length, e2.offset, e2.offset + e2.length)) |
| |
| with open(output_path, 'wb') as firmware_image: |
| # resize firmware image file |
| firmware_image.seek(0) |
| firmware_image.write('\0' * image_size) |
| |
| for entry in entries: |
| entry.Pack(firmware_image, entries) |
| |
| |
| def _Info(msg): |
| if VERBOSE: |
| print >>sys.stderr, 'INFO: %s' % msg |
| |
| |
| def main(): |
| global VERBOSE |
| |
| if len(sys.argv) < 2: |
| print 'Usage: %s [-v] CONFIG_FILE [NAME=VALUE...]' % sys.argv[0] |
| sys.exit(1) |
| |
| if sys.argv[1] == '-v': |
| VERBOSE = True |
| argv = sys.argv[0:1] + sys.argv[2:] |
| else: |
| argv = sys.argv |
| |
| if len(argv) > 2: |
| env = dict(parse_assignment(stmt) for stmt in argv[2:]) |
| else: |
| env = {} |
| |
| execfile(argv[1], globals(), env) |
| |
| for varname in ('ENTRIES', 'OUTPUT', 'SIZE'): |
| if varname not in env: |
| raise ConfigError('undefined variable: %s' % varname) |
| _Info('%s = %s' % (varname, repr(env[varname]))) |
| |
| pack_firmware_image(env['ENTRIES'], env['OUTPUT'], env['SIZE']) |
| |
| sys.exit(0) |
| |
| |
| if __name__ == '__main__': |
| main() |