| # SPDX-License-Identifier: GPL-2.0+ |
| # Copyright 2019 Google LLC |
| # Written by Simon Glass <sjg@chromium.org> |
| # |
| # Entry-type module for a Coreboot Filesystem (CBFS) |
| # |
| |
| from collections import OrderedDict |
| |
| import cbfs_util |
| from cbfs_util import CbfsWriter |
| from entry import Entry |
| import fdt_util |
| import state |
| |
| class Entry_cbfs(Entry): |
| """Entry containing a Coreboot Filesystem (CBFS) |
| |
| A CBFS provides a way to group files into a group. It has a simple directory |
| structure and allows the position of individual files to be set, since it is |
| designed to support execute-in-place in an x86 SPI-flash device. Where XIP |
| is not used, it supports compression and storing ELF files. |
| |
| CBFS is used by coreboot as its way of orgnanising SPI-flash contents. |
| |
| The contents of the CBFS are defined by subnodes of the cbfs entry, e.g.: |
| |
| cbfs { |
| size = <0x100000>; |
| u-boot { |
| cbfs-type = "raw"; |
| }; |
| u-boot-dtb { |
| cbfs-type = "raw"; |
| }; |
| }; |
| |
| This creates a CBFS 1MB in size two files in it: u-boot.bin and u-boot.dtb. |
| Note that the size is required since binman does not support calculating it. |
| The contents of each entry is just what binman would normally provide if it |
| were not a CBFS node. A blob type can be used to import arbitrary files as |
| with the second subnode below: |
| |
| cbfs { |
| size = <0x100000>; |
| u-boot { |
| cbfs-name = "BOOT"; |
| cbfs-type = "raw"; |
| }; |
| |
| dtb { |
| type = "blob"; |
| filename = "u-boot.dtb"; |
| cbfs-type = "raw"; |
| cbfs-compress = "lz4"; |
| cbfs-offset = <0x100000>; |
| }; |
| }; |
| |
| This creates a CBFS 1MB in size with u-boot.bin (named "BOOT") and |
| u-boot.dtb (named "dtb") and compressed with the lz4 algorithm. |
| |
| |
| Properties supported in the top-level CBFS node: |
| |
| cbfs-arch: |
| Defaults to "x86", but you can specify the architecture if needed. |
| |
| |
| Properties supported in the CBFS entry subnodes: |
| |
| cbfs-name: |
| This is the name of the file created in CBFS. It defaults to the entry |
| name (which is the node name), but you can override it with this |
| property. |
| |
| cbfs-type: |
| This is the CBFS file type. The following are supported: |
| |
| raw: |
| This is a 'raw' file, although compression is supported. It can be |
| used to store any file in CBFS. |
| |
| stage: |
| This is an ELF file that has been loaded (i.e. mapped to memory), so |
| appears in the CBFS as a flat binary. The input file must be an ELF |
| image, for example this puts "u-boot" (the ELF image) into a 'stage' |
| entry: |
| |
| cbfs { |
| size = <0x100000>; |
| u-boot-elf { |
| cbfs-name = "BOOT"; |
| cbfs-type = "stage"; |
| }; |
| }; |
| |
| You can use your own ELF file with something like: |
| |
| cbfs { |
| size = <0x100000>; |
| something { |
| type = "blob"; |
| filename = "cbfs-stage.elf"; |
| cbfs-type = "stage"; |
| }; |
| }; |
| |
| As mentioned, the file is converted to a flat binary, so it is |
| equivalent to adding "u-boot.bin", for example, but with the load and |
| start addresses specified by the ELF. At present there is no option |
| 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 |
| 'files' entry with a pattern supported by binman), putting files at a |
| particular offset in the CBFS and a few other things. |
| |
| Of course binman can create images containing multiple CBFSs, simply by |
| defining these in the binman config: |
| |
| |
| binman { |
| size = <0x800000>; |
| cbfs { |
| offset = <0x100000>; |
| size = <0x100000>; |
| u-boot { |
| cbfs-type = "raw"; |
| }; |
| u-boot-dtb { |
| cbfs-type = "raw"; |
| }; |
| }; |
| |
| cbfs2 { |
| offset = <0x700000>; |
| size = <0x100000>; |
| u-boot { |
| cbfs-type = "raw"; |
| }; |
| u-boot-dtb { |
| cbfs-type = "raw"; |
| }; |
| image { |
| type = "blob"; |
| filename = "image.jpg"; |
| }; |
| }; |
| }; |
| |
| This creates an 8MB image with two CBFSs, one at offset 1MB, one at 7MB, |
| both of size 1MB. |
| """ |
| def __init__(self, section, etype, node): |
| Entry.__init__(self, section, etype, node) |
| self._cbfs_arg = fdt_util.GetString(node, 'cbfs-arch', 'x86') |
| self._cbfs_entries = OrderedDict() |
| self._ReadSubnodes() |
| self.reader = None |
| |
| def ObtainContents(self, skip=None): |
| arch = cbfs_util.find_arch(self._cbfs_arg) |
| if arch is None: |
| self.Raise("Invalid architecture '%s'" % self._cbfs_arg) |
| if self.size is None: |
| self.Raise("'cbfs' entry must have a size property") |
| cbfs = CbfsWriter(self.size, arch) |
| for entry in self._cbfs_entries.values(): |
| # First get the input data and put it in a file. If not available, |
| # try later. |
| if entry != skip and not entry.ObtainContents(): |
| return False |
| data = entry.GetData() |
| cfile = None |
| if entry._type == 'raw': |
| cfile = cbfs.add_file_raw(entry._cbfs_name, data, |
| entry._cbfs_offset, |
| entry._cbfs_compress) |
| elif entry._type == 'stage': |
| cfile = cbfs.add_file_stage(entry._cbfs_name, data, |
| entry._cbfs_offset) |
| else: |
| entry.Raise("Unknown cbfs-type '%s' (use 'raw', 'stage')" % |
| entry._type) |
| if cfile: |
| entry._cbfs_file = cfile |
| data = cbfs.get_data() |
| self.SetContents(data) |
| return True |
| |
| def _ReadSubnodes(self): |
| """Read the subnodes to find out what should go in this IFWI""" |
| for node in self._node.subnodes: |
| entry = Entry.Create(self, node) |
| entry.ReadNode() |
| 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'" % |
| (node.name, compress)) |
| self._cbfs_entries[entry._cbfs_name] = entry |
| |
| def SetImagePos(self, image_pos): |
| """Override this function to set all the entry properties from CBFS |
| |
| We can only do this once image_pos is known |
| |
| Args: |
| image_pos: Position of this entry in the image |
| """ |
| Entry.SetImagePos(self, image_pos) |
| |
| # Now update the entries with info from the CBFS entries |
| for entry in self._cbfs_entries.values(): |
| cfile = entry._cbfs_file |
| entry.size = cfile.data_len |
| entry.offset = cfile.calced_cbfs_offset |
| entry.image_pos = self.image_pos + entry.offset |
| if entry._cbfs_compress: |
| entry.uncomp_size = cfile.memlen |
| |
| def AddMissingProperties(self): |
| Entry.AddMissingProperties(self) |
| for entry in self._cbfs_entries.values(): |
| entry.AddMissingProperties() |
| if entry._cbfs_compress: |
| state.AddZeroProp(entry._node, 'uncomp-size') |
| # Store the 'compress' property, since we don't look at |
| # 'cbfs-compress' in Entry.ReadData() |
| state.AddString(entry._node, 'compress', |
| cbfs_util.compress_name(entry._cbfs_compress)) |
| |
| def SetCalculatedProperties(self): |
| """Set the value of device-tree properties calculated by binman""" |
| Entry.SetCalculatedProperties(self) |
| for entry in self._cbfs_entries.values(): |
| state.SetInt(entry._node, 'offset', entry.offset) |
| state.SetInt(entry._node, 'size', entry.size) |
| state.SetInt(entry._node, 'image-pos', entry.image_pos) |
| if entry.uncomp_size is not None: |
| state.SetInt(entry._node, 'uncomp-size', entry.uncomp_size) |
| |
| def ListEntries(self, entries, indent): |
| """Override this method to list all files in the section""" |
| Entry.ListEntries(self, entries, indent) |
| for entry in self._cbfs_entries.values(): |
| entry.ListEntries(entries, indent + 1) |
| |
| def GetEntries(self): |
| return self._cbfs_entries |
| |
| def ReadData(self, decomp=True): |
| data = Entry.ReadData(self, True) |
| return data |
| |
| def ReadChildData(self, child, decomp=True): |
| if not self.reader: |
| data = Entry.ReadData(self, True) |
| self.reader = cbfs_util.CbfsReader(data) |
| reader = self.reader |
| cfile = reader.files.get(child.name) |
| return cfile.data if decomp else cfile.orig_data |
| |
| def WriteChildData(self, child): |
| self.ObtainContents(skip=child) |
| return True |