/** @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.
Copyright (c) 2017, AMD Inc, All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #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 ); }