blob: 076768ae839b8358c571b1631d79628ff2ef1253 [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
Simon Glass4997a7e2019-07-08 13:18:52 -060018from collections import OrderedDict
19import io
20import struct
21import sys
22
Stefan Herbrechtsmeieredafeb82022-08-19 16:25:29 +020023from binman import bintool
Simon Glass16287932020-04-17 18:09:03 -060024from binman import elf
Simon Glass4583c002023-02-23 18:18:04 -070025from u_boot_pylib import command
26from u_boot_pylib import tools
Simon Glass4997a7e2019-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'
Simon Glassab326012023-10-14 14:40:28 -060045ATTRIBUTE_ALIGN = 4 # All attribute sizes must be divisible by this
Simon Glass4997a7e2019-07-08 13:18:52 -060046
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
Simon Glass4997a7e2019-07-08 13:18:52 -060057FILE_ATTR_TAG_COMPRESSION = 0x42435a4c
58FILE_ATTR_TAG_HASH = 0x68736148
59FILE_ATTR_TAG_POSITION = 0x42435350 # PSCB
60FILE_ATTR_TAG_ALIGNMENT = 0x42434c41 # ALCB
61FILE_ATTR_TAG_PADDING = 0x47444150 # PDNG
62
63# This is 'the size of bootblock reserved in firmware image (cbfs.txt)'
64# Not much more info is available, but we set it to 4, due to this comment in
65# cbfstool.c:
66# This causes 4 bytes to be left out at the end of the image, for two reasons:
67# 1. The cbfs master header pointer resides there
68# 2. Ssme cbfs implementations assume that an image that resides below 4GB has
69# a bootblock and get confused when the end of the image is at 4GB == 0.
70MIN_BOOTBLOCK_SIZE = 4
71
72# Files start aligned to this boundary in the CBFS
73ENTRY_ALIGN = 0x40
74
75# CBFSs must declare an architecture since much of the logic is designed with
76# x86 in mind. The effect of setting this value is not well documented, but in
77# general x86 is used and this makes use of a boot block and an image that ends
78# at the end of 32-bit address space.
79ARCHITECTURE_UNKNOWN = 0xffffffff
80ARCHITECTURE_X86 = 0x00000001
81ARCHITECTURE_ARM = 0x00000010
82ARCHITECTURE_AARCH64 = 0x0000aa64
83ARCHITECTURE_MIPS = 0x00000100
84ARCHITECTURE_RISCV = 0xc001d0de
85ARCHITECTURE_PPC64 = 0x407570ff
86
87ARCH_NAMES = {
88 ARCHITECTURE_UNKNOWN : 'unknown',
89 ARCHITECTURE_X86 : 'x86',
90 ARCHITECTURE_ARM : 'arm',
91 ARCHITECTURE_AARCH64 : 'arm64',
92 ARCHITECTURE_MIPS : 'mips',
93 ARCHITECTURE_RISCV : 'riscv',
94 ARCHITECTURE_PPC64 : 'ppc64',
95 }
96
97# File types. Only supported ones are included here
98TYPE_CBFSHEADER = 0x02 # Master header, HEADER_FORMAT
99TYPE_STAGE = 0x10 # Stage, holding an executable, see STAGE_FORMAT
100TYPE_RAW = 0x50 # Raw file, possibly compressed
Simon Glass7c173ce2019-07-08 13:18:55 -0600101TYPE_EMPTY = 0xffffffff # Empty data
Simon Glass4997a7e2019-07-08 13:18:52 -0600102
103# Compression types
104COMPRESS_NONE, COMPRESS_LZMA, COMPRESS_LZ4 = range(3)
105
106COMPRESS_NAMES = {
107 COMPRESS_NONE : 'none',
108 COMPRESS_LZMA : 'lzma',
109 COMPRESS_LZ4 : 'lz4',
110 }
111
112def find_arch(find_name):
113 """Look up an architecture name
114
115 Args:
116 find_name: Architecture name to find
117
118 Returns:
119 ARCHITECTURE_... value or None if not found
120 """
121 for arch, name in ARCH_NAMES.items():
122 if name == find_name:
123 return arch
124 return None
125
126def find_compress(find_name):
127 """Look up a compression algorithm name
128
129 Args:
130 find_name: Compression algorithm name to find
131
132 Returns:
133 COMPRESS_... value or None if not found
134 """
135 for compress, name in COMPRESS_NAMES.items():
136 if name == find_name:
137 return compress
138 return None
139
Simon Glass3a9c2522019-07-08 14:25:51 -0600140def compress_name(compress):
141 """Look up the name of a compression algorithm
142
143 Args:
144 compress: Compression algorithm number to find (COMPRESS_...)
145
146 Returns:
147 Compression algorithm name (string)
148
149 Raises:
150 KeyError if the algorithm number is invalid
151 """
152 return COMPRESS_NAMES[compress]
153
Simon Glass4997a7e2019-07-08 13:18:52 -0600154def align_int(val, align):
155 """Align a value up to the given alignment
156
157 Args:
158 val: Integer value to align
159 align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
160
161 Returns:
162 integer value aligned to the required boundary, rounding up if necessary
163 """
164 return int((val + align - 1) / align) * align
165
Simon Glass7c173ce2019-07-08 13:18:55 -0600166def align_int_down(val, align):
167 """Align a value down to the given alignment
168
169 Args:
170 val: Integer value to align
171 align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
172
173 Returns:
174 integer value aligned to the required boundary, rounding down if
175 necessary
176 """
177 return int(val / align) * align
178
Simon Glass4997a7e2019-07-08 13:18:52 -0600179def _pack_string(instr):
180 """Pack a string to the required aligned size by adding padding
181
182 Args:
183 instr: String to process
184
185 Returns:
186 String with required padding (at least one 0x00 byte) at the end
187 """
Simon Glassc1aa66e2022-01-29 14:14:04 -0700188 val = tools.to_bytes(instr)
Simon Glassab326012023-10-14 14:40:28 -0600189 pad_len = align_int(len(val) + 1, ATTRIBUTE_ALIGN)
Simon Glassc1aa66e2022-01-29 14:14:04 -0700190 return val + tools.get_bytes(0, pad_len - len(val))
Simon Glass4997a7e2019-07-08 13:18:52 -0600191
192
193class CbfsFile(object):
194 """Class to represent a single CBFS file
195
196 This is used to hold the information about a file, including its contents.
Simon Glass1223db02019-07-08 14:25:39 -0600197 Use the get_data_and_offset() method to obtain the raw output for writing to
198 CBFS.
Simon Glass4997a7e2019-07-08 13:18:52 -0600199
200 Properties:
201 name: Name of file
202 offset: Offset of file data from start of file header
Simon Glasse073d4e2019-07-08 13:18:56 -0600203 cbfs_offset: Offset of file data in bytes from start of CBFS, or None to
204 place this file anyway
Simon Glass4997a7e2019-07-08 13:18:52 -0600205 data: Contents of file, uncompressed
Simon Glasseb0f4a42019-07-20 12:24:06 -0600206 orig_data: Original data added to the file, possibly compressed
Simon Glass4997a7e2019-07-08 13:18:52 -0600207 data_len: Length of (possibly compressed) data in bytes
208 ftype: File type (TYPE_...)
209 compression: Compression type (COMPRESS_...)
Simon Glass52107ee2019-07-08 14:25:40 -0600210 memlen: Length of data in memory, i.e. the uncompressed length, None if
211 no compression algortihm is selected
Simon Glass4997a7e2019-07-08 13:18:52 -0600212 load: Load address in memory if known, else None
213 entry: Entry address in memory if known, else None. This is where
214 execution starts after the file is loaded
215 base_address: Base address to use for 'stage' files
Simon Glass7c173ce2019-07-08 13:18:55 -0600216 erase_byte: Erase byte to use for padding between the file header and
217 contents (used for empty files)
218 size: Size of the file in bytes (used for empty files)
Simon Glass4997a7e2019-07-08 13:18:52 -0600219 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600220 def __init__(self, name, ftype, data, cbfs_offset, compress=COMPRESS_NONE):
Simon Glass4997a7e2019-07-08 13:18:52 -0600221 self.name = name
222 self.offset = None
Simon Glasse073d4e2019-07-08 13:18:56 -0600223 self.cbfs_offset = cbfs_offset
Simon Glass4997a7e2019-07-08 13:18:52 -0600224 self.data = data
Simon Glasseb0f4a42019-07-20 12:24:06 -0600225 self.orig_data = data
Simon Glass4997a7e2019-07-08 13:18:52 -0600226 self.ftype = ftype
227 self.compress = compress
Simon Glass52107ee2019-07-08 14:25:40 -0600228 self.memlen = None
Simon Glass4997a7e2019-07-08 13:18:52 -0600229 self.load = None
230 self.entry = None
231 self.base_address = None
Simon Glass52107ee2019-07-08 14:25:40 -0600232 self.data_len = len(data)
Simon Glass7c173ce2019-07-08 13:18:55 -0600233 self.erase_byte = None
234 self.size = None
Stefan Herbrechtsmeieredafeb82022-08-19 16:25:29 +0200235 if self.compress == COMPRESS_LZ4:
236 self.comp_bintool = bintool.Bintool.create('lz4')
237 elif self.compress == COMPRESS_LZMA:
238 self.comp_bintool = bintool.Bintool.create('lzma_alone')
239 else:
240 self.comp_bintool = None
Simon Glass4997a7e2019-07-08 13:18:52 -0600241
242 def decompress(self):
243 """Handle decompressing data if necessary"""
244 indata = self.data
Stefan Herbrechtsmeieredafeb82022-08-19 16:25:29 +0200245 if self.comp_bintool:
246 data = self.comp_bintool.decompress(indata)
Simon Glass4997a7e2019-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 Glasse073d4e2019-07-08 13:18:56 -0600254 def stage(cls, base_address, name, data, cbfs_offset):
Simon Glass4997a7e2019-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 Glasse073d4e2019-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 Glass4997a7e2019-07-08 13:18:52 -0600264
265 Returns:
266 CbfsFile object containing the file information
267 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600268 cfile = CbfsFile(name, TYPE_STAGE, data, cbfs_offset)
Simon Glass4997a7e2019-07-08 13:18:52 -0600269 cfile.base_address = base_address
270 return cfile
271
272 @classmethod
Simon Glasse073d4e2019-07-08 13:18:56 -0600273 def raw(cls, name, data, cbfs_offset, compress):
Simon Glass4997a7e2019-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 Glasse073d4e2019-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 Glass4997a7e2019-07-08 13:18:52 -0600282 compress: Compression algorithm to use (COMPRESS_...)
283
284 Returns:
285 CbfsFile object containing the file information
286 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600287 return CbfsFile(name, TYPE_RAW, data, cbfs_offset, compress)
Simon Glass4997a7e2019-07-08 13:18:52 -0600288
Simon Glass7c173ce2019-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 Glasse073d4e2019-07-08 13:18:56 -0600302 cfile = CbfsFile('', TYPE_EMPTY, b'', None)
Simon Glassab326012023-10-14 14:40:28 -0600303 cfile.size = space_to_use - FILE_HEADER_LEN - ATTRIBUTE_ALIGN
Simon Glass7c173ce2019-07-08 13:18:55 -0600304 cfile.erase_byte = erase_byte
305 return cfile
306
Simon Glasse073d4e2019-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:
Simon Glassbd132552023-10-14 14:40:26 -0600332 if self.compress:
333 hdr_len += ATTR_COMPRESSION_LEN
Simon Glasse073d4e2019-07-08 13:18:56 -0600334 elif self.ftype == TYPE_EMPTY:
335 pass
336 else:
337 raise ValueError('Unknown file type %#x\n' % self.ftype)
338 return hdr_len
339
Simon Glass1223db02019-07-08 14:25:39 -0600340 def get_data_and_offset(self, offset=None, pad_byte=None):
341 """Obtain the contents of the file, in CBFS format and the offset of
342 the data within the file
Simon Glass4997a7e2019-07-08 13:18:52 -0600343
344 Returns:
Simon Glass1223db02019-07-08 14:25:39 -0600345 tuple:
346 bytes representing the contents of this file, packed and aligned
347 for directly inserting into the final CBFS output
348 offset to the file data from the start of the returned data.
Simon Glass4997a7e2019-07-08 13:18:52 -0600349 """
350 name = _pack_string(self.name)
351 hdr_len = len(name) + FILE_HEADER_LEN
352 attr_pos = 0
353 content = b''
354 attr = b''
Simon Glasse073d4e2019-07-08 13:18:56 -0600355 pad = b''
Simon Glass4997a7e2019-07-08 13:18:52 -0600356 data = self.data
357 if self.ftype == TYPE_STAGE:
358 elf_data = elf.DecodeElf(data, self.base_address)
359 content = struct.pack(STAGE_FORMAT, self.compress,
360 elf_data.entry, elf_data.load,
361 len(elf_data.data), elf_data.memsize)
362 data = elf_data.data
363 elif self.ftype == TYPE_RAW:
364 orig_data = data
Stefan Herbrechtsmeieredafeb82022-08-19 16:25:29 +0200365 if self.comp_bintool:
366 data = self.comp_bintool.compress(orig_data)
Simon Glass52107ee2019-07-08 14:25:40 -0600367 self.memlen = len(orig_data)
368 self.data_len = len(data)
Simon Glassbd132552023-10-14 14:40:26 -0600369 if self.compress:
370 attr = struct.pack(ATTR_COMPRESSION_FORMAT,
371 FILE_ATTR_TAG_COMPRESSION,
372 ATTR_COMPRESSION_LEN, self.compress,
373 self.memlen)
Simon Glass7c173ce2019-07-08 13:18:55 -0600374 elif self.ftype == TYPE_EMPTY:
Simon Glassc1aa66e2022-01-29 14:14:04 -0700375 data = tools.get_bytes(self.erase_byte, self.size)
Simon Glass4997a7e2019-07-08 13:18:52 -0600376 else:
377 raise ValueError('Unknown type %#x when writing\n' % self.ftype)
378 if attr:
379 attr_pos = hdr_len
380 hdr_len += len(attr)
Simon Glasse073d4e2019-07-08 13:18:56 -0600381 if self.cbfs_offset is not None:
382 pad_len = self.cbfs_offset - offset - hdr_len
383 if pad_len < 0: # pragma: no cover
384 # Test coverage of this is not available since this should never
385 # happen. It indicates that get_header_len() provided an
386 # incorrect value (too small) so that we decided that we could
387 # put this file at the requested place, but in fact a previous
388 # file extends far enough into the CBFS that this is not
389 # possible.
390 raise ValueError("Internal error: CBFS file '%s': Requested offset %#x but current output position is %#x" %
391 (self.name, self.cbfs_offset, offset))
Simon Glassc1aa66e2022-01-29 14:14:04 -0700392 pad = tools.get_bytes(pad_byte, pad_len)
Simon Glasse9199a72023-10-14 14:40:27 -0600393 if attr_pos:
394 attr_pos += pad_len
Simon Glasse073d4e2019-07-08 13:18:56 -0600395 hdr_len += pad_len
Simon Glass1223db02019-07-08 14:25:39 -0600396
397 # This is the offset of the start of the file's data,
398 size = len(content) + len(data)
399 hdr = struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, size,
Simon Glass4997a7e2019-07-08 13:18:52 -0600400 self.ftype, attr_pos, hdr_len)
Simon Glasse073d4e2019-07-08 13:18:56 -0600401
402 # Do a sanity check of the get_header_len() function, to ensure that it
403 # stays in lockstep with this function
404 expected_len = self.get_header_len()
405 actual_len = len(hdr + name + attr)
406 if expected_len != actual_len: # pragma: no cover
407 # Test coverage of this is not available since this should never
408 # happen. It probably indicates that get_header_len() is broken.
Simon Glassbd132552023-10-14 14:40:26 -0600409 raise ValueError("Internal error: CBFS file '%s': Expected headers of %#x bytes, got %#x" %
Simon Glasse073d4e2019-07-08 13:18:56 -0600410 (self.name, expected_len, actual_len))
Simon Glasse9199a72023-10-14 14:40:27 -0600411 return hdr + name + pad + attr + content + data, hdr_len
Simon Glass4997a7e2019-07-08 13:18:52 -0600412
413
414class CbfsWriter(object):
415 """Class to handle writing a Coreboot File System (CBFS)
416
417 Usage is something like:
418
419 cbw = CbfsWriter(size)
Simon Glassc1aa66e2022-01-29 14:14:04 -0700420 cbw.add_file_raw('u-boot', tools.read_file('u-boot.bin'))
Simon Glass4997a7e2019-07-08 13:18:52 -0600421 ...
Simon Glass1223db02019-07-08 14:25:39 -0600422 data, cbfs_offset = cbw.get_data_and_offset()
Simon Glass4997a7e2019-07-08 13:18:52 -0600423
424 Attributes:
425 _master_name: Name of the file containing the master header
426 _size: Size of the filesystem, in bytes
427 _files: Ordered list of files in the CBFS, each a CbfsFile
428 _arch: Architecture of the CBFS (ARCHITECTURE_...)
429 _bootblock_size: Size of the bootblock, typically at the end of the CBFS
430 _erase_byte: Byte to use for empty space in the CBFS
431 _align: Alignment to use for files, typically ENTRY_ALIGN
432 _base_address: Boot block offset in bytes from the start of CBFS.
433 Typically this is located at top of the CBFS. It is 0 when there is
434 no boot block
435 _header_offset: Offset of master header in bytes from start of CBFS
436 _contents_offset: Offset of first file header
437 _hdr_at_start: True if the master header is at the start of the CBFS,
438 instead of the end as normal for x86
439 _add_fileheader: True to add a fileheader around the master header
440 """
441 def __init__(self, size, arch=ARCHITECTURE_X86):
442 """Set up a new CBFS
443
444 This sets up all properties to default values. Files can be added using
445 add_file_raw(), etc.
446
447 Args:
448 size: Size of CBFS in bytes
449 arch: Architecture to declare for CBFS
450 """
451 self._master_name = 'cbfs master header'
452 self._size = size
453 self._files = OrderedDict()
454 self._arch = arch
455 self._bootblock_size = 0
456 self._erase_byte = 0xff
Simon Glasse9199a72023-10-14 14:40:27 -0600457
458 # Small padding to align a file uses 0
459 self._small_pad_byte = 0
Simon Glass4997a7e2019-07-08 13:18:52 -0600460 self._align = ENTRY_ALIGN
461 self._add_fileheader = False
462 if self._arch == ARCHITECTURE_X86:
463 # Allow 4 bytes for the header pointer. That holds the
464 # twos-compliment negative offset of the master header in bytes
465 # measured from one byte past the end of the CBFS
466 self._base_address = self._size - max(self._bootblock_size,
467 MIN_BOOTBLOCK_SIZE)
468 self._header_offset = self._base_address - HEADER_LEN
469 self._contents_offset = 0
470 self._hdr_at_start = False
471 else:
472 # For non-x86, different rules apply
473 self._base_address = 0
474 self._header_offset = align_int(self._base_address +
475 self._bootblock_size, 4)
476 self._contents_offset = align_int(self._header_offset +
477 FILE_HEADER_LEN +
478 self._bootblock_size, self._align)
479 self._hdr_at_start = True
480
Simon Glasse9199a72023-10-14 14:40:27 -0600481 def _skip_to(self, fd, offset, pad_byte):
Simon Glass4997a7e2019-07-08 13:18:52 -0600482 """Write out pad bytes until a given offset
483
484 Args:
485 fd: File objext to write to
486 offset: Offset to write to
487 """
488 if fd.tell() > offset:
489 raise ValueError('No space for data before offset %#x (current offset %#x)' %
490 (offset, fd.tell()))
Simon Glasse9199a72023-10-14 14:40:27 -0600491 fd.write(tools.get_bytes(pad_byte, offset - fd.tell()))
Simon Glass4997a7e2019-07-08 13:18:52 -0600492
Simon Glasse9199a72023-10-14 14:40:27 -0600493 def _pad_to(self, fd, offset, pad_byte):
Simon Glass7c173ce2019-07-08 13:18:55 -0600494 """Write out pad bytes and/or an empty file until a given offset
495
496 Args:
497 fd: File objext to write to
498 offset: Offset to write to
499 """
Simon Glasse9199a72023-10-14 14:40:27 -0600500 self._align_to(fd, self._align, pad_byte)
Simon Glass7c173ce2019-07-08 13:18:55 -0600501 upto = fd.tell()
502 if upto > offset:
503 raise ValueError('No space for data before pad offset %#x (current offset %#x)' %
504 (offset, upto))
505 todo = align_int_down(offset - upto, self._align)
506 if todo:
507 cbf = CbfsFile.empty(todo, self._erase_byte)
Simon Glass1223db02019-07-08 14:25:39 -0600508 fd.write(cbf.get_data_and_offset()[0])
Simon Glasse9199a72023-10-14 14:40:27 -0600509 self._skip_to(fd, offset, pad_byte)
Simon Glass7c173ce2019-07-08 13:18:55 -0600510
Simon Glasse9199a72023-10-14 14:40:27 -0600511 def _align_to(self, fd, align, pad_byte):
Simon Glass4997a7e2019-07-08 13:18:52 -0600512 """Write out pad bytes until a given alignment is reached
513
514 This only aligns if the resulting output would not reach the end of the
515 CBFS, since we want to leave the last 4 bytes for the master-header
516 pointer.
517
518 Args:
519 fd: File objext to write to
520 align: Alignment to require (e.g. 4 means pad to next 4-byte
521 boundary)
522 """
523 offset = align_int(fd.tell(), align)
524 if offset < self._size:
Simon Glasse9199a72023-10-14 14:40:27 -0600525 self._skip_to(fd, offset, pad_byte)
Simon Glass4997a7e2019-07-08 13:18:52 -0600526
Simon Glasse073d4e2019-07-08 13:18:56 -0600527 def add_file_stage(self, name, data, cbfs_offset=None):
Simon Glass4997a7e2019-07-08 13:18:52 -0600528 """Add a new stage file to the CBFS
529
530 Args:
531 name: String file name to put in CBFS (does not need to correspond
532 to the name that the file originally came from)
533 data: Contents of file
Simon Glasse073d4e2019-07-08 13:18:56 -0600534 cbfs_offset: Offset of this file's data within the CBFS, in bytes,
535 or None to place this file anywhere
Simon Glass4997a7e2019-07-08 13:18:52 -0600536
537 Returns:
538 CbfsFile object created
539 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600540 cfile = CbfsFile.stage(self._base_address, name, data, cbfs_offset)
Simon Glass4997a7e2019-07-08 13:18:52 -0600541 self._files[name] = cfile
542 return cfile
543
Simon Glasse073d4e2019-07-08 13:18:56 -0600544 def add_file_raw(self, name, data, cbfs_offset=None,
545 compress=COMPRESS_NONE):
Simon Glass4997a7e2019-07-08 13:18:52 -0600546 """Create a new raw file
547
548 Args:
549 name: String file name to put in CBFS (does not need to correspond
550 to the name that the file originally came from)
551 data: Contents of file
Simon Glasse073d4e2019-07-08 13:18:56 -0600552 cbfs_offset: Offset of this file's data within the CBFS, in bytes,
553 or None to place this file anywhere
Simon Glass4997a7e2019-07-08 13:18:52 -0600554 compress: Compression algorithm to use (COMPRESS_...)
555
556 Returns:
557 CbfsFile object created
558 """
Simon Glasse073d4e2019-07-08 13:18:56 -0600559 cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
Simon Glass4997a7e2019-07-08 13:18:52 -0600560 self._files[name] = cfile
561 return cfile
562
563 def _write_header(self, fd, add_fileheader):
564 """Write out the master header to a CBFS
565
566 Args:
567 fd: File object
568 add_fileheader: True to place the master header in a file header
569 record
570 """
571 if fd.tell() > self._header_offset:
572 raise ValueError('No space for header at offset %#x (current offset %#x)' %
573 (self._header_offset, fd.tell()))
574 if not add_fileheader:
Simon Glasse9199a72023-10-14 14:40:27 -0600575 self._pad_to(fd, self._header_offset, self._erase_byte)
Simon Glass4997a7e2019-07-08 13:18:52 -0600576 hdr = struct.pack(HEADER_FORMAT, HEADER_MAGIC, HEADER_VERSION2,
577 self._size, self._bootblock_size, self._align,
578 self._contents_offset, self._arch, 0xffffffff)
579 if add_fileheader:
580 name = _pack_string(self._master_name)
581 fd.write(struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, len(hdr),
582 TYPE_CBFSHEADER, 0,
583 FILE_HEADER_LEN + len(name)))
584 fd.write(name)
585 self._header_offset = fd.tell()
586 fd.write(hdr)
Simon Glasse9199a72023-10-14 14:40:27 -0600587 self._align_to(fd, self._align, self._erase_byte)
Simon Glass4997a7e2019-07-08 13:18:52 -0600588 else:
589 fd.write(hdr)
590
591 def get_data(self):
592 """Obtain the full contents of the CBFS
593
594 Thhis builds the CBFS with headers and all required files.
595
596 Returns:
597 'bytes' type containing the data
598 """
599 fd = io.BytesIO()
600
601 # THe header can go at the start in some cases
602 if self._hdr_at_start:
603 self._write_header(fd, add_fileheader=self._add_fileheader)
Simon Glasse9199a72023-10-14 14:40:27 -0600604 self._skip_to(fd, self._contents_offset, self._erase_byte)
Simon Glass4997a7e2019-07-08 13:18:52 -0600605
606 # Write out each file
607 for cbf in self._files.values():
Simon Glasse073d4e2019-07-08 13:18:56 -0600608 # Place the file at its requested place, if any
609 offset = cbf.calc_start_offset()
610 if offset is not None:
Simon Glasse9199a72023-10-14 14:40:27 -0600611 self._pad_to(fd, align_int_down(offset, self._align),
612 self._erase_byte)
Simon Glass1223db02019-07-08 14:25:39 -0600613 pos = fd.tell()
Simon Glasse9199a72023-10-14 14:40:27 -0600614 data, data_offset = cbf.get_data_and_offset(pos,
615 self._small_pad_byte)
Simon Glass1223db02019-07-08 14:25:39 -0600616 fd.write(data)
Simon Glasse9199a72023-10-14 14:40:27 -0600617 self._align_to(fd, self._align, self._erase_byte)
Simon Glass1223db02019-07-08 14:25:39 -0600618 cbf.calced_cbfs_offset = pos + data_offset
Simon Glass4997a7e2019-07-08 13:18:52 -0600619 if not self._hdr_at_start:
620 self._write_header(fd, add_fileheader=self._add_fileheader)
621
622 # Pad to the end and write a pointer to the CBFS master header
Simon Glasse9199a72023-10-14 14:40:27 -0600623 self._pad_to(fd, self._base_address or self._size - 4, self._erase_byte)
Simon Glass4997a7e2019-07-08 13:18:52 -0600624 rel_offset = self._header_offset - self._size
625 fd.write(struct.pack('<I', rel_offset & 0xffffffff))
626
627 return fd.getvalue()
628
629
630class CbfsReader(object):
631 """Class to handle reading a Coreboot File System (CBFS)
632
633 Usage is something like:
634 cbfs = cbfs_util.CbfsReader(data)
635 cfile = cbfs.files['u-boot']
636 self.WriteFile('u-boot.bin', cfile.data)
637
638 Attributes:
639 files: Ordered list of CbfsFile objects
640 align: Alignment to use for files, typically ENTRT_ALIGN
641 stage_base_address: Base address to use when mapping ELF files into the
642 CBFS for TYPE_STAGE files. If this is larger than the code address
643 of the ELF file, then data at the start of the ELF file will not
644 appear in the CBFS. Currently there are no tests for behaviour as
645 documentation is sparse
646 magic: Integer magic number from master header (HEADER_MAGIC)
647 version: Version number of CBFS (HEADER_VERSION2)
648 rom_size: Size of CBFS
649 boot_block_size: Size of boot block
650 cbfs_offset: Offset of the first file in bytes from start of CBFS
651 arch: Architecture of CBFS file (ARCHITECTURE_...)
652 """
653 def __init__(self, data, read=True):
654 self.align = ENTRY_ALIGN
655 self.arch = None
656 self.boot_block_size = None
657 self.cbfs_offset = None
658 self.files = OrderedDict()
659 self.magic = None
660 self.rom_size = None
661 self.stage_base_address = 0
662 self.version = None
663 self.data = data
664 if read:
665 self.read()
666
667 def read(self):
668 """Read all the files in the CBFS and add them to self.files"""
669 with io.BytesIO(self.data) as fd:
670 # First, get the master header
671 if not self._find_and_read_header(fd, len(self.data)):
672 raise ValueError('Cannot find master header')
673 fd.seek(self.cbfs_offset)
674
675 # Now read in the files one at a time
676 while True:
677 cfile = self._read_next_file(fd)
678 if cfile:
679 self.files[cfile.name] = cfile
680 elif cfile is False:
681 break
682
683 def _find_and_read_header(self, fd, size):
684 """Find and read the master header in the CBFS
685
686 This looks at the pointer word at the very end of the CBFS. This is an
687 offset to the header relative to the size of the CBFS, which is assumed
688 to be known. Note that the offset is in *little endian* format.
689
690 Args:
691 fd: File to read from
692 size: Size of file
693
694 Returns:
695 True if header was found, False if not
696 """
697 orig_pos = fd.tell()
698 fd.seek(size - 4)
699 rel_offset, = struct.unpack('<I', fd.read(4))
700 pos = (size + rel_offset) & 0xffffffff
701 fd.seek(pos)
702 found = self._read_header(fd)
703 if not found:
704 print('Relative offset seems wrong, scanning whole image')
705 for pos in range(0, size - HEADER_LEN, 4):
706 fd.seek(pos)
707 found = self._read_header(fd)
708 if found:
709 break
710 fd.seek(orig_pos)
711 return found
712
713 def _read_next_file(self, fd):
714 """Read the next file from a CBFS
715
716 Args:
717 fd: File to read from
718
719 Returns:
720 CbfsFile object, if found
721 None if no object found, but data was parsed (e.g. TYPE_CBFSHEADER)
722 False if at end of CBFS and reading should stop
723 """
724 file_pos = fd.tell()
725 data = fd.read(FILE_HEADER_LEN)
726 if len(data) < FILE_HEADER_LEN:
Simon Glass17a74212019-07-20 12:24:03 -0600727 print('File header at %#x ran out of data' % file_pos)
Simon Glass4997a7e2019-07-08 13:18:52 -0600728 return False
729 magic, size, ftype, attr, offset = struct.unpack(FILE_HEADER_FORMAT,
730 data)
731 if magic != FILE_MAGIC:
732 return False
733 pos = fd.tell()
734 name = self._read_string(fd)
735 if name is None:
Simon Glass17a74212019-07-20 12:24:03 -0600736 print('String at %#x ran out of data' % pos)
Simon Glass4997a7e2019-07-08 13:18:52 -0600737 return False
738
739 if DEBUG:
740 print('name', name)
741
742 # If there are attribute headers present, read those
743 compress = self._read_attr(fd, file_pos, attr, offset)
744 if compress is None:
745 return False
746
747 # Create the correct CbfsFile object depending on the type
748 cfile = None
Simon Glasse073d4e2019-07-08 13:18:56 -0600749 cbfs_offset = file_pos + offset
750 fd.seek(cbfs_offset, io.SEEK_SET)
Simon Glass4997a7e2019-07-08 13:18:52 -0600751 if ftype == TYPE_CBFSHEADER:
752 self._read_header(fd)
753 elif ftype == TYPE_STAGE:
754 data = fd.read(STAGE_LEN)
Simon Glasse073d4e2019-07-08 13:18:56 -0600755 cfile = CbfsFile.stage(self.stage_base_address, name, b'',
756 cbfs_offset)
Simon Glass4997a7e2019-07-08 13:18:52 -0600757 (cfile.compress, cfile.entry, cfile.load, cfile.data_len,
758 cfile.memlen) = struct.unpack(STAGE_FORMAT, data)
759 cfile.data = fd.read(cfile.data_len)
760 elif ftype == TYPE_RAW:
761 data = fd.read(size)
Simon Glasse073d4e2019-07-08 13:18:56 -0600762 cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
Simon Glass4997a7e2019-07-08 13:18:52 -0600763 cfile.decompress()
764 if DEBUG:
765 print('data', data)
Simon Glass7c173ce2019-07-08 13:18:55 -0600766 elif ftype == TYPE_EMPTY:
767 # Just read the data and discard it, since it is only padding
768 fd.read(size)
Simon Glasse073d4e2019-07-08 13:18:56 -0600769 cfile = CbfsFile('', TYPE_EMPTY, b'', cbfs_offset)
Simon Glass4997a7e2019-07-08 13:18:52 -0600770 else:
771 raise ValueError('Unknown type %#x when reading\n' % ftype)
772 if cfile:
773 cfile.offset = offset
774
775 # Move past the padding to the start of a possible next file. If we are
776 # already at an alignment boundary, then there is no padding.
777 pad = (self.align - fd.tell() % self.align) % self.align
778 fd.seek(pad, io.SEEK_CUR)
779 return cfile
780
781 @classmethod
782 def _read_attr(cls, fd, file_pos, attr, offset):
783 """Read attributes from the file
784
785 CBFS files can have attributes which are things that cannot fit into the
Simon Glasse073d4e2019-07-08 13:18:56 -0600786 header. The only attributes currently supported are compression and the
787 unused tag.
Simon Glass4997a7e2019-07-08 13:18:52 -0600788
789 Args:
790 fd: File to read from
791 file_pos: Position of file in fd
792 attr: Offset of attributes, 0 if none
793 offset: Offset of file data (used to indicate the end of the
794 attributes)
795
796 Returns:
797 Compression to use for the file (COMPRESS_...)
798 """
799 compress = COMPRESS_NONE
800 if not attr:
801 return compress
802 attr_size = offset - attr
803 fd.seek(file_pos + attr, io.SEEK_SET)
804 while attr_size:
805 pos = fd.tell()
806 hdr = fd.read(8)
807 if len(hdr) < 8:
808 print('Attribute tag at %x ran out of data' % pos)
809 return None
810 atag, alen = struct.unpack(">II", hdr)
811 data = hdr + fd.read(alen - 8)
812 if atag == FILE_ATTR_TAG_COMPRESSION:
813 # We don't currently use this information
814 atag, alen, compress, _decomp_size = struct.unpack(
815 ATTR_COMPRESSION_FORMAT, data)
816 else:
817 print('Unknown attribute tag %x' % atag)
818 attr_size -= len(data)
819 return compress
820
821 def _read_header(self, fd):
822 """Read the master header
823
824 Reads the header and stores the information obtained into the member
825 variables.
826
827 Args:
828 fd: File to read from
829
830 Returns:
831 True if header was read OK, False if it is truncated or has the
832 wrong magic or version
833 """
834 pos = fd.tell()
835 data = fd.read(HEADER_LEN)
836 if len(data) < HEADER_LEN:
837 print('Header at %x ran out of data' % pos)
838 return False
839 (self.magic, self.version, self.rom_size, self.boot_block_size,
840 self.align, self.cbfs_offset, self.arch, _) = struct.unpack(
841 HEADER_FORMAT, data)
842 return self.magic == HEADER_MAGIC and (
843 self.version == HEADER_VERSION1 or
844 self.version == HEADER_VERSION2)
845
846 @classmethod
847 def _read_string(cls, fd):
848 """Read a string from a file
849
Simon Glasse9199a72023-10-14 14:40:27 -0600850 This reads a string and aligns the data to the next alignment boundary.
851 The string must be nul-terminated
Simon Glass4997a7e2019-07-08 13:18:52 -0600852
853 Args:
854 fd: File to read from
855
856 Returns:
857 string read ('str' type) encoded to UTF-8, or None if we ran out of
858 data
859 """
860 val = b''
861 while True:
Simon Glassab326012023-10-14 14:40:28 -0600862 data = fd.read(ATTRIBUTE_ALIGN)
863 if len(data) < ATTRIBUTE_ALIGN:
Simon Glass4997a7e2019-07-08 13:18:52 -0600864 return None
865 pos = data.find(b'\0')
866 if pos == -1:
867 val += data
868 else:
869 val += data[:pos]
870 break
871 return val.decode('utf-8')