diff options
author | Michael Brown <mcb30@ipxe.org> | 2021-01-25 16:34:22 +0000 |
---|---|---|
committer | Michael Brown <mcb30@ipxe.org> | 2021-01-25 17:04:44 +0000 |
commit | a3f1e8fb6707811e6eb90e339d7ebe813fd89a63 (patch) | |
tree | d97f693d64a39191a02f472f8fdc3ca4f02a80e0 /src/interface | |
parent | 989a7a8032db02eb0524bd78a674d3b087dea3a6 (diff) | |
download | ipxe-a3f1e8fb6707811e6eb90e339d7ebe813fd89a63.zip ipxe-a3f1e8fb6707811e6eb90e339d7ebe813fd89a63.tar.gz ipxe-a3f1e8fb6707811e6eb90e339d7ebe813fd89a63.tar.bz2 |
[efi] Automatically load "/autoexec.ipxe" when booted from a filesystem
When booting iPXE from a filesystem (e.g. a FAT-formatted USB key) it
can be useful to have an iPXE script loaded automatically from the
same filesystem. Compared to using an embedded script, this has the
advantage that the script can be edited without recompiling the iPXE
binary.
For the BIOS version of iPXE, loading from a filesystem is handled
using syslinux (or isolinux) which allows the script to be passed to
the iPXE .lkrn image as an initrd.
For the UEFI version of iPXE, the platform firmware loads the iPXE
.efi image directly and there is currently no equivalent of the BIOS
initrd mechanism.
Add support for automatically loading a file "autoexec.ipxe" (if
present) from the root of the filesystem containing the UEFI iPXE
binary.
A combined BIOS and UEFI image for a USB key can be created using e.g.
./util/genfsimg -o usbkey.img -s myscript.ipxe \
bin-x86_64-efi/ipxe.efi bin/ipxe.lkrn
The file "myscript.ipxe" would appear as "autoexec.ipxe" on the USB
key, and would be loaded automatically on both BIOS and UEFI systems.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/interface')
-rw-r--r-- | src/interface/efi/efi_autoboot.c | 210 |
1 files changed, 200 insertions, 10 deletions
diff --git a/src/interface/efi/efi_autoboot.c b/src/interface/efi/efi_autoboot.c index a9e807e..33a780f 100644 --- a/src/interface/efi/efi_autoboot.c +++ b/src/interface/efi/efi_autoboot.c @@ -23,22 +23,42 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); +#include <string.h> +#include <errno.h> +#include <ipxe/image.h> +#include <ipxe/init.h> #include <ipxe/efi/efi.h> #include <ipxe/efi/efi_autoboot.h> #include <ipxe/efi/Protocol/SimpleNetwork.h> +#include <ipxe/efi/Protocol/SimpleFileSystem.h> +#include <ipxe/efi/Guid/FileInfo.h> #include <usr/autoboot.h> /** @file * - * EFI autoboot device + * EFI automatic booting * */ +/** Autoexec script filename */ +#define AUTOEXEC_FILENAME L"autoexec.ipxe" + +/** Autoexec script image name */ +#define AUTOEXEC_NAME "autoexec.ipxe" + +/** Autoexec script (if any) */ +static void *efi_autoexec; + +/** Autoexec script length */ +static size_t efi_autoexec_len; + /** * Identify autoboot device * + * @v device Device handle + * @ret rc Return status code */ -void efi_set_autoboot ( void ) { +static int efi_set_autoboot_ll_addr ( EFI_HANDLE device ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; union { EFI_SIMPLE_NETWORK_PROTOCOL *snp; @@ -46,26 +66,196 @@ void efi_set_autoboot ( void ) { } snp; EFI_SIMPLE_NETWORK_MODE *mode; EFI_STATUS efirc; + int rc; /* Look for an SNP instance on the image's device handle */ - if ( ( efirc = bs->OpenProtocol ( efi_loaded_image->DeviceHandle, + if ( ( efirc = bs->OpenProtocol ( device, &efi_simple_network_protocol_guid, &snp.interface, efi_image_handle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ - DBGC ( efi_loaded_image, "EFI found no autoboot device\n" ); - return; + rc = -EEFI ( efirc ); + DBGC ( device, "EFI %s has no SNP instance: %s\n", + efi_handle_name ( device ), strerror ( rc ) ); + return rc; } /* Record autoboot device */ mode = snp.snp->Mode; set_autoboot_ll_addr ( &mode->CurrentAddress, mode->HwAddressSize ); - DBGC ( efi_loaded_image, "EFI found autoboot link-layer address:\n" ); - DBGC_HDA ( efi_loaded_image, 0, &mode->CurrentAddress, - mode->HwAddressSize ); + DBGC ( device, "EFI %s found autoboot link-layer address:\n", + efi_handle_name ( device ) ); + DBGC_HDA ( device, 0, &mode->CurrentAddress, mode->HwAddressSize ); /* Close protocol */ - bs->CloseProtocol ( efi_loaded_image->DeviceHandle, - &efi_simple_network_protocol_guid, + bs->CloseProtocol ( device, &efi_simple_network_protocol_guid, efi_image_handle, NULL ); + + return 0; } + +/** + * Load autoexec script + * + * @v device Device handle + * @ret rc Return status code + */ +static int efi_load_autoexec ( EFI_HANDLE device ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + static wchar_t name[] = AUTOEXEC_FILENAME; + union { + void *interface; + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs; + } u; + struct { + EFI_FILE_INFO info; + CHAR16 name[ sizeof ( name ) / sizeof ( name[0] ) ]; + } info; + EFI_FILE_PROTOCOL *root; + EFI_FILE_PROTOCOL *file; + UINTN size; + VOID *data; + EFI_STATUS efirc; + int rc; + + /* Sanity check */ + assert ( efi_autoexec == UNULL ); + assert ( efi_autoexec_len == 0 ); + + /* Open simple file system protocol */ + if ( ( efirc = bs->OpenProtocol ( device, + &efi_simple_file_system_protocol_guid, + &u.interface, efi_image_handle, + device, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( device, "EFI %s has no filesystem instance: %s\n", + efi_handle_name ( device ), strerror ( rc ) ); + goto err_filesystem; + } + + /* Open root directory */ + if ( ( efirc = u.fs->OpenVolume ( u.fs, &root ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFI %s could not open volume: %s\n", + efi_handle_name ( device ), strerror ( rc ) ); + goto err_volume; + } + + /* Open autoexec script */ + if ( ( efirc = root->Open ( root, &file, name, + EFI_FILE_MODE_READ, 0 ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFI %s has no %ls: %s\n", + efi_handle_name ( device ), name, strerror ( rc ) ); + goto err_open; + } + + /* Get file information */ + size = sizeof ( info ); + if ( ( efirc = file->GetInfo ( file, &efi_file_info_id, &size, + &info ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFI %s could not get %ls info: %s\n", + efi_handle_name ( device ), name, strerror ( rc ) ); + goto err_getinfo; + } + size = info.info.FileSize; + + /* Ignore zero-length files */ + if ( ! size ) { + rc = -EINVAL; + DBGC ( device, "EFI %s has zero-length %ls\n", + efi_handle_name ( device ), name ); + goto err_empty; + } + + /* Allocate temporary copy */ + if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, size, + &data ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFI %s could not allocate %ls: %s\n", + efi_handle_name ( device ), name, strerror ( rc ) ); + goto err_alloc; + } + + /* Read file */ + if ( ( efirc = file->Read ( file, &size, data ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFI %s could not read %ls: %s\n", + efi_handle_name ( device ), name, strerror ( rc ) ); + goto err_read; + } + + /* Record autoexec script */ + efi_autoexec = data; + efi_autoexec_len = size; + data = NULL; + DBGC ( device, "EFI %s found %ls\n", + efi_handle_name ( device ), name ); + + err_read: + if ( data ) + bs->FreePool ( data ); + err_alloc: + err_empty: + err_getinfo: + file->Close ( file ); + err_open: + root->Close ( root ); + err_volume: + bs->CloseProtocol ( device, &efi_simple_file_system_protocol_guid, + efi_image_handle, device ); + err_filesystem: + return rc; +} + +/** + * Configure automatic booting + * + */ +void efi_set_autoboot ( void ) { + EFI_HANDLE device = efi_loaded_image->DeviceHandle; + + /* Identify autoboot device, if any */ + efi_set_autoboot_ll_addr ( device ); + + /* Load autoexec script, if any */ + efi_load_autoexec ( device ); +} + +/** + * Register automatic boot image + * + */ +static void efi_autoboot_startup ( void ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_HANDLE device = efi_loaded_image->DeviceHandle; + const char *name = AUTOEXEC_NAME; + struct image *image; + + /* Do nothing if we have no autoexec script */ + if ( ! efi_autoexec ) + return; + + /* Create autoexec image */ + image = image_memory ( name, virt_to_user ( efi_autoexec ), + efi_autoexec_len ); + if ( ! image ) { + DBGC ( device, "EFI %s could not create %s\n", + efi_handle_name ( device ), name ); + return; + } + DBGC ( device, "EFI %s registered %s\n", + efi_handle_name ( device ), name ); + + /* Free temporary copy */ + bs->FreePool ( efi_autoexec ); + efi_autoexec = NULL; +} + +/** Automatic boot startup function */ +struct startup_fn efi_autoboot_startup_fn __startup_fn ( STARTUP_NORMAL ) = { + .name = "efi_autoboot", + .startup = efi_autoboot_startup, +}; |