Skip to content
Merged
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
3 changes: 2 additions & 1 deletion docs/design/monotonic-clock-design.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Monotonic Clock / Uptime — Design Document

> **Version:** v0.1-draft
> **Status:** Design
> **Status:** Implemented (`Uptime` 46 + `HighResTime` 47 + `uptime.nxe`); the
> blocking `sleep(ms)`/wait-with-timeout part is tracked in #283.
> **Target Release:** v0.52+
> **Issue:** [#467](https://github.com/NeoDOS-Project/NeoDOS/issues/467)
> **Related:** [#283](https://github.com/NeoDOS-Project/NeoDOS/issues/283),
Expand Down
4 changes: 4 additions & 0 deletions libneodos/src/syscall/ob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ pub enum ObInfoClass {
/// output buffer receives the new connection's fd (u32). Returns EAGAIN
/// (`-8`) when none is pending.
SocketAccept = 43,
/// Monotonic uptime: `{ uptime_ms: u64, tick_hz: u32, _pad: u32 }` (#467).
Uptime = 46,
/// High-resolution monotonic time: `{ tsc: u64, tsc_khz: u32, _pad: u32 }` (#467).
HighResTime = 47,
}

pub mod ob_type {
Expand Down
46 changes: 45 additions & 1 deletion libneodos/src/syscall/time.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,50 @@
//! Time-related Ob wrappers.

use super::{sys_ob_open, sys_ob_set_info, sys_close, ObSetInfoClass, ob_access, DateTime};
use super::{sys_ob_open, sys_ob_query_info, sys_ob_set_info, sys_close, ObInfoClass, ObSetInfoClass, ob_access, DateTime};

/// Monotonic system uptime in milliseconds and the tick rate in Hz (#467).
///
/// Never decreases. Use it for profiling, frame timing, timeouts or animation;
/// it is unrelated to the wall clock (RTC/`ob_set_datetime`). The design-doc
/// name is `ob_uptime_ms`; this returns both the ms value and the tick rate.
pub fn ob_uptime() -> Result<(u64, u32), i64> {
let fd = sys_ob_open("\\Global\\Info\\DateTime", ob_access::READ)?;
let mut buf = [0u8; 16];
let r = sys_ob_query_info(fd, ObInfoClass::Uptime, &mut buf);
let _ = sys_close(fd);
let n = r?;
if n < 16 {
return Err(super::EINVAL);
}
let ms = u64::from_le_bytes([
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
]);
let hz = u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]);
Ok((ms, hz))
}

/// Monotonic uptime in milliseconds (never decreases) (#467).
pub fn ob_uptime_ms() -> Result<u64, i64> {
Ok(ob_uptime()?.0)
}

/// High-resolution monotonic time `(tsc, tsc_khz)` from the calibrated TSC
/// (#467). Derive sub-millisecond deltas as `(tsc2-tsc1) / tsc_khz` ms.
pub fn ob_high_res_time() -> Result<(u64, u32), i64> {
let fd = sys_ob_open("\\Global\\Info\\DateTime", ob_access::READ)?;
let mut buf = [0u8; 16];
let r = sys_ob_query_info(fd, ObInfoClass::HighResTime, &mut buf);
let _ = sys_close(fd);
let n = r?;
if n < 16 {
return Err(super::EINVAL);
}
let tsc = u64::from_le_bytes([
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
]);
let khz = u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]);
Ok((tsc, khz))
}

/// Set the system (RTC) clock via ob_set_info(DateTime).
///
Expand Down
13 changes: 13 additions & 0 deletions neodos-kernel/src/hal/x64/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ pub extern "C" fn get_tick_rate() -> u64 {
TICK_RATE_HZ.load(Ordering::Relaxed)
}

/// Monotonic system uptime in milliseconds, plus the tick rate in Hz (#467).
///
/// Backed by the single BSP-only `TIMER_TICKS` counter, so it never decreases
/// and is SMP-safe (the value is global, not per-CPU).
pub fn uptime_ms() -> (u64, u32) {
let hz = get_tick_rate().max(1);
let ms = TIMER_TICKS
.load(Ordering::Relaxed)
.saturating_mul(1000)
/ hz;
(ms, hz.min(u32::MAX as u64) as u32)
}

