aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Brown <mcb30@ipxe.org>2021-01-25 16:34:22 +0000
committerMichael Brown <mcb30@ipxe.org>2021-01-25 17:04:44 +0000
commita3f1e8fb6707811e6eb90e339d7ebe813fd89a63 (patch)
treed97f693d64a39191a02f472f8fdc3ca4f02a80e0
parent989a7a8032db02eb0524bd78a674d3b087dea3a6 (diff)
downloadipxe-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>
-rw-r--r--src/include/ipxe/errfile.h1
-rw-r--r--src/interface/efi/efi_autoboot.c210
-rwxr-xr-xsrc/util/genfsimg3
3 files changed, 204 insertions, 10 deletions
diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h
index d317ce5..f14cb86 100644
--- a/src/include/ipxe/errfile.h
+++ b/src/include/ipxe/errfile.h
@@ -383,6 +383,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define ERRFILE_acpi_settings ( ERRFILE_OTHER | 0x00500000 )
#define ERRFILE_ntlm ( ERRFILE_OTHER | 0x00510000 )
#define ERRFILE_efi_veto ( ERRFILE_OTHER | 0x00520000 )
+#define ERRFILE_efi_autoboot ( ERRFILE_OTHER | 0x00530000 )
/** @} */
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,
+};
diff --git a/src/util/genfsimg b/src/util/genfsimg
index 3456734..d31e499 100755
--- a/src/util/genfsimg
+++ b/src/util/genfsimg
@@ -203,6 +203,9 @@ for FILENAME ; do
DESTFILE=$(efi_boot_name "${FILENAME}")
if [ -z "${EFI}" ] ; then
mkdir -p "${DESTDIR}"
+ if [ -n "${SCRIPT}" ] ; then
+ cp "${SCRIPT}" "${FATDIR}/autoexec.ipxe"
+ fi
fi
EFI=1
;;