diff options
Diffstat (limited to '')
-rw-r--r-- | private/ntos/miniport/wd7000ex/wd7000ex.c | 1684 |
1 files changed, 1684 insertions, 0 deletions
diff --git a/private/ntos/miniport/wd7000ex/wd7000ex.c b/private/ntos/miniport/wd7000ex/wd7000ex.c new file mode 100644 index 000000000..6dfb8af32 --- /dev/null +++ b/private/ntos/miniport/wd7000ex/wd7000ex.c @@ -0,0 +1,1684 @@ +/*++ + +Copyright (c) 1991 Microsoft Corporation + +Module Name: + + wd7000ex.c + +Abstract: + + This is the port driver for the WD7000EX SCSI adapter. + +Authors: + + Mike Glass + Shashir Shah (Western Digital) + +Environment: + + kernel mode only + +Notes: + +Revision History: + +--*/ + +#include "miniport.h" +#include "wd7000ex.h" // includes scsi.h + +// +// The following structure is allocated +// from noncached memory as data will be DMA'd to +// and from it. +// + +typedef struct _NONCACHED_EXTENSION { + + // + // Internal Control Block + // + + ICB Icb; + + // + // Adapter inquiry buffer + // + + ADAPTER_INQUIRY AdapterInquiry; + +} NONCACHED_EXTENSION, *PNONCACHED_EXTENSION; + +// +// Device extension +// + +typedef struct _HW_DEVICE_EXTENSION { + + PEISA_CONTROLLER EisaController; + + // + // Adapter parameters + // + + CCHAR IdtVector; + + // + // Adapter host adapter Id. + // + + UCHAR HostTargetId; + + // + // Real-Mode interrupt state. + // + + UCHAR InterruptState; + UCHAR NumberOfBuses; + +} HW_DEVICE_EXTENSION, *PHW_DEVICE_EXTENSION; + + +// +// Function declarations +// +// Functions that start with 'Wd7000Ex' are entry points +// for the OS port driver. +// + +ULONG +Wd7000ExFindAdapter( + IN PVOID DeviceExtension, + IN PVOID Context, + IN PVOID BusInformation, + IN PCHAR ArgumentString, + IN OUT PPORT_CONFIGURATION_INFORMATION ConfigInfo, + OUT PBOOLEAN Again + ); + +BOOLEAN +Wd7000ExInitialize( + IN PVOID DeviceExtension + ); + +BOOLEAN +Wd7000ExStartIo( + IN PVOID DeviceExtension, + IN PSCSI_REQUEST_BLOCK Srb + ); + +BOOLEAN +Wd7000ExInterrupt( + IN PVOID DeviceExtension + ); + +BOOLEAN +Wd7000ExResetBus( + IN PVOID HwDeviceExtension, + IN ULONG PathId + ); + +// +// This function is called from Wd7000ExStartIo. +// + +VOID +BuildCcb( + IN PHW_DEVICE_EXTENSION DeviceExtension, + IN PSCSI_REQUEST_BLOCK Srb + ); + +// +// This function is called from BuildCcb. +// + +VOID +BuildSdl( + IN PHW_DEVICE_EXTENSION DeviceExtension, + IN PSCSI_REQUEST_BLOCK Srb + ); + +// +// This function is called from Wd7000Initialize. +// + +BOOLEAN +AdapterPresent( + IN PVOID HwDeviceExtension, + IN OUT PPORT_CONFIGURATION_INFORMATION ConfigInfo, + IN OUT PULONG AdapterCount + ); + +BOOLEAN +SendCommand( + IN UCHAR Opcode, + IN ULONG Address, + IN PHW_DEVICE_EXTENSION DeviceExtension + ); + + +BOOLEAN +Wd7000ExAdapterState( + IN PVOID DeviceExtension, + IN PVOID AdaptersFound, + IN BOOLEAN SaveState + ) + +/*++ + +Routine Description: + + Saves/restores adapter's real-mode configuration state. + +Arguments: + + DeviceExtension - Adapter object device extension. + AdaptersFound - Passed through from DriverEntry as additional + context for the call. + SaveState - TRUE = Save adapter state, FALSE = restore state. + +Return Value: + + The spec did not intend for this routine to have a return value. + Whoever did the header file just forgot to change the BOOLEAN to + a VOID. We will just return FALSE to shot the compiler up. + +--*/ + +{ + PHW_DEVICE_EXTENSION deviceExtension = DeviceExtension; + + if (SaveState) { + + // + // Remember system interrupt state. + // + deviceExtension->InterruptState = ScsiPortReadPortUchar( + &deviceExtension->EisaController->SystemInterruptEnable); + + } else { + + // + // Restore system interrupt state. + // + ScsiPortWritePortUchar(&deviceExtension->EisaController->SystemInterruptEnable, + deviceExtension->InterruptState); + + } + + return FALSE; +} + + +ULONG +DriverEntry ( + IN PVOID DriverObject, + IN PVOID Argument2 + ) + +/*++ + +Routine Description: + + Installable driver initialization entry point for system. + +Arguments: + + Driver Object + Argument2 - Not used. + +Return Value: + + Status from ScsiPortInitialize() + +--*/ + +{ + HW_INITIALIZATION_DATA hwInitializationData; + ULONG AdapterCount = 0; + ULONG i; + + DebugPrint((1,"\n\nSCSI WD7000EX MiniPort Driver\n")); + + // + // Zero out structure. + // + + for (i=0; i<sizeof(HW_INITIALIZATION_DATA); i++) { + ((PUCHAR)&hwInitializationData)[i] = 0; + } + + // + // Set size of hwInitializationData. + // + + hwInitializationData.HwInitializationDataSize = sizeof(HW_INITIALIZATION_DATA); + + // + // Set entry points. + // + + hwInitializationData.HwInitialize = Wd7000ExInitialize; + hwInitializationData.HwFindAdapter = Wd7000ExFindAdapter; + hwInitializationData.HwStartIo = Wd7000ExStartIo; + hwInitializationData.HwInterrupt = Wd7000ExInterrupt; + hwInitializationData.HwResetBus = Wd7000ExResetBus; + hwInitializationData.HwAdapterState = Wd7000ExAdapterState; + + // + // Set number of access ranges and bus type. + // + + hwInitializationData.NumberOfAccessRanges = 1; + hwInitializationData.AdapterInterfaceType = Eisa; + + // + // Indicate no buffer mapping but will need physical addresses. + // + + hwInitializationData.NeedPhysicalAddresses = TRUE; + + // + // Specify size of extensions. + // + + hwInitializationData.DeviceExtensionSize = sizeof(HW_DEVICE_EXTENSION); + + // + // Ask for SRB extensions for CCBs. + // + + hwInitializationData.SrbExtensionSize = sizeof(CCB); + + return ScsiPortInitialize(DriverObject, Argument2, &hwInitializationData, &AdapterCount); + +} // end DriverEntry() + + +BOOLEAN +AdapterPresent( + IN PVOID HwDeviceExtension, + IN OUT PPORT_CONFIGURATION_INFORMATION ConfigInfo, + IN OUT PULONG AdapterCount + ) + +/*++ + +Routine Description: + + Determine if WD7000EX SCSI adapter is installed in system + by reading the EISA board configuration registers for each + EISA slot looking for the correct signature. + +Arguments: + + HwDeviceExtension - HBA miniport driver's adapter data storage + + ConfigInfo - Supplies the configuration information stucture. If an + adapter is found the access range is update. + + AdapterCount - Supplies the count of slots which have already been checked. + +Return Value: + + TRUE if adapter present. + +--*/ + +{ + PHW_DEVICE_EXTENSION deviceExtension = HwDeviceExtension; + ULONG eisaSlotNumber; + PEISA_CONTROLLER eisaController; + + // + // Check to see if adapter present in system. + // + + + for (eisaSlotNumber=*AdapterCount + 1; eisaSlotNumber<MAXIMUM_EISA_SLOTS; eisaSlotNumber++) { + + // + // Update the adapter count. + // + + (*AdapterCount)++; + + // + // Get the system address for this card. The card uses I/O space. + // If ConfigInfo already has default information about this + // controller, use it. If not, then we derive our own. This + // is for Chicago compatibility. + // + if (ScsiPortConvertPhysicalAddressToUlong( + (*ConfigInfo->AccessRanges)[0].RangeStart) != 0) { + + eisaController = ScsiPortGetDeviceBase( + deviceExtension, + ConfigInfo->AdapterInterfaceType, + ConfigInfo->SystemIoBusNumber, + (*ConfigInfo->AccessRanges)[0].RangeStart, + (*ConfigInfo->AccessRanges)[0].RangeLength, + (BOOLEAN) !((*ConfigInfo->AccessRanges)[0].RangeInMemory)); + } else { + + eisaController = ScsiPortGetDeviceBase( + deviceExtension, + ConfigInfo->AdapterInterfaceType, + ConfigInfo->SystemIoBusNumber, + ScsiPortConvertUlongToPhysicalAddress(0x1000 * eisaSlotNumber), + 0x1000, + TRUE); + } + + eisaController = + (PEISA_CONTROLLER)((PUCHAR)eisaController + EISA_ADDRESS_BASE); + + if ((ScsiPortReadPortUchar(&eisaController->BoardId[0]) == 0x5C) && + (ScsiPortReadPortUchar(&eisaController->BoardId[1]) == 0x83) && + (ScsiPortReadPortUchar(&eisaController->BoardId[2]) == 0x20)) { + + deviceExtension->EisaController = eisaController; + + return TRUE; + } + + // + // The card is not here so clean up. + // + + ScsiPortFreeDeviceBase(deviceExtension, + (PUCHAR)eisaController - EISA_ADDRESS_BASE); + + } // end for (eisaSlotNumber ... + + // + // Clear the adapter count for the next bus. + // + + *AdapterCount = 0; + + return FALSE; + +} // end AdapterPresent() + + +ULONG +Wd7000ExFindAdapter( + IN PVOID HwDeviceExtension, + IN PVOID Context, + IN PVOID BusInformation, + IN PCHAR ArgumentString, + IN OUT PPORT_CONFIGURATION_INFORMATION ConfigInfo, + OUT PBOOLEAN Again + ) + +/*++ + +Routine Description: + + This function is called by the OS-specific port driver after + the necessary storage has been allocated, to gather information + about the adapter's configuration. + +Arguments: + + HwDeviceExtension - HBA miniport driver's adapter data storage + ConfigInfo - Configuration information structure describing HBA + +Return Value: + + TRUE if adapter present in system + +--*/ + +{ + PHW_DEVICE_EXTENSION deviceExtension = HwDeviceExtension; + PEISA_CONTROLLER eisaController; + PULONG adapterCount = Context; + PNONCACHED_EXTENSION ncExtension; + ULONG eisaSlotNumber; + PICB icb; + PADAPTER_INQUIRY adapterInquiry; + ULONG physicalIcb; + ULONG i; + ULONG length; + UCHAR status; + + // + // Check to see if adapter present in system. + // + if (!AdapterPresent(deviceExtension, ConfigInfo, Context)) { + DebugPrint((1,"Wd7000EX: SCSI adapter not present\n")); + *Again = FALSE; + return SP_RETURN_NOT_FOUND; + } + + // + // There is still more to look at. + // + + *Again = FALSE; + + // + // Fill in the access array information only if there are no + // default parameters already there. + // + if (ScsiPortConvertPhysicalAddressToUlong( + (*ConfigInfo->AccessRanges)[0].RangeStart) == 0) { + + *Again = TRUE; + (*ConfigInfo->AccessRanges)[0].RangeStart = + ScsiPortConvertUlongToPhysicalAddress(0x1000 * (*((PULONG) Context)) + EISA_ADDRESS_BASE); + (*ConfigInfo->AccessRanges)[0].RangeLength = sizeof(EISA_CONTROLLER); + (*ConfigInfo->AccessRanges)[0].RangeInMemory = FALSE; + + // + // Indicate maximum transfer length in bytes. + // + ConfigInfo->MaximumTransferLength = MAXIMUM_TRANSFER_SIZE; + + // + // Maximum number of physical segments is 32. + // + ConfigInfo->NumberOfPhysicalBreaks = MAXIMUM_SDL_SIZE; + + // + // Set the configuration parameters for this card. + // + + ConfigInfo->NumberOfBuses = 1; + deviceExtension->NumberOfBuses = 1; + ConfigInfo->ScatterGather = TRUE; + ConfigInfo->Master = TRUE; + + // + // Get a noncached extension for an adapter inquiry command. + // + + ncExtension = ScsiPortGetUncachedExtension( + deviceExtension, + ConfigInfo, + sizeof(NONCACHED_EXTENSION)); + + if (ncExtension == NULL) { + + // + // Log error. + // + + ScsiPortLogError( + deviceExtension, + NULL, + 0, + 0, + 0, + SP_INTERNAL_ADAPTER_ERROR, + 6 << 16 + ); + + return SP_RETURN_ERROR; + } + + length = sizeof(NONCACHED_EXTENSION); + + // + // Convert virtual to physical address. + // + + physicalIcb = ScsiPortConvertPhysicalAddressToUlong( + ScsiPortGetPhysicalAddress(deviceExtension, + NULL, + ncExtension, + &length)); + + // + // Initialize the pointers. + // + + icb = &ncExtension->Icb; + adapterInquiry = &ncExtension->AdapterInquiry; + + // + // Create ICB for Adapter Inquiry Command. + // + + icb->IcbFlags = 0; + icb->CompletionStatus = 0; + icb->Reserved = 0; + icb->DataBufferAddress = ScsiPortConvertPhysicalAddressToUlong( + ScsiPortGetPhysicalAddress( + deviceExtension, + NULL, + adapterInquiry, + &length)); + + icb->TransferCount = sizeof(ADAPTER_INQUIRY); + + icb->OpCode = ADAPTER_INQUIRY_COMMAND; + + // + // Get ICB physical address. + // + + physicalIcb = ScsiPortConvertPhysicalAddressToUlong( + ScsiPortGetPhysicalAddress( + deviceExtension, + NULL, + icb, + &length)); + + // + // Disable system interrupts. + // + + ScsiPortWritePortUchar(&deviceExtension->EisaController->SystemInterruptEnable, + SYSTEM_INTERRUPTS_DISABLE); + + // + // Write ICB physical address and command to mailbox. + // + + SendCommand(PROCESS_ICB, + physicalIcb, + deviceExtension); + + // + // Poll for ICB completion. + // + + i = 0; + while ((status = + ScsiPortReadPortUchar( + &deviceExtension->EisaController->ResponseRegister)) == 0) { + + i++; + + if (i > 100000) { + + break; + } + + ScsiPortStallExecution(10); + } + + if (status == 0) { + + // + // The request timed out. Log an error and return. + // + + ScsiPortLogError( + deviceExtension, + NULL, + 0, + 0, + 0, + SP_INTERNAL_ADAPTER_ERROR, + 7 << 16 + ); + return SP_RETURN_ERROR; + } + + DebugPrint((1, "Wd7000ExFindAdapter: Get configuration request time = %d.\n", i * 10)); + + // + // Acknowledge interrupt. + // + + ScsiPortWritePortUchar(&deviceExtension->EisaController->ResponseRegister, 0xFF); + + // + // Enable system interrupts. + // + + ScsiPortWritePortUchar(&deviceExtension->EisaController->SystemInterruptEnable, + SYSTEM_INTERRUPTS_ENABLE); + + // + // Check returned status for success. + // + + if (status != COMPLETE_SUCCESS) { + + // + // Give up. + // + + DebugPrint((1,"Wd7000Ex: Response register %x\n", status)); + DebugPrint((1,"Wd7000Ex: Adapter inquiry failed\n")); + + // + // Log error. + // + + ScsiPortLogError( + deviceExtension, + NULL, + 0, + 0, + 0, + SP_INTERNAL_ADAPTER_ERROR, + 8 << 16 + ); + + return SP_RETURN_ERROR; + } + + // + // NOTE: Delay here. I don't understand this latency between + // when the device interrupts and the status of the ICB + // is success and when the data is actually available in + // the buffer. + // + + ScsiPortStallExecution(300); + + if (adapterInquiry->AdapterInformation & DUAL_CHANNEL) { + + // + // There are two buses on the adapter. + // + + ConfigInfo->InitiatorBusId[1] = + (adapterInquiry->ChannelInformation >> 4) & BUS_ID_MASK; + ConfigInfo->NumberOfBuses = 2; + deviceExtension->NumberOfBuses = 2; + } + + ConfigInfo->InitiatorBusId[0] = + adapterInquiry->ChannelInformation & BUS_ID_MASK; + ConfigInfo->BusInterruptLevel = adapterInquiry->Irq; + } + deviceExtension->HostTargetId = ConfigInfo->InitiatorBusId[0]; + return SP_RETURN_FOUND; +} // end Wd7000ExFindAdapter() + + +BOOLEAN +Wd7000ExInitialize( + IN PVOID HwDeviceExtension + ) + +/*++ + +Routine Description: + + Inititialize adapter and mailbox. + +Arguments: + + HwDeviceExtension - HBA miniport driver's adapter data storage + +Return Value: + + TRUE - if initialization successful. + FALSE - if initialization unsuccessful. + +--*/ + +{ + PHW_DEVICE_EXTENSION deviceExtension = HwDeviceExtension; + BOOLEAN returnValue; + + // + // Reset Wd7000Ex and SCSI bus. + // + + returnValue = Wd7000ExResetBus(deviceExtension, 0); + ScsiPortNotification( + ResetDetected, + deviceExtension, + 0 + ); + + if (deviceExtension->NumberOfBuses == 2) { + + Wd7000ExResetBus(deviceExtension, 1); + ScsiPortNotification( + ResetDetected, + deviceExtension, + 1 + ); + + } + + if (!returnValue) { + + return FALSE; + + } else { + + return TRUE; + } + +} // end Wd7000ExInitialize() + + +BOOLEAN +Wd7000ExStartIo( + IN PVOID HwDeviceExtension, + IN PSCSI_REQUEST_BLOCK Srb + ) + +/*++ + +Routine Description: + + This routine is called from the SCSI port driver synchronized + with the kernel. The mailboxes are scanned for an empty one and + the CCB is written to it. Then the doorbell is rung and the + OS port driver is notified that the adapter can take + another request, if any are available. + +Arguments: + + HwDeviceExtension - HBA miniport driver's adapter data storage + Srb - IO request packet + +Return Value: + + Nothing + +--*/ + +{ + PHW_DEVICE_EXTENSION deviceExtension = HwDeviceExtension; + PEISA_CONTROLLER eisaController = deviceExtension->EisaController; + PCCB ccb; + UCHAR opCode; + ULONG physicalCcb; + ULONG length; + ULONG i = 0; + + DebugPrint((3,"Wd7000ExStartIo: Enter routine\n")); + + // + // Get CCB from SRB. + // + + if (Srb->Function == SRB_FUNCTION_ABORT_COMMAND) { + + // + // Get CCB to abort. + // + + ccb = Srb->NextSrb->SrbExtension; + + // + // Set abort SRB for completion. + // + + ccb->AbortSrb = Srb; + + } else { + + ccb = Srb->SrbExtension; + + // + // Save SRB back pointer in CCB. + // + + ccb->SrbAddress = Srb; + } + + // + // Get CCB physical address. + // + + physicalCcb = + ScsiPortConvertPhysicalAddressToUlong( + ScsiPortGetPhysicalAddress(deviceExtension, NULL, ccb, &length)); + + // + // Assume physical address is contiguous for size of CCB. + // + + ASSERT(length >= sizeof(CCB)); + + switch (Srb->Function) { + + case SRB_FUNCTION_EXECUTE_SCSI: + + // + // Build CCB. + // + + BuildCcb(deviceExtension, Srb); + + // + // Or in PathId. + // + + opCode = PROCESS_CCB | Srb->PathId << 6; + + break; + + case SRB_FUNCTION_ABORT_COMMAND: + + DebugPrint((1, "Wd7000ExStartIo: Abort request received\n")); + + // + // NOTE: Race condition if aborts occur + // (what if CCB to be aborted + // completes after setting new SrbAddress?) + // + + opCode = ABORT_CCB; + + break; + + case SRB_FUNCTION_RESET_BUS: + + // + // Reset Wd7000Ex and SCSI bus. + // + + DebugPrint((1, "Wd7000ExStartIo: Reset bus request received\n")); + + Wd7000ExResetBus(deviceExtension, + Srb->PathId); + + return TRUE; + + case SRB_FUNCTION_RESET_DEVICE: + + break; + + default: + + // + // Set error, complete request + // and signal ready for next request. + // + + Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST; + + ScsiPortNotification(RequestComplete, + deviceExtension, + Srb); + + ScsiPortNotification(NextRequest, + deviceExtension, + NULL); + + return TRUE; + + } // end switch + + SendCommand(opCode, + physicalCcb, + deviceExtension); + + // + // Adapter ready for next request. + // + + ScsiPortNotification(NextRequest, + deviceExtension, + NULL); + + return TRUE; + +} // end Wd7000ExStartIo() + + +BOOLEAN +Wd7000ExInterrupt( + IN PVOID HwDeviceExtension + ) + +/*++ + +Routine Description: + + This is the interrupt service routine for the WD7000EX SCSI adapter. + It reads the interrupt register to determine if the adapter is indeed + the source of the interrupt and clears the interrupt at the device. + If the adapter is interrupting because a mailbox is full, the CCB is + retrieved to complete the request. + +Arguments: + + HwDeviceExtension - HBA miniport driver's adapter data storage + +Return Value: + + TRUE if MailboxIn full + +--*/ + +{ + PHW_DEVICE_EXTENSION deviceExtension = HwDeviceExtension; + PCCB ccb; + PSCSI_REQUEST_BLOCK srb; + PSCSI_REQUEST_BLOCK abortedSrb; + PEISA_CONTROLLER eisaController = deviceExtension->EisaController; + ULONG logError = 0; + ULONG physicalCcb; + UCHAR srbStatus; + UCHAR response; + + if (!(ScsiPortReadPortUchar( + &eisaController->SystemInterruptEnable) & SYSTEM_INTERRUPT_PENDING)) { + + DebugPrint((2, "WD7000EX: Spurious interrupt\n")); + return FALSE; + } + + // + // Get physical address of CCB. + // + + physicalCcb = ScsiPortReadPortUlong(&eisaController->ResponseMailbox); + + // + // Read status from response register. + // + + response = ScsiPortReadPortUchar(&eisaController->ResponseRegister); + + // + // Acknowledge interrupt. + // + + ScsiPortWritePortUchar(&eisaController->ResponseRegister, 0xFF); + + // + // Get virtual CCB address. + // + + ccb = ScsiPortGetVirtualAddress(deviceExtension, ScsiPortConvertUlongToPhysicalAddress(physicalCcb)); + + // + // Make sure the ccb is valid, if it is supposed to be. + // + + if (ccb == NULL && + response != ABORT_COMMAND_COMPLETE && + response != BUS_RESET && + response != RESET_DEVICE_COMPLETE && + response != ABORT_CCB_NOT_FOUND) { + + ScsiPortLogError( + HwDeviceExtension, + NULL, + 0, + deviceExtension->HostTargetId, + 0, + SP_INTERNAL_ADAPTER_ERROR, + (5 << 16) | response + ); + + return TRUE; + } + + switch (response & STATUS_MASK) { + + case COMPLETE_SUCCESS: + case REQUEST_SENSE_COMPLETE: + + DebugPrint((3, "Wd7000ExInterrupt: Command complete\n")); + srbStatus = SRB_STATUS_SUCCESS; + + break; + + case COMPLETE_ERROR: + + DebugPrint((2, "Wd7000ExInterrupt: Command complete with error\n")); + srbStatus = SRB_STATUS_ERROR; + + break; + + case BUS_RESET: + + DebugPrint((3, "Wd7000ExInterrupt: Bus reset\n")); + srbStatus = SRB_STATUS_BUS_RESET; + + return TRUE; + + case ABORT_COMMAND_COMPLETE: + + // + // This interrupt is not associated with a CCB. + // + + DebugPrint((1, "Wd7000ExInterrupt: Command aborted\n")); + + return TRUE; + + case RESET_DEVICE_COMPLETE: + + DebugPrint((3, "Wd7000ExInterrupt: Device reset complete\n")); + srbStatus = SRB_STATUS_SUCCESS; + + break; + + case COMMAND_ABORTED: + + DebugPrint((1, "Wd7000ExInterrupt: Abort command complete\n")); + + // + // Get SRB from CCB. + // + + srb = ccb->SrbAddress; + + // + // Get aborted SRB. + // + + abortedSrb = ccb->AbortSrb; + + // + // Update the abort SRB status. + // + + abortedSrb->SrbStatus = SRB_STATUS_SUCCESS; + + // + // Call notification routine for the aborted SRB. + // + + ScsiPortNotification(RequestComplete, + (PVOID)deviceExtension, + abortedSrb); + + // + // Update status of the aborted SRB. + // + + srbStatus = SRB_STATUS_TIMEOUT; + + break; + + case ABORT_CCB_NOT_FOUND: + + // + // This normally occurs when the adapter has started processing + // the request. Drop it on the floor and let the abort time out. + // + + DebugPrint((1, "Wd7000ExInterrupt: Abort command failed\n")); + return TRUE; + + case ILLEGAL_STATUS: + + DebugPrint((3, "Wd7000ExInterrupt: Illegal status\n")); + logError = SP_INTERNAL_ADAPTER_ERROR; + srbStatus = SRB_STATUS_ERROR; + + break; + + case DEVICE_TIMEOUT: + + DebugPrint((3, "Wd7000ExInterrupt: Device timeout\n")); + srbStatus = SRB_STATUS_SELECTION_TIMEOUT; + + break; + + case SHORT_RECORD_EXCEPTION: + + DebugPrint((3, "Wd7000ExInterrupt: Data record short\n")); + + srb = ccb->SrbAddress; + if (ccb->ScsiDeviceStatus == SCSISTAT_CHECK_CONDITION) { + srbStatus = SRB_STATUS_ERROR; + } else { + srbStatus = SRB_STATUS_DATA_OVERRUN; + + // + // Update SRB with actual number of bytes transferred. + // + + srb->DataTransferLength -= ccb->TransferCount; + } + + break; + + case LONG_RECORD_EXCEPTION: + + DebugPrint((3, "Wd7000ExInterrupt: Data overrun\n")); + logError = SP_INTERNAL_ADAPTER_ERROR; + + srbStatus = SRB_STATUS_DATA_OVERRUN; + + break; + + case PARITY_ERROR: + + DebugPrint((3, "Wd7000ExInterrupt: Parity error\n")); + logError = SP_BUS_PARITY_ERROR; + srbStatus = SRB_STATUS_PARITY_ERROR; + + break; + + case UNEXPECTED_BUS_FREE: + + DebugPrint((3, "Wd7000ExInterrupt: Unexpected bus free\n")); + logError = SP_UNEXPECTED_DISCONNECT; + srbStatus = SRB_STATUS_UNEXPECTED_BUS_FREE; + + break; + + case INVALID_STATE: + + DebugPrint((3, "Wd7000ExInterrupt: Phase sequence error\n")); + logError = SP_INTERNAL_ADAPTER_ERROR; + srbStatus = SRB_STATUS_PHASE_SEQUENCE_FAILURE; + + break; + + case HOST_DMA_ERROR: + + DebugPrint((3, "Wd7000ExInterrupt: Host DMA error\n")); + logError = SP_INTERNAL_ADAPTER_ERROR; + srbStatus = SRB_STATUS_ERROR; + + break; + + case INVALID_COMMAND: + + DebugPrint((3, "Wd7000ExInterrupt: Invalid command\n")); + logError = SP_INTERNAL_ADAPTER_ERROR; + srbStatus = SRB_STATUS_ERROR; + + break; + + case INCORRECT_COMMAND_DIRECTION: + default: + + DebugPrint((3, "Wd7000ExInterrupt: Incorrect command direction\n")); + logError = SP_INTERNAL_ADAPTER_ERROR; + srbStatus = SRB_STATUS_ERROR; + + break; + + } // end switch + + // + // Get SRB from CCB. + // + + srb = ccb->SrbAddress; + + // + // Update SRB status. + // + + srb->SrbStatus = srbStatus; + + // + // Copy SCSI status from CCB to SRB. + // + + srb->ScsiStatus = ccb->ScsiDeviceStatus; + + if (logError != 0) { + + // + // Log error. + // + + ScsiPortLogError( + deviceExtension, + srb, + srb->PathId, + srb->TargetId, + srb->Lun, + logError, + 2 << 16 | response + ); + + } + + // + // Call notification routine for the SRB. + // + + ScsiPortNotification(RequestComplete, + (PVOID)deviceExtension, + srb); + + return TRUE; + +} // end Wd7000ExInterrupt() + + +VOID +BuildCcb( + IN PHW_DEVICE_EXTENSION DeviceExtension, + IN PSCSI_REQUEST_BLOCK Srb + ) + +/*++ + +Routine Description: + + Build CCB for WD7000EX. + +Arguments: + + DeviceExtenson + SRB + +Return Value: + + Nothing. + +--*/ + +{ + PCCB ccb = Srb->SrbExtension; + + DebugPrint((3,"BuildCcb: Enter routine\n")); + + // + // Zero out CCB statuses. + // + + ccb->CompletionStatus = 0; + ccb->ScsiDeviceStatus = 0; + + // + // Set transfer direction bit. + // + + if (Srb->SrbFlags & SRB_FLAGS_DATA_OUT) { + ccb->CommandFlags = DIRECTION_WRITE | SCATTER_GATHER; + } else if (Srb->SrbFlags & SRB_FLAGS_DATA_IN) { + ccb->CommandFlags = DIRECTION_READ | SCATTER_GATHER; + } else { + ccb->CommandFlags = 0; + } + + // + // Check if disconnect explicity forbidden. + // + + if (!(Srb->SrbFlags & SRB_FLAGS_DISABLE_DISCONNECT)) { + + ccb->CommandFlags |= DISCONNECTION; + } + + // + // Check if synchronous data transfers are allowed. + // + + if (!(Srb->SrbFlags & SRB_FLAGS_DISABLE_SYNCH_TRANSFER)) { + + ccb->CommandFlags |= SYNCHRONOUS_NEGOCIATION; + + } + + // + // Set target id and LUN. + // PathId is indicated in upper bits of command byte. + // + + ccb->TargetId = Srb->TargetId; + ccb->Lun = Srb->Lun; + + // + // Set CDB length and copy to CCB. + // + + ScsiPortMoveMemory(ccb->Cdb, Srb->Cdb, Srb->CdbLength); + + // + // Build SDL in CCB if data transfer. + // + + if (Srb->DataTransferLength > 0) { + BuildSdl(DeviceExtension, Srb); + } else { + ccb->DataBufferAddress = 0; + ccb->TransferCount = 0; + } + + return; + +} // end BuildCcb() + + +VOID +BuildSdl( + IN PHW_DEVICE_EXTENSION DeviceExtension, + IN PSCSI_REQUEST_BLOCK Srb + ) + +/*++ + +Routine Description: + + This routine builds a scatter/gather descriptor list for the CCB. + +Arguments: + + DeviceExtension + Srb + +Return Value: + + None + +--*/ + +{ + PVOID dataPointer = Srb->DataBuffer; + ULONG bytesLeft = Srb->DataTransferLength; + PCCB ccb = Srb->SrbExtension; + PSDL sdl = &ccb->Sdl; + ULONG physicalSdl; + ULONG physicalAddress; + ULONG length; + ULONG descriptorCount = 0; + + DebugPrint((3,"BuildSdl: Enter routine\n")); + + // + // Get physical SDL address. + // + + physicalSdl = ScsiPortConvertPhysicalAddressToUlong( + ScsiPortGetPhysicalAddress(DeviceExtension, NULL, + sdl, &length)); + + // + // Assume physical memory contiguous for sizeof(SDL) bytes. + // + + ASSERT(length >= sizeof(SDL)); + + // + // Create SDL segment descriptors. + // + + do { + + DebugPrint((3, "BuildSdl: Data buffer %lx\n", dataPointer)); + + // + // Get physical address and length of contiguous + // physical buffer. + // + + physicalAddress = + ScsiPortConvertPhysicalAddressToUlong( + ScsiPortGetPhysicalAddress(DeviceExtension, + Srb, + dataPointer, + &length)); + + DebugPrint((3, "BuildSdl: Physical address %lx\n", physicalAddress)); + DebugPrint((3, "BuildSdl: Data length %lx\n", length)); + DebugPrint((3, "BuildSdl: Bytes left %lx\n", bytesLeft)); + + // + // If length of physical memory is more + // than bytes left in transfer, use bytes + // left as final length. + // + + if (length > bytesLeft) { + length = bytesLeft; + } + + sdl->Descriptor[descriptorCount].Address = physicalAddress; + sdl->Descriptor[descriptorCount].Length = length; + + // + // Adjust counts. + // + + dataPointer = (PUCHAR)dataPointer + length; + bytesLeft -= length; + descriptorCount++; + + } while (bytesLeft); + + // + // Write SDL length to CCB. + // + + ccb->TransferCount = descriptorCount * sizeof(SG_DESCRIPTOR); + + DebugPrint((3,"BuildSdl: SDL length is %d\n", descriptorCount)); + + // + // Write SDL address to CCB. + // + + ccb->DataBufferAddress = physicalSdl; + + DebugPrint((3,"BuildSdl: SDL address is %lx\n", sdl)); + + DebugPrint((3,"BuildSdl: CCB address is %lx\n", ccb)); + + return; + +} // end BuildSdl() + + +BOOLEAN +Wd7000ExResetBus( + IN PVOID HwDeviceExtension, + IN ULONG PathId +) + +/*++ + +Routine Description: + + Reset Wd7000Ex SCSI adapter and SCSI bus. + +Arguments: + + HwDeviceExtension - HBA miniport driver's adapter data storage + +Return Value: + + Nothing. + +--*/ + +{ + PHW_DEVICE_EXTENSION deviceExtension = HwDeviceExtension; + PEISA_CONTROLLER eisaController = deviceExtension->EisaController; + UCHAR control; + ULONG i; + + DebugPrint((2,"ResetBus: Reset Wd7000Ex and SCSI bus\n")); + + // + // Disable system interrupts. + // + + ScsiPortWritePortUchar(&eisaController->SystemInterruptEnable, + SYSTEM_INTERRUPTS_DISABLE); + + // + // Set interrupt enable, SCSI bus reset bits in control register. + // + + control = ADAPTER_INTERRUPT_ENABLE; + control += PathId == 0 ? SCSI_BUS_RESET_CHANNEL_0 : SCSI_BUS_RESET_CHANNEL_1; + + ScsiPortWritePortUchar(&eisaController->ControlRegister, control); + + // + // Wait 30 microseconds. + // + + ScsiPortStallExecution(30); + + // + // Reset adapter and clear SCSI bus reset. + // + + ScsiPortWritePortUchar(&eisaController->ControlRegister, + ADAPTER_RESET + ADAPTER_INTERRUPT_ENABLE); + + // + // Wait for up to 2 seconds. + // + + for (i=0; i<20; i++) { + + // + // Stall 100 milliseconds. + // + + ScsiPortStallExecution(100 * 1000); + + // + // Check status. + // + + if (ScsiPortReadPortUchar(&eisaController->ResponseRegister) == + COMPLETE_SUCCESS) { + + break; + } + } + + if (i==20) { + + DebugPrint((1, "ResetBus: Reset failed\n")); + + ScsiPortLogError( + deviceExtension, + NULL, + 0, + deviceExtension->HostTargetId, + 0, + SP_INTERNAL_ADAPTER_ERROR, + 3 << 16 + ); + + return FALSE; + } + + // + // Acknowledge interrupt. + // + + ScsiPortWritePortUchar(&eisaController->ResponseRegister, 0xFF); + + // + // Enable system interrupts. + // + + ScsiPortWritePortUchar(&eisaController->SystemInterruptEnable, + SYSTEM_INTERRUPTS_ENABLE); + + // + // Enable response interrupt mask. + // + + ScsiPortWritePortUchar(&eisaController->ResponseInterruptMask, 0xFF); + + // + // Complete all outstanding requests with SRB_STATUS_BUS_RESET. + // + + ScsiPortCompleteRequest((PVOID) deviceExtension, + (UCHAR) PathId, + (UCHAR) 0xFF, + (UCHAR) 0xFF, + (UCHAR) SRB_STATUS_BUS_RESET); + + return TRUE; + +} // end Wd7000ExResetBus() + + +BOOLEAN +SendCommand( + IN UCHAR Opcode, + IN ULONG Address, + IN PHW_DEVICE_EXTENSION DeviceExtension + ) + +/*++ + +Routine Description: + + Send a command the the WD7000ex. + +Arguments: + + Opcode - Operation code to send. + + Address - Physical address for the operation. + + DeviceExtension - Pointer to device extension. + +Return Value: + + True if command sent. + False if adapter never reached 'ready for next command' state. + +--*/ + +{ + + PEISA_CONTROLLER Registers = DeviceExtension->EisaController; + ULONG i; + + for (i=0; i<1000; i++) { + + UCHAR status; + + status = ScsiPortReadPortUchar(&Registers->CommandRegister); + + if (status == 0) { + + // + // Adapter ready for next command. + // + + break; + + } else { + + // + // Stall 1 microsecond before trying again. + // + + ScsiPortStallExecution(1); + } + } + + if (i == 100) { + + // + // Timed out waiting for adapter to become ready. + // Do not complete this command. Instead, let it + // timeout so normal recovery will take place. + // + + ScsiPortLogError( + DeviceExtension, + NULL, + 0, + DeviceExtension->HostTargetId, + 0, + SP_INTERNAL_ADAPTER_ERROR, + 4 << 16 + ); + + return FALSE; + } + + // + // Write the address and opcode to adapter. + // + + ScsiPortWritePortUlong(&Registers->CommandMailbox, Address); + ScsiPortWritePortUchar(&Registers->CommandRegister, Opcode); + + return TRUE; + +} // end SendCommand() + |