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 u_boot_utils as util 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 efi { 70 description = "Test EFI"; 71 data = /incbin/("%(efi-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 fdt { 80 description = "Test FDT"; 81 data = /incbin/("%(fdt-bin)s"); 82 type = "flat_dt"; 83 arch = "%(sys-arch)s"; 84 compression = "%(fdt-comp)s"; 85 }; 86 }; 87 88 configurations { 89 default = "config-efi-fdt"; 90 config-efi-fdt { 91 description = "EFI FIT w/ FDT"; 92 kernel = "efi"; 93 fdt = "fdt"; 94 }; 95 config-efi-nofdt { 96 description = "EFI FIT w/o FDT"; 97 kernel = "efi"; 98 }; 99 }; 100}; 101''' 102 103# Define the parametrized FDT data to be used for DTB images generation. 104FDT_DATA = ''' 105/dts-v1/; 106 107/ { 108 #address-cells = <1>; 109 #size-cells = <1>; 110 111 model = "%(sys-arch)s %(fdt_type)s EFI FIT Boot Test"; 112 compatible = "%(sys-arch)s"; 113 114 reset@0 { 115 compatible = "%(sys-arch)s,reset"; 116 reg = <0 4>; 117 }; 118}; 119''' 120 121@pytest.mark.buildconfigspec('bootm_efi') 122@pytest.mark.buildconfigspec('cmd_bootefi_hello_compile') 123@pytest.mark.buildconfigspec('fit') 124@pytest.mark.notbuildconfigspec('generate_acpi_table') 125@pytest.mark.requiredtool('dtc') 126def test_efi_fit_launch(u_boot_console): 127 """Test handling of UEFI binaries inside FIT images. 128 129 The tests are trying to launch U-Boot's helloworld.efi embedded into 130 FIT images, in uncompressed or gzip compressed format. 131 132 Additionally, a sample FDT blob is created and embedded into the above 133 mentioned FIT images, in uncompressed or gzip compressed format. 134 135 For more details, see launch_efi(). 136 137 The following test cases are currently defined and enabled: 138 - Launch uncompressed FIT EFI & internal FDT 139 - Launch uncompressed FIT EFI & FIT FDT 140 - Launch compressed FIT EFI & internal FDT 141 - Launch compressed FIT EFI & FIT FDT 142 """ 143 144 def net_pre_commands(): 145 """Execute any commands required to enable network hardware. 146 147 These commands are provided by the boardenv_* file; see the comment 148 at the beginning of this file. 149 """ 150 151 init_usb = cons.config.env.get('env__net_uses_usb', False) 152 if init_usb: 153 cons.run_command('usb start') 154 155 init_pci = cons.config.env.get('env__net_uses_pci', False) 156 if init_pci: 157 cons.run_command('pci enum') 158 159 def net_dhcp(): 160 """Execute the dhcp command. 161 162 The boardenv_* file may be used to enable/disable DHCP; see the 163 comment at the beginning of this file. 164 """ 165 166 has_dhcp = cons.config.buildconfig.get('config_cmd_dhcp', 'n') == 'y' 167 if not has_dhcp: 168 cons.log.warning('CONFIG_CMD_DHCP != y: Skipping DHCP network setup') 169 return False 170 171 test_dhcp = cons.config.env.get('env__net_dhcp_server', False) 172 if not test_dhcp: 173 cons.log.info('No DHCP server available') 174 return False 175 176 cons.run_command('setenv autoload no') 177 output = cons.run_command('dhcp') 178 assert 'DHCP client bound to address ' in output 179 return True 180 181 def net_setup_static(): 182 """Set up a static IP configuration. 183 184 The configuration is provided by the boardenv_* file; see the comment at 185 the beginning of this file. 186 """ 187 188 has_dhcp = cons.config.buildconfig.get('config_cmd_dhcp', 'n') == 'y' 189 if not has_dhcp: 190 cons.log.warning('CONFIG_NET != y: Skipping static network setup') 191 return False 192 193 env_vars = cons.config.env.get('env__net_static_env_vars', None) 194 if not env_vars: 195 cons.log.info('No static network configuration is defined') 196 return False 197 198 for (var, val) in env_vars: 199 cons.run_command('setenv %s %s' % (var, val)) 200 return True 201 202 def make_fpath(file_name): 203 """Compute the path of a given (temporary) file. 204 205 Args: 206 file_name: The name of a file within U-Boot build dir. 207 Return: 208 The computed file path. 209 """ 210 211 return os.path.join(cons.config.build_dir, file_name) 212 213 def make_efi(fname, comp): 214 """Create an UEFI binary. 215 216 This simply copies lib/efi_loader/helloworld.efi into U-Boot 217 build dir and, optionally, compresses the file using gzip. 218 219 Args: 220 fname: The target file name within U-Boot build dir. 221 comp: Flag to enable gzip compression. 222 Return: 223 The path of the created file. 224 """ 225 226 bin_path = make_fpath(fname) 227 util.run_and_log(cons, 228 ['cp', make_fpath('lib/efi_loader/helloworld.efi'), 229 bin_path]) 230 if comp: 231 util.run_and_log(cons, ['gzip', '-f', bin_path]) 232 bin_path += '.gz' 233 return bin_path 234 235 def make_dtb(fdt_type, comp): 236 """Create a sample DTB file. 237 238 Creates a DTS file and compiles it to a DTB. 239 240 Args: 241 fdt_type: The type of the FDT, i.e. internal, user. 242 comp: Flag to enable gzip compression. 243 Return: 244 The path of the created file. 245 """ 246 247 # Generate resources referenced by FDT. 248 fdt_params = { 249 'sys-arch': sys_arch, 250 'fdt_type': fdt_type, 251 } 252 253 # Generate a test FDT file. 254 dts = make_fpath('test-efi-fit-%s.dts' % fdt_type) 255 with open(dts, 'w') as file: 256 file.write(FDT_DATA % fdt_params) 257 258 # Build the test FDT. 259 dtb = make_fpath('test-efi-fit-%s.dtb' % fdt_type) 260 util.run_and_log(cons, ['dtc', '-I', 'dts', '-O', 'dtb', '-o', dtb, dts]) 261 if comp: 262 util.run_and_log(cons, ['gzip', '-f', dtb]) 263 dtb += '.gz' 264 return dtb 265 266 def make_fit(comp): 267 """Create a sample FIT image. 268 269 Runs 'mkimage' to create a FIT image within U-Boot build dir. 270 Args: 271 comp: Enable gzip compression for the EFI binary and FDT blob. 272 Return: 273 The path of the created file. 274 """ 275 276 # Generate resources referenced by ITS. 277 its_params = { 278 'sys-arch': sys_arch, 279 'efi-bin': os.path.basename(make_efi('test-efi-fit-helloworld.efi', comp)), 280 'kernel-type': 'kernel' if comp else 'kernel_noload', 281 'efi-comp': 'gzip' if comp else 'none', 282 'fdt-bin': os.path.basename(make_dtb('user', comp)), 283 'fdt-comp': 'gzip' if comp else 'none', 284 } 285 286 # Generate a test ITS file. 287 its_path = make_fpath('test-efi-fit-helloworld.its') 288 with open(its_path, 'w') as file: 289 file.write(ITS_DATA % its_params) 290 291 # Build the test ITS. 292 fit_path = make_fpath('test-efi-fit-helloworld.fit') 293 util.run_and_log( 294 cons, [make_fpath('tools/mkimage'), '-f', its_path, fit_path]) 295 return fit_path 296 297 def load_fit_from_host(fit): 298 """Load the FIT image using the 'host load' command and return its address. 299 300 Args: 301 fit: Dictionary describing the FIT image to load, see env__efi_fit_test_file 302 in the comment at the beginning of this file. 303 Return: 304 The address where the file has been loaded. 305 """ 306 307 addr = fit.get('addr', None) 308 if not addr: 309 addr = util.find_ram_base(cons) 310 311 output = cons.run_command( 312 'host load hostfs - %x %s/%s' % (addr, fit['dn'], fit['fn'])) 313 expected_text = ' bytes read' 314 size = fit.get('size', None) 315 if size: 316 expected_text = '%d' % size + expected_text 317 assert expected_text in output 318 319 return addr 320 321 def load_fit_from_tftp(fit): 322 """Load the FIT image using the tftpboot command and return its address. 323 324 The file is downloaded from the TFTP server, its size and optionally its 325 CRC32 are validated. 326 327 Args: 328 fit: Dictionary describing the FIT image to load, see env__efi_fit_tftp_file 329 in the comment at the beginning of this file. 330 Return: 331 The address where the file has been loaded. 332 """ 333 334 addr = fit.get('addr', None) 335 if not addr: 336 addr = util.find_ram_base(cons) 337 338 file_name = fit['fn'] 339 output = cons.run_command('tftpboot %x %s' % (addr, file_name)) 340 expected_text = 'Bytes transferred = ' 341 size = fit.get('size', None) 342 if size: 343 expected_text += '%d' % size 344 assert expected_text in output 345 346 expected_crc = fit.get('crc32', None) 347 if not expected_crc: 348 return addr 349 350 if cons.config.buildconfig.get('config_cmd_crc32', 'n') != 'y': 351 return addr 352 353 output = cons.run_command('crc32 $fileaddr $filesize') 354 assert expected_crc in output 355 356 return addr 357 358 def launch_efi(enable_fdt, enable_comp): 359 """Launch U-Boot's helloworld.efi binary from a FIT image. 360 361 An external image file can be downloaded from TFTP, when related 362 details are provided by the boardenv_* file; see the comment at the 363 beginning of this file. 364 365 If the size of the TFTP file is not provided within env__efi_fit_tftp_file, 366 the test image is generated automatically and placed in the TFTP root 367 directory specified via the 'dn' field. 368 369 When running the tests on Sandbox, the image file is loaded directly 370 from the host filesystem. 371 372 Once the load address is available on U-Boot console, the 'bootm' 373 command is executed for either 'config-efi-fdt' or 'config-efi-nofdt' 374 FIT configuration, depending on the value of the 'enable_fdt' function 375 argument. 376 377 Eventually the 'Hello, world' message is expected in the U-Boot console. 378 379 Args: 380 enable_fdt: Flag to enable using the FDT blob inside FIT image. 381 enable_comp: Flag to enable GZIP compression on EFI and FDT 382 generated content. 383 """ 384 385 with cons.log.section('FDT=%s;COMP=%s' % (enable_fdt, enable_comp)): 386 if is_sandbox: 387 fit = { 388 'dn': cons.config.build_dir, 389 } 390 else: 391 # Init networking. 392 net_pre_commands() 393 net_set_up = net_dhcp() 394 net_set_up = net_setup_static() or net_set_up 395 if not net_set_up: 396 pytest.skip('Network not initialized') 397 398 fit = cons.config.env.get('env__efi_fit_tftp_file', None) 399 if not fit: 400 pytest.skip('No env__efi_fit_tftp_file binary specified in environment') 401 402 size = fit.get('size', None) 403 if not size: 404 if not fit.get('dn', None): 405 pytest.skip('Neither "size", nor "dn" info provided in env__efi_fit_tftp_file') 406 407 # Create test FIT image. 408 fit_path = make_fit(enable_comp) 409 fit['fn'] = os.path.basename(fit_path) 410 fit['size'] = os.path.getsize(fit_path) 411 412 # Copy image to TFTP root directory. 413 if fit['dn'] != cons.config.build_dir: 414 util.run_and_log(cons, ['mv', '-f', fit_path, '%s/' % fit['dn']]) 415 416 # Load FIT image. 417 addr = load_fit_from_host(fit) if is_sandbox else load_fit_from_tftp(fit) 418 419 # Select boot configuration. 420 fit_config = 'config-efi-fdt' if enable_fdt else 'config-efi-nofdt' 421 422 # Try booting. 423 output = cons.run_command('bootm %x#%s' % (addr, fit_config)) 424 if enable_fdt: 425 assert 'Booting using the fdt blob' in output 426 assert 'Hello, world' in output 427 assert '## Application failed' not in output 428 cons.restart_uboot() 429 430 cons = u_boot_console 431 # Array slice removes leading/trailing quotes. 432 sys_arch = cons.config.buildconfig.get('config_sys_arch', '"sandbox"')[1:-1] 433 is_sandbox = sys_arch == 'sandbox' 434 435 try: 436 if is_sandbox: 437 # Use our own device tree file, will be restored afterwards. 438 control_dtb = make_dtb('internal', False) 439 old_dtb = cons.config.dtb 440 cons.config.dtb = control_dtb 441 442 # Run tests 443 # - fdt OFF, gzip OFF 444 launch_efi(False, False) 445 # - fdt ON, gzip OFF 446 launch_efi(True, False) 447 448 if is_sandbox: 449 # - fdt OFF, gzip ON 450 launch_efi(False, True) 451 # - fdt ON, gzip ON 452 launch_efi(True, True) 453 454 finally: 455 if is_sandbox: 456 # Go back to the original U-Boot with the correct dtb. 457 cons.config.dtb = old_dtb 458 cons.restart_uboot() 459