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