1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright 2019 Google LLC
3 # Written by Simon Glass <sjg@chromium.org>
5 """Support for coreboot's CBFS format
7 CBFS supports a header followed by a number of files, generally targeted at SPI
10 The format is somewhat defined by documentation in the coreboot tree although
11 it is necessary to rely on the C structures and source code (mostly cbfstool)
12 to fully understand it.
14 Currently supported: raw and stage types with compression
17 from __future__ import print_function
19 from collections import OrderedDict
28 # Set to True to enable printing output while working
31 # Set to True to enable output from running cbfstool for debugging
34 # The master header, at the start of the CBFS
35 HEADER_FORMAT = '>IIIIIIII'
37 HEADER_MAGIC = 0x4f524243
38 HEADER_VERSION1 = 0x31313131
39 HEADER_VERSION2 = 0x31313132
41 # The file header, at the start of each file in the CBFS
42 FILE_HEADER_FORMAT = b'>8sIIII'
43 FILE_HEADER_LEN = 0x18
44 FILE_MAGIC = b'LARCHIVE'
45 FILENAME_ALIGN = 16 # Filename lengths are aligned to this
47 # A stage header containing information about 'stage' files
48 # Yes this is correct: this header is in litte-endian format
49 STAGE_FORMAT = '<IQQII'
52 # An attribute describring the compression used in a file
53 ATTR_COMPRESSION_FORMAT = '>IIII'
54 ATTR_COMPRESSION_LEN = 0x10
57 # Depending on how the header was initialised, it may be backed with 0x00 or
59 FILE_ATTR_TAG_UNUSED = 0
60 FILE_ATTR_TAG_UNUSED2 = 0xffffffff
61 FILE_ATTR_TAG_COMPRESSION = 0x42435a4c
62 FILE_ATTR_TAG_HASH = 0x68736148
63 FILE_ATTR_TAG_POSITION = 0x42435350 # PSCB
64 FILE_ATTR_TAG_ALIGNMENT = 0x42434c41 # ALCB
65 FILE_ATTR_TAG_PADDING = 0x47444150 # PDNG
67 # This is 'the size of bootblock reserved in firmware image (cbfs.txt)'
68 # Not much more info is available, but we set it to 4, due to this comment in
70 # This causes 4 bytes to be left out at the end of the image, for two reasons:
71 # 1. The cbfs master header pointer resides there
72 # 2. Ssme cbfs implementations assume that an image that resides below 4GB has
73 # a bootblock and get confused when the end of the image is at 4GB == 0.
74 MIN_BOOTBLOCK_SIZE = 4
76 # Files start aligned to this boundary in the CBFS
79 # CBFSs must declare an architecture since much of the logic is designed with
80 # x86 in mind. The effect of setting this value is not well documented, but in
81 # general x86 is used and this makes use of a boot block and an image that ends
82 # at the end of 32-bit address space.
83 ARCHITECTURE_UNKNOWN = 0xffffffff
84 ARCHITECTURE_X86 = 0x00000001
85 ARCHITECTURE_ARM = 0x00000010
86 ARCHITECTURE_AARCH64 = 0x0000aa64
87 ARCHITECTURE_MIPS = 0x00000100
88 ARCHITECTURE_RISCV = 0xc001d0de
89 ARCHITECTURE_PPC64 = 0x407570ff
92 ARCHITECTURE_UNKNOWN : 'unknown',
93 ARCHITECTURE_X86 : 'x86',
94 ARCHITECTURE_ARM : 'arm',
95 ARCHITECTURE_AARCH64 : 'arm64',
96 ARCHITECTURE_MIPS : 'mips',
97 ARCHITECTURE_RISCV : 'riscv',
98 ARCHITECTURE_PPC64 : 'ppc64',
101 # File types. Only supported ones are included here
102 TYPE_CBFSHEADER = 0x02 # Master header, HEADER_FORMAT
103 TYPE_STAGE = 0x10 # Stage, holding an executable, see STAGE_FORMAT
104 TYPE_RAW = 0x50 # Raw file, possibly compressed
107 COMPRESS_NONE, COMPRESS_LZMA, COMPRESS_LZ4 = range(3)
110 COMPRESS_NONE : 'none',
111 COMPRESS_LZMA : 'lzma',
112 COMPRESS_LZ4 : 'lz4',
115 def find_arch(find_name):
116 """Look up an architecture name
119 find_name: Architecture name to find
122 ARCHITECTURE_... value or None if not found
124 for arch, name in ARCH_NAMES.items():
125 if name == find_name:
129 def find_compress(find_name):
130 """Look up a compression algorithm name
133 find_name: Compression algorithm name to find
136 COMPRESS_... value or None if not found
138 for compress, name in COMPRESS_NAMES.items():
139 if name == find_name:
143 def align_int(val, align):
144 """Align a value up to the given alignment
147 val: Integer value to align
148 align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
151 integer value aligned to the required boundary, rounding up if necessary
153 return int((val + align - 1) / align) * align
155 def _pack_string(instr):
156 """Pack a string to the required aligned size by adding padding
159 instr: String to process
162 String with required padding (at least one 0x00 byte) at the end
164 val = tools.ToBytes(instr)
165 pad_len = align_int(len(val) + 1, FILENAME_ALIGN)
166 return val + tools.GetBytes(0, pad_len - len(val))
169 class CbfsFile(object):
170 """Class to represent a single CBFS file
172 This is used to hold the information about a file, including its contents.
173 Use the get_data() method to obtain the raw output for writing to CBFS.
177 offset: Offset of file data from start of file header
178 data: Contents of file, uncompressed
179 data_len: Length of (possibly compressed) data in bytes
180 ftype: File type (TYPE_...)
181 compression: Compression type (COMPRESS_...)
182 memlen: Length of data in memory (typically the uncompressed length)
183 load: Load address in memory if known, else None
184 entry: Entry address in memory if known, else None. This is where
185 execution starts after the file is loaded
186 base_address: Base address to use for 'stage' files
188 def __init__(self, name, ftype, data, compress=COMPRESS_NONE):
193 self.compress = compress
194 self.memlen = len(data)
197 self.base_address = None
200 def decompress(self):
201 """Handle decompressing data if necessary"""
203 if self.compress == COMPRESS_LZ4:
204 data = tools.Decompress(indata, 'lz4')
205 elif self.compress == COMPRESS_LZMA:
206 data = tools.Decompress(indata, 'lzma')
209 self.memlen = len(data)
211 self.data_len = len(indata)
214 def stage(cls, base_address, name, data):
215 """Create a new stage file
218 base_address: Int base address for memory-mapping of ELF file
219 name: String file name to put in CBFS (does not need to correspond
220 to the name that the file originally came from)
221 data: Contents of file
224 CbfsFile object containing the file information
226 cfile = CbfsFile(name, TYPE_STAGE, data)
227 cfile.base_address = base_address
231 def raw(cls, name, data, compress):
232 """Create a new raw file
235 name: String file name to put in CBFS (does not need to correspond
236 to the name that the file originally came from)
237 data: Contents of file
238 compress: Compression algorithm to use (COMPRESS_...)
241 CbfsFile object containing the file information
243 return CbfsFile(name, TYPE_RAW, data, compress)
246 """Obtain the contents of the file, in CBFS format
249 bytes representing the contents of this file, packed and aligned
250 for directly inserting into the final CBFS output
252 name = _pack_string(self.name)
253 hdr_len = len(name) + FILE_HEADER_LEN
258 if self.ftype == TYPE_STAGE:
259 elf_data = elf.DecodeElf(data, self.base_address)
260 content = struct.pack(STAGE_FORMAT, self.compress,
261 elf_data.entry, elf_data.load,
262 len(elf_data.data), elf_data.memsize)
264 elif self.ftype == TYPE_RAW:
266 if self.compress == COMPRESS_LZ4:
267 data = tools.Compress(orig_data, 'lz4')
268 elif self.compress == COMPRESS_LZMA:
269 data = tools.Compress(orig_data, 'lzma')
270 attr = struct.pack(ATTR_COMPRESSION_FORMAT,
271 FILE_ATTR_TAG_COMPRESSION, ATTR_COMPRESSION_LEN,
272 self.compress, len(orig_data))
274 raise ValueError('Unknown type %#x when writing\n' % self.ftype)
278 hdr = struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC,
279 len(content) + len(data),
280 self.ftype, attr_pos, hdr_len)
281 return hdr + name + attr + content + data
284 class CbfsWriter(object):
285 """Class to handle writing a Coreboot File System (CBFS)
287 Usage is something like:
289 cbw = CbfsWriter(size)
290 cbw.add_file_raw('u-boot', tools.ReadFile('u-boot.bin'))
292 data = cbw.get_data()
295 _master_name: Name of the file containing the master header
296 _size: Size of the filesystem, in bytes
297 _files: Ordered list of files in the CBFS, each a CbfsFile
298 _arch: Architecture of the CBFS (ARCHITECTURE_...)
299 _bootblock_size: Size of the bootblock, typically at the end of the CBFS
300 _erase_byte: Byte to use for empty space in the CBFS
301 _align: Alignment to use for files, typically ENTRY_ALIGN
302 _base_address: Boot block offset in bytes from the start of CBFS.
303 Typically this is located at top of the CBFS. It is 0 when there is
305 _header_offset: Offset of master header in bytes from start of CBFS
306 _contents_offset: Offset of first file header
307 _hdr_at_start: True if the master header is at the start of the CBFS,
308 instead of the end as normal for x86
309 _add_fileheader: True to add a fileheader around the master header
311 def __init__(self, size, arch=ARCHITECTURE_X86):
314 This sets up all properties to default values. Files can be added using
318 size: Size of CBFS in bytes
319 arch: Architecture to declare for CBFS
321 self._master_name = 'cbfs master header'
323 self._files = OrderedDict()
325 self._bootblock_size = 0
326 self._erase_byte = 0xff
327 self._align = ENTRY_ALIGN
328 self._add_fileheader = False
329 if self._arch == ARCHITECTURE_X86:
330 # Allow 4 bytes for the header pointer. That holds the
331 # twos-compliment negative offset of the master header in bytes
332 # measured from one byte past the end of the CBFS
333 self._base_address = self._size - max(self._bootblock_size,
335 self._header_offset = self._base_address - HEADER_LEN
336 self._contents_offset = 0
337 self._hdr_at_start = False
339 # For non-x86, different rules apply
340 self._base_address = 0
341 self._header_offset = align_int(self._base_address +
342 self._bootblock_size, 4)
343 self._contents_offset = align_int(self._header_offset +
345 self._bootblock_size, self._align)
346 self._hdr_at_start = True
348 def _skip_to(self, fd, offset):
349 """Write out pad bytes until a given offset
352 fd: File objext to write to
353 offset: Offset to write to
355 if fd.tell() > offset:
356 raise ValueError('No space for data before offset %#x (current offset %#x)' %
358 fd.write(tools.GetBytes(self._erase_byte, offset - fd.tell()))
360 def _align_to(self, fd, align):
361 """Write out pad bytes until a given alignment is reached
363 This only aligns if the resulting output would not reach the end of the
364 CBFS, since we want to leave the last 4 bytes for the master-header
368 fd: File objext to write to
369 align: Alignment to require (e.g. 4 means pad to next 4-byte
372 offset = align_int(fd.tell(), align)
373 if offset < self._size:
374 self._skip_to(fd, offset)
376 def add_file_stage(self, name, data):
377 """Add a new stage file to the CBFS
380 name: String file name to put in CBFS (does not need to correspond
381 to the name that the file originally came from)
382 data: Contents of file
385 CbfsFile object created
387 cfile = CbfsFile.stage(self._base_address, name, data)
388 self._files[name] = cfile
391 def add_file_raw(self, name, data, compress=COMPRESS_NONE):
392 """Create a new raw file
395 name: String file name to put in CBFS (does not need to correspond
396 to the name that the file originally came from)
397 data: Contents of file
398 compress: Compression algorithm to use (COMPRESS_...)
401 CbfsFile object created
403 cfile = CbfsFile.raw(name, data, compress)
404 self._files[name] = cfile
407 def _write_header(self, fd, add_fileheader):
408 """Write out the master header to a CBFS
412 add_fileheader: True to place the master header in a file header
415 if fd.tell() > self._header_offset:
416 raise ValueError('No space for header at offset %#x (current offset %#x)' %
417 (self._header_offset, fd.tell()))
418 if not add_fileheader:
419 self._skip_to(fd, self._header_offset)
420 hdr = struct.pack(HEADER_FORMAT, HEADER_MAGIC, HEADER_VERSION2,
421 self._size, self._bootblock_size, self._align,
422 self._contents_offset, self._arch, 0xffffffff)
424 name = _pack_string(self._master_name)
425 fd.write(struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, len(hdr),
427 FILE_HEADER_LEN + len(name)))
429 self._header_offset = fd.tell()
431 self._align_to(fd, self._align)
436 """Obtain the full contents of the CBFS
438 Thhis builds the CBFS with headers and all required files.
441 'bytes' type containing the data
445 # THe header can go at the start in some cases
446 if self._hdr_at_start:
447 self._write_header(fd, add_fileheader=self._add_fileheader)
448 self._skip_to(fd, self._contents_offset)
450 # Write out each file
451 for cbf in self._files.values():
452 fd.write(cbf.get_data())
453 self._align_to(fd, self._align)
454 if not self._hdr_at_start:
455 self._write_header(fd, add_fileheader=self._add_fileheader)
457 # Pad to the end and write a pointer to the CBFS master header
458 self._skip_to(fd, self._base_address or self._size - 4)
459 rel_offset = self._header_offset - self._size
460 fd.write(struct.pack('<I', rel_offset & 0xffffffff))
465 class CbfsReader(object):
466 """Class to handle reading a Coreboot File System (CBFS)
468 Usage is something like:
469 cbfs = cbfs_util.CbfsReader(data)
470 cfile = cbfs.files['u-boot']
471 self.WriteFile('u-boot.bin', cfile.data)
474 files: Ordered list of CbfsFile objects
475 align: Alignment to use for files, typically ENTRT_ALIGN
476 stage_base_address: Base address to use when mapping ELF files into the
477 CBFS for TYPE_STAGE files. If this is larger than the code address
478 of the ELF file, then data at the start of the ELF file will not
479 appear in the CBFS. Currently there are no tests for behaviour as
480 documentation is sparse
481 magic: Integer magic number from master header (HEADER_MAGIC)
482 version: Version number of CBFS (HEADER_VERSION2)
483 rom_size: Size of CBFS
484 boot_block_size: Size of boot block
485 cbfs_offset: Offset of the first file in bytes from start of CBFS
486 arch: Architecture of CBFS file (ARCHITECTURE_...)
488 def __init__(self, data, read=True):
489 self.align = ENTRY_ALIGN
491 self.boot_block_size = None
492 self.cbfs_offset = None
493 self.files = OrderedDict()
496 self.stage_base_address = 0
503 """Read all the files in the CBFS and add them to self.files"""
504 with io.BytesIO(self.data) as fd:
505 # First, get the master header
506 if not self._find_and_read_header(fd, len(self.data)):
507 raise ValueError('Cannot find master header')
508 fd.seek(self.cbfs_offset)
510 # Now read in the files one at a time
512 cfile = self._read_next_file(fd)
514 self.files[cfile.name] = cfile
518 def _find_and_read_header(self, fd, size):
519 """Find and read the master header in the CBFS
521 This looks at the pointer word at the very end of the CBFS. This is an
522 offset to the header relative to the size of the CBFS, which is assumed
523 to be known. Note that the offset is in *little endian* format.
526 fd: File to read from
530 True if header was found, False if not
534 rel_offset, = struct.unpack('<I', fd.read(4))
535 pos = (size + rel_offset) & 0xffffffff
537 found = self._read_header(fd)
539 print('Relative offset seems wrong, scanning whole image')
540 for pos in range(0, size - HEADER_LEN, 4):
542 found = self._read_header(fd)
548 def _read_next_file(self, fd):
549 """Read the next file from a CBFS
552 fd: File to read from
555 CbfsFile object, if found
556 None if no object found, but data was parsed (e.g. TYPE_CBFSHEADER)
557 False if at end of CBFS and reading should stop
560 data = fd.read(FILE_HEADER_LEN)
561 if len(data) < FILE_HEADER_LEN:
562 print('File header at %x ran out of data' % file_pos)
564 magic, size, ftype, attr, offset = struct.unpack(FILE_HEADER_FORMAT,
566 if magic != FILE_MAGIC:
569 name = self._read_string(fd)
571 print('String at %x ran out of data' % pos)
577 # If there are attribute headers present, read those
578 compress = self._read_attr(fd, file_pos, attr, offset)
582 # Create the correct CbfsFile object depending on the type
584 fd.seek(file_pos + offset, io.SEEK_SET)
585 if ftype == TYPE_CBFSHEADER:
586 self._read_header(fd)
587 elif ftype == TYPE_STAGE:
588 data = fd.read(STAGE_LEN)
589 cfile = CbfsFile.stage(self.stage_base_address, name, b'')
590 (cfile.compress, cfile.entry, cfile.load, cfile.data_len,
591 cfile.memlen) = struct.unpack(STAGE_FORMAT, data)
592 cfile.data = fd.read(cfile.data_len)
593 elif ftype == TYPE_RAW:
595 cfile = CbfsFile.raw(name, data, compress)
600 raise ValueError('Unknown type %#x when reading\n' % ftype)
602 cfile.offset = offset
604 # Move past the padding to the start of a possible next file. If we are
605 # already at an alignment boundary, then there is no padding.
606 pad = (self.align - fd.tell() % self.align) % self.align
607 fd.seek(pad, io.SEEK_CUR)
611 def _read_attr(cls, fd, file_pos, attr, offset):
612 """Read attributes from the file
614 CBFS files can have attributes which are things that cannot fit into the
615 header. The only attribute currently supported is compression.
618 fd: File to read from
619 file_pos: Position of file in fd
620 attr: Offset of attributes, 0 if none
621 offset: Offset of file data (used to indicate the end of the
625 Compression to use for the file (COMPRESS_...)
627 compress = COMPRESS_NONE
630 attr_size = offset - attr
631 fd.seek(file_pos + attr, io.SEEK_SET)
636 print('Attribute tag at %x ran out of data' % pos)
638 atag, alen = struct.unpack(">II", hdr)
639 data = hdr + fd.read(alen - 8)
640 if atag == FILE_ATTR_TAG_COMPRESSION:
641 # We don't currently use this information
642 atag, alen, compress, _decomp_size = struct.unpack(
643 ATTR_COMPRESSION_FORMAT, data)
645 print('Unknown attribute tag %x' % atag)
646 attr_size -= len(data)
649 def _read_header(self, fd):
650 """Read the master header
652 Reads the header and stores the information obtained into the member
656 fd: File to read from
659 True if header was read OK, False if it is truncated or has the
660 wrong magic or version
663 data = fd.read(HEADER_LEN)
664 if len(data) < HEADER_LEN:
665 print('Header at %x ran out of data' % pos)
667 (self.magic, self.version, self.rom_size, self.boot_block_size,
668 self.align, self.cbfs_offset, self.arch, _) = struct.unpack(
670 return self.magic == HEADER_MAGIC and (
671 self.version == HEADER_VERSION1 or
672 self.version == HEADER_VERSION2)
675 def _read_string(cls, fd):
676 """Read a string from a file
678 This reads a string and aligns the data to the next alignment boundary
681 fd: File to read from
684 string read ('str' type) encoded to UTF-8, or None if we ran out of
689 data = fd.read(FILENAME_ALIGN)
690 if len(data) < FILENAME_ALIGN:
692 pos = data.find(b'\0')
698 return val.decode('utf-8')
701 def cbfstool(fname, *cbfs_args):
702 """Run cbfstool with provided arguments
704 If the tool fails then this function raises an exception and prints out the
708 fname: Filename of CBFS
709 *cbfs_args: List of arguments to pass to cbfstool
712 CommandResult object containing the results
714 args = ('cbfstool', fname) + cbfs_args
715 result = command.RunPipe([args], capture=not VERBOSE,
716 capture_stderr=not VERBOSE, raise_on_error=False)
717 if result.return_code:
718 print(result.stderr, file=sys.stderr)
719 raise Exception("Failed to run (error %d): '%s'" %
720 (result.return_code, ' '.join(args)))