forked from 0ad/0ad
WIP: driver for HPET timer (useful for WinXP, SMP systems)
can successfully read the counter. This was SVN commit r5085.
This commit is contained in:
parent
bd42f083e0
commit
b53b75b4fc
326
source/lib/sysdep/acpi.cpp
Normal file
326
source/lib/sysdep/acpi.cpp
Normal file
@ -0,0 +1,326 @@
|
||||
#include "precompiled.h"
|
||||
#include "acpi.h"
|
||||
|
||||
#include "win/mahaf.h"
|
||||
#include "lib/sysdep/cpu.h"
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// table utility functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// return 8-bit checksum of a buffer (should be 0)
|
||||
static u8 ComputeChecksum(const void* buf, size_t numBytes)
|
||||
{
|
||||
u8 sum = 0;
|
||||
const u8* end = (const u8*)buf+numBytes;
|
||||
for(const u8* p = (const u8*)buf; p < end; p++)
|
||||
sum += *p;
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
// caller is responsible for verifying the table is valid and must
|
||||
// free() the returned pointer.
|
||||
static const AcpiTable* AllocateCopyOfTable(u64 physicalAddress)
|
||||
{
|
||||
// 4 KiB ought to be enough; if not, the table will be
|
||||
// re-mapped with the actual size.
|
||||
const size_t initialSize = 4*KiB;
|
||||
|
||||
const AcpiTable* mappedTable = (const AcpiTable*)MapPhysicalMemory(physicalAddress, initialSize);
|
||||
if(!mappedTable)
|
||||
return 0;
|
||||
const size_t size = mappedTable->size;
|
||||
|
||||
if(size > initialSize)
|
||||
{
|
||||
UnmapPhysicalMemory((void*)mappedTable);
|
||||
mappedTable = (const AcpiTable*)MapPhysicalMemory(physicalAddress, size);
|
||||
if(!mappedTable)
|
||||
return 0;
|
||||
}
|
||||
|
||||
AcpiTable* table = (AcpiTable*)malloc(size);
|
||||
if(table)
|
||||
cpu_memcpy(table, mappedTable, size);
|
||||
|
||||
UnmapPhysicalMemory((void*)mappedTable);
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
static bool VerifyTable(const AcpiTable* table, const char* signature = 0)
|
||||
{
|
||||
// caller knowns the signature; make sure it matches
|
||||
if(signature)
|
||||
{
|
||||
if(memcmp(table->signature, signature, 4) != 0)
|
||||
return false;
|
||||
}
|
||||
// no specific signature is called for; just make sure it's 4 letters
|
||||
else
|
||||
{
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
if(!isalpha(table->signature[i]))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// must be at least as large as the common header
|
||||
if(table->size < sizeof(AcpiTable))
|
||||
return false;
|
||||
|
||||
// checksum of table must be 0
|
||||
// .. AMIBIOS OEMB table has an incorrect checksum (off-by-one),
|
||||
// so don't complain about any OEM tables (ignored anyway).
|
||||
const bool isOemTable = (memcmp(table->signature, "OEM", 3) == 0);
|
||||
if(ComputeChecksum(table, table->size) != 0 && !isOemTable)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// get pointer to (eXtended) Root System Descriptor Table
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Root System Descriptor Pointer
|
||||
|
||||
static const size_t RSDP_ALIGNMENT = 16;
|
||||
|
||||
struct RSDPv1
|
||||
{
|
||||
char signature[8]; // "RSD PTR "
|
||||
u8 checksum; // sum of this struct = 0
|
||||
char oemId[6];
|
||||
u8 revision; // 0 for 1.0, 2 for 2.0
|
||||
u32 rsdtPhysicalAddress;
|
||||
};
|
||||
|
||||
struct RSDPv2Additions
|
||||
{
|
||||
u32 size; // of entire table (including V1)
|
||||
u64 xsdtPhysicalAddress64;
|
||||
u8 extendedChecksum; // sum of entire table (including V1) = 0
|
||||
char reserved[3]; // must be 0
|
||||
};
|
||||
|
||||
struct RSDP
|
||||
{
|
||||
RSDPv1 v1;
|
||||
RSDPv2Additions v2;
|
||||
};
|
||||
|
||||
|
||||
static const RSDP* LocateRsdp(const u8* buf, size_t numBytes)
|
||||
{
|
||||
const u8* const end = buf+numBytes;
|
||||
for(const u8* p = buf; p < end; p += RSDP_ALIGNMENT)
|
||||
{
|
||||
const RSDP* rsdp = (const RSDP*)p;
|
||||
|
||||
if(memcmp(rsdp->v1.signature, "RSD PTR ", 8) != 0)
|
||||
continue;
|
||||
|
||||
if(ComputeChecksum(p, 20) != 0)
|
||||
continue;
|
||||
|
||||
return rsdp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static bool LocateAndRetrieveRsdp(uintptr_t physicalAddress, size_t numBytes, RSDP& rsdp)
|
||||
{
|
||||
void* virtualAddress = MapPhysicalMemory(physicalAddress, numBytes);
|
||||
|
||||
const RSDP* prsdp = LocateRsdp((const u8*)virtualAddress, numBytes);
|
||||
if(prsdp)
|
||||
rsdp = *prsdp; // stash in output parameter before unmapping
|
||||
|
||||
UnmapPhysicalMemory(virtualAddress);
|
||||
return (prsdp != 0);
|
||||
}
|
||||
|
||||
|
||||
static uintptr_t LocateEbdaPhysicalAddress()
|
||||
{
|
||||
struct BiosDataArea
|
||||
{
|
||||
u16 serialBase[4];
|
||||
u16 parallelBase[3];
|
||||
u16 ebdaSegment;
|
||||
// ...
|
||||
};
|
||||
const BiosDataArea* bda = (const BiosDataArea*)MapPhysicalMemory(0x400, 0x100);
|
||||
if(!bda)
|
||||
return 0;
|
||||
const uintptr_t ebdaPhysicalAddress = ((uintptr_t)bda->ebdaSegment) * 16;
|
||||
|
||||
return ebdaPhysicalAddress;
|
||||
}
|
||||
|
||||
|
||||
static bool RetrieveRsdp(RSDP& rsdp)
|
||||
{
|
||||
// See ACPIspec30b, section 5.2.5.1:
|
||||
// RSDP is either in the first KIB of the extended BIOS data area,
|
||||
if(LocateAndRetrieveRsdp(LocateEbdaPhysicalAddress(), 1*KiB, rsdp))
|
||||
return true;
|
||||
|
||||
// or in read-only BIOS memory.
|
||||
if(LocateAndRetrieveRsdp(0xE0000, 0x20000, rsdp))
|
||||
return true;
|
||||
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
|
||||
static bool VerifyRsdp(const RSDP& rsdp)
|
||||
{
|
||||
if(ComputeChecksum(&rsdp.v1, sizeof(rsdp.v1)) != 0)
|
||||
return false;
|
||||
|
||||
if(rsdp.v1.revision >= 2)
|
||||
{
|
||||
if(ComputeChecksum(&rsdp, rsdp.v2.size) != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Root System Descriptor Table
|
||||
struct RSDT
|
||||
{
|
||||
AcpiTable header;
|
||||
u32 tables[1];
|
||||
};
|
||||
|
||||
// eXtended root System Descriptor Table
|
||||
struct XSDT
|
||||
{
|
||||
AcpiTable header;
|
||||
u64 tables[1];
|
||||
};
|
||||
|
||||
|
||||
// caller is responsible for verifying the table is valid and must
|
||||
// free() the returned pointer.
|
||||
static const XSDT* AllocateCopyOfXsdt()
|
||||
{
|
||||
RSDP rsdp;
|
||||
if(!RetrieveRsdp(rsdp))
|
||||
return 0;
|
||||
|
||||
if(!VerifyRsdp(rsdp))
|
||||
return 0;
|
||||
|
||||
// callers should only have to deal with XSDTs (same as RSDT but with
|
||||
// 64-bit pointers). if running on ACPI 2.0, just return XSDT,
|
||||
// otherwise convert RSDT to XSDT.
|
||||
|
||||
if(rsdp.v1.revision >= 2)
|
||||
return (const XSDT*)AllocateCopyOfTable(rsdp.v2.xsdtPhysicalAddress64);
|
||||
|
||||
const RSDT* rsdt = (const RSDT*)AllocateCopyOfTable(rsdp.v1.rsdtPhysicalAddress);
|
||||
if(!rsdt)
|
||||
return 0;
|
||||
if(!VerifyTable((const AcpiTable*)rsdt, "RSDT"))
|
||||
{
|
||||
free((void*)rsdt);
|
||||
return 0;
|
||||
}
|
||||
const size_t numTables = (rsdt->header.size - sizeof(AcpiTable)) / sizeof(u32);
|
||||
const size_t xsdtSize = sizeof(AcpiTable) + numTables * sizeof(u64);
|
||||
XSDT* xsdt = (XSDT*)malloc(xsdtSize);
|
||||
if(xsdt)
|
||||
{
|
||||
xsdt->header = rsdt->header;
|
||||
cpu_memcpy(xsdt->header.signature, "XSDT", 4);
|
||||
xsdt->header.size = (u32)xsdtSize;
|
||||
for(size_t i = 0; i < numTables; i++)
|
||||
xsdt->tables[i] = (u64)rsdt->tables[i];
|
||||
xsdt->header.checksum = -ComputeChecksum(xsdt, xsdtSize);
|
||||
}
|
||||
|
||||
free((void*)rsdt);
|
||||
return xsdt;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
typedef std::map<u32, const AcpiTable*> Tables;
|
||||
static Tables tables;
|
||||
|
||||
static bool LatchAllTables()
|
||||
{
|
||||
const XSDT* xsdt = AllocateCopyOfXsdt();
|
||||
if(!xsdt)
|
||||
return false;
|
||||
|
||||
if(!VerifyTable((const AcpiTable*)xsdt, "XSDT"))
|
||||
{
|
||||
free((void*)xsdt);
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t numTables = (xsdt->header.size - sizeof(AcpiTable)) / sizeof(u64);
|
||||
for(size_t i = 0; i < numTables; i++)
|
||||
{
|
||||
const AcpiTable* table = AllocateCopyOfTable(xsdt->tables[i]);
|
||||
if(!table)
|
||||
continue;
|
||||
if(!VerifyTable(table))
|
||||
debug_warn("invalid ACPI table");
|
||||
const u32 signature32 = *(u32*)table->signature;
|
||||
tables[signature32] = table;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void FreeAllTables()
|
||||
{
|
||||
for(Tables::iterator it(tables.begin()); it != tables.end(); ++it)
|
||||
{
|
||||
std::pair<u32, const AcpiTable*> item = *it;
|
||||
free((void*)item.second);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const AcpiTable* acpiGetTable(const char* signature)
|
||||
{
|
||||
const u32 signature32 = *(u32*)signature;
|
||||
const AcpiTable* table = tables[signature32];
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool acpiInit()
|
||||
{
|
||||
if(!MahafInit())
|
||||
return false;
|
||||
|
||||
LatchAllTables();
|
||||
return true;
|
||||
}
|
||||
|
||||
void acpiShutdown()
|
||||
{
|
||||
FreeAllTables();
|
||||
|
||||
MahafShutdown();
|
||||
}
|
56
source/lib/sysdep/acpi.h
Normal file
56
source/lib/sysdep/acpi.h
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* =========================================================================
|
||||
* File : acpi.h
|
||||
* Project : 0 A.D.
|
||||
* Description : minimal subset of ACPI
|
||||
* =========================================================================
|
||||
*/
|
||||
|
||||
// license: GPL; see lib/license.txt
|
||||
|
||||
#ifndef INCLUDED_ACPI
|
||||
#define INCLUDED_ACPI
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
// common header for all ACPI tables
|
||||
struct AcpiTable
|
||||
{
|
||||
char signature[4];
|
||||
u32 size; // table size [bytes], including header
|
||||
u8 revision;
|
||||
u8 checksum; // to make sum of entire table == 0
|
||||
char oemId[6];
|
||||
char oemTableId[8];
|
||||
u32 oemRevision;
|
||||
char creatorId[4];
|
||||
u32 creatorRevision;
|
||||
};
|
||||
|
||||
enum AcpiAddressSpace
|
||||
{
|
||||
// (these are not generally powers-of-two - some values have been omitted.)
|
||||
ACPI_AS_MEMORY = 0,
|
||||
ACPI_AS_IO = 1,
|
||||
ACPI_AS_PCI_CONFIG = 2,
|
||||
ACPI_AS_SMBUS = 4,
|
||||
};
|
||||
|
||||
// address of a struct or register
|
||||
struct AcpiGenericAddress
|
||||
{
|
||||
u8 addressSpaceId;
|
||||
u8 registerBitWidth;
|
||||
u8 registerBitOffset;
|
||||
u8 accessSize;
|
||||
u64 address;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
extern bool acpiInit();
|
||||
extern void acpiShutdown();
|
||||
|
||||
extern const AcpiTable* acpiGetTable(const char* signature);
|
||||
|
||||
#endif // #ifndef INCLUDED_ACPI
|
75
source/lib/sysdep/hpet.cpp
Normal file
75
source/lib/sysdep/hpet.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* =========================================================================
|
||||
* File : hpet.cpp
|
||||
* Project : 0 A.D.
|
||||
* Description : HPET timer backend
|
||||
* =========================================================================
|
||||
*/
|
||||
|
||||
// license: GPL; see lib/license.txt
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "hpet.h"
|
||||
#include "acpi.h"
|
||||
#include "win/mahaf.h"
|
||||
#include "lib/bits.h"
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
struct HpetDescriptionTable
|
||||
{
|
||||
AcpiTable header;
|
||||
u32 eventTimerBlockId;
|
||||
AcpiGenericAddress baseAddress;
|
||||
u8 sequenceNumber;
|
||||
u16 minimumPeriodicTicks;
|
||||
u8 attributes;
|
||||
};
|
||||
|
||||
struct HpetRegisters
|
||||
{
|
||||
u64 capabilities;
|
||||
u64 reserved1;
|
||||
u64 config;
|
||||
u64 reserved2;
|
||||
u64 interruptStatus;
|
||||
u64 reserved3[25];
|
||||
u64 counterValue;
|
||||
u64 reserved4;
|
||||
|
||||
// .. followed by blocks for timers 0..31
|
||||
};
|
||||
|
||||
static volatile HpetRegisters* hpetRegisters;
|
||||
|
||||
static const u64 CONFIG_ENABLE = BIT64(0);
|
||||
|
||||
|
||||
|
||||
bool hpetInit()
|
||||
{
|
||||
if(!acpiInit())
|
||||
return false;
|
||||
|
||||
const HpetDescriptionTable* desc = (const HpetDescriptionTable*)acpiGetTable("HPET");
|
||||
debug_assert(desc->baseAddress.addressSpaceId == ACPI_AS_MEMORY);
|
||||
hpetRegisters = (volatile HpetRegisters*)MapPhysicalMemory(desc->baseAddress.address, sizeof(HpetRegisters));
|
||||
if(!hpetRegisters)
|
||||
return false;
|
||||
|
||||
const u32 timerPeriod_fs = bits64(hpetRegisters->capabilities, 32, 63);
|
||||
const double freq = 1e15 / timerPeriod_fs;
|
||||
|
||||
hpetRegisters->config &= ~CONFIG_ENABLE;
|
||||
hpetRegisters->counterValue = 0ull;
|
||||
hpetRegisters->config |= CONFIG_ENABLE;
|
||||
|
||||
debug_printf("HPET freq=%f counter=%I64d\n", freq, hpetRegisters->counterValue);
|
||||
}
|
||||
|
||||
|
||||
void hpetShutdown()
|
||||
{
|
||||
UnmapPhysicalMemory(hpetRegisters);
|
||||
}
|
17
source/lib/sysdep/hpet.h
Normal file
17
source/lib/sysdep/hpet.h
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* =========================================================================
|
||||
* File : hpet.cpp
|
||||
* Project : 0 A.D.
|
||||
* Description : HPET timer backend
|
||||
* =========================================================================
|
||||
*/
|
||||
|
||||
// license: GPL; see lib/license.txt
|
||||
|
||||
#ifndef INCLUDED_HPET
|
||||
#define INCLUDED_HPET
|
||||
|
||||
extern bool hpetInit();
|
||||
extern void hpetShutdown();
|
||||
|
||||
#endif // #ifndef INCLUDED_HPET
|
327
source/lib/sysdep/win/aken/aken.cpp
Normal file
327
source/lib/sysdep/win/aken/aken.cpp
Normal file
@ -0,0 +1,327 @@
|
||||
extern "C" { // must come before ntddk.h
|
||||
|
||||
#include <ntddk.h>
|
||||
#include "aken.h"
|
||||
|
||||
#define KDPRINT KdPrint(("")); KdPrint
|
||||
|
||||
#define WIN32_NAME L"\\DosDevices\\Aken"
|
||||
#define DEVICE_NAME L"\\Device\\Aken"
|
||||
|
||||
// note: this driver isn't much larger than a page anyway, so
|
||||
// there's little point in using #pragma alloc_text.
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// memory mapping
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// references: DDK mapmem.c sample,
|
||||
// http://support.microsoft.com/kb/189327/en-us
|
||||
static NTSTATUS AkenMapPhysicalMemory(const DWORD64 physicalAddress64, const DWORD64 numBytes64, DWORD64& virtualAddress64)
|
||||
{
|
||||
NTSTATUS ntStatus;
|
||||
|
||||
// (convenience)
|
||||
LARGE_INTEGER physicalAddress;
|
||||
physicalAddress.QuadPart = physicalAddress64;
|
||||
|
||||
// get handle to PhysicalMemory object
|
||||
HANDLE hMemory;
|
||||
{
|
||||
OBJECT_ATTRIBUTES objectAttributes;
|
||||
UNICODE_STRING objectName = RTL_CONSTANT_STRING(L"\\Device\\PhysicalMemory");
|
||||
const ULONG attributes = OBJ_CASE_INSENSITIVE;
|
||||
const HANDLE rootDirectory = 0;
|
||||
InitializeObjectAttributes(&objectAttributes, &objectName, attributes, rootDirectory, (PSECURITY_DESCRIPTOR)0);
|
||||
ntStatus = ZwOpenSection(&hMemory, SECTION_ALL_ACCESS, &objectAttributes);
|
||||
if(!NT_SUCCESS(ntStatus))
|
||||
{
|
||||
KDPRINT(("ZwOpenSection failed\n"));
|
||||
return ntStatus;
|
||||
}
|
||||
}
|
||||
|
||||
// add a reference (required to prevent the handle from being deleted)
|
||||
{
|
||||
PVOID physicalMemorySection = NULL;
|
||||
const POBJECT_TYPE objectType = 0; // allowed since specifying KernelMode
|
||||
ntStatus = ObReferenceObjectByHandle(hMemory, SECTION_ALL_ACCESS, objectType, KernelMode, &physicalMemorySection, 0);
|
||||
if(!NT_SUCCESS(ntStatus))
|
||||
{
|
||||
KDPRINT(("ObReferenceObjectByHandle failed\n"));
|
||||
goto close_handle;
|
||||
}
|
||||
}
|
||||
|
||||
// map desired memory into user PTEs (note: don't use MmMapIoSpace
|
||||
// because that occupies precious non-paged pool)
|
||||
{
|
||||
const HANDLE hProcess = (HANDLE)-1;
|
||||
PVOID virtualBaseAddress = 0; // let ZwMapViewOfSection pick
|
||||
const ULONG zeroBits = 0; // # high-order bits in address that must be 0
|
||||
SIZE_T mappedSize = (SIZE_T)numBytes64; // will receive the actual page-aligned size
|
||||
LARGE_INTEGER physicalBaseAddress = physicalAddress; // will be rounded down to 64KB boundary
|
||||
const SECTION_INHERIT inheritDisposition = ViewShare;
|
||||
const ULONG allocationType = 0;
|
||||
const ULONG protect = PAGE_READWRITE|PAGE_NOCACHE;
|
||||
ntStatus = ZwMapViewOfSection(hMemory, hProcess, &virtualBaseAddress, zeroBits, mappedSize, &physicalBaseAddress, &mappedSize, inheritDisposition, allocationType, protect);
|
||||
if(!NT_SUCCESS(ntStatus))
|
||||
{
|
||||
KDPRINT(("ZwMapViewOfSection failed\n"));
|
||||
goto close_handle;
|
||||
}
|
||||
|
||||
// the mapping rounded our physical base address down to the nearest
|
||||
// 64KiB boundary, so adjust the virtual address accordingly.
|
||||
const DWORD32 numBytesRoundedDown = physicalAddress.LowPart - physicalBaseAddress.LowPart;
|
||||
ASSERT(numBytesRoundedDown < 0x10000);
|
||||
virtualAddress64 = (DWORD64)virtualBaseAddress + numBytesRoundedDown;
|
||||
|
||||
KDPRINT(("mapped phys=%I64x physBase=%I64x physSize=%d to virt=%p. roundDown=%d => final virt=%I64x\n", physicalAddress64, physicalBaseAddress.QuadPart, mappedSize, virtualBaseAddress, numBytesRoundedDown, virtualAddress64));
|
||||
}
|
||||
|
||||
ntStatus = STATUS_SUCCESS;
|
||||
|
||||
close_handle:
|
||||
// closing the handle even on success means that callers won't have to
|
||||
// pass it back when unmapping. why does this work? ZwMapViewOfSection
|
||||
// apparently adds a reference to hMemory.
|
||||
ZwClose(hMemory);
|
||||
|
||||
return ntStatus;
|
||||
}
|
||||
|
||||
|
||||
static NTSTATUS AkenUnmapPhysicalMemory(const DWORD64 virtualAddress)
|
||||
{
|
||||
const HANDLE hProcess = (HANDLE)-1;
|
||||
PVOID baseAddress = (PVOID)virtualAddress;
|
||||
NTSTATUS ntStatus = ZwUnmapViewOfSection(hProcess, baseAddress);
|
||||
if(!NT_SUCCESS(ntStatus))
|
||||
{
|
||||
KDPRINT(("ZwUnmapViewOfSection failed\n"));
|
||||
return ntStatus;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// helper functions called from DeviceControl
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static NTSTATUS AkenIoctlReadPort(PVOID buf, const ULONG inSize, ULONG& outSize)
|
||||
{
|
||||
KDPRINT(("IOCTL_AKEN_READ_PORT\n"));
|
||||
|
||||
if(inSize != sizeof(AkenReadPortIn) || outSize != sizeof(AkenReadPortOut))
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
const AkenReadPortIn* in = (const AkenReadPortIn*)buf;
|
||||
const USHORT port = in->port;
|
||||
const UCHAR numBytes = in->numBytes;
|
||||
DWORD32 value;
|
||||
switch(numBytes)
|
||||
{
|
||||
case 1:
|
||||
value = (DWORD32)READ_PORT_UCHAR((PUCHAR)port);
|
||||
break;
|
||||
case 2:
|
||||
value = (DWORD32)READ_PORT_USHORT((PUSHORT)port);
|
||||
break;
|
||||
case 4:
|
||||
value = (DWORD32)READ_PORT_ULONG((PULONG)port);
|
||||
break;
|
||||
default:
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
KDPRINT((" port %x = %x\n", port, value));
|
||||
|
||||
AkenReadPortOut* out = (AkenReadPortOut*)buf;
|
||||
out->value = value;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS AkenIoctlWritePort(PVOID buf, const ULONG inSize, ULONG& outSize)
|
||||
{
|
||||
KDPRINT(("IOCTL_AKEN_WRITE_PORT\n"));
|
||||
|
||||
if(inSize != sizeof(AkenWritePortIn) || outSize != 0)
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
const AkenWritePortIn* in = (const AkenWritePortIn*)buf;
|
||||
const DWORD32 value = in->value;
|
||||
const USHORT port = in->port;
|
||||
const UCHAR numBytes = in->numBytes;
|
||||
switch(numBytes)
|
||||
{
|
||||
case 1:
|
||||
WRITE_PORT_UCHAR((PUCHAR)port, (UCHAR)(value & 0xFF));
|
||||
break;
|
||||
case 2:
|
||||
WRITE_PORT_USHORT((PUSHORT)port, (USHORT)(value & 0xFFFF));
|
||||
break;
|
||||
case 4:
|
||||
WRITE_PORT_ULONG((PULONG)port, value);
|
||||
break;
|
||||
default:
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
KDPRINT((" port %x := %x\n", port, value));
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static NTSTATUS AkenIoctlMap(PVOID buf, const ULONG inSize, ULONG& outSize)
|
||||
{
|
||||
KDPRINT(("IOCTL_AKEN_MAP\n"));
|
||||
|
||||
if(inSize != sizeof(AkenMapIn) || outSize != sizeof(AkenMapOut))
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
const AkenMapIn* in = (const AkenMapIn*)buf;
|
||||
const DWORD64 physicalAddress = in->physicalAddress;
|
||||
const DWORD64 numBytes = in->numBytes;
|
||||
DWORD64 virtualAddress;
|
||||
NTSTATUS ntStatus = AkenMapPhysicalMemory(physicalAddress, numBytes, virtualAddress);
|
||||
|
||||
AkenMapOut* out = (AkenMapOut*)buf;
|
||||
out->virtualAddress = virtualAddress;
|
||||
return ntStatus;
|
||||
}
|
||||
|
||||
static NTSTATUS AkenIoctlUnmap(PVOID buf, const ULONG inSize, ULONG& outSize)
|
||||
{
|
||||
KDPRINT(("IOCTL_AKEN_UNMAP\n"));
|
||||
|
||||
if(inSize != sizeof(AkenUnmapIn) || outSize != 0)
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
|
||||
const AkenUnmapIn* in = (const AkenUnmapIn*)buf;
|
||||
const DWORD64 virtualAddress = in->virtualAddress;
|
||||
NTSTATUS ntStatus = AkenUnmapPhysicalMemory(virtualAddress);
|
||||
|
||||
return ntStatus;
|
||||
}
|
||||
|
||||
static NTSTATUS AkenIoctlUnknown(PVOID buf, const ULONG inSize, ULONG& outSize)
|
||||
{
|
||||
KDPRINT(("Unknown IOCTL\n"));
|
||||
outSize = 0;
|
||||
return STATUS_INVALID_DEVICE_REQUEST;
|
||||
}
|
||||
|
||||
|
||||
typedef NTSTATUS (*AkenIoctl)(PVOID buf, ULONG inSize, ULONG& outSize);
|
||||
|
||||
static inline AkenIoctl AkenIoctlFromCode(ULONG ioctlCode)
|
||||
{
|
||||
switch(ioctlCode)
|
||||
{
|
||||
case IOCTL_AKEN_READ_PORT:
|
||||
return AkenIoctlReadPort;
|
||||
|
||||
case IOCTL_AKEN_WRITE_PORT:
|
||||
return AkenIoctlWritePort;
|
||||
|
||||
case IOCTL_AKEN_MAP:
|
||||
return AkenIoctlMap;
|
||||
|
||||
case IOCTL_AKEN_UNMAP:
|
||||
return AkenIoctlUnmap;
|
||||
|
||||
default:
|
||||
return AkenIoctlUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// entry points
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// used as Create and Close entry point since this driver is stateless.
|
||||
static NTSTATUS MarkRequestComplete(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
|
||||
{
|
||||
irp->IoStatus.Status = STATUS_SUCCESS;
|
||||
irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(irp, IO_NO_INCREMENT);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static NTSTATUS DeviceControl(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
|
||||
{
|
||||
// get buffer from IRP. all our IOCTLs are METHOD_BUFFERED, so buf is
|
||||
// allocated by the I/O manager and used for both input and output.
|
||||
PVOID buf = irp->AssociatedIrp.SystemBuffer;
|
||||
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(irp);
|
||||
ULONG ioctlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
|
||||
const ULONG inSize = irpStack->Parameters.DeviceIoControl.InputBufferLength;
|
||||
ULONG outSize = irpStack->Parameters.DeviceIoControl.OutputBufferLength; // modified by AkenIoctl*
|
||||
|
||||
const AkenIoctl akenIoctl = AkenIoctlFromCode(ioctlCode);
|
||||
const NTSTATUS ntStatus = akenIoctl(buf, inSize, outSize);
|
||||
|
||||
irp->IoStatus.Information = outSize; // number of bytes to copy from buf to user's buffer
|
||||
irp->IoStatus.Status = ntStatus;
|
||||
IoCompleteRequest(irp, IO_NO_INCREMENT);
|
||||
return ntStatus;
|
||||
}
|
||||
|
||||
|
||||
static VOID Unload(IN PDRIVER_OBJECT driverObject)
|
||||
{
|
||||
KDPRINT(("Unload\n"));
|
||||
|
||||
UNICODE_STRING win32Name = RTL_CONSTANT_STRING(WIN32_NAME);
|
||||
IoDeleteSymbolicLink(&win32Name);
|
||||
|
||||
if(driverObject->DeviceObject)
|
||||
IoDeleteDevice(driverObject->DeviceObject);
|
||||
}
|
||||
|
||||
|
||||
NTSTATUS DriverEntry(IN OUT PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath)
|
||||
{
|
||||
KDPRINT(("DriverEntry\n"));
|
||||
|
||||
UNICODE_STRING deviceName = RTL_CONSTANT_STRING(DEVICE_NAME);
|
||||
|
||||
// create device object
|
||||
PDEVICE_OBJECT deviceObject;
|
||||
{
|
||||
const ULONG deviceExtensionSize = 0;
|
||||
const ULONG deviceCharacteristics = FILE_DEVICE_SECURE_OPEN;
|
||||
const BOOLEAN exlusive = TRUE;
|
||||
NTSTATUS ntStatus = IoCreateDevice(driverObject, deviceExtensionSize, &deviceName, FILE_DEVICE_AKEN, deviceCharacteristics, exlusive, &deviceObject);
|
||||
if(!NT_SUCCESS(ntStatus))
|
||||
{
|
||||
KDPRINT(("IoCreateDevice failed\n"));
|
||||
return ntStatus;
|
||||
}
|
||||
}
|
||||
|
||||
// set entry points
|
||||
driverObject->MajorFunction[IRP_MJ_CREATE] = MarkRequestComplete;
|
||||
driverObject->MajorFunction[IRP_MJ_CLOSE] = MarkRequestComplete;
|
||||
driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControl;
|
||||
driverObject->DriverUnload = Unload;
|
||||
|
||||
// symlink NT device name to Win32 namespace
|
||||
{
|
||||
UNICODE_STRING win32Name = RTL_CONSTANT_STRING(WIN32_NAME);
|
||||
NTSTATUS ntStatus = IoCreateSymbolicLink(&win32Name, &deviceName);
|
||||
if(!NT_SUCCESS(ntStatus))
|
||||
{
|
||||
KDPRINT(("IoCreateSymbolicLink failed\n"));
|
||||
IoDeleteDevice(deviceObject);
|
||||
return ntStatus;
|
||||
}
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
} // extern "C" {
|
72
source/lib/sysdep/win/aken/aken.h
Normal file
72
source/lib/sysdep/win/aken/aken.h
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* =========================================================================
|
||||
* File : aken.h
|
||||
* Project : 0 A.D.
|
||||
* Description : Aken driver interface
|
||||
* =========================================================================
|
||||
*/
|
||||
|
||||
// license: GPL; see lib/license.txt
|
||||
|
||||
// Aken - custodian of the ferryboat to the underworld in Egyptian mythology,
|
||||
// and a driver that shuttles between applications and kernel mode resources.
|
||||
|
||||
#ifndef INCLUDED_AKEN
|
||||
#define INCLUDED_AKEN
|
||||
|
||||
#define AKEN_NAME "Aken"
|
||||
|
||||
// device type
|
||||
#define FILE_DEVICE_AKEN 53498 // in the "User Defined" range."
|
||||
|
||||
#define AKEN_IOCTL 0x800 // 0x800..0xFFF are for 'customer' use.
|
||||
|
||||
#define IOCTL_AKEN_READ_PORT CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+0, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
#define IOCTL_AKEN_WRITE_PORT CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+1, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
#define IOCTL_AKEN_MAP CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+2, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
#define IOCTL_AKEN_UNMAP CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+3, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
|
||||
// input and output data structures for the IOCTLs
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct AkenReadPortIn
|
||||
{
|
||||
USHORT port;
|
||||
UCHAR numBytes;
|
||||
};
|
||||
|
||||
struct AkenReadPortOut
|
||||
{
|
||||
DWORD32 value;
|
||||
};
|
||||
|
||||
struct AkenWritePortIn
|
||||
{
|
||||
DWORD32 value;
|
||||
USHORT port;
|
||||
UCHAR numBytes;
|
||||
};
|
||||
|
||||
struct AkenMapIn
|
||||
{
|
||||
// note: fixed-width types allow the 32 or 64-bit Mahaf wrapper to
|
||||
// interoperate with the 32 or 64-bit Aken driver.
|
||||
DWORD64 physicalAddress;
|
||||
DWORD64 numBytes;
|
||||
};
|
||||
|
||||
struct AkenMapOut
|
||||
{
|
||||
DWORD64 virtualAddress;
|
||||
};
|
||||
|
||||
struct AkenUnmapIn
|
||||
{
|
||||
DWORD64 virtualAddress;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif // #ifndef INCLUDED_AKEN
|
248
source/lib/sysdep/win/mahaf.cpp
Normal file
248
source/lib/sysdep/win/mahaf.cpp
Normal file
@ -0,0 +1,248 @@
|
||||
/**
|
||||
* =========================================================================
|
||||
* File : mahaf.cpp
|
||||
* Project : 0 A.D.
|
||||
* Description : user-mode interface to Aken driver
|
||||
* =========================================================================
|
||||
*/
|
||||
|
||||
// license: GPL; see lib/license.txt
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "win.h"
|
||||
#include <winioctl.h>
|
||||
#include "aken/aken.h"
|
||||
#include "wutil.h"
|
||||
#include "lib/path_util.h"
|
||||
|
||||
|
||||
static HANDLE hAken; // handle to Aken driver
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// ioctl wrappers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static u32 ReadPort(u16 port, u8 numBytes)
|
||||
{
|
||||
AkenReadPortIn in;
|
||||
in.port = (USHORT)port;
|
||||
in.numBytes = (UCHAR)numBytes;
|
||||
AkenReadPortOut out;
|
||||
|
||||
DWORD bytesReturned;
|
||||
LPOVERLAPPED ovl = 0; // synchronous
|
||||
BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_READ_PORT, &in, sizeof(in), &out, sizeof(out), &bytesReturned, ovl);
|
||||
if(!ok)
|
||||
{
|
||||
WARN_WIN32_ERR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
debug_assert(bytesReturned == sizeof(out));
|
||||
const u32 value = out.value;
|
||||
return value;
|
||||
}
|
||||
|
||||
u8 ReadPort8(u16 port)
|
||||
{
|
||||
const u32 value = ReadPort(port, 1);
|
||||
debug_assert(value <= 0xFF);
|
||||
return (u8)(value & 0xFF);
|
||||
}
|
||||
|
||||
u16 ReadPort16(u16 port)
|
||||
{
|
||||
const u32 value = ReadPort(port, 2);
|
||||
debug_assert(value <= 0xFFFF);
|
||||
return (u16)(value & 0xFFFF);
|
||||
}
|
||||
|
||||
u32 ReadPort32(u16 port)
|
||||
{
|
||||
const u32 value = ReadPort(port, 4);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
static void WritePort(u16 port, u32 value, u8 numBytes)
|
||||
{
|
||||
AkenWritePortIn in;
|
||||
in.value = (DWORD32)value;
|
||||
in.port = (USHORT)port;
|
||||
in.numBytes = (UCHAR)numBytes;
|
||||
|
||||
DWORD bytesReturned; // unused but must be passed to DeviceIoControl
|
||||
LPOVERLAPPED ovl = 0; // synchronous
|
||||
BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_WRITE_PORT, &in, sizeof(in), 0, 0u, &bytesReturned, ovl);
|
||||
if(!ok)
|
||||
WARN_WIN32_ERR;
|
||||
}
|
||||
|
||||
void WritePort8(u16 port, u8 value)
|
||||
{
|
||||
WritePort(port, (u32)value, 1);
|
||||
}
|
||||
|
||||
void WritePort16(u16 port, u16 value)
|
||||
{
|
||||
WritePort(port, (u32)value, 2);
|
||||
}
|
||||
|
||||
void WritePort32(u16 port, u32 value)
|
||||
{
|
||||
WritePort(port, value, 4);
|
||||
}
|
||||
|
||||
|
||||
void* MapPhysicalMemory(uintptr_t physicalAddress, size_t numBytes)
|
||||
{
|
||||
AkenMapIn in;
|
||||
in.physicalAddress = (DWORD64)physicalAddress;
|
||||
in.numBytes = (DWORD64)numBytes;
|
||||
AkenMapOut out;
|
||||
|
||||
DWORD bytesReturned;
|
||||
LPOVERLAPPED ovl = 0; // synchronous
|
||||
BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_MAP, &in, sizeof(in), &out, sizeof(out), &bytesReturned, ovl);
|
||||
if(!ok)
|
||||
{
|
||||
WARN_WIN32_ERR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
debug_assert(bytesReturned == sizeof(out));
|
||||
void* virtualAddress = (void*)out.virtualAddress;
|
||||
return virtualAddress;
|
||||
}
|
||||
|
||||
|
||||
void UnmapPhysicalMemory(void* virtualAddress)
|
||||
{
|
||||
AkenUnmapIn in;
|
||||
in.virtualAddress = (DWORD64)virtualAddress;
|
||||
|
||||
DWORD bytesReturned; // unused but must be passed to DeviceIoControl
|
||||
LPOVERLAPPED ovl = 0; // synchronous
|
||||
BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_UNMAP, &in, sizeof(in), 0, 0u, &bytesReturned, ovl);
|
||||
if(!ok)
|
||||
WARN_WIN32_ERR;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// driver installation
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static bool Is64BitOs()
|
||||
{
|
||||
#if OS_WIN64
|
||||
return true;
|
||||
#else
|
||||
// import kernel32!IsWow64Process
|
||||
const HMODULE hKernel32Dll = LoadLibrary("kernel32.dll");
|
||||
BOOL (WINAPI *pIsWow64Process)(HANDLE, PBOOL);
|
||||
*(void**)&pIsWow64Process = GetProcAddress(hKernel32Dll, "IsWow64Process");
|
||||
FreeLibrary(hKernel32Dll);
|
||||
|
||||
// function not found => running on 32-bit Windows
|
||||
if(!pIsWow64Process)
|
||||
return false;
|
||||
|
||||
BOOL isWow64Process = FALSE;
|
||||
const BOOL ok = IsWow64Process(GetCurrentProcess(), &isWow64Process);
|
||||
WARN_IF_FALSE(ok);
|
||||
|
||||
return (isWow64Process == TRUE);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void StartDriver(const char* driverPathname)
|
||||
{
|
||||
// open SC manager
|
||||
SC_HANDLE serviceControlManager;
|
||||
{
|
||||
LPCSTR machineName = 0; // local
|
||||
LPCSTR databaseName = 0; // default
|
||||
serviceControlManager = OpenSCManager(machineName, databaseName, SC_MANAGER_ALL_ACCESS);
|
||||
// non-admin account => we can't start the driver. note that installing
|
||||
// the driver and having it start with Windows would allow access to
|
||||
// the service even from least-permission accounts.
|
||||
if(!serviceControlManager)
|
||||
return;
|
||||
}
|
||||
|
||||
// create service (note: this just enters the service into SCM's DB;
|
||||
// no error is raised if the driver binary doesn't exist etc.)
|
||||
SC_HANDLE service;
|
||||
{
|
||||
create:
|
||||
LPCSTR startName = 0; // LocalSystem
|
||||
service = CreateService(serviceControlManager, AKEN_NAME, AKEN_NAME,
|
||||
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
||||
driverPathname, 0, 0, 0, startName, 0);
|
||||
if(!service)
|
||||
{
|
||||
// was already created
|
||||
if(GetLastError() == ERROR_SERVICE_EXISTS)
|
||||
{
|
||||
service = OpenService(serviceControlManager, AKEN_NAME, SERVICE_ALL_ACCESS);
|
||||
|
||||
#if 1
|
||||
// during development, we want to unload and re-create the
|
||||
// service every time to ensure the newest build is used.
|
||||
BOOL ok;
|
||||
SERVICE_STATUS serviceStatus;
|
||||
ok = ControlService(service, SERVICE_CONTROL_STOP, &serviceStatus);
|
||||
WARN_IF_FALSE(ok);
|
||||
ok = DeleteService(service);
|
||||
WARN_IF_FALSE(ok);
|
||||
ok = CloseServiceHandle(service);
|
||||
WARN_IF_FALSE(ok);
|
||||
goto create;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
WARN_IF_FALSE(0); // creating failed
|
||||
}
|
||||
}
|
||||
|
||||
// start service
|
||||
{
|
||||
DWORD numArgs = 0;
|
||||
BOOL ok = StartService(service, numArgs, 0);
|
||||
if(!ok)
|
||||
{
|
||||
// if it wasn't already running, starting failed
|
||||
if(GetLastError() != ERROR_SERVICE_ALREADY_RUNNING)
|
||||
WARN_IF_FALSE(0);
|
||||
}
|
||||
}
|
||||
|
||||
CloseServiceHandle(service);
|
||||
CloseServiceHandle(serviceControlManager);
|
||||
}
|
||||
|
||||
|
||||
bool MahafInit()
|
||||
{
|
||||
char driverPathname[PATH_MAX];
|
||||
const char* const driverName = Is64BitOs()? "aken64.sys" : "aken.sys";
|
||||
(void)path_append(driverPathname, win_exe_dir, driverName);
|
||||
|
||||
StartDriver(driverPathname);
|
||||
|
||||
DWORD shareMode = 0;
|
||||
hAken = CreateFile("\\\\.\\Aken", GENERIC_READ, shareMode, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if(hAken == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void MahafShutdown()
|
||||
{
|
||||
CloseHandle(hAken);
|
||||
}
|
30
source/lib/sysdep/win/mahaf.h
Normal file
30
source/lib/sysdep/win/mahaf.h
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* =========================================================================
|
||||
* File : mahaf.h
|
||||
* Project : 0 A.D.
|
||||
* Description : user-mode interface to Aken driver
|
||||
* =========================================================================
|
||||
*/
|
||||
|
||||
// license: GPL; see lib/license.txt
|
||||
|
||||
// Mahaf - ferryman in Egyptian mythology that wakes up Aken,
|
||||
// and the interface to the Aken driver.
|
||||
|
||||
#ifndef INCLUDED_MAHAF
|
||||
#define INCLUDED_MAHAF
|
||||
|
||||
extern bool MahafInit();
|
||||
extern void MahafShutdown();
|
||||
|
||||
extern u8 ReadPort8 (u16 port);
|
||||
extern u16 ReadPort16(u16 port);
|
||||
extern u32 ReadPort32(u16 port);
|
||||
extern void WritePort8 (u16 port, u8 value);
|
||||
extern void WritePort16(u16 port, u16 value);
|
||||
extern void WritePort32(u16 port, u32 value);
|
||||
|
||||
extern void* MapPhysicalMemory(uintptr_t physicalAddress, size_t numBytes);
|
||||
extern void UnmapPhysicalMemory(void* virtualAddress);
|
||||
|
||||
#endif // INCLUDED_MAHAF
|
Loading…
Reference in New Issue
Block a user