From 37078a63b1911f8b320bab6d82a2183a84f8858c Mon Sep 17 00:00:00 2001 From: jljusten Date: Thu, 18 Oct 2012 17:07:48 +0000 Subject: [PATCH] OvmfPkg: introduce virtio-scsi driver Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Laszlo Ersek Reviewed-by: Jordan Justen [jordan.l.justen@intel.com: fix build for VS2012] Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Jordan Justen git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@13867 6f19259b-4bc3-4df7-8a09-765794883524 --- OvmfPkg/Include/IndustryStandard/VirtioScsi.h | 100 ++ OvmfPkg/OvmfPkg.dec | 11 + OvmfPkg/OvmfPkgIa32.dsc | 1 + OvmfPkg/OvmfPkgIa32.fdf | 1 + OvmfPkg/OvmfPkgIa32X64.dsc | 1 + OvmfPkg/OvmfPkgIa32X64.fdf | 1 + OvmfPkg/OvmfPkgX64.dsc | 1 + OvmfPkg/OvmfPkgX64.fdf | 1 + OvmfPkg/VirtioScsiDxe/VirtioScsi.c | 1275 +++++++++++++++++ OvmfPkg/VirtioScsiDxe/VirtioScsi.h | 209 +++ OvmfPkg/VirtioScsiDxe/VirtioScsi.inf | 47 + 11 files changed, 1648 insertions(+) create mode 100644 OvmfPkg/Include/IndustryStandard/VirtioScsi.h create mode 100644 OvmfPkg/VirtioScsiDxe/VirtioScsi.c create mode 100644 OvmfPkg/VirtioScsiDxe/VirtioScsi.h create mode 100644 OvmfPkg/VirtioScsiDxe/VirtioScsi.inf diff --git a/OvmfPkg/Include/IndustryStandard/VirtioScsi.h b/OvmfPkg/Include/IndustryStandard/VirtioScsi.h new file mode 100644 index 0000000000..59ce97e070 --- /dev/null +++ b/OvmfPkg/Include/IndustryStandard/VirtioScsi.h @@ -0,0 +1,100 @@ +/** @file + + Virtio SCSI Host Device specific 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. + +**/ + +#ifndef _VIRTIO_SCSI_H_ +#define _VIRTIO_SCSI_H_ + +#include + + +// +// virtio-0.9.5, Appendix I: SCSI Host Device +// +#pragma pack(1) +typedef struct { + VIRTIO_HDR Generic; + UINT32 VhdrNumQueues; + UINT32 VhdrSegMax; + UINT32 VhdrMaxSectors; + UINT32 VhdrCmdPerLun; + UINT32 VhdrEventInfoSize; + UINT32 VhdrSenseSize; + UINT32 VhdrCdbSize; + UINT16 VhdrMaxChannel; + UINT16 VhdrMaxTarget; + UINT32 VhdrMaxLun; +} VSCSI_HDR; +#pragma pack() + +#define OFFSET_OF_VSCSI(Field) OFFSET_OF (VSCSI_HDR, Field) +#define SIZE_OF_VSCSI(Field) (sizeof ((VSCSI_HDR *) 0)->Field) + +#define VIRTIO_SCSI_F_INOUT BIT0 +#define VIRTIO_SCSI_F_HOTPLUG BIT1 + +// +// We expect these maximum sizes from the host. Also we force the CdbLength and +// SenseDataLength parameters of EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() not +// to exceed these limits. See UEFI 2.3.1 errata C 14.7. +// +#define VIRTIO_SCSI_CDB_SIZE 32 +#define VIRTIO_SCSI_SENSE_SIZE 96 + +// +// We pass the dynamically sized buffers ("dataout", "datain") in separate ring +// descriptors. +// +#pragma pack(1) +typedef struct { + UINT8 Lun[8]; + UINT64 Id; + UINT8 TaskAttr; + UINT8 Prio; + UINT8 Crn; + UINT8 Cdb[VIRTIO_SCSI_CDB_SIZE]; +} VIRTIO_SCSI_REQ; + +typedef struct { + UINT32 SenseLen; + UINT32 Residual; + UINT16 StatusQualifier; + UINT8 Status; + UINT8 Response; + UINT8 Sense[VIRTIO_SCSI_SENSE_SIZE]; +} VIRTIO_SCSI_RESP; +#pragma pack() + +// +// selector of first virtio queue usable for request transfer +// +#define VIRTIO_SCSI_REQUEST_QUEUE 2 + +// +// host response codes +// +#define VIRTIO_SCSI_S_OK 0 +#define VIRTIO_SCSI_S_OVERRUN 1 +#define VIRTIO_SCSI_S_ABORTED 2 +#define VIRTIO_SCSI_S_BAD_TARGET 3 +#define VIRTIO_SCSI_S_RESET 4 +#define VIRTIO_SCSI_S_BUSY 5 +#define VIRTIO_SCSI_S_TRANSPORT_FAILURE 6 +#define VIRTIO_SCSI_S_TARGET_FAILURE 7 +#define VIRTIO_SCSI_S_NEXUS_FAILURE 8 +#define VIRTIO_SCSI_S_FAILURE 9 + +#endif // _VIRTIO_SCSI_H_ diff --git a/OvmfPkg/OvmfPkg.dec b/OvmfPkg/OvmfPkg.dec index 26600dcf42..73db6af772 100644 --- a/OvmfPkg/OvmfPkg.dec +++ b/OvmfPkg/OvmfPkg.dec @@ -53,6 +53,17 @@ # to PIIX4 function 3 offset 0x40-0x43 bits [15:6]. gUefiOvmfPkgTokenSpaceGuid.PcdAcpiPmBaseAddress|0xB000|UINT16|5 + ## When VirtioScsiDxe is instantiated for a HBA, the numbers of targets and + # LUNs are retrieved from the host during virtio-scsi setup. + # MdeModulePkg/Bus/Scsi/ScsiBusDxe then scans all MaxTarget * MaxLun + # possible devices. This can take extremely long, for example with + # MaxTarget=255 and MaxLun=16383. The *inclusive* constants below limit + # MaxTarget and MaxLun, independently, should the host report higher values, + # so that scanning the number of devices given by their product is still + # acceptably fast. + gUefiOvmfPkgTokenSpaceGuid.PcdVirtioScsiMaxTargetLimit|31|UINT16|6 + gUefiOvmfPkgTokenSpaceGuid.PcdVirtioScsiMaxLunLimit|7|UINT32|7 + [PcdsDynamic, PcdsDynamicEx] gUefiOvmfPkgTokenSpaceGuid.PcdEmuVariableEvent|0|UINT64|2 diff --git a/OvmfPkg/OvmfPkgIa32.dsc b/OvmfPkg/OvmfPkgIa32.dsc index d0c632979d..bd86572413 100644 --- a/OvmfPkg/OvmfPkgIa32.dsc +++ b/OvmfPkg/OvmfPkgIa32.dsc @@ -409,6 +409,7 @@ OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf OvmfPkg/VirtioBlkDxe/VirtioBlk.inf + OvmfPkg/VirtioScsiDxe/VirtioScsi.inf OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf { PlatformFvbLib|OvmfPkg/Library/EmuVariableFvbLib/EmuVariableFvbLib.inf diff --git a/OvmfPkg/OvmfPkgIa32.fdf b/OvmfPkg/OvmfPkgIa32.fdf index be5e6d8deb..36c9756656 100644 --- a/OvmfPkg/OvmfPkgIa32.fdf +++ b/OvmfPkg/OvmfPkgIa32.fdf @@ -181,6 +181,7 @@ INF PcAtChipsetPkg/PcatRealTimeClockRuntimeDxe/PcatRealTimeClockRuntimeDxe.inf INF OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf INF OvmfPkg/VirtioBlkDxe/VirtioBlk.inf +INF OvmfPkg/VirtioScsiDxe/VirtioScsi.inf INF OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf INF MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf diff --git a/OvmfPkg/OvmfPkgIa32X64.dsc b/OvmfPkg/OvmfPkgIa32X64.dsc index b442314680..0a1609f313 100644 --- a/OvmfPkg/OvmfPkgIa32X64.dsc +++ b/OvmfPkg/OvmfPkgIa32X64.dsc @@ -416,6 +416,7 @@ OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf OvmfPkg/VirtioBlkDxe/VirtioBlk.inf + OvmfPkg/VirtioScsiDxe/VirtioScsi.inf OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf { PlatformFvbLib|OvmfPkg/Library/EmuVariableFvbLib/EmuVariableFvbLib.inf diff --git a/OvmfPkg/OvmfPkgIa32X64.fdf b/OvmfPkg/OvmfPkgIa32X64.fdf index 07faec49fe..a762dc9f21 100644 --- a/OvmfPkg/OvmfPkgIa32X64.fdf +++ b/OvmfPkg/OvmfPkgIa32X64.fdf @@ -181,6 +181,7 @@ INF PcAtChipsetPkg/PcatRealTimeClockRuntimeDxe/PcatRealTimeClockRuntimeDxe.inf INF OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf INF OvmfPkg/VirtioBlkDxe/VirtioBlk.inf +INF OvmfPkg/VirtioScsiDxe/VirtioScsi.inf INF OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf INF MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf diff --git a/OvmfPkg/OvmfPkgX64.dsc b/OvmfPkg/OvmfPkgX64.dsc index 11f90a4818..b24d1b9df0 100644 --- a/OvmfPkg/OvmfPkgX64.dsc +++ b/OvmfPkg/OvmfPkgX64.dsc @@ -414,6 +414,7 @@ OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf OvmfPkg/VirtioBlkDxe/VirtioBlk.inf + OvmfPkg/VirtioScsiDxe/VirtioScsi.inf OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf { PlatformFvbLib|OvmfPkg/Library/EmuVariableFvbLib/EmuVariableFvbLib.inf diff --git a/OvmfPkg/OvmfPkgX64.fdf b/OvmfPkg/OvmfPkgX64.fdf index 615bc0a1c2..fcdac0316b 100644 --- a/OvmfPkg/OvmfPkgX64.fdf +++ b/OvmfPkg/OvmfPkgX64.fdf @@ -181,6 +181,7 @@ INF PcAtChipsetPkg/PcatRealTimeClockRuntimeDxe/PcatRealTimeClockRuntimeDxe.inf INF OvmfPkg/BlockMmioToBlockIoDxe/BlockIo.inf INF OvmfPkg/VirtioBlkDxe/VirtioBlk.inf +INF OvmfPkg/VirtioScsiDxe/VirtioScsi.inf INF OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf INF MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf diff --git a/OvmfPkg/VirtioScsiDxe/VirtioScsi.c b/OvmfPkg/VirtioScsiDxe/VirtioScsi.c new file mode 100644 index 0000000000..66f6d31d74 --- /dev/null +++ b/OvmfPkg/VirtioScsiDxe/VirtioScsi.c @@ -0,0 +1,1275 @@ +/** @file + + This driver produces Extended SCSI Pass Thru Protocol instances for + virtio-scsi devices. + + The implementation is basic: + + - No hotplug / hot-unplug. + + - Although EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() could be a good match + for multiple in-flight virtio-scsi requests, we stick to synchronous + requests for now. + + - Timeouts are not supported for EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru(). + + - Only one channel is supported. (At the time of this writing, host-side + virtio-scsi supports a single channel too.) + + - Only one request queue is used (for the one synchronous request). + + - The ResetChannel() and ResetTargetLun() functions of + EFI_EXT_SCSI_PASS_THRU_PROTOCOL are not supported (which is allowed by the + UEFI 2.3.1 Errata C specification), although + VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET could be a good match. That would + however require client code for the control queue, which is deemed + unreasonable for now. + + Copyright (C) 2012, Red Hat, Inc. + Copyright (c) 2012, 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. + +**/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VirtioScsi.h" + +/** + + Convenience macros to read and write region 0 IO space elements of the + virtio-scsi 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 VSCSI_DEV structure whose PCI IO space + we're accessing. Dev->PciIo must be valid. + + @param[in] Field A field name from VSCSI_HDR, identifying the virtio-scsi + 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 codes returned by VirtioWrite() / VirtioRead(). + +**/ + +#define VIRTIO_CFG_WRITE(Dev, Field, Value) (VirtioWrite ( \ + (Dev)->PciIo, \ + OFFSET_OF_VSCSI (Field), \ + SIZE_OF_VSCSI (Field), \ + (Value) \ + )) + +#define VIRTIO_CFG_READ(Dev, Field, Pointer) (VirtioRead ( \ + (Dev)->PciIo, \ + OFFSET_OF_VSCSI (Field), \ + SIZE_OF_VSCSI (Field), \ + sizeof *(Pointer), \ + (Pointer) \ + )) + + +// +// UEFI Spec 2.3.1 + Errata C, 14.7 Extended SCSI Pass Thru Protocol specifies +// the PassThru() interface. Beside returning a status code, the function must +// set some fields in the EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET in/out +// parameter on return. The following is a full list of those fields, for +// easier validation of PopulateRequest(), ParseResponse(), and +// VirtioScsiPassThru() below. +// +// - InTransferLength +// - OutTransferLength +// - HostAdapterStatus +// - TargetStatus +// - SenseDataLength +// - SenseData +// +// On any return from the PassThru() interface, these fields must be set, +// except if the returned status code is explicitly exempt. (Actually the +// implementation here conservatively sets these fields even in case not all +// of them would be required by the specification.) +// + +/** + + Populate a virtio-scsi request from the Extended SCSI Pass Thru Protocol + packet. + + The caller is responsible for pre-zeroing the virtio-scsi request. The + Extended SCSI Pass Thru Protocol packet is modified, to be forwarded outwards + by VirtioScsiPassThru(), if invalid or unsupported parameters are detected. + + @param[in] Dev The virtio-scsi host device the packet targets. + + @param[in] Target The SCSI target controlled by the virtio-scsi host + device. + + @param[in] Lun The Logical Unit Number under the SCSI target. + + @param[in out] Packet The Extended SCSI Pass Thru Protocol packet the + function translates to a virtio-scsi request. On + failure this parameter relays error contents. + + @param[out] Request The pre-zeroed virtio-scsi request to populate. This + parameter is volatile-qualified because we expect the + caller to append it to a virtio ring, thus + assignments to Request must be visible when the + function returns. + + + @retval EFI_SUCCESS The Extended SCSI Pass Thru Protocol packet was valid, + Request has been populated. + + @return Otherwise, invalid or unsupported parameters were + detected. Status codes are meant for direct forwarding + by the EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() + implementation. + +**/ +STATIC +EFI_STATUS +EFIAPI +PopulateRequest ( + IN CONST VSCSI_DEV *Dev, + IN UINT16 Target, + IN UINT64 Lun, + IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet, + OUT volatile VIRTIO_SCSI_REQ *Request + ) +{ + UINTN Idx; + + if ( + // + // bidirectional transfer was requested, but the host doesn't support it + // + (Packet->InTransferLength > 0 && Packet->OutTransferLength > 0 && + !Dev->InOutSupported) || + + // + // a target / LUN was addressed that's impossible to encode for the host + // + Target > 0xFF || Lun >= 0x4000 || + + // + // Command Descriptor Block bigger than VIRTIO_SCSI_CDB_SIZE + // + Packet->CdbLength > VIRTIO_SCSI_CDB_SIZE || + + // + // From virtio-0.9.5, 2.3.2 Descriptor Table: + // "no descriptor chain may be more than 2^32 bytes long in total". + // + (UINT64) Packet->InTransferLength + Packet->OutTransferLength > SIZE_1GB + ) { + + // + // this error code doesn't require updates to the Packet output fields + // + return EFI_UNSUPPORTED; + } + + if ( + // + // addressed invalid device + // + Target > Dev->MaxTarget || Lun > Dev->MaxLun || + + // + // invalid direction (there doesn't seem to be a macro for the "no data + // transferred" "direction", eg. for TEST UNIT READY) + // + Packet->DataDirection > EFI_EXT_SCSI_DATA_DIRECTION_BIDIRECTIONAL || + + // + // trying to receive, but destination pointer is NULL, or contradicting + // transfer direction + // + (Packet->InTransferLength > 0 && + (Packet->InDataBuffer == NULL || + Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_WRITE + ) + ) || + + // + // trying to send, but source pointer is NULL, or contradicting transfer + // direction + // + (Packet->OutTransferLength > 0 && + (Packet->OutDataBuffer == NULL || + Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ + ) + ) + ) { + + // + // this error code doesn't require updates to the Packet output fields + // + return EFI_INVALID_PARAMETER; + } + + // + // Catch oversized requests eagerly. If this condition evaluates to false, + // then the combined size of a bidirectional request will not exceed the + // virtio-scsi device's transfer limit either. + // + if (ALIGN_VALUE (Packet->OutTransferLength, 512) / 512 + > Dev->MaxSectors / 2 || + ALIGN_VALUE (Packet->InTransferLength, 512) / 512 + > Dev->MaxSectors / 2) { + Packet->InTransferLength = (Dev->MaxSectors / 2) * 512; + Packet->OutTransferLength = (Dev->MaxSectors / 2) * 512; + Packet->HostAdapterStatus = + EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN; + Packet->TargetStatus = EFI_EXT_SCSI_STATUS_TARGET_GOOD; + Packet->SenseDataLength = 0; + return EFI_BAD_BUFFER_SIZE; + } + + // + // target & LUN encoding: see virtio-0.9.5, Appendix I: SCSI Host Device, + // Device Operation: request queues + // + Request->Lun[0] = 1; + Request->Lun[1] = (UINT8) Target; + Request->Lun[2] = (UINT8) ((Lun >> 8) | 0x40); + Request->Lun[3] = (UINT8) Lun; + + // + // CopyMem() would cast away the "volatile" qualifier before access, which is + // undefined behavior (ISO C99 6.7.3p5) + // + for (Idx = 0; Idx < Packet->CdbLength; ++Idx) { + Request->Cdb[Idx] = ((UINT8 *) Packet->Cdb)[Idx]; + } + + return EFI_SUCCESS; +} + + +/** + + Parse the virtio-scsi device's response, translate it to an EFI status code, + and update the Extended SCSI Pass Thru Protocol packet, to be returned by + the EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() implementation. + + @param[in out] Packet The Extended SCSI Pass Thru Protocol packet that has + been translated to a virtio-scsi request with + PopulateRequest(), and processed by the host. On + output this parameter is updated with response or + error contents. + + @param[in] Response The virtio-scsi response structure to parse. We expect + it to come from a virtio ring, thus it is qualified + volatile. + + + @return PassThru() status codes mandated by UEFI Spec 2.3.1 + Errata C, 14.7 + Extended SCSI Pass Thru Protocol. + +**/ +STATIC +EFI_STATUS +EFIAPI +ParseResponse ( + IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet, + IN CONST volatile VIRTIO_SCSI_RESP *Response + ) +{ + UINTN ResponseSenseLen; + UINTN Idx; + + // + // return sense data (length and contents) in all cases, truncated if needed + // + ResponseSenseLen = MIN (Response->SenseLen, VIRTIO_SCSI_SENSE_SIZE); + if (Packet->SenseDataLength > ResponseSenseLen) { + Packet->SenseDataLength = (UINT8) ResponseSenseLen; + } + for (Idx = 0; Idx < Packet->SenseDataLength; ++Idx) { + ((UINT8 *) Packet->SenseData)[Idx] = Response->Sense[Idx]; + } + + // + // Report actual transfer lengths. The logic below covers all three + // DataDirections (read, write, bidirectional). + // + // -+- @ 0 + // | + // | write ^ @ Residual (unprocessed) + // | | + // -+- @ OutTransferLength -+- @ InTransferLength + // | | + // | read | + // | | + // V @ OutTransferLength + InTransferLength -+- @ 0 + // + if (Response->Residual <= Packet->InTransferLength) { + Packet->InTransferLength -= Response->Residual; + } + else { + Packet->OutTransferLength -= Response->Residual - Packet->InTransferLength; + Packet->InTransferLength = 0; + } + + // + // report target status in all cases + // + Packet->TargetStatus = Response->Status; + + // + // host adapter status and function return value depend on virtio-scsi + // response code + // + switch (Response->Response) { + case VIRTIO_SCSI_S_OK: + Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK; + return EFI_SUCCESS; + + case VIRTIO_SCSI_S_OVERRUN: + Packet->HostAdapterStatus = + EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN; + break; + + case VIRTIO_SCSI_S_BAD_TARGET: + // + // This is non-intuitive but explicitly required by the + // EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() specification for + // disconnected (but otherwise valid) target / LUN addresses. + // + Packet->HostAdapterStatus = + EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT_COMMAND; + return EFI_TIMEOUT; + + case VIRTIO_SCSI_S_RESET: + Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_RESET; + break; + + case VIRTIO_SCSI_S_BUSY: + Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK; + return EFI_NOT_READY; + + // + // Lump together the rest. The mapping for VIRTIO_SCSI_S_ABORTED is + // intentional as well, not an oversight. + // + case VIRTIO_SCSI_S_ABORTED: + case VIRTIO_SCSI_S_TRANSPORT_FAILURE: + case VIRTIO_SCSI_S_TARGET_FAILURE: + case VIRTIO_SCSI_S_NEXUS_FAILURE: + case VIRTIO_SCSI_S_FAILURE: + default: + Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER; + } + + return EFI_DEVICE_ERROR; +} + + +// +// The next seven functions implement EFI_EXT_SCSI_PASS_THRU_PROTOCOL +// for the virtio-scsi HBA. Refer to UEFI Spec 2.3.1 + Errata C, sections +// - 14.1 SCSI Driver Model Overview, +// - 14.7 Extended SCSI Pass Thru Protocol. +// + +EFI_STATUS +EFIAPI +VirtioScsiPassThru ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN UINT8 *Target, + IN UINT64 Lun, + IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet, + IN EFI_EVENT Event OPTIONAL + ) +{ + VSCSI_DEV *Dev; + UINT16 TargetValue; + EFI_STATUS Status; + volatile VIRTIO_SCSI_REQ Request; + volatile VIRTIO_SCSI_RESP Response; + DESC_INDICES Indices; + + // + // Zero-initialization of Request & Response with "= { 0 };" doesn't build + // with gcc-4.4: "undefined reference to `memset'". Direct SetMem() is not + // allowed as it would cast away the volatile qualifier. Work it around. + // + union { + VIRTIO_SCSI_REQ Request; + VIRTIO_SCSI_RESP Response; + } Zero; + + SetMem (&Zero, sizeof Zero, 0x00); + Request = Zero.Request; + Response = Zero.Response; + + Dev = VIRTIO_SCSI_FROM_PASS_THRU (This); + CopyMem (&TargetValue, Target, sizeof TargetValue); + + Status = PopulateRequest (Dev, TargetValue, Lun, Packet, &Request); + if (EFI_ERROR (Status)) { + return Status; + } + + VirtioPrepare (&Dev->Ring, &Indices); + + // + // preset a host status for ourselves that we do not accept as success + // + Response.Response = VIRTIO_SCSI_S_FAILURE; + + // + // ensured by VirtioScsiInit() -- this predicate, in combination with the + // lock-step progress, ensures we don't have to track free descriptors. + // + ASSERT (Dev->Ring.QueueSize >= 4); + + // + // enqueue Request + // + VirtioAppendDesc (&Dev->Ring, (UINTN) &Request, sizeof Request, + VRING_DESC_F_NEXT, &Indices); + + // + // enqueue "dataout" if any + // + if (Packet->OutTransferLength > 0) { + VirtioAppendDesc (&Dev->Ring, (UINTN) Packet->OutDataBuffer, + Packet->OutTransferLength, VRING_DESC_F_NEXT, &Indices); + } + + // + // enqueue Response, to be written by the host + // + VirtioAppendDesc (&Dev->Ring, (UINTN) &Response, sizeof Response, + VRING_DESC_F_WRITE | (Packet->InTransferLength > 0 ? + VRING_DESC_F_NEXT : 0), + &Indices); + + // + // enqueue "datain" if any, to be written by the host + // + if (Packet->InTransferLength > 0) { + VirtioAppendDesc (&Dev->Ring, (UINTN) Packet->InDataBuffer, + Packet->InTransferLength, VRING_DESC_F_WRITE, &Indices); + } + + // If kicking the host fails, we must fake a host adapter error. + // EFI_NOT_READY would save us the effort, but it would also suggest that the + // caller retry. + // + if (VirtioFlush (Dev->PciIo, VIRTIO_SCSI_REQUEST_QUEUE, &Dev->Ring, + &Indices) != EFI_SUCCESS) { + Packet->InTransferLength = 0; + Packet->OutTransferLength = 0; + Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER; + Packet->TargetStatus = EFI_EXT_SCSI_STATUS_TARGET_GOOD; + Packet->SenseDataLength = 0; + return EFI_DEVICE_ERROR; + } + + return ParseResponse (Packet, &Response); +} + + +EFI_STATUS +EFIAPI +VirtioScsiGetNextTargetLun ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN OUT UINT8 **TargetPointer, + IN OUT UINT64 *Lun + ) +{ + UINT8 *Target; + UINTN Idx; + UINT16 LastTarget; + VSCSI_DEV *Dev; + + // + // the TargetPointer input parameter is unnecessarily a pointer-to-pointer + // + Target = *TargetPointer; + + // + // Search for first non-0xFF byte. If not found, return first target & LUN. + // + for (Idx = 0; Idx < TARGET_MAX_BYTES && Target[Idx] == 0xFF; ++Idx) + ; + if (Idx == TARGET_MAX_BYTES) { + SetMem (Target, TARGET_MAX_BYTES, 0x00); + *Lun = 0; + return EFI_SUCCESS; + } + + // + // see the TARGET_MAX_BYTES check in "VirtioScsi.h" + // + CopyMem (&LastTarget, Target, sizeof LastTarget); + + // + // increment (target, LUN) pair if valid on input + // + Dev = VIRTIO_SCSI_FROM_PASS_THRU (This); + if (LastTarget > Dev->MaxTarget || *Lun > Dev->MaxLun) { + return EFI_INVALID_PARAMETER; + } + + if (*Lun < Dev->MaxLun) { + ++*Lun; + return EFI_SUCCESS; + } + + if (LastTarget < Dev->MaxTarget) { + *Lun = 0; + ++LastTarget; + CopyMem (Target, &LastTarget, sizeof LastTarget); + return EFI_SUCCESS; + } + + return EFI_NOT_FOUND; +} + + +EFI_STATUS +EFIAPI +VirtioScsiBuildDevicePath ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN UINT8 *Target, + IN UINT64 Lun, + IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath + ) +{ + UINT16 TargetValue; + VSCSI_DEV *Dev; + SCSI_DEVICE_PATH *ScsiDevicePath; + + if (DevicePath == NULL) { + return EFI_INVALID_PARAMETER; + } + + CopyMem (&TargetValue, Target, sizeof TargetValue); + Dev = VIRTIO_SCSI_FROM_PASS_THRU (This); + if (TargetValue > Dev->MaxTarget || Lun > Dev->MaxLun || Lun > 0xFFFF) { + return EFI_NOT_FOUND; + } + + ScsiDevicePath = AllocatePool (sizeof *ScsiDevicePath); + if (ScsiDevicePath == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + ScsiDevicePath->Header.Type = MESSAGING_DEVICE_PATH; + ScsiDevicePath->Header.SubType = MSG_SCSI_DP; + ScsiDevicePath->Header.Length[0] = (UINT8) sizeof *ScsiDevicePath; + ScsiDevicePath->Header.Length[1] = (UINT8) (sizeof *ScsiDevicePath >> 8); + ScsiDevicePath->Pun = TargetValue; + ScsiDevicePath->Lun = (UINT16) Lun; + + *DevicePath = &ScsiDevicePath->Header; + return EFI_SUCCESS; +} + + +EFI_STATUS +EFIAPI +VirtioScsiGetTargetLun ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, + OUT UINT8 **TargetPointer, + OUT UINT64 *Lun + ) +{ + SCSI_DEVICE_PATH *ScsiDevicePath; + VSCSI_DEV *Dev; + UINT8 *Target; + + if (DevicePath == NULL || TargetPointer == NULL || *TargetPointer == NULL || + Lun == NULL) { + return EFI_INVALID_PARAMETER; + } + + if (DevicePath->Type != MESSAGING_DEVICE_PATH || + DevicePath->SubType != MSG_SCSI_DP) { + return EFI_UNSUPPORTED; + } + + ScsiDevicePath = (SCSI_DEVICE_PATH *) DevicePath; + Dev = VIRTIO_SCSI_FROM_PASS_THRU (This); + if (ScsiDevicePath->Pun > Dev->MaxTarget || + ScsiDevicePath->Lun > Dev->MaxLun) { + return EFI_NOT_FOUND; + } + + // + // a) the TargetPointer input parameter is unnecessarily a pointer-to-pointer + // b) see the TARGET_MAX_BYTES check in "VirtioScsi.h" + // c) ScsiDevicePath->Pun is an UINT16 + // + Target = *TargetPointer; + CopyMem (Target, &ScsiDevicePath->Pun, 2); + SetMem (Target + 2, TARGET_MAX_BYTES - 2, 0x00); + + *Lun = ScsiDevicePath->Lun; + return EFI_SUCCESS; +} + + +EFI_STATUS +EFIAPI +VirtioScsiResetChannel ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This + ) +{ + return EFI_UNSUPPORTED; +} + + +EFI_STATUS +EFIAPI +VirtioScsiResetTargetLun ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN UINT8 *Target, + IN UINT64 Lun + ) +{ + return EFI_UNSUPPORTED; +} + + +EFI_STATUS +EFIAPI +VirtioScsiGetNextTarget ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN OUT UINT8 **TargetPointer + ) +{ + UINT8 *Target; + UINTN Idx; + UINT16 LastTarget; + VSCSI_DEV *Dev; + + // + // the TargetPointer input parameter is unnecessarily a pointer-to-pointer + // + Target = *TargetPointer; + + // + // Search for first non-0xFF byte. If not found, return first target. + // + for (Idx = 0; Idx < TARGET_MAX_BYTES && Target[Idx] == 0xFF; ++Idx) + ; + if (Idx == TARGET_MAX_BYTES) { + SetMem (Target, TARGET_MAX_BYTES, 0x00); + return EFI_SUCCESS; + } + + // + // see the TARGET_MAX_BYTES check in "VirtioScsi.h" + // + CopyMem (&LastTarget, Target, sizeof LastTarget); + + // + // increment target if valid on input + // + Dev = VIRTIO_SCSI_FROM_PASS_THRU (This); + if (LastTarget > Dev->MaxTarget) { + return EFI_INVALID_PARAMETER; + } + + if (LastTarget < Dev->MaxTarget) { + ++LastTarget; + CopyMem (Target, &LastTarget, sizeof LastTarget); + return EFI_SUCCESS; + } + + return EFI_NOT_FOUND; +} + + +STATIC +EFI_STATUS +EFIAPI +VirtioScsiInit ( + IN OUT VSCSI_DEV *Dev + ) +{ + UINT8 NextDevStat; + EFI_STATUS Status; + + UINT32 Features; + UINT16 MaxChannel; // for validation only + UINT32 NumQueues; // for validation only + UINT16 QueueSize; + + // + // Execute virtio-0.9.5, 2.2.1 Device Initialization Sequence. + // + NextDevStat = 0; // step 1 -- reset device + Status = VIRTIO_CFG_WRITE (Dev, Generic.VhdrDeviceStatus, NextDevStat); + if (EFI_ERROR (Status)) { + goto Failed; + } + + NextDevStat |= VSTAT_ACK; // step 2 -- acknowledge device presence + Status = VIRTIO_CFG_WRITE (Dev, Generic.VhdrDeviceStatus, NextDevStat); + if (EFI_ERROR (Status)) { + goto Failed; + } + + NextDevStat |= VSTAT_DRIVER; // step 3 -- we know how to drive it + Status = VIRTIO_CFG_WRITE (Dev, Generic.VhdrDeviceStatus, NextDevStat); + if (EFI_ERROR (Status)) { + goto Failed; + } + + // + // step 4a -- retrieve and validate features + // + Status = VIRTIO_CFG_READ (Dev, Generic.VhdrDeviceFeatureBits, &Features); + if (EFI_ERROR (Status)) { + goto Failed; + } + Dev->InOutSupported = !!(Features & VIRTIO_SCSI_F_INOUT); + + Status = VIRTIO_CFG_READ (Dev, VhdrMaxChannel, &MaxChannel); + if (EFI_ERROR (Status)) { + goto Failed; + } + if (MaxChannel != 0) { + // + // this driver is for a single-channel virtio-scsi HBA + // + Status = EFI_UNSUPPORTED; + goto Failed; + } + + Status = VIRTIO_CFG_READ (Dev, VhdrNumQueues, &NumQueues); + if (EFI_ERROR (Status)) { + goto Failed; + } + if (NumQueues < 1) { + Status = EFI_UNSUPPORTED; + goto Failed; + } + + Status = VIRTIO_CFG_READ (Dev, VhdrMaxTarget, &Dev->MaxTarget); + if (EFI_ERROR (Status)) { + goto Failed; + } + if (Dev->MaxTarget > PcdGet16 (PcdVirtioScsiMaxTargetLimit)) { + Dev->MaxTarget = PcdGet16 (PcdVirtioScsiMaxTargetLimit); + } + + Status = VIRTIO_CFG_READ (Dev, VhdrMaxLun, &Dev->MaxLun); + if (EFI_ERROR (Status)) { + goto Failed; + } + if (Dev->MaxLun > PcdGet32 (PcdVirtioScsiMaxLunLimit)) { + Dev->MaxLun = PcdGet32 (PcdVirtioScsiMaxLunLimit); + } + + Status = VIRTIO_CFG_READ (Dev, VhdrMaxSectors, &Dev->MaxSectors); + if (EFI_ERROR (Status)) { + goto Failed; + } + if (Dev->MaxSectors < 2) { + // + // We must be able to halve it for bidirectional transfers + // (see EFI_BAD_BUFFER_SIZE in PopulateRequest()). + // + Status = EFI_UNSUPPORTED; + goto Failed; + } + + // + // step 4b -- allocate request virtqueue + // + Status = VIRTIO_CFG_WRITE (Dev, Generic.VhdrQueueSelect, + VIRTIO_SCSI_REQUEST_QUEUE); + if (EFI_ERROR (Status)) { + goto Failed; + } + Status = VIRTIO_CFG_READ (Dev, Generic.VhdrQueueSize, &QueueSize); + if (EFI_ERROR (Status)) { + goto Failed; + } + // + // VirtioScsiPassThru() uses at most four descriptors + // + if (QueueSize < 4) { + 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, Generic.VhdrQueueAddress, + (UINTN) Dev->Ring.Base >> EFI_PAGE_SHIFT); + if (EFI_ERROR (Status)) { + goto ReleaseQueue; + } + + // + // step 5 -- Report understood features and guest-tuneables. We want none of + // the known (or unknown) VIRTIO_SCSI_F_* or VIRTIO_F_* capabilities (see + // virtio-0.9.5, Appendices B and I), except bidirectional transfers. + // + Status = VIRTIO_CFG_WRITE (Dev, Generic.VhdrGuestFeatureBits, + Features & VIRTIO_SCSI_F_INOUT); + if (EFI_ERROR (Status)) { + goto ReleaseQueue; + } + + // + // We expect these maximum sizes from the host. Since they are + // guest-negotiable, ask for them rather than just checking them. + // + Status = VIRTIO_CFG_WRITE (Dev, VhdrCdbSize, VIRTIO_SCSI_CDB_SIZE); + if (EFI_ERROR (Status)) { + goto ReleaseQueue; + } + Status = VIRTIO_CFG_WRITE (Dev, VhdrSenseSize, VIRTIO_SCSI_SENSE_SIZE); + if (EFI_ERROR (Status)) { + goto ReleaseQueue; + } + + // + // step 6 -- initialization complete + // + NextDevStat |= VSTAT_DRIVER_OK; + Status = VIRTIO_CFG_WRITE (Dev, Generic.VhdrDeviceStatus, NextDevStat); + if (EFI_ERROR (Status)) { + goto ReleaseQueue; + } + + // + // populate the exported interface's attributes + // + Dev->PassThru.Mode = &Dev->PassThruMode; + Dev->PassThru.PassThru = &VirtioScsiPassThru; + Dev->PassThru.GetNextTargetLun = &VirtioScsiGetNextTargetLun; + Dev->PassThru.BuildDevicePath = &VirtioScsiBuildDevicePath; + Dev->PassThru.GetTargetLun = &VirtioScsiGetTargetLun; + Dev->PassThru.ResetChannel = &VirtioScsiResetChannel; + Dev->PassThru.ResetTargetLun = &VirtioScsiResetTargetLun; + Dev->PassThru.GetNextTarget = &VirtioScsiGetNextTarget; + + // + // AdapterId is a target for which no handle will be created during bus scan. + // Prevent any conflict with real devices. + // + Dev->PassThruMode.AdapterId = 0xFFFFFFFF; + + // + // Set both physical and logical attributes for non-RAID SCSI channel. See + // Driver Writer's Guide for UEFI 2.3.1 v1.01, 20.1.5 Implementing Extended + // SCSI Pass Thru Protocol. + // + Dev->PassThruMode.Attributes = EFI_EXT_SCSI_PASS_THRU_ATTRIBUTES_PHYSICAL | + EFI_EXT_SCSI_PASS_THRU_ATTRIBUTES_LOGICAL; + + // + // no restriction on transfer buffer alignment + // + Dev->PassThruMode.IoAlign = 0; + + 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, Generic.VhdrDeviceStatus, NextDevStat); + + Dev->InOutSupported = FALSE; + Dev->MaxTarget = 0; + Dev->MaxLun = 0; + Dev->MaxSectors = 0; + + return Status; // reached only via Failed above +} + + + +STATIC +VOID +EFIAPI +VirtioScsiUninit ( + IN OUT VSCSI_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, Generic.VhdrDeviceStatus, 0); + + Dev->InOutSupported = FALSE; + Dev->MaxTarget = 0; + Dev->MaxLun = 0; + Dev->MaxSectors = 0; + + VirtioRingUninit (&Dev->Ring); + + SetMem (&Dev->PassThru, sizeof Dev->PassThru, 0x00); + SetMem (&Dev->PassThruMode, sizeof Dev->PassThruMode, 0x00); +} + + +// +// Probe, start and stop functions of this driver, called by the DXE core for +// specific devices. +// +// The following specifications document these interfaces: +// - Driver Writer's Guide for UEFI 2.3.1 v1.01, 9 Driver Binding Protocol +// - UEFI Spec 2.3.1 + Errata C, 10.1 EFI Driver Binding Protocol +// +// The implementation follows: +// - Driver Writer's Guide for UEFI 2.3.1 v1.01 +// - 5.1.3.4 OpenProtocol() and CloseProtocol() +// - 18 PCI Driver Design Guidelines +// - 18.3 PCI drivers +// - UEFI Spec 2.3.1 + Errata C +// - 6.3 Protocol Handler Services +// - 13.4 EFI PCI I/O Protocol +// + +EFI_STATUS +EFIAPI +VirtioScsiDriverBindingSupported ( + 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 == 0x08) ? 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; +} + + +EFI_STATUS +EFIAPI +VirtioScsiDriverBindingStart ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ) +{ + VSCSI_DEV *Dev; + EFI_STATUS Status; + + Dev = (VSCSI_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 FreeVirtioScsi; + } + + // + // 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-scsi 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-scsi device. + // + Status = VirtioScsiInit (Dev); + if (EFI_ERROR (Status)) { + goto RestorePciAttributes; + } + + // + // Setup complete, attempt to export the driver instance's PassThru + // interface. + // + Dev->Signature = VSCSI_SIG; + Status = gBS->InstallProtocolInterface (&DeviceHandle, + &gEfiExtScsiPassThruProtocolGuid, EFI_NATIVE_INTERFACE, + &Dev->PassThru); + if (EFI_ERROR (Status)) { + goto UninitDev; + } + + return EFI_SUCCESS; + +UninitDev: + VirtioScsiUninit (Dev); + +RestorePciAttributes: + Dev->PciIo->Attributes (Dev->PciIo, EfiPciIoAttributeOperationSet, + Dev->OriginalPciAttributes, NULL); + +ClosePciIo: + gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, + This->DriverBindingHandle, DeviceHandle); + +FreeVirtioScsi: + FreePool (Dev); + + return Status; +} + + +EFI_STATUS +EFIAPI +VirtioScsiDriverBindingStop ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer + ) +{ + EFI_STATUS Status; + EFI_EXT_SCSI_PASS_THRU_PROTOCOL *PassThru; + VSCSI_DEV *Dev; + + Status = gBS->OpenProtocol ( + DeviceHandle, // candidate device + &gEfiExtScsiPassThruProtocolGuid, // retrieve the SCSI iface + (VOID **)&PassThru, // target pointer + This->DriverBindingHandle, // requestor driver ident. + DeviceHandle, // lookup req. for dev. + EFI_OPEN_PROTOCOL_GET_PROTOCOL // lookup only, no new ref. + ); + if (EFI_ERROR (Status)) { + return Status; + } + + Dev = VIRTIO_SCSI_FROM_PASS_THRU (PassThru); + + // + // Handle Stop() requests for in-use driver instances gracefully. + // + Status = gBS->UninstallProtocolInterface (DeviceHandle, + &gEfiExtScsiPassThruProtocolGuid, &Dev->PassThru); + if (EFI_ERROR (Status)) { + return Status; + } + + VirtioScsiUninit (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 = { + &VirtioScsiDriverBindingSupported, + &VirtioScsiDriverBindingStart, + &VirtioScsiDriverBindingStop, + 0x10, // Version, must be in [0x10 .. 0xFFFFFFEF] for IHV-developed drivers + NULL, // ImageHandle, to be overwritten by + // EfiLibInstallDriverBindingComponentName2() in VirtioScsiEntryPoint() + 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 SCSI Host Device") are not formatted because the +// driver supports only that device type. Therefore the driver name suffices +// for unambiguous identification. +// + +STATIC +EFI_UNICODE_STRING_TABLE mDriverNameTable[] = { + { "eng;en", L"Virtio SCSI Host Driver" }, + { NULL, NULL } +}; + +STATIC +EFI_COMPONENT_NAME_PROTOCOL gComponentName; + +EFI_STATUS +EFIAPI +VirtioScsiGetDriverName ( + 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 +VirtioScsiGetDeviceName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_HANDLE ChildHandle, + IN CHAR8 *Language, + OUT CHAR16 **ControllerName + ) +{ + return EFI_UNSUPPORTED; +} + +STATIC +EFI_COMPONENT_NAME_PROTOCOL gComponentName = { + &VirtioScsiGetDriverName, + &VirtioScsiGetDeviceName, + "eng" // SupportedLanguages, ISO 639-2 language codes +}; + +STATIC +EFI_COMPONENT_NAME2_PROTOCOL gComponentName2 = { + (EFI_COMPONENT_NAME2_GET_DRIVER_NAME) &VirtioScsiGetDriverName, + (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME) &VirtioScsiGetDeviceName, + "en" // SupportedLanguages, RFC 4646 language codes +}; + + +// +// Entry point of this driver. +// +EFI_STATUS +EFIAPI +VirtioScsiEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + return EfiLibInstallDriverBindingComponentName2 ( + ImageHandle, + SystemTable, + &gDriverBinding, + ImageHandle, + &gComponentName, + &gComponentName2 + ); +} diff --git a/OvmfPkg/VirtioScsiDxe/VirtioScsi.h b/OvmfPkg/VirtioScsiDxe/VirtioScsi.h new file mode 100644 index 0000000000..f5220b2215 --- /dev/null +++ b/OvmfPkg/VirtioScsiDxe/VirtioScsi.h @@ -0,0 +1,209 @@ +/** @file + + Internal definitions for the virtio-scsi driver, which produces Extended SCSI + Pass Thru Protocol instances for virtio-scsi 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. + +**/ + +#ifndef _VIRTIO_SCSI_DXE_H_ +#define _VIRTIO_SCSI_DXE_H_ + +#include +#include +#include +#include + +#include + + +// +// This driver supports 2-byte target identifiers and 4-byte LUN identifiers. +// +// EFI_EXT_SCSI_PASS_THRU_PROTOCOL provides TARGET_MAX_BYTES bytes for target +// identification, and 8 bytes for LUN identification. +// +// EFI_EXT_SCSI_PASS_THRU_MODE.AdapterId is also a target identifier, +// consisting of 4 bytes. Make sure TARGET_MAX_BYTES can accomodate both +// AdapterId and our target identifiers. +// +#if TARGET_MAX_BYTES < 4 +# error "virtio-scsi requires TARGET_MAX_BYTES >= 4" +#endif + + +#define VSCSI_SIG SIGNATURE_32 ('V', 'S', 'C', 'S') + +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 depth + // ---------------------- ------------------ ---------- + UINT32 Signature; // DriverBindingStart 0 + EFI_PCI_IO_PROTOCOL *PciIo; // DriverBindingStart 0 + UINT64 OriginalPciAttributes; // DriverBindingStart 0 + BOOLEAN InOutSupported; // VirtioScsiInit 1 + UINT16 MaxTarget; // VirtioScsiInit 1 + UINT32 MaxLun; // VirtioScsiInit 1 + UINT32 MaxSectors; // VirtioScsiInit 1 + VRING Ring; // VirtioRingInit 2 + EFI_EXT_SCSI_PASS_THRU_PROTOCOL PassThru; // VirtioScsiInit 1 + EFI_EXT_SCSI_PASS_THRU_MODE PassThruMode; // VirtioScsiInit 1 +} VSCSI_DEV; + +#define VIRTIO_SCSI_FROM_PASS_THRU(PassThruPointer) \ + CR (PassThruPointer, VSCSI_DEV, PassThru, VSCSI_SIG) + + +// +// Probe, start and stop functions of this driver, called by the DXE core for +// specific devices. +// +// The following specifications document these interfaces: +// - Driver Writer's Guide for UEFI 2.3.1 v1.01, 9 Driver Binding Protocol +// - UEFI Spec 2.3.1 + Errata C, 10.1 EFI Driver Binding Protocol +// + +EFI_STATUS +EFIAPI +VirtioScsiDriverBindingSupported ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ); + + +EFI_STATUS +EFIAPI +VirtioScsiDriverBindingStart ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ); + + +EFI_STATUS +EFIAPI +VirtioScsiDriverBindingStop ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer + ); + + +// +// The next seven functions implement EFI_EXT_SCSI_PASS_THRU_PROTOCOL +// for the virtio-scsi HBA. Refer to UEFI Spec 2.3.1 + Errata C, sections +// - 14.1 SCSI Driver Model Overview, +// - 14.7 Extended SCSI Pass Thru Protocol. +// + +EFI_STATUS +EFIAPI +VirtioScsiPassThru ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN UINT8 *Target, + IN UINT64 Lun, + IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet, + IN EFI_EVENT Event OPTIONAL + ); + + +EFI_STATUS +EFIAPI +VirtioScsiGetNextTargetLun ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN OUT UINT8 **Target, + IN OUT UINT64 *Lun + ); + + +EFI_STATUS +EFIAPI +VirtioScsiBuildDevicePath ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN UINT8 *Target, + IN UINT64 Lun, + IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath + ); + + +EFI_STATUS +EFIAPI +VirtioScsiGetTargetLun ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, + OUT UINT8 **Target, + OUT UINT64 *Lun + ); + + +EFI_STATUS +EFIAPI +VirtioScsiResetChannel ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This + ); + + +EFI_STATUS +EFIAPI +VirtioScsiResetTargetLun ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN UINT8 *Target, + IN UINT64 Lun + ); + + +EFI_STATUS +EFIAPI +VirtioScsiGetNextTarget ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, + IN OUT UINT8 **Target + ); + + +// +// 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 SCSI Host Device") are not formatted because the +// driver supports only that device type. Therefore the driver name suffices +// for unambiguous identification. +// + +EFI_STATUS +EFIAPI +VirtioScsiGetDriverName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName + ); + + +EFI_STATUS +EFIAPI +VirtioScsiGetDeviceName ( + IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN EFI_HANDLE ChildHandle, + IN CHAR8 *Language, + OUT CHAR16 **ControllerName + ); + +#endif // _VIRTIO_SCSI_DXE_H_ diff --git a/OvmfPkg/VirtioScsiDxe/VirtioScsi.inf b/OvmfPkg/VirtioScsiDxe/VirtioScsi.inf new file mode 100644 index 0000000000..8209c50275 --- /dev/null +++ b/OvmfPkg/VirtioScsiDxe/VirtioScsi.inf @@ -0,0 +1,47 @@ +## @file +# This driver produces Extended SCSI Pass Thru Protocol instances for +# virtio-scsi 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 = VirtioScsiDxe + FILE_GUID = FAB5D4F4-83C0-4AAF-8480-442D11DF6CEA + MODULE_TYPE = UEFI_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = VirtioScsiEntryPoint + +[Sources] + VirtioScsi.c + +[Packages] + MdePkg/MdePkg.dec + OvmfPkg/OvmfPkg.dec + +[LibraryClasses] + BaseMemoryLib + DebugLib + MemoryAllocationLib + UefiBootServicesTableLib + UefiDriverEntryPoint + UefiLib + VirtioLib + +[Protocols] + gEfiExtScsiPassThruProtocolGuid ## BY_START + gEfiPciIoProtocolGuid ## TO_START + +[Pcd] + gUefiOvmfPkgTokenSpaceGuid.PcdVirtioScsiMaxTargetLimit ## CONSUMES + gUefiOvmfPkgTokenSpaceGuid.PcdVirtioScsiMaxLunLimit ## CONSUMES