Previous to this change, PvScsiFreeRings() was not undoing all operations that was done by PvScsiInitRings(). This is because PvScsiInitRings() was both preparing rings (Allocate memory and map it for device DMA) and setup the rings against device by issueing a device command. While PvScsiFreeRings() only unmaps the rings and free their memory. Driver do not have a functional error as it makes sure to reset device before every call site to PvScsiFreeRings(). However, this is not intuitive. Therefore, prefer to refactor the setup of the ring against device to a separate function than PvScsiInitRings(). Signed-off-by: Liran Alon <liran.alon@oracle.com> Message-Id: <20200331225637.123318-1-liran.alon@oracle.com> [lersek@redhat.com: rename FreeDMACommBuffer label to FreeDmaCommBuffer] Reviewed-by: Laszlo Ersek <lersek@redhat.com>
1560 lines
38 KiB
C
1560 lines
38 KiB
C
/** @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
|
|
);
|
|
}
|