rCore Notes: Implement Sleep Functions

Introduction

In the last article, we used sbi_hart_suspend and sbi_set_timer to implement sleep functions. But the method is unusual, and we can implement the sleep function in a more common way. In this article, we will implement sleep by comparing the current cycles of timebase frequency with the cycles when the function is called in loops instead of suspending the hart.

Analysis

In RISC-V, Control and status register (CSR) is a register that stores various information in CPU. The mtime CSR stores the cycles based on the timebase frequency since the CPU started, and we can calculate the uptime of the CPU by dividing the value of the mtime CSR by the timebase frequency of the CPU.

uptime = mtime / timebase_frequency

So we can implement the sleep function by comparing the current time with the time when the function is called in loops. When the current time is greater than the time when the function is called plus the sleep time, the loop will exit.

However, It’s not efficient to compare the current time with the time when the function is called in loops. CPU will keep running in the loop, which will consume a lot of power. So we can use the wfi instruction to suspend the CPU until the interrupt occurs. wfi is short for “wait for interrupt”, when the CPU executes this instruction, wfi puts the CPU core on which it is executing into a low power state until an interrupt is received.

In the last article, we have learned how to use sbi_set_timer to set the timer interrupt. We can use the same method to implement the sleep function.

Solution

Prepare

In the last article, we have learned how to get the timebase frequency of the CPU and how to add riscv crate to dependencies. We can use the same method to prepare the environment.

Implementation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// os/src/timer.rs

use riscv::{
    asm::wfi,
    register::time,
};
use sbi_rt::set_timer;

const TIMEBASE_FREQ: usize = 0x989680; // 10_000_000 is QEMU's timebase frequency.

pub fn sleep_secs(sec: usize) {
    sleep_base_impl(sec, 1);
}

const MILLI_PER_SEC: usize = 1_000;
pub fn sleep_ms(ms: usize) {
    sleep_base_impl(ms, MILLI_PER_SEC);

}

const MICRO_PER_SEC: usize = 1_000_000;
pub fn sleep_us(us: usize) {
    sleep_base_impl(us, MICRO_PER_SEC);
}

fn sleep_base_impl(num: usize, base: usize) {
    let stime =  time::read() + num * (TIMEBASE_FREQ / base);

    set_timer(stime as u64);
    
    while time::read() < stime {
        wfi();
    }
}

In this implementation, we use the riscv::register::time::read() function to get mtime. Then we calculate stime by adding the number of cycles of a certain time to the value of the mtime CSR. After that, we set the timer interrupt to this value by calling sbi_set_timer. Finally, we use wfi to put CPU core to low power state until the interrupt occurs. The condition of loop is needed to avoid sleep function returning before the time is up caused by other interrupts.

Use and Test

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// os/src/main.rs

#[no_mangle]
pub fn rust_main() -> ! {
    clear_bss();

    println!("5s sleep start");
    timer::sleep_secs(5);
    println!("5s sleep end");

    sbi::shutdown(false);
}

After adding the sleep function, we can use it in the rust_main function to test it.

Summary

In this article, we implemented the sleep function by comparing the current time with the time when the function is called in loops. We used the wfi instruction to reduce power consumption, and we used the sbi_set_timer function to set the timer interrupt to wake up the CPU. It’s a more common way to implement the sleep function.

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy