Skip to content

Commit b42df15

Browse files
authored
Merge pull request #75 from Mes0903/ticks-increment
Manage ticks to suppress RCU CPU stall warning
2 parents 2a521f6 + cd565d0 commit b42df15

File tree

5 files changed

+111
-20
lines changed

5 files changed

+111
-20
lines changed

Makefile

+10
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ DTC ?= dtc
132132
E :=
133133
S := $E $E
134134

135+
# During boot process, he emulator manually manages the growth of ticks to
136+
# suppress RCU CPU stall warnings. Thus, we need an target time to set the
137+
# increment of ticks. According to Using RCU’s CPU Stall Detector[1], the
138+
# grace period for RCU CPU stalls is typically set to 21 seconds.
139+
# By dividing this value by two as the expected completion time, we can
140+
# provide a sufficient buffer to reduce the impact of errors and avoid
141+
# RCU CPU stall warnings.
142+
# [1] docs.kernel.org/RCU/stallwarn.html#config-rcu-cpu-stall-timeout
143+
CFLAGS += -D SEMU_BOOT_TARGET_TIME=10
144+
135145
SMP ?= 1
136146
.PHONY: riscv-harts.dtsi
137147
riscv-harts.dtsi:

main.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ static int semu_init(emu_state_t *emu, int argc, char **argv)
678678
virtio_rng_init();
679679
#endif
680680
/* Set up ACLINT */
681-
semu_timer_init(&emu->mtimer.mtime, CLOCK_FREQ);
681+
semu_timer_init(&emu->mtimer.mtime, CLOCK_FREQ, hart_count);
682682
emu->mtimer.mtimecmp = calloc(vm->n_hart, sizeof(uint64_t));
683683
emu->mswi.msip = calloc(vm->n_hart, sizeof(uint32_t));
684684
emu->sswi.ssip = calloc(vm->n_hart, sizeof(uint32_t));

riscv.c

+8
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,14 @@ static void op_sret(hart_t *vm)
382382
vm->s_mode = vm->sstatus_spp;
383383
vm->sstatus_sie = vm->sstatus_spie;
384384

385+
/* After the booting process is complete, initrd will be loaded. At this
386+
* point, the sytstem will switch to U mode for the first time. Therefore,
387+
* by checking whether the switch to U mode has already occurred, we can
388+
* determine if the boot process has been completed.
389+
*/
390+
if (!boot_complete && !vm->s_mode)
391+
boot_complete = true;
392+
385393
/* Reset stack */
386394
vm->sstatus_spp = false;
387395
vm->sstatus_spie = true;

utils.c

+81-18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <stdbool.h>
12
#include <time.h>
23

34
#include "utils.h"
@@ -19,6 +20,10 @@
1920
#endif
2021
#endif
2122

23+
bool boot_complete = false;
24+
static double ticks_increment;
25+
static double boot_ticks;
26+
2227
/* Calculate "x * n / d" without unnecessary overflow or loss of precision.
2328
*
2429
* Reference:
@@ -32,35 +37,93 @@ static inline uint64_t mult_frac(uint64_t x, uint64_t n, uint64_t d)
3237
return q * n + r * n / d;
3338
}
3439

35-
void semu_timer_init(semu_timer_t *timer, uint64_t freq)
36-
{
37-
timer->freq = freq;
38-
semu_timer_rebase(timer, 0);
39-
}
40-
41-
static uint64_t semu_timer_clocksource(uint64_t freq)
40+
/* High-precision time measurement:
41+
* - POSIX systems: clock_gettime() for nanosecond precision
42+
* - macOS: mach_absolute_time() with timebase conversion
43+
* - Other platforms: time(0) with conversion to nanoseconds as fallback
44+
*
45+
* The platform-specific timing logic is now clearly separated: POSIX and macOS
46+
* implementations provide high-precision measurements, while the fallback path
47+
* uses time(0) for a coarser but portable approach.
48+
*/
49+
static inline uint64_t host_time_ns()
4250
{
4351
#if defined(HAVE_POSIX_TIMER)
44-
struct timespec t;
45-
clock_gettime(CLOCKID, &t);
46-
return t.tv_sec * freq + mult_frac(t.tv_nsec, freq, 1e9);
52+
struct timespec ts;
53+
clock_gettime(CLOCKID, &ts);
54+
return (uint64_t) ts.tv_sec * 1e9 + (uint64_t) ts.tv_nsec;
55+
4756
#elif defined(HAVE_MACH_TIMER)
48-
static mach_timebase_info_data_t t;
49-
if (t.denom == 0)
50-
(void) mach_timebase_info(&t);
51-
return mult_frac(mult_frac(mach_absolute_time(), t.numer, t.denom), freq,
52-
1e9);
57+
static mach_timebase_info_data_t ts = {0};
58+
if (ts.denom == 0)
59+
(void) mach_timebase_info(&ts);
60+
61+
uint64_t now = mach_absolute_time();
62+
/* convert to nanoseconds: (now * t.numer / t.denom) */
63+
return mult_frac(now, ts.numer, (uint64_t) ts.denom);
64+
5365
#else
54-
return time(0) * freq;
66+
/* Fallback to non-HRT calls time(0) in seconds => convert to ns. */
67+
time_t now_sec = time(0);
68+
return (uint64_t) now_sec * 1e9;
5569
#endif
5670
}
5771

