|
|
/*++
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; }
|