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