72+
/* The function that returns the "emulator time" in ticks.
73+
*
74+
* Before the boot process is completed, the emulator manually manages the
75+
* growth of ticks to suppress RCU CPU stall warnings. After the boot process is
76+
* completed, the emulator switches back to the real-time timer, using an offset
77+
* bridging to ensure that the ticks of both timers remain consistent.
78+
*/
79+
static uint64_t semu_timer_clocksource(semu_timer_t *timer)
80+
{
81+
/* After boot process complete, the timer will switch to real time. Thus,
82+
* there is an offset between the real time and the emulator time.
83+
*
84+
* After switching to real time, the correct way to update time is to
85+
* calculate the increment of time. Then add it to the emulator time.
86+
*/
87+
static int64_t offset = 0;
88+
static bool first_switch = true;
89+
90+
if (!boot_complete) {
91+
boot_ticks += ticks_increment;
92+
return (uint64_t) boot_ticks;
93+
}
94+
95+
uint64_t real_ticks = mult_frac(host_time_ns(), timer->freq, 1e9);
96+
if (first_switch) {
97+
first_switch = false;
98+
99+
/* Calculate the offset between the real time and the emulator time */
100+
offset = (int64_t) (real_ticks - boot_ticks);
101+
}
102+
return (uint64_t) ((int64_t) real_ticks - offset);
103+
}
104+
105+
void semu_timer_init(semu_timer_t *timer, uint64_t freq, int n_harts)
106+
{
107+
timer->freq = freq;
108+
timer->begin = mult_frac(host_time_ns(), timer->freq, 1e9);
109+
boot_ticks = timer->begin; /* Initialize the fake ticks for boot process */
110+
111+
/* According to statistics, the number of times 'semu_timer_clocksource'
112+
* called is approximately 'SMP count * 2.15 * 1e8'. By the time the boot
113+
* process is completed, the emulator will have a total of 'boot seconds *
114+
* frequency' ticks. Therefore, each time, '(boot seconds * frequency) /
115+
* (2.15 * 1e8 * SMP count)' ticks need to be added.
116+
*/
117+
ticks_increment =
118+
(SEMU_BOOT_TARGET_TIME * CLOCK_FREQ) / (2.15 * 1e8 * n_harts);
119+
}
120+
58121
uint64_t semu_timer_get(semu_timer_t *timer)
59122
{
60-
return semu_timer_clocksource(timer->freq) - timer->begin;
123+
return semu_timer_clocksource(timer) - timer->begin;
61124
}
62125

63126
void semu_timer_rebase(semu_timer_t *timer, uint64_t time)
64127
{
65-
timer->begin = semu_timer_clocksource(timer->freq) - time;
128+
timer->begin = semu_timer_clocksource(timer) - time;
66129
}

utils.h

+11-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,23 @@
33
#include <stddef.h>
44
#include <stdint.h>
55

6+
/* To suppress RCU CPU stall warnings, the emulator provides a fake timer to
7+
* the Guest OS during the boot process. After the boot process is complete, the
8+
* emulator will switch to real-time timer.
9+
*
10+
* Since the Guest OS transitions to U mode for the first time when it loads the
11+
* initial user-mode process, we use this transition to determine whether the
12+
* boot process has completed.
13+
*/
14+
extern bool boot_complete;
15+
616
/* TIMER */
717
typedef struct {
818
uint64_t begin;
919
uint64_t freq;
1020
} semu_timer_t;
1121

12-
void semu_timer_init(semu_timer_t *timer, uint64_t freq);
22+
void semu_timer_init(semu_timer_t *timer, uint64_t freq, int n_harts);
1323
uint64_t semu_timer_get(semu_timer_t *timer);
1424
void semu_timer_rebase(semu_timer_t *timer, uint64_t time);
1525

0 commit comments

Comments
 (0)