| OLD | NEW |
| (Empty) | |
| 1 #!/usr/bin/env python2.6 |
| 2 |
| 3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. |
| 6 |
| 7 import os |
| 8 import re |
| 9 import struct |
| 10 import subprocess |
| 11 import sys |
| 12 import tempfile |
| 13 |
| 14 # TODO(clchiou): Rewrite this part after official flashmap implementation is |
| 15 # pulled into Chromium OS code base |
| 16 |
| 17 # constants imported from lib/fmap.h |
| 18 FMAP_SIGNATURE = "__FMAP__" |
| 19 FMAP_VER_MAJOR = 1 |
| 20 FMAP_VER_MINOR = 0 |
| 21 FMAP_STRLEN = 32 |
| 22 |
| 23 FMAP_AREA_STATIC = 1 << 0 |
| 24 FMAP_AREA_COMPRESSED = 1 << 1 |
| 25 FMAP_AREA_RO = 1 << 2 |
| 26 |
| 27 FMAP_HEADER_FORMAT = "<8sBBQI%dsH" % (FMAP_STRLEN) |
| 28 FMAP_AREA_FORMAT = "<II%dsH" % (FMAP_STRLEN) |
| 29 |
| 30 FMAP_HEADER_NAMES = ( |
| 31 'signature', |
| 32 'ver_major', |
| 33 'ver_minor', |
| 34 'base', |
| 35 'size', |
| 36 'name', |
| 37 'nareas', |
| 38 ) |
| 39 |
| 40 FMAP_AREA_NAMES = ( |
| 41 'offset', |
| 42 'size', |
| 43 'name', |
| 44 'flags', |
| 45 ) |
| 46 |
| 47 RE_ASSIGNMENT = re.compile(r'^(\w+)=(.*)$') |
| 48 |
| 49 VERBOSE = False |
| 50 |
| 51 |
| 52 class ConfigError(Exception): |
| 53 pass |
| 54 |
| 55 |
| 56 class PackError(Exception): |
| 57 pass |
| 58 |
| 59 |
| 60 class Entry(dict): |
| 61 |
| 62 @staticmethod |
| 63 def _CheckFields(kwargs, fields): |
| 64 for f in fields: |
| 65 if f not in kwargs: |
| 66 raise ConfigError('Entry: missing required field: %s' % f) |
| 67 |
| 68 def __init__(self, **kwargs): |
| 69 Entry._CheckFields(kwargs, ('offset', 'length', 'name')) |
| 70 super(Entry, self).__init__(kwargs) |
| 71 |
| 72 def __getattr__(self, name): |
| 73 return self[name] |
| 74 |
| 75 def IsOverlapped(self, entry): |
| 76 return (entry.offset <= self.offset < entry.offset + entry.length or |
| 77 self.offset <= entry.offset < self.offset + self.length) |
| 78 |
| 79 def Pack(self, firmware_image, entries): |
| 80 raise PackError('class Entry does not implement Pack()') |
| 81 |
| 82 |
| 83 class EntryFmap(Entry): |
| 84 |
| 85 def __init__(self, **kwargs): |
| 86 Entry._CheckFields(kwargs, ('ver_major', 'ver_minor', 'base', 'size')) |
| 87 super(EntryFmap, self).__init__(**kwargs) |
| 88 |
| 89 def Pack(self, firmware_image, entries): |
| 90 # prepare header areas |
| 91 areas = [] |
| 92 for e in entries: |
| 93 if isinstance(e, EntryFmapArea): |
| 94 areas.append(dict((name, e[name] if name != 'size' else e['length']) |
| 95 for name in FMAP_AREA_NAMES)) |
| 96 |
| 97 # prepare header |
| 98 obj = {'areas':areas} |
| 99 for name in FMAP_HEADER_NAMES: |
| 100 if name == 'nareas': |
| 101 v = len(areas) |
| 102 elif name == 'signature': |
| 103 v = FMAP_SIGNATURE |
| 104 else: |
| 105 v = self[name] |
| 106 obj[name] = v |
| 107 |
| 108 blob = fmap_encode(obj) |
| 109 |
| 110 if len(blob) > self.length: |
| 111 raise PackError('fmap too large: %d > %d' % (len(blob), self.length)) |
| 112 |
| 113 firmware_image.seek(self.offset) |
| 114 firmware_image.write(blob) |
| 115 |
| 116 |
| 117 class EntryFmapArea(Entry): |
| 118 |
| 119 def __init__(self, **kwargs): |
| 120 Entry._CheckFields(kwargs, ('flags',)) |
| 121 super(EntryFmapArea, self).__init__(**kwargs) |
| 122 |
| 123 |
| 124 class EntryBlob(EntryFmapArea): |
| 125 |
| 126 def __init__(self, **kwargs): |
| 127 Entry._CheckFields(kwargs, ('path',)) |
| 128 super(EntryBlob, self).__init__(**kwargs) |
| 129 |
| 130 def Pack(self, firmware_image, entries): |
| 131 size = os.stat(self.path).st_size |
| 132 if size > 0: |
| 133 size = min(size, self.length) |
| 134 else: |
| 135 size = self.length |
| 136 with open(self.path, 'rb') as blob_image: |
| 137 firmware_image.seek(self.offset) |
| 138 firmware_image.write(blob_image.read(size)) |
| 139 |
| 140 |
| 141 class EntryKeyBlock(EntryFmapArea): |
| 142 |
| 143 stdout = None |
| 144 stderr = None |
| 145 |
| 146 def __init__(self, **kwargs): |
| 147 Entry._CheckFields(kwargs, |
| 148 ('keyblock', 'signprivate', 'version', 'fv', 'kernelkey')) |
| 149 super(EntryKeyBlock, self).__init__(**kwargs) |
| 150 if VERBOSE: |
| 151 EntryKeyBlock.stdout = sys.stdout |
| 152 EntryKeyBlock.stderr = sys.stderr |
| 153 |
| 154 def Pack(self, firmware_image, entries): |
| 155 fd, path = tempfile.mkstemp() |
| 156 try: |
| 157 args = [ |
| 158 'vbutil_firmware', |
| 159 '--vblock', path, |
| 160 '--keyblock', self.keyblock, |
| 161 '--signprivate', self.signprivate, |
| 162 '--version', '%d' % self.version, |
| 163 '--fv', self.fv, |
| 164 '--kernelkey', self.kernelkey, |
| 165 ] |
| 166 _Info('run: %s' % ' '.join(args)) |
| 167 proc = subprocess.Popen(args, |
| 168 stdout=EntryKeyBlock.stdout, stderr=EntryKeyBlock.stderr) |
| 169 proc.wait() |
| 170 if proc.returncode != 0: |
| 171 raise PackError('cannot make key block: vbutil_firmware returns %d' % |
| 172 proc.returncode) |
| 173 |
| 174 size = os.stat(path).st_size |
| 175 if size > self.length: |
| 176 raise PackError('key block too large: %d > %d' % (size, self.length)) |
| 177 |
| 178 with open(path, 'rb') as keyblock_image: |
| 179 firmware_image.seek(self.offset) |
| 180 firmware_image.write(keyblock_image.read()) |
| 181 finally: |
| 182 os.unlink(path) |
| 183 |
| 184 |
| 185 # TODO(clchiou): Keep fmap_encode interface compatible with official's flashmap |
| 186 # implementation, and remove it after it is pulled in. |
| 187 def fmap_encode(obj): |
| 188 def _FormatBlob(format, names, obj): |
| 189 return struct.pack(format, *(obj[name] for name in names)) |
| 190 obj['nareas'] = len(obj['areas']) |
| 191 blob = _FormatBlob(FMAP_HEADER_FORMAT, FMAP_HEADER_NAMES, obj) |
| 192 for area in obj['areas']: |
| 193 blob = blob + _FormatBlob(FMAP_AREA_FORMAT, FMAP_AREA_NAMES, area) |
| 194 return blob |
| 195 |
| 196 |
| 197 def parse_assignment(stmt): |
| 198 m = RE_ASSIGNMENT.match(stmt) |
| 199 if m is None: |
| 200 raise ConfigError('illegal statement: %s' % repr(stmt)) |
| 201 return (m.group(1), parse_value(m.group(2))) |
| 202 |
| 203 |
| 204 def parse_value(expr): |
| 205 if ((expr.startswith('"') and expr.endswith('"')) or |
| 206 (expr.startswith("'") and expr.endswith("'"))): |
| 207 return expr[1:-1] # if it is quoted, always interpreted as string literals |
| 208 try: |
| 209 return int(expr, 0) |
| 210 except ValueError: |
| 211 return expr # if not a number, interpret as string literals |
| 212 |
| 213 |
| 214 def pack_firmware_image(entries, output_path, image_size): |
| 215 entries = sorted(entries, key=lambda e: e.offset) |
| 216 for e1, e2 in zip(entries, entries[1:]): |
| 217 if e1.IsOverlapped(e2): |
| 218 raise PackError('overlapped entries: [%08x:%08x], [%08x:%08x]' % |
| 219 (e1.offset, e1.offset + e1.length, e2.offset, e2.offset + e2.length)) |
| 220 |
| 221 with open(output_path, 'wb') as firmware_image: |
| 222 # resize firmware image file |
| 223 firmware_image.seek(0) |
| 224 firmware_image.write('\0' * image_size) |
| 225 |
| 226 for entry in entries: |
| 227 entry.Pack(firmware_image, entries) |
| 228 |
| 229 |
| 230 def _Info(msg): |
| 231 if VERBOSE: |
| 232 print >>sys.stderr, 'INFO: %s' % msg |
| 233 |
| 234 |
| 235 def main(): |
| 236 global VERBOSE |
| 237 |
| 238 if len(sys.argv) < 2: |
| 239 print 'Usage: %s [-v] CONFIG_FILE [NAME=VALUE...]' % sys.argv[0] |
| 240 sys.exit(1) |
| 241 |
| 242 if sys.argv[1] == '-v': |
| 243 VERBOSE = True |
| 244 argv = sys.argv[0:1] + sys.argv[2:] |
| 245 else: |
| 246 argv = sys.argv |
| 247 |
| 248 if len(argv) > 2: |
| 249 env = dict(parse_assignment(stmt) for stmt in argv[2:]) |
| 250 else: |
| 251 env = {} |
| 252 |
| 253 execfile(argv[1], globals(), env) |
| 254 |
| 255 for varname in ('ENTRIES', 'OUTPUT', 'SIZE'): |
| 256 if varname not in env: |
| 257 raise ConfigError('undefined variable: %s' % varname) |
| 258 _Info('%s = %s' % (varname, repr(env[varname]))) |
| 259 |
| 260 pack_firmware_image(env['ENTRIES'], env['OUTPUT'], env['SIZE']) |
| 261 |
| 262 sys.exit(0) |
| 263 |
| 264 |
| 265 if __name__ == '__main__': |
| 266 main() |
| OLD | NEW |