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