blob: eea7868b16c56ec461572da5aabadf9500bcc3bd [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
Simon Glassa61e6fe2019-07-08 13:18:55 -060014Currently supported: raw and stage types with compression, padding empty areas
Simon Glassc2f1aed2019-07-08 13:18:56 -060015 with empty files, fixed-offset files
Simon Glass96a62962019-07-08 13:18:52 -060016"""
17
Simon Glass96a62962019-07-08 13:18:52 -060018from collections import OrderedDict
19import io
20import struct
21import sys
22
Simon Glass3ac7d832022-01-09 20:14:03 -070023from binman import comp_util
Simon Glassc585dd42020-04-17 18:09:03 -060024from binman import elf
Simon Glassa997ea52020-04-17 18:09:04 -060025from patman import command
26from patman import tools
Simon Glass96a62962019-07-08 13:18:52 -060027
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
Simon Glassa61e6fe2019-07-08 13:18:55 -0600105TYPE_EMPTY = 0xffffffff # Empty data
Simon Glass96a62962019-07-08 13:18:52 -0600106
107# Compression types
108COMPRESS_NONE, COMPRESS_LZMA, COMPRESS_LZ4 = range(3)
109
110COMPRESS_NAMES = {
111 COMPRESS_NONE : 'none',
112 COMPRESS_LZMA : 'lzma',
113 COMPRESS_LZ4 : 'lz4',
114 }
115
116def find_arch(find_name):
117 """Look up an architecture name
118
119 Args:
120 find_name: Architecture name to find
121
122 Returns:
123 ARCHITECTURE_... value or None if not found
124 """
125 for arch, name in ARCH_NAMES.items():
126 if name == find_name:
127 return arch
128 return None
129
130def find_compress(find_name):
131 """Look up a compression algorithm name
132
133 Args:
134 find_name: Compression algorithm name to find
135
136 Returns:
137 COMPRESS_... value or None if not found
138 """
139 for compress, name in COMPRESS_NAMES.items():
140 if name == find_name:
141 return compress
142 return None
143
Simon Glasse1b605d2019-07-08 14:25:51 -0600144def compress_name(compress):
145 """Look up the name of a compression algorithm
146
147 Args:
148 compress: Compression algorithm number to find (COMPRESS_...)
149
150 Returns:
151 Compression algorithm name (string)
152
153 Raises:
154 KeyError if the algorithm number is invalid
155 """
156 return COMPRESS_NAMES[compress]
157
Simon Glass96a62962019-07-08 13:18:52 -0600158def align_int(val, align):
159 """Align a value up to the given alignment
160
161 Args:
162 val: Integer value to align
163 align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
164
165 Returns:
166 integer value aligned to the required boundary, rounding up if necessary
167 """
168 return int((val + align - 1) / align) * align
169
Simon Glassa61e6fe2019-07-08 13:18:55 -0600170def align_int_down(val, align):
171 """Align a value down to the given alignment
172
173 Args:
174 val: Integer value to align
175 align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
176
177 Returns:
178 integer value aligned to the required boundary, rounding down if
179 necessary
180 """
181 return int(val / align) * align
182
Simon Glass96a62962019-07-08 13:18:52 -0600183def _pack_string(instr):
184 """Pack a string to the required aligned size by adding padding
185
186 Args:
187 instr: String to process
188
189 Returns:
190 String with required padding (at least one 0x00 byte) at the end
191 """
192 val = tools.ToBytes(instr)
193 pad_len = align_int(len(val) + 1, FILENAME_ALIGN)
194 return val + tools.GetBytes(0, pad_len - len(val))
195
196
197class CbfsFile(object):
198 """Class to represent a single CBFS file
199
200 This is used to hold the information about a file, including its contents.
Simon Glass3170e542019-07-08 14:25:39 -0600201 Use the get_data_and_offset() method to obtain the raw output for writing to
202 CBFS.
Simon Glass96a62962019-07-08 13:18:52 -0600203
204 Properties:
205 name: Name of file
206 offset: Offset of file data from start of file header
Simon Glassc2f1aed2019-07-08 13:18:56 -0600207 cbfs_offset: Offset of file data in bytes from start of CBFS, or None to
208 place this file anyway
Simon Glass96a62962019-07-08 13:18:52 -0600209 data: Contents of file, uncompressed
Simon Glass37fdd142019-07-20 12:24:06 -0600210 orig_data: Original data added to the file, possibly compressed
Simon Glass96a62962019-07-08 13:18:52 -0600211 data_len: Length of (possibly compressed) data in bytes
212 ftype: File type (TYPE_...)
213 compression: Compression type (COMPRESS_...)
Simon Glass4f374332019-07-08 14:25:40 -0600214 memlen: Length of data in memory, i.e. the uncompressed length, None if
215 no compression algortihm is selected
Simon Glass96a62962019-07-08 13:18:52 -0600216 load: Load address in memory if known, else None
217 entry: Entry address in memory if known, else None. This is where
218 execution starts after the file is loaded
219 base_address: Base address to use for 'stage' files
Simon Glassa61e6fe2019-07-08 13:18:55 -0600220 erase_byte: Erase byte to use for padding between the file header and
221 contents (used for empty files)
222 size: Size of the file in bytes (used for empty files)
Simon Glass96a62962019-07-08 13:18:52 -0600223 """
Simon Glassc2f1aed2019-07-08 13:18:56 -0600224 def __init__(self, name, ftype, data, cbfs_offset, compress=COMPRESS_NONE):
Simon Glass96a62962019-07-08 13:18:52 -0600225 self.name = name
226 self.offset = None
Simon Glassc2f1aed2019-07-08 13:18:56 -0600227 self.cbfs_offset = cbfs_offset
Simon Glass96a62962019-07-08 13:18:52 -0600228 self.data = data
Simon Glass37fdd142019-07-20 12:24:06 -0600229 self.orig_data = data
Simon Glass96a62962019-07-08 13:18:52 -0600230 self.ftype = ftype
231 self.compress = compress
Simon Glass4f374332019-07-08 14:25:40 -0600232 self.memlen = None
Simon Glass96a62962019-07-08 13:18:52 -0600233 self.load = None
234 self.entry = None
235 self.base_address = None
Simon Glass4f374332019-07-08 14:25:40 -0600236 self.data_len = len(data)
Simon Glassa61e6fe2019-07-08 13:18:55 -0600237 self.erase_byte = None
238 self.size = None
Simon Glass96a62962019-07-08 13:18:52 -0600239
240 def decompress(self):
241 """Handle decompressing data if necessary"""
242 indata = self.data
243 if self.compress == COMPRESS_LZ4:
Simon Glassdd5c14ec2022-01-09 20:14:04 -0700244 data = comp_util.decompress(indata, 'lz4', with_header=False)
Simon Glass96a62962019-07-08 13:18:52 -0600245 elif self.compress == COMPRESS_LZMA:
Simon Glassdd5c14ec2022-01-09 20:14:04 -0700246 data = comp_util.decompress(indata, 'lzma', with_header=False)
Simon Glass96a62962019-07-08 13:18:52 -0600247 else:
248 data = indata
249 self.memlen = len(data)
250 self.data = data
251 self.data_len = len(indata)
252
253 @classmethod
Simon Glassc2f1aed2019-07-08 13:18:56 -0600254 def stage(cls, base_address, name, data, cbfs_offset):
Simon Glass96a62962019-07-08 13:18:52 -0600255 """Create a new stage file
256
257 Args:
258 base_address: Int base address for memory-mapping of ELF file
259 name: String file name to put in CBFS (does not need to correspond
260 to the name that the file originally came from)
261 data: Contents of file
Simon Glassc2f1aed2019-07-08 13:18:56 -0600262 cbfs_offset: Offset of file data in bytes from start of CBFS, or
263 None to place this file anyway
Simon Glass96a62962019-07-08 13:18:52 -0600264
265 Returns:
266 CbfsFile object containing the file information
267 """
Simon Glassc2f1aed2019-07-08 13:18:56 -0600268 cfile = CbfsFile(name, TYPE_STAGE, data, cbfs_offset)
Simon Glass96a62962019-07-08 13:18:52 -0600269 cfile.base_address = base_address
270 return cfile
271
272 @classmethod
Simon Glassc2f1aed2019-07-08 13:18:56 -0600273 def raw(cls, name, data, cbfs_offset, compress):
Simon Glass96a62962019-07-08 13:18:52 -0600274 """Create a new raw file
275
276 Args:
277 name: String file name to put in CBFS (does not need to correspond
278 to the name that the file originally came from)
279 data: Contents of file
Simon Glassc2f1aed2019-07-08 13:18:56 -0600280 cbfs_offset: Offset of file data in bytes from start of CBFS, or
281 None to place this file anyway
Simon Glass96a62962019-07-08 13:18:52 -0600282 compress: Compression algorithm to use (COMPRESS_...)
283
284 Returns:
285 CbfsFile object containing the file information
286 """
Simon Glassc2f1aed2019-07-08 13:18:56 -0600287 return CbfsFile(name, TYPE_RAW, data, cbfs_offset, compress)
Simon Glass96a62962019-07-08 13:18:52 -0600288
Simon Glassa61e6fe2019-07-08 13:18:55 -0600289 @classmethod
290 def empty(cls, space_to_use, erase_byte):
291 """Create a new empty file of a given size
292
293 Args:
294 space_to_use:: Size of available space, which must be at least as
295 large as the alignment size for this CBFS
296 erase_byte: Byte to use for contents of file (repeated through the
297 whole file)
298
299 Returns:
300 CbfsFile object containing the file information
301 """
Simon Glassc2f1aed2019-07-08 13:18:56 -0600302 cfile = CbfsFile('', TYPE_EMPTY, b'', None)
Simon Glassa61e6fe2019-07-08 13:18:55 -0600303 cfile.size = space_to_use - FILE_HEADER_LEN - FILENAME_ALIGN
304 cfile.erase_byte = erase_byte
305 return cfile
306
Simon Glassc2f1aed2019-07-08 13:18:56 -0600307 def calc_start_offset(self):
308 """Check if this file needs to start at a particular offset in CBFS
309
310 Returns:
311 None if the file can be placed anywhere, or
312 the largest offset where the file could start (integer)
313 """
314 if self.cbfs_offset is None:
315 return None
316 return self.cbfs_offset - self.get_header_len()
317
318 def get_header_len(self):
319 """Get the length of headers required for a file
320
321 This is the minimum length required before the actual data for this file
322 could start. It might start later if there is padding.
323
324 Returns:
325 Total length of all non-data fields, in bytes
326 """
327 name = _pack_string(self.name)
328 hdr_len = len(name) + FILE_HEADER_LEN
329 if self.ftype == TYPE_STAGE:
330 pass
331 elif self.ftype == TYPE_RAW:
332 hdr_len += ATTR_COMPRESSION_LEN
333 elif self.ftype == TYPE_EMPTY:
334 pass
335 else:
336 raise ValueError('Unknown file type %#x\n' % self.ftype)
337 return hdr_len
338
Simon Glass3170e542019-07-08 14:25:39 -0600339 def get_data_and_offset(self, offset=None, pad_byte=None):
340 """Obtain the contents of the file, in CBFS format and the offset of
341 the data within the file
Simon Glass96a62962019-07-08 13:18:52 -0600342
343 Returns:
Simon Glass3170e542019-07-08 14:25:39 -0600344 tuple:
345 bytes representing the contents of this file, packed and aligned
346 for directly inserting into the final CBFS output
347 offset to the file data from the start of the returned data.
Simon Glass96a62962019-07-08 13:18:52 -0600348 """
349 name = _pack_string(self.name)
350 hdr_len = len(name) + FILE_HEADER_LEN
351 attr_pos = 0
352 content = b''
353 attr = b''
Simon Glassc2f1aed2019-07-08 13:18:56 -0600354 pad = b''
Simon Glass96a62962019-07-08 13:18:52 -0600355 data = self.data
356 if self.ftype == TYPE_STAGE:
357 elf_data = elf.DecodeElf(data, self.base_address)
358 content = struct.pack(STAGE_FORMAT, self.compress,
359 elf_data.entry, elf_data.load,
360 len(elf_data.data), elf_data.memsize)
361 data = elf_data.data
362 elif self.ftype == TYPE_RAW:
363 orig_data = data
364 if self.compress == COMPRESS_LZ4:
Simon Glassdd5c14ec2022-01-09 20:14:04 -0700365 data = comp_util.compress(orig_data, 'lz4', with_header=False)
Simon Glass96a62962019-07-08 13:18:52 -0600366 elif self.compress == COMPRESS_LZMA:
Simon Glassdd5c14ec2022-01-09 20:14:04 -0700367 data = comp_util.compress(orig_data, 'lzma', with_header=False)
Simon Glass4f374332019-07-08 14:25:40 -0600368 self.memlen = len(orig_data)
369 self.data_len = len(data)
Simon Glass96a62962019-07-08 13:18:52 -0600370 attr = struct.pack(ATTR_COMPRESSION_FORMAT,
371 FILE_ATTR_TAG_COMPRESSION, ATTR_COMPRESSION_LEN,
Simon Glass4f374332019-07-08 14:25:40 -0600372 self.compress, self.memlen)
Simon Glassa61e6fe2019-07-08 13:18:55 -0600373 elif self.ftype == TYPE_EMPTY:
374 data = tools.GetBytes(self.erase_byte, self.size)
Simon Glass96a62962019-07-08 13:18:52 -0600375 else:
376 raise ValueError('Unknown type %#x when writing\n' % self.ftype)
377 if attr:
378 attr_pos = hdr_len
379 hdr_len += len(attr)
Simon Glassc2f1aed2019-07-08 13:18:56 -0600380 if self.cbfs_offset is not None:
381 pad_len = self.cbfs_offset - offset - hdr_len
382 if pad_len < 0: # pragma: no cover
383 # Test coverage of this is not available since this should never
384 # happen. It indicates that get_header_len() provided an
385 # incorrect value (too small) so that we decided that we could
386 # put this file at the requested place, but in fact a previous
387 # file extends far enough into the CBFS that this is not
388 # possible.
389 raise ValueError("Internal error: CBFS file '%s': Requested offset %#x but current output position is %#x" %
390 (self.name, self.cbfs_offset, offset))
391 pad = tools.GetBytes(pad_byte, pad_len)
392 hdr_len += pad_len
Simon Glass3170e542019-07-08 14:25:39 -0600393
394 # This is the offset of the start of the file's data,
395 size = len(content) + len(data)
396 hdr = struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, size,
Simon Glass96a62962019-07-08 13:18:52 -0600397 self.ftype, attr_pos, hdr_len)
Simon Glassc2f1aed2019-07-08 13:18:56 -0600398
399 # Do a sanity check of the get_header_len() function, to ensure that it
400 # stays in lockstep with this function
401 expected_len = self.get_header_len()
402 actual_len = len(hdr + name + attr)
403 if expected_len != actual_len: # pragma: no cover
404 # Test coverage of this is not available since this should never
405 # happen. It probably indicates that get_header_len() is broken.
406 raise ValueError("Internal error: CBFS file '%s': Expected headers of %#x bytes, got %#d" %
407 (self.name, expected_len, actual_len))
Simon Glass3170e542019-07-08 14:25:39 -0600408 return hdr + name + attr + pad + content + data, hdr_len
Simon Glass96a62962019-07-08 13:18:52 -0600409
410
411class CbfsWriter(object):
412 """Class to handle writing a Coreboot File System (CBFS)
413
414 Usage is something like:
415
416 cbw = CbfsWriter(size)
417 cbw.add_file_raw('u-boot', tools.ReadFile('u-boot.bin'))
418 ...
Simon Glass3170e542019-07-08 14:25:39 -0600419 data, cbfs_offset = cbw.get_data_and_offset()
Simon Glass96a62962019-07-08 13:18:52 -0600420
421 Attributes:
422 _master_name: Name of the file containing the master header
423 _size: Size of the filesystem, in bytes
424 _files: Ordered list of files in the CBFS, each a CbfsFile
425 _arch: Architecture of the CBFS (ARCHITECTURE_...)
426 _bootblock_size: Size of the bootblock, typically at the end of the CBFS
427 _erase_byte: Byte to use for empty space in the CBFS
428 _align: Alignment to use for files, typically ENTRY_ALIGN
429 _base_address: Boot block offset in bytes from the start of CBFS.
430 Typically this is located at top of the CBFS. It is 0 when there is
431 no boot block
432 _header_offset: Offset of master header in bytes from start of CBFS
433 _contents_offset: Offset of first file header
434 _hdr_at_start: True if the master header is at the start of the CBFS,
435 instead of the end as normal for x86
436 _add_fileheader: True to add a fileheader around the master header
437 """
438 def __init__(self, size, arch=ARCHITECTURE_X86):
439 """Set up a new CBFS
440
441 This sets up all properties to default values. Files can be added using
442 add_file_raw(), etc.
443
444 Args:
445 size: Size of CBFS in bytes
446 arch: Architecture to declare for CBFS
447 """
448 self._master_name = 'cbfs master header'
449 self._size = size
450 self._files = OrderedDict()
451 self._arch = arch
452 self._bootblock_size = 0
453 self._erase_byte = 0xff
454 self._align = ENTRY_ALIGN
455 self._add_fileheader = False
456 if self._arch == ARCHITECTURE_X86:
457 # Allow 4 bytes for the header pointer. That holds the
458 # twos-compliment negative offset of the master header in bytes
459 # measured from one byte past the end of the CBFS
460 self._base_address = self._size - max(self._bootblock_size,
461 MIN_BOOTBLOCK_SIZE)
462 self._header_offset = self._base_address - HEADER_LEN
463 self._contents_offset = 0
464 self._hdr_at_start = False
465 else:
466 # For non-x86, different rules apply
467 self._base_address = 0
468 self._header_offset = align_int(self._base_address +
469 self._bootblock_size, 4)
470 self._contents_offset = align_int(self._header_offset +
471 FILE_HEADER_LEN +
472 self._bootblock_size, self._align)
473 self._hdr_at_start = True
474
475 def _skip_to(self, fd, offset):
476 """Write out pad bytes until a given offset
477
478 Args:
479 fd: File objext to write to
480 offset: Offset to write to
481 """
482 if fd.tell() > offset:
483 raise ValueError('No space for data before offset %#x (current offset %#x)' %
484 (offset, fd.tell()))
485 fd.write(tools.GetBytes(self._erase_byte, offset - fd.tell()))
486
Simon Glassa61e6fe2019-07-08 13:18:55 -0600487 def _pad_to(self, fd, offset):
488 """Write out pad bytes and/or an empty file until a given offset
489
490 Args:
491 fd: File objext to write to
492 offset: Offset to write to
493 """
494 self._align_to(fd, self._align)
495 upto = fd.tell()
496 if upto > offset:
497 raise ValueError('No space for data before pad offset %#x (current offset %#x)' %
498 (offset, upto))
499 todo = align_int_down(offset - upto, self._align)
500 if todo:
501 cbf = CbfsFile.empty(todo, self._erase_byte)
Simon Glass3170e542019-07-08 14:25:39 -0600502 fd.write(cbf.get_data_and_offset()[0])
Simon Glassa61e6fe2019-07-08 13:18:55 -0600503 self._skip_to(fd, offset)
504
Simon Glass96a62962019-07-08 13:18:52 -0600505 def _align_to(self, fd, align):
506 """Write out pad bytes until a given alignment is reached
507
508 This only aligns if the resulting output would not reach the end of the
509 CBFS, since we want to leave the last 4 bytes for the master-header
510 pointer.
511
512 Args:
513 fd: File objext to write to
514 align: Alignment to require (e.g. 4 means pad to next 4-byte
515 boundary)
516 """
517 offset = align_int(fd.tell(), align)
518 if offset < self._size:
519 self._skip_to(fd, offset)
520
Simon Glassc2f1aed2019-07-08 13:18:56 -0600521 def add_file_stage(self, name, data, cbfs_offset=None):
Simon Glass96a62962019-07-08 13:18:52 -0600522 """Add a new stage file to the CBFS
523
524 Args:
525 name: String file name to put in CBFS (does not need to correspond
526 to the name that the file originally came from)
527 data: Contents of file
Simon Glassc2f1aed2019-07-08 13:18:56 -0600528 cbfs_offset: Offset of this file's data within the CBFS, in bytes,
529 or None to place this file anywhere
Simon Glass96a62962019-07-08 13:18:52 -0600530
531 Returns:
532 CbfsFile object created
533 """
Simon Glassc2f1aed2019-07-08 13:18:56 -0600534 cfile = CbfsFile.stage(self._base_address, name, data, cbfs_offset)
Simon Glass96a62962019-07-08 13:18:52 -0600535 self._files[name] = cfile
536 return cfile
537
Simon Glassc2f1aed2019-07-08 13:18:56 -0600538 def add_file_raw(self, name, data, cbfs_offset=None,
539 compress=COMPRESS_NONE):
Simon Glass96a62962019-07-08 13:18:52 -0600540 """Create a new raw file
541
542 Args:
543 name: String file name to put in CBFS (does not need to correspond
544 to the name that the file originally came from)
545 data: Contents of file
Simon Glassc2f1aed2019-07-08 13:18:56 -0600546 cbfs_offset: Offset of this file's data within the CBFS, in bytes,
547 or None to place this file anywhere
Simon Glass96a62962019-07-08 13:18:52 -0600548 compress: Compression algorithm to use (COMPRESS_...)
549
550 Returns:
551 CbfsFile object created
552 """
Simon Glassc2f1aed2019-07-08 13:18:56 -0600553 cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
Simon Glass96a62962019-07-08 13:18:52 -0600554 self._files[name] = cfile
555 return cfile
556
557 def _write_header(self, fd, add_fileheader):
558 """Write out the master header to a CBFS
559
560 Args:
561 fd: File object
562 add_fileheader: True to place the master header in a file header
563 record
564 """
565 if fd.tell() > self._header_offset:
566 raise ValueError('No space for header at offset %#x (current offset %#x)' %
567 (self._header_offset, fd.tell()))
568 if not add_fileheader:
Simon Glassa61e6fe2019-07-08 13:18:55 -0600569 self._pad_to(fd, self._header_offset)
Simon Glass96a62962019-07-08 13:18:52 -0600570 hdr = struct.pack(HEADER_FORMAT, HEADER_MAGIC, HEADER_VERSION2,
571 self._size, self._bootblock_size, self._align,
572 self._contents_offset, self._arch, 0xffffffff)
573 if add_fileheader:
574 name = _pack_string(self._master_name)
575 fd.write(struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, len(hdr),
576 TYPE_CBFSHEADER, 0,
577 FILE_HEADER_LEN + len(name)))
578 fd.write(name)
579 self._header_offset = fd.tell()
580 fd.write(hdr)
581 self._align_to(fd, self._align)
582 else:
583 fd.write(hdr)
584
585 def get_data(self):
586 """Obtain the full contents of the CBFS
587
588 Thhis builds the CBFS with headers and all required files.
589
590 Returns:
591 'bytes' type containing the data
592 """
593 fd = io.BytesIO()
594
595 # THe header can go at the start in some cases
596 if self._hdr_at_start:
597 self._write_header(fd, add_fileheader=self._add_fileheader)
598 self._skip_to(fd, self._contents_offset)
599
600 # Write out each file
601 for cbf in self._files.values():
Simon Glassc2f1aed2019-07-08 13:18:56 -0600602 # Place the file at its requested place, if any
603 offset = cbf.calc_start_offset()
604 if offset is not None:
605 self._pad_to(fd, align_int_down(offset, self._align))
Simon Glass3170e542019-07-08 14:25:39 -0600606 pos = fd.tell()
607 data, data_offset = cbf.get_data_and_offset(pos, self._erase_byte)
608 fd.write(data)
Simon Glass96a62962019-07-08 13:18:52 -0600609 self._align_to(fd, self._align)
Simon Glass3170e542019-07-08 14:25:39 -0600610 cbf.calced_cbfs_offset = pos + data_offset
Simon Glass96a62962019-07-08 13:18:52 -0600611 if not self._hdr_at_start:
612 self._write_header(fd, add_fileheader=self._add_fileheader)
613
614 # Pad to the end and write a pointer to the CBFS master header
Simon Glassa61e6fe2019-07-08 13:18:55 -0600615 self._pad_to(fd, self._base_address or self._size - 4)
Simon Glass96a62962019-07-08 13:18:52 -0600616 rel_offset = self._header_offset - self._size
617 fd.write(struct.pack('<I', rel_offset & 0xffffffff))
618
619 return fd.getvalue()
620
621
622class CbfsReader(object):
623 """Class to handle reading a Coreboot File System (CBFS)
624
625 Usage is something like:
626 cbfs = cbfs_util.CbfsReader(data)
627 cfile = cbfs.files['u-boot']
628 self.WriteFile('u-boot.bin', cfile.data)
629
630 Attributes:
631 files: Ordered list of CbfsFile objects
632 align: Alignment to use for files, typically ENTRT_ALIGN
633 stage_base_address: Base address to use when mapping ELF files into the
634 CBFS for TYPE_STAGE files. If this is larger than the code address
635 of the ELF file, then data at the start of the ELF file will not
636 appear in the CBFS. Currently there are no tests for behaviour as
637 documentation is sparse
638 magic: Integer magic number from master header (HEADER_MAGIC)
639 version: Version number of CBFS (HEADER_VERSION2)
640 rom_size: Size of CBFS
641 boot_block_size: Size of boot block
642 cbfs_offset: Offset of the first file in bytes from start of CBFS
643 arch: Architecture of CBFS file (ARCHITECTURE_...)
644 """
645 def __init__(self, data, read=True):
646 self.align = ENTRY_ALIGN
647 self.arch = None
648 self.boot_block_size = None
649 self.cbfs_offset = None
650 self.files = OrderedDict()
651 self.magic = None
652 self.rom_size = None
653 self.stage_base_address = 0
654 self.version = None
655 self.data = data
656 if read:
657 self.read()
658
659 def read(self):
660 """Read all the files in the CBFS and add them to self.files"""
661 with io.BytesIO(self.data) as fd:
662 # First, get the master header
663 if not self._find_and_read_header(fd, len(self.data)):
664 raise ValueError('Cannot find master header')
665 fd.seek(self.cbfs_offset)
666
667 # Now read in the files one at a time
668 while True:
669 cfile = self._read_next_file(fd)
670 if cfile:
671 self.files[cfile.name] = cfile
672 elif cfile is False:
673 break
674
675 def _find_and_read_header(self, fd, size):
676 """Find and read the master header in the CBFS
677
678 This looks at the pointer word at the very end of the CBFS. This is an
679 offset to the header relative to the size of the CBFS, which is assumed
680 to be known. Note that the offset is in *little endian* format.
681
682 Args:
683 fd: File to read from
684 size: Size of file
685
686 Returns:
687 True if header was found, False if not
688 """
689 orig_pos = fd.tell()
690 fd.seek(size - 4)
691 rel_offset, = struct.unpack('<I', fd.read(4))
692 pos = (size + rel_offset) & 0xffffffff
693 fd.seek(pos)
694 found = self._read_header(fd)
695 if not found:
696 print('Relative offset seems wrong, scanning whole image')
697 for pos in range(0, size - HEADER_LEN, 4):
698 fd.seek(pos)
699 found = self._read_header(fd)
700 if found:
701 break
702 fd.seek(orig_pos)
703 return found
704
705 def _read_next_file(self, fd):
706 """Read the next file from a CBFS
707
708 Args:
709 fd: File to read from
710
711 Returns:
712 CbfsFile object, if found
713 None if no object found, but data was parsed (e.g. TYPE_CBFSHEADER)
714 False if at end of CBFS and reading should stop
715 """
716 file_pos = fd.tell()
717 data = fd.read(FILE_HEADER_LEN)
718 if len(data) < FILE_HEADER_LEN:
Simon Glassd4ed3b02019-07-20 12:24:03 -0600719 print('File header at %#x ran out of data' % file_pos)
Simon Glass96a62962019-07-08 13:18:52 -0600720 return False
721 magic, size, ftype, attr, offset = struct.unpack(FILE_HEADER_FORMAT,
722 data)
723 if magic != FILE_MAGIC:
724 return False
725 pos = fd.tell()
726 name = self._read_string(fd)
727 if name is None:
Simon Glassd4ed3b02019-07-20 12:24:03 -0600728 print('String at %#x ran out of data' % pos)
Simon Glass96a62962019-07-08 13:18:52 -0600729 return False
730
731 if DEBUG:
732 print('name', name)
733
734 # If there are attribute headers present, read those
735 compress = self._read_attr(fd, file_pos, attr, offset)
736 if compress is None:
737 return False
738
739 # Create the correct CbfsFile object depending on the type
740 cfile = None
Simon Glassc2f1aed2019-07-08 13:18:56 -0600741 cbfs_offset = file_pos + offset
742 fd.seek(cbfs_offset, io.SEEK_SET)
Simon Glass96a62962019-07-08 13:18:52 -0600743 if ftype == TYPE_CBFSHEADER:
744 self._read_header(fd)
745 elif ftype == TYPE_STAGE:
746 data = fd.read(STAGE_LEN)
Simon Glassc2f1aed2019-07-08 13:18:56 -0600747 cfile = CbfsFile.stage(self.stage_base_address, name, b'',
748 cbfs_offset)
Simon Glass96a62962019-07-08 13:18:52 -0600749 (cfile.compress, cfile.entry, cfile.load, cfile.data_len,
750 cfile.memlen) = struct.unpack(STAGE_FORMAT, data)
751 cfile.data = fd.read(cfile.data_len)
752 elif ftype == TYPE_RAW:
753 data = fd.read(size)
Simon Glassc2f1aed2019-07-08 13:18:56 -0600754 cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
Simon Glass96a62962019-07-08 13:18:52 -0600755 cfile.decompress()
756 if DEBUG:
757 print('data', data)
Simon Glassa61e6fe2019-07-08 13:18:55 -0600758 elif ftype == TYPE_EMPTY:
759 # Just read the data and discard it, since it is only padding
760 fd.read(size)
Simon Glassc2f1aed2019-07-08 13:18:56 -0600761 cfile = CbfsFile('', TYPE_EMPTY, b'', cbfs_offset)
Simon Glass96a62962019-07-08 13:18:52 -0600762 else:
763 raise ValueError('Unknown type %#x when reading\n' % ftype)
764 if cfile:
765 cfile.offset = offset
766
767 # Move past the padding to the start of a possible next file. If we are
768 # already at an alignment boundary, then there is no padding.
769 pad = (self.align - fd.tell() % self.align) % self.align
770 fd.seek(pad, io.SEEK_CUR)
771 return cfile
772
773 @classmethod
774 def _read_attr(cls, fd, file_pos, attr, offset):
775 """Read attributes from the file
776
777 CBFS files can have attributes which are things that cannot fit into the
Simon Glassc2f1aed2019-07-08 13:18:56 -0600778 header. The only attributes currently supported are compression and the
779 unused tag.
Simon Glass96a62962019-07-08 13:18:52 -0600780
781 Args:
782 fd: File to read from
783 file_pos: Position of file in fd
784 attr: Offset of attributes, 0 if none
785 offset: Offset of file data (used to indicate the end of the
786 attributes)
787
788 Returns:
789 Compression to use for the file (COMPRESS_...)
790 """
791 compress = COMPRESS_NONE
792 if not attr:
793 return compress
794 attr_size = offset - attr
795 fd.seek(file_pos + attr, io.SEEK_SET)
796 while attr_size:
797 pos = fd.tell()
798 hdr = fd.read(8)
799 if len(hdr) < 8:
800 print('Attribute tag at %x ran out of data' % pos)
801 return None
802 atag, alen = struct.unpack(">II", hdr)
803 data = hdr + fd.read(alen - 8)
804 if atag == FILE_ATTR_TAG_COMPRESSION:
805 # We don't currently use this information
806 atag, alen, compress, _decomp_size = struct.unpack(
807 ATTR_COMPRESSION_FORMAT, data)
Simon Glassc2f1aed2019-07-08 13:18:56 -0600808 elif atag == FILE_ATTR_TAG_UNUSED2:
809 break
Simon Glass96a62962019-07-08 13:18:52 -0600810 else:
811 print('Unknown attribute tag %x' % atag)
812 attr_size -= len(data)
813 return compress
814
815 def _read_header(self, fd):
816 """Read the master header
817
818 Reads the header and stores the information obtained into the member
819 variables.
820
821 Args:
822 fd: File to read from
823
824 Returns:
825 True if header was read OK, False if it is truncated or has the
826 wrong magic or version
827 """
828 pos = fd.tell()
829 data = fd.read(HEADER_LEN)
830 if len(data) < HEADER_LEN:
831 print('Header at %x ran out of data' % pos)
832 return False
833 (self.magic, self.version, self.rom_size, self.boot_block_size,
834 self.align, self.cbfs_offset, self.arch, _) = struct.unpack(
835 HEADER_FORMAT, data)
836 return self.magic == HEADER_MAGIC and (
837 self.version == HEADER_VERSION1 or
838 self.version == HEADER_VERSION2)
839
840 @classmethod
841 def _read_string(cls, fd):
842 """Read a string from a file
843
844 This reads a string and aligns the data to the next alignment boundary
845
846 Args:
847 fd: File to read from
848
849 Returns:
850 string read ('str' type) encoded to UTF-8, or None if we ran out of
851 data
852 """
853 val = b''
854 while True:
855 data = fd.read(FILENAME_ALIGN)
856 if len(data) < FILENAME_ALIGN:
857 return None
858 pos = data.find(b'\0')
859 if pos == -1:
860 val += data
861 else:
862 val += data[:pos]
863 break
864 return val.decode('utf-8')