mirror of
https://github.com/reactos/reactos.git
synced 2026-05-24 08:11:38 +08:00
532 lines
14 KiB
C
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);
|
|
}
|