/** @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 - 2014, Intel Corporation. All rights reserved.<BR> | |
This program and the accompanying materials are licensed and made available | |
under the terms and conditions of the BSD License which accompanies this | |
distribution. The full text of the license may be found at | |
http://opensource.org/licenses/bsd-license.php | |
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT | |
WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
**/ | |
#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 | |
// VirtioScsiPassThru() 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 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; | |
DESC_INDICES Indices; | |
ZeroMem ((VOID*) &Request, sizeof (Request)); | |
ZeroMem ((VOID*) &Response, sizeof (Response)); | |
Dev = VIRTIO_SCSI_FROM_PASS_THRU (This); | |
CopyMem (&TargetValue, Target, sizeof TargetValue); | |
Status = PopulateRequest (Dev, TargetValue, Lun, Packet, &Request); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
VirtioPrepare (&Dev->Ring, &Indices); | |
// | |
// preset a host status for ourselves that we do not accept as success | |
// | |
Response.Response = VIRTIO_SCSI_S_FAILURE; | |
// | |
// 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, (UINTN) &Request, sizeof Request, | |
VRING_DESC_F_NEXT, &Indices); | |
// | |
// enqueue "dataout" if any | |
// | |
if (Packet->OutTransferLength > 0) { | |
VirtioAppendDesc (&Dev->Ring, (UINTN) Packet->OutDataBuffer, | |
Packet->OutTransferLength, VRING_DESC_F_NEXT, &Indices); | |
} | |
// | |
// enqueue Response, to be written by the host | |
// | |
VirtioAppendDesc (&Dev->Ring, (UINTN) &Response, 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, (UINTN) Packet->InDataBuffer, | |
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) != EFI_SUCCESS) { | |
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; | |
} | |
return ParseResponse (Packet, &Response); | |
} | |
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; | |
UINT32 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; | |
} | |
// | |
// 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 (QueueSize, &Dev->Ring); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
// | |
// Additional steps for MMIO: align the queue appropriately, and set the | |
// size. If anything fails from here on, we must release the ring resources. | |
// | |
Status = Dev->VirtIo->SetQueueNum (Dev->VirtIo, QueueSize); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseQueue; | |
} | |
Status = Dev->VirtIo->SetQueueAlign (Dev->VirtIo, EFI_PAGE_SIZE); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseQueue; | |
} | |
// | |
// step 4c -- Report GPFN (guest-physical frame number) of queue. | |
// | |
Status = Dev->VirtIo->SetQueueAddress (Dev->VirtIo, | |
(UINT32) ((UINTN) Dev->Ring.Base >> EFI_PAGE_SHIFT)); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseQueue; | |
} | |
// | |
// step 5 -- Report understood features and guest-tuneables. We want none of | |
// the known (or unknown) VIRTIO_SCSI_F_* or VIRTIO_F_* capabilities (see | |
// virtio-0.9.5, Appendices B and I), except bidirectional transfers. | |
// | |
Status = Dev->VirtIo->SetGuestFeatures (Dev->VirtIo, | |
Features & VIRTIO_SCSI_F_INOUT); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseQueue; | |
} | |
// | |
// 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 ReleaseQueue; | |
} | |
Status = VIRTIO_CFG_WRITE (Dev, SenseSize, VIRTIO_SCSI_SENSE_SIZE); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseQueue; | |
} | |
// | |
// step 6 -- initialization complete | |
// | |
NextDevStat |= VSTAT_DRIVER_OK; | |
Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseQueue; | |
} | |
// | |
// 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; | |
ReleaseQueue: | |
VirtioRingUninit (&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; | |
VirtioRingUninit (&Dev->Ring); | |
SetMem (&Dev->PassThru, sizeof Dev->PassThru, 0x00); | |
SetMem (&Dev->PassThruMode, sizeof Dev->PassThruMode, 0x00); | |
} | |
// | |
// 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; | |
} | |
// | |
// 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 UninitDev; | |
} | |
return EFI_SUCCESS; | |
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; | |
} | |
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 | |
); | |
} |