blob: ed5fc3bfdd9d1ec98105fca11d665d54dac9529c [file] [log] [blame]
/** @file
This driver produces Extended SCSI Pass Thru Protocol instances for
LSI 53C895A SCSI devices.
Copyright (C) 2020, SUSE LLC.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <IndustryStandard/LsiScsi.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>
#include "LsiScsi.h"
STATIC
EFI_STATUS
Out8 (
IN LSI_SCSI_DEV *Dev,
IN UINT32 Addr,
IN UINT8 Data
)
{
return Dev->PciIo->Io.Write (
Dev->PciIo,
EfiPciIoWidthUint8,
PCI_BAR_IDX0,
Addr,
1,
&Data
);
}
STATIC
EFI_STATUS
Out32 (
IN LSI_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
In8 (
IN LSI_SCSI_DEV *Dev,
IN UINT32 Addr,
OUT UINT8 *Data
)
{
return Dev->PciIo->Io.Read (
Dev->PciIo,
EfiPciIoWidthUint8,
PCI_BAR_IDX0,
Addr,
1,
Data
);
}
STATIC
EFI_STATUS
In32 (
IN LSI_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
LsiScsiReset (
IN LSI_SCSI_DEV *Dev
)
{
return Out8 (Dev, LSI_REG_ISTAT0, LSI_ISTAT0_SRST);
}
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;
}
/**
Check the request packet from the Extended SCSI Pass Thru Protocol. The
request packet is modified, to be forwarded outwards by LsiScsiPassThru(),
if invalid or unsupported parameters are detected.
@param[in] Dev The LSI 53C895A SCSI device the packet targets.
@param[in] Target The SCSI target controlled by the LSI 53C895A SCSI
device.
@param[in] Lun The Logical Unit Number under the SCSI target.
@param[in out] Packet The Extended SCSI Pass Thru Protocol packet.
@retval EFI_SUCCESS The Extended SCSI Pass Thru Protocol packet was valid.
@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
LsiScsiCheckRequest (
IN LSI_SCSI_DEV *Dev,
IN UINT8 Target,
IN UINT64 Lun,
IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet
)
{
if (Target > Dev->MaxTarget || Lun > Dev->MaxLun ||
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->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_BIDIRECTIONAL ||
(Packet->InTransferLength > 0 && Packet->OutTransferLength > 0) ||
Packet->CdbLength > sizeof Dev->Dma->Cdb) {
return EFI_UNSUPPORTED;
}
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);
}
return EFI_SUCCESS;
}
/**
Interpret the request packet from the Extended SCSI Pass Thru Protocol and
compose the script to submit the command and data to the controller.
@param[in] Dev The LSI 53C895A SCSI device the packet targets.
@param[in] Target The SCSI target controlled by the LSI 53C895A SCSI
device.
@param[in] Lun The Logical Unit Number under the SCSI target.
@param[in out] Packet The Extended SCSI Pass Thru Protocol packet.
@retval EFI_SUCCESS The Extended SCSI Pass Thru Protocol packet was valid.
@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
LsiScsiProcessRequest (
IN LSI_SCSI_DEV *Dev,
IN UINT8 Target,
IN UINT64 Lun,
IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet
)
{
EFI_STATUS Status;
UINT32 *Script;
UINT8 *Cdb;
UINT8 *MsgOut;
UINT8 *MsgIn;
UINT8 *ScsiStatus;
UINT8 *Data;
UINT8 DStat;
UINT8 SIst0;
UINT8 SIst1;
UINT32 Csbc;
UINT32 CsbcBase;
UINT32 Transferred;
Script = Dev->Dma->Script;
Cdb = Dev->Dma->Cdb;
Data = Dev->Dma->Data;
MsgIn = Dev->Dma->MsgIn;
MsgOut = &Dev->Dma->MsgOut;
ScsiStatus = &Dev->Dma->Status;
*ScsiStatus = 0xFF;
DStat = 0;
SIst0 = 0;
SIst1 = 0;
SetMem (Cdb, sizeof Dev->Dma->Cdb, 0x00);
CopyMem (Cdb, Packet->Cdb, Packet->CdbLength);
//
// Fetch the first Cumulative SCSI Byte Count (CSBC).
//
// CSBC is a cumulative counter of the actual number of bytes that have been
// transferred across the SCSI bus during data phases, i.e. it will not
// count bytes sent in command, status, message in and out phases.
//
Status = In32 (Dev, LSI_REG_CSBC, &CsbcBase);
if (EFI_ERROR (Status)) {
goto Error;
}
//
// Clean up the DMA buffer for the script.
//
SetMem (Script, sizeof Dev->Dma->Script, 0x00);
//
// Compose the script to transfer data between the host and the device.
//
// References:
// * LSI53C895A PCI to Ultra2 SCSI Controller Version 2.2
// - Chapter 5 SCSI SCRIPT Instruction Set
// * SEABIOS lsi-scsi driver
//
// All instructions used here consist of 2 32bit words. The first word
// contains the command to execute. The second word is loaded into the
// DMA SCRIPTS Pointer Save (DSPS) register as either the DMA address
// for data transmission or the address/offset for the jump command.
// Some commands, such as the selection of the target, don't need to
// transfer data through DMA or jump to another instruction, then DSPS
// has to be zero.
//
// There are 3 major parts in this script. The first part (1~3) contains
// the instructions to select target and LUN and send the SCSI command
// from the request packet. The second part (4~7) is to handle the
// potential disconnection and prepare for the data transmission. The
// instructions in the third part (8~10) transmit the given data and
// collect the result. Instruction 11 raises the interrupt and marks the
// end of the script.
//
//
// 1. Select target.
//
*Script++ = LSI_INS_TYPE_IO | LSI_INS_IO_OPC_SEL | (UINT32)Target << 16;
*Script++ = 0x00000000;
//
// 2. Select LUN.
//
*MsgOut = 0x80 | (UINT8) Lun; // 0x80: Identify bit
*Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_MSG_OUT |
(UINT32)sizeof Dev->Dma->MsgOut;
*Script++ = LSI_SCSI_DMA_ADDR (Dev, MsgOut);
//
// 3. Send the SCSI Command.
//
*Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_CMD |
(UINT32)sizeof Dev->Dma->Cdb;
*Script++ = LSI_SCSI_DMA_ADDR (Dev, Cdb);
//
// 4. Check whether the current SCSI phase is "Message In" or not
// and jump to 7 if it is.
// Note: LSI_INS_TC_RA stands for "Relative Address Mode", so the
// offset 0x18 in the second word means jumping forward
// 3 (0x18/8) instructions.
//
*Script++ = LSI_INS_TYPE_TC | LSI_INS_TC_OPC_JMP |
LSI_INS_TC_SCSIP_MSG_IN | LSI_INS_TC_RA |
LSI_INS_TC_CP;
*Script++ = 0x00000018;
//
// 5. Read "Message" from the initiator to trigger reselect.
//
*Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_MSG_IN |
(UINT32)sizeof Dev->Dma->MsgIn;
*Script++ = LSI_SCSI_DMA_ADDR (Dev, MsgIn);
//
// 6. Wait reselect.
//
*Script++ = LSI_INS_TYPE_IO | LSI_INS_IO_OPC_WAIT_RESEL;
*Script++ = 0x00000000;
//
// 7. Read "Message" from the initiator again
//
*Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_MSG_IN |
(UINT32)sizeof Dev->Dma->MsgIn;
*Script++ = LSI_SCSI_DMA_ADDR (Dev, MsgIn);
//
// 8. Set the DMA command for the read/write operations.
// Note: Some requests, e.g. "TEST UNIT READY", do not come with
// allocated InDataBuffer or OutDataBuffer. We skip the DMA
// data command for those requests or this script would fail
// with LSI_SIST0_SGE due to the zero data length.
//
// LsiScsiCheckRequest() prevents both integer overflows in the command
// opcodes, and buffer overflows.
//
if (Packet->InTransferLength > 0) {
ASSERT (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ);
ASSERT (Packet->InTransferLength <= sizeof Dev->Dma->Data);
*Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_DAT_IN |
Packet->InTransferLength;
*Script++ = LSI_SCSI_DMA_ADDR (Dev, Data);
} else if (Packet->OutTransferLength > 0) {
ASSERT (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_WRITE);
ASSERT (Packet->OutTransferLength <= sizeof Dev->Dma->Data);
CopyMem (Data, Packet->OutDataBuffer, Packet->OutTransferLength);
*Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_DAT_OUT |
Packet->OutTransferLength;
*Script++ = LSI_SCSI_DMA_ADDR (Dev, Data);
}
//
// 9. Get the SCSI status.
//
*Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_STAT |
(UINT32)sizeof Dev->Dma->Status;
*Script++ = LSI_SCSI_DMA_ADDR (Dev, Status);
//
// 10. Get the SCSI message.
//
*Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_MSG_IN |
(UINT32)sizeof Dev->Dma->MsgIn;
*Script++ = LSI_SCSI_DMA_ADDR (Dev, MsgIn);
//
// 11. Raise the interrupt to end the script.
//
*Script++ = LSI_INS_TYPE_TC | LSI_INS_TC_OPC_INT |
LSI_INS_TC_SCSIP_DAT_OUT | LSI_INS_TC_JMP;
*Script++ = 0x00000000;
//
// Make sure the size of the script doesn't exceed the buffer.
//
ASSERT (Script <= Dev->Dma->Script + ARRAY_SIZE (Dev->Dma->Script));
//
// The controller starts to execute the script once the DMA Script
// Pointer (DSP) register is set.
//
Status = Out32 (Dev, LSI_REG_DSP, LSI_SCSI_DMA_ADDR (Dev, Script));
if (EFI_ERROR (Status)) {
goto Error;
}
//
// Poll the device registers (DSTAT, SIST0, and SIST1) until the SIR
// bit sets.
//
for(;;) {
Status = In8 (Dev, LSI_REG_DSTAT, &DStat);
if (EFI_ERROR (Status)) {
goto Error;
}
Status = In8 (Dev, LSI_REG_SIST0, &SIst0);
if (EFI_ERROR (Status)) {
goto Error;
}
Status = In8 (Dev, LSI_REG_SIST1, &SIst1);
if (EFI_ERROR (Status)) {
goto Error;
}
if (SIst0 != 0 || SIst1 != 0) {
goto Error;
}
//
// Check the SIR (SCRIPTS Interrupt Instruction Received) bit.
//
if (DStat & LSI_DSTAT_SIR) {
break;
}
gBS->Stall (Dev->StallPerPollUsec);
}
//
// Check if everything is good.
// SCSI Message Code 0x00: COMMAND COMPLETE
// SCSI Status Code 0x00: Good
//
if (MsgIn[0] != 0 || *ScsiStatus != 0) {
goto Error;
}
//
// Fetch CSBC again to calculate the transferred bytes and update
// InTransferLength/OutTransferLength.
//
// Note: The number of transferred bytes is bounded by
// "sizeof Dev->Dma->Data", so it's safe to subtract CsbcBase
// from Csbc. If the CSBC register wraps around, the correct
// difference is ensured by the standard C modular arithmetic.
//
Status = In32 (Dev, LSI_REG_CSBC, &Csbc);
if (EFI_ERROR (Status)) {
goto Error;
}
Transferred = Csbc - CsbcBase;
if (Packet->InTransferLength > 0) {
if (Transferred <= Packet->InTransferLength) {
Packet->InTransferLength = Transferred;
} else {
goto Error;
}
} else if (Packet->OutTransferLength > 0) {
if (Transferred <= Packet->OutTransferLength) {
Packet->OutTransferLength = Transferred;
} else {
goto Error;
}
}
//
// Copy Data to InDataBuffer if necessary.
//
if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {
CopyMem (Packet->InDataBuffer, Data, Packet->InTransferLength);
}
//
// Always set SenseDataLength to 0.
// The instructions of LSI53C895A don't reply sense data. Instead, it
// relies on the SCSI command, "REQUEST SENSE", to get sense data. We set
// SenseDataLength to 0 to notify ScsiDiskDxe that there is no sense data
// written even if this request is processed successfully, so that It will
// issue "REQUEST SENSE" later to retrieve sense data.
//
Packet->SenseDataLength = 0;
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK;
Packet->TargetStatus = EFI_EXT_SCSI_STATUS_TARGET_GOOD;
return EFI_SUCCESS;
Error:
DEBUG ((DEBUG_VERBOSE, "%a: dstat: %02X, sist0: %02X, sist1: %02X\n",
__FUNCTION__, DStat, SIst0, SIst1));
//
// Update the request packet to reflect the status.
//
if (*ScsiStatus != 0xFF) {
Packet->TargetStatus = *ScsiStatus;
} else {
Packet->TargetStatus = EFI_EXT_SCSI_STATUS_TARGET_TASK_ABORTED;
}
if (SIst0 & LSI_SIST0_PAR) {
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PARITY_ERROR;
} else if (SIst0 & LSI_SIST0_RST) {
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_RESET;
} else if (SIst0 & LSI_SIST0_UDC) {
//
// The target device is disconnected unexpectedly. According to UEFI spec,
// this is TIMEOUT_COMMAND.
//
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT_COMMAND;
} else if (SIst0 & LSI_SIST0_SGE) {
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN;
} else if (SIst1 & LSI_SIST1_HTH) {
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT;
} else if (SIst1 & LSI_SIST1_GEN) {
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT;
} else if (SIst1 & LSI_SIST1_STO) {
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_SELECTION_TIMEOUT;
} else {
Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;
}
//
// SenseData may be used to inspect the error. Since we don't set sense data,
// SenseDataLength has to be 0.
//
Packet->SenseDataLength = 0;
return EFI_DEVICE_ERROR;
}
//
// The next seven functions implement EFI_EXT_SCSI_PASS_THRU_PROTOCOL
// for the LSI 53C895A SCSI Controller. 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
LsiScsiPassThru (
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;
LSI_SCSI_DEV *Dev;
Dev = LSI_SCSI_FROM_PASS_THRU (This);
Status = LsiScsiCheckRequest (Dev, *Target, Lun, Packet);
if (EFI_ERROR (Status)) {
return Status;
}
return LsiScsiProcessRequest (Dev, *Target, Lun, Packet);
}
EFI_STATUS
EFIAPI
LsiScsiGetNextTargetLun (
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This,
IN OUT UINT8 **TargetPointer,
IN OUT UINT64 *Lun
)
{
LSI_SCSI_DEV *Dev;
UINTN Idx;
UINT8 *Target;
UINT16 LastTarget;
//
// 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;
}
CopyMem (&LastTarget, Target, sizeof LastTarget);
//
// increment (target, LUN) pair if valid on input
//
Dev = LSI_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
LsiScsiBuildDevicePath (
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This,
IN UINT8 *Target,
IN UINT64 Lun,
IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath
)
{
UINT16 TargetValue;
LSI_SCSI_DEV *Dev;
SCSI_DEVICE_PATH *ScsiDevicePath;
if (DevicePath == NULL) {
return EFI_INVALID_PARAMETER;
}
CopyMem (&TargetValue, Target, sizeof TargetValue);
Dev = LSI_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
LsiScsiGetTargetLun (
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This,
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
OUT UINT8 **TargetPointer,
OUT UINT64 *Lun
)
{
SCSI_DEVICE_PATH *ScsiDevicePath;
LSI_SCSI_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 = LSI_SCSI_FROM_PASS_THRU (This);
if (ScsiDevicePath->Pun > Dev->MaxTarget ||
ScsiDevicePath->Lun > Dev->MaxLun) {
return EFI_NOT_FOUND;
}
Target = *TargetPointer;
ZeroMem (Target, TARGET_MAX_BYTES);
CopyMem (Target, &ScsiDevicePath->Pun, sizeof ScsiDevicePath->Pun);
*Lun = ScsiDevicePath->Lun;
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
LsiScsiResetChannel (
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This
)
{
return EFI_UNSUPPORTED;
}
EFI_STATUS
EFIAPI
LsiScsiResetTargetLun (
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This,
IN UINT8 *Target,
IN UINT64 Lun
)
{
return EFI_UNSUPPORTED;
}
EFI_STATUS
EFIAPI
LsiScsiGetNextTarget (
IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *This,
IN OUT UINT8 **TargetPointer
)
{
LSI_SCSI_DEV *Dev;
UINTN Idx;
UINT8 *Target;
UINT16 LastTarget;
//
// 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;
}
CopyMem (&LastTarget, Target, sizeof LastTarget);
//
// increment target if valid on input
//
Dev = LSI_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
VOID
EFIAPI
LsiScsiExitBoot (
IN EFI_EVENT Event,
IN VOID *Context
)
{
LSI_SCSI_DEV *Dev;
Dev = Context;
DEBUG ((DEBUG_VERBOSE, "%a: Context=0x%p\n", __FUNCTION__, Context));
LsiScsiReset (Dev);
}
//
// 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
//
EFI_STATUS
EFIAPI
LsiScsiControllerSupported (
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_53C895A_PCI_DEVICE_ID) {
Status = EFI_SUCCESS;
} else {
Status = EFI_UNSUPPORTED;
}
Done:
gBS->CloseProtocol (
ControllerHandle,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
return Status;
}
EFI_STATUS
EFIAPI
LsiScsiControllerStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
EFI_STATUS Status;
LSI_SCSI_DEV *Dev;
UINTN Pages;
UINTN BytesMapped;
Dev = AllocateZeroPool (sizeof (*Dev));
if (Dev == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Dev->Signature = LSI_SCSI_DEV_SIGNATURE;
STATIC_ASSERT (
FixedPcdGet8 (PcdLsiScsiMaxTargetLimit) < 8,
"LSI 53C895A supports targets [0..7]"
);
STATIC_ASSERT (
FixedPcdGet8 (PcdLsiScsiMaxLunLimit) < 128,
"LSI 53C895A supports LUNs [0..127]"
);
Dev->MaxTarget = PcdGet8 (PcdLsiScsiMaxTargetLimit);
Dev->MaxLun = PcdGet8 (PcdLsiScsiMaxLunLimit);
Dev->StallPerPollUsec = PcdGet32 (PcdLsiScsiStallPerPollUsec);
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->OrigPciAttrs
);
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;
}
//
// 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 = LsiScsiReset (Dev);
if (EFI_ERROR (Status)) {
goto Unmap;
}
Status = gBS->CreateEvent (
EVT_SIGNAL_EXIT_BOOT_SERVICES,
TPL_CALLBACK,
&LsiScsiExitBoot,
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 = &LsiScsiPassThru;
Dev->PassThru.GetNextTargetLun = &LsiScsiGetNextTargetLun;
Dev->PassThru.BuildDevicePath = &LsiScsiBuildDevicePath;
Dev->PassThru.GetTargetLun = &LsiScsiGetTargetLun;
Dev->PassThru.ResetChannel = &LsiScsiResetChannel;
Dev->PassThru.ResetTargetLun = &LsiScsiResetTargetLun;
Dev->PassThru.GetNextTarget = &LsiScsiGetNextTarget;
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:
LsiScsiReset (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->OrigPciAttrs,
NULL
);
CloseProtocol:
gBS->CloseProtocol (
ControllerHandle,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
FreePool:
FreePool (Dev);
return Status;
}
EFI_STATUS
EFIAPI
LsiScsiControllerStop (
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;
LSI_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 = LSI_SCSI_FROM_PASS_THRU (PassThru);
Status = gBS->UninstallProtocolInterface (
ControllerHandle,
&gEfiExtScsiPassThruProtocolGuid,
&Dev->PassThru
);
if (EFI_ERROR (Status)) {
return Status;
}
gBS->CloseEvent (Dev->ExitBoot);
LsiScsiReset (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->OrigPciAttrs,
NULL
);
gBS->CloseProtocol (
ControllerHandle,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
ControllerHandle
);
FreePool (Dev);
return Status;
}
//
// 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 = {
&LsiScsiControllerSupported,
&LsiScsiControllerStart,
&LsiScsiControllerStop,
0x10, // Version, must be in [0x10 .. 0xFFFFFFEF] for IHV-developed drivers
NULL, // ImageHandle, to be overwritten by
// EfiLibInstallDriverBindingComponentName2() in LsiScsiEntryPoint()
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 ("LSI 53C895A SCSI Controller") 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"LSI 53C895A SCSI Controller Driver" },
{ NULL, NULL }
};
STATIC
EFI_COMPONENT_NAME_PROTOCOL gComponentName;
EFI_STATUS
EFIAPI
LsiScsiGetDriverName (
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
LsiScsiGetDeviceName (
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 = {
&LsiScsiGetDriverName,
&LsiScsiGetDeviceName,
"eng" // SupportedLanguages, ISO 639-2 language codes
};
STATIC
EFI_COMPONENT_NAME2_PROTOCOL gComponentName2 = {
(EFI_COMPONENT_NAME2_GET_DRIVER_NAME) &LsiScsiGetDriverName,
(EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME) &LsiScsiGetDeviceName,
"en" // SupportedLanguages, RFC 4646 language codes
};
//
// Entry point of this driver
//
EFI_STATUS
EFIAPI
LsiScsiEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
return EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gDriverBinding,
ImageHandle, // The handle to install onto
&gComponentName,
&gComponentName2
);
}