From: jljusten Date: Mon, 8 Oct 2012 07:32:59 +0000 (+0000) Subject: OvmfPkg: introduce virtio-blk driver X-Git-Tag: edk2-stable201903~12992 X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=commitdiff_plain;h=fd51d7599173164be618d26e9d3673c09f60dbb2 OvmfPkg: introduce virtio-blk driver Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Laszlo Ersek Reviewed-by: Jordan Justen git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@13798 6f19259b-4bc3-4df7-8a09-765794883524 --- diff --git a/OvmfPkg/OvmfPkgIa32.dsc b/OvmfPkg/OvmfPkgIa32.dsc index e1cf6f4e25..16037b3772 100644 --- a/OvmfPkg/OvmfPkgIa32.dsc +++ b/OvmfPkg/OvmfPkgIa32.dsc @@ -407,6 +407,7 @@ } OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf + OvmfPkg/VirtioBlkDxe/VirtioBlk.inf OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf { PlatformFvbLib|OvmfPkg/Library/EmuVariableFvbLib/EmuVariableFvbLib.inf diff --git a/OvmfPkg/OvmfPkgIa32.fdf b/OvmfPkg/OvmfPkgIa32.fdf index 563b1bb650..be5e6d8deb 100644 --- a/OvmfPkg/OvmfPkgIa32.fdf +++ b/OvmfPkg/OvmfPkgIa32.fdf @@ -180,6 +180,7 @@ INF MdeModulePkg/Universal/Metronome/Metronome.inf INF PcAtChipsetPkg/PcatRealTimeClockRuntimeDxe/PcatRealTimeClockRuntimeDxe.inf INF OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf +INF OvmfPkg/VirtioBlkDxe/VirtioBlk.inf INF OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf INF MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf diff --git a/OvmfPkg/OvmfPkgIa32X64.dsc b/OvmfPkg/OvmfPkgIa32X64.dsc index 40fd5e97b2..f73f079d07 100644 --- a/OvmfPkg/OvmfPkgIa32X64.dsc +++ b/OvmfPkg/OvmfPkgIa32X64.dsc @@ -414,6 +414,7 @@ } OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf + OvmfPkg/VirtioBlkDxe/VirtioBlk.inf OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf { PlatformFvbLib|OvmfPkg/Library/EmuVariableFvbLib/EmuVariableFvbLib.inf diff --git a/OvmfPkg/OvmfPkgIa32X64.fdf b/OvmfPkg/OvmfPkgIa32X64.fdf index 4721368824..07faec49fe 100644 --- a/OvmfPkg/OvmfPkgIa32X64.fdf +++ b/OvmfPkg/OvmfPkgIa32X64.fdf @@ -180,6 +180,7 @@ INF MdeModulePkg/Universal/Metronome/Metronome.inf INF PcAtChipsetPkg/PcatRealTimeClockRuntimeDxe/PcatRealTimeClockRuntimeDxe.inf INF OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf +INF OvmfPkg/VirtioBlkDxe/VirtioBlk.inf INF OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf INF MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf diff --git a/OvmfPkg/OvmfPkgX64.dsc b/OvmfPkg/OvmfPkgX64.dsc index c61d41dccb..97d4ec8a93 100644 --- a/OvmfPkg/OvmfPkgX64.dsc +++ b/OvmfPkg/OvmfPkgX64.dsc @@ -412,6 +412,7 @@ } OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf + OvmfPkg/VirtioBlkDxe/VirtioBlk.inf OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf { PlatformFvbLib|OvmfPkg/Library/EmuVariableFvbLib/EmuVariableFvbLib.inf diff --git a/OvmfPkg/OvmfPkgX64.fdf b/OvmfPkg/OvmfPkgX64.fdf index d74043dfc7..615bc0a1c2 100644 --- a/OvmfPkg/OvmfPkgX64.fdf +++ b/OvmfPkg/OvmfPkgX64.fdf @@ -180,6 +180,7 @@ INF MdeModulePkg/Universal/Metronome/Metronome.inf INF PcAtChipsetPkg/PcatRealTimeClockRuntimeDxe/PcatRealTimeClockRuntimeDxe.inf INF OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf +INF OvmfPkg/VirtioBlkDxe/VirtioBlk.inf INF OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf INF MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf diff --git a/OvmfPkg/VirtioBlkDxe/Virtio.h b/OvmfPkg/VirtioBlkDxe/Virtio.h new file mode 100644 index 0000000000..5e2e8f4c6f --- /dev/null +++ b/OvmfPkg/VirtioBlkDxe/Virtio.h @@ -0,0 +1,176 @@ +/** @file + + Type and macro definitions corresponding to the virtio-0.9.5 specification. + + Copyright (C) 2012, Red Hat, Inc. + + 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. + +**/ + +#include + + +// +// Data in the communication area is defined as packed and accessed as +// volatile. +// +// Some structures contain arrays with dynamically determined size. In such +// cases the array and its sibling fields are replaced with pointers. +// +// All indices (variables and fields named *Idx) are free-running and wrap +// around after 0xFFFF. The queue size reported by the host is always an +// integral power of 2, not greater than 32768. Actual array indices are +// consistently calculated by taking the remainder of a given Idx object modulo +// QueueSize. Since 0x10000 is an integral multiple of the QueueSize, UINT16 +// wraparound is a correct wraparound modulo QueueSize too (it doesn't offset +// the remainder class). +// +// virtio-0.9.5, 2.3.4 Available Ring +// +#define VRING_AVAIL_F_NO_INTERRUPT BIT0 + +typedef struct { + volatile UINT16 *Flags; + volatile UINT16 *Idx; + + volatile UINT16 *Ring; // QueueSize elements + volatile UINT16 *UsedEvent; // unused as per negotiation +} VRING_AVAIL; + + +// +// virtio-0.9.5, 2.3.5 Used Ring +// +#define VRING_USED_F_NO_NOTIFY BIT0 + +#pragma pack(1) +typedef struct { + UINT32 Id; + UINT32 Len; +} VRING_USED_ELEM; +#pragma pack() + +typedef struct { + volatile UINT16 *Flags; + volatile UINT16 *Idx; + volatile VRING_USED_ELEM *UsedElem; // QueueSize elements + volatile UINT16 *AvailEvent; // unused as per negotiation +} VRING_USED; + + +// +// virtio-0.9.5, 2.3.2 Descriptor Table +// +#define VRING_DESC_F_NEXT BIT0 // more descriptors in this request +#define VRING_DESC_F_WRITE BIT1 // buffer to be written *by the host* +#define VRING_DESC_F_INDIRECT BIT2 // unused + +#pragma pack(1) +typedef struct { + UINT64 Addr; + UINT32 Len; + UINT16 Flags; + UINT16 Next; +} VRING_DESC; +#pragma pack() + +typedef struct { + UINTN NumPages; + VOID *Base; // deallocate only this field + volatile VRING_DESC *Desc; // QueueSize elements + VRING_AVAIL Avail; + VRING_USED Used; + UINT16 QueueSize; +} VRING; + + +// +// virtio-0.9.5, 2.2.2 Virtio Header -- no MSI-X +// virtio-0.9.5, Appendix D +// +#pragma pack(1) +typedef struct { + UINT32 VhdrDeviceFeatureBits; + UINT32 VhdrGuestFeatureBits; + UINT32 VhdrQueueAddress; + UINT16 VhdrQueueSize; + UINT16 VhdrQueueSelect; + UINT16 VhdrQueueNotify; + UINT8 VhdrDeviceStatus; + UINT8 VhdrISR; + UINT64 VhdrCapacity; + UINT32 VhdrSizeMax; + UINT32 VhdrSegMax; + UINT16 VhdrCylinders; + UINT8 VhdrHeads; + UINT8 VhdrSectors; + UINT32 VhdrBlkSize; +} VBLK_HDR; +#pragma pack() + +#define OFFSET_OF_VHDR(Field) ((UINTN)(UINT8 *)&((VBLK_HDR *) 0)->Field) +#define SIZE_OF_VHDR(Field) (sizeof ((VBLK_HDR *) 0)->Field) + + +// +// virtio-0.9.5, 2.2.2.1 Device Status +// +#define VSTAT_ACK BIT0 +#define VSTAT_DRIVER BIT1 +#define VSTAT_DRIVER_OK BIT2 +#define VSTAT_FAILED BIT7 + +// +// virtio-0.9.5, Appendix B: Reserved (Device-Independent) Feature Bits +// +#define VIRTIO_F_NOTIFY_ON_EMPTY BIT24 +#define VIRTIO_F_RING_INDIRECT_DESC BIT28 +#define VIRTIO_F_RING_EVENT_IDX BIT29 + +// +// virtio-0.9.5, Appendix D: Block Device +// +#define VIRTIO_BLK_F_BARRIER BIT0 +#define VIRTIO_BLK_F_SIZE_MAX BIT1 +#define VIRTIO_BLK_F_SEG_MAX BIT2 +#define VIRTIO_BLK_F_GEOMETRY BIT4 +#define VIRTIO_BLK_F_RO BIT5 +#define VIRTIO_BLK_F_BLK_SIZE BIT6 // treated as "logical block size" in + // practice; actual host side implementation + // negotiates "optimal" block size + // separately +#define VIRTIO_BLK_F_SCSI BIT7 +#define VIRTIO_BLK_F_FLUSH BIT9 // identical to "write cache enabled" + + +// +// We keep the status byte separate from the rest of the virtio-blk request +// header. See description of historical scattering at the end of Appendix D: +// we're going to put the status byte in a separate VRING_DESC. +// +#pragma pack(1) +typedef struct { + UINT32 Type; + UINT32 IoPrio; + UINT64 Sector; +} VIRTIO_BLK_REQ; +#pragma pack() + +#define VIRTIO_BLK_T_IN 0x00000000 +#define VIRTIO_BLK_T_OUT 0x00000001 +#define VIRTIO_BLK_T_SCSI_CMD 0x00000002 +#define VIRTIO_BLK_T_SCSI_CMD_OUT 0x00000003 +#define VIRTIO_BLK_T_FLUSH 0x00000004 +#define VIRTIO_BLK_T_FLUSH_OUT 0x00000005 +#define VIRTIO_BLK_T_BARRIER BIT31 + +#define VIRTIO_BLK_S_OK 0x00 +#define VIRTIO_BLK_S_IOERR 0x01 +#define VIRTIO_BLK_S_UNSUPP 0x02 diff --git a/OvmfPkg/VirtioBlkDxe/VirtioBlk.c b/OvmfPkg/VirtioBlkDxe/VirtioBlk.c new file mode 100644 index 0000000000..9969aab66f --- /dev/null +++ b/OvmfPkg/VirtioBlkDxe/VirtioBlk.c @@ -0,0 +1,1432 @@ +/** @file + + This driver produces Block I/O Protocol instances for virtio-blk devices. + + The implementation is basic: + + - No attach/detach (ie. removable media). + + - Although the non-blocking interfaces of EFI_BLOCK_IO2_PROTOCOL could be a + good match for multiple in-flight virtio-blk requests, we stick to + synchronous requests and EFI_BLOCK_IO_PROTOCOL for now. + + Copyright (C) 2012, Red Hat, Inc. + + 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. + +**/ + +#include +#include +#include +#include +#include +#include + +#include "VirtioBlk.h" + +/** + + Write a word into Region 0 of the device specified by PciIo. + + Region 0 must be an iomem region. This is an internal function for the + VIRTIO_CFG_WRITE() macro below. + + @param[in] PciIo Target PCI device. + + @param[in] FieldOffset Destination offset. + + @param[in] FieldSize Destination field size, must be in { 1, 2, 4, 8 }. + + @param[in] Value Little endian value to write, converted to UINT64. + The least significant FieldSize bytes will be used. + + + @return Status code returned by PciIo->Io.Write(). + +**/ +STATIC +EFIAPI +EFI_STATUS +VirtioWrite ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINTN FieldOffset, + IN UINTN FieldSize, + IN UINT64 Value + ) +{ + UINTN Count; + EFI_PCI_IO_PROTOCOL_WIDTH Width; + + Count = 1; + switch (FieldSize) { + case 1: + Width = EfiPciIoWidthUint8; + break; + + case 2: + Width = EfiPciIoWidthUint16; + break; + + case 8: + Count = 2; + // fall through + + case 4: + Width = EfiPciIoWidthUint32; + break; + + default: + ASSERT (FALSE); + } + + return PciIo->Io.Write ( + PciIo, + Width, + PCI_BAR_IDX0, + FieldOffset, + Count, + &Value + ); +} + + +/** + + Read a word from Region 0 of the device specified by PciIo. + + Region 0 must be an iomem region. This is an internal function for the + VIRTIO_CFG_READ() macro below. + + @param[in] PciIo Source PCI device. + + @param[in] FieldOffset Source offset. + + @param[in] FieldSize Source field size, must be in { 1, 2, 4, 8 }. + + @param[in] BufferSize Number of bytes available in the target buffer. Must + equal FieldSize. + + @param[out] Buffer Target buffer. + + + @return Status code returned by PciIo->Io.Read(). + +**/ +STATIC +EFIAPI +EFI_STATUS +VirtioRead ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINTN FieldOffset, + IN UINTN FieldSize, + IN UINTN BufferSize, + OUT VOID *Buffer + ) +{ + UINTN Count; + EFI_PCI_IO_PROTOCOL_WIDTH Width; + + ASSERT (FieldSize == BufferSize); + + Count = 1; + switch (FieldSize) { + case 1: + Width = EfiPciIoWidthUint8; + break; + + case 2: + Width = EfiPciIoWidthUint16; + break; + + case 8: + Count = 2; + // fall through + + case 4: + Width = EfiPciIoWidthUint32; + break; + + default: + ASSERT (FALSE); + } + + return PciIo->Io.Read ( + PciIo, + Width, + PCI_BAR_IDX0, + FieldOffset, + Count, + Buffer + ); +} + + +/** + + Convenience macros to read and write region 0 IO space elements of the + virtio-blk PCI device, for configuration purposes. + + The following macros make it possible to specify only the "core parameters" + for such accesses and to derive the rest. By the time VIRTIO_CFG_WRITE() + returns, the transaction will have been completed. + + @param[in] Dev Pointer to the VBLK_DEV structure whose PCI IO space + we're accessing. Dev->PciIo must be valid. + + @param[in] Field A field name from VBLK_HDR, identifying the virtio-blk + configuration item to access. + + @param[in] Value (VIRTIO_CFG_WRITE() only.) The value to write to the + selected configuration item. + + @param[out] Pointer (VIRTIO_CFG_READ() only.) The object to receive the + value read from the configuration item. Its type must be + one of UINT8, UINT16, UINT32, UINT64. + + + @return Status code returned by VirtioWrite() / VirtioRead(). + +**/ + +#define VIRTIO_CFG_WRITE(Dev, Field, Value) (VirtioWrite ( \ + (Dev)->PciIo, \ + OFFSET_OF_VHDR (Field), \ + SIZE_OF_VHDR (Field), \ + (Value) \ + )) + +#define VIRTIO_CFG_READ(Dev, Field, Pointer) (VirtioRead ( \ + (Dev)->PciIo, \ + OFFSET_OF_VHDR (Field), \ + SIZE_OF_VHDR (Field), \ + sizeof *(Pointer), \ + (Pointer) \ + )) + + +// +// UEFI Spec 2.3.1 + Errata C, 12.8 EFI Block I/O Protocol +// Driver Writer's Guide for UEFI 2.3.1 v1.01, +// 24.2 Block I/O Protocol Implementations +// +EFI_STATUS +EFIAPI +VirtioBlkReset ( + IN EFI_BLOCK_IO_PROTOCOL *This, + IN BOOLEAN ExtendedVerification + ) +{ + // + // If we managed to initialize and install the driver, then the device is + // working correctly. + // + return EFI_SUCCESS; +} + +/** + + Verify correctness of the read/write (not flush) request submitted to the + EFI_BLOCK_IO_PROTOCOL instance. + + This function provides most verification steps described in: + + UEFI Spec 2.3.1 + Errata C, 12.8 EFI Block I/O Protocol, 12.8 EFI Block I/O + Protocol, + - EFI_BLOCK_IO_PROTOCOL.ReadBlocks() + - EFI_BLOCK_IO_PROTOCOL.WriteBlocks() + + Driver Writer's Guide for UEFI 2.3.1 v1.01, + - 24.2.2. ReadBlocks() and ReadBlocksEx() Implementation + - 24.2.3 WriteBlocks() and WriteBlockEx() Implementation + + Request sizes are limited to 1 GB (checked). This is not a practical + limitation, just conformance to virtio-0.9.5, 2.3.2 Descriptor Table: "no + descriptor chain may be more than 2^32 bytes long in total". + + Some Media characteristics are hardcoded in VirtioBlkInit() below (like + non-removable media, no restriction on buffer alignment etc); we rely on + those here without explicit mention. + + @param[in] Media The EFI_BLOCK_IO_MEDIA characteristics for + this driver instance, extracted from the + underlying virtio-blk device at initialization + time. We validate the request against this set + of attributes. + + + @param[in] Lba Logical Block Address: number of logical + blocks to skip from the beginning of the + device. + + @param[in] PositiveBufferSize Size of buffer to transfer, in bytes. The + caller is responsible to ensure this parameter + is positive. + + @param[in] RequestIsWrite TRUE iff data transfer goes from guest to + device. + + + @@return Validation result to be forwarded outwards by + ReadBlocks() and WriteBlocks, as required by + the specs above. + +**/ +STATIC +EFI_STATUS +EFIAPI +VerifyReadWriteRequest ( + IN EFI_BLOCK_IO_MEDIA *Media, + IN EFI_LBA Lba, + IN UINTN PositiveBufferSize, + IN BOOLEAN RequestIsWrite + ) +{ + UINTN BlockCount; + + ASSERT (PositiveBufferSize > 0); + + if (PositiveBufferSize > SIZE_1GB || + PositiveBufferSize % Media->BlockSize > 0) { + return EFI_BAD_BUFFER_SIZE; + } + BlockCount = PositiveBufferSize / Media->BlockSize; + + // + // Avoid unsigned wraparound on either side in the second comparison. + // + if (Lba > Media->LastBlock || BlockCount - 1 > Media->LastBlock - Lba) { + return EFI_INVALID_PARAMETER; + } + + if (RequestIsWrite && Media->ReadOnly) { + return EFI_WRITE_PROTECTED; + } + + return EFI_SUCCESS; +} + + +/** + + Append a contiguous buffer for transmission / reception via the virtio ring. + + This function implements the following sections from virtio-0.9.5: + - 2.4.1.1 Placing Buffers into the Descriptor Table + - 2.4.1.2 Updating the Available Ring + + Free space is taken as granted, since this driver supports only synchronous + requests and host side status is processed in lock-step with request + submission. VirtioBlkInit() verifies the ring size in advance. + + @param[in out] Ring The virtio ring to append the buffer to, as a + descriptor. + + @param [in] BufferPhysAddr (Guest pseudo-physical) start address of the + transmit / receive buffer + + @param [in] BufferSize Number of bytes to transmit or receive. + + @param [in] Flags A bitmask of VRING_DESC_F_* flags. The caller + computes this mask dependent on further buffers + to append and transfer direction. + VRING_DESC_F_INDIRECT is unsupported. The + VRING_DESC.Next field is always set, but the + host only interprets it dependent on + VRING_DESC_F_NEXT. + + @param [in] HeadIdx The index identifying the head buffer (first + buffer appended) belonging to this same + request. + + @param [in out] NextAvailIdx On input, the index identifying the next + descriptor available to carry the buffer. On + output, incremented by one, modulo 2^16. + +**/ + +STATIC +VOID +EFIAPI +AppendDesc ( + IN OUT VRING *Ring, + IN UINTN BufferPhysAddr, + IN UINT32 BufferSize, + IN UINT16 Flags, + IN UINT16 HeadIdx, + IN OUT UINT16 *NextAvailIdx + ) +{ + volatile VRING_DESC *Desc; + + Desc = &Ring->Desc[*NextAvailIdx % Ring->QueueSize]; + Desc->Addr = BufferPhysAddr; + Desc->Len = BufferSize; + Desc->Flags = Flags; + Ring->Avail.Ring[(*NextAvailIdx)++ % Ring->QueueSize] = + HeadIdx % Ring->QueueSize; + Desc->Next = *NextAvailIdx % Ring->QueueSize; +} + + +/** + + Format a read / write / flush request as three consecutive virtio + descriptors, push them to the host, and poll for the response. + + This is the main workhorse function. Two use cases are supported, read/write + and flush. The function may only be called after the request parameters have + been verified by + - specific checks in ReadBlocks() / WriteBlocks() / FlushBlocks(), and + - VerifyReadWriteRequest() (for read/write only). + + Parameters handled commonly: + + @param[in] Dev The virtio-blk device the request is targeted + at. + + Flush request: + + @param[in] Lba Must be zero. + + @param[in] BufferSize Must be zero. + + @param[in out] Buffer Ignored by the function. + + @param[in] RequestIsWrite Must be TRUE. + + Read/Write request: + + @param[in] Lba Logical Block Address: number of logical blocks + to skip from the beginning of the device. + + @param[in] BufferSize Size of buffer to transfer, in bytes. The caller + is responsible to ensure this parameter is + positive. + + @param[in out] Buffer The guest side area to read data from the device + into, or write data to the device from. + + @param[in] RequestIsWrite TRUE iff data transfer goes from guest to + device. + + Return values are common to both use cases, and are appropriate to be + forwarded by the EFI_BLOCK_IO_PROTOCOL functions (ReadBlocks(), + WriteBlocks(), FlushBlocks()). + + + @retval EFI_SUCCESS Transfer complete. + + @retval EFI_DEVICE_ERROR Failed to notify host side via PCI write, or + unable to parse host response, or host response + is not VIRTIO_BLK_S_OK. + +**/ + +STATIC +EFI_STATUS +EFIAPI +SynchronousRequest ( + IN VBLK_DEV *Dev, + IN EFI_LBA Lba, + IN UINTN BufferSize, + IN OUT volatile VOID *Buffer, + IN BOOLEAN RequestIsWrite + ) +{ + UINT32 BlockSize; + volatile VIRTIO_BLK_REQ Request; + volatile UINT8 HostStatus; + UINT16 FirstAvailIdx; + UINT16 NextAvailIdx; + UINTN PollPeriodUsecs; + + BlockSize = Dev->BlockIoMedia.BlockSize; + + // + // ensured by VirtioBlkInit() + // + ASSERT (BlockSize > 0); + ASSERT (BlockSize % 512 == 0); + + // + // ensured by contract above, plus VerifyReadWriteRequest() + // + ASSERT (BufferSize % BlockSize == 0); + + // + // Prepare virtio-blk request header, setting zero size for flush. + // IO Priority is homogeneously 0. + // + Request.Type = RequestIsWrite ? + (BufferSize == 0 ? VIRTIO_BLK_T_FLUSH : VIRTIO_BLK_T_OUT) : + VIRTIO_BLK_T_IN; + Request.IoPrio = 0; + Request.Sector = Lba * (BlockSize / 512); + + // + // Prepare for virtio-0.9.5, 2.4.2 Receiving Used Buffers From the Device. + // We're going to poll the answer, the host should not send an interrupt. + // + *Dev->Ring.Avail.Flags = (UINT16) VRING_AVAIL_F_NO_INTERRUPT; + + // + // preset a host status for ourselves that we do not accept as success + // + HostStatus = VIRTIO_BLK_S_IOERR; + + // + // ensured by VirtioBlkInit() -- this predicate, in combination with the + // lock-step progress, ensures we don't have to track free descriptors. + // + ASSERT (Dev->Ring.QueueSize >= 3); + + // + // Implement virtio-0.9.5, 2.4.1 Supplying Buffers to the Device. + // + FirstAvailIdx = *Dev->Ring.Avail.Idx; + NextAvailIdx = FirstAvailIdx; + + // + // virtio-blk header in first desc + // + AppendDesc (&Dev->Ring, (UINTN) &Request, sizeof Request, VRING_DESC_F_NEXT, + FirstAvailIdx, &NextAvailIdx); + + // + // data buffer for read/write in second desc + // + if (BufferSize > 0) { + // + // From virtio-0.9.5, 2.3.2 Descriptor Table: + // "no descriptor chain may be more than 2^32 bytes long in total". + // + // The predicate is ensured by the call contract above (for flush), or + // VerifyReadWriteRequest() (for read/write). It also implies that + // converting BufferSize to UINT32 will not truncate it. + // + ASSERT (BufferSize <= SIZE_1GB); + + // + // VRING_DESC_F_WRITE is interpreted from the host's point of view. + // + AppendDesc (&Dev->Ring, (UINTN) Buffer, (UINT32) BufferSize, + VRING_DESC_F_NEXT | (RequestIsWrite ? 0 : VRING_DESC_F_WRITE), + FirstAvailIdx, &NextAvailIdx); + } + + // + // host status in last (second or third) desc + // + AppendDesc (&Dev->Ring, (UINTN) &HostStatus, sizeof HostStatus, + VRING_DESC_F_WRITE, FirstAvailIdx, &NextAvailIdx); + + // + // virtio-0.9.5, 2.4.1.3 Updating the Index Field + // + MemoryFence(); + *Dev->Ring.Avail.Idx = NextAvailIdx; + + // + // virtio-0.9.5, 2.4.1.4 Notifying the Device -- gratuitous notifications are + // OK. virtio-blk's only virtqueue is #0, called "requestq" (see Appendix D). + // + MemoryFence(); + if (EFI_ERROR (VIRTIO_CFG_WRITE (Dev, VhdrQueueNotify, 0))) { + return EFI_DEVICE_ERROR; + } + + // + // virtio-0.9.5, 2.4.2 Receiving Used Buffers From the Device + // Wait until the host processes and acknowledges our 3-part descriptor + // chain. The condition we use for polling is greatly simplified and relies + // on synchronous, the lock-step progress. + // + // Keep slowing down until we reach a poll period of slightly above 1 ms. + // + PollPeriodUsecs = 1; + MemoryFence(); + while (*Dev->Ring.Used.Idx != NextAvailIdx) { + gBS->Stall (PollPeriodUsecs); // calls AcpiTimerLib::MicroSecondDelay + + if (PollPeriodUsecs < 1024) { + PollPeriodUsecs *= 2; + } + MemoryFence(); + } + + if (HostStatus == VIRTIO_BLK_S_OK) { + return EFI_SUCCESS; + } + + return EFI_DEVICE_ERROR; +} + + +/** + + ReadBlocks() operation for virtio-blk. + + See + - UEFI Spec 2.3.1 + Errata C, 12.8 EFI Block I/O Protocol, 12.8 EFI Block I/O + Protocol, EFI_BLOCK_IO_PROTOCOL.ReadBlocks(). + - Driver Writer's Guide for UEFI 2.3.1 v1.01, 24.2.2. ReadBlocks() and + ReadBlocksEx() Implementation. + + Parameter checks and conformant return values are implemented in + VerifyReadWriteRequest() and SynchronousRequest(). + + A zero BufferSize doesn't seem to be prohibited, so do nothing in that case, + successfully. + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkReadBlocks ( + IN EFI_BLOCK_IO_PROTOCOL *This, + IN UINT32 MediaId, + IN EFI_LBA Lba, + IN UINTN BufferSize, + OUT VOID *Buffer + ) +{ + VBLK_DEV *Dev; + EFI_STATUS Status; + + if (BufferSize == 0) { + return EFI_SUCCESS; + } + + Dev = VIRTIO_BLK_FROM_BLOCK_IO (This); + Status = VerifyReadWriteRequest ( + &Dev->BlockIoMedia, + Lba, + BufferSize, + FALSE // RequestIsWrite + ); + if (EFI_ERROR (Status)) { + return Status; + } + + return SynchronousRequest ( + Dev, + Lba, + BufferSize, + Buffer, + FALSE // RequestIsWrite + ); +} + +/** + + WriteBlocks() operation for virtio-blk. + + See + - UEFI Spec 2.3.1 + Errata C, 12.8 EFI Block I/O Protocol, 12.8 EFI Block I/O + Protocol, EFI_BLOCK_IO_PROTOCOL.WriteBlocks(). + - Driver Writer's Guide for UEFI 2.3.1 v1.01, 24.2.3 WriteBlocks() and + WriteBlockEx() Implementation. + + Parameter checks and conformant return values are implemented in + VerifyReadWriteRequest() and SynchronousRequest(). + + A zero BufferSize doesn't seem to be prohibited, so do nothing in that case, + successfully. + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkWriteBlocks ( + IN EFI_BLOCK_IO_PROTOCOL *This, + IN UINT32 MediaId, + IN EFI_LBA Lba, + IN UINTN BufferSize, + IN VOID *Buffer + ) +{ + VBLK_DEV *Dev; + EFI_STATUS Status; + + if (BufferSize == 0) { + return EFI_SUCCESS; + } + + Dev = VIRTIO_BLK_FROM_BLOCK_IO (This); + Status = VerifyReadWriteRequest ( + &Dev->BlockIoMedia, + Lba, + BufferSize, + TRUE // RequestIsWrite + ); + if (EFI_ERROR (Status)) { + return Status; + } + + return SynchronousRequest ( + Dev, + Lba, + BufferSize, + Buffer, + TRUE // RequestIsWrite + ); +} + + +/** + + FlushBlocks() operation for virtio-blk. + + See + - UEFI Spec 2.3.1 + Errata C, 12.8 EFI Block I/O Protocol, 12.8 EFI Block I/O + Protocol, EFI_BLOCK_IO_PROTOCOL.FlushBlocks(). + - Driver Writer's Guide for UEFI 2.3.1 v1.01, 24.2.4 FlushBlocks() and + FlushBlocksEx() Implementation. + + If the underlying virtio-blk device doesn't support flushing (ie. + write-caching), then this function should not be called by higher layers, + according to EFI_BLOCK_IO_MEDIA characteristics set in VirtioBlkInit(). + Should they do nonetheless, we do nothing, successfully. + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkFlushBlocks ( + IN EFI_BLOCK_IO_PROTOCOL *This + ) +{ + VBLK_DEV *Dev; + + Dev = VIRTIO_BLK_FROM_BLOCK_IO (This); + return Dev->BlockIoMedia.WriteCaching ? + SynchronousRequest ( + Dev, + 0, // Lba + 0, // BufferSize + NULL, // Buffer + TRUE // RequestIsWrite + ) : + EFI_SUCCESS; +} + + +/** + + Device probe function for this driver. + + The DXE core calls this function for any given device in order to see if the + driver can drive the device. + + Specs relevant in the general sense: + + - UEFI Spec 2.3.1 + Errata C: + - 6.3 Protocol Handler Services -- for accessing the underlying device + - 10.1 EFI Driver Binding Protocol -- for exporting ourselves + + - Driver Writer's Guide for UEFI 2.3.1 v1.01: + - 5.1.3.4 OpenProtocol() and CloseProtocol() -- for accessing the + underlying device + - 9 Driver Binding Protocol -- for exporting ourselves + + Specs relevant in the specific sense: + - UEFI Spec 2.3.1 + Errata C, 13.4 EFI PCI I/O Protocol + - Driver Writer's Guide for UEFI 2.3.1 v1.01, 18 PCI Driver Design + Guidelines, 18.3 PCI drivers. + + @param[in] This The EFI_DRIVER_BINDING_PROTOCOL object + incorporating this driver (independently of + any device). + + @param[in] DeviceHandle The device to probe. + + @param[in] RemainingDevicePath Relevant only for bus drivers, ignored. + + + @retval EFI_SUCCESS The driver supports the device being probed. + + @retval EFI_UNSUPPORTED Based on virtio-blk PCI discovery, we do not support + the device. + + @return Error codes from the OpenProtocol() boot service or + the PciIo protocol. + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkDriverBindingSupported ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ) +{ + EFI_STATUS Status; + EFI_PCI_IO_PROTOCOL *PciIo; + PCI_TYPE00 Pci; + + // + // Attempt to open the device with the PciIo set of interfaces. On success, + // the protocol is "instantiated" for the PCI device. Covers duplicate open + // attempts (EFI_ALREADY_STARTED). + // + Status = gBS->OpenProtocol ( + DeviceHandle, // candidate device + &gEfiPciIoProtocolGuid, // for generic PCI access + (VOID **)&PciIo, // handle to instantiate + This->DriverBindingHandle, // requestor driver identity + DeviceHandle, // ControllerHandle, according to + // the UEFI Driver Model + EFI_OPEN_PROTOCOL_BY_DRIVER // get exclusive PciIo access to + // the device; to be released + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Read entire PCI configuration header for more extensive check ahead. + // + Status = PciIo->Pci.Read ( + PciIo, // (protocol, device) + // handle + EfiPciIoWidthUint32, // access width & copy + // mode + 0, // Offset + sizeof Pci / sizeof (UINT32), // Count + &Pci // target buffer + ); + + if (Status == EFI_SUCCESS) { + // + // virtio-0.9.5, 2.1 PCI Discovery + // + Status = (Pci.Hdr.VendorId == 0x1AF4 && + Pci.Hdr.DeviceId >= 0x1000 && Pci.Hdr.DeviceId <= 0x103F && + Pci.Hdr.RevisionID == 0x00 && + Pci.Device.SubsystemID == 0x02) ? EFI_SUCCESS : EFI_UNSUPPORTED; + } + + // + // We needed PCI IO access only transitorily, to see whether we support the + // device or not. + // + gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, DeviceHandle); + return Status; +} + + +/** + + Configure a virtio ring. + + This function sets up internal storage (the guest-host communication area) + and lays out several "navigation" (ie. no-ownership) pointers to parts of + that storage. + + Relevant sections from the virtio-0.9.5 spec: + - 1.1 Virtqueues, + - 2.3 Virtqueue Configuration. + + @param[in] The number of descriptors to allocate for the + virtio ring, as requested by the host. + + @param[out] Ring The virtio ring to set up. + + @retval EFI_OUT_OF_RESOURCES AllocatePages() failed to allocate contiguous + pages for the requested QueueSize. Fields of + Ring have indeterminate value. + + @retval EFI_SUCCESS Allocation and setup successful. Ring->Base + (and nothing else) is responsible for + deallocation. + +**/ + +STATIC +EFI_STATUS +EFIAPI +VirtioRingInit ( + IN UINT16 QueueSize, + OUT VRING *Ring + ) +{ + UINTN RingSize; + volatile UINT8 *RingPagesPtr; + + RingSize = ALIGN_VALUE ( + sizeof *Ring->Desc * QueueSize + + sizeof *Ring->Avail.Flags + + sizeof *Ring->Avail.Idx + + sizeof *Ring->Avail.Ring * QueueSize + + sizeof *Ring->Avail.UsedEvent, + EFI_PAGE_SIZE); + + RingSize += ALIGN_VALUE ( + sizeof *Ring->Used.Flags + + sizeof *Ring->Used.Idx + + sizeof *Ring->Used.UsedElem * QueueSize + + sizeof *Ring->Used.AvailEvent, + EFI_PAGE_SIZE); + + Ring->NumPages = EFI_SIZE_TO_PAGES (RingSize); + Ring->Base = AllocatePages (Ring->NumPages); + if (Ring->Base == NULL) { + return EFI_OUT_OF_RESOURCES; + } + SetMem (Ring->Base, RingSize, 0x00); + RingPagesPtr = Ring->Base; + + Ring->Desc = (volatile VOID *) RingPagesPtr; + RingPagesPtr += sizeof *Ring->Desc * QueueSize; + + Ring->Avail.Flags = (volatile VOID *) RingPagesPtr; + RingPagesPtr += sizeof *Ring->Avail.Flags; + + Ring->Avail.Idx = (volatile VOID *) RingPagesPtr; + RingPagesPtr += sizeof *Ring->Avail.Idx; + + Ring->Avail.Ring = (volatile VOID *) RingPagesPtr; + RingPagesPtr += sizeof *Ring->Avail.Ring * QueueSize; + + Ring->Avail.UsedEvent = (volatile VOID *) RingPagesPtr; + RingPagesPtr += sizeof *Ring->Avail.UsedEvent; + + RingPagesPtr = (volatile UINT8 *) Ring->Base + + ALIGN_VALUE (RingPagesPtr - (volatile UINT8 *) Ring->Base, + EFI_PAGE_SIZE); + + Ring->Used.Flags = (volatile VOID *) RingPagesPtr; + RingPagesPtr += sizeof *Ring->Used.Flags; + + Ring->Used.Idx = (volatile VOID *) RingPagesPtr; + RingPagesPtr += sizeof *Ring->Used.Idx; + + Ring->Used.UsedElem = (volatile VOID *) RingPagesPtr; + RingPagesPtr += sizeof *Ring->Used.UsedElem * QueueSize; + + Ring->Used.AvailEvent = (volatile VOID *) RingPagesPtr; + RingPagesPtr += sizeof *Ring->Used.AvailEvent; + + Ring->QueueSize = QueueSize; + return EFI_SUCCESS; +} + + +/** + + Tear down the internal resources of a configured virtio ring. + + The caller is responsible to stop the host from using this ring before + invoking this function: the VSTAT_DRIVER_OK bit must be clear in + VhdrDeviceStatus. + + @param[out] Ring The virtio ring to clean up. + +**/ + + +STATIC +VOID +EFIAPI +VirtioRingUninit ( + IN OUT VRING *Ring + ) +{ + FreePages (Ring->Base, Ring->NumPages); + SetMem (Ring, sizeof *Ring, 0x00); +} + + +/** + + Set up all BlockIo and virtio-blk aspects of this driver for the specified + device. + + @param[in out] Dev The driver instance to configure. The caller is + responsible for Dev->PciIo's validity (ie. working IO + access to the underlying virtio-blk PCI device). + + @retval EFI_SUCCESS Setup complete. + + @retval EFI_UNSUPPORTED The driver is unable to work with the virtio ring or + virtio-blk attributes the host provides. + + @return Error codes from VirtioRingInit() or + VIRTIO_CFG_READ() / VIRTIO_CFG_WRITE(). + +**/ + +STATIC +EFI_STATUS +EFIAPI +VirtioBlkInit ( + IN OUT VBLK_DEV *Dev + ) +{ + UINT8 NextDevStat; + EFI_STATUS Status; + + UINT32 Features; + UINT64 NumSectors; + UINT32 BlockSize; + UINT16 QueueSize; + + // + // Execute virtio-0.9.5, 2.2.1 Device Initialization Sequence. + // + NextDevStat = 0; // step 1 -- reset device + Status = VIRTIO_CFG_WRITE (Dev, VhdrDeviceStatus, NextDevStat); + if (EFI_ERROR (Status)) { + goto Failed; + } + + NextDevStat |= VSTAT_ACK; // step 2 -- acknowledge device presence + Status = VIRTIO_CFG_WRITE (Dev, VhdrDeviceStatus, NextDevStat); + if (EFI_ERROR (Status)) { + goto Failed; + } + + NextDevStat |= VSTAT_DRIVER; // step 3 -- we know how to drive it + Status = VIRTIO_CFG_WRITE (Dev, VhdrDeviceStatus, NextDevStat); + if (EFI_ERROR (Status)) { + goto Failed; + } + + // + // step 4a -- retrieve and validate features + // + Status = VIRTIO_CFG_READ (Dev, VhdrDeviceFeatureBits, &Features); + if (EFI_ERROR (Status)) { + goto Failed; + } + Status = VIRTIO_CFG_READ (Dev, VhdrCapacity, &NumSectors); + if (EFI_ERROR (Status)) { + goto Failed; + } + if (NumSectors == 0) { + Status = EFI_UNSUPPORTED; + goto Failed; + } + + if (Features & VIRTIO_BLK_F_BLK_SIZE) { + Status = VIRTIO_CFG_READ (Dev, VhdrBlkSize, &BlockSize); + if (EFI_ERROR (Status)) { + goto Failed; + } + if (BlockSize == 0 || BlockSize % 512 != 0 || + NumSectors % (BlockSize / 512) != 0) { + // + // We can only handle a logical block consisting of whole sectors, + // and only a disk composed of whole logical blocks. + // + Status = EFI_UNSUPPORTED; + goto Failed; + } + } + else { + BlockSize = 512; + } + + // + // step 4b -- allocate virtqueue + // + Status = VIRTIO_CFG_WRITE (Dev, VhdrQueueSelect, 0); + if (EFI_ERROR (Status)) { + goto Failed; + } + Status = VIRTIO_CFG_READ (Dev, VhdrQueueSize, &QueueSize); + if (EFI_ERROR (Status)) { + goto Failed; + } + if (QueueSize < 3) { // SynchronousRequest() uses at most three descriptors + Status = EFI_UNSUPPORTED; + goto Failed; + } + + Status = VirtioRingInit (QueueSize, &Dev->Ring); + if (EFI_ERROR (Status)) { + goto Failed; + } + + // + // step 4c -- Report GPFN (guest-physical frame number) of queue. If anything + // fails from here on, we must release the ring resources. + // + Status = VIRTIO_CFG_WRITE (Dev, VhdrQueueAddress, + (UINTN) Dev->Ring.Base >> EFI_PAGE_SHIFT); + if (EFI_ERROR (Status)) { + goto ReleaseQueue; + } + + // + // step 5 -- Report understood features. There are no virtio-blk specific + // features to negotiate in virtio-0.9.5, plus we do not want any of the + // device-independent (known or unknown) VIRTIO_F_* capabilities (see + // Appendix B). + // + Status = VIRTIO_CFG_WRITE (Dev, VhdrGuestFeatureBits, 0); + if (EFI_ERROR (Status)) { + goto ReleaseQueue; + } + + // + // step 6 -- initialization complete + // + NextDevStat |= VSTAT_DRIVER_OK; + Status = VIRTIO_CFG_WRITE (Dev, VhdrDeviceStatus, NextDevStat); + if (EFI_ERROR (Status)) { + goto ReleaseQueue; + } + + // + // Populate the exported interface's attributes; see UEFI spec v2.3.1 + + // Errata C, 12.8 EFI Block I/O Protocol. We stick to the lowest possible + // EFI_BLOCK_IO_PROTOCOL revision for now. + // + Dev->BlockIo.Revision = 0; + Dev->BlockIo.Media = &Dev->BlockIoMedia; + Dev->BlockIo.Reset = &VirtioBlkReset; + Dev->BlockIo.ReadBlocks = &VirtioBlkReadBlocks; + Dev->BlockIo.WriteBlocks = &VirtioBlkWriteBlocks; + Dev->BlockIo.FlushBlocks = &VirtioBlkFlushBlocks; + Dev->BlockIoMedia.MediaId = 0; + Dev->BlockIoMedia.RemovableMedia = FALSE; + Dev->BlockIoMedia.MediaPresent = TRUE; + Dev->BlockIoMedia.LogicalPartition = FALSE; + Dev->BlockIoMedia.ReadOnly = !!(Features & VIRTIO_BLK_F_RO); + Dev->BlockIoMedia.WriteCaching = !!(Features & VIRTIO_BLK_F_FLUSH); + Dev->BlockIoMedia.BlockSize = BlockSize; + Dev->BlockIoMedia.IoAlign = 0; + Dev->BlockIoMedia.LastBlock = NumSectors / (BlockSize / 512) - 1; + return EFI_SUCCESS; + +ReleaseQueue: + VirtioRingUninit (&Dev->Ring); + +Failed: + // + // Notify the host about our failure to setup: virtio-0.9.5, 2.2.2.1 Device + // Status. PCI IO access failure here should not mask the original error. + // + NextDevStat |= VSTAT_FAILED; + VIRTIO_CFG_WRITE (Dev, VhdrDeviceStatus, NextDevStat); + + return Status; // reached only via Failed above +} + + +/** + + Uninitialize the internals of a virtio-blk device that has been successfully + set up with VirtioBlkInit(). + + @param[in out] Dev The device to clean up. + +**/ + +STATIC +VOID +EFIAPI +VirtioBlkUninit ( + IN OUT VBLK_DEV *Dev + ) +{ + // + // Reset the virtual device -- see virtio-0.9.5, 2.2.2.1 Device Status. When + // VIRTIO_CFG_WRITE() returns, the host will have learned to stay away from + // the old comms area. + // + VIRTIO_CFG_WRITE (Dev, VhdrDeviceStatus, 0); + + VirtioRingUninit (&Dev->Ring); + + SetMem (&Dev->BlockIo, sizeof Dev->BlockIo, 0x00); + SetMem (&Dev->BlockIoMedia, sizeof Dev->BlockIoMedia, 0x00); +} + + +/** + + After we've pronounced support for a specific device in + DriverBindingSupported(), we start managing said device (passed in by the + Driver Exeuction Environment) with the following service. + + See DriverBindingSupported() for specification references. + + @param[in] This The EFI_DRIVER_BINDING_PROTOCOL object + incorporating this driver (independently of + any device). + + @param[in] DeviceHandle The supported device to drive. + + @param[in] RemainingDevicePath Relevant only for bus drivers, ignored. + + + @retval EFI_SUCCESS Driver instance has been created and + initialized for the virtio-blk PCI device, it + is now accessibla via EFI_BLOCK_IO_PROTOCOL. + + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + + @return Error codes from the OpenProtocol() boot + service, the PciIo protocol, VirtioBlkInit(), + or the InstallProtocolInterface() boot service. + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkDriverBindingStart ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ) +{ + VBLK_DEV *Dev; + EFI_STATUS Status; + + Dev = (VBLK_DEV *) AllocateZeroPool (sizeof *Dev); + if (Dev == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = gBS->OpenProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, + (VOID **)&Dev->PciIo, This->DriverBindingHandle, + DeviceHandle, EFI_OPEN_PROTOCOL_BY_DRIVER); + if (EFI_ERROR (Status)) { + goto FreeVirtioBlk; + } + + // + // We must retain and ultimately restore the original PCI attributes of the + // device. See Driver Writer's Guide for UEFI 2.3.1 v1.01, 18.3 PCI drivers / + // 18.3.2 Start() and Stop(). + // + // The third parameter ("Attributes", input) is ignored by the Get operation. + // The fourth parameter ("Result", output) is ignored by the Enable and Set + // operations. + // + // For virtio-blk we only need IO space access. + // + Status = Dev->PciIo->Attributes (Dev->PciIo, EfiPciIoAttributeOperationGet, + 0, &Dev->OriginalPciAttributes); + if (EFI_ERROR (Status)) { + goto ClosePciIo; + } + + Status = Dev->PciIo->Attributes (Dev->PciIo, + EfiPciIoAttributeOperationEnable, + EFI_PCI_IO_ATTRIBUTE_IO, NULL); + if (EFI_ERROR (Status)) { + goto ClosePciIo; + } + + // + // PCI IO access granted, configure virtio-blk device. + // + Status = VirtioBlkInit (Dev); + if (EFI_ERROR (Status)) { + goto RestorePciAttributes; + } + + // + // Setup complete, attempt to export the driver instance's BlockIo interface. + // + Dev->Signature = VBLK_SIG; + Status = gBS->InstallProtocolInterface (&DeviceHandle, + &gEfiBlockIoProtocolGuid, EFI_NATIVE_INTERFACE, + &Dev->BlockIo); + if (EFI_ERROR (Status)) { + goto UninitDev; + } + + return EFI_SUCCESS; + +UninitDev: + VirtioBlkUninit (Dev); + +RestorePciAttributes: + Dev->PciIo->Attributes (Dev->PciIo, EfiPciIoAttributeOperationSet, + Dev->OriginalPciAttributes, NULL); + +ClosePciIo: + gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, DeviceHandle); + +FreeVirtioBlk: + FreePool (Dev); + + return Status; +} + + +/** + + Stop driving a virtio-blk device and remove its BlockIo interface. + + This function replays the success path of DriverBindingStart() in reverse. + The host side virtio-blk device is reset, so that the OS boot loader or the + OS may reinitialize it. + + @param[in] This The EFI_DRIVER_BINDING_PROTOCOL object + incorporating this driver (independently of any + device). + + @param[in] DeviceHandle Stop driving this device. + + @param[in] NumberOfChildren Since this function belongs to a device driver + only (as opposed to a bus driver), the caller + environment sets NumberOfChildren to zero, and + we ignore it. + + @param[in] ChildHandleBuffer Ignored (corresponding to NumberOfChildren). + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkDriverBindingStop ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer + ) +{ + VBLK_DEV *Dev; + EFI_STATUS Status; + + Dev = VIRTIO_BLK_FROM_BLOCK_IO (This); + + // + // If DriverBindingStop() is called with the driver instance still in use, + // or any of the parameters are invalid, we've caught a bug. + // + Status = gBS->UninstallProtocolInterface (DeviceHandle, + &gEfiBlockIoProtocolGuid, &Dev->BlockIo); + ASSERT (Status == EFI_SUCCESS); + + VirtioBlkUninit (Dev); + + Dev->PciIo->Attributes (Dev->PciIo, EfiPciIoAttributeOperationSet, + Dev->OriginalPciAttributes, NULL); + + gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, DeviceHandle); + + FreePool (Dev); + + return EFI_SUCCESS; +} + + +// +// The static object that groups the Supported() (ie. probe), Start() and +// Stop() functions of the driver together. Refer to UEFI Spec 2.3.1 + Errata +// C, 10.1 EFI Driver Binding Protocol. +// +STATIC EFI_DRIVER_BINDING_PROTOCOL gDriverBinding = { + &VirtioBlkDriverBindingSupported, + &VirtioBlkDriverBindingStart, + &VirtioBlkDriverBindingStop, + 0x10, // Version, must be in [0x10 .. 0xFFFFFFEF] for IHV-developed drivers + NULL, // ImageHandle, to be overwritten by + // EfiLibInstallDriverBindingComponentName2() in VirtioBlkEntryPoint() + NULL // DriverBindingHandle, ditto +}; + + +// +// The purpose of the following scaffolding (EFI_COMPONENT_NAME_PROTOCOL and +// EFI_COMPONENT_NAME2_PROTOCOL implementation) is to format the driver's name +// in English, for display on standard console devices. This is recommended for +// UEFI drivers that follow the UEFI Driver Model. Refer to the Driver Writer's +// Guide for UEFI 2.3.1 v1.01, 11 UEFI Driver and Controller Names. +// +// Device type names ("Virtio Block Device") are not formatted because the +// driver supports only that device type. Therefore the driver name suffices +// for unambiguous identification. +// + +STATIC GLOBAL_REMOVE_IF_UNREFERENCED +EFI_UNICODE_STRING_TABLE mDriverNameTable[] = { + { "eng;en", L"Virtio Block Driver" }, + { NULL, NULL } +}; + +STATIC GLOBAL_REMOVE_IF_UNREFERENCED +EFI_COMPONENT_NAME_PROTOCOL gComponentName; + +EFI_STATUS +EFIAPI +VirtioBlkGetDriverName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName + ) +{ + return LookupUnicodeString2 ( + Language, + This->SupportedLanguages, + mDriverNameTable, + DriverName, + (BOOLEAN)(This == &gComponentName) // Iso639Language + ); +} + +EFI_STATUS +EFIAPI +VirtioBlkGetDeviceName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_HANDLE ChildHandle, + IN CHAR8 *Language, + OUT CHAR16 **ControllerName + ) +{ + return EFI_UNSUPPORTED; +} + +STATIC GLOBAL_REMOVE_IF_UNREFERENCED +EFI_COMPONENT_NAME_PROTOCOL gComponentName = { + &VirtioBlkGetDriverName, + &VirtioBlkGetDeviceName, + "eng" // SupportedLanguages, ISO 639-2 language codes +}; + +STATIC GLOBAL_REMOVE_IF_UNREFERENCED +EFI_COMPONENT_NAME2_PROTOCOL gComponentName2 = { + (EFI_COMPONENT_NAME2_GET_DRIVER_NAME) &VirtioBlkGetDriverName, + (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME) &VirtioBlkGetDeviceName, + "en" // SupportedLanguages, RFC 4646 language codes +}; + + +// +// Entry point of this driver. +// +EFI_STATUS +EFIAPI +VirtioBlkEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + return EfiLibInstallDriverBindingComponentName2 ( + ImageHandle, + SystemTable, + &gDriverBinding, + ImageHandle, + &gComponentName, + &gComponentName2 + ); +} + diff --git a/OvmfPkg/VirtioBlkDxe/VirtioBlk.h b/OvmfPkg/VirtioBlkDxe/VirtioBlk.h new file mode 100644 index 0000000000..e846dab514 --- /dev/null +++ b/OvmfPkg/VirtioBlkDxe/VirtioBlk.h @@ -0,0 +1,293 @@ +/** @file + + Internal definitions for the virtio-blk driver, which produces Block I/O + Protocol instances for virtio-blk devices. + + Copyright (C) 2012, Red Hat, Inc. + + 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. + +**/ + +#include +#include +#include +#include + +#include "Virtio.h" + + +#define VBLK_SIG SIGNATURE_32 ('V', 'B', 'L', 'K') + +typedef struct { + // + // Parts of this structure are initialized / torn down in various functions + // at various call depths. The table to the right should make it easier to + // track them. + // + // field init function init dpth + // ---------------------- ------------------ --------- + UINT32 Signature; // DriverBindingStart 0 + EFI_PCI_IO_PROTOCOL *PciIo; // DriverBindingStart 0 + UINT64 OriginalPciAttributes; // DriverBindingStart 0 + VRING Ring; // VirtioRingInit 2 + EFI_BLOCK_IO_PROTOCOL BlockIo; // VirtioBlkInit 1 + EFI_BLOCK_IO_MEDIA BlockIoMedia; // VirtioBlkInit 1 +} VBLK_DEV; + +#define VIRTIO_BLK_FROM_BLOCK_IO(BlockIoPointer) \ + CR (BlockIoPointer, VBLK_DEV, BlockIo, VBLK_SIG) + + +/** + + Device probe function for this driver. + + The DXE core calls this function for any given device in order to see if the + driver can drive the device. + + Specs relevant in the general sense: + + - UEFI Spec 2.3.1 + Errata C: + - 6.3 Protocol Handler Services -- for accessing the underlying device + - 10.1 EFI Driver Binding Protocol -- for exporting ourselves + + - Driver Writer's Guide for UEFI 2.3.1 v1.01: + - 5.1.3.4 OpenProtocol() and CloseProtocol() -- for accessing the + underlying device + - 9 Driver Binding Protocol -- for exporting ourselves + + Specs relevant in the specific sense: + - UEFI Spec 2.3.1 + Errata C, 13.4 EFI PCI I/O Protocol + - Driver Writer's Guide for UEFI 2.3.1 v1.01, 18 PCI Driver Design + Guidelines, 18.3 PCI drivers. + + @param[in] This The EFI_DRIVER_BINDING_PROTOCOL object + incorporating this driver (independently of + any device). + + @param[in] DeviceHandle The device to probe. + + @param[in] RemainingDevicePath Relevant only for bus drivers, ignored. + + + @retval EFI_SUCCESS The driver supports the device being probed. + + @retval EFI_UNSUPPORTED Based on virtio-blk PCI discovery, we do not support + the device. + + @return Error codes from the OpenProtocol() boot service or + the PciIo protocol. + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkDriverBindingSupported ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ); + + +/** + + After we've pronounced support for a specific device in + DriverBindingSupported(), we start managing said device (passed in by the + Driver Exeuction Environment) with the following service. + + See DriverBindingSupported() for specification references. + + @param[in] This The EFI_DRIVER_BINDING_PROTOCOL object + incorporating this driver (independently of + any device). + + @param[in] DeviceHandle The supported device to drive. + + @param[in] RemainingDevicePath Relevant only for bus drivers, ignored. + + + @retval EFI_SUCCESS Driver instance has been created and + initialized for the virtio-blk PCI device, it + is now accessibla via EFI_BLOCK_IO_PROTOCOL. + + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + + @return Error codes from the OpenProtocol() boot + service, the PciIo protocol, VirtioBlkInit(), + or the InstallProtocolInterface() boot service. + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkDriverBindingStart ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ); + + +/** + + Stop driving a virtio-blk device and remove its BlockIo interface. + + This function replays the success path of DriverBindingStart() in reverse. + The host side virtio-blk device is reset, so that the OS boot loader or the + OS may reinitialize it. + + @param[in] This The EFI_DRIVER_BINDING_PROTOCOL object + incorporating this driver (independently of any + device). + + @param[in] DeviceHandle Stop driving this device. + + @param[in] NumberOfChildren Since this function belongs to a device driver + only (as opposed to a bus driver), the caller + environment sets NumberOfChildren to zero, and + we ignore it. + + @param[in] ChildHandleBuffer Ignored (corresponding to NumberOfChildren). + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkDriverBindingStop ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer + ); + + +// +// UEFI Spec 2.3.1 + Errata C, 12.8 EFI Block I/O Protocol +// Driver Writer's Guide for UEFI 2.3.1 v1.01, +// 24.2 Block I/O Protocol Implementations +// +EFI_STATUS +EFIAPI +VirtioBlkReset ( + IN EFI_BLOCK_IO_PROTOCOL *This, + IN BOOLEAN ExtendedVerification + ); + + +/** + + ReadBlocks() operation for virtio-blk. + + See + - UEFI Spec 2.3.1 + Errata C, 12.8 EFI Block I/O Protocol, 12.8 EFI Block I/O + Protocol, EFI_BLOCK_IO_PROTOCOL.ReadBlocks(). + - Driver Writer's Guide for UEFI 2.3.1 v1.01, 24.2.2. ReadBlocks() and + ReadBlocksEx() Implementation. + + Parameter checks and conformant return values are implemented in + VerifyReadWriteRequest() and SynchronousRequest(). + + A zero BufferSize doesn't seem to be prohibited, so do nothing in that case, + successfully. + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkReadBlocks ( + IN EFI_BLOCK_IO_PROTOCOL *This, + IN UINT32 MediaId, + IN EFI_LBA Lba, + IN UINTN BufferSize, + OUT VOID *Buffer + ); + + +/** + + WriteBlocks() operation for virtio-blk. + + See + - UEFI Spec 2.3.1 + Errata C, 12.8 EFI Block I/O Protocol, 12.8 EFI Block I/O + Protocol, EFI_BLOCK_IO_PROTOCOL.WriteBlocks(). + - Driver Writer's Guide for UEFI 2.3.1 v1.01, 24.2.3 WriteBlocks() and + WriteBlockEx() Implementation. + + Parameter checks and conformant return values are implemented in + VerifyReadWriteRequest() and SynchronousRequest(). + + A zero BufferSize doesn't seem to be prohibited, so do nothing in that case, + successfully. + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkWriteBlocks ( + IN EFI_BLOCK_IO_PROTOCOL *This, + IN UINT32 MediaId, + IN EFI_LBA Lba, + IN UINTN BufferSize, + IN VOID *Buffer + ); + + +/** + + FlushBlocks() operation for virtio-blk. + + See + - UEFI Spec 2.3.1 + Errata C, 12.8 EFI Block I/O Protocol, 12.8 EFI Block I/O + Protocol, EFI_BLOCK_IO_PROTOCOL.FlushBlocks(). + - Driver Writer's Guide for UEFI 2.3.1 v1.01, 24.2.4 FlushBlocks() and + FlushBlocksEx() Implementation. + + If the underlying virtio-blk device doesn't support flushing (ie. + write-caching), then this function should not be called by higher layers, + according to EFI_BLOCK_IO_MEDIA characteristics set in VirtioBlkInit(). + Should they do nonetheless, we do nothing, successfully. + +**/ + +EFI_STATUS +EFIAPI +VirtioBlkFlushBlocks ( + IN EFI_BLOCK_IO_PROTOCOL *This + ); + + +// +// The purpose of the following scaffolding (EFI_COMPONENT_NAME_PROTOCOL and +// EFI_COMPONENT_NAME2_PROTOCOL implementation) is to format the driver's name +// in English, for display on standard console devices. This is recommended for +// UEFI drivers that follow the UEFI Driver Model. Refer to the Driver Writer's +// Guide for UEFI 2.3.1 v1.01, 11 UEFI Driver and Controller Names. +// +// Device type names ("Virtio Block Device") are not formatted because the +// driver supports only that device type. Therefore the driver name suffices +// for unambiguous identification. +// + +EFI_STATUS +EFIAPI +VirtioBlkGetDriverName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName + ); + +EFI_STATUS +EFIAPI +VirtioBlkGetDeviceName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_HANDLE ChildHandle, + IN CHAR8 *Language, + OUT CHAR16 **ControllerName + ); diff --git a/OvmfPkg/VirtioBlkDxe/VirtioBlk.inf b/OvmfPkg/VirtioBlkDxe/VirtioBlk.inf new file mode 100644 index 0000000000..26ad8f8aa7 --- /dev/null +++ b/OvmfPkg/VirtioBlkDxe/VirtioBlk.inf @@ -0,0 +1,40 @@ +## @file +# This driver produces Block I/O Protocol instances for virtio-blk devices. +# +# Copyright (C) 2012, Red Hat, Inc. +# +# 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. +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = VirtioBlkDxe + FILE_GUID = 11D92DFB-3CA9-4F93-BA2E-4780ED3E03B5 + MODULE_TYPE = UEFI_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = VirtioBlkEntryPoint + +[Sources] + VirtioBlk.c + +[Packages] + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseMemoryLib + DebugLib + MemoryAllocationLib + UefiBootServicesTableLib + UefiDriverEntryPoint + UefiLib + +[Protocols] + gEfiBlockIoProtocolGuid ## BY_START + gEfiPciIoProtocolGuid ## TO_START