blob: 197cff895094ea3b5d867ad46d926be82e5efc74 [file] [log] [blame]
Simon Glass96a62962019-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
14Currently supported: raw and stage types with compression
15"""
16
17from __future__ import print_function
18
19from collections import OrderedDict
20import io
21import struct
22import sys
23
24import command
25import elf
26import tools
27
28# Set to True to enable printing output while working
29DEBUG = False
30
31# Set to True to enable output from running cbfstool for debugging
32VERBOSE = False
33
34# The master header, at the start of the CBFS
35HEADER_FORMAT = '>IIIIIIII'
36HEADER_LEN = 0x20
37HEADER_MAGIC = 0x4f524243
38HEADER_VERSION1 = 0x31313131
39HEADER_VERSION2 = 0x31313132
40
41# The file header, at the start of each file in the CBFS
42FILE_HEADER_FORMAT = b'>8sIIII'
43FILE_HEADER_LEN = 0x18
44FILE_MAGIC = b'LARCHIVE'
45FILENAME_ALIGN = 16 # Filename lengths are aligned to this
46
47# A stage header containing information about 'stage' files
48# Yes this is correct: this header is in litte-endian format
49STAGE_FORMAT = '<IQQII'
50STAGE_LEN = 0x1c
51
52# An attribute describring the compression used in a file
53ATTR_COMPRESSION_FORMAT = '>IIII'
54ATTR_COMPRESSION_LEN = 0x10
55
56# Attribute tags
57# Depending on how the header was initialised, it may be backed with 0x00 or
58# 0xff. Support both.
59FILE_ATTR_TAG_UNUSED = 0
60FILE_ATTR_TAG_UNUSED2 = 0xffffffff
61FILE_ATTR_TAG_COMPRESSION = 0x42435a4c
62FILE_ATTR_TAG_HASH = 0x68736148
63FILE_ATTR_TAG_POSITION = 0x42435350 # PSCB
64FILE_ATTR_TAG_ALIGNMENT = 0x42434c41 # ALCB
65FILE_ATTR_TAG_PADDING = 0x47444150 # PDNG
66
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
69# cbfstool.c:
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.
74MIN_BOOTBLOCK_SIZE = 4
75
76# Files start aligned to this boundary in the CBFS
77ENTRY_ALIGN = 0x40
78
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.
83ARCHITECTURE_UNKNOWN = 0xffffffff
84ARCHITECTURE_X86 = 0x00000001
85ARCHITECTURE_ARM = 0x00000010
86ARCHITECTURE_AARCH64 = 0x0000aa64
87ARCHITECTURE_MIPS = 0x00000100
88ARCHITECTURE_RISCV = 0xc001d0de
89ARCHITECTURE_PPC64 = 0x407570ff
90
91ARCH_NAMES = {
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',
99 }
100
101# File types. Only supported ones are included here
102TYPE_CBFSHEADER = 0x02 # Master header, HEADER_FORMAT
103TYPE_STAGE = 0x10 # Stage, holding an executable, see STAGE_FORMAT
104TYPE_RAW = 0x50 # Raw file, possibly compressed
105
106# Compression types
107COMPRESS_NONE, COMPRESS_LZMA, COMPRESS_LZ4 = range(3)
108
109COMPRESS_NAMES = {
110 COMPRESS_NONE : 'none',
111 COMPRESS_LZMA : 'lzma',
112 COMPRESS_LZ4 : 'lz4',
113 }
114
115def find_arch(find_name):
116 """Look up an architecture name
117
118 Args:
119 find_name: Architecture name to find
120
121 Returns:
122 ARCHITECTURE_... value or None if not found
123 """
124 for arch, name in ARCH_NAMES.items():
125 if name == find_name:
126 return arch
127 return None
128
129def find_compress(find_name):
130 """Look up a compression algorithm name
131
132 Args:
133 find_name: Compression algorithm name to find
134
135 Returns:
136 COMPRESS_... value or None if not found
137 """
138 for compress, name in COMPRESS_NAMES.items():
139 if name == find_name:
140 return compress
141 return None
142
143def align_int(val, align):
144 """Align a value up to the given alignment
145
146 Args:
147 val: Integer value to align
148 align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
149
150 Returns:
151 integer value aligned to the required boundary, rounding up if necessary
152 """
153 return int((val + align - 1) / align) * align
154
155def _pack_string(instr):
156 """Pack a string to the required aligned size by adding padding
157
158 Args:
159 instr: String to process
160
161 Returns:
162 String with required padding (at least one 0x00 byte) at the end
163 """
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))
167
168
169class CbfsFile(object):
170 """Class to represent a single CBFS file
171
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.
174
175 Properties:
176 name: Name of file
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
187 """
188 def __init__(self, name, ftype, data, compress=COMPRESS_NONE):
189 self.name = name
190 self.offset = None
191 self.data = data
192 self.ftype = ftype
193 self.compress = compress
194 self.memlen = len(data)
195 self.load = None
196 self.entry = None
197 self.base_address = None
198 self.data_len = 0
199
200 def decompress(self):
201 """Handle decompressing data if necessary"""
202 indata = self.data
203 if self.compress == COMPRESS_LZ4:
204 data = tools.Decompress(indata, 'lz4')
205 elif self.compress == COMPRESS_LZMA:
206 data = tools.Decompress(indata, 'lzma')
207 else:
208 data = indata
209 self.memlen = len(data)
210 self.data = data
211 self.data_len = len(indata)
212
213 @classmethod
214 def stage(cls, base_address, name, data):
215 """Create a new stage file
216
217 Args:
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
222
223 Returns:
224 CbfsFile object containing the file information
225 """
226 cfile = CbfsFile(name, TYPE_STAGE, data)
227 cfile.base_address = base_address
228 return cfile
229
230 @classmethod
231 def raw(cls, name, data, compress):
232 """Create a new raw file
233
234 Args:
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_...)
239
240 Returns:
241 CbfsFile object containing the file information
242 """
243 return CbfsFile(name, TYPE_RAW, data, compress)
244
245 def get_data(self):
246 """Obtain the contents of the file, in CBFS format
247
248 Returns:
249 bytes representing the contents of this file, packed and aligned
250 for directly inserting into the final CBFS output
251 """
252 name = _pack_string(self.name)
253 hdr_len = len(name) + FILE_HEADER_LEN
254 attr_pos = 0
255 content = b''
256 attr = b''
257 data = self.data
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)
263 data = elf_data.data
264 elif self.ftype == TYPE_RAW:
265 orig_data = data
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))
273 else:
274 raise ValueError('Unknown type %#x when writing\n' % self.ftype)
275 if attr:
276 attr_pos = hdr_len
277 hdr_len += len(attr)
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
282
283
284class CbfsWriter(object):
285 """Class to handle writing a Coreboot File System (CBFS)
286
287 Usage is something like:
288
289 cbw = CbfsWriter(size)
290 cbw.add_file_raw('u-boot', tools.ReadFile('u-boot.bin'))
291 ...
292 data = cbw.get_data()
293
294 Attributes:
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
304 no boot block
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
310 """
311 def __init__(self, size, arch=ARCHITECTURE_X86):
312 """Set up a new CBFS
313
314 This sets up all properties to default values. Files can be added using
315 add_file_raw(), etc.
316
317 Args:
318 size: Size of CBFS in bytes
319 arch: Architecture to declare for CBFS
320 """
321 self._master_name = 'cbfs master header'
322 self._size = size
323 self._files = OrderedDict()
324 self._arch = arch
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,
334 MIN_BOOTBLOCK_SIZE)
335 self._header_offset = self._base_address - HEADER_LEN
336 self._contents_offset = 0
337 self._hdr_at_start = False
338 else:
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 +
344 FILE_HEADER_LEN +
345 self._bootblock_size, self._align)
346 self._hdr_at_start = True
347
348 def _skip_to(self, fd, offset):
349 """Write out pad bytes until a given offset
350
351 Args:
352 fd: File objext to write to
353 offset: Offset to write to
354 """
355 if fd.tell() > offset:
356 raise ValueError('No space for data before offset %#x (current offset %#x)' %
357 (offset, fd.tell()))
358 fd.write(tools.GetBytes(self._erase_byte, offset - fd.tell()))
359
360 def _align_to(self, fd, align):
361 """Write out pad bytes until a given alignment is reached
362
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
365 pointer.
366
367 Args:
368 fd: File objext to write to
369 align: Alignment to require (e.g. 4 means pad to next 4-byte
370 boundary)
371 """
372 offset = align_int(fd.tell(), align)
373 if offset < self._size:
374 self._skip_to(fd, offset)
375
376 def add_file_stage(self, name, data):
377 """Add a new stage file to the CBFS
378
379 Args:
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
383
384 Returns:
385 CbfsFile object created
386 """
387 cfile = CbfsFile.stage(self._base_address, name, data)
388 self._files[name] = cfile
389 return cfile
390
391 def add_file_raw(self, name, data, compress=COMPRESS_NONE):
392 """Create a new raw file
393
394 Args:
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_...)
399
400 Returns:
401 CbfsFile object created
402 """
403 cfile = CbfsFile.raw(name, data, compress)
404 self._files[name] = cfile
405 return cfile
406
407 def _write_header(self, fd, add_fileheader):
408 """Write out the master header to a CBFS
409
410 Args:
411 fd: File object
412 add_fileheader: True to place the master header in a file header
413 record
414 """
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)
423 if add_fileheader:
424 name = _pack_string(self._master_name)
425 fd.write(struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, len(hdr),
426 TYPE_CBFSHEADER, 0,
427 FILE_HEADER_LEN + len(name)))
428 fd.write(name)
429 self._header_offset = fd.tell()
430 fd.write(hdr)
431 self._align_to(fd, self._align)
432 else:
433 fd.write(hdr)
434
435 def get_data(self):
436 """Obtain the full contents of the CBFS
437
438 Thhis builds the CBFS with headers and all required files.
439
440 Returns:
441 'bytes' type containing the data
442 """
443 fd = io.BytesIO()
444
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)
449
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)
456
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))
461
462 return fd.getvalue()
463
464
465class CbfsReader(object):
466 """Class to handle reading a Coreboot File System (CBFS)
467
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)
472
473 Attributes:
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_...)
487 """
488 def __init__(self, data, read=True):
489 self.align = ENTRY_ALIGN
490 self.arch = None
491 self.boot_block_size = None
492 self.cbfs_offset = None
493 self.files = OrderedDict()
494 self.magic = None
495 self.rom_size = None
496 self.stage_base_address = 0
497 self.version = None
498 self.data = data
499 if read:
500 self.read()
501
502 def read(self):
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)
509
510 # Now read in the files one at a time
511 while True:
512 cfile = self._read_next_file(fd)
513 if cfile:
514 self.files[cfile.name] = cfile
515 elif cfile is False:
516 break
517
518 def _find_and_read_header(self, fd, size):
519 """Find and read the master header in the CBFS
520
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.
524
525 Args:
526 fd: File to read from
527 size: Size of file
528
529 Returns:
530 True if header was found, False if not
531 """
532 orig_pos = fd.tell()
533 fd.seek(size - 4)
534 rel_offset, = struct.unpack('<I', fd.read(4))
535 pos = (size + rel_offset) & 0xffffffff
536 fd.seek(pos)
537 found = self._read_header(fd)
538 if not found:
539 print('Relative offset seems wrong, scanning whole image')
540 for pos in range(0, size - HEADER_LEN, 4):
541 fd.seek(pos)
542 found = self._read_header(fd)
543 if found:
544 break
545 fd.seek(orig_pos)
546 return found
547
548 def _read_next_file(self, fd):
549 """Read the next file from a CBFS
550
551 Args:
552 fd: File to read from
553
554 Returns:
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
558 """
559 file_pos = fd.tell()
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)
563 return False
564 magic, size, ftype, attr, offset = struct.unpack(FILE_HEADER_FORMAT,
565 data)
566 if magic != FILE_MAGIC:
567 return False
568 pos = fd.tell()
569 name = self._read_string(fd)
570 if name is None:
571 print('String at %x ran out of data' % pos)
572 return False
573
574 if DEBUG:
575 print('name', name)
576
577 # If there are attribute headers present, read those
578 compress = self._read_attr(fd, file_pos, attr, offset)
579 if compress is None:
580 return False
581
582 # Create the correct CbfsFile object depending on the type
583 cfile = None
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:
594 data = fd.read(size)
595 cfile = CbfsFile.raw(name, data, compress)
596 cfile.decompress()
597 if DEBUG:
598 print('data', data)
599 else:
600 raise ValueError('Unknown type %#x when reading\n' % ftype)
601 if cfile:
602 cfile.offset = offset
603
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)
608 return cfile
609
610 @classmethod
611 def _read_attr(cls, fd, file_pos, attr, offset):
612 """Read attributes from the file
613
614 CBFS files can have attributes which are things that cannot fit into the
615 header. The only attribute currently supported is compression.
616
617 Args:
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
622 attributes)
623
624 Returns:
625 Compression to use for the file (COMPRESS_...)
626 """
627 compress = COMPRESS_NONE
628 if not attr:
629 return compress
630 attr_size = offset - attr
631 fd.seek(file_pos + attr, io.SEEK_SET)
632 while attr_size:
633 pos = fd.tell()
634 hdr = fd.read(8)
635 if len(hdr) < 8:
636 print('Attribute tag at %x ran out of data' % pos)
637 return None
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)
644 else:
645 print('Unknown attribute tag %x' % atag)
646 attr_size -= len(data)
647 return compress
648
649 def _read_header(self, fd):
650 """Read the master header
651
652 Reads the header and stores the information obtained into the member
653 variables.
654
655 Args:
656 fd: File to read from
657
658 Returns:
659 True if header was read OK, False if it is truncated or has the
660 wrong magic or version
661 """
662 pos = fd.tell()
663 data = fd.read(HEADER_LEN)
664 if len(data) < HEADER_LEN:
665 print('Header at %x ran out of data' % pos)
666 return False
667 (self.magic, self.version, self.rom_size, self.boot_block_size,
668 self.align, self.cbfs_offset, self.arch, _) = struct.unpack(
669 HEADER_FORMAT, data)
670 return self.magic == HEADER_MAGIC and (
671 self.version == HEADER_VERSION1 or
672 self.version == HEADER_VERSION2)
673
674 @classmethod
675 def _read_string(cls, fd):
676 """Read a string from a file
677
678 This reads a string and aligns the data to the next alignment boundary
679
680 Args:
681 fd: File to read from
682
683 Returns:
684 string read ('str' type) encoded to UTF-8, or None if we ran out of
685 data
686 """
687 val = b''
688 while True:
689 data = fd.read(FILENAME_ALIGN)
690 if len(data) < FILENAME_ALIGN:
691 return None
692 pos = data.find(b'\0')
693 if pos == -1:
694 val += data
695 else:
696 val += data[:pos]
697 break
698 return val.decode('utf-8')
699
700
701def cbfstool(fname, *cbfs_args):
702 """Run cbfstool with provided arguments
703
704 If the tool fails then this function raises an exception and prints out the
705 output and stderr.
706
707 Args:
708 fname: Filename of CBFS
709 *cbfs_args: List of arguments to pass to cbfstool
710
711 Returns:
712 CommandResult object containing the results
713 """
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)))