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

900 lines
20 KiB
C
Raw Permalink 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:
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;
}