diff --git a/OvmfPkg/Include/IndustryStandard/LsiScsi.h b/OvmfPkg/Include/IndustryStandard/LsiScsi.h index 185e553c8e..01d75323cd 100644 --- a/OvmfPkg/Include/IndustryStandard/LsiScsi.h +++ b/OvmfPkg/Include/IndustryStandard/LsiScsi.h @@ -25,6 +25,19 @@ #define LSI_REG_DSP 0x2C #define LSI_REG_SIST0 0x42 #define LSI_REG_SIST1 0x43 +#define LSI_REG_CSBC 0xDC + +// +// The status bits for DMA Status (DSTAT) +// +#define LSI_DSTAT_IID BIT0 +#define LSI_DSTAT_R BIT1 +#define LSI_DSTAT_SIR BIT2 +#define LSI_DSTAT_SSI BIT3 +#define LSI_DSTAT_ABRT BIT4 +#define LSI_DSTAT_BF BIT5 +#define LSI_DSTAT_MDPE BIT6 +#define LSI_DSTAT_DFE BIT7 // // The status bits for Interrupt Status Zero (ISTAT0) @@ -38,4 +51,55 @@ #define LSI_ISTAT0_SRST BIT6 #define LSI_ISTAT0_ABRT BIT7 +// +// The status bits for SCSI Interrupt Status Zero (SIST0) +// +#define LSI_SIST0_PAR BIT0 +#define LSI_SIST0_RST BIT1 +#define LSI_SIST0_UDC BIT2 +#define LSI_SIST0_SGE BIT3 +#define LSI_SIST0_RSL BIT4 +#define LSI_SIST0_SEL BIT5 +#define LSI_SIST0_CMP BIT6 +#define LSI_SIST0_MA BIT7 + +// +// The status bits for SCSI Interrupt Status One (SIST1) +// +#define LSI_SIST1_HTH BIT0 +#define LSI_SIST1_GEN BIT1 +#define LSI_SIST1_STO BIT2 +#define LSI_SIST1_R3 BIT3 +#define LSI_SIST1_SBMC BIT4 +#define LSI_SIST1_R5 BIT5 +#define LSI_SIST1_R6 BIT6 +#define LSI_SIST1_R7 BIT7 + +// +// LSI 53C895A Script Instructions +// +#define LSI_INS_TYPE_BLK 0x00000000 +#define LSI_INS_TYPE_IO BIT30 +#define LSI_INS_TYPE_TC BIT31 + +#define LSI_INS_BLK_SCSIP_DAT_OUT 0x00000000 +#define LSI_INS_BLK_SCSIP_DAT_IN BIT24 +#define LSI_INS_BLK_SCSIP_CMD BIT25 +#define LSI_INS_BLK_SCSIP_STAT (BIT24 | BIT25) +#define LSI_INS_BLK_SCSIP_MSG_OUT (BIT25 | BIT26) +#define LSI_INS_BLK_SCSIP_MSG_IN (BIT24 | BIT25 | BIT26) + +#define LSI_INS_IO_OPC_SEL 0x00000000 +#define LSI_INS_IO_OPC_WAIT_RESEL BIT28 + +#define LSI_INS_TC_CP BIT17 +#define LSI_INS_TC_JMP BIT19 +#define LSI_INS_TC_RA BIT23 + +#define LSI_INS_TC_OPC_JMP 0x00000000 +#define LSI_INS_TC_OPC_INT (BIT27 | BIT28) + +#define LSI_INS_TC_SCSIP_DAT_OUT 0x00000000 +#define LSI_INS_TC_SCSIP_MSG_IN (BIT24 | BIT25 | BIT26) + #endif // _LSI_SCSI_H_ diff --git a/OvmfPkg/LsiScsiDxe/LsiScsi.c b/OvmfPkg/LsiScsiDxe/LsiScsi.c index 9859632e02..ed5fc3bfdd 100644 --- a/OvmfPkg/LsiScsiDxe/LsiScsi.c +++ b/OvmfPkg/LsiScsiDxe/LsiScsi.c @@ -43,6 +43,60 @@ Out8 ( ); } +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 ( @@ -141,6 +195,357 @@ LsiScsiCheckRequest ( 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, @@ -168,7 +573,7 @@ LsiScsiPassThru ( return Status; } - return EFI_UNSUPPORTED; + return LsiScsiProcessRequest (Dev, *Target, Lun, Packet); } EFI_STATUS @@ -474,6 +879,7 @@ LsiScsiControllerStart ( ); Dev->MaxTarget = PcdGet8 (PcdLsiScsiMaxTargetLimit); Dev->MaxLun = PcdGet8 (PcdLsiScsiMaxLunLimit); + Dev->StallPerPollUsec = PcdGet32 (PcdLsiScsiStallPerPollUsec); Status = gBS->OpenProtocol ( ControllerHandle, diff --git a/OvmfPkg/LsiScsiDxe/LsiScsi.h b/OvmfPkg/LsiScsiDxe/LsiScsi.h index 05deeed379..6ecf523f5a 100644 --- a/OvmfPkg/LsiScsiDxe/LsiScsi.h +++ b/OvmfPkg/LsiScsiDxe/LsiScsi.h @@ -13,6 +13,11 @@ #define _LSI_SCSI_DXE_H_ typedef struct { + // + // Allocate 32 UINT32 entries for the script and it's sufficient for + // 16 instructions. + // + UINT32 Script[32]; // // The max size of CDB is 32. // @@ -25,6 +30,18 @@ typedef struct { // Count (DBC), a 24-bit register, so the maximum is 0xFFFFFF (16MB-1). // UINT8 Data[SIZE_64KB]; + // + // For SCSI Message In phase + // + UINT8 MsgIn[2]; + // + // For SCSI Message Out phase + // + UINT8 MsgOut; + // + // For SCSI Status phase + // + UINT8 Status; } LSI_SCSI_DMA_BUFFER; typedef struct { @@ -34,6 +51,7 @@ typedef struct { EFI_PCI_IO_PROTOCOL *PciIo; UINT8 MaxTarget; UINT8 MaxLun; + UINT32 StallPerPollUsec; LSI_SCSI_DMA_BUFFER *Dma; EFI_PHYSICAL_ADDRESS DmaPhysical; VOID *DmaMapping; @@ -46,6 +64,9 @@ typedef struct { #define LSI_SCSI_FROM_PASS_THRU(PassThruPtr) \ CR (PassThruPtr, LSI_SCSI_DEV, PassThru, LSI_SCSI_DEV_SIGNATURE) +#define LSI_SCSI_DMA_ADDR(Dev, MemberName) \ + ((UINT32)(Dev->DmaPhysical + OFFSET_OF (LSI_SCSI_DMA_BUFFER, MemberName))) + // // Probe, start and stop functions of this driver, called by the DXE core for diff --git a/OvmfPkg/LsiScsiDxe/LsiScsiDxe.inf b/OvmfPkg/LsiScsiDxe/LsiScsiDxe.inf index 6111449577..4c7abcec61 100644 --- a/OvmfPkg/LsiScsiDxe/LsiScsiDxe.inf +++ b/OvmfPkg/LsiScsiDxe/LsiScsiDxe.inf @@ -41,3 +41,6 @@ [FixedPcd] gUefiOvmfPkgTokenSpaceGuid.PcdLsiScsiMaxTargetLimit ## CONSUMES gUefiOvmfPkgTokenSpaceGuid.PcdLsiScsiMaxLunLimit ## CONSUMES + +[Pcd] + gUefiOvmfPkgTokenSpaceGuid.PcdLsiScsiStallPerPollUsec ## CONSUMES diff --git a/OvmfPkg/OvmfPkg.dec b/OvmfPkg/OvmfPkg.dec index ca13a3cb11..f16c00ad5b 100644 --- a/OvmfPkg/OvmfPkg.dec +++ b/OvmfPkg/OvmfPkg.dec @@ -179,6 +179,9 @@ gUefiOvmfPkgTokenSpaceGuid.PcdLsiScsiMaxTargetLimit|7|UINT8|0x3b gUefiOvmfPkgTokenSpaceGuid.PcdLsiScsiMaxLunLimit|0|UINT8|0x3c + ## Microseconds to stall between polling for LsiScsi request result + gUefiOvmfPkgTokenSpaceGuid.PcdLsiScsiStallPerPollUsec|5|UINT32|0x3d + gUefiOvmfPkgTokenSpaceGuid.PcdOvmfFlashNvStorageEventLogBase|0x0|UINT32|0x8 gUefiOvmfPkgTokenSpaceGuid.PcdOvmfFlashNvStorageEventLogSize|0x0|UINT32|0x9 gUefiOvmfPkgTokenSpaceGuid.PcdOvmfFirmwareFdSize|0x0|UINT32|0xa