1# SPDX-License-Identifier: GPL-2.0 2# Copyright (c) 2019, Cristian Ciocaltea <cristian.ciocaltea@gmail.com> 3# 4# Work based on: 5# - test_net.py 6# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. 7# - test_fit.py 8# Copyright (c) 2013, Google Inc. 9# 10# Test launching UEFI binaries from FIT images. 11 12""" 13Note: This test relies on boardenv_* containing configuration values to define 14which network environment is available for testing. Without this, the parts 15that rely on network will be automatically skipped. 16 17For example: 18 19# Boolean indicating whether the Ethernet device is attached to USB, and hence 20# USB enumeration needs to be performed prior to network tests. 21# This variable may be omitted if its value is False. 22env__net_uses_usb = False 23 24# Boolean indicating whether the Ethernet device is attached to PCI, and hence 25# PCI enumeration needs to be performed prior to network tests. 26# This variable may be omitted if its value is False. 27env__net_uses_pci = True 28 29# True if a DHCP server is attached to the network, and should be tested. 30# If DHCP testing is not possible or desired, this variable may be omitted or 31# set to False. 32env__net_dhcp_server = True 33 34# A list of environment variables that should be set in order to configure a 35# static IP. If solely relying on DHCP, this variable may be omitted or set to 36# an empty list. 37env__net_static_env_vars = [ 38 ('ipaddr', '10.0.0.100'), 39 ('netmask', '255.255.255.0'), 40 ('serverip', '10.0.0.1'), 41] 42 43# Details regarding a file that may be read from a TFTP server. This variable 44# may be omitted or set to None if TFTP testing is not possible or desired. 45# Additionally, when the 'size' is not available, the file will be generated 46# automatically in the TFTP root directory, as specified by the 'dn' field. 47env__efi_fit_tftp_file = { 48 'fn': 'test-efi-fit.img', # File path relative to TFTP root 49 'size': 3831, # File size 50 'crc32': '9fa3f79c', # Checksum using CRC-32 algorithm, optional 51 'addr': 0x40400000, # Loading address, integer, optional 52 'dn': 'tftp/root/dir', # TFTP root directory path, optional 53} 54""" 55 56import os.path 57import pytest 58import utils 59 60# Define the parametrized ITS data to be used for FIT images generation. 61ITS_DATA = ''' 62/dts-v1/; 63 64/ { 65 description = "EFI image with FDT blob"; 66 #address-cells = <1>; 67 68 images { 69 helloworld { 70 description = "Test EFI"; 71 data = /incbin/("%(hello-bin)s"); 72 type = "%(kernel-type)s"; 73 arch = "%(sys-arch)s"; 74 os = "efi"; 75 compression = "%(efi-comp)s"; 76 load = <0x0>; 77 entry = <0x0>; 78 }; 79 dtbdump { 80 description = "Test EFI fdtdump"; 81 data = /incbin/("%(dtbdump-bin)s"); 82 type = "%(kernel-type)s"; 83 arch = "%(sys-arch)s"; 84 os = "efi"; 85 compression = "%(efi-comp)s"; 86 load = <0x0>; 87 entry = <0x0>; 88 }; 89 initrddump { 90 description = "Test EFI initrddump"; 91 data = /incbin/("%(initrddump-bin)s"); 92 type = "%(kernel-type)s"; 93 arch = "%(sys-arch)s"; 94 os = "efi"; 95 compression = "%(efi-comp)s"; 96 load = <0x0>; 97 entry = <0x0>; 98 }; 99 fdt { 100 description = "Test FDT"; 101 data = /incbin/("%(fdt-bin)s"); 102 type = "flat_dt"; 103 arch = "%(sys-arch)s"; 104 compression = "%(fdt-comp)s"; 105 }; 106 initrd { 107 description = "Initial RAM Disk"; 108 data = /incbin/("%(initrd-fs)s"); 109 type = "ramdisk"; 110 compression = "%(initrd-comp)s"; 111 os = "efi"; 112 }; 113 }; 114 115 configurations { 116 default = "config-efi-fdt"; 117 118 config-efi { 119 description = "EFI FIT w/o FDT"; 120 kernel = "helloworld"; 121 }; 122 123 config-efi-fdt { 124 description = "EFI FIT w/ FDT"; 125 kernel = "dtbdump"; 126 fdt = "fdt"; 127 }; 128 129 config-efi-initrd { 130 description = "EFI FIT w/ initrd"; 131 kernel = "initrddump"; 132 ramdisk = "initrd"; 133 }; 134 }; 135}; 136''' 137 138# Define the parametrized FDT data to be used for DTB images generation. 139FDT_DATA = ''' 140/dts-v1/; 141 142/ { 143 #address-cells = <1>; 144 #size-cells = <1>; 145 146 model = "%(sys-arch)s %(fdt_type)s EFI FIT FDT Boot Test"; 147 compatible = "%(sys-arch)s"; 148 149 reset@0 { 150 compatible = "%(sys-arch)s,reset"; 151 reg = <0 4>; 152 }; 153}; 154''' 155 156@pytest.mark.buildconfigspec('bootm_efi') 157@pytest.mark.buildconfigspec('BOOTEFI_HELLO_COMPILE') 158@pytest.mark.buildconfigspec('EFI_LOAD_FILE2_INITRD') 159@pytest.mark.buildconfigspec('fit') 160@pytest.mark.notbuildconfigspec('generate_acpi_table') 161@pytest.mark.requiredtool('dtc') 162def test_efi_fit_launch(ubman): 163 """Test handling of UEFI binaries inside FIT images. 164 165 The tests are trying to launch U-Boot's helloworld.efi embedded into 166 FIT images, in uncompressed or gzip compressed format. 167 168 Additionally, a sample FDT blob is created and embedded into the above 169 mentioned FIT images, in uncompressed or gzip compressed format. 170 171 For more details, see launch_efi(). 172 173 The following test cases are currently defined and enabled: 174 - Launch uncompressed FIT EFI & internal FDT 175 - Launch uncompressed FIT EFI & FIT FDT 176 - Launch uncompressed FIT EFI & internal FDT & FIT initrd 177 - Launch compressed FIT EFI & internal FDT 178 - Launch compressed FIT EFI & FIT FDT 179 - Launch compressed FIT EFI & internal FDT & FIT initrd 180 """ 181 182 def net_pre_commands(): 183 """Execute any commands required to enable network hardware. 184 185 These commands are provided by the boardenv_* file; see the comment 186 at the beginning of this file. 187 """ 188 189 init_usb = ubman.config.env.get('env__net_uses_usb', False) 190 if init_usb: 191 ubman.run_command('usb start') 192 193 init_pci = ubman.config.env.get('env__net_uses_pci', False) 194 if init_pci: 195 ubman.run_command('pci enum') 196 197 def net_dhcp(): 198 """Execute the dhcp command. 199 200 The boardenv_* file may be used to enable/disable DHCP; see the 201 comment at the beginning of this file. 202 """ 203 204 has_dhcp = ubman.config.buildconfig.get('config_cmd_dhcp', 'n') == 'y' 205 if not has_dhcp: 206 ubman.log.warning('CONFIG_CMD_DHCP != y: Skipping DHCP network setup') 207 return False 208 209 test_dhcp = ubman.config.env.get('env__net_dhcp_server', False) 210 if not test_dhcp: 211 ubman.log.info('No DHCP server available') 212 return False 213 214 ubman.run_command('setenv autoload no') 215 output = ubman.run_command('dhcp') 216 assert 'DHCP client bound to address ' in output 217 return True 218 219 def net_setup_static(): 220 """Set up a static IP configuration. 221 222 The configuration is provided by the boardenv_* file; see the comment at 223 the beginning of this file. 224 """ 225 226 has_dhcp = ubman.config.buildconfig.get('config_cmd_dhcp', 'n') == 'y' 227 if not has_dhcp: 228 ubman.log.warning('CONFIG_NET != y: Skipping static network setup') 229 return False 230 231 env_vars = ubman.config.env.get('env__net_static_env_vars', None) 232 if not env_vars: 233 ubman.log.info('No static network configuration is defined') 234 return False 235 236 for (var, val) in env_vars: 237 ubman.run_command('setenv %s %s' % (var, val)) 238 return True 239 240 def make_fpath(file_name): 241 """Compute the path of a given (temporary) file. 242 243 Args: 244 file_name -- The name of a file within U-Boot build dir. 245 Return: 246 The computed file path. 247 """ 248 249 return os.path.join(ubman.config.build_dir, file_name) 250 251 def make_efi(fname, efi_file, comp): 252 """Create an UEFI binary. 253 254 This simply copies lib/efi_loader/helloworld.efi into U-Boot 255 build dir and, optionally, compresses the file using gzip. 256 257 Args: 258 fname -- The target file name within U-Boot build dir. 259 efi_file -- The source .efi application 260 comp -- Flag to enable gzip compression. 261 Return: 262 The path of the created file. 263 """ 264 265 bin_path = make_fpath(fname) 266 utils.run_and_log(ubman, 267 ['cp', make_fpath(f'lib/efi_loader/{efi_file}'), 268 bin_path]) 269 if comp: 270 utils.run_and_log(ubman, ['gzip', '-f', bin_path]) 271 bin_path += '.gz' 272 return bin_path 273 274 def make_dtb(fdt_type, comp): 275 """Create a sample DTB file. 276 277 Creates a DTS file and compiles it to a DTB. 278 279 Args: 280 fdt_type -- The type of the FDT, i.e. internal, user. 281 comp -- Flag to enable gzip compression. 282 Return: 283 The path of the created file. 284 """ 285 286 # Generate resources referenced by FDT. 287 fdt_params = { 288 'sys-arch': sys_arch, 289 'fdt_type': fdt_type, 290 } 291 292 # Generate a test FDT file. 293 dts = make_fpath('test-efi-fit-%s.dts' % fdt_type) 294 with open(dts, 'w', encoding='ascii') as file: 295 file.write(FDT_DATA % fdt_params) 296 297 # Build the test FDT. 298 dtb = make_fpath('test-efi-fit-%s.dtb' % fdt_type) 299 utils.run_and_log(ubman, 300 ['dtc', '-I', 'dts', '-O', 'dtb', '-o', dtb, dts]) 301 if comp: 302 utils.run_and_log(ubman, ['gzip', '-f', dtb]) 303 dtb += '.gz' 304 return dtb 305 306 def make_initrd(comp): 307 """Create a sample initrd. 308 309 Creates an initrd. 310 311 Args: 312 comp -- Flag to enable gzip compression. 313 Return: 314 The path of the created file. 315 """ 316 317 # Generate a test initrd file. 318 initrd = make_fpath('test-efi-initrd') 319 with open(initrd, 'w', encoding='ascii') as file: 320 file.write('test-efi-initrd') 321 322 if comp: 323 utils.run_and_log(ubman, ['gzip', '-f', initrd]) 324 initrd += '.gz' 325 return initrd 326 327 def make_fit(comp): 328 """Create a sample FIT image. 329 330 Runs 'mkimage' to create a FIT image within U-Boot build dir. 331 Args: 332 comp -- Enable gzip compression for the EFI binary and FDT blob. 333 Return: 334 The path of the created file. 335 """ 336 337 # Generate resources referenced by ITS. 338 hello_bin = os.path.basename(make_efi('test-efi-helloworld.efi', 'helloworld.efi', comp)) 339 dtbdump_bin = os.path.basename(make_efi('test-efi-dtbdump.efi', 'dtbdump.efi', comp)) 340 initrddump_bin = os.path.basename(make_efi('test-efi-initrddump.efi', 'initrddump.efi', comp)) 341 fdt_bin = os.path.basename(make_dtb('user', comp)) 342 initrd_fs = make_initrd(comp) 343 initrd_fs = os.path.basename(initrd_fs) 344 compression = 'gzip' if comp else 'none' 345 kernel_type = 'kernel' if comp else 'kernel_noload' 346 347 its_params = { 348 'sys-arch': sys_arch, 349 'hello-bin': hello_bin, 350 'dtbdump-bin': dtbdump_bin, 351 'initrddump-bin': initrddump_bin, 352 'kernel-type': kernel_type, 353 'efi-comp': compression, 354 'fdt-bin': fdt_bin, 355 'fdt-comp': compression, 356 'initrd-fs': initrd_fs, 357 'initrd-comp': compression, 358 } 359 360 # Generate a test ITS file. 361 its_path = make_fpath('test-efi-fit.its') 362 with open(its_path, 'w', encoding='ascii') as file: 363 file.write(ITS_DATA % its_params) 364 365 # Build the test ITS. 366 fit_path = make_fpath('test-efi-fit.fit') 367 utils.run_and_log( 368 ubman, [make_fpath('tools/mkimage'), '-f', its_path, fit_path]) 369 return fit_path 370 371 def load_fit_from_host(fit): 372 """Load the FIT image using the 'host load' command and return its address. 373 374 Args: 375 fit -- Dictionary describing the FIT image to load, see 376 env__efi_fit_test_file in the comment at the beginning of 377 this file. 378 Return: 379 The address where the file has been loaded. 380 """ 381 382 addr = fit.get('addr', None) 383 if not addr: 384 addr = utils.find_ram_base(ubman) 385 386 output = ubman.run_command( 387 'host load hostfs - %x %s/%s' % (addr, fit['dn'], fit['fn'])) 388 expected_text = ' bytes read' 389 size = fit.get('size', None) 390 if size: 391 expected_text = '%d' % size + expected_text 392 assert expected_text in output 393 394 return addr 395 396 def load_fit_from_tftp(fit): 397 """Load the FIT image using the tftpboot command and return its address. 398 399 The file is downloaded from the TFTP server, its size and optionally its 400 CRC32 are validated. 401 402 Args: 403 fit -- Dictionary describing the FIT image to load, see env__efi_fit_tftp_file 404 in the comment at the beginning of this file. 405 Return: 406 The address where the file has been loaded. 407 """ 408 409 addr = fit.get('addr', None) 410 if not addr: 411 addr = utils.find_ram_base(ubman) 412 413 file_name = fit['fn'] 414 output = ubman.run_command('tftpboot %x %s' % (addr, file_name)) 415 expected_text = 'Bytes transferred = ' 416 size = fit.get('size', None) 417 if size: 418 expected_text += '%d' % size 419 assert expected_text in output 420 421 expected_crc = fit.get('crc32', None) 422 if not expected_crc: 423 return addr 424 425 if ubman.config.buildconfig.get('config_cmd_crc32', 'n') != 'y': 426 return addr 427 428 output = ubman.run_command('crc32 $fileaddr $filesize') 429 assert expected_crc in output 430 431 return addr 432 433 def launch_efi(enable_fdt, enable_initrd, enable_comp): 434 """Launch U-Boot's helloworld.efi binary from a FIT image. 435 436 An external image file can be downloaded from TFTP, when related 437 details are provided by the boardenv_* file; see the comment at the 438 beginning of this file. 439 440 If the size of the TFTP file is not provided within env__efi_fit_tftp_file, 441 the test image is generated automatically and placed in the TFTP root 442 directory specified via the 'dn' field. 443 444 When running the tests on Sandbox, the image file is loaded directly 445 from the host filesystem. 446 447 Once the load address is available on U-Boot console, the 'bootm' 448 command is executed for either 'config-efi', 'config-efi-fdt' or 449 'config-efi-initrd' FIT configuration, depending on the value of the 450 'enable_fdt' and 'enable_initrd' function arguments. 451 452 Eventually the 'Hello, world' message is expected in the U-Boot console. 453 454 Args: 455 enable_fdt -- Flag to enable using the FDT blob inside FIT image. 456 enable_initrd -- Flag to enable using an initrd inside FIT image. 457 enable_comp -- Flag to enable GZIP compression on EFI and FDT 458 generated content. 459 """ 460 461 with ubman.log.section('FDT=%s;INITRD=%s;COMP=%s' % (enable_fdt, enable_initrd, enable_comp)): 462 if is_sandbox: 463 fit = { 464 'dn': ubman.config.build_dir, 465 } 466 else: 467 # Init networking. 468 net_pre_commands() 469 net_set_up = net_dhcp() 470 net_set_up = net_setup_static() or net_set_up 471 if not net_set_up: 472 pytest.skip('Network not initialized') 473 474 fit = ubman.config.env.get('env__efi_fit_tftp_file', None) 475 if not fit: 476 pytest.skip('No env__efi_fit_tftp_file binary specified in environment') 477 478 size = fit.get('size', None) 479 if not size: 480 if not fit.get('dn', None): 481 pytest.skip('Neither "size", nor "dn" info provided in env__efi_fit_tftp_file') 482 483 # Create test FIT image. 484 fit_path = make_fit(enable_comp) 485 fit['fn'] = os.path.basename(fit_path) 486 fit['size'] = os.path.getsize(fit_path) 487 488 # Copy image to TFTP root directory. 489 if fit['dn'] != ubman.config.build_dir: 490 utils.run_and_log(ubman, 491 ['mv', '-f', fit_path, '%s/' % fit['dn']]) 492 493 # Load FIT image. 494 addr = load_fit_from_host(fit) if is_sandbox else load_fit_from_tftp(fit) 495 496 # Select boot configuration. 497 fit_config = 'config-efi' 498 fit_config = fit_config + '-fdt' if enable_fdt else fit_config 499 fit_config = fit_config + '-initrd' if enable_initrd else fit_config 500 501 # Try booting. 502 ubman.run_command('setenv bootargs nocolor') 503 output = ubman.run_command('bootm %x#%s' % (addr, fit_config)) 504 assert '## Application failed' not in output 505 if enable_fdt: 506 assert 'Booting using the fdt blob' in output 507 assert 'DTB Dump' in output 508 if enable_initrd: 509 assert 'Loading ramdisk' in output 510 assert 'INITRD Dump' in output 511 if enable_fdt: 512 response = ubman.run_command(cmd = 'dump', wait_for_echo=False) 513 assert 'EFI FIT FDT Boot Test' in response 514 if enable_initrd: 515 response = ubman.run_command('load', wait_for_echo=False) 516 assert f"crc32: 0x0c77b025" in response 517 if not enable_fdt and not enable_initrd: 518 assert 'Hello, world' in output 519 ubman.restart_uboot() 520 521 # Array slice removes leading/trailing quotes. 522 sys_arch = ubman.config.buildconfig.get('config_sys_arch', '"sandbox"')[1:-1] 523 if sys_arch == 'arm': 524 arm64 = ubman.config.buildconfig.get('config_arm64') 525 if arm64: 526 sys_arch = 'arm64' 527 528 is_sandbox = sys_arch == 'sandbox' 529 530 if is_sandbox: 531 old_dtb = ubman.config.dtb 532 533 try: 534 if is_sandbox: 535 # Use our own device tree file, will be restored afterwards. 536 control_dtb = make_dtb('internal', False) 537 ubman.config.dtb = control_dtb 538 539 # Run tests 540 # - fdt OFF, initrd OFF, gzip OFF 541 launch_efi(False, False, False) 542 # - fdt ON, initrd OFF, gzip OFF 543 launch_efi(True, False, False) 544 # - fdt OFF, initrd ON, gzip OFF 545 launch_efi(False, True, False) 546 547 if is_sandbox: 548 # - fdt OFF, initrd OFF, gzip ON 549 launch_efi(False, False, True) 550 # - fdt ON, initrd OFF, gzip ON 551 launch_efi(True, False, True) 552 # - fdt OFF, initrd ON, gzip ON 553 launch_efi(False, True, True) 554 555 finally: 556 if is_sandbox: 557 # Go back to the original U-Boot with the correct dtb. 558 ubman.config.dtb = old_dtb 559 ubman.restart_uboot() 560