diff --git a/include/villas/rdtsc.h b/include/villas/rdtsc.h new file mode 100644 index 000000000..bda3c1599 --- /dev/null +++ b/include/villas/rdtsc.h @@ -0,0 +1,111 @@ +/** Measure time and sleep with IA-32 time-stamp counter. + * + * @file + * @author Steffen Vogel + * @copyright 2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include +#include + +#include + +#ifdef __APPLE__ + #include + #include +#endif + +#define bit_TSC_INVARIANT (1 << 8) +#define bit_RDTSCP (1 << 27) + +#if !(__x86_64__ || __i386__) + #error this header is for x86 only +#endif + +/** Get CPU timestep counter */ +__attribute__((always_inline)) +static inline uint64_t rdtscp() +{ + uint64_t tsc; + + __asm__ __volatile__("rdtscp;" + "shl $32, %%rdx;" + "or %%rdx,%%rax" + : "=a" (tsc) + : + : "%rcx", "%rdx", "memory"); + + return tsc; +} + +int rdtsc_init(uint64_t *freq) +{ + uint32_t eax, ebx, ecx, edx; + + /** Check if TSC is supported */ + __get_cpuid(0x1, &eax, &ebx, &ecx, &edx); + if (!(edx & bit_TSC)) + return -1; + + /** Check if RDTSCP instruction is supported */ + __get_cpuid(0x80000001, &eax, &ebx, &ecx, &edx); + if (!(edx & bit_RDTSCP)) + return -1; + + /** Check if TSC is invariant */ + __get_cpuid(0x80000007, &eax, &ebx, &ecx, &edx); + if (!(edx & bit_TSC_INVARIANT)) + return -1; + + /** Intel SDM Vol 3, Section 18.7.3: + * Nominal TSC frequency = CPUID.15H.ECX[31:0] * CPUID.15H.EBX[31:0] ) รท CPUID.15H.EAX[31:0] + */ + __get_cpuid(0x15, &eax, &ebx, &ecx, &edx); + + if (ecx != 0) + *freq = ecx * ebx / eax; + else { + int ret; +#ifdef __linux__ + FILE *f = fopen(SYSFS_PATH "/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r"); + if (!f) + return -1; + + ret = fscanf(f, "%d", freq); + + fclose(f); + + if (ret != 1) + return -1; +#elif defined(__APPLE__) + int64_t tscfreq; + size_t lenp = sizeof(tscfreq); + + ret = sysctlbyname("machdep.tsc.frequency", &tscfreq, &lenp, NULL, 0); + if (ret) + return ret; + + *freq = tscfreq; +#endif + } + + return 0; +} diff --git a/include/villas/task.h b/include/villas/task.h index 07eadcb50..a15b87981 100644 --- a/include/villas/task.h +++ b/include/villas/task.h @@ -37,6 +37,7 @@ extern "C" { #define TIMERFD 1 #define CLOCK_NANOSLEEP 2 #define NANOSLEEP 3 +#define RDTSC 4 #if defined(__MACH__) #define PERIODIC_TASK_IMPL NANOSLEEP @@ -49,8 +50,16 @@ extern "C" { struct task { int clock; /**< CLOCK_{MONOTONIC,REALTIME} */ +#if PERIODIC_TASK_IMPL == RDTSC /* We use cycle counts in RDTSC mode */ + uint64_t frequency; + + uint64_t period; + uint64_t next; +#else struct timespec period; /**< The period of periodic invations of this task */ struct timespec next; /**< The timer value for the next invocation */ +#endif + #if PERIODIC_TASK_IMPL == TIMERFD int fd; /**< The timerfd_create(2) file descriptior. */ #endif diff --git a/include/villas/utils.h b/include/villas/utils.h index 942087fbb..ee473572f 100644 --- a/include/villas/utils.h +++ b/include/villas/utils.h @@ -244,21 +244,6 @@ int version_parse(const char *s, struct version *v); /** Fill buffer with random data */ ssize_t read_random(char *buf, size_t len); -/** Get CPU timestep counter */ -__attribute__((always_inline)) static inline uint64_t rdtsc() -{ - uint64_t tsc; - - __asm__ ("rdtsc;" - "shl $32, %%rdx;" - "or %%rdx,%%rax" - : "=a" (tsc) - : - : "%rcx", "%rdx", "memory"); - - return tsc; -} - /** Get log2 of long long integers */ static inline int log2i(long long x) { if (x == 0) @@ -267,9 +252,6 @@ static inline int log2i(long long x) { return sizeof(x) * 8 - __builtin_clzll(x) - 1; } -/** Sleep with rdtsc */ -void rdtsc_sleep(uint64_t nanosecs, uint64_t start); - /** Register a exit callback for program termination: SIGINT, SIGKILL & SIGALRM. */ int signals_init(void (*cb)(int signal, siginfo_t *sinfo, void *ctx)); @@ -284,4 +266,4 @@ size_t strlenp(const char *str); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/lib/task.c b/lib/task.c index 4bc325da6..6b6fbae3f 100644 --- a/lib/task.c +++ b/lib/task.c @@ -30,6 +30,8 @@ #if PERIODIC_TASK_IMPL == TIMERFD #include +#elif PERIODIC_TASK_IMPL == RDTSC + #include #endif int task_init(struct task *t, double rate, int clock) @@ -42,6 +44,10 @@ int task_init(struct task *t, double rate, int clock) t->fd = timerfd_create(t->clock, 0); if (t->fd < 0) return -1; +#elif PERIODIC_TASK_IMPL == RDTSC + ret = rdtsc_init(&t->frequency); + if (ret) + return ret; #endif ret = task_set_rate(t, rate); @@ -65,9 +71,13 @@ int task_set_timeout(struct task *t, double to) int task_set_next(struct task *t, struct timespec *next) { + +#if PERIODIC_TASK_IMPL == RDTSC + +#else t->next = *next; -#if PERIODIC_TASK_IMPL == TIMERFD + #if PERIODIC_TASK_IMPL == TIMERFD int ret; struct itimerspec its = { .it_interval = (struct timespec) { 0, 0 }, @@ -77,6 +87,7 @@ int task_set_next(struct task *t, struct timespec *next) ret = timerfd_settime(t->fd, TFD_TIMER_ABSTIME, &its, NULL); if (ret) return ret; + #endif #endif return 0; @@ -84,10 +95,14 @@ int task_set_next(struct task *t, struct timespec *next) int task_set_rate(struct task *t, double rate) { + +#if PERIODIC_TASK_IMPL == RDTSC + t->period = t->frequency / rate; +#else /* A rate of 0 will disarm the timer */ t->period = rate ? time_from_double(1.0 / rate) : (struct timespec) { 0, 0 }; -#if PERIODIC_TASK_IMPL == CLOCK_NANOSLEEP || PERIODIC_TASK_IMPL == NANOSLEEP + #if PERIODIC_TASK_IMPL == CLOCK_NANOSLEEP || PERIODIC_TASK_IMPL == NANOSLEEP struct timespec now, next; clock_gettime(t->clock, &now); @@ -95,7 +110,7 @@ int task_set_rate(struct task *t, double rate) next = time_add(&now, &t->period); return task_set_next(t, &next); -#elif PERIODIC_TASK_IMPL == TIMERFD + #elif PERIODIC_TASK_IMPL == TIMERFD int ret; struct itimerspec its = { .it_interval = t->period, @@ -105,6 +120,7 @@ int task_set_rate(struct task *t, double rate) ret = timerfd_settime(t->fd, 0, &its, NULL); if (ret) return ret; + #endif #endif return 0; @@ -167,6 +183,15 @@ uint64_t task_wait(struct task *t) ret = read(t->fd, &runs, sizeof(runs)); if (ret < 0) return 0; +#elif PERIODIC_TASK_IMPL == RDTSC + uint64_t now; + + do { + now = rdtscp(); + } while (now < t->next); + + for (runs = 0; t->next < now; runs++) + t->next += t->period; #else #error "Invalid period task implementation" #endif diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 7945614f8..fefcde90e 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -41,6 +41,7 @@ if(CRITERION_FOUND) task.c timing.c utils.c + rdtsc.c ) add_executable(unit-tests ${TEST_SRC}) diff --git a/tests/unit/queue.c b/tests/unit/queue.c index dca93f12d..d33f82d5c 100644 --- a/tests/unit/queue.c +++ b/tests/unit/queue.c @@ -320,13 +320,13 @@ ParameterizedTest(struct param *p, queue, multi_threaded, .timeout = 20) sleep(0.2); - start_tsc_time = rdtsc(); + start_tsc_time = rdtscp(); p->start = 1; for (int i = 0; i < p->thread_count; ++i) pthread_join(threads[i], NULL); - end_tsc_time = rdtsc(); + end_tsc_time = rdtscp(); cycpop = (end_tsc_time - start_tsc_time) / p->iter_count; if (cycpop < 400) diff --git a/tests/unit/rdtsc.c b/tests/unit/rdtsc.c new file mode 100644 index 000000000..460a236ba --- /dev/null +++ b/tests/unit/rdtsc.c @@ -0,0 +1,66 @@ +/** Unit tests for rdtsc + * + * @author Steffen Vogel + * @copyright 2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include +#include +#include + +Test(rdtsc, increasing) +{ + uint64_t *cntrs; + + cntrs = alloc(sizeof(uint64_t) << 18); + cr_assert_not_null(cntrs); + + for (int i = 0; i < ARRAY_LEN(cntrs); i++) + cntrs[i] = rdtscp(); + + for (int i = 1; i < ARRAY_LEN(cntrs); i++) + cr_assert_lt(cntrs[i-1], cntrs[i]); + + free(cntrs); +} + +Test(rdtsc, sleep) +{ + int ret; + double delta, duration = 1; + struct timespec start, stop; + uint64_t freq, start_cycles, end_cycles; + + ret = rdtsc_init(&freq); + cr_assert_eq(ret, 0); + + clock_gettime(CLOCK_MONOTONIC, &start); + + start_cycles = rdtscp(); + end_cycles = start_cycles + duration * freq; + + while (rdtscp() < end_cycles); + + clock_gettime(CLOCK_MONOTONIC, &stop); + delta = time_delta(&start, &stop); + + cr_assert_float_eq(delta, duration, 1e-3, "Error: %f, Delta: %lf, Freq: %llu", delta - duration, delta, freq); +} diff --git a/tests/unit/task.c b/tests/unit/task.c index 388855823..0cee93426 100644 --- a/tests/unit/task.c +++ b/tests/unit/task.c @@ -30,6 +30,7 @@ Test(task, rate, .timeout = 10) { int ret; + int runs = 10; double rate = 5, waited; struct timespec start, end; struct task task; @@ -38,7 +39,7 @@ Test(task, rate, .timeout = 10) cr_assert_eq(ret, 0); int i; - for (i = 0; i < 10; i++) { + for (i = 0; i < runs; i++) { clock_gettime(CLOCK_MONOTONIC, &start); task_wait(&task); @@ -51,7 +52,7 @@ Test(task, rate, .timeout = 10) break; } - if (i < 10) + if (i < runs) cr_assert_float_eq(waited, 1.0 / rate, 1e-2, "We slept for %f instead of %f secs in round %d", waited, 1.0 / rate, i); ret = task_destroy(&task);