From b53b75b4fc444acf32b62baa48eaa10a0b666a96 Mon Sep 17 00:00:00 2001 From: janwas Date: Mon, 21 May 2007 19:27:16 +0000 Subject: [PATCH] WIP: driver for HPET timer (useful for WinXP, SMP systems) can successfully read the counter. This was SVN commit r5085. --- source/lib/sysdep/acpi.cpp | 326 +++++++++++++++++++++++++++ source/lib/sysdep/acpi.h | 56 +++++ source/lib/sysdep/hpet.cpp | 75 +++++++ source/lib/sysdep/hpet.h | 17 ++ source/lib/sysdep/win/aken/aken.cpp | 327 ++++++++++++++++++++++++++++ source/lib/sysdep/win/aken/aken.h | 72 ++++++ source/lib/sysdep/win/mahaf.cpp | 248 +++++++++++++++++++++ source/lib/sysdep/win/mahaf.h | 30 +++ 8 files changed, 1151 insertions(+) create mode 100644 source/lib/sysdep/acpi.cpp create mode 100644 source/lib/sysdep/acpi.h create mode 100644 source/lib/sysdep/hpet.cpp create mode 100644 source/lib/sysdep/hpet.h create mode 100644 source/lib/sysdep/win/aken/aken.cpp create mode 100644 source/lib/sysdep/win/aken/aken.h create mode 100644 source/lib/sysdep/win/mahaf.cpp create mode 100644 source/lib/sysdep/win/mahaf.h diff --git a/source/lib/sysdep/acpi.cpp b/source/lib/sysdep/acpi.cpp new file mode 100644 index 0000000000..68a921be1a --- /dev/null +++ b/source/lib/sysdep/acpi.cpp @@ -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 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 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(); +} diff --git a/source/lib/sysdep/acpi.h b/source/lib/sysdep/acpi.h new file mode 100644 index 0000000000..2ae9d6a9bb --- /dev/null +++ b/source/lib/sysdep/acpi.h @@ -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 diff --git a/source/lib/sysdep/hpet.cpp b/source/lib/sysdep/hpet.cpp new file mode 100644 index 0000000000..61a8eae291 --- /dev/null +++ b/source/lib/sysdep/hpet.cpp @@ -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); +} diff --git a/source/lib/sysdep/hpet.h b/source/lib/sysdep/hpet.h new file mode 100644 index 0000000000..20681d0c91 --- /dev/null +++ b/source/lib/sysdep/hpet.h @@ -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 diff --git a/source/lib/sysdep/win/aken/aken.cpp b/source/lib/sysdep/win/aken/aken.cpp new file mode 100644 index 0000000000..4820f021a0 --- /dev/null +++ b/source/lib/sysdep/win/aken/aken.cpp @@ -0,0 +1,327 @@ +extern "C" { // must come before ntddk.h + +#include +#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" { diff --git a/source/lib/sysdep/win/aken/aken.h b/source/lib/sysdep/win/aken/aken.h new file mode 100644 index 0000000000..a02dbcb440 --- /dev/null +++ b/source/lib/sysdep/win/aken/aken.h @@ -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 diff --git a/source/lib/sysdep/win/mahaf.cpp b/source/lib/sysdep/win/mahaf.cpp new file mode 100644 index 0000000000..659eae57f7 --- /dev/null +++ b/source/lib/sysdep/win/mahaf.cpp @@ -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 +#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); +} diff --git a/source/lib/sysdep/win/mahaf.h b/source/lib/sysdep/win/mahaf.h new file mode 100644 index 0000000000..8baa722bd0 --- /dev/null +++ b/source/lib/sysdep/win/mahaf.h @@ -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