diff options
author | Nikita Leshenko <nikita.leshchenko@oracle.com> | 2020-05-05 00:06:06 +0300 |
---|---|---|
committer | mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> | 2020-05-05 20:43:02 +0000 |
commit | 505812ae1d2d1d3efa5d9c622d87a990918442ee (patch) | |
tree | e7f3b0968ab70f03fec659a013823610005e9ef9 /OvmfPkg/MptScsiDxe | |
parent | 81cada9892cc4bf3725009c5fb836115da10b1c8 (diff) | |
download | edk2-505812ae1d2d1d3efa5d9c622d87a990918442ee.zip edk2-505812ae1d2d1d3efa5d9c622d87a990918442ee.tar.gz edk2-505812ae1d2d1d3efa5d9c622d87a990918442ee.tar.bz2 |
OvmfPkg/MptScsiDxe: Implement the PassThru method
Machines should be able to boot after this commit. Tested with different
Linux distributions (Ubuntu, CentOS) and different Windows
versions (Windows 7, Windows 10, Server 2016).
Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=2390
Signed-off-by: Nikita Leshenko <nikita.leshchenko@oracle.com>
Reviewed-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20200504210607.144434-12-nikita.leshchenko@oracle.com>
[lersek@redhat.com: MPT_SCSI_DMA_ADDR_HIGH: drop redundant space char]
Diffstat (limited to 'OvmfPkg/MptScsiDxe')
-rw-r--r-- | OvmfPkg/MptScsiDxe/MptScsi.c | 409 | ||||
-rw-r--r-- | OvmfPkg/MptScsiDxe/MptScsiDxe.inf | 4 |
2 files changed, 411 insertions, 2 deletions
diff --git a/OvmfPkg/MptScsiDxe/MptScsi.c b/OvmfPkg/MptScsiDxe/MptScsi.c index 2cc69b8..8be38f6 100644 --- a/OvmfPkg/MptScsiDxe/MptScsi.c +++ b/OvmfPkg/MptScsiDxe/MptScsi.c @@ -11,6 +11,7 @@ #include <IndustryStandard/FusionMptScsi.h>
#include <IndustryStandard/Pci.h>
+#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
@@ -18,6 +19,7 @@ #include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Protocol/PciIo.h>
+#include <Protocol/PciRootBridgeIo.h>
#include <Protocol/ScsiPassThruExt.h>
#include <Uefi/UefiSpec.h>
@@ -31,19 +33,50 @@ // Runtime Structures
//
+typedef struct {
+ MPT_SCSI_REQUEST_ALIGNED IoRequest;
+ MPT_SCSI_IO_REPLY_ALIGNED IoReply;
+ //
+ // 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 Sense[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];
+} MPT_SCSI_DMA_BUFFER;
+
#define MPT_SCSI_DEV_SIGNATURE SIGNATURE_32 ('M','P','T','S')
typedef struct {
UINT32 Signature;
EFI_EXT_SCSI_PASS_THRU_PROTOCOL PassThru;
EFI_EXT_SCSI_PASS_THRU_MODE PassThruMode;
UINT8 MaxTarget;
+ UINT32 StallPerPollUsec;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT64 OriginalPciAttributes;
+ MPT_SCSI_DMA_BUFFER *Dma;
+ EFI_PHYSICAL_ADDRESS DmaPhysical;
+ VOID *DmaMapping;
+ BOOLEAN IoReplyEnqueued;
} MPT_SCSI_DEV;
#define MPT_SCSI_FROM_PASS_THRU(PassThruPtr) \
CR (PassThruPtr, MPT_SCSI_DEV, PassThru, MPT_SCSI_DEV_SIGNATURE)
+#define MPT_SCSI_DMA_ADDR(Dev, MemberName) \
+ (Dev->DmaPhysical + OFFSET_OF (MPT_SCSI_DMA_BUFFER, MemberName))
+
+#define MPT_SCSI_DMA_ADDR_HIGH(Dev, MemberName) \
+ ((UINT32)RShiftU64 (MPT_SCSI_DMA_ADDR (Dev, MemberName), 32))
+
+#define MPT_SCSI_DMA_ADDR_LOW(Dev, MemberName) \
+ ((UINT32)MPT_SCSI_DMA_ADDR (Dev, MemberName))
+
//
// Hardware functions
//
@@ -165,6 +198,9 @@ MptScsiInit ( );
Req->MaxDevices = Dev->MaxTarget + 1;
Req->MaxBuses = 1;
+ Req->ReplyFrameSize = sizeof Dev->Dma->IoReply.Data;
+ Req->HostMfaHighAddr = MPT_SCSI_DMA_ADDR_HIGH (Dev, IoRequest);
+ Req->SenseBufferHighAddr = MPT_SCSI_DMA_ADDR_HIGH (Dev, Sense);
//
// Send controller init through doorbell
@@ -230,6 +266,288 @@ MptScsiInit ( return EFI_SUCCESS;
}
+STATIC
+EFI_STATUS
+ReportHostAdapterError (
+ OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet
+ )
+{
+ DEBUG ((DEBUG_ERROR, "%a: fatal error in scsi request\n", __FUNCTION__));
+ 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_TASK_ABORTED;
+ return EFI_DEVICE_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;
+}
+
+STATIC
+EFI_STATUS
+MptScsiPopulateRequest (
+ IN MPT_SCSI_DEV *Dev,
+ IN UINT8 Target,
+ IN UINT64 Lun,
+ IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet
+ )
+{
+ MPT_SCSI_REQUEST_WITH_SG *Request;
+
+ Request = &Dev->Dma->IoRequest.Data;
+
+ if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_BIDIRECTIONAL ||
+ (Packet->InTransferLength > 0 && Packet->OutTransferLength > 0) ||
+ Packet->CdbLength > sizeof (Request->Header.Cdb)) {
+ return EFI_UNSUPPORTED;
+ }
+
+ if (Target > Dev->MaxTarget || Lun > 0 ||
+ 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
+ )
+ )
+ ) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ if (Packet->InTransferLength > sizeof (Dev->Dma->Data)) {
+ Packet->InTransferLength = sizeof (Dev->Dma->Data);
+ return ReportHostAdapterOverrunError (Packet);
+ }
+ if (Packet->OutTransferLength > sizeof (Dev->Dma->Data)) {
+ Packet->OutTransferLength = sizeof (Dev->Dma->Data);
+ return ReportHostAdapterOverrunError (Packet);
+ }
+
+ ZeroMem (Request, sizeof (*Request));
+ Request->Header.TargetId = Target;
+ //
+ // Only LUN 0 is currently supported, hence the cast is safe
+ //
+ Request->Header.Lun[1] = (UINT8)Lun;
+ Request->Header.Function = MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST;
+ Request->Header.MessageContext = 1; // We handle one request at a time
+
+ Request->Header.CdbLength = Packet->CdbLength;
+ CopyMem (Request->Header.Cdb, Packet->Cdb, Packet->CdbLength);
+
+ //
+ // SenseDataLength is UINT8, Sense[] is MAX_UINT8, so we can't overflow
+ //
+ ZeroMem (Dev->Dma->Sense, Packet->SenseDataLength);
+ Request->Header.SenseBufferLength = Packet->SenseDataLength;
+ Request->Header.SenseBufferLowAddress = MPT_SCSI_DMA_ADDR_LOW (Dev, Sense);
+
+ Request->Sg.EndOfList = 1;
+ Request->Sg.EndOfBuffer = 1;
+ Request->Sg.LastElement = 1;
+ Request->Sg.ElementType = MPT_SG_ENTRY_TYPE_SIMPLE;
+ Request->Sg.Is64BitAddress = 1;
+ Request->Sg.DataBufferAddress = MPT_SCSI_DMA_ADDR (Dev, Data);
+
+ //
+ // "MPT_SG_ENTRY_SIMPLE.Length" is a 24-bit quantity.
+ //
+ STATIC_ASSERT (
+ sizeof (Dev->Dma->Data) < SIZE_16MB,
+ "MPT_SCSI_DMA_BUFFER.Data must be smaller than 16MB"
+ );
+
+ if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {
+ Request->Header.DataLength = Packet->InTransferLength;
+ Request->Sg.Length = Packet->InTransferLength;
+ Request->Header.Control = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_READ;
+ } else {
+ Request->Header.DataLength = Packet->OutTransferLength;
+ Request->Sg.Length = Packet->OutTransferLength;
+ Request->Header.Control = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_WRITE;
+
+ CopyMem (Dev->Dma->Data, Packet->OutDataBuffer, Packet->OutTransferLength);
+ Request->Sg.BufferContainsData = 1;
+ }
+
+ if (Request->Header.DataLength == 0) {
+ Request->Header.Control = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_NONE;
+ }
+
+ return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+MptScsiSendRequest (
+ IN MPT_SCSI_DEV *Dev,
+ IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet
+ )
+{
+ EFI_STATUS Status;
+
+ if (!Dev->IoReplyEnqueued) {
+ //
+ // Put one free reply frame on the reply queue, the hardware may use it to
+ // report an error to us.
+ //
+ Status = Out32 (Dev, MPT_REG_REP_Q, MPT_SCSI_DMA_ADDR_LOW (Dev, IoReply));
+ if (EFI_ERROR (Status)) {
+ return EFI_DEVICE_ERROR;
+ }
+ Dev->IoReplyEnqueued = TRUE;
+ }
+
+ Status = Out32 (Dev, MPT_REG_REQ_Q, MPT_SCSI_DMA_ADDR_LOW (Dev, IoRequest));
+ if (EFI_ERROR (Status)) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+MptScsiGetReply (
+ IN MPT_SCSI_DEV *Dev,
+ OUT UINT32 *Reply
+ )
+{
+ EFI_STATUS Status;
+ UINT32 Istatus;
+ UINT32 EmptyReply;
+
+ //
+ // Timeouts are not supported for
+ // EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() in this implementation.
+ //
+ for (;;) {
+ Status = In32 (Dev, MPT_REG_ISTATUS, &Istatus);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ //
+ // Interrupt raised
+ //
+ if (Istatus & MPT_IMASK_REPLY) {
+ break;
+ }
+
+ gBS->Stall (Dev->StallPerPollUsec);
+ }
+
+ Status = In32 (Dev, MPT_REG_REP_Q, Reply);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ //
+ // The driver is supposed to fetch replies until 0xffffffff is returned, which
+ // will reset the interrupt status. We put only one request, so we expect the
+ // next read reply to be the last.
+ //
+ Status = In32 (Dev, MPT_REG_REP_Q, &EmptyReply);
+ if (EFI_ERROR (Status) || EmptyReply != MAX_UINT32) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ return EFI_SUCCESS;
+}
+
+STATIC
+EFI_STATUS
+MptScsiHandleReply (
+ IN MPT_SCSI_DEV *Dev,
+ IN UINT32 Reply,
+ OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet
+ )
+{
+ if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {
+ CopyMem (Packet->InDataBuffer, Dev->Dma->Data, Packet->InTransferLength);
+ }
+
+ if (Reply == Dev->Dma->IoRequest.Data.Header.MessageContext) {
+ //
+ // This is a turbo reply, everything is good
+ //
+ Packet->SenseDataLength = 0;
+ Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK;
+ Packet->TargetStatus = EFI_EXT_SCSI_STATUS_TARGET_GOOD;
+
+ } else if ((Reply & BIT31) != 0) {
+ DEBUG ((DEBUG_INFO, "%a: Full reply returned\n", __FUNCTION__));
+ //
+ // When reply MSB is set, we got a full reply. Since we submitted only one
+ // reply frame, we know it's IoReply.
+ //
+ Dev->IoReplyEnqueued = FALSE;
+
+ Packet->TargetStatus = Dev->Dma->IoReply.Data.ScsiStatus;
+ //
+ // Make sure device only lowers SenseDataLength before copying sense
+ //
+ ASSERT (Dev->Dma->IoReply.Data.SenseCount <= Packet->SenseDataLength);
+ Packet->SenseDataLength =
+ (UINT8)MIN (Dev->Dma->IoReply.Data.SenseCount, Packet->SenseDataLength);
+ CopyMem (Packet->SenseData, Dev->Dma->Sense, Packet->SenseDataLength);
+
+ if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {
+ Packet->InTransferLength = Dev->Dma->IoReply.Data.TransferCount;
+ } else {
+ Packet->OutTransferLength = Dev->Dma->IoReply.Data.TransferCount;
+ }
+
+ switch (Dev->Dma->IoReply.Data.IocStatus) {
+ case MPT_SCSI_IOCSTATUS_SUCCESS:
+ Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK;
+ break;
+ case MPT_SCSI_IOCSTATUS_DEVICE_NOT_THERE:
+ Packet->HostAdapterStatus =
+ EFI_EXT_SCSI_STATUS_HOST_ADAPTER_SELECTION_TIMEOUT;
+ return EFI_TIMEOUT;
+ case MPT_SCSI_IOCSTATUS_DATA_UNDERRUN:
+ case MPT_SCSI_IOCSTATUS_DATA_OVERRUN:
+ Packet->HostAdapterStatus =
+ EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN;
+ break;
+ default:
+ Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;
+ return EFI_DEVICE_ERROR;
+ }
+
+ } else {
+ DEBUG ((DEBUG_ERROR, "%a: unexpected reply (%x)\n", __FUNCTION__, Reply));
+ return ReportHostAdapterError (Packet);
+ }
+
+ return EFI_SUCCESS;
+}
+
//
// Ext SCSI Pass Thru
//
@@ -245,7 +563,33 @@ MptScsiPassThru ( IN EFI_EVENT Event OPTIONAL
)
{
- return EFI_UNSUPPORTED;
+ EFI_STATUS Status;
+ MPT_SCSI_DEV *Dev;
+ UINT32 Reply;
+
+ Dev = MPT_SCSI_FROM_PASS_THRU (This);
+ //
+ // We only use first byte of target identifer
+ //
+ Status = MptScsiPopulateRequest (Dev, *Target, Lun, Packet);
+ if (EFI_ERROR (Status)) {
+ //
+ // MptScsiPopulateRequest modified packet according to the error
+ //
+ return Status;
+ }
+
+ Status = MptScsiSendRequest (Dev, Packet);
+ if (EFI_ERROR (Status)) {
+ return ReportHostAdapterError (Packet);
+ }
+
+ Status = MptScsiGetReply (Dev, &Reply);
+ if (EFI_ERROR (Status)) {
+ return ReportHostAdapterError (Packet);
+ }
+
+ return MptScsiHandleReply (Dev, Reply, Packet);
}
STATIC
@@ -500,6 +844,8 @@ MptScsiControllerStart ( {
EFI_STATUS Status;
MPT_SCSI_DEV *Dev;
+ UINTN Pages;
+ UINTN BytesMapped;
Dev = AllocateZeroPool (sizeof (*Dev));
if (Dev == NULL) {
@@ -509,6 +855,7 @@ MptScsiControllerStart ( Dev->Signature = MPT_SCSI_DEV_SIGNATURE;
Dev->MaxTarget = PcdGet8 (PcdMptScsiMaxTargetLimit);
+ Dev->StallPerPollUsec = PcdGet32 (PcdMptScsiStallPerPollUsec);
Status = gBS->OpenProtocol (
ControllerHandle,
@@ -569,11 +916,45 @@ MptScsiControllerStart ( ));
}
- Status = MptScsiInit (Dev);
+ //
+ // Create buffers for data transfer
+ //
+ Pages = EFI_SIZE_TO_PAGES (sizeof (*Dev->Dma));
+ Status = Dev->PciIo->AllocateBuffer (
+ Dev->PciIo,
+ AllocateAnyPages,
+ EfiBootServicesData,
+ Pages,
+ (VOID **)&Dev->Dma,
+ EFI_PCI_ATTRIBUTE_MEMORY_CACHED
+ );
if (EFI_ERROR (Status)) {
goto RestoreAttributes;
}
+ BytesMapped = EFI_PAGES_TO_SIZE (Pages);
+ Status = Dev->PciIo->Map (
+ Dev->PciIo,
+ EfiPciIoOperationBusMasterCommonBuffer,
+ Dev->Dma,
+ &BytesMapped,
+ &Dev->DmaPhysical,
+ &Dev->DmaMapping
+ );
+ if (EFI_ERROR (Status)) {
+ goto FreeBuffer;
+ }
+
+ if (BytesMapped != EFI_PAGES_TO_SIZE (Pages)) {
+ Status = EFI_OUT_OF_RESOURCES;
+ goto Unmap;
+ }
+
+ Status = MptScsiInit (Dev);
+ if (EFI_ERROR (Status)) {
+ goto Unmap;
+ }
+
//
// Host adapter channel, doesn't exist
//
@@ -606,6 +987,19 @@ MptScsiControllerStart ( UninitDev:
MptScsiReset (Dev);
+Unmap:
+ Dev->PciIo->Unmap (
+ Dev->PciIo,
+ Dev->DmaMapping
+ );
+
+FreeBuffer:
+ Dev->PciIo->FreeBuffer (
+ Dev->PciIo,
+ Pages,
+ Dev->Dma
+ );
+
RestoreAttributes:
Dev->PciIo->Attributes (
Dev->PciIo,
@@ -667,6 +1061,17 @@ MptScsiControllerStop ( MptScsiReset (Dev);
+ Dev->PciIo->Unmap (
+ Dev->PciIo,
+ Dev->DmaMapping
+ );
+
+ Dev->PciIo->FreeBuffer (
+ Dev->PciIo,
+ EFI_SIZE_TO_PAGES (sizeof (*Dev->Dma)),
+ Dev->Dma
+ );
+
Dev->PciIo->Attributes (
Dev->PciIo,
EfiPciIoAttributeOperationSet,
diff --git a/OvmfPkg/MptScsiDxe/MptScsiDxe.inf b/OvmfPkg/MptScsiDxe/MptScsiDxe.inf index d5fd251..0910893 100644 --- a/OvmfPkg/MptScsiDxe/MptScsiDxe.inf +++ b/OvmfPkg/MptScsiDxe/MptScsiDxe.inf @@ -24,6 +24,7 @@ OvmfPkg/OvmfPkg.dec
[LibraryClasses]
+ BaseLib
BaseMemoryLib
DebugLib
MemoryAllocationLib
@@ -38,3 +39,6 @@ [FixedPcd]
gUefiOvmfPkgTokenSpaceGuid.PcdMptScsiMaxTargetLimit ## CONSUMES
+
+[Pcd]
+ gUefiOvmfPkgTokenSpaceGuid.PcdMptScsiStallPerPollUsec ## CONSUMES
|