Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # SPDX-License-Identifier: GPL-2.0+ |
| 3 | # Copyright 2019 Google LLC |
| 4 | # Written by Simon Glass <sjg@chromium.org> |
| 5 | |
| 6 | """Tests for cbfs_util |
| 7 | |
| 8 | These create and read various CBFSs and compare the results with expected |
| 9 | values and with cbfstool |
| 10 | """ |
| 11 | |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 12 | import io |
| 13 | import os |
| 14 | import shutil |
| 15 | import struct |
| 16 | import tempfile |
| 17 | import unittest |
| 18 | |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame^] | 19 | from binman import bintool |
Simon Glass | c585dd4 | 2020-04-17 18:09:03 -0600 | [diff] [blame] | 20 | from binman import cbfs_util |
| 21 | from binman.cbfs_util import CbfsWriter |
| 22 | from binman import elf |
Simon Glass | a997ea5 | 2020-04-17 18:09:04 -0600 | [diff] [blame] | 23 | from patman import test_util |
| 24 | from patman import tools |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 25 | |
| 26 | U_BOOT_DATA = b'1234' |
| 27 | U_BOOT_DTB_DATA = b'udtb' |
| 28 | COMPRESS_DATA = b'compress xxxxxxxxxxxxxxxxxxxxxx data' |
| 29 | |
| 30 | |
| 31 | class TestCbfs(unittest.TestCase): |
| 32 | """Test of cbfs_util classes""" |
| 33 | #pylint: disable=W0212 |
| 34 | @classmethod |
| 35 | def setUpClass(cls): |
| 36 | # Create a temporary directory for test files |
| 37 | cls._indir = tempfile.mkdtemp(prefix='cbfs_util.') |
| 38 | tools.SetInputDirs([cls._indir]) |
| 39 | |
| 40 | # Set up some useful data files |
| 41 | TestCbfs._make_input_file('u-boot.bin', U_BOOT_DATA) |
| 42 | TestCbfs._make_input_file('u-boot.dtb', U_BOOT_DTB_DATA) |
| 43 | TestCbfs._make_input_file('compress', COMPRESS_DATA) |
| 44 | |
| 45 | # Set up a temporary output directory, used by the tools library when |
| 46 | # compressing files |
| 47 | tools.PrepareOutputDir(None) |
| 48 | |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame^] | 49 | cls.cbfstool = bintool.Bintool.create('cbfstool') |
| 50 | cls.have_cbfstool = cls.cbfstool.is_present() |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 51 | |
| 52 | cls.have_lz4 = True |
| 53 | try: |
| 54 | tools.Run('lz4', '--no-frame-crc', '-c', |
Simon Glass | cc311ac | 2019-10-31 07:42:50 -0600 | [diff] [blame] | 55 | tools.GetInputFilename('u-boot.bin'), binary=True) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 56 | except: |
| 57 | cls.have_lz4 = False |
| 58 | |
| 59 | @classmethod |
| 60 | def tearDownClass(cls): |
| 61 | """Remove the temporary input directory and its contents""" |
| 62 | if cls._indir: |
| 63 | shutil.rmtree(cls._indir) |
| 64 | cls._indir = None |
| 65 | tools.FinaliseOutputDir() |
| 66 | |
| 67 | @classmethod |
| 68 | def _make_input_file(cls, fname, contents): |
| 69 | """Create a new test input file, creating directories as needed |
| 70 | |
| 71 | Args: |
| 72 | fname: Filename to create |
| 73 | contents: File contents to write in to the file |
| 74 | Returns: |
| 75 | Full pathname of file created |
| 76 | """ |
| 77 | pathname = os.path.join(cls._indir, fname) |
| 78 | tools.WriteFile(pathname, contents) |
| 79 | return pathname |
| 80 | |
| 81 | def _check_hdr(self, data, size, offset=0, arch=cbfs_util.ARCHITECTURE_X86): |
| 82 | """Check that the CBFS has the expected header |
| 83 | |
| 84 | Args: |
| 85 | data: Data to check |
| 86 | size: Expected ROM size |
| 87 | offset: Expected offset to first CBFS file |
| 88 | arch: Expected architecture |
| 89 | |
| 90 | Returns: |
| 91 | CbfsReader object containing the CBFS |
| 92 | """ |
| 93 | cbfs = cbfs_util.CbfsReader(data) |
| 94 | self.assertEqual(cbfs_util.HEADER_MAGIC, cbfs.magic) |
| 95 | self.assertEqual(cbfs_util.HEADER_VERSION2, cbfs.version) |
| 96 | self.assertEqual(size, cbfs.rom_size) |
| 97 | self.assertEqual(0, cbfs.boot_block_size) |
| 98 | self.assertEqual(cbfs_util.ENTRY_ALIGN, cbfs.align) |
| 99 | self.assertEqual(offset, cbfs.cbfs_offset) |
| 100 | self.assertEqual(arch, cbfs.arch) |
| 101 | return cbfs |
| 102 | |
| 103 | def _check_uboot(self, cbfs, ftype=cbfs_util.TYPE_RAW, offset=0x38, |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 104 | data=U_BOOT_DATA, cbfs_offset=None): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 105 | """Check that the U-Boot file is as expected |
| 106 | |
| 107 | Args: |
| 108 | cbfs: CbfsReader object to check |
| 109 | ftype: Expected file type |
| 110 | offset: Expected offset of file |
| 111 | data: Expected data in file |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 112 | cbfs_offset: Expected CBFS offset for file's data |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 113 | |
| 114 | Returns: |
| 115 | CbfsFile object containing the file |
| 116 | """ |
| 117 | self.assertIn('u-boot', cbfs.files) |
| 118 | cfile = cbfs.files['u-boot'] |
| 119 | self.assertEqual('u-boot', cfile.name) |
| 120 | self.assertEqual(offset, cfile.offset) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 121 | if cbfs_offset is not None: |
| 122 | self.assertEqual(cbfs_offset, cfile.cbfs_offset) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 123 | self.assertEqual(data, cfile.data) |
| 124 | self.assertEqual(ftype, cfile.ftype) |
| 125 | self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress) |
| 126 | self.assertEqual(len(data), cfile.memlen) |
| 127 | return cfile |
| 128 | |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 129 | def _check_dtb(self, cbfs, offset=0x38, data=U_BOOT_DTB_DATA, |
| 130 | cbfs_offset=None): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 131 | """Check that the U-Boot dtb file is as expected |
| 132 | |
| 133 | Args: |
| 134 | cbfs: CbfsReader object to check |
| 135 | offset: Expected offset of file |
| 136 | data: Expected data in file |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 137 | cbfs_offset: Expected CBFS offset for file's data |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 138 | """ |
| 139 | self.assertIn('u-boot-dtb', cbfs.files) |
| 140 | cfile = cbfs.files['u-boot-dtb'] |
| 141 | self.assertEqual('u-boot-dtb', cfile.name) |
| 142 | self.assertEqual(offset, cfile.offset) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 143 | if cbfs_offset is not None: |
| 144 | self.assertEqual(cbfs_offset, cfile.cbfs_offset) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 145 | self.assertEqual(U_BOOT_DTB_DATA, cfile.data) |
| 146 | self.assertEqual(cbfs_util.TYPE_RAW, cfile.ftype) |
| 147 | self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress) |
| 148 | self.assertEqual(len(U_BOOT_DTB_DATA), cfile.memlen) |
| 149 | |
| 150 | def _check_raw(self, data, size, offset=0, arch=cbfs_util.ARCHITECTURE_X86): |
| 151 | """Check that two raw files are added as expected |
| 152 | |
| 153 | Args: |
| 154 | data: Data to check |
| 155 | size: Expected ROM size |
| 156 | offset: Expected offset to first CBFS file |
| 157 | arch: Expected architecture |
| 158 | """ |
| 159 | cbfs = self._check_hdr(data, size, offset=offset, arch=arch) |
| 160 | self._check_uboot(cbfs) |
| 161 | self._check_dtb(cbfs) |
| 162 | |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 163 | def _get_expected_cbfs(self, size, arch='x86', compress=None, base=None): |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 164 | """Get the file created by cbfstool for a particular scenario |
| 165 | |
| 166 | Args: |
| 167 | size: Size of the CBFS in bytes |
| 168 | arch: Architecture of the CBFS, as a string |
| 169 | compress: Compression to use, e.g. cbfs_util.COMPRESS_LZMA |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 170 | base: Base address of file, or None to put it anywhere |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 171 | |
| 172 | Returns: |
| 173 | Resulting CBFS file, or None if cbfstool is not available |
| 174 | """ |
| 175 | if not self.have_cbfstool or not self.have_lz4: |
| 176 | return None |
| 177 | cbfs_fname = os.path.join(self._indir, 'test.cbfs') |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame^] | 178 | self.cbfstool.create_new(cbfs_fname, size, arch) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 179 | if base: |
| 180 | base = [(1 << 32) - size + b for b in base] |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame^] | 181 | self.cbfstool.add_raw( |
| 182 | cbfs_fname, 'u-boot', |
| 183 | tools.GetInputFilename(compress and 'compress' or 'u-boot.bin'), |
| 184 | compress[0] if compress else None, |
| 185 | base[0] if base else None) |
| 186 | self.cbfstool.add_raw( |
| 187 | cbfs_fname, 'u-boot-dtb', |
| 188 | tools.GetInputFilename(compress and 'compress' or 'u-boot.dtb'), |
| 189 | compress[1] if compress else None, |
| 190 | base[1] if base else None) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 191 | return cbfs_fname |
| 192 | |
| 193 | def _compare_expected_cbfs(self, data, cbfstool_fname): |
| 194 | """Compare against what cbfstool creates |
| 195 | |
| 196 | This compares what binman creates with what cbfstool creates for what |
| 197 | is proportedly the same thing. |
| 198 | |
| 199 | Args: |
| 200 | data: CBFS created by binman |
| 201 | cbfstool_fname: CBFS created by cbfstool |
| 202 | """ |
| 203 | if not self.have_cbfstool or not self.have_lz4: |
| 204 | return |
| 205 | expect = tools.ReadFile(cbfstool_fname) |
| 206 | if expect != data: |
| 207 | tools.WriteFile('/tmp/expect', expect) |
| 208 | tools.WriteFile('/tmp/actual', data) |
| 209 | print('diff -y <(xxd -g1 /tmp/expect) <(xxd -g1 /tmp/actual) | colordiff') |
| 210 | self.fail('cbfstool produced a different result') |
| 211 | |
| 212 | def test_cbfs_functions(self): |
| 213 | """Test global functions of cbfs_util""" |
| 214 | self.assertEqual(cbfs_util.ARCHITECTURE_X86, cbfs_util.find_arch('x86')) |
| 215 | self.assertIsNone(cbfs_util.find_arch('bad-arch')) |
| 216 | |
| 217 | self.assertEqual(cbfs_util.COMPRESS_LZMA, cbfs_util.find_compress('lzma')) |
| 218 | self.assertIsNone(cbfs_util.find_compress('bad-comp')) |
| 219 | |
| 220 | def test_cbfstool_failure(self): |
| 221 | """Test failure to run cbfstool""" |
| 222 | if not self.have_cbfstool: |
| 223 | self.skipTest('No cbfstool available') |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame^] | 224 | with self.assertRaises(ValueError) as exc: |
| 225 | out = self.cbfstool.fail() |
| 226 | self.assertIn('cbfstool missing-file bad-command', str(exc.exception)) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 227 | |
| 228 | def test_cbfs_raw(self): |
| 229 | """Test base handling of a Coreboot Filesystem (CBFS)""" |
| 230 | size = 0xb0 |
| 231 | cbw = CbfsWriter(size) |
| 232 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 233 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 234 | data = cbw.get_data() |
| 235 | self._check_raw(data, size) |
| 236 | cbfs_fname = self._get_expected_cbfs(size=size) |
| 237 | self._compare_expected_cbfs(data, cbfs_fname) |
| 238 | |
| 239 | def test_cbfs_invalid_file_type(self): |
| 240 | """Check handling of an invalid file type when outputiing a CBFS""" |
| 241 | size = 0xb0 |
| 242 | cbw = CbfsWriter(size) |
| 243 | cfile = cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 244 | |
| 245 | # Change the type manually before generating the CBFS, and make sure |
| 246 | # that the generator complains |
| 247 | cfile.ftype = 0xff |
| 248 | with self.assertRaises(ValueError) as e: |
| 249 | cbw.get_data() |
| 250 | self.assertIn('Unknown type 0xff when writing', str(e.exception)) |
| 251 | |
| 252 | def test_cbfs_invalid_file_type_on_read(self): |
| 253 | """Check handling of an invalid file type when reading the CBFS""" |
| 254 | size = 0xb0 |
| 255 | cbw = CbfsWriter(size) |
| 256 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 257 | |
| 258 | data = cbw.get_data() |
| 259 | |
| 260 | # Read in the first file header |
| 261 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 262 | with io.BytesIO(data) as fd: |
| 263 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 264 | pos = fd.tell() |
| 265 | hdr_data = fd.read(cbfs_util.FILE_HEADER_LEN) |
| 266 | magic, size, ftype, attr, offset = struct.unpack( |
| 267 | cbfs_util.FILE_HEADER_FORMAT, hdr_data) |
| 268 | |
| 269 | # Create a new CBFS with a change to the file type |
| 270 | ftype = 0xff |
| 271 | newdata = data[:pos] |
| 272 | newdata += struct.pack(cbfs_util.FILE_HEADER_FORMAT, magic, size, ftype, |
| 273 | attr, offset) |
| 274 | newdata += data[pos + cbfs_util.FILE_HEADER_LEN:] |
| 275 | |
| 276 | # Read in this CBFS and make sure that the reader complains |
| 277 | with self.assertRaises(ValueError) as e: |
| 278 | cbfs_util.CbfsReader(newdata) |
| 279 | self.assertIn('Unknown type 0xff when reading', str(e.exception)) |
| 280 | |
| 281 | def test_cbfs_no_space(self): |
| 282 | """Check handling of running out of space in the CBFS""" |
| 283 | size = 0x60 |
| 284 | cbw = CbfsWriter(size) |
| 285 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 286 | with self.assertRaises(ValueError) as e: |
| 287 | cbw.get_data() |
| 288 | self.assertIn('No space for header', str(e.exception)) |
| 289 | |
| 290 | def test_cbfs_no_space_skip(self): |
| 291 | """Check handling of running out of space in CBFS with file header""" |
Simon Glass | a61e6fe | 2019-07-08 13:18:55 -0600 | [diff] [blame] | 292 | size = 0x5c |
| 293 | cbw = CbfsWriter(size, arch=cbfs_util.ARCHITECTURE_PPC64) |
| 294 | cbw._add_fileheader = True |
| 295 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 296 | with self.assertRaises(ValueError) as e: |
| 297 | cbw.get_data() |
| 298 | self.assertIn('No space for data before offset', str(e.exception)) |
| 299 | |
| 300 | def test_cbfs_no_space_pad(self): |
| 301 | """Check handling of running out of space in CBFS with file header""" |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 302 | size = 0x70 |
| 303 | cbw = CbfsWriter(size) |
| 304 | cbw._add_fileheader = True |
| 305 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 306 | with self.assertRaises(ValueError) as e: |
| 307 | cbw.get_data() |
Simon Glass | a61e6fe | 2019-07-08 13:18:55 -0600 | [diff] [blame] | 308 | self.assertIn('No space for data before pad offset', str(e.exception)) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 309 | |
| 310 | def test_cbfs_bad_header_ptr(self): |
| 311 | """Check handling of a bad master-header pointer""" |
| 312 | size = 0x70 |
| 313 | cbw = CbfsWriter(size) |
| 314 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 315 | data = cbw.get_data() |
| 316 | |
| 317 | # Add one to the pointer to make it invalid |
| 318 | newdata = data[:-4] + struct.pack('<I', cbw._header_offset + 1) |
| 319 | |
| 320 | # We should still be able to find the master header by searching |
| 321 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 322 | cbfs = cbfs_util.CbfsReader(newdata) |
| 323 | self.assertIn('Relative offset seems wrong', stdout.getvalue()) |
| 324 | self.assertIn('u-boot', cbfs.files) |
| 325 | self.assertEqual(size, cbfs.rom_size) |
| 326 | |
| 327 | def test_cbfs_bad_header(self): |
| 328 | """Check handling of a bad master header""" |
| 329 | size = 0x70 |
| 330 | cbw = CbfsWriter(size) |
| 331 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 332 | data = cbw.get_data() |
| 333 | |
| 334 | # Drop most of the header and try reading the modified CBFS |
| 335 | newdata = data[:cbw._header_offset + 4] |
| 336 | |
| 337 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 338 | with self.assertRaises(ValueError) as e: |
| 339 | cbfs_util.CbfsReader(newdata) |
| 340 | self.assertIn('Relative offset seems wrong', stdout.getvalue()) |
| 341 | self.assertIn('Cannot find master header', str(e.exception)) |
| 342 | |
| 343 | def test_cbfs_bad_file_header(self): |
| 344 | """Check handling of a bad file header""" |
| 345 | size = 0x70 |
| 346 | cbw = CbfsWriter(size) |
| 347 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 348 | data = cbw.get_data() |
| 349 | |
| 350 | # Read in the CBFS master header (only), then stop |
| 351 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 352 | with io.BytesIO(data) as fd: |
| 353 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 354 | pos = fd.tell() |
| 355 | |
| 356 | # Remove all but 4 bytes of the file headerm and try to read the file |
| 357 | newdata = data[:pos + 4] |
| 358 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 359 | with io.BytesIO(newdata) as fd: |
| 360 | fd.seek(pos) |
| 361 | self.assertEqual(False, cbr._read_next_file(fd)) |
Simon Glass | d4ed3b0 | 2019-07-20 12:24:03 -0600 | [diff] [blame] | 362 | self.assertIn('File header at 0x0 ran out of data', stdout.getvalue()) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 363 | |
| 364 | def test_cbfs_bad_file_string(self): |
| 365 | """Check handling of an incomplete filename string""" |
| 366 | size = 0x70 |
| 367 | cbw = CbfsWriter(size) |
| 368 | cbw.add_file_raw('16-characters xx', U_BOOT_DATA) |
| 369 | data = cbw.get_data() |
| 370 | |
| 371 | # Read in the CBFS master header (only), then stop |
| 372 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 373 | with io.BytesIO(data) as fd: |
| 374 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 375 | pos = fd.tell() |
| 376 | |
| 377 | # Create a new CBFS with only the first 16 bytes of the file name, then |
| 378 | # try to read the file |
| 379 | newdata = data[:pos + cbfs_util.FILE_HEADER_LEN + 16] |
| 380 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 381 | with io.BytesIO(newdata) as fd: |
| 382 | fd.seek(pos) |
| 383 | self.assertEqual(False, cbr._read_next_file(fd)) |
Simon Glass | d4ed3b0 | 2019-07-20 12:24:03 -0600 | [diff] [blame] | 384 | self.assertIn('String at %#x ran out of data' % |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 385 | cbfs_util.FILE_HEADER_LEN, stdout.getvalue()) |
| 386 | |
| 387 | def test_cbfs_debug(self): |
| 388 | """Check debug output""" |
| 389 | size = 0x70 |
| 390 | cbw = CbfsWriter(size) |
| 391 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 392 | data = cbw.get_data() |
| 393 | |
| 394 | try: |
| 395 | cbfs_util.DEBUG = True |
| 396 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 397 | cbfs_util.CbfsReader(data) |
| 398 | self.assertEqual('name u-boot\ndata %s\n' % U_BOOT_DATA, |
| 399 | stdout.getvalue()) |
| 400 | finally: |
| 401 | cbfs_util.DEBUG = False |
| 402 | |
| 403 | def test_cbfs_bad_attribute(self): |
| 404 | """Check handling of bad attribute tag""" |
| 405 | if not self.have_lz4: |
| 406 | self.skipTest('lz4 --no-frame-crc not available') |
| 407 | size = 0x140 |
| 408 | cbw = CbfsWriter(size) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 409 | cbw.add_file_raw('u-boot', COMPRESS_DATA, None, |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 410 | compress=cbfs_util.COMPRESS_LZ4) |
| 411 | data = cbw.get_data() |
| 412 | |
| 413 | # Search the CBFS for the expected compression tag |
| 414 | with io.BytesIO(data) as fd: |
| 415 | while True: |
| 416 | pos = fd.tell() |
| 417 | tag, = struct.unpack('>I', fd.read(4)) |
| 418 | if tag == cbfs_util.FILE_ATTR_TAG_COMPRESSION: |
| 419 | break |
| 420 | |
| 421 | # Create a new CBFS with the tag changed to something invalid |
| 422 | newdata = data[:pos] + struct.pack('>I', 0x123) + data[pos + 4:] |
| 423 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 424 | cbfs_util.CbfsReader(newdata) |
| 425 | self.assertEqual('Unknown attribute tag 123\n', stdout.getvalue()) |
| 426 | |
| 427 | def test_cbfs_missing_attribute(self): |
| 428 | """Check handling of an incomplete attribute tag""" |
| 429 | if not self.have_lz4: |
| 430 | self.skipTest('lz4 --no-frame-crc not available') |
| 431 | size = 0x140 |
| 432 | cbw = CbfsWriter(size) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 433 | cbw.add_file_raw('u-boot', COMPRESS_DATA, None, |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 434 | compress=cbfs_util.COMPRESS_LZ4) |
| 435 | data = cbw.get_data() |
| 436 | |
| 437 | # Read in the CBFS master header (only), then stop |
| 438 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 439 | with io.BytesIO(data) as fd: |
| 440 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 441 | pos = fd.tell() |
| 442 | |
| 443 | # Create a new CBFS with only the first 4 bytes of the compression tag, |
| 444 | # then try to read the file |
| 445 | tag_pos = pos + cbfs_util.FILE_HEADER_LEN + cbfs_util.FILENAME_ALIGN |
| 446 | newdata = data[:tag_pos + 4] |
| 447 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 448 | with io.BytesIO(newdata) as fd: |
| 449 | fd.seek(pos) |
| 450 | self.assertEqual(False, cbr._read_next_file(fd)) |
| 451 | self.assertIn('Attribute tag at %x ran out of data' % tag_pos, |
| 452 | stdout.getvalue()) |
| 453 | |
| 454 | def test_cbfs_file_master_header(self): |
| 455 | """Check handling of a file containing a master header""" |
| 456 | size = 0x100 |
| 457 | cbw = CbfsWriter(size) |
| 458 | cbw._add_fileheader = True |
| 459 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 460 | data = cbw.get_data() |
| 461 | |
| 462 | cbr = cbfs_util.CbfsReader(data) |
| 463 | self.assertIn('u-boot', cbr.files) |
| 464 | self.assertEqual(size, cbr.rom_size) |
| 465 | |
| 466 | def test_cbfs_arch(self): |
| 467 | """Test on non-x86 architecture""" |
| 468 | size = 0x100 |
| 469 | cbw = CbfsWriter(size, arch=cbfs_util.ARCHITECTURE_PPC64) |
| 470 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 471 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 472 | data = cbw.get_data() |
| 473 | self._check_raw(data, size, offset=0x40, |
| 474 | arch=cbfs_util.ARCHITECTURE_PPC64) |
| 475 | |
| 476 | # Compare against what cbfstool creates |
| 477 | cbfs_fname = self._get_expected_cbfs(size=size, arch='ppc64') |
| 478 | self._compare_expected_cbfs(data, cbfs_fname) |
| 479 | |
| 480 | def test_cbfs_stage(self): |
| 481 | """Tests handling of a Coreboot Filesystem (CBFS)""" |
| 482 | if not elf.ELF_TOOLS: |
| 483 | self.skipTest('Python elftools not available') |
| 484 | elf_fname = os.path.join(self._indir, 'cbfs-stage.elf') |
| 485 | elf.MakeElf(elf_fname, U_BOOT_DATA, U_BOOT_DTB_DATA) |
| 486 | |
| 487 | size = 0xb0 |
| 488 | cbw = CbfsWriter(size) |
| 489 | cbw.add_file_stage('u-boot', tools.ReadFile(elf_fname)) |
| 490 | |
| 491 | data = cbw.get_data() |
| 492 | cbfs = self._check_hdr(data, size) |
| 493 | load = 0xfef20000 |
| 494 | entry = load + 2 |
| 495 | |
| 496 | cfile = self._check_uboot(cbfs, cbfs_util.TYPE_STAGE, offset=0x28, |
| 497 | data=U_BOOT_DATA + U_BOOT_DTB_DATA) |
| 498 | |
| 499 | self.assertEqual(entry, cfile.entry) |
| 500 | self.assertEqual(load, cfile.load) |
| 501 | self.assertEqual(len(U_BOOT_DATA) + len(U_BOOT_DTB_DATA), |
| 502 | cfile.data_len) |
| 503 | |
| 504 | # Compare against what cbfstool creates |
| 505 | if self.have_cbfstool: |
| 506 | cbfs_fname = os.path.join(self._indir, 'test.cbfs') |
Simon Glass | a7c941e | 2022-01-09 20:13:58 -0700 | [diff] [blame^] | 507 | self.cbfstool.create_new(cbfs_fname, size) |
| 508 | self.cbfstool.add_stage(cbfs_fname, 'u-boot', elf_fname) |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 509 | self._compare_expected_cbfs(data, cbfs_fname) |
| 510 | |
| 511 | def test_cbfs_raw_compress(self): |
| 512 | """Test base handling of compressing raw files""" |
| 513 | if not self.have_lz4: |
| 514 | self.skipTest('lz4 --no-frame-crc not available') |
| 515 | size = 0x140 |
| 516 | cbw = CbfsWriter(size) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 517 | cbw.add_file_raw('u-boot', COMPRESS_DATA, None, |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 518 | compress=cbfs_util.COMPRESS_LZ4) |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 519 | cbw.add_file_raw('u-boot-dtb', COMPRESS_DATA, None, |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 520 | compress=cbfs_util.COMPRESS_LZMA) |
| 521 | data = cbw.get_data() |
| 522 | |
| 523 | cbfs = self._check_hdr(data, size) |
| 524 | self.assertIn('u-boot', cbfs.files) |
| 525 | cfile = cbfs.files['u-boot'] |
| 526 | self.assertEqual(cfile.name, 'u-boot') |
| 527 | self.assertEqual(cfile.offset, 56) |
| 528 | self.assertEqual(cfile.data, COMPRESS_DATA) |
| 529 | self.assertEqual(cfile.ftype, cbfs_util.TYPE_RAW) |
| 530 | self.assertEqual(cfile.compress, cbfs_util.COMPRESS_LZ4) |
| 531 | self.assertEqual(cfile.memlen, len(COMPRESS_DATA)) |
| 532 | |
| 533 | self.assertIn('u-boot-dtb', cbfs.files) |
| 534 | cfile = cbfs.files['u-boot-dtb'] |
| 535 | self.assertEqual(cfile.name, 'u-boot-dtb') |
| 536 | self.assertEqual(cfile.offset, 56) |
| 537 | self.assertEqual(cfile.data, COMPRESS_DATA) |
| 538 | self.assertEqual(cfile.ftype, cbfs_util.TYPE_RAW) |
| 539 | self.assertEqual(cfile.compress, cbfs_util.COMPRESS_LZMA) |
| 540 | self.assertEqual(cfile.memlen, len(COMPRESS_DATA)) |
| 541 | |
| 542 | cbfs_fname = self._get_expected_cbfs(size=size, compress=['lz4', 'lzma']) |
| 543 | self._compare_expected_cbfs(data, cbfs_fname) |
| 544 | |
Simon Glass | a61e6fe | 2019-07-08 13:18:55 -0600 | [diff] [blame] | 545 | def test_cbfs_raw_space(self): |
| 546 | """Test files with unused space in the CBFS""" |
| 547 | size = 0xf0 |
| 548 | cbw = CbfsWriter(size) |
| 549 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 550 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 551 | data = cbw.get_data() |
| 552 | self._check_raw(data, size) |
| 553 | cbfs_fname = self._get_expected_cbfs(size=size) |
| 554 | self._compare_expected_cbfs(data, cbfs_fname) |
| 555 | |
Simon Glass | c2f1aed | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 556 | def test_cbfs_offset(self): |
| 557 | """Test a CBFS with files at particular offsets""" |
| 558 | size = 0x200 |
| 559 | cbw = CbfsWriter(size) |
| 560 | cbw.add_file_raw('u-boot', U_BOOT_DATA, 0x40) |
| 561 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA, 0x140) |
| 562 | |
| 563 | data = cbw.get_data() |
| 564 | cbfs = self._check_hdr(data, size) |
| 565 | self._check_uboot(cbfs, ftype=cbfs_util.TYPE_RAW, offset=0x40, |
| 566 | cbfs_offset=0x40) |
| 567 | self._check_dtb(cbfs, offset=0x40, cbfs_offset=0x140) |
| 568 | |
| 569 | cbfs_fname = self._get_expected_cbfs(size=size, base=(0x40, 0x140)) |
| 570 | self._compare_expected_cbfs(data, cbfs_fname) |
| 571 | |
| 572 | def test_cbfs_invalid_file_type_header(self): |
| 573 | """Check handling of an invalid file type when outputting a header""" |
| 574 | size = 0xb0 |
| 575 | cbw = CbfsWriter(size) |
| 576 | cfile = cbw.add_file_raw('u-boot', U_BOOT_DATA, 0) |
| 577 | |
| 578 | # Change the type manually before generating the CBFS, and make sure |
| 579 | # that the generator complains |
| 580 | cfile.ftype = 0xff |
| 581 | with self.assertRaises(ValueError) as e: |
| 582 | cbw.get_data() |
| 583 | self.assertIn('Unknown file type 0xff', str(e.exception)) |
| 584 | |
| 585 | def test_cbfs_offset_conflict(self): |
| 586 | """Test a CBFS with files that want to overlap""" |
| 587 | size = 0x200 |
| 588 | cbw = CbfsWriter(size) |
| 589 | cbw.add_file_raw('u-boot', U_BOOT_DATA, 0x40) |
| 590 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA, 0x80) |
| 591 | |
| 592 | with self.assertRaises(ValueError) as e: |
| 593 | cbw.get_data() |
| 594 | self.assertIn('No space for data before pad offset', str(e.exception)) |
| 595 | |
| 596 | def test_cbfs_check_offset(self): |
| 597 | """Test that we can discover the offset of a file after writing it""" |
| 598 | size = 0xb0 |
| 599 | cbw = CbfsWriter(size) |
| 600 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 601 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 602 | data = cbw.get_data() |
| 603 | |
| 604 | cbfs = cbfs_util.CbfsReader(data) |
| 605 | self.assertEqual(0x38, cbfs.files['u-boot'].cbfs_offset) |
| 606 | self.assertEqual(0x78, cbfs.files['u-boot-dtb'].cbfs_offset) |
| 607 | |
Simon Glass | 96a6296 | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 608 | |
| 609 | if __name__ == '__main__': |
| 610 | unittest.main() |