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 */ + } }