summaryrefslogtreecommitdiffstats
path: root/src/interface
diff options
context:
space:
mode:
Diffstat (limited to 'src/interface')
-rw-r--r--src/interface/efi/efi_download.c34
-rw-r--r--src/interface/efi/efi_file.c594
2 files changed, 607 insertions, 21 deletions
diff --git a/src/interface/efi/efi_download.c b/src/interface/efi/efi_download.c
index 250946e28..7b19ad3a2 100644
--- a/src/interface/efi/efi_download.c
+++ b/src/interface/efi/efi_download.c
@@ -25,7 +25,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/iobuf.h>
#include <ipxe/xfer.h>
#include <ipxe/efi/efi.h>
-#include <ipxe/efi/ipxe_download.h>
+#include <ipxe/efi/efi_download.h>
/** iPXE download protocol GUID */
static EFI_GUID ipxe_download_protocol_guid
@@ -187,47 +187,39 @@ static IPXE_DOWNLOAD_PROTOCOL ipxe_download_protocol_interface = {
};
/**
- * Create a new device handle with a iPXE download protocol attached to it.
+ * Install iPXE download protocol
*
- * @v device_handle Newly created device handle (output)
+ * @v handle EFI handle
* @ret rc Return status code
*/
-int efi_download_install ( EFI_HANDLE *device_handle ) {
+int efi_download_install ( EFI_HANDLE *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,
+ handle,
&ipxe_download_protocol_guid,
&ipxe_download_protocol_interface,
NULL );
if ( efirc ) {
- DBG ( "failed (%s)\n", efi_strerror ( efirc ) );
+ DBG ( "Could not install download protocol: %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.
+ * Uninstall iPXE download protocol
*
- * @v device_handle EFI device handle to remove from
+ * @v handle EFI handle
*/
-void efi_download_uninstall ( EFI_HANDLE device_handle ) {
+void efi_download_uninstall ( EFI_HANDLE handle ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
bs->UninstallMultipleProtocolInterfaces (
- device_handle,
- ipxe_download_protocol_guid,
- ipxe_download_protocol_interface );
+ handle,
+ &ipxe_download_protocol_guid,
+ &ipxe_download_protocol_interface, NULL );
}
diff --git a/src/interface/efi/efi_file.c b/src/interface/efi/efi_file.c
new file mode 100644
index 000000000..ffe25fd66
--- /dev/null
+++ b/src/interface/efi/efi_file.c
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/**
+ * @file
+ *
+ * EFI file protocols
+ *
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+#include <ipxe/image.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/Protocol/SimpleFileSystem.h>
+#include <ipxe/efi/Protocol/BlockIo.h>
+#include <ipxe/efi/Guid/FileInfo.h>
+#include <ipxe/efi/Guid/FileSystemInfo.h>
+#include <ipxe/efi/efi_strings.h>
+#include <ipxe/efi/efi_file.h>
+
+/** EFI simple file system protocol GUID */
+static EFI_GUID efi_simple_file_system_protocol_guid
+ = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
+
+/** EFI file information GUID */
+static EFI_GUID efi_file_info_id = EFI_FILE_INFO_ID;
+
+/** EFI file system information GUID */
+static EFI_GUID efi_file_system_info_id = EFI_FILE_SYSTEM_INFO_ID;
+
+/** EFI block I/O protocol GUID */
+static EFI_GUID efi_block_io_protocol_guid
+ = EFI_BLOCK_IO_PROTOCOL_GUID;
+
+/** EFI media ID */
+#define EFI_MEDIA_ID_MAGIC 0x69505845
+
+/** An image exposed as an EFI file */
+struct efi_file {
+ /** EFI file protocol */
+ EFI_FILE_PROTOCOL file;
+ /** Image */
+ struct image *image;
+ /** Current file position */
+ size_t pos;
+};
+
+static struct efi_file efi_file_root;
+
+/**
+ * Get EFI file name (for debugging)
+ *
+ * @v file EFI file
+ * @ret name Name
+ */
+static const char * efi_file_name ( struct efi_file *file ) {
+
+ return ( file->image ? file->image->name : "<root>" );
+}
+
+/**
+ * Open file
+ *
+ * @v this EFI file
+ * @ret new New EFI file
+ * @v wname Filename
+ * @v mode File mode
+ * @v attributes File attributes (for newly-created files)
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new,
+ CHAR16 *wname, UINT64 mode __unused,
+ UINT64 attributes __unused ) {
+ struct efi_file *file = container_of ( this, struct efi_file, file );
+ char name[ wcslen ( wname ) + 1 /* NUL */ ];
+ struct efi_file *new_file;
+ struct image *image;
+
+ /* Initial '\' indicates opening from the root directory */
+ while ( *wname == L'\\' ) {
+ file = &efi_file_root;
+ wname++;
+ }
+
+ /* Allow root directory itself to be opened */
+ if ( ( wname[0] == L'\0' ) || ( wname[0] == L'.' ) ) {
+ *new = &efi_file_root.file;
+ return 0;
+ }
+
+ /* Fail unless opening from the root */
+ if ( file->image ) {
+ DBGC ( file, "EFIFILE %s is not a directory\n",
+ efi_file_name ( file ) );
+ return EFI_NOT_FOUND;
+ }
+
+ /* Identify image */
+ snprintf ( name, sizeof ( name ), "%ls", wname );
+ image = find_image ( name );
+ if ( ! image ) {
+ DBGC ( file, "EFIFILE \"%s\" does not exist\n", name );
+ return EFI_NOT_FOUND;
+ }
+
+ /* Fail unless opening read-only */
+ if ( mode != EFI_FILE_MODE_READ ) {
+ DBGC ( file, "EFIFILE %s cannot be opened in mode %#08llx\n",
+ image->name, mode );
+ return EFI_WRITE_PROTECTED;
+ }
+
+ /* Allocate and initialise file */
+ new_file = zalloc ( sizeof ( *new_file ) );
+ memcpy ( &new_file->file, &efi_file_root.file,
+ sizeof ( new_file->file ) );
+ new_file->image = image_get ( image );
+ *new = &new_file->file;
+ DBGC ( new_file, "EFIFILE %s opened\n", efi_file_name ( new_file ) );
+
+ return 0;
+}
+
+/**
+ * Close file
+ *
+ * @v this EFI file
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI efi_file_close ( EFI_FILE_PROTOCOL *this ) {
+ struct efi_file *file = container_of ( this, struct efi_file, file );
+
+ /* Do nothing if this is the root */
+ if ( ! file->image )
+ return 0;
+
+ /* Close file */
+ DBGC ( file, "EFIFILE %s closed\n", efi_file_name ( file ) );
+ image_put ( file->image );
+ free ( file );
+
+ return 0;
+}
+
+/**
+ * Close and delete file
+ *
+ * @v this EFI file
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI efi_file_delete ( EFI_FILE_PROTOCOL *this ) {
+ struct efi_file *file = container_of ( this, struct efi_file, file );
+
+ DBGC ( file, "EFIFILE %s cannot be deleted\n", efi_file_name ( file ) );
+
+ /* Close file */
+ efi_file_close ( this );
+
+ /* Warn of failure to delete */
+ return EFI_WARN_DELETE_FAILURE;
+}
+
+/**
+ * Return variable-length data structure
+ *
+ * @v base Base data structure (starting with UINT64)
+ * @v base_len Length of base data structure
+ * @v name Name to append to base data structure
+ * @v len Length of data buffer
+ * @v data Data buffer
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS efi_file_varlen ( UINT64 *base, size_t base_len,
+ const char *name, UINTN *len, VOID *data ) {
+ size_t name_len;
+
+ /* Calculate structure length */
+ name_len = strlen ( name );
+ *base = ( base_len + ( name_len + 1 /* NUL */ ) * sizeof ( wchar_t ) );
+ if ( *len < *base ) {
+ *len = *base;
+ return EFI_BUFFER_TOO_SMALL;
+ }
+
+ /* Copy data to buffer */
+ *len = *base;
+ memcpy ( data, base, base_len );
+ efi_snprintf ( ( data + base_len ), ( name_len + 1 /* NUL */ ),
+ "%s", name );
+
+ return 0;
+}
+
+/**
+ * Return file information structure
+ *
+ * @v image Image, or NULL for the root directory
+ * @v len Length of data buffer
+ * @v data Data buffer
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS efi_file_info ( struct image *image, UINTN *len,
+ VOID *data ) {
+ EFI_FILE_INFO info;
+ const char *name;
+
+ /* Populate file information */
+ memset ( &info, 0, sizeof ( info ) );
+ if ( image ) {
+ info.FileSize = image->len;
+ info.PhysicalSize = image->len;
+ info.Attribute = EFI_FILE_READ_ONLY;
+ name = image->name;
+ } else {
+ info.Attribute = ( EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY );
+ name = "";
+ }
+
+ return efi_file_varlen ( &info.Size, SIZE_OF_EFI_FILE_INFO, name,
+ len, data );
+}
+
+/**
+ * Read directory entry
+ *
+ * @v file EFI file
+ * @v len Length to read
+ * @v data Data buffer
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS efi_file_read_dir ( struct efi_file *file, UINTN *len,
+ VOID *data ) {
+ EFI_STATUS efirc;
+ struct image *image;
+ unsigned int index;
+
+ /* Construct directory entry at current position */
+ index = file->pos;
+ for_each_image ( image ) {
+ if ( index-- == 0 ) {
+ efirc = efi_file_info ( image, len, data );
+ if ( efirc == 0 )
+ file->pos++;
+ return efirc;
+ }
+ }
+
+ /* No more entries */
+ *len = 0;
+ return 0;
+}
+
+/**
+ * Read from file
+ *
+ * @v this EFI file
+ * @v len Length to read
+ * @v data Data buffer
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI efi_file_read ( EFI_FILE_PROTOCOL *this,
+ UINTN *len, VOID *data ) {
+ struct efi_file *file = container_of ( this, struct efi_file, file );
+ size_t remaining;
+
+ /* If this is the root directory, then construct a directory entry */
+ if ( ! file->image )
+ return efi_file_read_dir ( file, len, data );
+
+ /* Read from the file */
+ remaining = ( file->image->len - file->pos );
+ if ( *len > remaining )
+ *len = remaining;
+ DBGC ( file, "EFIFILE %s read [%#08zx,%#08zx)\n",
+ efi_file_name ( file ), file->pos,
+ ( ( size_t ) ( file->pos + *len ) ) );
+ copy_from_user ( data, file->image->data, file->pos, *len );
+ file->pos += *len;
+ return 0;
+}
+
+/**
+ * Write to file
+ *
+ * @v this EFI file
+ * @v len Length to write
+ * @v data Data buffer
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI efi_file_write ( EFI_FILE_PROTOCOL *this,
+ UINTN *len, VOID *data __unused ) {
+ struct efi_file *file = container_of ( this, struct efi_file, file );
+
+ DBGC ( file, "EFIFILE %s cannot write [%#08zx, %#08zx)\n",
+ efi_file_name ( file ), file->pos,
+ ( ( size_t ) ( file->pos + *len ) ) );
+ return EFI_WRITE_PROTECTED;
+}
+
+/**
+ * Set file position
+ *
+ * @v this EFI file
+ * @v position New file position
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI efi_file_set_position ( EFI_FILE_PROTOCOL *this,
+ UINT64 position ) {
+ struct efi_file *file = container_of ( this, struct efi_file, file );
+
+ /* If this is the root directory, reset to the start */
+ if ( ! file->image ) {
+ DBGC ( file, "EFIFILE root directory rewound\n" );
+ file->pos = 0;
+ return 0;
+ }
+
+ /* Check for the magic end-of-file value */
+ if ( position == 0xffffffffffffffffULL )
+ position = file->image->len;
+
+ /* Fail if we attempt to seek past the end of the file (since
+ * we do not support writes).
+ */
+ if ( position > file->image->len ) {
+ DBGC ( file, "EFIFILE %s cannot seek to %#08llx of %#08zx\n",
+ efi_file_name ( file ), position, file->image->len );
+ return EFI_UNSUPPORTED;
+ }
+
+ /* Set position */
+ file->pos = position;
+ DBGC ( file, "EFIFILE %s position set to %#08zx\n",
+ efi_file_name ( file ), file->pos );
+
+ return 0;
+}
+
+/**
+ * Get file position
+ *
+ * @v this EFI file
+ * @ret position New file position
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI efi_file_get_position ( EFI_FILE_PROTOCOL *this,
+ UINT64 *position ) {
+ struct efi_file *file = container_of ( this, struct efi_file, file );
+
+ *position = file->pos;
+ return 0;
+}
+
+/**
+ * Get file information
+ *
+ * @v this EFI file
+ * @v type Type of information
+ * @v len Buffer size
+ * @v data Buffer
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI efi_file_get_info ( EFI_FILE_PROTOCOL *this,
+ EFI_GUID *type,
+ UINTN *len, VOID *data ) {
+ struct efi_file *file = container_of ( this, struct efi_file, file );
+ EFI_FILE_SYSTEM_INFO fsinfo;
+ struct image *image;
+
+ /* Determine information to return */
+ if ( memcmp ( type, &efi_file_info_id, sizeof ( *type ) ) == 0 ) {
+
+ /* Get file information */
+ DBGC ( file, "EFIFILE %s get file information\n",
+ efi_file_name ( file ) );
+ return efi_file_info ( file->image, len, data );
+
+ } else if ( memcmp ( type, &efi_file_system_info_id,
+ sizeof ( *type ) ) == 0 ) {
+
+ /* Get file system information */
+ DBGC ( file, "EFIFILE %s get file system information\n",
+ efi_file_name ( file ) );
+ memset ( &fsinfo, 0, sizeof ( fsinfo ) );
+ fsinfo.ReadOnly = 1;
+ for_each_image ( image )
+ fsinfo.VolumeSize += image->len;
+ return efi_file_varlen ( &fsinfo.Size,
+ SIZE_OF_EFI_FILE_SYSTEM_INFO, "iPXE",
+ len, data );
+ } else {
+
+ DBGC ( file, "EFIFILE %s cannot get information of type %s\n",
+ efi_file_name ( file ),
+ uuid_ntoa ( ( union uuid * ) type ) );
+ return EFI_UNSUPPORTED;
+ }
+}
+
+/**
+ * Set file information
+ *
+ * @v this EFI file
+ * @v type Type of information
+ * @v len Buffer size
+ * @v data Buffer
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_file_set_info ( EFI_FILE_PROTOCOL *this, EFI_GUID *type,
+ UINTN len __unused, VOID *data __unused ) {
+ struct efi_file *file = container_of ( this, struct efi_file, file );
+
+ DBGC ( file, "EFIFILE %s cannot set information of type %s\n",
+ efi_file_name ( file ), uuid_ntoa ( ( union uuid * ) type ) );
+ return EFI_WRITE_PROTECTED;
+}
+
+/**
+ * Flush file modified data
+ *
+ * @v this EFI file
+ * @v type Type of information
+ * @v len Buffer size
+ * @v data Buffer
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI efi_file_flush ( EFI_FILE_PROTOCOL *this ) {
+ struct efi_file *file = container_of ( this, struct efi_file, file );
+
+ DBGC ( file, "EFIFILE %s flushed\n", efi_file_name ( file ) );
+ return 0;
+}
+
+/** Root directory */
+static struct efi_file efi_file_root = {
+ .file = {
+ .Revision = EFI_FILE_PROTOCOL_REVISION,
+ .Open = efi_file_open,
+ .Close = efi_file_close,
+ .Delete = efi_file_delete,
+ .Read = efi_file_read,
+ .Write = efi_file_write,
+ .GetPosition = efi_file_get_position,
+ .SetPosition = efi_file_set_position,
+ .GetInfo = efi_file_get_info,
+ .SetInfo = efi_file_set_info,
+ .Flush = efi_file_flush,
+ },
+ .image = NULL,
+};
+
+/**
+ * Open root directory
+ *
+ * @v filesystem EFI simple file system
+ * @ret file EFI file handle
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_file_open_volume ( EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *filesystem __unused,
+ EFI_FILE_PROTOCOL **file ) {
+
+ *file = &efi_file_root.file;
+ return 0;
+}
+
+/** EFI simple file system protocol */
+static EFI_SIMPLE_FILE_SYSTEM_PROTOCOL efi_simple_file_system_protocol = {
+ .Revision = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION,
+ .OpenVolume = efi_file_open_volume,
+};
+
+/** Dummy block I/O reset */
+static EFI_STATUS EFIAPI
+efi_block_io_reset ( EFI_BLOCK_IO_PROTOCOL *this __unused,
+ BOOLEAN extended __unused ) {
+ return 0;
+}
+
+/** Dummy block I/O read */
+static EFI_STATUS EFIAPI
+efi_block_io_read_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused,
+ UINT32 MediaId __unused, EFI_LBA lba __unused,
+ UINTN len __unused, VOID *data __unused ) {
+ return EFI_DEVICE_ERROR;
+}
+
+/** Dummy block I/O write */
+static EFI_STATUS EFIAPI
+efi_block_io_write_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused,
+ UINT32 MediaId __unused, EFI_LBA lba __unused,
+ UINTN len __unused, VOID *data __unused ) {
+ return EFI_DEVICE_ERROR;
+}
+
+/** Dummy block I/O flush */
+static EFI_STATUS EFIAPI
+efi_block_io_flush_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused ) {
+ return 0;
+}
+
+/** Dummy block I/O media */
+static EFI_BLOCK_IO_MEDIA efi_block_io_media = {
+ .MediaId = EFI_MEDIA_ID_MAGIC,
+ .MediaPresent = 1,
+ .ReadOnly = 1,
+ .BlockSize = 1,
+};
+
+/** Dummy EFI block I/O protocol */
+static EFI_BLOCK_IO_PROTOCOL efi_block_io_protocol = {
+ .Revision = EFI_BLOCK_IO_PROTOCOL_REVISION,
+ .Media = &efi_block_io_media,
+ .Reset = efi_block_io_reset,
+ .ReadBlocks = efi_block_io_read_blocks,
+ .WriteBlocks = efi_block_io_write_blocks,
+ .FlushBlocks = efi_block_io_flush_blocks,
+};
+
+/**
+ * Install EFI simple file system protocol
+ *
+ * @v handle EFI handle
+ * @ret rc Return status code
+ */
+int efi_file_install ( EFI_HANDLE *handle ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_STATUS efirc;
+
+ /* Install the simple file system protocol and the block I/O
+ * protocol. We don't have a block device, but large parts of
+ * the EDK2 codebase make the assumption that file systems are
+ * normally attached to block devices, and so we create a
+ * dummy block device on the same handle just to keep things
+ * looking normal.
+ */
+ if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
+ handle,
+ &efi_block_io_protocol_guid,
+ &efi_block_io_protocol,
+ &efi_simple_file_system_protocol_guid,
+ &efi_simple_file_system_protocol, NULL ) ) != 0 ) {
+ DBGC ( handle, "Could not install simple file system protocol: "
+ "%s\n", efi_strerror ( efirc ) );
+ return EFIRC_TO_RC ( efirc );
+ }
+
+ return 0;
+}
+
+/**
+ * Uninstall EFI simple file system protocol
+ *
+ * @v handle EFI handle
+ */
+void efi_file_uninstall ( EFI_HANDLE handle ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+
+ /* We must install the file system protocol first, since
+ * otherwise the EDK2 code will attempt to helpfully uninstall
+ * it when the block I/O protocol is uninstalled, leading to a
+ * system lock-up.
+ */
+ bs->UninstallMultipleProtocolInterfaces (
+ handle,
+ &efi_simple_file_system_protocol_guid,
+ &efi_simple_file_system_protocol,
+ &efi_block_io_protocol_guid,
+ &efi_block_io_protocol, NULL );
+}