/** @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
           );
}
