Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scale frequency to suppress RCU CPU stall warning #67

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ E :=
S := $E $E

SMP ?= 1
CFLAGS += -D SEMU_SMP=$(SMP)
CFLAGS += -D SEMU_BOOT_TARGET_TIME=10
.PHONY: riscv-harts.dtsi
riscv-harts.dtsi:
$(Q)python3 scripts/gen-hart-dts.py $@ $(SMP) $(CLOCK_FREQ)
Expand Down
8 changes: 8 additions & 0 deletions riscv.c
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,14 @@ static void op_sret(hart_t *vm)
vm->s_mode = vm->sstatus_spp;
vm->sstatus_sie = vm->sstatus_spie;

/* After the booting process is complete, initrd will be loaded. At this
* point, the sytstem will switch to U mode for the first time. Therefore,
* by checking whether the switch to U mode has already occurred, we can
* determine if the boot process has been completed.
*/
if (!boot_complete && !vm->s_mode)
boot_complete = true;

/* Reset stack */
vm->sstatus_spp = false;
vm->sstatus_spie = true;
Expand Down
134 changes: 121 additions & 13 deletions utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,156 @@
#endif
#endif

#ifndef SEMU_SMP
#define SEMU_SMP 1
#endif

#ifndef SEMU_BOOT_TARGET_TIME
#define SEMU_BOOT_TARGET_TIME 10
#endif

bool boot_complete = false;
Copy link
Collaborator

@ranvd ranvd Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest moving boot_complete variable into vm_t for a more conceptually accurate design.

Copy link
Collaborator Author

@Mes0903 Mes0903 Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we move boot_complete into vm_t, all existing functions for semu_timer_t would need an additional vm_t parameter. For example, semu_timer_get would change to:

semu_timer_get(vm_t *vm, semu_timer_t *timer)

This change would indirectly require the areas that call this function to also pass in a vm_tparameter. For instance, since semu_timer_get is called within aclint_mtimer_update_interrupts, the API of aclint_mtimer_update_interrupts would also need to be updated to include vm_t.

As this pattern continues, the API changes would proliferate significantly. Perhaps we could introduce a static bool pointer pointing to boot_complete and assign its value during semu_timer_init. This way, we would only need to modify the parameters of semu_timer_init.

static double scale_factor;

/* Calculate "x * n / d" without unnecessary overflow or loss of precision.
*
* Reference:
* https://elixir.bootlin.com/linux/v6.10.7/source/include/linux/math.h#L121
*/
static inline uint64_t mult_frac(uint64_t x, uint64_t n, uint64_t d)
static inline uint64_t mult_frac(uint64_t x, double n, uint64_t d)
{
const uint64_t q = x / d;
const uint64_t r = x % d;

return q * n + r * n / d;
}

/* Use timespec and frequency to calculate how many ticks to increment. For
* example, if the frequency is set to 65,000,000, then there are 65,000,000
* ticks per second. Respectively, if the time is set to 1 second, then there
* are 65,000,000 ticks.
*
* Thus, by seconds * frequency + nanoseconds * frequency / 1,000,000,000, we
* can get the number of ticks.
*/
static inline uint64_t get_ticks(struct timespec *ts, double freq)
{
return ts->tv_sec * freq + mult_frac(ts->tv_nsec, freq, 1000000000ULL);
}

/* Measure how long it takes for the high resolution timer to update once, to
* scale real time in order to set the emulator time.
*/
static void measure_bogomips_ns(uint64_t target_loop)
{
struct timespec start, end;
clock_gettime(CLOCKID, &start);

for (uint64_t loops = 0; loops < target_loop; loops++)
clock_gettime(CLOCKID, &end);
Comment on lines +64 to +68
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not get the idea behind this code snip. What did you update the value end several times?

Copy link
Collaborator Author

@Mes0903 Mes0903 Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is meant to measure the execution time of target_loop times clock_gettime call. In my understanding, the following code can achieve the same purpose:

struct timespec start, end;
clock_gettime(CLOCKID, &start);

for (uint64_t loops = 0; loops < target_loop - 1; loops++) {
    struct timespec ts;
    clock_gettime(CLOCKID, &ts);
}

clock_gettime(CLOCKID, &end);

However, in the for loop, the variable ts is not used. Therefore, I simply replaced it with end as the parameter of clock_gettime. This way, when exiting the loop, the value of end will be the time from the last execution of clock_gettime, which can also achieve the purpose of measure the execution time of target_loop times clock_gettime call.

Then, by dividing target_loop, the execution time of a single clock_gettime call can be calculated.


int64_t sec_diff = end.tv_sec - start.tv_sec;
int64_t nsec_diff = end.tv_nsec - start.tv_nsec;
double ns_per_call = (sec_diff * 1e9 + nsec_diff) / target_loop;

/* Based on simple statistics, 'semu_timer_clocksource' accounts for
* approximately 10% of the boot process execution time. Since the logic
* inside 'semu_timer_clocksource' is relatively simple, it can be assumed
* that its execution time is roughly equivalent to that of a
* 'clock_gettime' call.
*
* Similarly, based on statistics, 'semu_timer_clocksource' is called
* approximately 2*1e8 times. Therefore, we can roughly estimate that the
* boot process will take '(ns_per_call/1e9) * SEMU_SMP * 2 * 1e8 *
* (100%/10%)' seconds.
*/
double predict_sec = ns_per_call * SEMU_SMP * 2;
scale_factor = SEMU_BOOT_TARGET_TIME / predict_sec;
}

