summaryrefslogtreecommitdiffstats
path: root/src/interface/efi/efi_autoexec.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/interface/efi/efi_autoexec.c')
-rw-r--r--src/interface/efi/efi_autoexec.c238
1 files changed, 217 insertions, 21 deletions
diff --git a/src/interface/efi/efi_autoexec.c b/src/interface/efi/efi_autoexec.c
index 88eb379bb..79d4a4caf 100644
--- a/src/interface/efi/efi_autoexec.c
+++ b/src/interface/efi/efi_autoexec.c
@@ -24,11 +24,14 @@
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <string.h>
+#include <stdlib.h>
#include <errno.h>
#include <ipxe/image.h>
#include <ipxe/init.h>
+#include <ipxe/in.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_autoexec.h>
+#include <ipxe/efi/Protocol/PxeBaseCode.h>
#include <ipxe/efi/Protocol/SimpleFileSystem.h>
#include <ipxe/efi/Guid/FileInfo.h>
@@ -39,10 +42,10 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*/
/** Autoexec script filename */
-#define AUTOEXEC_FILENAME L"autoexec.ipxe"
+static wchar_t efi_autoexec_wname[] = L"autoexec.ipxe";
/** Autoexec script image name */
-#define AUTOEXEC_NAME "autoexec.ipxe"
+static char efi_autoexec_name[] = "autoexec.ipxe";
/** Autoexec script (if any) */
static void *efi_autoexec;
@@ -51,21 +54,21 @@ static void *efi_autoexec;
static size_t efi_autoexec_len;
/**
- * Load autoexec script
+ * Load autoexec script from filesystem
*
* @v device Device handle
* @ret rc Return status code
*/
-int efi_autoexec_load ( EFI_HANDLE device ) {
+static int efi_autoexec_filesystem ( 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] ) ];
+ CHAR16 name[ sizeof ( efi_autoexec_wname ) /
+ sizeof ( efi_autoexec_wname[0] ) ];
} info;
EFI_FILE_PROTOCOL *root;
EFI_FILE_PROTOCOL *file;
@@ -74,10 +77,6 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
EFI_STATUS efirc;
int rc;
- /* Sanity check */
- assert ( efi_autoexec == NULL );
- assert ( efi_autoexec_len == 0 );
-
/* Open simple file system protocol */
if ( ( efirc = bs->OpenProtocol ( device,
&efi_simple_file_system_protocol_guid,
@@ -99,11 +98,12 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
}
/* Open autoexec script */
- if ( ( efirc = root->Open ( root, &file, name,
+ if ( ( efirc = root->Open ( root, &file, efi_autoexec_wname,
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 ) );
+ efi_handle_name ( device ), efi_autoexec_wname,
+ strerror ( rc ) );
goto err_open;
}
@@ -113,7 +113,8 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
&info ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( device, "EFI %s could not get %ls info: %s\n",
- efi_handle_name ( device ), name, strerror ( rc ) );
+ efi_handle_name ( device ), efi_autoexec_wname,
+ strerror ( rc ) );
goto err_getinfo;
}
size = info.info.FileSize;
@@ -122,7 +123,7 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
if ( ! size ) {
rc = -EINVAL;
DBGC ( device, "EFI %s has zero-length %ls\n",
- efi_handle_name ( device ), name );
+ efi_handle_name ( device ), efi_autoexec_wname );
goto err_empty;
}
@@ -131,7 +132,8 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
&data ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( device, "EFI %s could not allocate %ls: %s\n",
- efi_handle_name ( device ), name, strerror ( rc ) );
+ efi_handle_name ( device ), efi_autoexec_wname,
+ strerror ( rc ) );
goto err_alloc;
}
@@ -139,7 +141,8 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
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 ) );
+ efi_handle_name ( device ), efi_autoexec_wname,
+ strerror ( rc ) );
goto err_read;
}
@@ -148,7 +151,7 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
efi_autoexec_len = size;
data = NULL;
DBGC ( device, "EFI %s found %ls\n",
- efi_handle_name ( device ), name );
+ efi_handle_name ( device ), efi_autoexec_wname );
/* Success */
rc = 0;
@@ -170,13 +173,205 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
}
/**
+ * Load autoexec script from TFTP server
+ *
+ * @v device Device handle
+ * @ret rc Return status code
+ */
+static int efi_autoexec_tftp ( EFI_HANDLE device ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ union {
+ void *interface;
+ EFI_PXE_BASE_CODE_PROTOCOL *pxe;
+ } u;
+ EFI_PXE_BASE_CODE_MODE *mode;
+ EFI_PXE_BASE_CODE_PACKET *packet;
+ union {
+ struct in_addr in;
+ EFI_IP_ADDRESS ip;
+ } server;
+ size_t filename_max;
+ char *filename;
+ char *sep;
+ UINT64 size;
+ VOID *data;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Open PXE base code protocol */
+ if ( ( efirc = bs->OpenProtocol ( device,
+ &efi_pxe_base_code_protocol_guid,
+ &u.interface, efi_image_handle,
+ device,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
+ rc = -EEFI ( efirc );
+ DBGC ( device, "EFI %s has no PXE base code instance: %s\n",
+ efi_handle_name ( device ), strerror ( rc ) );
+ goto err_pxe;
+ }
+
+ /* Do not attempt to parse DHCPv6 packets */
+ mode = u.pxe->Mode;
+ if ( mode->UsingIpv6 ) {
+ rc = -ENOTSUP;
+ DBGC ( device, "EFI %s has IPv6 PXE base code\n",
+ efi_handle_name ( device ) );
+ goto err_ipv6;
+ }
+
+ /* Identify relevant reply packet */
+ if ( mode->PxeReplyReceived &&
+ mode->PxeReply.Dhcpv4.BootpBootFile[0] ) {
+ /* Use boot filename if present in PXE reply */
+ DBGC ( device, "EFI %s using PXE reply filename\n",
+ efi_handle_name ( device ) );
+ packet = &mode->PxeReply;
+ } else if ( mode->DhcpAckReceived &&
+ mode->DhcpAck.Dhcpv4.BootpBootFile[0] ) {
+ /* Otherwise, use boot filename if present in DHCPACK */
+ DBGC ( device, "EFI %s using DHCPACK filename\n",
+ efi_handle_name ( device ) );
+ packet = &mode->DhcpAck;
+ } else if ( mode->ProxyOfferReceived &&
+ mode->ProxyOffer.Dhcpv4.BootpBootFile[0] ) {
+ /* Otherwise, use boot filename if present in ProxyDHCPOFFER */
+ DBGC ( device, "EFI %s using ProxyDHCPOFFER filename\n",
+ efi_handle_name ( device ) );
+ packet = &mode->ProxyOffer;
+ } else {
+ /* No boot filename available */
+ rc = -ENOENT;
+ DBGC ( device, "EFI %s has no PXE boot filename\n",
+ efi_handle_name ( device ) );
+ goto err_packet;
+ }
+
+ /* Allocate filename */
+ filename_max = ( sizeof ( packet->Dhcpv4.BootpBootFile )
+ + ( sizeof ( efi_autoexec_name ) - 1 /* NUL */ )
+ + 1 /* NUL */ );
+ filename = zalloc ( filename_max );
+ if ( ! filename ) {
+ rc = -ENOMEM;
+ goto err_filename;
+ }
+
+ /* Extract next-server address and boot filename */
+ memset ( &server, 0, sizeof ( server ) );
+ memcpy ( &server.in, packet->Dhcpv4.BootpSiAddr,
+ sizeof ( server.in ) );
+ memcpy ( filename, packet->Dhcpv4.BootpBootFile,
+ sizeof ( packet->Dhcpv4.BootpBootFile ) );
+
+ /* Update filename to autoexec script name */
+ sep = strrchr ( filename, '/' );
+ if ( ! sep )
+ sep = strrchr ( filename, '\\' );
+ if ( ! sep )
+ sep = ( filename - 1 );
+ strcpy ( ( sep + 1 ), efi_autoexec_name );
+
+ /* Get file size */
+ if ( ( efirc = u.pxe->Mtftp ( u.pxe,
+ EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE,
+ NULL, FALSE, &size, NULL, &server.ip,
+ ( ( UINT8 * ) filename ), NULL,
+ FALSE ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( device, "EFI %s could not get size of %s:%s: %s\n",
+ efi_handle_name ( device ), inet_ntoa ( server.in ),
+ filename, strerror ( rc ) );
+ goto err_size;
+ }
+
+ /* Ignore zero-length files */
+ if ( ! size ) {
+ rc = -EINVAL;
+ DBGC ( device, "EFI %s has zero-length %s:%s\n",
+ efi_handle_name ( device ), inet_ntoa ( server.in ),
+ filename );
+ goto err_empty;
+ }
+
+ /* Allocate temporary copy */
+ if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, size,
+ &data ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( device, "EFI %s could not allocate %s:%s: %s\n",
+ efi_handle_name ( device ), inet_ntoa ( server.in ),
+ filename, strerror ( rc ) );
+ goto err_alloc;
+ }
+
+ /* Download file */
+ if ( ( efirc = u.pxe->Mtftp ( u.pxe, EFI_PXE_BASE_CODE_TFTP_READ_FILE,
+ data, FALSE, &size, NULL, &server.ip,
+ ( ( UINT8 * ) filename ), NULL,
+ FALSE ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( device, "EFI %s could not download %s:%s: %s\n",
+ efi_handle_name ( device ), inet_ntoa ( server.in ),
+ filename, strerror ( rc ) );
+ goto err_download;
+ }
+
+ /* Record autoexec script */
+ efi_autoexec = data;
+ efi_autoexec_len = size;
+ data = NULL;
+ DBGC ( device, "EFI %s found %s:%s\n", efi_handle_name ( device ),
+ inet_ntoa ( server.in ), filename );
+
+ /* Success */
+ rc = 0;
+
+ err_download:
+ if ( data )
+ bs->FreePool ( data );
+ err_alloc:
+ err_empty:
+ err_size:
+ free ( filename );
+ err_filename:
+ err_packet:
+ err_ipv6:
+ bs->CloseProtocol ( device, &efi_pxe_base_code_protocol_guid,
+ efi_image_handle, device );
+ err_pxe:
+ return rc;
+}
+
+/**
+ * Load autoexec script
+ *
+ * @v device Device handle
+ * @ret rc Return status code
+ */
+int efi_autoexec_load ( EFI_HANDLE device ) {
+ int rc;
+
+ /* Sanity check */
+ assert ( efi_autoexec == NULL );
+ assert ( efi_autoexec_len == 0 );
+
+ /* Try loading from file system, if supported */
+ if ( ( rc = efi_autoexec_filesystem ( device ) ) == 0 )
+ return 0;
+
+ /* Try loading via TFTP, if supported */
+ if ( ( rc = efi_autoexec_tftp ( device ) ) == 0 )
+ return 0;
+
+ return -ENOENT;
+}
+
+/**
* Register autoexec script
*
*/
static void efi_autoexec_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 */
@@ -184,15 +379,16 @@ static void efi_autoexec_startup ( void ) {
return;
/* Create autoexec image */
- image = image_memory ( name, virt_to_user ( efi_autoexec ),
+ image = image_memory ( efi_autoexec_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 );
+ efi_handle_name ( device ), efi_autoexec_name );
return;
}
DBGC ( device, "EFI %s registered %s\n",
- efi_handle_name ( device ), name );
+ efi_handle_name ( device ), efi_autoexec_name );
/* Free temporary copy */
bs->FreePool ( efi_autoexec );