NT4/private/ntos/miniport/wd7000ex/wd7000ex.c
2020-09-30 17:12:29 +02:00

1685 lines
36 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
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()