audk/OvmfPkg/LsiScsiDxe/LsiScsi.c

1211 lines
34 KiB
C
Raw Normal View History

/** @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
);
}
OvmfPkg/LsiScsiDxe: Process the SCSI Request Packet This is the second part of LsiScsiPassThru(). LsiScsiProcessRequest() is added to translate the SCSI Request Packet into the LSI 53C895A commands. This function utilizes the so-called Script buffer to transmit a series of commands to the chip and then polls the DMA Status (DSTAT) register until the Scripts Interrupt Instruction Received (SIR) bit sets. Once the script is done, the SCSI Request Packet will be modified to reflect the result of the script. The Cumulative SCSI Byte Count (CSBC) register is fetched before and after the script to calculate the transferred bytes and update InTransferLength/OutTransferLength if necessary. v3: - Set DStat, SIst0, and SIst1 to 0 before using them - Amend the if statements for the DMA data instruction and add the assertions for the data direction - Also set SenseDataLength to 0 on the error path - Fix typos and amend comments - Amend the error handling of the calculation of transferred bytes v2: - Use the BITx macros for the most of LSI_* constants - Fix a typo: contorller => controller - Add SeaBIOS lsi-scsi driver as one of the references of the script - Cast the result of sizeof to UINT32 for the instructions of the script - Drop the backslashes - Replace LSI_SCSI_DMA_ADDR_LOW with LSI_SCSI_DMA_ADDR since we already removed DUAL_ADDRESS_CYCLE - Add more comments for the script - Fix the check of the script size at the end of the script - Always set SenseDataLength to 0 to avoid the caller to access SenseData - Improve the error handling in LsiScsiProcessRequest() Cc: Jordan Justen <jordan.l.justen@intel.com> Cc: Laszlo Ersek <lersek@redhat.com> Cc: Ard Biesheuvel <ard.biesheuvel@arm.com> Signed-off-by: Gary Lin <glin@suse.com> Message-Id: <20200717061130.8881-11-glin@suse.com> Reviewed-by: Laszlo Ersek <lersek@redhat.com>
2020-07-17 08:11:29 +02:00
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;
}
OvmfPkg/LsiScsiDxe: Process the SCSI Request Packet This is the second part of LsiScsiPassThru(). LsiScsiProcessRequest() is added to translate the SCSI Request Packet into the LSI 53C895A commands. This function utilizes the so-called Script buffer to transmit a series of commands to the chip and then polls the DMA Status (DSTAT) register until the Scripts Interrupt Instruction Received (SIR) bit sets. Once the script is done, the SCSI Request Packet will be modified to reflect the result of the script. The Cumulative SCSI Byte Count (CSBC) register is fetched before and after the script to calculate the transferred bytes and update InTransferLength/OutTransferLength if necessary. v3: - Set DStat, SIst0, and SIst1 to 0 before using them - Amend the if statements for the DMA data instruction and add the assertions for the data direction - Also set SenseDataLength to 0 on the error path - Fix typos and amend comments - Amend the error handling of the calculation of transferred bytes v2: - Use the BITx macros for the most of LSI_* constants - Fix a typo: contorller => controller - Add SeaBIOS lsi-scsi driver as one of the references of the script - Cast the result of sizeof to UINT32 for the instructions of the script - Drop the backslashes - Replace LSI_SCSI_DMA_ADDR_LOW with LSI_SCSI_DMA_ADDR since we already removed DUAL_ADDRESS_CYCLE - Add more comments for the script - Fix the check of the script size at the end of the script - Always set SenseDataLength to 0 to avoid the caller to access SenseData - Improve the error handling in LsiScsiProcessRequest() Cc: Jordan Justen <jordan.l.justen@intel.com> Cc: Laszlo Ersek <lersek@redhat.com> Cc: Ard Biesheuvel <ard.biesheuvel@arm.com> Signed-off-by: Gary Lin <glin@suse.com> Message-Id: <20200717061130.8881-11-glin@suse.com> Reviewed-by: Laszlo Ersek <lersek@redhat.com>
2020-07-17 08:11:29 +02:00
/**
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;
}
OvmfPkg/LsiScsiDxe: Process the SCSI Request Packet This is the second part of LsiScsiPassThru(). LsiScsiProcessRequest() is added to translate the SCSI Request Packet into the LSI 53C895A commands. This function utilizes the so-called Script buffer to transmit a series of commands to the chip and then polls the DMA Status (DSTAT) register until the Scripts Interrupt Instruction Received (SIR) bit sets. Once the script is done, the SCSI Request Packet will be modified to reflect the result of the script. The Cumulative SCSI Byte Count (CSBC) register is fetched before and after the script to calculate the transferred bytes and update InTransferLength/OutTransferLength if necessary. v3: - Set DStat, SIst0, and SIst1 to 0 before using them - Amend the if statements for the DMA data instruction and add the assertions for the data direction - Also set SenseDataLength to 0 on the error path - Fix typos and amend comments - Amend the error handling of the calculation of transferred bytes v2: - Use the BITx macros for the most of LSI_* constants - Fix a typo: contorller => controller - Add SeaBIOS lsi-scsi driver as one of the references of the script - Cast the result of sizeof to UINT32 for the instructions of the script - Drop the backslashes - Replace LSI_SCSI_DMA_ADDR_LOW with LSI_SCSI_DMA_ADDR since we already removed DUAL_ADDRESS_CYCLE - Add more comments for the script - Fix the check of the script size at the end of the script - Always set SenseDataLength to 0 to avoid the caller to access SenseData - Improve the error handling in LsiScsiProcessRequest() Cc: Jordan Justen <jordan.l.justen@intel.com> Cc: Laszlo Ersek <lersek@redhat.com> Cc: Ard Biesheuvel <ard.biesheuvel@arm.com> Signed-off-by: Gary Lin <glin@suse.com> Message-Id: <20200717061130.8881-11-glin@suse.com> Reviewed-by: Laszlo Ersek <lersek@redhat.com>
2020-07-17 08:11:29 +02:00
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);
OvmfPkg/LsiScsiDxe: Process the SCSI Request Packet This is the second part of LsiScsiPassThru(). LsiScsiProcessRequest() is added to translate the SCSI Request Packet into the LSI 53C895A commands. This function utilizes the so-called Script buffer to transmit a series of commands to the chip and then polls the DMA Status (DSTAT) register until the Scripts Interrupt Instruction Received (SIR) bit sets. Once the script is done, the SCSI Request Packet will be modified to reflect the result of the script. The Cumulative SCSI Byte Count (CSBC) register is fetched before and after the script to calculate the transferred bytes and update InTransferLength/OutTransferLength if necessary. v3: - Set DStat, SIst0, and SIst1 to 0 before using them - Amend the if statements for the DMA data instruction and add the assertions for the data direction - Also set SenseDataLength to 0 on the error path - Fix typos and amend comments - Amend the error handling of the calculation of transferred bytes v2: - Use the BITx macros for the most of LSI_* constants - Fix a typo: contorller => controller - Add SeaBIOS lsi-scsi driver as one of the references of the script - Cast the result of sizeof to UINT32 for the instructions of the script - Drop the backslashes - Replace LSI_SCSI_DMA_ADDR_LOW with LSI_SCSI_DMA_ADDR since we already removed DUAL_ADDRESS_CYCLE - Add more comments for the script - Fix the check of the script size at the end of the script - Always set SenseDataLength to 0 to avoid the caller to access SenseData - Improve the error handling in LsiScsiProcessRequest() Cc: Jordan Justen <jordan.l.justen@intel.com> Cc: Laszlo Ersek <lersek@redhat.com> Cc: Ard Biesheuvel <ard.biesheuvel@arm.com> Signed-off-by: Gary Lin <glin@suse.com> Message-Id: <20200717061130.8881-11-glin@suse.com> Reviewed-by: Laszlo Ersek <lersek@redhat.com>
2020-07-17 08:11:29 +02:00
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
);
}