]> git.proxmox.com Git - mirror_edk2.git/blobdiff - OvmfPkg/LsiScsiDxe/LsiScsi.c
OvmfPkg/LsiScsiDxe: Process the SCSI Request Packet
[mirror_edk2.git] / OvmfPkg / LsiScsiDxe / LsiScsi.c
index 9859632e02d685a317073267c3be1d1268af34af..ed5fc3bfdd9d1ec98105fca11d665d54dac9529c 100644 (file)
@@ -43,6 +43,60 @@ Out8 (
                           );\r
 }\r
 \r
+STATIC\r
+EFI_STATUS\r
+Out32 (\r
+  IN LSI_SCSI_DEV       *Dev,\r
+  IN UINT32             Addr,\r
+  IN UINT32             Data\r
+  )\r
+{\r
+  return Dev->PciIo->Io.Write (\r
+                          Dev->PciIo,\r
+                          EfiPciIoWidthUint32,\r
+                          PCI_BAR_IDX0,\r
+                          Addr,\r
+                          1,\r
+                          &Data\r
+                          );\r
+}\r
+\r
+STATIC\r
+EFI_STATUS\r
+In8 (\r
+  IN  LSI_SCSI_DEV *Dev,\r
+  IN  UINT32       Addr,\r
+  OUT UINT8        *Data\r
+  )\r
+{\r
+  return Dev->PciIo->Io.Read (\r
+                          Dev->PciIo,\r
+                          EfiPciIoWidthUint8,\r
+                          PCI_BAR_IDX0,\r
+                          Addr,\r
+                          1,\r
+                          Data\r
+                          );\r
+}\r
+\r
+STATIC\r
+EFI_STATUS\r
+In32 (\r
+  IN  LSI_SCSI_DEV *Dev,\r
+  IN  UINT32       Addr,\r
+  OUT UINT32       *Data\r
+  )\r
+{\r
+  return Dev->PciIo->Io.Read (\r
+                          Dev->PciIo,\r
+                          EfiPciIoWidthUint32,\r
+                          PCI_BAR_IDX0,\r
+                          Addr,\r
+                          1,\r
+                          Data\r
+                          );\r
+}\r
+\r
 STATIC\r
 EFI_STATUS\r
 LsiScsiReset (\r
@@ -141,6 +195,357 @@ LsiScsiCheckRequest (
   return EFI_SUCCESS;\r
 }\r
 \r
+/**\r
+\r
+  Interpret the request packet from the Extended SCSI Pass Thru Protocol and\r
+  compose the script to submit the command and data to the controller.\r
+\r
+  @param[in] Dev          The LSI 53C895A SCSI device the packet targets.\r
+\r
+  @param[in] Target       The SCSI target controlled by the LSI 53C895A SCSI\r
+                          device.\r
+\r
+  @param[in] Lun          The Logical Unit Number under the SCSI target.\r
+\r
+  @param[in out] Packet   The Extended SCSI Pass Thru Protocol packet.\r
+\r
+\r
+  @retval EFI_SUCCESS  The Extended SCSI Pass Thru Protocol packet was valid.\r
+\r
+  @return              Otherwise, invalid or unsupported parameters were\r
+                       detected. Status codes are meant for direct forwarding\r
+                       by the EFI_EXT_SCSI_PASS_THRU_PROTOCOL.PassThru()\r
+                       implementation.\r
+\r
+ **/\r
+STATIC\r
+EFI_STATUS\r
+LsiScsiProcessRequest (\r
+  IN LSI_SCSI_DEV                                   *Dev,\r
+  IN UINT8                                          Target,\r
+  IN UINT64                                         Lun,\r
+  IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet\r
+  )\r
+{\r
+  EFI_STATUS Status;\r
+  UINT32     *Script;\r
+  UINT8      *Cdb;\r
+  UINT8      *MsgOut;\r
+  UINT8      *MsgIn;\r
+  UINT8      *ScsiStatus;\r
+  UINT8      *Data;\r
+  UINT8      DStat;\r
+  UINT8      SIst0;\r
+  UINT8      SIst1;\r
+  UINT32     Csbc;\r
+  UINT32     CsbcBase;\r
+  UINT32     Transferred;\r
+\r
+  Script      = Dev->Dma->Script;\r
+  Cdb         = Dev->Dma->Cdb;\r
+  Data        = Dev->Dma->Data;\r
+  MsgIn       = Dev->Dma->MsgIn;\r
+  MsgOut      = &Dev->Dma->MsgOut;\r
+  ScsiStatus  = &Dev->Dma->Status;\r
+\r
+  *ScsiStatus = 0xFF;\r
+\r
+  DStat = 0;\r
+  SIst0 = 0;\r
+  SIst1 = 0;\r
+\r
+  SetMem (Cdb, sizeof Dev->Dma->Cdb, 0x00);\r
+  CopyMem (Cdb, Packet->Cdb, Packet->CdbLength);\r
+\r
+  //\r
+  // Fetch the first Cumulative SCSI Byte Count (CSBC).\r
+  //\r
+  // CSBC is a cumulative counter of the actual number of bytes that have been\r
+  // transferred across the SCSI bus during data phases, i.e. it will not\r
+  // count bytes sent in command, status, message in and out phases.\r
+  //\r
+  Status = In32 (Dev, LSI_REG_CSBC, &CsbcBase);\r
+  if (EFI_ERROR (Status)) {\r
+    goto Error;\r
+  }\r
+\r
+  //\r
+  // Clean up the DMA buffer for the script.\r
+  //\r
+  SetMem (Script, sizeof Dev->Dma->Script, 0x00);\r
+\r
+  //\r
+  // Compose the script to transfer data between the host and the device.\r
+  //\r
+  // References:\r
+  //   * LSI53C895A PCI to Ultra2 SCSI Controller Version 2.2\r
+  //     - Chapter 5 SCSI SCRIPT Instruction Set\r
+  //   * SEABIOS lsi-scsi driver\r
+  //\r
+  // All instructions used here consist of 2 32bit words. The first word\r
+  // contains the command to execute. The second word is loaded into the\r
+  // DMA SCRIPTS Pointer Save (DSPS) register as either the DMA address\r
+  // for data transmission or the address/offset for the jump command.\r
+  // Some commands, such as the selection of the target, don't need to\r
+  // transfer data through DMA or jump to another instruction, then DSPS\r
+  // has to be zero.\r
+  //\r
+  // There are 3 major parts in this script. The first part (1~3) contains\r
+  // the instructions to select target and LUN and send the SCSI command\r
+  // from the request packet. The second part (4~7) is to handle the\r
+  // potential disconnection and prepare for the data transmission. The\r
+  // instructions in the third part (8~10) transmit the given data and\r
+  // collect the result. Instruction 11 raises the interrupt and marks the\r
+  // end of the script.\r
+  //\r
+\r
+  //\r
+  // 1. Select target.\r
+  //\r
+  *Script++ = LSI_INS_TYPE_IO | LSI_INS_IO_OPC_SEL | (UINT32)Target << 16;\r
+  *Script++ = 0x00000000;\r
+\r
+  //\r
+  // 2. Select LUN.\r
+  //\r
+  *MsgOut   = 0x80 | (UINT8) Lun; // 0x80: Identify bit\r
+  *Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_MSG_OUT |\r
+              (UINT32)sizeof Dev->Dma->MsgOut;\r
+  *Script++ = LSI_SCSI_DMA_ADDR (Dev, MsgOut);\r
+\r
+  //\r
+  // 3. Send the SCSI Command.\r
+  //\r
+  *Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_CMD |\r
+              (UINT32)sizeof Dev->Dma->Cdb;\r
+  *Script++ = LSI_SCSI_DMA_ADDR (Dev, Cdb);\r
+\r
+  //\r
+  // 4. Check whether the current SCSI phase is "Message In" or not\r
+  //    and jump to 7 if it is.\r
+  //    Note: LSI_INS_TC_RA stands for "Relative Address Mode", so the\r
+  //          offset 0x18 in the second word means jumping forward\r
+  //          3 (0x18/8) instructions.\r
+  //\r
+  *Script++ = LSI_INS_TYPE_TC | LSI_INS_TC_OPC_JMP |\r
+              LSI_INS_TC_SCSIP_MSG_IN | LSI_INS_TC_RA |\r
+              LSI_INS_TC_CP;\r
+  *Script++ = 0x00000018;\r
+\r
+  //\r
+  // 5. Read "Message" from the initiator to trigger reselect.\r
+  //\r
+  *Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_MSG_IN |\r
+              (UINT32)sizeof Dev->Dma->MsgIn;\r
+  *Script++ = LSI_SCSI_DMA_ADDR (Dev, MsgIn);\r
+\r
+  //\r
+  // 6. Wait reselect.\r
+  //\r
+  *Script++ = LSI_INS_TYPE_IO | LSI_INS_IO_OPC_WAIT_RESEL;\r
+  *Script++ = 0x00000000;\r
+\r
+  //\r
+  // 7. Read "Message" from the initiator again\r
+  //\r
+  *Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_MSG_IN |\r
+              (UINT32)sizeof Dev->Dma->MsgIn;\r
+  *Script++ = LSI_SCSI_DMA_ADDR (Dev, MsgIn);\r
+\r
+  //\r
+  // 8. Set the DMA command for the read/write operations.\r
+  //    Note: Some requests, e.g. "TEST UNIT READY", do not come with\r
+  //          allocated InDataBuffer or OutDataBuffer. We skip the DMA\r
+  //          data command for those requests or this script would fail\r
+  //          with LSI_SIST0_SGE due to the zero data length.\r
+  //\r
+  // LsiScsiCheckRequest() prevents both integer overflows in the command\r
+  // opcodes, and buffer overflows.\r
+  //\r
+  if (Packet->InTransferLength > 0) {\r
+    ASSERT (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ);\r
+    ASSERT (Packet->InTransferLength <= sizeof Dev->Dma->Data);\r
+    *Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_DAT_IN |\r
+                Packet->InTransferLength;\r
+    *Script++ = LSI_SCSI_DMA_ADDR (Dev, Data);\r
+  } else if (Packet->OutTransferLength > 0) {\r
+    ASSERT (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_WRITE);\r
+    ASSERT (Packet->OutTransferLength <= sizeof Dev->Dma->Data);\r
+    CopyMem (Data, Packet->OutDataBuffer, Packet->OutTransferLength);\r
+    *Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_DAT_OUT |\r
+                Packet->OutTransferLength;\r
+    *Script++ = LSI_SCSI_DMA_ADDR (Dev, Data);\r
+  }\r
+\r
+  //\r
+  // 9. Get the SCSI status.\r
+  //\r
+  *Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_STAT |\r
+              (UINT32)sizeof Dev->Dma->Status;\r
+  *Script++ = LSI_SCSI_DMA_ADDR (Dev, Status);\r
+\r
+  //\r
+  // 10. Get the SCSI message.\r
+  //\r
+  *Script++ = LSI_INS_TYPE_BLK | LSI_INS_BLK_SCSIP_MSG_IN |\r
+              (UINT32)sizeof Dev->Dma->MsgIn;\r
+  *Script++ = LSI_SCSI_DMA_ADDR (Dev, MsgIn);\r
+\r
+  //\r
+  // 11. Raise the interrupt to end the script.\r
+  //\r
+  *Script++ = LSI_INS_TYPE_TC | LSI_INS_TC_OPC_INT |\r
+              LSI_INS_TC_SCSIP_DAT_OUT | LSI_INS_TC_JMP;\r
+  *Script++ = 0x00000000;\r
+\r
+  //\r
+  // Make sure the size of the script doesn't exceed the buffer.\r
+  //\r
+  ASSERT (Script <= Dev->Dma->Script + ARRAY_SIZE (Dev->Dma->Script));\r
+\r
+  //\r
+  // The controller starts to execute the script once the DMA Script\r
+  // Pointer (DSP) register is set.\r
+  //\r
+  Status = Out32 (Dev, LSI_REG_DSP, LSI_SCSI_DMA_ADDR (Dev, Script));\r
+  if (EFI_ERROR (Status)) {\r
+    goto Error;\r
+  }\r
+\r
+  //\r
+  // Poll the device registers (DSTAT, SIST0, and SIST1) until the SIR\r
+  // bit sets.\r
+  //\r
+  for(;;) {\r
+    Status = In8 (Dev, LSI_REG_DSTAT, &DStat);\r
+    if (EFI_ERROR (Status)) {\r
+      goto Error;\r
+    }\r
+    Status = In8 (Dev, LSI_REG_SIST0, &SIst0);\r
+    if (EFI_ERROR (Status)) {\r
+      goto Error;\r
+    }\r
+    Status = In8 (Dev, LSI_REG_SIST1, &SIst1);\r
+    if (EFI_ERROR (Status)) {\r
+      goto Error;\r
+    }\r
+\r
+    if (SIst0 != 0 || SIst1 != 0) {\r
+      goto Error;\r
+    }\r
+\r
+    //\r
+    // Check the SIR (SCRIPTS Interrupt Instruction Received) bit.\r
+    //\r
+    if (DStat & LSI_DSTAT_SIR) {\r
+      break;\r
+    }\r
+\r
+    gBS->Stall (Dev->StallPerPollUsec);\r
+  }\r
+\r
+  //\r
+  // Check if everything is good.\r
+  //   SCSI Message Code 0x00: COMMAND COMPLETE\r
+  //   SCSI Status  Code 0x00: Good\r
+  //\r
+  if (MsgIn[0] != 0 || *ScsiStatus != 0) {\r
+    goto Error;\r
+  }\r
+\r
+  //\r
+  // Fetch CSBC again to calculate the transferred bytes and update\r
+  // InTransferLength/OutTransferLength.\r
+  //\r
+  // Note: The number of transferred bytes is bounded by\r
+  //       "sizeof Dev->Dma->Data", so it's safe to subtract CsbcBase\r
+  //       from Csbc. If the CSBC register wraps around, the correct\r
+  //       difference is ensured by the standard C modular arithmetic.\r
+  //\r
+  Status = In32 (Dev, LSI_REG_CSBC, &Csbc);\r
+  if (EFI_ERROR (Status)) {\r
+    goto Error;\r
+  }\r
+\r
+  Transferred = Csbc - CsbcBase;\r
+  if (Packet->InTransferLength > 0) {\r
+    if (Transferred <= Packet->InTransferLength) {\r
+      Packet->InTransferLength = Transferred;\r
+    } else {\r
+      goto Error;\r
+    }\r
+  } else if (Packet->OutTransferLength > 0) {\r
+    if (Transferred <= Packet->OutTransferLength) {\r
+      Packet->OutTransferLength = Transferred;\r
+    } else {\r
+      goto Error;\r
+    }\r
+  }\r
+\r
+  //\r
+  // Copy Data to InDataBuffer if necessary.\r
+  //\r
+  if (Packet->DataDirection == EFI_EXT_SCSI_DATA_DIRECTION_READ) {\r
+    CopyMem (Packet->InDataBuffer, Data, Packet->InTransferLength);\r
+  }\r
+\r
+  //\r
+  // Always set SenseDataLength to 0.\r
+  // The instructions of LSI53C895A don't reply sense data. Instead, it\r
+  // relies on the SCSI command, "REQUEST SENSE", to get sense data. We set\r
+  // SenseDataLength to 0 to notify ScsiDiskDxe that there is no sense data\r
+  // written even if this request is processed successfully, so that It will\r
+  // issue "REQUEST SENSE" later to retrieve sense data.\r
+  //\r
+  Packet->SenseDataLength   = 0;\r
+  Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OK;\r
+  Packet->TargetStatus      = EFI_EXT_SCSI_STATUS_TARGET_GOOD;\r
+\r
+  return EFI_SUCCESS;\r
+\r
+Error:\r
+  DEBUG ((DEBUG_VERBOSE, "%a: dstat: %02X, sist0: %02X, sist1: %02X\n",\r
+    __FUNCTION__, DStat, SIst0, SIst1));\r
+  //\r
+  // Update the request packet to reflect the status.\r
+  //\r
+  if (*ScsiStatus != 0xFF) {\r
+    Packet->TargetStatus    = *ScsiStatus;\r
+  } else {\r
+    Packet->TargetStatus    = EFI_EXT_SCSI_STATUS_TARGET_TASK_ABORTED;\r
+  }\r
+\r
+  if (SIst0 & LSI_SIST0_PAR) {\r
+    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_PARITY_ERROR;\r
+  } else if (SIst0 & LSI_SIST0_RST) {\r
+    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_BUS_RESET;\r
+  } else if (SIst0 & LSI_SIST0_UDC) {\r
+    //\r
+    // The target device is disconnected unexpectedly. According to UEFI spec,\r
+    // this is TIMEOUT_COMMAND.\r
+    //\r
+    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT_COMMAND;\r
+  } else if (SIst0 & LSI_SIST0_SGE) {\r
+    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_DATA_OVERRUN_UNDERRUN;\r
+  } else if (SIst1 & LSI_SIST1_HTH) {\r
+    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT;\r
+  } else if (SIst1 & LSI_SIST1_GEN) {\r
+    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_TIMEOUT;\r
+  } else if (SIst1 & LSI_SIST1_STO) {\r
+    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_SELECTION_TIMEOUT;\r
+  } else {\r
+    Packet->HostAdapterStatus = EFI_EXT_SCSI_STATUS_HOST_ADAPTER_OTHER;\r
+  }\r
+\r
+  //\r
+  // SenseData may be used to inspect the error. Since we don't set sense data,\r
+  // SenseDataLength has to be 0.\r
+  //\r
+  Packet->SenseDataLength = 0;\r
+\r
+  return EFI_DEVICE_ERROR;\r
+}\r
+\r
 //\r
 // The next seven functions implement EFI_EXT_SCSI_PASS_THRU_PROTOCOL\r
 // for the LSI 53C895A SCSI Controller. Refer to UEFI Spec 2.3.1 + Errata C,\r
@@ -168,7 +573,7 @@ LsiScsiPassThru (
     return Status;\r
   }\r
 \r
-  return EFI_UNSUPPORTED;\r
+  return LsiScsiProcessRequest (Dev, *Target, Lun, Packet);\r
 }\r
 \r
 EFI_STATUS\r
@@ -474,6 +879,7 @@ LsiScsiControllerStart (
     );\r
   Dev->MaxTarget = PcdGet8 (PcdLsiScsiMaxTargetLimit);\r
   Dev->MaxLun = PcdGet8 (PcdLsiScsiMaxLunLimit);\r
+  Dev->StallPerPollUsec = PcdGet32 (PcdLsiScsiStallPerPollUsec);\r
 \r
   Status = gBS->OpenProtocol (\r
                   ControllerHandle,\r