Fix the random slowdowns and speedups on AMD CPUs for Windows

Remove the MAHAF code and binaries, which doesn't work on anything more
recent than Windows XP due to the usage of an unsigned driver.
Remove the custom timer (PMT, TSC...) code to use the Windows
recommended way aka, QueryPerformanceCounter (QPC)
Remove the deprecated functions in wpthread relating to this. Some
further cleanup will be needed, but this patch is already big enough.

This patch voluntarily excludes the Linux Fix for further investigation
as we haven't ruled out a kernel bug yet;

Discussed with: @wraitii, @vladislavbelov, @janwas, AMD Inc,
Tested by: Dakara, @OptimusShepard, Stan (On W10, Win7; with and without
PCH)

Threads:
https://wildfiregames.com/forum/topic/28367-amd-ryzen-threadripper-user-read-before-posting/
https://wildfiregames.com/forum/index.php?/topic/26890-problem-with-ryzen-3000er-series/page/3/

Differential Revision: https://code.wildfiregames.com/D2726
This was SVN commit r24137.
This commit is contained in:
Stan 2020-11-06 23:18:16 +00:00
parent 429d190fd2
commit ada16439ab
38 changed files with 23 additions and 3522 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -84,22 +84,6 @@ Advanced / diagnostic:
-hashtest-full=X whether to enable computation of full hashes in replaymode (default true). Can be disabled to improve performance.
-hashtest-quick=X whether to enable computation of quick hashes in replaymode (default false). Can be enabled for debugging purposes.
Windows-specific:
-wQpcTscSafe allow timing via QueryPerformanceCounter despite the fact
that it's using TSC and it may be unsafe. has no effect if
a better timer (i.e. the HPET) is available.
should only be specified if:
- you are sure your system does not engage in
thermal throttling (including STPCLK) OR
- an "RDTSC patch" is installed
this flag is also useful if all other alternatives are worse
than a potentially risky or slightly broken TSC-based QPC.
-wNoMahaf prevent any physical memory mapping or direct port I/O.
this disables all ACPI-related code and thus some of the
timer backends. specify this if problems are observed with
one of the abovementioned subsystems.
Archive builder:
-archivebuild=PATH system PATH of the base directory containing mod data to be archived/precached
specify all mods it depends on with -mod=NAME

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -67,19 +67,6 @@
# endif
#endif
// allow an attempt to start the Aken driver (i.e. service) at runtime.
// enable at your own risk on WinXP systems to allow access to
// better timers than Windows provides. on newer Windows versions,
// attempts to start the service from code fail unless the process
// is elevated, and definitely fail due to lack of cross-signing unless
// test-signing mode is active.
// if the user has taken explicit action to install and start the
// service via aken_install.bat, mahaf.cpp will be able to access it
// even if this is defined to 0.
#ifndef CONFIG2_MAHAF_ATTEMPT_DRIVER_START
# define CONFIG2_MAHAF_ATTEMPT_DRIVER_START 0
#endif
// build in OpenGL ES 2.0 mode, instead of the default mode designed for
// GL 1.1 + extensions.
// this disables various features that are not supported by GLES.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -27,12 +27,7 @@
#include "lib/sysdep/cpu.h"
#include "lib/module_init.h"
#define ENABLE_MAHAF 0
#if ENABLE_MAHAF
# include "lib/sysdep/os/win/mahaf.h"
#else
# include "lib/sysdep/os/win/wfirmware.h"
#endif
#pragma pack(1)
@ -108,206 +103,8 @@ static bool ValidateTable(const AcpiTable* table, const char* signature = 0)
return true;
}
#if ENABLE_MAHAF
//-----------------------------------------------------------------------------
// exception-safe transactional map/use/unmap
// note: if the OS happens to unmap our physical memory, the Unsafe*
// functions may crash. we catch this via SEH; on Unix, we'd need handlers
// for SIGBUS and/or SIGSEGV. the code is safe in that it releases the
// mapped memory and returns an error code.
static void* SUCCEEDED = (void*)(intptr_t)1;
static void* FAILED = (void*)(intptr_t)-1;
typedef void* (*UnsafeFunction)(PCV_u8 mem, size_t numBytes, void* arg);
static void* CallWithSafetyBlanket(UnsafeFunction func, PCV_u8 mem, size_t numBytes, void* arg)
{
#if MSC_VERSION
__try
{
return func(mem, numBytes, arg);
}
__except(1)
{
return FAILED;
}
#else
return func(mem, numBytes, arg);
#endif
}
static void* TransactPhysicalMemory(uintptr_t physicalAddress, size_t numBytes, UnsafeFunction func, void* arg = 0)
{
PCV_u8 mem = (PCV_u8)mahaf_MapPhysicalMemory(physicalAddress, numBytes);
if(!mem)
return FAILED;
void* ret = CallWithSafetyBlanket(func, mem, numBytes, arg);
mahaf_UnmapPhysicalMemory((volatile void*)mem);
return ret;
}
//-----------------------------------------------------------------------------
// Root System Descriptor Pointer
struct BiosDataArea
{
u16 serialBase[4];
u16 parallelBase[3];
u16 ebdaSegment;
};
typedef const volatile BiosDataArea* PCV_BiosDataArea;
static void* UnsafeReadEbdaPhysicalAddress(PCV_u8 mem, size_t numBytes, void* UNUSED(arg))
{
ENSURE(numBytes >= sizeof(BiosDataArea));
PCV_BiosDataArea bda = (PCV_BiosDataArea)mem;
const uintptr_t ebdaPhysicalAddress = ((uintptr_t)bda->ebdaSegment) * 16;
return (void*)ebdaPhysicalAddress;
}
struct RSDP
{
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;
};
typedef const volatile RSDP* PCV_RSDP;
static const size_t RSDP_ALIGNMENT = 16;
static void* UnsafeLocateAndRetrieveRsdp(PCV_u8 buf, size_t numBytes, void* arg)
{
ENSURE(numBytes >= sizeof(RSDP));
for(PCV_u8 p = buf; p < buf+numBytes; p += RSDP_ALIGNMENT)
{
RSDP* prsdp = (RSDP*)p;
if(memcmp(prsdp->signature, "RSD PTR ", 8) != 0)
continue;
if(ComputeChecksum(p, sizeof(RSDP)) != 0)
continue;
memcpy(arg, prsdp, sizeof(RSDP));
return SUCCEEDED;
}
return FAILED;
}
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,
void* ret = TransactPhysicalMemory(0x400, 0x100, UnsafeReadEbdaPhysicalAddress);
if(ret != FAILED)
{
const uintptr_t ebdaPhysicalAddress = (uintptr_t)ret;
ret = TransactPhysicalMemory(ebdaPhysicalAddress, 0x400, UnsafeLocateAndRetrieveRsdp, &rsdp);
if(ret == SUCCEEDED)
return true;
}
// or in read-only BIOS memory.
ret = TransactPhysicalMemory(0xE0000, 0x20000, UnsafeLocateAndRetrieveRsdp, &rsdp);
if(ret == SUCCEEDED)
return true;
return false; // not found
}
//-----------------------------------------------------------------------------
// copy tables from physical memory
static void* UnsafeAllocateAndCopyTable(PCV_u8 mem, size_t numBytes, void* arg)
{
ENSURE(numBytes >= sizeof(AcpiTable));
PCV_AcpiTable table = (PCV_AcpiTable)mem;
const size_t tableSize = table->size;
// physical memory window is smaller than the table
// (caller will map a larger window and call us again)
if(numBytes < tableSize)
{
memcpy(arg, &tableSize, sizeof(size_t));
return 0;
}
PCV_u8 copy = (PCV_u8)AllocateTable(tableSize);
if(!copy)
return FAILED;
memcpy((void*)copy, (const void*)mem, tableSize);
return (void*)copy;
}
static const AcpiTable* AllocateAndCopyTable(uintptr_t physicalAddress)
{
// ACPI table sizes are not known until they've been mapped. since that
// is slow, we don't always want to do it twice. the solution is to map
// enough for a typical table; if that is too small, realloc and map again.
static const size_t initialSize = 4*KiB;
size_t actualSize = 0;
void* ret = TransactPhysicalMemory(physicalAddress, initialSize, UnsafeAllocateAndCopyTable, &actualSize);
// initialSize was too small; actualSize has been set
if(ret == 0)
ret = TransactPhysicalMemory(physicalAddress, actualSize, UnsafeAllocateAndCopyTable);
// *either* of the above calls failed to allocate memory
if(ret == FAILED)
return 0;
return (const AcpiTable*)ret;
}
#endif // ENABLE_MAHAF
static void AllocateAndCopyTables(const AcpiTable**& tables, size_t& numTables)
{
#if ENABLE_MAHAF
if(mahaf_IsPhysicalMappingDangerous())
return;
if(mahaf_Init() != INFO::OK)
return;
RSDP rsdp;
if(!RetrieveRsdp(rsdp))
return;
// Root System Descriptor Table
struct RSDT
{
AcpiTable header;
u32 tableAddresses[1];
};
const RSDT* rsdt = (const RSDT*)AllocateAndCopyTable(rsdp.rsdtPhysicalAddress);
if(!ValidateTable(&rsdt->header, "RSDT"))
{
DeallocateTable(rsdt);
return;
}
numTables = (rsdt->header.size - sizeof(AcpiTable)) / sizeof(rsdt->tableAddresses[0]);
ENSURE(numTables != 0);
tables = new const AcpiTable*[numTables];
for(size_t i = 0; i < numTables; i++)
tables[i] = AllocateAndCopyTable(rsdt->tableAddresses[i]);
DeallocateTable(rsdt);
#else
const wfirmware::Provider provider = FOURCC_BE('A','C','P','I');
const wfirmware::TableIds tableIDs = wfirmware::GetTableIDs(provider);
@ -321,7 +118,6 @@ static void AllocateAndCopyTables(const AcpiTable**& tables, size_t& numTables)
tables[i] = AllocateTable(table.size());
memcpy((void*)tables[i], &table[0], table.size());
}
#endif
// to prevent callers from choking on invalid tables, we
// zero out the corresponding tables[] entries.
@ -352,10 +148,6 @@ void acpi_Shutdown()
SAFE_ARRAY_DELETE(tables);
numTables = 0;
}
#if ENABLE_MAHAF
mahaf_Shutdown();
#endif
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -30,8 +30,8 @@ typedef u8 ApicId; // not necessarily contiguous values
* platform does not have an xAPIC (i.e. 7th generation x86 or below).
*
* rationale: the alternative of accessing the APIC mmio registers is not
* feasible - mahaf_MapPhysicalMemory only works reliably on WinXP. we also
* don't want to interfere with the OS's constant use of the APIC registers.
* feasible. We also don't want to interfere with the OS's constant use of
* the APIC registers.
**/
LIB_API ApicId GetApicId();

View File

