2365 lines
54 KiB
C
2365 lines
54 KiB
C
/*++
|
||
|
||
Copyright (c) 1991 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
oliesc1.c
|
||
|
||
Abstract:
|
||
|
||
This is the port driver for the Olivetti ESC-1 SCSI adapters.
|
||
|
||
Authors:
|
||
|
||
Bruno Sartirana (o-obruno) 13-Dec-1991
|
||
|
||
Environment:
|
||
|
||
kernel mode only
|
||
|
||
Notes:
|
||
|
||
Revision History:
|
||
|
||
12-Feb-1992: (o-obruno) Replaced calls to HAL and MM with calls to
|
||
ScsiPortGetDeviceBase and ScsiPortFreeDeviceBase.
|
||
|
||
8-Nov-1992: (o-obruno)
|
||
- Added error logging
|
||
- Removed adapter reset at initialization time
|
||
(it saves 2-3 secs.)
|
||
- Removed list of present peripherals
|
||
- Enhanced interrupt status check for better handling
|
||
of the ESC-2 interrupt status codes.
|
||
|
||
9-Apr-1993: (v-egidis)
|
||
- Removed the search for ESC-2 cards.
|
||
- Added call to ESC-2/EFP-2 driver init routine.
|
||
- Added code to claim the primary AT disk ctrl
|
||
if the AT mode is enabled.
|
||
- Now all the routine names start with the "OliEsc1".
|
||
- Removed all the "static" directives, this will
|
||
be very useful during the debugging sessions.
|
||
- Now if there is an error during the execution of
|
||
OliEsc1Configuration routine the SP_RETURN_ERROR
|
||
error code is returned instead of
|
||
SP_RETURN_NOT_FOUND.
|
||
- Now all the physical slots 1-15 are checked,
|
||
before only slots 1-7 were checked.
|
||
|
||
7-Jul-1993: (v-egidis)
|
||
- The reset has been changed to use the scsiport's
|
||
timer. This modification fixes the following
|
||
problem: the reset is taking too much DPC time.
|
||
|
||
|
||
--*/
|
||
|
||
#include "miniport.h"
|
||
#include "oliesc1.h" // includes scsi.h
|
||
|
||
|
||
//
|
||
// Function declarations
|
||
//
|
||
// Functions that start with 'OliEsc1' are entry points
|
||
// for the OS port driver.
|
||
//
|
||
|
||
VOID
|
||
OliEsc1BuildCcb(
|
||
IN PHW_DEVICE_EXTENSION DeviceExtension,
|
||
IN PSCSI_REQUEST_BLOCK Srb
|
||
);
|
||
|
||
VOID
|
||
OliEsc1BuildSgl(
|
||
IN PHW_DEVICE_EXTENSION DeviceExtension,
|
||
IN PSCSI_REQUEST_BLOCK Srb
|
||
);
|
||
|
||
ULONG
|
||
DriverEntry (
|
||
IN PVOID DriverObject,
|
||
IN PVOID Argument2
|
||
);
|
||
|
||
BOOLEAN
|
||
OliEsc1GetIrql(
|
||
IN PEISA_CONTROLLER eisaController,
|
||
PUCHAR Irql
|
||
);
|
||
|
||
BOOLEAN
|
||
OliEsc1CheckAtMode(
|
||
IN PEISA_CONTROLLER eisaController,
|
||
PBOOLEAN AtModeEnabled
|
||
);
|
||
|
||
ULONG
|
||
OliEsc1DriverEntry(
|
||
IN PVOID DriverObject,
|
||
IN PVOID Argument2
|
||
);
|
||
|
||
ULONG
|
||
OliEsc1Configuration(
|
||
IN PVOID HwDeviceExtension,
|
||
IN PVOID Context,
|
||
IN PVOID BusInformation,
|
||
IN PCHAR ArgumentString,
|
||
IN OUT PPORT_CONFIGURATION_INFORMATION ConfigInfo,
|
||
OUT PBOOLEAN Again
|
||
);
|
||
|
||
BOOLEAN
|
||
OliEsc1Initialize(
|
||
IN PVOID HwDeviceExtension
|
||
);
|
||
|
||
BOOLEAN
|
||
OliEsc1Interrupt(
|
||
IN PVOID HwDeviceExtension
|
||
);
|
||
|
||
BOOLEAN
|
||
OliEsc1ResetBus(
|
||
IN PVOID HwDeviceExtension,
|
||
IN ULONG PathId
|
||
);
|
||
|
||
BOOLEAN
|
||
OliEsc1StartIo(
|
||
IN PVOID HwDeviceExtension,
|
||
IN PSCSI_REQUEST_BLOCK Srb
|
||
);
|
||
|
||
BOOLEAN
|
||
OliEsc1SendCommand(
|
||
IN PHW_DEVICE_EXTENSION DeviceExtension,
|
||
IN UCHAR OperationCode,
|
||
IN PCCB ccb
|
||
);
|
||
|
||
BOOLEAN
|
||
OliEsc1SendCommandQuick(
|
||
IN PEISA_CONTROLLER EisaController,
|
||
IN UCHAR TaskId,
|
||
IN UCHAR OperationCode,
|
||
IN USHORT CommandLength,
|
||
IN ULONG Address
|
||
);
|
||
|
||
VOID
|
||
OliEsc1ResetAdapter(
|
||
IN PVOID Context
|
||
);
|
||
|
||
|
||
//
|
||
// External entry points
|
||
//
|
||
|
||
ULONG
|
||
OliEsc2DriverEntry(
|
||
IN PVOID DriverObject,
|
||
IN PVOID Argument2
|
||
);
|
||
|
||
|
||
VOID
|
||
OliEsc1BuildCcb(
|
||
IN PHW_DEVICE_EXTENSION DeviceExtension,
|
||
IN PSCSI_REQUEST_BLOCK Srb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Build a Command Control Block for the ESC-1.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension Pointer to the device extension for this driver.
|
||
Srb Pointer to the Scsi Request Block to service
|
||
|
||
Return Value:
|
||
|
||
Nothing.
|
||
|
||
--*/
|
||
|
||
{
|
||
PCCB ccb; // Virtual address of the CCB
|
||
ULONG physicalAddress; // Physical address of the CCB
|
||
ULONG length; // Length of contiguous memory in the
|
||
// data buffer, starting at the
|
||
// beginning of the buffer
|
||
|
||
DebugPrint((3,"OliEsc1BuildCcb: Enter routine\n"));
|
||
|
||
//
|
||
// Get the CCB address
|
||
//
|
||
|
||
ccb = Srb->SrbExtension;
|
||
|
||
//
|
||
// Set LUN and Target ID
|
||
//
|
||
|
||
ccb->TaskId = Srb->Lun | (UCHAR) (Srb->TargetId << CCB_TARGET_ID_SHIFT);
|
||
|
||
//
|
||
// We distinguish between the Abort command and all the others, because
|
||
// we translate the Abort into a Target Reset, which does not require
|
||
// a CCB. Since a Terget Reset doesn't imply any data transfer, we skip
|
||
// some proceessing here below, but we use a CCB anyway, so as to allow
|
||
// the interrupt service routine to complete the Target Reset request
|
||
// like any others, without special case handling.
|
||
//
|
||
|
||
if (Srb->Function != SRB_FUNCTION_ABORT_COMMAND) {
|
||
|
||
//
|
||
// Set transfer direction bit.
|
||
//
|
||
|
||
if (Srb->SrbFlags & SRB_FLAGS_DATA_OUT) {
|
||
|
||
//
|
||
// Adapter to system transfer
|
||
//
|
||
|
||
ccb->TaskId |= CCB_DATA_XFER_OUT;
|
||
|
||
} else if (Srb->SrbFlags & SRB_FLAGS_DATA_IN) {
|
||
|
||
//
|
||
// System to adapter transfer
|
||
//
|
||
|
||
ccb->TaskId |= CCB_DATA_XFER_IN;
|
||
|
||
} else ccb->TaskId |= CCB_DATA_XFER_NONE;
|
||
|
||
//
|
||
// Set the LinkedCommandAddress to NULL. It is not used by the ESC-1,
|
||
// but, for safety, it's better to set it.
|
||
//
|
||
|
||
ccb->LinkedCommandAddress = (ULONG) NULL;
|
||
|
||
//
|
||
// Copy the Command Descriptor Block (CDB) into the CCB.
|
||
//
|
||
|
||
ScsiPortMoveMemory(ccb->Cdb, Srb->Cdb, Srb->CdbLength);
|
||
|
||
DebugPrint((3,"OliEsc1BuildCcb: CDB at %lx, length=%x\n",
|
||
ccb->Cdb, Srb->CdbLength));
|
||
|
||
//
|
||
// Set the CDB length and the data transfer length in the CCB
|
||
//
|
||
|
||
ccb->CdbLength = (UCHAR) Srb->CdbLength;
|
||
ccb->DataLength = Srb->DataTransferLength;
|
||
|
||
//
|
||
// Build a actter/gather list in the CCB if necessary
|
||
//
|
||
|
||
if (Srb->DataTransferLength > 0) {
|
||
|
||
physicalAddress = ScsiPortConvertPhysicalAddressToUlong(
|
||
ScsiPortGetPhysicalAddress(DeviceExtension,
|
||
Srb,
|
||
Srb->DataBuffer,
|
||
&length));
|
||
|
||
//
|
||
// length contains the length of contiguous memory starting
|
||
// at Srb->DataBuffer
|
||
//
|
||
|
||
if (length >= Srb->DataTransferLength) {
|
||
|
||
//
|
||
// The Srb->DataBuffer is contiguous: no need of
|
||
// scatter/gather descriptors
|
||
//
|
||
|
||
ccb->DataAddress = physicalAddress;
|
||
ccb->AdditionalRequestBlockLength = 0;
|
||
|
||
} else {
|
||
|
||
//
|
||
// The Srb->DataBuffer is not contiguous: we need
|
||
// scatter/gather descriptors
|
||
//
|
||
|
||
OliEsc1BuildSgl(DeviceExtension, Srb);
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// No data transfer is requested
|
||
//
|
||
|
||
ccb->AdditionalRequestBlockLength = 0;
|
||
}
|
||
}
|
||
|
||
return;
|
||
|
||
} // end OliEsc1BuildCcb()
|
||
|
||
|
||
VOID
|
||
OliEsc1BuildSgl(
|
||
IN PHW_DEVICE_EXTENSION DeviceExtension,
|
||
IN PSCSI_REQUEST_BLOCK Srb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine builds a scatter/gather descriptor list for the Command
|
||
Control Block.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension Pointer to the device extension for this driver.
|
||
Srb Pointer to the Scsi Request Block to service
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG bytesLeft; // # of bytes left to be described
|
||
// in an SGL
|
||
PCCB ccb; // CCB address
|
||
PVOID dataPointer; // Pointer to the data buffer to send
|
||
ULONG descriptorCount; // # of scatter/gather descriptors
|
||
// built
|
||
ULONG length; // Length of contiguous memory in the
|
||
// data buffer, starting at a given
|
||
// offset
|
||
ULONG physicalAddress; // Physical address of the data buffer
|
||
ULONG physicalSgl; // Physical SGL address
|
||
PSGL sgl; // Virtual SGL address
|
||
|
||
|
||
DebugPrint((3,"OliEsc1BuildSgl: Enter routine\n"));
|
||
|
||
//
|
||
// Initialize some variables
|
||
//
|
||
|
||
dataPointer = Srb->DataBuffer;
|
||
bytesLeft = Srb->DataTransferLength;
|
||
ccb = Srb->SrbExtension;
|
||
sgl = &ccb->Sgl;
|
||
descriptorCount = 0;
|
||
|
||
//
|
||
// Get physical SGL address.
|
||
//
|
||
|
||
physicalSgl = ScsiPortConvertPhysicalAddressToUlong(
|
||
ScsiPortGetPhysicalAddress(DeviceExtension, NULL,
|
||
sgl, &length));
|
||
|
||
//
|
||
// Assume physical memory contiguous for sizeof(SGL) bytes.
|
||
//
|
||
|
||
ASSERT(length >= sizeof(SGL));
|
||
|
||
//
|
||
// Create SGL segment descriptors.
|
||
//
|
||
|
||
do {
|
||
|
||
DebugPrint((3,
|
||
"OliEsc1BuildSgl: Data buffer %lx\n", dataPointer));
|
||
|
||
//
|
||
// Get physical address and length of contiguous
|
||
// physical buffer.
|
||
//
|
||
|
||
physicalAddress = ScsiPortConvertPhysicalAddressToUlong(
|
||
ScsiPortGetPhysicalAddress(DeviceExtension,
|
||
Srb,
|
||
dataPointer,
|
||
&length));
|
||
|
||
DebugPrint((3, "OliEsc1BuildSgl: Physical address %lx\n",
|
||
physicalAddress));
|
||
DebugPrint((3, "OliEsc1BuildSgl: Data length %lx\n", length));
|
||
DebugPrint((3, "OliEsc1BuildSgl: 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;
|
||
}
|
||
|
||
sgl->Descriptor[descriptorCount].Address = physicalAddress;
|
||
sgl->Descriptor[descriptorCount].Length = length;
|
||
|
||
//
|
||
// Adjust counts.
|
||
//
|
||
|
||
dataPointer = (PUCHAR)dataPointer + length;
|
||
bytesLeft -= length;
|
||
descriptorCount++;
|
||
|
||
} while (bytesLeft);
|
||
|
||
//
|
||
// Write SGL length to CCB.
|
||
//
|
||
|
||
ccb->AdditionalRequestBlockLength = descriptorCount * sizeof(SG_DESCRIPTOR);
|
||
|
||
DebugPrint((3,"OliEsc1BuildSgl: SGL length is %d\n", descriptorCount));
|
||
|
||
//
|
||
// Write SGL address to CCB.
|
||
//
|
||
|
||
ccb->DataAddress = physicalSgl;
|
||
|
||
DebugPrint((3,"OliEsc1BuildSgl: SGL address is %lx\n", sgl));
|
||
|
||
DebugPrint((3,"OliEsc1BuildSgl: CCB address is %lx\n", ccb));
|
||
|
||
return;
|
||
|
||
} // end OliEsc1BuildSgl()
|
||
|
||
|
||
ULONG
|
||
DriverEntry (
|
||
IN PVOID DriverObject,
|
||
IN PVOID Argument2
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Installable driver initialization entry point for the OS.
|
||
|
||
Arguments:
|
||
|
||
Driver Object Pointer to the driver object for this driver
|
||
|
||
Return Value:
|
||
|
||
Status from OliEsc1DriverEntry()
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG Esc1Status, Esc2Status;
|
||
|
||
//
|
||
// Search for any ESC-1
|
||
//
|
||
|
||
Esc1Status = OliEsc1DriverEntry(DriverObject, Argument2);
|
||
|
||
//
|
||
// Search for any ESC-2 and EFP-2
|
||
//
|
||
|
||
Esc2Status = OliEsc2DriverEntry(DriverObject, Argument2);
|
||
|
||
//
|
||
// The driver should return the lowest status
|
||
//
|
||
|
||
return MIN( Esc1Status, Esc2Status );
|
||
|
||
} // end DriverEntry()
|
||
|
||
|
||
BOOLEAN
|
||
OliEsc1GetIrql(
|
||
IN PEISA_CONTROLLER eisaController,
|
||
PUCHAR Irql
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
It reads the ESC-1's IRQL. It is assumed to be called at system
|
||
initialization time only, since it uses polling.
|
||
|
||
Arguments:
|
||
|
||
|
||
Return Value:
|
||
|
||
TRUE Success
|
||
FALSE Failure
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
BOOLEAN Success; // Return value
|
||
UCHAR IntMask; // Current System Doorbell interrupt mask value
|
||
ULONG i; // Auxiliary variable
|
||
|
||
|
||
|
||
//
|
||
// Get the current System Doorbell Interrupt Mask
|
||
//
|
||
|
||
IntMask = ScsiPortReadPortUchar(&eisaController->SystemDoorBellMask);
|
||
|
||
//
|
||
// Disable ESC-1 interrupts
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask,
|
||
INTERRUPTS_DISABLE);
|
||
|
||
|
||
//
|
||
// Get the ESC-1 Irql
|
||
//
|
||
|
||
Success = OliEsc1SendCommandQuick(eisaController,
|
||
CCB_DATA_XFER_NONE,
|
||
GET_CONFIGURATION,
|
||
IRQL_REGISTER,
|
||
(ULONG) NULL);
|
||
|
||
if (Success) {
|
||
|
||
i = 0;
|
||
|
||
//
|
||
// Poll interrupt pending bit
|
||
//
|
||
|
||
while (!(ScsiPortReadPortUchar(&eisaController->SystemDoorBell) &
|
||
INTERRUPT_PENDING) && i < INTERRUPT_POLLING_TIME) {
|
||
|
||
ScsiPortStallExecution(1);
|
||
i++;
|
||
|
||
}
|
||
|
||
DebugPrint((4, "OliEsc1GetIrql: got INT after %ld us\n", i));
|
||
|
||
if (i < INTERRUPT_POLLING_TIME) {
|
||
|
||
//
|
||
// Sensed the INTERRUPT_PENDING bit. Reset the interrupt pending.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBell,
|
||
INTERRUPT_PENDING);
|
||
|
||
//
|
||
// Check to see whether the command completed correctly
|
||
//
|
||
|
||
if ((UCHAR)
|
||
((ScsiPortReadPortUshort(&eisaController->Status) >> 8)) ==
|
||
NO_ERROR) {
|
||
|
||
Success = TRUE;
|
||
|
||
//
|
||
// The IRQL value is available in the OutAddress mailbox
|
||
// register, in the low byte
|
||
//
|
||
|
||
switch((UCHAR) ScsiPortReadPortUlong(
|
||
&eisaController->OutAddress)) {
|
||
|
||
case 0: *Irql = (KIRQL) 11;
|
||
break;
|
||
case 1: *Irql = (KIRQL) 10;
|
||
break;
|
||
case 2: *Irql = (KIRQL) 5;
|
||
break;
|
||
case 3: *Irql = (KIRQL) 15;
|
||
break;
|
||
default: Success = FALSE;
|
||
}
|
||
|
||
//
|
||
// Unlock the Result Semaphore, so that the ESC-1 can load
|
||
// new values in the output mailboxes.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->ResultSemaphore,
|
||
SEM_UNLOCK);
|
||
|
||
} else Success = FALSE;
|
||
|
||
} else Success = FALSE;
|
||
}
|
||
|
||
//
|
||
// Restore the original interrupt mask
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask, IntMask);
|
||
|
||
return Success;
|
||
|
||
} // end OliEsc1GetIrql
|
||
|
||
|
||
BOOLEAN
|
||
OliEsc1CheckAtMode(
|
||
IN PEISA_CONTROLLER eisaController,
|
||
PBOOLEAN AtModeEnabled
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine checks if this board has the AT compatible mode enabled.
|
||
|
||
Arguments:
|
||
|
||
eisaController I/O address of ESC-1 controller.
|
||
AtModeEnabled pointer to a variable. This variable is
|
||
set to FALSE if the AT mode is not enable,
|
||
and to TRUE otherwise.
|
||
|
||
Return Value:
|
||
|
||
TRUE Success
|
||
FALSE Failure
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
BOOLEAN Success; // Return value
|
||
UCHAR IntMask; // Current System Doorbell interrupt mask value
|
||
ULONG i; // Auxiliary variable
|
||
|
||
//
|
||
// Get the current System Doorbell Interrupt Mask
|
||
//
|
||
|
||
IntMask = ScsiPortReadPortUchar(&eisaController->SystemDoorBellMask);
|
||
|
||
//
|
||
// Disable ESC-1 interrupts
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask,
|
||
INTERRUPTS_DISABLE);
|
||
|
||
|
||
//
|
||
// Get the ESC-1 Hard Disk Configuration value
|
||
//
|
||
|
||
Success = OliEsc1SendCommandQuick(eisaController,
|
||
CCB_DATA_XFER_NONE,
|
||
GET_CONFIGURATION,
|
||
ATCFG_REGISTER,
|
||
(ULONG) NULL);
|
||
|
||
if (Success) {
|
||
|
||
i = 0;
|
||
|
||
//
|
||
// Poll interrupt pending bit
|
||
//
|
||
|
||
while (!(ScsiPortReadPortUchar(&eisaController->SystemDoorBell) &
|
||
INTERRUPT_PENDING) && i < INTERRUPT_POLLING_TIME) {
|
||
|
||
ScsiPortStallExecution(1);
|
||
i++;
|
||
|
||
}
|
||
|
||
DebugPrint((4, "OliEsc1CheckAtMode: got INT after %ld us\n", i));
|
||
|
||
if (i < INTERRUPT_POLLING_TIME) {
|
||
|
||
//
|
||
// Sensed the INTERRUPT_PENDING bit. Reset the interrupt pending.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBell,
|
||
INTERRUPT_PENDING);
|
||
|
||
//
|
||
// Check to see whether the command completed correctly
|
||
//
|
||
|
||
if ((UCHAR)
|
||
((ScsiPortReadPortUshort(&eisaController->Status) >> 8)) ==
|
||
NO_ERROR) {
|
||
|
||
Success = TRUE;
|
||
|
||
//
|
||
// The AT info is available in the OutAddress mailbox
|
||
// register, in the low byte
|
||
//
|
||
|
||
if (ScsiPortReadPortUlong(&eisaController->OutAddress)& 0x01) {
|
||
|
||
*AtModeEnabled = TRUE;
|
||
}
|
||
else {
|
||
|
||
*AtModeEnabled = FALSE;
|
||
}
|
||
|
||
//
|
||
// Unlock the Result Semaphore, so that the ESC-1 can load
|
||
// new values in the output mailboxes.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->ResultSemaphore,
|
||
SEM_UNLOCK);
|
||
|
||
} else Success = FALSE;
|
||
|
||
} else Success = FALSE;
|
||
}
|
||
|
||
//
|
||
// Restore the original interrupt mask
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask, IntMask);
|
||
|
||
return Success;
|
||
|
||
} // end OliEsc1CheckAtMode
|
||
|
||
|
||
|
||
ULONG
|
||
OliEsc1DriverEntry(
|
||
IN PVOID DriverObject,
|
||
IN PVOID Argument2
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is called from DriverEntry(). It initializes some fields
|
||
in a data structure of type HW_INITIALIZATION_DATA for use by the port
|
||
driver.
|
||
|
||
Arguments:
|
||
|
||
DriverObject Address of the context to pass to ScsiPortInitialize
|
||
|
||
Return Value:
|
||
|
||
Status from ScsiPortInitialize()
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
HW_INITIALIZATION_DATA hwInitializationData;
|
||
// Structure used to tell the upper
|
||
// layer the entry points of this
|
||
// driver
|
||
ULONG i; // Auxiliary variable
|
||
ULONG adapterCount = 0; // Indicates the slot which have been
|
||
// check for adapters.
|
||
|
||
DebugPrint((1,"\n\nSCSI ESC-1 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 = OliEsc1Initialize;
|
||
hwInitializationData.HwFindAdapter = OliEsc1Configuration;
|
||
hwInitializationData.HwStartIo = OliEsc1StartIo;
|
||
hwInitializationData.HwInterrupt = OliEsc1Interrupt;
|
||
hwInitializationData.HwResetBus = OliEsc1ResetBus;
|
||
|
||
//
|
||
// Set number of access ranges and the bus type.
|
||
//
|
||
|
||
hwInitializationData.NumberOfAccessRanges = 1;
|
||
hwInitializationData.AdapterInterfaceType = Eisa;
|
||
|
||
//
|
||
// The ESC-1 supports tagged queuing
|
||
//
|
||
|
||
hwInitializationData.TaggedQueuing = TRUE;
|
||
|
||
//
|
||
// Indicate no buffer mapping but will need physical
|
||
// addresses.
|
||
//
|
||
|
||
hwInitializationData.NeedPhysicalAddresses = TRUE;
|
||
|
||
//
|
||
// Specify size of extensions.
|
||
//
|
||
|
||
hwInitializationData.DeviceExtensionSize = sizeof(HW_DEVICE_EXTENSION);
|
||
hwInitializationData.SpecificLuExtensionSize = sizeof(LU_EXTENSION);
|
||
|
||
//
|
||
// Ask for SRB extensions for CCBs.
|
||
//
|
||
|
||
hwInitializationData.SrbExtensionSize = sizeof(CCB);
|
||
|
||
DebugPrint((1,
|
||
"OliEsc1DriverEntry: hwInitializationData address %lx\n",
|
||
&hwInitializationData));
|
||
|
||
return(ScsiPortInitialize(DriverObject, Argument2, &hwInitializationData, &adapterCount));
|
||
|
||
} // end OliEsc1DriverEntry()
|
||
|
||
|
||
ULONG
|
||
OliEsc1Configuration(
|
||
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.
|
||
The EISA bus is scanned in search for an ESC-1 or an ESC-2 board.
|
||
ES: The search for an ESC-2 has been removed.
|
||
|
||
Arguments:
|
||
|
||
HwDeviceExtension Device extension for this driver
|
||
Context ESC-1 registers' address space
|
||
ConfigInfo Configuration information structure describing
|
||
the board configuration
|
||
|
||
Return Value:
|
||
|
||
TRUE if adapter present in system
|
||
|
||
--*/
|
||
|
||
{
|
||
PHW_DEVICE_EXTENSION deviceExtension; // Pointer to the device extension
|
||
// for this driver
|
||
PEISA_CONTROLLER eisaController; // Base address of the ESC-1
|
||
// registers' address space
|
||
ULONG eisaSlotNumber; // Auxiliary variable
|
||
// in case of initialization failure
|
||
BOOLEAN Success = FALSE; // Indicates an adapter was found.
|
||
PULONG adapterCount = Context; // Indicates which slots have been
|
||
// checked.
|
||
|
||
deviceExtension = HwDeviceExtension;
|
||
|
||
//
|
||
// Check to see if an adapter is present in the system
|
||
//
|
||
|
||
for (eisaSlotNumber = *adapterCount + 1;
|
||
eisaSlotNumber < MAX_EISA_SLOTS_STD; eisaSlotNumber++) {
|
||
|
||
//
|
||
// Update the adpater count to indicate this slot has been checked.
|
||
//
|
||
|
||
(*adapterCount)++;
|
||
|
||
//
|
||
// Get the system physical address for this card.
|
||
// The card uses I/O space.
|
||
//
|
||
|
||
eisaController = ScsiPortGetDeviceBase(
|
||
deviceExtension,
|
||
ConfigInfo->AdapterInterfaceType,
|
||
ConfigInfo->SystemIoBusNumber,
|
||
ScsiPortConvertUlongToPhysicalAddress(0x1000 * eisaSlotNumber),
|
||
0x1000,
|
||
(BOOLEAN) TRUE);
|
||
|
||
eisaController =
|
||
(PEISA_CONTROLLER)((PUCHAR)eisaController + EISA_ADDRESS_BASE);
|
||
|
||
//
|
||
// Read the EISA board ID and check to see whether it identifies
|
||
// an ESC-1
|
||
//
|
||
|
||
if ((ScsiPortReadPortUchar(&eisaController->BoardId[0]) == 0x3D) &&
|
||
(ScsiPortReadPortUchar(&eisaController->BoardId[1]) == 0x89) &&
|
||
(ScsiPortReadPortUchar(&eisaController->BoardId[2]) == 0x10) &&
|
||
(ScsiPortReadPortUchar(&eisaController->BoardId[3]) == 0x21)) {
|
||
|
||
DebugPrint((1,"ESC-1 Adapter found at EISA slot %d\n",
|
||
eisaSlotNumber));
|
||
|
||
//
|
||
// Immediately disable system interrupts (bellinte).
|
||
// They will remain disabled (polling mode only) during
|
||
// the EFP initialization sequence.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemIntEnable,
|
||
INTERRUPTS_DISABLE);
|
||
|
||
//
|
||
// The adpater is not reset and assumed to be functioning.
|
||
// Resetting the adapter is particularly time consuming (2 secs.
|
||
// for the ESC-1, 3.1 secs. for the ESC-2). The BIOS on x86
|
||
// computers or the EISA Support F/W on the M700 computers have
|
||
// already reset the adapter and checked its status.
|
||
//
|
||
|
||
//
|
||
// Reset System Doorbell interrupts
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBell, 0x0FF);
|
||
|
||
//
|
||
// Enable the ESC-1 High Performance interrupt.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask,
|
||
INTERRUPTS_ENABLE);
|
||
|
||
Success = TRUE;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// In the current slot there isn't an ESC-1/ESC-2 card. Try next
|
||
// slot.
|
||
//
|
||
|
||
ScsiPortFreeDeviceBase(deviceExtension,
|
||
(PUCHAR)eisaController - EISA_ADDRESS_BASE);
|
||
|
||
} // end for (eisaSlotNumber ...
|
||
|
||
if (!Success) {
|
||
|
||
//
|
||
// No adapter was found. Clear the call again flag, reset the adapter
|
||
// count for the next bus and return.
|
||
//
|
||
|
||
*Again = FALSE;
|
||
*adapterCount = 0;
|
||
return(SP_RETURN_NOT_FOUND);
|
||
}
|
||
|
||
//
|
||
// There are more slots to search so call again.
|
||
//
|
||
|
||
*Again = TRUE;
|
||
|
||
//
|
||
// Store base address of EISA registers in device extension.
|
||
//
|
||
|
||
deviceExtension->EisaController = eisaController;
|
||
|
||
//
|
||
// Reset the "ResetInProgress" variable.
|
||
//
|
||
|
||
deviceExtension->ResetInProgress = 0;
|
||
|
||
//
|
||
// Indicate the SCSI ID of the ESC-x, that's always 7
|
||
//
|
||
|
||
ConfigInfo->InitiatorBusId[0] = ADAPTER_ID;
|
||
|
||
//
|
||
// Indicate the maximum transfer length in bytes.
|
||
//
|
||
|
||
ConfigInfo->MaximumTransferLength = MAXIMUM_TRANSFER_SIZE;
|
||
|
||
//
|
||
// Indicate the maximum number of physical segments
|
||
//
|
||
|
||
ConfigInfo->NumberOfPhysicalBreaks = MAXIMUM_SGL_DESCRIPTORS;
|
||
|
||
ConfigInfo->ScatterGather = TRUE;
|
||
ConfigInfo->Master = TRUE;
|
||
ConfigInfo->NumberOfBuses = 1;
|
||
|
||
//
|
||
// Get the "AtdiskPrimaryClaimed" info
|
||
//
|
||
|
||
if (!OliEsc1CheckAtMode(deviceExtension->EisaController,
|
||
&ConfigInfo->AtdiskPrimaryClaimed)) {
|
||
|
||
DebugPrint((1,
|
||
"OliEsc1Configuration: Adapter initialization error.\n"));
|
||
|
||
ScsiPortLogError(
|
||
deviceExtension,
|
||
NULL,
|
||
0,
|
||
0,
|
||
0,
|
||
SP_INTERNAL_ADAPTER_ERROR,
|
||
ESCX_INIT_FAILED
|
||
);
|
||
|
||
return SP_RETURN_ERROR;
|
||
}
|
||
|
||
//
|
||
// Indicate which interrupt mode the adapter uses
|
||
//
|
||
|
||
if (ScsiPortReadPortUchar(&eisaController->GlobalConfiguration) &
|
||
EDGE_SENSITIVE) {
|
||
|
||
ConfigInfo->InterruptMode = Latched;
|
||
|
||
} else {
|
||
|
||
ConfigInfo->InterruptMode = LevelSensitive;
|
||
}
|
||
|
||
//
|
||
// Fill in the access array information.
|
||
//
|
||
|
||
(*ConfigInfo->AccessRanges)[0].RangeStart = ScsiPortConvertUlongToPhysicalAddress(0x1000 * eisaSlotNumber + EISA_ADDRESS_BASE);
|
||
(*ConfigInfo->AccessRanges)[0].RangeLength = sizeof(EISA_CONTROLLER);
|
||
(*ConfigInfo->AccessRanges)[0].RangeInMemory = FALSE;
|
||
|
||
//
|
||
// Get the ESC-x IRQL.
|
||
//
|
||
|
||
if (OliEsc1GetIrql(deviceExtension->EisaController,
|
||
(UCHAR *) &ConfigInfo->BusInterruptLevel)) {
|
||
|
||
return SP_RETURN_FOUND;
|
||
|
||
} else {
|
||
|
||
DebugPrint((1,
|
||
"OliEsc1Configuration: Adapter initialization error.\n"));
|
||
|
||
ScsiPortLogError(
|
||
deviceExtension,
|
||
NULL,
|
||
0,
|
||
0,
|
||
0,
|
||
SP_INTERNAL_ADAPTER_ERROR,
|
||
ESCX_INIT_FAILED
|
||
);
|
||
|
||
return SP_RETURN_ERROR;
|
||
}
|
||
|
||
} // end OliEsc1Configuration()
|
||
|
||
|
||
BOOLEAN
|
||
OliEsc1Initialize(
|
||
IN PVOID HwDeviceExtension
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
It does nothing (for now).
|
||
|
||
Arguments:
|
||
|
||
HwDeviceExtension Device extension for this driver
|
||
|
||
Return Value:
|
||
|
||
Always TRUE.
|
||
|
||
--*/
|
||
|
||
{
|
||
PHW_DEVICE_EXTENSION deviceExtension; // Pointer to the device extension
|
||
// for this driver
|
||
PEISA_CONTROLLER eisaController; // Base address of the ESC-1
|
||
|
||
deviceExtension = HwDeviceExtension;
|
||
eisaController = deviceExtension->EisaController;
|
||
|
||
//
|
||
// Make sure that the ESC-1 High Performance interrupt is enabled.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask,
|
||
INTERRUPTS_ENABLE);
|
||
|
||
//
|
||
// Enable system interrupts (bellinte).
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemIntEnable,
|
||
SYSTEM_INTS_ENABLE);
|
||
|
||
return TRUE;
|
||
|
||
} // end OliEsc1Initialize()
|
||
|
||
|
||
BOOLEAN
|
||
OliEsc1Interrupt(
|
||
IN PVOID HwDeviceExtension
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the interrupt service routine for the ESC-1 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 an outuput mailbox is full,
|
||
the CCB is retrieved to complete the request.
|
||
|
||
NOTE: if the semaphore 1 is used, it must be released after resetting
|
||
the associated interrupt !
|
||
|
||
|
||
Arguments:
|
||
|
||
HwDeviceExtension Device extention for this driver
|
||
|
||
Return Value:
|
||
|
||
TRUE if the interrupt was expected
|
||
|
||
--*/
|
||
|
||
{
|
||
PCCB ccb;
|
||
PHW_DEVICE_EXTENSION deviceExtension;
|
||
PEISA_CONTROLLER eisaController;
|
||
USHORT interruptStatus;
|
||
ULONG physicalCcb;
|
||
PLU_EXTENSION LuExtension;
|
||
UCHAR Lun;
|
||
PSCSI_REQUEST_BLOCK srb;
|
||
|
||
deviceExtension = HwDeviceExtension;
|
||
eisaController = deviceExtension->EisaController;
|
||
|
||
//
|
||
// Disable interrupts to diminish the chance for other CPUs to "spin-lock"
|
||
// for the same interrupt vector (multi-processor environment with dynamic
|
||
// interrupt dispatching).
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask,
|
||
INTERRUPTS_DISABLE);
|
||
|
||
//
|
||
// Check interrupt pending.
|
||
//
|
||
|
||
if (!(ScsiPortReadPortUchar(&eisaController->SystemDoorBell) &
|
||
INTERRUPT_PENDING)) {
|
||
|
||
//
|
||
// No interrupt is pending.
|
||
// Enable interrupts.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask,
|
||
INTERRUPTS_ENABLE);
|
||
|
||
DebugPrint((2, "OliEsc1Interrupt: Unrecognized interrupt\n"));
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Read interrupt status. The high byte is the adapter status, whereas
|
||
// the low byte is the device status. If the device status is not zero,
|
||
// an error will be returned, regardless of the adapter status.
|
||
//
|
||
|
||
interruptStatus = ScsiPortReadPortUshort(&eisaController->Status);
|
||
|
||
//
|
||
// Get physical address of CCB.
|
||
//
|
||
|
||
physicalCcb = ScsiPortReadPortUlong(&eisaController->OutAddress);
|
||
|
||
//
|
||
// Acknowledge interrupt.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBell, INTERRUPT_RESET);
|
||
|
||
//
|
||
// Unlock the Result Semaphore, so that the ESC-1 can load
|
||
// new values in the output mailboxes.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->ResultSemaphore, SEM_UNLOCK);
|
||
|
||
//
|
||
// Enable interrupts
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask,
|
||
INTERRUPTS_ENABLE);
|
||
|
||
|
||
//
|
||
// Get virtual CCB address.
|
||
//
|
||
|
||
ccb = ScsiPortGetVirtualAddress(deviceExtension, ScsiPortConvertUlongToPhysicalAddress(physicalCcb));
|
||
|
||
//
|
||
// Make sure the virtual address was found.
|
||
//
|
||
|
||
if (ccb == NULL) {
|
||
|
||
//
|
||
// A bad physcial address was return by the adapter.
|
||
// Log it as an error.
|
||
//
|
||
|
||
ScsiPortLogError(
|
||
deviceExtension,
|
||
NULL,
|
||
0,
|
||
ADAPTER_ID,
|
||
0,
|
||
SP_INTERNAL_ADAPTER_ERROR,
|
||
ESC1_BAD_PHYSICAL_ADDRESS | (ULONG) interruptStatus
|
||
);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// Get SRB from CCB.
|
||
//
|
||
|
||
srb = ccb->SrbAddress;
|
||
|
||
DebugPrint((5,
|
||
"OliEsc1Interrupt: ccb = %lx, srb = %lx, Int Status = %x\n",
|
||
ccb, srb, interruptStatus));
|
||
|
||
//
|
||
// Check the adapter status.
|
||
//
|
||
|
||
switch (interruptStatus >> 8) {
|
||
|
||
case NO_ERROR:
|
||
|
||
//
|
||
// Check the device status.
|
||
//
|
||
|
||
if ((interruptStatus & 0xff) != NO_ERROR) {
|
||
|
||
//
|
||
// The device status is not ok: return an error. This allows
|
||
// the class driver to detect a media change on a removable disk
|
||
// unit.
|
||
//
|
||
|
||
DebugPrint((1, "OliEsc1Interrupt: Status = %x\n",
|
||
interruptStatus));
|
||
srb->SrbStatus = SRB_STATUS_ERROR;
|
||
srb->ScsiStatus = (UCHAR) (interruptStatus & 0xff);
|
||
} else {
|
||
srb->SrbStatus = SRB_STATUS_SUCCESS;
|
||
srb->ScsiStatus = SCSISTAT_GOOD;
|
||
}
|
||
break;
|
||
|
||
|
||
case SELECTION_TIMEOUT_EXPIRED:
|
||
|
||
srb->SrbStatus = SRB_STATUS_SELECTION_TIMEOUT;
|
||
break;
|
||
|
||
case DATA_OVERRUN_UNDERRUN:
|
||
|
||
srb->SrbStatus = SRB_STATUS_DATA_OVERRUN;
|
||
|
||
//
|
||
// On the ESC-1 it is not possible to distinguish the overrun error
|
||
// from the underrun error.
|
||
// We don't log the error because the underrun error can be very
|
||
// common on some devices (example: scanner).
|
||
//
|
||
// ScsiPortLogError(
|
||
// deviceExtension,
|
||
// NULL,
|
||
// 0,
|
||
// ADAPTER_ID,
|
||
// 0,
|
||
// SP_INTERNAL_ADAPTER_ERROR,
|
||
// DATA_OVERRUN_UNDERRUN
|
||
// );
|
||
//
|
||
|
||
break;
|
||
|
||
case UNEXPECTED_BUS_FREE:
|
||
|
||
srb->SrbStatus = SRB_STATUS_UNEXPECTED_BUS_FREE;
|
||
break;
|
||
|
||
case SCSI_PHASE_SEQUENCE_FAILURE:
|
||
|
||
srb->SrbStatus = SRB_STATUS_PHASE_SEQUENCE_FAILURE;
|
||
break;
|
||
|
||
case QUEUE_FULL:
|
||
|
||
srb->SrbStatus = SRB_STATUS_BUSY;
|
||
break;
|
||
|
||
case PARITY_ERROR:
|
||
|
||
//
|
||
// This is a severe error. The controller is now shut down.
|
||
//
|
||
|
||
srb->SrbStatus = SRB_STATUS_PARITY_ERROR;
|
||
|
||
ScsiPortLogError(
|
||
deviceExtension,
|
||
NULL,
|
||
0,
|
||
ADAPTER_ID,
|
||
0,
|
||
SP_BUS_PARITY_ERROR,
|
||
0
|
||
);
|
||
break;
|
||
|
||
case PROTOCOL_ERROR:
|
||
|
||
//
|
||
// Return bus reset error, because the bus has been reset.
|
||
//
|
||
|
||
srb->SrbStatus = SRB_STATUS_BUS_RESET;
|
||
|
||
ScsiPortLogError(
|
||
deviceExtension,
|
||
NULL,
|
||
0,
|
||
ADAPTER_ID,
|
||
0,
|
||
SP_PROTOCOL_ERROR,
|
||
0
|
||
);
|
||
break;
|
||
|
||
case BUS_RESET_BY_TARGET:
|
||
|
||
//
|
||
// No error logging.
|
||
//
|
||
// Return bus reset error, because the bus has been reset.
|
||
//
|
||
|
||
srb->SrbStatus = SRB_STATUS_BUS_RESET;
|
||
break;
|
||
|
||
case UNEXPECTED_PHASE_CHANGE:
|
||
|
||
//
|
||
// This is a severe error. The controller is now shut down.
|
||
//
|
||
|
||
case PARITY_ERROR_DURING_DATA_PHASE:
|
||
case AUTO_REQUEST_SENSE_FAILURE:
|
||
case NO_REQUEST_SENSE_ISSUED:
|
||
case INVALID_CONFIGURATION_COMMAND:
|
||
case INVALID_CONFIGURATION_REGISTER:
|
||
case INVALID_COMMAND:
|
||
|
||
srb->SrbStatus = SRB_STATUS_ERROR;
|
||
srb->ScsiStatus = SCSISTAT_GOOD;
|
||
|
||
ScsiPortLogError(
|
||
deviceExtension,
|
||
NULL,
|
||
0,
|
||
ADAPTER_ID,
|
||
0,
|
||
SP_INTERNAL_ADAPTER_ERROR,
|
||
interruptStatus
|
||
);
|
||
break;
|
||
|
||
default:
|
||
|
||
DebugPrint((1,
|
||
"OliEsc1Interrupt: Unrecognized interrupt status %x\n",
|
||
interruptStatus));
|
||
|
||
srb->SrbStatus = SRB_STATUS_ERROR;
|
||
srb->ScsiStatus = SCSISTAT_GOOD;
|
||
|
||
ScsiPortLogError(
|
||
deviceExtension,
|
||
NULL,
|
||
0,
|
||
ADAPTER_ID,
|
||
0,
|
||
SP_INTERNAL_ADAPTER_ERROR,
|
||
interruptStatus
|
||
);
|
||
|
||
} // end switch
|
||
|
||
|
||
if (srb->Function == SRB_FUNCTION_ABORT_COMMAND ||
|
||
srb->Function == SRB_FUNCTION_RESET_DEVICE) {
|
||
|
||
if (srb->Function == SRB_FUNCTION_RESET_DEVICE) {
|
||
|
||
//
|
||
// Call notification routine for the SRB.
|
||
//
|
||
|
||
ScsiPortNotification(RequestComplete, (PVOID)deviceExtension, srb);
|
||
|
||
}
|
||
|
||
//
|
||
// The interrupt refers to a Target Reset command issued
|
||
// instead of the unsupported Abort command or to a real one.
|
||
// All the pending requests for the target have to be completed
|
||
// with status SRB_STATUS_BUS_RESET (any better idea?).
|
||
//
|
||
|
||
ScsiPortCompleteRequest(deviceExtension,
|
||
(UCHAR) 0,
|
||
srb->TargetId,
|
||
(UCHAR) ALL_LUNS,
|
||
(UCHAR) SRB_STATUS_BUS_RESET);
|
||
|
||
//
|
||
// Reset all the pending request counters for the target
|
||
//
|
||
|
||
for (Lun = 0; Lun < 8; Lun++) {
|
||
LuExtension = ScsiPortGetLogicalUnit(deviceExtension,
|
||
0,
|
||
srb->TargetId,
|
||
Lun);
|
||
|
||
if (LuExtension != NULL) {
|
||
LuExtension->NumberOfPendingRequests = 0;
|
||
}
|
||
}
|
||
} else {
|
||
|
||
//
|
||
// Decrement the pending requests counter for this (targetId, LUN)
|
||
// pair
|
||
//
|
||
|
||
LuExtension = ScsiPortGetLogicalUnit(deviceExtension,
|
||
0,
|
||
srb->TargetId,
|
||
srb->Lun);
|
||
ASSERT(LuExtension);
|
||
LuExtension->NumberOfPendingRequests--;
|
||
ASSERT (LuExtension->NumberOfPendingRequests >= 0);
|
||
|
||
//
|
||
// Call notification routine for the SRB.
|
||
//
|
||
|
||
ScsiPortNotification(RequestComplete, (PVOID)deviceExtension, srb);
|
||
}
|
||
|
||
return TRUE;
|
||
|
||
} // end OliEsc1Interrupt()
|
||
|
||
|
||
BOOLEAN
|
||
OliEsc1ResetBus(
|
||
IN PVOID HwDeviceExtension,
|
||
IN ULONG PathId
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Reset ESC-1 SCSI adapter and SCSI bus.
|
||
|
||
Arguments:
|
||
|
||
HwDeviceExtension Device extension for this driver
|
||
PathId SCSI Bus path ID (always 0 for the ESC-1)
|
||
|
||
Return Value:
|
||
|
||
Nothing.
|
||
|
||
--*/
|
||
|
||
{
|
||
PHW_DEVICE_EXTENSION deviceExtension;
|
||
PEISA_CONTROLLER eisaController;
|
||
PLU_EXTENSION LuExtension;
|
||
UCHAR Lun;
|
||
UCHAR TargetId;
|
||
|
||
UNREFERENCED_PARAMETER(PathId);
|
||
|
||
DebugPrint((2,"OliEsc1ResetBus: Reset ESC-1 and SCSI bus\n"));
|
||
|
||
//
|
||
// Get the ESC-1 registers' base address
|
||
//
|
||
|
||
deviceExtension = HwDeviceExtension;
|
||
eisaController = deviceExtension->EisaController;
|
||
|
||
//
|
||
// If the reset is already in progress, return TRUE.
|
||
// This should never happen!
|
||
//
|
||
|
||
if (deviceExtension->ResetInProgress) {
|
||
|
||
DebugPrint((2,"OliEsc1ResetBus: The reset is already in progess.\n"));
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// Issue a board reset
|
||
//
|
||
|
||
OliEsc1ResetAdapter(deviceExtension);
|
||
|
||
//
|
||
// Complete all outstanding requests with SRB_STATUS_BUS_RESET
|
||
//
|
||
|
||
ScsiPortCompleteRequest(deviceExtension,
|
||
(UCHAR) 0,
|
||
(UCHAR) ALL_TARGET_IDS,
|
||
(UCHAR) ALL_LUNS,
|
||
(UCHAR) SRB_STATUS_BUS_RESET);
|
||
|
||
//
|
||
// Reset to zero all the pending request counters
|
||
//
|
||
|
||
for (TargetId = 0; TargetId < 8; TargetId++) {
|
||
for (Lun = 0; Lun < 8; Lun++) {
|
||
LuExtension = ScsiPortGetLogicalUnit(deviceExtension,
|
||
0,
|
||
TargetId,
|
||
Lun);
|
||
|
||
if (LuExtension != NULL) {
|
||
LuExtension->NumberOfPendingRequests = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Send a "reset detected" notification.
|
||
//
|
||
|
||
ScsiPortNotification(ResetDetected, deviceExtension, 0);
|
||
|
||
return TRUE;
|
||
|
||
} // end Oliesc1ResetBus
|
||
|
||
BOOLEAN
|
||
OliEsc1StartIo(
|
||
IN PVOID HwDeviceExtension,
|
||
IN PSCSI_REQUEST_BLOCK Srb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is called from the SCSI port driver synchronized
|
||
with the kernel to send a CCB or issue an immediate command.
|
||
|
||
Arguments:
|
||
|
||
HwDeviceExtension Device extension for this driver
|
||
Srb Pointer to the Scsi Request Block to service
|
||
|
||
Return Value:
|
||
|
||
Nothing
|
||
|
||
--*/
|
||
|
||
{
|
||
PHW_DEVICE_EXTENSION deviceExtension;
|
||
PEISA_CONTROLLER eisaController;
|
||
PLU_EXTENSION luExtension;
|
||
PCCB ccb;
|
||
UCHAR opCode;
|
||
BOOLEAN Send;
|
||
|
||
DebugPrint((3,"OliEsc1StartIo: Enter routine\n"));
|
||
|
||
//
|
||
// Get the base address of the ESC-1 registers' address space
|
||
//
|
||
|
||
deviceExtension = HwDeviceExtension;
|
||
eisaController = deviceExtension->EisaController;
|
||
|
||
//
|
||
// If the "ResetInProgress" flag is TRUE, no SRBs are allowed to go
|
||
// through because the SCSI controller needs more time to complete its
|
||
// initialization.
|
||
//
|
||
|
||
if (deviceExtension->ResetInProgress) {
|
||
|
||
DebugPrint((2,"OliEsc1StartIo: The reset is not completed yet.\n"));
|
||
|
||
//
|
||
// Complete the current request.
|
||
//
|
||
|
||
Srb->SrbStatus = SRB_STATUS_BUS_RESET;
|
||
ScsiPortNotification(RequestComplete, deviceExtension, Srb);
|
||
|
||
//
|
||
// Notify that a reset was detected on the SCSI bus.
|
||
//
|
||
|
||
ScsiPortNotification(ResetDetected, deviceExtension, 0);
|
||
|
||
//
|
||
// The controller is now ready for the next request.
|
||
//
|
||
|
||
ScsiPortNotification(NextRequest, deviceExtension, NULL);
|
||
|
||
return(TRUE);
|
||
}
|
||
|
||
//
|
||
// Assume we are going to send a command to the ESC-1
|
||
//
|
||
|
||
Send = TRUE;
|
||
|
||
//
|
||
// Get CCB from SRB
|
||
//
|
||
|
||
ccb = Srb->SrbExtension;
|
||
|
||
//
|
||
// Save SRB back pointer in CCB
|
||
//
|
||
|
||
ccb->SrbAddress = Srb;
|
||
|
||
//
|
||
// Get the pointer to the extension data area associated with the
|
||
// pair (Srb->TargetId, Srb->Lun)
|
||
//
|
||
|
||
luExtension = ScsiPortGetLogicalUnit(deviceExtension,
|
||
0,
|
||
Srb->TargetId,
|
||
Srb->Lun);
|
||
ASSERT(luExtension);
|
||
|
||
switch (Srb->Function) {
|
||
|
||
case SRB_FUNCTION_EXECUTE_SCSI:
|
||
|
||
if (Srb->TargetId == ADAPTER_ID) {
|
||
|
||
//
|
||
// No SCSI massages directed to the adatpter are let
|
||
// go through, because the adapter doesn't support any
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"OliEsc1StartIo: SCSI command to adapter rejected\n"));
|
||
Srb->SrbStatus = SRB_STATUS_NO_DEVICE;
|
||
ScsiPortNotification(RequestComplete, deviceExtension, Srb);
|
||
Send = FALSE;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Increment the number of pending requests for this (targetId,
|
||
// LUN), so that we can process an abort request in case this
|
||
// command gets timed out
|
||
//
|
||
|
||
luExtension->NumberOfPendingRequests++;
|
||
|
||
//
|
||
// Build CCB.
|
||
//
|
||
|
||
OliEsc1BuildCcb(deviceExtension, Srb);
|
||
|
||
opCode = START_CCB;
|
||
}
|
||
|
||
break;
|
||
|
||
case SRB_FUNCTION_ABORT_COMMAND:
|
||
|
||
DebugPrint((1,
|
||
"OliEsc1StartIo: Abort Cmd Target ID %d\n", Srb->TargetId));
|
||
//
|
||
// The Abort command is not supported by the ESC-x. Here we do what
|
||
// we can.
|
||
//
|
||
|
||
if (luExtension->NumberOfPendingRequests) {
|
||
|
||
//
|
||
// A command sent to a device has to be aborted.
|
||
// All we can do is to reset the target.
|
||
//
|
||
|
||
OliEsc1BuildCcb(deviceExtension, Srb);
|
||
opCode = RESET_TARGET;
|
||
|
||
} else {
|
||
|
||
//
|
||
// The request to abort has already completed.
|
||
//
|
||
|
||
Srb->SrbStatus = SRB_STATUS_ABORT_FAILED;
|
||
ScsiPortNotification(RequestComplete, deviceExtension, Srb);
|
||
Send = FALSE;
|
||
}
|
||
|
||
break;
|
||
|
||
case SRB_FUNCTION_RESET_BUS:
|
||
|
||
//
|
||
// Reset ESC-1 and SCSI bus.
|
||
//
|
||
|
||
DebugPrint((1, "OliEsc1StartIo: Reset bus request received\n"));
|
||
|
||
//
|
||
// The following routine will ...
|
||
//
|
||
// a) reset the bus.
|
||
// b) complete all the active requests (including this one).
|
||
// c) notify that a reset was detected on the SCSI bus.
|
||
//
|
||
|
||
OliEsc1ResetBus(deviceExtension, (ULONG) NULL);
|
||
|
||
Send = FALSE;
|
||
break;
|
||
|
||
case SRB_FUNCTION_RESET_DEVICE:
|
||
|
||
if (Srb->TargetId == ADAPTER_ID) {
|
||
|
||
//
|
||
// No SCSI massages directed to the adatpter are let
|
||
// go through, because the adapter doesn't support any
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"OliEsc1StartIo: Reset Device sent to the adapter rjected\n"));
|
||
Srb->SrbStatus = SRB_STATUS_NO_DEVICE;
|
||
ScsiPortNotification(RequestComplete, deviceExtension, Srb);
|
||
Send = FALSE;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Increment the number of pending requests for this (targetId,
|
||
// LUN), so that we can process an abort request in case this
|
||
// command gets timed out
|
||
//
|
||
|
||
DebugPrint((4,"OliEsc1StartIo: Reset device ID %d\n",
|
||
Srb->TargetId));
|
||
OliEsc1BuildCcb(deviceExtension, Srb);
|
||
opCode = RESET_TARGET;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
|
||
//
|
||
// Set error and complete request
|
||
//
|
||
|
||
DebugPrint((1,"OliEsc1StartIo: Invalid Request\n"));
|
||
|
||
Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST;
|
||
|
||
ScsiPortNotification(RequestComplete, deviceExtension, Srb);
|
||
|
||
Send = FALSE;
|
||
|
||
} // end switch
|
||
|
||
if (Send) {
|
||
if (!OliEsc1SendCommand(deviceExtension, opCode, ccb)) {
|
||
|
||
DebugPrint((1,"OliEsc1StartIo: Send command timed out\n"));
|
||
|
||
//
|
||
// Let operating system time out SRB.
|
||
//
|
||
}
|
||
}
|
||
|
||
//
|
||
// Adapter ready for next request.
|
||
//
|
||
|
||
ScsiPortNotification(NextRequest, deviceExtension, NULL);
|
||
|
||
return TRUE;
|
||
|
||
} // end OliEsc1StartIo()
|
||
|
||
|
||
|
||
VOID
|
||
OliEsc1ResetAdapter(
|
||
IN PVOID Context
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
The routine resets the SCSI controller.
|
||
|
||
Arguments:
|
||
|
||
Context Device adapter context pointer.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PHW_DEVICE_EXTENSION deviceExtension;
|
||
PEISA_CONTROLLER eisaController;
|
||
ULONG Delay;
|
||
BOOLEAN Error = FALSE;
|
||
|
||
deviceExtension = Context;
|
||
eisaController = deviceExtension->EisaController;
|
||
|
||
//
|
||
// The routine releases the control of the CPU while waiting for some
|
||
// status/interrupt, this is required because the reset/re-initialization
|
||
// of the controller can take several seconds.
|
||
//
|
||
// Reset Controller:
|
||
//
|
||
// Phase 0: Reset the controller.
|
||
// Phase 1: Waiting for the controller to complete its initialization.
|
||
// Phase 2: Small delay.
|
||
//
|
||
|
||
switch(deviceExtension->ResetInProgress) {
|
||
|
||
//
|
||
// Phase 0: Reset the controller.
|
||
//
|
||
|
||
case 0:
|
||
|
||
//////////////////////////////////////////////////////////////////////
|
||
//
|
||
// Disable interrupts.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask,
|
||
INTERRUPTS_DISABLE);
|
||
|
||
//////////////////////////////////////////////////////////////////////
|
||
//
|
||
// Reset controller.
|
||
//
|
||
|
||
DebugPrint((3,
|
||
"OliEsc1ResetAdapter: Phase 1 (reset adapter) max time = %ld us.\n",
|
||
ESC_RESET_DELAY + ESC_RESET_INTERVAL * ESC_RESET_LOOPS
|
||
));
|
||
|
||
//
|
||
// Initialize the output location to a known value.
|
||
//
|
||
|
||
ScsiPortWritePortUchar( (PUCHAR)&eisaController->Status,
|
||
(UCHAR)(~DIAGNOSTICS_OK_NO_CONFIG_RECEIVED));
|
||
|
||
//
|
||
// Reset ESC-1 and SCSI bus. Wait to allow the
|
||
// board diagnostics to complete
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->LocalDoorBell, ADAPTER_RESET);
|
||
|
||
//
|
||
// Request a timer call to complete the reset.
|
||
//
|
||
|
||
deviceExtension->ResetTimerCalls = ESC_RESET_LOOPS + 1;
|
||
Delay = ESC_RESET_DELAY;
|
||
|
||
//
|
||
// The "ResetNotification" variable is used to keep track of the
|
||
// time during the reset. If the reset is not completed before
|
||
// the next ESC1_RESET_NOTIFICATION usec. unit, we call the
|
||
// "ScsiPortNotification(ResetDetected...)" routine.
|
||
// After the call the ScsiPort stops the delivery of SRBs for a
|
||
// little bit (~4 sec.).
|
||
//
|
||
|
||
deviceExtension->ResetNotification = 0;
|
||
deviceExtension->ResetInProgress++;
|
||
break;
|
||
|
||
//
|
||
// Phase 1: Waiting for the controller to complete its initialization.
|
||
//
|
||
|
||
case 1:
|
||
|
||
//
|
||
// Note that after a reset the LOW byte of the Status register is
|
||
// loaded with the diagnostics result code. This should be the
|
||
// only case, since usually the high byte reports the adapter status.
|
||
//
|
||
|
||
if ( (UCHAR)ScsiPortReadPortUshort(&eisaController->Status) !=
|
||
DIAGNOSTICS_OK_NO_CONFIG_RECEIVED ) {
|
||
|
||
Delay = ESC_RESET_INTERVAL;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Reset completed!
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"OliEsc1ResetAdapter: Reset bus succeeded after %ld us\n",
|
||
ESC_RESET_DELAY + ESC_RESET_INTERVAL *
|
||
(ESC_RESET_LOOPS - deviceExtension->ResetTimerCalls)
|
||
));
|
||
|
||
//
|
||
// The following delay is necessary because the adapter, immediately
|
||
// after a reset, is insensitive to interrupts through the Local
|
||
// Doorbell Register for almost 50ms. This shouldn't be and needs
|
||
// to be investigated further. After the adapter has accepted the very
|
||
// first command (see OliEsc1GetIrql()), it take 500ms to return the answer to
|
||
// the CPU (i.e., to generate an interrupt). The very first command sent
|
||
// to the adapter is a "Get Configuration" to read the IRQL register
|
||
// at system boot time
|
||
//
|
||
|
||
deviceExtension->ResetTimerCalls = 1;
|
||
Delay = POST_RESET_DELAY;
|
||
deviceExtension->ResetInProgress++;
|
||
break;
|
||
|
||
//
|
||
// Phase 2: Small delay.
|
||
//
|
||
|
||
case 2:
|
||
|
||
//////////////////////////////////////////////////////////////////////
|
||
//
|
||
// Remove any interrupt that was pending before issuing the reset.
|
||
// The controller doesn't reset these interrupts.
|
||
//
|
||
|
||
if (ScsiPortReadPortUchar(&eisaController->SystemDoorBell) &
|
||
INTERRUPT_PENDING) {
|
||
|
||
DebugPrint((3,
|
||
"OliEsc1ResetAdapter: The HP interrupt was pending.\n"));
|
||
|
||
//
|
||
// Reset the interrupt
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBell,
|
||
INTERRUPT_PENDING);
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////
|
||
//
|
||
// Enable interrupts.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask,
|
||
INTERRUPTS_ENABLE);
|
||
|
||
//////////////////////////////////////////////////////////////////////
|
||
//
|
||
// All done !
|
||
//
|
||
|
||
deviceExtension->ResetInProgress = 0;
|
||
return;
|
||
|
||
default:
|
||
|
||
//
|
||
// Invalid reset phase number. This should never happen!
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"OliEsc1ResetAdapter: Invalid reset phase number: %x hex.\n",
|
||
deviceExtension->ResetInProgress ));
|
||
|
||
ASSERT(0);
|
||
|
||
Error = TRUE;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// If no error, request a timer call.
|
||
//
|
||
|
||
if (!Error) {
|
||
|
||
//
|
||
// Check if time-out.
|
||
//
|
||
|
||
if (deviceExtension->ResetTimerCalls--) {
|
||
|
||
//
|
||
// Request a timer call.
|
||
//
|
||
|
||
ScsiPortNotification(RequestTimerCall,
|
||
deviceExtension,
|
||
OliEsc1ResetAdapter,
|
||
Delay);
|
||
|
||
//
|
||
// The "ResetNotification" variable is used to keep track of the
|
||
// time during the reset. If the reset is not completed before
|
||
// the next ESC1_RESET_NOTIFICATION usec. unit, we call the
|
||
// "ScsiPortNotification(ResetDetected...)" routine.
|
||
// After the call the ScsiPort stops the delivery of SRBs for a
|
||
// little bit (~4 sec.).
|
||
//
|
||
|
||
if (deviceExtension->ResetNotification >= ESC1_RESET_NOTIFICATION) {
|
||
|
||
//
|
||
// Notify that a reset was detected on the SCSI bus.
|
||
//
|
||
|
||
ScsiPortNotification(ResetDetected, deviceExtension, 0);
|
||
|
||
//
|
||
// Reset the "reset notification timer".
|
||
//
|
||
|
||
deviceExtension->ResetNotification = 0;
|
||
}
|
||
|
||
//
|
||
// Update the "reset notification timer".
|
||
//
|
||
|
||
deviceExtension->ResetNotification += Delay;
|
||
}
|
||
else {
|
||
|
||
//
|
||
// Time-out !
|
||
//
|
||
|
||
DebugPrint((1,
|
||
"OliEsc1ResetAdapter: Time-out! Reset phase number: %x hex.\n",
|
||
deviceExtension->ResetInProgress ));
|
||
|
||
Error = TRUE;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If error, log it.
|
||
//
|
||
|
||
if (Error) {
|
||
|
||
//
|
||
// Log an error.
|
||
//
|
||
|
||
ScsiPortLogError(
|
||
deviceExtension,
|
||
NULL,
|
||
0,
|
||
ADAPTER_ID,
|
||
0,
|
||
SP_INTERNAL_ADAPTER_ERROR,
|
||
ESCX_RESET_FAILED
|
||
);
|
||
|
||
//
|
||
// We clear the "ResetInProgress" variable to force another SCSI
|
||
// bus reset when the driver receives the first SRB request.
|
||
// Note that the interrupts are left disabled at the controller level.
|
||
//
|
||
|
||
deviceExtension->ResetInProgress = 0;
|
||
}
|
||
|
||
//
|
||
// Done for now.
|
||
//
|
||
|
||
return;
|
||
|
||
} // end OliEsc1ResetAdapter
|
||
|
||
|
||
|
||
BOOLEAN
|
||
OliEsc1SendCommand(
|
||
IN PHW_DEVICE_EXTENSION DeviceExtension,
|
||
IN UCHAR OperationCode,
|
||
IN PCCB ccb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Send a Command Control Block to ESC-1.
|
||
|
||
Arguments:
|
||
|
||
DeviceExtension Device extension for this driver
|
||
OperationCode Command for the ESC-1
|
||
ccb Pointer to the CCB
|
||
|
||
Return Value:
|
||
|
||
True if command was sent.
|
||
False if the Command Semaphore was busy.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
PEISA_CONTROLLER EisaController;
|
||
ULONG physicalCcb;
|
||
ULONG length;
|
||
|
||
//
|
||
// Get the base address of the ESC-1 registers' address space
|
||
//
|
||
|
||
EisaController = DeviceExtension->EisaController;
|
||
|
||
length = 0;
|
||
|
||
//
|
||
// Get the CCB physical address
|
||
//
|
||
|
||
physicalCcb =
|
||
ScsiPortConvertPhysicalAddressToUlong(
|
||
ScsiPortGetPhysicalAddress(DeviceExtension, NULL, ccb, &length));
|
||
|
||
ASSERT (length >= sizeof(CCB));
|
||
|
||
return(OliEsc1SendCommandQuick(EisaController, ccb->TaskId, OperationCode,
|
||
(USHORT) (CCB_FIXED_LENGTH + ccb->CdbLength),
|
||
physicalCcb));
|
||
|
||
}
|
||
|
||
BOOLEAN
|
||
OliEsc1SendCommandQuick(
|
||
IN PEISA_CONTROLLER EisaController,
|
||
IN UCHAR TaskId,
|
||
IN UCHAR OperationCode,
|
||
IN USHORT CommandLength,
|
||
IN ULONG Address
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Send CCB or immediate command to ESC-1.
|
||
|
||
Arguments:
|
||
|
||
EisaController Base address of the ESC-1 registers' address space
|
||
TaskId Task ID for the ESC-1
|
||
OperationCode Command code for the ESC-1
|
||
CommandLength Total CCB length
|
||
Address Physical address of the CCB
|
||
|
||
Return Value:
|
||
|
||
True if the command was sent.
|
||
False if the Command Semaphore was busy.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG i;
|
||
BOOLEAN ReturnCode = FALSE;
|
||
|
||
//
|
||
// Try to send the command for up to 100 microsends
|
||
//
|
||
|
||
for (i = 0; i < 100; i++) {
|
||
|
||
|
||
ScsiPortWritePortUchar(&EisaController->CommandSemaphore, SEM_LOCK);
|
||
|
||
if ((ScsiPortReadPortUchar(&EisaController->CommandSemaphore) &
|
||
SEM_TAKEN_MASK) == SEM_TAKEN) {
|
||
|
||
//
|
||
// We can send a command to the ESC-1.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&EisaController->InTaskId, TaskId);
|
||
ScsiPortWritePortUchar(&EisaController->Command, OperationCode);
|
||
ScsiPortWritePortUshort(&EisaController->CommandLength,
|
||
CommandLength);
|
||
ScsiPortWritePortUlong(&EisaController->InAddress, Address);
|
||
|
||
//
|
||
// Send an attention interrupt to the adapter.
|
||
//
|
||
|
||
ScsiPortWritePortUchar(&EisaController->LocalDoorBell,
|
||
INTERRUPT_PENDING);
|
||
|
||
ReturnCode = TRUE;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Stall execution for 1 microsecond.
|
||
//
|
||
|
||
ScsiPortStallExecution(1);
|
||
}
|
||
|
||
return ReturnCode;
|
||
|
||
} // end OliEsc1SendCommand()
|