summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/image/efi_image.c126
-rw-r--r--src/include/ipxe/efi/efi.h2
-rw-r--r--src/include/ipxe/efi/ipxe_download.h154
-rw-r--r--src/interface/efi/efi_download.c233
4 files changed, 513 insertions, 2 deletions
diff --git a/src/image/efi_image.c b/src/image/efi_image.c
index 0575496c..9b9e7600 100644
--- a/src/image/efi_image.c
+++ b/src/image/efi_image.c
@@ -19,13 +19,95 @@
FILE_LICENCE ( GPL2_OR_LATER );
#include <errno.h>
+#include <stdlib.h>
#include <ipxe/efi/efi.h>
#include <ipxe/image.h>
#include <ipxe/init.h>
#include <ipxe/features.h>
+#include <ipxe/uri.h>
FEATURE ( FEATURE_IMAGE, "EFI", DHCP_EB_FEATURE_EFI, 1 );
+/** EFI loaded image protocol GUID */
+static EFI_GUID efi_loaded_image_protocol_guid =
+ EFI_LOADED_IMAGE_PROTOCOL_GUID;
+
+/**
+ * Create a Unicode command line for the image
+ *
+ * @v image EFI image
+ * @v devpath_out Device path to pass to image (output)
+ * @v cmdline_out Unicode command line (output)
+ * @v cmdline_len_out Length of command line in bytes (output)
+ * @ret rc Return status code
+ */
+static int efi_image_make_cmdline ( struct image *image,
+ EFI_DEVICE_PATH **devpath_out,
+ VOID **cmdline_out,
+ UINT32 *cmdline_len_out ) {
+ char *uri;
+ size_t uri_len;
+ FILEPATH_DEVICE_PATH *devpath;
+ EFI_DEVICE_PATH *endpath;
+ size_t devpath_len;
+ CHAR16 *cmdline;
+ UINT32 cmdline_len;
+ size_t args_len = 0;
+ UINT32 i;
+
+ /* Get the URI string of the image */
+ uri_len = unparse_uri ( NULL, 0, image->uri, URI_ALL ) + 1;
+
+ /* Compute final command line length */
+ if ( image->cmdline ) {
+ args_len = strlen ( image->cmdline ) + 1;
+ }
+ cmdline_len = args_len + uri_len;
+
+ /* Allocate space for the uri, final command line and device path */
+ cmdline = malloc ( cmdline_len * sizeof ( CHAR16 ) + uri_len
+ + SIZE_OF_FILEPATH_DEVICE_PATH
+ + uri_len * sizeof ( CHAR16 )
+ + sizeof ( EFI_DEVICE_PATH ) );
+ if ( ! cmdline )
+ return -ENOMEM;
+ uri = (char *) ( cmdline + cmdline_len );
+ devpath = (FILEPATH_DEVICE_PATH *) ( uri + uri_len );
+ endpath = (EFI_DEVICE_PATH *) ( (char *) devpath
+ + SIZE_OF_FILEPATH_DEVICE_PATH
+ + uri_len * sizeof ( CHAR16 ) );
+
+ /* Build the iPXE device path */
+ devpath->Header.Type = MEDIA_DEVICE_PATH;
+ devpath->Header.SubType = MEDIA_FILEPATH_DP;
+ devpath_len = SIZE_OF_FILEPATH_DEVICE_PATH
+ + uri_len * sizeof ( CHAR16 );
+ devpath->Header.Length[0] = devpath_len & 0xFF;
+ devpath->Header.Length[1] = devpath_len >> 8;
+ endpath->Type = END_DEVICE_PATH_TYPE;
+ endpath->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE;
+ endpath->Length[0] = 4;
+ endpath->Length[1] = 0;
+ unparse_uri ( uri, uri_len, image->uri, URI_ALL );
+
+ /* Convert to Unicode */
+ for ( i = 0 ; i < uri_len ; i++ ) {
+ cmdline[i] = uri[i];
+ devpath->PathName[i] = uri[i];
+ }
+ if ( image->cmdline ) {
+ cmdline[uri_len - 1] = ' ';
+ }
+ for ( i = 0 ; i < args_len ; i++ ) {
+ cmdline[i + uri_len] = image->cmdline[i];
+ }
+
+ *devpath_out = &devpath->Header;
+ *cmdline_out = cmdline;
+ *cmdline_len_out = cmdline_len * sizeof ( CHAR16 );
+ return 0;
+}
+
/**
* Execute EFI image
*
@@ -34,7 +116,12 @@ FEATURE ( FEATURE_IMAGE, "EFI", DHCP_EB_FEATURE_EFI, 1 );
*/
static int efi_image_exec ( struct image *image ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ union {
+ EFI_LOADED_IMAGE_PROTOCOL *image;
+ void *interface;
+ } loaded;
EFI_HANDLE handle;
+ EFI_HANDLE device_handle = NULL;
UINTN exit_data_size;
CHAR16 *exit_data;
EFI_STATUS efirc;
@@ -47,21 +134,56 @@ static int efi_image_exec ( struct image *image ) {
/* Not an EFI image */
DBGC ( image, "EFIIMAGE %p could not load: %s\n",
image, efi_strerror ( efirc ) );
- return -ENOEXEC;
+ rc = -ENOEXEC;
+ goto err_load_image;
}
+ /* Get the loaded image protocol for the newly loaded image */
+ efirc = bs->OpenProtocol ( handle, &efi_loaded_image_protocol_guid,
+ &loaded.interface, efi_image_handle,
+ NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL );
+ if ( efirc ) {
+ /* Should never happen */
+ rc = EFIRC_TO_RC ( efirc );
+ goto err_open_protocol;
+ }
+
+ /* Pass an iPXE download protocol to the image */
+ if ( ( rc = efi_download_install ( &device_handle ) ) != 0 ) {
+ DBGC ( image, "EFIIMAGE %p could not install iPXE download "
+ "protocol: %s\n", image, strerror ( rc ) );
+ goto err_download_install;
+ }
+ loaded.image->DeviceHandle = device_handle;
+ loaded.image->ParentHandle = efi_loaded_image;
+ if ( ( rc = efi_image_make_cmdline ( image, &loaded.image->FilePath,
+ &loaded.image->LoadOptions,
+ &loaded.image->LoadOptionsSize ) ) != 0 )
+ goto err_make_cmdline;
+
/* Start the image */
if ( ( efirc = bs->StartImage ( handle, &exit_data_size,
&exit_data ) ) != 0 ) {
DBGC ( image, "EFIIMAGE %p returned with status %s\n",
image, efi_strerror ( efirc ) );
+ rc = EFIRC_TO_RC ( efirc );
+ goto err_start_image;
}
- rc = EFIRC_TO_RC ( efirc );
+ /* Success */
+ rc = 0;
+
+ err_start_image:
+ free ( loaded.image->LoadOptions );
+ err_make_cmdline:
+ efi_download_uninstall ( device_handle );
+ err_download_install:
+ err_open_protocol:
/* Unload the image. We can't leave it loaded, because we
* have no "unload" operation.
*/
bs->UnloadImage ( handle );
+ err_load_image:
return rc;
}
diff --git a/src/include/ipxe/efi/efi.h b/src/include/ipxe/efi/efi.h
index 8a216b53..b5ce7df9 100644
--- a/src/include/ipxe/efi/efi.h
+++ b/src/include/ipxe/efi/efi.h
@@ -142,5 +142,7 @@ extern EFI_SYSTEM_TABLE *efi_systab;
extern const char * efi_strerror ( EFI_STATUS efirc );
extern EFI_STATUS efi_init ( EFI_HANDLE image_handle,
EFI_SYSTEM_TABLE *systab );
+extern int efi_download_install ( EFI_HANDLE *device_handle );
+extern void efi_download_uninstall ( EFI_HANDLE device_handle );
#endif /* _IPXE_EFI_H */
diff --git a/src/include/ipxe/efi/ipxe_download.h b/src/include/ipxe/efi/ipxe_download.h
new file mode 100644
index 00000000..282d1eed
--- /dev/null
+++ b/src/include/ipxe/efi/ipxe_download.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2010 VMware, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef IPXE_DOWNLOAD_H
+#define IPXE_DOWNLOAD_H
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/** @file
+ *
+ * iPXE Download Protocol
+ *
+ * EFI applications started by iPXE may use this interface to download files.
+ */
+
+typedef struct _IPXE_DOWNLOAD_PROTOCOL IPXE_DOWNLOAD_PROTOCOL;
+
+/** Token to represent a currently downloading file */
+typedef VOID *IPXE_DOWNLOAD_FILE;
+
+/**
+ * Callback function that is invoked when data arrives for a particular file.
+ *
+ * Not all protocols will deliver data in order. Clients should not rely on the
+ * order of data delivery matching the order in the file.
+ *
+ * Some protocols are capable of determining the file size near the beginning
+ * of data transfer. To allow the client to allocate memory more efficiently,
+ * iPXE may give a hint about the file size by calling the Data callback with
+ * a zero BufferLength and the file size in FileOffset. Clients should be
+ * prepared to deal with more or less data than the hint actually arriving.
+ *
+ * @v Context Context provided to the Start function
+ * @v Buffer New data
+ * @v BufferLength Length of new data in bytes
+ * @v FileOffset Offset of new data in the file
+ * @ret Status EFI_SUCCESS to continue the download,
+ * or any error code to abort.
+ */
+typedef
+EFI_STATUS
+(EFIAPI *IPXE_DOWNLOAD_DATA_CALLBACK)(
+ IN VOID *Context,
+ IN VOID *Buffer,
+ IN UINTN BufferLength,
+ IN UINTN FileOffset
+ );
+
+/**
+ * Callback function that is invoked when the file is finished downloading, or
+ * when a connection unexpectedly closes or times out.
+ *
+ * The finish callback is also called when a download is aborted by the Abort
+ * function (below).
+ *
+ * @v Context Context provided to the Start function
+ * @v Status Reason for termination: EFI_SUCCESS when the entire
+ * file was transferred successfully, or an error
+ * otherwise
+ */
+typedef
+void
+(EFIAPI *IPXE_DOWNLOAD_FINISH_CALLBACK)(
+ IN VOID *Context,
+ IN EFI_STATUS Status
+ );
+
+/**
+ * Start downloading a file, and register callback functions to handle the
+ * download.
+ *
+ * @v This iPXE Download Protocol instance
+ * @v Url URL to download from
+ * @v DataCallback Callback that will be invoked when data arrives
+ * @v FinishCallback Callback that will be invoked when the download ends
+ * @v Context Context passed to the Data and Finish callbacks
+ * @v File Token that can be used to abort the download
+ * @ret Status EFI status code
+ */
+typedef
+EFI_STATUS
+(EFIAPI *IPXE_DOWNLOAD_START)(
+ IN IPXE_DOWNLOAD_PROTOCOL *This,
+ IN CHAR8 *Url,
+ IN IPXE_DOWNLOAD_DATA_CALLBACK DataCallback,
+ IN IPXE_DOWNLOAD_FINISH_CALLBACK FinishCallback,
+ IN VOID *Context,
+ OUT IPXE_DOWNLOAD_FILE *File
+ );
+
+/**
+ * Forcibly abort downloading a file that is currently in progress.
+ *
+ * It is not safe to call this function after the Finish callback has executed.
+ *
+ * @v This iPXE Download Protocol instance
+ * @v File Token obtained from Start
+ * @v Status Reason for aborting the download
+ * @ret Status EFI status code
+ */
+typedef
+EFI_STATUS
+(EFIAPI *IPXE_DOWNLOAD_ABORT)(
+ IN IPXE_DOWNLOAD_PROTOCOL *This,
+ IN IPXE_DOWNLOAD_FILE File,
+ IN EFI_STATUS Status
+ );
+
+/**
+ * Poll for more data from iPXE. This function will invoke the registered
+ * callbacks if data is available or if downloads complete.
+ *
+ * @v This iPXE Download Protocol instance
+ * @ret Status EFI status code
+ */
+typedef
+EFI_STATUS
+(EFIAPI *IPXE_DOWNLOAD_POLL)(
+ IN IPXE_DOWNLOAD_PROTOCOL *This
+ );
+
+/**
+ * The iPXE Download Protocol.
+ *
+ * iPXE will attach a iPXE Download Protocol to the DeviceHandle in the Loaded
+ * Image Protocol of all child EFI applications.
+ */
+struct _IPXE_DOWNLOAD_PROTOCOL {
+ IPXE_DOWNLOAD_START Start;
+ IPXE_DOWNLOAD_ABORT Abort;
+ IPXE_DOWNLOAD_POLL Poll;
+};
+
+#define IPXE_DOWNLOAD_PROTOCOL_GUID \
+ { \
+ 0x3eaeaebd, 0xdecf, 0x493b, { 0x9b, 0xd1, 0xcd, 0xb2, 0xde, 0xca, 0xe7, 0x19 } \
+ }
+
+#endif
diff --git a/src/interface/efi/efi_download.c b/src/interface/efi/efi_download.c
new file mode 100644
index 00000000..250946e2
--- /dev/null
+++ b/src/interface/efi/efi_download.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2010 VMware, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdlib.h>
+#include <string.h>
+#include <ipxe/open.h>
+#include <ipxe/process.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/xfer.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/ipxe_download.h>
+
+/** iPXE download protocol GUID */
+static EFI_GUID ipxe_download_protocol_guid
+ = IPXE_DOWNLOAD_PROTOCOL_GUID;
+
+/** A single in-progress file */
+struct efi_download_file {
+ /** Data transfer interface that provides downloaded data */
+ struct interface xfer;
+
+ /** Current file position */
+ size_t pos;
+
+ /** Data callback */
+ IPXE_DOWNLOAD_DATA_CALLBACK data_callback;
+
+ /** Finish callback */
+ IPXE_DOWNLOAD_FINISH_CALLBACK finish_callback;
+
+ /** Callback context */
+ void *context;
+};
+
+/* xfer interface */
+
+/**
+ * Transfer finished or was aborted
+ *
+ * @v file Data transfer file
+ * @v rc Reason for close
+ */
+static void efi_download_close ( struct efi_download_file *file, int rc ) {
+
+ file->finish_callback ( file->context, RC_TO_EFIRC ( rc ) );
+
+ intf_shutdown ( &file->xfer, rc );
+}
+
+/**
+ * Process received data
+ *
+ * @v file Data transfer file
+ * @v iobuf I/O buffer
+ * @v meta Data transfer metadata
+ * @ret rc Return status code
+ */
+static int efi_download_deliver_iob ( struct efi_download_file *file,
+ struct io_buffer *iobuf,
+ struct xfer_metadata *meta ) {
+ EFI_STATUS efirc;
+ size_t len = iob_len ( iobuf );
+
+ /* Calculate new buffer position */
+ if ( meta->flags & XFER_FL_ABS_OFFSET )
+ file->pos = 0;
+ file->pos += meta->offset;
+
+ /* Call out to the data handler */
+ efirc = file->data_callback ( file->context, iobuf->data,
+ len, file->pos );
+
+ /* Update current buffer position */
+ file->pos += len;
+
+ free_iob ( iobuf );
+ return EFIRC_TO_RC ( efirc );
+}
+
+/** Data transfer interface operations */
+static struct interface_operation efi_xfer_operations[] = {
+ INTF_OP ( xfer_deliver, struct efi_download_file *, efi_download_deliver_iob ),
+ INTF_OP ( intf_close, struct efi_download_file *, efi_download_close ),
+};
+
+/** EFI download data transfer interface descriptor */
+static struct interface_descriptor efi_download_file_xfer_desc =
+ INTF_DESC ( struct efi_download_file, xfer, efi_xfer_operations );
+
+/**
+ * Start downloading a file, and register callback functions to handle the
+ * download.
+ *
+ * @v This iPXE Download Protocol instance
+ * @v Url URL to download from
+ * @v DataCallback Callback that will be invoked when data arrives
+ * @v FinishCallback Callback that will be invoked when the download ends
+ * @v Context Context passed to the Data and Finish callbacks
+ * @v File Token that can be used to abort the download
+ * @ret Status EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_download_start ( IPXE_DOWNLOAD_PROTOCOL *This __unused,
+ CHAR8 *Url,
+ IPXE_DOWNLOAD_DATA_CALLBACK DataCallback,
+ IPXE_DOWNLOAD_FINISH_CALLBACK FinishCallback,
+ VOID *Context,
+ IPXE_DOWNLOAD_FILE *File ) {
+ struct efi_download_file *file;
+ int rc;
+
+ file = malloc ( sizeof ( struct efi_download_file ) );
+ if ( file == NULL ) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ intf_init ( &file->xfer, &efi_download_file_xfer_desc, NULL );
+ rc = xfer_open ( &file->xfer, LOCATION_URI_STRING, Url );
+ if ( rc ) {
+ free ( file );
+ return RC_TO_EFIRC ( rc );
+ }
+
+ file->pos = 0;
+ file->data_callback = DataCallback;
+ file->finish_callback = FinishCallback;
+ file->context = Context;
+ *File = file;
+ return EFI_SUCCESS;
+}
+
+/**
+ * Forcibly abort downloading a file that is currently in progress.
+ *
+ * It is not safe to call this function after the Finish callback has executed.
+ *
+ * @v This iPXE Download Protocol instance
+ * @v File Token obtained from Start
+ * @v Status Reason for aborting the download
+ * @ret Status EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_download_abort ( IPXE_DOWNLOAD_PROTOCOL *This __unused,
+ IPXE_DOWNLOAD_FILE File,
+ EFI_STATUS Status ) {
+ struct efi_download_file *file = File;
+
+ efi_download_close ( file, EFIRC_TO_RC ( Status ) );
+ return EFI_SUCCESS;
+}
+
+/**
+ * Poll for more data from iPXE. This function will invoke the registered
+ * callbacks if data is available or if downloads complete.
+ *
+ * @v This iPXE Download Protocol instance
+ * @ret Status EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_download_poll ( IPXE_DOWNLOAD_PROTOCOL *This __unused ) {
+ step();
+ return EFI_SUCCESS;
+}
+
+/** Publicly exposed iPXE download protocol */
+static IPXE_DOWNLOAD_PROTOCOL ipxe_download_protocol_interface = {
+ .Start = efi_download_start,
+ .Abort = efi_download_abort,
+ .Poll = efi_download_poll
+};
+
+/**
+ * Create a new device handle with a iPXE download protocol attached to it.
+ *
+ * @v device_handle Newly created device handle (output)
+ * @ret rc Return status code
+ */
+int efi_download_install ( EFI_HANDLE *device_handle ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_STATUS efirc;
+ EFI_HANDLE handle = NULL;
+ if (efi_loaded_image->DeviceHandle) { /* TODO: ensure handle is the NIC (maybe efi_image has a better way to indicate the handle doing SNP?) */
+ handle = efi_loaded_image->DeviceHandle;
+ }
+
+ DBG ( "Installing ipxe protocol interface (%p)... ",
+ &ipxe_download_protocol_interface );
+ efirc = bs->InstallMultipleProtocolInterfaces (
+ &handle,
+ &ipxe_download_protocol_guid,
+ &ipxe_download_protocol_interface,
+ NULL );
+ if ( efirc ) {
+ DBG ( "failed (%s)\n", efi_strerror ( efirc ) );
+ return EFIRC_TO_RC ( efirc );
+ }
+
+ DBG ( "success (%p)\n", handle );
+ *device_handle = handle;
+ return 0;
+}
+
+/**
+ * Remove the iPXE download protocol from the given handle, and if nothing
+ * else is attached, destroy the handle.
+ *
+ * @v device_handle EFI device handle to remove from
+ */
+void efi_download_uninstall ( EFI_HANDLE device_handle ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+
+ bs->UninstallMultipleProtocolInterfaces (
+ device_handle,
+ ipxe_download_protocol_guid,
+ ipxe_download_protocol_interface );
+}