@ -1,141 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "precompiled.h"
#include "lib/sysdep/arch/x86_x64/msr.h"
#include "lib/sysdep/os/win/mahaf.h"
#include "lib/sysdep/arch/x86_x64/x86_x64.h"
namespace MSR {
bool IsAccessible()
{
if(!x86_x64::Cap(x86_x64::CAP_MSR))
return false;
// only read/writable from ring 0, so we need the driver.
if(mahaf_Init() < 0)
return false;
return true;
}
bool HasEnergyPerfBias()
{
#if 1
// the documentation is unclear. until it improves, disable
// this, lest we provoke a GPF.
return false;
#else
if(x86_x64::Vendor() != x86_x64::VENDOR_INTEL)
return false;
if(x86_x64::Family() < 6)
return false;
if(x86_x64::Model() < 0xE)
return false;
return true;
#endif
}
bool HasPlatformInfo()
{
if(x86_x64::Vendor() != x86_x64::VENDOR_INTEL)
return false;
if(x86_x64::Family() != 6)
return false;
switch(x86_x64::Model())
{
// section 34.4 in 253665-041US
case x86_x64::MODEL_NEHALEM_EP:
case x86_x64::MODEL_NEHALEM_EP_2:
case x86_x64::MODEL_NEHALEM_EX:
case x86_x64::MODEL_I7_I5:
return true;
// section 34.5
case x86_x64::MODEL_CLARKDALE:
case x86_x64::MODEL_WESTMERE_EP:
return true;
// section 34.6
case x86_x64::MODEL_WESTMERE_EX:
return true;
// section 34.7
case x86_x64::MODEL_SANDY_BRIDGE:
case x86_x64::MODEL_SANDY_BRIDGE_2:
return true;
default:
return false;
}
}
bool HasUncore()
{
if(x86_x64::Vendor() != x86_x64::VENDOR_INTEL)
return false;
if(x86_x64::Family() != 6)
return false;
switch(x86_x64::Model())
{
// Xeon 5500 / i7 (section B.4.1 in 253669-037US)
case 0x1A: // Bloomfield, Gainstown
case 0x1E: // Clarksfield, Lynnfield, Jasper Forest
case 0x1F:
return true;
// Xeon 5600 / Westmere (section B.5)
case 0x25: // Clarkdale, Arrandale
case 0x2C: // Gulftown
return true;
default:
return false;
}
}
u64 Read(u64 reg)
{
return mahaf_ReadModelSpecificRegister(reg);
}
void Write(u64 reg, u64 value)
{
mahaf_WriteModelSpecificRegister(reg, value);
}
} // namespace MSR

View File

@ -1,69 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* model-specific registers
*/
#ifndef INCLUDED_X86_X64_MSR
#define INCLUDED_X86_X64_MSR
namespace MSR {
enum ModelSpecificRegisters
{
// architectural (will not change on future processors)
IA32_MISC_ENABLE = 0x1A0,
IA32_ENERGY_PERF_BIAS = 0x1B0, // requires HasEnergyPerfBias
// PMU v1
IA32_PMC0 = 0x0C1,
IA32_PERFEVTSEL0 = 0x186,
// PMU v2
IA32_PERF_GLOBAL_STATUS = 0x38E,
IA32_PERF_GLOBAL_CTRL = 0x38F,
IA32_PERF_GLOBAL_OVF_CTRL = 0x390,
// Nehalem and later
PLATFORM_INFO = 0x0CE, // requires HasPlatformInfo
// Nehalem, Westmere (requires HasUncore)
UNCORE_PERF_GLOBAL_CTRL = 0x391,
UNCORE_PERF_GLOBAL_STATUS = 0x392,
UNCORE_PERF_GLOBAL_OVF_CTRL = 0x393,
UNCORE_PMC0 = 0x3B0,
UNCORE_PERFEVTSEL0 = 0x3C0
};
LIB_API bool IsAccessible();
LIB_API bool HasEnergyPerfBias();
LIB_API bool HasPlatformInfo();
LIB_API bool HasUncore();
LIB_API u64 Read(u64 reg);
LIB_API void Write(u64 reg, u64 value);
} // namespace MSR
#endif // #ifndef INCLUDED_X86_X64_MSR

View File

