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:
janwas 2007-05-21 19:27:16 +00:00
parent bd42f083e0
commit b53b75b4fc
8 changed files with 1151 additions and 0 deletions

326
source/lib/sysdep/acpi.cpp Normal file
View 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
View 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

View 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
View 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

View 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" {

View 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

View 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);
}

View 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