Files
reactos/drivers/storage/ide/atapi/dev_power.c
Dmitry Borisov ac33647888 [ATAPI] Add ATA storage driver
CORE-17256
CORE-17191
CORE-17716
CORE-17977
CORE-13976
CORE-16216
2026-04-21 15:01:22 -05:00

532 lines
14 KiB
C

/*
* PROJECT: ReactOS ATA Port Driver
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Power management
* COPYRIGHT: Copyright 2026 Dmitry Borisov <di.sean@protonmail.com>
*/
/* 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);
}