mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-22 00:43:24 +00:00
Refactor project structure
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
bd878dd1c9
commit
e3c227ae06
221
kernel/comps/time/src/clocksource.rs
Normal file
221
kernel/comps/time/src/clocksource.rs
Normal file
@ -0,0 +1,221 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! This module provides abstractions for hardware-assisted timing mechanisms, encapsulated by the `ClockSource` struct.
|
||||
//! A `ClockSource` can be constructed from any counter with a stable frequency, enabling precise time measurements to be taken
|
||||
//! by retrieving instances of `Instant`.
|
||||
//!
|
||||
//! The `ClockSource` module is a fundamental building block for timing in systems that require high precision and accuracy.
|
||||
//! It can be integrated into larger systems to provide timing capabilities, or used standalone for time tracking and elapsed time measurements.
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use core::{cmp::max, ops::Add, time::Duration};
|
||||
|
||||
use aster_frame::sync::SpinLock;
|
||||
use aster_util::coeff::Coeff;
|
||||
|
||||
use crate::NANOS_PER_SECOND;
|
||||
|
||||
/// `ClockSource` is an abstraction for hardware-assisted timing mechanisms.
|
||||
/// A `ClockSource` can be created based on any counter that operates at a stable frequency.
|
||||
/// Users are able to measure time by retrieving `Instant` from this source.
|
||||
///
|
||||
/// # Implementation
|
||||
/// The `ClockSource` relies on obtaining the frequency of the counter and the method for reading the cycles in order to measure time.
|
||||
/// The **cycles** here refer the counts of the base time counter.
|
||||
/// Additionally, the `ClockSource` also holds a last recorded instant, which acts as a reference point for subsequent time retrieval.
|
||||
/// To prevent numerical overflow during the calculation of `Instant`, this last recorded instant **must be periodically refreshed**.
|
||||
/// The maximum interval for these updates must be determined at the time of the `ClockSource` initialization.
|
||||
///
|
||||
/// # Examples
|
||||
/// Suppose we have a counter called `counter` which have the frequency `counter.freq`, and the method to read its cycles called `read_counter()`.
|
||||
/// We can create a corresponding `ClockSource` and use it as follows:
|
||||
///
|
||||
/// ```rust
|
||||
/// // here we set the max_delay_secs = 10
|
||||
/// let max_delay_secs = 10;
|
||||
/// // create a clocksource named counter_clock
|
||||
/// let counter_clock = ClockSource::new(counter.freq, max_delay_secs, Arc::new(read_counter));
|
||||
/// // read an instant.
|
||||
/// let instant = counter_clock.read_instant();
|
||||
/// ```
|
||||
///
|
||||
/// If using this `ClockSource`, you must ensure its internal instant will be updated
|
||||
/// at least once within a time interval of not more than `max_delay_secs.
|
||||
pub struct ClockSource {
|
||||
read_cycles: Arc<dyn Fn() -> u64 + Sync + Send>,
|
||||
base: ClockSourceBase,
|
||||
coeff: Coeff,
|
||||
last_instant: SpinLock<Instant>,
|
||||
last_cycles: SpinLock<u64>,
|
||||
}
|
||||
|
||||
impl ClockSource {
|
||||
/// Create a new `ClockSource` instance.
|
||||
/// Require basic information of based time counter, including the function for reading cycles, the frequency
|
||||
/// and the maximum delay seconds to update this `ClockSource`.
|
||||
/// The `ClockSource` also calculates a reliable `Coeff` based on the counter's frequency and the maximum delay seconds.
|
||||
/// This `Coeff` is used to convert the number of cycles into the duration of time that has passed for those cycles.
|
||||
pub fn new(
|
||||
freq: u64,
|
||||
max_delay_secs: u64,
|
||||
read_cycles: Arc<dyn Fn() -> u64 + Sync + Send>,
|
||||
) -> Self {
|
||||
let base = ClockSourceBase::new(freq, max_delay_secs);
|
||||
// Too big `max_delay_secs` will lead to a low resolution Coeff.
|
||||
debug_assert!(max_delay_secs < 600);
|
||||
let coeff = Coeff::new(NANOS_PER_SECOND as u64, freq, max_delay_secs * freq);
|
||||
Self {
|
||||
read_cycles,
|
||||
base,
|
||||
coeff,
|
||||
last_instant: SpinLock::new(Instant::zero()),
|
||||
last_cycles: SpinLock::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn cycles_to_nanos(&self, cycles: u64) -> u64 {
|
||||
self.coeff * cycles
|
||||
}
|
||||
|
||||
/// Use the instant cycles to calculate the instant.
|
||||
/// It first calculates the difference between the instant cycles and the last recorded cycles stored in the clocksource.
|
||||
/// Then `ClockSource` will convert the passed cycles into passed time and calculate the current instant.
|
||||
fn calculate_instant(&self, instant_cycles: u64) -> Instant {
|
||||
let delta_nanos = {
|
||||
let delta_cycles = instant_cycles - self.last_cycles();
|
||||
self.cycles_to_nanos(delta_cycles)
|
||||
};
|
||||
let duration = Duration::from_nanos(delta_nanos);
|
||||
self.last_instant() + duration
|
||||
}
|
||||
|
||||
/// Use an input instant to update the internal instant in the `ClockSource`.
|
||||
fn update_last_instant(&self, instant: &Instant) {
|
||||
*self.last_instant.lock() = *instant;
|
||||
}
|
||||
|
||||
/// Use an input cycles to update the internal instant in the `ClockSource`.
|
||||
fn update_last_cycles(&self, cycles: u64) {
|
||||
*self.last_cycles.lock() = cycles;
|
||||
}
|
||||
|
||||
/// read current cycles of the `ClockSource`.
|
||||
pub fn read_cycles(&self) -> u64 {
|
||||
(self.read_cycles)()
|
||||
}
|
||||
|
||||
/// Return the last instant recorded in the `ClockSource`.
|
||||
pub fn last_instant(&self) -> Instant {
|
||||
return *self.last_instant.lock();
|
||||
}
|
||||
|
||||
/// Return the last cycles recorded in the `ClockSource`.
|
||||
pub fn last_cycles(&self) -> u64 {
|
||||
return *self.last_cycles.lock();
|
||||
}
|
||||
|
||||
/// Return the maximum delay seconds for updating of the `ClockSource`.
|
||||
pub fn max_delay_secs(&self) -> u64 {
|
||||
self.base.max_delay_secs()
|
||||
}
|
||||
|
||||
/// Return the reference to the generated cycles coeff of the `ClockSource`.
|
||||
pub fn coeff(&self) -> &Coeff {
|
||||
&self.coeff
|
||||
}
|
||||
|
||||
/// Return the frequency of the counter used in the `ClockSource`.
|
||||
pub fn freq(&self) -> u64 {
|
||||
self.base.freq()
|
||||
}
|
||||
|
||||
/// Calibrate the recorded `Instant` to zero, and record the instant cycles.
|
||||
pub(crate) fn calibrate(&self, instant_cycles: u64) {
|
||||
self.update_last_cycles(instant_cycles);
|
||||
self.update_last_instant(&Instant::zero());
|
||||
}
|
||||
|
||||
/// Get the instant to update the internal instant in the `ClockSource`.
|
||||
pub(crate) fn update(&self) {
|
||||
let instant_cycles = self.read_cycles();
|
||||
let instant = self.calculate_instant(instant_cycles);
|
||||
self.update_last_cycles(instant_cycles);
|
||||
self.update_last_instant(&instant);
|
||||
}
|
||||
|
||||
/// Read the instant corresponding to the current time.
|
||||
/// When trying to read an instant from the clocksource, it will use the reading method to read instant cycles.
|
||||
/// Then leverage it to calculate the instant.
|
||||
pub(crate) fn read_instant(&self) -> Instant {
|
||||
let instant_cycles = self.read_cycles();
|
||||
self.calculate_instant(instant_cycles)
|
||||
}
|
||||
}
|
||||
|
||||
/// `Instant` captures a specific moment, storing the duration of time
|
||||
/// elapsed since a reference point (typically the system boot time).
|
||||
/// The `Instant` is expressed in seconds and the fractional part is expressed in nanoseconds.
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct Instant {
|
||||
secs: u64,
|
||||
nanos: u32,
|
||||
}
|
||||
|
||||
impl Instant {
|
||||
pub const fn zero() -> Self {
|
||||
Self { secs: 0, nanos: 0 }
|
||||
}
|
||||
|
||||
pub fn new(secs: u64, nanos: u32) -> Self {
|
||||
Self { secs, nanos }
|
||||
}
|
||||
|
||||
/// Return the seconds recorded in the Instant.
|
||||
pub fn secs(&self) -> u64 {
|
||||
self.secs
|
||||
}
|
||||
|
||||
/// Return the nanoseconds recorded in the Instant.
|
||||
pub fn nanos(&self) -> u32 {
|
||||
self.nanos
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Duration> for Instant {
|
||||
type Output = Instant;
|
||||
|
||||
fn add(self, other: Duration) -> Self::Output {
|
||||
let mut secs = self.secs + other.as_secs();
|
||||
let mut nanos = self.nanos + other.subsec_nanos();
|
||||
if nanos >= NANOS_PER_SECOND {
|
||||
secs += 1;
|
||||
nanos -= NANOS_PER_SECOND;
|
||||
}
|
||||
Instant::new(secs, nanos)
|
||||
}
|
||||
}
|
||||
|
||||
/// The basic properties of `ClockSource`.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct ClockSourceBase {
|
||||
freq: u64,
|
||||
max_delay_secs: u64,
|
||||
}
|
||||
|
||||
impl ClockSourceBase {
|
||||
fn new(freq: u64, max_delay_secs: u64) -> Self {
|
||||
let max_delay_secs = max(2, max_delay_secs);
|
||||
ClockSourceBase {
|
||||
freq,
|
||||
max_delay_secs,
|
||||
}
|
||||
}
|
||||
|
||||
fn max_delay_secs(&self) -> u64 {
|
||||
self.max_delay_secs
|
||||
}
|
||||
|
||||
fn freq(&self) -> u64 {
|
||||
self.freq
|
||||
}
|
||||
}
|
160
kernel/comps/time/src/lib.rs
Normal file
160
kernel/comps/time/src/lib.rs
Normal file
@ -0,0 +1,160 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! The system time of Asterinas.
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use core::{sync::atomic::Ordering::Relaxed, time::Duration};
|
||||
|
||||
use aster_frame::sync::Mutex;
|
||||
use clocksource::ClockSource;
|
||||
pub use clocksource::Instant;
|
||||
use component::{init_component, ComponentInitError};
|
||||
use rtc::{get_cmos, is_updating, CENTURY_REGISTER};
|
||||
use spin::Once;
|
||||
|
||||
mod clocksource;
|
||||
mod rtc;
|
||||
mod tsc;
|
||||
|
||||
pub const NANOS_PER_SECOND: u32 = 1_000_000_000;
|
||||
pub static VDSO_DATA_UPDATE: Once<Arc<dyn Fn(Instant, u64) + Sync + Send>> = Once::new();
|
||||
|
||||
#[init_component]
|
||||
fn time_init() -> Result<(), ComponentInitError> {
|
||||
rtc::init();
|
||||
tsc::init();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct SystemTime {
|
||||
century: u8,
|
||||
pub year: u16,
|
||||
pub month: u8,
|
||||
pub day: u8,
|
||||
pub hour: u8,
|
||||
pub minute: u8,
|
||||
pub second: u8,
|
||||
pub nanos: u64,
|
||||
}
|
||||
|
||||
impl SystemTime {
|
||||
pub(crate) const fn zero() -> Self {
|
||||
Self {
|
||||
century: 0,
|
||||
year: 0,
|
||||
month: 0,
|
||||
day: 0,
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
nanos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_from_rtc(&mut self) {
|
||||
while is_updating() {}
|
||||
self.second = get_cmos(0x00);
|
||||
self.minute = get_cmos(0x02);
|
||||
self.hour = get_cmos(0x04);
|
||||
self.day = get_cmos(0x07);
|
||||
self.month = get_cmos(0x08);
|
||||
self.year = get_cmos(0x09) as u16;
|
||||
|
||||
let century_register = CENTURY_REGISTER.load(Relaxed);
|
||||
if century_register != 0 {
|
||||
self.century = get_cmos(century_register);
|
||||
}
|
||||
}
|
||||
|
||||
/// convert BCD to binary values
|
||||
/// ref:https://wiki.osdev.org/CMOS#Reading_All_RTC_Time_and_Date_Registers
|
||||
pub(crate) fn convert_bcd_to_binary(&mut self, register_b: u8) {
|
||||
if register_b & 0x04 == 0 {
|
||||
self.second = (self.second & 0x0F) + ((self.second / 16) * 10);
|
||||
self.minute = (self.minute & 0x0F) + ((self.minute / 16) * 10);
|
||||
self.hour =
|
||||
((self.hour & 0x0F) + (((self.hour & 0x70) / 16) * 10)) | (self.hour & 0x80);
|
||||
self.day = (self.day & 0x0F) + ((self.day / 16) * 10);
|
||||
self.month = (self.month & 0x0F) + ((self.month / 16) * 10);
|
||||
self.year = (self.year & 0x0F) + ((self.year / 16) * 10);
|
||||
if CENTURY_REGISTER.load(Relaxed) != 0 {
|
||||
self.century = (self.century & 0x0F) + ((self.century / 16) * 10);
|
||||
} else {
|
||||
// 2000 ~ 2099
|
||||
const DEFAULT_21_CENTURY: u8 = 20;
|
||||
self.century = DEFAULT_21_CENTURY;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// convert 12 hour clock to 24 hour clock
|
||||
pub(crate) fn convert_12_hour_to_24_hour(&mut self, register_b: u8) {
|
||||
// bit1 in register_b is not set if 12 hour format is enable
|
||||
// if highest bit in hour is set, then it is pm
|
||||
if ((register_b & 0x02) == 0) && ((self.hour & 0x80) != 0) {
|
||||
self.hour = ((self.hour & 0x7F) + 12) % 24;
|
||||
}
|
||||
}
|
||||
|
||||
/// convert raw year (10, 20 etc.) to real year (2010, 2020 etc.)
|
||||
pub(crate) fn modify_year(&mut self) {
|
||||
self.year += self.century as u16 * 100;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) static READ_TIME: Mutex<SystemTime> = Mutex::new(SystemTime::zero());
|
||||
pub(crate) static START_TIME: Once<SystemTime> = Once::new();
|
||||
|
||||
/// get real time
|
||||
pub fn get_real_time() -> SystemTime {
|
||||
read()
|
||||
}
|
||||
|
||||
pub fn read() -> SystemTime {
|
||||
update_time();
|
||||
*READ_TIME.lock()
|
||||
}
|
||||
|
||||
/// read year,month,day and other data
|
||||
/// ref: https://wiki.osdev.org/CMOS#Reading_All_RTC_Time_and_Date_Registers
|
||||
fn update_time() {
|
||||
let mut last_time: SystemTime;
|
||||
|
||||
let mut lock = READ_TIME.lock();
|
||||
|
||||
lock.update_from_rtc();
|
||||
|
||||
last_time = *lock;
|
||||
|
||||
lock.update_from_rtc();
|
||||
|
||||
while *lock != last_time {
|
||||
last_time = *lock;
|
||||
lock.update_from_rtc();
|
||||
}
|
||||
let register_b: u8 = get_cmos(0x0B);
|
||||
|
||||
lock.convert_bcd_to_binary(register_b);
|
||||
lock.convert_12_hour_to_24_hour(register_b);
|
||||
lock.modify_year();
|
||||
}
|
||||
|
||||
/// Return the `START_TIME`, which is the actual time when doing calibrate.
|
||||
pub fn read_start_time() -> SystemTime {
|
||||
*START_TIME.get().unwrap()
|
||||
}
|
||||
|
||||
/// Return the monotonic time from the tsc clocksource.
|
||||
pub fn read_monotonic_time() -> Duration {
|
||||
let instant = tsc::read_instant();
|
||||
Duration::new(instant.secs(), instant.nanos())
|
||||
}
|
||||
|
||||
/// Return the tsc clocksource.
|
||||
pub fn default_clocksource() -> Arc<ClockSource> {
|
||||
tsc::CLOCK.get().unwrap().clone()
|
||||
}
|
24
kernel/comps/time/src/rtc.rs
Normal file
24
kernel/comps/time/src/rtc.rs
Normal file
@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use core::sync::atomic::{AtomicU8, Ordering::Relaxed};
|
||||
|
||||
use aster_frame::arch::x86::device::cmos::{get_century_register, CMOS_ADDRESS, CMOS_DATA};
|
||||
|
||||
pub(crate) static CENTURY_REGISTER: AtomicU8 = AtomicU8::new(0);
|
||||
|
||||
pub fn init() {
|
||||
let Some(century_register) = get_century_register() else {
|
||||
return;
|
||||
};
|
||||
CENTURY_REGISTER.store(century_register, Relaxed);
|
||||
}
|
||||
|
||||
pub fn get_cmos(reg: u8) -> u8 {
|
||||
CMOS_ADDRESS.write(reg);
|
||||
CMOS_DATA.read()
|
||||
}
|
||||
|
||||
pub fn is_updating() -> bool {
|
||||
CMOS_ADDRESS.write(0x0A);
|
||||
CMOS_DATA.read() & 0x80 != 0
|
||||
}
|
80
kernel/comps/time/src/tsc.rs
Normal file
80
kernel/comps/time/src/tsc.rs
Normal file
@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! This module provide a instance of `ClockSource` based on TSC.
|
||||
//!
|
||||
//! Use `init` to initialize this module.
|
||||
use alloc::sync::Arc;
|
||||
use core::time::Duration;
|
||||
|
||||
use aster_frame::{
|
||||
arch::{read_tsc, x86::tsc_freq},
|
||||
timer::Timer,
|
||||
};
|
||||
use spin::Once;
|
||||
|
||||
use crate::{
|
||||
clocksource::{ClockSource, Instant},
|
||||
START_TIME, VDSO_DATA_UPDATE,
|
||||
};
|
||||
|
||||
/// A instance of TSC clocksource.
|
||||
pub static CLOCK: Once<Arc<ClockSource>> = Once::new();
|
||||
|
||||
const MAX_DELAY_SECS: u64 = 100;
|
||||
|
||||
/// Init tsc clocksource module.
|
||||
pub(super) fn init() {
|
||||
init_clock();
|
||||
calibrate();
|
||||
init_timer();
|
||||
}
|
||||
|
||||
fn init_clock() {
|
||||
CLOCK.call_once(|| {
|
||||
Arc::new(ClockSource::new(
|
||||
tsc_freq(),
|
||||
MAX_DELAY_SECS,
|
||||
Arc::new(read_tsc),
|
||||
))
|
||||
});
|
||||
}
|
||||
|
||||
/// Calibrate the TSC and system time based on the RTC time.
|
||||
fn calibrate() {
|
||||
let clock = CLOCK.get().unwrap();
|
||||
let cycles = clock.read_cycles();
|
||||
clock.calibrate(cycles);
|
||||
START_TIME.call_once(crate::read);
|
||||
}
|
||||
|
||||
/// Read an `Instant` of tsc clocksource.
|
||||
pub(super) fn read_instant() -> Instant {
|
||||
let clock = CLOCK.get().unwrap();
|
||||
clock.read_instant()
|
||||
}
|
||||
|
||||
fn update_clocksource(timer: Arc<Timer>) {
|
||||
let clock = CLOCK.get().unwrap();
|
||||
clock.update();
|
||||
|
||||
// Update vdso data.
|
||||
if VDSO_DATA_UPDATE.is_completed() {
|
||||
VDSO_DATA_UPDATE.get().unwrap()(clock.last_instant(), clock.last_cycles());
|
||||
}
|
||||
// Setting the timer as `clock.max_delay_secs() - 1` is to avoid
|
||||
// the actual delay time is greater than the maximum delay seconds due to the latency of execution.
|
||||
timer.set(Duration::from_secs(clock.max_delay_secs() - 1));
|
||||
}
|
||||
|
||||
fn init_timer() {
|
||||
let timer = Timer::new(update_clocksource).unwrap();
|
||||
// The initial timer should be set as `clock.max_delay_secs() >> 1` or something much smaller than `max_delay_secs`.
|
||||
// This is because the initialization of this timer occurs during system startup,
|
||||
// and the system will also undergo other initialization processes, during which time interrupts are disabled.
|
||||
// This results in the actual trigger time of the timer being delayed by about 5 seconds compared to the set time.
|
||||
// If without KVM, the delayed time will be larger.
|
||||
// TODO: This is a temporary solution, and should be modified in the future.
|
||||
timer.set(Duration::from_secs(
|
||||
CLOCK.get().unwrap().max_delay_secs() >> 1,
|
||||
));
|
||||
}
|
Reference in New Issue
Block a user