blob: 4691be4aee22e9498e408f7cef7f90e8984b8797 [file] [log] [blame]
Simon Glass4997a7e2019-07-08 13:18:52 -06001# SPDX-License-Identifier: GPL-2.0+
2# Copyright 2019 Google LLC
3# Written by Simon Glass <sjg@chromium.org>
4
5"""Support for coreboot's CBFS format
6
7CBFS supports a header followed by a number of files, generally targeted at SPI
8flash.
9
10The format is somewhat defined by documentation in the coreboot tree although
11it is necessary to rely on the C structures and source code (mostly cbfstool)
12to fully understand it.
13
Simon Glass7c173ce2019-07-08 13:18:55 -060014Currently supported: raw and stage types with compression, padding empty areas
Simon Glasse073d4e2019-07-08 13:18:56 -060015 with empty files, fixed-offset files
Simon Glass4997a7e2019-07-08 13:18:52 -060016"""
17
18from __future__ import print_function
19
20from collections import OrderedDict
21import io
22import struct
23import sys
24
25import command
26import elf
27import tools
28
29# Set to True to enable printing output while working
30DEBUG = False
31
32# Set to True to enable output from running cbfstool for debugging
33VERBOSE = False
34
35# The master header, at the start of the CBFS
36HEADER_FORMAT = '>IIIIIIII'
37HEADER_LEN = 0x20
38HEADER_MAGIC = 0x4f524243
39HEADER_VERSION1 = 0x31313131
40HEADER_VERSION2 = 0x31313132
41
42# The file header, at the start of each file in the CBFS
43FILE_HEADER_FORMAT = b'>8sIIII'
44FILE_HEADER_LEN = 0x18
45FILE_MAGIC = b'LARCHIVE'
46FILENAME_ALIGN = 16 # Filename lengths are aligned to this
47
48# A stage header containing information about 'stage' files
49# Yes this is correct: this header is in litte-endian format
50STAGE_FORMAT = '<IQQII'
51STAGE_LEN = 0x1c
52
53# An attribute describring the compression used in a file
54ATTR_COMPRESSION_FORMAT = '>IIII'
55ATTR_COMPRESSION_LEN = 0x10
56
57# Attribute tags
58# Depending on how the header was initialised, it may be backed with 0x00 or
59# 0xff. Support both.
60FILE_ATTR_TAG_UNUSED = 0
61FILE_ATTR_TAG_UNUSED2 = 0xffffffff
62FILE_ATTR_TAG_COMPRESSION = 0x42435a4c
63FILE_ATTR_TAG_HASH = 0x68736148
64FILE_ATTR_TAG_POSITION = 0x42435350 # PSCB
65FILE_ATTR_TAG_ALIGNMENT = 0x42434c41 # ALCB
66FILE_ATTR_TAG_PADDING = 0x47444150 # PDNG
67
68# This is 'the size of bootblock reserved in firmware image (cbfs.txt)'
69# Not much more info is available, but we set it to 4, due to this comment in
70# cbfstool.c:
71# This causes 4 bytes to be left out at the end of the image, for two reasons:
72# 1. The cbfs master header pointer resides there
73# 2. Ssme cbfs implementations assume that an image that resides below 4GB has
74# a bootblock and get confused when the end of the image is at 4GB == 0.
75MIN_BOOTBLOCK_SIZE = 4
76
77# Files start aligned to this boundary in the CBFS
78ENTRY_ALIGN = 0x40
79
80# CBFSs must declare an architecture since much of the logic is designed with
81# x86 in mind. The effect of setting this value is not well documented, but in
82# general x86 is used and this makes use of a boot block and an image that ends
83# at the end of 32-bit address space.
84ARCHITECTURE_UNKNOWN = 0xffffffff
85ARCHITECTURE_X86 = 0x00000001
86ARCHITECTURE_ARM = 0x00000010
87ARCHITECTURE_AARCH64 = 0x0000aa64
88ARCHITECTURE_MIPS = 0x00000100
89ARCHITECTURE_RISCV = 0xc001d0de
90ARCHITECTURE_PPC64 = 0x407570ff
91
92ARCH_NAMES = {
93 ARCHITECTURE_UNKNOWN : 'unknown',
94 ARCHITECTURE_X86 : 'x86',
95 ARCHITECTURE_ARM : 'arm',
96 ARCHITECTURE_AARCH64 : 'arm64',
97 ARCHITECTURE_MIPS : 'mips',
98 ARCHITECTURE_RISCV : 'riscv',
99 ARCHITECTURE_PPC64 : 'ppc64',
100 }
101
102# File types. Only supported ones are included here
103TYPE_CBFSHEADER = 0x02 # Master header, HEADER_FORMAT
104TYPE_STAGE = 0x10 # Stage, holding an executable, see STAGE_FORMAT
105TYPE_RAW = 0x50 # Raw file, possibly compressed
Simon Glass7c173ce2019-07-08 13:18:55 -0600106TYPE_EMPTY = 0xffffffff # Empty data
Simon Glass4997a7e2019-07-08 13:18:52 -0600107
108# Compression types
109COMPRESS_NONE, COMPRESS_LZMA, COMPRESS_LZ4 = range(3)
110
111COMPRESS_NAMES = {
112 COMPRESS_NONE : 'none',
113 COMPRESS_LZMA : 'lzma',
114 COMPRESS_LZ4 : 'lz4',
115 }
116
117def find_arch(find_name):
118 """Look up an architecture name
119
120 Args:
121 find_name: Architecture name to find
122
123 Returns:
124 ARCHITECTURE_... value or None if not found
125 """
126 for arch, name in ARCH_NAMES.items():
127 if name == find_name:
128 return arch
129 return None
130
131def find_compress(find_name):
132 """Look up a compression algorithm name
133
134 Args:
135 find_name: Compression algorithm name to find
136
137 Returns:
138 COMPRESS_... value or None if not found
139 """
140 for compress, name in COMPRESS_NAMES.items():
141 if name == find_name:
142 return compress
143 return None
144
145def align_int(val, align):
146 """Align a value up to the given alignment
147
148 Args:
149 val: Integer value to align
150 align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
151
152 Returns:
153 integer value aligned to the required boundary, rounding up if necessary
154 """
155 return int((val + align - 1) / align) * align
156
Simon Glass7c173ce2019-07-08 13:18:55 -0600157def align_int_down(val, align):
158 """Align a value down to the given alignment
159
160 Args:
161 val: Integer value to align
162 align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
163
164 Returns:
165 integer value aligned to the required boundary, rounding down if
166 necessary
167 """
168 return int(val / align) * align
169
Simon Glass4997a7e2019-07-08 13:18:52 -0600170def _pack_string(instr):
171 """Pack a string to the required aligned size by adding padding
172
173 Args:
174 instr: String to process
175
176 Returns:
177 String with required padding (at least one 0x00 byte) at the end
178 """
179 val = tools.ToBytes(instr)
180 pad_len = align_int(len(val) + 1, FILENAME_ALIGN)
181 return val + tools.GetBytes(0, pad_len - len(val))
182
183
184class CbfsFile(object):
185 """Class to represent a single CBFS file
186
187 This is used to hold the information about a file, including its contents.
Simon Glass1223db02019-07-08 14:25:39 -0600188 Use the get_data_and_offset() method to obtain the raw output for writing to
189 CBFS.
Simon Glass4997a7e2019-07-08 13:18:52 -0600190
191 Properties:
192 name: Name of file
193 offset: Offset of file data from start of file header
Simon Glasse073d4e2019-07-08 13:18:56 -0600194 cbfs_offset: Offset of file data in bytes from start of CBFS, or None to
195 place this file anyway
Simon Glass4997a7e2019-07-08 13:18:52 -0600196 data: Contents of file, uncompressed
197 data_len: Length of (possibly compressed) data in bytes
198 ftype: File type (TYPE_...)
199 compression: Compression type (COMPRESS_...)
Simon Glass52107ee2019-07-08 14:25:40 -0600200 memlen: Length of data in memory, i.e. the uncompressed length, None if
201 no compression algortihm is selected
Simon Glass4997a7e2019-07-08 13:18:52 -0600202 load: Load address in memory if known, else None
203 entry: Entry address in memory if known, else None. This is where
204 execution starts after the file is loaded
205 base_address: Base address to use for 'stage' files
Simon Glass7c173ce2019-07-08 13:18:55 -0600206 erase_byte: Erase byte to use for padding between the file header and
207 contents (used for empty files)
208 size: Size of the file in bytes (used for empty files)
Simon Glass4997a7e2019-07-08 13:18:52 -0600209 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600210 def __init__(self, name, ftype, data, cbfs_offset, compress=COMPRESS_NONE):
Simon Glass4997a7e2019-07-08 13:18:52 -0600211 self.name = name
212 self.offset = None
Simon Glasse073d4e2019-07-08 13:18:56 -0600213 self.cbfs_offset = cbfs_offset
Simon Glass4997a7e2019-07-08 13:18:52 -0600214 self.data = data
215 self.ftype = ftype
216 self.compress = compress
Simon Glass52107ee2019-07-08 14:25:40 -0600217 self.memlen = None
Simon Glass4997a7e2019-07-08 13:18:52 -0600218 self.load = None
219 self.entry = None
220 self.base_address = None
Simon Glass52107ee2019-07-08 14:25:40 -0600221 self.data_len = len(data)
Simon Glass7c173ce2019-07-08 13:18:55 -0600222 self.erase_byte = None
223 self.size = None
Simon Glass4997a7e2019-07-08 13:18:52 -0600224
225 def decompress(self):
226 """Handle decompressing data if necessary"""
227 indata = self.data
228 if self.compress == COMPRESS_LZ4:
229 data = tools.Decompress(indata, 'lz4')
230 elif self.compress == COMPRESS_LZMA:
231 data = tools.Decompress(indata, 'lzma')
232 else:
233 data = indata
234 self.memlen = len(data)
235 self.data = data
236 self.data_len = len(indata)
237
238 @classmethod
Simon Glasse073d4e2019-07-08 13:18:56 -0600239 def stage(cls, base_address, name, data, cbfs_offset):
Simon Glass4997a7e2019-07-08 13:18:52 -0600240 """Create a new stage file
241
242 Args:
243 base_address: Int base address for memory-mapping of ELF file
244 name: String file name to put in CBFS (does not need to correspond
245 to the name that the file originally came from)
246 data: Contents of file
Simon Glasse073d4e2019-07-08 13:18:56 -0600247 cbfs_offset: Offset of file data in bytes from start of CBFS, or
248 None to place this file anyway
Simon Glass4997a7e2019-07-08 13:18:52 -0600249
250 Returns:
251 CbfsFile object containing the file information
252 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600253 cfile = CbfsFile(name, TYPE_STAGE, data, cbfs_offset)
Simon Glass4997a7e2019-07-08 13:18:52 -0600254 cfile.base_address = base_address
255 return cfile
256
257 @classmethod
Simon Glasse073d4e2019-07-08 13:18:56 -0600258 def raw(cls, name, data, cbfs_offset, compress):
Simon Glass4997a7e2019-07-08 13:18:52 -0600259 """Create a new raw file
260
261 Args:
262 name: String file name to put in CBFS (does not need to correspond
263 to the name that the file originally came from)
264 data: Contents of file
Simon Glasse073d4e2019-07-08 13:18:56 -0600265 cbfs_offset: Offset of file data in bytes from start of CBFS, or
266 None to place this file anyway
Simon Glass4997a7e2019-07-08 13:18:52 -0600267 compress: Compression algorithm to use (COMPRESS_...)
268
269 Returns:
270 CbfsFile object containing the file information
271 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600272 return CbfsFile(name, TYPE_RAW, data, cbfs_offset, compress)
Simon Glass4997a7e2019-07-08 13:18:52 -0600273
Simon Glass7c173ce2019-07-08 13:18:55 -0600274 @classmethod
275 def empty(cls, space_to_use, erase_byte):
276 """Create a new empty file of a given size
277
278 Args:
279 space_to_use:: Size of available space, which must be at least as
280 large as the alignment size for this CBFS
281 erase_byte: Byte to use for contents of file (repeated through the
282 whole file)
283
284 Returns:
285 CbfsFile object containing the file information
286 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600287 cfile = CbfsFile('', TYPE_EMPTY, b'', None)
Simon Glass7c173ce2019-07-08 13:18:55 -0600288 cfile.size = space_to_use - FILE_HEADER_LEN - FILENAME_ALIGN
289 cfile.erase_byte = erase_byte
290 return cfile
291
Simon Glasse073d4e2019-07-08 13:18:56 -0600292 def calc_start_offset(self):
293 """Check if this file needs to start at a particular offset in CBFS
294
295 Returns:
296 None if the file can be placed anywhere, or
297 the largest offset where the file could start (integer)
298 """
299 if self.cbfs_offset is None:
300 return None
301 return self.cbfs_offset - self.get_header_len()
302
303 def get_header_len(self):
304 """Get the length of headers required for a file
305
306 This is the minimum length required before the actual data for this file
307 could start. It might start later if there is padding.
308
309 Returns:
310 Total length of all non-data fields, in bytes
311 """
312 name = _pack_string(self.name)
313 hdr_len = len(name) + FILE_HEADER_LEN
314 if self.ftype == TYPE_STAGE:
315 pass
316 elif self.ftype == TYPE_RAW:
317 hdr_len += ATTR_COMPRESSION_LEN
318 elif self.ftype == TYPE_EMPTY:
319 pass
320 else:
321 raise ValueError('Unknown file type %#x\n' % self.ftype)
322 return hdr_len
323
Simon Glass1223db02019-07-08 14:25:39 -0600324 def get_data_and_offset(self, offset=None, pad_byte=None):
325 """Obtain the contents of the file, in CBFS format and the offset of
326 the data within the file
Simon Glass4997a7e2019-07-08 13:18:52 -0600327
328 Returns:
Simon Glass1223db02019-07-08 14:25:39 -0600329 tuple:
330 bytes representing the contents of this file, packed and aligned
331 for directly inserting into the final CBFS output
332 offset to the file data from the start of the returned data.
Simon Glass4997a7e2019-07-08 13:18:52 -0600333 """
334 name = _pack_string(self.name)
335 hdr_len = len(name) + FILE_HEADER_LEN
336 attr_pos = 0
337 content = b''
338 attr = b''
Simon Glasse073d4e2019-07-08 13:18:56 -0600339 pad = b''
Simon Glass4997a7e2019-07-08 13:18:52 -0600340 data = self.data
341 if self.ftype == TYPE_STAGE:
342 elf_data = elf.DecodeElf(data, self.base_address)
343 content = struct.pack(STAGE_FORMAT, self.compress,
344 elf_data.entry, elf_data.load,
345 len(elf_data.data), elf_data.memsize)
346 data = elf_data.data
347 elif self.ftype == TYPE_RAW:
348 orig_data = data
349 if self.compress == COMPRESS_LZ4:
350 data = tools.Compress(orig_data, 'lz4')
351 elif self.compress == COMPRESS_LZMA:
352 data = tools.Compress(orig_data, 'lzma')
Simon Glass52107ee2019-07-08 14:25:40 -0600353 self.memlen = len(orig_data)
354 self.data_len = len(data)
Simon Glass4997a7e2019-07-08 13:18:52 -0600355 attr = struct.pack(ATTR_COMPRESSION_FORMAT,
356 FILE_ATTR_TAG_COMPRESSION, ATTR_COMPRESSION_LEN,
Simon Glass52107ee2019-07-08 14:25:40 -0600357 self.compress, self.memlen)
Simon Glass7c173ce2019-07-08 13:18:55 -0600358 elif self.ftype == TYPE_EMPTY:
359 data = tools.GetBytes(self.erase_byte, self.size)
Simon Glass4997a7e2019-07-08 13:18:52 -0600360 else:
361 raise ValueError('Unknown type %#x when writing\n' % self.ftype)
362 if attr:
363 attr_pos = hdr_len
364 hdr_len += len(attr)
Simon Glasse073d4e2019-07-08 13:18:56 -0600365 if self.cbfs_offset is not None:
366 pad_len = self.cbfs_offset - offset - hdr_len
367 if pad_len < 0: # pragma: no cover
368 # Test coverage of this is not available since this should never
369 # happen. It indicates that get_header_len() provided an
370 # incorrect value (too small) so that we decided that we could
371 # put this file at the requested place, but in fact a previous
372 # file extends far enough into the CBFS that this is not
373 # possible.
374 raise ValueError("Internal error: CBFS file '%s': Requested offset %#x but current output position is %#x" %
375 (self.name, self.cbfs_offset, offset))
376 pad = tools.GetBytes(pad_byte, pad_len)
377 hdr_len += pad_len
Simon Glass1223db02019-07-08 14:25:39 -0600378
379 # This is the offset of the start of the file's data,
380 size = len(content) + len(data)
381 hdr = struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, size,
Simon Glass4997a7e2019-07-08 13:18:52 -0600382 self.ftype, attr_pos, hdr_len)
Simon Glasse073d4e2019-07-08 13:18:56 -0600383
384 # Do a sanity check of the get_header_len() function, to ensure that it
385 # stays in lockstep with this function
386 expected_len = self.get_header_len()
387 actual_len = len(hdr + name + attr)
388 if expected_len != actual_len: # pragma: no cover
389 # Test coverage of this is not available since this should never
390 # happen. It probably indicates that get_header_len() is broken.
391 raise ValueError("Internal error: CBFS file '%s': Expected headers of %#x bytes, got %#d" %
392 (self.name, expected_len, actual_len))
Simon Glass1223db02019-07-08 14:25:39 -0600393 return hdr + name + attr + pad + content + data, hdr_len
Simon Glass4997a7e2019-07-08 13:18:52 -0600394
395
396class CbfsWriter(object):
397 """Class to handle writing a Coreboot File System (CBFS)
398
399 Usage is something like:
400
401 cbw = CbfsWriter(size)
402 cbw.add_file_raw('u-boot', tools.ReadFile('u-boot.bin'))
403 ...
Simon Glass1223db02019-07-08 14:25:39 -0600404 data, cbfs_offset = cbw.get_data_and_offset()
Simon Glass4997a7e2019-07-08 13:18:52 -0600405
406 Attributes:
407 _master_name: Name of the file containing the master header
408 _size: Size of the filesystem, in bytes
409 _files: Ordered list of files in the CBFS, each a CbfsFile
410 _arch: Architecture of the CBFS (ARCHITECTURE_...)
411 _bootblock_size: Size of the bootblock, typically at the end of the CBFS
412 _erase_byte: Byte to use for empty space in the CBFS
413 _align: Alignment to use for files, typically ENTRY_ALIGN
414 _base_address: Boot block offset in bytes from the start of CBFS.
415 Typically this is located at top of the CBFS. It is 0 when there is
416 no boot block
417 _header_offset: Offset of master header in bytes from start of CBFS
418 _contents_offset: Offset of first file header
419 _hdr_at_start: True if the master header is at the start of the CBFS,
420 instead of the end as normal for x86
421 _add_fileheader: True to add a fileheader around the master header
422 """
423 def __init__(self, size, arch=ARCHITECTURE_X86):
424 """Set up a new CBFS
425
426 This sets up all properties to default values. Files can be added using
427 add_file_raw(), etc.
428
429 Args:
430 size: Size of CBFS in bytes
431 arch: Architecture to declare for CBFS
432 """
433 self._master_name = 'cbfs master header'
434 self._size = size
435 self._files = OrderedDict()
436 self._arch = arch
437 self._bootblock_size = 0
438 self._erase_byte = 0xff
439 self._align = ENTRY_ALIGN
440 self._add_fileheader = False
441 if self._arch == ARCHITECTURE_X86:
442 # Allow 4 bytes for the header pointer. That holds the
443 # twos-compliment negative offset of the master header in bytes
444 # measured from one byte past the end of the CBFS
445 self._base_address = self._size - max(self._bootblock_size,
446 MIN_BOOTBLOCK_SIZE)
447 self._header_offset = self._base_address - HEADER_LEN
448 self._contents_offset = 0
449 self._hdr_at_start = False
450 else:
451 # For non-x86, different rules apply
452 self._base_address = 0
453 self._header_offset = align_int(self._base_address +
454 self._bootblock_size, 4)
455 self._contents_offset = align_int(self._header_offset +
456 FILE_HEADER_LEN +
457 self._bootblock_size, self._align)
458 self._hdr_at_start = True
459
460 def _skip_to(self, fd, offset):
461 """Write out pad bytes until a given offset
462
463 Args:
464 fd: File objext to write to
465 offset: Offset to write to
466 """
467 if fd.tell() > offset:
468 raise ValueError('No space for data before offset %#x (current offset %#x)' %
469 (offset, fd.tell()))
470 fd.write(tools.GetBytes(self._erase_byte, offset - fd.tell()))
471
Simon Glass7c173ce2019-07-08 13:18:55 -0600472 def _pad_to(self, fd, offset):
473 """Write out pad bytes and/or an empty file until a given offset
474
475 Args:
476 fd: File objext to write to
477 offset: Offset to write to
478 """
479 self._align_to(fd, self._align)
480 upto = fd.tell()
481 if upto > offset:
482 raise ValueError('No space for data before pad offset %#x (current offset %#x)' %
483 (offset, upto))
484 todo = align_int_down(offset - upto, self._align)
485 if todo:
486 cbf = CbfsFile.empty(todo, self._erase_byte)
Simon Glass1223db02019-07-08 14:25:39 -0600487 fd.write(cbf.get_data_and_offset()[0])
Simon Glass7c173ce2019-07-08 13:18:55 -0600488 self._skip_to(fd, offset)
489
Simon Glass4997a7e2019-07-08 13:18:52 -0600490 def _align_to(self, fd, align):
491 """Write out pad bytes until a given alignment is reached
492
493 This only aligns if the resulting output would not reach the end of the
494 CBFS, since we want to leave the last 4 bytes for the master-header
495 pointer.
496
497 Args:
498 fd: File objext to write to
499 align: Alignment to require (e.g. 4 means pad to next 4-byte
500 boundary)
501 """
502 offset = align_int(fd.tell(), align)
503 if offset < self._size:
504 self._skip_to(fd, offset)
505
Simon Glasse073d4e2019-07-08 13:18:56 -0600506 def add_file_stage(self, name, data, cbfs_offset=None):
Simon Glass4997a7e2019-07-08 13:18:52 -0600507 """Add a new stage file to the CBFS
508
509 Args:
510 name: String file name to put in CBFS (does not need to correspond
511 to the name that the file originally came from)
512 data: Contents of file
Simon Glasse073d4e2019-07-08 13:18:56 -0600513 cbfs_offset: Offset of this file's data within the CBFS, in bytes,
514 or None to place this file anywhere
Simon Glass4997a7e2019-07-08 13:18:52 -0600515
516 Returns:
517 CbfsFile object created
518 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600519 cfile = CbfsFile.stage(self._base_address, name, data, cbfs_offset)
Simon Glass4997a7e2019-07-08 13:18:52 -0600520 self._files[name] = cfile
521 return cfile
522
Simon Glasse073d4e2019-07-08 13:18:56 -0600523 def add_file_raw(self, name, data, cbfs_offset=None,
524 compress=COMPRESS_NONE):
Simon Glass4997a7e2019-07-08 13:18:52 -0600525 """Create a new raw file
526
527 Args:
528 name: String file name to put in CBFS (does not need to correspond
529 to the name that the file originally came from)
530 data: Contents of file
Simon Glasse073d4e2019-07-08 13:18:56 -0600531 cbfs_offset: Offset of this file's data within the CBFS, in bytes,
532 or None to place this file anywhere
Simon Glass4997a7e2019-07-08 13:18:52 -0600533 compress: Compression algorithm to use (COMPRESS_...)
534
535 Returns:
536 CbfsFile object created
537 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600538 cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
Simon Glass4997a7e2019-07-08 13:18:52 -0600539 self._files[name] = cfile
540 return cfile
541
542 def _write_header(self, fd, add_fileheader):
543 """Write out the master header to a CBFS
544
545 Args:
546 fd: File object
547 add_fileheader: True to place the master header in a file header
548 record
549 """
550 if fd.tell() > self._header_offset:
551 raise ValueError('No space for header at offset %#x (current offset %#x)' %
552 (self._header_offset, fd.tell()))
553 if not add_fileheader:
Simon Glass7c173ce2019-07-08 13:18:55 -0600554 self._pad_to(fd, self._header_offset)
Simon Glass4997a7e2019-07-08 13:18:52 -0600555 hdr = struct.pack(HEADER_FORMAT, HEADER_MAGIC, HEADER_VERSION2,
556 self._size, self._bootblock_size, self._align,
557 self._contents_offset, self._arch, 0xffffffff)
558 if add_fileheader:
559 name = _pack_string(self._master_name)
560 fd.write(struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, len(hdr),
561 TYPE_CBFSHEADER, 0,
562 FILE_HEADER_LEN + len(name)))
563 fd.write(name)
564 self._header_offset = fd.tell()
565 fd.write(hdr)
566 self._align_to(fd, self._align)
567 else:
568 fd.write(hdr)
569
570 def get_data(self):
571 """Obtain the full contents of the CBFS
572
573 Thhis builds the CBFS with headers and all required files.
574
575 Returns:
576 'bytes' type containing the data
577 """
578 fd = io.BytesIO()
579
580 # THe header can go at the start in some cases
581 if self._hdr_at_start:
582 self._write_header(fd, add_fileheader=self._add_fileheader)
583 self._skip_to(fd, self._contents_offset)
584
585 # Write out each file
586 for cbf in self._files.values():
Simon Glasse073d4e2019-07-08 13:18:56 -0600587 # Place the file at its requested place, if any
588 offset = cbf.calc_start_offset()
589 if offset is not None:
590 self._pad_to(fd, align_int_down(offset, self._align))
Simon Glass1223db02019-07-08 14:25:39 -0600591 pos = fd.tell()
592 data, data_offset = cbf.get_data_and_offset(pos, self._erase_byte)
593 fd.write(data)
Simon Glass4997a7e2019-07-08 13:18:52 -0600594 self._align_to(fd, self._align)
Simon Glass1223db02019-07-08 14:25:39 -0600595 cbf.calced_cbfs_offset = pos + data_offset
Simon Glass4997a7e2019-07-08 13:18:52 -0600596 if not self._hdr_at_start:
597 self._write_header(fd, add_fileheader=self._add_fileheader)
598
599 # Pad to the end and write a pointer to the CBFS master header
Simon Glass7c173ce2019-07-08 13:18:55 -0600600 self._pad_to(fd, self._base_address or self._size - 4)
Simon Glass4997a7e2019-07-08 13:18:52 -0600601 rel_offset = self._header_offset - self._size
602 fd.write(struct.pack('<I', rel_offset & 0xffffffff))
603
604 return fd.getvalue()
605
606
607class CbfsReader(object):
608 """Class to handle reading a Coreboot File System (CBFS)
609
610 Usage is something like:
611 cbfs = cbfs_util.CbfsReader(data)
612 cfile = cbfs.files['u-boot']
613 self.WriteFile('u-boot.bin', cfile.data)
614
615 Attributes:
616 files: Ordered list of CbfsFile objects
617 align: Alignment to use for files, typically ENTRT_ALIGN
618 stage_base_address: Base address to use when mapping ELF files into the
619 CBFS for TYPE_STAGE files. If this is larger than the code address
620 of the ELF file, then data at the start of the ELF file will not
621 appear in the CBFS. Currently there are no tests for behaviour as
622 documentation is sparse
623 magic: Integer magic number from master header (HEADER_MAGIC)
624 version: Version number of CBFS (HEADER_VERSION2)
625 rom_size: Size of CBFS
626 boot_block_size: Size of boot block
627 cbfs_offset: Offset of the first file in bytes from start of CBFS
628 arch: Architecture of CBFS file (ARCHITECTURE_...)
629 """
630 def __init__(self, data, read=True):
631 self.align = ENTRY_ALIGN
632 self.arch = None
633 self.boot_block_size = None
634 self.cbfs_offset = None
635 self.files = OrderedDict()
636 self.magic = None
637 self.rom_size = None
638 self.stage_base_address = 0
639 self.version = None
640 self.data = data
641 if read:
642 self.read()
643
644 def read(self):
645 """Read all the files in the CBFS and add them to self.files"""
646 with io.BytesIO(self.data) as fd:
647 # First, get the master header
648 if not self._find_and_read_header(fd, len(self.data)):
649 raise ValueError('Cannot find master header')
650 fd.seek(self.cbfs_offset)
651
652 # Now read in the files one at a time
653 while True:
654 cfile = self._read_next_file(fd)
655 if cfile:
656 self.files[cfile.name] = cfile
657 elif cfile is False:
658 break
659
660 def _find_and_read_header(self, fd, size):
661 """Find and read the master header in the CBFS
662
663 This looks at the pointer word at the very end of the CBFS. This is an
664 offset to the header relative to the size of the CBFS, which is assumed
665 to be known. Note that the offset is in *little endian* format.
666
667 Args:
668 fd: File to read from
669 size: Size of file
670
671 Returns:
672 True if header was found, False if not
673 """
674 orig_pos = fd.tell()
675 fd.seek(size - 4)
676 rel_offset, = struct.unpack('<I', fd.read(4))
677 pos = (size + rel_offset) & 0xffffffff
678 fd.seek(pos)
679 found = self._read_header(fd)
680 if not found:
681 print('Relative offset seems wrong, scanning whole image')
682 for pos in range(0, size - HEADER_LEN, 4):
683 fd.seek(pos)
684 found = self._read_header(fd)
685 if found:
686 break
687 fd.seek(orig_pos)
688 return found
689
690 def _read_next_file(self, fd):
691 """Read the next file from a CBFS
692
693 Args:
694 fd: File to read from
695
696 Returns:
697 CbfsFile object, if found
698 None if no object found, but data was parsed (e.g. TYPE_CBFSHEADER)
699 False if at end of CBFS and reading should stop
700 """
701 file_pos = fd.tell()
702 data = fd.read(FILE_HEADER_LEN)
703 if len(data) < FILE_HEADER_LEN:
704 print('File header at %x ran out of data' % file_pos)
705 return False
706 magic, size, ftype, attr, offset = struct.unpack(FILE_HEADER_FORMAT,
707 data)
708 if magic != FILE_MAGIC:
709 return False
710 pos = fd.tell()
711 name = self._read_string(fd)
712 if name is None:
713 print('String at %x ran out of data' % pos)
714 return False
715
716 if DEBUG:
717 print('name', name)
718
719 # If there are attribute headers present, read those
720 compress = self._read_attr(fd, file_pos, attr, offset)
721 if compress is None:
722 return False
723
724 # Create the correct CbfsFile object depending on the type
725 cfile = None
Simon Glasse073d4e2019-07-08 13:18:56 -0600726 cbfs_offset = file_pos + offset
727 fd.seek(cbfs_offset, io.SEEK_SET)
Simon Glass4997a7e2019-07-08 13:18:52 -0600728 if ftype == TYPE_CBFSHEADER:
729 self._read_header(fd)
730 elif ftype == TYPE_STAGE:
731 data = fd.read(STAGE_LEN)
Simon Glasse073d4e2019-07-08 13:18:56 -0600732 cfile = CbfsFile.stage(self.stage_base_address, name, b'',
733 cbfs_offset)
Simon Glass4997a7e2019-07-08 13:18:52 -0600734 (cfile.compress, cfile.entry, cfile.load, cfile.data_len,
735 cfile.memlen) = struct.unpack(STAGE_FORMAT, data)
736 cfile.data = fd.read(cfile.data_len)
737 elif ftype == TYPE_RAW:
738 data = fd.read(size)
Simon Glasse073d4e2019-07-08 13:18:56 -0600739 cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
Simon Glass4997a7e2019-07-08 13:18:52 -0600740 cfile.decompress()
741 if DEBUG:
742 print('data', data)
Simon Glass7c173ce2019-07-08 13:18:55 -0600743 elif ftype == TYPE_EMPTY:
744 # Just read the data and discard it, since it is only padding
745 fd.read(size)
Simon Glasse073d4e2019-07-08 13:18:56 -0600746 cfile = CbfsFile('', TYPE_EMPTY, b'', cbfs_offset)
Simon Glass4997a7e2019-07-08 13:18:52 -0600747 else:
748 raise ValueError('Unknown type %#x when reading\n' % ftype)
749 if cfile:
750 cfile.offset = offset
751
752 # Move past the padding to the start of a possible next file. If we are
753 # already at an alignment boundary, then there is no padding.
754 pad = (self.align - fd.tell() % self.align) % self.align
755 fd.seek(pad, io.SEEK_CUR)
756 return cfile
757
758 @classmethod
759 def _read_attr(cls, fd, file_pos, attr, offset):
760 """Read attributes from the file
761
762 CBFS files can have attributes which are things that cannot fit into the
Simon Glasse073d4e2019-07-08 13:18:56 -0600763 header. The only attributes currently supported are compression and the
764 unused tag.
Simon Glass4997a7e2019-07-08 13:18:52 -0600765
766 Args:
767 fd: File to read from
768 file_pos: Position of file in fd
769 attr: Offset of attributes, 0 if none
770 offset: Offset of file data (used to indicate the end of the
771 attributes)
772
773 Returns:
774 Compression to use for the file (COMPRESS_...)
775 """
776 compress = COMPRESS_NONE
777 if not attr:
778 return compress
779 attr_size = offset - attr
780 fd.seek(file_pos + attr, io.SEEK_SET)
781 while attr_size:
782 pos = fd.tell()
783 hdr = fd.read(8)
784 if len(hdr) < 8:
785 print('Attribute tag at %x ran out of data' % pos)
786 return None
787 atag, alen = struct.unpack(">II", hdr)
788 data = hdr + fd.read(alen - 8)
789 if atag == FILE_ATTR_TAG_COMPRESSION:
790 # We don't currently use this information
791 atag, alen, compress, _decomp_size = struct.unpack(
792 ATTR_COMPRESSION_FORMAT, data)
Simon Glasse073d4e2019-07-08 13:18:56 -0600793 elif atag == FILE_ATTR_TAG_UNUSED2:
794 break
Simon Glass4997a7e2019-07-08 13:18:52 -0600795 else:
796 print('Unknown attribute tag %x' % atag)
797 attr_size -= len(data)
798 return compress
799
800 def _read_header(self, fd):
801 """Read the master header
802
803 Reads the header and stores the information obtained into the member
804 variables.
805
806 Args:
807 fd: File to read from
808
809 Returns:
810 True if header was read OK, False if it is truncated or has the
811 wrong magic or version
812 """
813 pos = fd.tell()
814 data = fd.read(HEADER_LEN)
815 if len(data) < HEADER_LEN:
816 print('Header at %x ran out of data' % pos)
817 return False
818 (self.magic, self.version, self.rom_size, self.boot_block_size,
819 self.align, self.cbfs_offset, self.arch, _) = struct.unpack(
820 HEADER_FORMAT, data)
821 return self.magic == HEADER_MAGIC and (
822 self.version == HEADER_VERSION1 or
823 self.version == HEADER_VERSION2)
824
825 @classmethod
826 def _read_string(cls, fd):
827 """Read a string from a file
828
829 This reads a string and aligns the data to the next alignment boundary
830
831 Args:
832 fd: File to read from
833
834 Returns:
835 string read ('str' type) encoded to UTF-8, or None if we ran out of
836 data
837 """
838 val = b''
839 while True:
840 data = fd.read(FILENAME_ALIGN)
841 if len(data) < FILENAME_ALIGN:
842 return None
843 pos = data.find(b'\0')
844 if pos == -1:
845 val += data
846 else:
847 val += data[:pos]
848 break
849 return val.decode('utf-8')
850
851
Simon Glasse073d4e2019-07-08 13:18:56 -0600852def cbfstool(fname, *cbfs_args, **kwargs):
Simon Glass4997a7e2019-07-08 13:18:52 -0600853 """Run cbfstool with provided arguments
854
855 If the tool fails then this function raises an exception and prints out the
856 output and stderr.
857
858 Args:
859 fname: Filename of CBFS
860 *cbfs_args: List of arguments to pass to cbfstool
861
862 Returns:
863 CommandResult object containing the results
864 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600865 args = ['cbfstool', fname] + list(cbfs_args)
866 if kwargs.get('base') is not None:
867 args += ['-b', '%#x' % kwargs['base']]
Simon Glass4997a7e2019-07-08 13:18:52 -0600868 result = command.RunPipe([args], capture=not VERBOSE,
869 capture_stderr=not VERBOSE, raise_on_error=False)
870 if result.return_code:
871 print(result.stderr, file=sys.stderr)
872 raise Exception("Failed to run (error %d): '%s'" %
873 (result.return_code, ' '.join(args)))