aboutsummaryrefslogtreecommitdiffstats
path: root/roms/edk2/OvmfPkg/PvScsiDxe
diff options
context:
space:
mode:
Diffstat (limited to 'roms/edk2/OvmfPkg/PvScsiDxe')
-rw-r--r--roms/edk2/OvmfPkg/PvScsiDxe/PvScsi.c1559
-rw-r--r--roms/edk2/OvmfPkg/PvScsiDxe/PvScsi.h72
-rw-r--r--roms/edk2/OvmfPkg/PvScsiDxe/PvScsiDxe.inf44
3 files changed, 1675 insertions, 0 deletions
diff --git a/roms/edk2/OvmfPkg/PvScsiDxe/PvScsi.c b/roms/edk2/OvmfPkg/PvScsiDxe/PvScsi.c
new file mode 100644
index 000000000..843534ebf
--- /dev/null
+++ b/roms/edk2/OvmfPkg/PvScsiDxe/PvScsi.c
@@ -0,0 +1,1559 @@
+/** @file
+
+ This driver produces Extended SCSI Pass Thru Protocol instances for
+ pvscsi devices.
+
+ Copyright (C) 2020, Oracle and/or its affiliates.
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include <IndustryStandard/Pci.h>
+#include <IndustryStandard/PvScsi.h>
+#include <Library/BaseLib.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiLib.h>
+#include <Protocol/PciIo.h>
+#include <Protocol/PciRootBridgeIo.h>
+#include <Uefi/UefiSpec.h>
+
+#include "PvScsi.h"
+
+//
+// Higher versions will be used before lower, 0x10-0xffffffef is the version
+// range for IHV (Indie Hardware Vendors)
+//
+#define PVSCSI_BINDING_VERSION 0x10
+
+//
+// Ext SCSI Pass Thru utilities
+//
+
+/**
+ Reads a 32-bit value into BAR0 using MMIO
+**/
+STATIC
+EFI_STATUS
+PvScsiMmioRead32 (
+ IN CONST PVSCSI_DEV *Dev,
+ IN UINT64 Offset,
+ OUT UINT32 *Value
+ )
+{
+ return Dev->PciIo->Mem.Read (
+ Dev->PciIo,
+ EfiPciIoWidthUint32,
+ PCI_BAR_IDX0,
+ Offset,
+ 1, // Count
+ Value
+ );
+}
+
+/**
+ Writes a 32-bit value into BAR0 using MMIO
+**/
+STATIC
+EFI_STATUS
+PvScsiMmioWrite32 (
+ IN CONST PVSCSI_DEV *Dev,
+ IN UINT64 Offset,
+ IN UINT32 Value
+ )
+{
+ return Dev->PciIo->Mem.Write (
+ Dev->PciIo,
+ EfiPciIoWidthUint32,
+ PCI_BAR_IDX0,
+ Offset,
+ 1, // Count
+ &Value
+ );
+}
+
+/**
+ Writes multiple words of data into BAR0 using MMIO
+**/
+STATIC
+EFI_STATUS
+PvScsiMmioWrite32Multiple (
+ IN CONST PVSCSI_DEV *Dev,
+ IN UINT64 Offset,
+ IN UINTN Count,
+ IN UINT32 *Words
+ )
+{
+ return Dev->PciIo->Mem.Write (
+ Dev->PciIo,
+ EfiPciIoWidthFifoUint32,
+ PCI_BAR_IDX0,
+ Offset,
+ Count,
+ Words
+ );
+}
+
+/**
+ Send a PVSCSI command to device.
+
+ @param[in] Dev The pvscsi host device.
+ @param[in] Cmd The command to send to device.
+ @param[in] OPTIONAL DescWords An optional command descriptor (If command
+ have a descriptor). The descriptor is
+ provided as an array of UINT32 words and
+ is must be 32-bit aligned.
+ @param[in] DescWordsCount The number of words in command descriptor.
+ Caller must specify here 0 if DescWords
+ is not supplied (It is optional). In that
+ case, DescWords is ignored.
+
+ @return Status codes returned by Dev->PciIo->Mem.Write().
+
+**/
+STATIC
+EFI_STATUS
+PvScsiWriteCmdDesc (
+ IN CONST PVSCSI_DEV *Dev,
+ IN UINT32 Cmd,
+ IN UINT32 *DescWords OPTIONAL,
+ IN UINTN DescWordsCount
+ )
+{
+ EFI_STATUS Status;
+
+ if (DescWordsCount > PVSCSI_MAX_CMD_DATA_WORDS) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ Status = PvScsiMmioWrite32 (Dev, PvScsiRegOffsetCommand, Cmd);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ if (DescWordsCount > 0) {
+ return PvScsiMmioWrite32Multiple (
+ Dev,
+ PvScsiRegOffsetCommandData,
+ DescWordsCount,
+ DescWords
+ );
+ }
+
+ return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+PvScsiResetAdapter (
+ IN CONST PVSCSI_DEV *Dev
+ )
+{
+ return PvScsiWriteCmdDesc (Dev, PvScsiCmdAdapterReset, NULL, 0);
+}
+
+/**
+ Returns if PVSCSI request ring is full
+**/
+STATIC
+BOOLEAN
+PvScsiIsReqRingFull (
+ IN CONST PVSCSI_DEV *Dev
+ )
+{
+ PVSCSI_RINGS_STATE *RingsState;
+ UINT32 ReqNumEntries;
+
+ RingsState = Dev->RingDesc.RingState;
+ ReqNumEntries = 1U << RingsState->ReqNumEntriesLog2;
+ return (RingsState->ReqProdIdx - RingsState->CmpConsIdx) >= ReqNumEntries;
+}
+
+/**
+ Returns pointer to current request descriptor to produce
+**/
+STATIC
+PVSCSI_RING_REQ_DESC *
+PvScsiGetCurrentRequest (
+ IN CONST PVSCSI_DEV *Dev
+ )
+{
+ PVSCSI_RINGS_STATE *RingState;
+ UINT32 ReqNumEntries;
+
+ RingState = Dev->RingDesc.RingState;
+ ReqNumEntries = 1U << RingState->ReqNumEntriesLog2;
+ return Dev->RingDesc.RingReqs +
+ (RingState->ReqProdIdx & (ReqNumEntries - 1));
+}
+
+/**
+ Returns pointer to current completion descriptor to consume
+**/
+STATIC
+PVSCSI_RING_CMP_DESC *
+PvScsiGetCurrentResponse (
+ IN CONST PVSCSI_DEV *Dev
+ )
+{
+ PVSCSI_RINGS_STATE *RingState;
+ UINT32 CmpNumEntries;
+
+ RingState = Dev->RingDesc.RingState;
+ CmpNumEntries = 1U << RingState->CmpNumEntriesLog2;
+ return Dev->RingDesc.RingCmps +
+ (RingState->CmpConsIdx & (CmpNumEntries - 1));
+}
+
+/**
+ Wait for device to signal completion of submitted requests
+**/
+STATIC
+EFI_STATUS
+PvScsiWaitForRequestCompletion (
+ IN CONST PVSCSI_DEV *Dev
+ )
+{
+ EFI_STATUS Status;
+ UINT32 IntrStatus;
+
+ //
+ // Note: We don't yet support Timeout according to
+ // EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET.Timeout.
+ //
+ // This is consistent with some other Scsi PassThru drivers
+ // such as VirtioScsi.
+ //
+ for (;;) {
+ Status = PvScsiMmioRead32 (Dev, PvScsiRegOffsetIntrStatus, &IntrStatus);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ //
+ // PVSCSI_INTR_CMPL_MASK is set if device completed submitted requests
+ //
+ if ((IntrStatus & PVSCSI_INTR_CMPL_MASK) != 0) {
+ break;
+ }
+
+ gBS->Stall (Dev->WaitForCmpStallInUsecs);
+ }
+
+ //
+ // Acknowledge PVSCSI_INTR_CMPL_MASK in device interrupt-status register
+ //
+ return PvScsiMmioWrite32 (
+ Dev,
+ PvScsiRegOffsetIntrStatus,
+ PVSCSI_INTR_CMPL_MASK
+ );
+}
+
+/**
+ Create a fake host adapter error
+**/
+STATIC
+EFI_STATUS
+ReportHostAdapterError (
+ OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet
+ )
+{
+ Packet->InTransferLength = 0;
+ Packet->OutTransferLength = 0;
+ Packet->SenseDataLength = 0;
+ Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;
+ Packet->TargetStatus = EFI_EXT_SCSI_STATUS_TARGET_GOOD;
+ return EFI_DEVICE_ERROR;
+}
+
+/**
+ Create a fake host adapter overrun error
+**/
+STATIC
+EFI_STATUS
+ReportHostAdapterOverrunError (
+ OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet
+ )
+{
+ Packet->SenseDataLength = 0;
+ Packet->HostAdapterStatus =
+ EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN;
+ Packet->TargetStatus = EFI_EXT_SCSI_STATUS_TARGET_GOOD;
+ return EFI_BAD_BUFFER_SIZE;
+}
+
+/**
+ Populate a PVSCSI request descriptor from the Extended SCSI Pass Thru
+ Protocol packet.
+**/
+STATIC
+EFI_STATUS
+PopulateRequest (
+ IN CONST PVSCSI_DEV *Dev,
+ IN UINT8 *Target,
+ IN UINT64 Lun,
+ IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet,
+ OUT PVSCSI_RING_REQ_DESC *Request
+ )
+{
+ UINT8 TargetValue;
+
+ //
+ // We only use first byte of target identifer
+ //
+ TargetValue = *Target;
+
+ //
+ // Check for unsupported requests
+ //
+ if (
+ //
+ // Bidirectional transfer was requested
+ //
+ (Packet->InTransferLength > 0 && Packet->OutTransferLength > 0) ||
+ (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_BIDIRECTIONAL) ||
+ //
+ // Command Descriptor Block bigger than this constant should be considered
+ // out-of-band. We currently don't support these CDBs.
+ //
+ (Packet->CdbLength > PVSCSI_CDB_MAX_SIZE)
+ ) {
+
+ //
+ // This error code doesn't require updates to the Packet output fields
+ //
+ return EFI_UNSUPPORTED;
+ }
+
+ //
+ // Check for invalid parameters
+ //
+ if (
+ //
+ // Addressed invalid device
+ //
+ (TargetValue > 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;
+ }
+
+ //
+ // Check for input/output buffer too large for DMA communication buffer
+ //
+ if (Packet->InTransferLength > sizeof (Dev->DmaBuf->Data)) {
+ Packet->InTransferLength = sizeof (Dev->DmaBuf->Data);
+ return ReportHostAdapterOverrunError (Packet);
+ }
+ if (Packet->OutTransferLength > sizeof (Dev->DmaBuf->Data)) {
+ Packet->OutTransferLength = sizeof (Dev->DmaBuf->Data);
+ return ReportHostAdapterOverrunError (Packet);
+ }
+
+ //
+ // Encode PVSCSI request
+ //
+ ZeroMem (Request, sizeof (*Request));
+
+ Request->Bus = 0;
+ Request->Target = TargetValue;
+ //
+ // This cast is safe as PVSCSI_DEV.MaxLun is defined as UINT8
+ //
+ Request->Lun[1] = (UINT8)Lun;
+ Request->SenseLen = Packet->SenseDataLength;
+ //
+ // DMA communication buffer SenseData overflow is not possible
+ // due to Packet->SenseDataLength defined as UINT8
+ //
+ Request->SenseAddr = PVSCSI_DMA_BUF_DEV_ADDR (Dev, SenseData);
+ Request->CdbLen = Packet->CdbLength;
+ CopyMem (Request->Cdb, Packet->Cdb, Packet->CdbLength);
+ Request->VcpuHint = 0;
+ Request->Tag = PVSCSI_SIMPLE_QUEUE_TAG;
+ if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {
+ Request->Flags = PVSCSI_FLAG_CMD_DIR_TOHOST;
+ Request->DataLen = Packet->InTransferLength;
+ } else {
+ Request->Flags = PVSCSI_FLAG_CMD_DIR_TODEVICE;
+ Request->DataLen = Packet->OutTransferLength;
+ CopyMem (
+ Dev->DmaBuf->Data,
+ Packet->OutDataBuffer,
+ Packet->OutTransferLength
+ );
+ }
+ Request->DataAddr = PVSCSI_DMA_BUF_DEV_ADDR (Dev, Data);
+
+ return EFI_SUCCESS;
+}
+
+/**
+ Handle the PVSCSI device response:
+ - Copy returned data from DMA communication buffer.
+ - Update fields in Extended SCSI Pass Thru Protocol packet as required.
+ - Translate response code to EFI status code and host adapter status.
+**/
+STATIC
+EFI_STATUS
+HandleResponse (
+ IN PVSCSI_DEV *Dev,
+ IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet,
+ IN CONST PVSCSI_RING_CMP_DESC *Response
+ )
+{
+ //
+ // Fix SenseDataLength to amount of data returned
+ //
+ if (Packet->SenseDataLength > Response->SenseLen) {
+ Packet->SenseDataLength = (UINT8)Response->SenseLen;
+ }
+ //
+ // Copy sense data from DMA communication buffer
+ //
+ CopyMem (
+ Packet->SenseData,
+ Dev->DmaBuf->SenseData,
+ Packet->SenseDataLength
+ );
+
+ //
+ // Copy device output from DMA communication buffer
+ //
+ if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {
+ CopyMem (Packet->InDataBuffer, Dev->DmaBuf->Data, Packet->InTransferLength);
+ }
+
+ //
+ // Report target status
+ // (Strangely, PVSCSI interface defines Response->ScsiStatus as UINT16.
+ // But it should de-facto always have a value that fits UINT8. To avoid
+ // unexpected behavior, verify value is in UINT8 bounds before casting)
+ //
+ ASSERT (Response->ScsiStatus <= MAX_UINT8);
+ Packet->TargetStatus = (UINT8)Response->ScsiStatus;
+
+ //
+ // Host adapter status and function return value depend on
+ // device response's host status
+ //
+ switch (Response->HostStatus) {
+ case PvScsiBtStatSuccess:
+ case PvScsiBtStatLinkedCommandCompleted:
+ case PvScsiBtStatLinkedCommandCompletedWithFlag:
+ Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK;
+ return EFI_SUCCESS;
+
+ case PvScsiBtStatDataUnderrun:
+ //
+ // Report transferred amount in underrun
+ //
+ if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {
+ Packet->InTransferLength = (UINT32)Response->DataLen;
+ } else {
+ Packet->OutTransferLength = (UINT32)Response->DataLen;
+ }
+ Packet->HostAdapterStatus =
+ EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN;
+ return EFI_SUCCESS;
+
+ case PvScsiBtStatDatarun:
+ Packet->HostAdapterStatus =
+ EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN;
+ return EFI_SUCCESS;
+
+ case PvScsiBtStatSelTimeout:
+ Packet->HostAdapterStatus =
+ EFI_EXT_SCSI_STATUS_HOST_ADAPTER_SELECTION_TIMEOUT;
+ return EFI_TIMEOUT;
+
+ case PvScsiBtStatBusFree:
+ Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_FREE;
+ break;
+
+ case PvScsiBtStatInvPhase:
+ Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PHASE_ERROR;
+ break;
+
+ case PvScsiBtStatSensFailed:
+ Packet->HostAdapterStatus =
+ EFI_EXT_SCSI_STATUS_HOST_ADAPTER_REQUEST_SENSE_FAILED;
+ break;
+
+ case PvScsiBtStatTagReject:
+ case PvScsiBtStatBadMsg:
+ Packet->HostAdapterStatus =
+ EFI_EXT_SCSI_STATUS_HOST_ADAPTER_MESSAGE_REJECT;
+ break;
+
+ case PvScsiBtStatBusReset:
+ Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_RESET;
+ break;
+
+ case PvScsiBtStatHaTimeout:
+ Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT;
+ return EFI_TIMEOUT;
+
+ case PvScsiBtStatScsiParity:
+ Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PARITY_ERROR;
+ break;
+
+ default:
+ Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;
+ break;
+ }
+
+ return EFI_DEVICE_ERROR;
+}
+
+/**
+ Check if Target argument to EXT_SCSI_PASS_THRU.GetNextTarget() and
+ EXT_SCSI_PASS_THRU.GetNextTargetLun() is initialized
+**/
+STATIC
+BOOLEAN
+IsTargetInitialized (
+ IN UINT8 *Target
+ )
+{
+ UINTN Idx;
+
+ for (Idx = 0; Idx < TARGET_MAX_BYTES; ++Idx) {
+ if (Target[Idx] != 0xFF) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+//
+// Ext SCSI Pass Thru
+//
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiPassThru (
+ 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
+ )
+{
+ PVSCSI_DEV *Dev;
+ EFI_STATUS Status;
+ PVSCSI_RING_REQ_DESC *Request;
+ PVSCSI_RING_CMP_DESC *Response;
+
+ Dev = PVSCSI_FROM_PASS_THRU (This);
+
+ if (PvScsiIsReqRingFull (Dev)) {
+ return EFI_NOT_READY;
+ }
+
+ Request = PvScsiGetCurrentRequest (Dev);
+
+ Status = PopulateRequest (Dev, Target, Lun, Packet, Request);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ //
+ // Writes to Request must be globally visible before making request
+ // available to device
+ //
+ MemoryFence ();
+ Dev->RingDesc.RingState->ReqProdIdx++;
+
+ Status = PvScsiMmioWrite32 (Dev, PvScsiRegOffsetKickRwIo, 0);
+ if (EFI_ERROR (Status)) {
+ //
+ // 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.
+ //
+ return ReportHostAdapterError (Packet);
+ }
+
+ Status = PvScsiWaitForRequestCompletion (Dev);
+ if (EFI_ERROR (Status)) {
+ //
+ // If waiting for request completion 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.
+ //
+ return ReportHostAdapterError (Packet);
+ }
+
+ Response = PvScsiGetCurrentResponse (Dev);
+ Status = HandleResponse (Dev, Packet, Response);
+
+ //
+ // Reads from response must complete before releasing completion entry
+ // to device
+ //
+ MemoryFence ();
+ Dev->RingDesc.RingState->CmpConsIdx++;
+
+ return Status;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiGetNextTargetLun (
+ IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This,
+ IN OUT UINT8 **Target,
+ IN OUT UINT64 *Lun
+ )
+{
+ UINT8 *TargetPtr;
+ UINT8 LastTarget;
+ PVSCSI_DEV *Dev;
+
+ if (Target == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ //
+ // The Target input parameter is unnecessarily a pointer-to-pointer
+ //
+ TargetPtr = *Target;
+
+ //
+ // If target not initialized, return first target & LUN
+ //
+ if (!IsTargetInitialized (TargetPtr)) {
+ ZeroMem (TargetPtr, TARGET_MAX_BYTES);
+ *Lun = 0;
+ return EFI_SUCCESS;
+ }
+
+ //
+ // We only use first byte of target identifer
+ //
+ LastTarget = *TargetPtr;
+
+ //
+ // Increment (target, LUN) pair if valid on input
+ //
+ Dev = PVSCSI_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;
+ *TargetPtr = LastTarget;
+ return EFI_SUCCESS;
+ }
+
+ return EFI_NOT_FOUND;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiBuildDevicePath (
+ IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This,
+ IN UINT8 *Target,
+ IN UINT64 Lun,
+ IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath
+ )
+{
+ UINT8 TargetValue;
+ PVSCSI_DEV *Dev;
+ SCSI_DEVICE_PATH *ScsiDevicePath;
+
+ if (DevicePath == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ //
+ // We only use first byte of target identifer
+ //
+ TargetValue = *Target;
+
+ Dev = PVSCSI_FROM_PASS_THRU (This);
+ if (TargetValue > Dev->MaxTarget || Lun > Dev->MaxLun) {
+ 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;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiGetTargetLun (
+ IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This,
+ IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
+ OUT UINT8 **Target,
+ OUT UINT64 *Lun
+ )
+{
+ SCSI_DEVICE_PATH *ScsiDevicePath;
+ PVSCSI_DEV *Dev;
+
+ if (DevicePath == NULL || Target == NULL || *Target == 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 = PVSCSI_FROM_PASS_THRU (This);
+ if (ScsiDevicePath->Pun > Dev->MaxTarget ||
+ ScsiDevicePath->Lun > Dev->MaxLun) {
+ return EFI_NOT_FOUND;
+ }
+
+ //
+ // We only use first byte of target identifer
+ //
+ **Target = (UINT8)ScsiDevicePath->Pun;
+ ZeroMem (*Target + 1, TARGET_MAX_BYTES - 1);
+ *Lun = ScsiDevicePath->Lun;
+
+ return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiResetChannel (
+ IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This
+ )
+{
+ return EFI_UNSUPPORTED;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiResetTargetLun (
+ IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This,
+ IN UINT8 *Target,
+ IN UINT64 Lun
+ )
+{
+ return EFI_UNSUPPORTED;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiGetNextTarget (
+ IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This,
+ IN OUT UINT8 **Target
+ )
+{
+ UINT8 *TargetPtr;
+ UINT8 LastTarget;
+ PVSCSI_DEV *Dev;
+
+ if (Target == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ //
+ // The Target input parameter is unnecessarily a pointer-to-pointer
+ //
+ TargetPtr = *Target;
+
+ //
+ // If target not initialized, return first target
+ //
+ if (!IsTargetInitialized (TargetPtr)) {
+ ZeroMem (TargetPtr, TARGET_MAX_BYTES);
+ return EFI_SUCCESS;
+ }
+
+ //
+ // We only use first byte of target identifer
+ //
+ LastTarget = *TargetPtr;
+
+ //
+ // Increment target if valid on input
+ //
+ Dev = PVSCSI_FROM_PASS_THRU (This);
+ if (LastTarget > Dev->MaxTarget) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ if (LastTarget < Dev->MaxTarget) {
+ ++LastTarget;
+ *TargetPtr = LastTarget;
+ return EFI_SUCCESS;
+ }
+
+ return EFI_NOT_FOUND;
+}
+
+STATIC
+EFI_STATUS
+PvScsiSetPciAttributes (
+ IN OUT PVSCSI_DEV *Dev
+ )
+{
+ EFI_STATUS Status;
+
+ //
+ // Backup original PCI Attributes
+ //
+ Status = Dev->PciIo->Attributes (
+ Dev->PciIo,
+ EfiPciIoAttributeOperationGet,
+ 0,
+ &Dev->OriginalPciAttributes
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ //
+ // Enable MMIO-Space & Bus-Mastering
+ //
+ Status = Dev->PciIo->Attributes (
+ Dev->PciIo,
+ EfiPciIoAttributeOperationEnable,
+ (EFI_PCI_IO_ATTRIBUTE_MEMORY |
+ EFI_PCI_IO_ATTRIBUTE_BUS_MASTER),
+ NULL
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ //
+ // Signal device supports 64-bit DMA addresses
+ //
+ Status = Dev->PciIo->Attributes (
+ Dev->PciIo,
+ EfiPciIoAttributeOperationEnable,
+ EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE,
+ NULL
+ );
+ if (EFI_ERROR (Status)) {
+ //
+ // Warn user that device will only be using 32-bit DMA addresses.
+ //
+ // Note that this does not prevent the device/driver from working
+ // and therefore we only warn and continue as usual.
+ //
+ DEBUG ((
+ DEBUG_WARN,
+ "%a: failed to enable 64-bit DMA addresses\n",
+ __FUNCTION__
+ ));
+ }
+
+ return EFI_SUCCESS;
+}
+
+STATIC
+VOID
+PvScsiRestorePciAttributes (
+ IN PVSCSI_DEV *Dev
+ )
+{
+ Dev->PciIo->Attributes (
+ Dev->PciIo,
+ EfiPciIoAttributeOperationSet,
+ Dev->OriginalPciAttributes,
+ NULL
+ );
+}
+
+STATIC
+EFI_STATUS
+PvScsiAllocateSharedPages (
+ IN PVSCSI_DEV *Dev,
+ IN UINTN Pages,
+ OUT VOID **HostAddress,
+ OUT PVSCSI_DMA_DESC *DmaDesc
+ )
+{
+ EFI_STATUS Status;
+ UINTN NumberOfBytes;
+
+ Status = Dev->PciIo->AllocateBuffer (
+ Dev->PciIo,
+ AllocateAnyPages,
+ EfiBootServicesData,
+ Pages,
+ HostAddress,
+ EFI_PCI_ATTRIBUTE_MEMORY_CACHED
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ NumberOfBytes = EFI_PAGES_TO_SIZE (Pages);
+ Status = Dev->PciIo->Map (
+ Dev->PciIo,
+ EfiPciIoOperationBusMasterCommonBuffer,
+ *HostAddress,
+ &NumberOfBytes,
+ &DmaDesc->DeviceAddress,
+ &DmaDesc->Mapping
+ );
+ if (EFI_ERROR (Status)) {
+ goto FreeBuffer;
+ }
+
+ if (NumberOfBytes != EFI_PAGES_TO_SIZE (Pages)) {
+ Status = EFI_OUT_OF_RESOURCES;
+ goto Unmap;
+ }
+
+ return EFI_SUCCESS;
+
+Unmap:
+ Dev->PciIo->Unmap (Dev->PciIo, DmaDesc->Mapping);
+
+FreeBuffer:
+ Dev->PciIo->FreeBuffer (Dev->PciIo, Pages, *HostAddress);
+
+ return Status;
+}
+
+STATIC
+VOID
+PvScsiFreeSharedPages (
+ IN PVSCSI_DEV *Dev,
+ IN UINTN Pages,
+ IN VOID *HostAddress,
+ IN PVSCSI_DMA_DESC *DmaDesc
+ )
+{
+ Dev->PciIo->Unmap (Dev->PciIo, DmaDesc->Mapping);
+ Dev->PciIo->FreeBuffer (Dev->PciIo, Pages, HostAddress);
+}
+
+STATIC
+EFI_STATUS
+PvScsiInitRings (
+ IN OUT PVSCSI_DEV *Dev
+ )
+{
+ EFI_STATUS Status;
+
+ Status = PvScsiAllocateSharedPages (
+ Dev,
+ 1,
+ (VOID **)&Dev->RingDesc.RingState,
+ &Dev->RingDesc.RingStateDmaDesc
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+ ZeroMem (Dev->RingDesc.RingState, EFI_PAGE_SIZE);
+
+ Status = PvScsiAllocateSharedPages (
+ Dev,
+ 1,
+ (VOID **)&Dev->RingDesc.RingReqs,
+ &Dev->RingDesc.RingReqsDmaDesc
+ );
+ if (EFI_ERROR (Status)) {
+ goto FreeRingState;
+ }
+ ZeroMem (Dev->RingDesc.RingReqs, EFI_PAGE_SIZE);
+
+ Status = PvScsiAllocateSharedPages (
+ Dev,
+ 1,
+ (VOID **)&Dev->RingDesc.RingCmps,
+ &Dev->RingDesc.RingCmpsDmaDesc
+ );
+ if (EFI_ERROR (Status)) {
+ goto FreeRingReqs;
+ }
+ ZeroMem (Dev->RingDesc.RingCmps, EFI_PAGE_SIZE);
+
+ return EFI_SUCCESS;
+
+FreeRingReqs:
+ PvScsiFreeSharedPages (
+ Dev,
+ 1,
+ Dev->RingDesc.RingReqs,
+ &Dev->RingDesc.RingReqsDmaDesc
+ );
+
+FreeRingState:
+ PvScsiFreeSharedPages (
+ Dev,
+ 1,
+ Dev->RingDesc.RingState,
+ &Dev->RingDesc.RingStateDmaDesc
+ );
+
+ return Status;
+}
+
+STATIC
+VOID
+PvScsiFreeRings (
+ IN OUT PVSCSI_DEV *Dev
+ )
+{
+ PvScsiFreeSharedPages (
+ Dev,
+ 1,
+ Dev->RingDesc.RingCmps,
+ &Dev->RingDesc.RingCmpsDmaDesc
+ );
+
+ PvScsiFreeSharedPages (
+ Dev,
+ 1,
+ Dev->RingDesc.RingReqs,
+ &Dev->RingDesc.RingReqsDmaDesc
+ );
+
+ PvScsiFreeSharedPages (
+ Dev,
+ 1,
+ Dev->RingDesc.RingState,
+ &Dev->RingDesc.RingStateDmaDesc
+ );
+}
+
+STATIC
+EFI_STATUS
+PvScsiSetupRings (
+ IN OUT PVSCSI_DEV *Dev
+ )
+{
+ union {
+ PVSCSI_CMD_DESC_SETUP_RINGS Cmd;
+ UINT32 Uint32;
+ } AlignedCmd;
+ PVSCSI_CMD_DESC_SETUP_RINGS *Cmd;
+
+ Cmd = &AlignedCmd.Cmd;
+
+ ZeroMem (Cmd, sizeof (*Cmd));
+ Cmd->ReqRingNumPages = 1;
+ Cmd->CmpRingNumPages = 1;
+ Cmd->RingsStatePPN = RShiftU64 (
+ Dev->RingDesc.RingStateDmaDesc.DeviceAddress,
+ EFI_PAGE_SHIFT
+ );
+ Cmd->ReqRingPPNs[0] = RShiftU64 (
+ Dev->RingDesc.RingReqsDmaDesc.DeviceAddress,
+ EFI_PAGE_SHIFT
+ );
+ Cmd->CmpRingPPNs[0] = RShiftU64 (
+ Dev->RingDesc.RingCmpsDmaDesc.DeviceAddress,
+ EFI_PAGE_SHIFT
+ );
+
+ STATIC_ASSERT (
+ sizeof (*Cmd) % sizeof (UINT32) == 0,
+ "Cmd must be multiple of 32-bit words"
+ );
+ return PvScsiWriteCmdDesc (
+ Dev,
+ PvScsiCmdSetupRings,
+ (UINT32 *)Cmd,
+ sizeof (*Cmd) / sizeof (UINT32)
+ );
+}
+
+STATIC
+EFI_STATUS
+PvScsiInit (
+ IN OUT PVSCSI_DEV *Dev
+ )
+{
+ EFI_STATUS Status;
+
+ //
+ // Init configuration
+ //
+ Dev->MaxTarget = PcdGet8 (PcdPvScsiMaxTargetLimit);
+ Dev->MaxLun = PcdGet8 (PcdPvScsiMaxLunLimit);
+ Dev->WaitForCmpStallInUsecs = PcdGet32 (PcdPvScsiWaitForCmpStallInUsecs);
+
+ //
+ // Set PCI Attributes
+ //
+ Status = PvScsiSetPciAttributes (Dev);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ //
+ // Reset adapter
+ //
+ Status = PvScsiResetAdapter (Dev);
+ if (EFI_ERROR (Status)) {
+ goto RestorePciAttributes;
+ }
+
+ //
+ // Init PVSCSI rings
+ //
+ Status = PvScsiInitRings (Dev);
+ if (EFI_ERROR (Status)) {
+ goto RestorePciAttributes;
+ }
+
+ //
+ // Allocate DMA communication buffer
+ //
+ Status = PvScsiAllocateSharedPages (
+ Dev,
+ EFI_SIZE_TO_PAGES (sizeof (*Dev->DmaBuf)),
+ (VOID **)&Dev->DmaBuf,
+ &Dev->DmaBufDmaDesc
+ );
+ if (EFI_ERROR (Status)) {
+ goto FreeRings;
+ }
+
+ //
+ // Setup rings against device
+ //
+ Status = PvScsiSetupRings (Dev);
+ if (EFI_ERROR (Status)) {
+ goto FreeDmaCommBuffer;
+ }
+
+ //
+ // Populate the exported interface's attributes
+ //
+ Dev->PassThru.Mode = &Dev->PassThruMode;
+ Dev->PassThru.PassThru = &PvScsiPassThru;
+ Dev->PassThru.GetNextTargetLun = &PvScsiGetNextTargetLun;
+ Dev->PassThru.BuildDevicePath = &PvScsiBuildDevicePath;
+ Dev->PassThru.GetTargetLun = &PvScsiGetTargetLun;
+ Dev->PassThru.ResetChannel = &PvScsiResetChannel;
+ Dev->PassThru.ResetTargetLun = &PvScsiResetTargetLun;
+ Dev->PassThru.GetNextTarget = &PvScsiGetNextTarget;
+
+ //
+ // AdapterId is a target for which no handle will be created during bus scan.
+ // Prevent any conflict with real devices.
+ //
+ Dev->PassThruMode.AdapterId = MAX_UINT32;
+
+ //
+ // Set both physical and logical attributes for non-RAID SCSI channel
+ //
+ 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;
+
+FreeDmaCommBuffer:
+ PvScsiFreeSharedPages (
+ Dev,
+ EFI_SIZE_TO_PAGES (sizeof (*Dev->DmaBuf)),
+ Dev->DmaBuf,
+ &Dev->DmaBufDmaDesc
+ );
+
+FreeRings:
+ PvScsiFreeRings (Dev);
+
+RestorePciAttributes:
+ PvScsiRestorePciAttributes (Dev);
+
+ return Status;
+}
+
+STATIC
+VOID
+PvScsiUninit (
+ IN OUT PVSCSI_DEV *Dev
+ )
+{
+ //
+ // Reset device to:
+ // - Make device stop processing all requests.
+ // - Stop device usage of the rings.
+ //
+ // This is required to safely free the DMA communication buffer
+ // and the rings.
+ //
+ PvScsiResetAdapter (Dev);
+
+ //
+ // Free DMA communication buffer
+ //
+ PvScsiFreeSharedPages (
+ Dev,
+ EFI_SIZE_TO_PAGES (sizeof (*Dev->DmaBuf)),
+ Dev->DmaBuf,
+ &Dev->DmaBufDmaDesc
+ );
+
+ PvScsiFreeRings (Dev);
+
+ PvScsiRestorePciAttributes (Dev);
+}
+
+/**
+ Event notification called by ExitBootServices()
+**/
+STATIC
+VOID
+EFIAPI
+PvScsiExitBoot (
+ IN EFI_EVENT Event,
+ IN VOID *Context
+ )
+{
+ PVSCSI_DEV *Dev;
+
+ Dev = Context;
+ DEBUG ((DEBUG_VERBOSE, "%a: Context=0x%p\n", __FUNCTION__, Context));
+
+ //
+ // Reset the device to stop device usage of the rings.
+ //
+ // We allocated said rings in EfiBootServicesData type memory, and code
+ // executing after ExitBootServices() is permitted to overwrite it.
+ //
+ PvScsiResetAdapter (Dev);
+}
+
+//
+// Driver Binding
+//
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiDriverBindingSupported (
+ IN EFI_DRIVER_BINDING_PROTOCOL *This,
+ IN EFI_HANDLE ControllerHandle,
+ IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
+ )
+{
+ EFI_STATUS Status;
+ EFI_PCI_IO_PROTOCOL *PciIo;
+ PCI_TYPE00 Pci;
+
+ Status = gBS->OpenProtocol (
+ ControllerHandle,
+ &gEfiPciIoProtocolGuid,
+ (VOID **)&PciIo,
+ This->DriverBindingHandle,
+ ControllerHandle,
+ EFI_OPEN_PROTOCOL_BY_DRIVER
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Status = PciIo->Pci.Read (
+ PciIo,
+ EfiPciIoWidthUint32,
+ 0,
+ sizeof (Pci) / sizeof (UINT32),
+ &Pci
+ );
+ if (EFI_ERROR (Status)) {
+ goto Done;
+ }
+
+ if ((Pci.Hdr.VendorId != PCI_VENDOR_ID_VMWARE) ||
+ (Pci.Hdr.DeviceId != PCI_DEVICE_ID_VMWARE_PVSCSI)) {
+ Status = EFI_UNSUPPORTED;
+ goto Done;
+ }
+
+ Status = EFI_SUCCESS;
+
+Done:
+ gBS->CloseProtocol (
+ ControllerHandle,
+ &gEfiPciIoProtocolGuid,
+ This->DriverBindingHandle,
+ ControllerHandle
+ );
+
+ return Status;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiDriverBindingStart (
+ IN EFI_DRIVER_BINDING_PROTOCOL *This,
+ IN EFI_HANDLE ControllerHandle,
+ IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
+ )
+{
+ PVSCSI_DEV *Dev;
+ EFI_STATUS Status;
+
+ Dev = (PVSCSI_DEV *) AllocateZeroPool (sizeof (*Dev));
+ if (Dev == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ Status = gBS->OpenProtocol (
+ ControllerHandle,
+ &gEfiPciIoProtocolGuid,
+ (VOID **)&Dev->PciIo,
+ This->DriverBindingHandle,
+ ControllerHandle,
+ EFI_OPEN_PROTOCOL_BY_DRIVER
+ );
+ if (EFI_ERROR (Status)) {
+ goto FreePvScsi;
+ }
+
+ Status = PvScsiInit (Dev);
+ if (EFI_ERROR (Status)) {
+ goto ClosePciIo;
+ }
+
+ Status = gBS->CreateEvent (
+ EVT_SIGNAL_EXIT_BOOT_SERVICES,
+ TPL_CALLBACK,
+ &PvScsiExitBoot,
+ Dev,
+ &Dev->ExitBoot
+ );
+ if (EFI_ERROR (Status)) {
+ goto UninitDev;
+ }
+
+ //
+ // Setup complete, attempt to export the driver instance's PassThru interface
+ //
+ Dev->Signature = PVSCSI_SIG;
+ Status = gBS->InstallProtocolInterface (
+ &ControllerHandle,
+ &gEfiExtScsiPassThruProtocolGuid,
+ EFI_NATIVE_INTERFACE,
+ &Dev->PassThru
+ );
+ if (EFI_ERROR (Status)) {
+ goto CloseExitBoot;
+ }
+
+ return EFI_SUCCESS;
+
+CloseExitBoot:
+ gBS->CloseEvent (Dev->ExitBoot);
+
+UninitDev:
+ PvScsiUninit (Dev);
+
+ClosePciIo:
+ gBS->CloseProtocol (
+ ControllerHandle,
+ &gEfiPciIoProtocolGuid,
+ This->DriverBindingHandle,
+ ControllerHandle
+ );
+
+FreePvScsi:
+ FreePool (Dev);
+
+ return Status;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiDriverBindingStop (
+ IN EFI_DRIVER_BINDING_PROTOCOL *This,
+ IN EFI_HANDLE ControllerHandle,
+ IN UINTN NumberOfChildren,
+ IN EFI_HANDLE *ChildHandleBuffer
+ )
+{
+ EFI_STATUS Status;
+ EFI_EXT_SCSI_PASS_THRU_PROTOCOL *PassThru;
+ PVSCSI_DEV *Dev;
+
+ Status = gBS->OpenProtocol (
+ ControllerHandle,
+ &gEfiExtScsiPassThruProtocolGuid,
+ (VOID **)&PassThru,
+ This->DriverBindingHandle,
+ ControllerHandle,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL // Lookup only
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Dev = PVSCSI_FROM_PASS_THRU (PassThru);
+
+ Status = gBS->UninstallProtocolInterface (
+ ControllerHandle,
+ &gEfiExtScsiPassThruProtocolGuid,
+ &Dev->PassThru
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ gBS->CloseEvent (Dev->ExitBoot);
+
+ PvScsiUninit (Dev);
+
+ gBS->CloseProtocol (
+ ControllerHandle,
+ &gEfiPciIoProtocolGuid,
+ This->DriverBindingHandle,
+ ControllerHandle
+ );
+
+ FreePool (Dev);
+
+ return EFI_SUCCESS;
+}
+
+STATIC EFI_DRIVER_BINDING_PROTOCOL mPvScsiDriverBinding = {
+ &PvScsiDriverBindingSupported,
+ &PvScsiDriverBindingStart,
+ &PvScsiDriverBindingStop,
+ PVSCSI_BINDING_VERSION,
+ NULL, // ImageHandle, filled by EfiLibInstallDriverBindingComponentName2()
+ NULL // DriverBindingHandle, filled as well
+};
+
+//
+// Component Name
+//
+
+STATIC EFI_UNICODE_STRING_TABLE mDriverNameTable[] = {
+ { "eng;en", L"PVSCSI Host Driver" },
+ { NULL, NULL }
+};
+
+STATIC EFI_COMPONENT_NAME_PROTOCOL mComponentName;
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiGetDriverName (
+ IN EFI_COMPONENT_NAME_PROTOCOL *This,
+ IN CHAR8 *Language,
+ OUT CHAR16 **DriverName
+ )
+{
+ return LookupUnicodeString2 (
+ Language,
+ This->SupportedLanguages,
+ mDriverNameTable,
+ DriverName,
+ (BOOLEAN)(This == &mComponentName) // Iso639Language
+ );
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+PvScsiGetDeviceName (
+ 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 mComponentName = {
+ &PvScsiGetDriverName,
+ &PvScsiGetDeviceName,
+ "eng" // SupportedLanguages, ISO 639-2 language codes
+};
+
+STATIC EFI_COMPONENT_NAME2_PROTOCOL mComponentName2 = {
+ (EFI_COMPONENT_NAME2_GET_DRIVER_NAME) &PvScsiGetDriverName,
+ (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME) &PvScsiGetDeviceName,
+ "en" // SupportedLanguages, RFC 4646 language codes
+};
+
+//
+// Entry Point
+//
+
+EFI_STATUS
+EFIAPI
+PvScsiEntryPoint (
+ IN EFI_HANDLE ImageHandle,
+ IN EFI_SYSTEM_TABLE *SystemTable
+ )
+{
+ return EfiLibInstallDriverBindingComponentName2 (
+ ImageHandle,
+ SystemTable,
+ &mPvScsiDriverBinding,
+ ImageHandle,
+ &mComponentName,
+ &mComponentName2
+ );
+}
diff --git a/roms/edk2/OvmfPkg/PvScsiDxe/PvScsi.h b/roms/edk2/OvmfPkg/PvScsiDxe/PvScsi.h
new file mode 100644
index 000000000..544359ebc
--- /dev/null
+++ b/roms/edk2/OvmfPkg/PvScsiDxe/PvScsi.h
@@ -0,0 +1,72 @@
+/** @file
+
+ Internal definitions for the PVSCSI driver, which produces Extended SCSI
+ Pass Thru Protocol instances for pvscsi devices.
+
+ Copyright (C) 2020, Oracle and/or its affiliates.
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#ifndef __PVSCSI_DXE_H_
+#define __PVSCSI_DXE_H_
+
+#include <Library/DebugLib.h>
+#include <Protocol/ScsiPassThruExt.h>
+
+typedef struct {
+ EFI_PHYSICAL_ADDRESS DeviceAddress;
+ VOID *Mapping;
+} PVSCSI_DMA_DESC;
+
+typedef struct {
+ PVSCSI_RINGS_STATE *RingState;
+ PVSCSI_DMA_DESC RingStateDmaDesc;
+
+ PVSCSI_RING_REQ_DESC *RingReqs;
+ PVSCSI_DMA_DESC RingReqsDmaDesc;
+
+ PVSCSI_RING_CMP_DESC *RingCmps;
+ PVSCSI_DMA_DESC RingCmpsDmaDesc;
+} PVSCSI_RING_DESC;
+
+typedef struct {
+ //
+ // As EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET.SenseDataLength is defined
+ // as UINT8, defining here SenseData size to MAX_UINT8 will guarantee it
+ // cannot overflow when passed to device.
+ //
+ UINT8 SenseData[MAX_UINT8];
+ //
+ // This size of the data is arbitrarily chosen.
+ // It seems to be sufficient for all I/O requests sent through
+ // EFI_SCSI_PASS_THRU_PROTOCOL.PassThru() for common boot scenarios.
+ //
+ UINT8 Data[0x2000];
+} PVSCSI_DMA_BUFFER;
+
+#define PVSCSI_SIG SIGNATURE_32 ('P', 'S', 'C', 'S')
+
+typedef struct {
+ UINT32 Signature;
+ EFI_PCI_IO_PROTOCOL *PciIo;
+ EFI_EVENT ExitBoot;
+ UINT64 OriginalPciAttributes;
+ PVSCSI_RING_DESC RingDesc;
+ PVSCSI_DMA_BUFFER *DmaBuf;
+ PVSCSI_DMA_DESC DmaBufDmaDesc;
+ UINT8 MaxTarget;
+ UINT8 MaxLun;
+ UINTN WaitForCmpStallInUsecs;
+ EFI_EXT_SCSI_PASS_THRU_PROTOCOL PassThru;
+ EFI_EXT_SCSI_PASS_THRU_MODE PassThruMode;
+} PVSCSI_DEV;
+
+#define PVSCSI_FROM_PASS_THRU(PassThruPointer) \
+ CR (PassThruPointer, PVSCSI_DEV, PassThru, PVSCSI_SIG)
+
+#define PVSCSI_DMA_BUF_DEV_ADDR(Dev, MemberName) \
+ (Dev->DmaBufDmaDesc.DeviceAddress + OFFSET_OF(PVSCSI_DMA_BUFFER, MemberName))
+
+#endif // __PVSCSI_DXE_H_
diff --git a/roms/edk2/OvmfPkg/PvScsiDxe/PvScsiDxe.inf b/roms/edk2/OvmfPkg/PvScsiDxe/PvScsiDxe.inf
new file mode 100644
index 000000000..284035fb1
--- /dev/null
+++ b/roms/edk2/OvmfPkg/PvScsiDxe/PvScsiDxe.inf
@@ -0,0 +1,44 @@
+## @file
+#
+# This driver produces Extended SCSI Pass Thru Protocol instances for
+# pvscsi devices.
+#
+# Copyright (C) 2020, Oracle and/or its affiliates.
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+ INF_VERSION = 1.29
+ BASE_NAME = PvScsiDxe
+ FILE_GUID = 30346B14-1580-4781-879D-BA0C55AE9BB2
+ MODULE_TYPE = UEFI_DRIVER
+ VERSION_STRING = 1.0
+ ENTRY_POINT = PvScsiEntryPoint
+
+[Sources]
+ PvScsi.c
+ PvScsi.h
+
+[Packages]
+ MdePkg/MdePkg.dec
+ OvmfPkg/OvmfPkg.dec
+
+[LibraryClasses]
+ BaseLib
+ BaseMemoryLib
+ DebugLib
+ MemoryAllocationLib
+ UefiBootServicesTableLib
+ UefiDriverEntryPoint
+ UefiLib
+
+[Protocols]
+ gEfiExtScsiPassThruProtocolGuid ## BY_START
+ gEfiPciIoProtocolGuid ## TO_START
+
+[Pcd]
+ gUefiOvmfPkgTokenSpaceGuid.PcdPvScsiMaxLunLimit ## CONSUMES
+ gUefiOvmfPkgTokenSpaceGuid.PcdPvScsiMaxTargetLimit ## CONSUMES
+ gUefiOvmfPkgTokenSpaceGuid.PcdPvScsiWaitForCmpStallInUsecs ## CONSUMES