"Das U-Boot" Source Tree

binman: Support replacing data in a cbfs

At present binman cannot replace data within a CBFS since it does not
allow rewriting of the files in that CBFS. Implement this by using the
new WriteData() method to handle the case.

Add a header to compressed data so that the amount of compressed data can
be determined without reference to the size of the containing entry. This
allows the entry to be larger that the contents, without causing errors in
decompression. This is necessary to cope with a compressed device tree
being updated in such a way that it shrinks after the entry size is
already set (an obscure case). It is not used with CBFS since it has its
own metadata for this. Increase the number of passes allowed to resolve
the position of entries, to handle this case.

Add a test for this new logic.

Signed-off-by: Simon Glass <sjg@chromium.org>

+87 -9
+6 -4
tools/binman/cbfs_util.py
··· 208 208 cbfs_offset: Offset of file data in bytes from start of CBFS, or None to 209 209 place this file anyway 210 210 data: Contents of file, uncompressed 211 + orig_data: Original data added to the file, possibly compressed 211 212 data_len: Length of (possibly compressed) data in bytes 212 213 ftype: File type (TYPE_...) 213 214 compression: Compression type (COMPRESS_...) ··· 226 227 self.offset = None 227 228 self.cbfs_offset = cbfs_offset 228 229 self.data = data 230 + self.orig_data = data 229 231 self.ftype = ftype 230 232 self.compress = compress 231 233 self.memlen = None ··· 240 242 """Handle decompressing data if necessary""" 241 243 indata = self.data 242 244 if self.compress == COMPRESS_LZ4: 243 - data = tools.Decompress(indata, 'lz4') 245 + data = tools.Decompress(indata, 'lz4', with_header=False) 244 246 elif self.compress == COMPRESS_LZMA: 245 - data = tools.Decompress(indata, 'lzma') 247 + data = tools.Decompress(indata, 'lzma', with_header=False) 246 248 else: 247 249 data = indata 248 250 self.memlen = len(data) ··· 361 363 elif self.ftype == TYPE_RAW: 362 364 orig_data = data 363 365 if self.compress == COMPRESS_LZ4: 364 - data = tools.Compress(orig_data, 'lz4') 366 + data = tools.Compress(orig_data, 'lz4', with_header=False) 365 367 elif self.compress == COMPRESS_LZMA: 366 - data = tools.Compress(orig_data, 'lzma') 368 + data = tools.Compress(orig_data, 'lzma', with_header=False) 367 369 self.memlen = len(orig_data) 368 370 self.data_len = len(data) 369 371 attr = struct.pack(ATTR_COMPRESSION_FORMAT,
+1 -1
tools/binman/control.py
··· 311 311 # since changing an offset from 0x100 to 0x104 (for example) can 312 312 # alter the compressed size of the device tree. So we need a 313 313 # third pass for this. 314 - passes = 3 314 + passes = 5 315 315 for pack_pass in range(passes): 316 316 try: 317 317 image.PackEntries()
+5
tools/binman/entry_test.py
··· 92 92 dtb = entry.Entry.Create(None, self.GetNode(), 'u-boot-dtb') 93 93 self.assertEqual('u-boot-dtb', dtb.GetFdtEtype()) 94 94 95 + def testWriteChildData(self): 96 + """Test the WriteChildData() method of the base class""" 97 + base = entry.Entry.Create(None, self.GetNode(), 'blob-dtb') 98 + self.assertTrue(base.WriteChildData(base)) 99 + 95 100 96 101 if __name__ == "__main__": 97 102 unittest.main()
+2 -1
tools/binman/etype/cbfs.py
··· 168 168 self._cbfs_arg = fdt_util.GetString(node, 'cbfs-arch', 'x86') 169 169 self._cbfs_entries = OrderedDict() 170 170 self._ReadSubnodes() 171 + self.reader = None 171 172 172 173 def ObtainContents(self, skip=None): 173 174 arch = cbfs_util.find_arch(self._cbfs_arg) ··· 202 203 def _ReadSubnodes(self): 203 204 """Read the subnodes to find out what should go in this IFWI""" 204 205 for node in self._node.subnodes: 205 - entry = Entry.Create(self.section, node) 206 + entry = Entry.Create(self, node) 206 207 entry.ReadNode() 207 208 entry._cbfs_name = fdt_util.GetString(node, 'cbfs-name', entry.name) 208 209 entry._type = fdt_util.GetString(node, 'cbfs-type')
+27 -1
tools/binman/ftest.py
··· 2485 2485 def testExtractCbfsRaw(self): 2486 2486 """Test extracting CBFS compressed data without decompressing it""" 2487 2487 data = self._RunExtractCmd('section/cbfs/u-boot-dtb', decomp=False) 2488 - dtb = tools.Decompress(data, 'lzma') 2488 + dtb = tools.Decompress(data, 'lzma', with_header=False) 2489 2489 self.assertEqual(EXTRACT_DTB_SIZE, len(dtb)) 2490 2490 2491 2491 def testExtractBadEntry(self): ··· 2983 2983 desc = entries['intel-descriptor'] 2984 2984 self.assertEqual(0xff800000, desc.offset); 2985 2985 self.assertEqual(0xff800000, desc.image_pos); 2986 + 2987 + def testReplaceCbfs(self): 2988 + """Test replacing a single file in CBFS without changing the size""" 2989 + self._CheckLz4() 2990 + expected = b'x' * len(U_BOOT_DATA) 2991 + data = self._DoReadFileRealDtb('142_replace_cbfs.dts') 2992 + updated_fname = tools.GetOutputFilename('image-updated.bin') 2993 + tools.WriteFile(updated_fname, data) 2994 + entry_name = 'section/cbfs/u-boot' 2995 + control.WriteEntry(updated_fname, entry_name, expected, 2996 + allow_resize=True) 2997 + data = control.ReadEntry(updated_fname, entry_name) 2998 + self.assertEqual(expected, data) 2999 + 3000 + def testReplaceResizeCbfs(self): 3001 + """Test replacing a single file in CBFS with one of a different size""" 3002 + self._CheckLz4() 3003 + expected = U_BOOT_DATA + b'x' 3004 + data = self._DoReadFileRealDtb('142_replace_cbfs.dts') 3005 + updated_fname = tools.GetOutputFilename('image-updated.bin') 3006 + tools.WriteFile(updated_fname, data) 3007 + entry_name = 'section/cbfs/u-boot' 3008 + control.WriteEntry(updated_fname, entry_name, expected, 3009 + allow_resize=True) 3010 + data = control.ReadEntry(updated_fname, entry_name) 3011 + self.assertEqual(expected, data) 2986 3012 2987 3013 2988 3014 if __name__ == "__main__":
+37
tools/binman/test/142_replace_cbfs.dts
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + 3 + /dts-v1/; 4 + 5 + / { 6 + #address-cells = <1>; 7 + #size-cells = <1>; 8 + 9 + binman { 10 + size = <0xe00>; 11 + allow-repack; 12 + u-boot { 13 + }; 14 + section { 15 + align = <0x100>; 16 + cbfs { 17 + size = <0x400>; 18 + u-boot { 19 + cbfs-type = "raw"; 20 + }; 21 + u-boot-dtb { 22 + cbfs-type = "raw"; 23 + cbfs-compress = "lzma"; 24 + cbfs-offset = <0x80>; 25 + }; 26 + }; 27 + u-boot-dtb { 28 + compress = "lz4"; 29 + }; 30 + }; 31 + fdtmap { 32 + }; 33 + image-header { 34 + location = "end"; 35 + }; 36 + }; 37 + };
+9 -2
tools/patman/tools.py
··· 9 9 import glob 10 10 import os 11 11 import shutil 12 + import struct 12 13 import sys 13 14 import tempfile 14 15 ··· 377 378 return string.encode('utf-8') 378 379 return string 379 380 380 - def Compress(indata, algo): 381 + def Compress(indata, algo, with_header=True): 381 382 """Compress some data using a given algorithm 382 383 383 384 Note that for lzma this uses an old version of the algorithm, not that ··· 408 409 data = Run('gzip', '-c', fname, binary=True) 409 410 else: 410 411 raise ValueError("Unknown algorithm '%s'" % algo) 412 + if with_header: 413 + hdr = struct.pack('<I', len(data)) 414 + data = hdr + data 411 415 return data 412 416 413 - def Decompress(indata, algo): 417 + def Decompress(indata, algo, with_header=True): 414 418 """Decompress some data using a given algorithm 415 419 416 420 Note that for lzma this uses an old version of the algorithm, not that ··· 428 432 """ 429 433 if algo == 'none': 430 434 return indata 435 + if with_header: 436 + data_len = struct.unpack('<I', indata[:4])[0] 437 + indata = indata[4:4 + data_len] 431 438 fname = GetOutputFilename('%s.decomp.tmp' % algo) 432 439 with open(fname, 'wb') as fd: 433 440 fd.write(indata)