#[no_mangle]
#[inline(never)]
pub extern "C" fn init_system_timer() {
Expand Down
11 changes: 11 additions & 0 deletions neodos-kernel/src/net/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,17 @@ pub fn tsc_ticks_from_ms(ms: u64, tsc_per_us: u64) -> u64 {
ms.saturating_mul(1000).saturating_mul(tsc_per_us)
}

/// High-resolution monotonic time `(tsc, tsc_khz)` (#467).
///
/// Routed through this (baselined) module because `boot_benchmark` may not be
/// imported by `hal`/`syscall` directly (dependency-graph isolation), same
/// precedent as `tcp_deadline_from_ms`. Derive sub-millisecond deltas as
/// `(tsc2 - tsc1) / tsc_khz` ms.
pub fn tcp_high_res_time() -> (u64, u32) {
let khz = crate::boot_benchmark::get_tsc_khz();
(crate::boot_benchmark::rdtsc(), khz.min(u32::MAX as u64) as u32)
}

/// Absolute TSC deadline for a millisecond timeout.
pub fn tcp_deadline_from_ms(ms: u64) -> u64 {
crate::boot_benchmark::rdtsc().wrapping_add(tsc_ticks_from_ms(ms, tsc_per_us()))
Expand Down
6 changes: 6 additions & 0 deletions neodos-kernel/src/object/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ pub enum ObInfoClass {
/// (u32) as a direct fd. Returns `Again` (EAGAIN) when no connection is
/// pending. Read on the listener's Socket object.
SocketAccept = 43,
/// Monotonic system uptime: `{ uptime_ms: u64, tick_hz: u32, _pad: u32 }`
/// (16 bytes). Never decreases; SMP-safe (single BSP tick source) (#467).
Uptime = 46,
/// High-resolution monotonic time: `{ tsc: u64, tsc_khz: u32, _pad: u32 }`
/// (16 bytes), from the calibrated TSC (#467).
HighResTime = 47,
}

/// Info classes for sys_ob_set_info (RAX=43).
Expand Down
54 changes: 54 additions & 0 deletions neodos-kernel/src/syscall/ob/query/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub(super) fn handles(info_class: u32) -> bool {
info_class == ObInfoClass::DateTime as u32
|| info_class == ObInfoClass::TimeZone as u32
|| info_class == ObInfoClass::LocalDateTime as u32
|| info_class == ObInfoClass::Uptime as u32
|| info_class == ObInfoClass::HighResTime as u32
}

/// Dispatch the `time` info classes.
Expand All @@ -19,6 +21,58 @@ pub(super) fn dispatch(
buf_size: usize,
) -> u64 {
match info_class {
// ── Uptime (44): monotonic uptime in ms + tick rate (#467) ──
_ if info_class == ObInfoClass::Uptime as u32 => {
#[repr(C)]
struct SysUptime {
uptime_ms: u64,
tick_hz: u32,
_pad: u32,
}
let sz = core::mem::size_of::<SysUptime>();
if buf_size < sz {
return err_to_u64(SyscallError::Inval);
}
let (uptime_ms, tick_hz) = crate::hal::uptime_ms();
let info = SysUptime {
uptime_ms,
tick_hz,
_pad: 0,
};
let bytes = unsafe {
core::slice::from_raw_parts(&info as *const SysUptime as *const u8, sz)
};
if copy_to_user(buf_ptr, bytes).is_err() {
return err_to_u64(SyscallError::Fault);
}
sz as u64
}
// ── HighResTime (47): calibrated TSC + kHz (#467) ──
_ if info_class == ObInfoClass::HighResTime as u32 => {
#[repr(C)]
struct SysHighRes {
tsc: u64,
tsc_khz: u32,
_pad: u32,
}
let sz = core::mem::size_of::<SysHighRes>();
if buf_size < sz {
return err_to_u64(SyscallError::Inval);
}
let (tsc, tsc_khz) = crate::net::tcp::tcp_high_res_time();
let info = SysHighRes {
tsc,
tsc_khz,
_pad: 0,
};
let bytes = unsafe {
core::slice::from_raw_parts(&info as *const SysHighRes as *const u8, sz)
};
if copy_to_user(buf_ptr, bytes).is_err() {
return err_to_u64(SyscallError::Fault);
}
sz as u64
}
_ if info_class == ObInfoClass::DateTime as u32 => {
if entry.object_id == 0 {
return err_to_u64(SyscallError::Inval);
Expand Down
17 changes: 17 additions & 0 deletions neodos-kernel/src/syscall/tests/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,5 +640,22 @@ pub fn register_syscall_table_tests() {
});
test_true!(result.is_ok());
});

test_case!("sys_uptime_monotonic", {
// #467: the monotonic uptime is non-decreasing and reports a sane rate.
let (m0, hz) = crate::hal::uptime_ms();
test_true!(hz >= 18); // at least the PIT fallback rate
let (m1, hz1) = crate::hal::uptime_ms();
test_true!(m1 >= m0);
test_eq!(hz1, hz);
});

test_case!("sys_highres_time_advances", {
// #467: TSC-based high-resolution time is non-zero with a sane kHz.
let (t0, khz) = crate::net::tcp::tcp_high_res_time();
test_true!(khz > 0);
let (t1, _) = crate::net::tcp::tcp_high_res_time();
test_true!(t1 >= t0);
});
}

3 changes: 2 additions & 1 deletion neodos-kernel/src/timers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ pub fn init() {
fn register_hal_hooks() {
let rate = match active() {
TimerSource::Pit => 18,
TimerSource::Hpet | TimerSource::ApicTimer => 1_000_000_000 / TICK_INTERVAL_US,
// Period is TICK_INTERVAL_US (1000 µs = 1 ms) → 1e6 µs/s / 1000 µs = 1 kHz.
TimerSource::Hpet | TimerSource::ApicTimer => 1_000_000 / TICK_INTERVAL_US,
};
let sleep = if active() == TimerSource::Hpet {
Some(hpet_sleep_adapter as fn(u32))
Expand Down
9 changes: 9 additions & 0 deletions userbin/uptime/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[build]
target = "x86_64-unknown-none"

[target.x86_64-unknown-none]
rustflags = [
"-C", "link-arg=-T../../libneodos/user.ld",
"-C", "link-arg=-melf_x86_64",
"-C", "relocation-model=pie",
]
21 changes: 21 additions & 0 deletions userbin/uptime/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions userbin/uptime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "uptime"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "uptime"
path = "src/main.rs"
test = false

[dependencies]
libneodos = { path = "../../libneodos" }

[profile.release]
panic = "abort"
opt-level = 3
lto = true
debug = false

[profile.dev]
panic = "abort"
87 changes: 87 additions & 0 deletions userbin/uptime/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#![no_std]
#![no_main]

//! `uptime` — show the monotonic system uptime (#467).
//!
//! Unlike `datetime` (wall clock from the RTC), this reads the kernel's
//! monotonic uptime, which never decreases and is unaffected by clock steps.
//!
//! Usage:
//! uptime human form: "up 0h 3m 12s (192000 ms, tick 1000 Hz)"
//! uptime -r raw: the millisecond count only
//! uptime -v also print the high-resolution TSC clock

extern crate alloc;

use core::alloc::{GlobalAlloc, Layout};
use libneodos::syscall;

struct SbrkAlloc;
unsafe impl GlobalAlloc for SbrkAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let size = layout.size().max(8) as i64;
let ptr = libneodos::mem::sbrk(size).ok().unwrap_or(0) as *mut u8;
if ptr.is_null() { core::ptr::null_mut() } else { ptr }
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
}
#[global_allocator]
static ALLOC: SbrkAlloc = SbrkAlloc;

fn write_str(s: &[u8]) {
let _ = syscall::sys_write(1, s);
}
fn write_dec(mut v: u64) {
let mut b = [0u8; 20];
let mut i = b.len();
if v == 0 { write_str(b"0"); return; }
while v > 0 && i > 0 { i -= 1; b[i] = b'0' + (v % 10) as u8; v /= 10; }
write_str(&b[i..]);
}

#[no_mangle]
pub extern "C" fn _start() -> ! {
let args = libneodos::args::read_args();
let raw = args.windows(2).any(|w| w == b"-r");
let verbose = args.windows(2).any(|w| w == b"-v");

let (ms, hz) = match syscall::ob_uptime() {
Ok(v) => v,
Err(_) => {
write_str(b"[uptime] monotonic clock unavailable\r\n");
syscall::sys_exit(1);
}
};

if raw {
write_dec(ms);
write_str(b"\r\n");
} else {
let total_s = ms / 1000;
let (d, h, m, s) = (
total_s / 86400,
(total_s % 86400) / 3600,
(total_s % 3600) / 60,
total_s % 60,
);
write_str(b"up ");
if d > 0 { write_dec(d); write_str(b"d "); }
write_dec(h); write_str(b"h ");
write_dec(m); write_str(b"m ");
write_dec(s); write_str(b"s");
write_str(b" (monotonic ");
write_dec(ms); write_str(b" ms, tick ");
write_dec(hz as u64); write_str(b" Hz)\r\n");
}

if verbose {
if let Ok((tsc, khz)) = syscall::ob_high_res_time() {
write_str(b"high-res: tsc=");
write_dec(tsc);
write_str(b" tsc_khz=");
write_dec(khz as u64);
write_str(b"\r\n");
}
}
syscall::sys_exit(0)
}
Loading