1# SPDX-License-Identifier: GPL-2.0+
2# Copyright 2019 Google LLC
3# Written by Simon Glass <sjg@chromium.org>
4#
5# Entry-type module for a Coreboot Filesystem (CBFS)
6#
7
8from __future__ import annotations
9from collections import OrderedDict
10
11from binman import cbfs_util
12from binman.cbfs_util import CbfsWriter
13from binman.entry import Entry
14from dtoc import fdt_util
15
16# This is imported if needed
17state = None
18
19class Entry_cbfs(Entry):
20    """Coreboot Filesystem (CBFS)
21
22    A CBFS provides a way to group files into a group. It has a simple directory
23    structure and allows the position of individual files to be set, since it is
24    designed to support execute-in-place in an x86 SPI-flash device. Where XIP
25    is not used, it supports compression and storing ELF files.
26
27    CBFS is used by coreboot as its way of orgnanising SPI-flash contents.
28
29    The contents of the CBFS are defined by subnodes of the cbfs entry, e.g.::
30
31        cbfs {
32            size = <0x100000>;
33            u-boot {
34                cbfs-type = "raw";
35            };
36            u-boot-dtb {
37                cbfs-type = "raw";
38            };
39        };
40
41    This creates a CBFS 1MB in size two files in it: u-boot.bin and u-boot.dtb.
42    Note that the size is required since binman does not support calculating it.
43    The contents of each entry is just what binman would normally provide if it
44    were not a CBFS node. A blob type can be used to import arbitrary files as
45    with the second subnode below::
46
47        cbfs {
48            size = <0x100000>;
49            u-boot {
50                cbfs-name = "BOOT";
51                cbfs-type = "raw";
52            };
53
54            dtb {
55                type = "blob";
56                filename = "u-boot.dtb";
57                cbfs-type = "raw";
58                cbfs-compress = "lz4";
59                cbfs-offset = <0x100000>;
60            };
61        };
62
63    This creates a CBFS 1MB in size with u-boot.bin (named "BOOT") and
64    u-boot.dtb (named "dtb") and compressed with the lz4 algorithm.
65
66
67    Properties supported in the top-level CBFS node:
68
69    cbfs-arch:
70        Defaults to "x86", but you can specify the architecture if needed.
71
72
73    Properties supported in the CBFS entry subnodes:
74
75    cbfs-name:
76        This is the name of the file created in CBFS. It defaults to the entry
77        name (which is the node name), but you can override it with this
78        property.
79
80    cbfs-type:
81        This is the CBFS file type. The following are supported:
82
83        raw:
84            This is a 'raw' file, although compression is supported. It can be
85            used to store any file in CBFS.
86
87        stage:
88            This is an ELF file that has been loaded (i.e. mapped to memory), so
89            appears in the CBFS as a flat binary. The input file must be an ELF
90            image, for example this puts "u-boot" (the ELF image) into a 'stage'
91            entry::
92
93                cbfs {
94                    size = <0x100000>;
95                    u-boot-elf {
96                        cbfs-name = "BOOT";
97                        cbfs-type = "stage";
98                    };
99                };
100
101            You can use your own ELF file with something like::
102
103                cbfs {
104                    size = <0x100000>;
105                    something {
106                        type = "blob";
107                        filename = "cbfs-stage.elf";
108                        cbfs-type = "stage";
109                    };
110                };
111
112            As mentioned, the file is converted to a flat binary, so it is
113            equivalent to adding "u-boot.bin", for example, but with the load and
114            start addresses specified by the ELF. At present there is no option
115            to add a flat binary with a load/start address, similar to the
116            'add-flat-binary' option in cbfstool.
117
118    cbfs-offset:
119        This is the offset of the file's data within the CBFS. It is used to
120        specify where the file should be placed in cases where a fixed position
121        is needed. Typical uses are for code which is not relocatable and must
122        execute in-place from a particular address. This works because SPI flash
123        is generally mapped into memory on x86 devices. The file header is
124        placed before this offset so that the data start lines up exactly with
125        the chosen offset. If this property is not provided, then the file is
126        placed in the next available spot.
127
128    The current implementation supports only a subset of CBFS features. It does
129    not support other file types (e.g. payload), adding multiple files (like the
130    'files' entry with a pattern supported by binman), putting files at a
131    particular offset in the CBFS and a few other things.
132
133    Of course binman can create images containing multiple CBFSs, simply by
134    defining these in the binman config::
135
136
137        binman {
138            size = <0x800000>;
139            cbfs {
140                offset = <0x100000>;
141                size = <0x100000>;
142                u-boot {
143                    cbfs-type = "raw";
144                };
145                u-boot-dtb {
146                    cbfs-type = "raw";
147                };
148            };
149
150            cbfs2 {
151                offset = <0x700000>;
152                size = <0x100000>;
153                u-boot {
154                    cbfs-type = "raw";
155                };
156                u-boot-dtb {
157                    cbfs-type = "raw";
158                };
159                image {
160                    type = "blob";
161                    filename = "image.jpg";
162                };
163            };
164        };
165
166    This creates an 8MB image with two CBFSs, one at offset 1MB, one at 7MB,
167    both of size 1MB.
168    """
169    def __init__(self, section, etype, node):
170        # Put this here to allow entry-docs and help to work without libfdt
171        global state
172        from binman import state
173
174        super().__init__(section, etype, node)
175        self.align_default = None
176        self._entries = OrderedDict()
177        self.reader = None
178
179    def ReadNode(self):
180        """Read properties from the atf-fip node"""
181        super().ReadNode()
182        self._cbfs_arg = fdt_util.GetString(self._node, 'cbfs-arch', 'x86')
183        self.ReadEntries()
184
185    def ReadEntries(self):
186        """Read the subnodes to find out what should go in this CBFS"""
187        for node in self._node.subnodes:
188            entry = Entry.Create(self, node)
189            entry.ReadNode()
190            entry._cbfs_name = fdt_util.GetString(node, 'cbfs-name', entry.name)
191            entry._type = fdt_util.GetString(node, 'cbfs-type')
192            compress = fdt_util.GetString(node, 'cbfs-compress', 'none')
193            entry._cbfs_offset = fdt_util.GetInt(node, 'cbfs-offset')
194            entry._cbfs_compress = cbfs_util.find_compress(compress)
195            if entry._cbfs_compress is None:
196                self.Raise("Invalid compression in '%s': '%s'" %
197                           (node.name, compress))
198            self._entries[entry._cbfs_name] = entry
199
200    def ObtainCfile(self, cbfs, entry):
201        # First get the input data and put it in a file. If not available,
202        # try later.
203        data = entry.GetData()
204        cfile = None
205        if entry._type == 'raw':
206            cfile = cbfs.add_file_raw(entry._cbfs_name, data,
207                                      entry._cbfs_offset,
208                                      entry._cbfs_compress)
209        elif entry._type == 'stage':
210            cfile = cbfs.add_file_stage(entry._cbfs_name, data,
211                                        entry._cbfs_offset)
212        else:
213            entry.Raise("Unknown cbfs-type '%s' (use 'raw', 'stage')" %
214                        entry._type)
215        return cfile
216
217    def ObtainContents(self, skip_entry=None):
218        arch = cbfs_util.find_arch(self._cbfs_arg)
219        if arch is None:
220            self.Raise("Invalid architecture '%s'" % self._cbfs_arg)
221        if self.size is None:
222            self.Raise("'cbfs' entry must have a size property")
223        cbfs = CbfsWriter(self.size, arch)
224        for entry in self._entries.values():
225            if entry != skip_entry and not entry.ObtainContents():
226                return False
227            cfile = self.ObtainCfile(cbfs, entry)
228            if cfile:
229                entry._cbfs_file = cfile
230        data = cbfs.get_data()
231        self.SetContents(data)
232        return True
233
234    def SetImagePos(self, image_pos):
235        """Override this function to set all the entry properties from CBFS
236
237        We can only do this once image_pos is known
238
239        Args:
240            image_pos: Position of this entry in the image
241        """
242        super().SetImagePos(image_pos)
243
244        # Now update the entries with info from the CBFS entries
245        for entry in self._entries.values():
246            cfile = entry._cbfs_file
247            entry.size = cfile.data_len
248            entry.offset = cfile.calced_cbfs_offset
249            entry.SetImagePos(image_pos + self.offset)
250            if entry._cbfs_compress:
251                entry.uncomp_size = cfile.memlen
252
253    def AddMissingProperties(self, have_image_pos):
254        super().AddMissingProperties(have_image_pos)
255        for entry in self._entries.values():
256            entry.AddMissingProperties(have_image_pos)
257            if entry._cbfs_compress:
258                state.AddZeroProp(entry._node, 'uncomp-size')
259                # Store the 'compress' property, since we don't look at
260                # 'cbfs-compress' in Entry.ReadData()
261                state.AddString(entry._node, 'compress',
262                                cbfs_util.compress_name(entry._cbfs_compress))
263
264    def SetCalculatedProperties(self):
265        """Set the value of device-tree properties calculated by binman"""
266        super().SetCalculatedProperties()
267        for entry in self._entries.values():
268            state.SetInt(entry._node, 'offset', entry.offset)
269            state.SetInt(entry._node, 'size', entry.size)
270            state.SetInt(entry._node, 'image-pos', entry.image_pos)
271            if entry.uncomp_size is not None:
272                state.SetInt(entry._node, 'uncomp-size', entry.uncomp_size)
273
274    def ListEntries(self, entries, indent):
275        """Override this method to list all files in the section"""
276        super().ListEntries(entries, indent)
277        for entry in self._entries.values():
278            entry.ListEntries(entries, indent + 1)
279
280    def GetEntries(self) -> dict[str, Entry]:
281        """Returns the entries (tree children) of this section"""
282        return self._entries
283
284    def ReadData(self, decomp=True, alt_format=None):
285        data = super().ReadData(True, alt_format)
286        return data
287
288    def ReadChildData(self, child, decomp=True, alt_format=None):
289        if not self.reader:
290            data = super().ReadData(True, alt_format)
291            self.reader = cbfs_util.CbfsReader(data)
292        reader = self.reader
293        cfile = reader.files.get(child.name)
294        return cfile.data if decomp else cfile.orig_data
295
296    def WriteChildData(self, child):
297        # Recreate the data structure, leaving the data for this child alone,
298        # so that child.data is used to pack into the FIP.
299        self.ObtainContents(skip_entry=child)
300        return super().WriteChildData(child)
301
302    def AddBintools(self, btools):
303        super().AddBintools(btools)
304        for entry in self._entries.values():
305            entry.AddBintools(btools)
306