/** @file | |
This driver produces Extended SCSI Pass Thru Protocol instances for | |
virtio-scsi devices. | |
The implementation is basic: | |
- No hotplug / hot-unplug. | |
- Although EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() could be a good match | |
for multiple in-flight virtio-scsi requests, we stick to synchronous | |
requests for now. | |
- Timeouts are not supported for EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru(). | |
- Only one channel is supported. (At the time of this writing, host-side | |
virtio-scsi supports a single channel too.) | |
- Only one request queue is used (for the one synchronous request). | |
- The ResetChannel() and ResetTargetLun() functions of | |
EFI_EXT_SCSI_PASS_THRU_PROTOCOL are not supported (which is allowed by the | |
UEFI 2.3.1 Errata C specification), although | |
VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET could be a good match. That would | |
however require client code for the control queue, which is deemed | |
unreasonable for now. | |
Copyright (C) 2012, Red Hat, Inc. | |
Copyright (c) 2012 - 2018, Intel Corporation. All rights reserved.<BR> | |
Copyright (c) 2017, AMD Inc, All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <IndustryStandard/VirtioScsi.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/UefiLib.h> | |
#include <Library/VirtioLib.h> | |
#include "VirtioScsi.h" | |
/** | |
Convenience macros to read and write configuration elements of the | |
virtio-scsi VirtIo device. | |
The following macros make it possible to specify only the "core parameters" | |
for such accesses and to derive the rest. By the time VIRTIO_CFG_WRITE() | |
returns, the transaction will have been completed. | |
@param[in] Dev Pointer to the VSCSI_DEV structure. | |
@param[in] Field A field name from VSCSI_HDR, identifying the virtio-scsi | |
configuration item to access. | |
@param[in] Value (VIRTIO_CFG_WRITE() only.) The value to write to the | |
selected configuration item. | |
@param[out] Pointer (VIRTIO_CFG_READ() only.) The object to receive the | |
value read from the configuration item. Its type must be | |
one of UINT8, UINT16, UINT32, UINT64. | |
@return Status codes returned by Virtio->WriteDevice() / Virtio->ReadDevice(). | |
**/ | |
#define VIRTIO_CFG_WRITE(Dev, Field, Value) ((Dev)->VirtIo->WriteDevice ( \ | |
(Dev)->VirtIo, \ | |
OFFSET_OF_VSCSI (Field), \ | |
SIZE_OF_VSCSI (Field), \ | |
(Value) \ | |
)) | |
#define VIRTIO_CFG_READ(Dev, Field, Pointer) ((Dev)->VirtIo->ReadDevice ( \ | |
(Dev)->VirtIo, \ | |
OFFSET_OF_VSCSI (Field), \ | |
SIZE_OF_VSCSI (Field), \ | |
sizeof *(Pointer), \ | |
(Pointer) \ | |
)) | |
// | |
// UEFI Spec 2.3.1 + Errata C, 14.7 Extended SCSI Pass Thru Protocol specifies | |
// the PassThru() interface. Beside returning a status code, the function must | |
// set some fields in the EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET in/out | |
// parameter on return. The following is a full list of those fields, for | |
// easier validation of PopulateRequest(), ParseResponse(), and | |
// ReportHostAdapterError() below. | |
// | |
// - InTransferLength | |
// - OutTransferLength | |
// - HostAdapterStatus | |
// - TargetStatus | |
// - SenseDataLength | |
// - SenseData | |
// | |
// On any return from the PassThru() interface, these fields must be set, | |
// except if the returned status code is explicitly exempt. (Actually the | |
// implementation here conservatively sets these fields even in case not all | |
// of them would be required by the specification.) | |
// | |
/** | |
Populate a virtio-scsi request from the Extended SCSI Pass Thru Protocol | |
packet. | |
The caller is responsible for pre-zeroing the virtio-scsi request. The | |
Extended SCSI Pass Thru Protocol packet is modified, to be forwarded outwards | |
by VirtioScsiPassThru(), if invalid or unsupported parameters are detected. | |
@param[in] Dev The virtio-scsi host device the packet targets. | |
@param[in] Target The SCSI target controlled by the virtio-scsi host | |
device. | |
@param[in] Lun The Logical Unit Number under the SCSI target. | |
@param[in out] Packet The Extended SCSI Pass Thru Protocol packet the | |
function translates to a virtio-scsi request. On | |
failure this parameter relays error contents. | |
@param[out] Request The pre-zeroed virtio-scsi request to populate. This | |
parameter is volatile-qualified because we expect the | |
caller to append it to a virtio ring, thus | |
assignments to Request must be visible when the | |
function returns. | |
@retval EFI_SUCCESS The Extended SCSI Pass Thru Protocol packet was valid, | |
Request has been populated. | |
@return Otherwise, invalid or unsupported parameters were | |
detected. Status codes are meant for direct forwarding | |
by the EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() | |
implementation. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
PopulateRequest ( | |
IN CONST VSCSI_DEV *Dev, | |
IN UINT16 Target, | |
IN UINT64 Lun, | |
IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet, | |
OUT volatile VIRTIO_SCSI_REQ *Request | |
) | |
{ | |
UINTN Idx; | |
if ( | |
// | |
// bidirectional transfer was requested, but the host doesn't support it | |
// | |
((Packet->InTransferLength > 0) && (Packet->OutTransferLength > 0) && | |
!Dev->InOutSupported) || | |
// | |
// a target / LUN was addressed that's impossible to encode for the host | |
// | |
(Target > 0xFF) || (Lun >= 0x4000) || | |
// | |
// Command Descriptor Block bigger than VIRTIO_SCSI_CDB_SIZE | |
// | |
(Packet->CdbLength > VIRTIO_SCSI_CDB_SIZE) || | |
// | |
// From virtio-0.9.5, 2.3.2 Descriptor Table: | |
// "no descriptor chain may be more than 2^32 bytes long in total". | |
// | |
((UINT64)Packet->InTransferLength + Packet->OutTransferLength > SIZE_1GB) | |
) | |
{ | |
// | |
// this error code doesn't require updates to the Packet output fields | |
// | |
return EFI_UNSUPPORTED; | |
} | |
if ( | |
// | |
// addressed invalid device | |
// | |
(Target > Dev->MaxTarget) || (Lun > Dev->MaxLun) || | |
// | |
// invalid direction (there doesn't seem to be a macro for the "no data | |
// transferred" "direction", eg. for TEST UNIT READY) | |
// | |
(Packet->DataDirection > EFI_EXT_SCSI_DATA_DIRECTION_BIDIRECTIONAL) || | |
// | |
// trying to receive, but destination pointer is NULL, or contradicting | |
// transfer direction | |
// | |
((Packet->InTransferLength > 0) && | |
((Packet->InDataBuffer == NULL) || | |
(Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_WRITE) | |
) | |
) || | |
// | |
// trying to send, but source pointer is NULL, or contradicting transfer | |
// direction | |
// | |
((Packet->OutTransferLength > 0) && | |
((Packet->OutDataBuffer == NULL) || | |
(Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) | |
) | |
) | |
) | |
{ | |
// | |
// this error code doesn't require updates to the Packet output fields | |
// | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Catch oversized requests eagerly. If this condition evaluates to false, | |
// then the combined size of a bidirectional request will not exceed the | |
// virtio-scsi device's transfer limit either. | |
// | |
if ((ALIGN_VALUE (Packet->OutTransferLength, 512) / 512 | |
> Dev->MaxSectors / 2) || | |
(ALIGN_VALUE (Packet->InTransferLength, 512) / 512 | |
> Dev->MaxSectors / 2)) | |
{ | |
Packet->InTransferLength = (Dev->MaxSectors / 2) * 512; | |
Packet->OutTransferLength = (Dev->MaxSectors / 2) * 512; | |
Packet->HostAdapterStatus = | |
EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN; | |
Packet->TargetStatus = EFI_EXT_SCSI_STATUS_TARGET_GOOD; | |
Packet->SenseDataLength = 0; | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
// | |
// target & LUN encoding: see virtio-0.9.5, Appendix I: SCSI Host Device, | |
// Device Operation: request queues | |
// | |
Request->Lun[0] = 1; | |
Request->Lun[1] = (UINT8)Target; | |
Request->Lun[2] = (UINT8)(((UINT32)Lun >> 8) | 0x40); | |
Request->Lun[3] = (UINT8)Lun; | |
// | |
// CopyMem() would cast away the "volatile" qualifier before access, which is | |
// undefined behavior (ISO C99 6.7.3p5) | |
// | |
for (Idx = 0; Idx < Packet->CdbLength; ++Idx) { | |
Request->Cdb[Idx] = ((UINT8 *)Packet->Cdb)[Idx]; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Parse the virtio-scsi device's response, translate it to an EFI status code, | |
and update the Extended SCSI Pass Thru Protocol packet, to be returned by | |
the EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() implementation. | |
@param[in out] Packet The Extended SCSI Pass Thru Protocol packet that has | |
been translated to a virtio-scsi request with | |
PopulateRequest(), and processed by the host. On | |
output this parameter is updated with response or | |
error contents. | |
@param[in] Response The virtio-scsi response structure to parse. We expect | |
it to come from a virtio ring, thus it is qualified | |
volatile. | |
@return PassThru() status codes mandated by UEFI Spec 2.3.1 + Errata C, 14.7 | |
Extended SCSI Pass Thru Protocol. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
ParseResponse ( | |
IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet, | |
IN CONST volatile VIRTIO_SCSI_RESP *Response | |
) | |
{ | |
UINTN ResponseSenseLen; | |
UINTN Idx; | |
// | |
// return sense data (length and contents) in all cases, truncated if needed | |
// | |
ResponseSenseLen = MIN (Response->SenseLen, VIRTIO_SCSI_SENSE_SIZE); | |
if (Packet->SenseDataLength > ResponseSenseLen) { | |
Packet->SenseDataLength = (UINT8)ResponseSenseLen; | |
} | |
for (Idx = 0; Idx < Packet->SenseDataLength; ++Idx) { | |
((UINT8 *)Packet->SenseData)[Idx] = Response->Sense[Idx]; | |
} | |
// | |
// Report actual transfer lengths. The logic below covers all three | |
// DataDirections (read, write, bidirectional). | |
// | |
// -+- @ 0 | |
// | | |
// | write ^ @ Residual (unprocessed) | |
// | | | |
// -+- @ OutTransferLength -+- @ InTransferLength | |
// | | | |
// | read | | |
// | | | |
// V @ OutTransferLength + InTransferLength -+- @ 0 | |
// | |
if (Response->Residual <= Packet->InTransferLength) { | |
Packet->InTransferLength -= Response->Residual; | |
} else { | |
Packet->OutTransferLength -= Response->Residual - Packet->InTransferLength; | |
Packet->InTransferLength = 0; | |
} | |
// | |
// report target status in all cases | |
// | |
Packet->TargetStatus = Response->Status; | |
// | |
// host adapter status and function return value depend on virtio-scsi | |
// response code | |
// | |
switch (Response->Response) { | |
case VIRTIO_SCSI_S_OK: | |
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK; | |
return EFI_SUCCESS; | |
case VIRTIO_SCSI_S_OVERRUN: | |
Packet->HostAdapterStatus = | |
EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN; | |
break; | |
case VIRTIO_SCSI_S_BAD_TARGET: | |
// | |
// This is non-intuitive but explicitly required by the | |
// EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() specification for | |
// disconnected (but otherwise valid) target / LUN addresses. | |
// | |
Packet->HostAdapterStatus = | |
EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT_COMMAND; | |
return EFI_TIMEOUT; | |
case VIRTIO_SCSI_S_RESET: | |
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_RESET; | |
break; | |
case VIRTIO_SCSI_S_BUSY: | |
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK; | |
return EFI_NOT_READY; | |
// | |
// Lump together the rest. The mapping for VIRTIO_SCSI_S_ABORTED is | |
// intentional as well, not an oversight. | |
// | |
case VIRTIO_SCSI_S_ABORTED: | |
case VIRTIO_SCSI_S_TRANSPORT_FAILURE: | |
case VIRTIO_SCSI_S_TARGET_FAILURE: | |
case VIRTIO_SCSI_S_NEXUS_FAILURE: | |
case VIRTIO_SCSI_S_FAILURE: | |
default: | |
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER; | |
} | |
return EFI_DEVICE_ERROR; | |
} | |
/** | |
The function can be used to create a fake host adapter error. | |
When VirtioScsiPassThru() is failed due to some reasons then this function | |
can be called to construct a host adapter error. | |
@param[out] Packet The Extended SCSI Pass Thru Protocol packet that the host | |
adapter error shall be placed in. | |
@retval EFI_DEVICE_ERROR The function returns this status code | |
unconditionally, to be propagated by | |
VirtioScsiPassThru(). | |
**/ | |
STATIC | |
EFI_STATUS | |
ReportHostAdapterError ( | |
OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet | |
) | |
{ | |
Packet->InTransferLength = 0; | |
Packet->OutTransferLength = 0; | |
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER; | |
Packet->TargetStatus = EFI_EXT_SCSI_STATUS_TARGET_GOOD; | |
Packet->SenseDataLength = 0; | |
return EFI_DEVICE_ERROR; | |
} | |
// | |
// The next seven functions implement EFI_EXT_SCSI_PASS_THRU_PROTOCOL | |
// for the virtio-scsi HBA. Refer to UEFI Spec 2.3.1 + Errata C, sections | |
// - 14.1 SCSI Driver Model Overview, | |
// - 14.7 Extended SCSI Pass Thru Protocol. | |
// | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiPassThru ( | |
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, | |
IN UINT8 *Target, | |
IN UINT64 Lun, | |
IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet, | |
IN EFI_EVENT Event OPTIONAL | |
) | |
{ | |
VSCSI_DEV *Dev; | |
UINT16 TargetValue; | |
EFI_STATUS Status; | |
volatile VIRTIO_SCSI_REQ Request; | |
volatile VIRTIO_SCSI_RESP *Response; | |
VOID *ResponseBuffer; | |
DESC_INDICES Indices; | |
VOID *RequestMapping; | |
VOID *ResponseMapping; | |
VOID *InDataMapping; | |
VOID *OutDataMapping; | |
EFI_PHYSICAL_ADDRESS RequestDeviceAddress; | |
EFI_PHYSICAL_ADDRESS ResponseDeviceAddress; | |
EFI_PHYSICAL_ADDRESS InDataDeviceAddress; | |
EFI_PHYSICAL_ADDRESS OutDataDeviceAddress; | |
VOID *InDataBuffer; | |
UINTN InDataNumPages; | |
BOOLEAN OutDataBufferIsMapped; | |
// | |
// Set InDataMapping,OutDataMapping,InDataDeviceAddress and OutDataDeviceAddress to | |
// suppress incorrect compiler/analyzer warnings. | |
// | |
InDataMapping = NULL; | |
OutDataMapping = NULL; | |
InDataDeviceAddress = 0; | |
OutDataDeviceAddress = 0; | |
ZeroMem ((VOID *)&Request, sizeof (Request)); | |
Dev = VIRTIO_SCSI_FROM_PASS_THRU (This); | |
CopyMem (&TargetValue, Target, sizeof TargetValue); | |
InDataBuffer = NULL; | |
OutDataBufferIsMapped = FALSE; | |
InDataNumPages = 0; | |
Status = PopulateRequest (Dev, TargetValue, Lun, Packet, &Request); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Map the virtio-scsi Request header buffer | |
// | |
Status = VirtioMapAllBytesInSharedBuffer ( | |
Dev->VirtIo, | |
VirtioOperationBusMasterRead, | |
(VOID *)&Request, | |
sizeof Request, | |
&RequestDeviceAddress, | |
&RequestMapping | |
); | |
if (EFI_ERROR (Status)) { | |
return ReportHostAdapterError (Packet); | |
} | |
// | |
// Map the input buffer | |
// | |
if (Packet->InTransferLength > 0) { | |
// | |
// Allocate a intermediate input buffer. This is mainly to handle the | |
// following case: | |
// * caller submits a bi-directional request | |
// * we perform the request fine | |
// * but we fail to unmap the "InDataMapping" | |
// | |
// In that case simply returning the EFI_DEVICE_ERROR is not sufficient. In | |
// addition to the error code we also need to update Packet fields | |
// accordingly so that we report the full loss of the incoming transfer. | |
// | |
// We allocate a temporary buffer and map it with BusMasterCommonBuffer. If | |
// the Virtio request is successful then we copy the data from temporary | |
// buffer into Packet->InDataBuffer. | |
// | |
InDataNumPages = EFI_SIZE_TO_PAGES ((UINTN)Packet->InTransferLength); | |
Status = Dev->VirtIo->AllocateSharedPages ( | |
Dev->VirtIo, | |
InDataNumPages, | |
&InDataBuffer | |
); | |
if (EFI_ERROR (Status)) { | |
Status = ReportHostAdapterError (Packet); | |
goto UnmapRequestBuffer; | |
} | |
ZeroMem (InDataBuffer, Packet->InTransferLength); | |
Status = VirtioMapAllBytesInSharedBuffer ( | |
Dev->VirtIo, | |
VirtioOperationBusMasterCommonBuffer, | |
InDataBuffer, | |
Packet->InTransferLength, | |
&InDataDeviceAddress, | |
&InDataMapping | |
); | |
if (EFI_ERROR (Status)) { | |
Status = ReportHostAdapterError (Packet); | |
goto FreeInDataBuffer; | |
} | |
} | |
// | |
// Map the output buffer | |
// | |
if (Packet->OutTransferLength > 0) { | |
Status = VirtioMapAllBytesInSharedBuffer ( | |
Dev->VirtIo, | |
VirtioOperationBusMasterRead, | |
Packet->OutDataBuffer, | |
Packet->OutTransferLength, | |
&OutDataDeviceAddress, | |
&OutDataMapping | |
); | |
if (EFI_ERROR (Status)) { | |
Status = ReportHostAdapterError (Packet); | |
goto UnmapInDataBuffer; | |
} | |
OutDataBufferIsMapped = TRUE; | |
} | |
// | |
// Response header is bi-direction (we preset with host status and expect | |
// the device to update it). Allocate a response buffer which can be mapped | |
// to access equally by both processor and device. | |
// | |
Status = Dev->VirtIo->AllocateSharedPages ( | |
Dev->VirtIo, | |
EFI_SIZE_TO_PAGES (sizeof *Response), | |
&ResponseBuffer | |
); | |
if (EFI_ERROR (Status)) { | |
Status = ReportHostAdapterError (Packet); | |
goto UnmapOutDataBuffer; | |
} | |
Response = ResponseBuffer; | |
ZeroMem ((VOID *)Response, sizeof (*Response)); | |
// | |
// preset a host status for ourselves that we do not accept as success | |
// | |
Response->Response = VIRTIO_SCSI_S_FAILURE; | |
// | |
// Map the response buffer with BusMasterCommonBuffer so that response | |
// buffer can be accessed by both host and device. | |
// | |
Status = VirtioMapAllBytesInSharedBuffer ( | |
Dev->VirtIo, | |
VirtioOperationBusMasterCommonBuffer, | |
ResponseBuffer, | |
sizeof (*Response), | |
&ResponseDeviceAddress, | |
&ResponseMapping | |
); | |
if (EFI_ERROR (Status)) { | |
Status = ReportHostAdapterError (Packet); | |
goto FreeResponseBuffer; | |
} | |
VirtioPrepare (&Dev->Ring, &Indices); | |
// | |
// ensured by VirtioScsiInit() -- this predicate, in combination with the | |
// lock-step progress, ensures we don't have to track free descriptors. | |
// | |
ASSERT (Dev->Ring.QueueSize >= 4); | |
// | |
// enqueue Request | |
// | |
VirtioAppendDesc ( | |
&Dev->Ring, | |
RequestDeviceAddress, | |
sizeof Request, | |
VRING_DESC_F_NEXT, | |
&Indices | |
); | |
// | |
// enqueue "dataout" if any | |
// | |
if (Packet->OutTransferLength > 0) { | |
VirtioAppendDesc ( | |
&Dev->Ring, | |
OutDataDeviceAddress, | |
Packet->OutTransferLength, | |
VRING_DESC_F_NEXT, | |
&Indices | |
); | |
} | |
// | |
// enqueue Response, to be written by the host | |
// | |
VirtioAppendDesc ( | |
&Dev->Ring, | |
ResponseDeviceAddress, | |
sizeof *Response, | |
VRING_DESC_F_WRITE | (Packet->InTransferLength > 0 ? VRING_DESC_F_NEXT : 0), | |
&Indices | |
); | |
// | |
// enqueue "datain" if any, to be written by the host | |
// | |
if (Packet->InTransferLength > 0) { | |
VirtioAppendDesc ( | |
&Dev->Ring, | |
InDataDeviceAddress, | |
Packet->InTransferLength, | |
VRING_DESC_F_WRITE, | |
&Indices | |
); | |
} | |
// If kicking the host fails, we must fake a host adapter error. | |
// EFI_NOT_READY would save us the effort, but it would also suggest that the | |
// caller retry. | |
// | |
if (VirtioFlush ( | |
Dev->VirtIo, | |
VIRTIO_SCSI_REQUEST_QUEUE, | |
&Dev->Ring, | |
&Indices, | |
NULL | |
) != EFI_SUCCESS) | |
{ | |
Status = ReportHostAdapterError (Packet); | |
goto UnmapResponseBuffer; | |
} | |
Status = ParseResponse (Packet, Response); | |
// | |
// If virtio request was successful and it was a CPU read request then we | |
// have used an intermediate buffer. Copy the data from intermediate buffer | |
// to the final buffer. | |
// | |
if (InDataBuffer != NULL) { | |
CopyMem (Packet->InDataBuffer, InDataBuffer, Packet->InTransferLength); | |
} | |
UnmapResponseBuffer: | |
Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, ResponseMapping); | |
FreeResponseBuffer: | |
Dev->VirtIo->FreeSharedPages ( | |
Dev->VirtIo, | |
EFI_SIZE_TO_PAGES (sizeof *Response), | |
ResponseBuffer | |
); | |
UnmapOutDataBuffer: | |
if (OutDataBufferIsMapped) { | |
Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, OutDataMapping); | |
} | |
UnmapInDataBuffer: | |
if (InDataBuffer != NULL) { | |
Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, InDataMapping); | |
} | |
FreeInDataBuffer: | |
if (InDataBuffer != NULL) { | |
Dev->VirtIo->FreeSharedPages (Dev->VirtIo, InDataNumPages, InDataBuffer); | |
} | |
UnmapRequestBuffer: | |
Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, RequestMapping); | |
return Status; | |
} | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiGetNextTargetLun ( | |
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, | |
IN OUT UINT8 **TargetPointer, | |
IN OUT UINT64 *Lun | |
) | |
{ | |
UINT8 *Target; | |
UINTN Idx; | |
UINT16 LastTarget; | |
VSCSI_DEV *Dev; | |
// | |
// the TargetPointer input parameter is unnecessarily a pointer-to-pointer | |
// | |
Target = *TargetPointer; | |
// | |
// Search for first non-0xFF byte. If not found, return first target & LUN. | |
// | |
for (Idx = 0; Idx < TARGET_MAX_BYTES && Target[Idx] == 0xFF; ++Idx) { | |
} | |
if (Idx == TARGET_MAX_BYTES) { | |
SetMem (Target, TARGET_MAX_BYTES, 0x00); | |
*Lun = 0; | |
return EFI_SUCCESS; | |
} | |
// | |
// see the TARGET_MAX_BYTES check in "VirtioScsi.h" | |
// | |
CopyMem (&LastTarget, Target, sizeof LastTarget); | |
// | |
// increment (target, LUN) pair if valid on input | |
// | |
Dev = VIRTIO_SCSI_FROM_PASS_THRU (This); | |
if ((LastTarget > Dev->MaxTarget) || (*Lun > Dev->MaxLun)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (*Lun < Dev->MaxLun) { | |
++*Lun; | |
return EFI_SUCCESS; | |
} | |
if (LastTarget < Dev->MaxTarget) { | |
*Lun = 0; | |
++LastTarget; | |
CopyMem (Target, &LastTarget, sizeof LastTarget); | |
return EFI_SUCCESS; | |
} | |
return EFI_NOT_FOUND; | |
} | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiBuildDevicePath ( | |
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, | |
IN UINT8 *Target, | |
IN UINT64 Lun, | |
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath | |
) | |
{ | |
UINT16 TargetValue; | |
VSCSI_DEV *Dev; | |
SCSI_DEVICE_PATH *ScsiDevicePath; | |
if (DevicePath == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
CopyMem (&TargetValue, Target, sizeof TargetValue); | |
Dev = VIRTIO_SCSI_FROM_PASS_THRU (This); | |
if ((TargetValue > Dev->MaxTarget) || (Lun > Dev->MaxLun) || (Lun > 0xFFFF)) { | |
return EFI_NOT_FOUND; | |
} | |
ScsiDevicePath = AllocatePool (sizeof *ScsiDevicePath); | |
if (ScsiDevicePath == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
ScsiDevicePath->Header.Type = MESSAGING_DEVICE_PATH; | |
ScsiDevicePath->Header.SubType = MSG_SCSI_DP; | |
ScsiDevicePath->Header.Length[0] = (UINT8)sizeof *ScsiDevicePath; | |
ScsiDevicePath->Header.Length[1] = (UINT8)(sizeof *ScsiDevicePath >> 8); | |
ScsiDevicePath->Pun = TargetValue; | |
ScsiDevicePath->Lun = (UINT16)Lun; | |
*DevicePath = &ScsiDevicePath->Header; | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiGetTargetLun ( | |
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, | |
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, | |
OUT UINT8 **TargetPointer, | |
OUT UINT64 *Lun | |
) | |
{ | |
SCSI_DEVICE_PATH *ScsiDevicePath; | |
VSCSI_DEV *Dev; | |
UINT8 *Target; | |
if ((DevicePath == NULL) || (TargetPointer == NULL) || (*TargetPointer == NULL) || | |
(Lun == NULL)) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((DevicePath->Type != MESSAGING_DEVICE_PATH) || | |
(DevicePath->SubType != MSG_SCSI_DP)) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
ScsiDevicePath = (SCSI_DEVICE_PATH *)DevicePath; | |
Dev = VIRTIO_SCSI_FROM_PASS_THRU (This); | |
if ((ScsiDevicePath->Pun > Dev->MaxTarget) || | |
(ScsiDevicePath->Lun > Dev->MaxLun)) | |
{ | |
return EFI_NOT_FOUND; | |
} | |
// | |
// a) the TargetPointer input parameter is unnecessarily a pointer-to-pointer | |
// b) see the TARGET_MAX_BYTES check in "VirtioScsi.h" | |
// c) ScsiDevicePath->Pun is an UINT16 | |
// | |
Target = *TargetPointer; | |
CopyMem (Target, &ScsiDevicePath->Pun, 2); | |
SetMem (Target + 2, TARGET_MAX_BYTES - 2, 0x00); | |
*Lun = ScsiDevicePath->Lun; | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiResetChannel ( | |
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This | |
) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiResetTargetLun ( | |
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, | |
IN UINT8 *Target, | |
IN UINT64 Lun | |
) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiGetNextTarget ( | |
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This, | |
IN OUT UINT8 **TargetPointer | |
) | |
{ | |
UINT8 *Target; | |
UINTN Idx; | |
UINT16 LastTarget; | |
VSCSI_DEV *Dev; | |
// | |
// the TargetPointer input parameter is unnecessarily a pointer-to-pointer | |
// | |
Target = *TargetPointer; | |
// | |
// Search for first non-0xFF byte. If not found, return first target. | |
// | |
for (Idx = 0; Idx < TARGET_MAX_BYTES && Target[Idx] == 0xFF; ++Idx) { | |
} | |
if (Idx == TARGET_MAX_BYTES) { | |
SetMem (Target, TARGET_MAX_BYTES, 0x00); | |
return EFI_SUCCESS; | |
} | |
// | |
// see the TARGET_MAX_BYTES check in "VirtioScsi.h" | |
// | |
CopyMem (&LastTarget, Target, sizeof LastTarget); | |
// | |
// increment target if valid on input | |
// | |
Dev = VIRTIO_SCSI_FROM_PASS_THRU (This); | |
if (LastTarget > Dev->MaxTarget) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (LastTarget < Dev->MaxTarget) { | |
++LastTarget; | |
CopyMem (Target, &LastTarget, sizeof LastTarget); | |
return EFI_SUCCESS; | |
} | |
return EFI_NOT_FOUND; | |
} | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiInit ( | |
IN OUT VSCSI_DEV *Dev | |
) | |
{ | |
UINT8 NextDevStat; | |
EFI_STATUS Status; | |
UINT64 RingBaseShift; | |
UINT64 Features; | |
UINT16 MaxChannel; // for validation only | |
UINT32 NumQueues; // for validation only | |
UINT16 QueueSize; | |
// | |
// Execute virtio-0.9.5, 2.2.1 Device Initialization Sequence. | |
// | |
NextDevStat = 0; // step 1 -- reset device | |
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
NextDevStat |= VSTAT_ACK; // step 2 -- acknowledge device presence | |
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
NextDevStat |= VSTAT_DRIVER; // step 3 -- we know how to drive it | |
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
// | |
// Set Page Size - MMIO VirtIo Specific | |
// | |
Status = Dev->VirtIo->SetPageSize (Dev->VirtIo, EFI_PAGE_SIZE); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
// | |
// step 4a -- retrieve and validate features | |
// | |
Status = Dev->VirtIo->GetDeviceFeatures (Dev->VirtIo, &Features); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
Dev->InOutSupported = (BOOLEAN)((Features & VIRTIO_SCSI_F_INOUT) != 0); | |
Status = VIRTIO_CFG_READ (Dev, MaxChannel, &MaxChannel); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
if (MaxChannel != 0) { | |
// | |
// this driver is for a single-channel virtio-scsi HBA | |
// | |
Status = EFI_UNSUPPORTED; | |
goto Failed; | |
} | |
Status = VIRTIO_CFG_READ (Dev, NumQueues, &NumQueues); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
if (NumQueues < 1) { | |
Status = EFI_UNSUPPORTED; | |
goto Failed; | |
} | |
Status = VIRTIO_CFG_READ (Dev, MaxTarget, &Dev->MaxTarget); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
if (Dev->MaxTarget > PcdGet16 (PcdVirtioScsiMaxTargetLimit)) { | |
Dev->MaxTarget = PcdGet16 (PcdVirtioScsiMaxTargetLimit); | |
} | |
Status = VIRTIO_CFG_READ (Dev, MaxLun, &Dev->MaxLun); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
if (Dev->MaxLun > PcdGet32 (PcdVirtioScsiMaxLunLimit)) { | |
Dev->MaxLun = PcdGet32 (PcdVirtioScsiMaxLunLimit); | |
} | |
Status = VIRTIO_CFG_READ (Dev, MaxSectors, &Dev->MaxSectors); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
if (Dev->MaxSectors < 2) { | |
// | |
// We must be able to halve it for bidirectional transfers | |
// (see EFI_BAD_BUFFER_SIZE in PopulateRequest()). | |
// | |
Status = EFI_UNSUPPORTED; | |
goto Failed; | |
} | |
Features &= VIRTIO_SCSI_F_INOUT | VIRTIO_F_VERSION_1 | | |
VIRTIO_F_IOMMU_PLATFORM; | |
// | |
// In virtio-1.0, feature negotiation is expected to complete before queue | |
// discovery, and the device can also reject the selected set of features. | |
// | |
if (Dev->VirtIo->Revision >= VIRTIO_SPEC_REVISION (1, 0, 0)) { | |
Status = Virtio10WriteFeatures (Dev->VirtIo, Features, &NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
} | |
// | |
// step 4b -- allocate request virtqueue | |
// | |
Status = Dev->VirtIo->SetQueueSel (Dev->VirtIo, VIRTIO_SCSI_REQUEST_QUEUE); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
Status = Dev->VirtIo->GetQueueNumMax (Dev->VirtIo, &QueueSize); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
// | |
// VirtioScsiPassThru() uses at most four descriptors | |
// | |
if (QueueSize < 4) { | |
Status = EFI_UNSUPPORTED; | |
goto Failed; | |
} | |
Status = VirtioRingInit (Dev->VirtIo, QueueSize, &Dev->Ring); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
// | |
// If anything fails from here on, we must release the ring resources | |
// | |
Status = VirtioRingMap ( | |
Dev->VirtIo, | |
&Dev->Ring, | |
&RingBaseShift, | |
&Dev->RingMap | |
); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseQueue; | |
} | |
// | |
// Additional steps for MMIO: align the queue appropriately, and set the | |
// size. If anything fails from here on, we must unmap the ring resources. | |
// | |
Status = Dev->VirtIo->SetQueueNum (Dev->VirtIo, QueueSize); | |
if (EFI_ERROR (Status)) { | |
goto UnmapQueue; | |
} | |
Status = Dev->VirtIo->SetQueueAlign (Dev->VirtIo, EFI_PAGE_SIZE); | |
if (EFI_ERROR (Status)) { | |
goto UnmapQueue; | |
} | |
// | |
// step 4c -- Report GPFN (guest-physical frame number) of queue. | |
// | |
Status = Dev->VirtIo->SetQueueAddress ( | |
Dev->VirtIo, | |
&Dev->Ring, | |
RingBaseShift | |
); | |
if (EFI_ERROR (Status)) { | |
goto UnmapQueue; | |
} | |
// | |
// step 5 -- Report understood features and guest-tuneables. | |
// | |
if (Dev->VirtIo->Revision < VIRTIO_SPEC_REVISION (1, 0, 0)) { | |
Features &= ~(UINT64)(VIRTIO_F_VERSION_1 | VIRTIO_F_IOMMU_PLATFORM); | |
Status = Dev->VirtIo->SetGuestFeatures (Dev->VirtIo, Features); | |
if (EFI_ERROR (Status)) { | |
goto UnmapQueue; | |
} | |
} | |
// | |
// We expect these maximum sizes from the host. Since they are | |
// guest-negotiable, ask for them rather than just checking them. | |
// | |
Status = VIRTIO_CFG_WRITE (Dev, CdbSize, VIRTIO_SCSI_CDB_SIZE); | |
if (EFI_ERROR (Status)) { | |
goto UnmapQueue; | |
} | |
Status = VIRTIO_CFG_WRITE (Dev, SenseSize, VIRTIO_SCSI_SENSE_SIZE); | |
if (EFI_ERROR (Status)) { | |
goto UnmapQueue; | |
} | |
// | |
// step 6 -- initialization complete | |
// | |
NextDevStat |= VSTAT_DRIVER_OK; | |
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto UnmapQueue; | |
} | |
// | |
// populate the exported interface's attributes | |
// | |
Dev->PassThru.Mode = &Dev->PassThruMode; | |
Dev->PassThru.PassThru = &VirtioScsiPassThru; | |
Dev->PassThru.GetNextTargetLun = &VirtioScsiGetNextTargetLun; | |
Dev->PassThru.BuildDevicePath = &VirtioScsiBuildDevicePath; | |
Dev->PassThru.GetTargetLun = &VirtioScsiGetTargetLun; | |
Dev->PassThru.ResetChannel = &VirtioScsiResetChannel; | |
Dev->PassThru.ResetTargetLun = &VirtioScsiResetTargetLun; | |
Dev->PassThru.GetNextTarget = &VirtioScsiGetNextTarget; | |
// | |
// AdapterId is a target for which no handle will be created during bus scan. | |
// Prevent any conflict with real devices. | |
// | |
Dev->PassThruMode.AdapterId = 0xFFFFFFFF; | |
// | |
// Set both physical and logical attributes for non-RAID SCSI channel. See | |
// Driver Writer's Guide for UEFI 2.3.1 v1.01, 20.1.5 Implementing Extended | |
// SCSI Pass Thru Protocol. | |
// | |
Dev->PassThruMode.Attributes = EFI_EXT_SCSI_PASS_THRU_ATTRIBUTES_PHYSICAL | | |
EFI_EXT_SCSI_PASS_THRU_ATTRIBUTES_LOGICAL; | |
// | |
// no restriction on transfer buffer alignment | |
// | |
Dev->PassThruMode.IoAlign = 0; | |
return EFI_SUCCESS; | |
UnmapQueue: | |
Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, Dev->RingMap); | |
ReleaseQueue: | |
VirtioRingUninit (Dev->VirtIo, &Dev->Ring); | |
Failed: | |
// | |
// Notify the host about our failure to setup: virtio-0.9.5, 2.2.2.1 Device | |
// Status. VirtIo access failure here should not mask the original error. | |
// | |
NextDevStat |= VSTAT_FAILED; | |
Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); | |
Dev->InOutSupported = FALSE; | |
Dev->MaxTarget = 0; | |
Dev->MaxLun = 0; | |
Dev->MaxSectors = 0; | |
return Status; // reached only via Failed above | |
} | |
STATIC | |
VOID | |
EFIAPI | |
VirtioScsiUninit ( | |
IN OUT VSCSI_DEV *Dev | |
) | |
{ | |
// | |
// Reset the virtual device -- see virtio-0.9.5, 2.2.2.1 Device Status. When | |
// VIRTIO_CFG_WRITE() returns, the host will have learned to stay away from | |
// the old comms area. | |
// | |
Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, 0); | |
Dev->InOutSupported = FALSE; | |
Dev->MaxTarget = 0; | |
Dev->MaxLun = 0; | |
Dev->MaxSectors = 0; | |
Dev->VirtIo->UnmapSharedBuffer (Dev->VirtIo, Dev->RingMap); | |
VirtioRingUninit (Dev->VirtIo, &Dev->Ring); | |
SetMem (&Dev->PassThru, sizeof Dev->PassThru, 0x00); | |
SetMem (&Dev->PassThruMode, sizeof Dev->PassThruMode, 0x00); | |
} | |
// | |
// Event notification function enqueued by ExitBootServices(). | |
// | |
STATIC | |
VOID | |
EFIAPI | |
VirtioScsiExitBoot ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
VSCSI_DEV *Dev; | |
DEBUG ((DEBUG_VERBOSE, "%a: Context=0x%p\n", __func__, Context)); | |
// | |
// Reset the device. This causes the hypervisor to forget about the virtio | |
// ring. | |
// | |
// We allocated said ring in EfiBootServicesData type memory, and code | |
// executing after ExitBootServices() is permitted to overwrite it. | |
// | |
Dev = Context; | |
Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, 0); | |
} | |
// | |
// Probe, start and stop functions of this driver, called by the DXE core for | |
// specific devices. | |
// | |
// The following specifications document these interfaces: | |
// - Driver Writer's Guide for UEFI 2.3.1 v1.01, 9 Driver Binding Protocol | |
// - UEFI Spec 2.3.1 + Errata C, 10.1 EFI Driver Binding Protocol | |
// | |
// The implementation follows: | |
// - Driver Writer's Guide for UEFI 2.3.1 v1.01 | |
// - 5.1.3.4 OpenProtocol() and CloseProtocol() | |
// - UEFI Spec 2.3.1 + Errata C | |
// - 6.3 Protocol Handler Services | |
// | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiDriverBindingSupported ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE DeviceHandle, | |
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath | |
) | |
{ | |
EFI_STATUS Status; | |
VIRTIO_DEVICE_PROTOCOL *VirtIo; | |
// | |
// Attempt to open the device with the VirtIo set of interfaces. On success, | |
// the protocol is "instantiated" for the VirtIo device. Covers duplicate open | |
// attempts (EFI_ALREADY_STARTED). | |
// | |
Status = gBS->OpenProtocol ( | |
DeviceHandle, // candidate device | |
&gVirtioDeviceProtocolGuid, // for generic VirtIo access | |
(VOID **)&VirtIo, // handle to instantiate | |
This->DriverBindingHandle, // requestor driver identity | |
DeviceHandle, // ControllerHandle, according to | |
// the UEFI Driver Model | |
EFI_OPEN_PROTOCOL_BY_DRIVER // get exclusive VirtIo access to | |
// the device; to be released | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if (VirtIo->SubSystemDeviceId != VIRTIO_SUBSYSTEM_SCSI_HOST) { | |
Status = EFI_UNSUPPORTED; | |
} | |
// | |
// We needed VirtIo access only transitorily, to see whether we support the | |
// device or not. | |
// | |
gBS->CloseProtocol ( | |
DeviceHandle, | |
&gVirtioDeviceProtocolGuid, | |
This->DriverBindingHandle, | |
DeviceHandle | |
); | |
return Status; | |
} | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiDriverBindingStart ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE DeviceHandle, | |
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath | |
) | |
{ | |
VSCSI_DEV *Dev; | |
EFI_STATUS Status; | |
Dev = (VSCSI_DEV *)AllocateZeroPool (sizeof *Dev); | |
if (Dev == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Status = gBS->OpenProtocol ( | |
DeviceHandle, | |
&gVirtioDeviceProtocolGuid, | |
(VOID **)&Dev->VirtIo, | |
This->DriverBindingHandle, | |
DeviceHandle, | |
EFI_OPEN_PROTOCOL_BY_DRIVER | |
); | |
if (EFI_ERROR (Status)) { | |
goto FreeVirtioScsi; | |
} | |
// | |
// VirtIo access granted, configure virtio-scsi device. | |
// | |
Status = VirtioScsiInit (Dev); | |
if (EFI_ERROR (Status)) { | |
goto CloseVirtIo; | |
} | |
Status = gBS->CreateEvent ( | |
EVT_SIGNAL_EXIT_BOOT_SERVICES, | |
TPL_CALLBACK, | |
&VirtioScsiExitBoot, | |
Dev, | |
&Dev->ExitBoot | |
); | |
if (EFI_ERROR (Status)) { | |
goto UninitDev; | |
} | |
// | |
// Setup complete, attempt to export the driver instance's PassThru | |
// interface. | |
// | |
Dev->Signature = VSCSI_SIG; | |
Status = gBS->InstallProtocolInterface ( | |
&DeviceHandle, | |
&gEfiExtScsiPassThruProtocolGuid, | |
EFI_NATIVE_INTERFACE, | |
&Dev->PassThru | |
); | |
if (EFI_ERROR (Status)) { | |
goto CloseExitBoot; | |
} | |
return EFI_SUCCESS; | |
CloseExitBoot: | |
gBS->CloseEvent (Dev->ExitBoot); | |
UninitDev: | |
VirtioScsiUninit (Dev); | |
CloseVirtIo: | |
gBS->CloseProtocol ( | |
DeviceHandle, | |
&gVirtioDeviceProtocolGuid, | |
This->DriverBindingHandle, | |
DeviceHandle | |
); | |
FreeVirtioScsi: | |
FreePool (Dev); | |
return Status; | |
} | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiDriverBindingStop ( | |
IN EFI_DRIVER_BINDING_PROTOCOL *This, | |
IN EFI_HANDLE DeviceHandle, | |
IN UINTN NumberOfChildren, | |
IN EFI_HANDLE *ChildHandleBuffer | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_EXT_SCSI_PASS_THRU_PROTOCOL *PassThru; | |
VSCSI_DEV *Dev; | |
Status = gBS->OpenProtocol ( | |
DeviceHandle, // candidate device | |
&gEfiExtScsiPassThruProtocolGuid, // retrieve the SCSI iface | |
(VOID **)&PassThru, // target pointer | |
This->DriverBindingHandle, // requestor driver ident. | |
DeviceHandle, // lookup req. for dev. | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL // lookup only, no new ref. | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Dev = VIRTIO_SCSI_FROM_PASS_THRU (PassThru); | |
// | |
// Handle Stop() requests for in-use driver instances gracefully. | |
// | |
Status = gBS->UninstallProtocolInterface ( | |
DeviceHandle, | |
&gEfiExtScsiPassThruProtocolGuid, | |
&Dev->PassThru | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
gBS->CloseEvent (Dev->ExitBoot); | |
VirtioScsiUninit (Dev); | |
gBS->CloseProtocol ( | |
DeviceHandle, | |
&gVirtioDeviceProtocolGuid, | |
This->DriverBindingHandle, | |
DeviceHandle | |
); | |
FreePool (Dev); | |
return EFI_SUCCESS; | |
} | |
// | |
// The static object that groups the Supported() (ie. probe), Start() and | |
// Stop() functions of the driver together. Refer to UEFI Spec 2.3.1 + Errata | |
// C, 10.1 EFI Driver Binding Protocol. | |
// | |
STATIC EFI_DRIVER_BINDING_PROTOCOL gDriverBinding = { | |
&VirtioScsiDriverBindingSupported, | |
&VirtioScsiDriverBindingStart, | |
&VirtioScsiDriverBindingStop, | |
0x10, // Version, must be in [0x10 .. 0xFFFFFFEF] for IHV-developed drivers | |
NULL, // ImageHandle, to be overwritten by | |
// EfiLibInstallDriverBindingComponentName2() in VirtioScsiEntryPoint() | |
NULL // DriverBindingHandle, ditto | |
}; | |
// | |
// The purpose of the following scaffolding (EFI_COMPONENT_NAME_PROTOCOL and | |
// EFI_COMPONENT_NAME2_PROTOCOL implementation) is to format the driver's name | |
// in English, for display on standard console devices. This is recommended for | |
// UEFI drivers that follow the UEFI Driver Model. Refer to the Driver Writer's | |
// Guide for UEFI 2.3.1 v1.01, 11 UEFI Driver and Controller Names. | |
// | |
// Device type names ("Virtio SCSI Host Device") are not formatted because the | |
// driver supports only that device type. Therefore the driver name suffices | |
// for unambiguous identification. | |
// | |
STATIC | |
EFI_UNICODE_STRING_TABLE mDriverNameTable[] = { | |
{ "eng;en", L"Virtio SCSI Host Driver" }, | |
{ NULL, NULL } | |
}; | |
STATIC | |
EFI_COMPONENT_NAME_PROTOCOL gComponentName; | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiGetDriverName ( | |
IN EFI_COMPONENT_NAME_PROTOCOL *This, | |
IN CHAR8 *Language, | |
OUT CHAR16 **DriverName | |
) | |
{ | |
return LookupUnicodeString2 ( | |
Language, | |
This->SupportedLanguages, | |
mDriverNameTable, | |
DriverName, | |
(BOOLEAN)(This == &gComponentName) // Iso639Language | |
); | |
} | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiGetDeviceName ( | |
IN EFI_COMPONENT_NAME_PROTOCOL *This, | |
IN EFI_HANDLE DeviceHandle, | |
IN EFI_HANDLE ChildHandle, | |
IN CHAR8 *Language, | |
OUT CHAR16 **ControllerName | |
) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
STATIC | |
EFI_COMPONENT_NAME_PROTOCOL gComponentName = { | |
&VirtioScsiGetDriverName, | |
&VirtioScsiGetDeviceName, | |
"eng" // SupportedLanguages, ISO 639-2 language codes | |
}; | |
STATIC | |
EFI_COMPONENT_NAME2_PROTOCOL gComponentName2 = { | |
(EFI_COMPONENT_NAME2_GET_DRIVER_NAME)&VirtioScsiGetDriverName, | |
(EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME)&VirtioScsiGetDeviceName, | |
"en" // SupportedLanguages, RFC 4646 language codes | |
}; | |
// | |
// Entry point of this driver. | |
// | |
EFI_STATUS | |
EFIAPI | |
VirtioScsiEntryPoint ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
return EfiLibInstallDriverBindingComponentName2 ( | |
ImageHandle, | |
SystemTable, | |
&gDriverBinding, | |
ImageHandle, | |
&gComponentName, | |
&gComponentName2 | |
); | |
} |