/*++ Copyright (c) 1992 Ing. C. Olivetti & C., S.p.A. Copyright (c) 1991 Microsoft Corporation Module Name: oliesc2.c Abstract: This is the miniport driver for the Olivetti ESC-2 SCSI adapter. Authors: Kris Karnos 1-Nov-1992 Young-Chi Tan 1-Nov-1992 Environment: kernel mode only Notes: Revision History: Founded on 8-Nov-1992 version of Bruno Sartirana's NT miniport for the ESC-1 9-Apr-1993: (v-egidis) - The "FindAdapter" process has been subdivided in two phases: #0, gather the # of devices on each SCSI controller. #1, allocate the right quantity of uncached memory. - Now if there is an error during the FindAdapter process (phase #1) 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. - Only the 1st, the 2nd, the 3rd and the upper nibble of the 4th ID byte are used to identify the EFP boards. This will allow future EFP controllers to be supported by this driver. - The StartIo routine has been modified to behave correctly when the device's command queue is full. - The enqueue command routine has been modified to return different error codes. - The dequeue reply routine has been modified to return a different error code. - The interrupt routine has been modified to handle more efficiently critical situations. - Implemented the EFP reset. CHECK BACK WITH RUFFONI THE FOLLOWING: From Ruffoni's answer to one of my e-mails, the EFP-2 controller re-sends all the commands interrupted by a SCSI reset (this is the reason why I didn't abort any pending SRBs during a SCSI bus reset). - ScsiPortLogError: removed the PathId, TargetId and Lun (substituted with zeros) when the error is global to the whole adapter. 28-May-1993: (v-egidis) - The EFP reset is now done resetting the EFP board. - The "Reset Device" commands has been changed to behave like the "Reset Bus" command. - The "Abort" routine now verifies that the SRB to abort is still outstanding before resetting the SCSI bus. 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. - The EISA bus number is now checked during the OliEsc2FindAdapter routine because this miniport supports only one EISA bus. (see MAX_EISA_BUSES for more info). 14-Sep-1993: (v-egidis) - Added support for the EFP-2 mirroring mode. --*/ #include "miniport.h" #include "oliesc2.h" // includes scsi.h // // Function declarations // // Functions that start with 'OliEsc2' are entry points for this // NT miniport driver. // ULONG OliEsc2DriverEntry ( IN PVOID DriverObject, IN PVOID Argument2 ); ULONG OliEsc2FindAdapter( IN PVOID HwDeviceExtension, IN PVOID Context, IN PVOID BusInformation, IN PCHAR ArgumentString, IN OUT PPORT_CONFIGURATION_INFORMATION ConfigInfo, OUT PBOOLEAN Again ); ULONG OliEsc2FindAdapterPhase0( IN PVOID HwDeviceExtension, IN PVOID Context, IN PVOID BusInformation, IN PCHAR ArgumentString, IN OUT PPORT_CONFIGURATION_INFORMATION ConfigInfo, OUT PBOOLEAN Again ); ULONG OliEsc2FindAdapterPhase1( IN PVOID HwDeviceExtension, IN PVOID Context, IN PVOID BusInformation, IN PCHAR ArgumentString, IN OUT PPORT_CONFIGURATION_INFORMATION ConfigInfo, OUT PBOOLEAN Again ); BOOLEAN OliEsc2Initialize( IN PVOID HwDeviceExtension ); BOOLEAN OliEsc2StartIo( IN PVOID HwDeviceExtension, IN PSCSI_REQUEST_BLOCK Srb ); BOOLEAN OliEsc2Interrupt( IN PVOID HwDeviceExtension ); BOOLEAN OliEsc2ResetBus( IN PVOID HwDeviceExtension, IN ULONG PathId ); BOOLEAN ReadEsc2ConfigReg( IN PEISA_CONTROLLER EisaController, IN UCHAR ConfigReg, OUT PUCHAR ConfigByteInfo ); BOOLEAN OliEsc2IrqRegToIrql( IN UCHAR IrqReg, OUT PUCHAR pIrql ); BOOLEAN RegisterEfpQueues( IN PHW_DEVICE_EXTENSION DeviceExtension ); VOID BuildSgl( IN PHW_DEVICE_EXTENSION DeviceExtension, IN PSCSI_REQUEST_BLOCK Srb ); VOID OliEsc2ResetAdapter( IN PVOID Context ); BOOLEAN GainSemaphore0( PEISA_CONTROLLER EisaController ); VOID BuildEfpCmd( IN PHW_DEVICE_EXTENSION DeviceExtension, IN PSCSI_REQUEST_BLOCK Srb ); BOOLEAN EfpGetDevinfo( IN PHW_DEVICE_EXTENSION DeviceExtension ); BOOLEAN OliEsc2DisableEfp( IN PHW_DEVICE_EXTENSION DeviceExtension ); BOOLEAN EnqueueEfpCmd ( IN PHW_DEVICE_EXTENSION DeviceExtension, IN PQ_ENTRY pEfpCmd, IN PCOMMAND_QUEUE qPtr, IN UCHAR TarLun, OUT PUCHAR pSignal ); BOOLEAN DequeueEfpReply ( IN PHW_DEVICE_EXTENSION DeviceExtension ); BOOLEAN EfpReplyQNotFull ( IN PHW_DEVICE_EXTENSION DeviceExtension ); BOOLEAN EfpCommand ( IN PHW_DEVICE_EXTENSION DeviceExtension, IN UCHAR TarLun ); BOOLEAN EfpGetInformation ( IN PHW_DEVICE_EXTENSION DeviceExtension ); BOOLEAN EfpGetConfiguration ( IN PHW_DEVICE_EXTENSION DeviceExtension ); #if EFP_MIRRORING_ENABLED VOID OliEsc2MirrorInitialize( IN PHW_DEVICE_EXTENSION DeviceExtension, IN BOOLEAN InitTime ); #endif // EFP_MIRRORING_ENABLED // Function definitions ULONG OliEsc2DriverEntry ( IN PVOID DriverObject, IN PVOID Argument2 ) /*++ Routine Description: Installable driver initialization entry point for the OS. This routine initializes the fields of the HW_INITIALIZATION_DATA structure (see SRB.H), reporting this miniport driver's entry point and storage requirements, and describing the features supported by this HBA. The routine then calls the Port driver's ScsiPortInitialize. Arguments: Driver Object Pointer to the driver object for this driver Return Value: Status from ScsiPortInitialize() --*/ { // This structure tells the upper layer the entry points of this driver. HW_INITIALIZATION_DATA hwInitializationData; ULONG i; // Auxiliary variable ESC2_CONTEXT Context; // Used to keep track of the // slots that have been checked // and to pass info between // phase #0 and phase #1. DebugPrint((1,"\n\nSCSI Olivetti ESC-2 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 adapter bus type // hwInitializationData.AdapterInterfaceType = Eisa; // // Set entry points. // hwInitializationData.HwFindAdapter = OliEsc2FindAdapter; hwInitializationData.HwInitialize = OliEsc2Initialize; hwInitializationData.HwStartIo = OliEsc2StartIo; hwInitializationData.HwInterrupt = OliEsc2Interrupt; hwInitializationData.HwResetBus = OliEsc2ResetBus; // // Specify size of extensions. // hwInitializationData.DeviceExtensionSize = sizeof(HW_DEVICE_EXTENSION); hwInitializationData.SpecificLuExtensionSize = sizeof(LU_EXTENSION); // // Ask for SRB extensions for SGL. // hwInitializationData.SrbExtensionSize = sizeof(EFP_SGL); // // Set number of access ranges. // hwInitializationData.NumberOfAccessRanges = 1; // // Indicate no buffer mapping but will need physical addresses. // hwInitializationData.NeedPhysicalAddresses = TRUE; // // The ESC-2 supports tagged queueing, but it's not currently enabled. // // KMK EFP queueing is done in the controller, not at the target. // Although the ESC-2 can support tagged queueing, the feature is // currently (and for the foreseeable future) disabled. // hwInitializationData.TaggedQueuing = FALSE; // // We will enable the ESC-2's Automatic Request Sense (ARS) capability // // Note: Other parts of the code assume that ARS is enabled; if this // capability is ever disabled, that code will need to be changed too. // hwInitializationData.AutoRequestSense = TRUE; // // The ESC-2 supports multiple requests per logical unit. // hwInitializationData.MultipleRequestPerLu = TRUE; // KMK Do we support ReceiveEvent function? DebugPrint((4, "OliEsc2DriverEntry: hwInitializationData address %lx.\n", &hwInitializationData)); // // Zero out the context structure used to pass info between phases. // for (i = 0; i < sizeof(ESC2_CONTEXT); i++) { ((PUCHAR)&Context)[i] = 0; } // // Phase #0, gather all the info about the devices. // Context.Phase = 0; ScsiPortInitialize(DriverObject, Argument2, &hwInitializationData, &Context); // // Phase #1, perform the real driver initialization. // Context.Phase = 1; Context.CheckedSlot = 0; // Start from the beginning. return(ScsiPortInitialize(DriverObject, Argument2, &hwInitializationData, &Context)); } // end OliEsc2DriverEntry() ULONG OliEsc2FindAdapter( 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. This routine checks each slot on the EISA bus, checking for ESC-2 adapters. This routine will act on two phases. The first one (see the 1st call to the ScsiPortInitialize routine in the OliEsc2DriverEntry routine) fills only the ESC2_CONTEXT structure (with the number of devices attached to each EFP-2/ESC-2 SCSI controllers). The second phase will use the info gathered within the ESC2_CONTEXT to allocate the right amount of "non cached" memory. Arguments: HwDeviceExtension Device extension for this driver Context ESC-2 registers' address space ConfigInfo Configuration information structure describing the board configuration Return Value: SP_RETURN_NOT_FOUND Adapter not found SP_RETURN_FOUND Adapter found SP_RETURN_ERROR General error SP_RETURN_BAD_CONFIG Configuration error --*/ { PESC2_CONTEXT pContext = Context; // // This miniport supports only one EISA bus. // if (ConfigInfo->SystemIoBusNumber >= MAX_EISA_BUSES) { *Again = FALSE; return(SP_RETURN_NOT_FOUND); } // // Call the routine associated to the current phase number // if (pContext->Phase == 0) { // // phase #0 // return OliEsc2FindAdapterPhase0( HwDeviceExtension, Context, BusInformation, ArgumentString, ConfigInfo, Again ); } // // phase #1 // return OliEsc2FindAdapterPhase1( HwDeviceExtension, Context, BusInformation, ArgumentString, ConfigInfo, Again ); } // end OliEsc2FindAdapter() ULONG OliEsc2FindAdapterPhase0( 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. This routine checks each slot on the EISA bus, checking for ESC-2 adapters. The routine will store all the devices info within the ESC2_CONTEXT structure (used during the 2nd phase). Arguments: HwDeviceExtension Device extension for this driver Context ESC-2 registers' address space ConfigInfo Configuration information structure describing the board configuration Return Value: SP_RETURN_FOUND Adapter found SP_RETURN_ERROR General error --*/ { PHW_DEVICE_EXTENSION DeviceExtension; // Pointer to device extension PEISA_CONTROLLER eisaController; // Base address of the ESC-2 // registers' address space PESC2_CONTEXT pContext = Context; // Used to pass info between // phase #0 and phase #1. BOOLEAN ExtensionAllocated = FALSE; // Non cached extension is // not yet allocated. UCHAR Slot; // EISA slot # UCHAR DataByte; DebugPrint((4,"OliEsc2FindAdapterPhase0: Phase #0 started.\n")); // // Initialize pointers. // DeviceExtension = HwDeviceExtension; // // Search for any ESC2/EFP2 adapters // for (Slot = 1; Slot < MAX_EISA_SLOTS_STD; Slot++) { // // Get the system physical address for this card. // The card uses I/O space. // eisaController = ScsiPortGetDeviceBase( DeviceExtension, ConfigInfo->AdapterInterfaceType, ConfigInfo->SystemIoBusNumber, ScsiPortConvertUlongToPhysicalAddress(0x1000 * Slot), 0x1000, (BOOLEAN) TRUE); // // eisaController stores all our HBA's registers // eisaController = (PEISA_CONTROLLER)((PUCHAR)eisaController + EISA_ADDRESS_BASE); // // Read the EISA board ID and check to see if it identifies // an ESC-2 (ID 2x10893D where x = any digit greater than 1, // because 2110893D is the ID of the ESC-1, which does not support // the EFP interface). // DataByte = ScsiPortReadPortUchar(&eisaController->BoardId[3]); if ((ScsiPortReadPortUchar(&eisaController->BoardId[0]) == 0x3D) && (ScsiPortReadPortUchar(&eisaController->BoardId[1]) == 0x89) && (ScsiPortReadPortUchar(&eisaController->BoardId[2]) == 0x10) && ( ((DataByte > 0x21) && ((DataByte & 0xF0) == 0x20)) || ((DataByte & 0xF0) == 0x50) )) { DebugPrint((2, "OliEsc2FindAdapterPhase0: ESC-2 Adapter found at EISA slot %d.\n", Slot)); // // Immediately disable system interrupts (bellinte). // They will remain disabled (polling mode only) during // the EFP initialization sequence. // ScsiPortWritePortUchar(&eisaController->SystemIntEnable, INTERRUPTS_DISABLE); // The adapter is not reset and is assumed to be functioning. // Resetting the adapter is particularly time consuming // (approximately 3 seconds for the ESC-2). The BIOS on x86 // computers or the EISA Support F/W on the M700 computers // has already reset the adapter and checked its status. // // ... // // Check if we need to allocate some "non cached" memory. // if (!ExtensionAllocated) { // // Fill-in the minimum info to make the call. // ConfigInfo->MaximumTransferLength = MAXIMUM_TRANSFER_SIZE; ConfigInfo->NumberOfPhysicalBreaks = MAXIMUM_SGL_DESCRIPTORS; ConfigInfo->ScatterGather = TRUE; ConfigInfo->Master = TRUE; // // Allocate some "non cached" memory. // DeviceExtension->NoncachedExt = (PNONCACHED_EXTENSION) ScsiPortGetUncachedExtension( DeviceExtension, ConfigInfo, sizeof(NONCACHED_EXTENSION)); // // Make sure that the call succeeded. // if (DeviceExtension->NoncachedExt == NULL) { DebugPrint((1, "OliEsc2FindAdapterPhase0: UncachedExtesion error.\n")); ScsiPortLogError( DeviceExtension, NULL, 0, 0, 0, SP_INTERNAL_ADAPTER_ERROR, ESCX_INIT_FAILED ); return(SP_RETURN_ERROR); } // // No need to make this call again. // ExtensionAllocated = TRUE; } // end if (no extension) .... // // Store base address of EISA registers in device extension. // DeviceExtension->EisaController = eisaController; // // Store the controller type // DataByte = ScsiPortReadPortUchar(&eisaController->BoardId[3]); DeviceExtension->Esc2 = (DataByte & 0xF0) != 0x50 ? TRUE : FALSE; // // Reset hardware interrupts. // // This process ensures that we can communicate with the // controller in polling mode during initialization time. // // // Reset System Doorbell interrupts // ScsiPortWritePortUchar(&eisaController->SystemDoorBell, 0x0FF); // // Enable System Doorbell interrupts // ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask, INTERRUPTS_ENABLE); // // Get the ESC-2 IRQL, // get the SCSI device info, // and finally disable the EFP mode. // if ( ReadEsc2ConfigReg(DeviceExtension->EisaController, IRQL_REGISTER, &DeviceExtension->IRQ_In_Use) && OliEsc2IrqRegToIrql( DeviceExtension->IRQ_In_Use, &DeviceExtension->IRQ_In_Use ) && EfpGetDevinfo(DeviceExtension) && OliEsc2DisableEfp(DeviceExtension) ) { //---------------------------------------- // Fill-in the ESC2_CONTEXT structure //---------------------------------------- pContext->ScsiInfo[Slot - 1].AdapterPresent = 1; pContext->ScsiInfo[Slot - 1].NumberOfDevices = DeviceExtension->TotalAttachedDevices; } else { // // Log errror and continue the search. // DebugPrint((1, "OliEsc2FindAdapterPhase0: Adapter initialization error.\n")); ScsiPortLogError( DeviceExtension, NULL, 0, 0, 0, SP_INTERNAL_ADAPTER_ERROR, ESCX_INIT_FAILED ); } // end if (Adapter configuration corret) } // end if (ESC-2/EFP-2) // // Deallocate this I/O address and go check the next slot. // ScsiPortFreeDeviceBase(DeviceExtension, (PUCHAR)eisaController - EISA_ADDRESS_BASE); } // end for (Slot=1 to 15) // // End of phase #0. // DebugPrint((4,"OliEsc2FindAdapterPhase0: Phase #0 completed.\n")); *Again = FALSE; return(SP_RETURN_NOT_FOUND); } // end OliEsc2FindAdapterPhase0() ULONG OliEsc2FindAdapterPhase1( 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. This routine checks each slot on the EISA bus, checking for ESC-2 adapters. The routine will store all the necessary info within the ESC2_CONTEXT structure (used during the 2nd phase). Arguments: HwDeviceExtension Device extension for this driver Context ESC-2 registers' address space ConfigInfo Configuration information structure describing the board configuration Return Value: SP_RETURN_NOT_FOUND Adapter not found SP_RETURN_FOUND Adapter found SP_RETURN_ERROR General error SP_RETURN_BAD_CONFIG Configuration error --*/ { PHW_DEVICE_EXTENSION DeviceExtension; // Pointer to device extension PEISA_CONTROLLER eisaController; // Base address of the ESC-2 // registers' address space UCHAR Slot, BusCount; ULONG i, TotalQueueSize; UCHAR DataByte; BOOLEAN Success = FALSE; // Indicates an adapter was found. PESC2_CONTEXT pContext = Context; // Context pointer. PUCHAR pCheckedSlot; // Indicates which slots have // been checked. DebugPrint((4,"OliEsc2FindAdapterPhase1: Phase #1 started.\n")); // // Initialize pointers. // DeviceExtension = HwDeviceExtension; pCheckedSlot = &pContext->CheckedSlot; // // Check to see if an adapter is present in the system // for (Slot = *pCheckedSlot + 1; Slot < MAX_EISA_SLOTS_STD; Slot++) { // // Update the adapter count to indicate this slot has been checked. // (*pCheckedSlot)++; // // Check next slot if this is empty. // if (pContext->ScsiInfo[Slot - 1].AdapterPresent == 1) { // // Get the system physical address for this card. // The card uses I/O space. // eisaController = ScsiPortGetDeviceBase( DeviceExtension, ConfigInfo->AdapterInterfaceType, ConfigInfo->SystemIoBusNumber, ScsiPortConvertUlongToPhysicalAddress(0x1000 * Slot), 0x1000, (BOOLEAN) TRUE); // eisaController stores all our HBA's registers eisaController = (PEISA_CONTROLLER)((PUCHAR)eisaController + EISA_ADDRESS_BASE); // // We found one! // Success = TRUE; break; } } // end for (Slot 1 to 15) if (!Success) { // // End of phase #1. // DebugPrint((4,"OliEsc2FindAdapterPhase1: Phase #1 completed.\n")); // // No adapter was found. Clear the call again flag, reset the // adapter count for the next bus and return. // *Again = FALSE; *pCheckedSlot = 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; // // Store the controller type // DataByte = ScsiPortReadPortUchar(&eisaController->BoardId[3]); DeviceExtension->Esc2 = (DataByte & 0xF0) != 0x50 ? TRUE : FALSE; // // Reset the "ResetInProgress" variable. // DeviceExtension->ResetInProgress = 0; // // Indicate the maximum transfer length in bytes. // ConfigInfo->MaximumTransferLength = MAXIMUM_TRANSFER_SIZE; // KMK Check.. // // Indicate the maximum number of physical segments // ConfigInfo->NumberOfPhysicalBreaks = MAXIMUM_SGL_DESCRIPTORS; // KMK yes? // no DMA, so we use the default values (no DMA) for DMA parameters. ConfigInfo->ScatterGather = TRUE; ConfigInfo->Master = TRUE; // // Indicate which interrupt mode the ESC-2 uses // if (ScsiPortReadPortUchar(&eisaController->GlobalConfiguration) & EDGE_SENSITIVE) { ConfigInfo->InterruptMode = Latched; DebugPrint((1, "OliEsc2FindAdapterPhase1: EDGE_SENSITIVE interrupts.\n")); } else { ConfigInfo->InterruptMode = LevelSensitive; DebugPrint((4, "OliEsc2FindAdapterPhase1: LEVEL_SENSITIVE interrupts.\n")); } // // Fill in the access array information. // (*ConfigInfo->AccessRanges)[0].RangeStart = ScsiPortConvertUlongToPhysicalAddress(0x1000 * Slot + EISA_ADDRESS_BASE); (*ConfigInfo->AccessRanges)[0].RangeLength = sizeof(EISA_CONTROLLER); (*ConfigInfo->AccessRanges)[0].RangeInMemory = FALSE; // // Reset hardware interrupts. // // This process ensures that we can communicate with the controller // in polling mode during initialization time. // // // Reset System Doorbell interrupts // ScsiPortWritePortUchar(&eisaController->SystemDoorBell, 0x0FF); // // Enable System Doorbell interrupts // ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask, INTERRUPTS_ENABLE); // // Read the ESC-2's / EFP-2's configuration registers. // for (i=0; i < CFG_REGS_NUMBER; i++) { if (ReadEsc2ConfigReg(DeviceExtension->EisaController, (UCHAR)i, &DeviceExtension->CfgRegs[i])) { // // Got it! // DeviceExtension->CfgRegsPresent[i] = TRUE; } else { // // Error reading the config register or // config register not present. // DeviceExtension->CfgRegsPresent[i] = FALSE; DeviceExtension->CfgRegs[i] = 0; } } // // Read the ESC-2's ATCFG register, to see if this adapter is in // AT-enabled mode. If so, report that this controller is acting // like an AT primary controller, to prevent access by the "AT" // controller driver. An ESC-2 cannot be an AT secondary controller. // if (DeviceExtension->CfgRegsPresent[ATCFG_REGISTER] && DeviceExtension->CfgRegs[ATCFG_REGISTER] & AT_ENABLED) { ConfigInfo->AtdiskPrimaryClaimed = TRUE; } // // Get the ESC-2 IRQL. // if (!DeviceExtension->CfgRegsPresent[IRQL_REGISTER] || !OliEsc2IrqRegToIrql( DeviceExtension->CfgRegs[IRQL_REGISTER], &DeviceExtension->IRQ_In_Use )) { DebugPrint((1, "OliEsc2FindAdapterPhase1: Adapter initialization error.\n")); ScsiPortLogError( DeviceExtension, NULL, 0, 0, 0, SP_INTERNAL_ADAPTER_ERROR, ESCX_INIT_FAILED ); return SP_RETURN_ERROR; } // // Save the IRQL value in the ConfigInfo structure. // ConfigInfo->BusInterruptLevel = DeviceExtension->IRQ_In_Use; // // Allocate queues here. // TotalQueueSize = (pContext->ScsiInfo[Slot - 1].NumberOfDevices) * sizeof(EFP_COMMAND_QUEUE) + sizeof(NONCACHED_EXTENSION); DebugPrint((1, "OliEsc2FindAdapterPhase1: TotalQueueSize = %d.\n",TotalQueueSize)); DeviceExtension->NoncachedExt = (PNONCACHED_EXTENSION) ScsiPortGetUncachedExtension( DeviceExtension, ConfigInfo, TotalQueueSize); // // Make sure that the call succeeded. // if (DeviceExtension->NoncachedExt == NULL) { DebugPrint((1, "OliEsc2FindAdapterPhase1: UncachedExtesion error.\n")); ScsiPortLogError( DeviceExtension, NULL, 0, 0, 0, SP_INTERNAL_ADAPTER_ERROR, ESCX_INIT_FAILED ); return(SP_RETURN_ERROR); } // // Get the SCSI devices info // if ( !EfpGetDevinfo(DeviceExtension) ) { DebugPrint((1, "OliEsc2FindAdapterPhase1: Adapter initialization error.\n")); ScsiPortLogError( DeviceExtension, NULL, 0, 0, 0, SP_INTERNAL_ADAPTER_ERROR, ESCX_INIT_FAILED ); return SP_RETURN_ERROR; }; // // Make sure that the two phases got the same number of devices. // if (pContext->ScsiInfo[Slot - 1].NumberOfDevices != DeviceExtension->TotalAttachedDevices) { DebugPrint((1, "OliEsc2FindAdapterPhase1: The number of devices is different:\n" " phase #0: %d, phase #1: %d.\n", pContext->ScsiInfo[Slot - 1].NumberOfDevices, DeviceExtension->TotalAttachedDevices)); ScsiPortLogError( DeviceExtension, NULL, 0, 0, 0, SP_INTERNAL_ADAPTER_ERROR, ESCX_INIT_FAILED ); return SP_RETURN_ERROR; } // // Count the number of buses // for ( i=0,BusCount=0 ; i < MAX_HAIDS ; i++ ) { if ( DeviceExtension->Adapter_ID[i] != NO_BUS_ID ) { ConfigInfo->InitiatorBusId[BusCount] = DeviceExtension->Adapter_ID[i]; DebugPrint((1, "OliEsc2FindAdapterPhase1: InitiatorBusId[%d] = %d.\n", BusCount, ConfigInfo->InitiatorBusId[BusCount])); BusCount++; }; }; ConfigInfo->NumberOfBuses = BusCount; DeviceExtension->NumberOfBuses = BusCount; // // End of phase #1. // DebugPrint((4,"OliEsc2FindAdapterPhase1: Phase #1 multi-stage.\n")); return SP_RETURN_FOUND; } // end OliEsc2FindAdapterPhase1() BOOLEAN OliEsc2Initialize( IN PVOID HwDeviceExtension ) /*++ Routine Description: Initializes the EFP interface on the adapter. As directed by the NT miniport specification, this routine avoids resetting the SCSI bus. Arguments: HwDeviceExtension Device extension for this driver Return Value: Returns TRUE if initialization completed successfully. Returns FALSE and logs an error if adapter initialization fails. --*/ { PHW_DEVICE_EXTENSION DeviceExtension; PEISA_CONTROLLER eisaController; DeviceExtension = HwDeviceExtension; eisaController = DeviceExtension->EisaController; // // At this point, we know how many devices are attached; therefore, // we know how many device queues are required. We call another // routine which will use the Get Configuration information stored // in the uncached extension to build a new queues descriptor, // allocate mailbox and device queues, and resend the EFP_SET and // EFP_START commands to register this new revised information with // the EFP-2 controller. // if ( !RegisterEfpQueues(DeviceExtension) ) { DebugPrint((1, "OliEsc2FindAdapterPhase1: Adapter initialization error.\n")); ScsiPortLogError( DeviceExtension, NULL, 0, 0, 0, SP_INTERNAL_ADAPTER_ERROR, ESCX_INIT_FAILED ); return(FALSE); } #if EFP_MIRRORING_ENABLED // // We need to initialize all the mirroring structures. // OliEsc2MirrorInitialize(DeviceExtension, TRUE); #endif // EFP_MIRRORING_ENABLED // // The EFP-2 interface is now ready for use, so now we can // enable interrupts. // // // Ensure that EFP interrupts (0 and 1) are enabled. // Enable also the ESC-1 High Performance interrupt, which is // need for implementation of the Reset_Bus command. // ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask, INTERRUPTS_ENABLE); // // Enable system interrupts (bellinte). // ScsiPortWritePortUchar(&eisaController->SystemIntEnable, SYSTEM_INTS_ENABLE); // // Initialization completed successfully. // return(TRUE); } // end OliEsc2Initialize() BOOLEAN EfpGetDevinfo( IN PHW_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Description: Initializes the EFP interface on the adapter. As directed by the NT miniport specification, this routine avoids resetting the SCSI bus. Arguments: DeviceExtension Device extension for this driver Return Value: Returns TRUE if initialization completed successfully. Returns FALSE and logs an error if adapter initialization fails. --*/ { PEISA_CONTROLLER eisaController; PNONCACHED_EXTENSION pNoncachedExt; ULONG i, length; BOOLEAN GotInt = FALSE; UCHAR EfpMsg; eisaController = DeviceExtension->EisaController; pNoncachedExt = DeviceExtension->NoncachedExt; // // Interrupts have already been disabled on entry (see OliEsc2FindAdapter). // Polling is possible through enabling the ESC-1 HP and EFP interface // in the System Doorbell Mask. // // // 1. Issue EFP_SET command via I/O instruction 'TYPE SERVICE' to the // BMIC mailbox registers. // if (!GainSemaphore0(eisaController)) { return(FALSE); } // if got the semaphore, place IRQ parameter in mailbox register 1 ScsiPortWritePortUchar(&eisaController->InParm1, DeviceExtension->IRQ_In_Use); // output a TYPE SERVICE request: 'efp_set' ScsiPortWritePortUchar(&eisaController->InTypeService, S_EFP_SET); // set bit 1 of local doorbell register to generate interrupt request // (EFP_ATTENTION) ScsiPortWritePortUchar(&eisaController->LocalDoorBell, EFP_INT_BIT); // Wait for controller to respond for (i = 0; i < WAIT_INT_LOOPS && !GotInt; i++) { if (ScsiPortReadPortUchar(&eisaController->SystemDoorBell) & EFP_TYPE_MSG) { GotInt = TRUE; } else { ScsiPortStallExecution(WAIT_INT_INTERVAL); } } if (!GotInt) { DebugPrint((1, "EfpGetDevinfo: No interrupt after EFP_SET.\n")); return(FALSE); } else { DebugPrint((4, "EfpGetDevinfo: Set, interrupt after %ld us.\n", i*WAIT_INT_INTERVAL)); EfpMsg = ScsiPortReadPortUchar(&eisaController->OutTypeMsg); if (EfpMsg != M_INIT_DIAG_OK) { DebugPrint((1, "EfpGetDevinfo: INIT_DIAG_OK not received after EFP_SET.\n")); if (EfpMsg == M_ERR_INIT) { DebugPrint((1, "EfpGetDevinfo: M_ERR_INIT received after EFP_SET.\n")); } return(FALSE); } } // reset bit 1 of the system doorbell register to clear request ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_MSG); // clear semaphore 1 after reading TYPE_MSG ScsiPortWritePortUchar(&eisaController->ResultSemaphore, 0); // // 2. Build queues descriptor. This initial QD defines only the mailbox // command queue and the reply queue. // // // First, initialize queue-related fields in HW_DEVICE_EXTENSION // and NONCACHED_EXTENSION. // DeviceExtension->Reply_Q_Full_Flag = 0; DeviceExtension->Reply_Q_Get = 0; DeviceExtension->RQ_In_Process = 0; pNoncachedExt->Command_Qs[0].Cmd_Q_Get = 0; pNoncachedExt->Command_Qs[0].Cmd_Q_Put = 0; for (i = 0; i < HA_QUEUES; i++) { DeviceExtension->Q_Full_Map[i] = 0; DeviceExtension->DevicesPresent[i].present = FALSE; } for (i = 0; i < REPLY_Q_ENTRIES; i++) { pNoncachedExt->Reply_Q[i].qnrply.nrply_flag = 0; } // set up Queues Descriptor header pNoncachedExt->QD_Head.qdh_maint = 0; // normal environment pNoncachedExt->QD_Head.qdh_n_cmd_q = 1; // # of queues pNoncachedExt->QD_Head.qdh_type_reply = 0; // int after 1+ pNoncachedExt->QD_Head.qdh_reserved1 = 0; pNoncachedExt->QD_Head.qdh_reply_q_addr = ScsiPortConvertPhysicalAddressToUlong( ScsiPortGetPhysicalAddress(DeviceExtension, NULL, // No SRB &pNoncachedExt->Reply_Q, &length)); pNoncachedExt->QD_Head.qdh_n_ent_reply = REPLY_Q_ENTRIES; pNoncachedExt->QD_Head.qdh_reserved2 = 0; // set up Queues Descriptor body for mailbox queue pNoncachedExt->QD_Bodies[0].qdb_scsi_level = 0xFF; pNoncachedExt->QD_Bodies[0].qdb_channel = 0xFF; pNoncachedExt->QD_Bodies[0].qdb_ID = 0xFF; pNoncachedExt->QD_Bodies[0].qdb_LUN = 0xFF; pNoncachedExt->QD_Bodies[0].qdb_n_entry_cmd = COMMAND_Q_ENTRIES; pNoncachedExt->QD_Bodies[0].qdb_notfull_int = 1; pNoncachedExt->QD_Bodies[0].qdb_no_ars = 0; // ESC2/EFP2 pNoncachedExt->QD_Bodies[0].qdb_timeout = 0; pNoncachedExt->QD_Bodies[0].qdb_cmd_q_addr = ScsiPortConvertPhysicalAddressToUlong( ScsiPortGetPhysicalAddress(DeviceExtension, NULL, // no SRB &pNoncachedExt->Command_Qs[0], &length)); pNoncachedExt->QD_Bodies[0].qdb_reserved = 0; // store the controller mailbox info in common area DeviceExtension->DevicesPresent[0].present = TRUE; DeviceExtension->DevicesPresent[0].qnumber = 0; DeviceExtension->DevicesPresent[0].qPtr = &pNoncachedExt->Command_Qs[0]; // // 3. Issue EFP_START command to BMIC mailbox registers. // if (!GainSemaphore0(eisaController)) { return(FALSE); } // // Get the physical address of the queues descriptor (and store it, // as we'll need it in RegisterEfpQueues). // DeviceExtension->QueuesDescriptor_PA = ScsiPortConvertPhysicalAddressToUlong( ScsiPortGetPhysicalAddress(DeviceExtension, NULL, // no SRB &pNoncachedExt->QD_Head, &length)); // if got the semaphore, output physical address of the queues descriptor // to mailbox registers 1 - 4. ScsiPortWritePortUlong((PULONG)&eisaController->InParm1, DeviceExtension->QueuesDescriptor_PA); // output a TYPE SERVICE request to 'efp_start' ScsiPortWritePortUchar(&eisaController->InTypeService, S_EFP_START); // set bit 1 of local doorbell register to generate an interrupt request ScsiPortWritePortUchar(&eisaController->LocalDoorBell, EFP_INT_BIT); // Wait for controller to respond. GotInt = FALSE; // re-initialize GotInt for (i = 0; i < WAIT_INT_LOOPS && !GotInt; i++) { if (ScsiPortReadPortUchar(&eisaController->SystemDoorBell) & EFP_TYPE_MSG) { // was EFP_MSG_INT() GotInt = TRUE; } else { ScsiPortStallExecution(WAIT_INT_INTERVAL); } } if (!GotInt) { DebugPrint((1, "EfpGetDevinfo: No interrupt after EFP_START.\n")); return(FALSE); } else { DebugPrint((4, "EfpGetDevinfo: Start, interrupt after %ld us.\n", i*WAIT_INT_INTERVAL)); EfpMsg = ScsiPortReadPortUchar(&eisaController->OutTypeMsg); if (EfpMsg != M_INIT_DIAG_OK) { DebugPrint((1, "EfpGetDevinfo: INIT_DIAG_OK not received after EFP_START.\n")); if (EfpMsg == M_ERR_INIT) { DebugPrint((1, "EfpGetDevinfo: M_ERR_INIT received after EFP_START.\n")); } return(FALSE); } } // reset bit 1 of the system doorbell register to clear request ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_MSG); // clear semaphore 1 after reading TYPE_MSG (RELEASE_SEM1) ScsiPortWritePortUchar(&eisaController->ResultSemaphore, 0); // // 4. Issue Get_Information command. // if (!EfpGetInformation(DeviceExtension)) { DebugPrint((1, "EfpGetDevinfo: EFP Get_Information failed.\n")); return(FALSE); } if (DeviceExtension->Max_CmdQ_ents < COMMAND_Q_ENTRIES) { DebugPrint((1, "EfpGetDevinfo: Controller's max command q size too small.\n")); return(FALSE); } // // 5. Issue Get_Configuration command. // if (!EfpGetConfiguration(DeviceExtension)) { DebugPrint((1, "EfpGetDevinfo: EfpGetConfiguration failed.\n")); return(FALSE); } DeviceExtension->TotalAttachedDevices = (USHORT)(DeviceExtension->Q_Buf.qmbr.mbr_length / sizeof(GET_CONF)); DebugPrint((1,"EfpGetDevinfo: TotalAttachedDevices = %d.\n", DeviceExtension->TotalAttachedDevices)); return(TRUE); } // end EfpGetDevinfo() BOOLEAN OliEsc2DisableEfp( IN PHW_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Description: Disables the EFP interface on the adapter sending the SET command. Arguments: DeviceExtension Device extension for this driver Return Value: Returns TRUE if initialization completed successfully. Returns FALSE and logs an error if adapter initialization fails. --*/ { PEISA_CONTROLLER eisaController; ULONG i; BOOLEAN GotInt = FALSE; UCHAR EfpMsg; eisaController = DeviceExtension->EisaController; // // Interrupts have already been disabled on entry (see OliEsc2FindAdapter). // Polling is possible through enabling the ESC-1 HP and EFP interface // in the System Doorbell Mask. // // // Issue EFP_SET command via I/O instruction 'TYPE SERVICE' to the // BMIC mailbox registers. // if (!GainSemaphore0(eisaController)) { return(FALSE); } // if got the semaphore, place IRQ parameter in mailbox register 1 ScsiPortWritePortUchar(&eisaController->InParm1, DeviceExtension->IRQ_In_Use); // output a TYPE SERVICE request: 'efp_set' ScsiPortWritePortUchar(&eisaController->InTypeService, S_EFP_SET); // set bit 1 of local doorbell register to generate interrupt request // (EFP_ATTENTION) ScsiPortWritePortUchar(&eisaController->LocalDoorBell, EFP_INT_BIT); // Wait for controller to respond for (i = 0; i < WAIT_INT_LOOPS && !GotInt; i++) { if (ScsiPortReadPortUchar(&eisaController->SystemDoorBell) & EFP_TYPE_MSG) { GotInt = TRUE; } else { ScsiPortStallExecution(WAIT_INT_INTERVAL); } } if (!GotInt) { DebugPrint((1, "OliEsc2Disable: No interrupt after EFP_SET.\n")); return(FALSE); } else { DebugPrint((4, "OliEsc2Disable: Set, interrupt after %ld us.\n", i*WAIT_INT_INTERVAL)); EfpMsg = ScsiPortReadPortUchar(&eisaController->OutTypeMsg); if (EfpMsg != M_INIT_DIAG_OK) { DebugPrint((1, "OliEsc2Disable: INIT_DIAG_OK not received after EFP_SET.\n")); if (EfpMsg == M_ERR_INIT) { DebugPrint((1, "OliEsc2Disable: M_ERR_INIT received after EFP_SET.\n")); } return(FALSE); } } // reset bit 1 of the system doorbell register to clear request ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_MSG); // clear semaphore 1 after reading TYPE_MSG ScsiPortWritePortUchar(&eisaController->ResultSemaphore, 0); return(TRUE); } BOOLEAN OliEsc2StartIo( 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 an SRB 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; PEFP_SGL pEFP; UCHAR opCode = 0; UCHAR qnumber, TarLun, SignalFlag; PSCSI_REQUEST_BLOCK abortedSrb; UCHAR Bus; DebugPrint((3,"OliEsc2StartIo: Enter routine.\n")); // // Get the base address of the ESC-2 registers' address space // DeviceExtension = HwDeviceExtension; eisaController = DeviceExtension->EisaController; // // Get EFP command extension from SRB // pEFP = Srb->SrbExtension; DebugPrint((3,"OliEsc2StartIo: Srb->Function = %x,\n", Srb->Function)); DebugPrint((3,"OliEsc2StartIo: PathId=%d TargetId=%d Lun=%d.\n", Srb->PathId,Srb->TargetId,Srb->Lun)); // // 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,"OliEsc2StartIo: 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. // for (Bus=0; Bus < DeviceExtension->NumberOfBuses; Bus++) { ScsiPortNotification(ResetDetected, DeviceExtension, Bus); } // // The controller is now ready for the next request. // ScsiPortNotification(NextRequest, DeviceExtension, NULL); return(TRUE); } // // Perform the requested function. // switch (Srb->Function) { case SRB_FUNCTION_EXECUTE_SCSI: TarLun = GET_QINDEX(Srb->PathId, Srb->TargetId, Srb->Lun); // // Do not process requests to non-existent devices. // if (Srb->TargetId == DeviceExtension->Adapter_ID[Srb->PathId] || !DeviceExtension->DevicesPresent[TarLun].present) { // // We are filtering SCSI messages directed to not present // devices and messages to the host adapter itself. // DebugPrint((2, "OliEsc2StartIo: Rejected SCSI cmd to TID %d LUN %d.\n", Srb->TargetId, Srb->Lun)); Srb->SrbStatus = SRB_STATUS_NO_DEVICE; ScsiPortNotification(RequestComplete, DeviceExtension, Srb); ScsiPortNotification(NextRequest, DeviceExtension, NULL); return(TRUE); } break; // // ... if the request is for a valid device, go at the end of this // switch body. // case SRB_FUNCTION_ABORT_COMMAND: DebugPrint((2, "OliEsc2StartIo: Abort Cmd Target ID %d.\n", Srb->TargetId)); // // Verify that SRB to abort is still outstanding. // abortedSrb = ScsiPortGetSrb(DeviceExtension, Srb->PathId, Srb->TargetId, Srb->Lun, Srb->QueueTag); if (abortedSrb != Srb->NextSrb || abortedSrb->SrbStatus != SRB_STATUS_PENDING) { DebugPrint((1, "OliEsc2StartIo: SRB to abort already completed.\n")); // // Complete abort SRB. // Srb->SrbStatus = SRB_STATUS_ABORT_FAILED; ScsiPortNotification(RequestComplete, DeviceExtension, Srb); } else { // // Only Reset Bus, not Abort Request or Reset Device, is supported // on the ESC-2 in EFP mode, so we will send a Reset Bus command. // // // 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. // OliEsc2ResetBus(DeviceExtension, Srb->PathId); } // // The controller is now ready for next request. // ScsiPortNotification(NextRequest, DeviceExtension, NULL); return(TRUE); case SRB_FUNCTION_RESET_BUS: // // Reset ESC-2 and SCSI bus. // DebugPrint((2, "OliEsc2StartIo: 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. // OliEsc2ResetBus(DeviceExtension, Srb->PathId); // // The controller is now ready for next request. // ScsiPortNotification(NextRequest, DeviceExtension, NULL); return(TRUE); case SRB_FUNCTION_RESET_DEVICE: // // Only Reset Bus, not Reset Device, is supported on the ESC-2 // in EFP mode, so we will send a Reset Bus command. // DebugPrint((2, "OliEsc2StartIo: Reset Device Target ID %d.\n", Srb->TargetId)); // // 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. // OliEsc2ResetBus(DeviceExtension, Srb->PathId); // // The controller is now ready for next request. // ScsiPortNotification(NextRequest, DeviceExtension, NULL); return(TRUE); default: // // Set error and complete request // DebugPrint((1, "OliEsc2StartIo: Invalid Request.\n")); Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST; ScsiPortNotification(RequestComplete, DeviceExtension, Srb); ScsiPortNotification(NextRequest, DeviceExtension, NULL); return(TRUE); } // end switch (function to perform) //----------------------------------------------------------------- // The following code can be executed only if we are coming from // the SRB_FUNCTION_EXECUTE_SCSI function. //----------------------------------------------------------------- // // Get the pointer to the extension data area associated with // the pair (Srb->TargetId, Srb->Lun) // luExtension = ScsiPortGetLogicalUnit(DeviceExtension, Srb->PathId, Srb->TargetId, Srb->Lun); ASSERT(luExtension); // // 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 EFP command. // BuildEfpCmd(DeviceExtension, Srb); // // Enqueue the command // if (!EnqueueEfpCmd(DeviceExtension, &pEFP->EfpCmd, DeviceExtension->DevicesPresent[TarLun].qPtr, TarLun, &SignalFlag)) { // // Error enqueuing the command. // if (SignalFlag) { // send command time out // // Let the operating system time-out this SRB. // ScsiPortLogError( DeviceExtension, NULL, Srb->PathId, DeviceExtension->Adapter_ID[Srb->PathId], 0, SP_INTERNAL_ADAPTER_ERROR, SEND_COMMAND_TIMED_OUT ); DebugPrint((1,"OliEsc2StartIo: Send command timed out.\n")); // // Ready for the next command but not for the same queue. // ScsiPortNotification(NextRequest, DeviceExtension, NULL); // // Note: the StartIo return code is not checked. // return (FALSE); } else { // command not enqueued, queue was full // // Note: this error should never happen ! // qnumber = DeviceExtension->DevicesPresent[TarLun].qnumber; DebugPrint((1, "OliEsc2StartIo: Command queue %d was full.\n", qnumber)); Srb->SrbStatus = SRB_STATUS_BUSY; ScsiPortNotification(RequestComplete, DeviceExtension, Srb); ScsiPortNotification(NextRequest, DeviceExtension, NULL); return (TRUE); } } // end if (command not enqueued) // // The command has been enqueued, // the adapter is ready for the next command. // if (SignalFlag) { // command enqueued, queue not full // // ESC-2 supports multiple requests and // the queue for this device is not full. // ScsiPortNotification(NextLuRequest, DeviceExtension, Srb->PathId, Srb->TargetId, Srb->Lun); return(TRUE); } else { // command enqueued, queue is now full // // The queue is now full, no more commands for this device. // qnumber = DeviceExtension->DevicesPresent[TarLun].qnumber; DeviceExtension->Q_Full_Map[qnumber] = 1; DebugPrint((2,"OliEsc2StartIo: Command queue %d is now full.\n", qnumber)); ScsiPortNotification(NextRequest, DeviceExtension, NULL); return(TRUE); } } // end OliEsc2StartIo() BOOLEAN OliEsc2Interrupt( IN PVOID HwDeviceExtension ) /*++ Routine Description: This is the interrupt service routine for the ESC-2 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 output mailbox is full, the SRB 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 // START NOTE EFP_MIRRORING_ENABLED. // // The OliEsc2Interrupt routine always uses the "userid" field of the // NORMAL_REPLY struct to retrieve the SRB. This is OK because the "userid" // field is at the same offset in both structures (NORMAL_REPLY and // MIRROR_REPLY). // // END NOTE EFP_MIRRORING_ENABLED. --*/ { PHW_DEVICE_EXTENSION DeviceExtension; PEISA_CONTROLLER eisaController; PLU_EXTENSION LuExtension; UCHAR messagedata, qnumber, intpending; PSCSI_REQUEST_BLOCK pSrb; UCHAR sensevalid = FALSE; #if EFP_MIRRORING_ENABLED PMIRROR_REPLY mr; PMREPLY_SDATA pMreplySdata; UCHAR TarLun; PTAR_Q pDevInfo; USHORT Index; SENSE_DATA Sdata; #endif // EFP_MIRRORING_ENABLED 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. // intpending = ScsiPortReadPortUchar(&eisaController->SystemDoorBell); DebugPrint((3, "OliEsc2Interrupt: intpending on entry is: %x.\n", intpending)); // // Check if this is our interrupt // if (!(intpending & INTERRUPTS_ENABLE)) { // // No interrupt is pending. // Enable interrupts. // ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask, INTERRUPTS_ENABLE); return(FALSE); } // // Check if any ESC-1 type interrupt // if (intpending & ESC_INT_BIT) { // // Int this driver, ESC-1 HP interrupts are used only for the // reset_bus command. // // // reset the interrupt // ScsiPortWritePortUchar(&eisaController->SystemDoorBell, ESC_INT_BIT); // // unlock the semaphore // ScsiPortWritePortUchar(&eisaController->ResultSemaphore, SEM_UNLOCK); } // // Check for any EFP_TYPE_MSG interrupt // if (intpending & EFP_TYPE_MSG) { // // Get the message. // messagedata = ScsiPortReadPortUchar(&eisaController->OutTypeMsg); // // Acknowledge the type message interrupt // ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_MSG); // // Unlock the Result Semaphore, so that the ESC-2 can load // new values in the output mailboxes. // ScsiPortWritePortUchar(&eisaController->ResultSemaphore, SEM_UNLOCK); // // Check the type of message. // if (messagedata & M_ERR_CMDQ_NFUL) { // // The queue is no longer full. Notify the ScsiPort driver. // messagedata &= 0x7F; qnumber = (UCHAR)messagedata; DeviceExtension->Q_Full_Map[qnumber] = 0; ScsiPortNotification( NextLuRequest, DeviceExtension, DeviceExtension->NoncachedExt->QD_Bodies[qnumber].qdb_channel - 1, DeviceExtension->NoncachedExt->QD_Bodies[qnumber].qdb_ID, DeviceExtension->NoncachedExt->QD_Bodies[qnumber].qdb_LUN ); // // Send a info message to the debug port // DebugPrint((2,"OliEsc2Interrupt: Command queue %d not full.\n", qnumber)); } else if (messagedata == M_REPLY_Q_FULL) { // // mark reply Q full // DeviceExtension->Reply_Q_Full_Flag = 0x01; // // NOTE: This is just a workaround for the ESC-2 firmware. // (at least up to revision 2.25). // If the reply queue gets full, the ESC-2 firmware // sends only the "Reply Queue Full" message. // To make it work with our logic we need to simulate // a "Command Complete" interrupt. // intpending |= EFP_CMD_COMPL; // // Send a info message to the debug port // DebugPrint((2,"OliEsc2Interrupt: Reply queue is full.\n")); } else { // // Send a warning message to the debug port // DebugPrint((1, "OliEsc2Interrupt: Unknown message: %x.\n", messagedata)); } } // end {int_status is EFP_TYPE_MESSAGE} // // Check if any EFP command complete interrupt // if (intpending & EFP_CMD_COMPL) { // // This is the EFP command completion processing // // // Loop through the reply queue start from Reply_Get to look for // valid reply entry and send out command completion // while (1) { // // Acknowledge interrupt. // // The interrupt is reset each time a reply is dequeued // to remove any subsequent interrupts of the same type. // ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_INT); // // Dequeue a reply. // if (!DequeueEfpReply(DeviceExtension)) { // // Note: also if there was an error during the dequeue // phase, we still need to check the RQ_In_Process // for any valid reply. // // // Log error. // ScsiPortLogError( DeviceExtension, NULL, 0, 0, 0, SP_INTERNAL_ADAPTER_ERROR, ESCX_REPLY_DEQUEUE_ERROR ); // // Send error message to the debug port // DebugPrint((1, "OliEsc2Interrupt: Reply dequeue error.\n")); } // // Note: this is the only place where we can exit the loop ! // if (DeviceExtension->RQ_In_Process == 0x00) { // any reply ? break; // No, exit loop. } // // Check for any EFP_TYPE_MSG interrupt. // We need to update the Reply_Q_Full_Flag before dequeuing // another request. // if (ScsiPortReadPortUchar(&eisaController->SystemDoorBell) & EFP_TYPE_MSG) { // // Get the message. // messagedata = ScsiPortReadPortUchar( &eisaController->OutTypeMsg); // // Acknowledge the type message interrupt // ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_MSG); // // Unlock the Result Semaphore, so that the ESC-2 can load // new values in the output mailboxes. // ScsiPortWritePortUchar(&eisaController->ResultSemaphore, SEM_UNLOCK); // // Check the type of message. // if (messagedata & M_ERR_CMDQ_NFUL) { // // The queue is no longer full. Notify the ScsiPort driver. // messagedata &= 0x7F; qnumber = (UCHAR)messagedata; DeviceExtension->Q_Full_Map[qnumber] = 0; ScsiPortNotification( NextLuRequest, DeviceExtension, DeviceExtension->NoncachedExt->QD_Bodies[qnumber]. qdb_channel - 1, DeviceExtension->NoncachedExt->QD_Bodies[qnumber]. qdb_ID, DeviceExtension->NoncachedExt->QD_Bodies[qnumber]. qdb_LUN ); // // Send a info message to the debug port // DebugPrint((2, "OliEsc2Interrupt: Command queue %d not full.\n", qnumber)); } else if (messagedata == M_REPLY_Q_FULL) { // // mark reply Q full // DeviceExtension->Reply_Q_Full_Flag = 0x01; // // Send a info message to the debug port // DebugPrint((2, "OliEsc2Interrupt: Reply queue is full.\n")); } else { // // Send a warning message to the debug port // DebugPrint((1, "OliEsc2Interrupt: Unknown message: %x.\n", messagedata)); } } // end {int_status is EFP_TYPE_MESSAGE} // // Make sure the reply is valid. // pSrb = (PVOID) DeviceExtension->Q_Buf.qnrply.nrply_userid; if (pSrb == NULL) { // // Can't get valid SRB pointer for this reply entry // Log it as an error. // ScsiPortLogError( DeviceExtension, NULL, 0, 0, 0, SP_INTERNAL_ADAPTER_ERROR, ESCX_BAD_PHYSICAL_ADDRESS ); // // Send error message to the debug port // DebugPrint((1, "OliEsc2Interrupt: Bad physical address.\n")); // // Go check if there is any other reply. // continue; } // // The ESC-2 and EFP-2 don't report the underrun/overrun error // during the execution of some commands (example: inquiry). // The following code detects these cases and simulates an // underrun/overrun error. // if (DeviceExtension->Q_Buf.qnrply.nrply_scsi_len != pSrb->DataTransferLength && (DeviceExtension->Q_Buf.qnrply.nrply_status == EFP_CMD_SUCC || DeviceExtension->Q_Buf.qnrply.nrply_status == EFP_AUTOREC_OK)){ DeviceExtension->Q_Buf.qnrply.nrply_status = EFP_DATA_RUN; DebugPrint((2, "OliEsc2Interrupt: Simulated an overrun/underrun error.\n")); } // // Check the status of the operation. // #if EFP_MIRRORING_ENABLED // --------------------------------------------------------------- // // MIRRORING REPLY // // --------------------------------------------------------------- // // Initialize pointer to mirroring reply // mr = &DeviceExtension->Q_Buf.qmrply; if (mr->mrply_flag == MREPLY_VALID) { // // Get the reply state. // switch (mr->mrply_off_attr){ case EFP_SOURCE_OFFLINE: // // Get the mirror disk status. // pMreplySdata = (PMREPLY_SDATA)&mr->mrply_valid2; break; case EFP_MIRROR_OFFLINE: // // Get the source disk status. // pMreplySdata = (PMREPLY_SDATA)&mr->mrply_valid1; break; default: // // Mirroring in progress, get the status of // the disk that has the SCSI status related // to the SCSI device and not to controller. // If both disks have a valid SCSI status, get // the lowest. The following table shows the // possible values of "Valid": // // 00 -> Ok or Sense Data related to the controller. // 70 -> Sense data related to the disk. // F0 -> Sense data related to the disk. // 31 -> Ok (condition met). // 32 -> EFP-2 never returns this value because if // the 1st and 2nd ARP fail, it returns // hardware error using the "00" value. // 34 -> Ok (intermediate good). // 35 -> Ok (intermediate good/condition met). // 36 -> EFP-2 never returns this value because if // the 1st and 2nd ARP fail, it returns // hardware error using the "00" value. // if (mr->mrply_valid2 == EFP_NO_ERROR) { // // Get the source disk status // pMreplySdata = (PMREPLY_SDATA)&mr->mrply_valid1; } else if (mr->mrply_valid1 == EFP_NO_ERROR) { // // Get the mirror disk status // pMreplySdata = (PMREPLY_SDATA)&mr->mrply_valid2; } else if (mr->mrply_valid1 <= mr->mrply_valid2) { // // Get the source disk status // pMreplySdata = (PMREPLY_SDATA)&mr->mrply_valid1; } else { // // Get the mirror disk status // pMreplySdata = (PMREPLY_SDATA)&mr->mrply_valid2; } break; } // // Convert EFP error in SRB error. // switch(mr->mrply_status) { case EFP_AUTOREC_OK: case EFP_CMD_SUCC: // // Successful command. // pSrb->SrbStatus = SRB_STATUS_SUCCESS; switch(pMreplySdata->Valid) { case EFP_NO_ERROR: pSrb->ScsiStatus = SCSISTAT_GOOD; break; case EFP_COND_MET: pSrb->ScsiStatus = SCSISTAT_CONDITION_MET; break; case EFP_INTER_GOOD: pSrb->ScsiStatus = SCSISTAT_INTERMEDIATE; break; case EFP_INTER_COND: pSrb->ScsiStatus = SCSISTAT_INTERMEDIATE_COND_MET; break; default: // // The autonomous recovery procedure was // successful and after the recovery the // controller received a CHECK_CONDITION // status (with ARS enabled) and a RECOVERED // error as a reply to a REQUEST SENSE. // pSrb->ScsiStatus = SCSISTAT_GOOD; break; } break; case EFP_DATA_RUN: pSrb->SrbStatus = SRB_STATUS_DATA_OVERRUN; // // If overrun error, log it. // The underrun error can be very common on // some devices (example: scanner). // if (pSrb->DataTransferLength <= mr->mrply_scsi_len) { ScsiPortLogError( DeviceExtension, NULL, pSrb->PathId, DeviceExtension->Adapter_ID[pSrb->PathId], 0, SP_INTERNAL_ADAPTER_ERROR, EFP_DATA_RUN ); } pSrb->DataTransferLength = mr->mrply_scsi_len; break; case EFP_AUTOREC_KO: case EFP_WARN_ERR: // // Get the device info pointer. // TarLun = GET_QINDEX(pSrb->PathId, pSrb->TargetId, pSrb->Lun); pDevInfo = &DeviceExtension->DevicesPresent[TarLun]; // // Check if the source disk is for the first time // off-line. // if (mr->mrply_off_attr == EFP_SOURCE_OFFLINE && (pDevInfo->SourceDiskState == EfpFtMemberHealthy || pDevInfo->SourceDiskState == EfpFtMemberMissing)) { // // Check if we need to log an error. // if (!pDevInfo->KnownError) { // // Remember it. // pDevInfo->KnownError = TRUE; // // Log the error // ScsiPortLogError( DeviceExtension, NULL, pSrb->PathId, pSrb->TargetId, pSrb->Lun, SP_INTERNAL_ADAPTER_ERROR, (pDevInfo->SourceDiskState == EfpFtMemberHealthy ? EFP_SOURCE_OFFLINE_ERROR : EFP_MISSING_SOURCE_ERROR) | mr->mrply_d_off ); // // Send an error message to the debug port // DebugPrint((1, "OliEsc2Interrupt: Source disk %s," " Bus=%x, Tid=%x, Lun=%x,\n", pDevInfo->SourceDiskState == EfpFtMemberHealthy ? "off-line" : "missing", pSrb->PathId, pSrb->TargetId, pSrb->Lun)); DebugPrint((1, "Valid=%x, " "Sense=%x, " "Addit=%x, " "Qualif=%x, " "Info=%x.\n", mr->mrply_valid1, mr->mrply_sense1, mr->mrply_addit1, mr->mrply_qualif1, mr->mrply_info1 )); } // // This is the first time that the source disk // is off-line. Update source state. // pDevInfo->SourceDiskState = EfpFtMemberDisabled; #if EFP_RETURN_BUSY // // Return the general SCSI busy error code. // The ScsiPort driver will retry the command. // pSrb->SrbStatus = SRB_STATUS_ERROR; pSrb->ScsiStatus = SCSISTAT_BUSY; #else // // Return the SRB bus reset error code. // The class driver will retry the command. // pSrb->SrbStatus = SRB_STATUS_BUS_RESET; #endif break; } // end if (1st time source disk off line) // // Check if the mirror disk is for the first time // off-line. // if (mr->mrply_off_attr == EFP_MIRROR_OFFLINE && (pDevInfo->MirrorDiskState == EfpFtMemberHealthy || pDevInfo->MirrorDiskState == EfpFtMemberMissing)) { // // Check if we need to log an error. // if (!pDevInfo->KnownError) { // // Remember it. // pDevInfo->KnownError = TRUE; // // Log the error // ScsiPortLogError( DeviceExtension, NULL, pSrb->PathId, pSrb->TargetId, pSrb->Lun, SP_INTERNAL_ADAPTER_ERROR, (pDevInfo->MirrorDiskState == EfpFtMemberHealthy ? EFP_MIRROR_OFFLINE_ERROR : EFP_MISSING_MIRROR_ERROR) | mr->mrply_d_off ); // // Send an error message to the debug port // DebugPrint((1, "OliEsc2Interrupt: Mirror disk %s," " Bus=%x, Tid=%x, Lun=%x,\n", pDevInfo->MirrorDiskState == EfpFtMemberHealthy ? "off-line" : "missing", D_OFF_TO_PATH(mr->mrply_d_off), D_OFF_TO_TARGET(mr->mrply_d_off), D_OFF_TO_LUN(mr->mrply_d_off))); DebugPrint((1, "Valid=%x, " "Sense=%x, " "Addit=%x, " "Qualif=%x, " "Info=%x.\n", mr->mrply_valid2, mr->mrply_sense2, mr->mrply_addit2, mr->mrply_qualif2, mr->mrply_info2 )); } // // This is the first time that the mirror disk // is off-line. Update mirroring state. // pDevInfo->MirrorDiskState = EfpFtMemberDisabled; #if EFP_RETURN_BUSY // // Return the general SCSI busy error code. // The ScsiPort driver will retry the command. // pSrb->SrbStatus = SRB_STATUS_ERROR; pSrb->ScsiStatus = SCSISTAT_BUSY; #else // // Return the SRB bus reset error code. // The class driver will retry the command. // pSrb->SrbStatus = SRB_STATUS_BUS_RESET; #endif break; } // end if (1st time mirror disk off line) // // Send an error message to the debug port // DebugPrint((2, "OliEsc2Interrupt: EFP command status error,\n" " Status=%x, Valid=%x.\n", mr->mrply_status, pMreplySdata->Valid)); // // SRB status error. // pSrb->SrbStatus = SRB_STATUS_ERROR; // // SCSI status error. // switch(pMreplySdata->Valid) { case EFP_DEV_BUSY: pSrb->ScsiStatus = SCSISTAT_BUSY; break; case EFP_RESV_CONF: pSrb->ScsiStatus = SCSISTAT_RESERVATION_CONFLICT; break; case EFP_SENSE_INFO: case EFP_SENSE_NO_INFO: pSrb->ScsiStatus = SCSISTAT_CHECK_CONDITION; // // Update the SRB sense data area // if (pSrb->SenseInfoBufferLength != 0 && !(pSrb->SrbFlags & SRB_FLAGS_DISABLE_AUTOSENSE )) { // // Initialize the sense area to zero // for( Index=0; Index < sizeof(SENSE_DATA); Index++) { *((PUCHAR)&Sdata + Index) = 0; } Sdata.ErrorCode = pMreplySdata->Valid; Sdata.SenseKey = pMreplySdata->Sense; Sdata.AdditionalSenseCode = pMreplySdata->Addit; Sdata.AdditionalSenseCodeQualifier = pMreplySdata->Qualif; Sdata.Information[3]= (UCHAR)pMreplySdata->Info; Sdata.Information[2]= (UCHAR)(pMreplySdata->Info >> 8); Sdata.Information[1]= (UCHAR)(pMreplySdata->Info >> 16); Sdata.Information[0]= (UCHAR)(pMreplySdata->Info >> 24); // // Copy the sense data in the SRB // ScsiPortMoveMemory( pSrb->SenseInfoBuffer, &Sdata, pSrb->SenseInfoBufferLength ); // // The sense data area is now ok to read // pSrb->SrbStatus |= SRB_STATUS_AUTOSENSE_VALID; // // Send info to the debug port // DebugPrint((2, "OliEsc2Interrupt: Sense data, " "Valid=%x, " "Sense=%x, " "Addit=%x, " "Qualif=%x, " "Info=%x.\n", pMreplySdata->Valid, pMreplySdata->Sense, pMreplySdata->Addit, pMreplySdata->Qualif, pMreplySdata->Info )); } // end if (sense data) else { // // Send a warning message to debug port // DebugPrint((1, "OliEsc2Interrupt: Warning, " "sense data is lost.\n")); } break; case EFP_NO_ERROR: // // Device status is related to autonomous // recovery procedure of the controller. // The real status of the device is unknown. // pSrb->ScsiStatus = SCSISTAT_GOOD; break; default: // // Unknown error. // pSrb->ScsiStatus = SCSISTAT_GOOD; DebugPrint((1, "OliEsc2Interrupt: Status=%x, Valid=%x ?.\n", mr->mrply_status, pMreplySdata->Valid)); break; } break; default: // // Unknown error. // pSrb->SrbStatus = SRB_STATUS_ERROR; pSrb->ScsiStatus = SCSISTAT_GOOD; DebugPrint((1, "OliEsc2Interrupt: Status=%x ?.\n", mr->mrply_status)); ScsiPortLogError( DeviceExtension, NULL, pSrb->PathId, DeviceExtension->Adapter_ID[pSrb->PathId], 0, SP_INTERNAL_ADAPTER_ERROR, mr->mrply_status ); break; } // end switch (reply status) } // end if (mirroring reply) // --------------------------------------------------------------- // // NORMAL/MAINTENANECE REPLY // // --------------------------------------------------------------- else { #endif // EFP_MIRRORING_ENABLED if (DeviceExtension->Q_Buf.qnrply.nrply_status == EFP_CMD_SUCC) { // // Successful command // pSrb->SrbStatus = SRB_STATUS_SUCCESS; switch(DeviceExtension->Q_Buf.qnrply.nrply_ex_stat) { case EFP_NO_ERROR: pSrb->ScsiStatus = SCSISTAT_GOOD; break; case EFP_COND_MET: pSrb->ScsiStatus = SCSISTAT_CONDITION_MET; break; case EFP_INTER_GOOD: pSrb->ScsiStatus = SCSISTAT_INTERMEDIATE; break; case EFP_INTER_COND: pSrb->ScsiStatus = SCSISTAT_INTERMEDIATE_COND_MET; break; default: DebugPrint((1, "OliEsc2Interrupt: Status=00, ExStat=%x ?.\n", DeviceExtension->Q_Buf.qnrply.nrply_ex_stat)); pSrb->ScsiStatus = SCSISTAT_GOOD; break; } // end switch } else if (DeviceExtension->Q_Buf.qnrply.nrply_status == EFP_AUTOREC_OK) { // // Successful command using the autonomous recovery procedure // (ARP). // // Note that only the EFP-2 controller can return this type // of error code. // pSrb->SrbStatus = SRB_STATUS_SUCCESS; switch(DeviceExtension->Q_Buf.qnrply.nrply_ex_stat) { case EFP_NO_ERROR: case EFP_CHK_COND: pSrb->ScsiStatus = SCSISTAT_GOOD; break; default: DebugPrint((1, "OliEsc2Interrupt: Status=10, ExStat=%x ?.\n", DeviceExtension->Q_Buf.qnrply.nrply_ex_stat)); pSrb->ScsiStatus = SCSISTAT_GOOD; break; } // end switch } else if (DeviceExtension->Q_Buf.qnrply.nrply_status == EFP_WARN_ERR) { // // The request completed with an error // DebugPrint((2, "OliEsc2Interrupt: EFP command status error,\n" " Status=%x, ExStat=%x.\n", DeviceExtension->Q_Buf.qnrply.nrply_status, DeviceExtension->Q_Buf.qnrply.nrply_ex_stat)); pSrb->SrbStatus = SRB_STATUS_ERROR; switch(DeviceExtension->Q_Buf.qnrply.nrply_ex_stat) { case EFP_NO_ERROR: // // We have automatic request sense enabled, // so the 00 in ex_stat together with status = 01 // will be interpreted here as a check condition // returned in the sense key. // pSrb->ScsiStatus = SCSISTAT_CHECK_CONDITION; sensevalid = TRUE; pSrb->DataTransferLength = DeviceExtension->Q_Buf.qnrply.nrply_scsi_len; break; case EFP_CHK_COND: // This should NEVER happen (because ARS enabled) DebugPrint((1, "OliEsc2Interrupt: Status=01, ExStat=30: w/ARS ?.\n")); pSrb->ScsiStatus = SCSISTAT_CHECK_CONDITION; pSrb->DataTransferLength = DeviceExtension->Q_Buf.qnrply.nrply_scsi_len; break; case EFP_DEV_BUSY: pSrb->ScsiStatus = SCSISTAT_BUSY; break; case EFP_RESV_CONF: pSrb->ScsiStatus = SCSISTAT_RESERVATION_CONFLICT; break; case EFP_ABORT_CMD: pSrb->ScsiStatus = SCSISTAT_COMMAND_TERMINATED; break; default: // // Unknown error. // pSrb->ScsiStatus = SCSISTAT_GOOD; DebugPrint((1, "OliEsc2Interrupt: Status=01, ExStat=%x ?.\n", DeviceExtension->Q_Buf.qnrply.nrply_ex_stat)); break; } // end switch // // Check if we need to copy the sense data to the SRB. // if (pSrb->SenseInfoBufferLength != 0 && !(pSrb->SrbFlags & SRB_FLAGS_DISABLE_AUTOSENSE) && sensevalid) { // // copy sense data information to the SRB SenseInfoBuffer // ScsiPortMoveMemory(pSrb->SenseInfoBuffer, &DeviceExtension->Q_Buf.qnrply.nrply_sense, pSrb->SenseInfoBufferLength); pSrb->SrbStatus |= SRB_STATUS_AUTOSENSE_VALID; DebugPrint((2, "SenseKey: %x, AdditCode: %x, AdditQual: %x.\n", ((PSENSE_DATA)pSrb->SenseInfoBuffer)->SenseKey, ((PSENSE_DATA)pSrb->SenseInfoBuffer)->AdditionalSenseCode, ((PSENSE_DATA) pSrb->SenseInfoBuffer)->AdditionalSenseCodeQualifier)); } else if (sensevalid) { DebugPrint((1, "OliEsc2Interrupt: Warning, sense data is lost.\n")); } } else if (DeviceExtension->Q_Buf.qnrply.nrply_status == EFP_AUTOREC_KO) { // // The request completed with an error, // the autonomous recovery procedure (ARP) was not // successful. // // Note that only the EFP-2 controller can return this type // of error code. // DebugPrint((2, "OliEsc2Interrupt: EFP command status error,\n" " Status=%x, ExStat=%x.\n", DeviceExtension->Q_Buf.qnrply.nrply_status, DeviceExtension->Q_Buf.qnrply.nrply_ex_stat)); pSrb->SrbStatus = SRB_STATUS_ERROR; switch(DeviceExtension->Q_Buf.qnrply.nrply_ex_stat) { case EFP_NO_ERROR: // // Unknown cause. // pSrb->ScsiStatus = SCSISTAT_GOOD; break; case EFP_CHK_COND: pSrb->ScsiStatus = SCSISTAT_GOOD; break; case EFP_DEV_BUSY: pSrb->ScsiStatus = SCSISTAT_BUSY; break; case EFP_RESV_CONF: pSrb->ScsiStatus = SCSISTAT_RESERVATION_CONFLICT; break; default: // // Unknown error. // pSrb->ScsiStatus = SCSISTAT_GOOD; DebugPrint((1, "OliEsc2Interrupt: Status=18, ExStat=%x ?.\n", DeviceExtension->Q_Buf.qnrply.nrply_ex_stat)); break; } // end switch } else { // // The request completed with an error. // DebugPrint((2, "OliEsc2Interrupt: EFP command status error,\n" " Status=%x, ExStat=%x.\n", DeviceExtension->Q_Buf.qnrply.nrply_status, DeviceExtension->Q_Buf.qnrply.nrply_ex_stat)); // note that no sense data is returned for these cases. switch (DeviceExtension->Q_Buf.qnrply.nrply_status) { case EFP_SEL_TIMEOUT: pSrb->SrbStatus = SRB_STATUS_SELECTION_TIMEOUT; break; case EFP_DATA_RUN: pSrb->SrbStatus = SRB_STATUS_DATA_OVERRUN; // // If overrun error, log it. // The underrun error can be very common on // some devices (example: scanner). // if (pSrb->DataTransferLength <= DeviceExtension->Q_Buf.qnrply.nrply_scsi_len) { ScsiPortLogError( DeviceExtension, NULL, pSrb->PathId, DeviceExtension->Adapter_ID[pSrb->PathId], 0, SP_INTERNAL_ADAPTER_ERROR, EFP_DATA_RUN ); } pSrb->DataTransferLength = DeviceExtension->Q_Buf.qnrply.nrply_scsi_len; break; case EFP_BUS_FREE: pSrb->SrbStatus = SRB_STATUS_UNEXPECTED_BUS_FREE; break; case EFP_PHASE_ERR: pSrb->SrbStatus = SRB_STATUS_PHASE_SEQUENCE_FAILURE; break; // // NOTE: The following error codes are only returned // by the ESC-2 controller. case PARITY_ERROR: // // This is a severe error. // The controller is now shut down. // pSrb->SrbStatus = SRB_STATUS_PARITY_ERROR; ScsiPortLogError( DeviceExtension, NULL, pSrb->PathId, DeviceExtension->Adapter_ID[pSrb->PathId], 0, SP_BUS_PARITY_ERROR, 0 ); break; case BUS_RESET_BY_TARGET: // // No error logging. // // Return bus reset error, because the bus has been // reset. // pSrb->SrbStatus = SRB_STATUS_BUS_RESET; break; case PROTOCOL_ERROR: // // The ESC-2 resets the bus when it detects this // type of error. // Return bus reset error. // pSrb->SrbStatus = SRB_STATUS_BUS_RESET; ScsiPortLogError( DeviceExtension, NULL, pSrb->PathId, DeviceExtension->Adapter_ID[pSrb->PathId], 0, SP_PROTOCOL_ERROR, 0 ); break; case UNEXPECTED_PHASE_CHANGE: // // This is a severe error. // The controller is now shut down. // case AUTO_REQUEST_SENSE_FAILURE: case PARITY_ERROR_DURING_DATA_PHASE: pSrb->SrbStatus = SRB_STATUS_ERROR; pSrb->ScsiStatus = SCSISTAT_GOOD; ScsiPortLogError( DeviceExtension, NULL, pSrb->PathId, DeviceExtension->Adapter_ID[pSrb->PathId], 0, SP_INTERNAL_ADAPTER_ERROR, DeviceExtension->Q_Buf.qnrply.nrply_status ); break; default: // // Unknown error. // pSrb->SrbStatus = SRB_STATUS_ERROR; pSrb->ScsiStatus = SCSISTAT_GOOD; DebugPrint((1, "OliEsc2Interrupt: Status=%x ?.\n", DeviceExtension->Q_Buf.qnrply.nrply_status)); ScsiPortLogError( DeviceExtension, NULL, pSrb->PathId, DeviceExtension->Adapter_ID[pSrb->PathId], 0, SP_INTERNAL_ADAPTER_ERROR, DeviceExtension->Q_Buf.qnrply.nrply_status ); break; } // end switch } // end status check #if EFP_MIRRORING_ENABLED } // end if (!mirroring reply) #endif // EFP_MIRRORING_ENABLED // // Decrement the pending requests counter for this // (targetId, LUN) pair // LuExtension = ScsiPortGetLogicalUnit(DeviceExtension, pSrb->PathId, pSrb->TargetId, pSrb->Lun); ASSERT(LuExtension); LuExtension->NumberOfPendingRequests--; ASSERT (LuExtension->NumberOfPendingRequests >= 0); // // Call notification routine for the SRB. // ScsiPortNotification(RequestComplete, (PVOID)DeviceExtension, pSrb); } // end while {w/EFP command completion processing} } // end if (command complete interrupt) // // Enable interrupts // ScsiPortWritePortUchar(&eisaController->SystemDoorBellMask, INTERRUPTS_ENABLE); return(TRUE); } // end OliEsc2Interrupt() BOOLEAN OliEsc2ResetBus( IN PVOID HwDeviceExtension, IN ULONG PathId ) /*++ Routine Description: Reset ESC-2 SCSI adapter and SCSI bus. Arguments: HwDeviceExtension Device extension for this driver PathId SCSI Bus path ID (always 0 for the ESC-2) Return Value: TRUE if reset and re-initialization of EFP queues appears successful. FALSE otherwise. --*/ { PHW_DEVICE_EXTENSION DeviceExtension; PEISA_CONTROLLER eisaController; PLU_EXTENSION LuExtension; UCHAR Lun; UCHAR TargetId; UCHAR Bus; DebugPrint((2,"OliEsc2ResetBus: Reset SCSI bus.\n")); // // Get the ESC-2 registers' base address // DeviceExtension = HwDeviceExtension; eisaController = DeviceExtension->EisaController; // // If the reset is already in progress, return TRUE. // This can only happen when this routine is called directly // by the above layer (scsiport.sys) to reset bus #1 and #2. // if (DeviceExtension->ResetInProgress) { DebugPrint((2,"OliEsc2ResetBus: The reset is already in progess.\n")); return TRUE; } // // Reset the controller. // OliEsc2ResetAdapter(DeviceExtension); // // a) complete all outstanding requests. // b) clear pending request counters. // c) send a "reset detected" notification. // for (Bus=0; Bus < DeviceExtension->NumberOfBuses; Bus++) { // // Complete all outstanding requests with SRB_STATUS_BUS_RESET // ScsiPortCompleteRequest(DeviceExtension, Bus, (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, Bus, TargetId, Lun); if (LuExtension != NULL) { LuExtension->NumberOfPendingRequests = 0; } } } // // Send a "reset detected" notification. // ScsiPortNotification(ResetDetected, DeviceExtension, Bus); } // // All done // return TRUE; } // end OliEsc2ResetBus BOOLEAN ReadEsc2ConfigReg( IN PEISA_CONTROLLER EisaController, IN UCHAR ConfigReg, OUT PUCHAR ConfigByteInfo ) /*++ Routine Description: Reads one of the ESC-2's 8-bit configuration registers, returning the information stored there. Should be called at system initialization time only, as it uses polling. Arguments: EisaController Pointer to the adapter-info structure for this driver ConfigReg configuration register to be read (eg. 2 = IRQ config) ConfigByteInfo the information obtained from the configuration register Return Value: TRUE Success (and returns the configuration byte in ConfigByteInfo) FALSE Failure --*/ { BOOLEAN Success = FALSE; // Return value UCHAR IntMask; // Current System Doorbell interrupt mask value ULONG i; // Auxiliary variable BOOLEAN GotInt = FALSE; // // Get the current System Doorbell Interrupt Mask // // KMK: We never used to do this (and the re-write of the mask at end) // IntMask = ScsiPortReadPortUchar(&EisaController->SystemDoorBellMask); // // Disable ESC-1 interrupts // // KMK: We used to use STI_CLI(), to let pending interrupts occur // ScsiPortWritePortUchar(&EisaController->SystemDoorBellMask, INTERRUPTS_DISABLE); // // Gain semaphore 0 // if (!GainSemaphore0(EisaController)) { // KMK re-enable interrupts Success = FALSE; } else { // successfully gained semaphore ScsiPortWritePortUchar(&EisaController->InTypeService, 0); ScsiPortWritePortUchar(&EisaController->InParm1, GET_CONF_INFO); ScsiPortWritePortUchar(&EisaController->InParm2, ConfigReg); //ScsiPortWritePortUlong(&EisaController->InAddress, Address); // // Send an attention interrupt to the adapter. // ScsiPortWritePortUchar(&EisaController->LocalDoorBell, ESC_INT_BIT); // // We would re-enable interrupts here. // //ENAB(); // now loop, waiting for the board to "interrupt" us (we don't yet // have an interrupt line set, so we will poll). // KMK: Note, this is shorter than the wait we used to use! for (i = 0; i < WAIT_INT_LOOPS && !GotInt; i++) { if (ScsiPortReadPortUchar(&EisaController->SystemDoorBell) & ESC_INT_BIT) { // was ESC_INTERRUPT GotInt = TRUE; } else { ScsiPortStallExecution(WAIT_INT_INTERVAL); } } if (!GotInt) { // Re-enable the interrupts here. DebugPrint((1, "No HP interrupt after read config byte attempt.\n")); Success = FALSE; } else { // did get the interrupt // // acknowledge the host interrupt, resetting the interrupt register // (was ESC_ACK) // ScsiPortWritePortUchar(&EisaController->SystemDoorBell, ESC_INT_BIT); // // read the mailbox registers. If the outgoing status was good, // we read the value of the configuration register we queried. // if (ScsiPortReadPortUshort((PUSHORT)&EisaController->OutReserved2) != 0) { Success = FALSE; } else { Success = TRUE; *ConfigByteInfo = ScsiPortReadPortUchar(&EisaController->OutReserved4); // reset outgoing semaphore 1 ScsiPortWritePortUchar(&EisaController->ResultSemaphore, 0); } // end of else {successful status from read conf reg command} } // end of else {did get the interrupt} } // end of else {successfully gained the semaphore} // // Restore the original interrupt mask // ScsiPortWritePortUchar(&EisaController->SystemDoorBellMask, IntMask); return(Success); // if an error occurred, Success holds FALSE } // end ReadEsc2ConfigReg BOOLEAN OliEsc2IrqRegToIrql( IN UCHAR IrqReg, OUT PUCHAR pIrql ) /*++ Routine Description: This routine converts the IRQ configuration register value in the corrisponding IRQ level. Arguments: IrqReg IRQ configuration register value. pIrql Location where to store the IRQ level. Return Value: TRUE Success FALSE Failure (IRQ config reg was invalid) --*/ { BOOLEAN Success = TRUE; switch(IrqReg) { case 0: *pIrql = 11; break; case 1: *pIrql = 10; break; case 2: *pIrql = 5; break; case 3: *pIrql = 15; break; default: Success = FALSE; break; } return Success; } // end of OliEsc2IrqRegToIrql BOOLEAN RegisterEfpQueues( IN PHW_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Description: This routine re-initializes the EFP interface, setting up the queues descriptor, plus the mailbox queue and device queues. It assumes that information on the attached SCSI devices has already been obtained by the Get Configuration call, and is stored in the Noncached Extension. Arguments: DeviceExtension Pointer to the device extension for this driver. Return Value: Returns TRUE if queues initialization completed successfully. Returns FALSE and logs an error if initialization fails. --*/ { PEISA_CONTROLLER eisaController; PNONCACHED_EXTENSION pNoncachedExt; UCHAR TarLun, EfpMsg; ULONG i, length; BOOLEAN GotInt = FALSE; PGET_CONF Info; PCOMMAND_QUEUE qPtr; eisaController = DeviceExtension->EisaController; pNoncachedExt = DeviceExtension->NoncachedExt; // // With the information obtained from Get Information and // Get Configuration, build a new queues descriptor, including // device command queues. Allocate queues for all devices, but // fill in Get Configuration device information only into device // command queue descriptor bodies for existing devices. // // // Interrupts have already been disabled on entry (see OliEsc2FindAdapter). // Polling is possible through enabling the ESC-1 HP and EFP interface // in the System Doorbell Mask. // // // 1. Issue EFP_SET command via I/O instruction 'TYPE SERVICE' to the // BMIC mailbox registers. // // // Issue EFP_Set command to register new Queues Descriptor. // if (!GainSemaphore0(eisaController)) { return(FALSE); } // if got the semaphore, place IRQ parameter in mailbox register 1 ScsiPortWritePortUchar(&eisaController->InParm1, DeviceExtension->IRQ_In_Use); // output a TYPE SERVICE request: 'efp_set' ScsiPortWritePortUchar(&eisaController->InTypeService, S_EFP_SET); // set bit 1 of the local doorbell register to generate interrupt request ScsiPortWritePortUchar(&eisaController->LocalDoorBell, EFP_INT_BIT); // Wait for controller to respond. GotInt = FALSE; // re-initialize GotInt for (i = 0; i < WAIT_INT_LOOPS && !GotInt; i++) { if (ScsiPortReadPortUchar(&eisaController->SystemDoorBell) & EFP_TYPE_MSG) { GotInt = TRUE; } else { ScsiPortStallExecution(WAIT_INT_INTERVAL); } } if (!GotInt) { DebugPrint((1, "RegisterEfpQueues: No interrupt after EFP_SET.\n")); return(FALSE); } else { DebugPrint((4, "RegisterEfpQueues: Set, interrupt after %ld us.\n", i*WAIT_INT_INTERVAL)); EfpMsg = ScsiPortReadPortUchar(&eisaController->OutTypeMsg); if (EfpMsg != M_INIT_DIAG_OK) { DebugPrint((1, "RegisterEfpQueues: INIT_DIAG_OK not received after EFP_SET.\n")); if (EfpMsg == M_ERR_INIT) { DebugPrint((1, "RegisterEfpQueues: M_ERR_INIT received after EFP_SET.\n")); } else { DebugPrint((1, "RegisterEfpQueues: Error after EFP_SET: %x hex.\n", EfpMsg)); } return(FALSE); } } // reset bit 1 of the system doorbell register to clear request ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_MSG); // clear semaphore 1 after reading TYPE_MSG (RELEASE_SEM1) ScsiPortWritePortUchar(&eisaController->ResultSemaphore, 0); // // 2. Build queues descriptor. // // // First, reinitialize queue-related fields in HW_DEVICE_EXTENSION // and NONCACHED_EXTENSION. // DeviceExtension->Reply_Q_Full_Flag = 0; DeviceExtension->Reply_Q_Get = 0; DeviceExtension->RQ_In_Process = 0; pNoncachedExt->Command_Qs[0].Cmd_Q_Get = 0; pNoncachedExt->Command_Qs[0].Cmd_Q_Put = 0; for (i = 0; i < HA_QUEUES; i++) { // device q's + mailbox q DeviceExtension->Q_Full_Map[i] = 0; } for (i = 0; i < REPLY_Q_ENTRIES; i++) { pNoncachedExt->Reply_Q[i].qnrply.nrply_flag = 0; } // Set up new queues descriptor header // set up Queues Descriptor header pNoncachedExt->QD_Head.qdh_maint = 0; // normal environment pNoncachedExt->QD_Head.qdh_n_cmd_q = (DeviceExtension->TotalAttachedDevices + 1); pNoncachedExt->QD_Head.qdh_type_reply = 0; // int after 1+ pNoncachedExt->QD_Head.qdh_reserved1 = 0; pNoncachedExt->QD_Head.qdh_reply_q_addr = ScsiPortConvertPhysicalAddressToUlong( ScsiPortGetPhysicalAddress(DeviceExtension, NULL, // No SRB &pNoncachedExt->Reply_Q, &length)); pNoncachedExt->QD_Head.qdh_n_ent_reply = REPLY_Q_ENTRIES; pNoncachedExt->QD_Head.qdh_reserved2 = 0; // set up Queues Descriptor body for mailbox queue pNoncachedExt->QD_Bodies[0].qdb_scsi_level = 0xFF; pNoncachedExt->QD_Bodies[0].qdb_channel = 0xFF; pNoncachedExt->QD_Bodies[0].qdb_ID = 0xFF; pNoncachedExt->QD_Bodies[0].qdb_LUN = 0xFF; pNoncachedExt->QD_Bodies[0].qdb_n_entry_cmd = COMMAND_Q_ENTRIES; pNoncachedExt->QD_Bodies[0].qdb_notfull_int = 1; pNoncachedExt->QD_Bodies[0].qdb_no_ars = 0; // ESC2/EFP2 pNoncachedExt->QD_Bodies[0].qdb_timeout = 0; pNoncachedExt->QD_Bodies[0].qdb_cmd_q_addr = ScsiPortConvertPhysicalAddressToUlong( ScsiPortGetPhysicalAddress(DeviceExtension, NULL, // no SRB &pNoncachedExt->Command_Qs[0], &length)); pNoncachedExt->QD_Bodies[0].qdb_reserved = 0; // store the controller mailbox info in common area DeviceExtension->DevicesPresent[0].present = TRUE; DeviceExtension->DevicesPresent[0].qnumber = 0; DeviceExtension->DevicesPresent[0].qPtr = &pNoncachedExt->Command_Qs[0]; // set up Queues Descriptor bodies for device queues // // Note that there will be garbage in QD_Bodies entries for non-existant // TarLuns. We must, then, always check if a device/queue exists before // trying to send a command to it. // for (i = 1; i <= DeviceExtension->TotalAttachedDevices; i++) { Info = &pNoncachedExt->GetConfigInfo[i-1] ; DebugPrint((1, "RegisterEfpQueues: channel=%d id=%d lun=%d.\n", Info->gc_channel - 1, Info->gc_id, Info->gc_lun)); TarLun = GET_QINDEX(((Info->gc_channel)-1), Info->gc_id, Info->gc_lun); qPtr = &pNoncachedExt->Command_Qs[i]; DeviceExtension->DevicesPresent[TarLun].present = TRUE; DeviceExtension->DevicesPresent[TarLun].qnumber = (UCHAR)i; DeviceExtension->DevicesPresent[TarLun].qPtr = qPtr ; pNoncachedExt->QD_Bodies[i].qdb_scsi_level = Info->gc_scsi_level; // Note: Discussed this with John Hanel. We believe it is unnecessary // to ensure that the device's reported SCSI protocol level does not // exceed the controller's level. If otherwise, change this code. pNoncachedExt->QD_Bodies[i].qdb_channel = Info->gc_channel; pNoncachedExt->QD_Bodies[i].qdb_ID = Info->gc_id; pNoncachedExt->QD_Bodies[i].qdb_LUN = Info->gc_lun; pNoncachedExt->QD_Bodies[i].qdb_n_entry_cmd = COMMAND_Q_ENTRIES; pNoncachedExt->QD_Bodies[i].qdb_notfull_int = 1; pNoncachedExt->QD_Bodies[i].qdb_no_ars = 0; pNoncachedExt->QD_Bodies[i].qdb_timeout = 0; pNoncachedExt->QD_Bodies[i].qdb_cmd_q_addr = ScsiPortConvertPhysicalAddressToUlong( ScsiPortGetPhysicalAddress(DeviceExtension, NULL, // no SRB qPtr, &length)); pNoncachedExt->QD_Bodies[i].qdb_reserved = 0; qPtr->Cmd_Q_Get = 0; qPtr->Cmd_Q_Put = 0; } // // 3. Issue EFP_Start. // if (!GainSemaphore0(eisaController)) { return(FALSE); } // if got the semaphore, output physical address of the queues descriptor // to mailbox registers 1 - 4. ScsiPortWritePortUlong((PULONG)&eisaController->InParm1, DeviceExtension->QueuesDescriptor_PA); // output a TYPE SERVICE request to 'efp_start' ScsiPortWritePortUchar(&eisaController->InTypeService, S_EFP_START); // set bit 1 of local doorbell register to generate an interrupt request ScsiPortWritePortUchar(&eisaController->LocalDoorBell, EFP_INT_BIT); // Wait for controller to respond. GotInt = FALSE; // re-initialize GotInt for (i = 0; i < WAIT_INT_LOOPS && !GotInt; i++) { if (ScsiPortReadPortUchar(&eisaController->SystemDoorBell) & EFP_TYPE_MSG) { // was EFP_MSG_INT() GotInt = TRUE; } else { ScsiPortStallExecution(WAIT_INT_INTERVAL); } } if (!GotInt) { DebugPrint((1, "RegisterEfpQueues: No interrupt after EFP_START.\n")); return(FALSE); } else { DebugPrint((4, "RegisterEfpQueues: Start, interrupt after %ld us.\n", i*WAIT_INT_INTERVAL)); EfpMsg = ScsiPortReadPortUchar(&eisaController->OutTypeMsg); if (EfpMsg != M_INIT_DIAG_OK) { DebugPrint((1, "RegisterEfpQueues: INIT_DIAG_OK not received after EFP_START.\n")); if (EfpMsg == M_ERR_INIT) { DebugPrint((1, "RegisterEfpQueues: M_ERR_INIT received after EFP_START.\n")); } else { DebugPrint((1, "RegisterEfpQueues: Error after EFP_START: %x hex.\n", EfpMsg)); } return(FALSE); } } // reset bit 1 of the system doorbell register to clear request ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_MSG); // clear semaphore 1 after reading TYPE_MSG (RELEASE_SEM1) ScsiPortWritePortUchar(&eisaController->ResultSemaphore, 0); // release sem return(TRUE); } // end of RegisterEfpQueues() VOID BuildSgl( IN PHW_DEVICE_EXTENSION DeviceExtension, IN PSCSI_REQUEST_BLOCK Srb ) /*++ Routine Description: This routine builds a scatter/gather descriptor list for the EFP command structure (SSG or ESG). 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 PEFP_SGL pEFP; // EFP command pointer PQ_ENTRY pCQCmd; // EFP command queue structure PVOID dataPointer; // Pointer to the data buffer to send USHORT 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 PSG_LIST sgl; // Virtual SGL address DebugPrint((3,"OliEsc2BuildSgl: Enter routine.\n")); // // Initialize some variables // dataPointer = Srb->DataBuffer; bytesLeft = Srb->DataTransferLength; pEFP = Srb->SrbExtension; pCQCmd = &pEFP->EfpCmd; sgl = &pEFP->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, "OliEsc2BuildSgl: Data buffer %lx.\n", dataPointer)); // // Get physical address and length of contiguous // physical buffer. // physicalAddress = ScsiPortConvertPhysicalAddressToUlong( ScsiPortGetPhysicalAddress(DeviceExtension, Srb, dataPointer, &length)); DebugPrint((3, "OliEsc2BuildSgl: Physical address %lx,\n", physicalAddress)); DebugPrint((3, "OliEsc2BuildSgl: Data length %lx,\n", length)); DebugPrint((3, "OliEsc2BuildSgl: 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); // Save the descriptor count in the EFP structure for performance tuning pEFP->SGCount = descriptorCount; DebugPrint((3, "OliEsc2BuildSgl: SGCount = >>>>>> %d.\n", descriptorCount)); // // The short/long scatter gather commands are not used because... // a) These commands can be used only for disk devices // (ESC-2 supports all SCSI devices). // b) The miniport need to make some assumtions on the device block // length (the miniport doesn't have enough knowledge to make them). // DebugPrint((3,"OliEsc2BuildSgl: Send << ESG >> command.\n")); if (Srb->SrbFlags & SRB_FLAGS_DATA_OUT) { // // Adapter to system transfer // ((PEXTENDED_SG)pCQCmd)->esg_cmd_type = ESG_WRITE; } else if (Srb->SrbFlags & SRB_FLAGS_DATA_IN) { // // System to adapter transfer // ((PEXTENDED_SG)pCQCmd)->esg_cmd_type = ESG_READ; } // // Copy the Command Descriptor Block (CDB) into the EFP command // ScsiPortMoveMemory(&pCQCmd->qncmd.ncmd_cdb, Srb->Cdb, Srb->CdbLength); // // Write SGL length to Short Scatter Gather command. // ((PEXTENDED_SG)pCQCmd)->esg_cdb_l = Srb->CdbLength; ((PEXTENDED_SG)pCQCmd)->esg_lb = descriptorCount * sizeof(SG_DESCRIPTOR); DebugPrint((3,"OliEsc2BuildSgl: SGL length is %d.\n", descriptorCount * sizeof(SG_DESCRIPTOR) )); // // Write SGL address to EFP structure. // ((PEXTENDED_SG)pCQCmd)->esg_address = physicalSgl; DebugPrint((3,"OliEsc2BuildSgl: SGL address is %lx\n", sgl)); return; } // end BuildSgl() VOID OliEsc2ResetAdapter( 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; PNONCACHED_EXTENSION pNoncachedExt; UCHAR intpending; UCHAR EfpMsg; ULONG Delay; ULONG i; BOOLEAN Error = FALSE; UCHAR Bus; DeviceExtension = Context; eisaController = DeviceExtension->EisaController; pNoncachedExt = DeviceExtension->NoncachedExt; // // 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. // // Re-register EFP queues: // // Phase 3: Waiting for the EFP_SET command to complete. // Phase 4: Waiting for the EFP_START command to complete. // switch(DeviceExtension->ResetInProgress) { // // Phase 0: Reset the controller. // case 0: ////////////////////////////////////////////////////////////////////// // // Disable interrupts. // ScsiPortWritePortUchar( &eisaController->SystemIntEnable, INTERRUPTS_DISABLE ); ////////////////////////////////////////////////////////////////////// // // Reset controller. // if (DeviceExtension->Esc2 == TRUE) { DebugPrint((2,"OliEsc2ResetAdapter: ESC reset type.\n")); DebugPrint((3, "OliEsc2ResetAdapter: " "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(&eisaController->OutReserved2, (UCHAR)(~DIAGNOSTICS_OK_NO_CONFIG_RECEIVED)); // // Reset ESC-2 and SCSI bus. // ScsiPortWritePortUchar(&eisaController->LocalDoorBell, ADAPTER_RESET); // // Request a timer call to complete the reset. // DeviceExtension->ResetTimerCalls = ESC_RESET_LOOPS + 1; Delay = ESC_RESET_DELAY; } else { DebugPrint((2,"OliEsc2ResetAdapter: EFP reset type.\n")); DebugPrint((3, "OliEsc2ResetAdapter: " "Phase 1 (reset adapter) max time = %ld us.\n", EFP_RESET_DELAY + EFP_RESET_INTERVAL * EFP_RESET_LOOPS )); // // Try to acquire the semaphore (note that this is not necessary). // if (!GainSemaphore0(eisaController)) { DebugPrint((1, "OliEsc2ResetAdapter: Warning, the semaphore is busy, " "issuing the reset anyway.\n")); } // // Initialize the input parameters for the reset. // for (i=0; i < CFG_REGS_NUMBER; i++) { // // The controller will re-initialize the board using // these configuration registers values. // ScsiPortWritePortUchar(&eisaController->InTypeService + i, DeviceExtension->CfgRegs[i]); } // // Reset EFP-2 and SCSI buses. // ScsiPortWritePortUchar(&eisaController->LocalDoorBell, ADAPTER_CFG_RESET); // // Request a timer call to complete the reset. // DeviceExtension->ResetTimerCalls = EFP_RESET_LOOPS + 1; Delay = EFP_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 ESC2_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: if (DeviceExtension->Esc2 == TRUE) { // // Note that after a reset the LOW byte of the ESC-2 Status // register is loaded with the diagnostics result code. // if (ScsiPortReadPortUchar(&eisaController->OutReserved2) != DIAGNOSTICS_OK_NO_CONFIG_RECEIVED) { Delay = ESC_RESET_INTERVAL; break; } DebugPrint((1, "OliEsc2ResetAdapter: Reset bus succeeded after %ld us.\n", ESC_RESET_DELAY + ESC_RESET_INTERVAL * (ESC_RESET_LOOPS - DeviceExtension->ResetTimerCalls) )); } else { // // The following code allows the next revision of the EFP firmware // to use more time during the reset phase. // if (ScsiPortReadPortUchar(&eisaController->LocalDoorBell) & ADAPTER_CFG_RESET) { Delay = EFP_RESET_INTERVAL; break; } DebugPrint((1, "OliEsc2ResetAdapter: Reset bus succeeded after %ld us.\n", EFP_RESET_DELAY + EFP_RESET_INTERVAL * (EFP_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 (ESC-2 controllers). // 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. // intpending = ScsiPortReadPortUchar(&eisaController->SystemDoorBell); // // Check if any ESC-1 type interrupt // if (intpending & ESC_INT_BIT) { DebugPrint((3, "OliEsc2ResetAdapter: " "The HP interrupt was pending.\n")); // // Acknowledge the interrupt. // // No need to unlock semaphore 1, because the controller already // unlocks it during the reset phase. // ScsiPortWritePortUchar(&eisaController->SystemDoorBell, ESC_INT_BIT); } // // Check if any EFP command complete interrupt // if (intpending & EFP_CMD_COMPL) { DebugPrint((3, "OliEsc2ResetAdapter: " "The EFP command complete interrupt was pending.\n")); // // Acknowledge interrupt. // ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_INT); } // // Check if any EFP_TYPE_MSG interrupt // if (intpending & EFP_TYPE_MSG) { DebugPrint((3, "OliEsc2ResetAdapter: " "The EFP type message interrupt was pending.\n")); // // Acknowledge the interrupt // // No need to unlock semaphore 1, because the controller already // unlocks it during the reset phase. // ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_MSG); } ////////////////////////////////////////////////////////////////////// // // Re-initialize the EFP queues, using information stored // from the init-time. // ////////////////////////////////////////////////////////////////////// // // Issue EFP_SET command via I/O instruction 'TYPE SERVICE' to the // BMIC mailbox registers. // DebugPrint((3, "OliEsc2ResetAdapter: " "Phase 3 (EFP_SET command) max time = %ld us.\n", TIMER_WAIT_INT_INTERVAL * TIMER_WAIT_INT_LOOPS )); if (!GainSemaphore0(eisaController)) { Error = TRUE; break; } // // Place IRQ parameter in mailbox register 1. // ScsiPortWritePortUchar(&eisaController->InParm1, DeviceExtension->IRQ_In_Use); // // output a TYPE SERVICE request: 'efp_set'. // ScsiPortWritePortUchar(&eisaController->InTypeService, S_EFP_SET); // // set bit 1 of the local doorbell register to generate interrupt // request. // ScsiPortWritePortUchar(&eisaController->LocalDoorBell, EFP_INT_BIT); // // Request a timer call to continue the re-initialization phase. // DeviceExtension->ResetTimerCalls = TIMER_WAIT_INT_LOOPS; Delay = TIMER_WAIT_INT_INTERVAL; DeviceExtension->ResetInProgress++; break; // // Phase 3: Waiting for the EFP_SET command to complete. // case 3: // // Check if a message interrupt is pending. // intpending = ScsiPortReadPortUchar(&eisaController->SystemDoorBell); if ( !(intpending & EFP_TYPE_MSG)) { Delay = TIMER_WAIT_INT_INTERVAL; break; } // // Check the command result. // EfpMsg = ScsiPortReadPortUchar(&eisaController->OutTypeMsg); if (EfpMsg != M_INIT_DIAG_OK) { DebugPrint((1, "OliEsc2ResetAdapter: Error after EFP_SET: %x hex.\n", EfpMsg)); Error = TRUE; break; } // // reset bit 1 of the system doorbell register to clear request // ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_MSG); // // clear semaphore 1 after reading TYPE_MSG (RELEASE_SEM1) // ScsiPortWritePortUchar(&eisaController->ResultSemaphore, 0); ////////////////////////////////////////////////////////////////////// // // Reinitialize the reply queue and the associated variables. // for (i = 0; i < REPLY_Q_ENTRIES; i++) { pNoncachedExt->Reply_Q[i].qnrply.nrply_flag = 0; } DeviceExtension->Reply_Q_Full_Flag = 0; DeviceExtension->Reply_Q_Get = 0; DeviceExtension->RQ_In_Process = 0; // // Reinitialize the command queues and the associated structures. // for (i = 0; i <= DeviceExtension->TotalAttachedDevices; i++) { pNoncachedExt->Command_Qs[i].Cmd_Q_Get = 0; pNoncachedExt->Command_Qs[i].Cmd_Q_Put = 0; } for (i = 0; i < HA_QUEUES; i++) { // device q's + mailbox q DeviceExtension->Q_Full_Map[i] = 0; } ////////////////////////////////////////////////////////////////////// // // Issue EFP_Start. // DebugPrint((3, "OliEsc2ResetAdapter: " "Phase 4 (EFP_START command) max time = %ld us.\n", TIMER_WAIT_INT_INTERVAL * TIMER_WAIT_INT_LOOPS )); if (!GainSemaphore0(eisaController)) { Error = TRUE; break; } // // Output physical address of the queues descriptor // to mailbox registers 1 - 4. // ScsiPortWritePortUlong((PULONG)&eisaController->InParm1, DeviceExtension->QueuesDescriptor_PA); // // output a TYPE SERVICE request to 'efp_start' // ScsiPortWritePortUchar(&eisaController->InTypeService, S_EFP_START); // // set bit 1 of local doorbell register to generate an interrupt // request. // ScsiPortWritePortUchar(&eisaController->LocalDoorBell, EFP_INT_BIT); // // Request a timer call to continue the re-initialization phase. // DeviceExtension->ResetTimerCalls = TIMER_WAIT_INT_LOOPS; Delay = TIMER_WAIT_INT_INTERVAL; DeviceExtension->ResetInProgress++; break; // // Phase 4: Waiting for the EFP_START command to complete. // case 4: // // Check if a message interrupt is pending. // intpending = ScsiPortReadPortUchar(&eisaController->SystemDoorBell); if ( !(intpending & EFP_TYPE_MSG)) { Delay = TIMER_WAIT_INT_INTERVAL; break; } // // Check the command result. // EfpMsg = ScsiPortReadPortUchar(&eisaController->OutTypeMsg); if (EfpMsg != M_INIT_DIAG_OK) { DebugPrint((1, "OliEsc2ResetAdapter: Error after EFP_START: %x hex.\n", EfpMsg)); Error = TRUE; break; } // // reset bit 1 of the system doorbell register to clear request // ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_MSG); // // clear semaphore 1 after reading TYPE_MSG (RELEASE_SEM1) // ScsiPortWritePortUchar(&eisaController->ResultSemaphore, 0); #if EFP_MIRRORING_ENABLED ////////////////////////////////////////////////////////////////////// // // We need to re-initialize all the mirroring structures. // OliEsc2MirrorInitialize(DeviceExtension, FALSE); #endif // EFP_MIRRORING_ENABLED ////////////////////////////////////////////////////////////////////// // // Re-enable the controller's interrupts. // ScsiPortWritePortUchar(&eisaController->SystemIntEnable, SYSTEM_INTS_ENABLE); ////////////////////////////////////////////////////////////////////// // // All done ! // DeviceExtension->ResetInProgress = 0; return; default: // // Invalid reset phase number. This should never happen! // DebugPrint((1, "OliEsc2ResetAdapter: 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, OliEsc2ResetAdapter, Delay); // // The "ResetNotification" variable is used to keep track of the // time during the reset. If the reset is not completed before // the next ESC2_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 >= ESC2_RESET_NOTIFICATION) { // // Notify that a reset was detected on the SCSI bus. // for (Bus=0; Bus < DeviceExtension->NumberOfBuses; Bus++) { ScsiPortNotification(ResetDetected, DeviceExtension, Bus); } // // Reset the "reset notification timer". // DeviceExtension->ResetNotification = 0; } // // Update the "reset notification timer". // DeviceExtension->ResetNotification += Delay; } else { // // Time-out ! // DebugPrint((1, "OliEsc2ResetAdapter: 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, 0, 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 OliEsc2ResetAdapter BOOLEAN GainSemaphore0( IN PEISA_CONTROLLER EisaController ) /*++ Routine Description: Acquires semaphore 0 (used by EFP TYPE_SERVICE requests) if the semaphore is available. Arguments: EisaController Base address of the ESC-2 registers' address space Return Value: Returns TRUE if semaphore successfully acquired. Returns FALSE if semaphore busy. --*/ { ULONG i; // loop counter UCHAR DataByte; BOOLEAN GotSem = FALSE; for (i = 0; i < SEMAPHORE_LOOPS && !GotSem; i++) { ScsiPortWritePortUchar(&EisaController->CommandSemaphore, SEM_LOCK); DataByte = ScsiPortReadPortUchar(&EisaController->CommandSemaphore); DataByte &= 3; // we're interested in only the lower the 2 bits if (DataByte == SEM_GAINED) { // did we get the semaphore? GotSem = TRUE; // if we got it, we'll exit the loop } else { ScsiPortStallExecution(SEMAPHORE_INTERVAL); // delay } } return(GotSem); } // end of GainSemaphore0() VOID BuildEfpCmd( IN PHW_DEVICE_EXTENSION DeviceExtension, IN PSCSI_REQUEST_BLOCK Srb ) /*++ Routine Description: Build an EFP command structure for the ESC-2 EFP interface mode. Arguments: DeviceExtension Pointer to the device extension for this driver. Srb Pointer to the Scsi Request Block to service Return Value: Nothing. --*/ { PEFP_SGL pEFP; // SRB extension pointer PQ_ENTRY pCQCmd; // EFP command queue entry pointer ULONG physicalAddress; // Physical address of the EFP cmd ULONG i; // loop counter ULONG length; // Length of contiguous memory in the // data buffer, starting at the // beginning of the buffer DebugPrint((3,"OliEsc2BuildEfpCmd: Enter routine.\n")); // // Get the EFP command address // pEFP = Srb->SrbExtension; pCQCmd = &pEFP->EfpCmd; // // Clear the SRB extension area. // for (i=0; i< (sizeof(EFP_SGL)/4); i++) { *(((PULONG)pEFP) + i) = 0; // 4 bytes for each loop } // // Save SRB back pointer in EFP_SGL (SRB extension). // The Srb is used at interrupt time. // pEFP->SrbAddress = Srb; // // The following "cast" is ONLY valid in the 32 bit world. // pCQCmd->qncmd.ncmd_userid = (ULONG)Srb; pCQCmd->qncmd.ncmd_sort = 1; // ESC-2 provide sorting pCQCmd->qncmd.ncmd_prior = 0; // highest priority (ESC-2 ignores) #if EFP_MIRRORING_ENABLED if (DeviceExtension->Esc2 == TRUE) { pCQCmd->qncmd.ncmd_mod = 0x01; // 01 hex for ESC2 (not 00-normal!) } else { pCQCmd->qncmd.ncmd_mod = 0x00; // command directed to both disks } #else pCQCmd->qncmd.ncmd_mod = 0x01; // 01 hex for ESC2 (not 00-normal!) #endif // EFP_MIRRORING_ENABLED // // Copy the Command Descriptor Block (CDB) into the EFP command // ScsiPortMoveMemory(&pCQCmd->qncmd.ncmd_cdb, Srb->Cdb, Srb->CdbLength); DebugPrint((3, "OliEsc2BuildEfpCmd: CDB at %lx, length=%x, SRB at %lx.\n", &pCQCmd->qncmd.ncmd_cdb, Srb->CdbLength, pEFP->SrbAddress)); // // Build a scatter/gather list in the SRB 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 // // // Set the CDB length and the data transfer length // pCQCmd->qncmd.ncmd_cdb_l = (UCHAR)Srb->CdbLength; pCQCmd->qncmd.ncmd_length = Srb->DataTransferLength; pCQCmd->qncmd.ncmd_address = physicalAddress; if (Srb->SrbFlags & SRB_FLAGS_DATA_OUT) { // // Adapter to system transfer // pCQCmd->qncmd.ncmd_cmd_type = NCMD_WRITE; } else if (Srb->SrbFlags & SRB_FLAGS_DATA_IN) { // // System to adapter transfer // pCQCmd->qncmd.ncmd_cmd_type = NCMD_READ; } else if (!Srb->SrbFlags & 0xF0) { // SRB_FLAGS_NO_DATA_TRANSFER pCQCmd->qncmd.ncmd_cmd_type = NCMD_NODATA; } } else { // need scatter/gather list // // The Srb->DataBuffer is not contiguous: we need // scatter/gather descriptors // BuildSgl(DeviceExtension, Srb); } } else { // // No data transfer is requested // pCQCmd->qncmd.ncmd_cmd_type = NCMD_NODATA; pCQCmd->qncmd.ncmd_address = 0; pCQCmd->qncmd.ncmd_cdb_l = (UCHAR)Srb->CdbLength; } return; } // end BuildEfpCmd() BOOLEAN EnqueueEfpCmd ( IN PHW_DEVICE_EXTENSION DeviceExtension, IN PQ_ENTRY pEfpCmd, IN PCOMMAND_QUEUE qPtr, IN UCHAR TarLun, OUT PUCHAR pSignal ) /*++ Routine Description: Enqueue a command into an EFP command queue. Update Put pointer as needed, observing possible need to wrap, if the addition of this command would exceed the length of the queue. Refer to algorithm in comments below. NOTE: This code assumes that a queue command takes up only queue entry (ESC-2 does NOT use Long scatter gather commands, 64 bytes long). Linkage: Called from Get_Information() Get_Configuration() Arguments: DeviceExtension Device extension for this adapter pEfpCmd Pointer to command to be compiled to the queue. TarLun Command queue array index (TARGET/LUN combination) pSignal return status signal Return Value: TRUE Command was enqueued. *pSignal = 0: the queue is now full. *pSignal = 1: the queue is not full. FALSE Error enqueuing the command. *pSignal = 0: the queue was full. *pSignal = 1: problem in signalling controller of empty to not empty transition. Algorithm: If Queue is not full (Put != Get - (1 | 2)) Then OldPut = Put copy command (@DS:SI) to Queue[Put] Put = (Put + (1 for normal, 2 for long)) mod Q length read current Get pointer (to prevent race condition) If Queue was empty (OldPut = Get) Then Signal controller of empty -> not empty transition Endif Return Endif Else Queue was initially full Set error flag Return ; caller is responsible for awaiting not full EndElse --*/ { PQ_ENTRY pqmem; USHORT getp, orig_putp, putp; // get and put pointers // // Initialize the local pointers with the real ones. // getp = qPtr->Cmd_Q_Get; putp = qPtr->Cmd_Q_Put; orig_putp = putp; // Used to empty to not empty message. // // Check if the queue is full (Put = Get - 1 or Put = Get - 2, defined // by the fact that we have both normal and dual commands). // if (putp >= getp) { getp += COMMAND_Q_ENTRIES; // Make Get > Put for easier comp. } getp = (getp - putp); // Entries left if (getp <= 2) { // Is the queue full ? *pSignal = 0; // Yes, it is. Error ! return(FALSE); } // Compile our command onto the queue at position of current PUT pointer. // First, advance queue pointer to beginning of queue (past Get and Put // pointers), then on up number of queue elements. // IMPORTANT: Note that sizeof(NORMAL_CMD) works as a standard queue // element size because all commands accepted by the ESC-2 are of the // same length. This would NOT work for an EFP-2! pqmem = &qPtr->Cmd_Entries[putp]; *pqmem = *pEfpCmd; // // Update the local Put pointer // // Note: the pointer is incremented only by 1 ! // Good only if the command is one entry in size. // putp++; if (putp == COMMAND_Q_ENTRIES) { // Need to wrap? putp -= COMMAND_Q_ENTRIES; // Yes, do it. } // // update Put pointer in the Queue itself (to match local put variable) // qPtr->Cmd_Q_Put = (UCHAR)putp; // // Re-read Get pointer (to avoid race conditions, we want current info) // getp = qPtr->Cmd_Q_Get; // // Check if the queue is empty using the original Put ptr and the // current Get ptr. // if (getp == orig_putp) { // // Yes, the queue is now empty. We need to sent the "empty to not // empty transition" message. // *pSignal = 1; // queue is not full. // // Send message // return(EfpCommand(DeviceExtension, TarLun)); } else { // // The queue is not empty. Check if it's full. // if (putp >= getp) { getp += COMMAND_Q_ENTRIES; // Make Get > Put for easier comp. } getp = (getp - putp); // Entries left if (getp <= 2) { // Is the queue full ? *pSignal = 0; // Yes, it is. } else { *pSignal = 1; // No, it isn't. } // // The command has been enqueued successfully. // return(TRUE); } } // end EnqueueEfpCmd BOOLEAN DequeueEfpReply ( IN PHW_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Description: Dequeue one reply entry from the reply queue. Linkage: Call Near Get_Information(), Get_Configuration() Arguments: DeviceExtension Device extension for this adapter Return Value: TRUE Ok, the RQ_In_Process (see device extension) variable is valid. FALSE Error (in EfpReplyQNotFull), the RQ_In_Process variable (see device extension) is valid. RQ_In_Process = 0, No valid reply entry. 1, Dequeued an entry. Algorithm: 1: If GET -> nrply_flag != 0 2: then copy the reply entry into ACB Q_Buf reset the nrply_flag increment the GET pointer 3: If Reply_QFull != 0 4: then reset the Reply_QFull to 0 signal the controller the respond queue is no longer full ( Type_service 08H) endif 5: set RQ_In_Process flag 6: return TRUE 7: else reset the RQ_In_Process flag (wait for interrupt from controller to signal a valid entry has been placed in the reply queue) return TRUE endif // START NOTE EFP_MIRRORING_ENABLED. // // The DequeueEfpReply routine always uses the NORMAL_REPLY struct to // dequeue a request. This is possible because the "flag" field is at // the same offset in both structures (NORMAL_REPLY and MIRROR_REPLY). // // The DequeueEfpReply routine validates the reply entry checking if the // "flag" field is different from zero. This is OK because a good reply // has the "flag" field is set to 1 in NORMAL/MAINTENANCE mode and to 3 // in MIRRORING mode. A value of zero means reply not good for both // environments. // // END NOTE EFP_MIRRORING_ENABLED. --*/ { UCHAR reply_get; PQ_ENTRY preply_entry; reply_get = DeviceExtension->Reply_Q_Get; preply_entry = &DeviceExtension->NoncachedExt->Reply_Q[reply_get]; // reply valid flag set indicates reply entry valid and in process if (preply_entry->qnrply.nrply_flag) { DeviceExtension->RQ_In_Process = 1; // indicate busy // copy reply queue entry to local queue entry buffer *(&DeviceExtension->Q_Buf.qnrply) = preply_entry->qnrply; // reset reply valid flag preply_entry->qnrply.nrply_flag = 0; // update the GET pointer, wrap around if necessary if (DeviceExtension->Reply_Q_Get == (REPLY_Q_ENTRIES - 1)) { DeviceExtension->Reply_Q_Get = 0; // yes, wraparound Get ptr } else { DeviceExtension->Reply_Q_Get += 1; // no, just inc Get ptr } // // test if the queue is(was) full, and reset reply queue full // signal if needed, signalling the controller that a queue // full to not full transition has occurred. // if (DeviceExtension->Reply_Q_Full_Flag == 0x1) { // full? DeviceExtension->Reply_Q_Full_Flag = 0; // reset flag // send Reply Q Not Full cmd (q was full, but not now) if (EfpReplyQNotFull(DeviceExtension)) { return (TRUE); } else { return (FALSE); } } else { return (TRUE); } } else { // valid flag did NOT indicate reply entry was valid & ready // No valid reply entry DeviceExtension->RQ_In_Process = 0; return (TRUE); } } // end DequeueEfpReply BOOLEAN EfpReplyQNotFull ( IN PHW_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Descriptions: Signal controller that reply queue is no longer full Issue efp_cmd TYPE_SERVICE request to the BMIC mailbox registers. Signal the controller that the previously full reply queue is no longer full. Linkage: Called from DequeueEfpReply() Arguments: DeviceExtension Device extension for this adapter Return Value: returns TRUE if no error. returns FALSE if error. --*/ { PEISA_CONTROLLER eisaController; eisaController = DeviceExtension->EisaController; if (!GainSemaphore0(eisaController)) { // try to get semaphore 0. return(FALSE); } // if got the semaphore, output a TYPE SERVICE request to 'efp_rqnf' ScsiPortWritePortUchar(&eisaController->InTypeService, S_EFP_REPNFUL); // set bit 1 of the local doorbell register to generate interrupt request ScsiPortWritePortUchar(&eisaController->LocalDoorBell, EFP_INT_BIT); return(TRUE); } // end EfpReplyQNotFull BOOLEAN EfpCommand ( IN PHW_DEVICE_EXTENSION DeviceExtension, IN UCHAR TarLun ) /*++ RoutineDescriptions: Signal the controller that at least one command has been entered into a command queue. Issue efp_cmd TYPE_SERVICE request to the BMIC mailbox registers. Tell the controller the queue number of the queue to which the command was added. Arguments: DeviceExtension Device extension for this adapter TarLun Command queue array index (TARGET/LUN combination) Return Value: returns TRUE if no error. returns FALSE if error. --*/ { PEISA_CONTROLLER eisaController; UCHAR qnumber; // the queue # associated with the TarLun // // Get the ESC-2 registers' base address // eisaController = DeviceExtension->EisaController; if (!GainSemaphore0(eisaController)) { // try to get semaphore 0. return(FALSE); } // if got the semaphore, output a TYPE SERVICE request to 'efp_rqnf' qnumber = DeviceExtension->DevicesPresent[TarLun].qnumber; ScsiPortWritePortUchar( &eisaController->InTypeService, (UCHAR)(qnumber | S_EFP_CMD)); // set bit 1 of local doorbell register to generate interrupt request ScsiPortWritePortUchar(&eisaController->LocalDoorBell, EFP_INT_BIT); return(TRUE); } // end EfpCommand() BOOLEAN EfpGetInformation ( IN PHW_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Descriptions: Issue EFP-interface mailbox command to get information about the controller and its EFP environment. Specifically, Get Information returns: 3 bytes -- Firmware release 1 byte -- SCSI level supported by the controller 4 bytes -- Controller ID on first - fourth SCSI bus 1 byte -- Controller environment (ie. MIRRORING or NORMAL) 1 byte -- Constraints on linked commands 1 byte -- Maximum size of a command queue (# of entries) Arguments: DeviceExtension Device extension for this adapter Return Value: returns TRUE if no error. returns FALSE if error. --*/ { PEISA_CONTROLLER eisaController; BOOLEAN GotInt = FALSE; UCHAR SignalFlag; ULONG i; // counter // // Get the ESC-2 registers' base address // eisaController = DeviceExtension->EisaController; // Set up the get_information EFP mailbox command DeviceExtension->Q_Buf.qmbc.mbc_userid = 1; DeviceExtension->Q_Buf.qmbc.mbc_sort = 0; DeviceExtension->Q_Buf.qmbc.mbc_prior = 0; DeviceExtension->Q_Buf.qmbc.mbc_reserved = 0; DeviceExtension->Q_Buf.qmbc.mbc_cmd_type = MB_GET_INFO; DeviceExtension->Q_Buf.qmbc.mbc_length = 0; DeviceExtension->Q_Buf.qmbc.mbc_user_data[0] = 0; DeviceExtension->Q_Buf.qmbc.mbc_user_data[1] = 0; DeviceExtension->Q_Buf.qmbc.mbc_user_data[2] = 0; DeviceExtension->Q_Buf.qmbc.mbc_user_data[3] = 0; DeviceExtension->Q_Buf.qmbc.mbc_addr = 0; // Enqueue the command if (!EnqueueEfpCmd(DeviceExtension, &DeviceExtension->Q_Buf, DeviceExtension->DevicesPresent[0].qPtr, 0, // mailbox queue is queue #0 &SignalFlag)) { DebugPrint((1, "Problem enqueueing Get Information EFP command.\n")); return(FALSE); } // Wait for controller to respond. // // NOTE: On an ESC-2, the Get_Configuration at this point does not // query devices. The ESC-2 does a Get Configuration early in // the boot process, and simply regurgitates this information when // later (now, at device driver init time) sent a Get Configuration. // Contrast this with the operation of an EFP-2, which may take many // seconds to perform a Get_Configuration, while it queries devices. for (i = 0; i < WAIT_INT_LOOPS && !GotInt; i++) { if (ScsiPortReadPortUchar(&eisaController->SystemDoorBell) & EFP_CMD_COMPL) { // was EFP_INTERRUPT() GotInt = TRUE; } else { ScsiPortStallExecution(WAIT_INT_INTERVAL); } } if (!GotInt) { DebugPrint((1, "Controller did not respond to Get_Information.\n")); return(FALSE); } // Get reply to the command. if ( !(DequeueEfpReply(DeviceExtension)) ) { DebugPrint((1, "Problem dequeueing reply to Get Information.\n")); return(FALSE); } if (!DeviceExtension->RQ_In_Process) { // was a reply properly dequeued? DebugPrint((1, "RQ_In_Process not 01 after Get_Information.\n")); return(FALSE); } // Collect reply information if (DeviceExtension->Q_Buf.qmbr.mbr_status) { DebugPrint((1, "Get Information command reply status was not 0.\n")); return(FALSE); } // Save into the DeviceExtnesion the information obtained by // Get Information. DeviceExtension->FW_Rel[0] = DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_fw_rel[0]; //minor DeviceExtension->FW_Rel[1] = DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_fw_rel[1]; //minor*10 DeviceExtension->FW_Rel[2] = DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_fw_rel[2]; //major DeviceExtension->SCSI_Level = DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_scsi_lev; // ESC2:01 DeviceExtension->Adapter_ID[0] = DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_id1; // channel #1 DeviceExtension->Adapter_ID[1] = DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_id2; // channel #2 DeviceExtension->Adapter_ID[2] = DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_id3; // channel #3 DeviceExtension->Adapter_ID[3] = DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_id4; // channel #4 DeviceExtension->Link_Cmd = DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_link; DeviceExtension->Max_CmdQ_ents = DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_maxcmds; #if EFP_MIRRORING_ENABLED DeviceExtension->Environment = DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_env; #endif // EFP_MIRRORING_ENABLED // reset bit 1 of the system doorbell register to clear request ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_INT); // was EFP_ACK DebugPrint((1, "EfpGetInformation: 1st channel ID = %d, 2nd channel ID = %d.\n", DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_id1, DeviceExtension->Q_Buf.qmbr.mbr_appl.appgi.gi_id2)); return(TRUE); } // end of EfpGetInformation() BOOLEAN EfpGetConfiguration ( IN PHW_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Descriptions: Issue EFP-interface mailbox command to get configuration information about each SCSI device attached to the host adapter. Specifically, for each device, Get Configuration returns: 1 byte -- Maximum size of a command queue (# of entries) 1 byte -- the device type (per standard SCSI protocol) 1 byte -- the device type qualifier (per std SCSI protocol) 1 byte -- SCSI level supported by the device 1 byte -- SCSI Channel to which dev is connected (ESC-2: 01) 1 byte -- SCSI ID of the device 1 byte -- SCSI LUN of the device 1 byte -- reserved Arguments: DeviceExtension Device extension for this adapter Return Value: returns TRUE if no error. returns FALSE if error. --*/ { PEISA_CONTROLLER eisaController; BOOLEAN GotInt = FALSE; UCHAR SignalFlag; ULONG i; // counter ULONG length; // Length of contiguous memory in the // data buffer, starting at the // beginning of the buffer // // Get the ESC-2 registers' base address // eisaController = DeviceExtension->EisaController; // Set up the get_configuration EFP mailbox command DeviceExtension->Q_Buf.qmbc.mbc_userid = 2; DeviceExtension->Q_Buf.qmbc.mbc_sort = 0; DeviceExtension->Q_Buf.qmbc.mbc_prior = 0; DeviceExtension->Q_Buf.qmbc.mbc_reserved = 0; DeviceExtension->Q_Buf.qmbc.mbc_cmd_type = MB_GET_CONF; DeviceExtension->Q_Buf.qmbc.mbc_length = sizeof(DeviceExtension->NoncachedExt->GetConfigInfo); DeviceExtension->Q_Buf.qmbc.mbc_user_data[0] = 0; DeviceExtension->Q_Buf.qmbc.mbc_user_data[1] = 0; DeviceExtension->Q_Buf.qmbc.mbc_user_data[2] = 0; DeviceExtension->Q_Buf.qmbc.mbc_user_data[3] = 0; DeviceExtension->Q_Buf.qmbc.mbc_addr = ScsiPortConvertPhysicalAddressToUlong( ScsiPortGetPhysicalAddress(DeviceExtension, NULL, // no SRB &DeviceExtension->NoncachedExt->GetConfigInfo, &length)); // Enqueue the command if (!EnqueueEfpCmd(DeviceExtension, &DeviceExtension->Q_Buf, DeviceExtension->DevicesPresent[0].qPtr, 0, // mailbox queue is queue #0 &SignalFlag)) { DebugPrint((1, "Problem enqueueing Get Configuration EFP command.\n")); return(FALSE); } // Wait for controller to respond. for (i = 0; i < WAIT_INT_LOOPS && !GotInt; i++) { if (ScsiPortReadPortUchar(&eisaController->SystemDoorBell) & EFP_CMD_COMPL) { // was EFP_INTERRUPT() GotInt = TRUE; } else { ScsiPortStallExecution(WAIT_INT_INTERVAL); } } if (!GotInt) { DebugPrint((1, "Controller did not respond to Get Configuration.\n")); return(FALSE); } // Get reply to the command. if ( !(DequeueEfpReply(DeviceExtension)) ) { DebugPrint((1, "Problem dequeueing reply to Get Configuration.\n")); return(FALSE); } if (!DeviceExtension->RQ_In_Process) { // was a reply properly dequeued? DebugPrint((1, "RQ_In_Process not 01 after Get Configuration.\n")); return(FALSE); } // Collect reply information (NOTE: since we allocate such a huge buffer // for Get Configuration information, due to the fixed size requirement // imposed by ScsiPortGetUncachedExtension, we never expect to see the // Get Configuration error message that indicates the supplied buffer // was too small (and if we did, there would be nothing we could do // dynamically to adjust the size of the buffer). For this reason, we // do no filtering of error status from this command. if (DeviceExtension->Q_Buf.qmbr.mbr_status) { DebugPrint((1, "Get Configuration cmd reply status was not 0.\n")); return(FALSE); } // reset bit 1 of the system doorbell register to clear request ScsiPortWritePortUchar(&eisaController->SystemDoorBell, EFP_ACK_INT); return(TRUE); } // end of EfpGetConfiguration() #if EFP_MIRRORING_ENABLED VOID OliEsc2MirrorInitialize ( IN PHW_DEVICE_EXTENSION DeviceExtension, IN BOOLEAN InitTime ) /*++ Routine Description: Initializes all the FT EFP-2 structures. It assumes that information on the attached SCSI devices has already been obtained by the Get Configuration call, and is stored in the noncached Extension. Note that this routine doesn't log any error. Arguments: DeviceExtension Device extension for this driver. Return Value: none --*/ { USHORT Index; UCHAR Bus, Tar, Lun, TarLun, Env; PGET_CONF pGetConfigInfo; PTAR_Q pDevInfo; // // Analyse the Get Configuration information. // for (Index=0; Index < DeviceExtension->TotalAttachedDevices; Index++) { // // Initialize pointer to Get Configuration data. // pGetConfigInfo = &DeviceExtension->NoncachedExt->GetConfigInfo[Index]; // // Read the initial mirror state of each TID/LUN from the ctrl info. // Env = pGetConfigInfo->gc_env; Bus = pGetConfigInfo->gc_channel - 1; // make it 0-based Tar = pGetConfigInfo->gc_id; Lun = pGetConfigInfo->gc_lun; TarLun = GET_QINDEX( Bus, Tar, Lun ); // // Initialize pointer to mirroring structures. // pDevInfo = &DeviceExtension->DevicesPresent[TarLun]; // // Find out the mirroring type if any. // if (InitTime) { // // Check if this device (TID/LUN) is mirrored. // Note: the following logic defaults to dual mirroring if the // EFP_DUAL_MIRRORING and EFP_SINGLE_MIRRORING bits are both set. // if (Env & EFP_DUAL_MIRRORING) { pDevInfo->Type = EfpFtDualBus; // // send a info message to the debug port. // DebugPrint((1, "OliEsc2MirrorInitialize: Dual bus mirroring," " Env=%x, Bus=%x, Tid=%x, Lun=%x.\n", Env, Bus, Tar, Lun )); } else if (Env & EFP_SINGLE_MIRRORING) { pDevInfo->Type = EfpFtSingleBus; // // Send a info message to the debug port. // DebugPrint((1, "OliEsc2MirrorInitialize: Single bus mirroring," " Env=%x, Bus=%x, Tid=%x, Lun=%x.\n", Env, Bus, Tar, Lun )); } else { pDevInfo->Type = EfpFtNone; } // // At the moment we don't know any error. // pDevInfo->KnownError = FALSE; } // // If this is a mirrored disk, get the states of the disks. // if (pDevInfo->Type != EfpFtNone) { // // Check if the source disk is present. // if (Env & EFP_DISK_SOURCE) { // // Source disk is present. // pDevInfo->SourceDiskState = EfpFtMemberHealthy; } else { // // Source disk is missing. // pDevInfo->SourceDiskState = EfpFtMemberMissing; } // // Check if the mirror disk is present. // if (Env & EFP_DISK_MIRROR) { // // Mirror disk is present. // pDevInfo->MirrorDiskState = EfpFtMemberHealthy; } else { // // Mirror disk is missing. // pDevInfo->MirrorDiskState = EfpFtMemberMissing; } } // end if (mirroring) ... } // end for (each TID/LUN) // // all done // return; } // end OliEsc2MirrorInitialize() #endif // EFP_MIRRORING_ENABLED