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