filename = "u-boot.dtb";
cbfs-type = "raw";
cbfs-compress = "lz4";
+ cbfs-offset = <0x100000>;
};
};
to add a flat binary with a load/start address, similar to the
'add-flat-binary' option in cbfstool.
+cbfs-offset:
+ This is the offset of the file's data within the CBFS. It is used to
+ specify where the file should be placed in cases where a fixed position
+ is needed. Typical uses are for code which is not relocatable and must
+ execute in-place from a particular address. This works because SPI flash
+ is generally mapped into memory on x86 devices. The file header is
+ placed before this offset so that the data start lines up exactly with
+ the chosen offset. If this property is not provided, then the file is
+ placed in the next available spot.
The current implementation supports only a subset of CBFS features. It does
not support other file types (e.g. payload), adding multiple files (like the
+Entry: intel-ifwi: Entry containing an Intel Integrated Firmware Image (IFWI) file
+----------------------------------------------------------------------------------
+
+Properties / Entry arguments:
+ - filename: Filename of file to read into entry. This is either the
+ IFWI file itself, or a file that can be converted into one using a
+ tool
+ - convert-fit: If present this indicates that the ifwitool should be
+ used to convert the provided file into a IFWI.
+
+This file contains code and data used by the SoC that is required to make
+it work. It includes U-Boot TPL, microcode, things related to the CSE
+(Converged Security Engine, the microcontroller that loads all the firmware)
+and other items beyond the wit of man.
+
+A typical filename is 'ifwi.bin' for an IFWI file, or 'fitimage.bin' for a
+file that will be converted to an IFWI.
+
+The position of this entry is generally set by the intel-descriptor entry.
+
+The contents of the IFWI are specified by the subnodes of the IFWI node.
+Each subnode describes an entry which is placed into the IFWFI with a given
+sub-partition (and optional entry name).
+
+See README.x86 for information about x86 binary blobs.
+
+
+
Entry: intel-me: Entry containing an Intel Management Engine (ME) file
----------------------------------------------------------------------
to fully understand it.
Currently supported: raw and stage types with compression, padding empty areas
- with empty files
+ with empty files, fixed-offset files
"""
from __future__ import print_function
Properties:
name: Name of file
offset: Offset of file data from start of file header
+ cbfs_offset: Offset of file data in bytes from start of CBFS, or None to
+ place this file anyway
data: Contents of file, uncompressed
data_len: Length of (possibly compressed) data in bytes
ftype: File type (TYPE_...)
contents (used for empty files)
size: Size of the file in bytes (used for empty files)
"""
- def __init__(self, name, ftype, data, compress=COMPRESS_NONE):
+ def __init__(self, name, ftype, data, cbfs_offset, compress=COMPRESS_NONE):
self.name = name
self.offset = None
+ self.cbfs_offset = cbfs_offset
self.data = data
self.ftype = ftype
self.compress = compress
self.data_len = len(indata)
@classmethod
- def stage(cls, base_address, name, data):
+ def stage(cls, base_address, name, data, cbfs_offset):
"""Create a new stage file
Args:
name: String file name to put in CBFS (does not need to correspond
to the name that the file originally came from)
data: Contents of file
+ cbfs_offset: Offset of file data in bytes from start of CBFS, or
+ None to place this file anyway
Returns:
CbfsFile object containing the file information
"""
- cfile = CbfsFile(name, TYPE_STAGE, data)
+ cfile = CbfsFile(name, TYPE_STAGE, data, cbfs_offset)
cfile.base_address = base_address
return cfile
@classmethod
- def raw(cls, name, data, compress):
+ def raw(cls, name, data, cbfs_offset, compress):
"""Create a new raw file
Args:
name: String file name to put in CBFS (does not need to correspond
to the name that the file originally came from)
data: Contents of file
+ cbfs_offset: Offset of file data in bytes from start of CBFS, or
+ None to place this file anyway
compress: Compression algorithm to use (COMPRESS_...)
Returns:
CbfsFile object containing the file information
"""
- return CbfsFile(name, TYPE_RAW, data, compress)
+ return CbfsFile(name, TYPE_RAW, data, cbfs_offset, compress)
@classmethod
def empty(cls, space_to_use, erase_byte):
Returns:
CbfsFile object containing the file information
"""
- cfile = CbfsFile('', TYPE_EMPTY, b'')
+ cfile = CbfsFile('', TYPE_EMPTY, b'', None)
cfile.size = space_to_use - FILE_HEADER_LEN - FILENAME_ALIGN
cfile.erase_byte = erase_byte
return cfile
- def get_data(self):
+ def calc_start_offset(self):
+ """Check if this file needs to start at a particular offset in CBFS
+
+ Returns:
+ None if the file can be placed anywhere, or
+ the largest offset where the file could start (integer)
+ """
+ if self.cbfs_offset is None:
+ return None
+ return self.cbfs_offset - self.get_header_len()
+
+ def get_header_len(self):
+ """Get the length of headers required for a file
+
+ This is the minimum length required before the actual data for this file
+ could start. It might start later if there is padding.
+
+ Returns:
+ Total length of all non-data fields, in bytes
+ """
+ name = _pack_string(self.name)
+ hdr_len = len(name) + FILE_HEADER_LEN
+ if self.ftype == TYPE_STAGE:
+ pass
+ elif self.ftype == TYPE_RAW:
+ hdr_len += ATTR_COMPRESSION_LEN
+ elif self.ftype == TYPE_EMPTY:
+ pass
+ else:
+ raise ValueError('Unknown file type %#x\n' % self.ftype)
+ return hdr_len
+
+ def get_data(self, offset=None, pad_byte=None):
"""Obtain the contents of the file, in CBFS format
Returns:
attr_pos = 0
content = b''
attr = b''
+ pad = b''
data = self.data
if self.ftype == TYPE_STAGE:
elf_data = elf.DecodeElf(data, self.base_address)
if attr:
attr_pos = hdr_len
hdr_len += len(attr)
- hdr = struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC,
- len(content) + len(data),
+ if self.cbfs_offset is not None:
+ pad_len = self.cbfs_offset - offset - hdr_len
+ if pad_len < 0: # pragma: no cover
+ # Test coverage of this is not available since this should never
+ # happen. It indicates that get_header_len() provided an
+ # incorrect value (too small) so that we decided that we could
+ # put this file at the requested place, but in fact a previous
+ # file extends far enough into the CBFS that this is not
+ # possible.
+ raise ValueError("Internal error: CBFS file '%s': Requested offset %#x but current output position is %#x" %
+ (self.name, self.cbfs_offset, offset))
+ pad = tools.GetBytes(pad_byte, pad_len)
+ hdr_len += pad_len
+ self.offset = len(content) + len(data)
+ hdr = struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, self.offset,
self.ftype, attr_pos, hdr_len)
- return hdr + name + attr + content + data
+
+ # Do a sanity check of the get_header_len() function, to ensure that it
+ # stays in lockstep with this function
+ expected_len = self.get_header_len()
+ actual_len = len(hdr + name + attr)
+ if expected_len != actual_len: # pragma: no cover
+ # Test coverage of this is not available since this should never
+ # happen. It probably indicates that get_header_len() is broken.
+ raise ValueError("Internal error: CBFS file '%s': Expected headers of %#x bytes, got %#d" %
+ (self.name, expected_len, actual_len))
+ return hdr + name + attr + pad + content + data
class CbfsWriter(object):
if offset < self._size:
self._skip_to(fd, offset)
- def add_file_stage(self, name, data):
+ def add_file_stage(self, name, data, cbfs_offset=None):
"""Add a new stage file to the CBFS
Args:
name: String file name to put in CBFS (does not need to correspond
to the name that the file originally came from)
data: Contents of file
+ cbfs_offset: Offset of this file's data within the CBFS, in bytes,
+ or None to place this file anywhere
Returns:
CbfsFile object created
"""
- cfile = CbfsFile.stage(self._base_address, name, data)
+ cfile = CbfsFile.stage(self._base_address, name, data, cbfs_offset)
self._files[name] = cfile
return cfile
- def add_file_raw(self, name, data, compress=COMPRESS_NONE):
+ def add_file_raw(self, name, data, cbfs_offset=None,
+ compress=COMPRESS_NONE):
"""Create a new raw file
Args:
name: String file name to put in CBFS (does not need to correspond
to the name that the file originally came from)
data: Contents of file
+ cbfs_offset: Offset of this file's data within the CBFS, in bytes,
+ or None to place this file anywhere
compress: Compression algorithm to use (COMPRESS_...)
Returns:
CbfsFile object created
"""
- cfile = CbfsFile.raw(name, data, compress)
+ cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
self._files[name] = cfile
return cfile
# Write out each file
for cbf in self._files.values():
- fd.write(cbf.get_data())
+ # Place the file at its requested place, if any
+ offset = cbf.calc_start_offset()
+ if offset is not None:
+ self._pad_to(fd, align_int_down(offset, self._align))
+ fd.write(cbf.get_data(fd.tell(), self._erase_byte))
self._align_to(fd, self._align)
if not self._hdr_at_start:
self._write_header(fd, add_fileheader=self._add_fileheader)
# Create the correct CbfsFile object depending on the type
cfile = None
- fd.seek(file_pos + offset, io.SEEK_SET)
+ cbfs_offset = file_pos + offset
+ fd.seek(cbfs_offset, io.SEEK_SET)
if ftype == TYPE_CBFSHEADER:
self._read_header(fd)
elif ftype == TYPE_STAGE:
data = fd.read(STAGE_LEN)
- cfile = CbfsFile.stage(self.stage_base_address, name, b'')
+ cfile = CbfsFile.stage(self.stage_base_address, name, b'',
+ cbfs_offset)
(cfile.compress, cfile.entry, cfile.load, cfile.data_len,
cfile.memlen) = struct.unpack(STAGE_FORMAT, data)
cfile.data = fd.read(cfile.data_len)
elif ftype == TYPE_RAW:
data = fd.read(size)
- cfile = CbfsFile.raw(name, data, compress)
+ cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
cfile.decompress()
if DEBUG:
print('data', data)
elif ftype == TYPE_EMPTY:
# Just read the data and discard it, since it is only padding
fd.read(size)
- cfile = CbfsFile('', TYPE_EMPTY, b'')
+ cfile = CbfsFile('', TYPE_EMPTY, b'', cbfs_offset)
else:
raise ValueError('Unknown type %#x when reading\n' % ftype)
if cfile:
"""Read attributes from the file
CBFS files can have attributes which are things that cannot fit into the
- header. The only attribute currently supported is compression.
+ header. The only attributes currently supported are compression and the
+ unused tag.
Args:
fd: File to read from
# We don't currently use this information
atag, alen, compress, _decomp_size = struct.unpack(
ATTR_COMPRESSION_FORMAT, data)
+ elif atag == FILE_ATTR_TAG_UNUSED2:
+ break
else:
print('Unknown attribute tag %x' % atag)
attr_size -= len(data)
return val.decode('utf-8')
-def cbfstool(fname, *cbfs_args):
+def cbfstool(fname, *cbfs_args, **kwargs):
"""Run cbfstool with provided arguments
If the tool fails then this function raises an exception and prints out the
Returns:
CommandResult object containing the results
"""
- args = ('cbfstool', fname) + cbfs_args
+ args = ['cbfstool', fname] + list(cbfs_args)
+ if kwargs.get('base') is not None:
+ args += ['-b', '%#x' % kwargs['base']]
result = command.RunPipe([args], capture=not VERBOSE,
capture_stderr=not VERBOSE, raise_on_error=False)
if result.return_code:
return cbfs
def _check_uboot(self, cbfs, ftype=cbfs_util.TYPE_RAW, offset=0x38,
- data=U_BOOT_DATA):
+ data=U_BOOT_DATA, cbfs_offset=None):
"""Check that the U-Boot file is as expected
Args:
ftype: Expected file type
offset: Expected offset of file
data: Expected data in file
+ cbfs_offset: Expected CBFS offset for file's data
Returns:
CbfsFile object containing the file
cfile = cbfs.files['u-boot']
self.assertEqual('u-boot', cfile.name)
self.assertEqual(offset, cfile.offset)
+ if cbfs_offset is not None:
+ self.assertEqual(cbfs_offset, cfile.cbfs_offset)
self.assertEqual(data, cfile.data)
self.assertEqual(ftype, cfile.ftype)
self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress)
self.assertEqual(len(data), cfile.memlen)
return cfile
- def _check_dtb(self, cbfs, offset=0x38, data=U_BOOT_DTB_DATA):
+ def _check_dtb(self, cbfs, offset=0x38, data=U_BOOT_DTB_DATA,
+ cbfs_offset=None):
"""Check that the U-Boot dtb file is as expected
Args:
cbfs: CbfsReader object to check
offset: Expected offset of file
data: Expected data in file
+ cbfs_offset: Expected CBFS offset for file's data
"""
self.assertIn('u-boot-dtb', cbfs.files)
cfile = cbfs.files['u-boot-dtb']
self.assertEqual('u-boot-dtb', cfile.name)
self.assertEqual(offset, cfile.offset)
+ if cbfs_offset is not None:
+ self.assertEqual(cbfs_offset, cfile.cbfs_offset)
self.assertEqual(U_BOOT_DTB_DATA, cfile.data)
self.assertEqual(cbfs_util.TYPE_RAW, cfile.ftype)
self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress)
self._check_uboot(cbfs)
self._check_dtb(cbfs)
- def _get_expected_cbfs(self, size, arch='x86', compress=None):
+ def _get_expected_cbfs(self, size, arch='x86', compress=None, base=None):
"""Get the file created by cbfstool for a particular scenario
Args:
size: Size of the CBFS in bytes
arch: Architecture of the CBFS, as a string
compress: Compression to use, e.g. cbfs_util.COMPRESS_LZMA
+ base: Base address of file, or None to put it anywhere
Returns:
Resulting CBFS file, or None if cbfstool is not available
return None
cbfs_fname = os.path.join(self._indir, 'test.cbfs')
cbfs_util.cbfstool(cbfs_fname, 'create', '-m', arch, '-s', '%#x' % size)
+ if base:
+ base = [(1 << 32) - size + b for b in base]
cbfs_util.cbfstool(cbfs_fname, 'add', '-n', 'u-boot', '-t', 'raw',
'-c', compress and compress[0] or 'none',
'-f', tools.GetInputFilename(
- compress and 'compress' or 'u-boot.bin'))
+ compress and 'compress' or 'u-boot.bin'),
+ base=base[0] if base else None)
cbfs_util.cbfstool(cbfs_fname, 'add', '-n', 'u-boot-dtb', '-t', 'raw',
'-c', compress and compress[1] or 'none',
'-f', tools.GetInputFilename(
- compress and 'compress' or 'u-boot.dtb'))
+ compress and 'compress' or 'u-boot.dtb'),
+ base=base[1] if base else None)
return cbfs_fname
def _compare_expected_cbfs(self, data, cbfstool_fname):
self.skipTest('lz4 --no-frame-crc not available')
size = 0x140
cbw = CbfsWriter(size)
- cbw.add_file_raw('u-boot', COMPRESS_DATA,
+ cbw.add_file_raw('u-boot', COMPRESS_DATA, None,
compress=cbfs_util.COMPRESS_LZ4)
data = cbw.get_data()
self.skipTest('lz4 --no-frame-crc not available')
size = 0x140
cbw = CbfsWriter(size)
- cbw.add_file_raw('u-boot', COMPRESS_DATA,
+ cbw.add_file_raw('u-boot', COMPRESS_DATA, None,
compress=cbfs_util.COMPRESS_LZ4)
data = cbw.get_data()
self.skipTest('lz4 --no-frame-crc not available')
size = 0x140
cbw = CbfsWriter(size)
- cbw.add_file_raw('u-boot', COMPRESS_DATA,
+ cbw.add_file_raw('u-boot', COMPRESS_DATA, None,
compress=cbfs_util.COMPRESS_LZ4)
- cbw.add_file_raw('u-boot-dtb', COMPRESS_DATA,
+ cbw.add_file_raw('u-boot-dtb', COMPRESS_DATA, None,
compress=cbfs_util.COMPRESS_LZMA)
data = cbw.get_data()
cbfs_fname = self._get_expected_cbfs(size=size)
self._compare_expected_cbfs(data, cbfs_fname)
+ def test_cbfs_offset(self):
+ """Test a CBFS with files at particular offsets"""
+ size = 0x200
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA, 0x40)
+ cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA, 0x140)
+
+ data = cbw.get_data()
+ cbfs = self._check_hdr(data, size)
+ self._check_uboot(cbfs, ftype=cbfs_util.TYPE_RAW, offset=0x40,
+ cbfs_offset=0x40)
+ self._check_dtb(cbfs, offset=0x40, cbfs_offset=0x140)
+
+ cbfs_fname = self._get_expected_cbfs(size=size, base=(0x40, 0x140))
+ self._compare_expected_cbfs(data, cbfs_fname)
+
+ def test_cbfs_invalid_file_type_header(self):
+ """Check handling of an invalid file type when outputting a header"""
+ size = 0xb0
+ cbw = CbfsWriter(size)
+ cfile = cbw.add_file_raw('u-boot', U_BOOT_DATA, 0)
+
+ # Change the type manually before generating the CBFS, and make sure
+ # that the generator complains
+ cfile.ftype = 0xff
+ with self.assertRaises(ValueError) as e:
+ cbw.get_data()
+ self.assertIn('Unknown file type 0xff', str(e.exception))
+
+ def test_cbfs_offset_conflict(self):
+ """Test a CBFS with files that want to overlap"""
+ size = 0x200
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA, 0x40)
+ cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA, 0x80)
+
+ with self.assertRaises(ValueError) as e:
+ cbw.get_data()
+ self.assertIn('No space for data before pad offset', str(e.exception))
+
+ def test_cbfs_check_offset(self):
+ """Test that we can discover the offset of a file after writing it"""
+ size = 0xb0
+ cbw = CbfsWriter(size)
+ cbw.add_file_raw('u-boot', U_BOOT_DATA)
+ cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA)
+ data = cbw.get_data()
+
+ cbfs = cbfs_util.CbfsReader(data)
+ self.assertEqual(0x38, cbfs.files['u-boot'].cbfs_offset)
+ self.assertEqual(0x78, cbfs.files['u-boot-dtb'].cbfs_offset)
+
if __name__ == '__main__':
unittest.main()
filename = "u-boot.dtb";
cbfs-type = "raw";
cbfs-compress = "lz4";
+ cbfs-offset = <0x100000>;
};
};
to add a flat binary with a load/start address, similar to the
'add-flat-binary' option in cbfstool.
+ cbfs-offset:
+ This is the offset of the file's data within the CBFS. It is used to
+ specify where the file should be placed in cases where a fixed position
+ is needed. Typical uses are for code which is not relocatable and must
+ execute in-place from a particular address. This works because SPI flash
+ is generally mapped into memory on x86 devices. The file header is
+ placed before this offset so that the data start lines up exactly with
+ the chosen offset. If this property is not provided, then the file is
+ placed in the next available spot.
The current implementation supports only a subset of CBFS features. It does
not support other file types (e.g. payload), adding multiple files (like the
return False
data = entry.GetData()
if entry._type == 'raw':
- cbfs.add_file_raw(entry._cbfs_name, data, entry._cbfs_compress)
+ cbfs.add_file_raw(entry._cbfs_name, data, entry._cbfs_offset,
+ entry._cbfs_compress)
elif entry._type == 'stage':
- cbfs.add_file_stage(entry._cbfs_name, data)
+ cbfs.add_file_stage(entry._cbfs_name, data, entry._cbfs_offset)
data = cbfs.get_data()
self.SetContents(data)
return True
entry._cbfs_name = fdt_util.GetString(node, 'cbfs-name', entry.name)
entry._type = fdt_util.GetString(node, 'cbfs-type')
compress = fdt_util.GetString(node, 'cbfs-compress', 'none')
+ entry._cbfs_offset = fdt_util.GetInt(node, 'cbfs-offset')
entry._cbfs_compress = cbfs_util.find_compress(compress)
if entry._cbfs_compress is None:
self.Raise("Invalid compression in '%s': '%s'" %
self.assertIn('Could not complete processing of contents',
str(e.exception))
+ def testCbfsOffset(self):
+ """Test a CBFS with files at particular offsets
+
+ Like all CFBS tests, this is just checking the logic that calls
+ cbfs_util. See cbfs_util_test for fully tests (e.g. test_cbfs_offset()).
+ """
+ data = self._DoReadFile('114_cbfs_offset.dts')
+ size = 0x200
+
+ cbfs = cbfs_util.CbfsReader(data)
+ self.assertEqual(size, cbfs.rom_size)
+
+ self.assertIn('u-boot', cbfs.files)
+ cfile = cbfs.files['u-boot']
+ self.assertEqual(U_BOOT_DATA, cfile.data)
+ self.assertEqual(0x40, cfile.cbfs_offset)
+
+ self.assertIn('u-boot-dtb', cbfs.files)
+ cfile2 = cbfs.files['u-boot-dtb']
+ self.assertEqual(U_BOOT_DTB_DATA, cfile2.data)
+ self.assertEqual(0x140, cfile2.cbfs_offset)
+
+
if __name__ == "__main__":
unittest.main()
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ sort-by-offset;
+ end-at-4gb;
+ size = <0x200>;
+ cbfs {
+ size = <0x200>;
+ offset = <0xfffffe00>;
+ u-boot {
+ cbfs-offset = <0x40>;
+ cbfs-type = "raw";
+ };
+ u-boot-dtb {
+ cbfs-offset = <0x140>;
+ cbfs-type = "raw";
+ };
+ };
+ };
+};