Skip to content

Commit 3aa2cc9

Browse files
committed
Make a library for user space Physical Counter Access.
This commit exposes a common API to interact with physical counters on arm and x86. The x86 logic was introduced to improve the performance of the timer driver see commit #645402620af9bf9a6bf4ef3bd17d5a1077a567b7 on x86. Using these counters provides significant performance benefits in contrast to interacting with an external timer driver. At the bare minimum this change will save thousands of cycles for applications that simply need to track time progression rather than reading a timestamp and or setting time outs, for the reasons discussed in #645402620af9bf9a6bf4ef3bd17d5a1077a567b7 as well as avoiding the overhead of invoking the kernel un-necessarily. Putting this code in a library like this means that applications that are not worried about architecture specific details can interact with the generic read_counter and read_freq functions, avoiding the need for #if defined checks. In addition, applications do not need to repeat the non trivial boiler plate logic on x86 that is required to calculate the tsc frequency. Furthermore for x86 in future if we want to calculate the tsc freq when it is otherwise not available, having a stable API now will avoid having to change more files later. For RISC-V the frequency must be supplied by the user of the library by patching a valid config struct into the elfs that use the library. Failure to do this will likely fault but to ensure this error is caught we check for magic bytes. Signed-off-by: Callum <c.berry@student.unsw.edu.au>
1 parent 0c87484 commit 3aa2cc9

5 files changed

Lines changed: 268 additions & 105 deletions

File tree

drivers/timer/tsc_hpet/timer.c

Lines changed: 3 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <stdint.h>
77
#include <microkit.h>
8+
#include <sddf/util/arch_counter.h>
89
#include <sddf/util/printf.h>
910
#include <sddf/resources/device.h>
1011
#include <sddf/timer/protocol.h>
@@ -14,35 +15,10 @@
1415

1516
/* Documents referenced:
1617
* 1. IA-PC HPET (High Precision Event Timers) Specification
17-
* 2. Intel® 64 and IA-32 Architectures Software Developer’s Manual
18-
* Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4
19-
* Order Number: 325462-080US June 2023
20-
* 3. Linux v6.17 source: native_calibrate_tsc() arch/x86/kernel/tsc.c
2118
*/
2219

2320
__attribute__((__section__(".device_resources"), retain, used)) device_resources_t device_resources;
2421

