diff --git a/src/include/ipxe/efi/Protocol/DiskIo.h b/src/include/ipxe/efi/Protocol/DiskIo.h
new file mode 100644
index 000000000..1b47ce520
--- /dev/null
+++ b/src/include/ipxe/efi/Protocol/DiskIo.h
@@ -0,0 +1,119 @@
+/** @file
+ Disk IO protocol as defined in the UEFI 2.0 specification.
+
+ The Disk IO protocol is used to convert block oriented devices into byte
+ oriented devices. The Disk IO protocol is intended to layer on top of the
+ Block IO protocol.
+
+ Copyright (c) 2006 - 2008, Intel Corporation. All rights reserved.
+ This program and the accompanying materials
+ are licensed and made available under the terms and conditions of the BSD License
+ which accompanies this distribution. The full text of the license may be found at
+ http://opensource.org/licenses/bsd-license.php
+
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
+ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+
+**/
+
+#ifndef __DISK_IO_H__
+#define __DISK_IO_H__
+
+FILE_LICENCE ( BSD3 );
+
+#define EFI_DISK_IO_PROTOCOL_GUID \
+ { \
+ 0xce345171, 0xba0b, 0x11d2, {0x8e, 0x4f, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b } \
+ }
+
+///
+/// Protocol GUID name defined in EFI1.1.
+///
+#define DISK_IO_PROTOCOL EFI_DISK_IO_PROTOCOL_GUID
+
+typedef struct _EFI_DISK_IO_PROTOCOL EFI_DISK_IO_PROTOCOL;
+
+///
+/// Protocol defined in EFI1.1.
+///
+typedef EFI_DISK_IO_PROTOCOL EFI_DISK_IO;
+
+/**
+ Read BufferSize bytes from Offset into Buffer.
+
+ @param This Protocol instance pointer.
+ @param MediaId Id of the media, changes every time the media is replaced.
+ @param Offset The starting byte offset to read from
+ @param BufferSize Size of Buffer
+ @param Buffer Buffer containing read data
+
+ @retval EFI_SUCCESS The data was read correctly from the device.
+ @retval EFI_DEVICE_ERROR The device reported an error while performing the read.
+ @retval EFI_NO_MEDIA There is no media in the device.
+ @retval EFI_MEDIA_CHNAGED The MediaId does not matched the current device.
+ @retval EFI_INVALID_PARAMETER The read request contains device addresses that are not
+ valid for the device.
+
+**/
+typedef
+EFI_STATUS
+(EFIAPI *EFI_DISK_READ)(
+ IN EFI_DISK_IO_PROTOCOL *This,
+ IN UINT32 MediaId,
+ IN UINT64 Offset,
+ IN UINTN BufferSize,
+ OUT VOID *Buffer
+ );
+
+/**
+ Writes a specified number of bytes to a device.
+
+ @param This Indicates a pointer to the calling context.
+ @param MediaId ID of the medium to be written.
+ @param Offset The starting byte offset on the logical block I/O device to write.
+ @param BufferSize The size in bytes of Buffer. The number of bytes to write to the device.
+ @param Buffer A pointer to the buffer containing the data to be written.
+
+ @retval EFI_SUCCESS The data was written correctly to the device.
+ @retval EFI_WRITE_PROTECTED The device can not be written to.
+ @retval EFI_DEVICE_ERROR The device reported an error while performing the write.
+ @retval EFI_NO_MEDIA There is no media in the device.
+ @retval EFI_MEDIA_CHNAGED The MediaId does not matched the current device.
+ @retval EFI_INVALID_PARAMETER The write request contains device addresses that are not
+ valid for the device.
+
+**/
+typedef
+EFI_STATUS
+(EFIAPI *EFI_DISK_WRITE)(
+ IN EFI_DISK_IO_PROTOCOL *This,
+ IN UINT32 MediaId,
+ IN UINT64 Offset,
+ IN UINTN BufferSize,
+ IN VOID *Buffer
+ );
+
+#define EFI_DISK_IO_PROTOCOL_REVISION 0x00010000
+
+///
+/// Revision defined in EFI1.1
+///
+#define EFI_DISK_IO_INTERFACE_REVISION EFI_DISK_IO_PROTOCOL_REVISION
+
+///
+/// This protocol is used to abstract Block I/O interfaces.
+///
+struct _EFI_DISK_IO_PROTOCOL {
+ ///
+ /// The revision to which the disk I/O interface adheres. All future
+ /// revisions must be backwards compatible. If a future version is not
+ /// backwards compatible, it is not the same GUID.
+ ///
+ UINT64 Revision;
+ EFI_DISK_READ ReadDisk;
+ EFI_DISK_WRITE WriteDisk;
+};
+
+extern EFI_GUID gEfiDiskIoProtocolGuid;
+
+#endif
diff --git a/src/interface/efi/efi_file.c b/src/interface/efi/efi_file.c
index 4c482d2e5..f8713750c 100644
--- a/src/interface/efi/efi_file.c
+++ b/src/interface/efi/efi_file.c
@@ -36,6 +36,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include
#include
#include
+#include
#include
#include
#include
@@ -55,6 +56,10 @@ static EFI_GUID efi_file_system_info_id = EFI_FILE_SYSTEM_INFO_ID;
static EFI_GUID efi_block_io_protocol_guid
= EFI_BLOCK_IO_PROTOCOL_GUID;
+/** EFI disk I/O protocol GUID */
+static EFI_GUID efi_disk_io_protocol_guid
+ = EFI_DISK_IO_PROTOCOL_GUID;
+
/** EFI media ID */
#define EFI_MEDIA_ID_MAGIC 0x69505845
@@ -506,7 +511,7 @@ 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;
+ return EFI_NO_MEDIA;
}
/** Dummy block I/O write */
@@ -514,7 +519,7 @@ 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;
+ return EFI_NO_MEDIA;
}
/** Dummy block I/O flush */
@@ -541,6 +546,29 @@ static EFI_BLOCK_IO_PROTOCOL efi_block_io_protocol = {
.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 __unused, UINT64 offset __unused,
+ UINTN len __unused, VOID *data __unused ) {
+ 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 __unused, UINT64 offset __unused,
+ UINTN len __unused, VOID *data __unused ) {
+ 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
*
@@ -549,29 +577,79 @@ static EFI_BLOCK_IO_PROTOCOL efi_block_io_protocol = {
*/
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 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.
+ /* 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 protocol: "
- "%s\n", strerror ( rc ) );
- return rc;
+ 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 ) );
+ 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;
}
/**
@@ -581,16 +659,29 @@ int efi_file_install ( EFI_HANDLE *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.
*/
- bs->UninstallMultipleProtocolInterfaces (
+ 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 );
+ &efi_block_io_protocol, NULL ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( handle, "Could not uninstall simple file system "
+ "protocols: %s\n", strerror ( rc ) );
+ /* Oh dear */
+ }
}