void semu_timer_init(semu_timer_t *timer, uint64_t freq)
{
measure_bogomips_ns(freq); /* Measure the time taken by 'clock_gettime' */

timer->freq = freq;
semu_timer_rebase(timer, 0);
}

static uint64_t semu_timer_clocksource(uint64_t freq)
static uint64_t semu_timer_clocksource(semu_timer_t *timer)
{
/* After boot process complete, the timer will switch to real time. Thus,
* there is an offset between the real time and the emulator time.
*
* After switching to real time, the correct way to update time is to
* calculate the increment of time. Then add it to the emulator time.
*/
static int64_t offset = 0;
static bool first_switch = true;

#if defined(HAVE_POSIX_TIMER)
struct timespec t;
clock_gettime(CLOCKID, &t);
return t.tv_sec * freq + mult_frac(t.tv_nsec, freq, 1e9);
struct timespec emulator_time;
clock_gettime(CLOCKID, &emulator_time);

if (!boot_complete)
return get_ticks(&emulator_time, timer->freq * scale_factor);

if (first_switch) {
first_switch = false;
uint64_t real_ticks = get_ticks(&emulator_time, timer->freq);
uint64_t scaled_ticks =
get_ticks(&emulator_time, timer->freq * scale_factor);

offset = (int64_t) (real_ticks - scaled_ticks);
}

uint64_t real_freq_ticks = get_ticks(&emulator_time, timer->freq);
return real_freq_ticks - offset;
#elif defined(HAVE_MACH_TIMER)
static mach_timebase_info_data_t t;
if (t.denom == 0)
(void) mach_timebase_info(&t);
return mult_frac(mult_frac(mach_absolute_time(), t.numer, t.denom), freq,
1e9);
static mach_timebase_info_data_t emulator_time;
if (emulator_time.denom == 0)
(void) mach_timebase_info(&emulator_time);

uint64_t now = mach_absolute_time();
uint64_t ns = mult_frac(now, emulator_time.numer, emulator_time.denom);
if (!boot_complete)
return mult_frac(ns, (uint64_t) (timer->freq * scale_factor),
1000000000ULL);

if (first_switch) {
first_switch = false;
uint64_t real_ticks = mult_frac(ns, timer->freq, 1000000000ULL);
uint64_t scaled_ticks = mult_frac(
ns, (uint64_t) (timer->freq * scale_factor), 1000000000ULL);
offset = (int64_t) (real_ticks - scaled_ticks);
}

uint64_t real_freq_ticks = mult_frac(ns, timer->freq, 1000000000ULL);
return real_freq_ticks - offset;
#else
return time(0) * freq;
time_t now_sec = time(0);

if (!boot_complete)
return ((uint64_t) now_sec) * (uint64_t) (timer->freq * scale_factor);

if (first_switch) {
first_switch = false;
uint64_t real_val = ((uint64_t) now_sec) * (uint64_t) (timer->freq);
uint64_t scaled_val =
((uint64_t) now_sec) * (uint64_t) (timer->freq * scale_factor);
offset = (int64_t) real_val - (int64_t) scaled_val;
}

uint64_t real_freq_val = ((uint64_t) now_sec) * (uint64_t) (timer->freq);
return real_freq_val - offset;
#endif
}

uint64_t semu_timer_get(semu_timer_t *timer)
{
return semu_timer_clocksource(timer->freq) - timer->begin;
return semu_timer_clocksource(timer) - timer->begin;
}

void semu_timer_rebase(semu_timer_t *timer, uint64_t time)
{
timer->begin = semu_timer_clocksource(timer->freq) - time;
timer->begin = semu_timer_clocksource(timer) - time;
}
5 changes: 4 additions & 1 deletion utils.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <stdbool.h>
#include <stdint.h>

/* TIMER */
Expand All @@ -8,6 +9,8 @@ typedef struct {
uint64_t freq;
} semu_timer_t;

extern bool boot_complete; /* complete boot process and get in initrd */

void semu_timer_init(semu_timer_t *timer, uint64_t freq);
uint64_t semu_timer_get(semu_timer_t *timer);
void semu_timer_rebase(semu_timer_t *timer, uint64_t time);
void semu_timer_rebase(semu_timer_t *timer, uint64_t time);
Loading