25-
/* CPUID related definitions for TSC detection. */
26-
27-
/* [2] "Vendor Identification String" page "3-240 Vol. 2A" */
28-
#define CPUID_VENDOR_ID_LEAF 0x0
29-
#define CPUID_VENDOR_ID_INTEL_EBX 0x756e6547 // "Genu"
30-
#define CPUID_VENDOR_ID_INTEL_ECX 0x6c65746e // "ineI"
31-
#define CPUID_VENDOR_ID_INTEL_EDX 0x49656e69 // "ntel"
32-
33-
/* [2] "Time Stamp Counter and Nominal Core Crystal Clock Information Leaf" page "Vol. 2A 3-231" */
34-
#define CPUID_TSC_LEAF 0x15
35-
36-
/* [2] "Processor Frequency Information Leaf" page "3-232 Vol. 2A" */
37-
#define CPUID_PROC_FREQ_LEAF 0x16
38-
39-
/* [2] "Maximum Input Value for Extended Function CPUID Information." page "Vol. 2A 3-245" */
40-
#define CPUID_MAX_EXT_LEAF 0x80000000
41-
42-
/* [2] "Invariant TSC available if 1" page "Vol. 2A 3-239" */
43-
#define CPUID_INVARIANT_TSC_LEAF 0x80000007
44-
#define CPUID_INVARIANT_TSC_EDX_BIT 8
45-
4622
/* hpet data structures / memory maps
4723
* each timer has its own configuration registers:
4824
* - Timer n Configuration and Capability Register
@@ -171,83 +147,6 @@ static void process_timeouts(uint64_t curr_ticks)
171147
}
172148
}
173149

174-
static bool is_intel_cpu(void)
175-
{
176-
uint32_t a, b, c, d;
177-
cpuid(CPUID_VENDOR_ID_LEAF, 0, &a, &b, &c, &d);
178-
return b == CPUID_VENDOR_ID_INTEL_EBX && d == CPUID_VENDOR_ID_INTEL_EDX && c == CPUID_VENDOR_ID_INTEL_ECX;
179-
}
180-
181-
static bool is_invariant_tsc(void)
182-
{
183-
/* [2] page "Vol. 2A 3-245" */
184-
/* Check "Maximum Input Value for Extended Function CPUID Information." */
185-
uint32_t max_ext_leaf, a, b, c, d;
186-
cpuid(CPUID_MAX_EXT_LEAF, 0, &max_ext_leaf, &b, &c, &d);
187-
188-
if (max_ext_leaf < CPUID_INVARIANT_TSC_LEAF) {
189-
return false;
190-
}
191-
192-
/* Then check whether Invariant TSC is true */
193-
uint32_t invariant_tsc;
194-
cpuid(CPUID_INVARIANT_TSC_LEAF, 0, &a, &b, &c, &invariant_tsc);
195-
return !!(invariant_tsc & BIT(CPUID_INVARIANT_TSC_EDX_BIT));
196-
}
197-
198-
static uint64_t get_tsc_frequency(void)
199-
{
200-
uint32_t max_basic_leaf, b, c, d;
201-
/* Checks whether the CPU expose TSC/Crystal ratio and Crystal frequency via cpuid leaf 0x15. */
202-
cpuid(CPUID_VENDOR_ID_LEAF, 0, &max_basic_leaf, &b, &c, &d);
203-
if (max_basic_leaf < CPUID_TSC_LEAF) {
204-
LOG_TIMER_DRIVER("CPU does not expose TSC leaf.\n");
205-
return 0;
206-
}
207-
208-
uint32_t denominator, numerator, crystal_khz;
209-
cpuid(CPUID_TSC_LEAF, 0, &denominator, &numerator, &crystal_khz, &d);
210-
if (!denominator || !numerator) {
211-
LOG_TIMER_DRIVER("TSC/Crystal ratio cannot be calculated.\n");
212-
return 0;
213-
}
214-
215-
double tsc_to_crystal_freq_ratio = numerator / (double)denominator;
216-
217-
uint32_t crystal_hz;
218-
if (!crystal_khz) {
219-
LOG_TIMER_DRIVER("CPU does not report Crystal frequency, deriving...\n");
220-
221-
/* From [3]: "Some Intel SoCs like Skylake and Kabylake don't report the crystal
222-
* clock, but we can easily calculate it to a high degree of accuracy
223-
* by considering the crystal ratio and the CPU speed." */
224-
if (max_basic_leaf < CPUID_PROC_FREQ_LEAF) {
225-
return 0;
226-
}
227-
228-
uint32_t proc_base_mhz;
229-
cpuid(CPUID_PROC_FREQ_LEAF, 0, &proc_base_mhz, &b, &c, &d);
230-
uint64_t proc_base_hz = proc_base_mhz * 1000ull * 1000ull;
231-
crystal_hz = proc_base_hz * (1 / tsc_to_crystal_freq_ratio);
232-
233-
LOG_TIMER_DRIVER("Processor base speed is %u MHz\n", proc_base_mhz);
234-
LOG_TIMER_DRIVER("Crystal clock is %u Hz\n", crystal_hz);
235-
} else {
236-
crystal_hz = crystal_khz * 1000;
237-
}
238-
239-
/* From [2]:
240-
* EAX: denominator of the TSC/”core crystal clock” ratio.
241-
* EBX: numerator of the TSC/”core crystal clock” ratio.
242-
* ECX: nominal frequency of the core crystal clock in Hz.
243-
* So “TSC frequency” = “core crystal clock frequency” * EBX/EAX.
244-
*/
245-
246-
uint64_t tsc_hz = crystal_hz * tsc_to_crystal_freq_ratio;
247-
LOG_TIMER_DRIVER("TSC frequency is %u * (%u / %u) = %lu Hz\n", crystal_hz, numerator, denominator, tsc_hz);
248-
return tsc_hz;
249-
}
250-
251150
static uint64_t tsc_ticks_to_ns(uint64_t tsc)
252151
{
253152
return ticks_to_ns(tsc, tsc_freq);
@@ -317,7 +216,7 @@ void init(void)
317216
* TSC took 0.601s, HPET took 12.263s!
318217
*/
319218
} else {
320-
tsc_freq = get_tsc_frequency();
219+
tsc_freq = read_freq();
321220
if (!tsc_freq) {
322221
LOG_TIMER_DRIVER_ERR("cannot detect TSC frequency via cpuid, expect performance degradation.\n");
323222
/* Because same reason as above. */
@@ -336,7 +235,7 @@ seL4_MessageInfo_t protected(microkit_channel ch, microkit_msginfo msginfo)
336235

337236
case SDDF_TIMER_GET_TIME: {
338237
if (tsc_freq) {
339-
microkit_mr_set(0, tsc_ticks_to_ns(rdtsc()));
238+
microkit_mr_set(0, tsc_ticks_to_ns(read_counter()));
340239
} else {
341240
microkit_mr_set(0, hpet_ticks_to_ns(get_hpet_ticks()));
342241
}

include/sddf/util/arch_counter.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2022, UNSW
3+
* SPDX-License-Identifier: BSD-2-Clause
4+
*/
5+
6+
#pragma once
7+
8+
#include <stdint.h>
9+
#include <stdbool.h>
10+
#include <microkit.h>
11+
12+
#if defined(CONFIG_ARCH_X86)
13+
bool is_intel_cpu(void);
14+
/* On intel x86 if the TSC is not invariant then if the processor is put into sleep states or is overclocked then the
15+
* result of reading it may be invalid.
16+
*/
17+
bool is_invariant_tsc(void);
18+
#endif //x86 specific helper functions
19+
20+
/*
21+
* Return the current architecture counter value.
22+
*
23+
* Each implementation aims to use the required serialisation instructions to ensure
24+
* an accurate counter read is returned. The ARM barriers try to reflect the x86 synchronisation
25+
* and the risc-v is the mapping of the ARM barriers from https://docs.riscv.org/reference/isa/unpriv/mm-eplan.html#armmappings
26+
*/
27+
uint64_t read_counter(void);
28+
/* This may return 0 if frequency was not available */
29+
uint64_t read_freq(void);
30+
31+
#if defined(CONFIG_ARCH_RISCV)
32+
#define COUNTER_UTIL_MAGIC_LEN 5
33+
static char COUNTER_UTIL_MAGIC[COUNTER_UTIL_MAGIC_LEN] = { 's', 'D', 'D', 'F', 0x4 };
34+
typedef struct arch_counter_config {
35+
char magic[COUNTER_UTIL_MAGIC_LEN];
36+
uint64_t frequency;
37+
} arch_counter_config_t;
38+
#endif

tools/make/board/qemu_virt_riscv64.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#
44
# SPDX-License-Identifier: BSD-2-Clause
55
#
6+
PLATFORM ?= riscv
67
BLK_DRIV_DIR ?= virtio/mmio
78
GPU_DRIV_DIR ?= virtio
89
NET_DRIV_DIR ?= virtio/mmio

0 commit comments

Comments
 (0)