/* * PROJECT: ReactOS Storage Stack / SCSIPORT storage port library * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: Adapter device object (FDO) support routines * COPYRIGHT: Eric Kohl (eric.kohl@reactos.org) * Aleksey Bragin (aleksey@reactos.org) */ #include "scsiport.h" #define NDEBUG #include static NTSTATUS SpiSendInquiry( _In_ PDEVICE_OBJECT DeviceObject, _Inout_ PSCSI_LUN_INFO LunInfo) { IO_STATUS_BLOCK IoStatusBlock; PIO_STACK_LOCATION IrpStack; KEVENT Event; KIRQL Irql; PIRP Irp; NTSTATUS Status; PINQUIRYDATA InquiryBuffer; PSENSE_DATA SenseBuffer; BOOLEAN KeepTrying = TRUE; ULONG RetryCount = 0; SCSI_REQUEST_BLOCK Srb; PCDB Cdb; PSCSI_PORT_LUN_EXTENSION LunExtension; PSCSI_PORT_DEVICE_EXTENSION DeviceExtension; DPRINT("SpiSendInquiry() called\n"); DeviceExtension = (PSCSI_PORT_DEVICE_EXTENSION)DeviceObject->DeviceExtension; InquiryBuffer = ExAllocatePoolWithTag(NonPagedPool, INQUIRYDATABUFFERSIZE, TAG_SCSIPORT); if (InquiryBuffer == NULL) return STATUS_INSUFFICIENT_RESOURCES; SenseBuffer = ExAllocatePoolWithTag(NonPagedPool, SENSE_BUFFER_SIZE, TAG_SCSIPORT); if (SenseBuffer == NULL) { ExFreePoolWithTag(InquiryBuffer, TAG_SCSIPORT); return STATUS_INSUFFICIENT_RESOURCES; } while (KeepTrying) { /* Initialize event for waiting */ KeInitializeEvent(&Event, NotificationEvent, FALSE); /* Create an IRP */ Irp = IoBuildDeviceIoControlRequest(IOCTL_SCSI_EXECUTE_IN, DeviceObject, NULL, 0, InquiryBuffer, INQUIRYDATABUFFERSIZE, TRUE, &Event, &IoStatusBlock); if (Irp == NULL) { DPRINT("IoBuildDeviceIoControlRequest() failed\n"); /* Quit the loop */ Status = STATUS_INSUFFICIENT_RESOURCES; KeepTrying = FALSE; continue; } /* Prepare SRB */ RtlZeroMemory(&Srb, sizeof(SCSI_REQUEST_BLOCK)); Srb.Length = sizeof(SCSI_REQUEST_BLOCK); Srb.OriginalRequest = Irp; Srb.PathId = LunInfo->PathId; Srb.TargetId = LunInfo->TargetId; Srb.Lun = LunInfo->Lun; Srb.Function = SRB_FUNCTION_EXECUTE_SCSI; Srb.SrbFlags = SRB_FLAGS_DATA_IN | SRB_FLAGS_DISABLE_SYNCH_TRANSFER; Srb.TimeOutValue = 4; Srb.CdbLength = 6; Srb.SenseInfoBuffer = SenseBuffer; Srb.SenseInfoBufferLength = SENSE_BUFFER_SIZE; Srb.DataBuffer = InquiryBuffer; Srb.DataTransferLength = INQUIRYDATABUFFERSIZE; /* Attach Srb to the Irp */ IrpStack = IoGetNextIrpStackLocation (Irp); IrpStack->Parameters.Scsi.Srb = &Srb; /* Fill in CDB */ Cdb = (PCDB)Srb.Cdb; Cdb->CDB6INQUIRY.OperationCode = SCSIOP_INQUIRY; Cdb->CDB6INQUIRY.LogicalUnitNumber = LunInfo->Lun; Cdb->CDB6INQUIRY.AllocationLength = INQUIRYDATABUFFERSIZE; /* Call the driver */ Status = IoCallDriver(DeviceObject, Irp); /* Wait for it to complete */ if (Status == STATUS_PENDING) { DPRINT("SpiSendInquiry(): Waiting for the driver to process request...\n"); KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); Status = IoStatusBlock.Status; } DPRINT("SpiSendInquiry(): Request processed by driver, status = 0x%08X\n", Status); if (SRB_STATUS(Srb.SrbStatus) == SRB_STATUS_SUCCESS) { /* All fine, copy data over */ RtlCopyMemory(LunInfo->InquiryData, InquiryBuffer, INQUIRYDATABUFFERSIZE); /* Quit the loop */ Status = STATUS_SUCCESS; KeepTrying = FALSE; continue; } DPRINT("Inquiry SRB failed with SrbStatus 0x%08X\n", Srb.SrbStatus); /* Check if the queue is frozen */ if (Srb.SrbStatus & SRB_STATUS_QUEUE_FROZEN) { /* Something weird happened, deal with it (unfreeze the queue) */ KeepTrying = FALSE; DPRINT("SpiSendInquiry(): the queue is frozen at TargetId %d\n", Srb.TargetId); LunExtension = SpiGetLunExtension(DeviceExtension, LunInfo->PathId, LunInfo->TargetId, LunInfo->Lun); /* Clear frozen flag */ LunExtension->Flags &= ~LUNEX_FROZEN_QUEUE; /* Acquire the spinlock */ KeAcquireSpinLock(&DeviceExtension->SpinLock, &Irql); /* Process the request */ SpiGetNextRequestFromLun(DeviceObject->DeviceExtension, LunExtension); /* SpiGetNextRequestFromLun() releases the spinlock, so we just lower irql back to what it was before */ KeLowerIrql(Irql); } /* Check if data overrun happened */ if (SRB_STATUS(Srb.SrbStatus) == SRB_STATUS_DATA_OVERRUN) { DPRINT("Data overrun at TargetId %d\n", LunInfo->TargetId); /* Nothing dramatic, just copy data, but limiting the size */ RtlCopyMemory(LunInfo->InquiryData, InquiryBuffer, (Srb.DataTransferLength > INQUIRYDATABUFFERSIZE) ? INQUIRYDATABUFFERSIZE : Srb.DataTransferLength); /* Quit the loop */ Status = STATUS_SUCCESS; KeepTrying = FALSE; } else if ((Srb.SrbStatus & SRB_STATUS_AUTOSENSE_VALID) && SenseBuffer->SenseKey == SCSI_SENSE_ILLEGAL_REQUEST) { /* LUN is not valid, but some device responds there. Mark it as invalid anyway */ /* Quit the loop */ Status = STATUS_INVALID_DEVICE_REQUEST; KeepTrying = FALSE; } else { /* Retry a couple of times if no timeout happened */ if ((RetryCount < 2) && (SRB_STATUS(Srb.SrbStatus) != SRB_STATUS_NO_DEVICE) && (SRB_STATUS(Srb.SrbStatus) != SRB_STATUS_SELECTION_TIMEOUT)) { RetryCount++; KeepTrying = TRUE; } else { /* That's all, quit the loop */ KeepTrying = FALSE; /* Set status according to SRB status */ if (SRB_STATUS(Srb.SrbStatus) == SRB_STATUS_BAD_FUNCTION || SRB_STATUS(Srb.SrbStatus) == SRB_STATUS_BAD_SRB_BLOCK_LENGTH) { Status = STATUS_INVALID_DEVICE_REQUEST; } else { Status = STATUS_IO_DEVICE_ERROR; } } } } /* Free buffers */ ExFreePoolWithTag(InquiryBuffer, TAG_SCSIPORT); ExFreePoolWithTag(SenseBuffer, TAG_SCSIPORT); DPRINT("SpiSendInquiry() done with Status 0x%08X\n", Status); return Status; } /* Scans all SCSI buses */ VOID SpiScanAdapter( _In_ PSCSI_PORT_DEVICE_EXTENSION DeviceExtension) { PSCSI_PORT_LUN_EXTENSION LunExtension; ULONG Bus; ULONG Target; ULONG Lun; PSCSI_BUS_SCAN_INFO BusScanInfo; PSCSI_LUN_INFO LastLunInfo, LunInfo, LunInfoExists; BOOLEAN DeviceExists; ULONG Hint; NTSTATUS Status; ULONG DevicesFound; DPRINT("SpiScanAdapter() called\n"); /* Scan all buses */ for (Bus = 0; Bus < DeviceExtension->BusNum; Bus++) { DPRINT(" Scanning bus %d\n", Bus); DevicesFound = 0; /* Get pointer to the scan information */ BusScanInfo = DeviceExtension->BusesConfig->BusScanInfo[Bus]; if (BusScanInfo) { /* Find the last LUN info in the list */ LunInfo = DeviceExtension->BusesConfig->BusScanInfo[Bus]->LunInfo; LastLunInfo = LunInfo; while (LunInfo != NULL) { LastLunInfo = LunInfo; LunInfo = LunInfo->Next; } } else { /* We need to allocate this buffer */ BusScanInfo = ExAllocatePoolWithTag(NonPagedPool, sizeof(SCSI_BUS_SCAN_INFO), TAG_SCSIPORT); if (!BusScanInfo) { DPRINT1("Out of resources!\n"); return; } /* Store the pointer in the BusScanInfo array */ DeviceExtension->BusesConfig->BusScanInfo[Bus] = BusScanInfo; /* Fill this struct (length and bus ids for now) */ BusScanInfo->Length = sizeof(SCSI_BUS_SCAN_INFO); BusScanInfo->LogicalUnitsCount = 0; BusScanInfo->BusIdentifier = DeviceExtension->PortConfig->InitiatorBusId[Bus]; BusScanInfo->LunInfo = NULL; /* Set pointer to the last LUN info to NULL */ LastLunInfo = NULL; } /* Create LUN information structure */ LunInfo = ExAllocatePoolWithTag(PagedPool, sizeof(SCSI_LUN_INFO), TAG_SCSIPORT); if (!LunInfo) { DPRINT1("Out of resources!\n"); return; } RtlZeroMemory(LunInfo, sizeof(SCSI_LUN_INFO)); /* Create LunExtension */ LunExtension = SpiAllocateLunExtension(DeviceExtension); /* And send INQUIRY to every target */ for (Target = 0; Target < DeviceExtension->PortConfig->MaximumNumberOfTargets; Target++) { /* TODO: Support scan bottom-up */ /* Skip if it's the same address */ if (Target == BusScanInfo->BusIdentifier) continue; /* Try to find an existing device here */ DeviceExists = FALSE; LunInfoExists = BusScanInfo->LunInfo; /* Find matching address on this bus */ while (LunInfoExists) { if (LunInfoExists->TargetId == Target) { DeviceExists = TRUE; break; } /* Advance to the next one */ LunInfoExists = LunInfoExists->Next; } /* No need to bother rescanning, since we already did that before */ if (DeviceExists) continue; /* Scan all logical units */ for (Lun = 0; Lun < SCSI_MAXIMUM_LOGICAL_UNITS; Lun++) { if ((!LunExtension) || (!LunInfo)) break; /* Add extension to the list */ Hint = (Target + Lun) % LUS_NUMBER; LunExtension->Next = DeviceExtension->LunExtensionList[Hint]; DeviceExtension->LunExtensionList[Hint] = LunExtension; /* Fill Path, Target, Lun fields */ LunExtension->PathId = LunInfo->PathId = (UCHAR)Bus; LunExtension->TargetId = LunInfo->TargetId = (UCHAR)Target; LunExtension->Lun = LunInfo->Lun = (UCHAR)Lun; /* Set flag to prevent race conditions */ LunExtension->Flags |= SCSI_PORT_SCAN_IN_PROGRESS; /* Zero LU extension contents */ if (DeviceExtension->LunExtensionSize) { RtlZeroMemory(LunExtension + 1, DeviceExtension->LunExtensionSize); } /* Finally send the inquiry command */ Status = SpiSendInquiry(DeviceExtension->DeviceObject, LunInfo); if (NT_SUCCESS(Status)) { /* Let's see if we really found a device */ PINQUIRYDATA InquiryData = (PINQUIRYDATA)LunInfo->InquiryData; /* Check if this device is unsupported */ if (InquiryData->DeviceTypeQualifier == DEVICE_QUALIFIER_NOT_SUPPORTED) { DeviceExtension->LunExtensionList[Hint] = DeviceExtension->LunExtensionList[Hint]->Next; continue; } /* Clear the "in scan" flag */ LunExtension->Flags &= ~SCSI_PORT_SCAN_IN_PROGRESS; DPRINT("SpiScanAdapter(): Found device of type %d at bus %d tid %d lun %d\n", InquiryData->DeviceType, Bus, Target, Lun); /* * Cache the inquiry data into the LUN extension (or alternatively * we could save a pointer to LunInfo within the LunExtension?) */ RtlCopyMemory(&LunExtension->InquiryData, InquiryData, INQUIRYDATABUFFERSIZE); /* Add this info to the linked list */ LunInfo->Next = NULL; if (LastLunInfo) LastLunInfo->Next = LunInfo; else BusScanInfo->LunInfo = LunInfo; /* Store the last LUN info */ LastLunInfo = LunInfo; /* Store DeviceObject */ LunInfo->DeviceObject = DeviceExtension->DeviceObject; /* Allocate another buffer */ LunInfo = ExAllocatePoolWithTag(PagedPool, sizeof(SCSI_LUN_INFO), TAG_SCSIPORT); if (!LunInfo) { DPRINT1("Out of resources!\n"); break; } RtlZeroMemory(LunInfo, sizeof(SCSI_LUN_INFO)); /* Create a new LU extension */ LunExtension = SpiAllocateLunExtension(DeviceExtension); DevicesFound++; } else { /* Remove this LUN from the list */ DeviceExtension->LunExtensionList[Hint] = DeviceExtension->LunExtensionList[Hint]->Next; /* Decide whether we are continuing or not */ if (Status == STATUS_INVALID_DEVICE_REQUEST) continue; else break; } } } /* Free allocated buffers */ if (LunExtension) ExFreePoolWithTag(LunExtension, TAG_SCSIPORT); if (LunInfo) ExFreePoolWithTag(LunInfo, TAG_SCSIPORT); /* Sum what we found */ BusScanInfo->LogicalUnitsCount += (UCHAR)DevicesFound; DPRINT(" Found %d devices on bus %d\n", DevicesFound, Bus); } DPRINT("SpiScanAdapter() done\n"); } /** * @brief Calls HwInitialize routine of the miniport and sets up interrupts * Should be called inside ScsiPortInitialize (for legacy drivers) * or inside IRP_MN_START_DEVICE for pnp drivers * * @param[in] DeviceExtension The device extension * * @return NTSTATUS of the operation */ NTSTATUS CallHWInitialize( _In_ PSCSI_PORT_DEVICE_EXTENSION DeviceExtension) { PPORT_CONFIGURATION_INFORMATION PortConfig = DeviceExtension->PortConfig; NTSTATUS Status = STATUS_SUCCESS; KIRQL OldIrql; /* Deal with interrupts */ if (DeviceExtension->HwInterrupt == NULL || (PortConfig->BusInterruptLevel == 0 && PortConfig->BusInterruptVector == 0)) { /* No interrupts */ DeviceExtension->InterruptCount = 0; DPRINT1("Interrupt Count: 0\n"); UNIMPLEMENTED; /* This code path will ALWAYS crash so stop it now */ __debugbreak(); } else { BOOLEAN InterruptShareable; KINTERRUPT_MODE InterruptMode[2]; ULONG InterruptVector[2], i, MappedIrq[2]; KIRQL Dirql[2], MaxDirql; KAFFINITY Affinity[2]; DeviceExtension->InterruptLevel[0] = PortConfig->BusInterruptLevel; DeviceExtension->InterruptLevel[1] = PortConfig->BusInterruptLevel2; InterruptVector[0] = PortConfig->BusInterruptVector; InterruptVector[1] = PortConfig->BusInterruptVector2; InterruptMode[0] = PortConfig->InterruptMode; InterruptMode[1] = PortConfig->InterruptMode2; DeviceExtension->InterruptCount = (PortConfig->BusInterruptLevel2 != 0 || PortConfig->BusInterruptVector2 != 0) ? 2 : 1; for (i = 0; i < DeviceExtension->InterruptCount; i++) { /* Register an interrupt handler for this device */ MappedIrq[i] = HalGetInterruptVector( PortConfig->AdapterInterfaceType, PortConfig->SystemIoBusNumber, DeviceExtension->InterruptLevel[i], InterruptVector[i], &Dirql[i], &Affinity[i]); } if (DeviceExtension->InterruptCount == 1 || Dirql[0] > Dirql[1]) MaxDirql = Dirql[0]; else MaxDirql = Dirql[1]; for (i = 0; i < DeviceExtension->InterruptCount; i++) { /* Determine IRQ sharability as usual */ if (PortConfig->AdapterInterfaceType == MicroChannel || InterruptMode[i] == LevelSensitive) { InterruptShareable = TRUE; } else { InterruptShareable = FALSE; } Status = IoConnectInterrupt( &DeviceExtension->Interrupt[i], (PKSERVICE_ROUTINE)ScsiPortIsr, DeviceExtension, &DeviceExtension->IrqLock, MappedIrq[i], Dirql[i], MaxDirql, InterruptMode[i], InterruptShareable, Affinity[i], FALSE); if (!(NT_SUCCESS(Status))) { DPRINT1("Could not connect interrupt %d\n", InterruptVector[i]); DeviceExtension->Interrupt[i] = NULL; return Status; } } if (!NT_SUCCESS(Status)) return Status; } /* Save IoAddress (from access ranges) */ if (PortConfig->NumberOfAccessRanges != 0) { DeviceExtension->IoAddress = ((*(PortConfig->AccessRanges))[0]).RangeStart.LowPart; DPRINT("Io Address %x\n", DeviceExtension->IoAddress); } /* Set flag that it's allowed to disconnect during this command */ DeviceExtension->Flags |= SCSI_PORT_DISCONNECT_ALLOWED; /* Initialize counter of active requests (-1 means there are none) */ DeviceExtension->ActiveRequestCounter = -1; /* Analyze what we have about DMA */ if (DeviceExtension->AdapterObject != NULL && PortConfig->Master && PortConfig->NeedPhysicalAddresses) { DeviceExtension->MapRegisters = TRUE; } else { DeviceExtension->MapRegisters = FALSE; } /* Call HwInitialize at DISPATCH_LEVEL */ KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); if (!KeSynchronizeExecution( DeviceExtension->Interrupt[0], DeviceExtension->HwInitialize, DeviceExtension->MiniPortDeviceExtension)) { DPRINT1("HwInitialize() failed!\n"); KeLowerIrql(OldIrql); return STATUS_ADAPTER_HARDWARE_ERROR; } /* Check if a notification is needed */ if (DeviceExtension->InterruptData.Flags & SCSI_PORT_NOTIFICATION_NEEDED) { /* Call DPC right away, because we're already at DISPATCH_LEVEL */ ScsiPortDpcForIsr(NULL, DeviceExtension->DeviceObject, NULL, NULL); } /* Lower irql back to what it was */ KeLowerIrql(OldIrql); return Status; } VOID SpiCleanupAfterInit( _In_ PSCSI_PORT_DEVICE_EXTENSION DeviceExtension) { PSCSI_LUN_INFO LunInfo; PVOID Ptr; ULONG Bus, Lun; /* Check if we have something to clean up */ if (DeviceExtension == NULL) return; /* Stop the timer */ IoStopTimer(DeviceExtension->DeviceObject); /* Disconnect the interrupts */ while (DeviceExtension->InterruptCount) { if (DeviceExtension->Interrupt[--DeviceExtension->InterruptCount]) IoDisconnectInterrupt(DeviceExtension->Interrupt[DeviceExtension->InterruptCount]); } /* Delete ConfigInfo */ if (DeviceExtension->BusesConfig) { for (Bus = 0; Bus < DeviceExtension->BusNum; Bus++) { if (!DeviceExtension->BusesConfig->BusScanInfo[Bus]) continue; LunInfo = DeviceExtension->BusesConfig->BusScanInfo[Bus]->LunInfo; while (LunInfo) { /* Free current, but save pointer to the next one */ Ptr = LunInfo->Next; ExFreePool(LunInfo); LunInfo = Ptr; } ExFreePool(DeviceExtension->BusesConfig->BusScanInfo[Bus]); } ExFreePool(DeviceExtension->BusesConfig); } /* Free PortConfig */ if (DeviceExtension->PortConfig) ExFreePool(DeviceExtension->PortConfig); /* Free LUNs*/ for(Lun = 0; Lun < LUS_NUMBER; Lun++) { while (DeviceExtension->LunExtensionList[Lun]) { Ptr = DeviceExtension->LunExtensionList[Lun]; DeviceExtension->LunExtensionList[Lun] = DeviceExtension->LunExtensionList[Lun]->Next; ExFreePool(Ptr); } } /* Free common buffer (if it exists) */ if (DeviceExtension->SrbExtensionBuffer != NULL && DeviceExtension->CommonBufferLength != 0) { if (!DeviceExtension->AdapterObject) { ExFreePool(DeviceExtension->SrbExtensionBuffer); } else { HalFreeCommonBuffer(DeviceExtension->AdapterObject, DeviceExtension->CommonBufferLength, DeviceExtension->PhysicalAddress, DeviceExtension->SrbExtensionBuffer, FALSE); } } /* Free SRB info */ if (DeviceExtension->SrbInfo != NULL) ExFreePool(DeviceExtension->SrbInfo); /* Unmap mapped addresses */ while (DeviceExtension->MappedAddressList != NULL) { MmUnmapIoSpace(DeviceExtension->MappedAddressList->MappedAddress, DeviceExtension->MappedAddressList->NumberOfBytes); Ptr = DeviceExtension->MappedAddressList; DeviceExtension->MappedAddressList = DeviceExtension->MappedAddressList->NextMappedAddress; ExFreePool(Ptr); } /* Finally delete the device object */ DPRINT("Deleting device %p\n", DeviceExtension->DeviceObject); IoDeleteDevice(DeviceExtension->DeviceObject); }