blob: 5810b9e0ca4ee9860d22653480f05258de4f3586 [file] [log] [blame]
/*
* Copyright (c) 2012 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/*
* NaCl Server Runtime time abstraction layer.
* This is the host-OS-dependent implementation.
*/
/* Make sure that winmm.lib is added to the linker's input. */
#pragma comment(lib, "winmm.lib")
#include <windows.h>
#include <intrin.h>
#include <mmsystem.h>
#include <sys/timeb.h>
#include <time.h>
#include "native_client/src/include/nacl_macros.h"
#include "native_client/src/shared/platform/nacl_time.h"
#include "native_client/src/shared/platform/win/nacl_time_types.h"
#include "native_client/src/shared/platform/nacl_log.h"
#include "native_client/src/shared/platform/nacl_sync.h"
#include "native_client/src/shared/platform/nacl_sync_checked.h"
#include "native_client/src/shared/platform/win/xlate_system_error.h"
#define kMillisecondsPerSecond 1000
#define kMicrosecondsPerMillisecond 1000
#define kMicrosecondsPerSecond 1000000
#define kCalibrateTimeoutMs 1000
#define kCoarseMks 10
/*
* Here's some usefull links related to timing on Windows:
* http://svn.wildfiregames.com/public/ps/trunk/docs/timing_pitfalls.pdf
* Rob Arnold (mozilla) blog:
* http://robarnold.org/measuring-performance/
* https://bugzilla.mozilla.org/show_bug.cgi?id=563082
*/
/*
* To get a 1ms resolution time-of-day clock, we have to jump thrugh
* some hoops. This approach has the following defect: ntp
* adjustments will be nullified, until the difference is larger than
* a recalibration threshold (below), at which point time may jump
* (until we introduce a mechanism to slowly phase in time changes).
* A system suspend/resume will generate a clock recalibration with
* high probability.
*
* The algorithm is as follows:
*
* At start up, we sample the system time (coarse timer) via
* GetSystemTimeAsFileTime until it changes. At the state transition,
* we record the value of the time-after-boot counter via timeGetTime.
*
* At subsequent gettimeofday syscall, we query the system time via
* GetSystemTimeAsFileTime. This is converted to an
* expected/corresponding return value from timeGetTime. The
* difference from the actual return value is the time that elapsed
* since the state transition of the system time counter, and we add
* that difference (assuming it is smaller than the max-drift
* threshold) to the system time and return that (minus the Unix
* epoch, etc), as the result of the gettimeofday syscall. If the
* difference is larger than the max-drift threshold, we invoke the
* calibration function.
*/
struct NaClTimeState g_NaCl_time_state;
static uint32_t const kMaxMillsecondDriftBeforeRecalibration = 60;
static uint32_t const kMaxCalibrationDiff = 4;
/*
* Translate Windows system time to milliseconds. Windows System time
* is in units of 100nS. Windows time is in 100e-9s == 1e-7s, and we
* want units of 1e-3s, so ms/wt = 1e-4.
*/
static uint64_t NaClFileTimeToMs(FILETIME *ftp) {
ULARGE_INTEGER t;
t.u.HighPart = ftp->dwHighDateTime;
t.u.LowPart = ftp->dwLowDateTime;
return t.QuadPart / NACL_100_NANOS_PER_MILLI;
}
#define kCpuInfoPageSize 4 /* Not in bytes, but in sizeof(uint32_t). */
static char* CPU_GetBrandString(void) {
const int kBaseInfoPage = 0x80000000;
const int kCpuBrandStringPage1 = 0x80000002;
const int kCpuBrandStringPage2 = 0x80000003;
const int kCpuBrandStringPage3 = 0x80000004;
static int cpu_info[kCpuInfoPageSize * 4] = {0};
__cpuid(cpu_info, kBaseInfoPage);
if (cpu_info[0] < kCpuBrandStringPage3)
return "";
__cpuid(&cpu_info[0], kCpuBrandStringPage1);
__cpuid(&cpu_info[kCpuInfoPageSize], kCpuBrandStringPage2);
__cpuid(&cpu_info[kCpuInfoPageSize * 2], kCpuBrandStringPage3);
return (char*)cpu_info;
}
static int CPU_GetFamily(void) {
const int kFeaturesPage = 1;
int cpu_info[kCpuInfoPageSize];
__cpuid(cpu_info, kFeaturesPage);
return (cpu_info[0] >> 8) & 0xf;
}
/*
* Returns 1 if success, 0 in case of QueryPerformanceCounter is not supported.
*
* Calibration is done as described above.
*
* To ensure that the calibration is accurate, we interleave sampling
* the microsecond resolution counter via QueryPerformanceCounter with the 1
* millisecond resolution system clock via GetSystemTimeAsFileTime.
* When we see the edge transition where the system clock ticks, we
* compare the before and after microsecond counter values. If it is
* within a calibration threshold (kMaxCalibrationDiff), then we
* record the instantaneous system time (1 msresolution) and the
* 64-bit counter value (~0.5 mks reslution). Since we have a before and
* after counter value, we interpolate it to get the "average" counter
* value to associate with the system time.
*/
static int NaClCalibrateWindowsClockQpc(struct NaClTimeState *ntsp) {
FILETIME ft_start;
FILETIME ft_prev;
FILETIME ft_now;
LARGE_INTEGER counter_before;
LARGE_INTEGER counter_after;
int64_t counter_diff;
int64_t counter_diff_ms;
uint64_t end_of_calibrate;
int sys_time_changed;
int calibration_success = 0;
NaClLog(5, "Entered NaClCalibrateWindowsClockQpc\n");
GetSystemTimeAsFileTime(&ft_start);
ft_prev = ft_start;
end_of_calibrate = NaClFileTimeToMs(&ft_start) + kCalibrateTimeoutMs;
do {
if (!QueryPerformanceCounter(&counter_before))
return 0;
GetSystemTimeAsFileTime(&ft_now);
if (!QueryPerformanceCounter(&counter_after))
return 0;
counter_diff = counter_after.QuadPart - counter_before.QuadPart;
counter_diff_ms =
(counter_diff * kMillisecondsPerSecond) / ntsp->qpc_frequency;
sys_time_changed = (ft_now.dwHighDateTime != ft_prev.dwHighDateTime) ||
(ft_now.dwLowDateTime != ft_prev.dwLowDateTime);
if ((counter_diff >= 0) &&
(counter_diff_ms <= kMaxCalibrationDiff) &&
sys_time_changed) {
calibration_success = 1;
break;
}
if (sys_time_changed)
ft_prev = ft_now;
} while (NaClFileTimeToMs(&ft_now) < end_of_calibrate);
ntsp->system_time_start_ms = NaClFileTimeToMs(&ft_now);
ntsp->qpc_start = counter_before.QuadPart + (counter_diff / 2);
ntsp->last_qpc = counter_after.QuadPart;
NaClLog(5,
"Leaving NaClCalibrateWindowsClockQpc : %d\n",
calibration_success);
return calibration_success;
}
/*
* Calibration is done as described above.
*
* To ensure that the calibration is accurate, we interleave sampling
* the millisecond resolution counter via timeGetTime with the 10-55
* millisecond resolution system clock via GetSystemTimeAsFileTime.
* When we see the edge transition where the system clock ticks, we
* compare the before and after millisecond counter values. If it is
* within a calibration threshold (kMaxCalibrationDiff), then we
* record the instantaneous system time (10-55 msresolution) and the
* 32-bit counter value (1ms reslution). Since we have a before and
* after counter value, we interpolate it to get the "average" counter
* value to associate with the system time.
*/
static void NaClCalibrateWindowsClockMu(struct NaClTimeState *ntsp) {
FILETIME ft_start;
FILETIME ft_now;
DWORD ms_counter_before;
DWORD ms_counter_after;
uint32_t ms_counter_diff;
NaClLog(5, "Entered NaClCalibrateWindowsClockMu\n");
GetSystemTimeAsFileTime(&ft_start);
ms_counter_before = timeGetTime();
for (;;) {
GetSystemTimeAsFileTime(&ft_now);
ms_counter_after = timeGetTime();
ms_counter_diff = ms_counter_after - (uint32_t) ms_counter_before;
NaClLog(5, "ms_counter_diff %u\n", ms_counter_diff);
if (ms_counter_diff <= kMaxCalibrationDiff &&
(ft_now.dwHighDateTime != ft_start.dwHighDateTime ||
ft_now.dwLowDateTime != ft_start.dwLowDateTime)) {
break;
}
ms_counter_before = ms_counter_after;
}
ntsp->system_time_start_ms = NaClFileTimeToMs(&ft_now);
/*
* Average the counter values. Note unsigned computation of
* ms_counter_diff, so that was mod 2**32 arithmetic, and the
* addition of half the difference is numerically correct, whereas
* (ms_counter_before + ms_counter_after)/2 is wrong due to
* overflow.
*/
ntsp->ms_counter_start = (DWORD) (ms_counter_before + (ms_counter_diff / 2));
NaClLog(5, "Leaving NaClCalibrateWindowsClockMu\n");
}
void NaClAllowLowResolutionTimeOfDay(void) {
g_NaCl_time_state.allow_low_resolution = 1;
}
void NaClTimeInternalInit(struct NaClTimeState *ntsp) {
TIMECAPS tc;
SYSTEMTIME st;
FILETIME ft;
LARGE_INTEGER qpc_freq;
/*
* Maximize timer/Sleep resolution.
*/
timeGetDevCaps(&tc, sizeof tc);
if (ntsp->allow_low_resolution) {
/* Set resolution to max so we don't over-promise. */
ntsp->wPeriodMin = tc.wPeriodMax;
} else {
ntsp->wPeriodMin = tc.wPeriodMin;
timeBeginPeriod(ntsp->wPeriodMin);
NaClLog(4, "NaClTimeInternalInit: timeBeginPeriod(%u)\n", ntsp->wPeriodMin);
}
ntsp->time_resolution_ns = ntsp->wPeriodMin * NACL_NANOS_PER_MILLI;
/*
* Compute Unix epoch start; calibrate high resolution clock.
*/
st.wYear = 1970;
st.wMonth = 1;
st.wDay = 1;
st.wHour = 0;
st.wMinute = 0;
st.wSecond = 0;
st.wMilliseconds = 0;
SystemTimeToFileTime(&st, &ft);
ntsp->epoch_start_ms = NaClFileTimeToMs(&ft);
NaClLog(4, "Unix epoch start is %"NACL_PRIu64"ms in Windows epoch time\n",
ntsp->epoch_start_ms);
NaClMutexCtor(&ntsp->mu);
/*
* We don't actually grab the lock, since the module initializer
* should be called before going threaded.
*/
ntsp->can_use_qpc = 0;
if (!ntsp->allow_low_resolution) {
ntsp->can_use_qpc = QueryPerformanceFrequency(&qpc_freq);
/*
* On Athlon X2 CPUs (e.g. model 15) QueryPerformanceCounter is
* unreliable. Fallback to low-res clock.
*/
if (strstr(CPU_GetBrandString(), "AuthenticAMD") && (CPU_GetFamily() == 15))
ntsp->can_use_qpc = 0;
NaClLog(4,
"CPU_GetBrandString->[%s] ntsp->can_use_qpc=%d\n",
CPU_GetBrandString(),
ntsp->can_use_qpc);
if (ntsp->can_use_qpc) {
ntsp->qpc_frequency = qpc_freq.QuadPart;
NaClLog(4, "qpc_frequency = %"NACL_PRId64" (counts/s)\n",
ntsp->qpc_frequency);
if (!NaClCalibrateWindowsClockQpc(ntsp))
ntsp->can_use_qpc = 0;
}
if (!ntsp->can_use_qpc)
NaClCalibrateWindowsClockMu(ntsp);
}
}
uint64_t NaClTimerResolutionNsInternal(struct NaClTimeState *ntsp) {
return ntsp->time_resolution_ns;
}
void NaClTimeInternalFini(struct NaClTimeState *ntsp) {
NaClMutexDtor(&ntsp->mu);
if (!ntsp->allow_low_resolution)
timeEndPeriod(ntsp->wPeriodMin);
}
void NaClTimeInit(void) {
NaClTimeInternalInit(&g_NaCl_time_state);
}
void NaClTimeFini(void) {
NaClTimeInternalFini(&g_NaCl_time_state);
}
uint64_t NaClTimerResolutionNanoseconds(void) {
return NaClTimerResolutionNsInternal(&g_NaCl_time_state);
}
int NaClGetTimeOfDayInternQpc(struct nacl_abi_timeval *tv,
struct NaClTimeState *ntsp,
int allow_calibration) {
FILETIME ft_now;
int64_t sys_now_mks;
LARGE_INTEGER qpc;
int64_t qpc_diff;
int64_t qpc_diff_mks;
int64_t qpc_now_mks;
int64_t drift_mks;
int64_t drift_ms;
NaClLog(5, "Entered NaClGetTimeOfDayInternQpc\n");
NaClXMutexLock(&ntsp->mu);
GetSystemTimeAsFileTime(&ft_now);
QueryPerformanceCounter(&qpc);
sys_now_mks = NaClFileTimeToMs(&ft_now) * kMicrosecondsPerMillisecond;
NaClLog(5, " sys_now_mks = %"NACL_PRId64" (us)\n", sys_now_mks);
qpc_diff = qpc.QuadPart - ntsp->qpc_start;
NaClLog(5, " qpc_diff = %"NACL_PRId64" (counts)\n", qpc_diff);
/*
* Coarse qpc_now_mks to 10 microseconds resolution,
* to match the other platforms and not make a side-channel
* attack any easier than it needs to be.
*/
qpc_diff_mks = ((qpc_diff * (kMicrosecondsPerSecond / kCoarseMks)) /
ntsp->qpc_frequency) * kCoarseMks;
NaClLog(5, " qpc_diff_mks = %"NACL_PRId64" (us)\n", qpc_diff_mks);
qpc_now_mks = (ntsp->system_time_start_ms * kMicrosecondsPerMillisecond) +
qpc_diff_mks;
NaClLog(5, " system_time_start_ms %"NACL_PRIu64"\n",
ntsp->system_time_start_ms);
NaClLog(5, " qpc_now_mks = %"NACL_PRId64" (us)\n", qpc_now_mks);
if ((qpc_diff < 0) || (qpc.QuadPart < ntsp->last_qpc)) {
NaClLog(5, " need recalibration\n");
if (allow_calibration) {
NaClCalibrateWindowsClockQpc(ntsp);
NaClXMutexUnlock(&ntsp->mu);
return NaClGetTimeOfDayInternQpc(tv, ntsp, 0);
} else {
NaClLog(5, " ... but using coarse, system time instead.\n");
/* use GetSystemTimeAsFileTime(), not QPC */
qpc_now_mks = sys_now_mks;
}
}
ntsp->last_qpc = qpc.QuadPart;
drift_mks = sys_now_mks - qpc_now_mks;
if (qpc_now_mks > sys_now_mks)
drift_mks = qpc_now_mks - sys_now_mks;
drift_ms = drift_mks / kMicrosecondsPerMillisecond;
NaClLog(5, " drift_ms = %"NACL_PRId64"\n", drift_ms);
if (allow_calibration &&
(drift_ms > kMaxMillsecondDriftBeforeRecalibration)) {
NaClLog(5, "drift_ms recalibration\n");
NaClCalibrateWindowsClockQpc(ntsp);
NaClXMutexUnlock(&ntsp->mu);
return NaClGetTimeOfDayInternQpc(tv, ntsp, 0);
}
NaClXMutexUnlock(&ntsp->mu);
/* translate to unix time base */
qpc_now_mks = qpc_now_mks
- ntsp->epoch_start_ms * kMicrosecondsPerMillisecond;
tv->nacl_abi_tv_sec =
(nacl_abi_time_t)(qpc_now_mks / kMicrosecondsPerSecond);
tv->nacl_abi_tv_usec =
(nacl_abi_suseconds_t)(qpc_now_mks % kMicrosecondsPerSecond);
return 0;
}
int NaClGetTimeOfDayIntern(struct nacl_abi_timeval *tv,
struct NaClTimeState *ntsp) {
FILETIME ft_now;
DWORD ms_counter_now;
uint64_t t_ms;
DWORD ms_counter_at_ft_now;
uint32_t ms_counter_diff;
uint64_t unix_time_ms;
if (ntsp->can_use_qpc)
return NaClGetTimeOfDayInternQpc(tv, ntsp, 1);
GetSystemTimeAsFileTime(&ft_now);
ms_counter_now = timeGetTime();
t_ms = NaClFileTimeToMs(&ft_now);
NaClXMutexLock(&ntsp->mu);
if (!ntsp->allow_low_resolution) {
NaClLog(5, "ms_counter_now %"NACL_PRIu32"\n",
(uint32_t) ms_counter_now);
NaClLog(5, "t_ms %"NACL_PRId64"\n", t_ms);
NaClLog(5, "system_time_start_ms %"NACL_PRIu64"\n",
ntsp->system_time_start_ms);
ms_counter_at_ft_now = (DWORD)
(ntsp->ms_counter_start +
(uint32_t) (t_ms - ntsp->system_time_start_ms));
NaClLog(5, "ms_counter_at_ft_now %"NACL_PRIu32"\n",
(uint32_t) ms_counter_at_ft_now);
ms_counter_diff = ms_counter_now - (uint32_t) ms_counter_at_ft_now;
NaClLog(5, "ms_counter_diff %"NACL_PRIu32"\n", ms_counter_diff);
if (ms_counter_diff <= kMaxMillsecondDriftBeforeRecalibration) {
t_ms = t_ms + ms_counter_diff;
} else {
NaClCalibrateWindowsClockMu(ntsp);
t_ms = ntsp->system_time_start_ms;
}
NaClLog(5, "adjusted t_ms = %"NACL_PRIu64"\n", t_ms);
}
unix_time_ms = t_ms - ntsp->epoch_start_ms;
NaClXMutexUnlock(&ntsp->mu);
NaClLog(5, "unix_time_ms = %"NACL_PRId64"\n", unix_time_ms);
/*
* Unix time is measured relative to a different epoch, Jan 1, 1970.
* See the module initialization for epoch_start_ms.
*/
tv->nacl_abi_tv_sec = (nacl_abi_time_t) (unix_time_ms / 1000);
tv->nacl_abi_tv_usec = (nacl_abi_suseconds_t) ((unix_time_ms % 1000) * 1000);
return 0;
}
int NaClGetTimeOfDay(struct nacl_abi_timeval *tv) {
return NaClGetTimeOfDayIntern(tv, &g_NaCl_time_state);
}
int NaClNanosleep(struct nacl_abi_timespec const *req,
struct nacl_abi_timespec *rem) {
DWORD sleep_ms;
uint64_t resolution;
DWORD resolution_gap = 0;
UNREFERENCED_PARAMETER(rem);
/* round up from ns resolution to ms resolution */
/* TODO(bsy): report an error or loop if req->tv_sec does not fit in DWORD */
sleep_ms = ((DWORD) req->tv_sec * NACL_MILLIS_PER_UNIT +
NACL_UNIT_CONVERT_ROUND(req->tv_nsec, NACL_NANOS_PER_MILLI));
/* round up to minimum timer resolution */
resolution = NaClTimerResolutionNanoseconds();
NaClLog(4, "Resolution %"NACL_PRId64"\n", resolution);
if (0 != resolution) {
resolution = NACL_UNIT_CONVERT_ROUND(resolution, NACL_NANOS_PER_MILLI);
resolution_gap = (DWORD) (sleep_ms % resolution);
if (0 != resolution_gap) {
resolution_gap = (DWORD) (resolution - resolution_gap);
}
}
NaClLog(4, "Resolution gap %d\n", resolution_gap);
sleep_ms += resolution_gap;
NaClLog(4, "Sleep(%d)\n", sleep_ms);
Sleep(sleep_ms);
return 0;
}