Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / ipxe / src / interface / efi / efi_file.c
diff --git a/qemu/roms/ipxe/src/interface/efi/efi_file.c b/qemu/roms/ipxe/src/interface/efi/efi_file.c
new file mode 100644 (file)
index 0000000..2ef3c57
--- /dev/null
@@ -0,0 +1,714 @@
+/*
+ * 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 <strings.h>
+#include <errno.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/Protocol/DiskIo.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 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 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>" );
+}
+
+/**
+ * Find EFI file image
+ *
+ * @v wname            Filename
+ * @ret image          Image, or NULL
+ */
+static struct image * efi_file_find ( const CHAR16 *wname ) {
+       char name[ wcslen ( wname ) + 1 /* NUL */ ];
+       struct image *image;
+
+       /* Find image */
+       snprintf ( name, sizeof ( name ), "%ls", wname );
+       list_for_each_entry ( image, &images, list ) {
+               if ( strcasecmp ( image->name, name ) == 0 )
+                       return image;
+       }
+
+       return NULL;
+
+}
+
+/**
+ * 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 );
+       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 */
+       image = efi_file_find ( wname );
+       if ( ! image ) {
+               DBGC ( file, "EFIFILE \"%ls\" does not exist\n", wname );
+               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 ), efi_guid_ntoa ( 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 ), efi_guid_ntoa ( 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 ) {
+
+       DBGC ( &efi_file_root, "EFIFILE open volume\n" );
+       *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 ) {
+
+       DBGC ( &efi_file_root, "EFIFILE block %sreset\n",
+              ( extended ? "extended " : "" ) );
+       return 0;
+}
+
+/** Dummy block I/O read */
+static EFI_STATUS EFIAPI
+efi_block_io_read_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused, UINT32 MediaId,
+                          EFI_LBA lba, UINTN len, VOID *data ) {
+
+       DBGC ( &efi_file_root, "EFIFILE block read ID %#08x LBA %#08llx -> "
+              "%p+%zx\n", MediaId, ( ( unsigned long long ) lba ),
+              data, ( ( size_t ) len ) );
+       return EFI_NO_MEDIA;
+}
+
+/** Dummy block I/O write */
+static EFI_STATUS EFIAPI
+efi_block_io_write_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused,
+                           UINT32 MediaId, EFI_LBA lba, UINTN len,
+                           VOID *data ) {
+
+       DBGC ( &efi_file_root, "EFIFILE block write ID %#08x LBA %#08llx <- "
+              "%p+%zx\n", MediaId, ( ( unsigned long long ) lba ),
+              data, ( ( size_t ) len ) );
+       return EFI_NO_MEDIA;
+}
+
+/** Dummy block I/O flush */
+static EFI_STATUS EFIAPI
+efi_block_io_flush_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused ) {
+
+       DBGC ( &efi_file_root, "EFIFILE block flush\n" );
+       return 0;
+}
+
+/** Dummy block I/O media */
+static EFI_BLOCK_IO_MEDIA efi_block_io_media = {
+       .MediaId = EFI_MEDIA_ID_MAGIC,
+       .MediaPresent = TRUE,
+       .ReadOnly = TRUE,
+       .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,
+};
+
+/** Dummy disk I/O read */
+static EFI_STATUS EFIAPI
+efi_disk_io_read_disk ( EFI_DISK_IO_PROTOCOL *this __unused, UINT32 MediaId,
+                       UINT64 offset, UINTN len, VOID *data ) {
+
+       DBGC ( &efi_file_root, "EFIFILE disk read ID %#08x offset %#08llx -> "
+              "%p+%zx\n", MediaId, ( ( unsigned long long ) offset ),
+              data, ( ( size_t ) len ) );
+       return EFI_NO_MEDIA;
+}
+
+/** Dummy disk I/O write */
+static EFI_STATUS EFIAPI
+efi_disk_io_write_disk ( EFI_DISK_IO_PROTOCOL *this __unused, UINT32 MediaId,
+                        UINT64 offset, UINTN len, VOID *data ) {
+
+       DBGC ( &efi_file_root, "EFIFILE disk write ID %#08x offset %#08llx <- "
+              "%p+%zx\n", MediaId, ( ( unsigned long long ) offset ),
+              data, ( ( size_t ) len ) );
+       return EFI_NO_MEDIA;
+}
+
+/** Dummy EFI disk I/O protocol */
+static EFI_DISK_IO_PROTOCOL efi_disk_io_protocol = {
+       .Revision = EFI_DISK_IO_PROTOCOL_REVISION,
+       .ReadDisk = efi_disk_io_read_disk,
+       .WriteDisk = efi_disk_io_write_disk,
+};
+
+/**
+ * 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;
+       union {
+               EFI_DISK_IO_PROTOCOL *diskio;
+               void *interface;
+       } diskio;
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Install the simple file system protocol, block I/O
+        * protocol, and disk 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_disk_io_protocol_guid,
+                       &efi_disk_io_protocol,
+                       &efi_simple_file_system_protocol_guid,
+                       &efi_simple_file_system_protocol, NULL ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( handle, "Could not install simple file system "
+                      "protocols: %s\n", strerror ( rc ) );
+               goto err_install;
+       }
+
+       /* The FAT filesystem driver has a bug: if a block device
+        * contains no FAT filesystem but does have an
+        * EFI_SIMPLE_FILE_SYSTEM_PROTOCOL instance, the FAT driver
+        * will assume that it must have previously installed the
+        * EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.  This causes the FAT
+        * driver to claim control of our device, and to refuse to
+        * stop driving it, which prevents us from later uninstalling
+        * correctly.
+        *
+        * Work around this bug by opening the disk I/O protocol
+        * ourselves, thereby preventing the FAT driver from opening
+        * it.
+        *
+        * Note that the alternative approach of opening the block I/O
+        * protocol (and thereby in theory preventing DiskIo from
+        * attaching to the block I/O protocol) causes an endless loop
+        * of calls to our DRIVER_STOP method when starting the EFI
+        * shell.  I have no idea why this is.
+        */
+       if ( ( efirc = bs->OpenProtocol ( handle, &efi_disk_io_protocol_guid,
+                                         &diskio.interface, efi_image_handle,
+                                         handle,
+                                         EFI_OPEN_PROTOCOL_BY_DRIVER ) ) != 0){
+               rc = -EEFI ( efirc );
+               DBGC ( handle, "Could not open disk I/O protocol: %s\n",
+                      strerror ( rc ) );
+               DBGC_EFI_OPENERS ( handle, handle, &efi_disk_io_protocol_guid );
+               goto err_open;
+       }
+       assert ( diskio.diskio == &efi_disk_io_protocol );
+
+       return 0;
+
+       bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid,
+                           efi_image_handle, handle );
+ err_open:
+       bs->UninstallMultipleProtocolInterfaces (
+                       handle,
+                       &efi_simple_file_system_protocol_guid,
+                       &efi_simple_file_system_protocol,
+                       &efi_disk_io_protocol_guid,
+                       &efi_disk_io_protocol,
+                       &efi_block_io_protocol_guid,
+                       &efi_block_io_protocol, NULL );
+ err_install:
+       return rc;
+}
+
+/**
+ * Uninstall EFI simple file system protocol
+ *
+ * @v handle           EFI handle
+ */
+void efi_file_uninstall ( EFI_HANDLE handle ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Close our own disk I/O protocol */
+       bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid,
+                           efi_image_handle, handle );
+
+       /* 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.
+        */
+       if ( ( efirc = bs->UninstallMultipleProtocolInterfaces (
+                       handle,
+                       &efi_simple_file_system_protocol_guid,
+                       &efi_simple_file_system_protocol,
+                       &efi_disk_io_protocol_guid,
+                       &efi_disk_io_protocol,
+                       &efi_block_io_protocol_guid,
+                       &efi_block_io_protocol, NULL ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( handle, "Could not uninstall simple file system "
+                      "protocols: %s\n", strerror ( rc ) );
+               /* Oh dear */
+       }
+}