@ -1,526 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// note: staticdv cannot yet check C++ code.
#include <ntddk.h>
#include "aken.h"
#include "intrinsics.h"
#define WIN32_NAME L"\\DosDevices\\Aken"
#define DEVICE_NAME L"\\Device\\Aken"
// placate PREfast
DRIVER_INITIALIZE DriverEntry;
__drv_dispatchType(IRP_MJ_CREATE) DRIVER_DISPATCH AkenCreate;
__drv_dispatchType(IRP_MJ_CLOSE) DRIVER_DISPATCH AkenClose;
__drv_dispatchType(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH AkenDeviceControl;
DRIVER_UNLOAD AkenUnload;
// this driver isn't large, but it's still slightly nicer to make its
// functions pageable and thus not waste precious non-paged pool.
// #pragma code_seg is more convenient than specifying alloc_text for
// every other function.
#pragma alloc_text(INIT, DriverEntry) // => discardable
#pragma code_seg(push, "PAGE")
//-----------------------------------------------------------------------------
// memory mapping
//-----------------------------------------------------------------------------
/*
there are three approaches to mapping physical memory:
(http://www.microsoft.com/whdc/driver/kernel/mem-mgmt.mspx)
- MmMapIoSpace (http://support.microsoft.com/kb/189327/en-us). despite the
name, it maps physical pages of any kind by allocating PTEs. very easy to
implement, but occupies precious kernel address space. possible bugs:
http://www.osronline.com/showThread.cfm?link=96737
http://support.microsoft.com/kb/925793/en-us
- ZwMapViewOfSection of PhysicalMemory (http://tinyurl.com/yozmgy).
the code is a bit bulky, but the WinXP API prevents mapping pages with
conflicting attributes (see below).
- MmMapLockedPagesSpecifyCache or MmGetSystemAddressForMdlSafe
(http://www.osronline.com/article.cfm?id=423). note: the latter is a macro
that calls the former. this is the 'normal' and fully documented way,
but it doesn't appear able to map a fixed physical address.
(MmAllocatePagesForMdl understandably doesn't work since some pages we
want to map are marked as unavailable for allocation, and I don't see
another documented way to fill an MDL with PFNs.)
our choice here is forced by a very insidious issue. if someone else has
already mapped a page with different attributes (e.g. cacheable), TLBs
may end up corrupted, leading to disaster. the search for a documented
means of accessing the page frame database (to check if mapped anywhere
and determine the previously set attributes) has not borne fruit, so we
must use ZwMapViewOfSection. (if taking this up again, see
http://www.woodmann.com/forum/archive/index.php/t-6516.html )
note that we guess if the page will have been mapped as cacheable and
even try the opposite if that turns out to have been incorrect.
*/
static int IsMemoryUncacheable(DWORD64 physicalAddress64)
{
PAGED_CODE();
// original PC memory - contains BIOS
if(physicalAddress64 < 0x100000)
return 1;
return 0;
}
static NTSTATUS AkenMapPhysicalMemory(const DWORD64 physicalAddress64, const DWORD64 numBytes64, DWORD64* virtualAddress64)
{
NTSTATUS ntStatus;
HANDLE hMemory;
LARGE_INTEGER physicalAddress; // convenience
physicalAddress.QuadPart = physicalAddress64;
PAGED_CODE();
// get handle to PhysicalMemory object
{
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(("AkenMapPhysicalMemory: 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(("AkenMapPhysicalMemory: ObReferenceObjectByHandle failed\n"));
goto close_handle;
}
}
// note: mapmem.c does HalTranslateBusAddress, but we only care about
// system memory. translating doesn't appear to be necessary, even if
// much existing code uses it (probably due to cargo cult).
// map desired memory into user PTEs
{
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;
ULONG protect = PAGE_READWRITE;
if(IsMemoryUncacheable(physicalAddress64))
protect |= PAGE_NOCACHE;
ntStatus = ZwMapViewOfSection(hMemory, hProcess, &virtualBaseAddress, zeroBits, mappedSize, &physicalBaseAddress, &mappedSize, inheritDisposition, allocationType, protect);
if(!NT_SUCCESS(ntStatus))
{
// try again with the opposite cacheability attribute
protect ^= PAGE_NOCACHE;
ntStatus = ZwMapViewOfSection(hMemory, hProcess, &virtualBaseAddress, zeroBits, mappedSize, &physicalBaseAddress, &mappedSize, inheritDisposition, allocationType, protect);
if(!NT_SUCCESS(ntStatus))
{
KdPrint(("AkenMapPhysicalMemory: 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;
}
}
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)
{
PAGED_CODE();
{
const HANDLE hProcess = (HANDLE)-1;
PVOID baseAddress = (PVOID)virtualAddress;
NTSTATUS ntStatus = ZwUnmapViewOfSection(hProcess, baseAddress);
if(!NT_SUCCESS(ntStatus))
{
KdPrint(("AkenUnmapPhysicalMemory: ZwUnmapViewOfSection failed\n"));
return ntStatus;
}
}
return STATUS_SUCCESS;
}
//-----------------------------------------------------------------------------
// helper functions called from DeviceControl
//-----------------------------------------------------------------------------
static NTSTATUS AkenIoctlReadPort(PVOID buf, const ULONG inSize, ULONG* outSize)
{
DWORD32 value;
PAGED_CODE();
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;
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;
}
}
{
AkenReadPortOut* out = (AkenReadPortOut*)buf;
out->value = value;
}
return STATUS_SUCCESS;
}
static NTSTATUS AkenIoctlWritePort(PVOID buf, const ULONG inSize, ULONG* outSize)
{
PAGED_CODE();
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;
}
}
return STATUS_SUCCESS;
}
static NTSTATUS AkenIoctlMap(PVOID buf, const ULONG inSize, ULONG* outSize)
{
DWORD64 virtualAddress;
NTSTATUS ntStatus;
PAGED_CODE();
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;
ntStatus = AkenMapPhysicalMemory(physicalAddress, numBytes, &virtualAddress);
}
{
AkenMapOut* out = (AkenMapOut*)buf;
out->virtualAddress = virtualAddress;
}
return ntStatus;
}
static NTSTATUS AkenIoctlUnmap(PVOID buf, const ULONG inSize, ULONG* outSize)
{
NTSTATUS ntStatus;
PAGED_CODE();
if(inSize != sizeof(AkenUnmapIn) || *outSize != 0)
return STATUS_BUFFER_TOO_SMALL;
{
const AkenUnmapIn* in = (const AkenUnmapIn*)buf;
const DWORD64 virtualAddress = in->virtualAddress;
ntStatus = AkenUnmapPhysicalMemory(virtualAddress);
}
return ntStatus;
}
static NTSTATUS AkenIoctlReadModelSpecificRegister(PVOID buf, const ULONG inSize, ULONG* outSize)
{
DWORD64 value;
PAGED_CODE();
if(inSize != sizeof(AkenReadRegisterIn) || *outSize != sizeof(AkenReadRegisterOut))
return STATUS_BUFFER_TOO_SMALL;
{
const AkenReadRegisterIn* in = (const AkenReadRegisterIn*)buf;
const DWORD64 reg = in->reg;
value = __readmsr((int)reg);
}
{
AkenReadRegisterOut* out = (AkenReadRegisterOut*)buf;
out->value = value;
}
return STATUS_SUCCESS;
}
static NTSTATUS AkenIoctlWriteModelSpecificRegister(PVOID buf, const ULONG inSize, ULONG* outSize)
{
PAGED_CODE();
if(inSize != sizeof(AkenWriteRegisterIn) || *outSize != 0)
return STATUS_BUFFER_TOO_SMALL;
{
const AkenWriteRegisterIn* in = (const AkenWriteRegisterIn*)buf;
const DWORD64 reg = in->reg;
const DWORD64 value = in->value;
__writemsr((unsigned long)reg, value);
}
return STATUS_SUCCESS;
}
static NTSTATUS AkenIoctlReadPerformanceMonitoringCounter(PVOID buf, const ULONG inSize, ULONG* outSize)
{
DWORD64 value;
PAGED_CODE();
if(inSize != sizeof(AkenReadRegisterIn) || *outSize != sizeof(AkenReadRegisterOut))
return STATUS_BUFFER_TOO_SMALL;
{
const AkenReadRegisterIn* in = (const AkenReadRegisterIn*)buf;
const DWORD64 reg = in->reg;
value = __readpmc((unsigned long)reg);
}
{
AkenReadRegisterOut* out = (AkenReadRegisterOut*)buf;
out->value = value;
}
return STATUS_SUCCESS;
}
static NTSTATUS AkenIoctlUnknown(PVOID buf, const ULONG inSize, ULONG* outSize)
{
PAGED_CODE();
KdPrint(("AkenIoctlUnknown\n"));
*outSize = 0;
return STATUS_INVALID_DEVICE_REQUEST;
}
typedef NTSTATUS (*AkenIoctl)(PVOID buf, ULONG inSize, ULONG* outSize);
static AkenIoctl AkenIoctlFromCode(ULONG ioctlCode)
{
PAGED_CODE();
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;
case IOCTL_AKEN_READ_MSR:
return AkenIoctlReadModelSpecificRegister;
case IOCTL_AKEN_WRITE_MSR:
return AkenIoctlWriteModelSpecificRegister;
default:
return AkenIoctlUnknown;
}
}
//-----------------------------------------------------------------------------
// entry points
//-----------------------------------------------------------------------------
static NTSTATUS AkenCreate(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
{
PAGED_CODE();
irp->IoStatus.Status = STATUS_SUCCESS;
irp->IoStatus.Information = 0;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
static NTSTATUS AkenClose(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
{
PAGED_CODE();
// same as AkenCreate ATM
irp->IoStatus.Status = STATUS_SUCCESS;
irp->IoStatus.Information = 0;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
static NTSTATUS AkenDeviceControl(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
{
PAGED_CODE();
{
// 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 AkenUnload(IN PDRIVER_OBJECT driverObject)
{
PAGED_CODE();
KdPrint(("AkenUnload\n"));
{
UNICODE_STRING win32Name = RTL_CONSTANT_STRING(WIN32_NAME);
IoDeleteSymbolicLink(&win32Name);
}
if(driverObject->DeviceObject)
IoDeleteDevice(driverObject->DeviceObject);
}
#pragma code_seg(pop) // make sure we don't countermand the alloc_text
NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath)
{
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(("DriverEntry: IoCreateDevice failed\n"));
return ntStatus;
}
}
// set entry points
driverObject->MajorFunction[IRP_MJ_CREATE] = AkenCreate;
driverObject->MajorFunction[IRP_MJ_CLOSE] = AkenClose;
driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = AkenDeviceControl;
driverObject->DriverUnload = AkenUnload;
// 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(("DriverEntry: IoCreateSymbolicLink failed\n"));
IoDeleteDevice(deviceObject);
return ntStatus;
}
}
return STATUS_SUCCESS;
}

View File

@ -1,116 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Aken driver interface
*/
// 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 L"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)
#define IOCTL_AKEN_READ_MSR CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+4, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_AKEN_WRITE_MSR CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+5, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_AKEN_READ_PMC CTL_CODE(FILE_DEVICE_AKEN, AKEN_IOCTL+6, METHOD_BUFFERED, FILE_ANY_ACCESS)
// input and output data structures for the IOCTLs
#pragma pack(push, 1)
typedef struct AkenReadPortIn_
{
USHORT port;
UCHAR numBytes;
}
AkenReadPortIn;
typedef struct AkenReadPortOut_
{
DWORD32 value;
}
AkenReadPortOut;
typedef struct AkenWritePortIn_
{
DWORD32 value;
USHORT port;
UCHAR numBytes;
}
AkenWritePortIn;
typedef 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;
}
AkenMapIn;
typedef struct AkenMapOut_
{
DWORD64 virtualAddress;
}
AkenMapOut;
typedef struct AkenUnmapIn_
{
DWORD64 virtualAddress;
}
AkenUnmapIn;
typedef struct AkenReadRegisterIn_
{
DWORD64 reg;
}
AkenReadRegisterIn;
typedef struct AkenReadRegisterOut_
{
DWORD64 value;
}
AkenReadRegisterOut;
typedef struct AkenWriteRegisterIn_
{
DWORD64 reg;
DWORD64 value;
}
AkenWriteRegisterIn;
#pragma pack(pop)
#endif // #ifndef INCLUDED_AKEN

View File

@ -1,87 +0,0 @@
@ECHO OFF
"%systemroot%\system32\cacls.exe" "%systemroot%\system32\config\system" >nul 2>&1
IF ERRORLEVEL 1 GOTO relaunch
REM detect whether OS is 32/64 bit
IF "%ProgramW6432%" == "%ProgramFiles%" (
SET aken_bits=64
) ELSE (
SET aken_bits=32
)
IF "%1" == "enabletest" GOTO enabletest
IF "%1" == "disabletest" GOTO disabletest
IF "%1" == "install" GOTO install
IF "%1" == "remove" GOTO remove
GOTO usage
:enabletest
bcdedit.exe /set TESTSIGNING ON
GOTO end
:disabletest
bcdedit.exe /set TESTSIGNING OFF
GOTO end
:install
IF (%2) == () (
SET aken_path="%~p0\aken%aken_bits%.sys"
) ELSE (
echo %2\aken%aken_bits%.sys
SET aken_path=%2\aken%aken_bits%.sys
)
echo %aken_path%
IF NOT EXIST %aken_path% GOTO notfound
sc create Aken DisplayName= Aken type= kernel start= auto binpath= %aken_path%
REM error= normal is default
IF ERRORLEVEL 1 GOTO failed
sc start Aken
IF ERRORLEVEL 1 GOTO failed
ECHO Success!
GOTO end
:remove
sc stop Aken
sc delete Aken
IF ERRORLEVEL 1 GOTO failed
ECHO Success! (The previous line should read: [SC] DeleteService SUCCESS)
GOTO end
:usage
ECHO To install the driver, please first enable test mode:
ECHO %0 enabletest
ECHO (This is necessary because Vista/Win7 x64 require signing with
ECHO a Microsoft "cross certificate". The Fraunhofer code signing certificate
ECHO is not enough, even though its chain of trust is impeccable.
ECHO Going the WHQL route, perhaps as an "unclassified" driver, might work.
ECHO see http://www.freeotfe.org/docs/Main/impact_of_kernel_driver_signing.htm )
ECHO Then reboot (!) and install the driver:
ECHO %0 install ["path_to_directory_containing_aken*.sys"]
ECHO (If no path is given, we will use the directory of this batch file)
ECHO To remove the driver and disable test mode, execute the following:
ECHO %0 remove
ECHO %0 disabletest
PAUSE
GOTO end
:relaunch
SET aken_vbs="%temp%\aken_run.vbs"
ECHO Set UAC = CreateObject^("Shell.Application"^) > %aken_vbs%
ECHO UAC.ShellExecute "cmd.exe", "/k %~s0 %1 %2", "", "runas", 1 >> %aken_vbs%
ECHO "To re-run this batch file as admin, we have created %aken_vbs% with the following contents:"
type %aken_vbs%
PAUSE
cscript //Nologo %aken_vbs%
DEL %aken_vbs%
GOTO end
:notfound
ECHO Driver not found at specified path (%aken_path%)
GOTO end
:failed
ECHO Something went wrong -- see previous line
GOTO end
:end

View File

@ -1,32 +0,0 @@
@echo off
REM ensure header is up to date (simpler than setting include path)
copy ..\..\..\..\..\include\lib\sysdep\os\win\aken\aken.h
cmd /c build_single.bat chk x64
cmd /c build_single.bat fre x64
cmd /c build_single.bat chk x86
REM must come last because each build_single.bat deletes aken.sys,
REM and that is the final output name of this step
cmd /c build_single.bat fre x86
cd amd64
copy /y aken64*.pdb ..\..\..\..\..\..\..\bin\x64
copy /y aken64*.sys ..\..\..\..\..\..\..\bin\x64
cd ..
cd i386
copy /y aken*.pdb ..\..\..\..\..\..\..\bin\Win32
copy /y aken*.sys ..\..\..\..\..\..\..\bin\Win32
cd ..
echo outputs copied to bin directory; will delete ALL output files after pressing a key
pause
if exist amd64 (rmdir /S /Q amd64)
if exist i386 (rmdir /S /Q i386)
if exist objchk_wnet_amd64 (rmdir /S /Q objchk_wnet_amd64)
if exist objfre_wnet_amd64 (rmdir /S /Q objfre_wnet_amd64)
if exist objchk_wnet_x86 (rmdir /S /Q objchk_wnet_x86)
if exist objfre_wnet_x86 (rmdir /S /Q objfre_wnet_x86)
if exist *.log (del /Q *.log)
if exist *.err (del /Q *.err)
if exist *.wrn (del /Q *.wrn)

View File

@ -1,67 +0,0 @@
@echo off
REM build a single configuration ({chk,fre} x {x64,x64})
REM (must be in a separate file to allow invoking in a new cmd - otherwise,
REM setenv complains that the environment have already been set)
REM arguments: chk|fre x64|x86
if %1==chk (goto configOK)
if %1==fre (goto configOK)
echo first parameter must be either chk or fre
goto :eof
:configOK
if %2==x64 (goto archOK)
if %2==x86 (goto archOK)
echo second parameter must be either x64 or x64
goto :eof
:archOK
call C:\WinDDK\7600.16385.1\bin\setenv.bat C:\WinDDK\7600.16385.1\ %1 %2 WNET
e:
cd \FOM_Work\Programmierung\lowlevel\src\sysdep\os\win\aken
REM delete outputs to ensure they get rebuilt
if %2==x64 (goto delete64) else (goto delete32)
:delete64
if not exist amd64 (goto deleteEnd)
cd amd64
if exist aken.sys (del /Q aken.sys)
if exist aken.pdb (del /Q aken.pdb)
cd ..
goto deleteEnd
:delete32
if not exist i386 (goto deleteEnd)
cd i386
if exist aken.sys (del /Q aken.sys)
if exist aken.pdb (del /Q aken.pdb)
cd ..
goto deleteEnd
:deleteEnd
build
REM rename outputs in preparation for build_all's copying them to the binaries directories
if %2==x64 (goto rename64) else (goto rename32)
:rename64
if not exist amd64 (goto renameEnd)
cd amd64
if %1==chk (ren aken.pdb aken64d.pdb) else (ren aken.pdb aken64.pdb)
if %1==chk (ren aken.sys aken64d.sys) else (ren aken.sys aken64.sys)
cd ..
goto renameEnd
:rename32
if not exist i386 (goto renameEnd)
cd i386
if %1==chk (ren aken.pdb akend.pdb)
if %1==chk (ren aken.sys akend.sys)
cd ..
goto renameEnd
:renameEnd

View File

@ -1,7 +0,0 @@
#
# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
# file to this component. This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#
!INCLUDE $(NTMAKEENV)\makefile.def

View File

@ -1,5 +0,0 @@
TARGETNAME=aken
TARGETPATH=.
TARGETTYPE=DRIVER
SOURCES=aken.c

View File

@ -1,398 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* user-mode interface to Aken driver
*/
#include "precompiled.h"
#include "lib/sysdep/os/win/mahaf.h"
#include "lib/config2.h"
#include "lib/module_init.h"
#include "lib/sysdep/os/win/wutil.h"
#include <winioctl.h>
#include "lib/sysdep/os/win/aken/aken.h"
#include "lib/sysdep/os/win/wversion.h"
static HANDLE hAken = INVALID_HANDLE_VALUE; // 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
const BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_READ_PORT, &in, sizeof(in), &out, sizeof(out), &bytesReturned, ovl);
WARN_RETURN_0_IF_FALSE(ok);
ENSURE(bytesReturned == sizeof(out));
return out.value;
}
u8 mahaf_ReadPort8(u16 port)
{
const u32 value = ReadPort(port, 1);
ENSURE(value <= 0xFF);
return (u8)(value & 0xFF);
}
u16 mahaf_ReadPort16(u16 port)
{
const u32 value = ReadPort(port, 2);
ENSURE(value <= 0xFFFF);
return (u16)(value & 0xFFFF);
}
u32 mahaf_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);
WARN_IF_FALSE(ok);
}
void mahaf_WritePort8(u16 port, u8 value)
{
WritePort(port, (u32)value, 1);
}
void mahaf_WritePort16(u16 port, u16 value)
{
WritePort(port, (u32)value, 2);
}
void mahaf_WritePort32(u16 port, u32 value)
{
WritePort(port, value, 4);
}
bool mahaf_IsPhysicalMappingDangerous()
{
// pre-XP versions don't prevent re-mapping pages with incompatible
// attributes, which may lead to disaster due to TLB corruption.
if(wversion_Number() < WVERSION_XP)
return true;
return false;
}
volatile void* mahaf_MapPhysicalMemory(uintptr_t physicalAddress, size_t numBytes)
{
ENSURE(!mahaf_IsPhysicalMappingDangerous());
AkenMapIn in;
in.physicalAddress = (DWORD64)physicalAddress;
in.numBytes = (DWORD64)numBytes;
AkenMapOut out;
DWORD bytesReturned;
LPOVERLAPPED ovl = 0; // synchronous
const BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_MAP, &in, sizeof(in), &out, sizeof(out), &bytesReturned, ovl);
WARN_RETURN_0_IF_FALSE(ok);
ENSURE(bytesReturned == sizeof(out));
volatile void* virtualAddress = (volatile void*)(uintptr_t)out.virtualAddress;
return virtualAddress;
}
void mahaf_UnmapPhysicalMemory(volatile void* virtualAddress)
{
ENSURE(!mahaf_IsPhysicalMappingDangerous());
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);
WARN_IF_FALSE(ok);
}
static u64 ReadRegister(DWORD ioctl, u64 reg)
{
AkenReadRegisterIn in;
in.reg = reg;
AkenReadRegisterOut out;
DWORD bytesReturned;
LPOVERLAPPED ovl = 0; // synchronous
const BOOL ok = DeviceIoControl(hAken, ioctl, &in, sizeof(in), &out, sizeof(out), &bytesReturned, ovl);
WARN_RETURN_0_IF_FALSE(ok);
ENSURE(bytesReturned == sizeof(out));
return out.value;
}
u64 mahaf_ReadModelSpecificRegister(u64 reg)
{
return ReadRegister((DWORD)IOCTL_AKEN_READ_MSR, reg);
}
u64 mahaf_ReadPerformanceMonitoringCounter(u64 reg)
{
return ReadRegister((DWORD)IOCTL_AKEN_READ_PMC, reg);
}
void mahaf_WriteModelSpecificRegister(u64 reg, u64 value)
{
AkenWriteRegisterIn in;
in.reg = reg;
in.value = value;
DWORD bytesReturned; // unused but must be passed to DeviceIoControl
LPOVERLAPPED ovl = 0; // synchronous
BOOL ok = DeviceIoControl(hAken, (DWORD)IOCTL_AKEN_WRITE_MSR, &in, sizeof(in), 0, 0u, &bytesReturned, ovl);
WARN_IF_FALSE(ok);
}
//-----------------------------------------------------------------------------
// driver installation
//-----------------------------------------------------------------------------
// @param access need not include SC_MANAGER_CONNECT ("implicitly specified")
static SC_HANDLE OpenServiceControlManager(DWORD access)
{
LPCWSTR machineName = 0; // local
LPCWSTR databaseName = 0; // default
SC_HANDLE hSCM = OpenSCManagerW(machineName, databaseName, access);
if(!hSCM)
{
// ensure no other problems arose
ENSURE(GetLastError() == ERROR_ACCESS_DENIED);
// administrator privileges are required for SC_MANAGER_CREATE_SERVICE.
// this is a problem on Vista / Win7, so users will have to use the
// separate aken_install.bat
return 0;
}
return hSCM; // success
}
static void UninstallDriver()
{
SC_HANDLE hSCM = OpenServiceControlManager(SC_MANAGER_ENUMERATE_SERVICE);
if(!hSCM)
return;
SC_HANDLE hService = OpenServiceW(hSCM, AKEN_NAME, SERVICE_STOP|SERVICE_INTERROGATE);
if(!hService)
return;
// stop service
SERVICE_STATUS serviceStatus;
if(!ControlService(hService, SERVICE_CONTROL_STOP, &serviceStatus))
{
// if the problem wasn't that the service is already stopped,
// something actually went wrong.
const DWORD err = GetLastError();
ENSURE(err == ERROR_SERVICE_NOT_ACTIVE || err == ERROR_SERVICE_CANNOT_ACCEPT_CTRL);
}
// delete service
BOOL ok;
ok = DeleteService(hService);
WARN_IF_FALSE(ok);
ok = CloseServiceHandle(hService);
WARN_IF_FALSE(ok);
ok = CloseServiceHandle(hSCM);
WARN_IF_FALSE(ok);
}
#if CONFIG2_MAHAF_ATTEMPT_DRIVER_START
static void StartDriver(const OsPath& driverPathname)
{
const SC_HANDLE hSCM = OpenServiceControlManager(SC_MANAGER_CREATE_SERVICE);
if(!hSCM)
{
ENSURE(GetLastError() == ERROR_ACCESS_DENIED);
SetLastError(0);
return;
}
SC_HANDLE hService = OpenServiceW(hSCM, AKEN_NAME, SERVICE_START);
// during development, we want to ensure the newest build is used, so
// unload and re-create the service if it's running/installed.
// as of 2008-03-24 no further changes to Aken are pending, so this is
// disabled (thus also avoiding trouble when running multiple instances)
#if 0
if(hService)
{
BOOL ok = CloseServiceHandle(hService);
WARN_IF_FALSE(ok);
hService = 0;
UninstallDriver();
}
#endif
// create service (note: this just enters the service into SCM's DB;
// no error is raised if the driver binary doesn't exist etc.)
if(!hService)
{
LPCWSTR startName = 0; // LocalSystem
// NB: Windows 7 seems to insist upon backslashes (i.e. external_file_string)
hService = CreateServiceW(hSCM, AKEN_NAME, AKEN_NAME,
SERVICE_START, SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
OsString(driverPathname).c_str(), 0, 0, 0, startName, 0);
ENSURE(hService != 0);
}
{
DWORD numArgs = 0;
BOOL ok = StartService(hService, numArgs, 0);
if(!ok)
{
switch(GetLastError())
{
case ERROR_SERVICE_ALREADY_RUNNING:
// ok, no action needed
break;
case ERROR_ACCESS_DENIED:
// Win7, can't start service; must use aken_install.bat
break;
case ERROR_INVALID_IMAGE_HASH:
// Win7 x86 rejects our code signing certificate; must enable
// "test signing" mode via aken_install.bat
break;
default:
// unexpected problem
DEBUG_WARN_ERR(ERR::LOGIC);
break;
}
}
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}
static bool Is64BitOs()
{
#if OS_WIN64
return true;
#else
return wutil_IsWow64();
#endif
}
static OsPath DriverPathname()
{
const char* const bits = Is64BitOs()? "64" : "";
#ifdef NDEBUG
const char* const debug = "";
#else
const char* const debug = "d";
#endif
char filename[PATH_MAX];
sprintf_s(filename, ARRAY_SIZE(filename), "aken%s%s.sys", bits, debug);
return wutil_ExecutablePath() / filename;
}
#endif // CONFIG2_MAHAF_ATTEMPT_DRIVER_START
//-----------------------------------------------------------------------------
static Status Init()
{
WinScopedPreserveLastError s;
if(wutil_HasCommandLineArgument(L"-wNoMahaf"))
return ERR::NOT_SUPPORTED; // NOWARN
#if CONFIG2_MAHAF_ATTEMPT_DRIVER_START
{
const OsPath driverPathname = DriverPathname();
StartDriver(driverPathname);
}
#endif
{
const DWORD shareMode = 0;
hAken = CreateFileW(L"\\\\.\\Aken", GENERIC_READ, shareMode, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(hAken == INVALID_HANDLE_VALUE)
{
// GetLastError() is ERROR_FILE_NOT_FOUND if the driver isn't running,
// but this is also reached when a handle has already been opened
// (e.g. by a second instance of the same program) - in which case we must
// indicate failure so that clients won't engage in unsynchronized ring 0 operations.
SetLastError(0);
return ERR::INVALID_HANDLE; // NOWARN (happens often due to security restrictions)
}
}
return INFO::OK;
}
static void Shutdown()
{
CloseHandle(hAken);
hAken = INVALID_HANDLE_VALUE;
UninstallDriver();
}
static ModuleInitState initState;
Status mahaf_Init()
{
return ModuleInit(&initState, Init);
}
void mahaf_Shutdown()
{
ModuleShutdown(&initState, Shutdown);
}

View File

@ -1,64 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* user-mode interface to Aken driver
*/
// Mahaf - ferryman in Egyptian mythology that wakes up Aken,
// and the interface to the Aken driver.
#ifndef INCLUDED_MAHAF
#define INCLUDED_MAHAF
/**
* @return whether mapping physical memory is known to be dangerous
* on this platform.
*
* callable before or after mahaf_Init.
*
* note: mahaf_MapPhysicalMemory will complain if it
* is called despite this function having returned true.
**/
LIB_API bool mahaf_IsPhysicalMappingDangerous();
LIB_API Status mahaf_Init();
LIB_API void mahaf_Shutdown();
LIB_API u8 mahaf_ReadPort8 (u16 port);
LIB_API u16 mahaf_ReadPort16(u16 port);
LIB_API u32 mahaf_ReadPort32(u16 port);
LIB_API void mahaf_WritePort8 (u16 port, u8 value);
LIB_API void mahaf_WritePort16(u16 port, u16 value);
LIB_API void mahaf_WritePort32(u16 port, u32 value);
LIB_API volatile void* mahaf_MapPhysicalMemory(uintptr_t physicalAddress, size_t numBytes);
LIB_API void mahaf_UnmapPhysicalMemory(volatile void* virtualAddress);
LIB_API u64 mahaf_ReadModelSpecificRegister(u64 reg);
LIB_API void mahaf_WriteModelSpecificRegister(u64 reg, u64 value);
// must be done in the driver because Windows clears CR4.PCE[8]
LIB_API u64 mahaf_ReadPerformanceMonitoringCounter(u64 reg);
#endif // INCLUDED_MAHAF

View File

@ -1,117 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Interface for counter implementations
*/
#include "precompiled.h"
#include "lib/sysdep/os/win/whrt/counter.h"
#include "lib/alignment.h"
#include "lib/sysdep/cpu.h" // cpu_CAS
#include "lib/sysdep/os/win/whrt/tsc.h"
#include "lib/sysdep/os/win/whrt/hpet.h"
#include "lib/sysdep/os/win/whrt/pmt.h"
#include "lib/sysdep/os/win/whrt/qpc.h"
#include "lib/sysdep/os/win/whrt/tgt.h"
// to add a new counter type, simply include its header here and
// insert a case in ConstructCounterAt's switch statement.
//-----------------------------------------------------------------------------
// create/destroy counters
/**
* @param id
* @param address
* @param size Maximum allowable size [bytes] of the subclass instance
* @return pointer to a newly constructed ICounter subclass of type \<id\> at
* the given address, or 0 iff the ID is invalid.
**/
static ICounter* ConstructCounterAt(size_t id, void* address, size_t size)
{
// rationale for placement new: see call site.
// counters are chosen according to the following order. rationale:
// - TSC must come before QPC and PMT to make sure a bug in the latter on
// Pentium systems doesn't come up.
// - PMT works, but is inexplicably slower than QPC on a PIII Mobile.
// - TGT really isn't as safe as the others, so it should be last.
// - low-overhead and high-resolution counters are preferred.
switch(id)
{
case 0:
return CreateCounterHPET(address, size);
case 1:
return CreateCounterTSC(address, size);
case 2:
return CreateCounterQPC(address, size);
case 3:
return CreateCounterPMT(address, size);
case 4:
return CreateCounterTGT(address, size);
default:
return 0;
}
}
static volatile intptr_t isCounterAllocated;
ICounter* CreateCounter(size_t id)
{
// we placement-new the Counter classes in a static buffer.
// this is dangerous, but we are careful to ensure alignment. it is
// unusual and thus bad, but there's also one advantage: we avoid
// using global operator new before the CRT is initialized (risky).
//
// alternatives:
// - defining as static doesn't work because the ctors (necessary for
// vptr initialization) run during _cinit, which comes after our
// first use of them.
// - using static_calloc isn't possible because we don't know the
// size until after the alloc / placement new.
if(!cpu_CAS(&isCounterAllocated, 0, 1))
DEBUG_WARN_ERR(ERR::LOGIC); // static counter memory is already in use!
static const size_t memSize = 200;
static u8 mem[memSize];
u8* alignedMem = (u8*)Align<16>((uintptr_t)mem);
const size_t bytesLeft = mem+memSize - alignedMem;
ICounter* counter = ConstructCounterAt(id, alignedMem, bytesLeft);
return counter;
}
void DestroyCounter(ICounter*& counter)
{
ENSURE(counter);
counter->Shutdown();
counter->~ICounter(); // must be called due to placement new
counter = 0;
isCounterAllocated = 0;
}

View File

@ -1,91 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Interface for counter implementations
*/
#ifndef INCLUDED_COUNTER
#define INCLUDED_COUNTER
// derived implementations must be called CounterIMPL,
// where IMPL matches the WHRT_IMPL identifier. (see CREATE)
class ICounter
{
public:
// (compiled-generated) ctor only sets up the vptr
virtual ~ICounter() {}
virtual const char* Name() const = 0;
// Activate with an error return value is much cleaner+safer than
// throwing exceptions in the ctor.
virtual Status Activate() = 0;
virtual void Shutdown() = 0;
virtual bool IsSafe() const = 0;
/**
* @return the current value of the counter (all but the lower
* CounterBits() bits must be zero)
**/
virtual u64 Counter() const = 0;
// note: implementations need not cache the following; that's taken
// care of by WHRT.
/**
* @return the bit width of the counter (<= 64)
* WHRT uses this to ensure the counter (running at nominal frequency)
* doesn't overflow more than once during CALIBRATION_INTERVAL_MS.
**/
virtual size_t CounterBits() const = 0;
/**
* initial measurement of the tick rate. not necessarily correct
* (e.g. when using TSC: os_cpu_ClockFrequency isn't exact).
**/
virtual double NominalFrequency() const = 0;
/**
* actual resolution [s]. differs from 1/NominalFrequency if the
* timer adjustment is greater than 1 tick.
**/
virtual double Resolution() const = 0;
};
/**
* @return a newly created ICounter of type \<id\> or 0 iff the ID is invalid.
* @param id integer ID (0..N-1)
*
* there can only be one active counter at a time; the previous one must
* have been destroyed before creating another!
**/
extern ICounter* CreateCounter(size_t id);
/**
* shut down the counter, free its resources and zero its pointer.
**/
extern void DestroyCounter(ICounter*& counter);
#endif // #ifndef INCLUDED_COUNTER

View File

@ -1,233 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Timer implementation using High Precision Event Timer
*/
#include "precompiled.h"
#include "lib/sysdep/os/win/whrt/hpet.h"
// for atomic 64-bit read/write:
#define HAVE_X64_MOVD ARCH_AMD64 && (ICC_VERSION || MSC_VERSION >= 1500)
#if HAVE_X64_MOVD
# include <intrin.h>
#else
# include <emmintrin.h>
#endif
#include "lib/sysdep/os/win/whrt/counter.h"
#include "lib/sysdep/os/win/win.h"
#include "lib/sysdep/os/win/mahaf.h"
#include "lib/sysdep/acpi.h"
#include "lib/bits.h"
class CounterHPET : public ICounter
{
public:
CounterHPET()
: m_hpetRegisters(0)
{
}
virtual const char* Name() const
{
return "HPET";
}
Status Activate()
{
RETURN_STATUS_IF_ERR(MapRegisters(m_hpetRegisters));
RETURN_STATUS_IF_ERR(VerifyCapabilities(m_frequency, m_counterBits));
// start the counter (if not already running)
Write64(CONFIG, Read64(CONFIG)|1);
// note: to avoid interfering with any other users of the timer
// (e.g. Vista QPC), we don't reset the counter value to 0.
return INFO::OK;
}
void Shutdown()
{
if(m_hpetRegisters)
{
mahaf_UnmapPhysicalMemory((void*)m_hpetRegisters);
m_hpetRegisters = 0;
}
}
bool IsSafe() const
{
// the HPET having been created to address other timers' problems,
// it has no issues of its own.
return true;
}
u64 Counter() const
{
// notes:
// - Read64 is atomic and avoids race conditions.
// - 32-bit counters (m_counterBits == 32) still allow
// reading the whole register (the upper bits are zero).
return Read64(COUNTER_VALUE);
}
size_t CounterBits() const
{
return m_counterBits;
}
double NominalFrequency() const
{
return m_frequency;
}
double Resolution() const
{
return 1.0 / m_frequency;
}
private:
#pragma pack(push, 1)
struct HpetDescriptionTable
{
AcpiTable header;
u32 eventTimerBlockId;
AcpiGenericAddress baseAddress;
u8 sequenceNumber;
u16 minimumPeriodicTicks;
u8 attributes;
};
#pragma pack(pop)
enum RegisterOffsets
{
CAPS_AND_ID = 0x00,
CONFIG = 0x10,
COUNTER_VALUE = 0xF0,
MAX_OFFSET = 0x3FF
};
static Status MapRegisters(volatile void*& registers)
{
if(mahaf_IsPhysicalMappingDangerous())
return ERR::FAIL; // NOWARN (happens on Win2k)
RETURN_STATUS_IF_ERR(mahaf_Init()); // (fails without Administrator privileges)
const HpetDescriptionTable* hpet = (const HpetDescriptionTable*)acpi_GetTable("HPET");
if(!hpet)
return ERR::NOT_SUPPORTED; // NOWARN (HPET not reported by BIOS)
if(hpet->baseAddress.addressSpaceId != ACPI_AS_MEMORY)
return ERR::NOT_SUPPORTED; // NOWARN (happens on some BIOSes)
// hpet->baseAddress.accessSize is reserved
const uintptr_t address = uintptr_t(hpet->baseAddress.address);
ENSURE(address % 8 == 0); // "registers are generally aligned on 64-bit boundaries"
registers = mahaf_MapPhysicalMemory(address, MAX_OFFSET+1);
if(!registers)
WARN_RETURN(ERR::NO_MEM);
return INFO::OK;
}
// note: this is atomic even on 32-bit CPUs (Pentium MMX and
// above have a 64-bit data bus and MOVQ instruction)
u64 Read64(size_t offset) const
{
ENSURE(offset <= MAX_OFFSET);
ENSURE(offset % 8 == 0);
const uintptr_t address = uintptr_t(m_hpetRegisters)+offset;
const __m128i value128 = _mm_loadl_epi64((__m128i*)address);
#if HAVE_X64_MOVD
return _mm_cvtsi128_si64x(value128);
#else
__declspec(align(16)) u32 values[4];
_mm_store_si128((__m128i*)values, value128);
return u64_from_u32(values[1], values[0]);
#endif
}
void Write64(size_t offset, u64 value) const
{
ENSURE(offset <= MAX_OFFSET);
ENSURE(offset % 8 == 0);
ENSURE(offset != CAPS_AND_ID); // can't write to read-only registers
const uintptr_t address = uintptr_t(m_hpetRegisters)+offset;
#if HAVE_X64_MOVD
const __m128i value128 = _mm_cvtsi64x_si128(value);
#else
const __m128i value128 = _mm_set_epi32(0, 0, int(value >> 32), int(value & 0xFFFFFFFF));
#endif
_mm_storel_epi64((__m128i*)address, value128);
}
Status VerifyCapabilities(double& frequency, u32& counterBits) const
{
// AMD document 43366 indicates the clock generator that drives the
// HPET is "spread-capable". Wikipedia's frequency hopping article
// explains that this reduces electromagnetic interference.
// The AMD document recommends BIOS writers add SMM hooks for
// reporting the resulting slightly different frequency.
// This apparently requires calibration triggered when the HPET is
// accessed, during which the config register is -1. We'll wait
// about 1 ms (MMIO is expected to take at least 1 us) and
// then ensure the HPET timer period is within reasonable bounds.
u64 caps_and_id = Read64(CAPS_AND_ID);
for(size_t reps = 0; reps < 1000; reps++)
{
if(caps_and_id != ~u64(0)) // register seems valid
break;
caps_and_id = Read64(CAPS_AND_ID);
}
const u8 revision = (u8)bits(caps_and_id, 0, 7);
ENSURE(revision != 0); // "the value must NOT be 00h"
counterBits = (caps_and_id & Bit<u64>(13))? 64 : 32;
const u16 vendorID = (u16)bits(caps_and_id, 16, 31);
const u32 period_fs = (u32)bits(caps_and_id, 32, 63);
ENSURE(period_fs != 0); // "a value of 0 in this field is not permitted"
frequency = 1e15 / period_fs;
debug_printf("HPET: rev=%X vendor=%X bits=%d period=%08X freq=%g\n", revision, vendorID, counterBits, period_fs, frequency);
if(period_fs > 0x05F5E100) // 100 ns (spec guarantees >= 10 MHz)
return ERR::CORRUPTED; // avoid using HPET (e.g. if calibration was still in progress)
return INFO::OK;
}
volatile void* m_hpetRegisters;
double m_frequency;
u32 m_counterBits;
};
ICounter* CreateCounterHPET(void* address, size_t size)
{
ENSURE(sizeof(CounterHPET) <= size);
return new(address) CounterHPET();
}

View File

@ -1,33 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Timer implementation using High Precision Event Timer
*/
#ifndef INCLUDED_HPET
#define INCLUDED_HPET
class ICounter;
extern ICounter* CreateCounterHPET(void* address, size_t size);
#endif // #ifndef INCLUDED_HPET

View File

@ -1,41 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Timer implementation using 82C53/4 PIT
*/
#ifndef INCLUDED_PIT
#define INCLUDED_PIT
// note: we don't access the PIT for two reasons:
// - it rolls over every 55 ms (1.193 MHz, 16 bit) and would have to be
// read at least that often, which would require setting high thread
// priority (dangerous).
// - reading it is slow and cannot be done by two independent users
// (the second being QPC) since the counter value must be latched.
//
// there are enough other counters anway.
static const i64 PIT_FREQ = 1193182; // (= master oscillator frequency/12)
#endif // #ifndef INCLUDED_PIT

View File

@ -1,115 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Timer implementation using ACPI PM timer
*/
#include "precompiled.h"
#include "lib/sysdep/os/win/whrt/pmt.h"
#include "lib/sysdep/os/win/whrt/counter.h"
#include "lib/sysdep/os/win/win.h"
#include "lib/sysdep/acpi.h"
#include "lib/sysdep/os/win/mahaf.h"
#include "lib/bits.h"
static const u32 TMR_VAL_EXT = Bit<u32>(8); // FADT flags
//-----------------------------------------------------------------------------
class CounterPMT : public ICounter
{
public:
CounterPMT()
: m_portAddress(0xFFFF)
{
}
virtual const char* Name() const
{
return "PMT";
}
Status Activate()
{
// mahaf is needed for port I/O.
RETURN_STATUS_IF_ERR(mahaf_Init()); // (fails without Administrator privileges)
// (note: it's called FADT, but the signature is "FACP")
const FADT* fadt = (const FADT*)acpi_GetTable("FACP");
if(!fadt)
return ERR::NOT_SUPPORTED; // NOWARN (ACPI tables might not be available)
m_portAddress = u16_from_larger(fadt->pmTimerPortAddress);
return INFO::OK;
}
void Shutdown()
{
}
bool IsSafe() const
{
// the PMT has one issue: "Performance counter value may unexpectedly
// leap forward" (Q274323). This happens on some buggy Pentium-era
// systems under heavy PCI bus load. We are clever and observe that
// the TSC implementation would be used on such systems (because it
// has higher precedence and is safe on P5 CPUs), so the PMT is fine
// in general.
return true;
}
u64 Counter() const
{
return mahaf_ReadPort32(m_portAddress);
}
size_t CounterBits() const
{
// (see previous acpi_GetTable call)
const FADT* fadt = (const FADT*)acpi_GetTable("FACP");
ENSURE(fadt); // Activate made sure FADT is available
const size_t counterBits = (fadt->flags & TMR_VAL_EXT)? 32 : 24;
return counterBits;
}
double NominalFrequency() const
{
return (double)PMT_FREQ;
}
double Resolution() const
{
return 1.0 / PMT_FREQ;
}
private:
u16 m_portAddress;
};
ICounter* CreateCounterPMT(void* address, size_t size)
{
ENSURE(sizeof(CounterPMT) <= size);
return new(address) CounterPMT();
}

View File

@ -1,35 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Timer implementation using ACPI PM timer
*/
#ifndef INCLUDED_PMT
#define INCLUDED_PMT
static const i64 PMT_FREQ = 3579545; // (= master oscillator frequency/4)
class ICounter;
extern ICounter* CreateCounterPMT(void* address, size_t size);
#endif // #ifndef INCLUDED_PMT

View File

@ -1,157 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Timer implementation using QueryPerformanceCounter
*/
#include "precompiled.h"
#include "lib/sysdep/os/win/whrt/qpc.h"
#include "lib/sysdep/os/win/whrt/counter.h"
#include "lib/sysdep/os_cpu.h"
#include "lib/sysdep/os/win/win.h"
#include "lib/sysdep/os/win/wutil.h" // wutil_argv
#include "lib/sysdep/os/win/whrt/pit.h" // PIT_FREQ
#include "lib/sysdep/os/win/whrt/pmt.h" // PMT_FREQ
class CounterQPC : public ICounter
{
public:
CounterQPC()
: m_frequency(-1)
{
}
virtual const char* Name() const
{
return "QPC";
}
Status Activate()
{
// note: QPC is observed to be universally supported, but the API
// provides for failure, so play it safe.
LARGE_INTEGER qpcFreq, qpcValue;
const BOOL ok1 = QueryPerformanceFrequency(&qpcFreq);
const BOOL ok2 = QueryPerformanceCounter(&qpcValue);
if(!ok1 || !ok2)
WARN_RETURN(ERR::FAIL);
if(!qpcFreq.QuadPart || !qpcValue.QuadPart)
WARN_RETURN(ERR::FAIL);
m_frequency = (i64)qpcFreq.QuadPart;
return INFO::OK;
}
void Shutdown()
{
}
bool IsSafe() const
{
// note: we have separate modules that directly access some of the
// counters potentially used by QPC. disabling the redundant counters
// would be ugly (increased coupling). instead, we'll make sure our
// implementations could (if necessary) coexist with QPC, but it
// shouldn't come to that since only one counter is needed/used.
// the PIT is entirely safe (even if annoyingly slow to read)
if(m_frequency == PIT_FREQ)
return true;
// the PMT is generally safe (see discussion in CounterPmt::IsSafe),
// but older QPC implementations had problems with 24-bit rollover.
// "System clock problem can inflate benchmark scores"
// (http://www.lionbridge.com/bi/cont2000/200012/perfcnt.asp ; no longer
// online, nor findable in Google Cache / archive.org) tells of
// incorrect values every 4.6 seconds (i.e. 24 bits @ 3.57 MHz) unless
// the timer is polled in the meantime. fortunately, this is guaranteed
// by our periodic updates (which come at least that often).
if(m_frequency == PMT_FREQ)
return true;
// the TSC has been known to be buggy (even mentioned in MSDN). it is
// used on MP HAL systems and can be detected by comparing QPF with the
// CPU clock. we consider it unsafe unless the user promises (via
// command line) that it's patched and thus reliable on their system.
bool usesTsc = IsSimilarMagnitude((double)m_frequency, os_cpu_ClockFrequency());
// unconfirmed reports indicate QPC sometimes uses 1/3 of the
// CPU clock frequency, so check that as well.
usesTsc |= IsSimilarMagnitude((double)m_frequency, os_cpu_ClockFrequency()/3);
if(usesTsc)
{
const bool isTscSafe = wutil_HasCommandLineArgument(L"-wQpcTscSafe");
return isTscSafe;
}
// the HPET is reliable and used on Vista. it can't easily be recognized
// since its frequency is variable (the spec says > 10 MHz; the master
// 14.318 MHz oscillator is often used). considering frequencies in
// [10, 100 MHz) to be a HPET would be dangerous because it may actually
// be faster or RDTSC slower. we have to exclude all other cases and
// assume it's a HPET - and thus safe - if we get here.
return true;
}
u64 Counter() const
{
// fairly time-critical here, don't check the return value
// (IsSupported made sure it succeeded initially)
LARGE_INTEGER qpc_value;
(void)QueryPerformanceCounter(&qpc_value);
return qpc_value.QuadPart;
}
size_t CounterBits() const
{
// there are reports of incorrect rollover handling in the PMT
// implementation of QPC (see CounterPMT::IsSafe). however, other
// counters would be used on those systems, so it's irrelevant.
// we'll report the full 64 bits.
return 64;
}
double NominalFrequency() const
{
return (double)m_frequency;
}
double Resolution() const
{
return 1.0 / m_frequency;
}
private:
// used in several places and QPF is a bit slow+cumbersome.
// (i64 allows easier conversion to double)
i64 m_frequency;
};
ICounter* CreateCounterQPC(void* address, size_t size)
{
ENSURE(sizeof(CounterQPC) <= size);
return new(address) CounterQPC();
}

View File

@ -1,33 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Timer implementation using QueryPerformanceCounter
*/
#ifndef INCLUDED_QPC
#define INCLUDED_QPC
class ICounter;
extern ICounter* CreateCounterQPC(void* address, size_t size);
#endif // #ifndef INCLUDED_QPC

View File

@ -1,106 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Timer implementation using timeGetTime
*/
// note: WinMM is delay-loaded to avoid dragging it in when this timer
// implementation isn't used. (this is relevant because its startup is
// fairly slow)
#include "precompiled.h"
#include "lib/sysdep/os/win/whrt/tgt.h"
#include "lib/sysdep/os/win/whrt/counter.h"
#include "lib/sysdep/os/win/win.h"
#include <mmsystem.h>
#if MSC_VERSION
#pragma comment(lib, "winmm.lib")
#endif
// "Guidelines For Providing Multimedia Timer Support" claims that
// speeding the timer up to 2 ms has little impact, while 1 ms
// causes significant slowdown.
static const UINT PERIOD_MS = 2;
class CounterTGT : public ICounter
{
public:
virtual const char* Name() const
{
return "TGT";
}
Status Activate()
{
// note: timeGetTime is always available and cannot fail.
MMRESULT ret = timeBeginPeriod(PERIOD_MS);
ENSURE(ret == TIMERR_NOERROR);
return INFO::OK;
}
void Shutdown()
{
timeEndPeriod(PERIOD_MS);
}
bool IsSafe() const
{
// the only point of criticism is the possibility of falling behind
// due to lost interrupts. this can happen to any interrupt-based timer
// and some systems may lack a counter-based timer, so consider TGT
// 'safe'. note that it is still only chosen when all other timers fail.
return true;
}
u64 Counter() const
{
return timeGetTime();
}
size_t CounterBits() const
{
return 32;
}
double NominalFrequency() const
{
return 1000.0;
}
double Resolution() const
{
return PERIOD_MS*1e-3;
}
};
ICounter* CreateCounterTGT(void* address, size_t size)
{
ENSURE(sizeof(CounterTGT) <= size);
return new(address) CounterTGT();
}

View File

@ -1,33 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Timer implementation using timeGetTime
*/
#ifndef INCLUDED_TGT
#define INCLUDED_TGT
class ICounter;
extern ICounter* CreateCounterTGT(void* address, size_t size);
#endif // #ifndef INCLUDED_TGT

View File

@ -1,259 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Timer implementation using RDTSC
*/
#include "precompiled.h"
#include "lib/sysdep/os/win/whrt/tsc.h"
#include "lib/sysdep/os/win/whrt/counter.h"
#include "lib/bits.h"
#include "lib/sysdep/acpi.h"
#include "lib/sysdep/os_cpu.h"
#include "lib/sysdep/os/win/win.h"
#include "lib/sysdep/os/win/wutil.h"
#if ARCH_X86_X64
# include "lib/sysdep/arch/x86_x64/x86_x64.h" // x86_x64::rdtsc
# include "lib/sysdep/arch/x86_x64/topology.h"
# include "lib/sysdep/arch/x86_x64/msr.h"
#endif
//-----------------------------------------------------------------------------
static bool IsUniprocessor()
{
if(topology::NumPackages() != 1)
return false;
if(topology::CoresPerPackage() != 1)
return false;
return true;
}
static bool IsInvariantTSC()
{
#if ARCH_X86_X64
// (we no longer need to check x86_x64::Vendor - Intel and AMD
// agreed on the definition of this feature check)
x86_x64::CpuidRegs regs = { 0 };
regs.eax = 0x80000007;
if(x86_x64::cpuid(&regs))
{
// TSC is invariant across P-state, C-state, turbo, and
// stop grant transitions (e.g. STPCLK)
if(regs.edx & BIT(8))
return true;
}
#endif
return false;
}
static bool IsThrottlingPossible()
{
#if ARCH_X86_X64
x86_x64::CpuidRegs regs = { 0 };
switch(x86_x64::Vendor())
{
case x86_x64::VENDOR_INTEL:
if(x86_x64::Cap(x86_x64::CAP_TM_SCC) || x86_x64::Cap(x86_x64::CAP_EST))
return true;
break;
case x86_x64::VENDOR_AMD:
regs.eax = 0x80000007;
if(x86_x64::cpuid(&regs))
{
enum AmdPowerNowFlags
{
PN_FREQ_ID_CTRL = BIT(1),
PN_HW_THERMAL_CTRL = BIT(4),
PN_SW_THERMAL_CTRL = BIT(5)
};
if(regs.edx & (PN_FREQ_ID_CTRL|PN_HW_THERMAL_CTRL|PN_SW_THERMAL_CTRL))
return true;
}
break;
default:
break;
}
#endif
return false;
}
static bool IsSandyBridge()
{
if(x86_x64::Vendor() != x86_x64::VENDOR_INTEL)
return false;
if(x86_x64::Model() == x86_x64::MODEL_SANDY_BRIDGE)
return true;
if(x86_x64::Model() == x86_x64::MODEL_SANDY_BRIDGE_2)
return true;
return false;
}
//-----------------------------------------------------------------------------
class CounterTSC : public ICounter
{
public:
virtual const char* Name() const
{
return "TSC";
}
Status Activate()
{
#if ARCH_X86_X64
if(!x86_x64::Cap(x86_x64::CAP_TSC))
return ERR::NOT_SUPPORTED; // NOWARN (CPU doesn't support RDTSC)
#endif
return INFO::OK;
}
void Shutdown()
{
}
bool IsSafe() const
{
// using the TSC for timing is subject to a litany of
// potential problems, discussed below:
if(IsInvariantTSC())
return true;
// SMP or multi-core => counters are unsynchronized. both offset and
// drift could be solved by maintaining separate per-core
// counter states, but that requires atomic reads of the TSC and
// the current processor number.
//
// (otherwise, we have a subtle race condition: if preempted while
// reading the time and rescheduled on a different core, incorrect
// results may be returned, which would be unacceptable.)
//
// unfortunately this isn't possible without OS support or the
// as yet unavailable RDTSCP instruction => unsafe.
//
// (note: if the TSC is invariant, drift is no longer a concern.
// we could synchronize the TSC MSRs during initialization and avoid
// per-core counter state and the race condition mentioned above.
// however, we won't bother, since such platforms aren't yet widespread
// and would surely support the nice and safe HPET, anyway)
if(!IsUniprocessor())
return false;
const FADT* fadt = (const FADT*)acpi_GetTable("FACP");
if(fadt)
{
ENSURE(fadt->header.size >= sizeof(FADT));
// TSC isn't incremented in deep-sleep states => unsafe.
if(fadt->IsC3Supported())
return false;
// frequency throttling possible => unsafe.
if(fadt->IsDutyCycleSupported())
return false;
}
#if ARCH_X86_X64
// recent CPU:
//if(x86_x64::Generation() >= 7)
{
// note: 8th generation CPUs support C1-clock ramping, which causes
// drift on multi-core systems, but those were excluded above.
// in addition to frequency changes due to P-state transitions,
// we're also subject to STPCLK throttling. this happens when
// the chipset thinks the system is dangerously overheated; the
// OS isn't even notified. this may be rare, but could cause
// incorrect results => unsafe.
//return false;
}
#endif
// we're dealing with a single older CPU; the only problem there is
// throttling, i.e. changes to the TSC frequency. we don't want to
// disable this because it may be important for cooling. the OS
// initiates changes but doesn't notify us; jumps are too frequent
// and drastic to detect and account for => unsafe.
if(IsThrottlingPossible())
return false;
return true;
}
u64 Counter() const
{
return x86_x64::rdtsc();
}
size_t CounterBits() const
{
return 64;
}
double NominalFrequency() const
{
// WARNING: do not call x86_x64::ClockFrequency because it uses the
// HRT, which we're currently in the process of initializing.
// instead query CPU clock frequency via OS.
//
// note: even here, initial accuracy isn't critical because the
// clock is subject to thermal drift and would require continual
// recalibration anyway.
#if ARCH_X86_X64
if(MSR::IsAccessible() && MSR::HasPlatformInfo())
{
const i64 busFrequency = IsSandyBridge()? 100000000 : 133333333;
const u64 platformInfo = MSR::Read(MSR::PLATFORM_INFO);
const u8 maxNonTurboRatio = bits(platformInfo, 8, 15);
return double(maxNonTurboRatio) * busFrequency;
}
else
#endif
return os_cpu_ClockFrequency();
}
double Resolution() const
{
return 1.0 / NominalFrequency();
}
};
ICounter* CreateCounterTSC(void* address, size_t size)
{
ENSURE(sizeof(CounterTSC) <= size);
return new(address) CounterTSC();
}

View File

@ -1,33 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Timer implementation using RDTSC
*/
#ifndef INCLUDED_TSC
#define INCLUDED_TSC
class ICounter;
extern ICounter* CreateCounterTSC(void* address, size_t size);
#endif // #ifndef INCLUDED_TSC

View File

@ -1,313 +0,0 @@
/* Copyright (C) 2018 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Windows High Resolution Timer
*/
#include "precompiled.h"
#include "lib/sysdep/os/win/whrt/whrt.h"
#include <process.h> // _beginthreadex
#include "lib/sysdep/cpu.h"
#include "lib/sysdep/os/win/wutil.h"
#include "lib/sysdep/os/win/winit.h"
#include "lib/sysdep/acpi.h"
#include "lib/bits.h"
#include "lib/sysdep/os/win/whrt/counter.h"
WINIT_REGISTER_EARLY_INIT2(whrt_Init); // wutil -> whrt -> wtime
WINIT_REGISTER_LATE_SHUTDOWN(whrt_Shutdown);
namespace ERR
{
const Status WHRT_COUNTER_UNSAFE = 140000;
}
//-----------------------------------------------------------------------------
// choose best available safe counter
// (moved into a separate function to simplify error handling)
static inline Status ActivateCounter(ICounter* counter)
{
RETURN_STATUS_IF_ERR(counter->Activate());
if(!counter->IsSafe())
return ERR::WHRT_COUNTER_UNSAFE; // NOWARN (happens often)
return INFO::OK;
}
/**
* @return the newly created and unique instance of the next best counter
* that is deemed safe, or 0 if all have already been created.
**/
static ICounter* GetNextBestSafeCounter()
{
for(;;)
{
static size_t nextCounterId = 0;
ICounter* counter = CreateCounter(nextCounterId++);
if(!counter)
return 0; // tried all, none were safe
Status err = ActivateCounter(counter);
if(err == INFO::OK)
{
debug_printf("HRT: using name=%s freq=%f\n", counter->Name(), counter->NominalFrequency());
return counter; // found a safe counter
}
else
{
wchar_t buf[100];
debug_printf("HRT: activating %s failed: %s\n", counter->Name(), utf8_from_wstring(StatusDescription(err, buf, ARRAY_SIZE(buf))).c_str());
DestroyCounter(counter);
}
}
}
//-----------------------------------------------------------------------------
// counter that drives the timer
static ICounter* counter;
// (these counter properties are cached for efficiency and convenience:)
static double nominalFrequency;
static double resolution;
static size_t counterBits;
static u64 counterMask;
static void InitCounter()
{
// we used to support switching counters at runtime, but that's
// unnecessarily complex. it need and should only be done once.
ENSURE(counter == 0);
counter = GetNextBestSafeCounter();
ENSURE(counter != 0);
nominalFrequency = counter->NominalFrequency();
resolution = counter->Resolution();
counterBits = counter->CounterBits();
debug_printf("HRT: counter=%s freq=%g res=%g bits=%d\n", counter->Name(), nominalFrequency, resolution, counterBits);
// sanity checks
ENSURE(nominalFrequency >= 500.0-DBL_EPSILON);
ENSURE(resolution <= 2e-3);
ENSURE(8 <= counterBits && counterBits <= 64);
counterMask = bit_mask<u64>(counterBits);
}
static void ShutdownCounter()
{
DestroyCounter(counter);
}
static inline u64 Counter()
{
return counter->Counter();
}
/**
* @return difference [ticks], taking rollover into account.
* (time-critical, so it's not called through ICounter.)
**/
static inline u64 CounterDelta(u64 oldCounter, u64 newCounter)
{
return (newCounter - oldCounter) & counterMask;
}
double whrt_Resolution()
{
ENSURE(resolution != 0.0);
return resolution;
}
//-----------------------------------------------------------------------------
// timer state
// we're not going to bother calibrating the counter (i.e. measuring its
// current frequency by means of a second timer). rationale:
// - all counters except the TSC are stable and run at fixed frequencies;
// - it's not clear that any other HRT or the tick count would be useful
// as a stable time reference (if it were, we should be using it instead);
// - calibration would complicate the code (we'd have to make sure the
// secondary counter is safe and can co-exist with the primary).
/**
* stores all timer state shared between readers and the update thread.
* (must be POD because it's used before static ctors run.)
**/
struct TimerState
{
// value of the counter at last update.
u64 counter;
// total elapsed time [seconds] since first update.
// converted from tick deltas with the *then current* frequency
// (this enables calibration, which is currently not implemented,
// but leaving open the possibility costs nothing)
double time;
u8 padding[48];
};
// how do we detect when the old TimerState is no longer in use and can be
// freed? we use two static instances (avoids dynamic allocation headaches)
// and swap between them ('double-buffering'). it is assumed that all
// entered critical sections (the latching of TimerState fields) will have
// been exited before the next update comes around; if not, TimerState.time
// changes, the critical section notices and re-reads the new values.
static __declspec(align(64)) TimerState timerStates[2];
// note: exchanging pointers is easier than XORing an index.
static volatile TimerState* volatile ts = &timerStates[0];
static volatile TimerState* volatile ts2 = &timerStates[1];
static void UpdateTimerState()
{
// how can we synchronize readers and the update thread? locks are
// preferably avoided since they're dangerous and can be slow. what we
// need to ensure is that TimerState doesn't change while another thread is
// accessing it. the first step is to linearize the update, i.e. have it
// appear to happen in an instant (done by building a new TimerState and
// having it go live by switching pointers). all that remains is to make
// reads of the state variables consistent, done by latching them all and
// retrying if an update came in the middle of this.
const u64 currentCounter = Counter();
const u64 deltaTicks = CounterDelta(ts->counter, currentCounter);
ts2->counter = currentCounter;
ts2->time = ts->time + deltaTicks/nominalFrequency;
ts = (volatile TimerState*)InterlockedExchangePointer((volatile PVOID*)&ts2, (PVOID)ts);
}
double whrt_Time()
{
// latch timer state (counter and time must be from the same update)
const volatile TimerState* state = ts;
return (state->time + CounterDelta(state->counter, Counter()) / nominalFrequency);
}
//-----------------------------------------------------------------------------
// update thread
// note: we used to discipline the HRT timestamp to the system time, so it
// was advantageous to trigger updates via WinMM event (thus reducing
// instances where we're called in the middle of a scheduler tick).
// since that's no longer relevant, we prefer using a thread, because that
// avoids the dependency on WinMM and its lengthy startup time.
// rationale: (+ and - are reasons for longer and shorter lengths)
// + minimize CPU usage
// + ensure all threads currently using TimerState return from those
// functions before the next interval
// - avoid more than 1 counter rollover per interval (InitUpdateThread makes
// sure our interval is shorter than the current counter's rollover rate)
static const DWORD UPDATE_INTERVAL_MS = 1000;
static HANDLE hExitEvent;
static HANDLE hUpdateThread;
static unsigned __stdcall UpdateThread(void* UNUSED(data))
{
debug_SetThreadName("whrt_UpdateThread");
for(;;)
{
const DWORD ret = WaitForSingleObject(hExitEvent, UPDATE_INTERVAL_MS);
// owner terminated or wait failed or exit event signaled - exit thread
if(ret != WAIT_TIMEOUT)
break;
UpdateTimerState();
}
return 0;
}
static inline Status InitUpdateThread()
{
WinScopedPreserveLastError s; // CreateEvent
// make sure our interval isn't too long
// (counterBits can be 64 => Bit() would overflow => calculate period/2)
const double period_2 = Bit<u64>(counterBits-1) / nominalFrequency;
const size_t rolloversPerInterval = size_t(UPDATE_INTERVAL_MS / i64(period_2*2.0*1000.0));
ENSURE(rolloversPerInterval <= 1);
hExitEvent = CreateEvent(0, TRUE, FALSE, 0); // manual reset, initially false
if(hExitEvent == INVALID_HANDLE_VALUE)
WARN_RETURN(ERR::LIMIT);
hUpdateThread = (HANDLE)_beginthreadex(0, 0, UpdateThread, 0, 0, 0);
if(!hUpdateThread)
WARN_RETURN(ERR::LIMIT);
return INFO::OK;
}
static inline void ShutdownUpdateThread()
{
// signal thread
BOOL ok = SetEvent(hExitEvent);
WARN_IF_FALSE(ok);
// the nice way is to wait for it to exit
if(WaitForSingleObject(hUpdateThread, 100) != WAIT_OBJECT_0)
TerminateThread(hUpdateThread, 0); // forcibly exit (dangerous)
CloseHandle(hExitEvent);
CloseHandle(hUpdateThread);
}
//-----------------------------------------------------------------------------
static Status whrt_Init()
{
InitCounter();
// latch initial counter value so that timer starts at 0
ts->counter = Counter(); // must come before UpdateTimerState
UpdateTimerState(); // must come before InitUpdateThread to avoid race
RETURN_STATUS_IF_ERR(InitUpdateThread());
return INFO::OK;
}
static Status whrt_Shutdown()
{
ShutdownUpdateThread();
ShutdownCounter();
acpi_Shutdown();
return INFO::OK;
}

View File

@ -1,33 +0,0 @@
/* Copyright (C) 2010 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Windows High Resolution Timer
*/
#ifndef INCLUDED_WHRT
#define INCLUDED_WHRT
extern double whrt_Resolution();
extern double whrt_Time();
#endif // #ifndef INCLUDED_WHRT

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -474,72 +474,6 @@ int sem_wait(sem_t* sem)
}
// helper function for sem_timedwait - multiple return is convenient.
// converts an absolute timeout deadline into a relative length for use with
// WaitForSingleObject with the following peculiarity: if the semaphore
// could be locked immediately, abs_timeout must be ignored (see SUS).
// to that end, we return a timeout of 0 and pass back <valid> = false if
// abs_timeout is invalid.
static DWORD calc_timeout_length_ms(const struct timespec* abs_timeout,
bool& timeout_is_valid)
{
timeout_is_valid = false;
if(!abs_timeout)
return 0;
// SUS requires we fail if not normalized
if(abs_timeout->tv_nsec >= 1000000000)
return 0;
struct timespec cur_time;
if(clock_gettime(CLOCK_REALTIME, &cur_time) != 0)
return 0;
timeout_is_valid = true;
// convert absolute deadline to relative length, rounding up to [ms].
// note: use i64 to avoid overflow in multiply.
const i64 ds = abs_timeout->tv_sec - cur_time.tv_sec;
const long dn = abs_timeout->tv_nsec - cur_time.tv_nsec;
i64 length_ms = ds*1000 + (dn+500000)/1000000;
// .. deadline already reached; we'll still attempt to lock once
if(length_ms < 0)
return 0;
// .. length > 49 days => result won't fit in 32 bits. most likely bogus.
// note: we're careful to avoid returning exactly -1 since
// that's the Win32 INFINITE value.
if(length_ms >= 0xFFFFFFFF)
{
WARN_IF_ERR(ERR::LIMIT);
length_ms = 0xfffffffe;
}
return (DWORD)(length_ms & 0xFFFFFFFF);
}
int sem_timedwait(sem_t* sem, const struct timespec* abs_timeout)
{
bool timeout_is_valid;
DWORD timeout_ms = calc_timeout_length_ms(abs_timeout, timeout_is_valid);
HANDLE h = HANDLE_from_sem_t(sem);
DWORD ret = WaitForSingleObject(h, timeout_ms);
// successfully decremented semaphore; bail.
if(ret == WAIT_OBJECT_0)
return 0;
// we're going to return -1. decide what happened:
// .. abs_timeout was invalid (must not check this before trying to lock)
if(!timeout_is_valid)
errno = EINVAL;
// .. timeout reached (not a failure)
else if(ret == WAIT_TIMEOUT)
errno = ETIMEDOUT;
return -1;
}
// wait until semaphore is locked or a message arrives. non-portable.
//
// background: on Win32, UI threads must periodically pump messages, or

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -24,15 +24,11 @@
* emulate POSIX time functionality on Windows.
*/
// note: clock_gettime et al. have been removed. callers should use the
// WHRT directly, rather than needlessly translating s -> ns -> s,
// which costs time and accuracy.
#include "precompiled.h"
#include "lib/sysdep/os/win/wposix/wtime.h"
#include "lib/sysdep/os/win/wposix/wposix_internal.h"
#include "lib/sysdep/os/win/whrt/whrt.h"
WINIT_REGISTER_MAIN_INIT(wtime_Init); // whrt -> wtime
@ -91,14 +87,6 @@ static void LatchInitialSystemTime()
stInitial_ns = (hns - posix_epoch_hns) * 100;
}
// return nanoseconds since POSIX epoch.
// algorithm: add current HRT value to the startup system time
static i64 CurrentSystemTime_ns()
{
const i64 ns = stInitial_ns + i64(whrt_Time() * _1e9);
return ns;
}
static timespec TimespecFromNs(i64 ns)
{
timespec ts;
@ -115,30 +103,6 @@ static size_t MsFromTimespec(const timespec& ts)
return size_t(ms);
}
//-----------------------------------------------------------------------------
int clock_gettime(clockid_t clock, struct timespec* ts)
{
ENSURE(clock == CLOCK_REALTIME || clock == CLOCK_MONOTONIC);
const i64 ns = CurrentSystemTime_ns();
*ts = TimespecFromNs(ns);
return 0;
}
int clock_getres(clockid_t clock, struct timespec* ts)
{
ENSURE(clock == CLOCK_REALTIME || clock == CLOCK_MONOTONIC);
const double resolution = whrt_Resolution();
const i64 ns = i64(resolution * 1e9);
*ts = TimespecFromNs(ns);
return 0;
}
int nanosleep(const struct timespec* rqtp, struct timespec* /* rmtp */)
{
const DWORD ms = (DWORD)MsFromTimespec(*rqtp);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -48,28 +48,7 @@ LIB_API int usleep(useconds_t us);
// <time.h>
//
typedef enum
{
// in our implementation, these actually do the same thing
// (a timer that latches the system time at startup and uses the
// monotonic HRT to add elapsed time since then)
CLOCK_REALTIME,
CLOCK_MONOTONIC
}
clockid_t;
// POSIX realtime clock_*
#if _MSC_VER < 1900
struct timespec
{
time_t tv_sec;
long tv_nsec;
};
#endif
extern int nanosleep(const struct timespec* rqtp, struct timespec* rmtp);
extern int clock_gettime(clockid_t clock, struct timespec* ts);
extern int clock_getres(clockid_t clock, struct timespec* res);
LIB_API char* strptime(const char* buf, const char* format, struct tm* timeptr);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2019 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -39,7 +39,7 @@
#include "lib/sysdep/cpu.h"
#if OS_WIN
# include "lib/sysdep/os/win/whrt/whrt.h"
#include "lib/sysdep/os/win/win.h"
#endif
#if OS_UNIX
# include <unistd.h>
@ -51,7 +51,7 @@
# define HAVE_GETTIMEOFDAY 0
#endif
#if (defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0) || OS_WIN
#if (defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0)
# define HAVE_CLOCK_GETTIME 1
#else
# define HAVE_CLOCK_GETTIME 0
@ -62,7 +62,9 @@
// timer values than their us / ns interface, via double [seconds].
// they're also not guaranteed to be monotonic.
#if HAVE_CLOCK_GETTIME
#if OS_WIN
static LARGE_INTEGER start;
#elif HAVE_CLOCK_GETTIME
static struct timespec start;
#elif HAVE_GETTIMEOFDAY
static struct timeval start;
@ -75,7 +77,7 @@ static struct timeval start;
void timer_LatchStartTime()
{
#if OS_WIN
// whrt_Time starts at zero, nothing needs to be done.
ENSURE(QueryPerformanceCounter(&start));
#elif HAVE_CLOCK_GETTIME
(void)clock_gettime(CLOCK_REALTIME, &start);
#elif HAVE_GETTIMEOFDAY
@ -94,13 +96,17 @@ static void EnsureMonotonic(double& newTime)
newTime = maxTime;
}
// Cached because the default implementation may take several milliseconds.
static double resolution;
double timer_Time()
{
double t;
#if OS_WIN
t = whrt_Time();
LARGE_INTEGER now;
ENSURE(QueryPerformanceCounter(&now));
t = static_cast<double>(now.QuadPart - start.QuadPart) * resolution;
#elif HAVE_CLOCK_GETTIME
ENSURE(start.tv_sec || start.tv_nsec); // must have called timer_LatchStartTime first
struct timespec cur;
@ -120,13 +126,12 @@ double timer_Time()
}
// cached because the default implementation may take several milliseconds
static double resolution;
static Status InitResolution()
{
#if OS_WIN
resolution = whrt_Resolution();
LARGE_INTEGER frequency;
ENSURE(QueryPerformanceFrequency(&frequency));
resolution = 1.0 / static_cast<double>(frequency.QuadPart);
#elif HAVE_CLOCK_GETTIME
struct timespec ts;
if(clock_getres(CLOCK_REALTIME, &ts) == 0)