/** @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", | |
__func__ | |
)); | |
} | |
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", __func__, 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 | |
); | |
} |