reactos/drivers/storage/class/cdrom/zpodd.c

833 lines
23 KiB
C

/*++
Copyright (C) Microsoft Corporation. All rights reserved.
Module Name:
zpodd.c
Abstract:
Code for Zero Power ODD support.
Environment:
kernel mode only
Notes:
Revision History:
--*/
#include "ntddk.h"
#include "ntddstor.h"
#include "wdmguid.h"
#include "cdrom.h"
#include "mmc.h"
#include "ioctl.h"
#include "scratch.h"
#ifdef DEBUG_USE_WPP
#include "zpodd.tmh"
#endif
_IRQL_requires_max_(PASSIVE_LEVEL)
ULONG
DeviceGetZPODDEnabledFromRegistry();
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceQueryD3ColdInterface(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_Out_ PD3COLD_SUPPORT_INTERFACE D3ColdInterface
);
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceSendEnableIdlePowerIoctl(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ BOOLEAN WakeCapable,
_In_ BOOLEAN Enable,
_In_ ULONG D3IdleTimeout
);
#if ALLOC_PRAGMA
#pragma alloc_text(PAGE, DeviceInitializeZPODD)
#pragma alloc_text(PAGE, DeviceGetZPODDEnabledFromRegistry)
#pragma alloc_text(PAGE, DeviceQueryD3ColdInterface)
#pragma alloc_text(PAGE, DeviceSendEnableIdlePowerIoctl)
#pragma alloc_text(PAGE, DeviceReleaseZPODDResources)
#pragma alloc_text(PAGE, DeviceZPODDIsInHomePosition)
#pragma alloc_text(PAGE, DeviceMarkActive)
#endif
#pragma warning(push)
#pragma warning(disable:4152) // nonstandard extension, function/data pointer conversion in expression
#pragma warning(disable:26000) // read overflow reported because of pointer type conversion
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceInitializeZPODD(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
This routine initialize the contents of ZPODD structure.
Arguments:
DeviceExtension - the device extension
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
NTSTATUS tempStatus = STATUS_SUCCESS;
PZERO_POWER_ODD_INFO zpoddInfo = NULL;
PFEATURE_DATA_REMOVABLE_MEDIUM removableMediumHeader = NULL;
ULONG ZPODDEnabledInRegistry = 0;
PD3COLD_SUPPORT_INTERFACE d3ColdInterface = NULL;
DEVICE_WAKE_DEPTH deepestWakeableDstate = DeviceWakeDepthNotWakeable;
BOOLEAN inHomePosition = FALSE;
PAGED_CODE();
if (DeviceExtension->ZeroPowerODDInfo != NULL)
{
//
// Already initialized.
//
goto Cleanup;
}
ZPODDEnabledInRegistry = DeviceGetZPODDEnabledFromRegistry();
if (ZPODDEnabledInRegistry == 0)
{
//
// User has explicitly disabled Zero Power ODD.
//
status = STATUS_NOT_SUPPORTED;
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"DeviceInitializeZPODD: ZPODD not enabled due to registry settings.\n"
));
goto Cleanup;
}
zpoddInfo = ExAllocatePoolWithTag(NonPagedPoolNx,
sizeof(ZERO_POWER_ODD_INFO),
CDROM_TAG_ZERO_POWER_ODD);
if (zpoddInfo == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlZeroMemory(zpoddInfo, sizeof (ZERO_POWER_ODD_INFO));
//
// Check the system for the following prerequisites:
//
// 1. SATA: Device Attention line
// 2. SATA: Asynchronous Notification
// 3. ODD: LoChange / MediaRemoval
// 4. ACPI: Wake capable
//
// Only drawer and slot loading types have well defined behaviors in the spec, so only these two
// types are supported.
//
//
// Check for DA & AN
//
if ((DeviceExtension->PowerDescriptor == NULL) ||
(DeviceExtension->PowerDescriptor->DeviceAttentionSupported == FALSE) ||
(DeviceExtension->PowerDescriptor->AsynchronousNotificationSupported == FALSE))
{
status = STATUS_NOT_SUPPORTED;
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"DeviceInitializeZPODD: ZPODD not enabled due to SATA features not present.\n"
));
goto Cleanup;
}
//
// Check for LoChange / MediaRemoval
//
removableMediumHeader = (PFEATURE_DATA_REMOVABLE_MEDIUM)
DeviceFindFeaturePage(DeviceExtension->DeviceAdditionalData.Mmc.CapabilitiesBuffer,
DeviceExtension->DeviceAdditionalData.Mmc.CapabilitiesBufferSize,
FeatureRemovableMedium);
if ((removableMediumHeader == NULL) ||
(!((removableMediumHeader->LoadingMechanism == LOADING_MECHANISM_TRAY) && (removableMediumHeader->Load == 0) && // Drawer ...
(removableMediumHeader->DBML != FALSE)) && // requires LoChange/NotBusy
!((removableMediumHeader->LoadingMechanism == LOADING_MECHANISM_CADDY) && (removableMediumHeader->Load == 0) && // Slot ...
(DeviceExtension->MediaChangeDetectionInfo->Gesn.Supported != FALSE)))) // requires MediaRemoval
{
status = STATUS_NOT_SUPPORTED;
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"DeviceInitializeZPODD: ZPODD not enabled due to ODD features not present.\n"
));
goto Cleanup;
}
zpoddInfo->LoadingMechanism = removableMediumHeader->LoadingMechanism;
zpoddInfo->Load = removableMediumHeader->Load;
//
// Check for ACPI
//
status = DeviceQueryD3ColdInterface(DeviceExtension, &zpoddInfo->D3ColdInterface);
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"DeviceInitializeZPODD: Query D3Cold support interface failed.\n"
));
goto Cleanup;
}
//
// If the platform supports Zero Power ODD, the following conditions must be met:
//
// 1. The deepest wakeable D-state for the device is D3Cold;
// 2. The platform supports D3Cold for the device.
//
d3ColdInterface = &zpoddInfo->D3ColdInterface;
status = d3ColdInterface->GetIdleWakeInfo(d3ColdInterface->Context,
PowerSystemWorking,
&deepestWakeableDstate);
if (!NT_SUCCESS(status))
{
goto Cleanup;
}
//
// DeviceExtension->PowerDescriptor->D3ColdSupported is retrieved from lower layer.
// It has more accurate supportive information than just uses d3ColdInterface->GetD3ColdCapability
//
if ((deepestWakeableDstate != DeviceWakeDepthD3cold) ||
(DeviceExtension->PowerDescriptor->D3ColdSupported == FALSE))
{
status = STATUS_NOT_SUPPORTED;
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"DeviceInitializeZPODD: ZPODD not enabled due to ACPI support not present.\n"
));
goto Cleanup;
}
//
// The system meets all requirements. Go ahead and enable ZPODD.
//
//
// Register with the runtime power framework.
// Note that no un-registration is needed during tear-down.
// D3Cold will be enabled (success case of following call) or disabled by port driver during processing Enable Idle Power IOCTL.
//
status = DeviceSendEnableIdlePowerIoctl(DeviceExtension, TRUE, TRUE, DELAY_TIME_TO_ENTER_ZERO_POWER_IN_MS);
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"DeviceInitializeZPODD: ZPODD not enabled due to runtime power framework.\n"
));
goto Cleanup;
}
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"DeviceInitializeZPODD: ZPODD is enabled.\n"
));
DeviceExtension->ZeroPowerODDInfo = zpoddInfo;
//
// If device is not in home position, then we should take an active reference here
// to prevent it from being powered off.
//
inHomePosition = DeviceZPODDIsInHomePosition(DeviceExtension);
if (inHomePosition == FALSE)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"DeviceInitializeZPODD: not ready to power off, device marked as active\n"));
DeviceMarkActive(DeviceExtension, TRUE, FALSE);
}
else
{
//
// cache get configuration response.
// failing is not critical, so we don't want to check for status here.
//
if (zpoddInfo->GetConfigurationBuffer == NULL)
{
tempStatus = DeviceGetConfigurationWithAlloc(DeviceExtension->Device,
&zpoddInfo->GetConfigurationBuffer,
&zpoddInfo->GetConfigurationBufferSize,
FeatureProfileList,
SCSI_GET_CONFIGURATION_REQUEST_TYPE_ALL);
UNREFERENCED_PARAMETER(tempStatus); // Avoid PREFAST warning.
}
}
Cleanup:
if (!NT_SUCCESS(status))
{
//
// We register always even in non-ZPODD case, per request from storport.
//
tempStatus = DeviceSendEnableIdlePowerIoctl(DeviceExtension, FALSE, FALSE, DELAY_TIME_TO_ENTER_ZERO_POWER_IN_MS);
if (NT_SUCCESS(tempStatus))
{
//
// Mark the device active; this reference will never be released unless the system enters a
// low power state.
//
DeviceMarkActive(DeviceExtension, TRUE, FALSE);
}
FREE_POOL(zpoddInfo);
}
//
// If Zero Power ODD is not supported, we should not block the device init sequence.
//
return STATUS_SUCCESS;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
ULONG
DeviceGetZPODDEnabledFromRegistry()
/*++
Routine Description:
Get the ZeroPowerODDEnabled value from registry, which dictates if Zero Power ODD
should be enabled or not. If the value is not in registry, by default Zero
Power ODD is enabled.
Arguments:
None
Return Value:
ULONG
--*/
{
NTSTATUS status = STATUS_SUCCESS;
WDFKEY registryKey = NULL;
ULONG ZPODDEnabled = 0;
DECLARE_CONST_UNICODE_STRING(registryValueName, L"ZeroPowerODDEnabled");
PAGED_CODE();
//
// open the Parameters key under the service key.
//
status = WdfDriverOpenParametersRegistryKey(WdfGetDriver(),
KEY_READ,
WDF_NO_OBJECT_ATTRIBUTES,
&registryKey);
if (NT_SUCCESS(status))
{
status = WdfRegistryQueryULong(registryKey,
&registryValueName,
&ZPODDEnabled);
WdfRegistryClose(registryKey);
}
if (!NT_SUCCESS(status))
{
//
// By default, Zero Power ODD is enabled
//
ZPODDEnabled = 1;
}
return ZPODDEnabled;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceQueryD3ColdInterface(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_Out_ PD3COLD_SUPPORT_INTERFACE D3ColdInterface
)
/*++
Routine Description:
Queries ACPI for the D3Cold support interface.
Arguments:
DeviceExtension - the device extension
D3ColdInterface - output buffer receiving the interface
Return Value:
NTSTATUS
--*/
{
PIRP irp = NULL;
KEVENT event;
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_OBJECT targetDevice = NULL;
IO_STATUS_BLOCK ioStatus = {0};
PIO_STACK_LOCATION irpStack = NULL;
PAGED_CODE();
RtlZeroMemory(D3ColdInterface, sizeof(D3COLD_SUPPORT_INTERFACE));
//
// Query D3COLD support interface synchronously
//
KeInitializeEvent(&event, NotificationEvent, FALSE);
targetDevice = IoGetAttachedDeviceReference(DeviceExtension->DeviceObject);
irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP,
targetDevice,
NULL,
0,
0,
&event,
&ioStatus);
if (irp == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
irp->IoStatus.Information = 0;
irpStack = IoGetNextIrpStackLocation(irp);
irpStack->MinorFunction = IRP_MN_QUERY_INTERFACE;
irpStack->Parameters.QueryInterface.InterfaceType = (LPGUID) &GUID_D3COLD_SUPPORT_INTERFACE;
irpStack->Parameters.QueryInterface.Size = sizeof (D3COLD_SUPPORT_INTERFACE);
irpStack->Parameters.QueryInterface.Version = D3COLD_SUPPORT_INTERFACE_VERSION;
irpStack->Parameters.QueryInterface.Interface = (PINTERFACE) D3ColdInterface;
irpStack->Parameters.QueryInterface.InterfaceSpecificData = NULL;
status = IoCallDriver(targetDevice, irp);
if (status == STATUS_PENDING)
{
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
status = ioStatus.Status;
}
if (!NT_SUCCESS(status))
{
goto Cleanup;
}
NT_ASSERT(D3ColdInterface->SetD3ColdSupport != NULL);
NT_ASSERT(D3ColdInterface->GetIdleWakeInfo != NULL);
NT_ASSERT(D3ColdInterface->GetD3ColdCapability != NULL);
Cleanup:
ObDereferenceObject(targetDevice);
return status;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceSendEnableIdlePowerIoctl(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ BOOLEAN WakeCapable,
_In_ BOOLEAN Enable,
_In_ ULONG D3IdleTimeout
)
/*++
Routine Description:
Enables idle power support.
Arguments:
DeviceExtension - the device extension
WakeCapable - whether the device is wake capable
Enable - enable / disable idle power management
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
STORAGE_IDLE_POWER idlePower = {0};
IO_STATUS_BLOCK ioStatus = {0};
PIRP irp = NULL;
KEVENT event;
PAGED_CODE();
idlePower.Version = 1;
idlePower.Size = sizeof (STORAGE_IDLE_POWER);
idlePower.WakeCapableHint = WakeCapable;
idlePower.D3ColdSupported = Enable;
idlePower.D3IdleTimeout = D3IdleTimeout;
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = IoBuildDeviceIoControlRequest(IOCTL_STORAGE_ENABLE_IDLE_POWER,
DeviceExtension->LowerPdo,
&idlePower,
sizeof(STORAGE_IDLE_POWER),
NULL,
0,
FALSE,
&event,
&ioStatus);
if (irp == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
else
{
//
// Send the synchronous request to port driver.
//
status = IoCallDriver(DeviceExtension->LowerPdo, irp);
if (status == STATUS_PENDING)
{
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
status = ioStatus.Status;
}
}
TracePrint((TRACE_LEVEL_INFORMATION,
TRACE_FLAG_POWER,
"DeviceSendEnableIdlePowerIoctl: Port driver returned status (%x) for FDO (%p)\n"
"\tD3ColdSupported: %u\n"
"\tD3IdleTimeout: %u (ms)",
status,
DeviceExtension->DeviceObject,
Enable,
DELAY_TIME_TO_ENTER_ZERO_POWER_IN_MS));
return status;
}
_IRQL_requires_max_(APC_LEVEL)
VOID
DeviceReleaseZPODDResources(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
This routine will cleanup any resources allocated for ZPODD.
Arguments:
DeviceExtension - the device context
Return Value:
None.
--*/
{
PZERO_POWER_ODD_INFO zpoddInfo = DeviceExtension->ZeroPowerODDInfo;
PAGED_CODE()
if (zpoddInfo != NULL)
{
FREE_POOL(zpoddInfo->GetConfigurationBuffer);
FREE_POOL(zpoddInfo);
}
return;
}
NTSTATUS
DeviceZPODDGetPowerupReason(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_Out_ PSTORAGE_IDLE_POWERUP_REASON PowerupReason
)
/*++
Routine Description:
This routine queries the port driver for what caused the power up.
Arguments:
DeviceExtension - device extension.
PowerupReason - what caused the power up.
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PIRP irp = NULL;
IO_STATUS_BLOCK ioStatus = {0};
KEVENT event;
RtlZeroMemory(PowerupReason, sizeof (STORAGE_IDLE_POWERUP_REASON));
PowerupReason->Size = sizeof (STORAGE_IDLE_POWERUP_REASON);
PowerupReason->Version = STORAGE_IDLE_POWERUP_REASON_VERSION_V1;
//
// Setup a synchronous irp.
//
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = IoBuildDeviceIoControlRequest(IOCTL_STORAGE_GET_IDLE_POWERUP_REASON,
DeviceExtension->LowerPdo,
PowerupReason,
sizeof (STORAGE_IDLE_POWERUP_REASON),
PowerupReason,
sizeof (STORAGE_IDLE_POWERUP_REASON),
FALSE,
&event,
&ioStatus);
if (irp == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
else
{
//
// Send the synchronous request to port driver.
//
status = IoCallDriver(DeviceExtension->LowerPdo, irp);
if (status == STATUS_PENDING)
{
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
status = ioStatus.Status;
}
}
return status;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
BOOLEAN
DeviceZPODDIsInHomePosition(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
Checks to see if the device is ready to be powered off.
Requirements are: 1. tray closed 2. no media present.
Arguments:
DeviceExtension - device extension.
Return Value:
BOOLEAN
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PZERO_POWER_ODD_INFO zpoddInfo = DeviceExtension->ZeroPowerODDInfo;
SCSI_REQUEST_BLOCK srb = {0};
PCDB cdb = (PCDB) srb.Cdb;
BOOLEAN inHomePosition = FALSE;
PAGED_CODE();
if (zpoddInfo != NULL)
{
//
// Clear sense data.
//
zpoddInfo->SenseKey = 0;
zpoddInfo->AdditionalSenseCode = 0;
zpoddInfo->AdditionalSenseCodeQualifier = 0;
//
// Send a Test Unit Ready to check media & tray status.
//
RtlZeroMemory(&srb, sizeof(SCSI_REQUEST_BLOCK));
srb.CdbLength = 6;
cdb->CDB6GENERIC.OperationCode = SCSIOP_TEST_UNIT_READY;
srb.TimeOutValue = CDROM_TEST_UNIT_READY_TIMEOUT;
status = DeviceSendSrbSynchronously(DeviceExtension->Device,
&srb,
NULL,
0,
FALSE,
NULL);
#ifdef __REACTOS__
if (!NT_SUCCESS(status))
{
return FALSE;
}
#endif
//
// At this time, sense data, if available, is already copied into zpoddInfo.
//
// We don't check for status because we expect it to fail in case there is no media in device.
//
// Should enter Zero Power state if:
//
// Drawer: 02/3A/01
// Slot: 02/3A/xx
//
if (((zpoddInfo->LoadingMechanism == LOADING_MECHANISM_TRAY) && (zpoddInfo->Load == 0) && // Drawer
(zpoddInfo->SenseKey == SCSI_SENSE_NOT_READY) &&
(zpoddInfo->AdditionalSenseCode == SCSI_ADSENSE_NO_MEDIA_IN_DEVICE) &&
(zpoddInfo->AdditionalSenseCodeQualifier == 0x01)) ||
((zpoddInfo->LoadingMechanism == LOADING_MECHANISM_CADDY) && (zpoddInfo->Load == 0) && // Slot
(zpoddInfo->SenseKey == SCSI_SENSE_NOT_READY) &&
(zpoddInfo->AdditionalSenseCode == SCSI_ADSENSE_NO_MEDIA_IN_DEVICE)))
{
inHomePosition = TRUE;
}
}
return inHomePosition;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
DeviceMarkActive(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ BOOLEAN IsActive,
_In_ BOOLEAN SetIdleTimeout
)
/*++
Routine Description:
This routine will mark the device as active / idle.
Arguments:
DeviceExtension - the device context
IsActive - if the device should be marked as active
Return Value:
None.
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PZERO_POWER_ODD_INFO zpoddInfo = DeviceExtension->ZeroPowerODDInfo;
PAGED_CODE()
if (DeviceExtension->IsActive != IsActive)
{
if ((IsActive == FALSE) && (zpoddInfo != NULL))
{
// cache get configuration response.
// failing is not critical, so we don't want to check for status here.
if (zpoddInfo->GetConfigurationBuffer == NULL)
{
(VOID)DeviceGetConfigurationWithAlloc(DeviceExtension->Device,
&zpoddInfo->GetConfigurationBuffer,
&zpoddInfo->GetConfigurationBufferSize,
FeatureProfileList,
SCSI_GET_CONFIGURATION_REQUEST_TYPE_ALL);
}
}
if (SetIdleTimeout)
{
status = DeviceSendEnableIdlePowerIoctl(DeviceExtension,
FALSE,
FALSE,
IsActive ? DELAY_TIME_TO_ENTER_ZERO_POWER_IN_MS : DELAY_TIME_TO_ENTER_AOAC_IDLE_POWER_IN_MS);
}
if (NT_SUCCESS(status))
{
DeviceSendIoctlAsynchronously(DeviceExtension,
IsActive ? IOCTL_STORAGE_POWER_ACTIVE : IOCTL_STORAGE_POWER_IDLE,
DeviceExtension->LowerPdo);
}
DeviceExtension->IsActive = IsActive;
}
}
#pragma warning(pop) // un-sets any local warning changes