windows-nt/Source/XPSP1/NT/drivers/apm/ntapm/i386/ntapm.c
2020-09-26 16:20:57 +08:00

960 lines
24 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Module Name:
ntapm.c
Abstract:
OS source for ntapm.sys
Author:
Environment:
Kernel mode
Notes:
Revision History:
--*/
#include "ntddk.h"
#include "ntpoapi.h"
#include "string.h"
#include "ntcrib.h"
#include "ntapmdbg.h"
#include "apm.h"
#include "apmp.h"
#include "ntapmp.h"
#include <ntapm.h>
#include <poclass.h>
#include <ntapmlog.h>
//
// Global debug flag. There are 3 separate groupings, see ntapmdbg.h for
// break out.
//
ULONG NtApmDebugFlag = 0;
ULONG ApmWorks = 0;
WCHAR rgzApmActiveFlag[] =
L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\ApmActive";
WCHAR rgzApmFlag[] =
L"Active";
WCHAR rgzAcpiKey[] =
L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\ACPI";
WCHAR rgzAcpiStart[] =
L"Start";
//
// Define driver entry routine.
//
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath
);
NTSTATUS ApmDispatch(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
);
BOOLEAN
IsAcpiMachine(
VOID
);
ULONG DoApmPoll();
NTSTATUS DoApmInitMachine();
VOID (*BattChangeNotify)() = NULL;
#define POLL_INTERVAL (500) // 500 milliseconds == 1/2 second
#define APM_POLL_MULTIPLY (4) // only call ApmInProgress once every 4 Poll intervals
// which with current values is once every 2 seconds
#define APM_SPIN_LIMIT (6) // 6 spin passes, each with a call to ApmInProgress,
// at APM_POLL_MULTIPLY * POLL_INTERVAL time spacing.
// Current values (500, 4, 6) should yield APM bios
// waiting from 12s to 17s, depending on how large
// or small their value of 5s is.
volatile BOOLEAN OperationDone = FALSE; // used to make some sync between SuspendPollThread
// and ApmSleep and ApmOff work.
//
// Our own driver object. This is rude, but this is a very weird
// and special driver. We will pass this to our APM library to
// allow error logging to work. Note that we don't actually have
// an active IRP around when the error occurs.
//
PDRIVER_OBJECT NtApmDriverObject = NULL;
//
// Define the local routines used by this driver module.
//
VOID SuspendPollThread(PVOID Dummy);
VOID ApmSleep(VOID);
VOID ApmOff(VOID);
KTIMER PollTimer;
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
/*++
Routine Description:
This is the initialization routine for the laptop driver.
Arguments:
DriverObject - Pointer to driver object created by the system.
Return Value:
The function value is the final status from the initialization operation.
--*/
{
NTSTATUS status;
ULONG MajorVersion;
ULONG MinorVersion;
//
// refuse to load on machines with more than 1 cpu
//
if (KeNumberProcessors != 1) {
DrDebug(SYS_INFO, ("ntapm: more than 1 cpu, ntapm will exit\n"));
return STATUS_UNSUCCESSFUL;
}
//
// refuse to load if version number is not 5.1 or 5.0
// NOTE WELL: This is a manual version check, do NOT put a system
// constant in here. This driver depends on hacks in
// the kernel that will someday go away...
//
PsGetVersion(&MajorVersion, &MinorVersion, NULL, NULL);
if ( !
(
((MajorVersion == 5) && (MinorVersion == 0)) ||
((MajorVersion == 5) && (MinorVersion == 1))
)
)
{
DrDebug(SYS_INFO, ("ntapm: system version number != 5.1, exit\n"));
return STATUS_UNSUCCESSFUL;
}
//
// refuse to load if ACPI.SYS should be running
//
if (IsAcpiMachine()) {
DrDebug(SYS_INFO, ("ntapm: this is an acpi machine apm exiting\n"));
return STATUS_UNSUCCESSFUL;
}
//
// init the driver object
//
DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = ApmDispatch;
DriverObject->MajorFunction[IRP_MJ_CREATE] = ApmDispatch;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = ApmDispatch;
DriverObject->MajorFunction[IRP_MJ_PNP] = NtApm_PnP;
DriverObject->MajorFunction[IRP_MJ_POWER] = NtApm_Power;
DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = ApmDispatch;
DriverObject->DriverExtension->AddDevice = NtApm_AddDevice;
NtApmDriverObject = DriverObject;
return STATUS_SUCCESS;
}
BOOLEAN ApmAddHelperDone = FALSE;
NTSTATUS
ApmAddHelper(
)
/*++
Routine Description:
We do these things in the Add routine so that we cannot fail
and leave the Kernel/Hal/Apm chain in a corrupt state.
This includes linking up with the Hal.
Turns out the caller doesn't know if this work has already
been done, so disallow doing it more than once here.
Arguments:
Return Value:
The function value is the final status from the initialization operation.
--*/
{
UCHAR HalTable[HAL_APM_TABLE_SIZE];
PPM_DISPATCH_TABLE InTable;
HANDLE ThreadHandle;
HANDLE hKey;
NTSTATUS status;
ULONG flagvalue;
OBJECT_ATTRIBUTES objectAttributes;
UNICODE_STRING unicodeString;
ULONG battresult;
if (ApmAddHelperDone) {
return STATUS_SUCCESS;
}
ApmAddHelperDone = TRUE;
//
// call ApmInitMachine so that Bios, etc, can be engaged
// no suspends can happen before this call.
//
if (! NT_SUCCESS(DoApmInitMachine()) ) {
DrDebug(SYS_INFO, ("ntapm: DoApmInitMachine failed\n"));
return STATUS_UNSUCCESSFUL;
}
//
// call the hal
//
InTable = (PPM_DISPATCH_TABLE)HalTable;
InTable->Signature = HAL_APM_SIGNATURE;
InTable->Version = HAL_APM_VERSION;
//
// In theory, APM should be fired up by now.
// So call off into it to see if there is any sign
// of a battery on the box.
//
// If we do not see a battery, then do NOT enable
// S3, but do allow S4. This keeps people's machines
// from puking on failed S3 calls (almost always desktops)
// while allowing auto-shutdown at the end of hibernate to work.
//
battresult = DoApmReportBatteryStatus();
if (battresult & NTAPM_NO_SYS_BATT) {
//
// it appears that the machine does not have
// a battery, or least APM doesn't report one.
//
InTable->Function[HAL_APM_SLEEP_VECTOR] = NULL;
} else {
InTable->Function[HAL_APM_SLEEP_VECTOR] = &ApmSleep;
}
InTable->Function[HAL_APM_OFF_VECTOR] = &ApmOff;
status = HalInitPowerManagement(InTable, NULL);
if (! NT_SUCCESS(status)) {
DrDebug(SYS_INFO, ("ntapm: HalInitPowerManagement failed\n"));
return STATUS_UNSUCCESSFUL;
}
//
// From this point on, INIT MUST succeed, otherwise we'll leave
// the Hal with hanging pointers. So long as ApmSleep and ApmOff
// are present in memory, things will be OK (though suspend may
// not work, the box won't bugcheck.)
//
// init periodic timer, init suspend done event, init suspend dpc,
// create and start poll thread
//
status = PsCreateSystemThread(&ThreadHandle,
(ACCESS_MASK) 0L,
NULL,
NULL,
NULL,
&SuspendPollThread,
NULL
);
//
// the create didn't work, turns out that some apm functions
// will still work, so just keep going.
//
if (! NT_SUCCESS(status)) {
DrDebug(SYS_INFO, ("ntapm: could not create thread, but continunuing\n"));
// return STATUS_INSUFFICIENT_RESOURCES;
}
KeInitializeTimerEx(&PollTimer, SynchronizationTimer);
//
// set a flag in the registry so that code with special hacks
// based on apm being active can tell we're here and at least
// nominally running
//
RtlInitUnicodeString(&unicodeString, rgzApmActiveFlag);
InitializeObjectAttributes(
&objectAttributes,
&unicodeString,
OBJ_CASE_INSENSITIVE,
NULL, // handle
NULL
);
status = ZwCreateKey(
&hKey,
KEY_WRITE,
&objectAttributes,
0,
NULL,
REG_OPTION_VOLATILE,
NULL
);
RtlInitUnicodeString(&unicodeString, rgzApmFlag);
if (NT_SUCCESS(status)) {
flagvalue = 1;
ZwSetValueKey(
hKey,
&unicodeString,
0,
REG_DWORD,
&flagvalue,
sizeof(ULONG)
);
ZwClose(hKey);
}
return STATUS_SUCCESS;
}
NTSTATUS
ApmDispatch(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
/*++
Routine Description:
When an application calls the Laptop driver, it comes here.
This is NOT the dispatch point for PNP or Power calls.
Arguments:
DeviceObject - Pointer to the device object for this driver.
Irp - Pointer to the request packet representing the I/O request.
Return Value:
The function value is the status of the operation.
--*/
{
NTSTATUS status;
PIO_STACK_LOCATION irpSp;
PVOID outbuffer;
ULONG outlength;
PFILE_OBJECT fileObject;
ULONG percent;
BOOLEAN acon;
PNTAPM_LINK pparms;
PULONG p;
ULONG t;
UNREFERENCED_PARAMETER( DeviceObject );
//
// Get a pointer to the current stack location in the IRP. This is where
// the function codes and parameters are stored.
//
irpSp = IoGetCurrentIrpStackLocation( Irp );
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
Irp->IoStatus.Information = 0;
switch (irpSp->MajorFunction) {
//
// device control
//
case IRP_MJ_INTERNAL_DEVICE_CONTROL:
//
// Only one valid command, which is to set (or null out) the
// the link call pointers.
//
if (irpSp->MinorFunction == 0) {
pparms = (PNTAPM_LINK) &(irpSp->Parameters.Others);
if ((pparms->Signature == NTAPM_LINK_SIGNATURE) &&
(pparms->Version == NTAPM_LINK_VERSION))
{
t = (ULONG) (&DoApmReportBatteryStatus);
p = (PULONG)(pparms->BattLevelPtr);
*p = t;
BattChangeNotify = (PVOID)(pparms->ChangeNotify);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
}
}
break;
default:
//
// for all other operations, including create/open and close,
// simply report failure, no matter what the operation is
//
break;
}
//
// Copy the final status into the return status, complete the request and
// get out of here.
//
status = Irp->IoStatus.Status;
IoCompleteRequest( Irp, 0 );
return status;
}
VOID
SuspendPollThread(
PVOID Dummy
)
/*++
Routine Description:
This routine is the laptop suspend polling thread.
Arguments:
Dummy Ignored parameter
Return Value:
None
--*/
{
LARGE_INTEGER DueTime;
ULONG LocalSuspendFlag;
ULONG LocalClockFlag;
KIRQL OldIrql;
POWER_ACTION SystemAction;
SYSTEM_POWER_STATE MinSystemState;
ULONG Flags;
ULONG ApmEvent;
ULONG count, count2;
LONG i, j;
ULONG BatteryResult, PriorBatteryResult;
ULONG BattPresentMask, PriorBattPresentMask;
BOOLEAN DoANotify;
PriorBatteryResult = BatteryResult = 0;
//
// Start the poll timer going, we'll wait for 1 second,
// then POLL_INTERVAL milliseconds after that
//
DueTime.HighPart = 0;
DueTime.LowPart = 10*1000*1000; // 10 million * 100nano = 1 second
KeSetTimerEx(&PollTimer, DueTime, POLL_INTERVAL, NULL);
while (1) {
KeWaitForSingleObject(&PollTimer, Executive, KernelMode, TRUE, NULL);
//
// Call APM to poll for us
//
Flags = 0; // clear all flags
switch (DoApmPoll()) {
case APM_DO_CRITICAL_SUSPEND:
//
// Here we force the Flags to have the
// CRITICAL flag set, other than that it's the same thing
// as for normal suspend and standby
//
Flags = POWER_ACTION_CRITICAL;
/* FALL FALL FALL */
case APM_DO_SUSPEND:
case APM_DO_STANDBY:
//
// For either Suspend or Standby, call the
// the system and tell it to suspend us
//
DrDebug(SYS_INFO, ("ntapm: about to call OS to suspend\n"));
SystemAction = PowerActionSleep;
MinSystemState = PowerSystemSleeping3;
OperationDone = FALSE;
ZwInitiatePowerAction(
SystemAction,
MinSystemState,
Flags,
TRUE // async
);
//
// If we just call ZwInitiatePowerAction, most machines
// will work, but a few get impatient and try to suspend
// out from under us before the OS comes back round and
// does the suspend. So, we need to call ApmInProgress
// every so often to make these bioses wait.
//
// BUT, if the system is truly wedged, or the suspend fails,
// we don't want to spin calling ApmInProgress forever, so
// limit the number of times we do that. And once the
// operation is about to happen, stop.
//
// Since we're not polling while we're waiting for something
// to happen, we'll use the poll timer...
//
if (OperationDone) goto Done;
ApmInProgress();
for (count = 0; count < APM_SPIN_LIMIT; count++) {
for (count2 = 0; count2 < APM_POLL_MULTIPLY; count2++) {
KeWaitForSingleObject(&PollTimer, Executive, KernelMode, TRUE, NULL);
}
if (OperationDone) goto Done;
ApmInProgress();
}
DrDebug(SYS_INFO, ("ntapm: back from suspend\n"));
Done:
break;
case APM_DO_NOTIFY:
//
// Call out to battery driver with Notify op here
//
if (BattChangeNotify) {
//DrDebug(SYS_INFO, ("ntapm: about to make notify call\n"));
BattChangeNotify();
//DrDebug(SYS_INFO, ("ntapm: back from notify call\n"));
PriorBatteryResult = DoApmReportBatteryStatus();
}
break;
case APM_DO_FIXCLOCK:
case APM_DO_NOTHING:
default:
//
// fixing the clock is too scary with other power
// code doing it, so we don't do it here.
//
// nothing is nothing
//
// if we don't understand, do nothing
// (remember, bios will force op under us if it's critical)
//
if (BattChangeNotify) {
//
// we hereby redefine "nothing" to be "check on the
// status of the bleeding battery" since not all bioses
// tell us what is going on in a timely fashion
//
DoANotify = FALSE;
BatteryResult = DoApmReportBatteryStatus();
if ((BatteryResult & NTAPM_ACON) !=
(PriorBatteryResult & NTAPM_ACON))
{
DoANotify = TRUE;
}
if ((BatteryResult & NTAPM_BATTERY_STATE) !=
(PriorBatteryResult & NTAPM_BATTERY_STATE))
{
DoANotify = TRUE;
}
i = BatteryResult & NTAPM_POWER_PERCENT;
j = PriorBatteryResult & NTAPM_POWER_PERCENT;
if (( (i - j) > 25 ) ||
( (j - i) > 25 ))
{
DoANotify = TRUE;
}
PriorBattPresentMask = PriorBatteryResult & (NTAPM_NO_BATT | NTAPM_NO_SYS_BATT);
BattPresentMask = BatteryResult & (NTAPM_NO_BATT | NTAPM_NO_SYS_BATT);
if (BattPresentMask != PriorBattPresentMask) {
//
// battery either went or reappeared
//
DoANotify = TRUE;
}
PriorBatteryResult = BatteryResult;
if (DoANotify) {
ASSERT(BattChangeNotify);
BattChangeNotify();
}
}
break;
} // switch
} // while
}
VOID
ApmSleep(
VOID
)
/*++
Routine Description:
When the OS calls the Hal's S3 vector, the hal calls us here.
We call APM to put the box to sleep
--*/
{
OperationDone = TRUE;
if (ApmWorks) {
DrDebug(SYS_L2,("ntapm: apmsleep: calling apm to sleep\n"));
ApmSuspendSystem();
DrDebug(SYS_L2,("ntapm: apmsleep: back from apm call\n"));
} else { // ApmWorks == FALSE
DrDebug(SYS_INFO, ("ntapm: ApmSleep: no APM attached, Exit\n"));
}
}
VOID
ApmOff(
VOID
)
/*++
Routine Description:
When the OS calls the Hal's S4 or S5 routines, the hal calls us here.
We turn the machine off.
--*/
{
OperationDone = TRUE;
if (ApmWorks) {
DrDebug(SYS_L2,("ntapm: ApmOff: calling APM\n"));
ApmTurnOffSystem();
DrDebug(SYS_INFO,("ntapm: ApmOff: we are back from Off, uh oh!\n"));
}
}
NTSTATUS
DoApmInitMachine(
)
/*++
Routine Description:
This routine makes the BIOS ready to interact with laptop.sys.
This code works with APM.
Return Value:
None
--*/
{
NTSTATUS Status;
ULONG Ebx, Ecx;
DrDebug(SYS_INIT,("ApmInitMachine: enter\n"));
Status = ApmInitializeConnection ();
if (NT_SUCCESS(Status)) {
DrDebug(SYS_INIT,("ApmInitMachine: Connection established!\n"));
//
// Note that ntdetect (2nd version) will have set apm bios
// to min of (machine version) and (1.2)
// (so a 1.1 bios will be set to 1.1, a 1.2 to 1.2, a 1.3 to 1.2
//
ApmWorks = 1;
} else {
DrDebug(SYS_INIT,("ApmInitMachine: No connection made!\n"));
ApmWorks = 0;
}
DrDebug(SYS_INIT,("ApmInitMachine: exit\n"));
return Status;
}
ULONG
DoApmPoll(
)
/*++
Routine Description:
This routine is called in the ntapm.sys polling loop to poll
for APM events. It returns APM_DO_NOTHING unless there is
actually something meaningful for us to do. (That is, things
we don't want and/or don't understand are filtered down to
APM_DO_NOTHING)
Return Value:
APM event code.
--*/
{
DrDebug(SYS_L2,("ApmPoll: enter\n"));
if (ApmWorks) {
return ApmCheckForEvent();
} else { // ApmWorks == FALSE
DrDebug(SYS_L2,("ApmPoll: no APM attachment, exit\n"));
return APM_DO_NOTHING;
}
}
ULONG
DoApmReportBatteryStatus()
/*++
Routine Description:
This routine queries the BIOS/HW for the state of the power connection
and the current battery level.
Arguments:
Return Value:
ULONG, fields defined by NTAPM_POWER_STATE and NTAPM_POWER_PERCENT
--*/
{
ULONG percent = 100;
ULONG ac = 1;
ULONG Status = 0;
ULONG Ebx = 0;
ULONG Ecx = 0;
ULONG flags = 0;
ULONG result = 0;
DrDebug(SYS_L2,("ntapm: DoApmReportBatteryStatus: enter\n"));
if (ApmWorks) {
//
// Call APM BIOS and get power status
//
Ebx = 1;
Ecx = 0;
Status = ApmFunction (APM_GET_POWER_STATUS, &Ebx, &Ecx);
if (!NT_SUCCESS(Status)) {
//
// If we cannot read the power, jam in 50% and power off!
//
DrDebug(SYS_INFO,("ntapm: DoApmReportBatteryStatus: Can't get power!\n"));
percent = 50;
ac = 0;
} else {
//
// Get battery/AC state -- anything but full 'on-line' means on
// battery
//
ac = (Ebx & APM_LINEMASK) >> APM_LINEMASK_SHIFT;
if (ac != APM_GET_LINE_ONLINE) {
ac = 0;
}
percent = Ecx & APM_PERCENT_MASK;
}
} else {
DrDebug(SYS_INFO,("ntapm: DoApmReportBatteryStatus: no APM attachment\n"));
DrDebug(SYS_INFO,("ntapm: Return AC OFF 50% Life\n"));
percent = 50;
ac = FALSE;
}
flags = 0;
result = 0;
if (Ecx & APM_NO_BATT) {
result |= NTAPM_NO_BATT;
}
if (Ecx & APM_NO_SYS_BATT) {
result |= NTAPM_NO_SYS_BATT;
}
if ((percent == 255) || (Ecx & APM_NO_BATT) || (Ecx & APM_NO_SYS_BATT)) {
percent = 0;
} else if (percent > 100) {
percent = 100;
}
if ((Ecx & APM_BATT_CHARGING) && (percent < 100)) {
flags |= BATTERY_CHARGING;
} else {
flags |= BATTERY_DISCHARGING;
}
if (Ecx & APM_BATT_CRITICAL) {
flags |= BATTERY_CRITICAL;
percent = 1;
}
if (ac) {
result |= NTAPM_ACON;
flags |= BATTERY_POWER_ON_LINE;
}
result |= (flags << NTAPM_BATTERY_STATE_SHIFT);
result |= percent;
DrDebug(SYS_L2,("ntapm: BatteryLevel: %08lx Percent: %d flags: %1x ac: %1x\n",
result, percent, flags, ac));
return result;
}
BOOLEAN
IsAcpiMachine(
VOID
)
/*++
Routine Description:
IsAcpiMachine reports whether the OS thinks this is an ACPI
machine or not.
Return Value:
FALSE - this is NOT an acpi machine
TRUE - this IS an acpi machine
--*/
{
UNICODE_STRING unicodeString;
OBJECT_ATTRIBUTES objectAttributes;
HANDLE hKey;
NTSTATUS status;
PKEY_VALUE_PARTIAL_INFORMATION pvpi;
UCHAR buffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION)+sizeof(ULONG)+1];
ULONG junk;
PULONG pdw;
ULONG start;
RtlInitUnicodeString(&unicodeString, rgzAcpiKey);
InitializeObjectAttributes(
&objectAttributes,
&unicodeString,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
status = ZwOpenKey(&hKey, KEY_READ, &objectAttributes);
if (!NT_SUCCESS(status)) {
return FALSE;
}
RtlInitUnicodeString(&unicodeString, rgzAcpiStart);
pvpi = (PKEY_VALUE_PARTIAL_INFORMATION)buffer;
status = ZwQueryValueKey(
hKey,
&unicodeString,
KeyValuePartialInformation,
pvpi,
sizeof(buffer),
&junk
);
if ( (NT_SUCCESS(status)) &&
(pvpi->Type == REG_DWORD) &&
(pvpi->DataLength == sizeof(ULONG)) )
{
pdw = (PULONG)&(pvpi->Data[0]);
if (*pdw == 0) {
ZwClose(hKey);
return TRUE;
}
}
ZwClose(hKey);
return FALSE;
}