text: /** @file
  
    This driver produces Extended SCSI Pass Thru Protocol instances for
    virtio-scsi devices.
  
    The implementation is basic:
  
    - No hotplug / hot-unplug.
  
    - Although EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() could be a good match
      for multiple in-flight virtio-scsi requests, we stick to synchronous
      requests for now.
  
    - Timeouts are not supported for EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru().
  
    - Only one channel is supported. (At the time of this writing, host-side
      virtio-scsi supports a single channel too.)
  
    - Only one request queue is used (for the one synchronous request).
  
    - The ResetChannel() and ResetTargetLun() functions of
      EFI_EXT_SCSI_PASS_THRU_PROTOCOL are not supported (which is allowed by the
      UEFI 2.3.1 Errata C specification), although
      VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET could be a good match. That would
      however require client code for the control queue, which is deemed
      unreasonable for now.
  
    Copyright (C) 2012, Red Hat, Inc.
    Copyright (c) 2012 - 2018, Intel Corporation. All rights reserved.<BR>
    Copyright (c) 2017, AMD Inc, All rights reserved.<BR>
  
    SPDX-License-Identifier: BSD-2-Clause-Patent
  
  **/
  
  #include <IndustryStandard/VirtioScsi.h>
  #include <Library/BaseMemoryLib.h>
  #include <Library/DebugLib.h>
  #include <Library/MemoryAllocationLib.h>
  #include <Library/UefiBootServicesTableLib.h>
  #include <Library/UefiLib.h>
  #include <Library/VirtioLib.h>
  
  #include "VirtioScsi.h"
  
  /**
  
    Convenience macros to read and write configuration elements of the
    virtio-scsi VirtIo device.
  
    The following macros make it possible to specify only the "core parameters"
    for such accesses and to derive the rest. By the time VIRTIO_CFG_WRITE()
    returns, the transaction will have been completed.
  
    @param[in] Dev       Pointer to the VSCSI_DEV structure.
  
    @param[in] Field     A field name from VSCSI_HDR, identifying the virtio-scsi
                         configuration item to access.
  
    @param[in] Value     (VIRTIO_CFG_WRITE() only.) The value to write to the
                         selected configuration item.
  
    @param[out] Pointer  (VIRTIO_CFG_READ() only.) The object to receive the
                         value read from the configuration item. Its type must be
                         one of UINT8, UINT16, UINT32, UINT64.
  
  
    @return  Status codes returned by Virtio->WriteDevice() / Virtio->ReadDevice().
  
  **/
  
  #define VIRTIO_CFG_WRITE(Dev, Field, Value)  ((Dev)->VirtIo->WriteDevice (  \
                                                  (Dev)->VirtIo,              \
                                                  OFFSET_OF_VSCSI (Field),    \
                                                  SIZE_OF_VSCSI (Field),      \
                                                  (Value)                     \
                                                  ))
  
  #define VIRTIO_CFG_READ(Dev, Field, Pointer) ((Dev)->VirtIo->ReadDevice (   \
                                                  (Dev)->VirtIo,              \
                                                  OFFSET_OF_VSCSI (Field),    \
                                                  SIZE_OF_VSCSI (Field),      \
                                                  sizeof *(Pointer),          \
                                                  (Pointer)                   \
                                                  ))
  
  
  //
  // UEFI Spec 2.3.1 + Errata C, 14.7 Extended SCSI Pass Thru Protocol specifies
  // the PassThru() interface. Beside returning a status code, the function must
  // set some fields in the EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET in/out
  // parameter on return. The following is a full list of those fields, for
  // easier validation of PopulateRequest(), ParseResponse(), and
  // ReportHostAdapterError() below.
  //
  // - InTransferLength
  // - OutTransferLength
  // - HostAdapterStatus
  // - TargetStatus
  // - SenseDataLength
  // - SenseData
  //
  // On any return from the PassThru() interface, these fields must be set,
  // except if the returned status code is explicitly exempt. (Actually the
  // implementation here conservatively sets these fields even in case not all
  // of them would be required by the specification.)
  //
  
  /**
  
    Populate a virtio-scsi request from the Extended SCSI Pass Thru Protocol
    packet.
  
    The caller is responsible for pre-zeroing the virtio-scsi request. The
    Extended SCSI Pass Thru Protocol packet is modified, to be forwarded outwards
    by VirtioScsiPassThru(), if invalid or unsupported parameters are detected.
  
    @param[in] Dev          The virtio-scsi host device the packet targets.
  
    @param[in] Target       The SCSI target controlled by the virtio-scsi host
                            device.
  
    @param[in] Lun          The Logical Unit Number under the SCSI target.
  
    @param[in out] Packet   The Extended SCSI Pass Thru Protocol packet the
                            function translates to a virtio-scsi request. On
                            failure this parameter relays error contents.
  
    @param[out]    Request  The pre-zeroed virtio-scsi request to populate. This
                            parameter is volatile-qualified because we expect the
                            caller to append it to a virtio ring, thus
                            assignments to Request must be visible when the
                            function returns.
  
  
    @retval EFI_SUCCESS  The Extended SCSI Pass Thru Protocol packet was valid,
                         Request has been populated.
  
    @return              Otherwise, invalid or unsupported parameters were
                         detected. Status codes are meant for direct forwarding
                         by the EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru()
                         implementation.
  
  **/
  STATIC
  EFI_STATUS
  EFIAPI
  PopulateRequest (
    IN     CONST    VSCSI_DEV                                   *Dev,
    IN              UINT16                                      Target,
    IN              UINT64                                      Lun,
    IN OUT          EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET  *Packet,
    OUT    volatile VIRTIO_SCSI_REQ                             *Request
    )
  {
    UINTN Idx;
  
    if (
        //
        // bidirectional transfer was requested, but the host doesn't support it
        //
        (Packet->InTransferLength > 0 && Packet->OutTransferLength > 0 &&
         !Dev->InOutSupported) ||
  
        //
        // a target / LUN was addressed that's impossible to encode for the host
        //
        Target > 0xFF || Lun >= 0x4000 ||
  
        //
        // Command Descriptor Block bigger than VIRTIO_SCSI_CDB_SIZE
        //
        Packet->CdbLength > VIRTIO_SCSI_CDB_SIZE ||
  
        //
        // From virtio-0.9.5, 2.3.2 Descriptor Table:
        // "no descriptor chain may be more than 2^32 bytes long in total".
        //
        (UINT64) Packet->InTransferLength + Packet->OutTransferLength > SIZE_1GB
        ) {
  
      //
      // this error code doesn't require updates to the Packet output fields
      //
      return EFI_UNSUPPORTED;
    }
  
    if (
        //
        // addressed invalid device
        //
        Target > Dev->MaxTarget || Lun > Dev->MaxLun ||
  
        //
        // invalid direction (there doesn't seem to be a macro for the "no data
        // transferred" "direction", eg. for TEST UNIT READY)
        //
        Packet->DataDirection > EFI_EXT_SCSI_DATA_DIRECTION_BIDIRECTIONAL ||
  
        //
        // trying to receive, but destination pointer is NULL, or contradicting
        // transfer direction
        //
        (Packet->InTransferLength > 0 &&
         (Packet->InDataBuffer == NULL ||
          Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_WRITE
          )
         ) ||
  
        //
        // trying to send, but source pointer is NULL, or contradicting transfer
        // direction
        //
        (Packet->OutTransferLength > 0 &&
         (Packet->OutDataBuffer == NULL ||
          Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ
          )
         )
        ) {
  
      //
      // this error code doesn't require updates to the Packet output fields
      //
      return EFI_INVALID_PARAMETER;
    }
  
    //
    // Catch oversized requests eagerly. If this condition evaluates to false,
    // then the combined size of a bidirectional request will not exceed the
    // virtio-scsi device's transfer limit either.
    //
    if (ALIGN_VALUE (Packet->OutTransferLength, 512) / 512
          > Dev->MaxSectors / 2 ||
        ALIGN_VALUE (Packet->InTransferLength,  512) / 512
          > Dev->MaxSectors / 2) {
      Packet->InTransferLength  = (Dev->MaxSectors / 2) * 512;
      Packet->OutTransferLength = (Dev->MaxSectors / 2) * 512;
      Packet->HostAdapterStatus =
                          EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN;
      Packet->TargetStatus      = EFI_EXT_SCSI_STATUS_TARGET_GOOD;
      Packet->SenseDataLength   = 0;
      return EFI_BAD_BUFFER_SIZE;
    }
  
    //
    // target & LUN encoding: see virtio-0.9.5, Appendix I: SCSI Host Device,
    // Device Operation: request queues
    //
    Request->Lun[0] = 1;
    Request->Lun[1] = (UINT8) Target;
    Request->Lun[2] = (UINT8) (((UINT32)Lun >> 8) | 0x40);
    Request->Lun[3] = (UINT8) Lun;
  
    //
    // CopyMem() would cast away the "volatile" qualifier before access, which is
    // undefined behavior (ISO C99 6.7.3p5)
    //
    for (Idx = 0; Idx < Packet->CdbLength; ++Idx) {
      Request->Cdb[Idx] = ((UINT8 *) Packet->Cdb)[Idx];
    }
  
    return EFI_SUCCESS;
  }
  
  
  /**
  
    Parse the virtio-scsi device's response, translate it to an EFI status code,
    and update the Extended SCSI Pass Thru Protocol packet, to be returned by
    the EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() implementation.
  
    @param[in out] Packet  The Extended SCSI Pass Thru Protocol packet that has
                           been translated to a virtio-scsi request with
                           PopulateRequest(), and processed by the host. On
                           output this parameter is updated with response or
                           error contents.
  
    @param[in] Response    The virtio-scsi response structure to parse. We expect
                           it to come from a virtio ring, thus it is qualified
                           volatile.
  
  
    @return  PassThru() status codes mandated by UEFI Spec 2.3.1 + Errata C, 14.7
             Extended SCSI Pass Thru Protocol.
  
  **/
  STATIC
  EFI_STATUS
  EFIAPI
  ParseResponse (
    IN OUT                EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet,
    IN     CONST volatile VIRTIO_SCSI_RESP                           *Response
    )
  {
    UINTN ResponseSenseLen;
    UINTN Idx;
  
    //
    // return sense data (length and contents) in all cases, truncated if needed
    //
    ResponseSenseLen = MIN (Response->SenseLen, VIRTIO_SCSI_SENSE_SIZE);
    if (Packet->SenseDataLength > ResponseSenseLen) {
      Packet->SenseDataLength = (UINT8) ResponseSenseLen;
    }
    for (Idx = 0; Idx < Packet->SenseDataLength; ++Idx) {
      ((UINT8 *) Packet->SenseData)[Idx] = Response->Sense[Idx];
    }
  
    //
    // Report actual transfer lengths. The logic below covers all three
    // DataDirections (read, write, bidirectional).
    //
    // -+- @ 0
    //  |
    //  | write                                       ^  @ Residual (unprocessed)
    //  |                                             |
    // -+- @ OutTransferLength                       -+- @ InTransferLength
    //  |                                             |
    //  | read                                        |
    //  |                                             |
    //  V  @ OutTransferLength + InTransferLength    -+- @ 0
    //
    if (Response->Residual <= Packet->InTransferLength) {
      Packet->InTransferLength  -= Response->Residual;
    }
    else {
      Packet->OutTransferLength -= Response->Residual - Packet->InTransferLength;
      Packet->InTransferLength   = 0;
    }
  
    //
    // report target status in all cases
    //
    Packet->TargetStatus = Response->Status;
  
    //
    // host adapter status and function return value depend on virtio-scsi
    // response code
    //
    switch (Response->Response) {
    case VIRTIO_SCSI_S_OK:
      Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK;
      return EFI_SUCCESS;
  
    case VIRTIO_SCSI_S_OVERRUN:
      Packet->HostAdapterStatus =
                          EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN;
      break;
  
    case VIRTIO_SCSI_S_BAD_TARGET:
      //
      // This is non-intuitive but explicitly required by the
      // EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() specification for
      // disconnected (but otherwise valid) target / LUN addresses.
      //
      Packet->HostAdapterStatus =
                                EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT_COMMAND;
      return EFI_TIMEOUT;
  
    case VIRTIO_SCSI_S_RESET:
      Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_RESET;
      break;
  
    case VIRTIO_SCSI_S_BUSY:
      Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK;
      return EFI_NOT_READY;
  
    //
    // Lump together the rest. The mapping for VIRTIO_SCSI_S_ABORTED is
    // intentional as well, not an oversight.
    //
    case VIRTIO_SCSI_S_ABORTED:
    case VIRTIO_SCSI_S_TRANSPORT_FAILURE:
    case VIRTIO_SCSI_S_TARGET_FAILURE:
    case VIRTIO_SCSI_S_NEXUS_FAILURE:
    case VIRTIO_SCSI_S_FAILURE:
    default:
      Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;
    }
  
    return EFI_DEVICE_ERROR;
  }
  
  
  /**
  
    The function can be used to create a fake host adapter error.
  
    When VirtioScsiPassThru() is failed due to some reasons then this function
    can be called to construct a host adapter error.
  
    @param[out] Packet  The Extended SCSI Pass Thru Protocol packet that the host
                        adapter error shall be placed in.
  
  
    @retval EFI_DEVICE_ERROR  The function returns this status code
                              unconditionally, to be propagated by
                              VirtioScsiPassThru().
  
  **/
  STATIC
  EFI_STATUS
  ReportHostAdapterError (
    OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET  *Packet
    )
  {
    Packet->InTransferLength  = 0;
    Packet->OutTransferLength = 0;
    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;
    Packet->TargetStatus      = EFI_EXT_SCSI_STATUS_TARGET_GOOD;
    Packet->SenseDataLength   = 0;
    return EFI_DEVICE_ERROR;
  }
  
  
  //
  // The next seven functions implement EFI_EXT_SCSI_PASS_THRU_PROTOCOL
  // for the virtio-scsi HBA. Refer to UEFI Spec 2.3.1 + Errata C, sections
  // - 14.1 SCSI Driver Model Overview,
  // - 14.7 Extended SCSI Pass Thru Protocol.
  //
  
  EFI_STATUS
  EFIAPI
  VirtioScsiPassThru (
    IN     EFI_EXT_SCSI_PASS_THRU_PROTOCOL            *This,
    IN     UINT8                                      *Target,
    IN     UINT64                                     Lun,
    IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet,
    IN     EFI_EVENT                                  Event   OPTIONAL
    )
  {
    VSCSI_DEV                 *Dev;
    UINT16                    TargetValue;
    EFI_STATUS                Status;
    volatile VIRTIO_SCSI_REQ  Request;
    volatile VIRTIO_SCSI_RESP *Response;
    VOID                      *ResponseBuffer;
    DESC_INDICES              Indices;
    VOID                      *RequestMapping;
    VOID                      *ResponseMapping;
    VOID                      *InDataMapping;
    VOID                      *OutDataMapping;
    EFI_PHYSICAL_ADDRESS      RequestDeviceAddress;
    EFI_PHYSICAL_ADDRESS      ResponseDeviceAddress;
    EFI_PHYSICAL_ADDRESS      InDataDeviceAddress;
    EFI_PHYSICAL_ADDRESS      OutDataDeviceAddress;
    VOID                      *InDataBuffer;
    UINTN                     InDataNumPages;
    BOOLEAN                   OutDataBufferIsMapped;
  
    //
    // Set InDataMapping,OutDataMapping,InDataDeviceAddress and OutDataDeviceAddress to
    // suppress incorrect compiler/analyzer warnings.
    //
    InDataMapping        = NULL;
    OutDataMapping       = NULL;
    InDataDeviceAddress  = 0;
    OutDataDeviceAddress = 0;
  
    ZeroMem ((VOID*) &Request, sizeof (Request));
  
    Dev = VIRTIO_SCSI_FROM_PASS_THRU (This);
    CopyMem (&TargetValue, Target, sizeof TargetValue);
  
    InDataBuffer = NULL;
    OutDataBufferIsMapped = FALSE;
    InDataNumPages = 0;
  
    Status = PopulateRequest (Dev, TargetValue, Lun, Packet, &Request);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    //
    // Map the virtio-scsi Request header buffer
    //
    Status = VirtioMapAllBytesInSharedBuffer (
               Dev->VirtIo,
               VirtioOperationBusMasterRead,
               (VOID *) &Request,
               sizeof Request,
               &RequestDeviceAddress,
               &RequestMapping);
    if (EFI_ERROR (Status)) {
      return ReportHostAdapterError (Packet);
    }
  
    //
    // Map the input buffer
    //
    if (Packet->InTransferLength > 0) {
      //
      // Allocate a intermediate input buffer. This is mainly to handle the
      // following case:
      //  * caller submits a bi-directional request
      //  * we perform the request fine
      //  * but we fail to unmap the "InDataMapping"
      //
      // In that case simply returing 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", __FUNCTION__, 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
             );
  }
  