diff --git a/drivers/storage/ide/CMakeLists.txt b/drivers/storage/ide/CMakeLists.txt index e68fd10a4d4..f49c4ce8bf3 100644 --- a/drivers/storage/ide/CMakeLists.txt +++ b/drivers/storage/ide/CMakeLists.txt @@ -1,4 +1,5 @@ +add_subdirectory(atapi) add_subdirectory(pciide) add_subdirectory(pciidex) add_subdirectory(uniata) diff --git a/drivers/storage/ide/atapi/CMakeLists.txt b/drivers/storage/ide/atapi/CMakeLists.txt new file mode 100644 index 00000000000..8830562e051 --- /dev/null +++ b/drivers/storage/ide/atapi/CMakeLists.txt @@ -0,0 +1,36 @@ + +include_directories( + ${REACTOS_SOURCE_DIR}/sdk/lib/drivers/sptilib) + +list(APPEND SOURCE + atapi.c + atapi.h + debug.h + dev_config.c + dev_error.c + dev_identify.c + dev_power.c + dev_timings.c + enum.c + fdo.c + ioctl.c + pdo.c + portstate.c + satl.c + scsi.c + scsiex.h + smart.c + wmi.c) + +add_library(atapi MODULE ${SOURCE} atapi.rc) + +if (STACK_PROTECTOR) + target_sources(atapi PRIVATE $) +endif() + +set_module_type(atapi kernelmodedriver) +target_link_libraries(atapi memcmp sptilib ${PSEH_LIB}) +add_pch(atapi atapi.h SOURCE) +add_importlibs(atapi ntoskrnl hal wmilib) +add_cd_file(TARGET atapi DESTINATION reactos/system32/drivers NO_CAB FOR all) +add_registry_inf(atapi_reg.inf) diff --git a/drivers/storage/ide/atapi/atapi.c b/drivers/storage/ide/atapi/atapi.c new file mode 100644 index 00000000000..b5ffbe2dd4c --- /dev/null +++ b/drivers/storage/ide/atapi/atapi.c @@ -0,0 +1,765 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Driver entrypoint and utility functions + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* GLOBALS ********************************************************************/ + +UNICODE_STRING AtapDriverRegistryPath; +BOOLEAN AtapInPEMode; + +ATAPORT_PAGED_DATA +static const struct +{ + PCSTR DeviceType; + PCSTR GenericType; + PCSTR PeripheralId; +} AtapGenericDeviceNames[] = +{ + {"Disk", "GenDisk", "DiskPeripheral" }, // DIRECT_ACCESS_DEVICE + {"Sequential", "GenSequential", "TapePeripheral" }, // SEQUENTIAL_ACCESS_DEVICE + {"Printer", "GenPrinter", "PrinterPeripheral" }, // PRINTER_DEVICE + {"Processor", "GenProcessor", "ProcessorPeripheral" }, // PROCESSOR_DEVICE + {"Worm", "GenWorm", "WormPeripheral" }, // WRITE_ONCE_READ_MULTIPLE_DEVICE + {"CdRom", "GenCdRom", "CdRomPeripheral" }, // READ_ONLY_DIRECT_ACCESS_DEVICE + {"Scanner", "GenScanner", "ScannerPeripheral" }, // SCANNER_DEVICE + {"Optical", "GenOptical", "OpticalDiskPeripheral" }, // OPTICAL_DEVICE + {"Changer", "GenChanger", "MediumChangerPeripheral" }, // MEDIUM_CHANGER + {"Net", "GenNet", "CommunicationsPeripheral"}, // COMMUNICATION_DEVICE + {"Other", "IdeOther", "OtherPeripheral" }, // 10 +}; + +/* FUNCTIONS ******************************************************************/ + +CODE_SEG("PAGE") +PCSTR +AtaTypeCodeToName( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ DEVICE_TYPE_NAME Type) +{ + ULONG DeviceType; + + PAGED_CODE(); + + DeviceType = DevExt->InquiryData.DeviceType; + DeviceType = min(DeviceType, RTL_NUMBER_OF(AtapGenericDeviceNames) - 1); + + switch (Type) + { + case GetDeviceType: + return AtapGenericDeviceNames[DeviceType].DeviceType; + + case GetPeripheralId: + return AtapGenericDeviceNames[DeviceType].PeripheralId; + + case GetGenericType: + if (DevExt->Device.DeviceFlags & DEVICE_IS_SUPER_FLOPPY) + return "GenSFloppy"; // Install the Super Floppy storage class driver + else + return AtapGenericDeviceNames[DeviceType].GenericType; + + default: + ASSERT(FALSE); + UNREACHABLE; + } +} + +CODE_SEG("PAGE") +NTSTATUS +AtaOpenRegistryKey( + _Out_ PHANDLE KeyHandle, + _In_ HANDLE RootKey, + _In_ PUNICODE_STRING KeyName, + _In_ BOOLEAN Create) +{ + NTSTATUS Status; + ULONG Disposition; + OBJECT_ATTRIBUTES ObjectAttributes; + + PAGED_CODE(); + + InitializeObjectAttributes(&ObjectAttributes, + KeyName, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + RootKey, + NULL); + + if (Create) + { + Status = ZwCreateKey(KeyHandle, + KEY_ALL_ACCESS, + &ObjectAttributes, + 0, + NULL, + REG_OPTION_VOLATILE, + &Disposition); + } + else + { + Status = ZwOpenKey(KeyHandle, KEY_ALL_ACCESS, &ObjectAttributes); + } + + return Status; +} + +CODE_SEG("PAGE") +VOID +AtaGetRegistryKey( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ UCHAR TargetId, + _In_ PCWSTR KeyName, + _Out_ PULONG KeyValue, + _In_ ULONG DefaultValue) +{ + UCHAR Buffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(*KeyValue)]; + HANDLE HwKeyHandle, TargetKeyHandle; + UNICODE_STRING ValueName, TargetKeyName; + NTSTATUS Status; + WCHAR TargetKeyBuffer[sizeof("Target99")]; + ULONG ResultLength; + PKEY_VALUE_PARTIAL_INFORMATION PartialInfo = (PVOID)Buffer; + + PAGED_CODE(); + + Status = IoOpenDeviceRegistryKey(ChanExt->Pdo, + PLUGPLAY_REGKEY_DEVICE, + KEY_ALL_ACCESS, + &HwKeyHandle); + if (!NT_SUCCESS(Status)) + { + TRACE("Failed to open device hardware key, status 0x%lx\n", Status); + return; + } + + /* Open or create the 'TargetX' key */ + Status = RtlStringCbPrintfW(TargetKeyBuffer, + sizeof(TargetKeyBuffer), + L"Target%u", + TargetId); + ASSERT(NT_SUCCESS(Status)); + RtlInitUnicodeString(&TargetKeyName, TargetKeyBuffer); + Status = AtaOpenRegistryKey(&TargetKeyHandle, HwKeyHandle, &TargetKeyName, TRUE); + if (!NT_SUCCESS(Status)) + { + TRACE("Failed to create '%wZ' key, status 0x%lx\n", &TargetKeyName, Status); + goto Cleanup; + } + + RtlInitUnicodeString(&ValueName, KeyName); + Status = ZwQueryValueKey(TargetKeyHandle, + &ValueName, + KeyValuePartialInformation, + PartialInfo, + sizeof(Buffer), + &ResultLength); + ZwClose(TargetKeyHandle); + if (!NT_SUCCESS(Status) || + (PartialInfo->Type != REG_DWORD) || + (PartialInfo->DataLength != sizeof(*KeyValue))) + { + TRACE("Failed to read '%wZ' key, status 0x%lx\n", &ValueName, Status); + + *KeyValue = DefaultValue; + goto Cleanup; + } + + *KeyValue = *(PULONG)&PartialInfo->Data; + +Cleanup: + ZwClose(HwKeyHandle); +} + +CODE_SEG("PAGE") +VOID +AtaSetRegistryKey( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ UCHAR TargetId, + _In_ PCWSTR KeyName, + _In_ ULONG KeyValue) +{ + HANDLE HwKeyHandle, TargetKeyHandle; + UNICODE_STRING ValueName, TargetKeyName; + NTSTATUS Status; + WCHAR TargetKeyBuffer[sizeof("Target99")]; + + PAGED_CODE(); + + Status = IoOpenDeviceRegistryKey(ChanExt->Pdo, + PLUGPLAY_REGKEY_DEVICE, + KEY_ALL_ACCESS, + &HwKeyHandle); + if (!NT_SUCCESS(Status)) + { + TRACE("Failed to open device hardware key, status 0x%lx\n", Status); + return; + } + + /* Open or create the 'TargetX' key */ + Status = RtlStringCbPrintfW(TargetKeyBuffer, + sizeof(TargetKeyBuffer), + L"Target%u", + TargetId); + ASSERT(NT_SUCCESS(Status)); + RtlInitUnicodeString(&TargetKeyName, TargetKeyBuffer); + Status = AtaOpenRegistryKey(&TargetKeyHandle, HwKeyHandle, &TargetKeyName, TRUE); + if (!NT_SUCCESS(Status)) + { + TRACE("Failed to create '%wZ' key, status 0x%lx\n", &TargetKeyName, Status); + goto Cleanup; + } + + RtlInitUnicodeString(&ValueName, KeyName); + Status = ZwSetValueKey(TargetKeyHandle, + &ValueName, + 0, + REG_DWORD, + &KeyValue, + sizeof(KeyValue)); + ZwClose(TargetKeyHandle); + if (!NT_SUCCESS(Status)) + { + TRACE("Failed to set '%wZ' key, status 0x%lx\n", &ValueName, Status); + } + +Cleanup: + ZwClose(HwKeyHandle); +} + +CODE_SEG("PAGE") +VOID +AtaSetPortRegistryKey( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PCWSTR KeyName, + _In_ ULONG KeyValue) +{ + HANDLE HwKeyHandle; + UNICODE_STRING ValueName; + NTSTATUS Status; + + PAGED_CODE(); + + Status = IoOpenDeviceRegistryKey(ChanExt->Pdo, + PLUGPLAY_REGKEY_DEVICE, + KEY_ALL_ACCESS, + &HwKeyHandle); + if (!NT_SUCCESS(Status)) + { + TRACE("Failed to open device hardware key, status 0x%lx\n", Status); + return; + } + + RtlInitUnicodeString(&ValueName, KeyName); + Status = ZwSetValueKey(HwKeyHandle, + &ValueName, + 0, + REG_DWORD, + &KeyValue, + sizeof(KeyValue)); + ZwClose(HwKeyHandle); + if (!NT_SUCCESS(Status)) + { + TRACE("Failed to set '%wZ' key, status 0x%lx\n", &ValueName, Status); + } +} + +CODE_SEG("PAGE") +NTSTATUS +NTAPI +AtaDispatchCreateClose( + _In_ PDEVICE_OBJECT DeviceObject, + _Inout_ PIRP Irp) +{ + PAGED_CODE(); + + Irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return STATUS_SUCCESS; +} + +CODE_SEG("PAGE") +VOID +NTAPI +AtaUnload( + _In_ PDRIVER_OBJECT DriverObject) +{ + PAGED_CODE(); + + RtlFreeUnicodeString(&AtapDriverRegistryPath); +} + +VOID +NTAPI +AtaStorageNotificationWorker( + _In_ PDEVICE_OBJECT DeviceObject, + _In_opt_ PVOID Context) +{ + PIO_WORKITEM WorkItem = Context; + PDEVICE_OBJECT TopDeviceObject; + PIRP Irp; + KEVENT Event; + IO_STATUS_BLOCK IoStatus; + STORAGE_EVENT_NOTIFICATION EventObject; + NTSTATUS Status; + + TopDeviceObject = IoGetAttachedDeviceReference(DeviceObject); + + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + EventObject.Version = STORAGE_EVENT_NOTIFICATION_VERSION_V1; + EventObject.Size = sizeof(EventObject); + EventObject.Events = STORAGE_EVENT_ALL; + + Irp = IoBuildDeviceIoControlRequest(IOCTL_STORAGE_EVENT_NOTIFICATION, + TopDeviceObject, + &EventObject, + sizeof(EventObject), + NULL, + 0, + FALSE, + &Event, + &IoStatus); + if (!Irp) + { + ERR("IoBuildDeviceIoControlRequest() failed\n"); + goto Exit; + } + + Status = IoCallDriver(DeviceObject, Irp); + if (Status == STATUS_PENDING) + { + KeWaitForSingleObject(&Event, Suspended, KernelMode, FALSE, NULL); + Status = IoStatus.Status; + } + + TRACE("Notification result %08lx\n", Status); + +Exit: + ObDereferenceObject(TopDeviceObject); + IoFreeWorkItem(WorkItem); +} + +VOID +NTAPI +AtaStorageNotificationlDpc( + _In_ PKDPC Dpc, + _In_opt_ PVOID DeferredContext, + _In_opt_ PVOID SystemArgument1, + _In_opt_ PVOID SystemArgument2) +{ + PATAPORT_PORT_DATA PortData = DeferredContext; + ULONG DeviceBitmap = PtrToUlong(SystemArgument1); + PATAPORT_CHANNEL_EXTENSION ChanExt; + ULONG i; + + UNREFERENCED_PARAMETER(Dpc); + UNREFERENCED_PARAMETER(SystemArgument2); + + ChanExt = CONTAINING_RECORD(PortData, ATAPORT_CHANNEL_EXTENSION, PortData); + + for (i = 0; i < ATA_MAX_DEVICE; ++i) + { + PATAPORT_DEVICE_EXTENSION DevExt; + PIO_WORKITEM WorkItem; + + if (!(DeviceBitmap & (1 << i))) + continue; + + DevExt = AtaFdoFindDeviceByPath(ChanExt, + AtaMarshallScsiAddress(PortData->PortNumber, i, 0), + AtaStorageNotificationlDpc); + if (!DevExt) + continue; + + WorkItem = IoAllocateWorkItem(DevExt->Common.Self); + if (!WorkItem) + { + ERR("Failed to allocate workitem"); + } + else + { + IoQueueWorkItem(WorkItem, AtaStorageNotificationWorker, CriticalWorkQueue, WorkItem); + } + + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, AtaStorageNotificationlDpc); + } +} + +CODE_SEG("PAGE") +NTSTATUS +AtaPnpQueryInterface( + _In_ PATAPORT_COMMON_EXTENSION CommonExt, + _In_ const GUID* Guid, + _Out_ PVOID Interface, + _In_ ULONG Version, + _In_ ULONG Size) +{ + KEVENT Event; + PIRP Irp; + IO_STATUS_BLOCK IoStatusBlock; + PIO_STACK_LOCATION IoStack; + NTSTATUS Status; + + PAGED_CODE(); + + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + Irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP, + CommonExt->LowerDeviceObject, + NULL, + 0, + NULL, + &Event, + &IoStatusBlock); + if (!Irp) + return STATUS_INSUFFICIENT_RESOURCES; + + Irp->IoStatus.Status = STATUS_NOT_SUPPORTED; + + IoStack = IoGetNextIrpStackLocation(Irp); + IoStack->MinorFunction = IRP_MN_QUERY_INTERFACE; + IoStack->Parameters.QueryInterface.InterfaceType = Guid; + IoStack->Parameters.QueryInterface.Size = Size; + IoStack->Parameters.QueryInterface.Version = Version; + IoStack->Parameters.QueryInterface.Interface = Interface; + IoStack->Parameters.QueryInterface.InterfaceSpecificData = NULL; + + Status = IoCallDriver(CommonExt->LowerDeviceObject, Irp); + if (Status == STATUS_PENDING) + { + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = IoStatusBlock.Status; + } + + return Status; +} + +CODE_SEG("PAGE") +NTSTATUS +AtaPnpRepeatRequest( + _In_ PATAPORT_COMMON_EXTENSION CommonExt, + _In_ PIRP Irp, + _In_opt_ PDEVICE_CAPABILITIES DeviceCapabilities) +{ + PATAPORT_COMMON_EXTENSION FdoExt = CommonExt->FdoExt; + PDEVICE_OBJECT TopDeviceObject; + PIO_STACK_LOCATION IoStack, SubStack; + PIRP SubIrp; + KEVENT Event; + NTSTATUS Status; + + PAGED_CODE(); + ASSERT(!IS_FDO(CommonExt)); + + TopDeviceObject = IoGetAttachedDeviceReference(FdoExt->Self); + + SubIrp = IoAllocateIrp(TopDeviceObject->StackSize, FALSE); + if (!SubIrp) + { + ObDereferenceObject(TopDeviceObject); + return STATUS_INSUFFICIENT_RESOURCES; + } + + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + IoStack = IoGetCurrentIrpStackLocation(Irp); + SubStack = IoGetNextIrpStackLocation(SubIrp); + RtlCopyMemory(SubStack, IoStack, sizeof(*SubStack)); + + if (DeviceCapabilities) + SubStack->Parameters.DeviceCapabilities.Capabilities = DeviceCapabilities; + + IoSetCompletionRoutine(SubIrp, + AtaPdoCompletionRoutine, + &Event, + TRUE, + TRUE, + TRUE); + + SubIrp->IoStatus.Status = STATUS_NOT_SUPPORTED; + + Status = IoCallDriver(TopDeviceObject, SubIrp); + if (Status == STATUS_PENDING) + { + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + } + + ObDereferenceObject(TopDeviceObject); + + Status = SubIrp->IoStatus.Status; + IoFreeIrp(SubIrp); + + return Status; +} + +CODE_SEG("PAGE") +NTSTATUS +AtaPnpQueryDeviceUsageNotification( + _In_ PATAPORT_COMMON_EXTENSION CommonExt, + _In_ PIRP Irp) +{ + PIO_STACK_LOCATION IoStack; + NTSTATUS Status; + volatile LONG* Counter; + + PAGED_CODE(); + + if (!IS_FDO(CommonExt)) + { + Status = AtaPnpRepeatRequest(CommonExt, Irp, NULL); + } + else + { + if (!NT_VERIFY(IoForwardIrpSynchronously(CommonExt->LowerDeviceObject, Irp))) + return STATUS_UNSUCCESSFUL; + Status = Irp->IoStatus.Status; + } + if (!NT_SUCCESS(Status)) + return Status; + + IoStack = IoGetCurrentIrpStackLocation(Irp); + switch (IoStack->Parameters.UsageNotification.Type) + { + case DeviceUsageTypePaging: + Counter = &CommonExt->PageFiles; + break; + + case DeviceUsageTypeHibernation: + Counter = &CommonExt->HibernateFiles; + break; + + case DeviceUsageTypeDumpFile: + Counter = &CommonExt->DumpFiles; + break; + + default: + return Status; + } + + IoAdjustPagingPathCount(Counter, IoStack->Parameters.UsageNotification.InPath); + + if (!IS_FDO(CommonExt)) + IoInvalidateDeviceState(CommonExt->Self); + + return STATUS_SUCCESS; +} + +CODE_SEG("PAGE") +NTSTATUS +AtaPnpQueryPnpDeviceState( + _In_ PATAPORT_COMMON_EXTENSION CommonExt, + _In_ PIRP Irp) +{ + PAGED_CODE(); + + if (CommonExt->PageFiles || CommonExt->HibernateFiles || CommonExt->DumpFiles) + Irp->IoStatus.Information |= PNP_DEVICE_NOT_DISABLEABLE; + + if (IS_FDO(CommonExt)) + Irp->IoStatus.Status = STATUS_SUCCESS; + + return STATUS_SUCCESS; +} + +CODE_SEG("PAGE") +VOID +AtaPnpInitializeCommonExtension( + _In_ PATAPORT_COMMON_EXTENSION CommonExt, + _In_ PDEVICE_OBJECT SelfDeviceObject, + _In_ ULONG Flags) +{ + PAGED_CODE(); + + CommonExt->Flags = Flags; + CommonExt->Self = SelfDeviceObject; + CommonExt->DevicePowerState = PowerDeviceD0; + CommonExt->SystemPowerState = PowerSystemWorking; + + IoInitializeRemoveLock(&CommonExt->RemoveLock, ATAPORT_TAG, 0, 0); +} + +CODE_SEG("PAGE") +NTSTATUS +NTAPI +AtaAddChannel( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PDEVICE_OBJECT PhysicalDeviceObject) +{ + NTSTATUS Status; + PDEVICE_OBJECT Fdo; + UNICODE_STRING DeviceName; + PATAPORT_CHANNEL_EXTENSION ChanExt; + WCHAR DeviceNameBuffer[sizeof("\\Device\\Ide\\IdePort99999")]; + DECLARE_PAGED_WSTRING(FdoFormat, L"\\Device\\Ide\\IdePort%lu"); + static ULONG AtapFdoNumber = 0; + + PAGED_CODE(); + + Status = RtlStringCbPrintfW(DeviceNameBuffer, + sizeof(DeviceNameBuffer), + FdoFormat, + AtapFdoNumber); + ASSERT(NT_SUCCESS(Status)); + RtlInitUnicodeString(&DeviceName, DeviceNameBuffer); + + INFO("%s(%p, %p) '%wZ'\n", __FUNCTION__, DriverObject, PhysicalDeviceObject, &DeviceName); + + Status = IoCreateDevice(DriverObject, + sizeof(*ChanExt), + &DeviceName, + FILE_DEVICE_CONTROLLER, + FILE_DEVICE_SECURE_OPEN, + FALSE, + &Fdo); + if (!NT_SUCCESS(Status)) + { + ERR("Failed to create the FDO with status 0x%lx\n", Status); + return Status; + } + + ChanExt = Fdo->DeviceExtension; + + RtlZeroMemory(ChanExt, sizeof(*ChanExt)); + AtaPnpInitializeCommonExtension(&ChanExt->Common, Fdo, DO_IS_FDO); + + ChanExt->Common.LowerDeviceObject = IoAttachDeviceToDeviceStack(Fdo, PhysicalDeviceObject); + if (!ChanExt->Common.LowerDeviceObject) + { + ERR("Failed to attach the FDO\n"); + Status = STATUS_DEVICE_REMOVED; + goto Failure; + } + ChanExt->DeviceObjectNumber = AtapFdoNumber++; + ChanExt->Pdo = PhysicalDeviceObject; + + /* DMA buffers alignment */ + Fdo->AlignmentRequirement = ChanExt->Common.LowerDeviceObject->AlignmentRequirement; + Fdo->AlignmentRequirement = max(Fdo->AlignmentRequirement, ATA_MIN_BUFFER_ALIGNMENT); + + KeInitializeSpinLock(&ChanExt->PdoListLock); + + Fdo->Flags &= ~DO_DEVICE_INITIALIZING; + + return STATUS_SUCCESS; + +Failure: + if (ChanExt->Common.LowerDeviceObject) + IoDetachDevice(ChanExt->Common.LowerDeviceObject); + + IoDeleteDevice(Fdo); + + return Status; +} + +static +CODE_SEG("INIT") +BOOLEAN +AtaInPEMode(VOID) +{ + OBJECT_ATTRIBUTES ObjectAttributes; + HANDLE KeyHandle; + NTSTATUS Status; + DECLARE_PAGED_UNICODE_STRING( + KeyName, L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\MiniNT"); + + PAGED_CODE(); + + InitializeObjectAttributes(&ObjectAttributes, + &KeyName, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + NULL, + NULL); + + Status = ZwOpenKey(&KeyHandle, KEY_READ, &ObjectAttributes); + if (NT_SUCCESS(Status)) + { + ZwClose(KeyHandle); + return TRUE; + } + + return FALSE; +} + +static +CODE_SEG("INIT") +VOID +AtaCreateIdeDirectory(VOID) +{ + HANDLE Handle; + NTSTATUS Status; + OBJECT_ATTRIBUTES ObjectAttributes; + DECLARE_PAGED_UNICODE_STRING(DirectoryName, L"\\Device\\Ide"); + + PAGED_CODE(); + + InitializeObjectAttributes(&ObjectAttributes, + &DirectoryName, + OBJ_CASE_INSENSITIVE | OBJ_PERMANENT | OBJ_KERNEL_HANDLE, + NULL, + NULL); + + Status = ZwCreateDirectoryObject(&Handle, DIRECTORY_ALL_ACCESS, &ObjectAttributes); + if (NT_SUCCESS(Status)) + { + /* We don't need a handle for a permanent object */ + ZwClose(Handle); + } + /* + * Ignore directory creation failures (don't report them as a driver initialization error) + * as the directory may have already been created by another driver. + * We will handle fatal errors later via IoCreateDevice() call. + */ +} + +CODE_SEG("INIT") +NTSTATUS +NTAPI +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath) +{ + INFO("ATAPI driver entry\n"); + + // FIXME: No crashdump/hibernation support + if (!DriverObject) + return STATUS_NOT_IMPLEMENTED; + + /* Make a copy of the registry path */ + AtapDriverRegistryPath.MaximumLength = RegistryPath->Length + sizeof(UNICODE_NULL); + AtapDriverRegistryPath.Buffer = + ExAllocatePoolUninitialized(NonPagedPool, + AtapDriverRegistryPath.MaximumLength, + ATAPORT_TAG); + if (!AtapDriverRegistryPath.Buffer) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + RtlCopyUnicodeString(&AtapDriverRegistryPath, RegistryPath); + AtapDriverRegistryPath.Buffer[RegistryPath->Length / sizeof(WCHAR)] = UNICODE_NULL; + + DriverObject->MajorFunction[IRP_MJ_CREATE] = AtaDispatchCreateClose; + DriverObject->MajorFunction[IRP_MJ_CLOSE] = AtaDispatchCreateClose; + DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = AtaDispatchDeviceControl; + DriverObject->MajorFunction[IRP_MJ_SCSI] = AtaDispatchScsi; + DriverObject->MajorFunction[IRP_MJ_POWER] = AtaDispatchPower; + DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = AtaDispatchWmi; + DriverObject->MajorFunction[IRP_MJ_PNP] = AtaDispatchPnp; + DriverObject->DriverExtension->AddDevice = AtaAddChannel; + DriverObject->DriverUnload = AtaUnload; + + KeInitializeDpc(&AtapCompletionDpc, AtaReqCompletionDpc, NULL); + InitializeSListHead(&AtapCompletionQueueList); + + /* Create a directory to hold the driver's device objects */ + AtaCreateIdeDirectory(); + + AtapInPEMode = AtaInPEMode(); + + return STATUS_SUCCESS; +} diff --git a/drivers/storage/ide/atapi/atapi.h b/drivers/storage/ide/atapi/atapi.h new file mode 100644 index 00000000000..0c3f9b492d9 --- /dev/null +++ b/drivers/storage/ide/atapi/atapi.h @@ -0,0 +1,1022 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Common header file + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +typedef struct _ATAPORT_CHANNEL_EXTENSION ATAPORT_CHANNEL_EXTENSION, *PATAPORT_CHANNEL_EXTENSION; +typedef struct _ATAPORT_DEVICE_EXTENSION ATAPORT_DEVICE_EXTENSION, *PATAPORT_DEVICE_EXTENSION; +typedef struct _ATAPORT_PORT_DATA ATAPORT_PORT_DATA, *PATAPORT_PORT_DATA; +typedef struct _ATAPORT_IO_CONTEXT ATAPORT_IO_CONTEXT, *PATAPORT_IO_CONTEXT; + +typedef union _ATA_SCSI_ADDRESS +{ + /* + * The ordering between Lun, TargetId, and PathId fields is important + * with address comparison. + */ + struct + { + /** + * The lun number 0-7. + * @sa ATA_MAX_LUN_COUNT + */ + UCHAR Lun; + + /** + * PATA: + * The device number 0 - Master, 1 - Slave, + * 2 - Master (PC-98), 3 - Slave (PC-98). + * + * AHCI: + * The device number 0-15. + */ + UCHAR TargetId; + + /** + * The ATA channel number 0-31. + */ + UCHAR PathId; + + UCHAR IsValid; + }; + ULONG AsULONG; +} ATA_SCSI_ADDRESS, *PATA_SCSI_ADDRESS; + +typedef VOID +(STOP_IO_CALLBACK)( + _In_ PATAPORT_IO_CONTEXT Device); +typedef STOP_IO_CALLBACK *PSTOP_IO_CALLBACK; + +typedef struct _ATAPORT_IO_CONTEXT +{ + ATA_IO_CONTEXT_COMMON; + + ULONG DeviceFlags; +#define DEVICE_PIO_ONLY 0x00000001 +#define DEVICE_LBA_MODE 0x00000002 +#define DEVICE_LBA48 0x00000004 +#define DEVICE_HAS_FUA 0x00000008 +#define DEVICE_NCQ 0x00000010 +#define DEVICE_HAS_MEDIA_STATUS 0x00000020 +#define DEVICE_SENSE_DATA_REPORTING 0x00000040 +#define DEVICE_IS_SUPER_FLOPPY 0x00000080 +#define DEVICE_IS_PDO_REMOVABLE 0x00000100 +#define DEVICE_UNINITIALIZED 0x00000200 +#define DEVICE_PNP_STARTED 0x00000400 +#define DEVICE_DESCRIPTOR_SENSE 0x00000800 +#define DEVICE_PIO_VIA_DMA 0x00001000 +#define DEVICE_PIO_FOR_LBA48_XFER 0x00002000 +#define DEVICE_CACHE_INQUIRY 0x00004000 + + PULONG PowerIdleCounter; + PVOID LocalBuffer; + ATA_SCSI_ADDRESS AtaScsiAddress; + PATAPORT_PORT_DATA PortData; + KSPIN_LOCK QueueLock; + + LONG QueueFlags; +#define QUEUE_FLAG_FROZEN_PORT_BUSY 0x00000001 +#define QUEUE_FLAG_FROZEN_SLOT 0x00000002 +#define QUEUE_FLAG_FROZEN_PNP 0x00000004 +#define QUEUE_FLAG_SIGNAL_STOP 0x00000008 +#define QUEUE_FLAG_FROZEN_QUEUE_FREEZE 0x00000010 +#define QUEUE_FLAG_FROZEN_POWER 0x00000020 +#define QUEUE_FLAG_FROZEN_REMOVED 0x00000040 +#define QUEUE_FLAG_FROZEN_QUEUE_LOCK 0x00080000 + +#define QUEUE_FLAGS_FROZEN \ + (QUEUE_FLAG_FROZEN_PORT_BUSY | \ + QUEUE_FLAG_FROZEN_SLOT | \ + QUEUE_FLAG_FROZEN_PNP | \ + QUEUE_FLAG_FROZEN_QUEUE_FREEZE | \ + QUEUE_FLAG_FROZEN_POWER | \ + QUEUE_FLAG_FROZEN_REMOVED | \ + QUEUE_FLAG_FROZEN_QUEUE_LOCK) + +#define QUEUE_FLAGS_FROZEN_NOT_BYPASS \ + (QUEUE_FLAGS_FROZEN & ~(QUEUE_FLAG_FROZEN_QUEUE_FREEZE | QUEUE_FLAG_FROZEN_QUEUE_LOCK)) + + USHORT Cylinders; + USHORT Heads; + USHORT SectorsPerTrack; + ULONG64 TotalSectors; + ULONG MaxRequestsBitmap; + ULONG FreeRequestsBitmap; + PATA_DEVICE_REQUEST Requests; + LIST_ENTRY DeviceQueueList; + KEVENT QueueStoppedEvent; + PSCSI_REQUEST_BLOCK QuiescenceSrb; +#if DBG + struct + { + ULONG RequestsStarted; + ULONG RequestsCompleted; + } Statistics; +#endif +} ATAPORT_IO_CONTEXT, *PATAPORT_IO_CONTEXT; + +#include "debug.h" +#include "scsiex.h" + +#if defined(_MSC_VER) +#pragma section("PAGECONS", read) +#endif + +/** Pageable read-only data */ +#define ATAPORT_PAGED_DATA DATA_SEG("PAGECONS") + +#define DECLARE_PAGED_UNICODE_STRING(Variable, Str) \ + static const ATAPORT_PAGED_DATA WCHAR Variable##_buffer[] = Str; \ + UNICODE_STRING Variable = { sizeof(Str) - sizeof(WCHAR), sizeof(Str), (PWCH)Variable##_buffer } \ + +#define DECLARE_PAGED_STRING(v, n) \ + static const ATAPORT_PAGED_DATA CHAR (v)[] = (n) + +#define DECLARE_PAGED_WSTRING(v, n) \ + static const ATAPORT_PAGED_DATA WCHAR (v)[] = (n) + +#define ASSUME(cond) \ + do { \ + ASSERT(cond); \ + __assume(cond); \ + } while (0) + +#define ATAPORT_TAG 'PedI' + +#define IS_FDO(p) \ + ((((PATAPORT_COMMON_EXTENSION)(p))->Flags & DO_IS_FDO) != 0) + +#define IS_ATAPI(Device) \ + (((Device)->TransportFlags & DEVICE_IS_ATAPI) != 0) + +#define NUM_TO_BITMAP(num) (0xFFFFFFFF >> (RTL_BITS_OF(ULONG) - (num))) + +/** + * @brief + * The maximum length of identifier strings for ATA devices excluding the terminating NULL. + * + * @sa + * See MSDN note: + * https://learn.microsoft.com/en-us/windows-hardware/drivers/install/identifiers-for-ide-devices + * + * and + * IDENTIFY_DEVICE_DATA.SerialNumber + * IDENTIFY_DEVICE_DATA.ModelNumber + * IDENTIFY_DEVICE_DATA.FirmwareRevision + */ +/*@{*/ +#define ATAPORT_FN_FIELD 40 +#define ATAPORT_SN_FIELD 40 +#define ATAPORT_RN_FIELD 8 +/*@}*/ + +#define ATA_RESERVED_PAGES 4 + +#define ATA_MAX_LUN_COUNT 8 + +/* Maximum size (ATA Information VPD page) */ +#define ATA_LOCAL_BUFFER_SIZE 572 + +#define MAX_SLOTS 32 + +#define ATA_MAX_LBA_28 0x0FFFFFFFULL +#define ATA_MAX_LBA_48 (1ULL << 48) + +#define IDE_DRIVE_SELECT_SLAVE 0x10 +#define IDE_HIGH_ORDER_BYTE 0x80 +#define IDE_DRIVE_SELECT 0xA0 + +#define IDE_ERROR_WRITE_PROTECT 0x40 + +#define IDE_FEATURE_PIO 0x00 +#define IDE_FEATURE_DMA 0x01 +#define IDE_FEATURE_DMADIR 0x04 + +#define IDE_DEVICE_FUA_NCQ 0x80 + +#define IDE_DC_ALWAYS 0x08 + +#define IDE_COMMAND_REQUEST_SENSE_DATA_EXT 0x0B + +#define SRB_FLAG_RETRY_COUNT_MASK 0x000000FF +#define SRB_FLAG_LOW_MEM_RETRY 0x00000100 +#define SRB_FLAG_PIO_RETRY 0x00000200 + +#define SRB_SET_FLAGS(Srb, Flags) \ + ((Srb)->SrbExtension = (PVOID)((ULONG_PTR)(Srb)->SrbExtension | (Flags))) + +#define SRB_CLEAR_FLAGS(Srb, Flags) \ + ((Srb)->SrbExtension = (PVOID)((ULONG_PTR)(Srb)->SrbExtension & ~(Flags))) + +#define SRB_GET_FLAGS(Srb) ((ULONG_PTR)(Srb)->SrbExtension) + +/* Values are assigned by priority */ +typedef enum _ATA_PORT_ACTION +{ + ACTION_PORT_RESET = (1 << 0), // High priority + ACTION_ENUM_PORT = (1 << 1), + ACTION_ENUM_DEVICE_NEW = (1 << 2), + ACTION_ENUM_DEVICE = (1 << 3), + ACTION_PORT_TIMING = (1 << 4), // Set transfer timings after device enumeration only + ACTION_DEVICE_CONFIG = (1 << 5), // Also use the timing information for configuration + ACTION_DEVICE_ERROR = (1 << 6), + ACTION_DEVICE_POWER = (1 << 7), +} ATA_PORT_ACTION; + +typedef enum _ATA_DEVICE_STATUS +{ + DEV_STATUS_NO_DEVICE, + DEV_STATUS_NEW_DEVICE, + DEV_STATUS_SAME_DEVICE, + DEV_STATUS_FAILED, +} ATA_DEVICE_STATUS; + +typedef enum _ATA_ERROR_LOG_VALUE +{ + EVENT_CODE_CRC_ERROR = 100, + EVENT_CODE_BAD_STATE, + EVENT_CODE_TIMEOUT, + EVENT_CODE_DOWNSHIFT, + EVENT_CODE_DMA_DISABLE, + EVENT_CODE_NCQ_DISABLE, +} ATA_ERROR_LOG_VALUE; + +typedef struct _ATA_WORKER_CONTEXT +{ + KSPIN_LOCK Lock; + KEVENT ThreadEvent; + volatile PATAPORT_DEVICE_EXTENSION EnumDevExt; + volatile ULONG DeviceCount; + volatile LONG EventsPending; + ULONG Flags; +#define WORKER_FLAG_NEED_RESCAN 0x00000001 +#define WORKER_FLAG_COMPLETE_PORT_ENUM_EVENT 0x00000002 + ULONG PausedSlotsBitmap; + ULONG BadDeviceBitmap; + ULONG ResetRetryCount; +#if DBG + ULONG StateLoopCount; +#endif + KDPC Dpc; + PATA_DEVICE_REQUEST FailedRequest; + PATA_DEVICE_REQUEST OldRequest; + KEVENT CompletionEvent; + ATA_DEVICE_REQUEST InternalRequest; + ATAPORT_IO_CONTEXT InternalDevice; + KEVENT EnumerationEvent; + KDPC NotificationDpc; + PKTHREAD Thread; +} ATA_WORKER_CONTEXT, *PATA_WORKER_CONTEXT; + +typedef struct _ATA_WORKER_DEVICE_CONTEXT +{ + volatile LONG EventsPending; + volatile LONG EnumStatus; + ULONG ResetRetryCount; + ULONG Flags; +#define DEV_WORKER_FLAG_HOLD_REFERENCE 0x00000001 +#define DEV_WORKER_FLAG_REMOVED 0x00000002 + KEVENT EnumerationEvent; + KEVENT ConfigureEvent; +} ATA_WORKER_DEVICE_CONTEXT, *PATA_WORKER_DEVICE_CONTEXT; + +#define QUEUE_ENTRY_FROM_IRP(Irp) \ + ((PREQUEST_QUEUE_ENTRY)&(((PIRP)(Irp))->Tail.Overlay.DriverContext[0])) + +#define IRP_FROM_QUEUE_ENTRY(QueueEntry) \ + (PIRP)CONTAINING_RECORD(QueueEntry, IRP, Tail.Overlay.DriverContext[0]) + +typedef struct _REQUEST_QUEUE_ENTRY +{ + LIST_ENTRY ListEntry; + PVOID Context; + ULONG SortKey; +} REQUEST_QUEUE_ENTRY, *PREQUEST_QUEUE_ENTRY; + +/* Check for Irp->Tail.Overlay.DriverContext */ +C_ASSERT(sizeof(REQUEST_QUEUE_ENTRY) <= 4 * sizeof(PVOID)); + +#define ASSERT_REQUEST(Request) \ + ASSERT((Request) && (Request)->Signature == ATA_DEVICE_REQUEST_SIGNATURE) + +C_ASSERT(REQUEST_FLAG_DATA_IN == SRB_FLAGS_DATA_IN); +C_ASSERT(REQUEST_FLAG_DATA_OUT == SRB_FLAGS_DATA_OUT); +C_ASSERT(REQUEST_FLAG_NO_KEEP_AWAKE == SRB_FLAGS_NO_KEEP_AWAKE); + +typedef struct _ATAPORT_PORT_DATA +{ + ULONG PortFlags; +#define PORT_FLAG_IS_SIMPLEX 0x00000001 +#define PORT_FLAG_PIO_VIA_DMA 0x00000002 +#define PORT_FLAG_IS_EXTERNAL 0x00000004 +#define PORT_FLAG_NCQ 0x00000008 +#define PORT_FLAG_IS_AHCI 0x00000010 +#define PORT_FLAG_SYMLINK_CREATED 0x00000020 +#define PORT_FLAG_IO_TIMER_ACTIVE 0x00000040 +#define PORT_FLAG_CHANNEL_ATTACHED 0x00000080 +#define PORT_FLAG_PIO_ONLY 0x00000100 +#define PORT_FLAG_PIO_FOR_LBA48_XFER 0x00000200 +#define PORT_FLAG_EXIT_THREAD 0x80000000 + + PVOID ChannelContext; + PCHANNEL_ALLOCATE_SLOT AllocateSlot; + PCHANNEL_PREPARE_PRD_TABLE PreparePrdTable; + PCHANNEL_PREPARE_IO PrepareIo; + PCHANNEL_START_IO StartIo; + PDMA_ADAPTER DmaAdapter; + PDEVICE_OBJECT ChannelObject; + PATA_DEVICE_REQUEST Slots[MAX_SLOTS]; + LONG TimerCount[MAX_SLOTS]; + PKINTERRUPT InterruptObject; + ULONG ActiveSlotsBitmap; + volatile LONG InterruptFlags; +#define PORT_INT_FLAG_IS_IO_ACTIVE 0x00000001 +#define PORT_INT_FLAG_IGNORE_LINK_IRQ 0x00000002 + + KSPIN_LOCK QueueLock; + LIST_ENTRY PortQueueList; + ULONG ActiveTimersBitmap; + ULONG FreeSlotsBitmap; + ULONG LastUsedSlot; + ULONG QueueFlags; +#define PORT_QUEUE_FLAG_EXCLUSIVE_MODE 0x00000001 +#define PORT_QUEUE_FLAG_SIGNAL_STOP 0x00000002 + + /** + * <0: we have native queued commands pending. + * =0: the slot queue is empty. + * >0: we have non-queued commands pending. + */ + LONG AllocatedSlots; + + volatile LONG ReservedMappingLock; + PVOID ReservedVaSpace; + PCONTROLLER_OBJECT HwSyncObject; + PCHANNEL_ABORT_CHANNEL AbortChannel; + PCHANNEL_RESET_CHANNEL ResetChannel; + PCHANNEL_ENUMERATE_CHANNEL EnumerateChannel; + PCHANNEL_IDENTIFY_DEVICE IdentifyDevice; + PCHANNEL_SET_MODE SetTransferMode; + PCHANNEL_SET_DEVICE_DATA SetDeviceData; + PCHANNEL_GET_INIT_TASK_FILE GetInitTaskFile; + PCHANNEL_DOWNGRADE_INTERFACE_SPEED DowngradeInterfaceSpeed; + PCONTROLLER_ATTACH_CHANNEL AttachChannel; + KEVENT QueueStoppedEvent; + ULONG MaximumTransferLength; + ULONG MaximumPhysicalPages; + ULONG MaxTargetId; + ULONG QueueDepth; + ULONG PortNumber; + ULONG MaxSlotsBitmap; + PVOID LocalBuffer; + SCATTER_GATHER_LIST LocalSgList; + ATA_WORKER_CONTEXT Worker; +} ATAPORT_PORT_DATA, *PATAPORT_PORT_DATA; + +typedef struct _ATAPORT_COMMON_EXTENSION +{ + _Write_guarded_by_(_Global_interlock_) + volatile LONG PageFiles; + + _Write_guarded_by_(_Global_interlock_) + volatile LONG HibernateFiles; + + _Write_guarded_by_(_Global_interlock_) + volatile LONG DumpFiles; + + /** PNP device power state. */ + DEVICE_POWER_STATE DevicePowerState; + + /** PNP system power state. */ + SYSTEM_POWER_STATE SystemPowerState; + + union + { + /** Lower device object. This applies to FDO only. */ + PDEVICE_OBJECT LowerDeviceObject; + + /** Parent FDO. This applies to PDO only. */ + PVOID FdoExt; + }; + + ULONG Flags; +#define DO_IS_FDO 0x80000000 + + PDEVICE_OBJECT Self; + + IO_REMOVE_LOCK RemoveLock; +} ATAPORT_COMMON_EXTENSION, *PATAPORT_COMMON_EXTENSION; + +typedef struct _ATAPORT_CHANNEL_EXTENSION +{ + /** Common data, must be the first member. */ + ATAPORT_COMMON_EXTENSION Common; + + ATAPORT_PORT_DATA PortData; + + PDEVICE_OBJECT Pdo; + KSPIN_LOCK PdoListLock; + SINGLE_LIST_ENTRY PdoList; + ULONG DeviceObjectNumber; + ULONG ScsiPortNumber; + UNICODE_STRING StorageInterfaceName; +} ATAPORT_CHANNEL_EXTENSION, *PATAPORT_CHANNEL_EXTENSION; + +typedef struct _ATAPORT_DEVICE_EXTENSION +{ + /** Common data, must be the first member. */ + ATAPORT_COMMON_EXTENSION Common; + + ATAPORT_IO_CONTEXT Device; + ATA_WORKER_DEVICE_CONTEXT Worker; + + BOOLEAN ReportedMissing; + BOOLEAN RemovalPending; + BOOLEAN NotPresent; + BOOLEAN DeviceClaimed; + + /** PDO list entry. */ + SINGLE_LIST_ENTRY ListEntry; + + /** Power IRP queue list. */ + LIST_ENTRY PowerIrpQueueList; + + /** 512-byte block of device identify data. */ + union + { + IDENTIFY_DEVICE_DATA IdentifyDeviceData; + IDENTIFY_PACKET_DATA IdentifyPacketData; + }; + + /** Standard inquiry data. */ + INQUIRYDATA InquiryData; + + /** Device strings for PNP and IOCTL operations. */ + /*@{*/ + _Field_z_ DECLSPEC_ALIGN(2) CHAR FriendlyName[ATAPORT_FN_FIELD + sizeof(ANSI_NULL)]; + _Field_z_ DECLSPEC_ALIGN(2) CHAR RevisionNumber[ATAPORT_RN_FIELD + sizeof(ANSI_NULL)]; + _Field_z_ DECLSPEC_ALIGN(2) CHAR SerialNumber[ATAPORT_SN_FIELD + sizeof(ANSI_NULL)]; + /*@}*/ + + /** + * Current device type. + * @sa ATA_DEVICE_TYPE + * + * Passed to storprop.dll through registry (REG_DWORD). + * @sa DD_ATA_REG_ATA_DEVICE_TYPE + */ + ULONG DeviceType; + + /** + * Bit map for the user-specified transfer modes on the device. + * + * Maintained by storprop.dll and always retrieved from the registry by the driver (REG_DWORD). + * @sa DD_ATA_REG_XFER_MODE_ALLOWED + */ + ULONG TransferModeUserAllowedMask; + + /** + * Bit map for enabled/active transfer modes on the device. + * Holds the value based off of identify data. + */ + ULONG TransferModeCurrentBitmap; + + /** + * Bit map for supported transfer modes on the device. + * Holds the value based off of identify data. + * + * Passed to storprop.dll through registry (REG_DWORD). + * @sa DD_ATA_REG_XFER_MODE_SUPPORTED + */ + ULONG TransferModeSupportedBitmap; + + /** + * Selected transfer modes on the device as a bit map + * with 1 or 2 bits set: +1 for PIO mode (always set) and +1 for DMA mode (may be disabled). + * + * Passed to storprop.dll through registry (REG_DWORD). + * @sa DD_ATA_REG_XFER_MODE_SELECTED + */ + ULONG TransferModeSelectedBitmap; + + /** + * Bit map for allowed transfer modes on the device. + * DMA errors cause the upper bits to be cleared until PIO mode is reached. + */ + ULONG TransferModeAllowedMask; + + /** + * The minimum cycle time in nanoseconds that the device can support + * for PIO and DMA transfer modes. -1 if not supported. + * + * @sa IDE_ACPI_TIMING_MODE_NOT_SUPPORTED + */ + /*@{*/ + ULONG MinimumPioCycleTime; + ULONG MinimumSingleWordDmaCycleTime; + ULONG MinimumMultiWordDmaCycleTime; + ULONG MinimumUltraDmaCycleTime; + /*@}*/ + + /** List of ATA commands to the drive created by the ACPI firmware. */ + PVOID GtfDataBuffer; + + /** Tracks the last time a DMA error was encountered. */ + LARGE_INTEGER LastDmaErrorTime; + + /** WMI data provider interface. */ + WMILIB_CONTEXT WmiLibInfo; +} ATAPORT_DEVICE_EXTENSION, *PATAPORT_DEVICE_EXTENSION; + +C_ASSERT(QUEUE_FLAG_FROZEN_QUEUE_FREEZE == SRB_FLAGS_BYPASS_FROZEN_QUEUE); +C_ASSERT(QUEUE_FLAG_FROZEN_QUEUE_LOCK == SRB_FLAGS_BYPASS_LOCKED_QUEUE); + +extern UNICODE_STRING AtapDriverRegistryPath; +extern BOOLEAN AtapInPEMode; + +typedef enum +{ + GetDeviceType, + GetGenericType, + GetPeripheralId +} DEVICE_TYPE_NAME; + +FORCEINLINE +BOOLEAN +AtaPortQueueEmpty( + _In_ PATAPORT_PORT_DATA PortData) +{ + ULONG SlotsBitmap; + + SlotsBitmap = PortData->Worker.PausedSlotsBitmap | PortData->FreeSlotsBitmap; + + return (SlotsBitmap == PortData->MaxSlotsBitmap); +} + +FORCEINLINE +ATA_SCSI_ADDRESS +AtaMarshallScsiAddress( + _In_ ULONG PathId, + _In_ ULONG TargetId, + _In_ ULONG Lun) +{ + ATA_SCSI_ADDRESS AtaScsiAddress; + + AtaScsiAddress.Lun = Lun; + AtaScsiAddress.TargetId = TargetId; + AtaScsiAddress.PathId = PathId; + + /* This is used for address comparison. See AtaFdoFindNextDeviceByPath() */ + AtaScsiAddress.IsValid = 0xAA; + + return AtaScsiAddress; +} + +FORCEINLINE +BOOLEAN +IsPowerOfTwo( + _In_ ULONG x) +{ + /* Also exclude zero numbers */ + return (x != 0) && ((x & (x - 1)) == 0); +} + +FORCEINLINE +BOOLEAN +AtaPacketCommandUseDma( + _In_ UCHAR OpCode) +{ + return (OpCode == SCSIOP_READ6 || + OpCode == SCSIOP_WRITE6 || + OpCode == SCSIOP_READ || + OpCode == SCSIOP_WRITE || + OpCode == SCSIOP_READ12 || + OpCode == SCSIOP_WRITE12 || + OpCode == SCSIOP_READ16 || + OpCode == SCSIOP_WRITE16 || + OpCode == SCSIOP_READ_CD || + OpCode == SCSIOP_READ_CD_MSF); +} + +FORCEINLINE +BOOLEAN +AtaCommandUseLba48( + _In_ ULONG64 SectorNumber, + _In_ ULONG SectorCount) +{ + /* Use the 48-bit command when reasonable */ + return (((SectorNumber + SectorCount) >= ATA_MAX_LBA_28) || (SectorCount > 0x100)); +} + +/* atapi.c ********************************************************************/ + +_Dispatch_type_(IRP_MJ_CREATE) +_Dispatch_type_(IRP_MJ_CLOSE) +CODE_SEG("PAGE") +DRIVER_DISPATCH_PAGED AtaDispatchCreateClose; + +CODE_SEG("PAGE") +DRIVER_ADD_DEVICE AtaAddChannel; + +CODE_SEG("PAGE") +DRIVER_UNLOAD AtaUnload; + +CODE_SEG("INIT") +DRIVER_INITIALIZE DriverEntry; + +IO_WORKITEM_ROUTINE AtaStorageNotificationWorker; +KDEFERRED_ROUTINE AtaStorageNotificationlDpc; + +CODE_SEG("PAGE") +PCSTR +AtaTypeCodeToName( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ DEVICE_TYPE_NAME Type); + +CODE_SEG("PAGE") +NTSTATUS +AtaOpenRegistryKey( + _Out_ PHANDLE KeyHandle, + _In_ HANDLE RootKey, + _In_ PUNICODE_STRING KeyName, + _In_ BOOLEAN Create); + +CODE_SEG("PAGE") +VOID +AtaGetRegistryKey( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ UCHAR TargetId, + _In_ PCWSTR KeyName, + _Out_ PULONG KeyValue, + _In_ ULONG DefaultValue); + +CODE_SEG("PAGE") +VOID +AtaSetRegistryKey( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ UCHAR TargetId, + _In_ PCWSTR KeyName, + _In_ ULONG KeyValue); + +VOID +AtaSetPortRegistryKey( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PCWSTR KeyName, + _In_ ULONG KeyValue); + +CODE_SEG("PAGE") +NTSTATUS +AtaPnpQueryInterface( + _In_ PATAPORT_COMMON_EXTENSION CommonExt, + _In_ const GUID* Guid, + _Out_ PVOID Interface, + _In_ ULONG Version, + _In_ ULONG Size); + +CODE_SEG("PAGE") +NTSTATUS +AtaPnpRepeatRequest( + _In_ PATAPORT_COMMON_EXTENSION CommonExt, + _In_ PIRP Irp, + _In_opt_ PDEVICE_CAPABILITIES DeviceCapabilities); + +CODE_SEG("PAGE") +NTSTATUS +AtaPnpQueryDeviceUsageNotification( + _In_ PATAPORT_COMMON_EXTENSION CommonExt, + _In_ PIRP Irp); + +CODE_SEG("PAGE") +NTSTATUS +AtaPnpQueryPnpDeviceState( + _In_ PATAPORT_COMMON_EXTENSION CommonExt, + _In_ PIRP Irp); + +CODE_SEG("PAGE") +VOID +AtaPnpInitializeCommonExtension( + _In_ PATAPORT_COMMON_EXTENSION CommonExt, + _In_ PDEVICE_OBJECT SelfDeviceObject, + _In_ ULONG Flags); + +/* dev_config.c ***************************************************************/ + +NTSTATUS +AtaPortDeviceProcessConfig( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt); + +/* dev_error.c ****************************************************************/ + +NTSTATUS +AtaPortDeviceProcessError( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt); + +/* dev_identify.c *************************************************************/ + +NTSTATUS +AtaDeviceSendIdentify( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ UCHAR Command); + +ATA_DEVICE_STATUS +AtaPortIdentifyDevice( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt); + +/* dev_power.c ****************************************************************/ + +_Dispatch_type_(IRP_MJ_POWER) +DRIVER_DISPATCH_RAISED AtaDispatchPower; + +VOID +AtaDeviceFlushPowerIrpQueue( + _In_ PATAPORT_DEVICE_EXTENSION DevExt); + +UCHAR +AtaDeviceGetFlushCacheCommand( + _In_ PATAPORT_DEVICE_EXTENSION DevExt); + +NTSTATUS +AtaPortDeviceProcessPowerChange( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt); + +NTSTATUS +AtaPortCheckDevicePowerState( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt); + +/* dev_timings.c **************************************************************/ + +VOID +AtaPortSelectTimings( + _In_ PATAPORT_PORT_DATA PortData, + _In_ BOOLEAN ForceCompatibleTimings); + +/* enum.c *********************************************************************/ + +PUCHAR +AtaCopyIdStringUnsafe( + _Out_writes_bytes_all_(Length) PUCHAR Destination, + _In_reads_bytes_(Length) PUCHAR Source, + _In_ ULONG Length); + +PCHAR +AtaCopyIdStringSafe( + _Out_writes_bytes_all_(MaxLength) PCHAR Destination, + _In_reads_bytes_(MaxLength) PUCHAR Source, + _In_ ULONG MaxLength, + _In_ CHAR DefaultCharacter); + +VOID +AtaSwapIdString( + _Inout_updates_bytes_(WordCount * sizeof(USHORT)) PVOID Buffer, + _In_range_(>, 0) ULONG WordCount); + +VOID +AtaDeviceSetAddressingMode( + _In_ PATAPORT_DEVICE_EXTENSION DevExt); + +CODE_SEG("PAGE") +NTSTATUS +AtaFdoQueryBusRelations( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PIRP Irp); + +/* fdo.c **********************************************************************/ + +CODE_SEG("PAGE") +NTSTATUS +AtaFdoStartDevice( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PCM_RESOURCE_LIST ResourcesTranslated); + +CODE_SEG("PAGE") +NTSTATUS +AtaFdoPnp( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _Inout_ PIRP Irp); + +DECLSPEC_NOINLINE_FROM_PAGED +PATAPORT_DEVICE_EXTENSION +AtaFdoFindDeviceByPath( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ ATA_SCSI_ADDRESS AtaScsiAddress, + _In_ PVOID ReferenceTag); + +DECLSPEC_NOINLINE_FROM_PAGED +PATAPORT_DEVICE_EXTENSION +AtaFdoFindNextDeviceByPath( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _Inout_ PATA_SCSI_ADDRESS AtaScsiAddress, + _In_ BOOLEAN SearchRemoveDev, + _In_ PVOID ReferenceTag); + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaFdoDeviceListInsert( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ BOOLEAN DoInsert); + +/* ioctl.c ********************************************************************/ + +_Dispatch_type_(IRP_MJ_DEVICE_CONTROL) +DRIVER_DISPATCH_RAISED AtaDispatchDeviceControl; + +/* pdo.c **********************************************************************/ + +IO_COMPLETION_ROUTINE AtaPdoCompletionRoutine; + +_Dispatch_type_(IRP_MJ_PNP) +CODE_SEG("PAGE") +DRIVER_DISPATCH_PAGED AtaDispatchPnp; + +CODE_SEG("PAGE") +VOID +AtaPdoFreeDevice( + _In_ PATAPORT_DEVICE_EXTENSION DevExt); + +CODE_SEG("PAGE") +PATAPORT_DEVICE_EXTENSION +AtaPdoCreateDevice( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ ATA_SCSI_ADDRESS AtaScsiAddress); + +/* portstate.c ****************************************************************/ + +KSTART_ROUTINE AtaPortWorkerThread; +KDEFERRED_ROUTINE AtaPortWorkerSignalDpc; +REQUEST_COMPLETION_ROUTINE AtaPortCompleteInternalRequest; +PORT_NOTIFICATION AtaPortNotification; + +NTSTATUS +AtaPortSendRequest( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt); + +VOID +AtaPortTimeout( + _In_ PATAPORT_PORT_DATA PortData, + _In_ ULONG Slot); + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaPortSignalWorkerThread( + _In_ PATAPORT_PORT_DATA PortData); + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaDeviceQueueEvent( + _In_ PATAPORT_PORT_DATA PortData, + _In_opt_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ATA_PORT_ACTION Action); + +/* satl.c *********************************************************************/ + +BOOLEAN +AtaReqDmaTransferToPioTransfer( + _In_ PATA_DEVICE_REQUEST Request); + +CODE_SEG("PAGE") +VOID +AtaCreateStandardInquiryData( + _In_ PATAPORT_DEVICE_EXTENSION DevExt); + +UCHAR +AtaReqExecuteScsi( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb); + +UCHAR +AtaReqSetFixedAtaSenseData( + _In_ PATA_DEVICE_REQUEST Request); + +VOID +AtaReqBuildReadLogTaskFile( + _In_ PATA_DEVICE_REQUEST Request, + _In_ UCHAR LogAddress, + _In_ UCHAR PageNumber, + _In_ USHORT LogPageCount); + +/* scsi.c *********************************************************************/ + +_Dispatch_type_(IRP_MJ_SCSI) +DRIVER_DISPATCH_RAISED AtaDispatchScsi; + +IO_TIMER_ROUTINE AtaPortIoTimer; +KDEFERRED_ROUTINE AtaReqCompletionDpc; +extern SLIST_HEADER AtapCompletionQueueList; +extern KDPC AtapCompletionDpc; + +VOID +AtaReqCompleteFailedRequest( + _In_ PATA_DEVICE_REQUEST Request); + +UCHAR +AtaReqSetFixedSenseData( + _In_ PSCSI_REQUEST_BLOCK Srb, + _In_ SCSI_SENSE_CODE SenseCode); + +VOID +AtaReqSetLbaInformation( + _In_ PSCSI_REQUEST_BLOCK Srb, + _In_ ULONG64 Lba); + +BOOLEAN +AtaReqAllocateMdl( + _In_ PATA_DEVICE_REQUEST Request); + +VOID +AtaReqSendRequest( + _In_ PATA_DEVICE_REQUEST Request); + +VOID +AtaReqStartCompletionDpc( + _In_ PATA_DEVICE_REQUEST Request); + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaReqFreezeQueue( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ULONG ReasonFlags); + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaReqThawQueue( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ULONG ReasonFlags); + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaReqWaitForOutstandingIoToComplete( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PSCSI_REQUEST_BLOCK Srb); + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaReqFlushDeviceQueue( + _In_ PATAPORT_IO_CONTEXT Device); + +/* smart.c ********************************************************************/ + +UCHAR +AtaReqSmartIoControl( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb); + +CODE_SEG("PAGE") +NTSTATUS +AtaPdoHandleMiniportSmartVersion( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PSCSI_REQUEST_BLOCK Srb); + +CODE_SEG("PAGE") +NTSTATUS +AtaPdoHandleMiniportIdentify( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PSCSI_REQUEST_BLOCK Srb); + +/* wmi.c **********************************************************************/ + +_Dispatch_type_(IRP_MJ_SYSTEM_CONTROL) +CODE_SEG("PAGE") +DRIVER_DISPATCH_PAGED AtaDispatchWmi; + +CODE_SEG("PAGE") +NTSTATUS +AtaPdoWmiRegistration( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ BOOLEAN Register); diff --git a/drivers/storage/ide/atapi/atapi.rc b/drivers/storage/ide/atapi/atapi.rc new file mode 100644 index 00000000000..1b6143a87d8 --- /dev/null +++ b/drivers/storage/ide/atapi/atapi.rc @@ -0,0 +1,5 @@ +#define REACTOS_VERSION_DLL +#define REACTOS_STR_FILE_DESCRIPTION "ATA Port Driver" +#define REACTOS_STR_INTERNAL_NAME "atapi" +#define REACTOS_STR_ORIGINAL_FILENAME "atapi.sys" +#include diff --git a/drivers/storage/ide/atapi/atapi_reg.inf b/drivers/storage/ide/atapi/atapi_reg.inf new file mode 100644 index 00000000000..8e3ece55f1c --- /dev/null +++ b/drivers/storage/ide/atapi/atapi_reg.inf @@ -0,0 +1,8 @@ +; ATA Port Driver +[AddReg] +HKLM,"SYSTEM\CurrentControlSet\Services\atapi","ErrorControl",0x00010001,0x00000003 +HKLM,"SYSTEM\CurrentControlSet\Services\atapi","Group",0x00000000,"SCSI Miniport" +HKLM,"SYSTEM\CurrentControlSet\Services\atapi","ImagePath",0x00020000,"system32\drivers\atapi.sys" +HKLM,"SYSTEM\CurrentControlSet\Services\atapi","Start",0x00010001,0x00000000 +HKLM,"SYSTEM\CurrentControlSet\Services\atapi","Type",0x00010001,0x00000001 +HKLM,"SYSTEM\CurrentControlSet\Services\atapi","Tag",0x00010001,0x00000021 diff --git a/drivers/storage/ide/atapi/debug.h b/drivers/storage/ide/atapi/debug.h new file mode 100644 index 00000000000..3468bd9dc5b --- /dev/null +++ b/drivers/storage/ide/atapi/debug.h @@ -0,0 +1,95 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Debug support header file + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +#pragma once + +#ifndef __RELFILE__ +#define __RELFILE__ __FILE__ +#endif + +#if DBG + +// #define DEBUG_TRACE +// #define DEBUG_INFO +#define DEBUG_WARN +#define DEBUG_ERR + +#ifdef DEBUG_TRACE +#define TRACE(fmt, ...) \ + do { \ + if (DbgPrint("(%s:%d) %s " fmt, __RELFILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__)) \ + DbgPrint("(%s:%d) DbgPrint() failed!\n", __RELFILE__, __LINE__); \ + } while (0) + +#else +#if defined(_MSC_VER) +#define TRACE __noop +#else +#define TRACE(...) do { if(0) { DbgPrint(__VA_ARGS__); } } while(0) +#endif +#endif + +#ifdef DEBUG_INFO +#define INFO(fmt, ...) \ + do { \ + if (DbgPrint("(%s:%d) %s " fmt, __RELFILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__)) \ + DbgPrint("(%s:%d) DbgPrint() failed!\n", __RELFILE__, __LINE__); \ + } while (0) + +#else +#if defined(_MSC_VER) +#define INFO __noop +#else +#define INFO(...) do { if(0) { DbgPrint(__VA_ARGS__); } } while(0) +#endif +#endif + +#ifdef DEBUG_WARN +#define WARN(fmt, ...) \ + do { \ + if (DbgPrint("(%s:%d) %s " fmt, __RELFILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__)) \ + DbgPrint("(%s:%d) DbgPrint() failed!\n", __RELFILE__, __LINE__); \ + } while (0) + +#else +#if defined(_MSC_VER) +#define WARN __noop +#else +#define WARN(...) do { if(0) { DbgPrint(__VA_ARGS__); } } while(0) +#endif +#endif + +#ifdef DEBUG_ERR +#define ERR(fmt, ...) \ + do { \ + if (DbgPrint("(%s:%d) %s " fmt, __RELFILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__)) \ + DbgPrint("(%s:%d) DbgPrint() failed!\n", __RELFILE__, __LINE__); \ + } while (0) + +#else +#if defined(_MSC_VER) +#define ERR __noop +#else +#define ERR(...) do { if(0) { DbgPrint(__VA_ARGS__); } } while(0) +#endif +#endif + +#else + +#if defined(_MSC_VER) +#define TRACE __noop +#define INFO __noop +#define WARN __noop +#define ERR __noop +#else +#define TRACE(...) do { if(0) { DbgPrint(__VA_ARGS__); } } while(0) +#define INFO(...) do { if(0) { DbgPrint(__VA_ARGS__); } } while(0) +#define WARN(...) do { if(0) { DbgPrint(__VA_ARGS__); } } while(0) +#define ERR(...) do { if(0) { DbgPrint(__VA_ARGS__); } } while(0) +#endif + +#endif diff --git a/drivers/storage/ide/atapi/dev_config.c b/drivers/storage/ide/atapi/dev_config.c new file mode 100644 index 00000000000..646de2e0f77 --- /dev/null +++ b/drivers/storage/ide/atapi/dev_config.c @@ -0,0 +1,396 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Device initialization and configuration code + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +#include + +/* FUNCTIONS ******************************************************************/ + +static +NTSTATUS +AtaDeviceSetGeometry( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + NTSTATUS Status; + + if (IS_ATAPI(&DevExt->Device) || (DevExt->Device.DeviceFlags & DEVICE_LBA_MODE)) + return STATUS_SUCCESS; + + Request->Flags = REQUEST_FLAG_SET_DEVICE_REGISTER; + Request->TimeOut = 5; + + RtlZeroMemory(&Request->TaskFile, sizeof(Request->TaskFile)); + Request->TaskFile.Command = IDE_COMMAND_SET_DRIVE_PARAMETERS; + Request->TaskFile.SectorCount = (UCHAR)DevExt->Device.SectorsPerTrack; + Request->TaskFile.DriveSelect = (UCHAR)(DevExt->Device.Heads - 1) | + DevExt->Device.DeviceSelect; + + Status = AtaPortSendRequest(PortData, DevExt); + if (!NT_SUCCESS(Status)) + { + WARN("CH %lu: Failed to set geometry for '%s'\n", + PortData->PortNumber, + DevExt->TransferModeSelectedBitmap, + DevExt->FriendlyName); + } + + return Status; +} + +static +VOID +AtaDeviceBuildSetTransferModeTaskFile( + _In_ PATA_DEVICE_REQUEST Request, + _In_ UCHAR Mode) +{ + Request->Flags = 0; + Request->TimeOut = 3; + + RtlZeroMemory(&Request->TaskFile, sizeof(Request->TaskFile)); + Request->TaskFile.Command = IDE_COMMAND_SET_FEATURE; + Request->TaskFile.Feature = IDE_FEATURE_SET_TRANSFER_MODE; + Request->TaskFile.SectorCount = Mode; +} + +static +NTSTATUS +AtaDeviceSetPioTransferMode( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + NTSTATUS Status; + ULONG Mode; + + NT_VERIFY(_BitScanReverse(&Mode, DevExt->TransferModeSelectedBitmap & PIO_ALL) != 0); + + Mode = IDE_SET_ADVANCE_PIO_MODE(Mode); + AtaDeviceBuildSetTransferModeTaskFile(Request, Mode); + + Status = AtaPortSendRequest(PortData, DevExt); + if (!NT_SUCCESS(Status)) + { + WARN("CH %lu: Failed to set %lu PIO settings for '%s'\n", + PortData->PortNumber, + Mode, + DevExt->FriendlyName); + } + + return Status; +} + +static +NTSTATUS +AtaDeviceSetDmaTransferMode( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + NTSTATUS Status; + ULONG Mode; + + if (!_BitScanReverse(&Mode, DevExt->TransferModeSelectedBitmap & ~PIO_ALL)) + return STATUS_SUCCESS; + + if (Mode >= UDMA_MODE(0)) + Mode = IDE_SET_UDMA_MODE(Mode - UDMA_MODE(0)); + else if (Mode >= MWDMA_MODE(0)) + Mode = IDE_SET_MWDMA_MODE(Mode - MWDMA_MODE(0)); + else + Mode = IDE_SET_SWDMA_MODE(Mode - SWDMA_MODE(0)); + AtaDeviceBuildSetTransferModeTaskFile(Request, Mode); + + Status = AtaPortSendRequest(PortData, DevExt); + if (NT_SUCCESS(Status)) + { + DevExt->Device.DeviceFlags &= ~DEVICE_PIO_ONLY; + } + else + { + WARN("CH %lu: Failed to set 0x%lx DMA settings for '%s'\n", + PortData->PortNumber, + Mode, + DevExt->FriendlyName); + + DevExt->Device.DeviceFlags &= ~DEVICE_NCQ; + DevExt->TransferModeSelectedBitmap &= PIO_ALL; + } + + return Status; +} + +static +NTSTATUS +AtaDeviceSetMultipleMode( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + NTSTATUS Status; + + if (IS_ATAPI(&DevExt->Device)) + return STATUS_SUCCESS; + + DevExt->Device.MultiSectorCount = AtaDevMaximumSectorsPerDrq(&DevExt->IdentifyDeviceData); + if (DevExt->Device.MultiSectorCount == 0) + return STATUS_SUCCESS; + + Request->Flags = 0; + Request->TimeOut = 3; + + RtlZeroMemory(&Request->TaskFile, sizeof(Request->TaskFile)); + Request->TaskFile.Command = IDE_COMMAND_SET_MULTIPLE; + Request->TaskFile.SectorCount = DevExt->Device.MultiSectorCount; + + Status = AtaPortSendRequest(PortData, DevExt); + if (!NT_SUCCESS(Status)) + { + WARN("CH %lu: Failed to set %u multiple mode for '%s'\n", + PortData->PortNumber, + DevExt->Device.MultiSectorCount, + DevExt->FriendlyName); + + DevExt->Device.MultiSectorCount = 0; + } + + return Status; +} + +static +BOOLEAN +AtaDeviceFilterAcpiTaskFile( + _In_ PATA_ACPI_TASK_FILE AcpiTaskFile) +{ + switch (AcpiTaskFile->Command) + { + /* These features are managed by the driver */ + case IDE_COMMAND_SET_FEATURE: + { + if (AcpiTaskFile->Feature == IDE_FEATURE_SET_TRANSFER_MODE) + return FALSE; + break; + } + case IDE_COMMAND_SET_DRIVE_PARAMETERS: + case IDE_COMMAND_SET_MULTIPLE: + return FALSE; + + // TODO: Anything else to check? + default: + break; + } + + return TRUE; +} + +static +VOID +AtaDeviceAcpiTaskFileToInternalTaskFile( + _In_ ATA_ACPI_TASK_FILE* __restrict AcpiTaskFile, + _Out_ ATA_DEVICE_REQUEST* __restrict Request) +{ + Request->Flags = REQUEST_FLAG_SET_DEVICE_REGISTER; + Request->TimeOut = 6; + + Request->TaskFile.Feature = AcpiTaskFile->Feature; + Request->TaskFile.SectorCount = AcpiTaskFile->SectorCount; + Request->TaskFile.LowLba = AcpiTaskFile->LowLba; + Request->TaskFile.MidLba = AcpiTaskFile->MidLba; + Request->TaskFile.HighLba = AcpiTaskFile->HighLba; + Request->TaskFile.DriveSelect = AcpiTaskFile->DriveSelect; + Request->TaskFile.Command = AcpiTaskFile->Command; +} + +static +NTSTATUS +AtaDeviceExecuteAcpiTaskFile( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PACPI_EVAL_OUTPUT_BUFFER GtfDataBuffer = DevExt->GtfDataBuffer; + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + PATA_ACPI_TASK_FILE AcpiTaskFile; + NTSTATUS Status; + ULONG i; + + if (!GtfDataBuffer) + return STATUS_SUCCESS; + + ASSERT(GtfDataBuffer->Signature == ACPI_EVAL_OUTPUT_BUFFER_SIGNATURE); + + AcpiTaskFile = (PATA_ACPI_TASK_FILE)GtfDataBuffer->Argument[0].Data; + + for (i = 0; i < GtfDataBuffer->Argument[0].DataLength / sizeof(*AcpiTaskFile); ++i) + { + PCSTR Result; + + if (!AtaDeviceFilterAcpiTaskFile(AcpiTaskFile)) + { + Result = "filtered out"; + } + else + { + AtaDeviceAcpiTaskFileToInternalTaskFile(AcpiTaskFile, Request); + + Status = AtaPortSendRequest(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + Result = NT_SUCCESS(Status) ? "completed" : "aborted"; + } + INFO("CH %lu: GTF[%lu]: %02x:%02x:%02x:%02x:%02x:%02x:%02x -- %s\n", + PortData->PortNumber, + i, + AcpiTaskFile->Feature, + AcpiTaskFile->SectorCount, + AcpiTaskFile->LowLba, + AcpiTaskFile->MidLba, + AcpiTaskFile->HighLba, + AcpiTaskFile->DriveSelect, + AcpiTaskFile->Command, + Result); + + ++AcpiTaskFile; + } + + return STATUS_SUCCESS; +} + +/* + * See MSDN note: + * https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/security-group-commands + */ +static +NTSTATUS +AtaDeviceLockSecurityModeFeatureCommands( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + + if (IS_ATAPI(&DevExt->Device) || + AtapInPEMode || + !AtaDevHasSecurityModeFeature(&DevExt->IdentifyDeviceData)) + { + return STATUS_SUCCESS; + } + + Request->Flags = 0; + Request->TimeOut = 3; + + RtlZeroMemory(&Request->TaskFile, sizeof(Request->TaskFile)); + Request->TaskFile.Command = IDE_COMMAND_SECURITY_FREEZE_LOCK; + + return AtaPortSendRequest(PortData, DevExt); +} + +static +NTSTATUS +AtaDeviceLockDeviceParameters( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + + Request->Flags = 0; + Request->TimeOut = 3; + + RtlZeroMemory(&Request->TaskFile, sizeof(Request->TaskFile)); + Request->TaskFile.Command = IDE_COMMAND_SET_FEATURE; + Request->TaskFile.Feature = IDE_FEATURE_DISABLE_REVERT_TO_POWER_ON; + + return AtaPortSendRequest(PortData, DevExt); +} + +static +NTSTATUS +AtaDeviceEnableMsnFeature( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + NTSTATUS Status; + + if (!AtaDevHasRemovableMediaStatusNotification(&DevExt->IdentifyDeviceData)) + return STATUS_SUCCESS; + + Request->Flags = 0; + Request->TimeOut = 3; + + RtlZeroMemory(&Request->TaskFile, sizeof(Request->TaskFile)); + Request->TaskFile.Command = IDE_COMMAND_SET_FEATURE; + Request->TaskFile.Feature = IDE_FEATURE_ENABLE_MSN; + + Status = AtaPortSendRequest(PortData, DevExt); + if (NT_SUCCESS(Status)) + { + DevExt->Device.DeviceFlags |= DEVICE_HAS_MEDIA_STATUS; + } + + return Status; +} + +NTSTATUS +AtaPortDeviceProcessConfig( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + NTSTATUS Status; + + DevExt->Device.DeviceFlags |= DEVICE_PIO_ONLY; + DevExt->Device.DeviceFlags &= ~(DEVICE_HAS_MEDIA_STATUS | DEVICE_SENSE_DATA_REPORTING); + DevExt->Device.MultiSectorCount = 0; + + Status = AtaDeviceSetGeometry(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + Status = AtaDeviceSetPioTransferMode(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + Status = AtaDeviceSetDmaTransferMode(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + Status = AtaDeviceSetMultipleMode(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + Status = AtaDeviceExecuteAcpiTaskFile(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + Status = AtaDeviceLockSecurityModeFeatureCommands(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + Status = AtaDeviceLockDeviceParameters(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + Status = AtaDeviceEnableMsnFeature(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + /* Identify data might have changed during the configuration, so update it here */ + Status = AtaDeviceSendIdentify(PortData, + DevExt, + IS_ATAPI(&DevExt->Device) ? + IDE_COMMAND_ATAPI_IDENTIFY : IDE_COMMAND_IDENTIFY); + if (NT_SUCCESS(Status)) + { + RtlCopyMemory(&DevExt->IdentifyDeviceData, + DevExt->Device.LocalBuffer, + sizeof(DevExt->IdentifyDeviceData)); + } + + return Status; +} diff --git a/drivers/storage/ide/atapi/dev_error.c b/drivers/storage/ide/atapi/dev_error.c new file mode 100644 index 00000000000..b9cf5b64a09 --- /dev/null +++ b/drivers/storage/ide/atapi/dev_error.c @@ -0,0 +1,657 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Device I/O error handling + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* FUNCTIONS ******************************************************************/ + +static +BOOLEAN +AtaDeviceRequestSenseNeeded( + _In_ PATA_DEVICE_REQUEST Request) +{ + return ((Request->Flags & REQUEST_FLAG_PACKET_COMMAND) && + !(Request->Srb->SrbFlags & SRB_FLAGS_DISABLE_AUTOSENSE) && + (Request->Srb->SenseInfoBuffer != NULL) && + (Request->Srb->SenseInfoBufferLength != 0)); +} + +static +BOOLEAN +AtaDeviceRequestSenseNeededExt( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request) +{ + /* NOTE: The ERROR bit and the SENSE DATA AVAILABLE bit may both be set to one */ + return ((DevExt->Device.DeviceFlags & DEVICE_SENSE_DATA_REPORTING) && + (Request->Output.Status & IDE_STATUS_INDEX) && + !(Request->Srb->SrbFlags & SRB_FLAGS_DISABLE_AUTOSENSE) && + (Request->Srb->SenseInfoBuffer != NULL) && + (Request->Srb->SenseInfoBufferLength != 0)); +} + +static +BOOLEAN +AtaDeviceFixedErrorNeeded( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request) +{ + return (!(Request->Flags & REQUEST_FLAG_PACKET_COMMAND) && + !(Request->Srb->SrbFlags & SRB_FLAGS_DISABLE_AUTOSENSE) && + (Request->Srb->SenseInfoBuffer != NULL) && + (Request->Srb->SenseInfoBufferLength != 0)); +} + +static +BOOLEAN +AtaDeviceIsDmaCrcError( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request) +{ + // TODO: Check also for SATA SErr or AHCI interrupt status bits + return (Request->Output.Error & IDE_ERROR_CRC_ERROR) && + (Request->Flags & REQUEST_FLAG_DMA); +} + +static +VOID +AtaDeviceHandleRequestSense( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + PATA_DEVICE_REQUEST FailedRequest = PortData->Worker.FailedRequest; + PSCSI_REQUEST_BLOCK Srb = FailedRequest->Srb; + PSENSE_DATA SenseData; + BOOLEAN Success; + + if (IS_ATAPI(&DevExt->Device)) + { + Success = (Request->SrbStatus == SRB_STATUS_SUCCESS) || + (Request->SrbStatus == SRB_STATUS_DATA_OVERRUN); + } + else + { + Success = (Request->SrbStatus == SRB_STATUS_SUCCESS) && + (Request->Flags & REQUEST_FLAG_HAS_TASK_FILE) && + (Request->TaskFile.HighLba != 0); + } + + if (!Success) + { + ERR("Request sense failed\n"); + + if (IS_ATAPI(&DevExt->Device)) + { + Srb->ScsiStatus = SCSISTAT_GOOD; + FailedRequest->SrbStatus = SRB_STATUS_REQUEST_SENSE_FAILED; + } + else + { + /* Request sense failed, translate the original ATA device error */ + FailedRequest->SrbStatus = AtaReqSetFixedAtaSenseData(FailedRequest); + } + + return; + } + + SenseData = Srb->SenseInfoBuffer; + + if (IS_ATAPI(&DevExt->Device)) + { + /* Copy the sense data from the local buffer */ + RtlCopyMemory(Srb->SenseInfoBuffer, + DevExt->Device.LocalBuffer, + min(Srb->SenseInfoBufferLength, ATA_LOCAL_BUFFER_SIZE)); + + Srb->ScsiStatus = SCSISTAT_CHECK_CONDITION; + FailedRequest->SrbStatus = SRB_STATUS_ERROR | SRB_STATUS_AUTOSENSE_VALID; + } + else + { + SCSI_SENSE_CODE SenseCode; + + /* Copy the sense code from the task file registers */ + SenseCode.SrbStatus = SRB_STATUS_ERROR; + SenseCode.SenseKey = Request->TaskFile.HighLba; + SenseCode.AdditionalSenseCode = Request->TaskFile.MidLba; + SenseCode.AdditionalSenseCodeQualifier = Request->TaskFile.LowLba; + FailedRequest->SrbStatus = AtaReqSetFixedSenseData(Srb, SenseCode); + } + + if (RTL_CONTAINS_FIELD(SenseData, + Srb->SenseInfoBufferLength, + AdditionalSenseCodeQualifier)) + { + /* INFO("0x%02X: SK 0x%02X, ASC 0x%02X, ASCQ 0x%02X\n", */ + /* Srb->Cdb[0], */ + /* SenseData->SenseKey, */ + /* SenseData->AdditionalSenseCode, */ + /* SenseData->AdditionalSenseCodeQualifier); */ + } +} + +static +NTSTATUS +AtaDeviceSendRequestSense( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + PATA_DEVICE_REQUEST FailedRequest = PortData->Worker.FailedRequest; + NTSTATUS Status; + PCDB Cdb; + + ASSERT_REQUEST(FailedRequest); + ASSERT(FailedRequest->Srb->SenseInfoBufferLength > 0); + + Request->Flags = REQUEST_FLAG_DATA_IN | + REQUEST_FLAG_PACKET_COMMAND | + REQUEST_FLAG_HAS_LOCAL_BUFFER; + Request->TimeOut = 3; + Request->DataTransferLength = FailedRequest->Srb->SenseInfoBufferLength; + + Cdb = (PCDB)Request->Cdb; + Cdb->CDB6INQUIRY.OperationCode = SCSIOP_REQUEST_SENSE; + Cdb->CDB6INQUIRY.LogicalUnitNumber = 0; + Cdb->CDB6INQUIRY.Reserved1 = 0; + Cdb->CDB6INQUIRY.PageCode = 0; + Cdb->CDB6INQUIRY.IReserved = 0; + Cdb->CDB6INQUIRY.AllocationLength = (UCHAR)Request->DataTransferLength; + Cdb->CDB6INQUIRY.Control = 0; + + Status = AtaPortSendRequest(PortData, DevExt); + + AtaDeviceHandleRequestSense(PortData, DevExt); + return Status; +} + +static +NTSTATUS +AtaDeviceSendRequestSenseExt( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + NTSTATUS Status; + + Request->Flags = REQUEST_FLAG_SAVE_TASK_FILE | REQUEST_FLAG_LBA48; + Request->TimeOut = 3; + + RtlZeroMemory(&Request->TaskFile, sizeof(Request->TaskFile)); + Request->TaskFile.Command = IDE_COMMAND_REQUEST_SENSE_DATA_EXT; + + Status = AtaPortSendRequest(PortData, DevExt); + + AtaDeviceHandleRequestSense(PortData, DevExt); + return Status; +} + +static +NTSTATUS +AtaDeviceSendReadNcqCommandErrorLog( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + + AtaReqBuildReadLogTaskFile(Request, + IDE_GP_LOG_NCQ_COMMAND_ERROR_ADDRESS, + 0, + 1); + Request->Flags |= REQUEST_FLAG_HAS_LOCAL_BUFFER; + Request->TimeOut = 3; + + return AtaPortSendRequest(PortData, DevExt); +} + +static +VOID +AtaDeviceSaveLogPageTaskFile( + _In_ GP_LOG_NCQ_COMMAND_ERROR* __restrict LogPage, + _Inout_ ATA_DEVICE_REQUEST* __restrict Request) +{ + PATA_TASKFILE TaskFile = &Request->Output; + + TaskFile->SectorCount = LogPage->Count7_0; + TaskFile->LowLba = LogPage->LBA7_0; + TaskFile->MidLba = LogPage->LBA15_8; + TaskFile->HighLba = LogPage->LBA23_16; + TaskFile->DriveSelect = LogPage->Device; + + if (Request->Flags & REQUEST_FLAG_LBA48) + { + TaskFile->FeatureEx = 0; // Reserved + TaskFile->SectorCountEx = LogPage->Count15_8; + TaskFile->LowLbaEx = LogPage->LBA31_24; + TaskFile->MidLbaEx = LogPage->LBA39_32; + TaskFile->HighLbaEx = LogPage->LBA47_40; + } +} + +static +VOID +AtaDeviceLogEvent( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ATA_ERROR_LOG_VALUE ErrorValue) +{ + PATAPORT_CHANNEL_EXTENSION ChanExt = DevExt->Common.FdoExt; + ULONG ErrorCode, FinalStatus; + PIO_ERROR_LOG_PACKET LogEntry; + UCHAR Size; + + Size = FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData); + LogEntry = IoAllocateErrorLogEntry(ChanExt->Common.Self, Size); + if (!LogEntry) + return; + + switch (ErrorValue) + { + case EVENT_CODE_TIMEOUT: + ErrorCode = IO_ERR_TIMEOUT; + FinalStatus = STATUS_IO_TIMEOUT; + break; + case EVENT_CODE_CRC_ERROR: + ErrorCode = IO_ERR_PARITY; + FinalStatus = STATUS_IO_DEVICE_ERROR; + break; + + default: + ErrorCode = IO_ERR_CONTROLLER_ERROR; + FinalStatus = STATUS_IO_DEVICE_ERROR; + break; + } + + RtlZeroMemory(LogEntry, Size); + LogEntry->FinalStatus = FinalStatus; + LogEntry->ErrorCode = ErrorCode; + LogEntry->MajorFunctionCode = IRP_MJ_SCSI; + LogEntry->UniqueErrorValue = ErrorValue; + + IoWriteErrorLogEntry(LogEntry); +} + +static +VOID +AtaDeviceDowngradeTransferSpeed( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + ULONG Mode, AllowedModesMask; + + /* Already in PIO mode */ + if (!_BitScanReverse(&Mode, DevExt->TransferModeSelectedBitmap & ~PIO_ALL)) + return; + + /* Clear the current mode and the upper bits */ + AllowedModesMask = ((1 << Mode) - 1); + + if ((DevExt->TransferModeSupportedBitmap & UDMA_ALL) && !(AllowedModesMask & UDMA_ALL)) + { + /* + * It makes no sense to disable DMA for AHCI hard drives + * because that would otherwise cause READ/WRITE commands to fail. + */ + if ((PortData->PortFlags & PORT_FLAG_PIO_VIA_DMA) && !IS_ATAPI(&DevExt->Device)) + return; + + /* From UDMA to PIO (intentionally skip MWDMA and SWDMA) */ + AllowedModesMask &= PIO_ALL; + } + else if (((DevExt->TransferModeSupportedBitmap & MWDMA_ALL) && + !(AllowedModesMask & MWDMA_ALL)) || + ((DevExt->TransferModeSupportedBitmap & SWDMA_ALL) && + !(AllowedModesMask & SWDMA_ALL))) + { + /* From MWDMA to PIO (intentionally skip SWDMA) or from SWDMA to PIO */ + AllowedModesMask &= PIO_ALL; + } + + /* We are about to disable DMA, log the change */ + if ((DevExt->TransferModeSupportedBitmap & ~PIO_ALL) && !(AllowedModesMask & ~PIO_ALL)) + { + WARN("Too many DMA failures, disabling DMA for '%s'\n", DevExt->FriendlyName); + + AtaDeviceLogEvent(DevExt, EVENT_CODE_DMA_DISABLE); + } + + WARN("Downgrading DMA speed from %lu for '%s'\n", Mode, DevExt->FriendlyName); + + DevExt->TransferModeAllowedMask &= AllowedModesMask; + + /* Program the new timings */ + _InterlockedOr(&PortData->Worker.EventsPending, ACTION_PORT_TIMING | ACTION_DEVICE_CONFIG); + _InterlockedOr(&DevExt->Worker.EventsPending, ACTION_DEVICE_CONFIG); + + /* Request a QBR to ensure that storprop.dll updates its data from the registry */ + PortData->Worker.Flags |= WORKER_FLAG_NEED_RESCAN; +} + +static +VOID +AtaDeviceAnalyzeDmaError( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ ATA_ERROR_LOG_VALUE ErrorValue) +{ + LARGE_INTEGER CurrentTime, TimeDifferenceMs; + + WARN("DMA error %lu on '%s'\n", ErrorValue, DevExt->FriendlyName); + + /* We silently ignore device errors caused by PASSTHROUGH commands came from user mode */ + if (Request->Flags & REQUEST_FLAG_PASSTHROUGH) + return; + + KeQuerySystemTime(&CurrentTime); + TimeDifferenceMs.QuadPart = (CurrentTime.QuadPart - DevExt->LastDmaErrorTime.QuadPart) / 10000; + DevExt->LastDmaErrorTime.QuadPart = CurrentTime.QuadPart; + + AtaDeviceLogEvent(DevExt, ErrorValue); + + /* Ignore all occasional DMA errors we encounter */ + if (TimeDifferenceMs.QuadPart >= (10LL * 60000LL)) // 10 min + return; + + /* DMA timeouts and DMA CRC errors usually indicate a bad connection (a bad cable) */ + + /* Try to disable NCQ */ + if (DevExt->Device.DeviceFlags & DEVICE_NCQ) + { + DevExt->Device.DeviceFlags &= ~DEVICE_NCQ; + + ERR("NCQ disabled for '%s'\n", DevExt->FriendlyName); + AtaDeviceLogEvent(DevExt, EVENT_CODE_NCQ_DISABLE); + return; + } + + /* Try to reduce the interface speed */ + if (PortData->DowngradeInterfaceSpeed(PortData->ChannelContext)) + { + WARN("Downgrading interface for '%s'\n", DevExt->FriendlyName); + AtaDeviceLogEvent(DevExt, EVENT_CODE_DOWNSHIFT); + return; + } + + /* Try to reduce the transfer speed */ + AtaDeviceDowngradeTransferSpeed(PortData, DevExt); +} + +static +VOID +AtaDeviceCompleteFailedRequest( + _In_ PATAPORT_PORT_DATA PortData) +{ + PATA_DEVICE_REQUEST Request = PortData->Worker.FailedRequest; + UCHAR SrbStatus; + SIZE_T RetryCount; + KIRQL OldIrql; + + ASSERT_REQUEST(Request); + ASSERT(Request != &PortData->Worker.InternalRequest); + + SrbStatus = SRB_STATUS(Request->SrbStatus); + + RetryCount = SRB_GET_FLAGS(Request->Srb) & SRB_FLAG_RETRY_COUNT_MASK; + RetryCount++; + SRB_CLEAR_FLAGS(Request->Srb, SRB_FLAG_RETRY_COUNT_MASK); + SRB_SET_FLAGS(Request->Srb, RetryCount); + + if (RetryCount > 3) + { + ASSERT(SrbStatus != SRB_STATUS_BUSY); + } + else + { + /* + * We usually do not want to retry a request, because the upper class driver + * is intended to effectively analyze errors and sense data. + */ + switch (SrbStatus) + { + case SRB_STATUS_BUS_RESET: + case SRB_STATUS_TIMEOUT: + { + /* DMA timeout, attempt to retry in PIO mode */ + if (!(PortData->PortFlags & PORT_FLAG_PIO_VIA_DMA)) + { + SRB_SET_FLAGS(Request->Srb, SRB_FLAG_PIO_RETRY); + } + + SrbStatus = SRB_STATUS_BUSY; + break; + } + + case SRB_STATUS_REQUEST_SENSE_FAILED: + { + SrbStatus = SRB_STATUS_BUSY; + break; + } + + default: + { + /* Retry paging I/O operations only */ + if (Request->Irp->Flags & (IRP_SYNCHRONOUS_PAGING_IO | IRP_PAGING_IO)) + SrbStatus = SRB_STATUS_BUSY; + break; + } + } + } + + SrbStatus |= Request->SrbStatus & (SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_QUEUE_FROZEN); + Request->SrbStatus = SrbStatus; + + if (SRB_STATUS(SrbStatus) == SRB_STATUS_BUSY) + return; + + ASSERT(SRB_STATUS(SrbStatus) != SRB_STATUS_SUCCESS); + + /* Fail the command with an error */ + Request->InternalState = REQUEST_STATE_FREEZE_QUEUE; + + OldIrql = KeAcquireInterruptSpinLock(PortData->InterruptObject); + + ASSERT(PortData->Worker.PausedSlotsBitmap & (1 << Request->Slot)); + PortData->Worker.PausedSlotsBitmap &= ~(1 << Request->Slot); + + KeReleaseInterruptSpinLock(PortData->InterruptObject, OldIrql); +} + +static +NTSTATUS +AtaDeviceNcqRecovery( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST FailedRequest; + PGP_LOG_NCQ_COMMAND_ERROR LogPage; + NTSTATUS Status; + ULONG i; + UCHAR Crc; + + Status = AtaDeviceSendReadNcqCommandErrorLog(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + /* We failed at retrieving the log page */ + if (!NT_SUCCESS(Status)) + { + ERR("READ LOG EXT failure\n"); + goto Failure; + } + + LogPage = DevExt->Device.LocalBuffer; + + /* It's not ours */ + if (LogPage->NonQueuedCmd) + { + ERR("Unexpected NQ bit in the log page 0x10 structure\n"); + goto Failure; + } + + /* Verify the checksum */ + Crc = 0; + for (i = 0; i < IDE_GP_LOG_SECTOR_SIZE; ++i) + { + Crc += ((PUCHAR)LogPage)[i]; + } + if (Crc != 0) + { + ERR("CRC error in the log page 0x10 structure\n"); + goto Failure; + } + + /* Find the failed queued command */ + if (!(PortData->Worker.PausedSlotsBitmap & (1 << LogPage->NcqTag))) + { + ERR("Failed command %08lx not found in %08lx\n", + 1 << LogPage->NcqTag, PortData->Worker.PausedSlotsBitmap); + goto Failure; + } + + FailedRequest = PortData->Slots[LogPage->NcqTag]; + ASSERT_REQUEST(FailedRequest); + PortData->Worker.FailedRequest = FailedRequest; + + /* Fail the command with an error */ + FailedRequest->SrbStatus = SRB_STATUS_ERROR; + FailedRequest->Output.Status = LogPage->Status; + FailedRequest->Output.Error = LogPage->Error; + if (FailedRequest->Flags & REQUEST_FLAG_SAVE_TASK_FILE) + { + AtaDeviceSaveLogPageTaskFile(LogPage, FailedRequest); + } + + if ((LogPage->SenseKey != 0) && AtaDevHasNcqAutosense(&DevExt->IdentifyDeviceData)) + { + SCSI_SENSE_CODE SenseCode; + + /* Set the sense data returned by the device */ + SenseCode.SrbStatus = FailedRequest->SrbStatus; + SenseCode.SenseKey = LogPage->SenseKey; + SenseCode.AdditionalSenseCode = LogPage->ASC; + SenseCode.AdditionalSenseCodeQualifier = LogPage->ASCQ; + FailedRequest->SrbStatus = AtaReqSetFixedSenseData(FailedRequest->Srb, SenseCode); + + /* Set the "Final LBA in Error" field */ + AtaReqSetLbaInformation(FailedRequest->Srb, + ((ULONG64)(((PUCHAR)LogPage)[17]) << 0) | + ((ULONG64)(((PUCHAR)LogPage)[18]) << 8) | + ((ULONG64)(((PUCHAR)LogPage)[19]) << 16) | + ((ULONG64)(((PUCHAR)LogPage)[20]) << 24) | + ((ULONG64)(((PUCHAR)LogPage)[21]) << 32) | + ((ULONG64)(((PUCHAR)LogPage)[22]) << 48)); + } + else if (AtaDeviceRequestSenseNeededExt(DevExt, FailedRequest)) + { + /* Get sense data */ + Status = AtaDeviceSendRequestSenseExt(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + } + else + { + FailedRequest->SrbStatus = AtaReqSetFixedAtaSenseData(FailedRequest); + } + + AtaDeviceCompleteFailedRequest(PortData); + return STATUS_SUCCESS; + +Failure: + /* A port reset is required to abort all outstanding queued commands */ + _InterlockedOr(&PortData->Worker.EventsPending, ACTION_PORT_RESET); + return STATUS_SUCCESS; +} + +static +NTSTATUS +AtaDeviceGenericRecovery( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = PortData->Worker.FailedRequest; + NTSTATUS Status; + + switch (Request->SrbStatus) + { + /* Unexpected channel/device state */ + case SRB_STATUS_BUS_RESET: + case SRB_STATUS_TIMEOUT: + { + if (Request->Flags & REQUEST_FLAG_DMA) + { + ATA_ERROR_LOG_VALUE ErrorValue; + + if (Request->SrbStatus == SRB_STATUS_TIMEOUT) + ErrorValue = EVENT_CODE_TIMEOUT; + else + ErrorValue = EVENT_CODE_BAD_STATE; + AtaDeviceAnalyzeDmaError(PortData, DevExt, Request, ErrorValue); + } + break; + } + + /* General errors */ + case SRB_STATUS_ERROR: + { + if (AtaDeviceIsDmaCrcError(DevExt, Request)) + { + AtaDeviceAnalyzeDmaError(PortData, DevExt, Request, EVENT_CODE_CRC_ERROR); + break; + } + + /* Send the recovery command to figure out why the current command failed */ + if (AtaDeviceRequestSenseNeeded(Request)) + { + /* Handle failed ATAPI commands */ + Status = AtaDeviceSendRequestSense(PortData, DevExt); + break; + } + else if (AtaDeviceRequestSenseNeededExt(DevExt, Request)) + { + /* Handle failed non-queued commands */ + Status = AtaDeviceSendRequestSenseExt(PortData, DevExt); + break; + } + else if (AtaDeviceFixedErrorNeeded(DevExt, Request)) + { + Request->SrbStatus = AtaReqSetFixedAtaSenseData(Request); + } + + /* Recovery command is not required, just complete the request with an error */ + __fallthrough; + } + + default: + break; + } + + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + AtaDeviceCompleteFailedRequest(PortData); + return STATUS_SUCCESS; +} + +NTSTATUS +AtaPortDeviceProcessError( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST FailedRequest = PortData->Worker.FailedRequest; + NTSTATUS Status; + + ASSERT_REQUEST(FailedRequest); + + if (FailedRequest->Flags & REQUEST_FLAG_NCQ) + Status = AtaDeviceNcqRecovery(PortData, DevExt); + else + Status = AtaDeviceGenericRecovery(PortData, DevExt); + + return Status; +} diff --git a/drivers/storage/ide/atapi/dev_identify.c b/drivers/storage/ide/atapi/dev_identify.c new file mode 100644 index 00000000000..7c64394054b --- /dev/null +++ b/drivers/storage/ide/atapi/dev_identify.c @@ -0,0 +1,367 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Device identification + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* FUNCTIONS ******************************************************************/ + +static +BOOLEAN +AtaDeviceIdentifyDataEqual( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData1, + _In_ PIDENTIFY_DEVICE_DATA IdentifyData2) +{ + if (!RtlEqualMemory(IdentifyData1->SerialNumber, + IdentifyData2->SerialNumber, + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, SerialNumber))) + { + return FALSE; + } + + if (!RtlEqualMemory(IdentifyData1->FirmwareRevision, + IdentifyData2->FirmwareRevision, + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, FirmwareRevision))) + { + return FALSE; + } + + if (!RtlEqualMemory(IdentifyData1->ModelNumber, + IdentifyData2->ModelNumber, + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, ModelNumber))) + { + return FALSE; + } + + return TRUE; +} + +NTSTATUS +AtaDeviceSendIdentify( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ UCHAR Command) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + + TRACE("CH %lu: Send %s identify device %u\n", + PortData->PortNumber, + Command == IDE_COMMAND_IDENTIFY ? "ATA" : "ATAPI", + DevExt->Device.AtaScsiAddress.TargetId); + + /* + * For PATA devices, disable interrupts for the identify command and use polling instead. + * + * On some single device 1 configurations or non-existent IDE channels + * the status register stuck permanently at a value of 0, + * and we incorrectly assume that the device is present. + * This will result in a taskfile timeout + * which must be avoided as it would cause hangs at boot time. + */ + Request->Flags = REQUEST_FLAG_POLL; + + Request->Flags |= REQUEST_FLAG_DATA_IN | REQUEST_FLAG_HAS_LOCAL_BUFFER; + Request->TimeOut = 10; + Request->DataTransferLength = sizeof(DevExt->IdentifyDeviceData); + + RtlZeroMemory(&Request->TaskFile, sizeof(Request->TaskFile)); + Request->TaskFile.Command = Command; + + return AtaPortSendRequest(PortData, DevExt); +} + +static +NTSTATUS +AtaDeviceSpinUp( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + + TRACE("CH %lu: Spin-up device %u\n", + PortData->PortNumber, + DevExt->Device.AtaScsiAddress.TargetId); + + Request->Flags = 0; + Request->TimeOut = 20; + + Request->TaskFile.Command = IDE_COMMAND_SET_FEATURE; + Request->TaskFile.Feature = IDE_FEATURE_PUIS_SPIN_UP; + + return AtaPortSendRequest(PortData, DevExt); +} + +static +BOOLEAN +AtaDeviceIsXboxDrive( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + static const PCSTR AtapBrokenInquiryDrive[] = + { + "THOMSON-DVD", + "PHILIPS XBOX DVD DRIVE", + "PHILIPS J5 3235C", + "SAMSUNG DVD-ROM SDG-605B" + // TODO: Xbox 360 drives are also affected. + }; + CHAR ModelNumber[26]; + PUCHAR End; + ULONG i; + + End = AtaCopyIdStringUnsafe((PUCHAR)ModelNumber, + DevExt->IdentifyDeviceData.ModelNumber, + sizeof(ModelNumber)); + *End = ANSI_NULL; + + for (i = 0; i < RTL_NUMBER_OF(AtapBrokenInquiryDrive); ++i) + { + if (strcmp(ModelNumber, AtapBrokenInquiryDrive[i]) == 0) + return TRUE; + } + + return FALSE; +} + +static +VOID +AtaCreateAtapiStandardInquiryData( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData, + _Out_ PINQUIRYDATA InquiryData) +{ + InquiryData->DeviceType = READ_ONLY_DIRECT_ACCESS_DEVICE; + InquiryData->RemovableMedia = AtaDevIsRemovable(IdentifyData); + InquiryData->Versions = 0x04; // SPC-2 + InquiryData->ResponseDataFormat = 2; // This means "Complies to this standard" + InquiryData->AdditionalLength = + INQUIRYDATABUFFERSIZE - RTL_SIZEOF_THROUGH_FIELD(INQUIRYDATA, AdditionalLength); + + /* Copy ModelNumber from a byte-swapped ATA string */ + AtaCopyIdStringSafe((PCHAR)InquiryData->VendorId, + IdentifyData->ModelNumber, + sizeof(IdentifyData->ModelNumber), + ' '); + AtaSwapIdString(InquiryData->VendorId, sizeof(IdentifyData->ModelNumber) / 2); + + /* Copy FirmwareRevision from a byte-swapped ATA string */ + AtaCopyIdStringSafe((PCHAR)InquiryData->ProductRevisionLevel, + IdentifyData->FirmwareRevision, + sizeof(InquiryData->ProductRevisionLevel), + ' '); + AtaSwapIdString(InquiryData->ProductRevisionLevel, + sizeof(InquiryData->ProductRevisionLevel) / 2); +} + +static +NTSTATUS +AtaDeviceSendInquiry( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + NTSTATUS Status; + PCDB Cdb; + + TRACE("CH %lx: Send inquiry device %u\n", + PortData->PortNumber, + DevExt->Device.AtaScsiAddress.TargetId); + + Request->Flags = REQUEST_FLAG_DATA_IN | + REQUEST_FLAG_PACKET_COMMAND | + REQUEST_FLAG_HAS_LOCAL_BUFFER; + Request->TimeOut = 3; + Request->DataTransferLength = INQUIRYDATABUFFERSIZE; + + RtlZeroMemory(Request->Cdb, sizeof(Request->Cdb)); + Cdb = (PCDB)&Request->Cdb; + Cdb->CDB6INQUIRY.OperationCode = SCSIOP_INQUIRY; + Cdb->CDB6INQUIRY.LogicalUnitNumber = DevExt->Device.AtaScsiAddress.Lun; + Cdb->CDB6INQUIRY.AllocationLength = INQUIRYDATABUFFERSIZE; + + Status = AtaPortSendRequest(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + if (!NT_SUCCESS(Status)) + { + if (DevExt->Device.AtaScsiAddress.Lun == 0 && AtaDeviceIsXboxDrive(DevExt)) + { + DevExt->Device.DeviceFlags |= DEVICE_CACHE_INQUIRY; + + /* + * The INQUIRY command is mandatory to be implemented by ATAPI devices, + * but some Xbox drives violate this. + */ + AtaCreateAtapiStandardInquiryData(&DevExt->IdentifyDeviceData, + DevExt->Device.LocalBuffer); + Status = DEV_STATUS_NEW_DEVICE; + } + else + { + Status = DEV_STATUS_NO_DEVICE; + } + } + + return STATUS_SUCCESS; +} + +ATA_DEVICE_STATUS +AtaPortIdentifyDevice( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PIDENTIFY_DEVICE_DATA IdentifyData = DevExt->Device.LocalBuffer; + ATA_CONNECTION_STATUS ConnectionStatus; + NTSTATUS Status; + BOOLEAN StartFromAtapi, IsSameDevice; + ULONG Attempt, RetryCount; + ATA_DEVICE_TYPE DeviceType; + + _InterlockedOr(&PortData->InterruptFlags, PORT_INT_FLAG_IGNORE_LINK_IRQ); + + ConnectionStatus = PortData->IdentifyDevice(PortData->ChannelContext, + DevExt->Device.AtaScsiAddress.TargetId); + + _InterlockedAnd(&PortData->InterruptFlags, ~PORT_INT_FLAG_IGNORE_LINK_IRQ); + + if (ConnectionStatus == CONN_STATUS_FAILURE) + return DEV_STATUS_FAILED; + + if (ConnectionStatus == CONN_STATUS_NO_DEVICE) + return DEV_STATUS_NO_DEVICE; + + /* Power up the device if needed */ + Status = AtaPortCheckDevicePowerState(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return DEV_STATUS_FAILED; + + /* + * Try the known device type first. + * This may speed up device detection by eliminating I/O errors. + */ + if (DevExt->Device.DeviceFlags & DEVICE_UNINITIALIZED) + StartFromAtapi = (ConnectionStatus == CONN_STATUS_DEV_ATAPI); + else + StartFromAtapi = IS_ATAPI(&DevExt->Device); + + /* Look for ATA/ATAPI devices */ + for (RetryCount = 0; RetryCount < 2; ++RetryCount) + { + /* Send the identify command */ + for (Attempt = 0; Attempt < 2; ++Attempt) + { + if (StartFromAtapi) + DeviceType = DEV_ATAPI; + else + DeviceType = DEV_ATA; + + /* Swap device types */ + if (Attempt != 0) + DeviceType ^= (DEV_ATAPI ^ DEV_ATA); + + Status = AtaDeviceSendIdentify(PortData, + DevExt, + DeviceType == DEV_ATAPI ? + IDE_COMMAND_ATAPI_IDENTIFY : IDE_COMMAND_IDENTIFY); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return DEV_STATUS_FAILED; + + if (NT_SUCCESS(Status)) + break; + } + if (!NT_SUCCESS(Status)) + return DEV_STATUS_NO_DEVICE; + + /* + * The drive needs to be spun up + * or the device was unable to return complete identity data. + */ + if (AtaDevInPuisState(IdentifyData) || AtaDevIsIdentifyDataIncomplete(IdentifyData)) + { + + Status = AtaDeviceSpinUp(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return DEV_STATUS_FAILED; + + if (!NT_SUCCESS(Status)) + ERR("Failed to spin-up device\n"); + continue; + } + } + + /* Verify the checksum */ + if (!AtaDevIsIdentifyDataValid(IdentifyData)) + { + ERR("Identify data CRC error\n"); + return DEV_STATUS_FAILED; + } + + if (DevExt->Device.DeviceFlags & DEVICE_UNINITIALIZED) + { + IsSameDevice = FALSE; + } + else + { + IsSameDevice = (DeviceType == DevExt->DeviceType) && + (AtaDeviceIdentifyDataEqual(&DevExt->IdentifyDeviceData, + DevExt->Device.LocalBuffer)); + } + + /* Update identify data */ + RtlCopyMemory(&DevExt->IdentifyDeviceData, + DevExt->Device.LocalBuffer, + sizeof(DevExt->IdentifyDeviceData)); + DevExt->DeviceType = DeviceType; + + if (IsSameDevice) + return DEV_STATUS_SAME_DEVICE; + + DevExt->Device.TransportFlags &= ~(DEVICE_IS_ATAPI | + DEVICE_HAS_CDB_INTERRUPT | + DEVICE_NEED_DMA_DIRECTION | + DEVICE_IS_NEC_CDR260); + + if (DeviceType == DEV_ATAPI) + { + DevExt->Device.TransportFlags |= DEVICE_IS_ATAPI; + + DevExt->Device.CdbSize = AtaDevCdbSizeInWords(&DevExt->IdentifyPacketData); + TRACE("Device has CDB size of %u bytes\n", DevExt->Device.CdbSize * 2); + + if (AtaDevHasCdbInterrupt(&DevExt->IdentifyPacketData)) + { + TRACE("Device has CDB interrupt\n"); + DevExt->Device.TransportFlags |= DEVICE_HAS_CDB_INTERRUPT; + } + + if (AtaDevIsDmaDirectionRequired(&DevExt->IdentifyPacketData)) + { + TRACE("Device needs DMA DIR\n"); + DevExt->Device.TransportFlags |= DEVICE_NEED_DMA_DIRECTION; + } + + /* The NEC CDR-260 string is not byteswapped */ + if (DevExt->IdentifyPacketData.ModelNumber[0] == 'N' && + DevExt->IdentifyPacketData.ModelNumber[1] == 'E' && + DevExt->IdentifyPacketData.ModelNumber[2] == 'C') + { + TRACE("Device is a NEC CDR-260 drive\n"); + DevExt->Device.TransportFlags |= DEVICE_IS_NEC_CDR260; + } + + Status = AtaDeviceSendInquiry(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return DEV_STATUS_FAILED; + + /* Update the inquiry data */ + RtlCopyMemory(&DevExt->InquiryData, + DevExt->Device.LocalBuffer, + sizeof(DevExt->InquiryData)); + } + + return DEV_STATUS_NEW_DEVICE; +} diff --git a/drivers/storage/ide/atapi/dev_power.c b/drivers/storage/ide/atapi/dev_power.c new file mode 100644 index 00000000000..daba032b37f --- /dev/null +++ b/drivers/storage/ide/atapi/dev_power.c @@ -0,0 +1,531 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Power management + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* GLOBALS ********************************************************************/ + +static IO_COMPLETION_ROUTINE AtaPdoPowerUpCompletionRoutine; +static REQUEST_POWER_COMPLETE AtaPdoPowerSetSysStateWithDevStateComplete; + +/* FUNCTIONS ******************************************************************/ + +static +NTSTATUS +AtaPowerCompleteIrp( + _In_ PATAPORT_COMMON_EXTENSION CommonExt, + _In_ PIRP Irp, + _In_ NTSTATUS Status) +{ + PoStartNextPowerIrp(Irp); + + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + IoReleaseRemoveLock(&CommonExt->RemoveLock, Irp); + + return Status; +} + +VOID +AtaDeviceFlushPowerIrpQueue( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + while (TRUE) + { + PLIST_ENTRY Entry; + PIRP PowerIrp; + KIRQL OldLevel; + + KeAcquireSpinLock(&DevExt->Device.QueueLock, &OldLevel); + if (IsListEmpty(&DevExt->PowerIrpQueueList)) + Entry = NULL; + else + Entry = RemoveHeadList(&DevExt->PowerIrpQueueList); + KeReleaseSpinLock(&DevExt->Device.QueueLock, OldLevel); + + if (!Entry) + break; + + PowerIrp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.ListEntry); + ASSERT(IoGetCurrentIrpStackLocation(PowerIrp)->MinorFunction == IRP_MN_SET_POWER); + AtaPowerCompleteIrp(&DevExt->Common, PowerIrp, STATUS_NO_SUCH_DEVICE); + } +} + +UCHAR +AtaDeviceGetFlushCacheCommand( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + UCHAR Command = 0; + + if (!IS_ATAPI(&DevExt->Device)) + { + if ((DevExt->Device.DeviceFlags & DEVICE_LBA48) && + AtaDevHasFlushCacheExt(&DevExt->IdentifyDeviceData)) + { + Command = IDE_COMMAND_FLUSH_CACHE_EXT; + } + else if (AtaDevHasFlushCache(&DevExt->IdentifyDeviceData)) + { + Command = IDE_COMMAND_FLUSH_CACHE; + } + } + + return Command; +} + +static +VOID +AtaDeviceCompletePowerIrp( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + POWER_STATE PowerState; + + PowerState.DeviceState = DevExt->Common.DevicePowerState; + PoSetPowerState(DevExt->Common.Self, DevicePowerState, PowerState); + + AtaPowerCompleteIrp(&DevExt->Common, Irp, STATUS_SUCCESS); +} + +static +NTSTATUS +AtaDeviceFlushCache( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + UCHAR Command; + + Command = AtaDeviceGetFlushCacheCommand(DevExt); + if (Command == 0) + return STATUS_SUCCESS; + + Request->Flags = 0; + Request->TimeOut = 30; + RtlZeroMemory(&Request->TaskFile, sizeof(Request->TaskFile)); + Request->TaskFile.Command = Command; + + return AtaPortSendRequest(PortData, DevExt); +} + +static +NTSTATUS +AtaDeviceSetIdleMode( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + + Request->Flags = 0; + Request->TimeOut = 20; + Request->TaskFile.Feature = 0; + Request->TaskFile.Command = IDE_COMMAND_IDLE_IMMEDIATE; + + return AtaPortSendRequest(PortData, DevExt); +} + +static +NTSTATUS +AtaDeviceSetStandbyMode( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + + Request->Flags = 0; + Request->TimeOut = 20; + Request->TaskFile.Command = IDE_COMMAND_STANDBY_IMMEDIATE; + + return AtaPortSendRequest(PortData, DevExt); +} + +NTSTATUS +AtaPortDeviceProcessPowerChange( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PLIST_ENTRY Entry; + PIRP PowerIrp; + PIO_STACK_LOCATION IoStack; + DEVICE_POWER_STATE NewState; + KIRQL OldLevel; + NTSTATUS Status; + + KeAcquireSpinLock(&DevExt->Device.QueueLock, &OldLevel); + if (IsListEmpty(&DevExt->PowerIrpQueueList)) + Entry = NULL; + else + Entry = RemoveHeadList(&DevExt->PowerIrpQueueList); + KeReleaseSpinLock(&DevExt->Device.QueueLock, OldLevel); + if (!Entry) + return STATUS_SUCCESS; + + PowerIrp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.ListEntry); + + IoStack = IoGetCurrentIrpStackLocation(PowerIrp); + ASSERT(IoStack->MinorFunction == IRP_MN_SET_POWER); + + NewState = IoStack->Parameters.Power.State.DeviceState; + if (NewState == DevExt->Common.DevicePowerState) + { + AtaDeviceCompletePowerIrp(DevExt, PowerIrp); + return STATUS_SUCCESS; + } + + if (NewState != PowerDeviceD3) + { + AtaReqThawQueue(DevExt, QUEUE_FLAG_FROZEN_POWER); + + /* Power up the device */ + Status = AtaDeviceSetIdleMode(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + /* Restore the software settings after power up */ + _InterlockedOr(&DevExt->Worker.EventsPending, ACTION_DEVICE_CONFIG); + _InterlockedOr(&PortData->Worker.EventsPending, ACTION_DEVICE_CONFIG); + } + else + { + AtaReqFreezeQueue(DevExt, QUEUE_FLAG_FROZEN_POWER); + + /* Flush device caches */ + Status = AtaDeviceFlushCache(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + + /* Power down the device */ + Status = AtaDeviceSetStandbyMode(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + return Status; + } + + AtaDeviceCompletePowerIrp(DevExt, PowerIrp); + + DevExt->Common.DevicePowerState = NewState; + return Status; +} + +NTSTATUS +AtaPortCheckDevicePowerState( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + NTSTATUS Status; + + if (DevExt->Common.DevicePowerState == PowerDeviceD0) + return STATUS_SUCCESS; + + INFO("CH %lu: Powering up idle device '%s'\n", PortData->PortNumber, DevExt->FriendlyName); + + Status = AtaDeviceSetIdleMode(PortData, DevExt); + if (!NT_SUCCESS(Status)) + { + /* + * If the power on device has failed, the next command can still work. + * The media access will result in a transition from the PM1:Idle/PM2:Standby state + * to the PM0:Active state by the ATA spec. + */ + ERR("CH %lu: Failed to power up device '%s' %lx\n", + PortData->PortNumber, DevExt->FriendlyName, Status); + } + else + { + DevExt->Common.DevicePowerState = PowerDeviceD0; + } + + return Status; +} + +static +VOID +AtaPdoQueuePowerIrp( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + KIRQL OldLevel; + + KeAcquireSpinLock(&DevExt->Device.QueueLock, &OldLevel); + InsertTailList(&DevExt->PowerIrpQueueList, &Irp->Tail.Overlay.ListEntry); + KeReleaseSpinLock(&DevExt->Device.QueueLock, OldLevel); + + AtaDeviceQueueEvent(DevExt->Device.PortData, DevExt, ACTION_DEVICE_POWER); +} + +static +NTSTATUS +NTAPI +AtaPdoPowerUpCompletionRoutine( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PIRP Irp, + _In_reads_opt_(_Inexpressible_("varies")) PVOID Context) +{ + PATAPORT_DEVICE_EXTENSION DevExt = Context; + NTSTATUS Status; + + UNREFERENCED_PARAMETER(DeviceObject); + + Status = Irp->IoStatus.Status; + + INFO("Powering up device, lower driver status %lx\n", Status); + + if (!NT_SUCCESS(Status)) + { + PoStartNextPowerIrp(Irp); + + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, Irp); + return STATUS_CONTINUE_COMPLETION; + } + + AtaPdoQueuePowerIrp(DevExt, Irp); + + /* Defer IRP completion */ + return STATUS_MORE_PROCESSING_REQUIRED; +} + +static +NTSTATUS +AtaPdoPowerSetDevicePowerState( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + PIO_STACK_LOCATION IoStack; + DEVICE_POWER_STATE NewState; + + IoStack = IoGetCurrentIrpStackLocation(Irp); + NewState = IoStack->Parameters.Power.State.DeviceState; + + IoMarkIrpPending(Irp); + + if (NewState < DevExt->Common.DevicePowerState) + { + PATAPORT_CHANNEL_EXTENSION ChanExt = DevExt->Common.FdoExt; + + IoCopyCurrentIrpStackLocationToNext(Irp); + IoSetCompletionRoutine(Irp, + AtaPdoPowerUpCompletionRoutine, + DevExt, + TRUE, + TRUE, + TRUE); + (VOID)PoCallDriver(ChanExt->Common.Self, Irp); + } + else + { + INFO("Powering down device\n"); + + AtaPdoQueuePowerIrp(DevExt, Irp); + } + + return STATUS_PENDING; +} + +static +VOID +NTAPI +AtaPdoPowerSetSysStateWithDevStateComplete( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ UCHAR MinorFunction, + _In_ POWER_STATE PowerState, + _In_opt_ PVOID Context, + _In_ PIO_STATUS_BLOCK IoStatus) +{ + PATAPORT_DEVICE_EXTENSION DevExt = DeviceObject->DeviceExtension; + PIRP Irp = Context; + + UNREFERENCED_PARAMETER(MinorFunction); + + if (DevExt->Common.SystemPowerState != PowerState.SystemState) + { + DevExt->Common.SystemPowerState = PowerState.SystemState; + PoSetPowerState(DeviceObject, SystemPowerState, PowerState); + } + + AtaPowerCompleteIrp(&DevExt->Common, Irp, IoStatus->Status); +} + +static +VOID +AtaPdoPowerGetDevStateForSysState( + _In_ SYSTEM_POWER_STATE SystemState, + _Out_ PDEVICE_POWER_STATE DeviceState) +{ + if (SystemState == PowerSystemWorking) + { + *DeviceState = PowerDeviceD0; + } + else + { + *DeviceState = PowerDeviceD3; + } +} + +static +NTSTATUS +AtaPdoPowerSetSystemPowerState( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + PIO_STACK_LOCATION IoStack; + NTSTATUS Status; + POWER_STATE NewPowerState; + SYSTEM_POWER_STATE NewSystemState; + + IoStack = IoGetCurrentIrpStackLocation(Irp); + NewSystemState = IoStack->Parameters.Power.State.SystemState; + + IoMarkIrpPending(Irp); + + AtaPdoPowerGetDevStateForSysState(NewSystemState, &NewPowerState.DeviceState); + Status = PoRequestPowerIrp(DevExt->Common.Self, + IRP_MN_SET_POWER, + NewPowerState, + AtaPdoPowerSetSysStateWithDevStateComplete, + Irp, + NULL); + if (!NT_SUCCESS(Status)) + { + IO_STATUS_BLOCK IoStatusBlock; + + ERR("Failed to change power state for device '%s' %lx\n", DevExt->FriendlyName, Status); + + IoStatusBlock.Status = Status; + AtaPdoPowerSetSysStateWithDevStateComplete(DevExt->Common.Self, + IRP_MN_SET_POWER, + NewPowerState, + Irp, + &IoStatusBlock); + } + + return Status; +} + +static +NTSTATUS +AtaPdoPower( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _Inout_ PIRP Irp) +{ + // FIXME: Investigate how to fix this kernel bug + // *** Assertion failed: KeGetCurrentThread()->SystemAffinityActive == FALSE + // *** Source File: ../ntoskrnl/ke/dpc.c, line 926 +#if 1 + NTSTATUS Status; + + UNREFERENCED_PARAMETER(AtaPdoPowerSetSystemPowerState); + UNREFERENCED_PARAMETER(AtaPdoPowerGetDevStateForSysState); + UNREFERENCED_PARAMETER(AtaPdoPowerSetSysStateWithDevStateComplete); + UNREFERENCED_PARAMETER(AtaPdoPowerSetDevicePowerState); + UNREFERENCED_PARAMETER(AtaPdoPowerUpCompletionRoutine); + UNREFERENCED_PARAMETER(AtaPdoQueuePowerIrp); + + Status = Irp->IoStatus.Status; + PoStartNextPowerIrp(Irp); + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return Status; +#else + NTSTATUS Status; + PIO_STACK_LOCATION IoStack; + + Status = IoAcquireRemoveLock(&DevExt->Common.RemoveLock, Irp); + if (!NT_SUCCESS(Status)) + { + PoStartNextPowerIrp(Irp); + + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return Status; + } + + IoStack = IoGetCurrentIrpStackLocation(Irp); + switch (IoStack->MinorFunction) + { + case IRP_MN_QUERY_POWER: + { + switch (IoStack->Parameters.Power.Type) + { + case SystemPowerState: + case DevicePowerState: + Status = AtaPowerCompleteIrp(&DevExt->Common, Irp, STATUS_SUCCESS); + break; + + default: + Status = AtaPowerCompleteIrp(&DevExt->Common, Irp, Irp->IoStatus.Status); + break; + } + break; + } + + case IRP_MN_SET_POWER: + { + switch (IoStack->Parameters.Power.Type) + { + case SystemPowerState: + Status = AtaPdoPowerSetSystemPowerState(DevExt, Irp); + break; + case DevicePowerState: + Status = AtaPdoPowerSetDevicePowerState(DevExt, Irp); + break; + + default: + Status = AtaPowerCompleteIrp(&DevExt->Common, Irp, Irp->IoStatus.Status); + break; + } + break; + } + + default: + Status = AtaPowerCompleteIrp(&DevExt->Common, Irp, Irp->IoStatus.Status); + break; + } + + return Status; +#endif +} + +static +NTSTATUS +AtaFdoPower( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _Inout_ PIRP Irp) +{ + NTSTATUS Status; + + Status = IoAcquireRemoveLock(&ChanExt->Common.RemoveLock, Irp); + if (!NT_SUCCESS(Status)) + { + PoStartNextPowerIrp(Irp); + + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return Status; + } + + PoStartNextPowerIrp(Irp); + IoSkipCurrentIrpStackLocation(Irp); + Status = PoCallDriver(ChanExt->Common.LowerDeviceObject, Irp); + + IoReleaseRemoveLock(&ChanExt->Common.RemoveLock, Irp); + + return Status; +} + +NTSTATUS +NTAPI +AtaDispatchPower( + _In_ PDEVICE_OBJECT DeviceObject, + _Inout_ PIRP Irp) +{ + if (IS_FDO(DeviceObject->DeviceExtension)) + return AtaFdoPower(DeviceObject->DeviceExtension, Irp); + else + return AtaPdoPower(DeviceObject->DeviceExtension, Irp); +} diff --git a/drivers/storage/ide/atapi/dev_timings.c b/drivers/storage/ide/atapi/dev_timings.c new file mode 100644 index 00000000000..9a5afdd2572 --- /dev/null +++ b/drivers/storage/ide/atapi/dev_timings.c @@ -0,0 +1,354 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: ATA PIO and DMA timings support + * COPYRIGHT: Copyright 2026 Dmitry Borisov + * + * REFERENCES: For more details refer to the + * "Intel ICH0~ICH5 Programmer's Reference Manual (PRM) 298600-004": + * "Determining a Drive's Transfer Rate Capabilities" + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* GLOBALS ********************************************************************/ + +static const ULONG AtapModeToCycleTime[] = +{ + /* 0 1 2 3 4 PIO_MODE */ + 600, 383, 240, 180, 120, + + /* 0 1 2 SWDMA_MODE */ + 960, 480, 240, + + /* 0 1 2 MWDMA_MODE */ + 480, 150, 120, + + /* 0 1 2 3 4 5 6 UDMA_MODE */ + 120, 80, 60, 45, 30, 20, 15 +}; + +/* FUNCTIONS ******************************************************************/ + +static +VOID +AtaTimQueryPioModeSupport( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ULONG AllowedModesMask) +{ + PIDENTIFY_DEVICE_DATA IdentifyData = &DevExt->IdentifyDeviceData; + ULONG SupportedMode, BestMode, CurrentMode, CycleTime; + + /* Find the best possible PIO mode */ + if (IdentifyData->TranslationFieldsValid & 0x2) + { + /* PIO 3-4 */ + SupportedMode = IdentifyData->AdvancedPIOModes & 0x3; + SupportedMode <<= PIO_MODE(3); + + /* The device is assumed to have PIO 0-2 */ + SupportedMode |= PIO_MODE0 | PIO_MODE1 | PIO_MODE2; + + SupportedMode &= AllowedModesMask; + + if (IdentifyData->Capabilities.IordySupported) + CycleTime = IdentifyData->MinimumPIOCycleTimeIORDY; + else + CycleTime = IdentifyData->MinimumPIOCycleTime; + + /* + * Any device that supports PIO 3 or above + * should support word 67 and word 68, but be defensive. + */ + if (CycleTime == 0) + { + WARN("Device '%s' returned zero cycle time\n", DevExt->FriendlyName); + NT_VERIFY(_BitScanReverse(&BestMode, SupportedMode)); + + /* Fall back to the default value */ + ASSERT(BestMode < RTL_NUMBER_OF(AtapModeToCycleTime)); + CycleTime = AtapModeToCycleTime[BestMode]; + } + } + else + { + /* PIO 0-4 (maximum supported mode) */ + SupportedMode = IdentifyData->ObsoleteWords51[0] >> 8; + if (SupportedMode > PIO_MODE(4)) + SupportedMode = PIO_MODE(0); + + /* Convert the maximum mode to a mask */ + SupportedMode = (1 << (SupportedMode + 1)) - 1; + SupportedMode &= AllowedModesMask; + + NT_VERIFY(_BitScanReverse(&BestMode, SupportedMode)); + + ASSERT(BestMode < RTL_NUMBER_OF(AtapModeToCycleTime)); + CycleTime = AtapModeToCycleTime[BestMode]; + } + DevExt->TransferModeSupportedBitmap |= SupportedMode; + DevExt->MinimumPioCycleTime = CycleTime; + + /* + * Since there is no way to obtain the current PIO mode from identify data + * we just look for the fastest supported mode. + */ + NT_VERIFY(_BitScanReverse(&BestMode, SupportedMode)); + CurrentMode = BestMode; + DevExt->TransferModeCurrentBitmap |= 1 << CurrentMode; +} + +static +VOID +AtaTimQuerySwDmaModeSupport( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ULONG AllowedModesMask) +{ + PIDENTIFY_DEVICE_DATA IdentifyData = &DevExt->IdentifyDeviceData; + ULONG SupportedMode, BestMode, CurrentMode, CycleTime; + + CycleTime = IDE_ACPI_TIMING_MODE_NOT_SUPPORTED; + + /* Find the current active SWDMA mode */ + CurrentMode = ((IdentifyData->ObsoleteWord62 & 0xFF00) >> 16) & 0x7; + if (CurrentMode != 0) + { + NT_VERIFY(_BitScanReverse(&CurrentMode, CurrentMode)); + CurrentMode += SWDMA_MODE(0); + + DevExt->TransferModeCurrentBitmap |= 1 << CurrentMode; + } + + /* Find the best possible SWDMA mode */ + SupportedMode = (IdentifyData->ObsoleteWord62 & 0x00FF) & 0x7; + SupportedMode <<= SWDMA_MODE(0); + SupportedMode &= AllowedModesMask; + if (SupportedMode != 0) + { + DevExt->TransferModeSupportedBitmap |= SupportedMode; + + NT_VERIFY(_BitScanReverse(&BestMode, SupportedMode)); + + ASSERT(BestMode < RTL_NUMBER_OF(AtapModeToCycleTime)); + CycleTime = AtapModeToCycleTime[BestMode]; + } + + DevExt->MinimumSingleWordDmaCycleTime = CycleTime; +} + +static +VOID +AtaTimQueryMwDmaModeSupport( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ULONG AllowedModesMask) +{ + PIDENTIFY_DEVICE_DATA IdentifyData = &DevExt->IdentifyDeviceData; + ULONG SupportedMode, BestMode, CurrentMode, CycleTime; + + CycleTime = IDE_ACPI_TIMING_MODE_NOT_SUPPORTED; + + /* Find the current active MWDMA mode */ + CurrentMode = IdentifyData->MultiWordDMAActive & 0x7; + if (CurrentMode != 0) + { + NT_VERIFY(_BitScanReverse(&CurrentMode, CurrentMode)); + CurrentMode += MWDMA_MODE(0); + + DevExt->TransferModeCurrentBitmap |= 1 << CurrentMode; + } + + /* Find the best possible MWDMA mode */ + SupportedMode = IdentifyData->MultiWordDMASupport & 0x7; + SupportedMode <<= MWDMA_MODE(0); + SupportedMode &= AllowedModesMask; + if (SupportedMode != 0) + { + DevExt->TransferModeSupportedBitmap |= SupportedMode; + + NT_VERIFY(_BitScanReverse(&BestMode, SupportedMode)); + + /* Prefer the minimum cycle time, if words 64-70 are valid */ + if ((IdentifyData->TranslationFieldsValid & 0x2) && + (IdentifyData->MinimumMWXferCycleTime != 0) && + (IdentifyData->RecommendedMWXferCycleTime != 0)) + { + CycleTime = IdentifyData->MinimumMWXferCycleTime; + } + else + { + ASSERT(BestMode < RTL_NUMBER_OF(AtapModeToCycleTime)); + CycleTime = AtapModeToCycleTime[BestMode]; + } + } + + DevExt->MinimumMultiWordDmaCycleTime = CycleTime; +} + +static +VOID +AtaTimQueryUDmaModeSupport( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ULONG AllowedModesMask) +{ + PIDENTIFY_DEVICE_DATA IdentifyData = &DevExt->IdentifyDeviceData; + ULONG SupportedMode, BestMode, CurrentMode, CycleTime; + + CycleTime = IDE_ACPI_TIMING_MODE_NOT_SUPPORTED; + + /* Word 88 is not valid */ + if (!(IdentifyData->TranslationFieldsValid & 0x4)) + goto Exit; + + /* Find the current active UDMA mode */ + CurrentMode = IdentifyData->UltraDMAActive & 0x7F; + if (CurrentMode != 0) + { + NT_VERIFY(_BitScanReverse(&CurrentMode, CurrentMode)); + CurrentMode += UDMA_MODE(0); + + DevExt->TransferModeCurrentBitmap |= 1 << CurrentMode; + } + + /* Find the best possible UDMA mode */ + SupportedMode = IdentifyData->UltraDMASupport & 0x7F; + SupportedMode <<= UDMA_MODE(0); + SupportedMode &= AllowedModesMask; + if (SupportedMode != 0) + { + DevExt->TransferModeSupportedBitmap |= SupportedMode; + + NT_VERIFY(_BitScanReverse(&BestMode, SupportedMode)); + + ASSERT(BestMode < RTL_NUMBER_OF(AtapModeToCycleTime)); + CycleTime = AtapModeToCycleTime[BestMode]; + } + +Exit: + DevExt->MinimumUltraDmaCycleTime = CycleTime; +} + +static +VOID +AtaTimDumpTimingInfo( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + INFO("CH %lu: Device %u '%s'\n" + "Cycle time UDMA[%ld] MWDMA[%ld] SWDMA[%ld] PIO[%ld]\n" + "Supported modes 0x%08lX\n" + "Active modes 0x%08lX\n" + "Selected modes 0x%08lX\n" + "IOREADY %u\n", + PortData->PortNumber, + DevExt->Device.AtaScsiAddress.TargetId, + DevExt->FriendlyName, + (LONG)DevExt->MinimumUltraDmaCycleTime, + (LONG)DevExt->MinimumMultiWordDmaCycleTime, + (LONG)DevExt->MinimumSingleWordDmaCycleTime, + (LONG)DevExt->MinimumPioCycleTime, + DevExt->TransferModeSupportedBitmap, + DevExt->TransferModeCurrentBitmap, + DevExt->TransferModeSelectedBitmap, + DevExt->IdentifyDeviceData.Capabilities.IordySupported); +} + +VOID +AtaPortSelectTimings( + _In_ PATAPORT_PORT_DATA PortData, + _In_ BOOLEAN ForceCompatibleTimings) +{ + PATAPORT_CHANNEL_EXTENSION ChanExt; + ULONG i; + CHANNEL_DEVICE_CONFIG Config[ATA_MAX_DEVICE] = { 0 }; + PCHANNEL_DEVICE_CONFIG DeviceList[ATA_MAX_DEVICE] = { 0 }; + + if (ForceCompatibleTimings) + { + for (i = 0; i < ATA_MAX_DEVICE; ++i) + { + /* Fake device presence */ + DeviceList[i] = &Config[i]; + + /* Disable DMA and select compatible timings */ + Config[i].SupportedModes = PIO_MODE0; + Config[i].CurrentModes = PIO_MODE0; + Config[i].MinPioCycleTime = AtapModeToCycleTime[PIO_MODE(0)]; + Config[i].MinSwDmaCycleTime = AtapModeToCycleTime[SWDMA_MODE(0)]; + Config[i].MinMwDmaCycleTime = AtapModeToCycleTime[MWDMA_MODE(0)]; + Config[i].IsFixedDisk = TRUE; + Config[i].IoReadySupported = FALSE; + Config[i].IsNewDevice = FALSE; + Config[i].FriendlyName = ""; + } + + /* Call the chipset driver */ + PortData->SetTransferMode(PortData->ChannelContext, DeviceList); + return; + } + + ChanExt = CONTAINING_RECORD(PortData, ATAPORT_CHANNEL_EXTENSION, PortData); + + for (i = 0; i < ATA_MAX_DEVICE; ++i) + { + PATAPORT_DEVICE_EXTENSION DevExt; + ULONG AllowedModesMask; + + DevExt = AtaFdoFindDeviceByPath(ChanExt, + AtaMarshallScsiAddress(PortData->PortNumber, i, 0), + NULL); + if (!DevExt) + continue; + + DevExt->TransferModeCurrentBitmap = 0; + DevExt->TransferModeSupportedBitmap = 0; + + AllowedModesMask = DevExt->TransferModeUserAllowedMask; + AllowedModesMask &= DevExt->TransferModeAllowedMask; + /* Mode cannot be slower than PIO0 */ + AllowedModesMask |= PIO_MODE0; + + AtaTimQueryPioModeSupport(DevExt, AllowedModesMask); + AtaTimQuerySwDmaModeSupport(DevExt, AllowedModesMask); + AtaTimQueryMwDmaModeSupport(DevExt, AllowedModesMask); + AtaTimQueryUDmaModeSupport(DevExt, AllowedModesMask); + + DeviceList[i] = &Config[i]; + Config[i].CurrentModes = DevExt->TransferModeCurrentBitmap; + Config[i].SupportedModes = DevExt->TransferModeSupportedBitmap; + Config[i].MinPioCycleTime = DevExt->MinimumPioCycleTime; + Config[i].MinSwDmaCycleTime = DevExt->MinimumSingleWordDmaCycleTime; + Config[i].MinMwDmaCycleTime = DevExt->MinimumMultiWordDmaCycleTime; + Config[i].IsFixedDisk = !IS_ATAPI(&DevExt->Device); + Config[i].IoReadySupported = !!DevExt->IdentifyDeviceData.Capabilities.IordySupported; + Config[i].IsNewDevice = !(DevExt->Device.DeviceFlags & DEVICE_PNP_STARTED); + Config[i].FriendlyName = DevExt->FriendlyName; + } + + /* Call the chipset driver */ + PortData->SetTransferMode(PortData->ChannelContext, DeviceList); + + /* Save the result */ + for (i = 0; i < ATA_MAX_DEVICE; ++i) + { + PCHANNEL_DEVICE_CONFIG DeviceConfig = DeviceList[i]; + PATAPORT_DEVICE_EXTENSION DevExt; + + if (!DeviceConfig) + continue; + + DevExt = AtaFdoFindDeviceByPath(ChanExt, + AtaMarshallScsiAddress(PortData->PortNumber, i, 0), + NULL); + ASSERT(DevExt); + + DevExt->TransferModeSelectedBitmap = 1 << DeviceConfig->PioMode; + if (DeviceConfig->DmaMode != PIO_MODE(0)) + { + DevExt->TransferModeSelectedBitmap |= 1 << DeviceConfig->DmaMode; + } + + AtaTimDumpTimingInfo(PortData, DevExt); + } +} diff --git a/drivers/storage/ide/atapi/enum.c b/drivers/storage/ide/atapi/enum.c new file mode 100644 index 00000000000..26e81e15f96 --- /dev/null +++ b/drivers/storage/ide/atapi/enum.c @@ -0,0 +1,658 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: ATA bus enumeration + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* FUNCTIONS ******************************************************************/ + +PUCHAR +AtaCopyIdStringUnsafe( + _Out_writes_bytes_all_(Length) PUCHAR Destination, + _In_reads_bytes_(Length) PUCHAR Source, + _In_ ULONG Length) +{ + ULONG i; + + ASSUME((Length >= sizeof(USHORT)) && (Length % sizeof(USHORT)) == 0); + + /* Copy the ATA string and swap it */ + for (i = 0; i < Length; i += sizeof(USHORT)) + { + Destination[i] = Source[i + 1]; + Destination[i + 1] = Source[i]; + } + + return &Destination[i - 1]; +} + +PCHAR +AtaCopyIdStringSafe( + _Out_writes_bytes_all_(MaxLength) PCHAR Destination, + _In_reads_bytes_(MaxLength) PUCHAR Source, + _In_ ULONG MaxLength, + _In_ CHAR DefaultCharacter) +{ + PCHAR Dest = Destination; + + PAGED_CODE(); + + while (MaxLength != 0) + { + const UCHAR Char = *Source; + + /* Only characters from space to tilde are allowed in an ID */ + if (Char > ' ' && Char <= '~' && Char != ',') + *Dest = Char; + else + *Dest = DefaultCharacter; + + ++Source; + ++Dest; + --MaxLength; + } + + return Dest; +} + +VOID +AtaSwapIdString( + _Inout_updates_bytes_(WordCount * sizeof(USHORT)) PVOID Buffer, + _In_range_(>, 0) ULONG WordCount) +{ + PUSHORT Word = Buffer; + + /* The buffer should be USHORT aligned for ARM compatibility */ + ASSERT(((ULONG_PTR)Buffer & 1) == 0); + + while (WordCount--) + { + *Word = RtlUshortByteSwap(*Word); + ++Word; + } +} + +static +CODE_SEG("PAGE") +PCHAR +AtaTrimIdString( + _In_ _Post_z_ PCHAR Start, + _In_ PCHAR End) +{ + PCHAR Current = End - 1; + + PAGED_CODE(); + + /* Remove trailing spaces */ + while (Current >= Start && *Current == ' ') + { + --Current; + } + Current[1] = ANSI_NULL; + + return (Current + 1); +} + +static +CODE_SEG("PAGE") +VOID +AtaPdoFillIdentificationStrings( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PIDENTIFY_DEVICE_DATA IdentifyData = &DevExt->IdentifyDeviceData; + PCHAR End; + ULONG i; + NTSTATUS Status; + size_t Remaining; + + PAGED_CODE(); + + /* + * For ATAPI devices inquiry data is preferred over identify data. + * ATA strings are *not* byte-swapped for early ATAPI drives (NEC CDR-260, etc.). + */ + if (IS_ATAPI(&DevExt->Device)) + { + /* Combine VendorId and ProductId, separated by a space */ + End = AtaCopyIdStringSafe(DevExt->FriendlyName, + DevExt->InquiryData.VendorId, + RTL_FIELD_SIZE(INQUIRYDATA, VendorId), + ' '); + End = AtaTrimIdString(DevExt->FriendlyName, End); + *End++ = ' '; + End = AtaCopyIdStringSafe(End, + DevExt->InquiryData.ProductId, + RTL_FIELD_SIZE(INQUIRYDATA, ProductId), + ' '); + AtaTrimIdString(DevExt->FriendlyName, End); + + /* Copy ProductRevisionLevel */ + End = AtaCopyIdStringSafe(DevExt->RevisionNumber, + DevExt->InquiryData.ProductRevisionLevel, + RTL_FIELD_SIZE(INQUIRYDATA, ProductRevisionLevel), + ' '); + AtaTrimIdString(DevExt->RevisionNumber, End); + } + else + { + /* Copy ModelNumber from a byte-swapped ATA string */ + End = AtaCopyIdStringSafe(DevExt->FriendlyName, + IdentifyData->ModelNumber, + ATAPORT_FN_FIELD, + ' '); + AtaSwapIdString(DevExt->FriendlyName, ATAPORT_FN_FIELD / 2); + AtaTrimIdString(DevExt->FriendlyName, End); + + /* Copy FirmwareRevision from a byte-swapped ATA string */ + End = AtaCopyIdStringSafe(DevExt->RevisionNumber, + IdentifyData->FirmwareRevision, + ATAPORT_RN_FIELD, + ' '); + AtaSwapIdString(DevExt->RevisionNumber, ATAPORT_RN_FIELD / 2); + AtaTrimIdString(DevExt->RevisionNumber, End); + } + + End = DevExt->SerialNumber; + Remaining = sizeof(DevExt->SerialNumber); + + /* Format the serial number */ + for (i = 0; i < sizeof(IdentifyData->SerialNumber); ++i) + { + Status = RtlStringCchPrintfExA(End, + Remaining, + &End, + &Remaining, + 0, + "%2x", + IdentifyData->SerialNumber[i]); + ASSERT(NT_SUCCESS(Status)); + } + + INFO("FriendlyName: '%s'\n", DevExt->FriendlyName); + INFO("RevisionNumber: '%s'\n", DevExt->RevisionNumber); + INFO("SerialNumber: '%s'\n", DevExt->SerialNumber); +} + +VOID +AtaDeviceSetAddressingMode( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PIDENTIFY_DEVICE_DATA IdentifyData = &DevExt->IdentifyDeviceData; + ULONG64 TotalSectors; + + DevExt->Device.DeviceFlags &= ~(DEVICE_LBA_MODE | DEVICE_LBA48 | DEVICE_HAS_FUA); + + /* Using LBA addressing mode */ + if (AtaDevHasLbaTranslation(IdentifyData)) + { + DevExt->Device.DeviceFlags |= DEVICE_LBA_MODE; + + if (AtaDevHas48BitAddressFeature(IdentifyData)) + { + /* Using LBA48 addressing mode */ + TotalSectors = AtaDevUserAddressableSectors48Bit(IdentifyData); + ASSERT(TotalSectors <= ATA_MAX_LBA_48); + + DevExt->Device.DeviceFlags |= DEVICE_LBA48; + + if (AtaDevHasForceUnitAccessCommands(IdentifyData)) + DevExt->Device.DeviceFlags |= DEVICE_HAS_FUA; + } + else + { + /* Using LBA28 addressing mode */ + TotalSectors = AtaDevUserAddressableSectors28Bit(IdentifyData); + ASSERT(TotalSectors <= ATA_MAX_LBA_28); + } + } + else + { + USHORT Cylinders, Heads, SectorsPerTrack; + + /* Using CHS addressing mode */ + if (AtaDevIsCurrentGeometryValid(IdentifyData)) + { + AtaDevCurrentChsTranslation(IdentifyData, &Cylinders, &Heads, &SectorsPerTrack); + } + else + { + AtaDevDefaultChsTranslation(IdentifyData, &Cylinders, &Heads, &SectorsPerTrack); + } + DevExt->Device.Cylinders = Cylinders; + DevExt->Device.Heads = Heads; + DevExt->Device.SectorsPerTrack = SectorsPerTrack; + + TotalSectors = (ULONG64)Cylinders * Heads * SectorsPerTrack; + } + + /* + * The sector count can be 0 on faulty devices. It's better to keep + * them available in the system to allow + * the user to send any command to the drive through the pass-through interface. + */ + if (TotalSectors == 0) + { + ERR("Unknown geometry\n"); + + /* Fix up sector count for the READ CAPACITY command */ + TotalSectors = 1; + + /* Avoid dividing by zero in READ/WRITE commands */ + DevExt->Device.SectorsPerTrack = 1; + DevExt->Device.Heads = 1; + } + + DevExt->Device.TotalSectors = TotalSectors; + + DevExt->Device.SectorSize = AtaDevBytesPerLogicalSector(IdentifyData); + ASSERT(DevExt->Device.SectorSize >= ATA_MIN_SECTOR_SIZE); + DevExt->Device.SectorSize = max(DevExt->Device.SectorSize, ATA_MIN_SECTOR_SIZE); + + INFO("Total sectors %I64u of size %lu, CHS %u:%u:%u\n", + DevExt->Device.TotalSectors, + DevExt->Device.SectorSize, + DevExt->Device.Cylinders, + DevExt->Device.Heads, + DevExt->Device.SectorsPerTrack); +} + +static +CODE_SEG("PAGE") +VOID +AtaDeviceEnableQueuedCommands( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + ULONG DeviceQueueDepth; + + PAGED_CODE(); + + DeviceQueueDepth = AtaDevQueueDepth(&DevExt->IdentifyDeviceData); + if (DeviceQueueDepth == 0) + return; + + /* Do not set the queue depth to larger than the HBA can handle */ + DeviceQueueDepth = min(DeviceQueueDepth, DevExt->Device.PortData->QueueDepth); + + DevExt->Device.TransportFlags |= DeviceQueueDepth << DEVICE_QUEUE_DEPTH_SHIFT; + + DevExt->Device.DeviceFlags |= DEVICE_NCQ; + + INFO("NCQ enabled, queue depth %lu/%lu\n", + DeviceQueueDepth, + DevExt->Device.PortData->QueueDepth); +} + +static +CODE_SEG("PAGE") +BOOLEAN +AtaDeviceIsSuperFloppy( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PAGED_CODE(); + + if (DevExt->InquiryData.DeviceType == DIRECT_ACCESS_DEVICE) + { + /* Look for ATAPI SuperDisk drives. For example, 'MATSHITA LS-120 COSM 03' */ + return (strstr(DevExt->FriendlyName, " LS-120") || + strstr(DevExt->FriendlyName, " LS-240")); + } + + return FALSE; +} + +static +CODE_SEG("PAGE") +VOID +AtaPdoInit( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PAGED_CODE(); + + AtaPdoFillIdentificationStrings(DevExt); + + if (IS_ATAPI(&DevExt->Device)) + { + if (AtaDeviceIsSuperFloppy(DevExt)) + DevExt->Device.DeviceFlags |= DEVICE_IS_SUPER_FLOPPY; + + if (AtaDevIsTape(&DevExt->IdentifyPacketData)) + { + INFO("Tape drive detected '%s'\n", DevExt->FriendlyName); + } + } + else + { + if (ChanExt->PortData.PortFlags & PORT_FLAG_NCQ) + AtaDeviceEnableQueuedCommands(DevExt); + + AtaDeviceSetAddressingMode(DevExt); + AtaCreateStandardInquiryData(DevExt); + } + + if (DevExt->Device.AtaScsiAddress.Lun == 0) + { + AtaSetRegistryKey(ChanExt, + DevExt->Device.AtaScsiAddress.TargetId, + DD_ATA_REG_ATA_DEVICE_TYPE, + DevExt->DeviceType); + + AtaSetRegistryKey(ChanExt, + DevExt->Device.AtaScsiAddress.TargetId, + DD_ATA_REG_SCSI_DEVICE_TYPE, + DevExt->InquiryData.DeviceType); + + AtaGetRegistryKey(ChanExt, + DevExt->Device.AtaScsiAddress.TargetId, + DD_ATA_REG_XFER_MODE_ALLOWED, + &DevExt->TransferModeUserAllowedMask, + MAXULONG); + } + + /* Will be unlocked upon PnP START IRP */ + AtaReqFreezeQueue(DevExt, QUEUE_FLAG_FROZEN_PNP); + + if (!AtaDevIsSsd(&DevExt->IdentifyDeviceData)) + { + /* The spindle motor requires an inrush of power at start-up */ + DevExt->Common.Self->Flags |= DO_POWER_INRUSH; + } + if (DevExt->InquiryData.RemovableMedia) + { + /* Distinguish between fixed and removable drives (e.g. CFA media) */ + DevExt->Common.Self->Characteristics |= FILE_REMOVABLE_MEDIA; + } + DevExt->Common.Self->Flags &= ~DO_DEVICE_INITIALIZING; + + DevExt->Device.DeviceFlags &= ~DEVICE_UNINITIALIZED; +} + +static +CODE_SEG("PAGE") +ULONG +AtaFdoQueryDeviceCount( + _In_ PATAPORT_PORT_DATA PortData) +{ + PAGED_CODE(); + + KeClearEvent(&PortData->Worker.EnumerationEvent); + AtaDeviceQueueEvent(PortData, NULL, ACTION_ENUM_PORT); + KeWaitForSingleObject(&PortData->Worker.EnumerationEvent, + Executive, + KernelMode, + FALSE, + NULL); + return PortData->Worker.DeviceCount; +} + +static +CODE_SEG("PAGE") +ATA_DEVICE_STATUS +AtaFdoQueryDeviceStatus( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ATA_PORT_ACTION Action) +{ + PAGED_CODE(); + + KeClearEvent(&DevExt->Worker.EnumerationEvent); + AtaDeviceQueueEvent(PortData, DevExt, Action); + KeWaitForSingleObject(&DevExt->Worker.EnumerationEvent, + Executive, + KernelMode, + FALSE, + NULL); + return DevExt->Worker.EnumStatus; +} + +static +CODE_SEG("PAGE") +VOID +AtaFdoEnumeratePort( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt) +{ + PATAPORT_PORT_DATA PortData = &ChanExt->PortData; + ULONG i, DeviceCount; + BOOLEAN DiscoveredNewDevice = FALSE; + + PAGED_CODE(); + + DeviceCount = AtaFdoQueryDeviceCount(PortData); + + for (i = 0; i < DeviceCount; ++i) + { + PATAPORT_DEVICE_EXTENSION DevExt; + ATA_SCSI_ADDRESS AtaScsiAddress; + ATA_DEVICE_STATUS EnumStatus; + + AtaScsiAddress = AtaMarshallScsiAddress(PortData->PortNumber, i, 0); + + /* Try to find an existing device */ + DevExt = AtaFdoFindDeviceByPath(ChanExt, AtaScsiAddress, NULL); + if (!DevExt) + { + DevExt = AtaPdoCreateDevice(ChanExt, AtaScsiAddress); + if (!DevExt) + { + /* We are out of memory, trying to continue process the QBR IRP anyway */ + ERR("Failed to allocate PDO extension\n"); + continue; + } + + /* Query the last known device type */ + AtaGetRegistryKey(ChanExt, + i, + DD_ATA_REG_ATA_DEVICE_TYPE, + &DevExt->DeviceType, + DEV_NONE); + + PortData->Worker.EnumDevExt = DevExt; + EnumStatus = AtaFdoQueryDeviceStatus(PortData, DevExt, ACTION_ENUM_DEVICE_NEW); + } + else + { + EnumStatus = AtaFdoQueryDeviceStatus(PortData, DevExt, ACTION_ENUM_DEVICE); + } + + /* Determine the type of the device */ + switch (EnumStatus) + { + /* No device present at this SCSI address */ + case DEV_STATUS_FAILED: + case DEV_STATUS_NO_DEVICE: + { + if (DevExt->Device.DeviceFlags & DEVICE_UNINITIALIZED) + AtaPdoFreeDevice(DevExt); + continue; + } + + /* It's the same device still */ + case DEV_STATUS_SAME_DEVICE: + { + ASSERT(!(DevExt->Device.DeviceFlags & DEVICE_UNINITIALIZED)); + + DevExt->NotPresent = FALSE; + continue; + } + + /* At this point, we assume that the drive is a new device */ + case DEV_STATUS_NEW_DEVICE: + { + /* Device type has changed because of a hot-plug event */ + if (!(DevExt->Device.DeviceFlags & DEVICE_UNINITIALIZED)) + { + /* Create a new PDO for the newly hotplugged device */ + --i; // Retry + continue; + } + break; + } + + default: + ASSERT(FALSE); + UNREACHABLE; + } + + AtaPdoInit(ChanExt, DevExt); + AtaFdoDeviceListInsert(ChanExt, DevExt, TRUE); + + DiscoveredNewDevice = TRUE; + } + + /* Prepare the channel for a new device */ + if (DiscoveredNewDevice) + AtaDeviceQueueEvent(PortData, NULL, ACTION_PORT_TIMING); +} + +static +CODE_SEG("PAGE") +VOID +AtaFdoInitializeDeviceRelations( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PDEVICE_RELATIONS DeviceRelations) +{ + PATAPORT_DEVICE_EXTENSION DevExt; + ATA_SCSI_ADDRESS AtaScsiAddress; + + PAGED_CODE(); + + DeviceRelations->Count = 0; + + AtaScsiAddress.AsULONG = 0; + while (TRUE) + { + DevExt = AtaFdoFindNextDeviceByPath(ChanExt, &AtaScsiAddress, TRUE, NULL); + if (!DevExt) + break; + + if (DevExt->NotPresent) + { + if (AtaScsiAddress.Lun == 0) + { + AtaSetRegistryKey(ChanExt, + AtaScsiAddress.TargetId, + DD_ATA_REG_ATA_DEVICE_TYPE, + DEV_NONE); + } + + DevExt->ReportedMissing = TRUE; + continue; + } + + if (AtaScsiAddress.Lun == 0) + { + AtaSetRegistryKey(ChanExt, AtaScsiAddress.TargetId, + DD_ATA_REG_XFER_MODE_SUPPORTED, + DevExt->TransferModeSupportedBitmap); + + AtaSetRegistryKey(ChanExt, + AtaScsiAddress.TargetId, + DD_ATA_REG_XFER_MODE_SELECTED, + DevExt->TransferModeSelectedBitmap); + } + + DeviceRelations->Objects[DeviceRelations->Count++] = DevExt->Common.Self; + ObReferenceObject(DevExt->Common.Self); + } +} + +CODE_SEG("PAGE") +NTSTATUS +AtaFdoQueryBusRelations( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PIRP Irp) +{ + PDEVICE_RELATIONS DeviceRelations = NULL; + ULONG Size, PdoCount; + ATA_SCSI_ADDRESS AtaScsiAddress; +#if DBG + LARGE_INTEGER TimeStart, TimeFinish; + ULONG EnumTimeMs; +#endif + + PAGED_CODE(); + +#if DBG + KeQuerySystemTime(&TimeStart); +#endif + + AtaScsiAddress.AsULONG = 0; + while (TRUE) + { + PATAPORT_DEVICE_EXTENSION DevExt; + + DevExt = AtaFdoFindNextDeviceByPath(ChanExt, &AtaScsiAddress, TRUE, NULL); + if (!DevExt) + break; + + DevExt->NotPresent = TRUE; + + if (DevExt->Device.AtaScsiAddress.Lun == 0) + { + AtaGetRegistryKey(ChanExt, + DevExt->Device.AtaScsiAddress.TargetId, + DD_ATA_REG_XFER_MODE_ALLOWED, + &DevExt->TransferModeUserAllowedMask, + MAXULONG); + } + } + + AtaFdoEnumeratePort(ChanExt); + + PdoCount = 0; + AtaScsiAddress.AsULONG = 0; + while (TRUE) + { + PATAPORT_DEVICE_EXTENSION DevExt; + + DevExt = AtaFdoFindNextDeviceByPath(ChanExt, &AtaScsiAddress, TRUE, NULL); + if (!DevExt) + break; + + if (!DevExt->NotPresent) + ++PdoCount; + } + + Size = FIELD_OFFSET(DEVICE_RELATIONS, Objects[PdoCount]); + DeviceRelations = ExAllocatePoolUninitialized(PagedPool, Size, ATAPORT_TAG); + if (!DeviceRelations) + { + ERR("Failed to allocate device relations\n"); + goto Cleanup; + } + + AtaFdoInitializeDeviceRelations(ChanExt, DeviceRelations); + +#if DBG + KeQuerySystemTime(&TimeFinish); + EnumTimeMs = (TimeFinish.QuadPart - TimeStart.QuadPart) / 10000; + if (EnumTimeMs >= 5000) + { + WARN("%lu: QBR request took %lu ms, %lu devices\n", + ChanExt->ScsiPortNumber, + EnumTimeMs, + DeviceRelations->Count); + } + else + { + INFO("%lu: QBR request took %lu ms, %lu devices\n", + ChanExt->ScsiPortNumber, + EnumTimeMs, + DeviceRelations->Count); + } +#endif + + Irp->IoStatus.Information = (ULONG_PTR)DeviceRelations; + return STATUS_SUCCESS; + +Cleanup: + if (DeviceRelations) + ExFreePoolWithTag(DeviceRelations, ATAPORT_TAG); + + return STATUS_INSUFFICIENT_RESOURCES; +} diff --git a/drivers/storage/ide/atapi/fdo.c b/drivers/storage/ide/atapi/fdo.c new file mode 100644 index 00000000000..9c38ccd1d00 --- /dev/null +++ b/drivers/storage/ide/atapi/fdo.c @@ -0,0 +1,758 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: ATA channel device object (FDO) dispatch routines + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* GLOBALS ********************************************************************/ + +DECLARE_PAGED_WSTRING(AtapDevSymLinkFormat, L"\\Device\\ScsiPort%lu"); +DECLARE_PAGED_WSTRING(AtapDosSymLinkFormat, L"\\DosDevices\\Scsi%lu:"); + +/* FUNCTIONS ******************************************************************/ + +static +CODE_SEG("PAGE") +NTSTATUS +AtaFdoCreateSymLinks( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt) +{ + UNICODE_STRING ChannelName; + ULONG ScsiAdapter, ScsiPortCount; + WCHAR ChannelNameBuffer[sizeof("\\Device\\Ide\\IdePort99999")]; + NTSTATUS Status; + DECLARE_PAGED_WSTRING(FdoFormat, L"\\Device\\Ide\\IdePort%lu"); + + PAGED_CODE(); + + if (ChanExt->PortData.PortFlags & PORT_FLAG_SYMLINK_CREATED) + return STATUS_SUCCESS; + + Status = RtlStringCbPrintfW(ChannelNameBuffer, + sizeof(ChannelNameBuffer), + FdoFormat, + ChanExt->DeviceObjectNumber); + ASSERT(NT_SUCCESS(Status)); + RtlInitUnicodeString(&ChannelName, ChannelNameBuffer); + + ScsiPortCount = IoGetConfigurationInformation()->ScsiPortCount; + + /* Search for a free SCSI port adapter in the system */ + for (ScsiAdapter = 0; ScsiAdapter <= ScsiPortCount; ++ScsiAdapter) + { + WCHAR SymLinkNameBuffer[sizeof("\\DosDevices\\Scsi99999:")]; + UNICODE_STRING SymLinkName; + + Status = RtlStringCbPrintfW(SymLinkNameBuffer, + sizeof(SymLinkNameBuffer), + AtapDevSymLinkFormat, + ScsiAdapter); + ASSERT(NT_SUCCESS(Status)); + RtlInitUnicodeString(&SymLinkName, SymLinkNameBuffer); + + /* Create a symbolic link '\Device\ScsiPortX' -> '\Device\Ide\IdePortN' */ + Status = IoCreateSymbolicLink(&SymLinkName, &ChannelName); + if (!NT_SUCCESS(Status)) + continue; + + INFO("Symlink created '%wZ' -> '%wZ'\n", &SymLinkName, &ChannelName); + + Status = RtlStringCbPrintfW(SymLinkNameBuffer, + sizeof(SymLinkNameBuffer), + AtapDosSymLinkFormat, + ScsiAdapter); + ASSERT(NT_SUCCESS(Status)); + RtlInitUnicodeString(&SymLinkName, SymLinkNameBuffer); + + /* Create a symbolic link '\DosDevices\ScsiX:' -> '\Device\Ide\IdePortN' */ + Status = IoCreateSymbolicLink(&SymLinkName, &ChannelName); + if (NT_SUCCESS(Status)) + { + INFO("Symlink created '%wZ' -> '%wZ'\n", &SymLinkName, &ChannelName); + } + + /* Register ourselves (ATA channel) as a SCSI port adapter */ + IoGetConfigurationInformation()->ScsiPortCount++; + + ChanExt->ScsiPortNumber = ScsiAdapter; + ChanExt->PortData.PortFlags |= PORT_FLAG_SYMLINK_CREATED; + break; + } + + return Status; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaFdoAllocateLocalBuffer( + _In_ PATAPORT_PORT_DATA PortData) +{ + PHYSICAL_ADDRESS PhysicalAddress; + PHYSICAL_ADDRESS HighestAcceptableAddress; + PHYSICAL_ADDRESS LowestAcceptableAddress; + PHYSICAL_ADDRESS BoundaryAddressMultiple; + + PAGED_CODE(); + + if (PortData->LocalBuffer) + return STATUS_SUCCESS; + + LowestAcceptableAddress.QuadPart = 0; + HighestAcceptableAddress.QuadPart = 0xFFFFFFFF; // 32-bit DMA + BoundaryAddressMultiple.QuadPart = 0x10000; // 64k, for PATA compability + PortData->LocalBuffer = MmAllocateContiguousMemorySpecifyCache(ATA_LOCAL_BUFFER_SIZE, + LowestAcceptableAddress, + HighestAcceptableAddress, + BoundaryAddressMultiple, + MmNonCached); + if (!PortData->LocalBuffer) + return STATUS_INSUFFICIENT_RESOURCES; + + PhysicalAddress = MmGetPhysicalAddress(PortData->LocalBuffer); + + PortData->LocalSgList.NumberOfElements = 1; + PortData->LocalSgList.Elements[0].Length = ATA_LOCAL_BUFFER_SIZE; + PortData->LocalSgList.Elements[0].Address.QuadPart = PhysicalAddress.QuadPart; + + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaFdoCreatePortThread( + _In_ PATAPORT_PORT_DATA PortData) +{ + OBJECT_ATTRIBUTES ObjectAttributes = RTL_CONSTANT_OBJECT_ATTRIBUTES(NULL, OBJ_KERNEL_HANDLE); + HANDLE ThreadHandle; + NTSTATUS Status; + + PAGED_CODE(); + + if (PortData->Worker.Thread) + return STATUS_SUCCESS; + + KeInitializeEvent(&PortData->Worker.ThreadEvent, NotificationEvent, FALSE); + + Status = PsCreateSystemThread(&ThreadHandle, + THREAD_ALL_ACCESS, + &ObjectAttributes, + NULL, + NULL, + AtaPortWorkerThread, + PortData); + if (!NT_SUCCESS(Status)) + return Status; + + Status = ObReferenceObjectByHandle(ThreadHandle, + THREAD_ALL_ACCESS, + NULL, + KernelMode, + (PVOID*)&PortData->Worker.Thread, + NULL); + if (!NT_SUCCESS(Status)) + return Status; + + ZwClose(ThreadHandle); + + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +VOID +AtaFdoDestroyPortThread( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt) +{ + PATAPORT_PORT_DATA PortData = &ChanExt->PortData; + + PAGED_CODE(); + + if (!PortData->Worker.Thread) + return; + + PortData->PortFlags |= PORT_FLAG_EXIT_THREAD; + AtaPortSignalWorkerThread(PortData); + + KeWaitForSingleObject(PortData->Worker.Thread, Executive, KernelMode, FALSE, NULL); + + ObDereferenceObject(PortData->Worker.Thread); + + PortData->Worker.Thread = NULL; +} + +static +CODE_SEG("PAGE") +VOID +AtaFdoRemoveSymLinks( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt) +{ + NTSTATUS Status; + UNICODE_STRING SymLinkName; + WCHAR SymLinkNameBuffer[sizeof("\\DosDevices\\Scsi99999:")]; + + PAGED_CODE(); + + if (!(ChanExt->PortData.PortFlags & PORT_FLAG_SYMLINK_CREATED)) + return; + + /* Delete the '\DosDevices\\ScsiX:' symbolic link */ + Status = RtlStringCbPrintfW(SymLinkNameBuffer, + sizeof(SymLinkNameBuffer), + AtapDevSymLinkFormat, + ChanExt->ScsiPortNumber); + ASSERT(NT_SUCCESS(Status)); + RtlInitUnicodeString(&SymLinkName, SymLinkNameBuffer); + (VOID)IoDeleteSymbolicLink(&SymLinkName); + + /* Delete the '\Device\\ScsiPortX' symbolic link */ + Status = RtlStringCbPrintfW(SymLinkNameBuffer, + sizeof(SymLinkNameBuffer), + AtapDosSymLinkFormat, + ChanExt->ScsiPortNumber); + ASSERT(NT_SUCCESS(Status)); + RtlInitUnicodeString(&SymLinkName, SymLinkNameBuffer); + (VOID)IoDeleteSymbolicLink(&SymLinkName); + + /* Unregister the SCSI port adapter */ + IoGetConfigurationInformation()->ScsiPortCount--; +} + +CODE_SEG("PAGE") +NTSTATUS +AtaFdoStartDevice( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PCM_RESOURCE_LIST ResourcesTranslated) +{ + PATAPORT_PORT_DATA PortData = &ChanExt->PortData; + PCIIDEX_CHANNEL_INTERFACE ChannelInterface = { 0 }; + NTSTATUS Status; + + PAGED_CODE(); + + INFO("Starting channel %lu\n", ChanExt->DeviceObjectNumber); + + /* Get the interface of ATA channel */ + Status = AtaPnpQueryInterface(&ChanExt->Common, + &GUID_PCIIDE_INTERFACE_ROS, + &ChannelInterface, + PCIIDEX_INTERFACE_VERSION, + sizeof(ChannelInterface)); + if (!NT_SUCCESS(Status)) + { + ERR("Failed to query channel interface %lx\n", Status); + return Status; + } + PortData->ChannelContext = ChannelInterface.ChannelContext; + PortData->AttachChannel = ChannelInterface.AttachChannel; + PortData->SetDeviceData = ChannelInterface.SetDeviceData; + PortData->GetInitTaskFile = ChannelInterface.GetInitTaskFile; + PortData->DowngradeInterfaceSpeed = ChannelInterface.DowngradeInterfaceSpeed; + PortData->InterruptObject = ChannelInterface.InterruptObject; + PortData->AbortChannel = ChannelInterface.AbortChannel; + PortData->ResetChannel = ChannelInterface.ResetChannel; + PortData->EnumerateChannel = ChannelInterface.EnumerateChannel; + PortData->IdentifyDevice = ChannelInterface.IdentifyDevice; + PortData->SetTransferMode = ChannelInterface.SetTransferMode; + PortData->AllocateSlot = ChannelInterface.AllocateSlot; + PortData->PreparePrdTable = ChannelInterface.PreparePrdTable; + PortData->PrepareIo = ChannelInterface.PrepareIo; + PortData->StartIo = ChannelInterface.StartIo; + PortData->MaxTargetId = ChannelInterface.MaxTargetId; + PortData->MaximumTransferLength = ChannelInterface.MaximumTransferLength; + PortData->MaximumPhysicalPages = ChannelInterface.MaximumPhysicalPages; + PortData->QueueDepth = ChannelInterface.QueueDepth; + PortData->PortNumber = ChannelInterface.Channel; + PortData->DmaAdapter = ChannelInterface.DmaAdapter; + PortData->ChannelObject = ChannelInterface.ChannelObject; + + PortData->InterruptFlags = PORT_INT_FLAG_IS_IO_ACTIVE; + PortData->FreeSlotsBitmap = + PortData->MaxSlotsBitmap = NUM_TO_BITMAP(PortData->QueueDepth); + /* We need the slot numbers to start from zero */ + PortData->LastUsedSlot = RTL_BITS_OF(ULONG) - 1; + + if (!(ChannelInterface.TransferModeSupported & ~PIO_ALL)) + PortData->PortFlags |= PORT_FLAG_PIO_ONLY; + + if (ChannelInterface.Flags & ATA_CHANNEL_FLAG_PIO_VIA_DMA) + PortData->PortFlags |= PORT_FLAG_PIO_VIA_DMA; + + if (ChannelInterface.Flags & ATA_CHANNEL_FLAG_NCQ) + PortData->PortFlags |= PORT_FLAG_NCQ; + + if (ChannelInterface.Flags & ATA_CHANNEL_FLAG_IS_AHCI) + PortData->PortFlags |= PORT_FLAG_IS_AHCI; + + if (ChannelInterface.Flags & ATA_CHANNEL_FLAG_IS_EXTERNAL) + PortData->PortFlags |= PORT_FLAG_IS_EXTERNAL; + + if (ChannelInterface.Flags & ATA_CHANNEL_FLAG_PIO_FOR_LBA48_XFER) + PortData->PortFlags |= PORT_FLAG_PIO_FOR_LBA48_XFER; + + if (ChannelInterface.HwSyncObject) + { + PortData->PortFlags |= PORT_FLAG_IS_SIMPLEX; + PortData->HwSyncObject = ChannelInterface.HwSyncObject; + } + + /* Reserve PIO memory resources early. Storage drivers should not fail paging I/O operations */ + if (!(ChannelInterface.Flags & ATA_CHANNEL_FLAG_PIO_VIA_DMA) && !PortData->ReservedVaSpace) + { + PortData->ReservedVaSpace = MmAllocateMappingAddress(ATA_RESERVED_PAGES * PAGE_SIZE, + ATAPORT_TAG); + } + + Status = AtaFdoAllocateLocalBuffer(PortData); + if (!NT_SUCCESS(Status)) + { + ERR("CH %lu: Failed to allocate local buffer 0x%lx\n", PortData->PortNumber, Status); + return Status; + } + + InitializeListHead(&PortData->PortQueueList); + KeInitializeEvent(&PortData->QueueStoppedEvent, NotificationEvent, FALSE); + + KeInitializeDpc(&PortData->Worker.Dpc, AtaPortWorkerSignalDpc, PortData); + KeInitializeDpc(&PortData->Worker.NotificationDpc, AtaStorageNotificationlDpc, PortData); + KeInitializeSpinLock(&PortData->Worker.Lock); + KeInitializeEvent(&PortData->Worker.EnumerationEvent, NotificationEvent, FALSE); + + KeInitializeEvent(&PortData->Worker.CompletionEvent, NotificationEvent, FALSE); + PortData->Worker.InternalRequest.Complete = AtaPortCompleteInternalRequest; +#if DBG + PortData->Worker.InternalRequest.Signature = ATA_DEVICE_REQUEST_SIGNATURE; +#endif + + Status = AtaFdoCreatePortThread(PortData); + if (!NT_SUCCESS(Status)) + { + ERR("CH %lu: Failed to create port thread 0x%lx\n", PortData->PortNumber, Status); + return Status; + } + + Status = AtaFdoCreateSymLinks(ChanExt); + if (!NT_SUCCESS(Status)) + { + ERR("CH %lu: Failed to create symbolic links 0x%lx\n", PortData->PortNumber, Status); + return Status; + } + + AtaSetPortRegistryKey(ChanExt, DD_ATA_REG_MAX_TARGET_ID, PortData->MaxTargetId); + + Status = IoRegisterDeviceInterface(ChanExt->Common.Self, + &GUID_DEVINTERFACE_STORAGEPORT, + NULL, + &ChanExt->StorageInterfaceName); + if (NT_SUCCESS(Status)) + { + INFO("InterfaceName: '%wZ'\n", &ChanExt->StorageInterfaceName); + + Status = IoSetDeviceInterfaceState(&ChanExt->StorageInterfaceName, TRUE); + if (!NT_SUCCESS(Status)) + { + RtlFreeUnicodeString(&ChanExt->StorageInterfaceName); + ChanExt->StorageInterfaceName.Buffer = NULL; + } + } + + *ChannelInterface.PortContext = PortData; + *ChannelInterface.PortNotification = AtaPortNotification; + *ChannelInterface.Slots = PortData->Slots; + + Status = IoInitializeTimer(ChanExt->Common.Self, AtaPortIoTimer, PortData); + if (!NT_SUCCESS(Status)) + return Status; + + IoStartTimer(ChanExt->Common.Self); + PortData->PortFlags |= PORT_FLAG_IO_TIMER_ACTIVE; + + Status = PortData->AttachChannel(PortData->ChannelContext, TRUE); + if (!NT_SUCCESS(Status)) + { + ERR("CH %lu: Failed to attach channel %lx\n", PortData->PortNumber, Status); + return Status; + } + PortData->PortFlags |= PORT_FLAG_CHANNEL_ATTACHED; + + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +VOID +AtaFdoDetachChannel( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt) +{ + PATAPORT_PORT_DATA PortData = &ChanExt->PortData; + + PAGED_CODE(); + + if (PortData->AttachChannel) + { + PortData->AttachChannel(PortData->ChannelContext, FALSE); + PortData->AttachChannel = NULL; + } +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaFdoStopDevice( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PIRP Irp) +{ + PAGED_CODE(); + + AtaFdoDetachChannel(ChanExt); + + if (ChanExt->StorageInterfaceName.Buffer) + IoSetDeviceInterfaceState(&ChanExt->StorageInterfaceName, FALSE); + + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaFdoRemoveDevice( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PIRP Irp, + _In_ BOOLEAN FinalRemove) +{ + NTSTATUS Status; + + PAGED_CODE(); + + if (ChanExt->StorageInterfaceName.Buffer) + { + IoSetDeviceInterfaceState(&ChanExt->StorageInterfaceName, FALSE); + + RtlFreeUnicodeString(&ChanExt->StorageInterfaceName); + ChanExt->StorageInterfaceName.Buffer = NULL; + } + + AtaFdoDestroyPortThread(ChanExt); + + if (ChanExt->PortData.PortFlags & PORT_FLAG_IO_TIMER_ACTIVE) + { + IoStopTimer(ChanExt->Common.Self); + ChanExt->PortData.PortFlags &= ~PORT_FLAG_IO_TIMER_ACTIVE; + } + + if (ChanExt->PortData.PortFlags & PORT_FLAG_CHANNEL_ATTACHED) + { + AtaFdoDetachChannel(ChanExt); + ChanExt->PortData.PortFlags &= ~PORT_FLAG_CHANNEL_ATTACHED; + } + + if (FinalRemove) + { + ATA_SCSI_ADDRESS AtaScsiAddress; + + IoReleaseRemoveLockAndWait(&ChanExt->Common.RemoveLock, Irp); + + AtaScsiAddress.AsULONG = 0; + while (TRUE) + { + PATAPORT_DEVICE_EXTENSION DevExt; + + DevExt = AtaFdoFindNextDeviceByPath(ChanExt, &AtaScsiAddress, TRUE, NULL); + if (!DevExt) + break; + + AtaReqFlushDeviceQueue(&DevExt->Device); + AtaPdoFreeDevice(DevExt); + } + + AtaFdoRemoveSymLinks(ChanExt); + + if (ChanExt->PortData.LocalBuffer) + { + MmFreeContiguousMemorySpecifyCache(ChanExt->PortData.LocalBuffer, + ATA_LOCAL_BUFFER_SIZE, + MmNonCached); + ChanExt->PortData.LocalBuffer = NULL; + } + + if (ChanExt->PortData.ReservedVaSpace) + { + MmFreeMappingAddress(ChanExt->PortData.ReservedVaSpace, ATAPORT_TAG); + ChanExt->PortData.ReservedVaSpace = NULL; + } + + Irp->IoStatus.Status = STATUS_SUCCESS; + IoSkipCurrentIrpStackLocation(Irp); + Status = IoCallDriver(ChanExt->Common.LowerDeviceObject, Irp); + + IoDetachDevice(ChanExt->Common.LowerDeviceObject); + IoDeleteDevice(ChanExt->Common.Self); + } + else + { + IoSkipCurrentIrpStackLocation(Irp); + Status = IoCallDriver(ChanExt->Common.LowerDeviceObject, Irp); + + IoReleaseRemoveLock(&ChanExt->Common.RemoveLock, Irp); + } + + return Status; +} + +CODE_SEG("PAGE") +NTSTATUS +AtaFdoPnp( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _Inout_ PIRP Irp) +{ + NTSTATUS Status; + PIO_STACK_LOCATION IoStack; + + PAGED_CODE(); + + INFO("(%p, %p) Ch.%lu %s\n", + ChanExt->Common.Self, + Irp, + ChanExt->DeviceObjectNumber, + GetIRPMinorFunctionString(IoGetCurrentIrpStackLocation(Irp)->MinorFunction)); + + Status = IoAcquireRemoveLock(&ChanExt->Common.RemoveLock, Irp); + if (!NT_SUCCESS(Status)) + { + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return Status; + } + + IoStack = IoGetCurrentIrpStackLocation(Irp); + switch (IoStack->MinorFunction) + { + case IRP_MN_START_DEVICE: + { + if (!NT_VERIFY(IoForwardIrpSynchronously(ChanExt->Common.LowerDeviceObject, Irp))) + { + Status = STATUS_UNSUCCESSFUL; + goto CompleteIrp; + } + Status = Irp->IoStatus.Status; + if (!NT_SUCCESS(Status)) + goto CompleteIrp; + + Status = AtaFdoStartDevice(ChanExt, + IoStack->Parameters. + StartDevice.AllocatedResourcesTranslated); + goto CompleteIrp; + } + + case IRP_MN_STOP_DEVICE: + Status = AtaFdoStopDevice(ChanExt, Irp); + break; + + case IRP_MN_REMOVE_DEVICE: + case IRP_MN_SURPRISE_REMOVAL: + return AtaFdoRemoveDevice(ChanExt, + Irp, + (IoStack->MinorFunction == IRP_MN_REMOVE_DEVICE)); + + case IRP_MN_QUERY_PNP_DEVICE_STATE: + Status = AtaPnpQueryPnpDeviceState(&ChanExt->Common, Irp); + break; + + case IRP_MN_QUERY_DEVICE_RELATIONS: + { + if (IoStack->Parameters.QueryDeviceRelations.Type != BusRelations) + break; + + Status = AtaFdoQueryBusRelations(ChanExt, Irp); + if (!NT_SUCCESS(Status)) + goto CompleteIrp; + + Irp->IoStatus.Status = Status; + break; + } + + case IRP_MN_DEVICE_USAGE_NOTIFICATION: + Status = AtaPnpQueryDeviceUsageNotification(&ChanExt->Common, Irp); + break; + + case IRP_MN_QUERY_STOP_DEVICE: + case IRP_MN_QUERY_REMOVE_DEVICE: + case IRP_MN_CANCEL_STOP_DEVICE: + case IRP_MN_CANCEL_REMOVE_DEVICE: + Irp->IoStatus.Status = STATUS_SUCCESS; + break; + + default: + break; + } + + IoSkipCurrentIrpStackLocation(Irp); + Status = IoCallDriver(ChanExt->Common.LowerDeviceObject, Irp); + + IoReleaseRemoveLock(&ChanExt->Common.RemoveLock, Irp); + + return Status; + +CompleteIrp: + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + IoReleaseRemoveLock(&ChanExt->Common.RemoveLock, Irp); + + return Status; +} + +DECLSPEC_NOINLINE_FROM_PAGED +PATAPORT_DEVICE_EXTENSION +AtaFdoFindDeviceByPath( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ ATA_SCSI_ADDRESS AtaScsiAddress, + _In_ PVOID ReferenceTag) +{ + PATAPORT_DEVICE_EXTENSION DevExt, Result = NULL; + PSINGLE_LIST_ENTRY Entry; + KIRQL OldLevel; + NTSTATUS Status; + + KeAcquireSpinLock(&ChanExt->PdoListLock, &OldLevel); + + for (Entry = ChanExt->PdoList.Next; Entry != NULL; Entry = Entry->Next) + { + DevExt = CONTAINING_RECORD(Entry, ATAPORT_DEVICE_EXTENSION, ListEntry); + + if (DevExt->Device.AtaScsiAddress.AsULONG != AtaScsiAddress.AsULONG) + continue; + + if (DevExt->ReportedMissing || DevExt->RemovalPending) + continue; + + if (ReferenceTag) + { + Status = IoAcquireRemoveLock(&DevExt->Common.RemoveLock, ReferenceTag); + if (!NT_SUCCESS(Status)) + break; + } + + Result = DevExt; + break; + } + + KeReleaseSpinLock(&ChanExt->PdoListLock, OldLevel); + + return Result; +} + +DECLSPEC_NOINLINE_FROM_PAGED +PATAPORT_DEVICE_EXTENSION +AtaFdoFindNextDeviceByPath( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _Inout_ PATA_SCSI_ADDRESS AtaScsiAddress, + _In_ BOOLEAN SearchRemoveDev, + _In_ PVOID ReferenceTag) +{ + PATAPORT_DEVICE_EXTENSION DevExt, Result = NULL; + PSINGLE_LIST_ENTRY Entry; + KIRQL OldLevel; + NTSTATUS Status; + + KeAcquireSpinLock(&ChanExt->PdoListLock, &OldLevel); + + for (Entry = ChanExt->PdoList.Next; Entry != NULL; Entry = Entry->Next) + { + DevExt = CONTAINING_RECORD(Entry, ATAPORT_DEVICE_EXTENSION, ListEntry); + + if (DevExt->Device.AtaScsiAddress.AsULONG <= AtaScsiAddress->AsULONG) + continue; + + if (DevExt->ReportedMissing) + continue; + + if (!SearchRemoveDev && DevExt->RemovalPending) + continue; + + *AtaScsiAddress = DevExt->Device.AtaScsiAddress; + + if (ReferenceTag) + { + Status = IoAcquireRemoveLock(&DevExt->Common.RemoveLock, ReferenceTag); + if (!NT_SUCCESS(Status)) + continue; + } + Result = DevExt; + break; + } + + KeReleaseSpinLock(&ChanExt->PdoListLock, OldLevel); + + return Result; +} + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaFdoDeviceListInsert( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ BOOLEAN DoInsert) +{ + PSINGLE_LIST_ENTRY Entry, PrevEntry; + KIRQL OldLevel; + ULONG Address = DevExt->Device.AtaScsiAddress.AsULONG; + + PAGED_CODE(); + + KeAcquireSpinLock(&ChanExt->PdoListLock, &OldLevel); + + for (Entry = ChanExt->PdoList.Next, PrevEntry = NULL; + Entry != NULL; + Entry = Entry->Next) + { + PATAPORT_DEVICE_EXTENSION CurrentDevExt; + + CurrentDevExt = CONTAINING_RECORD(Entry, ATAPORT_DEVICE_EXTENSION, ListEntry); + + if (DoInsert) + { + if (CurrentDevExt->Device.AtaScsiAddress.AsULONG > Address) + break; + } + else + { + if (CurrentDevExt->Device.AtaScsiAddress.AsULONG == Address) + break; + } + + PrevEntry = Entry; + } + + /* The device list is ordered by SCSI address (Path:Target:Lun), smallest first */ + if (PrevEntry) + { + /* Before the current entry */ + if (DoInsert) + { + DevExt->ListEntry.Next = PrevEntry->Next; + PrevEntry->Next = &DevExt->ListEntry; + } + else + { + PrevEntry->Next = DevExt->ListEntry.Next; + } + } + else + { + /* In the beginning */ + if (DoInsert) + PushEntryList(&ChanExt->PdoList, &DevExt->ListEntry); + else + PopEntryList(&DevExt->ListEntry); + } + + KeReleaseSpinLock(&ChanExt->PdoListLock, OldLevel); +} diff --git a/drivers/storage/ide/atapi/ioctl.c b/drivers/storage/ide/atapi/ioctl.c new file mode 100644 index 00000000000..dac08148ae2 --- /dev/null +++ b/drivers/storage/ide/atapi/ioctl.c @@ -0,0 +1,940 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: I/O control handling + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* GLOBALS ********************************************************************/ + +#define AtaVerifyInBuffer(IoStack, Length) \ + ((IoStack)->Parameters.DeviceIoControl.InputBufferLength >= (Length)) + +#define AtaVerifyOutBuffer(IoStack, Length) \ + ((IoStack)->Parameters.DeviceIoControl.OutputBufferLength >= (Length)) + +/* FUNCTIONS ******************************************************************/ + +static +CODE_SEG("PAGE") +BOOLEAN +AtaCheckPropertyQuery( + _Inout_ PIRP Irp, + _In_ ULONG DescriptorSize, + _Out_ NTSTATUS* Status) +{ + PSTORAGE_PROPERTY_QUERY PropertyQuery = Irp->AssociatedIrp.SystemBuffer; + PSTORAGE_DESCRIPTOR_HEADER DescriptorHeader; + PIO_STACK_LOCATION IoStack; + + PAGED_CODE(); + + IoStack = IoGetCurrentIrpStackLocation(Irp); + + if (!AtaVerifyInBuffer(IoStack, sizeof(*PropertyQuery))) + { + *Status = STATUS_INVALID_PARAMETER; + return FALSE; + } + + /* Check the type of a property query */ + if (PropertyQuery->QueryType != PropertyStandardQuery && + PropertyQuery->QueryType != PropertyExistsQuery) + { + *Status = STATUS_NOT_SUPPORTED; + return FALSE; + } + + /* The requested property is supported */ + if (PropertyQuery->QueryType == PropertyExistsQuery) + { + *Status = STATUS_SUCCESS; + return FALSE; + } + + if (!AtaVerifyOutBuffer(IoStack, sizeof(*DescriptorHeader))) + { + *Status = STATUS_INFO_LENGTH_MISMATCH; + return FALSE; + } + + /* Caller can determine required size based upon DescriptorHeader */ + if (!AtaVerifyOutBuffer(IoStack, DescriptorSize)) + { + DescriptorHeader = (PSTORAGE_DESCRIPTOR_HEADER)PropertyQuery; + DescriptorHeader->Version = DescriptorSize; + DescriptorHeader->Size = DescriptorSize; + + Irp->IoStatus.Information = sizeof(*DescriptorHeader); + + *Status = STATUS_SUCCESS; + return FALSE; + } + + return TRUE; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoQueryStorageDeviceProperty( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + PINQUIRYDATA InquiryData = &DevExt->InquiryData; + PSTORAGE_DEVICE_DESCRIPTOR DeviceDescriptor; + NTSTATUS Status; + ULONG DescriptorSize, RawPropertiesLength, Length1, Length2, Length3; + + PAGED_CODE(); + + Length1 = (ULONG)strlen(DevExt->FriendlyName) + sizeof(ANSI_NULL); + Length2 = (ULONG)strlen(DevExt->RevisionNumber) + sizeof(ANSI_NULL); + Length3 = (ULONG)strlen(DevExt->SerialNumber) + sizeof(ANSI_NULL); + RawPropertiesLength = Length1 + Length2 + Length3; + + DescriptorSize = FIELD_OFFSET(STORAGE_DEVICE_DESCRIPTOR, RawDeviceProperties) + + RawPropertiesLength; + + if (!AtaCheckPropertyQuery(Irp, DescriptorSize, &Status)) + return Status; + + DeviceDescriptor = Irp->AssociatedIrp.SystemBuffer; + DeviceDescriptor->RawPropertiesLength = RawPropertiesLength; + DeviceDescriptor->Version = sizeof(*DeviceDescriptor); + DeviceDescriptor->Size = DescriptorSize; + DeviceDescriptor->DeviceType = InquiryData->DeviceType; + DeviceDescriptor->DeviceTypeModifier = InquiryData->DeviceTypeModifier; + DeviceDescriptor->RemovableMedia = InquiryData->RemovableMedia; + DeviceDescriptor->CommandQueueing = FALSE; // Disable request tagging + if (DevExt->Device.PortData->PortFlags & PORT_FLAG_IS_AHCI) + DeviceDescriptor->BusType = BusTypeSata; + else + DeviceDescriptor->BusType = BusTypeAta; + + /* Property 1: The vendor ID. We return a NULL string here */ + DeviceDescriptor->VendorIdOffset = 0; + + /* Property 2: The product ID */ + DeviceDescriptor->ProductIdOffset = + FIELD_OFFSET(STORAGE_DEVICE_DESCRIPTOR, RawDeviceProperties); + RtlCopyMemory((PUCHAR)((ULONG_PTR)DeviceDescriptor + DeviceDescriptor->ProductIdOffset), + DevExt->FriendlyName, + Length1); + + /* Property 3: The product revision */ + DeviceDescriptor->ProductRevisionOffset = DeviceDescriptor->ProductIdOffset + Length1; + RtlCopyMemory((PUCHAR)((ULONG_PTR)DeviceDescriptor + DeviceDescriptor->ProductRevisionOffset), + DevExt->RevisionNumber, + Length2); + + /* Property 4: The serial number */ + DeviceDescriptor->SerialNumberOffset = DeviceDescriptor->ProductRevisionOffset + Length2; + RtlCopyMemory((PUCHAR)((ULONG_PTR)DeviceDescriptor + DeviceDescriptor->SerialNumberOffset), + DevExt->SerialNumber, + Length3); + + Irp->IoStatus.Information = DescriptorSize; + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoQueryStorageAccessAlignmentProperty( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + PSTORAGE_ACCESS_ALIGNMENT_DESCRIPTOR AccessAlignmentDescriptor; + ULONG LogicalSectorsPerPhysicalSector, Dummy; + NTSTATUS Status; + + PAGED_CODE(); + + if (IS_ATAPI(&DevExt->Device)) + return STATUS_NOT_SUPPORTED; + + if (!AtaCheckPropertyQuery(Irp, sizeof(*AccessAlignmentDescriptor), &Status)) + return Status; + + AccessAlignmentDescriptor = Irp->AssociatedIrp.SystemBuffer; + AccessAlignmentDescriptor->Version = sizeof(*AccessAlignmentDescriptor); + AccessAlignmentDescriptor->Size = sizeof(*AccessAlignmentDescriptor); + AccessAlignmentDescriptor->BytesPerCacheLine = 0; + AccessAlignmentDescriptor->BytesOffsetForCacheAlignment = 0; + AccessAlignmentDescriptor->BytesPerLogicalSector = DevExt->Device.SectorSize; + + LogicalSectorsPerPhysicalSector = + AtaDevLogicalSectorsPerPhysicalSector(&DevExt->IdentifyDeviceData, &Dummy); + + AccessAlignmentDescriptor->BytesPerPhysicalSector = + DevExt->Device.SectorSize * LogicalSectorsPerPhysicalSector; + + AccessAlignmentDescriptor->BytesOffsetForSectorAlignment = + DevExt->Device.SectorSize * AtaDevLogicalSectorAlignment(&DevExt->IdentifyDeviceData); + + Irp->IoStatus.Information = sizeof(*AccessAlignmentDescriptor); + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoQueryStorageDeviceSeekPenaltyProperty( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + PDEVICE_SEEK_PENALTY_DESCRIPTOR PenaltyDescriptor; + NTSTATUS Status; + BOOLEAN IncursSeekPenalty; + + PAGED_CODE(); + + if (!AtaCheckPropertyQuery(Irp, sizeof(*PenaltyDescriptor), &Status)) + return Status; + + if (AtaDevIsRotatingDevice(&DevExt->IdentifyDeviceData)) + IncursSeekPenalty = TRUE; + else if (AtaDevIsSsd(&DevExt->IdentifyDeviceData)) + IncursSeekPenalty = FALSE; + else + return STATUS_UNSUCCESSFUL; // Undetermined + + PenaltyDescriptor = Irp->AssociatedIrp.SystemBuffer; + PenaltyDescriptor->Version = sizeof(*PenaltyDescriptor); + PenaltyDescriptor->Size = sizeof(*PenaltyDescriptor); + PenaltyDescriptor->IncursSeekPenalty = IncursSeekPenalty; + + Irp->IoStatus.Information = sizeof(*PenaltyDescriptor); + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoQueryStorageDeviceTrimProperty( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + PDEVICE_TRIM_DESCRIPTOR TrimDescriptor; + NTSTATUS Status; + + PAGED_CODE(); + + if (IS_ATAPI(&DevExt->Device)) + return STATUS_NOT_SUPPORTED; + + if (!AtaCheckPropertyQuery(Irp, sizeof(*TrimDescriptor), &Status)) + return Status; + + TrimDescriptor = Irp->AssociatedIrp.SystemBuffer; + TrimDescriptor->Version = sizeof(*TrimDescriptor); + TrimDescriptor->Size = sizeof(*TrimDescriptor); + TrimDescriptor->TrimEnabled = AtaDevHasTrimFunction(&DevExt->IdentifyDeviceData); + + Irp->IoStatus.Information = sizeof(*TrimDescriptor); + return STATUS_SUCCESS; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaPdoHandleStorageQueryProperty( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack, + _Out_ PBOOLEAN ForwardToFdo) +{ + PSTORAGE_PROPERTY_QUERY PropertyQuery; + NTSTATUS Status; + + PAGED_CODE(); + + if (!AtaVerifyInBuffer(IoStack, sizeof(*PropertyQuery))) + { + ERR("Buffer too small\n"); + return STATUS_BUFFER_TOO_SMALL; + } + + PropertyQuery = Irp->AssociatedIrp.SystemBuffer; + switch (PropertyQuery->PropertyId) + { + case StorageDeviceProperty: + Status = AtaPdoQueryStorageDeviceProperty(DevExt, Irp); + break; + + case StorageAccessAlignmentProperty: + Status = AtaPdoQueryStorageAccessAlignmentProperty(DevExt, Irp); + break; + + case StorageDeviceSeekPenaltyProperty: + Status = AtaPdoQueryStorageDeviceSeekPenaltyProperty(DevExt, Irp); + break; + + case StorageDeviceTrimProperty: + Status = AtaPdoQueryStorageDeviceTrimProperty(DevExt, Irp); + break; + + default: + *ForwardToFdo = TRUE; + return STATUS_MORE_PROCESSING_REQUIRED; + } + + *ForwardToFdo = FALSE; + return Status; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaPdoHandleGetScsiAddress( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + PATAPORT_CHANNEL_EXTENSION ChanExt; + PSCSI_ADDRESS ScsiAddress; + ATA_SCSI_ADDRESS AtaScsiAddress; + + PAGED_CODE(); + + if (!AtaVerifyOutBuffer(IoStack, sizeof(*ScsiAddress))) + { + ERR("Buffer too small\n"); + return STATUS_BUFFER_TOO_SMALL; + } + + ChanExt = DevExt->Common.FdoExt; + AtaScsiAddress = DevExt->Device.AtaScsiAddress; + + ScsiAddress = Irp->AssociatedIrp.SystemBuffer; + ScsiAddress->Length = sizeof(*ScsiAddress); + ScsiAddress->PortNumber = ChanExt->ScsiPortNumber; + ScsiAddress->PathId = AtaScsiAddress.PathId; + ScsiAddress->TargetId = AtaScsiAddress.TargetId; + ScsiAddress->Lun = AtaScsiAddress.Lun; + + Irp->IoStatus.Information = sizeof(*ScsiAddress); + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoSendHbaControl( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PSRB_IO_CONTROL SrbControl, + _In_ ULONG BufferSize) +{ + PSCSI_REQUEST_BLOCK Srb; + IO_STATUS_BLOCK IoStatusBlock; + PIRP Irp; + KEVENT Event; + NTSTATUS Status; + LARGE_INTEGER LargeInt; + PIO_STACK_LOCATION IoStack; + + PAGED_CODE(); + + Srb = ExAllocatePoolZero(NonPagedPool, sizeof(*Srb), ATAPORT_TAG); + if (!Srb) + return STATUS_INSUFFICIENT_RESOURCES; + + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + LargeInt.QuadPart = 1; // For compatibility only + Irp = IoBuildSynchronousFsdRequest(IRP_MJ_SCSI, + DevExt->Common.Self, + SrbControl, + BufferSize, + &LargeInt, + &Event, + &IoStatusBlock); + if (!Irp) + { + Status = STATUS_INSUFFICIENT_RESOURCES; + goto Cleanup; + } + + Srb->OriginalRequest = Irp; + + Srb->Function = SRB_FUNCTION_IO_CONTROL; + Srb->Length = sizeof(*Srb); + + Srb->TargetId = DevExt->Device.AtaScsiAddress.TargetId; + Srb->Lun = DevExt->Device.AtaScsiAddress.Lun; + Srb->PathId = DevExt->Device.AtaScsiAddress.PathId; + + Srb->TimeOutValue = SrbControl->Timeout; + + Srb->SrbFlags = SRB_FLAGS_NO_QUEUE_FREEZE | SRB_FLAGS_DATA_IN; + + Srb->DataBuffer = SrbControl; + Srb->DataTransferLength = BufferSize; + + IoStack = IoGetNextIrpStackLocation(Irp); + IoStack->Parameters.Scsi.Srb = Srb; + + Status = IoCallDriver(DevExt->Common.Self, Irp); + if (Status == STATUS_PENDING) + { + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = IoStatusBlock.Status; + } + +Cleanup: + ExFreePoolWithTag(Srb, ATAPORT_TAG); + return Status; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaPdoHandleStorageManageDataSetAttributes( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + PAGED_CODE(); + + // TODO: Implement + return Irp->IoStatus.Status; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaPdoHandleScsiMiniport( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _Inout_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + PSRB_IO_CONTROL SrbControl; + NTSTATUS Status; + ULONG CmdBufferSize, BufferSize; + + PAGED_CODE(); + + if (!AtaVerifyInBuffer(IoStack, sizeof(*SrbControl))) + { + ERR("Buffer too small\n"); + return STATUS_BUFFER_TOO_SMALL; + } + + SrbControl = Irp->AssociatedIrp.SystemBuffer; + if (SrbControl->HeaderLength != sizeof(*SrbControl)) + { + ERR("Unknown structure size %lu\n", SrbControl->HeaderLength); + return STATUS_REVISION_MISMATCH; + } + + Status = RtlULongAdd(SrbControl->Length, sizeof(*SrbControl), &BufferSize); + if (!NT_SUCCESS(Status)) + { + ERR("Too large buffer 0x%lx\n", SrbControl->Length); + return Status; + } + + CmdBufferSize = IoStack->Parameters.DeviceIoControl.InputBufferLength; + CmdBufferSize = max(CmdBufferSize, IoStack->Parameters.DeviceIoControl.OutputBufferLength); + if (CmdBufferSize < BufferSize) + { + ERR("Cmd buffer too small\n"); + return STATUS_BUFFER_TOO_SMALL; + } + + if (RtlEqualMemory(SrbControl->Signature, "SCSIDISK", sizeof("SCSIDISK") - 1)) + Status = AtaPdoSendHbaControl(DevExt, SrbControl, BufferSize); + else + Status = STATUS_INVALID_PARAMETER; + + return Status; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaPdoHandleAtaPassthrough( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _Inout_ PIRP Irp) +{ + PATAPORT_CHANNEL_EXTENSION ChanExt = DevExt->Common.FdoExt; + + PAGED_CODE(); + + return SptiHandleAtaPassthru(DevExt->Common.Self, + Irp, + ChanExt->PortData.MaximumTransferLength, + ChanExt->PortData.MaximumPhysicalPages); +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaPdoHandleScsiPassthrough( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _Inout_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + PATAPORT_CHANNEL_EXTENSION ChanExt; + + PAGED_CODE(); + + /* Skip requests that bypassed the class driver. See also cdrom!RequestHandleScsiPassThrough */ + if ((IoStack->MinorFunction == 0) && DevExt->DeviceClaimed) + return STATUS_INVALID_DEVICE_REQUEST; + + ChanExt = DevExt->Common.FdoExt; + + return SptiHandleScsiPassthru(DevExt->Common.Self, + Irp, + ChanExt->PortData.MaximumTransferLength, + ChanExt->PortData.MaximumPhysicalPages); +} + +static +NTSTATUS +AtaPdoDeviceControl( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _Inout_ PIRP Irp) +{ + PIO_STACK_LOCATION IoStack; + NTSTATUS Status; + BOOLEAN ForwardToFdo = FALSE; + + Status = IoAcquireRemoveLock(&DevExt->Common.RemoveLock, Irp); + if (!NT_SUCCESS(Status)) + { + Irp->IoStatus.Information = 0; + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return Status; + } + + IoStack = IoGetCurrentIrpStackLocation(Irp); + switch (IoStack->Parameters.DeviceIoControl.IoControlCode) + { + case IOCTL_STORAGE_QUERY_PROPERTY: + Status = AtaPdoHandleStorageQueryProperty(DevExt, Irp, IoStack, &ForwardToFdo); + break; + + case IOCTL_SCSI_GET_ADDRESS: + Status = AtaPdoHandleGetScsiAddress(DevExt, Irp, IoStack); + break; + + case IOCTL_SCSI_MINIPORT: + Status = AtaPdoHandleScsiMiniport(DevExt, Irp, IoStack); + break; + + case IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES: + Status = AtaPdoHandleStorageManageDataSetAttributes(DevExt, Irp, IoStack); + break; + + case IOCTL_ATA_PASS_THROUGH: + case IOCTL_ATA_PASS_THROUGH_DIRECT: + Status = AtaPdoHandleAtaPassthrough(DevExt, Irp); + break; + + case IOCTL_SCSI_PASS_THROUGH: + case IOCTL_SCSI_PASS_THROUGH_DIRECT: + Status = AtaPdoHandleScsiPassthrough(DevExt, Irp, IoStack); + break; + + case IOCTL_SCSI_GET_CAPABILITIES: + case IOCTL_SCSI_GET_INQUIRY_DATA: + ForwardToFdo = TRUE; + break; + + default: + Status = STATUS_INVALID_DEVICE_REQUEST; + break; + } + + if (ForwardToFdo) + { + PATAPORT_CHANNEL_EXTENSION ChanExt = DevExt->Common.FdoExt; + + IoSkipCurrentIrpStackLocation(Irp); + Status = IoCallDriver(ChanExt->Common.Self, Irp); + } + else + { + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + } + + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, Irp); + + return Status; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaFdoQueryStorageAdapterProperty( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PIRP Irp) +{ + PSTORAGE_ADAPTER_DESCRIPTOR AdapterDescriptor; + NTSTATUS Status; + + PAGED_CODE(); + + if (!AtaCheckPropertyQuery(Irp, sizeof(*AdapterDescriptor), &Status)) + return Status; + + AdapterDescriptor = Irp->AssociatedIrp.SystemBuffer; + + /* + * This structure has to be zeroed out first + * in order to not confuse the upper storage class drivers. + * Also the new version of the descriptor structure (NT6.2+) + * has two extra fields instead of the unnamed "padding" fields. + */ + RtlZeroMemory(AdapterDescriptor, sizeof(*AdapterDescriptor)); + + AdapterDescriptor->Version = sizeof(*AdapterDescriptor); + AdapterDescriptor->Size = sizeof(*AdapterDescriptor); + AdapterDescriptor->MaximumTransferLength = ChanExt->PortData.MaximumTransferLength; + AdapterDescriptor->MaximumPhysicalPages = ChanExt->PortData.MaximumPhysicalPages; + AdapterDescriptor->AlignmentMask = ChanExt->Common.Self->AlignmentRequirement; + AdapterDescriptor->AdapterUsesPio = !!(ChanExt->PortData.PortFlags && PORT_FLAG_PIO_ONLY); + AdapterDescriptor->AdapterScansDown = FALSE; + AdapterDescriptor->CommandQueueing = FALSE; // Disable request tagging + AdapterDescriptor->AcceleratedTransfer = FALSE; + if (ChanExt->PortData.PortFlags & PORT_FLAG_IS_AHCI) + AdapterDescriptor->BusType = BusTypeSata; + else + AdapterDescriptor->BusType = BusTypeAta; + AdapterDescriptor->BusMajorVersion = 1; + AdapterDescriptor->BusMinorVersion = 0; +#if (NTDDI_VERSION >= NTDDI_WIN8) + AdapterDescriptor->SrbType = SRB_TYPE_SCSI_REQUEST_BLOCK; + AdapterDescriptor->AddressType = STORAGE_ADDRESS_TYPE_BTL8; +#endif + + Irp->IoStatus.Information = sizeof(*AdapterDescriptor); + return STATUS_SUCCESS; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaFdoHandleStorageQueryProperty( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + PSTORAGE_PROPERTY_QUERY PropertyQuery; + NTSTATUS Status; + + PAGED_CODE(); + + if (!AtaVerifyInBuffer(IoStack, sizeof(*PropertyQuery))) + { + ERR("Buffer too small\n"); + return STATUS_BUFFER_TOO_SMALL; + } + + PropertyQuery = Irp->AssociatedIrp.SystemBuffer; + switch (PropertyQuery->PropertyId) + { + case StorageAdapterProperty: + Status = AtaFdoQueryStorageAdapterProperty(ChanExt, Irp); + break; + + default: + Status = STATUS_NOT_SUPPORTED; + break; + } + + return Status; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaFdoHandleGetScsiCapabilities( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + PIO_SCSI_CAPABILITIES Capabilities; + + PAGED_CODE(); + + if (!AtaVerifyOutBuffer(IoStack, sizeof(*Capabilities))) + { + ERR("Buffer too small\n"); + return STATUS_BUFFER_TOO_SMALL; + } + + Capabilities = Irp->AssociatedIrp.SystemBuffer; + Capabilities->Length = sizeof(*Capabilities); + Capabilities->MaximumTransferLength = ChanExt->PortData.MaximumTransferLength; + Capabilities->MaximumPhysicalPages = ChanExt->PortData.MaximumPhysicalPages; + Capabilities->SupportedAsynchronousEvents = FALSE; + Capabilities->AlignmentMask = ChanExt->Common.Self->AlignmentRequirement; + Capabilities->TaggedQueuing = FALSE; + Capabilities->AdapterScansDown = FALSE; + Capabilities->AdapterUsesPio = !!(ChanExt->PortData.PortFlags && PORT_FLAG_PIO_ONLY); + + Irp->IoStatus.Information = sizeof(*Capabilities); + return STATUS_SUCCESS; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaFdoHandleGetScsiInquiryData( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + PSCSI_INQUIRY_DATA ScsiInquiryData; + PSCSI_ADAPTER_BUS_INFO ScsiAdapterBusInfo; + ULONG PdoCount, EntrySize, TotalSize; + ATA_SCSI_ADDRESS AtaScsiAddress; + + PAGED_CODE(); + + PdoCount = 0; + AtaScsiAddress.AsULONG = 0; + while (TRUE) + { + PATAPORT_DEVICE_EXTENSION DevExt; + + DevExt = AtaFdoFindNextDeviceByPath(ChanExt, &AtaScsiAddress, FALSE, NULL); + if (!DevExt) + break; + + ++PdoCount; + } + + EntrySize = ALIGN_UP(sizeof(*ScsiInquiryData) - 1 + INQUIRYDATABUFFERSIZE, ULONG); + TotalSize = sizeof(*ScsiAdapterBusInfo) + EntrySize * PdoCount; + TRACE("Total size %lu\n", TotalSize); + + if (!AtaVerifyOutBuffer(IoStack, TotalSize)) + return STATUS_BUFFER_TOO_SMALL; + + Irp->IoStatus.Information = TotalSize; + + ScsiAdapterBusInfo = Irp->AssociatedIrp.SystemBuffer; + ScsiAdapterBusInfo->NumberOfBuses = 1; + ScsiAdapterBusInfo->BusData[0].NumberOfLogicalUnits = 0; + ScsiAdapterBusInfo->BusData[0].InitiatorBusId = 0xFF; + ScsiAdapterBusInfo->BusData[0].InquiryDataOffset = sizeof(*ScsiAdapterBusInfo); + + ScsiInquiryData = (PSCSI_INQUIRY_DATA)(ScsiAdapterBusInfo + 1); + + AtaScsiAddress.AsULONG = 0; + while (TRUE) + { + PATAPORT_DEVICE_EXTENSION DevExt; + PINQUIRYDATA InquiryData; + + DevExt = AtaFdoFindNextDeviceByPath(ChanExt, &AtaScsiAddress, FALSE, Irp); + if (!DevExt) + break; + + AtaScsiAddress = DevExt->Device.AtaScsiAddress; + + ScsiInquiryData->PathId = AtaScsiAddress.PathId; + ScsiInquiryData->TargetId = AtaScsiAddress.TargetId; + ScsiInquiryData->Lun = AtaScsiAddress.Lun; + ScsiInquiryData->InquiryDataLength = INQUIRYDATABUFFERSIZE; + ScsiInquiryData->DeviceClaimed = DevExt->DeviceClaimed; + ScsiInquiryData->NextInquiryDataOffset = + (ULONG)((ULONG_PTR)ScsiInquiryData + EntrySize - (ULONG_PTR)ScsiAdapterBusInfo); + + RtlCopyMemory(ScsiInquiryData->InquiryData, + &DevExt->InquiryData, + INQUIRYDATABUFFERSIZE); + + /* This is a legacy IOCTL, limit the standard INQUIRY data to 36 bytes */ + InquiryData = (PINQUIRYDATA)ScsiInquiryData->InquiryData; + InquiryData->AdditionalLength = + INQUIRYDATABUFFERSIZE - RTL_SIZEOF_THROUGH_FIELD(INQUIRYDATA, AdditionalLength); + + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, Irp); + + ScsiInquiryData = (PSCSI_INQUIRY_DATA)((ULONG_PTR)ScsiInquiryData + EntrySize); + + if (++ScsiAdapterBusInfo->BusData[0].NumberOfLogicalUnits >= PdoCount) + break; + } + + /* Terminate the last entry */ + if (ScsiAdapterBusInfo->BusData[0].NumberOfLogicalUnits != 0) + { + ScsiInquiryData = ((PSCSI_INQUIRY_DATA)((ULONG_PTR)ScsiInquiryData - EntrySize)); + ScsiInquiryData->NextInquiryDataOffset = 0; + } + else + { + ScsiAdapterBusInfo->BusData[0].InquiryDataOffset = 0; + } + + return STATUS_SUCCESS; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaFdoHandleScsiMiniport( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + PATAPORT_DEVICE_EXTENSION DevExt; + NTSTATUS Status; + + PAGED_CODE(); + + /* FDO requests are routed directly to 0:0:0 */ + DevExt = AtaFdoFindDeviceByPath(ChanExt, AtaMarshallScsiAddress(0, 0, 0), Irp); + if (!DevExt) + return STATUS_NO_SUCH_DEVICE; + + Status = AtaPdoHandleScsiMiniport(DevExt, Irp, IoStack); + + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, Irp); + + return Status; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaFdoHandleScsiPassthrough( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + PSCSI_PASS_THROUGH Spt; + PATAPORT_DEVICE_EXTENSION DevExt; + NTSTATUS Status; + + PAGED_CODE(); + + if (!AtaVerifyInBuffer(IoStack, RTL_SIZEOF_THROUGH_FIELD(SCSI_PASS_THROUGH, Lun))) + { + ERR("Buffer too small\n"); + return STATUS_BUFFER_TOO_SMALL; + } + + Spt = Irp->AssociatedIrp.SystemBuffer; + + DevExt = AtaFdoFindDeviceByPath(ChanExt, + AtaMarshallScsiAddress(Spt->PathId, Spt->TargetId, Spt->Lun), + Irp); + if (!DevExt) + return STATUS_NO_SUCH_DEVICE; + + Status = AtaPdoHandleScsiPassthrough(DevExt, Irp, IoStack); + + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, Irp); + + return Status; +} + +static +NTSTATUS +AtaFdoDeviceControl( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _Inout_ PIRP Irp) +{ + PIO_STACK_LOCATION IoStack; + NTSTATUS Status; + + Status = IoAcquireRemoveLock(&ChanExt->Common.RemoveLock, Irp); + if (!NT_SUCCESS(Status)) + { + Irp->IoStatus.Information = 0; + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return Status; + } + + IoStack = IoGetCurrentIrpStackLocation(Irp); + switch (IoStack->Parameters.DeviceIoControl.IoControlCode) + { + case IOCTL_STORAGE_QUERY_PROPERTY: + Status = AtaFdoHandleStorageQueryProperty(ChanExt, Irp, IoStack); + break; + + case IOCTL_SCSI_GET_CAPABILITIES: + Status = AtaFdoHandleGetScsiCapabilities(ChanExt, Irp, IoStack); + break; + + case IOCTL_SCSI_GET_INQUIRY_DATA: + Status = AtaFdoHandleGetScsiInquiryData(ChanExt, Irp, IoStack); + break; + + case IOCTL_SCSI_MINIPORT: + Status = AtaFdoHandleScsiMiniport(ChanExt, Irp, IoStack); + break; + + case IOCTL_SCSI_PASS_THROUGH: + case IOCTL_SCSI_PASS_THROUGH_DIRECT: + Status = AtaFdoHandleScsiPassthrough(ChanExt, Irp, IoStack); + break; + + case IOCTL_SCSI_RESCAN_BUS: + IoInvalidateDeviceRelations(ChanExt->Pdo, BusRelations); + Status = STATUS_SUCCESS; + break; + + default: + { + IoSkipCurrentIrpStackLocation(Irp); + Status = IoCallDriver(ChanExt->Common.LowerDeviceObject, Irp); + + IoReleaseRemoveLock(&ChanExt->Common.RemoveLock, Irp); + + return Status; + } + } + + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + IoReleaseRemoveLock(&ChanExt->Common.RemoveLock, Irp); + + return Status; +} + +/* + * For storage drivers this dispatch function must be not paged, + * because it must be present when it is received unknown IOCTL, + * otherwise we risk locking up the whole system. + */ +NTSTATUS +NTAPI +AtaDispatchDeviceControl( + _In_ PDEVICE_OBJECT DeviceObject, + _Inout_ PIRP Irp) +{ + if (IS_FDO(DeviceObject->DeviceExtension)) + return AtaFdoDeviceControl(DeviceObject->DeviceExtension, Irp); + else + return AtaPdoDeviceControl(DeviceObject->DeviceExtension, Irp); +} diff --git a/drivers/storage/ide/atapi/pdo.c b/drivers/storage/ide/atapi/pdo.c new file mode 100644 index 00000000000..af9183af429 --- /dev/null +++ b/drivers/storage/ide/atapi/pdo.c @@ -0,0 +1,798 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Child device object (PDO) dispatch routines + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* GLOBALS ********************************************************************/ + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoStartDevice( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + PAGED_CODE(); + + if (!(DevExt->Device.DeviceFlags & DEVICE_PNP_STARTED)) + { + PATAPORT_PORT_DATA PortData = DevExt->Device.PortData; + + AtaPdoWmiRegistration(DevExt, TRUE); + + /* Update the type of device connected to the port */ + PortData->SetDeviceData(PortData->ChannelContext, + DevExt->Common.Self, + &DevExt->IdentifyDeviceData); + + /* Get the ATA initialization commands to restore the boot up defaults */ + if (!DevExt->GtfDataBuffer) + { + DevExt->GtfDataBuffer = PortData->GetInitTaskFile(PortData->ChannelContext, + DevExt->Common.Self); + } + + /* Use the standard power policy for mass storage devices */ + DevExt->Device.PowerIdleCounter = PoRegisterDeviceForIdleDetection(DevExt->Common.Self, + (ULONG)-1, + (ULONG)-1, + PowerDeviceD3); + + /* Set the new ATA volatile settings */ + KeClearEvent(&DevExt->Worker.ConfigureEvent); + AtaDeviceQueueEvent(DevExt->Device.PortData, DevExt, ACTION_DEVICE_CONFIG); + KeWaitForSingleObject(&DevExt->Worker.ConfigureEvent, + Executive, + KernelMode, + FALSE, + NULL); + + DevExt->Device.DeviceFlags |= DEVICE_PNP_STARTED; + } + + AtaReqThawQueue(DevExt, QUEUE_FLAG_FROZEN_PNP); + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoStopDevice( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + PAGED_CODE(); + + AtaReqFreezeQueue(DevExt, QUEUE_FLAG_FROZEN_PNP); + AtaReqWaitForOutstandingIoToComplete(&DevExt->Device, NULL); + + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoRemoveDevice( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _Inout_ PIRP Irp, + _In_ BOOLEAN FinalRemove) +{ + PATAPORT_CHANNEL_EXTENSION ChanExt = DevExt->Common.FdoExt; + NTSTATUS Status; + BOOLEAN WasRemoved = FALSE; + + PAGED_CODE(); + + AtaReqFreezeQueue(DevExt, QUEUE_FLAG_FROZEN_REMOVED); + AtaReqFlushDeviceQueue(&DevExt->Device); + AtaPdoWmiRegistration(DevExt, FALSE); + + if (FinalRemove && DevExt->ReportedMissing) + { + IoReleaseRemoveLockAndWait(&DevExt->Common.RemoveLock, Irp); + + AtaFdoDeviceListInsert(ChanExt, DevExt, FALSE); + AtaPdoFreeDevice(DevExt); + WasRemoved = TRUE; + } + + Status = STATUS_SUCCESS; + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + if (!WasRemoved) + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, Irp); + return Status; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoQueryStopRemoveDevice( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + PAGED_CODE(); + + if (DevExt->Common.PageFiles || + DevExt->Common.HibernateFiles || + DevExt->Common.DumpFiles) + { + return STATUS_DEVICE_BUSY; + } + + return STATUS_SUCCESS; +} + +NTSTATUS +NTAPI +AtaPdoCompletionRoutine( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PIRP Irp, + _In_reads_opt_(_Inexpressible_("varies")) PVOID Context) +{ + UNREFERENCED_PARAMETER(DeviceObject); + + if (Irp->PendingReturned) + KeSetEvent(Context, IO_NO_INCREMENT, FALSE); + + return STATUS_MORE_PROCESSING_REQUIRED; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoQueryCapabilities( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + DEVICE_CAPABILITIES ParentCapabilities; + PDEVICE_CAPABILITIES DeviceCapabilities; + NTSTATUS Status; + ATA_SCSI_ADDRESS AtaScsiAddress; + + PAGED_CODE(); + + /* Get the capabilities of the parent device */ + RtlZeroMemory(&ParentCapabilities, sizeof(ParentCapabilities)); + ParentCapabilities.Size = sizeof(ParentCapabilities); + ParentCapabilities.Version = 1; + ParentCapabilities.Address = MAXULONG; + ParentCapabilities.UINumber = MAXULONG; + Status = AtaPnpRepeatRequest(&DevExt->Common, Irp, &ParentCapabilities); + if (!NT_SUCCESS(Status)) + return Status; + + DeviceCapabilities = IoStack->Parameters.DeviceCapabilities.Capabilities; + RtlCopyMemory(DeviceCapabilities, &ParentCapabilities, sizeof(*DeviceCapabilities)); + + AtaScsiAddress = DevExt->Device.AtaScsiAddress; + + /* Override some fields */ + DeviceCapabilities->UINumber = AtaScsiAddress.TargetId; + DeviceCapabilities->UniqueID = FALSE; + DeviceCapabilities->SurpriseRemovalOK = FALSE; + DeviceCapabilities->Removable = !!(DevExt->Device.DeviceFlags & DEVICE_IS_PDO_REMOVABLE); + DeviceCapabilities->D1Latency = + DeviceCapabilities->D2Latency = + DeviceCapabilities->D3Latency = 31 * 10000; // 31 seconds (legacy ATA timeout) + /* + * See ACPI specification, _ADR (Address). + * The goal of the LUN field here is to hide devices with LUN>0 from the ACPI driver, + * since these devices are not mentioned in the ACPI spec at all. + */ + DeviceCapabilities->Address = (AtaScsiAddress.Lun << 4) | AtaScsiAddress.TargetId; + + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoQueryTargetDeviceRelations( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp) +{ + PDEVICE_RELATIONS DeviceRelations; + + PAGED_CODE(); + + DeviceRelations = ExAllocatePoolUninitialized(PagedPool, + sizeof(*DeviceRelations), + ATAPORT_TAG); + if (!DeviceRelations) + return STATUS_INSUFFICIENT_RESOURCES; + + DeviceRelations->Count = 1; + DeviceRelations->Objects[0] = DevExt->Common.Self; + ObReferenceObject(DevExt->Common.Self); + + Irp->IoStatus.Information = (ULONG_PTR)DeviceRelations; + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoQueryId( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + CHAR LocalBuffer[ATAPORT_FN_FIELD + ATAPORT_RN_FIELD]; + NTSTATUS Status; + PWCHAR Buffer, End; + PCSTR DeviceType, GenericType; + size_t CharCount, Remaining; + + PAGED_CODE(); + + switch (IoStack->Parameters.QueryId.IdType) + { + case BusQueryDeviceID: + { + DeviceType = AtaTypeCodeToName(DevExt, GetDeviceType); + + /* 'IDE\CdRomVBOX_CD-ROM_____________________________1.0_____' */ + CharCount = (sizeof("IDE\\") - 1) + strlen(DeviceType) + + ATAPORT_FN_FIELD + ATAPORT_RN_FIELD + + sizeof(ANSI_NULL); + + Buffer = ExAllocatePoolUninitialized(PagedPool, + CharCount * sizeof(WCHAR), + ATAPORT_TAG); + if (!Buffer) + return STATUS_INSUFFICIENT_RESOURCES; + + AtaCopyIdStringSafe(LocalBuffer, + (PUCHAR)DevExt->FriendlyName, + ATAPORT_FN_FIELD, + '_'); + + AtaCopyIdStringSafe(&LocalBuffer[ATAPORT_FN_FIELD], + (PUCHAR)DevExt->RevisionNumber, + ATAPORT_RN_FIELD, + '_'); + + Status = RtlStringCchPrintfExW(Buffer, + CharCount, + NULL, + NULL, + 0, + L"IDE\\%hs%.48hs", + DeviceType, + LocalBuffer); + ASSERT(NT_SUCCESS(Status)); + + INFO("DeviceID: '%S'\n", Buffer); + break; + } + + case BusQueryHardwareIDs: + { + PWCHAR IdStart; + + DBG_UNREFERENCED_LOCAL_VARIABLE(IdStart); + + DeviceType = AtaTypeCodeToName(DevExt, GetDeviceType); + GenericType = AtaTypeCodeToName(DevExt, GetGenericType); + + /* + * |------------------ 40 -----------------|-- 8 --| + * v v v + * 1) 'IDE\CdRomVBOX_CD-ROM_____________________________1.0_____' + * 2) 'IDE\VBOX_CD-ROM_____________________________1.0_____' + * 3) 'IDE\CdRomVBOX_CD-ROM_____________________________' + * 4) 'VBOX_CD-ROM_____________________________1.0_____' + * 5) 'GenCdRom' + */ + CharCount = strlen(DeviceType) * 2 + + strlen(GenericType) + + (sizeof("IDE\\") - 1) * 3 + + ATAPORT_FN_FIELD * 4 + + ATAPORT_RN_FIELD * 3 + + 5 * sizeof(ANSI_NULL) + + sizeof(ANSI_NULL); /* multi-string */ + + Buffer = ExAllocatePoolUninitialized(PagedPool, + CharCount * sizeof(WCHAR), + ATAPORT_TAG); + if (!Buffer) + return STATUS_INSUFFICIENT_RESOURCES; + + AtaCopyIdStringSafe(LocalBuffer, + (PUCHAR)DevExt->FriendlyName, + ATAPORT_FN_FIELD, + '_'); + + AtaCopyIdStringSafe(&LocalBuffer[ATAPORT_FN_FIELD], + (PUCHAR)DevExt->RevisionNumber, + ATAPORT_RN_FIELD, + '_'); + + INFO("HardwareIDs:\n"); + + /* ID 1 */ + Status = RtlStringCchPrintfExW(Buffer, + CharCount, + &End, + &Remaining, + 0, + L"IDE\\%hs%.48hs", + DeviceType, + LocalBuffer); + ASSERT(NT_SUCCESS(Status)); + + INFO(" '%S'\n", Buffer); + + ++End; + --Remaining; + + /* ID 2 */ + IdStart = End; + Status = RtlStringCchPrintfExW(End, + Remaining, + &End, + &Remaining, + 0, + L"IDE\\%.48hs", + LocalBuffer); + ASSERT(NT_SUCCESS(Status)); + + INFO(" '%S'\n", IdStart); + + ++End; + --Remaining; + + /* ID 3 */ + IdStart = End; + Status = RtlStringCchPrintfExW(End, + Remaining, + &End, + &Remaining, + 0, + L"IDE\\%hs%.40hs", + DeviceType, + LocalBuffer); + ASSERT(NT_SUCCESS(Status)); + + INFO(" '%S'\n", IdStart); + + ++End; + --Remaining; + + /* ID 4 */ + IdStart = End; + Status = RtlStringCchPrintfExW(End, + Remaining, + &End, + &Remaining, + 0, + L"%.48hs", + LocalBuffer); + ASSERT(NT_SUCCESS(Status)); + + INFO(" '%S'\n", IdStart); + + ++End; + --Remaining; + + /* ID 5 */ + IdStart = End; + Status = RtlStringCchPrintfExW(End, + Remaining, + &End, + &Remaining, + 0, + L"%hs", + GenericType); + ASSERT(NT_SUCCESS(Status)); + + *++End = UNICODE_NULL; /* multi-string */ + + INFO(" '%S'\n", IdStart); + break; + } + + case BusQueryCompatibleIDs: + { + GenericType = AtaTypeCodeToName(DevExt, GetGenericType); + + /* 'GenCdRom' */ + CharCount = strlen(GenericType) + 2 * sizeof(ANSI_NULL); /* multi-string */ + + Buffer = ExAllocatePoolUninitialized(PagedPool, + CharCount * sizeof(WCHAR), + ATAPORT_TAG); + if (!Buffer) + return STATUS_INSUFFICIENT_RESOURCES; + + Status = RtlStringCchPrintfExW(Buffer, + CharCount, + &End, + &Remaining, + 0, + L"%hs", + GenericType); + ASSERT(NT_SUCCESS(Status)); + + *++End = UNICODE_NULL; /* multi-string */ + + INFO("CompatibleIDs: '%S'\n", Buffer); + break; + } + + case BusQueryInstanceID: + { + ATA_SCSI_ADDRESS AtaScsiAddress; + + /* 'Path.Larget.Lun' */ + CharCount = sizeof("FF.FF.FF"); + + Buffer = ExAllocatePoolUninitialized(PagedPool, + CharCount * sizeof(WCHAR), + ATAPORT_TAG); + if (!Buffer) + return STATUS_INSUFFICIENT_RESOURCES; + + AtaScsiAddress = DevExt->Device.AtaScsiAddress; + Status = RtlStringCchPrintfExW(Buffer, + CharCount, + &End, + &Remaining, + 0, + L"%x.%x.%x", + AtaScsiAddress.PathId, + AtaScsiAddress.TargetId, + AtaScsiAddress.Lun); + ASSERT(NT_SUCCESS(Status)); + + INFO("InstanceID: '%S'\n", Buffer); + break; + } + + default: + return Irp->IoStatus.Status; + } + + Irp->IoStatus.Information = (ULONG_PTR)Buffer; + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoQueryDeviceText( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PIRP Irp, + _In_ PIO_STACK_LOCATION IoStack) +{ + NTSTATUS Status; + PWCHAR Buffer; + size_t CharCount; + + PAGED_CODE(); + + switch (IoStack->Parameters.QueryDeviceText.DeviceTextType) + { + case DeviceTextDescription: + { + CharCount = strlen(DevExt->FriendlyName) + sizeof(ANSI_NULL); + + Buffer = ExAllocatePoolUninitialized(PagedPool, + CharCount * sizeof(WCHAR), + ATAPORT_TAG); + if (!Buffer) + return STATUS_INSUFFICIENT_RESOURCES; + + Status = RtlStringCchPrintfExW(Buffer, + CharCount, + NULL, + NULL, + 0, + L"%hs", + DevExt->FriendlyName); + ASSERT(NT_SUCCESS(Status)); + + INFO("TextDescription: '%S'\n", Buffer); + break; + } + + case DeviceTextLocationInformation: + { + ATA_SCSI_ADDRESS AtaScsiAddress; + + CharCount = sizeof("Channel 255, Target 255, Lun 255"); + + Buffer = ExAllocatePoolUninitialized(PagedPool, + CharCount * sizeof(WCHAR), + ATAPORT_TAG); + if (!Buffer) + return STATUS_INSUFFICIENT_RESOURCES; + + AtaScsiAddress = DevExt->Device.AtaScsiAddress; + Status = RtlStringCchPrintfExW(Buffer, + CharCount, + NULL, + NULL, + 0, + L"Channel %u, Target %u, Lun %u", + AtaScsiAddress.PathId, + AtaScsiAddress.TargetId, + AtaScsiAddress.Lun); + ASSERT(NT_SUCCESS(Status)); + + INFO("TextLocationInformation: '%S'\n", Buffer); + break; + } + + default: + return Irp->IoStatus.Status; + } + + Irp->IoStatus.Information = (ULONG_PTR)Buffer; + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoPnp( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _Inout_ PIRP Irp) +{ + NTSTATUS Status; + PIO_STACK_LOCATION IoStack; + + PAGED_CODE(); + + INFO("(%p, %p) Tid.%lu %s\n", + DevExt->Common.Self, + Irp, + DevExt->Device.AtaScsiAddress.TargetId, + GetIRPMinorFunctionString(IoGetCurrentIrpStackLocation(Irp)->MinorFunction)); + + Status = IoAcquireRemoveLock(&DevExt->Common.RemoveLock, Irp); + if (!NT_SUCCESS(Status)) + { + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return Status; + } + + IoStack = IoGetCurrentIrpStackLocation(Irp); + + switch (IoStack->MinorFunction) + { + case IRP_MN_START_DEVICE: + Status = AtaPdoStartDevice(DevExt, Irp); + break; + + case IRP_MN_STOP_DEVICE: + Status = AtaPdoStopDevice(DevExt, Irp); + break; + + case IRP_MN_REMOVE_DEVICE: + case IRP_MN_SURPRISE_REMOVAL: + return AtaPdoRemoveDevice(DevExt, + Irp, + (IoStack->MinorFunction == IRP_MN_REMOVE_DEVICE)); + + case IRP_MN_QUERY_STOP_DEVICE: + case IRP_MN_QUERY_REMOVE_DEVICE: + Status = AtaPdoQueryStopRemoveDevice(DevExt, Irp); + break; + + case IRP_MN_CANCEL_REMOVE_DEVICE: + case IRP_MN_CANCEL_STOP_DEVICE: + Status = STATUS_SUCCESS; + break; + + case IRP_MN_QUERY_CAPABILITIES: + Status = AtaPdoQueryCapabilities(DevExt, Irp, IoStack); + break; + + case IRP_MN_QUERY_PNP_DEVICE_STATE: + Status = AtaPnpQueryPnpDeviceState(&DevExt->Common, Irp); + break; + + case IRP_MN_QUERY_ID: + Status = AtaPdoQueryId(DevExt, Irp, IoStack); + break; + + case IRP_MN_QUERY_DEVICE_TEXT: + Status = AtaPdoQueryDeviceText(DevExt, Irp, IoStack); + break; + + case IRP_MN_DEVICE_USAGE_NOTIFICATION: + Status = AtaPnpQueryDeviceUsageNotification(&DevExt->Common, Irp); + break; + + case IRP_MN_QUERY_DEVICE_RELATIONS: + { + if (IoStack->Parameters.QueryDeviceRelations.Type == TargetDeviceRelation) + Status = AtaPdoQueryTargetDeviceRelations(DevExt, Irp); + else + Status = Irp->IoStatus.Status; + break; + } + + default: + Status = Irp->IoStatus.Status; + break; + } + + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, Irp); + + return Status; +} + +CODE_SEG("PAGE") +NTSTATUS +NTAPI +AtaDispatchPnp( + _In_ PDEVICE_OBJECT DeviceObject, + _Inout_ PIRP Irp) +{ + PAGED_CODE(); + + if (IS_FDO(DeviceObject->DeviceExtension)) + return AtaFdoPnp(DeviceObject->DeviceExtension, Irp); + else + return AtaPdoPnp(DeviceObject->DeviceExtension, Irp); +} + +VOID +CODE_SEG("PAGE") +AtaPdoFreeDevice( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PAGED_CODE(); + + if (DevExt->Device.PowerIdleCounter) + { + PoRegisterDeviceForIdleDetection(DevExt->Common.Self, + 0, + 0, + PowerDeviceD3); + } + + if (DevExt->GtfDataBuffer) + { + ExFreePoolWithTag(DevExt->GtfDataBuffer, ATAPORT_TAG); + DevExt->GtfDataBuffer = NULL; + } + + if (DevExt->Device.Requests) + ExFreePoolWithTag(DevExt->Device.Requests, ATAPORT_TAG); + + IoDeleteDevice(DevExt->Common.Self); +} + +CODE_SEG("PAGE") +PATAPORT_DEVICE_EXTENSION +AtaPdoCreateDevice( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _In_ ATA_SCSI_ADDRESS AtaScsiAddress) +{ + PATAPORT_PORT_DATA PortData = &ChanExt->PortData; + NTSTATUS Status; + UNICODE_STRING DeviceName; + PATAPORT_DEVICE_EXTENSION DevExt; + PDEVICE_OBJECT Pdo; + WCHAR DeviceNameBuffer[sizeof("\\Device\\Ide\\IdeDeviceP99999T9L9-FFF")]; + ULONG i; + DECLARE_PAGED_WSTRING(PdoFormat, L"\\Device\\Ide\\IdeDeviceP%luT%luL%lu-%lx"); + static ULONG AtapPdoNumber = 0; + + PAGED_CODE(); + + Status = RtlStringCbPrintfW(DeviceNameBuffer, + sizeof(DeviceNameBuffer), + PdoFormat, + ChanExt->DeviceObjectNumber, + AtaScsiAddress.TargetId, + AtaScsiAddress.Lun, + AtapPdoNumber++); + ASSERT(NT_SUCCESS(Status)); + RtlInitUnicodeString(&DeviceName, DeviceNameBuffer); + + Status = IoCreateDevice(ChanExt->Common.Self->DriverObject, + sizeof(*DevExt), + &DeviceName, + FILE_DEVICE_MASS_STORAGE, + FILE_DEVICE_SECURE_OPEN, + FALSE, + &Pdo); + if (!NT_SUCCESS(Status)) + { + ERR("Failed to create PDO '%wZ' with status 0x%lx\n", &DeviceName, Status); + return NULL; + } + + INFO("Created device object %p '%wZ'\n", Pdo, &DeviceName); + + /* DMA buffers alignment */ + Pdo->AlignmentRequirement = ChanExt->Common.Self->AlignmentRequirement; + Pdo->AlignmentRequirement = max(Pdo->AlignmentRequirement, ATA_MIN_BUFFER_ALIGNMENT); + + Pdo->Flags |= DO_DIRECT_IO; + + DevExt = Pdo->DeviceExtension; + + RtlZeroMemory(DevExt, sizeof(*DevExt)); + AtaPnpInitializeCommonExtension(&DevExt->Common, Pdo, ChanExt->Common.Flags & ~DO_IS_FDO); + DevExt->Common.FdoExt = ChanExt; + DevExt->TransferModeAllowedMask = MAXULONG; + DevExt->Device.SectorSize = ATA_MIN_SECTOR_SIZE; + DevExt->Device.PortData = PortData; + DevExt->Device.LocalBuffer = PortData->LocalBuffer; + + DevExt->Device.AtaScsiAddress = AtaScsiAddress; + DevExt->Device.TransportFlags = AtaScsiAddress.TargetId; + DevExt->Device.DeviceSelect = IDE_DRIVE_SELECT | AtaScsiAddress.Lun; + + if (!(PortData->PortFlags & PORT_FLAG_IS_AHCI)) + { + /* Master/Slave select bit */ + DevExt->Device.DeviceSelect |= ((AtaScsiAddress.TargetId & 1) << 4); + } + + DevExt->Device.DeviceFlags = DEVICE_UNINITIALIZED; + + if (PortData->PortFlags & PORT_FLAG_IS_EXTERNAL) + DevExt->Device.DeviceFlags |= DEVICE_IS_PDO_REMOVABLE; + + if (PortData->PortFlags & PORT_FLAG_PIO_VIA_DMA) + DevExt->Device.DeviceFlags |= DEVICE_PIO_VIA_DMA; + + if (PortData->PortFlags & PORT_FLAG_PIO_FOR_LBA48_XFER) + DevExt->Device.DeviceFlags |= DEVICE_PIO_FOR_LBA48_XFER; + + /* Device's capability not explored, yet */ + DevExt->Device.DeviceFlags |= DEVICE_PIO_ONLY; + + DevExt->Device.FreeRequestsBitmap = + DevExt->Device.MaxRequestsBitmap = NUM_TO_BITMAP(PortData->QueueDepth); + + DevExt->Device.Requests = ExAllocatePoolZero(NonPagedPool, + sizeof(ATA_DEVICE_REQUEST) * PortData->QueueDepth, + ATAPORT_TAG); + if (!DevExt->Device.Requests) + goto Failure; + + for (i = 0; i < PortData->QueueDepth; ++i) + { + PATA_DEVICE_REQUEST Request = &DevExt->Device.Requests[i]; + + Request->Device = (PATA_IO_CONTEXT_COMMON)&DevExt->Device; +#if DBG + Request->Signature = ATA_DEVICE_REQUEST_SIGNATURE; +#endif + } + + KeInitializeSpinLock(&DevExt->Device.QueueLock); + InitializeListHead(&DevExt->Device.DeviceQueueList); + InitializeListHead(&DevExt->PowerIrpQueueList); + KeInitializeEvent(&DevExt->Device.QueueStoppedEvent, NotificationEvent, FALSE); + KeInitializeEvent(&DevExt->Worker.EnumerationEvent, NotificationEvent, FALSE); + KeInitializeEvent(&DevExt->Worker.ConfigureEvent, NotificationEvent, FALSE); + + return DevExt; + +Failure: + AtaPdoFreeDevice(DevExt); + + return NULL; +} diff --git a/drivers/storage/ide/atapi/portstate.c b/drivers/storage/ide/atapi/portstate.c new file mode 100644 index 00000000000..d10a5342e14 --- /dev/null +++ b/drivers/storage/ide/atapi/portstate.c @@ -0,0 +1,1004 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Port state machine core logic + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* FUNCTIONS ******************************************************************/ + +static +VOID +AtaPortQueueEvent( + _In_ PATAPORT_PORT_DATA PortData, + _In_opt_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_opt_ PATA_DEVICE_REQUEST FailedRequest, + _In_ ATA_PORT_ACTION Action) +{ + ASSERT(KeGetCurrentIrql() > DISPATCH_LEVEL); + + if (Action & (ACTION_PORT_RESET | ACTION_DEVICE_ERROR)) + { + if (Action & ACTION_DEVICE_ERROR) + { + if (FailedRequest) + { + ASSERT_REQUEST(FailedRequest); + + DevExt = CONTAINING_RECORD(FailedRequest->Device, ATAPORT_DEVICE_EXTENSION, Device); + + if (FailedRequest->Flags & REQUEST_FLAG_INTERNAL) + { + Action &= ~ACTION_DEVICE_ERROR; + } + else + { + ASSERT(PortData->Worker.FailedRequest == NULL); + PortData->Worker.FailedRequest = FailedRequest; + } + } + else + { + Action &= ~ACTION_DEVICE_ERROR; + Action |= ACTION_PORT_RESET; + } + } + + /* Special case for the internal request */ + if ((PortData->ActiveSlotsBitmap & 1) && + (PortData->Slots[0]->Flags & REQUEST_FLAG_INTERNAL)) + { + PATA_DEVICE_REQUEST InternalRequest = &PortData->Worker.InternalRequest; + + if (Action & ACTION_PORT_RESET) + InternalRequest->SrbStatus = SRB_STATUS_BUS_RESET; + + PortData->ActiveSlotsBitmap &= ~1; + + /* Internal request failed, kick off the port thread */ + AtaReqStartCompletionDpc(InternalRequest); + } + + /* Error recovery, save any commands pending on this port */ + PortData->Worker.PausedSlotsBitmap |= PortData->ActiveSlotsBitmap; + PortData->ActiveSlotsBitmap = 0; + + PortData->AbortChannel(PortData->ChannelContext, !!(Action & ACTION_PORT_RESET)); + } + + if (DevExt) + _InterlockedOr(&DevExt->Worker.EventsPending, Action); + _InterlockedOr(&PortData->Worker.EventsPending, Action); + + /* Kick off the port thread */ + if (PortData->InterruptFlags & PORT_INT_FLAG_IS_IO_ACTIVE) + { + _InterlockedAnd(&PortData->InterruptFlags, ~PORT_INT_FLAG_IS_IO_ACTIVE); + KeInsertQueueDpc(&PortData->Worker.Dpc, NULL, NULL); + } +} + +static +ATA_PORT_ACTION +AtaPortClearPortAction( + _In_ PATA_WORKER_CONTEXT Context, + _In_ ATA_PORT_ACTION Action) +{ + TRACE("Clear port action %lx, total %lx\n", Action, Context->EventsPending & ~Action); + + return _InterlockedAnd(&Context->EventsPending, ~(ULONG)Action); +} + +static +BOOLEAN +AtaPortClearDeviceAction( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ATA_PORT_ACTION Action) +{ + TRACE("Clear dev action %lx, total %lx\n", Action, DevExt->Worker.EventsPending & ~Action); + + return !!(_InterlockedAnd(&DevExt->Worker.EventsPending, ~(ULONG)Action) & Action); +} + +static +PATAPORT_DEVICE_EXTENSION +AtaPortFindDeviceForAction( + _In_ PATAPORT_PORT_DATA PortData, + _In_ ATA_PORT_ACTION Action) +{ + PATAPORT_DEVICE_EXTENSION DevExt, Result = NULL; + PATAPORT_CHANNEL_EXTENSION ChanExt; + PSINGLE_LIST_ENTRY Entry; + KIRQL OldIrql, OldLevel; + + ChanExt = CONTAINING_RECORD(PortData, ATAPORT_CHANNEL_EXTENSION, PortData); + + KeAcquireSpinLock(&ChanExt->PdoListLock, &OldLevel); + OldIrql = KeAcquireInterruptSpinLock(PortData->InterruptObject); + + for (Entry = ChanExt->PdoList.Next; Entry != NULL; Entry = Entry->Next) + { + DevExt = CONTAINING_RECORD(Entry, ATAPORT_DEVICE_EXTENSION, ListEntry); + + if (DevExt->Device.AtaScsiAddress.PathId != PortData->PortNumber) + continue; + + if (DevExt->ReportedMissing) + continue; + + if (DevExt->Worker.Flags & DEV_WORKER_FLAG_REMOVED) + continue; + + if (DevExt->Worker.EventsPending & Action) + { + Result = DevExt; + break; + } + } + + if (!Result) + AtaPortClearPortAction(&PortData->Worker, Action); + + KeReleaseInterruptSpinLock(PortData->InterruptObject, OldIrql); + KeReleaseSpinLock(&ChanExt->PdoListLock, OldLevel); + + return Result; +} + +static +VOID +AtaPortOnAsyncNotification( + _In_ PATAPORT_PORT_DATA PortData, + _In_ ULONG DeviceBitmap) +{ + ASSERT(KeGetCurrentIrql() > DISPATCH_LEVEL); + + KeInsertQueueDpc(&PortData->Worker.NotificationDpc, UlongToPtr(DeviceBitmap), NULL); +} + +static +VOID +AtaPortOnResetNotification( + _In_ PATAPORT_PORT_DATA PortData, + _In_ ULONG DeviceBitmap) +{ + PATAPORT_CHANNEL_EXTENSION ChanExt; + ATA_SCSI_ADDRESS AtaScsiAddress; + PATAPORT_DEVICE_EXTENSION DevExt; + + /* + * We are about to reset the channel which will in turn + * cause the affected devices to lose their software settings. + * Enqueue a config event so the state machine can re-initialize all devices later on. + */ + _InterlockedOr(&PortData->Worker.EventsPending, + ACTION_ENUM_DEVICE | + ACTION_DEVICE_CONFIG | + ACTION_PORT_TIMING); + + ChanExt = CONTAINING_RECORD(PortData, ATAPORT_CHANNEL_EXTENSION, PortData); + + AtaScsiAddress.AsULONG = 0; + while (TRUE) + { + ATA_PORT_ACTION Event; + + DevExt = AtaFdoFindNextDeviceByPath(ChanExt, &AtaScsiAddress, FALSE, NULL); + if (!DevExt) + break; + + if (!(DeviceBitmap & (1 << (ULONG)AtaScsiAddress.TargetId))) + continue; + + if (DevExt->Device.DeviceFlags & DEVICE_PNP_STARTED) + Event = ACTION_ENUM_DEVICE | ACTION_DEVICE_CONFIG; + else + Event = ACTION_ENUM_DEVICE; // ACPI _GTF is not ready yet + + _InterlockedOr(&DevExt->Worker.EventsPending, Event); + } +} + +static +VOID +AtaPortOnRequestComplete( + _In_ PATAPORT_PORT_DATA PortData, + _In_ ULONG CommandsCompleted) +{ + ULONG Slot; + + ASSERT(KeGetCurrentIrql() > DISPATCH_LEVEL); + + PortData->ActiveSlotsBitmap &= ~CommandsCompleted; + + while (_BitScanForward(&Slot, CommandsCompleted) != 0) + { + PATA_DEVICE_REQUEST Request; + + CommandsCompleted &= ~(1 << Slot); + + Request = PortData->Slots[Slot]; + ASSERT_REQUEST(Request); + ASSERT(Request->Slot == Slot); + + InterlockedPushEntrySList(&AtapCompletionQueueList, &Request->CompletionEntry); + } + + KeInsertQueueDpc(&AtapCompletionDpc, NULL, NULL); +} + +static +VOID +AtaPortMarkDeviceFailed( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + /* Ignore bad or incompatible devices, so we do not access them at all */ + if (++DevExt->Worker.ResetRetryCount >= 4) + { + ERR("CH %lu: Too many reset attempts for the device %u, giving up\n", + PortData->PortNumber, + DevExt->Device.AtaScsiAddress.TargetId); + + DevExt->Worker.Flags |= DEV_WORKER_FLAG_REMOVED; + + PortData->Worker.BadDeviceBitmap |= 1 << (ULONG)DevExt->Device.AtaScsiAddress.TargetId; + } +} + +static +NTSTATUS +AtaPortEnumeratePort( + _In_ PATAPORT_PORT_DATA PortData) +{ + PATAPORT_CHANNEL_EXTENSION ChanExt; + ATA_SCSI_ADDRESS AtaScsiAddress; + + _InterlockedOr(&PortData->InterruptFlags, PORT_INT_FLAG_IGNORE_LINK_IRQ); + PortData->Worker.DeviceCount = PortData->EnumerateChannel(PortData->ChannelContext); + _InterlockedAnd(&PortData->InterruptFlags, ~PORT_INT_FLAG_IGNORE_LINK_IRQ); + + INFO("CH %lu: Detected %lu devices\n", PortData->PortNumber, PortData->Worker.DeviceCount); + + if (AtaPortClearPortAction(&PortData->Worker, ACTION_ENUM_PORT)) + { + /* Defer the completion */ + PortData->Worker.Flags |= WORKER_FLAG_COMPLETE_PORT_ENUM_EVENT; + } + + ChanExt = CONTAINING_RECORD(PortData, ATAPORT_CHANNEL_EXTENSION, PortData); + + /* Remove detached devices */ + AtaScsiAddress.AsULONG = 0; + while (TRUE) + { + PATAPORT_DEVICE_EXTENSION DevExt; + + DevExt = AtaFdoFindNextDeviceByPath(ChanExt, &AtaScsiAddress, FALSE, NULL); + if (!DevExt) + break; + + if (AtaScsiAddress.TargetId >= PortData->Worker.DeviceCount) + DevExt->Worker.Flags |= DEV_WORKER_FLAG_REMOVED; + } + + return STATUS_SUCCESS; +} + +static +NTSTATUS +AtaPortResetPort( + _In_ PATAPORT_PORT_DATA PortData) +{ + _InterlockedOr(&PortData->InterruptFlags, PORT_INT_FLAG_IGNORE_LINK_IRQ); + PortData->ResetChannel(PortData->ChannelContext); + _InterlockedAnd(&PortData->InterruptFlags, ~PORT_INT_FLAG_IGNORE_LINK_IRQ); + + AtaPortEnumeratePort(PortData); + AtaPortOnResetNotification(PortData, MAXULONG); + + AtaPortClearPortAction(&PortData->Worker, ACTION_PORT_RESET); + + /* + * Reset bus timings, as attached devices + * may reset their current transfer mode to default during the processing of a software reset + * and the subsequent IDENTIFY DEVICE command would end up using incorrect timings. + */ + AtaPortSelectTimings(PortData, TRUE); + + if (++PortData->Worker.ResetRetryCount >= 10) + { + ERR("CH %lu: Too many port reset attempts, giving up\n", PortData->PortNumber); + PortData->Worker.BadDeviceBitmap = MAXULONG; + } + + return STATUS_SUCCESS; +} + +static +NTSTATUS +AtaPortEnumerateDevice( + _In_ PATAPORT_PORT_DATA PortData, + _In_ ATA_PORT_ACTION Action) +{ + PATAPORT_DEVICE_EXTENSION DevExt; + + if (Action == ACTION_ENUM_DEVICE_NEW) + { + DevExt = PortData->Worker.EnumDevExt; + ASSERT(DevExt); + AtaReqFreezeQueue(DevExt, QUEUE_FLAG_FROZEN_PORT_BUSY); + } + else + { + DevExt = AtaPortFindDeviceForAction(PortData, Action); + if (!DevExt) + return STATUS_SUCCESS; + } + + if (PortData->Worker.BadDeviceBitmap & (1 << (ULONG)DevExt->Device.AtaScsiAddress.TargetId)) + { + DevExt->Worker.EnumStatus = DEV_STATUS_NO_DEVICE; + } + else + { + DevExt->Worker.EnumStatus = AtaPortIdentifyDevice(PortData, DevExt); + if (DevExt->Worker.EnumStatus == DEV_STATUS_FAILED) + { + AtaPortMarkDeviceFailed(PortData, DevExt); + return STATUS_ADAPTER_HARDWARE_ERROR; + } + } + + if ((DevExt->Worker.EnumStatus != DEV_STATUS_SAME_DEVICE) && + !(DevExt->Device.DeviceFlags & DEVICE_UNINITIALIZED)) + { + DevExt->Worker.Flags |= DEV_WORKER_FLAG_REMOVED; + } + + if (AtaPortClearDeviceAction(DevExt, Action)) + { + if (Action == ACTION_ENUM_DEVICE_NEW) + AtaPortClearPortAction(&PortData->Worker, Action); + + KeSetEvent(&DevExt->Worker.EnumerationEvent, 0, FALSE); + } + + return STATUS_SUCCESS; +} + +static +NTSTATUS +AtaPortRecoveryFromError( + _In_ PATAPORT_PORT_DATA PortData) +{ + PATAPORT_DEVICE_EXTENSION DevExt; + NTSTATUS Status; + + DevExt = AtaPortFindDeviceForAction(PortData, ACTION_DEVICE_ERROR); + if (!DevExt) + return STATUS_SUCCESS; + + Status = AtaPortDeviceProcessError(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + { + AtaPortMarkDeviceFailed(PortData, DevExt); + return Status; + } + + AtaPortClearDeviceAction(DevExt, ACTION_DEVICE_ERROR); + return STATUS_SUCCESS; +} + +static +NTSTATUS +AtaPortConfigureDevice( + _In_ PATAPORT_PORT_DATA PortData) +{ + PATAPORT_DEVICE_EXTENSION DevExt; + NTSTATUS Status; + + DevExt = AtaPortFindDeviceForAction(PortData, ACTION_DEVICE_CONFIG); + if (!DevExt) + return STATUS_SUCCESS; + + Status = AtaPortDeviceProcessConfig(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + { + AtaPortMarkDeviceFailed(PortData, DevExt); + return Status; + } + + if (AtaPortClearDeviceAction(DevExt, ACTION_DEVICE_CONFIG)) + KeSetEvent(&DevExt->Worker.ConfigureEvent, 0, FALSE); + + return STATUS_SUCCESS; +} + +static +NTSTATUS +AtaPortDeviceChangePower( + _In_ PATAPORT_PORT_DATA PortData) +{ + PATAPORT_DEVICE_EXTENSION DevExt; + NTSTATUS Status; + + DevExt = AtaPortFindDeviceForAction(PortData, ACTION_DEVICE_POWER); + if (!DevExt) + return STATUS_SUCCESS; + + Status = AtaPortDeviceProcessPowerChange(PortData, DevExt); + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + { + AtaPortMarkDeviceFailed(PortData, DevExt); + return Status; + } + + AtaPortClearDeviceAction(DevExt, ACTION_DEVICE_POWER); + return STATUS_SUCCESS; +} + +static +NTSTATUS +AtaPortSetTransferMode( + _In_ PATAPORT_PORT_DATA PortData) +{ + AtaPortSelectTimings(PortData, FALSE); + AtaPortClearPortAction(&PortData->Worker, ACTION_PORT_TIMING); + + return STATUS_SUCCESS; +} + +static +BOOLEAN +AtaPortGetNextEvent( + _In_ PATAPORT_PORT_DATA PortData, + _Out_ ATA_PORT_ACTION* Action) +{ + KIRQL OldIrql; + ULONG i, EventIndex; + BOOLEAN Success; + + OldIrql = KeAcquireInterruptSpinLock(PortData->InterruptObject); + + /* Handle events by priority order */ + Success = _BitScanForward(&EventIndex, PortData->Worker.EventsPending); + if (!Success) + { + PATA_DEVICE_REQUEST Request; + + /* Resume port I/O */ + ASSERT(!(PortData->InterruptFlags & PORT_INT_FLAG_IS_IO_ACTIVE)); + _InterlockedOr(&PortData->InterruptFlags, PORT_INT_FLAG_IS_IO_ACTIVE); + + /* Complete failed request */ + Request = PortData->Worker.FailedRequest; + if (Request && !(PortData->Worker.PausedSlotsBitmap & (1 << Request->Slot))) + { + AtaReqStartCompletionDpc(Request); + } + PortData->Worker.FailedRequest = NULL; + + /* Requeue saved commands */ + for (i = 0; i < MAX_SLOTS; ++i) + { + if (!(PortData->Worker.PausedSlotsBitmap & (1 << i))) + continue; + + Request = PortData->Slots[i]; + ASSERT_REQUEST(Request); + ASSERT(Request != &PortData->Worker.InternalRequest); + + Request->SrbStatus = SRB_STATUS_BUSY; + Request->InternalState = REQUEST_STATE_REQUEUE; + AtaReqStartCompletionDpc(Request); + } + + PortData->Worker.PausedSlotsBitmap = 0; + } + + KeReleaseInterruptSpinLock(PortData->InterruptObject, OldIrql); + + *Action = 1 << EventIndex; + return Success; +} + +static +VOID +AtaPortRunStateMachine( + _In_ PATAPORT_PORT_DATA PortData) +{ + ATA_PORT_ACTION Action; + NTSTATUS Status; + + while (TRUE) + { + if (!AtaPortGetNextEvent(PortData, &Action)) + break; + +#if DBG + if (PortData->Worker.StateLoopCount++ > 1500) + { + ERR("CH %lu: Loop detected %lx\n", PortData->PortNumber, + PortData->Worker.EventsPending); + ASSERT(FALSE); + } +#endif + + switch (Action) + { + case ACTION_PORT_RESET: + Status = AtaPortResetPort(PortData); + break; + case ACTION_ENUM_PORT: + Status = AtaPortEnumeratePort(PortData); + break; + case ACTION_ENUM_DEVICE: + case ACTION_ENUM_DEVICE_NEW: + Status = AtaPortEnumerateDevice(PortData, Action); + break; + case ACTION_PORT_TIMING: + Status = AtaPortSetTransferMode(PortData); + break; + case ACTION_DEVICE_CONFIG: + Status = AtaPortConfigureDevice(PortData); + break; + case ACTION_DEVICE_ERROR: + Status = AtaPortRecoveryFromError(PortData); + break; + case ACTION_DEVICE_POWER: + Status = AtaPortDeviceChangePower(PortData); + break; + + default: + ASSERT(FALSE); + UNREACHABLE; + } + + if (Status == STATUS_ADAPTER_HARDWARE_ERROR) + _InterlockedOr(&PortData->Worker.EventsPending, ACTION_PORT_RESET); + } +} + +static +VOID +AtaPortWaitForIdle( + _In_ PATAPORT_PORT_DATA PortData) +{ + KIRQL OldIrql; + BOOLEAN DoWait = FALSE; + + KeAcquireSpinLock(&PortData->QueueLock, &OldIrql); + KeClearEvent(&PortData->QueueStoppedEvent); + if (!AtaPortQueueEmpty(PortData)) + { + PortData->QueueFlags |= PORT_QUEUE_FLAG_SIGNAL_STOP; + DoWait = TRUE; + } + KeReleaseSpinLock(&PortData->QueueLock, OldIrql); + if (DoWait) + { + KeWaitForSingleObject(&PortData->QueueStoppedEvent, Executive, KernelMode, FALSE, NULL); + } +} + +static +VOID +AtaPortEnterStateMachine( + _In_ PATAPORT_PORT_DATA PortData) +{ + PATAPORT_CHANNEL_EXTENSION ChanExt; + ATA_SCSI_ADDRESS AtaScsiAddress; + + ChanExt = CONTAINING_RECORD(PortData, ATAPORT_CHANNEL_EXTENSION, PortData); + ASSERT(IS_FDO(ChanExt)); + + AtaScsiAddress.AsULONG = 0; + while (TRUE) + { + PATAPORT_DEVICE_EXTENSION DevExt; + PULONG PowerIdleCounter; + + /* + * Acquire a reference to make sure the device object is valid + * for the duration of the port event handling. + */ + DevExt = AtaFdoFindNextDeviceByPath(ChanExt, + &AtaScsiAddress, + FALSE, + AtaPortWorkerThread); + if (!DevExt) + break; + + DevExt->Worker.Flags = DEV_WORKER_FLAG_HOLD_REFERENCE; + DevExt->Worker.ResetRetryCount = 0; + + if (DevExt->Device.QueueFlags & QUEUE_FLAG_FROZEN_REMOVED) + { + DevExt->Worker.Flags |= DEV_WORKER_FLAG_REMOVED; + continue; + } + + /* Stop the Srb processing */ + AtaReqFreezeQueue(DevExt, QUEUE_FLAG_FROZEN_PORT_BUSY); + + /* + * There is a chance that we can receive a power down request + * while the state maching is being running, so mark the device as busy + * to reduce the request probability. + */ + PowerIdleCounter = DevExt->Device.PowerIdleCounter; + if (PowerIdleCounter) + PoSetDeviceBusy(PowerIdleCounter); + } + + /* Wait for the port queue to become empty */ + AtaPortWaitForIdle(PortData); + + PortData->Worker.Flags = 0; + PortData->Worker.BadDeviceBitmap = 0; + PortData->Worker.ResetRetryCount = 0; +#if DBG + PortData->Worker.StateLoopCount = 0; +#endif + + // FIXME: Handle PORT_FLAG_IS_SIMPLEX +} + +static +VOID +AtaPortExitStateMachine( + _In_ PATAPORT_PORT_DATA PortData) +{ + PATAPORT_CHANNEL_EXTENSION ChanExt; + ATA_SCSI_ADDRESS AtaScsiAddress; + + ChanExt = CONTAINING_RECORD(PortData, ATAPORT_CHANNEL_EXTENSION, PortData); + + AtaScsiAddress.AsULONG = 0; + while (TRUE) + { + PATAPORT_DEVICE_EXTENSION DevExt; + KIRQL OldIrql; + + DevExt = AtaFdoFindNextDeviceByPath(ChanExt, &AtaScsiAddress, TRUE, NULL); + if (!DevExt) + break; + + if (DevExt->Worker.Flags & DEV_WORKER_FLAG_REMOVED) + { + KeAcquireSpinLock(&ChanExt->PdoListLock, &OldIrql); + if (!DevExt->RemovalPending) + PortData->Worker.Flags |= WORKER_FLAG_NEED_RESCAN; + DevExt->RemovalPending = TRUE; + KeReleaseSpinLock(&ChanExt->PdoListLock, OldIrql); + + AtaReqFreezeQueue(DevExt, QUEUE_FLAG_FROZEN_REMOVED); + AtaReqFlushDeviceQueue(&DevExt->Device); + + AtaDeviceFlushPowerIrpQueue(DevExt); + KeSetEvent(&DevExt->Worker.ConfigureEvent, 0, FALSE); + + DevExt->Worker.EnumStatus = DEV_STATUS_NO_DEVICE; + KeSetEvent(&DevExt->Worker.EnumerationEvent, 0, FALSE); + } + else + { + /* Resume the Srb processing */ + AtaReqThawQueue(DevExt, QUEUE_FLAG_FROZEN_PORT_BUSY); + } + + /* Release a reference */ + if (DevExt->Worker.Flags & DEV_WORKER_FLAG_HOLD_REFERENCE) + { + DevExt->Worker.Flags &= ~DEV_WORKER_FLAG_HOLD_REFERENCE; + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, AtaPortWorkerThread); + } + } + + if (PortData->Worker.Flags & WORKER_FLAG_COMPLETE_PORT_ENUM_EVENT) + { + /* We defer this event completion to avoid a BusRelations request */ + PortData->Worker.Flags &= ~WORKER_FLAG_NEED_RESCAN; + + KeSetEvent(&PortData->Worker.EnumerationEvent, 0, FALSE); + } + + if (PortData->Worker.Flags & WORKER_FLAG_NEED_RESCAN) + IoInvalidateDeviceRelations(ChanExt->Pdo, BusRelations); +} + +static +VOID +AtaPortWorkerClearSignal( + _In_ PATAPORT_PORT_DATA PortData) +{ + KIRQL OldIrql; + + KeAcquireSpinLock(&PortData->Worker.Lock, &OldIrql); + KeClearEvent(&PortData->Worker.ThreadEvent); + KeReleaseSpinLock(&PortData->Worker.Lock, OldIrql); +} + +VOID +NTAPI +AtaPortWorkerThread( + _In_ PVOID StartContext) +{ + PATAPORT_PORT_DATA PortData = StartContext; + + while (TRUE) + { + KeWaitForSingleObject(&PortData->Worker.ThreadEvent, + Executive, + KernelMode, + FALSE, + NULL); + if (PortData->PortFlags & PORT_FLAG_EXIT_THREAD) + break; + + AtaPortWorkerClearSignal(PortData); + AtaPortEnterStateMachine(PortData); + AtaPortRunStateMachine(PortData); + AtaPortExitStateMachine(PortData); + } + + PsTerminateSystemThread(STATUS_SUCCESS); +} + +VOID +__cdecl +AtaPortNotification( + _In_ PORT_NOTIFICATION_TYPE NotificationType, + _In_ PVOID PortContext, + ...) +{ + PATAPORT_PORT_DATA PortData = PortContext; + va_list ap; + + va_start(ap, PortContext); + + TRACE("CH %lu: Notification %lu\n", PortData->PortNumber, NotificationType); + + switch (NotificationType) + { + case AtaRequestComplete: + { + AtaPortOnRequestComplete(PortData, (ULONG)va_arg(ap, ULONG)); + break; + } + + case AtaResetDetected: + { + ASSERT(PortData->Worker.Thread == KeGetCurrentThread()); + ASSERT(!(PortData->InterruptFlags & PORT_INT_FLAG_IS_IO_ACTIVE)); + AtaPortOnResetNotification(PortData, (ULONG)va_arg(ap, ULONG)); + break; + } + + case AtaBusChangeDetected: + { + ASSERT(KeGetCurrentIrql() > DISPATCH_LEVEL); + + if (!(PortData->InterruptFlags & PORT_INT_FLAG_IGNORE_LINK_IRQ)) + AtaPortQueueEvent(PortData, NULL, NULL, ACTION_PORT_RESET); + else + TRACE("CH %lu: Ignore link IRQ\n", PortData->PortNumber); + break; + } + + case AtaRequestFailed: + { + AtaPortQueueEvent(PortData, + NULL, + (PATA_DEVICE_REQUEST)va_arg(ap, PATA_DEVICE_REQUEST), + ACTION_DEVICE_ERROR); + break; + } + + case AtaAsyncNotificationDetected: + { + AtaPortOnAsyncNotification(PortData, (ULONG)va_arg(ap, ULONG)); + break; + } + + default: + ERR("CH %lu: Unsupported notification %lu\n", PortData->PortNumber, NotificationType); + break; + } + + va_end(ap); +} + +ATA_COMPLETION_ACTION +AtaPortCompleteInternalRequest( + _In_ PATA_DEVICE_REQUEST Request) +{ + PATAPORT_IO_CONTEXT Device = (PATAPORT_IO_CONTEXT)Request->Device; + PATAPORT_PORT_DATA PortData = Device->PortData; + + ASSERT(Request->Flags & REQUEST_FLAG_INTERNAL); + KeSetEvent(&PortData->Worker.CompletionEvent, IO_NO_INCREMENT, FALSE); + + return COMPLETE_IRP; +} + +NTSTATUS +AtaPortSendRequest( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PATA_DEVICE_REQUEST Request = &PortData->Worker.InternalRequest; + KIRQL OldIrql; + + if (Request->Flags & REQUEST_FLAG_PACKET_COMMAND) + { + INFO("CH %lu: Send CDB %u %02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + PortData->PortNumber, + DevExt->Device.AtaScsiAddress.TargetId, + Request->Cdb[0], + Request->Cdb[1], + Request->Cdb[2], + Request->Cdb[3], + Request->Cdb[4], + Request->Cdb[5], + Request->Cdb[6]); + } + else + { + INFO("CH %lu: Send TF %u %02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + PortData->PortNumber, + DevExt->Device.AtaScsiAddress.TargetId, + Request->TaskFile.Command, + Request->TaskFile.Feature, + Request->TaskFile.SectorCount, + Request->TaskFile.LowLba, + Request->TaskFile.MidLba, + Request->TaskFile.HighLba, + Request->TaskFile.DriveSelect); + } + + KeClearEvent(&PortData->Worker.CompletionEvent); + + Request->Flags |= REQUEST_FLAG_INTERNAL; + Request->Device = (PATA_IO_CONTEXT_COMMON)&DevExt->Device; + + PortData->Worker.OldRequest = PortData->Slots[0]; + PortData->Slots[0] = Request; + + KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); + + KeAcquireSpinLockAtDpcLevel(&PortData->QueueLock); + PortData->ActiveTimersBitmap |= 1 << 0; + KeReleaseSpinLockFromDpcLevel(&PortData->QueueLock); + + AtaReqSendRequest(Request); + + KeLowerIrql(OldIrql); + + KeWaitForSingleObject(&PortData->Worker.CompletionEvent, Executive, KernelMode, FALSE, NULL); + + /* Stop the timer */ + KeAcquireSpinLock(&PortData->QueueLock, &OldIrql); + PortData->ActiveTimersBitmap = 0; + KeReleaseSpinLock(&PortData->QueueLock, OldIrql); + + PortData->Slots[0] = PortData->Worker.OldRequest; + + if ((Request->SrbStatus == SRB_STATUS_TIMEOUT) || + (Request->SrbStatus == SRB_STATUS_BUS_RESET)) + { + return STATUS_ADAPTER_HARDWARE_ERROR; + } + + if (Request->SrbStatus == SRB_STATUS_SUCCESS) + return STATUS_SUCCESS; + + return STATUS_IO_DEVICE_ERROR; +} + +VOID +AtaPortTimeout( + _In_ PATAPORT_PORT_DATA PortData, + _In_ ULONG Slot) +{ + PATA_DEVICE_REQUEST Request; + PATAPORT_DEVICE_EXTENSION DevExt; + + ASSERT(KeGetCurrentIrql() > DISPATCH_LEVEL); + + Request = PortData->Slots[Slot]; + ASSERT_REQUEST(Request); + + DevExt = CONTAINING_RECORD(Request->Device, ATAPORT_DEVICE_EXTENSION, Device); + + ERR("CH %lu: Slot %lu (%08lx) timed out %lx (%lus) %u '%s'\n", + PortData->PortNumber, + Slot, + 1 << Slot, + Request->Flags, + Request->TimeOut, + DevExt->Device.AtaScsiAddress.TargetId, + DevExt->FriendlyName); + if (Request->Flags & REQUEST_FLAG_PACKET_COMMAND) + { + ERR("CH %lu: CDB %02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + PortData->PortNumber, + Request->Cdb[0], + Request->Cdb[1], + Request->Cdb[2], + Request->Cdb[3], + Request->Cdb[4], + Request->Cdb[5], + Request->Cdb[6]); + } + else + { + ERR("CH %lu: TF %02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + PortData->PortNumber, + Request->TaskFile.Command, + Request->TaskFile.Feature, + Request->TaskFile.SectorCount, + Request->TaskFile.LowLba, + Request->TaskFile.MidLba, + Request->TaskFile.HighLba, + Request->TaskFile.DriveSelect); + } + + Request->SrbStatus = SRB_STATUS_TIMEOUT; + + /* The active command has timed out, set the ATA outputs to something meaningful */ + Request->Output.Status = IDE_STATUS_ERROR; + Request->Output.Error = IDE_ERROR_COMMAND_ABORTED; + + AtaPortQueueEvent(PortData, NULL, Request, ACTION_PORT_RESET | ACTION_DEVICE_ERROR); +} + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaPortSignalWorkerThread( + _In_ PATAPORT_PORT_DATA PortData) +{ + KIRQL OldIrql; + + KeAcquireSpinLock(&PortData->Worker.Lock, &OldIrql); + KeSetEvent(&PortData->Worker.ThreadEvent, IO_NO_INCREMENT, FALSE); + KeReleaseSpinLock(&PortData->Worker.Lock, OldIrql); +} + +VOID +NTAPI +AtaPortWorkerSignalDpc( + _In_ PKDPC Dpc, + _In_opt_ PVOID DeferredContext, + _In_opt_ PVOID SystemArgument1, + _In_opt_ PVOID SystemArgument2) +{ + PATAPORT_PORT_DATA PortData = DeferredContext; + + UNREFERENCED_PARAMETER(Dpc); + UNREFERENCED_PARAMETER(SystemArgument1); + UNREFERENCED_PARAMETER(SystemArgument2); + + AtaPortSignalWorkerThread(PortData); +} + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaDeviceQueueEvent( + _In_ PATAPORT_PORT_DATA PortData, + _In_opt_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ATA_PORT_ACTION Action) +{ + KIRQL OldIrql; + + TRACE("New action %lu\n", Action); + + OldIrql = KeAcquireInterruptSpinLock(PortData->InterruptObject); + AtaPortQueueEvent(PortData, DevExt, NULL, Action); + KeReleaseInterruptSpinLock(PortData->InterruptObject, OldIrql); +} diff --git a/drivers/storage/ide/atapi/satl.c b/drivers/storage/ide/atapi/satl.c new file mode 100644 index 00000000000..c6db30176ac --- /dev/null +++ b/drivers/storage/ide/atapi/satl.c @@ -0,0 +1,2063 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: SCSI/ATA Translation layer + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* GLOBALS ********************************************************************/ + +static const UCHAR AtapReadWriteCommandMap[12][2] = +{ + /* Write EXT Read EXT */ + { IDE_COMMAND_WRITE_EXT, IDE_COMMAND_READ_EXT }, // PIO single + { IDE_COMMAND_WRITE_MULTIPLE_EXT, IDE_COMMAND_READ_MULTIPLE_EXT }, // PIO multiple + { IDE_COMMAND_WRITE_DMA_EXT, IDE_COMMAND_READ_DMA_EXT }, // DMA + + /* Write FUA EXT Read FUA EXT */ + { 0, 0 }, // PIO single + { IDE_COMMAND_WRITE_MULTIPLE_FUA_EXT, 0 }, // PIO multiple + { IDE_COMMAND_WRITE_DMA_FUA_EXT, 0 }, // DMA + + /* Write Read */ + { IDE_COMMAND_WRITE, IDE_COMMAND_READ, }, // PIO single + { IDE_COMMAND_WRITE_MULTIPLE, IDE_COMMAND_READ_MULTIPLE, }, // PIO multiple + { IDE_COMMAND_WRITE_DMA, IDE_COMMAND_READ_DMA, }, // DMA + + /* Write FUA Read FUA */ + { 0, 0 }, // PIO single + { 0, 0 }, // PIO multiple + { 0, 0 }, // DMA +}; + +/* FUNCTIONS ******************************************************************/ + +static +inline +UCHAR +AtaReadWriteCommand( + _In_ PATA_DEVICE_REQUEST Request, + _In_ PATAPORT_IO_CONTEXT Device) +{ + ULONG CmdEntry; + + if (Request->Flags & REQUEST_FLAG_LBA48) + { + if (Device->DeviceFlags & DEVICE_PIO_FOR_LBA48_XFER) + Request->Flags &= ~REQUEST_FLAG_DMA; + + CmdEntry = 0; + } + else + { + CmdEntry = 6; + } + + if (Request->Flags & REQUEST_FLAG_FUA) + { + CmdEntry += 3; + } + + if (Request->Flags & REQUEST_FLAG_DMA) + { + CmdEntry += 2; + } + else if (Request->Flags & REQUEST_FLAG_READ_WRITE_MULTIPLE) + { + CmdEntry += 1; + } + + return AtapReadWriteCommandMap[CmdEntry][(Request->Flags & REQUEST_FLAG_DATA_IN) ? 1 : 0]; +} + +BOOLEAN +AtaReqDmaTransferToPioTransfer( + _In_ PATA_DEVICE_REQUEST Request) +{ + PATAPORT_IO_CONTEXT Device = (PATAPORT_IO_CONTEXT)Request->Device; + + ASSERT(!(Device->DeviceFlags & DEVICE_PIO_VIA_DMA)); + ASSERT(Request->Flags & REQUEST_DMA_FLAGS); + + /* ATAPI commands */ + if (Request->Flags & REQUEST_FLAG_PACKET_COMMAND) + { + Request->Flags &= ~REQUEST_DMA_FLAGS; + return TRUE; + } + + /* + * For ATA commands there is no simple way to achieve this, + * we have to fix the command opcode. + * Determine if it is safe or allowed to change the command. + */ + if (Request->Flags & REQUEST_FLAG_READ_WRITE) + { + Request->Flags &= ~REQUEST_DMA_FLAGS; + + Request->TaskFile.Command = AtaReadWriteCommand(Request, Device); + if (Request->TaskFile.Command == 0) + { + /* PIO is not available */ + Request->Flags |= REQUEST_DMA_FLAGS; + return FALSE; + } + + return TRUE; + } + + /* PIO is not available */ + return FALSE; +} + +static +ULONG64 +AtaReqLbaFromTaskFile( + _In_ PATA_DEVICE_REQUEST Request) +{ + PATA_TASKFILE TaskFile = &Request->TaskFile; + PATAPORT_IO_CONTEXT Device = (PATAPORT_IO_CONTEXT)Request->Device; + ULONG64 Lba; + + if (Device->DeviceFlags & DEVICE_LBA_MODE) + { + Lba = ((ULONG64)TaskFile->LowLba << 0) | + ((ULONG64)TaskFile->MidLba << 8) | + ((ULONG64)TaskFile->HighLba << 16); + + if (Request->Flags & REQUEST_FLAG_LBA48) + { + /* 48-bit command */ + Lba |= ((ULONG64)TaskFile->LowLbaEx << 24) | + ((ULONG64)TaskFile->MidLbaEx << 32) | + ((ULONG64)TaskFile->HighLbaEx << 40); + } + else + { + /* 28-bit command */ + Lba |= (ULONG64)(TaskFile->DriveSelect & 0x0F) << 24; + } + } + else + { + ULONG Cylinder, Head, Sector; + + /* Legacy CHS translation */ + Cylinder = TaskFile->LowLba; + Head = TaskFile->DriveSelect & 0x0F; + Sector = ((ULONG64)TaskFile->HighLba << 8) | TaskFile->MidLba; + + // TODO: Use 64bit math? + Lba = (((Cylinder * Device->Heads) + Head) * Device->SectorsPerTrack) + (Sector - 1); + } + + return Lba; +} + +static +BOOLEAN +AtaReqTranslateFixedError( + _In_ PATA_DEVICE_REQUEST Request, + _Out_ PUCHAR SK, + _Out_ PUCHAR ASK, + _Out_ PUCHAR ASCQ) +{ + if (Request->Output.Status & IDE_STATUS_DEVICE_FAULT) + { + *SK = SCSI_SENSE_HARDWARE_ERROR; + *ASK = SCSI_ADSENSE_INTERNAL_TARGET_FAILURE; + *ASCQ = SCSI_SENSEQ_INTERNAL_TARGET_FAILURE; + } + else if (Request->Output.Error & IDE_ERROR_DATA_ERROR) + { + if (Request->Flags & REQUEST_FLAG_DATA_OUT) + { + *SK = SCSI_SENSE_DATA_PROTECT; + *ASK = SCSI_ADSENSE_WRITE_PROTECT; + *ASCQ = 0; + } + else + { + *SK = SCSI_SENSE_MEDIUM_ERROR; + *ASK = SCSI_ADSENSE_UNRECOVERED_ERROR; + *ASCQ = SCSI_SENSEQ_UNRECOVERED_READ_ERROR; + } + } + else if (Request->Output.Error & IDE_ERROR_ID_NOT_FOUND) + { + *SK = SCSI_SENSE_ILLEGAL_REQUEST; + *ASK = SCSI_ADSENSE_ILLEGAL_BLOCK; + *ASCQ = SCSI_SENSEQ_LOGICAL_ADDRESS_OUT_OF_RANGE; + } + else if (Request->Output.Error & IDE_ERROR_CRC_ERROR) + { + *SK = SCSI_SENSE_HARDWARE_ERROR; + *ASK = SCSI_ADSENSE_LUN_COMMUNICATION; + *ASCQ = SCSI_SESNEQ_COMM_CRC_ERROR; + } + /* Return vendor specific codes for obsolete bits */ + else if (Request->Output.Error & IDE_ERROR_MEDIA_CHANGE) + { + *SK = SCSI_SENSE_UNIT_ATTENTION; + *ASK = SCSI_ADSENSE_MEDIUM_CHANGED; + *ASCQ = 0; + } + else if (Request->Output.Error & IDE_ERROR_MEDIA_CHANGE_REQ) + { + *SK = SCSI_SENSE_UNIT_ATTENTION; + *ASK = SCSI_ADSENSE_OPERATOR_REQUEST; + *ASCQ = SCSI_SENSEQ_MEDIUM_REMOVAL; + } + else if (Request->Output.Error & IDE_ERROR_END_OF_MEDIA) + { + *SK = SCSI_SENSE_NOT_READY; + *ASK = SCSI_ADSENSE_NO_MEDIA_IN_DEVICE; + *ASCQ = 0; + } + else if (Request->Output.Error & IDE_ERROR_ADDRESS_NOT_FOUND) + { + *SK = SCSI_SENSE_MEDIUM_ERROR; + *ASK = SCSI_ADSENSE_ADDRESS_MARK_NOT_FOUND_FOR_DATA_FIELD; + *ASCQ = 0; + } + /* The ABORT bit has low priority and indicates unknown error */ + else if (Request->Output.Error & IDE_ERROR_COMMAND_ABORTED) + { + *SK = SCSI_SENSE_ABORTED_COMMAND; + *ASK = SCSI_ADSENSE_NO_SENSE; + *ASCQ = 0; + } + else + { + *SK = SCSI_SENSE_ABORTED_COMMAND; + *ASK = SCSI_ADSENSE_NO_SENSE; + *ASCQ = 0; + + /* No sense data */ + return FALSE; + } + + return TRUE; +} + +UCHAR +AtaReqSetFixedAtaSenseData( + _In_ PATA_DEVICE_REQUEST Request) +{ + SCSI_SENSE_CODE SenseCode; + UCHAR SrbStatus, SK, ASK, ASCQ; + + ASSERT(!(Request->SrbStatus & SRB_STATUS_AUTOSENSE_VALID)); + + if (!AtaReqTranslateFixedError(Request, &SK, &ASK, &ASCQ)) + return Request->SrbStatus; + + SenseCode.SrbStatus = Request->SrbStatus; + SenseCode.SenseKey = SK; + SenseCode.AdditionalSenseCode = ASK; + SenseCode.AdditionalSenseCodeQualifier = ASCQ; + + SrbStatus = AtaReqSetFixedSenseData(Request->Srb, SenseCode); + AtaReqSetLbaInformation(Request->Srb, AtaReqLbaFromTaskFile(Request)); + + return SrbStatus; +} + +static +ULONG +AtaReqCopySatlBuffer( + _In_ PATA_DEVICE_REQUEST Request, + _In_ PVOID Buffer, + _In_ ULONG Length) +{ + PSCSI_REQUEST_BLOCK Srb = Request->Srb; + PMDL Mdl; + PVOID BaseAddress; + ULONG_PTR Offset; + ULONG BytesCount; + + /* The driver can overwrite the Request->Mdl field during translation */ + Mdl = Request->Irp->MdlAddress; + ASSERT(Mdl); + + BaseAddress = MmGetSystemAddressForMdlSafe(Mdl, HighPagePriority); + if (!BaseAddress) + return SRB_STATUS_INSUFFICIENT_RESOURCES; + + if (Length < Srb->DataTransferLength) + { + /* This indicates a residual underrun */ + BytesCount = Length; + Request->SrbStatus = SRB_STATUS_DATA_OVERRUN; + } + else + { + BytesCount = Srb->DataTransferLength; + Request->SrbStatus = SRB_STATUS_SUCCESS; + } + Request->DataTransferLength = BytesCount; + + /* Calculate the offset within DataBuffer */ + Offset = (ULONG_PTR)BaseAddress + + (ULONG_PTR)Srb->DataBuffer - + (ULONG_PTR)MmGetMdlVirtualAddress(Mdl); + + RtlCopyMemory((PVOID)Offset, Buffer, BytesCount); + return Request->SrbStatus; +} + +static +UCHAR +AtaReqTerminateInvalidOpCode( + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + SCSI_SENSE_CODE SenseCode; + + SenseCode.SrbStatus = SRB_STATUS_INVALID_REQUEST; + SenseCode.SenseKey = SCSI_SENSE_ILLEGAL_REQUEST; + SenseCode.AdditionalSenseCode = SCSI_ADSENSE_ILLEGAL_COMMAND; + SenseCode.AdditionalSenseCodeQualifier = 0; + + return AtaReqSetFixedSenseData(Srb, SenseCode); +} + +static +UCHAR +AtaReqTerminateInvalidField( + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + SCSI_SENSE_CODE SenseCode; + + SenseCode.SrbStatus = SRB_STATUS_INVALID_REQUEST; + SenseCode.SenseKey = SCSI_SENSE_ILLEGAL_REQUEST; + SenseCode.AdditionalSenseCode = SCSI_ADSENSE_INVALID_CDB; + SenseCode.AdditionalSenseCodeQualifier = 0; + + return AtaReqSetFixedSenseData(Srb, SenseCode); +} + +/* static */ +UCHAR +AtaReqTerminateInvalidFieldParameter( + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + SCSI_SENSE_CODE SenseCode; + + SenseCode.SrbStatus = SRB_STATUS_INVALID_REQUEST; + SenseCode.SenseKey = SCSI_SENSE_ILLEGAL_REQUEST; + SenseCode.AdditionalSenseCode = SCSI_ADSENSE_INVALID_FIELD_PARAMETER_LIST; + SenseCode.AdditionalSenseCodeQualifier = 0; + + return AtaReqSetFixedSenseData(Srb, SenseCode); +} + +static +UCHAR +AtaReqTerminateInvalidRange( + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + SCSI_SENSE_CODE SenseCode; + + /* + * This normally should not happen and only used + * to aid in detecting bugs in the class drivers or software. + */ + ASSERT(FALSE); + + SenseCode.SrbStatus = SRB_STATUS_INVALID_REQUEST; + SenseCode.SenseKey = SCSI_SENSE_ILLEGAL_REQUEST; + SenseCode.AdditionalSenseCode = SCSI_ADSENSE_ILLEGAL_BLOCK; + SenseCode.AdditionalSenseCodeQualifier = 0; + + return AtaReqSetFixedSenseData(Srb, SenseCode); +} + +static +BOOLEAN +AtaReqBuildIdentifyCommand( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request) +{ + Request->Flags = REQUEST_FLAG_DATA_IN | REQUEST_FLAG_EXCLUSIVE | REQUEST_FLAG_POLL; + Request->DataBuffer = &DevExt->IdentifyDeviceData; + Request->DataTransferLength = sizeof(DevExt->IdentifyDeviceData); + + if (!AtaReqAllocateMdl(Request)) + return FALSE; + + RtlZeroMemory(&Request->TaskFile, sizeof(Request->TaskFile)); + Request->TaskFile.Command = IDE_COMMAND_IDENTIFY; + + return TRUE; +} + +VOID +AtaReqBuildReadLogTaskFile( + _In_ PATA_DEVICE_REQUEST Request, + _In_ UCHAR LogAddress, + _In_ UCHAR PageNumber, + _In_ USHORT LogPageCount) +{ + PATA_TASKFILE TaskFile = &Request->TaskFile; + + /* PIO Data-In command */ + Request->Flags = REQUEST_FLAG_DATA_IN | REQUEST_FLAG_LBA48; + Request->DataTransferLength = LogPageCount * IDE_GP_LOG_SECTOR_SIZE; + + TaskFile->Feature = 0; + TaskFile->FeatureEx = 0; + + /* LOG PAGE COUNT */ + TaskFile->SectorCount = (UCHAR)LogPageCount; + TaskFile->SectorCountEx = (UCHAR)(LogPageCount >> 8); + + TaskFile->LowLba = LogAddress; // LOG ADDRESS + TaskFile->MidLba = PageNumber; // PAGE NUMBER + TaskFile->HighLba = 0; // Reserved + TaskFile->LowLbaEx = 0; // Reserved + TaskFile->MidLbaEx = 0; // PAGE NUMBER EX + TaskFile->HighLbaEx = 0; // Reserved + TaskFile->Command = IDE_COMMAND_READ_LOG_EXT; +} + +static +BOOLEAN +AtaReqBuildLbaTaskFile( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ ULONG64 Lba, + _In_ ULONG SectorCount) +{ + PATA_TASKFILE TaskFile = &Request->TaskFile; + UCHAR DriveSelect; + + /* Sector count must not exceed the maximum transfer length */ + ASSERT(SectorCount <= ATA_MAX_SECTORS_PER_IO); + + if (SectorCount > ATA_MAX_SECTORS_PER_IO) + return FALSE; + + Request->Flags |= REQUEST_FLAG_SET_DEVICE_REGISTER; + + /* 28-bit or 48-bit command */ + if (DevExt->Device.DeviceFlags & DEVICE_LBA_MODE) + { + DriveSelect = IDE_LBA_MODE; + + TaskFile->Feature = 0; + TaskFile->SectorCount = (UCHAR)SectorCount; + TaskFile->LowLba = (UCHAR)Lba; // LBA bits 0-7 + TaskFile->MidLba = (UCHAR)(Lba >> 8); // LBA bits 8-15 + TaskFile->HighLba = (UCHAR)(Lba >> 16); // LBA bits 16-23 + + if ((DevExt->Device.DeviceFlags & DEVICE_LBA48) && + ((Request->Flags & REQUEST_FLAG_FUA) || AtaCommandUseLba48(Lba, SectorCount))) + { + if ((Lba + SectorCount) > ATA_MAX_LBA_48) + return FALSE; + + /* 48-bit command */ + TaskFile->FeatureEx = 0; + TaskFile->SectorCountEx = (UCHAR)(SectorCount >> 8); + TaskFile->LowLbaEx = (UCHAR)(Lba >> 24); // LBA bits 24-31 + TaskFile->MidLbaEx = (UCHAR)(Lba >> 32); // LBA bits 32-39 + TaskFile->HighLbaEx = (UCHAR)(Lba >> 40); // LBA bits 40-47 + + Request->Flags |= REQUEST_FLAG_LBA48; + } + else + { + if ((Lba + SectorCount) > ATA_MAX_LBA_28) + return FALSE; + + /* 28-bit command */ + DriveSelect |= ((Lba >> 24) & 0x0F); // LBA bits 24-27 + } + } + else + { + ULONG ChsTemp, Cylinder, Head, Sector; + + if ((Lba + SectorCount) > ATA_MAX_LBA_28) + return FALSE; + + ChsTemp = (ULONG)Lba / DevExt->Device.SectorsPerTrack; + + /* Legacy CHS translation */ + Cylinder = ChsTemp / DevExt->Device.Heads; + Head = ChsTemp % DevExt->Device.Heads; + Sector = ((ULONG)Lba % DevExt->Device.SectorsPerTrack) + 1; + + /* Check for the 137 GB limit */ + if (Cylinder > 65535 || Head > 15 || Sector > 255) + return FALSE; + + TaskFile->Feature = 0; + TaskFile->SectorCount = (UCHAR)SectorCount; + TaskFile->LowLba = (UCHAR)Sector; + TaskFile->MidLba = (UCHAR)Cylinder; + TaskFile->HighLba = (UCHAR)(Cylinder >> 8); + + DriveSelect = Head; + } + TaskFile->DriveSelect = DevExt->Device.DeviceSelect | DriveSelect; + + return TRUE; +} + +static +BOOLEAN +AtaReqBuildNcqReadWriteTaskFile( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ ULONG64 Lba, + _In_ ULONG SectorCount) +{ + PATA_TASKFILE TaskFile = &Request->TaskFile; + + ASSERT(DevExt->Device.PortData->PortFlags & PORT_FLAG_NCQ); + ASSERT(!(DevExt->Device.DeviceFlags & DEVICE_PIO_ONLY)); + + /* Sector count must not exceed the maximum transfer length */ + ASSERT(SectorCount <= ATA_MAX_SECTORS_PER_IO); + + if (SectorCount > ATA_MAX_SECTORS_PER_IO) + return FALSE; + + if ((Lba + SectorCount) > ATA_MAX_LBA_48) + return FALSE; + + Request->Flags |= REQUEST_FLAG_NCQ | + REQUEST_DMA_FLAGS | + REQUEST_FLAG_LBA48 | + REQUEST_FLAG_SET_DEVICE_REGISTER; + + TaskFile->Feature = (UCHAR)SectorCount; + TaskFile->FeatureEx = (UCHAR)(SectorCount >> 8); + + // TODO: RARC, PRIO, ICC, AUX + TaskFile->SectorCount = 0; + TaskFile->SectorCountEx = 0; + + TaskFile->LowLba = (UCHAR)Lba; // LBA bits 0-7 + TaskFile->MidLba = (UCHAR)(Lba >> 8); // LBA bits 8-15 + TaskFile->HighLba = (UCHAR)(Lba >> 16); // LBA bits 16-23 + TaskFile->LowLbaEx = (UCHAR)(Lba >> 24); // LBA bits 24-31 + TaskFile->MidLbaEx = (UCHAR)(Lba >> 32); // LBA bits 32-39 + TaskFile->HighLbaEx = (UCHAR)(Lba >> 40); // LBA bits 40-47 + + TaskFile->DriveSelect = IDE_LBA_MODE; + if (Request->Flags & REQUEST_FLAG_FUA) + TaskFile->DriveSelect |= IDE_DEVICE_FUA_NCQ; + + if (Request->Flags & REQUEST_FLAG_DATA_OUT) + TaskFile->Command = IDE_COMMAND_WRITE_FPDMA_QUEUED; + else + TaskFile->Command = IDE_COMMAND_READ_FPDMA_QUEUED; + + return TRUE; +} + +static +UCHAR +AtaReqScsiReadWrite( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + PCDB Cdb; + ULONG64 Lba; + ULONG SectorCount; + + Request->Flags = REQUEST_FLAG_READ_WRITE; + + Cdb = (PCDB)Srb->Cdb; + switch (Cdb->AsByte[0]) + { + case SCSIOP_READ6: + case SCSIOP_WRITE6: + { + Lba = CdbGetLogicalBlockAddress6(Cdb); + break; + } + + case SCSIOP_READ: + case SCSIOP_WRITE: + { + Lba = CdbGetLogicalBlockAddress10(Cdb); + + if (Cdb->CDB10.ForceUnitAccess) + Request->Flags |= REQUEST_FLAG_FUA; + break; + } + + case SCSIOP_READ12: + case SCSIOP_WRITE12: + { + Lba = CdbGetLogicalBlockAddress12(Cdb); + + if (Cdb->CDB12.ForceUnitAccess) + Request->Flags |= REQUEST_FLAG_FUA; + break; + } + + case SCSIOP_READ16: + case SCSIOP_WRITE16: + { + Lba = CdbGetLogicalBlockAddress16(Cdb); + + if (Cdb->CDB16.ForceUnitAccess) + Request->Flags |= REQUEST_FLAG_FUA; + break; + } + + default: + ASSERT(FALSE); + UNREACHABLE; + } + + /* Check for write operations */ + if (Cdb->AsByte[0] & 0x02) + Request->Flags |= REQUEST_FLAG_DATA_OUT; + else + Request->Flags |= REQUEST_FLAG_DATA_IN; + + // FIXME: HACK Workaround for disk.sys which incorrectly enables FUA support + if (!(DevExt->Device.DeviceFlags & DEVICE_NCQ)) + Request->Flags &= ~REQUEST_FLAG_FUA; + + SectorCount = Request->DataTransferLength + (DevExt->Device.SectorSize - 1); + SectorCount /= DevExt->Device.SectorSize; + + if (DevExt->Device.DeviceFlags & DEVICE_NCQ) + { + if (!AtaReqBuildNcqReadWriteTaskFile(DevExt, Request, Lba, SectorCount)) + return AtaReqTerminateInvalidRange(Srb); + } + else + { + if (!AtaReqBuildLbaTaskFile(DevExt, Request, Lba, SectorCount)) + return AtaReqTerminateInvalidRange(Srb); + + if (!(DevExt->Device.DeviceFlags & DEVICE_PIO_ONLY) && + !(SRB_GET_FLAGS(Srb) & SRB_FLAG_PIO_RETRY)) + { + Request->Flags |= REQUEST_DMA_FLAGS; + } + else if (DevExt->Device.MultiSectorCount != 0) + { + Request->Flags |= REQUEST_FLAG_READ_WRITE_MULTIPLE; + } + + /* Choose the command opcode */ + Request->TaskFile.Command = AtaReadWriteCommand(Request, &DevExt->Device); + if (Request->TaskFile.Command == 0) + return AtaReqTerminateInvalidField(Srb); + } + + return SRB_STATUS_PENDING; +} + +static +UCHAR +AtaReqScsiSynchronizeCache( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + UCHAR Command; + + if (!AtaDevIsVolatileWriteCacheEnabled(&DevExt->IdentifyDeviceData)) + return SRB_STATUS_SUCCESS; + + Command = AtaDeviceGetFlushCacheCommand(DevExt); + if (Command == 0) + return SRB_STATUS_SUCCESS; + + /* Prepare a non-data command */ + Request->TaskFile.Command = Command; + + /* NOTE: This command may take longer than 30 seconds to complete */ + return SRB_STATUS_PENDING; +} + +static +UCHAR +AtaReqScsiVerify( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + PCDB Cdb; + ULONG64 Lba; + ULONG VerificationLength; + + Cdb = (PCDB)Srb->Cdb; + + /* Byte-by-byte comparison not supported */ + if (Cdb->VERIFY16.ByteCheck || Cdb->VERIFY16.BlockVerify) + return AtaReqTerminateInvalidField(Srb); + + /* Prepare a non-data command */ + switch (Cdb->AsByte[0]) + { + case SCSIOP_VERIFY: + { + Lba = CdbGetLogicalBlockAddress10(Cdb); + VerificationLength = CdbGetTransferLength10(Cdb); + break; + } + + case SCSIOP_VERIFY12: + { + Lba = CdbGetLogicalBlockAddress12(Cdb); + VerificationLength = CdbGetTransferLength12(Cdb); + break; + } + + case SCSIOP_VERIFY16: + { + Lba = CdbGetLogicalBlockAddress16(Cdb); + VerificationLength = CdbGetTransferLength16(Cdb); + break; + } + + default: + ASSERT(FALSE); + UNREACHABLE; + } + + if (!AtaReqBuildLbaTaskFile(DevExt, Request, Lba, VerificationLength)) + return AtaReqTerminateInvalidRange(Srb); + + /* Choose the command opcode */ + if (Request->Flags & REQUEST_FLAG_LBA48) + Request->TaskFile.Command = IDE_COMMAND_VERIFY_EXT; + else + Request->TaskFile.Command = IDE_COMMAND_VERIFY; + + return SRB_STATUS_PENDING; +} + +static +ATA_COMPLETION_ACTION +AtaReqCompleteReadCapacity( + _In_ PATA_DEVICE_REQUEST Request) +{ + PATAPORT_DEVICE_EXTENSION DevExt; + PSCSI_REQUEST_BLOCK Srb = Request->Srb; + ULONG Length; + + DevExt = CONTAINING_RECORD(Request->Device, ATAPORT_DEVICE_EXTENSION, Device); + + AtaDeviceSetAddressingMode(DevExt); + + if (DevExt->Device.TotalSectors == 1) + { + SCSI_SENSE_CODE SenseCode; + + SenseCode.SrbStatus = SRB_STATUS_ERROR; + SenseCode.SenseKey = SCSI_SENSE_NOT_READY; + SenseCode.AdditionalSenseCode = SCSI_ADSENSE_NO_MEDIA_IN_DEVICE; + SenseCode.AdditionalSenseCodeQualifier = 0; + + Request->SrbStatus = AtaReqSetFixedSenseData(Srb, SenseCode); + return COMPLETE_IRP; + } + + if (Srb->Cdb[0] == SCSIOP_READ_CAPACITY) + { + PREAD_CAPACITY_DATA CapacityData; + ULONG MaximumLba; + + MaximumLba = min(DevExt->Device.TotalSectors - 1, MAXULONG); + + CapacityData = DevExt->Device.LocalBuffer; + CapacityData->LogicalBlockAddress = RtlUlongByteSwap(MaximumLba); + CapacityData->BytesPerBlock = RtlUlongByteSwap(DevExt->Device.SectorSize); + + Length = sizeof(*CapacityData); + } + else // SCSIOP_READ_CAPACITY16 + { + PREAD_CAPACITY16_DATA CapacityData; + ULONG LogicalPerPhysicalExponent, LogicalSectorsPerPhysicalSector; + ULONG LogicalSectorAlignment, LowestAlignedBlock; + ULONG64 MaximumLba; + + LogicalSectorAlignment = AtaDevLogicalSectorAlignment(&DevExt->IdentifyDeviceData); + if (LogicalSectorAlignment != 0) + { + LogicalSectorsPerPhysicalSector = + AtaDevLogicalSectorsPerPhysicalSector(&DevExt->IdentifyDeviceData, + &LogicalPerPhysicalExponent); + + + LowestAlignedBlock = (LogicalSectorsPerPhysicalSector - LogicalSectorAlignment); + LowestAlignedBlock %= LogicalSectorsPerPhysicalSector; + } + else + { + LowestAlignedBlock = 0; + } + + MaximumLba = DevExt->Device.TotalSectors - 1; + + CapacityData = DevExt->Device.LocalBuffer; + + RtlZeroMemory(CapacityData, sizeof(*CapacityData)); + CapacityData->LogicalBlockAddress.QuadPart = RtlUlonglongByteSwap(MaximumLba); + CapacityData->BytesPerBlock = RtlUlongByteSwap(DevExt->Device.SectorSize); + CapacityData->LogicalPerPhysicalExponent = LogicalPerPhysicalExponent; + CapacityData->LowestAlignedBlock_MSB = (UCHAR)(LowestAlignedBlock >> 8); + CapacityData->LowestAlignedBlock_LSB = (UCHAR)LowestAlignedBlock; + + if (AtaDevHasTrimFunction(&DevExt->IdentifyDeviceData)) + { + if (AtaDevHasDratFunction(&DevExt->IdentifyDeviceData)) + { + CapacityData->LBPME = 1; + + if (AtaDevHasRzatFunction(&DevExt->IdentifyDeviceData)) + CapacityData->LBPRZ = 1; + } + } + + Length = CdbGetAllocationLength16((PCDB)Srb->Cdb); + } + + AtaReqCopySatlBuffer(Request, DevExt->Device.LocalBuffer, Length); + return COMPLETE_IRP; +} + +static +UCHAR +AtaReqScsiReadCapacity( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + if (Srb->Cdb[0] == SCSIOP_READ_CAPACITY) + { + if (Srb->DataTransferLength < sizeof(READ_CAPACITY_DATA)) + return AtaReqTerminateInvalidField(Srb); + } + else // SCSIOP_READ_CAPACITY16 + { + if (Srb->DataTransferLength < CdbGetAllocationLength16((PCDB)Srb->Cdb)) + return AtaReqTerminateInvalidField(Srb); + } + + /* Update the identify data */ + if (!AtaReqBuildIdentifyCommand(DevExt, Request)) + return SRB_STATUS_INSUFFICIENT_RESOURCES; + + Request->Complete = AtaReqCompleteReadCapacity; + return SRB_STATUS_PENDING; +} + +static +ULONG +AtaReqControlModePage( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PVOID Buffer) +{ + PMODE_CONTROL_PAGE PageData = Buffer; + + PageData->PageCode = MODE_PAGE_CONTROL; + PageData->PageLength = + sizeof(PageData) - RTL_SIZEOF_THROUGH_FIELD(MODE_CONTROL_PAGE, PageLength); + + PageData->QERR = 0; + PageData->QueueAlgorithmModifier = 1; + PageData->BusyTimeoutPeriod[0] = 0xFF; + PageData->BusyTimeoutPeriod[1] = 0xFF; + PageData->D_SENSE = !!(DevExt->Device.DeviceFlags & DEVICE_DESCRIPTOR_SENSE); + + return sizeof(PageData); +} + +static +ULONG +AtaReqControlExtensionModePage( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PVOID Buffer) +{ + PMODE_CONTROL_EXTENSION_PAGE PageData = Buffer; + + PageData->SubPageFormat = 1; + PageData->SubPageCode = 0x00; + PageData->PageCode = MODE_PAGE_CONTROL; + PageData->PageLength[1] = + sizeof(PageData) - RTL_SIZEOF_THROUGH_FIELD(MODE_CONTROL_EXTENSION_PAGE, PageLength); + + return sizeof(PageData); +} + +static +ULONG +AtaReqRwErrorRecoveryModePage( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PVOID Buffer) +{ + PMODE_READ_WRITE_RECOVERY_PAGE PageData = Buffer; + + PageData->PageCode = MODE_PAGE_ERROR_RECOVERY; + PageData->PageLength = + sizeof(PageData) - RTL_SIZEOF_THROUGH_FIELD(MODE_READ_WRITE_RECOVERY_PAGE, PageLength); + + PageData->AWRE = 1; + + return sizeof(PageData); +} + +static +ULONG +AtaReqCachingModePage( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PVOID Buffer) +{ + PMODE_CACHING_PAGE_SPC5 PageData = Buffer; + + PageData->PageCode = MODE_PAGE_CACHING; + PageData->PageLength = + sizeof(PageData) - RTL_SIZEOF_THROUGH_FIELD(MODE_CACHING_PAGE_SPC5, PageLength); + + if (AtaDevIsVolatileWriteCacheEnabled(&DevExt->IdentifyDeviceData)) + PageData->WriteCacheEnable = 1; + + if (AtaDevIsReadLookAHeadEnabled(&DevExt->IdentifyDeviceData)) + PageData->DisableReadAHead = 1; + + return sizeof(PageData); +} + +static +ULONG +AtaReqInformationalExceptionsControlModePage( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PVOID Buffer) +{ + PMODE_INFO_EXCEPTIONS PageData = Buffer; + + PageData->PageCode = MODE_PAGE_FAULT_REPORTING; + PageData->PageLength = + sizeof(PageData) - RTL_SIZEOF_THROUGH_FIELD(MODE_INFO_EXCEPTIONS, PageLength); + + /* Only report informational exception condition on request */ + PageData->ReportMethod = 6; + + return sizeof(PageData); +} + +static +ATA_COMPLETION_ACTION +AtaReqCompleteModeSense( + _In_ PATA_DEVICE_REQUEST Request) +{ + PATAPORT_DEVICE_EXTENSION DevExt; + UCHAR PageCode; + USHORT ModeDataLength; + PUCHAR Buffer; + BOOLEAN Is6ByteCommand, IsWriteProtected; + PCDB Cdb; + union _HEADER + { + PMODE_PARAMETER_HEADER Header6; + PMODE_PARAMETER_HEADER10 Header10; + } ModeHeader; + + DevExt = CONTAINING_RECORD(Request->Device, ATAPORT_DEVICE_EXTENSION, Device); + + // TODO: Handle MSN +#if 0 + /* Determine the media status */ + if ((Request->TranslationState == 0) && (DevExt->Device.DeviceFlags & DEVICE_HAS_MEDIA_STATUS)) + { + Request->Flags = 0; + Request->TaskFile.Command = IDE_COMMAND_GET_MEDIA_STATUS; + + ++Request->TranslationState; + return COMPLETE_START_AGAIN; + } +#endif + + RtlZeroMemory(DevExt->Device.LocalBuffer, ATA_LOCAL_BUFFER_SIZE); + + Cdb = (PCDB)Request->Srb->Cdb; + Is6ByteCommand = (Cdb->AsByte[0] == SCSIOP_MODE_SENSE); + + Buffer = DevExt->Device.LocalBuffer; + + /* Media status outputs */ + if (Request->TranslationState != 0) + { + IsWriteProtected = (Request->Output.Status & IDE_STATUS_ERROR) && + !(Request->Output.Error & IDE_ERROR_COMMAND_ABORTED) && + (Request->Output.Error & IDE_ERROR_WRITE_PROTECT); + } + else + { + IsWriteProtected = FALSE; + } + + ModeHeader.Header6 = (PMODE_PARAMETER_HEADER)Buffer; + + /* Mode parameter header */ + if (Is6ByteCommand) + { + if (IsWriteProtected) + ModeHeader.Header6->DeviceSpecificParameter |= MODE_DSP_WRITE_PROTECT; + if (DevExt->Device.DeviceFlags & DEVICE_HAS_FUA) + ModeHeader.Header6->DeviceSpecificParameter |= MODE_DSP_FUA_SUPPORTED; + + Buffer += sizeof(*ModeHeader.Header6); + } + else + { + if (IsWriteProtected) + ModeHeader.Header10->DeviceSpecificParameter |= MODE_DSP_WRITE_PROTECT; + if (DevExt->Device.DeviceFlags & DEVICE_HAS_FUA) + ModeHeader.Header10->DeviceSpecificParameter |= MODE_DSP_FUA_SUPPORTED; + + Buffer += sizeof(*ModeHeader.Header10); + } + + /* Short LBA mode parameter block descriptor */ + if (!Cdb->MODE_SENSE.Dbd) + { + PFORMAT_DESCRIPTOR FormatDescriptor; + + if (Is6ByteCommand) + { + ModeHeader.Header6->BlockDescriptorLength = sizeof(*FormatDescriptor); + } + else + { + ModeHeader.Header10->BlockDescriptorLength[0] = (UCHAR)(sizeof(*FormatDescriptor) >> 8); + ModeHeader.Header10->BlockDescriptorLength[1] = (UCHAR)sizeof(*FormatDescriptor); + } + + FormatDescriptor = (PFORMAT_DESCRIPTOR)Buffer; + FormatDescriptor->BlockLength[0] = (UCHAR)(DevExt->Device.SectorSize >> 16); + FormatDescriptor->BlockLength[1] = (UCHAR)(DevExt->Device.SectorSize >> 8); + FormatDescriptor->BlockLength[2] = (UCHAR)DevExt->Device.SectorSize; + + Buffer += sizeof(*FormatDescriptor); + } + + PageCode = Cdb->MODE_SENSE.PageCode; + + /* Return SCSI mode pages */ + if (PageCode == MODE_PAGE_CONTROL || PageCode == MODE_SENSE_RETURN_ALL) + { + UCHAR SubPageCode = Cdb->MODE_SENSE.Reserved3; + + if (SubPageCode == 0x00 || SubPageCode == 0xFF) + Buffer += AtaReqControlModePage(DevExt, Buffer); + if (SubPageCode == 0x01 || SubPageCode == 0xFF) + Buffer += AtaReqControlExtensionModePage(DevExt, Buffer); + } + if (PageCode == MODE_PAGE_ERROR_RECOVERY || PageCode == MODE_SENSE_RETURN_ALL) + { + Buffer += AtaReqRwErrorRecoveryModePage(DevExt, Buffer); + } + if (PageCode == MODE_PAGE_CACHING || PageCode == MODE_SENSE_RETURN_ALL) + { + Buffer += AtaReqCachingModePage(DevExt, Buffer); + } + if (PageCode == MODE_PAGE_FAULT_REPORTING || PageCode == MODE_SENSE_RETURN_ALL) + { + Buffer += AtaReqInformationalExceptionsControlModePage(DevExt, Buffer); + } + + ModeDataLength = Buffer - (PUCHAR)DevExt->Device.LocalBuffer; + + if (Is6ByteCommand) + { + ModeDataLength -= RTL_SIZEOF_THROUGH_FIELD(MODE_PARAMETER_HEADER, MediumType); + + ModeHeader.Header6->ModeDataLength = ModeDataLength; + } + else + { + ModeDataLength -= RTL_SIZEOF_THROUGH_FIELD(MODE_PARAMETER_HEADER10, MediumType); + + ModeHeader.Header10->ModeDataLength[0] = (UCHAR)(ModeDataLength >> 8); + ModeHeader.Header10->ModeDataLength[1] = (UCHAR)ModeDataLength; + } + + AtaReqCopySatlBuffer(Request, + DevExt->Device.LocalBuffer, + Buffer - (PUCHAR)DevExt->Device.LocalBuffer); + return COMPLETE_IRP; +} + +static +UCHAR +AtaReqScsiModeSense( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb, + _In_ BOOLEAN Is6ByteCommand) +{ + PCDB Cdb = (PCDB)Srb->Cdb; + + if (Cdb->MODE_SENSE.Pc == MODE_SENSE_SAVED_VALUES) + return AtaReqTerminateInvalidField(Srb); + + switch (Cdb->MODE_SENSE.PageCode) + { + case MODE_PAGE_FAULT_REPORTING: + { + if (!AtaDevHasSmartFeature(&DevExt->IdentifyDeviceData)) + return AtaReqTerminateInvalidField(Srb); + + break; + } + + case MODE_PAGE_CONTROL: + { + UCHAR SubPageCode = Cdb->MODE_SENSE.Reserved3; + + if (SubPageCode != 0x00 && SubPageCode != 0x01 && SubPageCode != 0xFF) + return AtaReqTerminateInvalidField(Srb); + + break; + } + + case MODE_PAGE_ERROR_RECOVERY: + case MODE_PAGE_POWER_CONDITION: + case MODE_PAGE_CACHING: + case MODE_SENSE_RETURN_ALL: + break; + + default: + return AtaReqTerminateInvalidField(Srb); + } + + /* Update the identify data */ + if (!AtaReqBuildIdentifyCommand(DevExt, Request)) + return SRB_STATUS_INSUFFICIENT_RESOURCES; + + Request->Complete = AtaReqCompleteModeSense; + + return SRB_STATUS_PENDING; +} + +static +UCHAR +AtaReqScsiModeSelect( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb, + _In_ BOOLEAN Is6ByteCommand) +{ + // TODO: Implement + return AtaReqTerminateInvalidField(Srb); +} + +CODE_SEG("PAGE") +VOID +AtaCreateStandardInquiryData( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PIDENTIFY_DEVICE_DATA IdentifyData = &DevExt->IdentifyDeviceData; + PINQUIRYDATA InquiryData = &DevExt->InquiryData; + ULONG Offset; + PUCHAR VersionDescriptor; + + PAGED_CODE(); + + InquiryData->DeviceType = DIRECT_ACCESS_DEVICE; + InquiryData->RemovableMedia = AtaDevIsRemovable(IdentifyData); + InquiryData->Versions = 0x07; // SPC-5 + InquiryData->ResponseDataFormat = 2; // This means "Complies to this standard" + + if (DevExt->Device.DeviceFlags & DEVICE_NCQ) + InquiryData->CommandQueue = 1; + + /* T10 vendor ID */ + RtlCopyMemory(InquiryData->VendorId, "ATA ", RTL_FIELD_SIZE(INQUIRYDATA, VendorId)); + + /* Product ID */ + AtaCopyIdStringUnsafe(InquiryData->ProductId, + IdentifyData->ModelNumber, + RTL_FIELD_SIZE(INQUIRYDATA, ProductId)); + + /* Product revision level */ + if (IdentifyData->FirmwareRevision[4] == ' ' && + IdentifyData->FirmwareRevision[5] == ' ' && + IdentifyData->FirmwareRevision[6] == ' ' && + IdentifyData->FirmwareRevision[7] == ' ') + { + Offset = 0; + } + else + { + Offset = 4; + } + AtaCopyIdStringUnsafe(InquiryData->ProductRevisionLevel, + &IdentifyData->FirmwareRevision[Offset], + RTL_FIELD_SIZE(INQUIRYDATA, ProductRevisionLevel)); + + VersionDescriptor = &InquiryData->Reserved3[3]; + + /* SAM-5 (no version claimed) */ + *VersionDescriptor++ = 0x00; + *VersionDescriptor++ = 0xA0; + + /* SPC-5 (no version claimed) */ + *VersionDescriptor++ = 0x05; + *VersionDescriptor++ = 0xC0; + + /* SBC-4 (no version claimed) */ + *VersionDescriptor++ = 0x06; + *VersionDescriptor++ = 0x00; + + if (AtaDevHasIeee1667(IdentifyData)) + { + /* IEEE 1667 (no version claimed) */ + *VersionDescriptor++ = 0xFF; + *VersionDescriptor++ = 0xC0; + } + + if (IdentifyData->MajorRevision != 0xFFFF) + { + if (IdentifyData->MajorRevision & (1 << 8)) + { + /* ATA/ATAPI-8 ATA8-ACS ATA/ATAPI Command Set (no version claimed) */ + *VersionDescriptor++ = 0x16; + *VersionDescriptor++ = 0x23; + } + else if (IdentifyData->MajorRevision & (1 << 7)) + { + if (IdentifyData->MinorRevision == 0x1D) + { + /* ATA/ATAPI-7 INCITS 397-2005 */ + *VersionDescriptor++ = 0x16; + *VersionDescriptor++ = 0x1C; + } + else + { + /* ATA/ATAPI-7 (no version claimed) */ + *VersionDescriptor++ = 0x16; + *VersionDescriptor++ = 0x00; + } + } + else if (IdentifyData->MajorRevision & (1 << 6)) + { + if (IdentifyData->MinorRevision == 0x22) + { + /* ATA/ATAPI-6 INCITS 361-2002 */ + *VersionDescriptor++ = 0x15; + *VersionDescriptor++ = 0xFD; + } + else + { + /* ATA/ATAPI-6 (no version claimed) */ + *VersionDescriptor++ = 0x15; + *VersionDescriptor++ = 0xE0; + } + } + } + + InquiryData->AdditionalLength = + ((ULONG_PTR)VersionDescriptor - (ULONG_PTR)InquiryData) - + RTL_SIZEOF_THROUGH_FIELD(INQUIRYDATA, AdditionalLength); + + TRACE("VendorId: '%.*s'\n", + RTL_FIELD_SIZE(INQUIRYDATA, VendorId), InquiryData->VendorId); + TRACE("ProductId: '%.*s'\n", + RTL_FIELD_SIZE(INQUIRYDATA, ProductId), InquiryData->ProductId); + TRACE("ProductRevisionLevel: '%.*s'\n", + RTL_FIELD_SIZE(INQUIRYDATA, ProductRevisionLevel), InquiryData->ProductRevisionLevel); +} + +static +ULONG +AtaReqScsiReportLuns( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + PCDB Cdb = (PCDB)Srb->Cdb; + PLUN_LIST LunList; + ULONG Length, LunListLength; + + Length = Cdb->REPORT_LUNS.AllocationLength[0] << 24 | + Cdb->REPORT_LUNS.AllocationLength[1] << 16 | + Cdb->REPORT_LUNS.AllocationLength[2] << 8 | + Cdb->REPORT_LUNS.AllocationLength[3]; + + if (Srb->DataTransferLength < Length) + return AtaReqTerminateInvalidField(Srb); + + LunList = DevExt->Device.LocalBuffer; + LunListLength = RTL_FIELD_SIZE(LUN_LIST, Lun[0]); + + RtlZeroMemory(LunList, sizeof(*LunList)); + LunList->LunListLength[0] = (UCHAR)(LunListLength >> 24); + LunList->LunListLength[1] = (UCHAR)(LunListLength >> 16); + LunList->LunListLength[2] = (UCHAR)(LunListLength >> 8); + LunList->LunListLength[3] = (UCHAR)LunListLength; + + return AtaReqCopySatlBuffer(Request, DevExt->Device.LocalBuffer, Length); +} + +static +ULONG +AtaReqScsiInquirySupportedPages( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PVPD_SUPPORTED_PAGES_PAGE SupportedPages = DevExt->Device.LocalBuffer; + ULONG i = 0; + + RtlZeroMemory(SupportedPages, sizeof(*SupportedPages)); + SupportedPages->SupportedPageList[i++] = VPD_SUPPORTED_PAGES; + SupportedPages->SupportedPageList[i++] = VPD_SERIAL_NUMBER; + SupportedPages->SupportedPageList[i++] = VPD_DEVICE_IDENTIFIERS; + //SupportedPages->SupportedPageList[i++] = VPD_EXTENDED_INQUIRY_DATA; // TODO: Implement + // SupportedPages->SupportedPageList[i++] = VPD_ATA_INFORMATION; // TODO: Implement + SupportedPages->SupportedPageList[i++] = VPD_BLOCK_LIMITS; + SupportedPages->SupportedPageList[i++] = VPD_BLOCK_DEVICE_CHARACTERISTICS; + SupportedPages->SupportedPageList[i++] = VPD_LOGICAL_BLOCK_PROVISIONING; + SupportedPages->PageLength = i; + + return sizeof(*SupportedPages) + i; +} + +static +ULONG +AtaReqScsiInquirySerialNumber( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PVPD_SERIAL_NUMBER_PAGE SerialNumberPage = DevExt->Device.LocalBuffer; + + RtlZeroMemory(SerialNumberPage, FIELD_OFFSET(VPD_SERIAL_NUMBER_PAGE, SerialNumber)); + SerialNumberPage->PageLength = RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, SerialNumber); + + AtaCopyIdStringUnsafe(SerialNumberPage->SerialNumber, + DevExt->IdentifyDeviceData.SerialNumber, + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, SerialNumber)); + + return FIELD_OFFSET(VPD_SERIAL_NUMBER_PAGE, SerialNumber) + + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, SerialNumber); +} + +static +ULONG +AtaReqScsiInquiryDeviceIdentifiers( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PVPD_IDENTIFICATION_PAGE DeviceIdentificationPage = DevExt->Device.LocalBuffer; + PVPD_IDENTIFICATION_DESCRIPTOR Descriptor; + UCHAR PageLength; + + PageLength = FIELD_OFFSET(VPD_IDENTIFICATION_PAGE, Descriptors) + + FIELD_OFFSET(VPD_IDENTIFICATION_DESCRIPTOR, Identifier); + + if (AtaDevHasWorldWideName(&DevExt->IdentifyDeviceData)) + { + PageLength += RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, WorldWideName); + } + else + { + PageLength += RTL_FIELD_SIZE(INQUIRYDATA, VendorId) + + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, ModelNumber) + + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, SerialNumber); + } + + RtlZeroMemory(DeviceIdentificationPage, PageLength); + DeviceIdentificationPage->PageLength = + PageLength - RTL_SIZEOF_THROUGH_FIELD(VPD_IDENTIFICATION_PAGE, PageLength); + + Descriptor = (PVPD_IDENTIFICATION_DESCRIPTOR)&DeviceIdentificationPage->Descriptors[0]; + + if (AtaDevHasWorldWideName(&DevExt->IdentifyDeviceData)) + { + /* NAA descriptor */ + Descriptor->CodeSet = VpdCodeSetBinary; + Descriptor->IdentifierType = VpdIdentifierTypeFCPHName; + Descriptor->IdentifierLength = RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, WorldWideName); + + AtaCopyIdStringUnsafe(Descriptor->Identifier, + (PUCHAR)DevExt->IdentifyDeviceData.WorldWideName, + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, WorldWideName)); + } + else + { + PUCHAR Identifier; + + /* T10 vendor ID based descriptor */ + Descriptor->CodeSet = VpdCodeSetAscii; + Descriptor->IdentifierType = VpdIdentifierTypeVendorId; + Descriptor->IdentifierLength = RTL_FIELD_SIZE(INQUIRYDATA, VendorId) + + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, ModelNumber) + + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, SerialNumber); + + Identifier = &Descriptor->Identifier[0]; + + /* T10 vendor ID */ + RtlCopyMemory(Identifier, "ATA ", RTL_FIELD_SIZE(INQUIRYDATA, VendorId)); + Identifier += RTL_FIELD_SIZE(INQUIRYDATA, VendorId); + + /* Model number field */ + AtaCopyIdStringUnsafe(Identifier, + DevExt->IdentifyDeviceData.ModelNumber, + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, ModelNumber)); + Identifier += RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, ModelNumber); + + /* Serial number field */ + AtaCopyIdStringUnsafe(Identifier, + DevExt->IdentifyDeviceData.SerialNumber, + RTL_FIELD_SIZE(IDENTIFY_DEVICE_DATA, SerialNumber)); + } + + return PageLength + RTL_SIZEOF_THROUGH_FIELD(VPD_IDENTIFICATION_PAGE, PageLength); +} + +static +ULONG +AtaReqScsiInquiryBlockLimits( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PVPD_BLOCK_LIMITS_PAGE BlockLimitsPage = DevExt->Device.LocalBuffer; + USHORT PageLength; + + PageLength = sizeof(*BlockLimitsPage) - + RTL_SIZEOF_THROUGH_FIELD(VPD_BLOCK_LIMITS_PAGE, PageLength); + + RtlZeroMemory(BlockLimitsPage, sizeof(*BlockLimitsPage)); + BlockLimitsPage->PageLength[0] = (UCHAR)(PageLength >> 8); + BlockLimitsPage->PageLength[1] = (UCHAR)PageLength; + + // TODO: Implement +#if 0 + if (AtaDevHasTrimFunction(&DevExt->IdentifyDeviceData)) + { + BlockLimitsPage->MaximumUnmapLBACount[0] = 0; + BlockLimitsPage->MaximumUnmapLBACount[1] = 0; + BlockLimitsPage->MaximumUnmapLBACount[2] = 0; + BlockLimitsPage->MaximumUnmapLBACount[4] = 0; + + BlockLimitsPage->MaximumUnmapBlockDescriptorCount[0] = 0; + BlockLimitsPage->MaximumUnmapBlockDescriptorCount[1] = 0; + BlockLimitsPage->MaximumUnmapBlockDescriptorCount[2] = 0; + BlockLimitsPage->MaximumUnmapBlockDescriptorCount[3] = 0; + } +#endif + + return sizeof(*BlockLimitsPage); +} + +static +ULONG +AtaReqScsiInquiryBlockDeviceCharacteristics( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PVPD_BLOCK_DEVICE_CHARACTERISTICS_PAGE Characteristics = DevExt->Device.LocalBuffer; + USHORT MediumRotationRate; + + RtlZeroMemory(Characteristics, sizeof(*Characteristics)); + Characteristics->PageLength = + sizeof(*Characteristics) - + RTL_SIZEOF_THROUGH_FIELD(VPD_BLOCK_DEVICE_CHARACTERISTICS_PAGE, PageLength); + + MediumRotationRate = AtaDevMediumRotationRate(&DevExt->IdentifyDeviceData); + Characteristics->MediumRotationRateMsb = (UCHAR)(MediumRotationRate >> 8); + Characteristics->MediumRotationRateLsb = (UCHAR)MediumRotationRate; + Characteristics->NominalFormFactor = AtaDevNominalFormFactor(&DevExt->IdentifyDeviceData); + Characteristics->ZONED = AtaDevZonedCapabilities(&DevExt->IdentifyDeviceData); + return sizeof(*Characteristics); +} + +static +ULONG +AtaReqScsiInquiryLogicalBlockProvisioning( + _In_ PATAPORT_DEVICE_EXTENSION DevExt) +{ + PVPD_LOGICAL_BLOCK_PROVISIONING_PAGE LogicalBlockProvisioningPage = DevExt->Device.LocalBuffer; + + RtlZeroMemory(LogicalBlockProvisioningPage, + FIELD_OFFSET(VPD_LOGICAL_BLOCK_PROVISIONING_PAGE, ProvisioningGroupDescr)); + LogicalBlockProvisioningPage->PageLength[1] = + FIELD_OFFSET(VPD_LOGICAL_BLOCK_PROVISIONING_PAGE, ProvisioningGroupDescr) - + RTL_SIZEOF_THROUGH_FIELD(VPD_LOGICAL_BLOCK_PROVISIONING_PAGE, PageLength); + + if (AtaDevHasTrimFunction(&DevExt->IdentifyDeviceData)) + { + // TODO: Implement +#if 0 + LogicalBlockProvisioningPage->LBPU = 1; // UNMAP + LogicalBlockProvisioningPage->LBPWS = 1; // WRITE SAME (16) + UNMAP + LogicalBlockProvisioningPage->LBPWS10 = 1; // WRITE SAME (10) + UNMAP +#endif + + if (AtaDevHasDratFunction(&DevExt->IdentifyDeviceData)) + LogicalBlockProvisioningPage->ANC_SUP = 1; + + if (AtaDevHasRzatFunction(&DevExt->IdentifyDeviceData)) + LogicalBlockProvisioningPage->LBPRZ = 1; + } + + return FIELD_OFFSET(VPD_LOGICAL_BLOCK_PROVISIONING_PAGE, ProvisioningGroupDescr); +} + +static +UCHAR +AtaReqScsiInquiry( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + PVPD_SUPPORTED_PAGES_PAGE Buffer; + PCDB Cdb = (PCDB)Srb->Cdb; + ULONG Length; + + /* Return the standard INQUIRY data */ + if (!Cdb->CDB6INQUIRY3.EnableVitalProductData) + { + if (Cdb->CDB6INQUIRY3.PageCode != 0) + return AtaReqTerminateInvalidField(Srb); + + return AtaReqCopySatlBuffer(Request, + &DevExt->InquiryData, + sizeof(DevExt->InquiryData)); + } + + switch (Cdb->CDB6INQUIRY3.PageCode) + { + case VPD_SUPPORTED_PAGES: + { + Length = AtaReqScsiInquirySupportedPages(DevExt); + break; + } + case VPD_SERIAL_NUMBER: + { + Length = AtaReqScsiInquirySerialNumber(DevExt); + break; + } + case VPD_DEVICE_IDENTIFIERS: + { + Length = AtaReqScsiInquiryDeviceIdentifiers(DevExt); + break; + } + case VPD_EXTENDED_INQUIRY_DATA: + { + // TODO: Implement + return AtaReqTerminateInvalidField(Srb); + } + case VPD_ATA_INFORMATION: + { + // TODO: Implement + return AtaReqTerminateInvalidField(Srb); + } + case VPD_BLOCK_LIMITS: + { + Length = AtaReqScsiInquiryBlockLimits(DevExt); + break; + } + case VPD_BLOCK_DEVICE_CHARACTERISTICS: + { + Length = AtaReqScsiInquiryBlockDeviceCharacteristics(DevExt); + break; + } + case VPD_LOGICAL_BLOCK_PROVISIONING: + { + Length = AtaReqScsiInquiryLogicalBlockProvisioning(DevExt); + break; + } + + default: + return AtaReqTerminateInvalidField(Srb); + } + + /* Data bytes common to all VPD pages */ + Buffer = DevExt->Device.LocalBuffer; + Buffer->DeviceType = DevExt->InquiryData.DeviceType; + Buffer->DeviceTypeQualifier = DevExt->InquiryData.DeviceTypeQualifier; + Buffer->PageCode = Cdb->CDB6INQUIRY3.PageCode; + + return AtaReqCopySatlBuffer(Request, DevExt->Device.LocalBuffer, Length); +} + +static +UCHAR +AtaReqScsiTestUnitReady( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + return SRB_STATUS_SUCCESS; +} + +static +UCHAR +AtaReqScsiMediumRemoval( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + PCDB Cdb = (PCDB)Srb->Cdb; + + if (AtaDevHasRemovableMediaFeature(&DevExt->IdentifyDeviceData)) + { + if (Cdb->MEDIA_REMOVAL.Prevent) + Request->TaskFile.Command = IDE_COMMAND_DOOR_LOCK; + else + Request->TaskFile.Command = IDE_COMMAND_DOOR_UNLOCK; + return SRB_STATUS_PENDING; + } + + /* Pretend success */ + return SRB_STATUS_SUCCESS; +} + +static +UCHAR +AtaReqScsiStartStopUnit( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + PCDB Cdb = (PCDB)Srb->Cdb; + + if (Cdb->START_STOP.LoadEject) + { + Request->TaskFile.Command = IDE_COMMAND_MEDIA_EJECT; + return SRB_STATUS_PENDING; + } + + /* Adding more handling will conflict with the power manager, assume success */ + return SRB_STATUS_SUCCESS; +} + +static +UCHAR +AtaReqScsiRequestSense( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + SCSI_SENSE_CODE SenseCode; + + SenseCode.SrbStatus = SRB_STATUS_SUCCESS; + SenseCode.SenseKey = SCSI_SENSE_NO_SENSE; + SenseCode.AdditionalSenseCode = SCSI_ADSENSE_NO_SENSE; + SenseCode.AdditionalSenseCodeQualifier = 0; + + return AtaReqSetFixedSenseData(Srb, SenseCode); +} + +static +BOOLEAN +AtaReqFilterPassThrough( + _In_ PATA_TASKFILE TaskFile) +{ + switch (TaskFile->Command) + { + case IDE_COMMAND_SET_FEATURE: + { + if (TaskFile->Feature == IDE_FEATURE_SET_TRANSFER_MODE) + return FALSE; + + break; + } + + case IDE_COMMAND_SET_DRIVE_PARAMETERS: + case IDE_COMMAND_SET_MULTIPLE: + return FALSE; + + // TODO: Anything else to check? + default: + break; + } + + return TRUE; +} + +static +VOID +AtaReqSetAtaStatusSenseData( + _In_ PATA_DEVICE_REQUEST Request, + _In_ UCHAR SK, + _In_ UCHAR ASK, + _In_ UCHAR ASCQ) +{ + PATA_TASKFILE TaskFile = &Request->Output; + PSCSI_REQUEST_BLOCK Srb = Request->Srb; + PDESCRIPTOR_SENSE_DATA SenseData; + ULONG BufferLength; + PSCSI_SENSE_DESCRIPTOR_ATA_STATUS_RETURN Descriptor; + + Srb->ScsiStatus = SCSISTAT_CHECK_CONDITION; + + BufferLength = FIELD_OFFSET(DESCRIPTOR_SENSE_DATA, DescriptorBuffer); + BufferLength += sizeof(*Descriptor); + if (Srb->SenseInfoBufferLength < BufferLength) + return; + + SenseData = Srb->SenseInfoBuffer; + RtlZeroMemory(SenseData, BufferLength); + + /* Descriptor format sense data */ + SenseData->ErrorCode = SCSI_SENSE_ERRORCODE_DESCRIPTOR_CURRENT; + SenseData->SenseKey = SK; + SenseData->AdditionalSenseCode = ASK; + SenseData->AdditionalSenseCodeQualifier = ASCQ; + + SenseData->AdditionalSenseLength = + BufferLength - RTL_SIZEOF_THROUGH_FIELD(DESCRIPTOR_SENSE_DATA, AdditionalSenseLength); + + /* ATA Status Return sense data descriptor */ + Descriptor = (PSCSI_SENSE_DESCRIPTOR_ATA_STATUS_RETURN)&SenseData->DescriptorBuffer[0]; + Descriptor->Header.DescriptorType = SCSI_SENSE_DESCRIPTOR_TYPE_ATA_STATUS_RETURN; + Descriptor->Header.AdditionalLength = 0x0C; + + /* Return the 28-bit extended status */ + Descriptor->Error = TaskFile->Error; + Descriptor->Device = TaskFile->DriveSelect; + Descriptor->Status = TaskFile->Status; + Descriptor->SectorCount7_0 = TaskFile->SectorCount; + Descriptor->LbaLow7_0 = TaskFile->LowLba; + Descriptor->LbaMid7_0 = TaskFile->MidLba; + Descriptor->LbaHigh7_0 = TaskFile->HighLba; + Descriptor->LbaLow15_8 = TaskFile->DriveSelect & 0x0F; + + /* Return the 48-bit extended status */ + if (Request->Flags & REQUEST_FLAG_LBA48) + { + Descriptor->Extend = 1; + Descriptor->SectorCount15_8 = TaskFile->SectorCountEx; + Descriptor->LbaLow15_8 = TaskFile->LowLbaEx; + Descriptor->LbaMid15_8 = TaskFile->MidLba; + Descriptor->LbaHigh15_8 = TaskFile->HighLbaEx; + } + + Request->SrbStatus |= SRB_STATUS_AUTOSENSE_VALID; +} + +static +ATA_COMPLETION_ACTION +AtaReqCompleteAtaPassThrough( + _In_ PATA_DEVICE_REQUEST Request) +{ + PCDB Cdb = (PCDB)Request->Srb->Cdb; + + if (!(Request->Flags & REQUEST_FLAG_HAS_TASK_FILE)) + return COMPLETE_IRP; + + if ((Cdb->ATA_PASSTHROUGH16.CkCond == 1) || (Request->SrbStatus != SRB_STATUS_SUCCESS)) + { + UCHAR SK, ASK, ASCQ; + + if (Request->SrbStatus != SRB_STATUS_SUCCESS) + { + AtaReqTranslateFixedError(Request, &SK, &ASK, &ASCQ); + } + else + { + SK = SCSI_SENSE_RECOVERED_ERROR; + ASK = SCSI_ADSENSE_NO_SENSE; + ASCQ = SCSI_SENSEQ_ATA_PASS_THROUGH_INFORMATION_AVAILABLE; + } + + AtaReqSetAtaStatusSenseData(Request, SK, ASK, ASCQ); + } + + return COMPLETE_IRP; +} + +static +UCHAR +AtaReqScsiAtaPassThrough( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + PATA_TASKFILE TaskFile = &Request->TaskFile; + PCDB Cdb; + + Request->Flags = REQUEST_FLAG_SET_DEVICE_REGISTER | + REQUEST_FLAG_SAVE_TASK_FILE | + REQUEST_FLAG_PASSTHROUGH | + (Srb->SrbFlags & SRB_FLAGS_UNSPECIFIED_DIRECTION); + + Cdb = (PCDB)Srb->Cdb; + switch (Cdb->ATA_PASSTHROUGH16.Protocol) + { + case ATA_PASSTHROUGH_PROTOCOL_NON_DATA: + { + /* Check for data transfer */ + if (Cdb->ATA_PASSTHROUGH16.TLength != 0) + return AtaReqTerminateInvalidField(Srb); + break; + } + + case ATA_PASSTHROUGH_PROTOCOL_PIO_DATA_IN: + { + /* Check for write operation */ + if (!Cdb->ATA_PASSTHROUGH16.TDir) + return AtaReqTerminateInvalidField(Srb); + break; + } + + case ATA_PASSTHROUGH_PROTOCOL_PIO_DATA_OUT: + { + /* Check for read operation */ + if (Cdb->ATA_PASSTHROUGH16.TDir) + return AtaReqTerminateInvalidField(Srb); + break; + } + + case ATA_PASSTHROUGH_PROTOCOL_DMA: + case ATA_PASSTHROUGH_PROTOCOL_UDMA_DATA_IN: + case ATA_PASSTHROUGH_PROTOCOL_UDMA_DATA_OUT: + { + if (DevExt->Device.DeviceFlags & DEVICE_PIO_ONLY) + return AtaReqTerminateInvalidField(Srb); + + Request->Flags |= REQUEST_DMA_FLAGS; + break; + } + + case ATA_PASSTHROUGH_PROTOCOL_NCQ: + { + if (!(DevExt->Device.DeviceFlags & DEVICE_NCQ)) + return AtaReqTerminateInvalidField(Srb); + + /* + * Do not set the REQUEST_FLAG_LBA48 flag. + * We can get here even when the command is SCSIOP_ATA_PASSTHROUGH12. + */ + Request->Flags |= REQUEST_FLAG_NCQ | REQUEST_DMA_FLAGS; + break; + } + + default: + return AtaReqTerminateInvalidField(Srb); + } + + /* + * NOTE: We ignore the T_LENGTH field in the CDB + * and use the DataTransferLength field instead. + */ + if ((Cdb->ATA_PASSTHROUGH16.TLength == 0) && (Srb->SrbFlags & SRB_FLAGS_UNSPECIFIED_DIRECTION)) + return AtaReqTerminateInvalidField(Srb); + + if (Srb->Cdb[0] == SCSIOP_ATA_PASSTHROUGH12) + { + TaskFile->Feature = Cdb->ATA_PASSTHROUGH12.Features; + TaskFile->SectorCount = Cdb->ATA_PASSTHROUGH12.SectorCount; + TaskFile->LowLba = Cdb->ATA_PASSTHROUGH12.LbaLow; + TaskFile->MidLba = Cdb->ATA_PASSTHROUGH12.LbaMid; + TaskFile->HighLba = Cdb->ATA_PASSTHROUGH12.LbaHigh; + TaskFile->DriveSelect = Cdb->ATA_PASSTHROUGH12.Device; + TaskFile->Command = Cdb->ATA_PASSTHROUGH12.Command; + } + else + { + TaskFile->Feature = Cdb->ATA_PASSTHROUGH16.Features7_0; + TaskFile->SectorCount = Cdb->ATA_PASSTHROUGH16.SectorCount7_0; + TaskFile->LowLba = Cdb->ATA_PASSTHROUGH16.LbaLow7_0; + TaskFile->MidLba = Cdb->ATA_PASSTHROUGH16.LbaMid7_0; + TaskFile->HighLba = Cdb->ATA_PASSTHROUGH16.LbaHigh7_0; + TaskFile->DriveSelect = Cdb->ATA_PASSTHROUGH16.Device; + TaskFile->Command = Cdb->ATA_PASSTHROUGH16.Command; + + if (Cdb->ATA_PASSTHROUGH16.Extend) + { + TaskFile->FeatureEx = Cdb->ATA_PASSTHROUGH16.Features15_8; + TaskFile->SectorCountEx = Cdb->ATA_PASSTHROUGH16.SectorCount15_8; + TaskFile->LowLbaEx = Cdb->ATA_PASSTHROUGH16.LbaLow15_8; + TaskFile->MidLbaEx = Cdb->ATA_PASSTHROUGH16.LbaMid15_8; + TaskFile->HighLbaEx = Cdb->ATA_PASSTHROUGH16.LbaHigh15_8; + + Request->Flags |= REQUEST_FLAG_LBA48; + } + } + + if (!AtaReqFilterPassThrough(TaskFile)) + return AtaReqTerminateInvalidField(Srb); + + /* We can set the unique queue tag only after having the slot allocation done */ + if (Request->Flags & REQUEST_FLAG_NCQ) + TaskFile->SectorCount &= ~0xF8; + + /* Set the master/slave bit to the correct value */ + TaskFile->DriveSelect &= ~IDE_DRIVE_SELECT_SLAVE; + TaskFile->DriveSelect |= DevExt->Device.DeviceSelect & IDE_DRIVE_SELECT_SLAVE; + + TRACE("TF: Send %02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + TaskFile->Command, + TaskFile->Feature, + TaskFile->LowLba, + TaskFile->MidLba, + TaskFile->HighLba, + TaskFile->DriveSelect, + TaskFile->SectorCount); + + Request->Complete = AtaReqCompleteAtaPassThrough; + return SRB_STATUS_PENDING; +} + +static +UCHAR +AtaReqExecuteScsiAta( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + switch (Srb->Cdb[0]) + { + case SCSIOP_REPORT_LUNS: + return AtaReqScsiReportLuns(DevExt, Request, Srb); + + case SCSIOP_INQUIRY: + return AtaReqScsiInquiry(DevExt, Request, Srb); + + case SCSIOP_TEST_UNIT_READY: + return AtaReqScsiTestUnitReady(DevExt, Request, Srb); + + case SCSIOP_MEDIUM_REMOVAL: + return AtaReqScsiMediumRemoval(DevExt, Request, Srb); + + case SCSIOP_START_STOP_UNIT: + return AtaReqScsiStartStopUnit(DevExt, Request, Srb); + + case SCSIOP_REQUEST_SENSE: + return AtaReqScsiRequestSense(DevExt, Request, Srb); + + case SCSIOP_MODE_SENSE: + case SCSIOP_MODE_SENSE10: + return AtaReqScsiModeSense(DevExt, + Request, + Srb, + (Srb->Cdb[0] == SCSIOP_MODE_SENSE)); + + case SCSIOP_MODE_SELECT: + case SCSIOP_MODE_SELECT10: + return AtaReqScsiModeSelect(DevExt, + Request, + Srb, + (Srb->Cdb[0] == SCSIOP_MODE_SELECT)); + + case SCSIOP_SERVICE_ACTION_IN16: + { + UCHAR ServiceAction = ((PCDB)Srb->Cdb)->READ_CAPACITY16.ServiceAction; + + if (ServiceAction != SERVICE_ACTION_READ_CAPACITY16) + break; + + __fallthrough; + } + case SCSIOP_READ_CAPACITY: + return AtaReqScsiReadCapacity(DevExt, Request, Srb); + + case SCSIOP_READ6: + case SCSIOP_WRITE6: + case SCSIOP_READ: + case SCSIOP_WRITE: + case SCSIOP_READ12: + case SCSIOP_WRITE12: + case SCSIOP_READ16: + case SCSIOP_WRITE16: + return AtaReqScsiReadWrite(DevExt, Request, Srb); + + case SCSIOP_SYNCHRONIZE_CACHE: + case SCSIOP_SYNCHRONIZE_CACHE16: + return AtaReqScsiSynchronizeCache(DevExt, Request, Srb); + + case SCSIOP_VERIFY: + case SCSIOP_VERIFY12: + case SCSIOP_VERIFY16: + return AtaReqScsiVerify(DevExt, Request, Srb); + + case SCSIOP_ATA_PASSTHROUGH12: + case SCSIOP_ATA_PASSTHROUGH16: + return AtaReqScsiAtaPassThrough(DevExt, Request, Srb); + + default: + break; + } + + INFO("Unknown command %02x\n", Srb->Cdb[0]); + + return AtaReqTerminateInvalidOpCode(Srb); +} + +static +UCHAR +AtaReqPreparePacketCommand( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + /* Prepare a packet command */ + Request->Flags = REQUEST_FLAG_PACKET_COMMAND | + (Srb->SrbFlags & SRB_FLAGS_UNSPECIFIED_DIRECTION); + + if ((Srb->SrbFlags & SRB_FLAGS_UNSPECIFIED_DIRECTION) && + AtaPacketCommandUseDma(Srb->Cdb[0]) && + !(DevExt->Device.DeviceFlags & DEVICE_PIO_ONLY) && + !(SRB_GET_FLAGS(Srb) & SRB_FLAG_PIO_RETRY)) + { + Request->Flags |= REQUEST_DMA_FLAGS; + } + + RtlCopyMemory(Request->Cdb, Srb->Cdb, RTL_FIELD_SIZE(SCSI_REQUEST_BLOCK, Cdb)); + + return SRB_STATUS_PENDING; +} + +static +UCHAR +AtaReqExecuteScsiAtapi( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + switch (Srb->Cdb[0]) + { + case SCSIOP_ATA_PASSTHROUGH12: + case SCSIOP_ATA_PASSTHROUGH16: + return AtaReqScsiAtaPassThrough(DevExt, Request, Srb); + + case SCSIOP_GET_CONFIGURATION: + // FIXME: HACK NP21/W emulation bug + if (IsNEC_98) + return AtaReqTerminateInvalidOpCode(Srb); + break; + + case SCSIOP_INQUIRY: + if (DevExt->Device.DeviceFlags & DEVICE_CACHE_INQUIRY) + { + return AtaReqCopySatlBuffer(Request, + &DevExt->InquiryData, + sizeof(DevExt->InquiryData)); + } + break; + + default: + break; + } + + return AtaReqPreparePacketCommand(DevExt, Request, Srb); +} + +UCHAR +AtaReqExecuteScsi( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + if (IS_ATAPI(&DevExt->Device)) + return AtaReqExecuteScsiAtapi(DevExt, Request, Srb); + else + return AtaReqExecuteScsiAta(DevExt, Request, Srb); +} diff --git a/drivers/storage/ide/atapi/scsi.c b/drivers/storage/ide/atapi/scsi.c new file mode 100644 index 00000000000..953092dd44d --- /dev/null +++ b/drivers/storage/ide/atapi/scsi.c @@ -0,0 +1,1812 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: SCSI I/O queue and requests handling + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* GLOBALS ********************************************************************/ + +SLIST_HEADER AtapCompletionQueueList; +KDPC AtapCompletionDpc; + +static DRIVER_CANCEL AtaReqDeviceQueueCancelIo; +static DRIVER_LIST_CONTROL AtaReqPreparePrdTable; +static DRIVER_CONTROL AtaReqCallSendRequestSerialized; + +static +NTSTATUS +AtaReqDeviceQueueAddSrb( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PSCSI_REQUEST_BLOCK Srb); + +static +VOID +AtaReqDispatchRequest( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PATA_DEVICE_REQUEST Request, + _In_ BOOLEAN DoReleaseDeviceQueueLock); + +static +BOOLEAN +AtaReqDeviceQueueDispatchNextRequest( + _In_ PATAPORT_IO_CONTEXT Device); + +/* FUNCTIONS ******************************************************************/ + +static +NTSTATUS +AtaSrbStatusToNtStatus( + _In_ UCHAR SrbStatus) +{ + ASSERT(SRB_STATUS(SrbStatus) != SRB_STATUS_PENDING); + + /* We translate only the values that are actually used by the driver */ + switch (SRB_STATUS(SrbStatus)) + { + case SRB_STATUS_SUCCESS: + return STATUS_SUCCESS; + + case SRB_STATUS_TIMEOUT: + return STATUS_IO_TIMEOUT; + + case SRB_STATUS_NO_DEVICE: + return STATUS_DEVICE_DOES_NOT_EXIST; + + case SRB_STATUS_DATA_OVERRUN: + return STATUS_BUFFER_OVERFLOW; + + case SRB_STATUS_SELECTION_TIMEOUT: + return STATUS_DEVICE_NOT_CONNECTED; + + default: + break; + } + + return STATUS_IO_DEVICE_ERROR; +} + +static +VOID +AtaDeviceCheckPowerState( + _In_ PATAPORT_IO_CONTEXT Device) +{ + PATAPORT_DEVICE_EXTENSION DevExt; + NTSTATUS Status; + POWER_STATE PowerState; + + /* + * The device might have been powered down as a part of an idle detection logic, + * so we have to power up the device again. + */ + if (!(Device->QueueFlags & QUEUE_FLAG_FROZEN_POWER)) + return; + + DevExt = CONTAINING_RECORD(Device, ATAPORT_DEVICE_EXTENSION, Device); + if (DevExt->Common.DevicePowerState == PowerDeviceD0) + return; + + if (DevExt->Common.SystemPowerState != PowerSystemWorking) + return; + + INFO("Powering up idle device\n"); + + PowerState.DeviceState = PowerDeviceD0; + Status = PoRequestPowerIrp(DevExt->Common.Self, + IRP_MN_SET_POWER, + PowerState, + NULL, + NULL, + NULL); + if (!NT_SUCCESS(Status)) + { + ERR("Failed to power up device '%s' %lx\n", DevExt->FriendlyName, Status); + } +} + +static +VOID +AtaDeviceQueueEmptyEvent( + _In_ PATAPORT_IO_CONTEXT Device) +{ + PSCSI_REQUEST_BLOCK Srb; + + KeSetEvent(&Device->QueueStoppedEvent, 0, FALSE); + + Srb = Device->QuiescenceSrb; + if (Srb) + { + PIRP Irp = Srb->OriginalRequest; + + Device->QuiescenceSrb = NULL; + + KeReleaseSpinLockFromDpcLevel(&Device->QueueLock); + + ASSERT(Srb->OriginalRequest == Irp); + + Srb->SrbStatus = SRB_STATUS_SUCCESS; + Irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(Irp, IO_DISK_INCREMENT); + + KeAcquireSpinLockAtDpcLevel(&Device->QueueLock); + } +} + +static +NTSTATUS +AtaReqRequeueRequest( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PATA_DEVICE_REQUEST Request, + _Out_ PUCHAR ResultSrbStatus) +{ + UCHAR SrbStatus; + + SrbStatus = SRB_STATUS(*ResultSrbStatus); + + /* Only requeue busy requests */ + if ((SrbStatus != SRB_STATUS_BUSY) && (SrbStatus != SRB_STATUS_INSUFFICIENT_RESOURCES)) + return COMPLETE_IRP; + + /* Retry at least once if the request was rejected due to lack of resources */ + if (SrbStatus == SRB_STATUS_INSUFFICIENT_RESOURCES) + { + if (SRB_GET_FLAGS(Request->Srb) & SRB_FLAG_LOW_MEM_RETRY) + { + /* We have no chance to dispatch it */ + return COMPLETE_IRP; + } + + WARN("Retrying operation\n"); + + SRB_SET_FLAGS(Request->Srb, SRB_FLAG_LOW_MEM_RETRY); + } + + ASSERT(!(Request->Flags & (REQUEST_FLAG_HAS_SG_LIST | + REQUEST_FLAG_HAS_MDL | + REQUEST_FLAG_HAS_RESERVED_MAPPING))); + + /* Place the Srb back into the queue */ + if (AtaReqDeviceQueueAddSrb(Device, Request->Srb) != STATUS_PENDING) + { + /* We failed because of the IRP was cancelled, just update the status */ + *ResultSrbStatus = Request->Srb->SrbStatus; + + return COMPLETE_IRP; + } + + return COMPLETE_NO_IRP; +} + +_Requires_lock_held_(PortData->QueueLock) +static +BOOLEAN +AtaReqPortQueueListDispatchNextRequest( + _In_ PATAPORT_PORT_DATA PortData) +{ + PATAPORT_IO_CONTEXT Device; + PATA_DEVICE_REQUEST Request; + PLIST_ENTRY Entry; + + ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); + + if (IsListEmpty(&PortData->PortQueueList)) + return FALSE; + + Entry = RemoveHeadList(&PortData->PortQueueList); + + Request = CONTAINING_RECORD(Entry, ATA_DEVICE_REQUEST, PortEntry); + ASSERT_REQUEST(Request); + + Device = (PATAPORT_IO_CONTEXT)Request->Device; + + /* Try to unfreeze the device queue */ + Device->QueueFlags &= ~QUEUE_FLAG_FROZEN_SLOT; + AtaReqDispatchRequest(Device, Request, FALSE); + + return TRUE; +} + +static +VOID +AtaReqReleaseResources( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PATA_DEVICE_REQUEST Request) +{ + ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); + + if (Request->Flags & REQUEST_FLAG_HAS_SG_LIST) + { + PDMA_ADAPTER DmaAdapter = Device->PortData->DmaAdapter; + PDMA_OPERATIONS DmaOperations = DmaAdapter->DmaOperations; + + ASSERT(Request->SgList); + ASSERT(Request->Flags & (REQUEST_FLAG_DATA_IN | REQUEST_FLAG_DATA_OUT)); + + DmaOperations->PutScatterGatherList(DmaAdapter, + Request->SgList, + !!(Request->Flags & REQUEST_FLAG_DATA_IN)); + } + else if (Request->Flags & REQUEST_FLAG_HAS_RESERVED_MAPPING) + { + MmUnmapReservedMapping(Device->PortData->ReservedVaSpace, ATAPORT_TAG, Request->Mdl); + _InterlockedExchange(&Device->PortData->ReservedMappingLock, 0); + } + + if (Request->Flags & REQUEST_FLAG_HAS_MDL) + { + ASSERT(Request->Mdl); + + IoFreeMdl(Request->Mdl); + } + +#if DBG + Request->Mdl = NULL; + Request->SgList = NULL; +#endif + + Request->Flags &= ~(REQUEST_FLAG_HAS_SG_LIST | + REQUEST_FLAG_HAS_MDL | + REQUEST_FLAG_HAS_RESERVED_MAPPING); +} + +static +VOID +AtaReqCompleteRequest( + _In_ PATA_DEVICE_REQUEST Request) +{ + PATAPORT_IO_CONTEXT Device = (PATAPORT_IO_CONTEXT)Request->Device; + PSCSI_REQUEST_BLOCK Srb; + PIRP Irp; + UCHAR SrbStatus; + ATA_COMPLETION_ACTION CompletionAction = COMPLETE_IRP; + + ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); + + if (Request->InternalState != REQUEST_STATE_NOT_STARTED) + { + if (Request->Complete) + { + if (Request->Flags & REQUEST_FLAG_INTERNAL) + { + Request->Complete(Request); + return; + } + else + { + KeAcquireSpinLockAtDpcLevel(&Device->QueueLock); + CompletionAction = Request->Complete(Request); + KeReleaseSpinLockFromDpcLevel(&Device->QueueLock); + } + } + + if (Device->PortData->PortFlags & PORT_FLAG_IS_SIMPLEX) + { + IoFreeController(Device->PortData->HwSyncObject); + } + } + + AtaReqReleaseResources(Device, Request); + + Srb = Request->Srb; + + Irp = Request->Irp; + Irp->IoStatus.Information = Request->DataTransferLength; + + KeAcquireSpinLockAtDpcLevel(&Device->QueueLock); + + /* Release the request back to the device queue */ + ASSERT(!(Device->FreeRequestsBitmap & Request->Tag)); + Device->FreeRequestsBitmap |= Request->Tag; + + /* Release the slot back to the port queue */ + if (Request->InternalState != REQUEST_STATE_NOT_STARTED) + { + PATAPORT_PORT_DATA PortData = Device->PortData; + + KeAcquireSpinLockAtDpcLevel(&PortData->QueueLock); + + /* Stop the timer tied to the completed slot */ + PortData->ActiveTimersBitmap &= ~(1 << Request->Slot); + + /* Release exclusive access to the port queue */ + if (Request->Flags & REQUEST_EXCLUSIVE_ACCESS_FLAGS) + PortData->QueueFlags &= ~PORT_QUEUE_FLAG_EXCLUSIVE_MODE; + + /* Release the slot */ + ASSERT(!(PortData->FreeSlotsBitmap & (1 << Request->Slot))); + PortData->FreeSlotsBitmap |= (1 << Request->Slot); +#if DBG + PortData->Slots[Request->Slot] = NULL; +#endif + if (Request->Flags & REQUEST_FLAG_NCQ) + --PortData->AllocatedSlots; + else + ++PortData->AllocatedSlots; + + PortData->AllocateSlot(PortData->ChannelContext, Request, FALSE); + + if ((PortData->QueueFlags & PORT_QUEUE_FLAG_SIGNAL_STOP) && AtaPortQueueEmpty(PortData)) + { + PortData->QueueFlags &= ~PORT_QUEUE_FLAG_SIGNAL_STOP; + KeSetEvent(&PortData->QueueStoppedEvent, 0, FALSE); + } + + /* Start the next request on the port queue */ + if (!AtaReqPortQueueListDispatchNextRequest(PortData)) + KeReleaseSpinLockFromDpcLevel(&PortData->QueueLock); + } + + /* Check if we need to retry later */ + if (Request->InternalState == REQUEST_STATE_NOT_STARTED || + Request->InternalState == REQUEST_STATE_REQUEUE) + { + CompletionAction = AtaReqRequeueRequest(Device, Request, &Request->SrbStatus); + } + +#if DBG + if (CompletionAction == COMPLETE_IRP) + ++Device->Statistics.RequestsCompleted; +#endif + + /* Freeze the Srb queue in case of device error */ + if ((Request->InternalState == REQUEST_STATE_FREEZE_QUEUE) && + !(Srb->SrbFlags & SRB_FLAGS_NO_QUEUE_FREEZE)) + { + ASSERT(SRB_STATUS(Request->SrbStatus) != SRB_STATUS_INSUFFICIENT_RESOURCES); + + Device->QueueFlags |= QUEUE_FLAG_FROZEN_QUEUE_FREEZE; + Request->SrbStatus |= SRB_STATUS_QUEUE_FROZEN; + INFO("Freeze queue\n"); + } + + /* Signal the event when the list of active IRPs is empty */ + if ((Device->QueueFlags & QUEUE_FLAG_SIGNAL_STOP) && + (Device->FreeRequestsBitmap == Device->MaxRequestsBitmap)) + { + Device->QueueFlags &= ~QUEUE_FLAG_SIGNAL_STOP; + AtaDeviceQueueEmptyEvent(Device); + } + + ASSERT(!(Request->Flags & (REQUEST_FLAG_HAS_SG_LIST | + REQUEST_FLAG_HAS_MDL | + REQUEST_FLAG_HAS_RESERVED_MAPPING))); + + SrbStatus = Request->SrbStatus; + + /* + * Start the next request on the device queue. + * It's important to do this before actually completing an IRP. + */ + if (!AtaReqDeviceQueueDispatchNextRequest(Device)) + KeReleaseSpinLockFromDpcLevel(&Device->QueueLock); + + /* + * Complete the IRP outside of the spinlock to avoid deadlocks. + * A new SCSI device I/O request might sent immediately after the IRP completed. + */ + if (CompletionAction == COMPLETE_IRP) + { + if (SRB_STATUS(SrbStatus) == SRB_STATUS_INSUFFICIENT_RESOURCES) + { + /* Special status for the upper class driver */ + Srb->SrbStatus = SRB_STATUS_INTERNAL_ERROR; + Srb->InternalStatus = STATUS_INSUFFICIENT_RESOURCES; + Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; + } + else + { + Srb->SrbStatus = SrbStatus; + Irp->IoStatus.Status = AtaSrbStatusToNtStatus(SrbStatus); + } + + IoCompleteRequest(Irp, IO_DISK_INCREMENT); + } +} + +UCHAR +AtaReqSetFixedSenseData( + _In_ PSCSI_REQUEST_BLOCK Srb, + _In_ SCSI_SENSE_CODE SenseCode) +{ + SENSE_DATA SenseData; + + if ((Srb->SenseInfoBuffer == NULL) || (Srb->SenseInfoBufferLength == 0)) + return SenseCode.SrbStatus; + + ASSERT(!(SenseCode.SrbStatus & SRB_STATUS_AUTOSENSE_VALID)); + + Srb->ScsiStatus = SCSISTAT_CHECK_CONDITION; + + // TODO: D_SENSE (fixed or descriptor format) is not supported yet + RtlZeroMemory(&SenseData, sizeof(SenseData)); + SenseData.Valid = 1; + SenseData.ErrorCode = SCSI_SENSE_ERRORCODE_FIXED_CURRENT; + SenseData.SenseKey = SenseCode.SenseKey; + SenseData.AdditionalSenseCode = SenseCode.AdditionalSenseCode; + SenseData.AdditionalSenseCodeQualifier = SenseCode.AdditionalSenseCodeQualifier; + SenseData.AdditionalSenseLength = + sizeof(SenseData) - RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength); + + RtlCopyMemory(Srb->SenseInfoBuffer, + &SenseData, + min(Srb->SenseInfoBufferLength, sizeof(SenseData))); + + return SenseCode.SrbStatus | SRB_STATUS_AUTOSENSE_VALID; +} + +VOID +AtaReqSetLbaInformation( + _In_ PSCSI_REQUEST_BLOCK Srb, + _In_ ULONG64 Lba) +{ + PSENSE_DATA SenseData; + + if ((Srb->SenseInfoBuffer == NULL) || (Srb->SenseInfoBufferLength == 0)) + return; + + SenseData = Srb->SenseInfoBuffer; + + if (RTL_CONTAINS_FIELD(SenseData, Srb->SenseInfoBufferLength, Information)) + { + ASSERT(SenseData->Valid); + + SenseData->Information[0] = (UCHAR)(Lba >> 24); + SenseData->Information[1] = (UCHAR)(Lba >> 16); + SenseData->Information[2] = (UCHAR)(Lba >> 8); + SenseData->Information[3] = (UCHAR)(Lba >> 0); + } + + // TODO: D_SENSE (fixed or descriptor format) is not supported yet +} + +BOOLEAN +AtaReqAllocateMdl( + _In_ PATA_DEVICE_REQUEST Request) +{ + PMDL Mdl; + + Mdl = IoAllocateMdl(Request->DataBuffer, + Request->DataTransferLength, + FALSE, + FALSE, + NULL); + if (!Mdl) + return FALSE; + + MmBuildMdlForNonPagedPool(Mdl); + + Request->Mdl = Mdl; + Request->Flags |= REQUEST_FLAG_HAS_MDL; + + return TRUE; +} + +static +VOID +AtaReqStartIo( + _In_ PATA_DEVICE_REQUEST Request) +{ + PATAPORT_IO_CONTEXT Device = (PATAPORT_IO_CONTEXT)Request->Device; + PATAPORT_PORT_DATA PortData = Device->PortData; + KIRQL OldIrql; + + ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); + ASSERT(PortData->Slots[Request->Slot] == Request); + + PortData->PrepareIo(PortData->ChannelContext, Request); + + OldIrql = KeAcquireInterruptSpinLock(PortData->InterruptObject); + + ASSERT(!(PortData->ActiveSlotsBitmap & (1 << Request->Slot))); + + if ((PortData->InterruptFlags & PORT_INT_FLAG_IS_IO_ACTIVE) || + (Request->Flags & REQUEST_FLAG_INTERNAL)) + { + PortData->ActiveSlotsBitmap |= 1 << Request->Slot; + + if (!PortData->StartIo(PortData->ChannelContext, Request)) + { + PortData->TimerCount[Request->Slot] = Request->TimeOut; + } + + KeReleaseInterruptSpinLock(PortData->InterruptObject, OldIrql); + } + else + { + PATAPORT_IO_CONTEXT Device = (PATAPORT_IO_CONTEXT)Request->Device; + + /* + * Needed to prevent an infinite loop that happens + * when the queue manager keeps retrying the SRB at dispatch level. + */ + _InterlockedOr(&Device->QueueFlags, QUEUE_FLAG_FROZEN_PORT_BUSY); + + KeReleaseInterruptSpinLock(PortData->InterruptObject, OldIrql); + + /* If the queue is frozen, then force queue manager to requeue the SRB */ + Request->SrbStatus = SRB_STATUS_BUSY; + Request->InternalState = REQUEST_STATE_REQUEUE; + + /* Defer the completion to a DPC to avoid the recursive call in some cases */ + AtaReqStartCompletionDpc(Request); + } +} + +static +VOID +NTAPI +AtaReqPreparePrdTable( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PIRP Irp, + _In_ PSCATTER_GATHER_LIST SgList, + _In_ PVOID Context) +{ + PATA_DEVICE_REQUEST Request = Context; + PATAPORT_IO_CONTEXT Device = (PATAPORT_IO_CONTEXT)Request->Device; + PATAPORT_PORT_DATA PortData = Device->PortData; + + UNREFERENCED_PARAMETER(DeviceObject); + UNREFERENCED_PARAMETER(Irp); + + ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); + + Request->SgList = SgList; + Request->Flags |= REQUEST_FLAG_HAS_SG_LIST; + + PortData->PreparePrdTable(PortData->ChannelContext, Request, SgList); + AtaReqStartIo(Request); +} + +static +PVOID +AtaReqMapBuffer( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PATA_DEVICE_REQUEST Request) +{ + PATAPORT_PORT_DATA PortData; + PVOID BaseAddress; + ULONG PagesNeeded; + + BaseAddress = MmGetSystemAddressForMdlSafe(Request->Mdl, HighPagePriority); + if (BaseAddress) + return BaseAddress; + + PortData = Device->PortData; + if (!PortData->ReservedVaSpace) + return NULL; + + /* The system is low resources, handle it in a non-fatal way */ + PagesNeeded = ADDRESS_AND_SIZE_TO_SPAN_PAGES(MmGetMdlVirtualAddress(Request->Mdl), + MmGetMdlByteCount(Request->Mdl)); + if (PagesNeeded > ATA_RESERVED_PAGES) + return NULL; + + /* Utilize the reserved mapping to overcome memory issues */ + if (!_InterlockedCompareExchange(&PortData->ReservedMappingLock, 1, 0)) + { + BaseAddress = MmMapLockedPagesWithReservedMapping(PortData->ReservedVaSpace, + ATAPORT_TAG, + Request->Mdl, + MmCached); + if (BaseAddress) + Request->Flags |= REQUEST_FLAG_HAS_RESERVED_MAPPING; + else + _InterlockedExchange(&PortData->ReservedMappingLock, 0); + } + + return BaseAddress; +} + +static +BOOLEAN +AtaReqGetScatterGatherList( + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATA_DEVICE_REQUEST Request) +{ + PDMA_ADAPTER DmaAdapter = PortData->DmaAdapter; + PDMA_OPERATIONS DmaOperations = DmaAdapter->DmaOperations; + NTSTATUS Status; + + ASSERT(Request->Mdl); + + Status = DmaOperations->GetScatterGatherList(DmaAdapter, + PortData->ChannelObject, + Request->Mdl, + Request->DataBuffer, + Request->DataTransferLength, + AtaReqPreparePrdTable, + Request, + !!(Request->Flags & REQUEST_FLAG_DATA_IN)); + if (NT_SUCCESS(Status)) + return TRUE; + + WARN("Failed to get the S/G list with status %lx\n", Status); + return FALSE; +} + +VOID +AtaReqSendRequest( + _In_ PATA_DEVICE_REQUEST Request) +{ + PATAPORT_IO_CONTEXT Device = (PATAPORT_IO_CONTEXT)Request->Device; + + PVOID BaseAddress; + ULONG_PTR Offset; + + ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); + + /* The channel can only perform DMA I/O and PIO is not supported */ + if (Device->DeviceFlags & DEVICE_PIO_VIA_DMA) + Request->Flags |= REQUEST_FLAG_PROGRAM_DMA; + + if (Request->Flags & REQUEST_FLAG_DMA) + ASSERT(Request->Flags & REQUEST_FLAG_PROGRAM_DMA); + + if (!(Request->Flags & REQUEST_FLAG_NO_KEEP_AWAKE)) + { + PULONG PowerIdleCounter = Device->PowerIdleCounter; + + if (PowerIdleCounter) + PoSetDeviceBusy(PowerIdleCounter); + } + + /* Local buffer transfer */ + if (Request->Flags & REQUEST_FLAG_HAS_LOCAL_BUFFER) + { + ASSERT(Request->Flags & REQUEST_FLAG_INTERNAL); + + if (Request->Flags & REQUEST_FLAG_PROGRAM_DMA) + { + /* DMA transfer */ + Device->PortData->LocalSgList.Elements[0].Length = Request->DataTransferLength; + AtaReqPreparePrdTable(NULL, NULL, &Device->PortData->LocalSgList, Request); + } + else + { + /* PIO data transfer */ + Request->DataBuffer = Device->LocalBuffer; + AtaReqStartIo(Request); + } + return; + } + + /* No data transfer */ + if (!(Request->Flags & (REQUEST_FLAG_DATA_IN | REQUEST_FLAG_DATA_OUT))) + { + AtaReqStartIo(Request); + return; + } + + /* DMA transfer, get the S/G list for the MDL */ + if (Request->Flags & REQUEST_FLAG_PROGRAM_DMA) + { + if (AtaReqGetScatterGatherList(Device->PortData, Request)) + return; + + /* This channel can only perform DMA I/O and PIO is not supported */ + if (Device->DeviceFlags & DEVICE_PIO_VIA_DMA) + goto CompleteNoMemory; + + /* S/G list construction failed, attempt to fall back to PIO mode */ + if (!AtaReqDmaTransferToPioTransfer(Request)) + goto CompleteNoMemory; + } + + /* PIO data transfer path */ + ASSERT(!(Device->DeviceFlags & DEVICE_PIO_VIA_DMA)); + + BaseAddress = AtaReqMapBuffer(Device, Request); + if (!BaseAddress) + goto CompleteNoMemory; + + /* Calculate the offset within DataBuffer */ + Offset = (ULONG_PTR)BaseAddress + + (ULONG_PTR)Request->DataBuffer - + (ULONG_PTR)MmGetMdlVirtualAddress(Request->Mdl); + Request->DataBuffer = (PVOID)Offset; + + AtaReqStartIo(Request); + return; + +CompleteNoMemory: + Request->SrbStatus = SRB_STATUS_INSUFFICIENT_RESOURCES; + Request->InternalState = REQUEST_STATE_REQUEUE; + + /* + * Defer the completion to a DPC. + * We cannot complete request with the queue spinlock held. + */ + AtaReqStartCompletionDpc(Request); +} + +static +IO_ALLOCATION_ACTION +NTAPI +AtaReqCallSendRequestSerialized( + _In_ PDEVICE_OBJECT DeviceObject, + _Inout_ PIRP Irp, + _In_ PVOID MapRegisterBase, + _In_ PVOID Context) +{ + PATA_DEVICE_REQUEST Request = Context; + + UNREFERENCED_PARAMETER(DeviceObject); + UNREFERENCED_PARAMETER(Irp); + UNREFERENCED_PARAMETER(MapRegisterBase); + + AtaReqSendRequest(Request); + return KeepObject; +} + +VOID +NTAPI +AtaReqCompletionDpc( + _In_ PKDPC Dpc, + _In_opt_ PVOID DeferredContext, + _In_opt_ PVOID SystemArgument1, + _In_opt_ PVOID SystemArgument2) +{ + PSLIST_ENTRY CurrentEntry, NextEntry; + + UNREFERENCED_PARAMETER(Dpc); + UNREFERENCED_PARAMETER(DeferredContext); + UNREFERENCED_PARAMETER(SystemArgument1); + UNREFERENCED_PARAMETER(SystemArgument2); + + CurrentEntry = ExInterlockedFlushSList(&AtapCompletionQueueList); + while (CurrentEntry) + { + PATA_DEVICE_REQUEST Request; + + NextEntry = CurrentEntry->Next; + + Request = CONTAINING_RECORD(CurrentEntry, ATA_DEVICE_REQUEST, CompletionEntry); + ASSERT_REQUEST(Request); + + AtaReqCompleteRequest(Request); + + CurrentEntry = NextEntry; + } +} + +VOID +AtaReqStartCompletionDpc( + _In_ PATA_DEVICE_REQUEST Request) +{ + ASSERT_REQUEST(Request); + + InterlockedPushEntrySList(&AtapCompletionQueueList, &Request->CompletionEntry); + KeInsertQueueDpc(&AtapCompletionDpc, NULL, NULL); +} + +/* + * The control flow is designed such that + * we can just return FALSE without having to undo the failed slot allocation later. + */ +static +BOOLEAN +AtaReqAllocateSlot( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PATAPORT_PORT_DATA PortData, + _In_ PATA_DEVICE_REQUEST Request) +{ + ULONG SlotMask, SlotNumber, SlotsBitmap; + + if (PortData->QueueFlags & PORT_QUEUE_FLAG_EXCLUSIVE_MODE) + return FALSE; + + /* Gain exclusive access to the slot queue */ + if (Request->Flags & REQUEST_EXCLUSIVE_ACCESS_FLAGS) + { + /* Check if we have any outstanding commands */ + if (PortData->AllocatedSlots != 0) + return FALSE; + } + + /* Queued command */ + if (Request->Flags & REQUEST_FLAG_NCQ) + { + ULONG QueueDepth; + + /* Check if we have any outstanding non-queued commands */ + if (PortData->AllocatedSlots < 0) + return FALSE; + + QueueDepth = Device->TransportFlags & DEVICE_QUEUE_DEPTH_MASK; + QueueDepth >>= DEVICE_QUEUE_DEPTH_SHIFT; + + /* The device capacity may be less than the total HBA capacity */ + if (PortData->AllocatedSlots >= QueueDepth) + return FALSE; + } + else + { + /* Check if we have any outstanding native queued commands */ + if (PortData->AllocatedSlots > 0) + return FALSE; + } + + SlotsBitmap = PortData->FreeSlotsBitmap; + + /* Mask off previously issued commands */ + SlotMask = ~(0xFFFFFFFF >> (MAX_SLOTS - (PortData->LastUsedSlot + 1))); + if (!(SlotsBitmap & SlotMask)) + SlotMask = 0xFFFFFFFF; + + /* Allocate slot in a circular fashion. This is required to maintain CCS update */ + if (!_BitScanForward(&SlotNumber, SlotsBitmap & SlotMask)) + return FALSE; + + if (!PortData->AllocateSlot(PortData->ChannelContext, Request, TRUE)) + return FALSE; + + /* The slot can safely be consumed at this point */ + if (Request->Flags & REQUEST_FLAG_NCQ) + ++PortData->AllocatedSlots; + else + --PortData->AllocatedSlots; + + if (Request->Flags & REQUEST_EXCLUSIVE_ACCESS_FLAGS) + PortData->QueueFlags |= PORT_QUEUE_FLAG_EXCLUSIVE_MODE; + + ASSERT(PortData->LastUsedSlot < MAX_SLOTS); + ASSERT(IsPowerOfTwo(SlotsBitmap) || (SlotNumber != PortData->LastUsedSlot)); + ASSERT(PortData->FreeSlotsBitmap & (1 << SlotNumber)); + ASSERT(PortData->Slots[SlotNumber] == NULL); + + Request->Slot = SlotNumber; + + PortData->LastUsedSlot = SlotNumber; + PortData->FreeSlotsBitmap &= ~(1 << SlotNumber); + PortData->Slots[SlotNumber] = Request; + return TRUE; +} + +static +VOID +AtaReqDispatchRequest( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PATA_DEVICE_REQUEST Request, + _In_ BOOLEAN DoReleaseDeviceQueueLock) +{ + PATAPORT_PORT_DATA PortData = Device->PortData; + BOOLEAN Success; + + Success = AtaReqAllocateSlot(Device, PortData, Request); + if (Success) + { + PortData->ActiveTimersBitmap |= 1 << Request->Slot; + } + else + { + /* + * If all slots are busy, overloading the port can starve other incoming I/O requests: + * - An IDE channel can only deal with one active request at a time. + * - A SATA Port Multiplier shares the bandwidth with up to 15 devices. + * In order to avoid starvation of the device, + * put the request on the high-priority port queue. + */ + InsertTailList(&PortData->PortQueueList, &Request->PortEntry); + + TRACE("Freeze slot queue\n"); + + /* + * Freeze the device queue to ensure + * that only one request per device is queued to the port queue. + */ + ASSERT(!(Device->QueueFlags & QUEUE_FLAG_FROZEN_SLOT)); + Device->QueueFlags |= QUEUE_FLAG_FROZEN_SLOT; + } + + KeReleaseSpinLockFromDpcLevel(&PortData->QueueLock); + + if (DoReleaseDeviceQueueLock) + KeReleaseSpinLockFromDpcLevel(&Device->QueueLock); + + if (Success) + { + if (PortData->PortFlags & PORT_FLAG_IS_SIMPLEX) + { + IoAllocateController(PortData->HwSyncObject, + PortData->ChannelObject, + AtaReqCallSendRequestSerialized, + Request); + } + else + { + AtaReqSendRequest(Request); + } + } +} + +static +ULONG +AtaReqTranslateRequest( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + UCHAR SrbStatus; + + Request->Srb = Srb; + Request->DataBuffer = Srb->DataBuffer; + Request->DataTransferLength = Srb->DataTransferLength; + Request->Irp = Srb->OriginalRequest; + Request->Mdl = ((PIRP)(Srb->OriginalRequest))->MdlAddress; + Request->TimeOut = Srb->TimeOutValue; + Request->Complete = NULL; + Request->Flags = 0; + Request->State = 0; + + switch (Srb->Function) + { + case SRB_FUNCTION_EXECUTE_SCSI: + { + SrbStatus = AtaReqExecuteScsi(DevExt, Request, Srb); + break; + } + + case SRB_FUNCTION_IO_CONTROL: + { + SrbStatus = AtaReqSmartIoControl(DevExt, Request, Srb); + break; + } + + case SRB_FUNCTION_SHUTDOWN: + case SRB_FUNCTION_FLUSH: + { + SrbStatus = SRB_STATUS_SUCCESS; + break; + } + + default: + { + ASSERT(FALSE); + UNREACHABLE; + } + } + + return SrbStatus; +} + +static +VOID +AtaReqStartRequest( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + PATAPORT_DEVICE_EXTENSION DevExt; + UCHAR SrbStatus; + + DevExt = CONTAINING_RECORD(Device, ATAPORT_DEVICE_EXTENSION, Device); + + SrbStatus = AtaReqTranslateRequest(DevExt, Request, Srb); + + if (SrbStatus != SRB_STATUS_PENDING) + { + KeReleaseSpinLockFromDpcLevel(&Device->QueueLock); + + Request->SrbStatus = SrbStatus; + Request->InternalState = REQUEST_STATE_NOT_STARTED; + + /* Defer the completion to a DPC to avoid the recursive call in some cases */ + AtaReqStartCompletionDpc(Request); + } + else + { + Request->Flags |= Srb->SrbFlags & REQUEST_FLAG_NO_KEEP_AWAKE; + + KeAcquireSpinLockAtDpcLevel(&Device->PortData->QueueLock); + + AtaReqDispatchRequest(Device, Request, TRUE); + } +} + +static +PATA_DEVICE_REQUEST +AtaReqRemovePortRequest( + _In_ PATAPORT_IO_CONTEXT Device) +{ + PATAPORT_PORT_DATA PortData = Device->PortData; + PLIST_ENTRY Entry; + PATA_DEVICE_REQUEST Request, Result = NULL; + + if (!(Device->QueueFlags & QUEUE_FLAG_FROZEN_SLOT)) + return NULL; + + Device->QueueFlags &= ~QUEUE_FLAG_FROZEN_SLOT; + + KeAcquireSpinLockAtDpcLevel(&PortData->QueueLock); + + for (Entry = PortData->PortQueueList.Flink; + Entry != &PortData->PortQueueList; + Entry = Entry->Flink) + { + Request = CONTAINING_RECORD(Entry, ATA_DEVICE_REQUEST, PortEntry); + ASSERT_REQUEST(Request); + + if (Request->Device != (PATA_IO_CONTEXT_COMMON)Device) + continue; + + RemoveEntryList(&Request->PortEntry); + + Result = Request; + break; + } + + KeReleaseSpinLockFromDpcLevel(&PortData->QueueLock); + + ASSERT(Result != NULL); + return Result; +} + +_Requires_lock_held_(Device->QueueLock) +static +VOID +AtaReqDeviceQueueRemoveEntry( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PREQUEST_QUEUE_ENTRY QueueEntry) +{ + RemoveEntryList(&QueueEntry->ListEntry); +} + +static +VOID +NTAPI +AtaReqDeviceQueueCancelIo( + _Inout_ PDEVICE_OBJECT DeviceObject, + _Inout_ _IRQL_uses_cancel_ PIRP Irp) +{ + PATAPORT_IO_CONTEXT Device; + KIRQL OldLevel; + PSCSI_REQUEST_BLOCK Srb; + PREQUEST_QUEUE_ENTRY QueueEntry; + + UNREFERENCED_PARAMETER(DeviceObject); + + IoReleaseCancelSpinLock(Irp->CancelIrql); + + QueueEntry = QUEUE_ENTRY_FROM_IRP(Irp); + Device = QueueEntry->Context; + + KeAcquireSpinLock(&Device->QueueLock, &OldLevel); + AtaReqDeviceQueueRemoveEntry(Device, QueueEntry); + KeReleaseSpinLock(&Device->QueueLock, OldLevel); + + Srb = IoGetCurrentIrpStackLocation(Irp)->Parameters.Scsi.Srb; + ASSERT(Srb); + ASSERT(Srb->OriginalRequest == Irp); + + Srb->SrbStatus = SRB_STATUS_ABORTED; + Srb->InternalStatus = STATUS_CANCELLED; + + Irp->IoStatus.Status = STATUS_CANCELLED; + IoCompleteRequest(Irp, IO_NO_INCREMENT); +} + +static +VOID +AtaReqDeviceQueueInsertSrb( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PSCSI_REQUEST_BLOCK Srb, + _In_ PIRP Irp) +{ + PLIST_ENTRY Entry; + PREQUEST_QUEUE_ENTRY QueueEntry; + ULONG SortKey; + + SortKey = Srb->QueueSortKey; + + /* Use the C-LOOK hard disk scheduling algorithm to service the PDO requests */ + for (Entry = Device->DeviceQueueList.Flink; + Entry != &Device->DeviceQueueList; + Entry = Entry->Flink) + { + QueueEntry = CONTAINING_RECORD(Entry, REQUEST_QUEUE_ENTRY, ListEntry); + ASSERT(QueueEntry->Context == Device); + + if (QueueEntry->SortKey > SortKey) + break; + } + + QueueEntry = QUEUE_ENTRY_FROM_IRP(Irp); + QueueEntry->Context = Device; + QueueEntry->SortKey = SortKey; + + InsertTailList(Entry, &QueueEntry->ListEntry); +} + +static +NTSTATUS +AtaReqDeviceQueueAddSrb( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + PIRP Irp = Srb->OriginalRequest; + + if (Device->QueueFlags & QUEUE_FLAG_FROZEN_REMOVED) + { + Srb->SrbStatus = SRB_STATUS_NO_DEVICE; + return STATUS_NO_SUCH_DEVICE; + } + + AtaReqDeviceQueueInsertSrb(Device, Srb, Irp); + + AtaDeviceCheckPowerState(Device); + + /* + * If the device queue is full or frozen, + * the requests may take a long period of time to process, + * and therefore we have to do the cancellation ourselves. + */ + (VOID)IoSetCancelRoutine(Irp, AtaReqDeviceQueueCancelIo); + + /* This IRP has already been cancelled */ + if (Irp->Cancel && IoSetCancelRoutine(Irp, NULL)) + { + /* Remove the IRP from the queue */ + AtaReqDeviceQueueRemoveEntry(Device, QUEUE_ENTRY_FROM_IRP(Irp)); + + Srb->SrbStatus = SRB_STATUS_ABORTED; + Srb->InternalStatus = STATUS_CANCELLED; + return STATUS_CANCELLED; + } + + return STATUS_PENDING; +} + +static +inline +UCHAR +AtaReqGetTagForRequest( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PSCSI_REQUEST_BLOCK Srb, + _Out_ PULONG Tag) +{ + /* Check if the queue is actually frozen and the request is not a bypass */ + if (Device->QueueFlags & (QUEUE_FLAGS_FROZEN & + ~(QUEUE_FLAG_FROZEN_QUEUE_LOCK | QUEUE_FLAG_FROZEN_QUEUE_FREEZE))) + { + return FALSE; + } + if ((Device->QueueFlags & QUEUE_FLAG_FROZEN_QUEUE_FREEZE) && + !(Srb->SrbFlags & SRB_FLAGS_BYPASS_FROZEN_QUEUE)) + { + return FALSE; + } + if ((Device->QueueFlags & QUEUE_FLAG_FROZEN_QUEUE_LOCK) && + !(Srb->SrbFlags & SRB_FLAGS_BYPASS_LOCKED_QUEUE)) + { + return FALSE; + } + + /* + * Stack-based request allocation. + * It will return us the last request structure in the CPU cache. + */ + return _BitScanForward(Tag, Device->FreeRequestsBitmap); +} + +static +inline +PATA_DEVICE_REQUEST +AtaReqAllocateRequestFromTag( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ ULONG Tag) +{ + PATA_DEVICE_REQUEST Request; + + ASSERT(Device->FreeRequestsBitmap & (1 << Tag)); + + Device->FreeRequestsBitmap &= ~(1 << Tag); + + Request = &Device->Requests[Tag]; + ASSERT_REQUEST(Request); + + Request->Tag = 1 << Tag; + + return Request; +} + +static +BOOLEAN +AtaReqDeviceQueueDispatchNextRequest( + _In_ PATAPORT_IO_CONTEXT Device) +{ + PREQUEST_QUEUE_ENTRY QueueEntry; + PIRP Irp; + PSCSI_REQUEST_BLOCK Srb; + PATA_DEVICE_REQUEST Request; + ULONG Tag; + + if (Device->FreeRequestsBitmap == 0) + return FALSE; + + /* Find a bypass request to dispatch */ + if ((Device->QueueFlags & (QUEUE_FLAG_FROZEN_QUEUE_FREEZE | QUEUE_FLAG_FROZEN_QUEUE_LOCK)) && + !(Device->QueueFlags & QUEUE_FLAGS_FROZEN_NOT_BYPASS)) + { + PLIST_ENTRY Entry; + + for (Entry = Device->DeviceQueueList.Flink; + Entry != &Device->DeviceQueueList; + Entry = Entry->Flink) + { + QueueEntry = CONTAINING_RECORD(Entry, REQUEST_QUEUE_ENTRY, ListEntry); + ASSERT(QueueEntry->Context == Device); + + Irp = IRP_FROM_QUEUE_ENTRY(QueueEntry); + Srb = IoGetCurrentIrpStackLocation(Irp)->Parameters.Scsi.Srb; + + ASSERT(Srb); + ASSERT(Srb->OriginalRequest == Irp); + + if (!AtaReqGetTagForRequest(Device, Srb, &Tag)) + continue; + + AtaReqDeviceQueueRemoveEntry(Device, QueueEntry); + + /* Clear our cancel routine */ + if (!IoSetCancelRoutine(Irp, NULL)) + { + /* We're already canceled, reset the list entry to point to itself */ + InitializeListHead(&QueueEntry->ListEntry); + continue; + } + + Request = AtaReqAllocateRequestFromTag(Device, Tag); + + AtaReqStartRequest(Device, Request, Srb); + return TRUE; + } + } + else + { + PLIST_ENTRY Entry; + + if (Device->QueueFlags & QUEUE_FLAGS_FROZEN) + return FALSE; + + NT_VERIFY(_BitScanForward(&Tag, Device->FreeRequestsBitmap)); + + for (Entry = Device->DeviceQueueList.Flink; + Entry != &Device->DeviceQueueList; + Entry = Entry->Flink) + { + QueueEntry = CONTAINING_RECORD(Entry, + REQUEST_QUEUE_ENTRY, + ListEntry); + ASSERT(QueueEntry->Context == Device); + + Irp = IRP_FROM_QUEUE_ENTRY(QueueEntry); + Srb = IoGetCurrentIrpStackLocation(Irp)->Parameters.Scsi.Srb; + + ASSERT(Srb); + ASSERT(Srb->OriginalRequest == Irp); + + AtaReqDeviceQueueRemoveEntry(Device, QueueEntry); + + /* Clear our cancel routine */ + if (!IoSetCancelRoutine(Irp, NULL)) + { + /* We're already canceled, reset the list entry to point to itself */ + InitializeListHead(&QueueEntry->ListEntry); + continue; + } + + Request = AtaReqAllocateRequestFromTag(Device, Tag); + + AtaReqStartRequest(Device, Request, Srb); + return TRUE; + } + } + + return FALSE; +} + +static +NTSTATUS +AtaReqStartSrb( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + KIRQL OldIrql; + NTSTATUS Status; + PATA_DEVICE_REQUEST Request; + ULONG Tag; + + KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); + KeAcquireSpinLockAtDpcLevel(&Device->QueueLock); + +#if DBG + ++Device->Statistics.RequestsStarted; +#endif + + if (!AtaReqGetTagForRequest(Device, Srb, &Tag)) + { + Status = AtaReqDeviceQueueAddSrb(Device, Srb); + + KeReleaseSpinLockFromDpcLevel(&Device->QueueLock); + } + else + { + Request = AtaReqAllocateRequestFromTag(Device, Tag); + AtaReqStartRequest(Device, Request, Srb); + + Status = STATUS_PENDING; + } + + KeLowerIrql(OldIrql); + + return Status; +} + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaReqFreezeQueue( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ULONG ReasonFlags) +{ + PATAPORT_IO_CONTEXT Device = &DevExt->Device; + KIRQL OldLevel; + + KeAcquireSpinLock(&Device->QueueLock, &OldLevel); + _InterlockedOr(&Device->QueueFlags, ReasonFlags); + KeReleaseSpinLock(&Device->QueueLock, OldLevel); +} + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaReqThawQueue( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ ULONG ReasonFlags) +{ + PATAPORT_IO_CONTEXT Device = &DevExt->Device; + PATAPORT_PORT_DATA PortData; + KIRQL OldIrql; + + KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); + KeAcquireSpinLockAtDpcLevel(&Device->QueueLock); + + if (Device->FreeRequestsBitmap != Device->MaxRequestsBitmap) + AtaDeviceCheckPowerState(Device); + + PortData = Device->PortData; + KeAcquireSpinLockAtDpcLevel(&PortData->QueueLock); + if (!AtaReqPortQueueListDispatchNextRequest(PortData)) + KeReleaseSpinLockFromDpcLevel(&PortData->QueueLock); + + _InterlockedAnd(&Device->QueueFlags, ~ReasonFlags); + + if (!AtaReqDeviceQueueDispatchNextRequest(Device)) + KeReleaseSpinLockFromDpcLevel(&Device->QueueLock); + + KeLowerIrql(OldIrql); +} + +/* Must not be paged */ +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaReqWaitForOutstandingIoToComplete( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + KIRQL OldIrql; + BOOLEAN DoWait; + PATA_DEVICE_REQUEST Request; + + KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); + KeAcquireSpinLockAtDpcLevel(&Device->QueueLock); + + ASSERT(Device->QueueFlags & QUEUE_FLAGS_FROZEN); + + KeClearEvent(&Device->QueueStoppedEvent); + + if (Srb) + Device->QuiescenceSrb = Srb; + + /* Wait for all the active IRPs to finish executing */ + if (Device->FreeRequestsBitmap != Device->MaxRequestsBitmap) + { + Device->QueueFlags |= QUEUE_FLAG_SIGNAL_STOP; + DoWait = !Srb; + } + else + { + AtaDeviceQueueEmptyEvent(Device); + + KeSetEvent(&Device->QueueStoppedEvent, 0, FALSE); + DoWait = FALSE; + } + + Request = AtaReqRemovePortRequest(Device); + + KeReleaseSpinLockFromDpcLevel(&Device->QueueLock); + KeLowerIrql(OldIrql); + + /* Requeue the pending request */ + if (Request) + { + Request->SrbStatus = SRB_STATUS_BUSY; + Request->InternalState = REQUEST_STATE_NOT_STARTED; + + KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); + AtaReqCompleteRequest(Request); + KeLowerIrql(OldIrql); + } + + if (!DoWait) + return; + + KeWaitForSingleObject(&Device->QueueStoppedEvent, Executive, KernelMode, FALSE, NULL); +} + +DECLSPEC_NOINLINE_FROM_PAGED +VOID +AtaReqFlushDeviceQueue( + _In_ PATAPORT_IO_CONTEXT Device) +{ + PATA_DEVICE_REQUEST Request; + KIRQL OldLevel; + PLIST_ENTRY Entry; + + KeAcquireSpinLock(&Device->QueueLock, &OldLevel); + + for (Entry = Device->DeviceQueueList.Flink; + Entry != &Device->DeviceQueueList; + Entry = Entry->Flink) + { + PREQUEST_QUEUE_ENTRY QueueEntry; + PIRP Irp; + PSCSI_REQUEST_BLOCK Srb; + + QueueEntry = CONTAINING_RECORD(Entry, REQUEST_QUEUE_ENTRY, ListEntry); + ASSERT(QueueEntry->Context == Device); + + AtaReqDeviceQueueRemoveEntry(Device, QueueEntry); + + /* Clear our cancel routine */ + Irp = IRP_FROM_QUEUE_ENTRY(QueueEntry); + if (!IoSetCancelRoutine(Irp, NULL)) + { + /* We're already canceled, reset the list entry to point to itself */ + InitializeListHead(&QueueEntry->ListEntry); + continue; + } + + KeReleaseSpinLock(&Device->QueueLock, OldLevel); + + Srb = IoGetCurrentIrpStackLocation(Irp)->Parameters.Scsi.Srb; + ASSERT(Srb); + ASSERT(Srb->OriginalRequest == Irp); + + Srb->SrbStatus = SRB_STATUS_ABORTED; + Irp->IoStatus.Status = STATUS_CANCELLED; + Irp->IoStatus.Information = Srb->DataTransferLength; + IoCompleteRequest(Irp, IO_DISK_INCREMENT); + + KeAcquireSpinLock(&Device->QueueLock, &OldLevel); + } + + Request = AtaReqRemovePortRequest(Device); + + KeReleaseSpinLock(&Device->QueueLock, OldLevel); + + if (Request) + { + KIRQL OldIrql; + + Request->SrbStatus = SRB_STATUS_ABORTED; + Request->InternalState = REQUEST_STATE_NOT_STARTED; + + KeRaiseIrql(DISPATCH_LEVEL, &OldIrql); + AtaReqCompleteRequest(Request); + KeLowerIrql(OldIrql); + } +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaPdoHandleAttachReleaseDevice( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + ULONG Status, SrbStatus; + + PAGED_CODE(); + + if (Srb->Function == SRB_FUNCTION_RELEASE_DEVICE) + { + DevExt->DeviceClaimed = FALSE; + + SrbStatus = SRB_STATUS_SUCCESS; + Status = STATUS_SUCCESS; + goto Exit; + } + + if (DevExt->DeviceClaimed) + { + SrbStatus = SRB_STATUS_BUSY; + Status = STATUS_DEVICE_BUSY; + goto Exit; + } + + DevExt->DeviceClaimed = TRUE; + Srb->DataBuffer = DevExt->Common.Self; + + SrbStatus = SRB_STATUS_SUCCESS; + Status = STATUS_SUCCESS; + +Exit: + Srb->SrbStatus = SrbStatus; + return Status; +} + +static +NTSTATUS +AtaPdoHandleQuiesceDevice( + _In_ PATAPORT_IO_CONTEXT Device, + _In_ PIRP Irp, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); + ASSERT(Device->QueueFlags & QUEUE_FLAG_FROZEN_QUEUE_LOCK); + + IoMarkIrpPending(Irp); + + /* Wait for outstanding I/O requests to finish */ + AtaReqWaitForOutstandingIoToComplete(Device, Srb); + + Srb->SrbStatus = SRB_STATUS_PENDING; + return STATUS_PENDING; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +BOOLEAN +AtaPdoHandleIoControl( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PSCSI_REQUEST_BLOCK Srb, + _Out_ NTSTATUS* Status) +{ + PSRB_IO_CONTROL SrbControl = (PSRB_IO_CONTROL)Srb->DataBuffer; + + PAGED_CODE(); + + switch (SrbControl->ControlCode) + { + case IOCTL_SCSI_MINIPORT_SMART_VERSION: + *Status = AtaPdoHandleMiniportSmartVersion(DevExt, Srb); + break; + + case IOCTL_SCSI_MINIPORT_IDENTIFY: + *Status = AtaPdoHandleMiniportIdentify(DevExt, Srb); + break; + + case IOCTL_SCSI_MINIPORT_DISABLE_SMART: + case IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTOSAVE: + case IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTO_OFFLINE: + case IOCTL_SCSI_MINIPORT_ENABLE_SMART: + case IOCTL_SCSI_MINIPORT_EXECUTE_OFFLINE_DIAGS: + case IOCTL_SCSI_MINIPORT_READ_SMART_ATTRIBS: + case IOCTL_SCSI_MINIPORT_READ_SMART_LOG: + case IOCTL_SCSI_MINIPORT_READ_SMART_THRESHOLDS: + case IOCTL_SCSI_MINIPORT_RETURN_STATUS: + case IOCTL_SCSI_MINIPORT_SAVE_ATTRIBUTE_VALUES: + case IOCTL_SCSI_MINIPORT_WRITE_SMART_LOG: + { + /* Queue the request */ + return FALSE; + } + + default: + Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST; + *Status = STATUS_INVALID_DEVICE_REQUEST; + break; + } + + return TRUE; +} + +static +NTSTATUS +AtaPdoDispatchScsi( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _Inout_ PIRP Irp) +{ + NTSTATUS Status; + PSCSI_REQUEST_BLOCK Srb; + + Srb = IoGetCurrentIrpStackLocation(Irp)->Parameters.Scsi.Srb; + ASSERT(Srb); + ASSERT(Srb->OriginalRequest == Irp); + + Status = IoAcquireRemoveLock(&DevExt->Common.RemoveLock, Irp); + if (!NT_SUCCESS(Status)) + { + Srb->SrbStatus = SRB_STATUS_NO_DEVICE; + Status = STATUS_NO_SUCH_DEVICE; + + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return Status; + } + + switch (Srb->Function) + { + case SRB_FUNCTION_CLAIM_DEVICE: + case SRB_FUNCTION_RELEASE_DEVICE: + { + Status = AtaPdoHandleAttachReleaseDevice(DevExt, Srb); + break; + } + + case SRB_FUNCTION_LOCK_QUEUE: + { + AtaReqFreezeQueue(DevExt, QUEUE_FLAG_FROZEN_QUEUE_LOCK); + + Srb->SrbStatus = SRB_STATUS_SUCCESS; + Status = STATUS_SUCCESS; + break; + } + + case SRB_FUNCTION_FLUSH_QUEUE: + { + ASSERT(DevExt->Device.QueueFlags & QUEUE_FLAGS_FROZEN); + AtaReqFlushDeviceQueue(&DevExt->Device); + __fallthrough; + } + case SRB_FUNCTION_UNLOCK_QUEUE: + case SRB_FUNCTION_RELEASE_QUEUE: + { + ULONG Reason; + + if (Srb->Function == SRB_FUNCTION_UNLOCK_QUEUE) + Reason = QUEUE_FLAG_FROZEN_QUEUE_LOCK; + else + Reason = QUEUE_FLAG_FROZEN_QUEUE_FREEZE; + AtaReqThawQueue(DevExt, Reason); + + Srb->SrbStatus = SRB_STATUS_SUCCESS; + Status = STATUS_SUCCESS; + break; + } + + case SRB_FUNCTION_QUIESCE_DEVICE: + { + Status = AtaPdoHandleQuiesceDevice(&DevExt->Device, Irp, Srb); + break; + } + + case SRB_FUNCTION_IO_CONTROL: + { + if (AtaPdoHandleIoControl(DevExt, Srb, &Status)) + break; + + __fallthrough; + } + case SRB_FUNCTION_SHUTDOWN: + case SRB_FUNCTION_FLUSH: + case SRB_FUNCTION_EXECUTE_SCSI: + { + ATA_SCSI_ADDRESS AtaScsiAddress; + + IoMarkIrpPending(Irp); + + /* Set the SCSI address to the correct value */ + AtaScsiAddress = DevExt->Device.AtaScsiAddress; + Srb->PathId = AtaScsiAddress.PathId; + Srb->TargetId = AtaScsiAddress.TargetId; + Srb->Lun = AtaScsiAddress.Lun; + + /* This field is used by the driver to mark internal requests */ + Srb->SrbExtension = NULL; + + /* + * NOTE: Disk I/O requests need a lot of the kernel stack space. + * We should avoid nesting several levels deep in the call chain. + */ + Status = AtaReqStartSrb(&DevExt->Device, Srb); + break; + } + + default: + Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST; + Status = STATUS_INVALID_DEVICE_REQUEST; + break; + } + + if (Status != STATUS_PENDING) + { + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + } + + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, Irp); + + return Status; +} + +static +DECLSPEC_NOINLINE_FROM_NOT_PAGED +CODE_SEG("PAGE") +NTSTATUS +AtaFdoDispatchScsi( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _Inout_ PIRP Irp) +{ + NTSTATUS Status; + PSCSI_REQUEST_BLOCK Srb; + + UNREFERENCED_PARAMETER(ChanExt); + + PAGED_CODE(); + + /* Drivers should not call the FDO */ + ASSERT(FALSE); + + Srb = IoGetCurrentIrpStackLocation(Irp)->Parameters.Scsi.Srb; + ASSERT(Srb); + ASSERT(Srb->OriginalRequest == Irp); + + Srb->SrbStatus = SRB_STATUS_NO_DEVICE; + Status = STATUS_NO_SUCH_DEVICE; + + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return Status; +} + +NTSTATUS +NTAPI +AtaDispatchScsi( + _In_ PDEVICE_OBJECT DeviceObject, + _Inout_ PIRP Irp) +{ + if (IS_FDO(DeviceObject->DeviceExtension)) + return AtaFdoDispatchScsi(DeviceObject->DeviceExtension, Irp); + else + return AtaPdoDispatchScsi(DeviceObject->DeviceExtension, Irp); +} + +VOID +NTAPI +AtaPortIoTimer( + _In_ PDEVICE_OBJECT DeviceObject, + _In_opt_ PVOID Context) +{ + PATAPORT_PORT_DATA PortData = Context; + ULONG Slot, ActiveTimersBitmap; + + KeAcquireSpinLockAtDpcLevel(&PortData->QueueLock); + + /* Check timeouts */ + ActiveTimersBitmap = PortData->ActiveTimersBitmap; + while (_BitScanForward(&Slot, ActiveTimersBitmap) != 0) + { + ActiveTimersBitmap &= ~(1 << Slot); + + /* Decrease the timeout counter */ + if ((PortData->TimerCount[Slot] > 0) && (--PortData->TimerCount[Slot] == 0)) + { + KIRQL OldIrql = KeAcquireInterruptSpinLock(PortData->InterruptObject); + + /* Handle timeout of an active command */ + if (PortData->ActiveSlotsBitmap & (1 << Slot)) + AtaPortTimeout(PortData, Slot); + + KeReleaseInterruptSpinLock(PortData->InterruptObject, OldIrql); + } + } + + KeReleaseSpinLockFromDpcLevel(&PortData->QueueLock); +} diff --git a/drivers/storage/ide/atapi/scsiex.h b/drivers/storage/ide/atapi/scsiex.h new file mode 100644 index 00000000000..d490232d5ff --- /dev/null +++ b/drivers/storage/ide/atapi/scsiex.h @@ -0,0 +1,210 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Extra SCSI definitions + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +#pragma once + +#include + +typedef union _SCSI_SENSE_CODE +{ + struct + { + UCHAR SrbStatus; + UCHAR SenseKey; + UCHAR AdditionalSenseCode; + UCHAR AdditionalSenseCodeQualifier; + }; + ULONG AsULONG; +} SCSI_SENSE_CODE, *PSCSI_SENSE_CODE; + +typedef struct _SCSI_SENSE_KEY_SPECIFIC_FIELD_POINTER +{ + UCHAR BitPointer:3; + UCHAR BitPointerValid:1; + UCHAR Reserved:2; + UCHAR CommandData:1; + UCHAR SenseKeySpecificValid:1; + UCHAR FieldPointer[2]; +} SCSI_SENSE_KEY_SPECIFIC_FIELD_POINTER, *PSCSI_SENSE_KEY_SPECIFIC_FIELD_POINTER; +// CommandData = 1 +// SenseKeySpecificValid = 1 + +C_ASSERT(sizeof(SCSI_SENSE_KEY_SPECIFIC_FIELD_POINTER) == 3); + +typedef struct _MODE_CACHING_PAGE_SPC5 +{ + UCHAR PageCode:6; + UCHAR Reserved:1; + UCHAR PageSavable:1; + UCHAR PageLength; + UCHAR ReadDisableCache:1; + UCHAR MultiplicationFactor:1; + UCHAR WriteCacheEnable:1; + UCHAR Reserved2:5; + UCHAR WriteRetensionPriority:4; + UCHAR ReadRetensionPriority:4; + UCHAR DisablePrefetchTransfer[2]; + UCHAR MinimumPrefetch[2]; + UCHAR MaximumPrefetch[2]; + UCHAR MaximumPrefetchCeiling[2]; + UCHAR NV_DIS:1; + UCHAR SYNC_PROG:2; + UCHAR Reserved1:2; + UCHAR DisableReadAHead:1; + UCHAR LBCSS:1; + UCHAR FSW:1; + UCHAR NumberOfCacheSegments; + UCHAR CacheSegmentSize[2]; + UCHAR Reserved3; + UCHAR Obsolete[3]; +} MODE_CACHING_PAGE_SPC5, *PMODE_CACHING_PAGE_SPC5; + +C_ASSERT(sizeof(MODE_CACHING_PAGE_SPC5) == 20); + +typedef struct _MODE_CONTROL_EXTENSION_PAGE +{ + UCHAR PageCode:6; + UCHAR SubPageFormat:1; + UCHAR PageSavable:1; + UCHAR SubPageCode; + UCHAR PageLength[2]; + UCHAR IALUAE:1; + UCHAR SCSIP:1; + UCHAR TCMOS:1; + UCHAR Reserved:5; + UCHAR InitialCommandPriority:4; + UCHAR Reserved1:4; + UCHAR MaximumSenseDataLength; + UCHAR Reserved2[25]; +} MODE_CONTROL_EXTENSION_PAGE, *PMODE_CONTROL_EXTENSION_PAGE; + +C_ASSERT(sizeof(MODE_CONTROL_EXTENSION_PAGE) == 32); + +#include + +#define SCSI_ADSENSE_ADDRESS_MARK_NOT_FOUND_FOR_DATA_FIELD 0x13 + +/* SAT-6 */ +C_ASSERT(sizeof(MODE_INFO_EXCEPTIONS) == 12); +C_ASSERT(sizeof(MODE_CONTROL_PAGE) == 12); +C_ASSERT(sizeof(MODE_READ_WRITE_RECOVERY_PAGE) == 12); +C_ASSERT(sizeof(POWER_CONDITION_PAGE) == 12); +C_ASSERT(sizeof(VPD_ATA_INFORMATION_PAGE) == 572); +C_ASSERT(sizeof(VPD_BLOCK_LIMITS_PAGE) == 0x3c+4); +C_ASSERT(sizeof(VPD_BLOCK_DEVICE_CHARACTERISTICS_PAGE) == 0x3c+4); + +FORCEINLINE +UCHAR +CdbGetAllocationLength6( + _In_ PCDB Cdb) +{ + return Cdb->CDB6GENERIC.CommandUniqueBytes[2]; +} + +FORCEINLINE +USHORT +CdbGetAllocationLength10( + _In_ PCDB Cdb) +{ + return (Cdb->CDB10.TransferBlocksMsb << 8) | + (Cdb->CDB10.TransferBlocksLsb << 0); +} + +FORCEINLINE +ULONG +CdbGetAllocationLength16( + _In_ PCDB Cdb) +{ + return (Cdb->CDB16.TransferLength[0] << 24) | + (Cdb->CDB16.TransferLength[1] << 16) | + (Cdb->CDB16.TransferLength[2] << 8) | + (Cdb->CDB16.TransferLength[3] << 0); +} + +FORCEINLINE +USHORT +CdbGetTransferLength10( + _In_ PCDB Cdb) +{ + /* Bytes 7:8 */ + return (Cdb->CDB10.TransferBlocksMsb << 8) | + (Cdb->CDB10.TransferBlocksLsb << 0); +} + +FORCEINLINE +ULONG +CdbGetTransferLength12( + _In_ PCDB Cdb) +{ + /* Bytes 6:9 */ + return (Cdb->CDB12.TransferLength[0] << 24) | + (Cdb->CDB12.TransferLength[1] << 16) | + (Cdb->CDB12.TransferLength[2] << 8) | + (Cdb->CDB12.TransferLength[3] << 0); +} + +FORCEINLINE +ULONG +CdbGetTransferLength16( + _In_ PCDB Cdb) +{ + /* Bytes 10:13 */ + return (Cdb->CDB16.TransferLength[0] << 24) | + (Cdb->CDB16.TransferLength[1] << 16) | + (Cdb->CDB16.TransferLength[2] << 8) | + (Cdb->CDB16.TransferLength[3] << 0); +} + +FORCEINLINE +ULONG +CdbGetLogicalBlockAddress6( + _In_ PCDB Cdb) +{ + /* Bytes 2:3 */ + return (Cdb->CDB6READWRITE.LogicalBlockMsb0 << 8) | + (Cdb->CDB6READWRITE.LogicalBlockLsb << 0); +} + +FORCEINLINE +ULONG +CdbGetLogicalBlockAddress10( + _In_ PCDB Cdb) +{ + /* Bytes 2:5 */ + return (Cdb->CDB10.LogicalBlockByte0 << 24) | + (Cdb->CDB10.LogicalBlockByte1 << 16) | + (Cdb->CDB10.LogicalBlockByte2 << 8) | + (Cdb->CDB10.LogicalBlockByte3 << 0); +} + +FORCEINLINE +ULONG +CdbGetLogicalBlockAddress12( + _In_ PCDB Cdb) +{ + /* Bytes 2:5 */ + return (Cdb->CDB12.LogicalBlock[0] << 24) | + (Cdb->CDB12.LogicalBlock[1] << 16) | + (Cdb->CDB12.LogicalBlock[2] << 8) | + (Cdb->CDB12.LogicalBlock[3] << 0); +} + +FORCEINLINE +ULONG64 +CdbGetLogicalBlockAddress16( + _In_ PCDB Cdb) +{ + /* Bytes 2:9 */ + return ((ULONG64)Cdb->CDB16.LogicalBlock[0] << 56) | + ((ULONG64)Cdb->CDB16.LogicalBlock[1] << 48) | + ((ULONG64)Cdb->CDB16.LogicalBlock[2] << 40) | + ((ULONG64)Cdb->CDB16.LogicalBlock[3] << 32) | + ((ULONG64)Cdb->CDB16.LogicalBlock[4] << 24) | + ((ULONG64)Cdb->CDB16.LogicalBlock[5] << 16) | + ((ULONG64)Cdb->CDB16.LogicalBlock[6] << 8) | + ((ULONG64)Cdb->CDB16.LogicalBlock[7] << 0); +} diff --git a/drivers/storage/ide/atapi/smart.c b/drivers/storage/ide/atapi/smart.c new file mode 100644 index 00000000000..4ad42ff17ae --- /dev/null +++ b/drivers/storage/ide/atapi/smart.c @@ -0,0 +1,298 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: SMART Feature Set support + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* FUNCTIONS ******************************************************************/ + +static +VOID +AtaIdeRegsToTaskFile( + _In_ ATAPORT_DEVICE_EXTENSION* __restrict DevExt, + _In_ IDEREGS* __restrict IdeRegs, + _Out_ ATA_DEVICE_REQUEST* __restrict Request) +{ + PATA_TASKFILE TaskFile = &Request->TaskFile; + + TaskFile->Feature = IdeRegs->bFeaturesReg; + TaskFile->SectorCount = IdeRegs->bSectorCountReg; + TaskFile->LowLba = IdeRegs->bSectorNumberReg; + TaskFile->MidLba = IdeRegs->bCylLowReg; + TaskFile->HighLba = IdeRegs->bCylHighReg; + TaskFile->Command = IDE_COMMAND_SMART; // SMART_CMD + + /* Set the master/slave bit to the correct value */ + TaskFile->DriveSelect = IdeRegs->bDriveHeadReg & ~IDE_DRIVE_SELECT_SLAVE; + TaskFile->DriveSelect |= DevExt->Device.DeviceSelect & IDE_DRIVE_SELECT_SLAVE; +} + +static +VOID +AtaTaskFileToIdeRegs( + _In_ ATA_DEVICE_REQUEST* __restrict Request, + _Out_ IDEREGS* __restrict IdeRegs) +{ + PATA_TASKFILE TaskFile = &Request->Output; + + IdeRegs->bFeaturesReg = TaskFile->Feature; + IdeRegs->bSectorCountReg = TaskFile->SectorCount; + IdeRegs->bSectorNumberReg = TaskFile->LowLba; + IdeRegs->bCylLowReg = TaskFile->MidLba; + IdeRegs->bCylHighReg = TaskFile->HighLba; + IdeRegs->bCommandReg = TaskFile->Command; + IdeRegs->bDriveHeadReg = TaskFile->DriveSelect; +} + +static +ATA_COMPLETION_ACTION +AtaReqCompleteSmartIoControl( + _In_ PATA_DEVICE_REQUEST Request) +{ + PSENDCMDOUTPARAMS CmdOut; + + CmdOut = (PSENDCMDOUTPARAMS)(((PUCHAR)Request->Srb->DataBuffer) + sizeof(SRB_IO_CONTROL)); + + if (Request->SrbStatus == SRB_STATUS_SUCCESS) + { + CmdOut->DriverStatus.bDriverError = SMART_NO_ERROR; + CmdOut->DriverStatus.bIDEError = 0; + } + else + { + CmdOut->DriverStatus.bDriverError = SMART_IDE_ERROR; + CmdOut->DriverStatus.bIDEError = Request->Output.Error; + } + + /* Return the SMART status */ + if (Request->Flags & REQUEST_FLAG_SAVE_TASK_FILE) + { + if (Request->Flags & REQUEST_FLAG_HAS_TASK_FILE) + { + PIDEREGS IdeRegs = (PIDEREGS)&CmdOut->bBuffer; + + AtaTaskFileToIdeRegs(Request, IdeRegs); + } + else + { + CmdOut->DriverStatus.bDriverError = SMART_IDE_ERROR; + CmdOut->DriverStatus.bIDEError = Request->Output.Error; + } + } + + return COMPLETE_IRP; +} + +UCHAR +AtaReqSmartIoControl( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PATA_DEVICE_REQUEST Request, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + union _PARAMS + { + PSENDCMDINPARAMS CmdIn; + PSENDCMDOUTPARAMS CmdOut; + } Buffer; + + if (Srb->DataTransferLength < (sizeof(SRB_IO_CONTROL) + sizeof(*Buffer.CmdIn) - 1)) + return SRB_STATUS_INVALID_REQUEST; + + Buffer.CmdIn = (PSENDCMDINPARAMS)(((PUCHAR)Srb->DataBuffer) + sizeof(SRB_IO_CONTROL)); + + if (Buffer.CmdIn->irDriveRegs.bCommandReg != SMART_CMD) + return SRB_STATUS_INVALID_REQUEST; + + switch (Buffer.CmdIn->irDriveRegs.bFeaturesReg) + { + case READ_ATTRIBUTES: + { + Request->DataTransferLength = READ_ATTRIBUTE_BUFFER_SIZE; + Request->Flags = REQUEST_FLAG_DATA_IN; + break; + } + + case READ_THRESHOLDS: + { + Request->DataTransferLength = READ_THRESHOLD_BUFFER_SIZE; + Request->Flags = REQUEST_FLAG_DATA_IN; + break; + } + + case SMART_READ_LOG: + { + Request->DataTransferLength = + Buffer.CmdIn->irDriveRegs.bSectorCountReg * SMART_LOG_SECTOR_SIZE; + Request->Flags = REQUEST_FLAG_DATA_IN; + break; + } + + case SMART_WRITE_LOG: + { + Request->DataTransferLength = + Buffer.CmdIn->irDriveRegs.bSectorCountReg * SMART_LOG_SECTOR_SIZE; + Request->Flags = REQUEST_FLAG_DATA_OUT; + break; + } + + case RETURN_SMART_STATUS: + { + Request->DataTransferLength = sizeof(IDEREGS); + Request->Flags = REQUEST_FLAG_SAVE_TASK_FILE; + break; + } + + case EXECUTE_OFFLINE_DIAGS: + { + UCHAR Subcommand = Buffer.CmdIn->irDriveRegs.bSectorNumberReg; + + if (Subcommand == SMART_SHORT_SELFTEST_CAPTIVE || + Subcommand == SMART_EXTENDED_SELFTEST_CAPTIVE) + { + return SRB_STATUS_INVALID_REQUEST; + } + + __fallthrough; + } + case ENABLE_DISABLE_AUTOSAVE: + case SAVE_ATTRIBUTE_VALUES: + case ENABLE_SMART: + case DISABLE_SMART: + case ENABLE_DISABLE_AUTO_OFFLINE: + { + Request->DataTransferLength = 0; + Request->Flags = 0; + break; + } + + default: + return SRB_STATUS_INVALID_REQUEST; + } + + if (Request->Flags & (REQUEST_FLAG_DATA_IN | REQUEST_FLAG_SAVE_TASK_FILE)) + { + if (Srb->DataTransferLength < + (sizeof(SRB_IO_CONTROL) + sizeof(*Buffer.CmdOut) - 1 + Request->DataTransferLength)) + { + return SRB_STATUS_INVALID_REQUEST; + } + + Request->DataBuffer = Buffer.CmdOut->bBuffer; + } + else if (Request->Flags & REQUEST_FLAG_DATA_OUT) + { + if (Srb->DataTransferLength < + (sizeof(SRB_IO_CONTROL) + sizeof(*Buffer.CmdIn) - 1 + Request->DataTransferLength)) + { + return SRB_STATUS_INVALID_REQUEST; + } + + Request->DataBuffer = Buffer.CmdIn->bBuffer; + } + + Request->Complete = AtaReqCompleteSmartIoControl; + + AtaIdeRegsToTaskFile(DevExt, &Buffer.CmdIn->irDriveRegs, Request); + + return SRB_STATUS_PENDING; +} + +CODE_SEG("PAGE") +NTSTATUS +AtaPdoHandleMiniportIdentify( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + union _PARAMS + { + PSENDCMDINPARAMS CmdIn; + PSENDCMDOUTPARAMS CmdOut; + } Buffer; + + PAGED_CODE(); + + if (Srb->DataTransferLength < + (sizeof(SRB_IO_CONTROL) + sizeof(*Buffer.CmdOut) - 1 + IDENTIFY_BUFFER_SIZE)) + { + Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST; + return STATUS_BUFFER_TOO_SMALL; + } + + Buffer.CmdIn = (PSENDCMDINPARAMS)(((PUCHAR)Srb->DataBuffer) + sizeof(SRB_IO_CONTROL)); + + if (IS_ATAPI(&DevExt->Device) || (Buffer.CmdIn->irDriveRegs.bCommandReg != ID_CMD)) + { + Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST; + return STATUS_INVALID_DEVICE_REQUEST; + } + + Buffer.CmdOut->cBufferSize = IDENTIFY_BUFFER_SIZE; + Buffer.CmdOut->DriverStatus.bDriverError = 0; + Buffer.CmdOut->DriverStatus.bIDEError = 0; + + RtlCopyMemory(Buffer.CmdOut->bBuffer, &DevExt->IdentifyDeviceData, IDENTIFY_BUFFER_SIZE); + + Srb->SrbStatus = SRB_STATUS_SUCCESS; + return STATUS_SUCCESS; +} + +CODE_SEG("PAGE") +NTSTATUS +AtaPdoHandleMiniportSmartVersion( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ PSCSI_REQUEST_BLOCK Srb) +{ + PATAPORT_CHANNEL_EXTENSION ChanExt = DevExt->Common.FdoExt; + PATAPORT_PORT_DATA PortData = &ChanExt->PortData; + PGETVERSIONINPARAMS VersionParameters; + ULONG i; + + PAGED_CODE(); + + if (Srb->DataTransferLength < (sizeof(*VersionParameters) + sizeof(SRB_IO_CONTROL))) + { + Srb->SrbStatus = SRB_STATUS_INVALID_REQUEST; + return STATUS_BUFFER_TOO_SMALL; + } + + VersionParameters = (PGETVERSIONINPARAMS)(((PUCHAR)Srb->DataBuffer) + sizeof(SRB_IO_CONTROL)); + + /* SMART 1.03 */ + VersionParameters->bVersion = 1; + VersionParameters->bRevision = 1; + VersionParameters->bReserved = 0; + + VersionParameters->fCapabilities = (CAP_ATA_ID_CMD | CAP_ATAPI_ID_CMD | CAP_SMART_CMD); + + VersionParameters->bIDEDeviceMap = 0; + + /* Emulate the PATA behavior */ + for (i = 0; i < MAX_IDE_DEVICE; ++i) + { + PATAPORT_DEVICE_EXTENSION DevExt; + ULONG DeviceFlag; + + DevExt = AtaFdoFindDeviceByPath(ChanExt, + AtaMarshallScsiAddress(PortData->PortNumber, i, 0), + Srb); + if (!DevExt) + continue; + + DeviceFlag = 1 << i; + if (PortData->PortNumber != 0) + DeviceFlag <<= 2; + if (IS_ATAPI(&DevExt->Device)) + DeviceFlag <<= 4; + VersionParameters->bIDEDeviceMap |= DeviceFlag; + + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, Srb); + } + + Srb->SrbStatus = SRB_STATUS_SUCCESS; + return STATUS_SUCCESS; +} diff --git a/drivers/storage/ide/atapi/wmi.c b/drivers/storage/ide/atapi/wmi.c new file mode 100644 index 00000000000..3a664778dee --- /dev/null +++ b/drivers/storage/ide/atapi/wmi.c @@ -0,0 +1,213 @@ +/* + * PROJECT: ReactOS ATA Port Driver + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: WMI support + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +/* INCLUDES *******************************************************************/ + +#include "atapi.h" + +/* GLOBALS ********************************************************************/ + +static WMIGUIDREGINFO AtapWmiGuidList[] = +{ + {&MSIde_PortDeviceInfo_GUID, 1, 0} +}; + +/* FUNCTIONS ******************************************************************/ + +static +CODE_SEG("PAGE") +NTSTATUS +NTAPI +AtaQueryWmiRegInfo( + _Inout_ PDEVICE_OBJECT DeviceObject, + _Inout_ PULONG RegFlags, + _Inout_ PUNICODE_STRING InstanceName, + _Outptr_result_maybenull_ PUNICODE_STRING *RegistryPath, + _Inout_ PUNICODE_STRING MofResourceName, + _Outptr_result_maybenull_ PDEVICE_OBJECT *Pdo) +{ + PATAPORT_DEVICE_EXTENSION DevExt = DeviceObject->DeviceExtension; + + UNREFERENCED_PARAMETER(InstanceName); + UNREFERENCED_PARAMETER(MofResourceName); + + PAGED_CODE(); + + *RegFlags = WMIREG_FLAG_INSTANCE_PDO; + *RegistryPath = &AtapDriverRegistryPath; + *Pdo = DevExt->Common.Self; + + return STATUS_SUCCESS; +} + +static +CODE_SEG("PAGE") +NTSTATUS +NTAPI +AtaQueryWmiDataBlock( + _Inout_ PDEVICE_OBJECT DeviceObject, + _Inout_ PIRP Irp, + _In_ ULONG GuidIndex, + _In_ ULONG InstanceIndex, + _In_ ULONG InstanceCount, + _Out_writes_opt_(InstanceCount) PULONG InstanceLengthArray, + _In_ ULONG BufferAvail, + _Out_writes_bytes_opt_(BufferAvail) PUCHAR Buffer) +{ + PATAPORT_DEVICE_EXTENSION DevExt; + NTSTATUS Status; + ATA_SCSI_ADDRESS AtaScsiAddress; + PMSIde_PortDeviceInfo DeviceInfo; + + PAGED_CODE(); + + if (GuidIndex > RTL_NUMBER_OF(AtapWmiGuidList)) + { + Status = STATUS_WMI_GUID_NOT_FOUND; + goto Complete; + } + + /* Only ever register 1 instance per GUID */ + if (InstanceIndex != 0 || InstanceCount != 1) + { + Status = STATUS_WMI_INSTANCE_NOT_FOUND; + goto Complete; + } + + if (!InstanceLengthArray || BufferAvail < sizeof(*DeviceInfo)) + { + Status = STATUS_BUFFER_TOO_SMALL; + goto Complete; + } + + DevExt = DeviceObject->DeviceExtension; + AtaScsiAddress = DevExt->Device.AtaScsiAddress; + + DeviceInfo = (PMSIde_PortDeviceInfo)Buffer; + DeviceInfo->Bus = AtaScsiAddress.PathId; + DeviceInfo->Target = AtaScsiAddress.TargetId; + DeviceInfo->Lun = AtaScsiAddress.Lun; + + *InstanceLengthArray = sizeof(*DeviceInfo); + + Status = STATUS_SUCCESS; + +Complete: + return WmiCompleteRequest(DeviceObject, + Irp, + Status, + sizeof(*DeviceInfo), + IO_NO_INCREMENT); +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaPdoWmi( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _Inout_ PIRP Irp) +{ + NTSTATUS Status; + SYSCTL_IRP_DISPOSITION Disposition; + + PAGED_CODE(); + + TRACE("%s(%p, %p)\n", __FUNCTION__, DevExt, Irp); + + Status = IoAcquireRemoveLock(&DevExt->Common.RemoveLock, Irp); + if (!NT_SUCCESS(Status)) + { + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return Status; + } + + Status = WmiSystemControl(&DevExt->WmiLibInfo, + DevExt->Common.Self, + Irp, + &Disposition); + switch (Disposition) + { + case IrpProcessed: + break; + + case IrpNotCompleted: + IoCompleteRequest(Irp, IO_NO_INCREMENT); + break; + + default: + ASSERT(FALSE); + __fallthrough; + case IrpForward: + case IrpNotWmi: + Status = STATUS_NOT_SUPPORTED; + Irp->IoStatus.Status = Status; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + break; + } + + IoReleaseRemoveLock(&DevExt->Common.RemoveLock, Irp); + + return Status; +} + +static +CODE_SEG("PAGE") +NTSTATUS +AtaFdoWmi( + _In_ PATAPORT_CHANNEL_EXTENSION ChanExt, + _Inout_ PIRP Irp) +{ + PAGED_CODE(); + + IoSkipCurrentIrpStackLocation(Irp); + return IoCallDriver(ChanExt->Common.LowerDeviceObject, Irp); +} + +CODE_SEG("PAGE") +NTSTATUS +NTAPI +AtaDispatchWmi( + _In_ PDEVICE_OBJECT DeviceObject, + _Inout_ PIRP Irp) +{ + PAGED_CODE(); + + if (IS_FDO(DeviceObject->DeviceExtension)) + return AtaFdoWmi(DeviceObject->DeviceExtension, Irp); + else + return AtaPdoWmi(DeviceObject->DeviceExtension, Irp); +} + +CODE_SEG("PAGE") +NTSTATUS +AtaPdoWmiRegistration( + _In_ PATAPORT_DEVICE_EXTENSION DevExt, + _In_ BOOLEAN Register) +{ + ULONG Action; + + PAGED_CODE(); + + if (Register) + { + DevExt->WmiLibInfo.GuidCount = RTL_NUMBER_OF(AtapWmiGuidList); + DevExt->WmiLibInfo.GuidList = AtapWmiGuidList; + + DevExt->WmiLibInfo.QueryWmiRegInfo = AtaQueryWmiRegInfo; + DevExt->WmiLibInfo.QueryWmiDataBlock = AtaQueryWmiDataBlock; + + Action = WMIREG_ACTION_REGISTER; + } + else + { + Action = WMIREG_ACTION_DEREGISTER; + } + + return IoWMIRegistrationControl(DevExt->Common.Self, Action); +} diff --git a/sdk/include/reactos/drivers/ata/identify_funcs.h b/sdk/include/reactos/drivers/ata/identify_funcs.h new file mode 100644 index 00000000000..1008a3f8656 --- /dev/null +++ b/sdk/include/reactos/drivers/ata/identify_funcs.h @@ -0,0 +1,595 @@ +/* + * PROJECT: ReactOS Storage Stack + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * or MIT (https://spdx.org/licenses/MIT) + * PURPOSE: ATA IDENTIFY DEVICE and IDENTIFY PACKET DEVICE data helper functions + * COPYRIGHT: Copyright 2026 Dmitry Borisov + */ + +#pragma once + +FORCEINLINE +BOOLEAN +AtaDevHasCdbInterrupt( + _In_ PIDENTIFY_PACKET_DATA IdentifyPacketData) +{ + /* Bits 5:6 of word 0 */ + return (IdentifyPacketData->GeneralConfiguration.DrqDelay == 1); +} + +FORCEINLINE +UCHAR +AtaDevCdbSizeInWords( + _In_ PIDENTIFY_PACKET_DATA IdentifyPacketData) +{ + /* Bits 0:2 of word 0 */ + return (IdentifyPacketData->GeneralConfiguration.PacketType != 0) ? 8 : 6; +} + +FORCEINLINE +BOOLEAN +AtaDevMaxLun( + _In_ PIDENTIFY_PACKET_DATA IdentifyPacketData) +{ + /* Bits 0:2 of word 126 */ + USHORT LastLunIdentifier = IdentifyPacketData->ReservedWord126 & 7; + + /* + * We perform additional validation because + * most ATAPI devices ignore the LUN field in the CDB and respond to each LUN. + */ + + /* Make sure this field has no value that represents all bits set */ + if (LastLunIdentifier != 7) + return LastLunIdentifier + 1; + + return 1; +} + +FORCEINLINE +BOOLEAN +AtaDevIsDmaDirectionRequired( + _In_ PIDENTIFY_PACKET_DATA IdentifyPacketData) +{ + /* Bit 15 of word 62 */ + if (IdentifyPacketData->DMADIR.DMADIRBitRequired) + { + return !(IdentifyPacketData->MultiWordDMASupport & 0x7) && // Bits 0:2 of word 63 + !IdentifyPacketData->Capabilities.DmaSupported && // Bit 8 of word 49 + !IdentifyPacketData->Capabilities.InterleavedDmaSupported && // Bit 15 of word 49 + !(IdentifyPacketData->UltraDMASupport & 0x7F); // Bits 0:6 of word 88 + } + + return FALSE; +} + +FORCEINLINE +BOOLEAN +AtaDevIsTape( + _In_ PIDENTIFY_PACKET_DATA IdentifyPacketData) +{ + /* Bits 8:12 of word 0 (sequential-access device) */ + return (IdentifyPacketData->GeneralConfiguration.CommandPacketType == 1); +} + +FORCEINLINE +BOOLEAN +AtaDevIsIdentifyDataValid( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + ULONG i; + UCHAR Crc; + + /* Bits 0:8 of word 255 */ + if (IdentifyData->Signature != 0xA5) + { + /* The integrity word is missing, assume the data provided by the device is valid */ + return TRUE; + } + + /* Verify the checksum */ + Crc = 0; + for (i = 0; i < sizeof(*IdentifyData); ++i) + { + Crc += ((PUCHAR)IdentifyData)[i]; + } + + return (Crc == 0); +} + +FORCEINLINE +BOOLEAN +AtaDevHasLbaTranslation( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Bit 9 of word 49 */ + return IdentifyData->Capabilities.LbaSupported; +} + +FORCEINLINE +ULONG +AtaDevUserAddressableSectors28Bit( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Words 60-61 */ + return IdentifyData->UserAddressableSectors; +} + +FORCEINLINE +ULONG64 +AtaDevUserAddressableSectors48Bit( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Words 100-103 */ + return ((ULONG64)IdentifyData->Max48BitLBA[1] << 32) | IdentifyData->Max48BitLBA[0]; +} + +FORCEINLINE +BOOLEAN +AtaDevHas48BitAddressFeature( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 83: 15 = 0, 14 = 1 */ + if (IdentifyData->CommandSetSupport.WordValid83 == 1) + { + /* Bit 10 of word 83 */ + return IdentifyData->CommandSetSupport.BigLba; + } + + return FALSE; +} + +FORCEINLINE +BOOLEAN +AtaDevIsCurrentGeometryValid( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + return ((IdentifyData->TranslationFieldsValid & 1) && + (IdentifyData->NumberOfCurrentCylinders != 0) && + (IdentifyData->NumberOfCurrentCylinders <= 63) && + (IdentifyData->NumberOfCurrentHeads != 0) && + (IdentifyData->NumberOfCurrentHeads <= 16) && + (IdentifyData->CurrentSectorsPerTrack != 0)); +} + +FORCEINLINE +VOID +AtaDevDefaultChsTranslation( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData, + _Out_ PUSHORT Cylinders, + _Out_ PUSHORT Heads, + _Out_ PUSHORT SectorsPerTrack) +{ + /* Word 1 */ + *Cylinders = IdentifyData->NumCylinders; + /* Word 3 */ + *Heads = IdentifyData->NumHeads; + /* Word 6 */ + *SectorsPerTrack = IdentifyData->NumSectorsPerTrack; +} + +FORCEINLINE +VOID +AtaDevCurrentChsTranslation( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData, + _Out_ PUSHORT Cylinders, + _Out_ PUSHORT Heads, + _Out_ PUSHORT SectorsPerTrack) +{ + /* Word 54 */ + *Cylinders = IdentifyData->NumberOfCurrentCylinders; + /* Word 55 */ + *Heads = IdentifyData->NumberOfCurrentHeads; + /* Word 55 */ + *SectorsPerTrack = IdentifyData->CurrentSectorsPerTrack; +} + +FORCEINLINE +UCHAR +AtaDevCurrentSectorsPerDrq( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + UCHAR MultiSectorCurrent; + + /* Bit 8 of word 59 */ + if (!(IdentifyData->MultiSectorSettingValid)) + return 0; + + /* The word 59 should be a power of 2 */ + MultiSectorCurrent = IdentifyData->CurrentMultiSectorSetting; + if ((MultiSectorCurrent > 0) && ((MultiSectorCurrent & (MultiSectorCurrent - 1)) == 0)) + return MultiSectorCurrent; + + return 0; +} + +FORCEINLINE +UCHAR +AtaDevMaximumSectorsPerDrq( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + UCHAR MultiSectorMax; + + /* The word 47 should be a power of 2 */ + MultiSectorMax = IdentifyData->MaximumBlockTransfer; + if ((MultiSectorMax > 0) && ((MultiSectorMax & (MultiSectorMax - 1)) == 0)) + return MultiSectorMax; + + return 0; +} + +FORCEINLINE +ULONG +AtaDevBytesPerLogicalSector( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + ULONG WordCount; + + /* Word 106: 15 = 0, 14 = 1, 12 = 1 */ + if (IdentifyData->PhysicalLogicalSectorSize.Reserved1 == 1 && + IdentifyData->PhysicalLogicalSectorSize.LogicalSectorLongerThan256Words) + { + /* Words 116-117 */ + WordCount = IdentifyData->WordsPerLogicalSector[0]; + WordCount |= (ULONG)IdentifyData->WordsPerLogicalSector[1] << 16; + } + else + { + /* 256 words = 512 bytes */ + WordCount = 256; + } + + return WordCount * sizeof(USHORT); +} + +FORCEINLINE +ULONG +AtaDevLogicalSectorsPerPhysicalSector( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData, + _Out_ PULONG Exponent) +{ + /* Word 106: 15 = 0, 14 = 1, 13 = 1 */ + if (IdentifyData->PhysicalLogicalSectorSize.Reserved1 == 1 && + IdentifyData->PhysicalLogicalSectorSize.MultipleLogicalSectorsPerPhysicalSector) + { + /* Bits 0:3 of word 106 */ + *Exponent = IdentifyData->PhysicalLogicalSectorSize.LogicalSectorsPerPhysicalSector; + + return 1 << *Exponent; + } + + *Exponent = 0; + return 1 << 0; +} + +FORCEINLINE +ULONG +AtaDevLogicalSectorAlignment( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 209: 15 = 0, 14 = 1 */ + if (IdentifyData->BlockAlignment.Word209Supported && + IdentifyData->BlockAlignment.Reserved0 == 0) + { + /* Bits 0:13 of word 209 */ + return IdentifyData->BlockAlignment.AlignmentOfLogicalWithinPhysical; + } + + return 0; +} + +FORCEINLINE +BOOLEAN +AtaDevInPuisState( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 2 */ + return (IdentifyData->SpecificConfiguration == 0x37C8) || + (IdentifyData->SpecificConfiguration == 0x738C); +} + +FORCEINLINE +BOOLEAN +AtaDevIsIdentifyDataIncomplete( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Bit 2 of word 0 */ + return IdentifyData->GeneralConfiguration.ResponseIncomplete; +} + +FORCEINLINE +BOOLEAN +AtaDevIsRemovable( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Bit 7 of word 0 */ + return IdentifyData->GeneralConfiguration.RemovableMedia; +} + +FORCEINLINE +BOOLEAN +AtaDevHasRemovableMediaFeature( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + if (AtaDevIsRemovable(IdentifyData)) + { + /* Word 83: 15 = 0, 14 = 1 */ + if (IdentifyData->CommandSetSupport.WordValid == 1) + { + /* Bit 2 of word 82 */ + return IdentifyData->CommandSetSupport.RemovableMediaFeature; + } + } + + return FALSE; +} + +FORCEINLINE +ULONG +AtaDevQueueDepth( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Bit 8 of word 76 */ + if (IdentifyData->SerialAtaCapabilities.NCQ) + { + /* Bits 0:4 of word 75 */ + return IdentifyData->QueueDepth + 1; + } + + return 0; +} + +FORCEINLINE +BOOLEAN +AtaDevHasNcqAutosense( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + USHORT Word76 = ((PUSHORT)IdentifyData)[76]; // IdentifyData->SerialAtaCapabilities + + if (Word76 != 0x0000 && Word76 != 0xFFFF) + { + /* Bit 7 of word 78 */ + return IdentifyData->SerialAtaFeaturesSupported.NCQAutosense; + } + + return FALSE; +} + +FORCEINLINE +BOOLEAN +AtaDevHasSenseDataReporting( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + if (IdentifyData->CommandSetActive.Words119_120Valid && // Word 86: bit 15 = 1 + IdentifyData->CommandSetSupportExt.WordValid == 1) // Word 119: 15 = 0, 14 = 1 + { + /* Bit 6 of word 119 */ + return IdentifyData->CommandSetSupportExt.SenseDataReporting; + } + + return FALSE; +} + +FORCEINLINE +BOOLEAN +AtaDevHasRemovableMediaStatusNotification( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 127: bit 0 = 1, bit 1 = 0 */ + return (IdentifyData->MsnSupport == 1); +} + +FORCEINLINE +BOOLEAN +AtaDevHasIeee1667( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + // TODO: Verify word 48 + + /* Bit 0 of word 48 */ + if (IdentifyData->TrustedComputing.FeatureSupported) + { + /* Bit 7 of word 69 */ + return IdentifyData->AdditionalSupported.IEEE1667; + } + + return FALSE; +} + +FORCEINLINE +BOOLEAN +AtaDevHasWorldWideName( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 87: 15 = 0, 14 = 1 */ + if (IdentifyData->CommandSetActive.Reserved4 == 1) + { + /* Bit 8 of word 87 */ + return IdentifyData->CommandSetActive.WWN64Bit; + } + + return FALSE; +} + +FORCEINLINE +BOOLEAN +AtaDevIsZonedDevice( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Bits 0:1 of word 69 */ + return (IdentifyData->AdditionalSupported.ZonedCapabilities != 0); +} + +FORCEINLINE +UCHAR +AtaDevZonedCapabilities( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Bits 0:1 of word 69 */ + return IdentifyData->AdditionalSupported.ZonedCapabilities; +} + +FORCEINLINE +BOOLEAN +AtaDevHasSecurityModeFeature( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 83: 15 = 0, 14 = 1 */ + if (IdentifyData->CommandSetSupport.WordValid == 1) + { + /* Bit 1 of word 82 */ + return IdentifyData->CommandSetSupport.SecurityMode; + } + + return FALSE; +} + +FORCEINLINE +BOOLEAN +AtaDevHasSmartFeature( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Bit 0 of word 82 */ + return IdentifyData->CommandSetSupport.SmartCommands; +} + +FORCEINLINE +BOOLEAN +AtaDevIsVolatileWriteCacheEnabled( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 83: 15 = 0, 14 = 1 */ + if (IdentifyData->CommandSetSupport.WordValid == 1) + { + /* Bit 5 of word 82 and bit 5 of word 85 */ + return (IdentifyData->CommandSetSupport.WriteCache && + IdentifyData->CommandSetActive.WriteCache); + } + + return FALSE; +} + +FORCEINLINE +BOOLEAN +AtaDevIsReadLookAHeadEnabled( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 83: 15 = 0, 14 = 1 */ + if (IdentifyData->CommandSetSupport.WordValid == 1) + { + /* Bit 6 of word 82 and bit 6 of word 85 */ + return (IdentifyData->CommandSetSupport.LookAhead && + IdentifyData->CommandSetActive.LookAhead); + } + + return FALSE; +} + +FORCEINLINE +UCHAR +AtaDevNominalFormFactor( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Bits 0:3 of word 168 */ + return IdentifyData->NominalFormFactor; +} + +FORCEINLINE +USHORT +AtaDevMediumRotationRate( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 217 */ + return IdentifyData->NominalMediaRotationRate; +} + +FORCEINLINE +BOOLEAN +AtaDevIsRotatingDevice( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 217 */ + return (IdentifyData->NominalMediaRotationRate >= 0x0401 && + IdentifyData->NominalMediaRotationRate <= 0xFFFE); +} + +FORCEINLINE +BOOLEAN +AtaDevIsSsd( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 217 */ + return (IdentifyData->NominalMediaRotationRate == 1); +} + +FORCEINLINE +BOOLEAN +AtaDevHasTrimFunction( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Bit 0 of word 169 */ + return IdentifyData->DataSetManagementFeature.SupportsTrim; +} + +FORCEINLINE +BOOLEAN +AtaDevHasRzatFunction( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Bit 5 of word 69 */ + return IdentifyData->AdditionalSupported.ReadZeroAfterTrimSupported; +} + +FORCEINLINE +BOOLEAN +AtaDevHasDratFunction( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Bit 14 of word 69 */ + return IdentifyData->AdditionalSupported.DeterministicReadAfterTrimSupported; +} + +FORCEINLINE +BOOLEAN +AtaDevHasForceUnitAccessCommands( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 83: 15 = 0, 14 = 1 */ + if (IdentifyData->CommandSetSupport.WordValid83 == 1) + { + /* Bit 6 of word 84 */ + return IdentifyData->CommandSetSupport.WriteFua; + } + + return FALSE; +} + +FORCEINLINE +BOOLEAN +AtaDevHasFlushCache( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 83: 15 = 0, 14 = 1 */ + if (IdentifyData->CommandSetSupport.WordValid == 1) + { + /* Bit 12 of word 83 */ + return IdentifyData->CommandSetSupport.FlushCache; + } + + return FALSE; +} + +FORCEINLINE +BOOLEAN +AtaDevHasFlushCacheExt( + _In_ PIDENTIFY_DEVICE_DATA IdentifyData) +{ + /* Word 83: 15 = 0, 14 = 1 */ + if (IdentifyData->CommandSetSupport.WordValid == 1) + { + /* Bit 13 of word 83 */ + return IdentifyData->CommandSetSupport.FlushCacheExt; + } + + return FALSE; +}