binman: Add support for fixed-offset files in CBFS
A feature of CBFS is that it allows files to be positioned at particular offset (as with binman in general). This is useful to support execute-in-place (XIP) code, since this may not be relocatable. Add a new cbfs-offset property to control this. Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
parent
7c173ced64
commit
e073d4e14f
@ -100,6 +100,7 @@ with the second subnode below:
|
|||||||
filename = "u-boot.dtb";
|
filename = "u-boot.dtb";
|
||||||
cbfs-type = "raw";
|
cbfs-type = "raw";
|
||||||
cbfs-compress = "lz4";
|
cbfs-compress = "lz4";
|
||||||
|
cbfs-offset = <0x100000>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -158,6 +159,15 @@ cbfs-type:
|
|||||||
to add a flat binary with a load/start address, similar to the
|
to add a flat binary with a load/start address, similar to the
|
||||||
'add-flat-binary' option in cbfstool.
|
'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
|
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
|
not support other file types (e.g. payload), adding multiple files (like the
|
||||||
@ -334,6 +344,34 @@ See README.x86 for information about x86 binary blobs.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
Entry: intel-me: Entry containing an Intel Management Engine (ME) file
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ it is necessary to rely on the C structures and source code (mostly cbfstool)
|
|||||||
to fully understand it.
|
to fully understand it.
|
||||||
|
|
||||||
Currently supported: raw and stage types with compression, padding empty areas
|
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
|
from __future__ import print_function
|
||||||
@ -190,6 +190,8 @@ class CbfsFile(object):
|
|||||||
Properties:
|
Properties:
|
||||||
name: Name of file
|
name: Name of file
|
||||||
offset: Offset of file data from start of file header
|
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: Contents of file, uncompressed
|
||||||
data_len: Length of (possibly compressed) data in bytes
|
data_len: Length of (possibly compressed) data in bytes
|
||||||
ftype: File type (TYPE_...)
|
ftype: File type (TYPE_...)
|
||||||
@ -203,9 +205,10 @@ class CbfsFile(object):
|
|||||||
contents (used for empty files)
|
contents (used for empty files)
|
||||||
size: Size of the file in bytes (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.name = name
|
||||||
self.offset = None
|
self.offset = None
|
||||||
|
self.cbfs_offset = cbfs_offset
|
||||||
self.data = data
|
self.data = data
|
||||||
self.ftype = ftype
|
self.ftype = ftype
|
||||||
self.compress = compress
|
self.compress = compress
|
||||||
@ -231,7 +234,7 @@ class CbfsFile(object):
|
|||||||
self.data_len = len(indata)
|
self.data_len = len(indata)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def stage(cls, base_address, name, data):
|
def stage(cls, base_address, name, data, cbfs_offset):
|
||||||
"""Create a new stage file
|
"""Create a new stage file
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -239,28 +242,32 @@ class CbfsFile(object):
|
|||||||
name: String file name to put in CBFS (does not need to correspond
|
name: String file name to put in CBFS (does not need to correspond
|
||||||
to the name that the file originally came from)
|
to the name that the file originally came from)
|
||||||
data: Contents of file
|
data: Contents of file
|
||||||
|
cbfs_offset: Offset of file data in bytes from start of CBFS, or
|
||||||
|
None to place this file anyway
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
CbfsFile object containing the file information
|
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
|
cfile.base_address = base_address
|
||||||
return cfile
|
return cfile
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def raw(cls, name, data, compress):
|
def raw(cls, name, data, cbfs_offset, compress):
|
||||||
"""Create a new raw file
|
"""Create a new raw file
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: String file name to put in CBFS (does not need to correspond
|
name: String file name to put in CBFS (does not need to correspond
|
||||||
to the name that the file originally came from)
|
to the name that the file originally came from)
|
||||||
data: Contents of file
|
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_...)
|
compress: Compression algorithm to use (COMPRESS_...)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
CbfsFile object containing the file information
|
CbfsFile object containing the file information
|
||||||
"""
|
"""
|
||||||
return CbfsFile(name, TYPE_RAW, data, compress)
|
return CbfsFile(name, TYPE_RAW, data, cbfs_offset, compress)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def empty(cls, space_to_use, erase_byte):
|
def empty(cls, space_to_use, erase_byte):
|
||||||
@ -275,12 +282,44 @@ class CbfsFile(object):
|
|||||||
Returns:
|
Returns:
|
||||||
CbfsFile object containing the file information
|
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.size = space_to_use - FILE_HEADER_LEN - FILENAME_ALIGN
|
||||||
cfile.erase_byte = erase_byte
|
cfile.erase_byte = erase_byte
|
||||||
return cfile
|
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
|
"""Obtain the contents of the file, in CBFS format
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -292,6 +331,7 @@ class CbfsFile(object):
|
|||||||
attr_pos = 0
|
attr_pos = 0
|
||||||
content = b''
|
content = b''
|
||||||
attr = b''
|
attr = b''
|
||||||
|
pad = b''
|
||||||
data = self.data
|
data = self.data
|
||||||
if self.ftype == TYPE_STAGE:
|
if self.ftype == TYPE_STAGE:
|
||||||
elf_data = elf.DecodeElf(data, self.base_address)
|
elf_data = elf.DecodeElf(data, self.base_address)
|
||||||
@ -315,10 +355,33 @@ class CbfsFile(object):
|
|||||||
if attr:
|
if attr:
|
||||||
attr_pos = hdr_len
|
attr_pos = hdr_len
|
||||||
hdr_len += len(attr)
|
hdr_len += len(attr)
|
||||||
hdr = struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC,
|
if self.cbfs_offset is not None:
|
||||||
len(content) + len(data),
|
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)
|
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):
|
class CbfsWriter(object):
|
||||||
@ -431,34 +494,39 @@ class CbfsWriter(object):
|
|||||||
if offset < self._size:
|
if offset < self._size:
|
||||||
self._skip_to(fd, offset)
|
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
|
"""Add a new stage file to the CBFS
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: String file name to put in CBFS (does not need to correspond
|
name: String file name to put in CBFS (does not need to correspond
|
||||||
to the name that the file originally came from)
|
to the name that the file originally came from)
|
||||||
data: Contents of file
|
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:
|
Returns:
|
||||||
CbfsFile object created
|
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
|
self._files[name] = cfile
|
||||||
return 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
|
"""Create a new raw file
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: String file name to put in CBFS (does not need to correspond
|
name: String file name to put in CBFS (does not need to correspond
|
||||||
to the name that the file originally came from)
|
to the name that the file originally came from)
|
||||||
data: Contents of file
|
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_...)
|
compress: Compression algorithm to use (COMPRESS_...)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
CbfsFile object created
|
CbfsFile object created
|
||||||
"""
|
"""
|
||||||
cfile = CbfsFile.raw(name, data, compress)
|
cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
|
||||||
self._files[name] = cfile
|
self._files[name] = cfile
|
||||||
return cfile
|
return cfile
|
||||||
|
|
||||||
@ -507,7 +575,11 @@ class CbfsWriter(object):
|
|||||||
|
|
||||||
# Write out each file
|
# Write out each file
|
||||||
for cbf in self._files.values():
|
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)
|
self._align_to(fd, self._align)
|
||||||
if not self._hdr_at_start:
|
if not self._hdr_at_start:
|
||||||
self._write_header(fd, add_fileheader=self._add_fileheader)
|
self._write_header(fd, add_fileheader=self._add_fileheader)
|
||||||
@ -639,25 +711,27 @@ class CbfsReader(object):
|
|||||||
|
|
||||||
# Create the correct CbfsFile object depending on the type
|
# Create the correct CbfsFile object depending on the type
|
||||||
cfile = None
|
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:
|
if ftype == TYPE_CBFSHEADER:
|
||||||
self._read_header(fd)
|
self._read_header(fd)
|
||||||
elif ftype == TYPE_STAGE:
|
elif ftype == TYPE_STAGE:
|
||||||
data = fd.read(STAGE_LEN)
|
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.compress, cfile.entry, cfile.load, cfile.data_len,
|
||||||
cfile.memlen) = struct.unpack(STAGE_FORMAT, data)
|
cfile.memlen) = struct.unpack(STAGE_FORMAT, data)
|
||||||
cfile.data = fd.read(cfile.data_len)
|
cfile.data = fd.read(cfile.data_len)
|
||||||
elif ftype == TYPE_RAW:
|
elif ftype == TYPE_RAW:
|
||||||
data = fd.read(size)
|
data = fd.read(size)
|
||||||
cfile = CbfsFile.raw(name, data, compress)
|
cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
|
||||||
cfile.decompress()
|
cfile.decompress()
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print('data', data)
|
print('data', data)
|
||||||
elif ftype == TYPE_EMPTY:
|
elif ftype == TYPE_EMPTY:
|
||||||
# Just read the data and discard it, since it is only padding
|
# Just read the data and discard it, since it is only padding
|
||||||
fd.read(size)
|
fd.read(size)
|
||||||
cfile = CbfsFile('', TYPE_EMPTY, b'')
|
cfile = CbfsFile('', TYPE_EMPTY, b'', cbfs_offset)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unknown type %#x when reading\n' % ftype)
|
raise ValueError('Unknown type %#x when reading\n' % ftype)
|
||||||
if cfile:
|
if cfile:
|
||||||
@ -674,7 +748,8 @@ class CbfsReader(object):
|
|||||||
"""Read attributes from the file
|
"""Read attributes from the file
|
||||||
|
|
||||||
CBFS files can have attributes which are things that cannot fit into the
|
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:
|
Args:
|
||||||
fd: File to read from
|
fd: File to read from
|
||||||
@ -703,6 +778,8 @@ class CbfsReader(object):
|
|||||||
# We don't currently use this information
|
# We don't currently use this information
|
||||||
atag, alen, compress, _decomp_size = struct.unpack(
|
atag, alen, compress, _decomp_size = struct.unpack(
|
||||||
ATTR_COMPRESSION_FORMAT, data)
|
ATTR_COMPRESSION_FORMAT, data)
|
||||||
|
elif atag == FILE_ATTR_TAG_UNUSED2:
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
print('Unknown attribute tag %x' % atag)
|
print('Unknown attribute tag %x' % atag)
|
||||||
attr_size -= len(data)
|
attr_size -= len(data)
|
||||||
@ -760,7 +837,7 @@ class CbfsReader(object):
|
|||||||
return val.decode('utf-8')
|
return val.decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def cbfstool(fname, *cbfs_args):
|
def cbfstool(fname, *cbfs_args, **kwargs):
|
||||||
"""Run cbfstool with provided arguments
|
"""Run cbfstool with provided arguments
|
||||||
|
|
||||||
If the tool fails then this function raises an exception and prints out the
|
If the tool fails then this function raises an exception and prints out the
|
||||||
@ -773,7 +850,9 @@ def cbfstool(fname, *cbfs_args):
|
|||||||
Returns:
|
Returns:
|
||||||
CommandResult object containing the results
|
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,
|
result = command.RunPipe([args], capture=not VERBOSE,
|
||||||
capture_stderr=not VERBOSE, raise_on_error=False)
|
capture_stderr=not VERBOSE, raise_on_error=False)
|
||||||
if result.return_code:
|
if result.return_code:
|
||||||
|
@ -105,7 +105,7 @@ class TestCbfs(unittest.TestCase):
|
|||||||
return cbfs
|
return cbfs
|
||||||
|
|
||||||
def _check_uboot(self, cbfs, ftype=cbfs_util.TYPE_RAW, offset=0x38,
|
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
|
"""Check that the U-Boot file is as expected
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -113,6 +113,7 @@ class TestCbfs(unittest.TestCase):
|
|||||||
ftype: Expected file type
|
ftype: Expected file type
|
||||||
offset: Expected offset of file
|
offset: Expected offset of file
|
||||||
data: Expected data in file
|
data: Expected data in file
|
||||||
|
cbfs_offset: Expected CBFS offset for file's data
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
CbfsFile object containing the file
|
CbfsFile object containing the file
|
||||||
@ -121,24 +122,30 @@ class TestCbfs(unittest.TestCase):
|
|||||||
cfile = cbfs.files['u-boot']
|
cfile = cbfs.files['u-boot']
|
||||||
self.assertEqual('u-boot', cfile.name)
|
self.assertEqual('u-boot', cfile.name)
|
||||||
self.assertEqual(offset, cfile.offset)
|
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(data, cfile.data)
|
||||||
self.assertEqual(ftype, cfile.ftype)
|
self.assertEqual(ftype, cfile.ftype)
|
||||||
self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress)
|
self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress)
|
||||||
self.assertEqual(len(data), cfile.memlen)
|
self.assertEqual(len(data), cfile.memlen)
|
||||||
return cfile
|
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
|
"""Check that the U-Boot dtb file is as expected
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
cbfs: CbfsReader object to check
|
cbfs: CbfsReader object to check
|
||||||
offset: Expected offset of file
|
offset: Expected offset of file
|
||||||
data: Expected data in file
|
data: Expected data in file
|
||||||
|
cbfs_offset: Expected CBFS offset for file's data
|
||||||
"""
|
"""
|
||||||
self.assertIn('u-boot-dtb', cbfs.files)
|
self.assertIn('u-boot-dtb', cbfs.files)
|
||||||
cfile = cbfs.files['u-boot-dtb']
|
cfile = cbfs.files['u-boot-dtb']
|
||||||
self.assertEqual('u-boot-dtb', cfile.name)
|
self.assertEqual('u-boot-dtb', cfile.name)
|
||||||
self.assertEqual(offset, cfile.offset)
|
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(U_BOOT_DTB_DATA, cfile.data)
|
||||||
self.assertEqual(cbfs_util.TYPE_RAW, cfile.ftype)
|
self.assertEqual(cbfs_util.TYPE_RAW, cfile.ftype)
|
||||||
self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress)
|
self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress)
|
||||||
@ -157,13 +164,14 @@ class TestCbfs(unittest.TestCase):
|
|||||||
self._check_uboot(cbfs)
|
self._check_uboot(cbfs)
|
||||||
self._check_dtb(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
|
"""Get the file created by cbfstool for a particular scenario
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
size: Size of the CBFS in bytes
|
size: Size of the CBFS in bytes
|
||||||
arch: Architecture of the CBFS, as a string
|
arch: Architecture of the CBFS, as a string
|
||||||
compress: Compression to use, e.g. cbfs_util.COMPRESS_LZMA
|
compress: Compression to use, e.g. cbfs_util.COMPRESS_LZMA
|
||||||
|
base: Base address of file, or None to put it anywhere
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Resulting CBFS file, or None if cbfstool is not available
|
Resulting CBFS file, or None if cbfstool is not available
|
||||||
@ -172,14 +180,18 @@ class TestCbfs(unittest.TestCase):
|
|||||||
return None
|
return None
|
||||||
cbfs_fname = os.path.join(self._indir, 'test.cbfs')
|
cbfs_fname = os.path.join(self._indir, 'test.cbfs')
|
||||||
cbfs_util.cbfstool(cbfs_fname, 'create', '-m', arch, '-s', '%#x' % size)
|
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',
|
cbfs_util.cbfstool(cbfs_fname, 'add', '-n', 'u-boot', '-t', 'raw',
|
||||||
'-c', compress and compress[0] or 'none',
|
'-c', compress and compress[0] or 'none',
|
||||||
'-f', tools.GetInputFilename(
|
'-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',
|
cbfs_util.cbfstool(cbfs_fname, 'add', '-n', 'u-boot-dtb', '-t', 'raw',
|
||||||
'-c', compress and compress[1] or 'none',
|
'-c', compress and compress[1] or 'none',
|
||||||
'-f', tools.GetInputFilename(
|
'-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
|
return cbfs_fname
|
||||||
|
|
||||||
def _compare_expected_cbfs(self, data, cbfstool_fname):
|
def _compare_expected_cbfs(self, data, cbfstool_fname):
|
||||||
@ -407,7 +419,7 @@ class TestCbfs(unittest.TestCase):
|
|||||||
self.skipTest('lz4 --no-frame-crc not available')
|
self.skipTest('lz4 --no-frame-crc not available')
|
||||||
size = 0x140
|
size = 0x140
|
||||||
cbw = CbfsWriter(size)
|
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)
|
compress=cbfs_util.COMPRESS_LZ4)
|
||||||
data = cbw.get_data()
|
data = cbw.get_data()
|
||||||
|
|
||||||
@ -431,7 +443,7 @@ class TestCbfs(unittest.TestCase):
|
|||||||
self.skipTest('lz4 --no-frame-crc not available')
|
self.skipTest('lz4 --no-frame-crc not available')
|
||||||
size = 0x140
|
size = 0x140
|
||||||
cbw = CbfsWriter(size)
|
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)
|
compress=cbfs_util.COMPRESS_LZ4)
|
||||||
data = cbw.get_data()
|
data = cbw.get_data()
|
||||||
|
|
||||||
@ -517,9 +529,9 @@ class TestCbfs(unittest.TestCase):
|
|||||||
self.skipTest('lz4 --no-frame-crc not available')
|
self.skipTest('lz4 --no-frame-crc not available')
|
||||||
size = 0x140
|
size = 0x140
|
||||||
cbw = CbfsWriter(size)
|
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)
|
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)
|
compress=cbfs_util.COMPRESS_LZMA)
|
||||||
data = cbw.get_data()
|
data = cbw.get_data()
|
||||||
|
|
||||||
@ -556,6 +568,58 @@ class TestCbfs(unittest.TestCase):
|
|||||||
cbfs_fname = self._get_expected_cbfs(size=size)
|
cbfs_fname = self._get_expected_cbfs(size=size)
|
||||||
self._compare_expected_cbfs(data, cbfs_fname)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -52,6 +52,7 @@ class Entry_cbfs(Entry):
|
|||||||
filename = "u-boot.dtb";
|
filename = "u-boot.dtb";
|
||||||
cbfs-type = "raw";
|
cbfs-type = "raw";
|
||||||
cbfs-compress = "lz4";
|
cbfs-compress = "lz4";
|
||||||
|
cbfs-offset = <0x100000>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -110,6 +111,15 @@ class Entry_cbfs(Entry):
|
|||||||
to add a flat binary with a load/start address, similar to the
|
to add a flat binary with a load/start address, similar to the
|
||||||
'add-flat-binary' option in cbfstool.
|
'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
|
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
|
not support other file types (e.g. payload), adding multiple files (like the
|
||||||
@ -172,9 +182,10 @@ class Entry_cbfs(Entry):
|
|||||||
return False
|
return False
|
||||||
data = entry.GetData()
|
data = entry.GetData()
|
||||||
if entry._type == 'raw':
|
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':
|
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()
|
data = cbfs.get_data()
|
||||||
self.SetContents(data)
|
self.SetContents(data)
|
||||||
return True
|
return True
|
||||||
@ -186,6 +197,7 @@ class Entry_cbfs(Entry):
|
|||||||
entry._cbfs_name = fdt_util.GetString(node, 'cbfs-name', entry.name)
|
entry._cbfs_name = fdt_util.GetString(node, 'cbfs-name', entry.name)
|
||||||
entry._type = fdt_util.GetString(node, 'cbfs-type')
|
entry._type = fdt_util.GetString(node, 'cbfs-type')
|
||||||
compress = fdt_util.GetString(node, 'cbfs-compress', 'none')
|
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)
|
entry._cbfs_compress = cbfs_util.find_compress(compress)
|
||||||
if entry._cbfs_compress is None:
|
if entry._cbfs_compress is None:
|
||||||
self.Raise("Invalid compression in '%s': '%s'" %
|
self.Raise("Invalid compression in '%s': '%s'" %
|
||||||
|
@ -2012,5 +2012,28 @@ class TestFunctional(unittest.TestCase):
|
|||||||
self.assertIn('Could not complete processing of contents',
|
self.assertIn('Could not complete processing of contents',
|
||||||
str(e.exception))
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
26
tools/binman/test/114_cbfs_offset.dts
Normal file
26
tools/binman/test/114_cbfs_offset.dts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user