900 lines
20 KiB
C
900 lines
20 KiB
C
/*++
|
||
|
||
Module Name:
|
||
|
||
apm.c
|
||
|
||
Abstract:
|
||
|
||
A collection of code that allows NT calls into APM.
|
||
The code in this routine depends on data being set up in the registry
|
||
|
||
Author:
|
||
|
||
Environment:
|
||
|
||
Kernel mode only.
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
|
||
#include "ntosp.h"
|
||
#include "zwapi.h"
|
||
#include "apmp.h"
|
||
#include "apm.h"
|
||
#include "apmcrib.h"
|
||
#include "ntapmdbg.h"
|
||
#include "ntapmlog.h"
|
||
#include "ntapmp.h"
|
||
|
||
|
||
#define MAX_SEL 30 // attempts before giving up
|
||
|
||
ULONG ApmCallActive = 0;
|
||
ULONG ApmCallEax = 0;
|
||
ULONG ApmCallEbx = 0;
|
||
ULONG ApmCallEcx = 0;
|
||
|
||
WCHAR rgzMultiFunctionAdapter[] =
|
||
L"\\Registry\\Machine\\Hardware\\Description\\System\\MultifunctionAdapter";
|
||
WCHAR rgzConfigurationData[] = L"Configuration Data";
|
||
WCHAR rgzIdentifier[] = L"Identifier";
|
||
WCHAR rgzPCIIndetifier[] = L"PCI";
|
||
|
||
WCHAR rgzApmConnect[]= L"\\Registry\\Machine\\Hardware\\ApmConnect";
|
||
WCHAR rgzApmConnectValue[] = L"ApmConnectValue";
|
||
|
||
APM_CONNECT Apm;
|
||
|
||
//
|
||
// First time we get any non-recoverable error back
|
||
// from APM, record what sort of call hit it and what
|
||
// the error code was here
|
||
//
|
||
ULONG ApmLogErrorFunction = -1L;
|
||
ULONG ApmLogErrorCode = 0L;
|
||
|
||
ULONG ApmErrorLogSequence = 0xf3;
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGE,ApmInitializeConnection)
|
||
#endif
|
||
|
||
//
|
||
// Internal prototypes
|
||
//
|
||
|
||
BOOLEAN
|
||
ApmpBuildGdtEntry (
|
||
IN ULONG Index,
|
||
PKGDTENTRY GdtEntry,
|
||
IN ULONG SegmentBase
|
||
);
|
||
|
||
|
||
VOID
|
||
NtApmLogError(
|
||
NTSTATUS ErrorCode,
|
||
UCHAR ErrorByte
|
||
);
|
||
|
||
|
||
NTSTATUS
|
||
ApmInitializeConnection (
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Initialize data needed to call APM bios functions -- look in the
|
||
registry to find out if this machine has had its APM capability
|
||
detected.
|
||
|
||
NOTE: If you change the recognition code, change the
|
||
code to IsApmPresent as well!
|
||
|
||
Arguments:
|
||
|
||
None
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS if we were able to connect to the APM BIOS.
|
||
|
||
--*/
|
||
{
|
||
PCM_PARTIAL_RESOURCE_DESCRIPTOR PDesc;
|
||
PCM_FULL_RESOURCE_DESCRIPTOR Desc;
|
||
PKEY_VALUE_FULL_INFORMATION ValueInfo;
|
||
PAPM_REGISTRY_INFO ApmEntry;
|
||
OBJECT_ATTRIBUTES objectAttributes;
|
||
UNICODE_STRING unicodeString, ConfigName, IdentName;
|
||
KGDTENTRY GdtEntry;
|
||
NTSTATUS status;
|
||
BOOLEAN Error;
|
||
HANDLE hMFunc, hBus, hApmConnect;
|
||
USHORT Sel[MAX_SEL], TSel;
|
||
UCHAR buffer [sizeof(APM_REGISTRY_INFO) + 99];
|
||
WCHAR wstr[8];
|
||
ULONG i, j, Count, junk;
|
||
PWSTR p;
|
||
USHORT volatile Offset;
|
||
|
||
//
|
||
// Look in the registery for the "APM bus" data
|
||
//
|
||
|
||
RtlInitUnicodeString(&unicodeString, rgzMultiFunctionAdapter);
|
||
InitializeObjectAttributes(
|
||
&objectAttributes,
|
||
&unicodeString,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL, // handle
|
||
NULL
|
||
);
|
||
|
||
|
||
status = ZwOpenKey(&hMFunc, KEY_READ, &objectAttributes);
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
unicodeString.Buffer = wstr;
|
||
unicodeString.MaximumLength = sizeof (wstr);
|
||
|
||
RtlInitUnicodeString(&ConfigName, rgzConfigurationData);
|
||
RtlInitUnicodeString(&IdentName, rgzIdentifier);
|
||
|
||
ValueInfo = (PKEY_VALUE_FULL_INFORMATION) buffer;
|
||
|
||
for (i=0; TRUE; i++) {
|
||
RtlIntegerToUnicodeString(i, 10, &unicodeString);
|
||
InitializeObjectAttributes(
|
||
&objectAttributes,
|
||
&unicodeString,
|
||
OBJ_CASE_INSENSITIVE,
|
||
hMFunc,
|
||
NULL
|
||
);
|
||
|
||
status = ZwOpenKey(&hBus, KEY_READ, &objectAttributes);
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
//
|
||
// Out of Multifunction adapter entries...
|
||
//
|
||
|
||
ZwClose (hMFunc);
|
||
return STATUS_UNSUCCESSFUL;
|
||
}
|
||
|
||
//
|
||
// Check the Indentifier to see if this is a APM entry
|
||
//
|
||
|
||
status = ZwQueryValueKey (
|
||
hBus,
|
||
&IdentName,
|
||
KeyValueFullInformation,
|
||
ValueInfo,
|
||
sizeof (buffer),
|
||
&junk
|
||
);
|
||
|
||
if (!NT_SUCCESS (status)) {
|
||
ZwClose (hBus);
|
||
continue;
|
||
}
|
||
|
||
p = (PWSTR) ((PUCHAR) ValueInfo + ValueInfo->DataOffset);
|
||
if (p[0] != L'A' || p[1] != L'P' || p[2] != L'M' || p[3] != 0) {
|
||
ZwClose (hBus);
|
||
continue;
|
||
}
|
||
|
||
status = ZwQueryValueKey(
|
||
hBus,
|
||
&ConfigName,
|
||
KeyValueFullInformation,
|
||
ValueInfo,
|
||
sizeof (buffer),
|
||
&junk
|
||
);
|
||
|
||
ZwClose (hBus);
|
||
if (!NT_SUCCESS(status)) {
|
||
continue ;
|
||
}
|
||
|
||
Desc = (PCM_FULL_RESOURCE_DESCRIPTOR) ((PUCHAR)
|
||
ValueInfo + ValueInfo->DataOffset);
|
||
PDesc = (PCM_PARTIAL_RESOURCE_DESCRIPTOR) ((PUCHAR)
|
||
Desc->PartialResourceList.PartialDescriptors);
|
||
|
||
if (PDesc->Type == CmResourceTypeDeviceSpecific) {
|
||
// got it..
|
||
ApmEntry = (PAPM_REGISTRY_INFO) (PDesc+1);
|
||
break;
|
||
}
|
||
}
|
||
|
||
//DbgPrint("ApmEntry: %08lx\n", ApmEntry);
|
||
//DbgPrint("Signature: %c%c%c\n", ApmEntry->Signature[0], ApmEntry->Signature[1], ApmEntry->Signature[2]);
|
||
if ( (ApmEntry->Signature[0] != 'A') ||
|
||
(ApmEntry->Signature[1] != 'P') ||
|
||
(ApmEntry->Signature[2] != 'M') )
|
||
{
|
||
return STATUS_UNSUCCESSFUL;
|
||
}
|
||
|
||
//DbgPrint("ApmEntry->Valid: %0d\n", ApmEntry->Valid);
|
||
if (ApmEntry->Valid != 1) {
|
||
return STATUS_UNSUCCESSFUL;
|
||
}
|
||
|
||
//
|
||
// Apm found - initialize the connection
|
||
//
|
||
|
||
KeInitializeSpinLock(&Apm.CallLock);
|
||
|
||
//
|
||
// Allocate a bunch of selectors
|
||
//
|
||
|
||
for (Count=0; Count < MAX_SEL; Count++) {
|
||
status = KeI386AllocateGdtSelectors (Sel+Count, 1);
|
||
if (!NT_SUCCESS(status)) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Sort the selctors via bubble sort
|
||
//
|
||
|
||
for (i=0; i < Count; i++) {
|
||
for (j = i+1; j < Count; j++) {
|
||
if (Sel[j] < Sel[i]) {
|
||
TSel = Sel[i];
|
||
Sel[i] = Sel[j];
|
||
Sel[j] = TSel;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Now look for 3 consecutive values
|
||
//
|
||
|
||
for (i=0; i < Count - 3; i++) {
|
||
if (Sel[i]+8 == Sel[i+1] && Sel[i]+16 == Sel[i+2]) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (i >= Count - 3) {
|
||
DrDebug(APM_INFO,("APM: Could not allocate consecutive selectors\n"));
|
||
return STATUS_UNSUCCESSFUL;
|
||
}
|
||
|
||
//
|
||
// Save the results
|
||
//
|
||
|
||
Apm.Selector[0] = Sel[i+0];
|
||
Apm.Selector[1] = Sel[i+1];
|
||
Apm.Selector[2] = Sel[i+2];
|
||
Sel[i+0] = 0;
|
||
Sel[i+1] = 0;
|
||
Sel[i+2] = 0;
|
||
|
||
//
|
||
// Free unused selectors
|
||
//
|
||
|
||
for (i=0; i < Count; i++) {
|
||
if (Sel[i]) {
|
||
KeI386ReleaseGdtSelectors (Sel+i, 1);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Initialize the selectors to use the APM bios
|
||
//
|
||
|
||
Error = FALSE;
|
||
|
||
//
|
||
// initialize 16 bit code selector
|
||
//
|
||
|
||
GdtEntry.LimitLow = 0xFFFF;
|
||
GdtEntry.HighWord.Bytes.Flags1 = 0;
|
||
GdtEntry.HighWord.Bytes.Flags2 = 0;
|
||
GdtEntry.HighWord.Bits.Pres = 1;
|
||
GdtEntry.HighWord.Bits.Dpl = DPL_SYSTEM;
|
||
GdtEntry.HighWord.Bits.Granularity = GRAN_BYTE;
|
||
GdtEntry.HighWord.Bits.Type = 31;
|
||
GdtEntry.HighWord.Bits.Default_Big = 0;
|
||
|
||
Error |= ApmpBuildGdtEntry (0, &GdtEntry, ApmEntry->Code16BitSegment);
|
||
|
||
//
|
||
// initialize 16 bit data selector
|
||
//
|
||
|
||
GdtEntry.LimitLow = 0xFFFF;
|
||
GdtEntry.HighWord.Bytes.Flags1 = 0;
|
||
GdtEntry.HighWord.Bytes.Flags2 = 0;
|
||
GdtEntry.HighWord.Bits.Pres = 1;
|
||
GdtEntry.HighWord.Bits.Dpl = DPL_SYSTEM;
|
||
GdtEntry.HighWord.Bits.Granularity = GRAN_BYTE;
|
||
GdtEntry.HighWord.Bits.Type = 19;
|
||
GdtEntry.HighWord.Bits.Default_Big = 1;
|
||
|
||
Error |= ApmpBuildGdtEntry (1, &GdtEntry, ApmEntry->Data16BitSegment);
|
||
|
||
//
|
||
// If we leave it like this, the compiler generates incorrect code!!!
|
||
// Apm.Code16BitOffset = ApmEntry->Code16BitOffset;
|
||
// So do this instead.
|
||
//
|
||
Offset = ApmEntry->Code16BitOffset;
|
||
Apm.Code16BitOffset = (ULONG) Offset;
|
||
|
||
//DbgPrint("Apm@%08lx ApmEntry@%08lx\n", &Apm, ApmEntry);
|
||
//DbgBreakPoint();
|
||
|
||
|
||
#if 0
|
||
//
|
||
// to make the poweroff path in the Hal about 20 times simpler,
|
||
// as well as make it work, pass our mappings on to the Hal, so
|
||
// it can use them.
|
||
//
|
||
RtlInitUnicodeString(&unicodeString, rgzApmConnect);
|
||
InitializeObjectAttributes(
|
||
&objectAttributes,
|
||
&unicodeString,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL,
|
||
NULL
|
||
);
|
||
status = ZwCreateKey(
|
||
&hApmConnect,
|
||
KEY_ALL_ACCESS,
|
||
&objectAttributes,
|
||
0,
|
||
NULL,
|
||
REG_OPTION_VOLATILE,
|
||
&junk
|
||
);
|
||
RtlInitUnicodeString(&unicodeString, rgzApmConnectValue);
|
||
if (NT_SUCCESS(status)) {
|
||
status = ZwSetValueKey(
|
||
hApmConnect,
|
||
&unicodeString,
|
||
0,
|
||
REG_BINARY,
|
||
&Apm,
|
||
sizeof(APM_CONNECT)
|
||
);
|
||
ZwClose(hApmConnect);
|
||
}
|
||
#endif
|
||
|
||
return Error ? STATUS_UNSUCCESSFUL : STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
ApmpBuildGdtEntry (
|
||
IN ULONG Index,
|
||
PKGDTENTRY GdtEntry,
|
||
IN ULONG SegmentBase
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Build the Gdt Entry
|
||
|
||
Arguments:
|
||
|
||
Index Index of entry
|
||
GdtEntry
|
||
SegmentBase
|
||
|
||
Return Value:
|
||
|
||
TRUE if we encountered any error, FALSE if successful
|
||
|
||
--*/
|
||
{
|
||
PHYSICAL_ADDRESS PhysAddr;
|
||
ULONG SegBase;
|
||
PVOID VirtualAddress;
|
||
ULONG AddressSpace;
|
||
BOOLEAN flag;
|
||
|
||
//
|
||
// Convert Segment to phyiscal address
|
||
//
|
||
|
||
PhysAddr.LowPart = SegmentBase << 4;
|
||
PhysAddr.HighPart = 0;
|
||
|
||
//
|
||
// Translate physical address from ISA bus 0
|
||
//
|
||
|
||
AddressSpace = 0;
|
||
flag = HalTranslateBusAddress (
|
||
Isa, 0,
|
||
PhysAddr,
|
||
&AddressSpace,
|
||
&PhysAddr
|
||
);
|
||
|
||
if (AddressSpace != 0 || !flag) {
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// Map into virtual address space
|
||
//
|
||
|
||
VirtualAddress = MmMapIoSpace (
|
||
PhysAddr,
|
||
0x10000, // 64k
|
||
TRUE
|
||
);
|
||
Apm.VirtualAddress[Index] = VirtualAddress;
|
||
|
||
//
|
||
// Map virtual address to selector:0 address
|
||
//
|
||
|
||
SegBase = (ULONG) VirtualAddress;
|
||
GdtEntry->BaseLow = (USHORT) (SegBase & 0xffff);
|
||
GdtEntry->HighWord.Bits.BaseMid = (UCHAR) (SegBase >> 16) & 0xff;
|
||
GdtEntry->HighWord.Bits.BaseHi = (UCHAR) (SegBase >> 24) & 0xff;
|
||
|
||
KeI386SetGdtSelector (Apm.Selector[Index], GdtEntry);
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
ApmFunction (
|
||
IN ULONG ApmFunctionCode,
|
||
IN OUT PULONG Ebx,
|
||
IN OUT PULONG Ecx
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Call APM BIOS with ApmFunctionCode and appropriate arguments
|
||
|
||
Arguments:
|
||
|
||
ApmFunctionCode Apm function code
|
||
Ebx Ebx param to APM BIOS
|
||
Ecx Ecx param to APM BIOS
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS with Ebx, Ebx
|
||
otherwise an NTSTATUS code
|
||
|
||
--*/
|
||
{
|
||
KIRQL OldIrql;
|
||
ULONG ApmStatus;
|
||
CONTEXT Regs;
|
||
|
||
|
||
if (!Apm.Selector[0]) {
|
||
|
||
//
|
||
// Attempting to call APM BIOS without a sucessfull connection
|
||
//
|
||
|
||
DrDebug(APM_INFO,("APM: ApmFunction - APM not initialized\n"));
|
||
DrDebug(APM_INFO,
|
||
("APM: ApmFunction failing function %x\n", ApmFunctionCode));
|
||
return STATUS_UNSUCCESSFUL;
|
||
}
|
||
|
||
//DbgPrint("APM: ApmFunction: %08lx Ebx: %08lx Ecx: %08lx\n", ApmFunctionCode, *Ebx, *Ecx);
|
||
|
||
|
||
//
|
||
// Serialize calls into the APM bios
|
||
//
|
||
KeAcquireSpinLock(&Apm.CallLock, &OldIrql);
|
||
ApmCallActive += 1;
|
||
|
||
//
|
||
// ASM interface to call the BIOS
|
||
//
|
||
|
||
//
|
||
// Fill in general registers for 16bit bios call.
|
||
// Note: only the following registers are passed. Specifically,
|
||
// SS and ESP are not passed and are generated by the system.
|
||
//
|
||
|
||
Regs.ContextFlags = CONTEXT_INTEGER | CONTEXT_SEGMENTS;
|
||
|
||
Regs.Eax = ApmFunctionCode;
|
||
Regs.Ebx = *Ebx;
|
||
Regs.Ecx = *Ecx;
|
||
Regs.Edx = 0;
|
||
Regs.Esi = 0;
|
||
Regs.Edi = 0;
|
||
Regs.SegGs = 0;
|
||
Regs.SegFs = 0;
|
||
Regs.SegEs = Apm.Selector[1];
|
||
Regs.SegDs = Apm.Selector[1];
|
||
Regs.SegCs = Apm.Selector[0];
|
||
Regs.Eip = Apm.Code16BitOffset;
|
||
Regs.EFlags = 0x200; // interrupts enabled
|
||
|
||
ApmCallEax = Regs.Eax;
|
||
ApmCallEbx = Regs.Ebx;
|
||
ApmCallEcx = Regs.Ecx;
|
||
|
||
//
|
||
// call the 16:16 bios function
|
||
//
|
||
|
||
KeI386Call16BitFunction (&Regs);
|
||
|
||
ApmCallActive -= 1;
|
||
|
||
//
|
||
// Release serialization
|
||
//
|
||
KeReleaseSpinLock(&Apm.CallLock, OldIrql);
|
||
|
||
//
|
||
// Get the results
|
||
//
|
||
|
||
ApmStatus = 0;
|
||
if (Regs.EFlags & 0x1) { // check carry flag
|
||
ApmStatus = (Regs.Eax >> 8) & 0xff;
|
||
}
|
||
|
||
*Ebx = Regs.Ebx;
|
||
*Ecx = Regs.Ecx;
|
||
|
||
//
|
||
// save for debug use
|
||
//
|
||
if (ApmStatus) {
|
||
if (ApmLogErrorCode != 0) {
|
||
ApmLogErrorFunction = ApmFunctionCode;
|
||
ApmLogErrorCode = ApmStatus;
|
||
}
|
||
}
|
||
|
||
//
|
||
// log specific errors of value to the user
|
||
//
|
||
if (ApmFunctionCode == APM_SET_POWER_STATE) {
|
||
if (ApmStatus != 0)
|
||
{
|
||
NtApmLogError(NTAPM_SET_POWER_FAILURE, (UCHAR)ApmStatus);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
DrDebug(APM_INFO,("APM: ApmFunction result is %x\n", ApmStatus));
|
||
return ApmStatus;
|
||
}
|
||
|
||
|
||
WCHAR ApmConvArray[] = {'0', '1','2','3','4','5','6','7','8','9','A','B','C','D','E','F',0};
|
||
VOID
|
||
NtApmLogError(
|
||
NTSTATUS ErrorCode,
|
||
UCHAR ErrorByte
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Report the incoming error to the event log.
|
||
|
||
Arguments:
|
||
|
||
ErrorCode - the ntstatus type value which will match the message template
|
||
and get reported to the user.
|
||
|
||
ErrorByte - the 1 byte value returned by APM bios
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
--*/
|
||
{
|
||
PIO_ERROR_LOG_PACKET errorLogPacket;
|
||
PUCHAR p;
|
||
PWCHAR pw;
|
||
|
||
errorLogPacket = IoAllocateErrorLogEntry(
|
||
NtApmDriverObject,
|
||
(UCHAR)(sizeof(IO_ERROR_LOG_PACKET)+8)
|
||
);
|
||
|
||
if (errorLogPacket != NULL) {
|
||
errorLogPacket->ErrorCode = ErrorCode;
|
||
errorLogPacket->SequenceNumber = ApmErrorLogSequence++;
|
||
errorLogPacket->FinalStatus = STATUS_UNSUCCESSFUL;
|
||
errorLogPacket->UniqueErrorValue = 0;
|
||
errorLogPacket->NumberOfStrings = 1;
|
||
errorLogPacket->RetryCount = 0;
|
||
errorLogPacket->MajorFunctionCode = 0;
|
||
errorLogPacket->DeviceOffset.HighPart = 0;
|
||
errorLogPacket->DeviceOffset.LowPart = 0;
|
||
errorLogPacket->DumpDataSize = 0;
|
||
|
||
//
|
||
// why our own conversion code? because we can't get the fine
|
||
// RTL routines to put the data in the right sized output buffer
|
||
//
|
||
p = (PUCHAR) &(errorLogPacket->DumpData[0]);
|
||
pw = (PWCHAR)p;
|
||
|
||
pw[0] = ApmConvArray[(ULONG)((ErrorByte & 0xf0)>>4)];
|
||
pw[1] = ApmConvArray[(ULONG)(ErrorByte & 0xf)];
|
||
pw[2] = L'\0';
|
||
|
||
errorLogPacket->StringOffset =
|
||
((PUCHAR)(&(errorLogPacket->DumpData[0]))) - ((PUCHAR)errorLogPacket);
|
||
IoWriteErrorLogEntry(errorLogPacket);
|
||
}
|
||
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
ApmSuspendSystem (
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Suspend the system
|
||
|
||
Arguments:
|
||
|
||
none
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS if the computer was suspended & then resumed
|
||
|
||
--*/
|
||
{
|
||
ULONG Ebx, Ecx;
|
||
NTSTATUS Status;
|
||
|
||
//
|
||
// Use ApmFunction to suspend machine
|
||
//
|
||
|
||
DrDebug(APM_L2,("APM: ApmSuspendSystem: enter\n"));
|
||
Ebx = APM_DEVICE_ALL;
|
||
Ecx = APM_SET_SUSPEND;
|
||
Status = ApmFunction (APM_SET_POWER_STATE, &Ebx, &Ecx);
|
||
DrDebug(APM_L2,("APM: ApmSuspendSystem: exit\n"));
|
||
return Status;
|
||
}
|
||
|
||
|
||
VOID
|
||
ApmTurnOffSystem(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Turn the system off.
|
||
|
||
Arguments:
|
||
|
||
none
|
||
|
||
|
||
--*/
|
||
{
|
||
ULONG Ebx, Ecx;
|
||
NTSTATUS Status;
|
||
|
||
//
|
||
// Use ApmFunction to put machine into StandBy mode
|
||
//
|
||
DrDebug(APM_L2,("APM: ApmTurnOffSystem: enter\n"));
|
||
Ebx = APM_DEVICE_ALL;
|
||
Ecx = APM_SET_OFF;
|
||
Status = ApmFunction (APM_SET_POWER_STATE, &Ebx, &Ecx);
|
||
DrDebug(APM_L2,("APM: ApmTurnOffSystem: exit\n"));
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
ApmInProgress(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine informs the BIOS to cool its jets for 5 seconds
|
||
while we continue to operate
|
||
|
||
Arguments:
|
||
|
||
none
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS if the computer was suspended & then resumed
|
||
|
||
--*/
|
||
{
|
||
ULONG Ebx, Ecx;
|
||
NTSTATUS Status;
|
||
|
||
//
|
||
// Use ApmFunction to tell BIOS to cool its heals
|
||
//
|
||
|
||
Ebx = APM_DEVICE_ALL;
|
||
Ecx = APM_SET_PROCESSING;
|
||
Status = ApmFunction (APM_SET_POWER_STATE, &Ebx, &Ecx);
|
||
return;
|
||
}
|
||
|
||
|
||
ULONG
|
||
ApmCheckForEvent (
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Poll for APM event
|
||
|
||
Arguments:
|
||
|
||
Return Value:
|
||
|
||
We return:
|
||
APM_DO_code from apmp.h
|
||
|
||
APM_DO_NOTHING 0
|
||
APM_DO_SUSPEND 1
|
||
APM_DO_STANDBY 2
|
||
APM_DO_FIXCLOCK 3
|
||
APM_DO_NOTIFY 4
|
||
APM_DO_CRITICAL_SUSPEND 5
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
ULONG Ebx, Ecx;
|
||
ULONG returnvalue;
|
||
|
||
//
|
||
// Read an event. Might get nothing.
|
||
//
|
||
|
||
returnvalue = APM_DO_NOTHING;
|
||
|
||
Ebx = 0;
|
||
Ecx = 0;
|
||
Status = ApmFunction (APM_GET_EVENT, &Ebx, &Ecx);
|
||
|
||
if (Status != STATUS_SUCCESS) {
|
||
return returnvalue;
|
||
}
|
||
|
||
//
|
||
// Handle APM reported event
|
||
//
|
||
|
||
DrDebug(APM_L2,("APM: ApmCheckForEvent, code is %d\n", Ebx));
|
||
|
||
switch (Ebx) {
|
||
|
||
//
|
||
// say wer're working on it and set up for standby
|
||
//
|
||
case APM_SYS_STANDBY_REQUEST:
|
||
case APM_USR_STANDBY_REQUEST:
|
||
DrDebug(APM_L2,("APM: ApmCheckForEvent, standby request\n"));
|
||
ApmInProgress();
|
||
returnvalue = APM_DO_STANDBY;
|
||
break;
|
||
|
||
//
|
||
// say we're working on it and set up for suspend
|
||
//
|
||
case APM_SYS_SUSPEND_REQUEST:
|
||
case APM_USR_SUSPEND_REQUEST:
|
||
case APM_BATTERY_LOW_NOTICE:
|
||
DrDebug(APM_L2,
|
||
("APM: ApmCheckForEvent, suspend or battery low\n"));
|
||
ApmInProgress();
|
||
returnvalue = APM_DO_SUSPEND;
|
||
break;
|
||
|
||
//
|
||
// Say we're working on it, and setup for CRITICAL suspend
|
||
//
|
||
case APM_CRITICAL_SYSTEM_SUSPEND_REQUEST:
|
||
DrDebug(APM_L2, ("APM: Apmcheckforevent, critical suspend\n"));
|
||
ApmInProgress();
|
||
returnvalue = APM_DO_CRITICAL_SUSPEND;
|
||
break;
|
||
|
||
//
|
||
// ignore this because we have no idea what to do with it
|
||
//
|
||
case APM_CRITICAL_RESUME_NOTICE:
|
||
DrDebug(APM_L2,("APM: ApmCheckForEvent, critical resume\n"));
|
||
break;
|
||
|
||
|
||
case APM_UPDATE_TIME_EVENT:
|
||
DrDebug(APM_L2,("APM: ApmCheckForEvent, update time\n"));
|
||
returnvalue = APM_DO_FIXCLOCK;
|
||
break;
|
||
|
||
case APM_POWER_STATUS_CHANGE_NOTICE:
|
||
DrDebug(APM_L2,("APM: ApmCheckForEvent, update battery\n"));
|
||
returnvalue = APM_DO_NOTIFY;
|
||
break;
|
||
|
||
case APM_NORMAL_RESUME_NOTICE:
|
||
case APM_STANDBY_RESUME_NOTICE:
|
||
case APM_CAPABILITIES_CHANGE_NOTICE:
|
||
|
||
//
|
||
// ignore these because we don't care and there's nothing to do
|
||
//
|
||
|
||
DrDebug(APM_L2,
|
||
("APM: ApmCheckForEvent, non-interesting event\n"));
|
||
break;
|
||
|
||
default:
|
||
DrDebug(APM_L2,("APM: ApmCheckForEvent, out of range event\n"));
|
||
break;
|
||
} //switch
|
||
|
||
return returnvalue;
|
||
}
|
||
|