| /** @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> | |
| Copyright (c) 2024, Arm Limited. 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; | |
| Request = AllocateZeroPool (sizeof (*Request)); | |
| if (Request == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| 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)) { | |
| goto FreeScsiRequest; | |
| } | |
| // | |
| // Map the virtio-scsi Request header buffer | |
| // | |
| Status = VirtioMapAllBytesInSharedBuffer ( | |
| Dev->VirtIo, | |
| VirtioOperationBusMasterRead, | |
| (VOID *)Request, | |
| sizeof (*Request), | |
| &RequestDeviceAddress, | |
| &RequestMapping | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| Status = ReportHostAdapterError (Packet); | |
| goto FreeScsiRequest; | |
| } | |
| // | |
| // 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); | |
| FreeScsiRequest: | |
| FreePool ((VOID *)Request); | |
| 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 | |
| ); | |
| } |