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