Browse Source

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>
Simon Glass 5 years ago
parent
commit
e073d4e14f

+ 38 - 0
tools/binman/README.entries

@@ -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
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 
 

+ 102 - 23
tools/binman/cbfs_util.py

@@ -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,
-                          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)
                           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:

+ 73 - 9
tools/binman/cbfs_util_test.py

@@ -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()

+ 14 - 2
tools/binman/etype/cbfs.py

@@ -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'" %

+ 23 - 0
tools/binman/ftest.py

@@ -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 - 0
tools/binman/test/114_cbfs_offset.dts

@@ -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";
+			};
+		};
+	};
+};