/** @file

  This driver produces Extended SCSI Pass Thru Protocol instances for
  LSI Fusion MPT SCSI devices.

  Copyright (C) 2020, Oracle and/or its affiliates.

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <IndustryStandard/FusionMptScsi.h>
#include <IndustryStandard/Pci.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PcdLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Protocol/PciIo.h>
#include <Protocol/PciRootBridgeIo.h>
#include <Protocol/ScsiPassThruExt.h>
#include <Uefi/UefiSpec.h>

//
// Higher versions will be used before lower, 0x10-0xffffffef is the version
// range for IVH (Indie Hardware Vendors)
//
#define MPT_SCSI_BINDING_VERSION  0x10

//
// Runtime Structures
//

typedef struct {
  MPT_SCSI_REQUEST_ALIGNED     IoRequest;
  MPT_SCSI_IO_REPLY_ALIGNED    IoReply;
  //
  // As EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET.SenseDataLength is defined
  // as UINT8, defining here SenseData size to MAX_UINT8 will guarantee it
  // cannot overflow when passed to device.
  //
  UINT8                        Sense[MAX_UINT8];
  //
  // This size of the data is arbitrarily chosen.
  // It seems to be sufficient for all I/O requests sent through
  // EFI_SCSI_PASS_THRU_PROTOCOL.PassThru() for common boot scenarios.
  //
  UINT8                        Data[0x2000];
} MPT_SCSI_DMA_BUFFER;

#define MPT_SCSI_DEV_SIGNATURE  SIGNATURE_32 ('M','P','T','S')
typedef struct {
  UINT32                             Signature;
  EFI_EXT_SCSI_PASS_THRU_PROTOCOL    PassThru;
  EFI_EXT_SCSI_PASS_THRU_MODE        PassThruMode;
  UINT8                              MaxTarget;
  UINT32                             StallPerPollUsec;
  EFI_PCI_IO_PROTOCOL                *PciIo;
  UINT64                             OriginalPciAttributes;
  EFI_EVENT                          ExitBoot;
  MPT_SCSI_DMA_BUFFER                *Dma;
  EFI_PHYSICAL_ADDRESS               DmaPhysical;
  VOID                               *DmaMapping;
  BOOLEAN                            IoReplyEnqueued;
} MPT_SCSI_DEV;

#define MPT_SCSI_FROM_PASS_THRU(PassThruPtr) \
  CR (PassThruPtr, MPT_SCSI_DEV, PassThru, MPT_SCSI_DEV_SIGNATURE)

#define MPT_SCSI_DMA_ADDR(Dev, MemberName) \
  (Dev->DmaPhysical + OFFSET_OF (MPT_SCSI_DMA_BUFFER, MemberName))

#define MPT_SCSI_DMA_ADDR_HIGH(Dev, MemberName) \
  ((UINT32)RShiftU64 (MPT_SCSI_DMA_ADDR (Dev, MemberName), 32))

#define MPT_SCSI_DMA_ADDR_LOW(Dev, MemberName) \
  ((UINT32)MPT_SCSI_DMA_ADDR (Dev, MemberName))

//
// Hardware functions
//

STATIC
EFI_STATUS
Out32 (
  IN MPT_SCSI_DEV  *Dev,
  IN UINT32        Addr,
  IN UINT32        Data
  )
{
  return Dev->PciIo->Io.Write (
                          Dev->PciIo,
                          EfiPciIoWidthUint32,
                          PCI_BAR_IDX0,
                          Addr,
                          1,
                          &Data
                          );
}

STATIC
EFI_STATUS
In32 (
  IN  MPT_SCSI_DEV  *Dev,
  IN  UINT32        Addr,
  OUT UINT32        *Data
  )
{
  return Dev->PciIo->Io.Read (
                          Dev->PciIo,
                          EfiPciIoWidthUint32,
                          PCI_BAR_IDX0,
                          Addr,
                          1,
                          Data
                          );
}

STATIC
EFI_STATUS
MptDoorbell (
  IN MPT_SCSI_DEV  *Dev,
  IN UINT8         DoorbellFunc,
  IN UINT8         DoorbellArg
  )
{
  return Out32 (
           Dev,
           MPT_REG_DOORBELL,
           (((UINT32)DoorbellFunc) << 24) | (DoorbellArg << 16)
           );
}

STATIC
EFI_STATUS
MptScsiReset (
  IN MPT_SCSI_DEV  *Dev
  )
{
  EFI_STATUS  Status;

  //
  // Reset hardware
  //
  Status = MptDoorbell (Dev, MPT_DOORBELL_RESET, 0);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Mask interrupts
  //
  Status = Out32 (Dev, MPT_REG_IMASK, MPT_IMASK_DOORBELL | MPT_IMASK_REPLY);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Clear interrupt status
  //
  Status = Out32 (Dev, MPT_REG_ISTATUS, 0);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
MptScsiInit (
  IN MPT_SCSI_DEV  *Dev
  )
{
  EFI_STATUS  Status;

  union {
    MPT_IO_CONTROLLER_INIT_REQUEST    Data;
    UINT32                            Uint32;
  } AlignedReq;
  MPT_IO_CONTROLLER_INIT_REQUEST  *Req;
  MPT_IO_CONTROLLER_INIT_REPLY    Reply;
  UINT8                           *ReplyBytes;
  UINT32                          ReplyWord;

  Req = &AlignedReq.Data;

  Status = MptScsiReset (Dev);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  ZeroMem (Req, sizeof (*Req));
  ZeroMem (&Reply, sizeof (Reply));
  Req->WhoInit  = MPT_IOC_WHOINIT_ROM_BIOS;
  Req->Function = MPT_MESSAGE_HDR_FUNCTION_IOC_INIT;
  STATIC_ASSERT (
    FixedPcdGet8 (PcdMptScsiMaxTargetLimit) < 255,
    "Req supports 255 targets only (max target is 254)"
    );
  Req->MaxDevices          = Dev->MaxTarget + 1;
  Req->MaxBuses            = 1;
  Req->ReplyFrameSize      = sizeof Dev->Dma->IoReply.Data;
  Req->HostMfaHighAddr     = MPT_SCSI_DMA_ADDR_HIGH (Dev, IoRequest);
  Req->SenseBufferHighAddr = MPT_SCSI_DMA_ADDR_HIGH (Dev, Sense);

  //
  // Send controller init through doorbell
  //
  STATIC_ASSERT (
    sizeof (*Req) % sizeof (UINT32) == 0,
    "Req must be multiple of UINT32"
    );
  STATIC_ASSERT (
    sizeof (*Req) / sizeof (UINT32) <= MAX_UINT8,
    "Req must fit in MAX_UINT8 Dwords"
    );
  Status = MptDoorbell (
             Dev,
             MPT_DOORBELL_HANDSHAKE,
             (UINT8)(sizeof (*Req) / sizeof (UINT32))
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = Dev->PciIo->Io.Write (
                            Dev->PciIo,
                            EfiPciIoWidthFifoUint32,
                            PCI_BAR_IDX0,
                            MPT_REG_DOORBELL,
                            sizeof (*Req) / sizeof (UINT32),
                            Req
                            );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Read reply through doorbell
  // Each 32bit (Dword) read produces 16bit (Word) of data
  //
  // The reply is read back to complete the doorbell function but it
  // isn't useful because it doesn't contain relevant data or status
  // codes.
  //
  STATIC_ASSERT (
    sizeof (Reply) % sizeof (UINT16) == 0,
    "Reply must be multiple of UINT16"
    );
  ReplyBytes = (UINT8 *)&Reply;
  while (ReplyBytes != (UINT8 *)(&Reply + 1)) {
    Status = In32 (Dev, MPT_REG_DOORBELL, &ReplyWord);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    CopyMem (ReplyBytes, &ReplyWord, sizeof (UINT16));
    ReplyBytes += sizeof (UINT16);
  }

  //
  // Clear interrupts generated by doorbell reply
  //
  Status = Out32 (Dev, MPT_REG_ISTATUS, 0);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
ReportHostAdapterError (
  OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET  *Packet
  )
{
  DEBUG ((DEBUG_ERROR, "%a: fatal error in scsi request\n", __func__));
  Packet->InTransferLength  = 0;
  Packet->OutTransferLength = 0;
  Packet->SenseDataLength   = 0;
  Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;
  Packet->TargetStatus      = EFI_EXT_SCSI_STATUS_TARGET_TASK_ABORTED;
  return EFI_DEVICE_ERROR;
}

STATIC
EFI_STATUS
ReportHostAdapterOverrunError (
  OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET  *Packet
  )
{
  Packet->SenseDataLength   = 0;
  Packet->HostAdapterStatus =
    EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN;
  Packet->TargetStatus = EFI_EXT_SCSI_STATUS_TARGET_GOOD;
  return EFI_BAD_BUFFER_SIZE;
}

STATIC
EFI_STATUS
MptScsiPopulateRequest (
  IN MPT_SCSI_DEV                                    *Dev,
  IN UINT8                                           Target,
  IN UINT64                                          Lun,
  IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET  *Packet
  )
{
  MPT_SCSI_REQUEST_WITH_SG  *Request;

  Request = &Dev->Dma->IoRequest.Data;

  if ((Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_BIDIRECTIONAL) ||
      ((Packet->InTransferLength > 0) && (Packet->OutTransferLength > 0)) ||
      (Packet->CdbLength > sizeof (Request->Header.Cdb)))
  {
    return EFI_UNSUPPORTED;
  }

  if ((Target > Dev->MaxTarget) || (Lun > 0) ||
      (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)
       )
      )
      )
  {
    return EFI_INVALID_PARAMETER;
  }

  if (Packet->InTransferLength > sizeof (Dev->Dma->Data)) {
    Packet->InTransferLength = sizeof (Dev->Dma->Data);
    return ReportHostAdapterOverrunError (Packet);
  }

  if (Packet->OutTransferLength > sizeof (Dev->Dma->Data)) {
    Packet->OutTransferLength = sizeof (Dev->Dma->Data);
    return ReportHostAdapterOverrunError (Packet);
  }

  ZeroMem (Request, sizeof (*Request));
  Request->Header.TargetId = Target;
  //
  // Only LUN 0 is currently supported, hence the cast is safe
  //
  Request->Header.Lun[1]         = (UINT8)Lun;
  Request->Header.Function       = MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST;
  Request->Header.MessageContext = 1; // We handle one request at a time

  Request->Header.CdbLength = Packet->CdbLength;
  CopyMem (Request->Header.Cdb, Packet->Cdb, Packet->CdbLength);

  //
  // SenseDataLength is UINT8, Sense[] is MAX_UINT8, so we can't overflow
  //
  ZeroMem (Dev->Dma->Sense, Packet->SenseDataLength);
  Request->Header.SenseBufferLength     = Packet->SenseDataLength;
  Request->Header.SenseBufferLowAddress = MPT_SCSI_DMA_ADDR_LOW (Dev, Sense);

  Request->Sg.EndOfList         = 1;
  Request->Sg.EndOfBuffer       = 1;
  Request->Sg.LastElement       = 1;
  Request->Sg.ElementType       = MPT_SG_ENTRY_TYPE_SIMPLE;
  Request->Sg.Is64BitAddress    = 1;
  Request->Sg.DataBufferAddress = MPT_SCSI_DMA_ADDR (Dev, Data);

  //
  // "MPT_SG_ENTRY_SIMPLE.Length" is a 24-bit quantity.
  //
  STATIC_ASSERT (
    sizeof (Dev->Dma->Data) < SIZE_16MB,
    "MPT_SCSI_DMA_BUFFER.Data must be smaller than 16MB"
    );

  if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {
    Request->Header.DataLength = Packet->InTransferLength;
    Request->Sg.Length         = Packet->InTransferLength;
    Request->Header.Control    = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_READ;
  } else {
    Request->Header.DataLength = Packet->OutTransferLength;
    Request->Sg.Length         = Packet->OutTransferLength;
    Request->Header.Control    = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_WRITE;

    CopyMem (Dev->Dma->Data, Packet->OutDataBuffer, Packet->OutTransferLength);
    Request->Sg.BufferContainsData = 1;
  }

  if (Request->Header.DataLength == 0) {
    Request->Header.Control = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_NONE;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
MptScsiSendRequest (
  IN MPT_SCSI_DEV                                    *Dev,
  IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET  *Packet
  )
{
  EFI_STATUS  Status;

  if (!Dev->IoReplyEnqueued) {
    //
    // Put one free reply frame on the reply queue, the hardware may use it to
    // report an error to us.
    //
    Status = Out32 (Dev, MPT_REG_REP_Q, MPT_SCSI_DMA_ADDR_LOW (Dev, IoReply));
    if (EFI_ERROR (Status)) {
      return EFI_DEVICE_ERROR;
    }

    Dev->IoReplyEnqueued = TRUE;
  }

  Status = Out32 (Dev, MPT_REG_REQ_Q, MPT_SCSI_DMA_ADDR_LOW (Dev, IoRequest));
  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
MptScsiGetReply (
  IN MPT_SCSI_DEV  *Dev,
  OUT UINT32       *Reply
  )
{
  EFI_STATUS  Status;
  UINT32      Istatus;
  UINT32      EmptyReply;

  //
  // Timeouts are not supported for
  // EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru() in this implementation.
  //
  for ( ; ;) {
    Status = In32 (Dev, MPT_REG_ISTATUS, &Istatus);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Interrupt raised
    //
    if (Istatus & MPT_IMASK_REPLY) {
      break;
    }

    gBS->Stall (Dev->StallPerPollUsec);
  }

  Status = In32 (Dev, MPT_REG_REP_Q, Reply);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // The driver is supposed to fetch replies until 0xffffffff is returned, which
  // will reset the interrupt status. We put only one request, so we expect the
  // next read reply to be the last.
  //
  Status = In32 (Dev, MPT_REG_REP_Q, &EmptyReply);
  if (EFI_ERROR (Status) || (EmptyReply != MAX_UINT32)) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
MptScsiHandleReply (
  IN MPT_SCSI_DEV                                 *Dev,
  IN UINT32                                       Reply,
  OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET  *Packet
  )
{
  if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {
    CopyMem (Packet->InDataBuffer, Dev->Dma->Data, Packet->InTransferLength);
  }

  if (Reply == Dev->Dma->IoRequest.Data.Header.MessageContext) {
    //
    // This is a turbo reply, everything is good
    //
    Packet->SenseDataLength   = 0;
    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK;
    Packet->TargetStatus      = EFI_EXT_SCSI_STATUS_TARGET_GOOD;
  } else if ((Reply & BIT31) != 0) {
    DEBUG ((DEBUG_INFO, "%a: Full reply returned\n", __func__));
    //
    // When reply MSB is set, we got a full reply. Since we submitted only one
    // reply frame, we know it's IoReply.
    //
    Dev->IoReplyEnqueued = FALSE;

    Packet->TargetStatus = Dev->Dma->IoReply.Data.ScsiStatus;
    //
    // Make sure device only lowers SenseDataLength before copying sense
    //
    ASSERT (Dev->Dma->IoReply.Data.SenseCount <= Packet->SenseDataLength);
    Packet->SenseDataLength =
      (UINT8)MIN (Dev->Dma->IoReply.Data.SenseCount, Packet->SenseDataLength);
    CopyMem (Packet->SenseData, Dev->Dma->Sense, Packet->SenseDataLength);

    if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {
      Packet->InTransferLength = Dev->Dma->IoReply.Data.TransferCount;
    } else {
      Packet->OutTransferLength = Dev->Dma->IoReply.Data.TransferCount;
    }

    switch (Dev->Dma->IoReply.Data.IocStatus) {
      case MPT_SCSI_IOCSTATUS_SUCCESS:
        Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK;
        break;
      case MPT_SCSI_IOCSTATUS_DEVICE_NOT_THERE:
        Packet->HostAdapterStatus =
          EFI_EXT_SCSI_STATUS_HOST_ADAPTER_SELECTION_TIMEOUT;
        return EFI_TIMEOUT;
      case MPT_SCSI_IOCSTATUS_DATA_UNDERRUN:
      case MPT_SCSI_IOCSTATUS_DATA_OVERRUN:
        Packet->HostAdapterStatus =
          EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN;
        break;
      default:
        Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;
        return EFI_DEVICE_ERROR;
    }
  } else {
    DEBUG ((DEBUG_ERROR, "%a: unexpected reply (%x)\n", __func__, Reply));
    return ReportHostAdapterError (Packet);
  }

  return EFI_SUCCESS;
}

//
// Ext SCSI Pass Thru
//

STATIC
EFI_STATUS
EFIAPI
MptScsiPassThru (
  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
  )
{
  EFI_STATUS    Status;
  MPT_SCSI_DEV  *Dev;
  UINT32        Reply;

  Dev = MPT_SCSI_FROM_PASS_THRU (This);
  //
  // We only use first byte of target identifer
  //
  Status = MptScsiPopulateRequest (Dev, *Target, Lun, Packet);
  if (EFI_ERROR (Status)) {
    //
    // MptScsiPopulateRequest modified packet according to the error
    //
    return Status;
  }

  Status = MptScsiSendRequest (Dev, Packet);
  if (EFI_ERROR (Status)) {
    return ReportHostAdapterError (Packet);
  }

  Status = MptScsiGetReply (Dev, &Reply);
  if (EFI_ERROR (Status)) {
    return ReportHostAdapterError (Packet);
  }

  return MptScsiHandleReply (Dev, Reply, Packet);
}

STATIC
BOOLEAN
IsTargetInitialized (
  IN UINT8  *Target
  )
{
  UINTN  Idx;

  for (Idx = 0; Idx < TARGET_MAX_BYTES; ++Idx) {
    if (Target[Idx] != 0xFF) {
      return TRUE;
    }
  }

  return FALSE;
}

STATIC
EFI_STATUS
EFIAPI
MptScsiGetNextTargetLun (
  IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL  *This,
  IN OUT UINT8                        **Target,
  IN OUT UINT64                       *Lun
  )
{
  MPT_SCSI_DEV  *Dev;

  Dev = MPT_SCSI_FROM_PASS_THRU (This);
  //
  // Currently support only LUN 0, so hardcode it
  //
  if (!IsTargetInitialized (*Target)) {
    ZeroMem (*Target, TARGET_MAX_BYTES);
    *Lun = 0;
  } else if ((**Target > Dev->MaxTarget) || (*Lun > 0)) {
    return EFI_INVALID_PARAMETER;
  } else if (**Target < Dev->MaxTarget) {
    //
    // This device interface support 256 targets only, so it's enough to
    // increment the LSB of Target, as it will never overflow.
    //
    **Target += 1;
  } else {
    return EFI_NOT_FOUND;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
EFIAPI
MptScsiGetNextTarget (
  IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL  *This,
  IN OUT UINT8                        **Target
  )
{
  MPT_SCSI_DEV  *Dev;

  Dev = MPT_SCSI_FROM_PASS_THRU (This);
  if (!IsTargetInitialized (*Target)) {
    ZeroMem (*Target, TARGET_MAX_BYTES);
  } else if (**Target > Dev->MaxTarget) {
    return EFI_INVALID_PARAMETER;
  } else if (**Target < Dev->MaxTarget) {
    //
    // This device interface support 256 targets only, so it's enough to
    // increment the LSB of Target, as it will never overflow.
    //
    **Target += 1;
  } else {
    return EFI_NOT_FOUND;
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
EFIAPI
MptScsiBuildDevicePath (
  IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL  *This,
  IN UINT8                            *Target,
  IN UINT64                           Lun,
  IN OUT EFI_DEVICE_PATH_PROTOCOL     **DevicePath
  )
{
  MPT_SCSI_DEV      *Dev;
  SCSI_DEVICE_PATH  *ScsiDevicePath;

  if (DevicePath == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // This device support 256 targets only, so it's enough to dereference
  // the LSB of Target.
  //
  Dev = MPT_SCSI_FROM_PASS_THRU (This);
  if ((*Target > Dev->MaxTarget) || (Lun > 0)) {
    return EFI_NOT_FOUND;
  }

  ScsiDevicePath = AllocateZeroPool (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              = *Target;
  ScsiDevicePath->Lun              = (UINT16)Lun;

  *DevicePath = &ScsiDevicePath->Header;
  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
EFIAPI
MptScsiGetTargetLun (
  IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL  *This,
  IN EFI_DEVICE_PATH_PROTOCOL         *DevicePath,
  OUT UINT8                           **Target,
  OUT UINT64                          *Lun
  )
{
  MPT_SCSI_DEV      *Dev;
  SCSI_DEVICE_PATH  *ScsiDevicePath;

  if ((DevicePath == NULL) ||
      (Target == NULL) || (*Target == NULL) || (Lun == NULL))
  {
    return EFI_INVALID_PARAMETER;
  }

  if ((DevicePath->Type    != MESSAGING_DEVICE_PATH) ||
      (DevicePath->SubType != MSG_SCSI_DP))
  {
    return EFI_UNSUPPORTED;
  }

  Dev            = MPT_SCSI_FROM_PASS_THRU (This);
  ScsiDevicePath = (SCSI_DEVICE_PATH *)DevicePath;
  if ((ScsiDevicePath->Pun > Dev->MaxTarget) ||
      (ScsiDevicePath->Lun > 0))
  {
    return EFI_NOT_FOUND;
  }

  ZeroMem (*Target, TARGET_MAX_BYTES);
  //
  // This device support 256 targets only, so it's enough to set the LSB
  // of Target.
  //
  **Target = (UINT8)ScsiDevicePath->Pun;
  *Lun     = ScsiDevicePath->Lun;

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
EFIAPI
MptScsiResetChannel (
  IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL  *This
  )
{
  return EFI_UNSUPPORTED;
}

STATIC
VOID
EFIAPI
MptScsiExitBoot (
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
  MPT_SCSI_DEV  *Dev;

  Dev = Context;
  DEBUG ((DEBUG_VERBOSE, "%a: Context=0x%p\n", __func__, Context));
  MptScsiReset (Dev);
}

STATIC
EFI_STATUS
EFIAPI
MptScsiResetTargetLun (
  IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL  *This,
  IN UINT8                            *Target,
  IN UINT64                           Lun
  )
{
  return EFI_UNSUPPORTED;
}

//
// Driver Binding
//

STATIC
EFI_STATUS
EFIAPI
MptScsiControllerSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS           Status;
  EFI_PCI_IO_PROTOCOL  *PciIo;
  PCI_TYPE00           Pci;

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiPciIoProtocolGuid,
                  (VOID **)&PciIo,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        0,
                        sizeof (Pci) / sizeof (UINT32),
                        &Pci
                        );
  if (EFI_ERROR (Status)) {
    goto Done;
  }

  if ((Pci.Hdr.VendorId == LSI_LOGIC_PCI_VENDOR_ID) &&
      ((Pci.Hdr.DeviceId == LSI_53C1030_PCI_DEVICE_ID) ||
       (Pci.Hdr.DeviceId == LSI_SAS1068_PCI_DEVICE_ID) ||
       (Pci.Hdr.DeviceId == LSI_SAS1068E_PCI_DEVICE_ID)))
  {
    Status = EFI_SUCCESS;
  } else {
    Status = EFI_UNSUPPORTED;
  }

Done:
  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiPciIoProtocolGuid,
         This->DriverBindingHandle,
         ControllerHandle
         );
  return Status;
}

STATIC
EFI_STATUS
EFIAPI
MptScsiControllerStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS    Status;
  MPT_SCSI_DEV  *Dev;
  UINTN         Pages;
  UINTN         BytesMapped;

  Dev = AllocateZeroPool (sizeof (*Dev));
  if (Dev == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Dev->Signature = MPT_SCSI_DEV_SIGNATURE;

  Dev->MaxTarget        = PcdGet8 (PcdMptScsiMaxTargetLimit);
  Dev->StallPerPollUsec = PcdGet32 (PcdMptScsiStallPerPollUsec);

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiPciIoProtocolGuid,
                  (VOID **)&Dev->PciIo,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto FreePool;
  }

  Status = Dev->PciIo->Attributes (
                         Dev->PciIo,
                         EfiPciIoAttributeOperationGet,
                         0,
                         &Dev->OriginalPciAttributes
                         );
  if (EFI_ERROR (Status)) {
    goto CloseProtocol;
  }

  //
  // Enable I/O Space & Bus-Mastering
  //
  Status = Dev->PciIo->Attributes (
                         Dev->PciIo,
                         EfiPciIoAttributeOperationEnable,
                         (EFI_PCI_IO_ATTRIBUTE_IO |
                          EFI_PCI_IO_ATTRIBUTE_BUS_MASTER),
                         NULL
                         );
  if (EFI_ERROR (Status)) {
    goto CloseProtocol;
  }

  //
  // Signal device supports 64-bit DMA addresses
  //
  Status = Dev->PciIo->Attributes (
                         Dev->PciIo,
                         EfiPciIoAttributeOperationEnable,
                         EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE,
                         NULL
                         );
  if (EFI_ERROR (Status)) {
    //
    // Warn user that device will only be using 32-bit DMA addresses.
    //
    // Note that this does not prevent the device/driver from working
    // and therefore we only warn and continue as usual.
    //
    DEBUG ((
      DEBUG_WARN,
      "%a: failed to enable 64-bit DMA addresses\n",
      __func__
      ));
  }

  //
  // Create buffers for data transfer
  //
  Pages  = EFI_SIZE_TO_PAGES (sizeof (*Dev->Dma));
  Status = Dev->PciIo->AllocateBuffer (
                         Dev->PciIo,
                         AllocateAnyPages,
                         EfiBootServicesData,
                         Pages,
                         (VOID **)&Dev->Dma,
                         EFI_PCI_ATTRIBUTE_MEMORY_CACHED
                         );
  if (EFI_ERROR (Status)) {
    goto RestoreAttributes;
  }

  BytesMapped = EFI_PAGES_TO_SIZE (Pages);
  Status      = Dev->PciIo->Map (
                              Dev->PciIo,
                              EfiPciIoOperationBusMasterCommonBuffer,
                              Dev->Dma,
                              &BytesMapped,
                              &Dev->DmaPhysical,
                              &Dev->DmaMapping
                              );
  if (EFI_ERROR (Status)) {
    goto FreeBuffer;
  }

  if (BytesMapped != EFI_PAGES_TO_SIZE (Pages)) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Unmap;
  }

  Status = MptScsiInit (Dev);
  if (EFI_ERROR (Status)) {
    goto Unmap;
  }

  Status = gBS->CreateEvent (
                  EVT_SIGNAL_EXIT_BOOT_SERVICES,
                  TPL_CALLBACK,
                  &MptScsiExitBoot,
                  Dev,
                  &Dev->ExitBoot
                  );
  if (EFI_ERROR (Status)) {
    goto UninitDev;
  }

  //
  // Host adapter channel, doesn't exist
  //
  Dev->PassThruMode.AdapterId  = MAX_UINT32;
  Dev->PassThruMode.Attributes =
    EFI_EXT_SCSI_PASS_THRU_ATTRIBUTES_PHYSICAL |
    EFI_EXT_SCSI_PASS_THRU_ATTRIBUTES_LOGICAL;

  Dev->PassThru.Mode             = &Dev->PassThruMode;
  Dev->PassThru.PassThru         = &MptScsiPassThru;
  Dev->PassThru.GetNextTargetLun = &MptScsiGetNextTargetLun;
  Dev->PassThru.BuildDevicePath  = &MptScsiBuildDevicePath;
  Dev->PassThru.GetTargetLun     = &MptScsiGetTargetLun;
  Dev->PassThru.ResetChannel     = &MptScsiResetChannel;
  Dev->PassThru.ResetTargetLun   = &MptScsiResetTargetLun;
  Dev->PassThru.GetNextTarget    = &MptScsiGetNextTarget;

  Status = gBS->InstallProtocolInterface (
                  &ControllerHandle,
                  &gEfiExtScsiPassThruProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &Dev->PassThru
                  );
  if (EFI_ERROR (Status)) {
    goto CloseExitBoot;
  }

  return EFI_SUCCESS;

CloseExitBoot:
  gBS->CloseEvent (Dev->ExitBoot);

UninitDev:
  MptScsiReset (Dev);

Unmap:
  Dev->PciIo->Unmap (
                Dev->PciIo,
                Dev->DmaMapping
                );

FreeBuffer:
  Dev->PciIo->FreeBuffer (
                Dev->PciIo,
                Pages,
                Dev->Dma
                );

RestoreAttributes:
  Dev->PciIo->Attributes (
                Dev->PciIo,
                EfiPciIoAttributeOperationSet,
                Dev->OriginalPciAttributes,
                NULL
                );

CloseProtocol:
  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiPciIoProtocolGuid,
         This->DriverBindingHandle,
         ControllerHandle
         );

FreePool:
  FreePool (Dev);

  return Status;
}

STATIC
EFI_STATUS
EFIAPI
MptScsiControllerStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                  ControllerHandle,
  IN  UINTN                       NumberOfChildren,
  IN  EFI_HANDLE                  *ChildHandleBuffer
  )
{
  EFI_STATUS                       Status;
  EFI_EXT_SCSI_PASS_THRU_PROTOCOL  *PassThru;
  MPT_SCSI_DEV                     *Dev;

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiExtScsiPassThruProtocolGuid,
                  (VOID **)&PassThru,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL // Lookup only
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Dev = MPT_SCSI_FROM_PASS_THRU (PassThru);

  Status = gBS->UninstallProtocolInterface (
                  ControllerHandle,
                  &gEfiExtScsiPassThruProtocolGuid,
                  &Dev->PassThru
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  gBS->CloseEvent (Dev->ExitBoot);

  MptScsiReset (Dev);

  Dev->PciIo->Unmap (
                Dev->PciIo,
                Dev->DmaMapping
                );

  Dev->PciIo->FreeBuffer (
                Dev->PciIo,
                EFI_SIZE_TO_PAGES (sizeof (*Dev->Dma)),
                Dev->Dma
                );

  Dev->PciIo->Attributes (
                Dev->PciIo,
                EfiPciIoAttributeOperationSet,
                Dev->OriginalPciAttributes,
                NULL
                );

  gBS->CloseProtocol (
         ControllerHandle,
         &gEfiPciIoProtocolGuid,
         This->DriverBindingHandle,
         ControllerHandle
         );

  FreePool (Dev);

  return Status;
}

STATIC
EFI_DRIVER_BINDING_PROTOCOL  mMptScsiDriverBinding = {
  &MptScsiControllerSupported,
  &MptScsiControllerStart,
  &MptScsiControllerStop,
  MPT_SCSI_BINDING_VERSION,
  NULL, // ImageHandle, filled by EfiLibInstallDriverBindingComponentName2
  NULL, // DriverBindingHandle, filled as well
};

//
// Component Name
//

STATIC
EFI_UNICODE_STRING_TABLE  mDriverNameTable[] = {
  { "eng;en", L"LSI Fusion MPT SCSI Driver" },
  { NULL,     NULL                          }
};

STATIC
EFI_COMPONENT_NAME_PROTOCOL  mComponentName;

EFI_STATUS
EFIAPI
MptScsiGetDriverName (
  IN  EFI_COMPONENT_NAME_PROTOCOL  *This,
  IN  CHAR8                        *Language,
  OUT CHAR16                       **DriverName
  )
{
  return LookupUnicodeString2 (
           Language,
           This->SupportedLanguages,
           mDriverNameTable,
           DriverName,
           (BOOLEAN)(This == &mComponentName) // Iso639Language
           );
}

EFI_STATUS
EFIAPI
MptScsiGetDeviceName (
  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  mComponentName = {
  &MptScsiGetDriverName,
  &MptScsiGetDeviceName,
  "eng" // SupportedLanguages, ISO 639-2 language codes
};

STATIC
EFI_COMPONENT_NAME2_PROTOCOL  mComponentName2 = {
  (EFI_COMPONENT_NAME2_GET_DRIVER_NAME)&MptScsiGetDriverName,
  (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME)&MptScsiGetDeviceName,
  "en" // SupportedLanguages, RFC 4646 language codes
};

//
// Entry Point
//

EFI_STATUS
EFIAPI
MptScsiEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  return EfiLibInstallDriverBindingComponentName2 (
           ImageHandle,
           SystemTable,
           &mMptScsiDriverBinding,
           ImageHandle, // The handle to install onto
           &mComponentName,
           &mComponentName2
           );
}
