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
17 changes: 0 additions & 17 deletions issues/portable-kernel-code-names-the-tsc.md

This file was deleted.

4 changes: 2 additions & 2 deletions kernel/pure/sched/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1266,7 +1266,7 @@ fn home_of(state: TaskState) -> Option<CpuId> {
/// response is to find out which pass grew and why — not to raise it.
///
/// **Why no panic stands over it.** The only clock either world can read across
/// a pass is wall clock — `rdtsc` in the kernel — and a guest's wall clock
/// a pass is wall clock — the CPU counter in the kernel — and a guest's wall clock
/// advances while its vCPU is descheduled by the host. `elapsed` is therefore
/// the pass plus any interval the hypervisor took the CPU away, and the second
/// term is set by the host's scheduler, which this CPU neither observes nor
Expand Down Expand Up @@ -2241,7 +2241,7 @@ pub struct CpuHandle<M> {
/// core owns, which is checkable in either world. Cost is not: the
/// simulator's clock does not advance inside a step, so on the sim side the
/// recorder is fed a modelled pass cost (`scenarios::overlong_pass`) and on
/// the kernel side the real TSC.
/// the kernel side the real CPU counter.
///
/// It lives on the handle rather than in the `CpuSched` because the
/// measurement lands *after* the pass has consumed every borrow of that,
Expand Down
14 changes: 7 additions & 7 deletions kernel/pure/sched/hw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
//! [`Kicker`] knows only a CPU id; [`Machine`] adds the rest of the
//! task-blind surface (clock, one-shot timer, interrupt gate, halt, trace);
//! [`Hw`] adds the two operations that carry a task — the context switch and
//! the finalize sink. The kernel implements them with LAPIC one-shot,
//! targeted ICR, TSC and the asm switch; the simulator over a virtual
//! clock and vcpu bookkeeping. No scheduling decision, state transition or
//! ordering-sensitive code may live behind them.
//! the finalize sink. The kernel implements them with each architecture's
//! one-shot timer, targeted IPI, CPU counter and asm switch; the simulator
//! over a virtual clock and vcpu bookkeeping. No scheduling decision, state
//! transition or ordering-sensitive code may live behind them.

use crate::sched::cpu::{RunToken, SleepToken};
use crate::sched::task::{SchedPayload, TaskAccounting, TaskKey};
Expand Down Expand Up @@ -91,8 +91,8 @@ pub trait Machine: Kicker + 'static {
fn now(&self) -> Nanos;

/// Program the one-shot timer for an **absolute** deadline. The kernel's
/// LAPIC one-shot is relative, so it converts; TSC-deadline mode is
/// absolute and will not.
/// one-shot takes a span (`irqchip::arm_one_shot`), so
/// `KernelHw::set_timer` converts.
fn set_timer(&self, deadline: Nanos);

fn stop_timer(&self);
Expand Down Expand Up @@ -126,7 +126,7 @@ pub trait Machine: Kicker + 'static {

/// The complete hardware surface. Everything above this trait is shared
/// between the kernel and the simulator; everything behind it is
/// LAPIC/TSC/ICR/asm in the kernel and virtual time/pending-IPI bookkeeping
/// timer/IPI/counter/asm in the kernel and virtual time/pending-IPI bookkeeping
/// in the sim.
pub trait Hw: Machine {
type Payload: SchedPayload;
Expand Down
2 changes: 1 addition & 1 deletion kernel/src/arch/x86_64/smp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const DATA_OFFSET: usize = 0xF00;
/// The `rdtsc` the AP being started read on its first instruction in Rust.
///
/// **The whole of the TSC-trail measurement.** `clock::nanos_since_boot`
/// subtracts one BSP-sampled `TSC_BOOT` whatever CPU asks, so a CPU whose TSC
/// subtracts one BSP-sampled `COUNTER_AT_BOOT` whatever CPU asks, so a CPU whose TSC
/// reads below the BSP's has no honest answer to give; the reading is bracketed
/// by two BSP samples taken either side of this AP's whole bring-up, which is
/// serialised, so a synchronised TSC lands inside the bracket by construction
Expand Down
30 changes: 15 additions & 15 deletions kernel/src/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ use crate::time::Instant;
use kernel::clock::{anchor, ticks_to_nanos};
use toyos_wallclock::Civil;

static TSC_BOOT: AtomicU64 = AtomicU64::new(0);
static TSC_PERIOD_FS: AtomicU64 = AtomicU64::new(0);
/// The counter's zero to `TSC_BOOT`, in nanoseconds.
static COUNTER_AT_BOOT: AtomicU64 = AtomicU64::new(0);
static COUNTER_PERIOD_FS: AtomicU64 = AtomicU64::new(0);
/// The counter's zero to `COUNTER_AT_BOOT`, in nanoseconds.
static STAMP_AT_BOOT: AtomicU64 = AtomicU64::new(0);
/// One counter tick at the rate the CPU states, in femtoseconds; zero where it
/// states none, or before [`state`].
Expand All @@ -41,8 +41,8 @@ pub fn set_counter(boot: u64, period_fs: u64) {
let stated = STATED_FS.load(Relaxed);
let at_boot = ticks_to_nanos(boot, if stated != 0 { stated } else { period_fs });
STAMP_AT_BOOT.store(at_boot, Relaxed);
TSC_BOOT.store(boot, Relaxed);
TSC_PERIOD_FS.store(period_fs, Relaxed);
COUNTER_AT_BOOT.store(boot, Relaxed);
COUNTER_PERIOD_FS.store(period_fs, Relaxed);
publish_page(boot, period_fs, at_boot);
}

Expand Down Expand Up @@ -105,15 +105,15 @@ pub fn map_page(space: &mut crate::mm::paging::AddressSpace) {

/// Whether [`nanos_since_boot`] measures anything yet; false before [`init`].
pub fn calibrated() -> bool {
TSC_PERIOD_FS.load(Relaxed) != 0
COUNTER_PERIOD_FS.load(Relaxed) != 0
}


/// Nanoseconds since boot; lock-free, no MMIO, and never panics — `log::emit`
/// reads it from inside a bracket where panicking would reenter the log.
/// Saturating, not wrapping: a trailing CPU reads as oldest, not lying newest after a 584-year wrap.
pub fn nanos_since_boot() -> u64 {
ticks_to_nanos(cpu::counter().saturating_sub(TSC_BOOT.load(Relaxed)), TSC_PERIOD_FS.load(Relaxed))
ticks_to_nanos(cpu::counter().saturating_sub(COUNTER_AT_BOOT.load(Relaxed)), COUNTER_PERIOD_FS.load(Relaxed))
}

/// The same reading as an [`Instant`], the type arithmetic on it is allowed in.
Expand All @@ -122,9 +122,9 @@ pub fn now() -> Instant {
Instant::from_nanos_since_boot(nanos_since_boot())
}

/// The [`cpu::rdtsc`] value `nanos` in the future, for a wait loop that must
/// The [`cpu::counter`] reading `nanos` in the future, for a wait loop that must
/// not call the nanosecond clock.
pub fn tsc_deadline(nanos: u64) -> u64 {
pub fn counter_deadline(nanos: u64) -> u64 {
cpu::counter().saturating_add(counter_ticks(nanos))
}

Expand All @@ -133,7 +133,7 @@ pub fn tsc_deadline(nanos: u64) -> u64 {
/// needs. Before [`init`] the period is unknown and this is zero, so a bound
/// derived from it is one its arm has to refuse.
pub fn counter_ticks(nanos: u64) -> u64 {
let period_fs = TSC_PERIOD_FS.load(Relaxed);
let period_fs = COUNTER_PERIOD_FS.load(Relaxed);
if period_fs == 0 {
return 0;
}
Expand All @@ -145,12 +145,12 @@ pub fn counter_ticks(nanos: u64) -> u64 {
/// Zero while the period is unknown, so a span taken on a machine that never
/// calibrated reads as no time rather than as an invented one.
pub fn nanos_of_ticks(ticks: u64) -> u64 {
ticks_to_nanos(ticks, TSC_PERIOD_FS.load(Relaxed))
ticks_to_nanos(ticks, COUNTER_PERIOD_FS.load(Relaxed))
}

/// Polls `ready` until it holds or `nanos` pass; `false` is the deadline.
/// Reads the TSC, not [`nanos_since_boot`], because that clock's out-of-line divide
/// would land an NMI sample's `rip` in `compiler_builtins` rather than in the wait.
/// Reads the counter, not [`nanos_since_boot`], because that clock's out-of-line divide
/// would land an NMI sample's PC in `compiler_builtins` rather than in the wait.
///
/// **Before [`init`] there is no period to measure a span with, so this asks
/// `ready` once and answers it.** A wait nothing can bound is the one thing an
Expand All @@ -160,7 +160,7 @@ pub fn settles(nanos: u64, ready: impl Fn() -> bool) -> bool {
if !calibrated() {
return ready();
}
let until = tsc_deadline(nanos);
let until = counter_deadline(nanos);
while !ready() {
if cpu::counter() >= until {
return false;
Expand All @@ -187,7 +187,7 @@ pub fn init_wall(reading: Result<(Civil, u64), impl core::fmt::Display>) {
}
};

BOOT_SECS.store(anchor(civil.to_unix_secs(), at, TSC_BOOT.load(Relaxed), TSC_PERIOD_FS.load(Relaxed)), Relaxed);
BOOT_SECS.store(anchor(civil.to_unix_secs(), at, COUNTER_AT_BOOT.load(Relaxed), COUNTER_PERIOD_FS.load(Relaxed)), Relaxed);
WALL_KNOWN.store(true, Release);
log!("clock: the RTC reads {civil} UTC");
}
Expand Down
30 changes: 15 additions & 15 deletions kernel/src/deadline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
//!
//! So this one is armed off the parameter line and polled from the timer
//! interrupt entry, on every CPU, in both rings. Its whole requirement is that
//! some CPU still takes an interrupt: [`poll`] reads two atomics and the TSC,
//! takes no lock, allocates nothing and calls nothing that can, so whatever a
//! some CPU still takes an interrupt: [`poll`] reads two atomics and the CPU's
//! counter, takes no lock, allocates nothing and calls nothing that can, so whatever a
//! wedged path is holding is not on this path.
//!
//! # What it does not cover, stated rather than implied
//!
//! - **The span before `clock::init`,** which no bound in this file or in
//! [`crate::hardlockup`] reaches: no TSC period to convert a bound with, and
//! [`crate::hardlockup`] reaches: no counter period to convert a bound with, and
//! no counter armed. A kernel that dies there still needs a hand.
//! - **A machine on which no CPU takes an interrupt at all.** Nothing polled
//! from a running CPU can cover that; **that half is [`crate::hardlockup`]**,
Expand Down Expand Up @@ -54,12 +54,12 @@ pub const PHASES: &[&str] = &[
/// named none. Written on the BSP before any AP exists.
static BOUND_MS: AtomicU64 = AtomicU64::new(0);

/// The `rdtsc` reading the machine is reset at, or 0 while unarmed.
/// The `cpu::counter` reading the machine is reset at, or 0 while unarmed.
///
/// **A TSC value and not a nanosecond one**, because `clock::nanos_since_boot`'s
/// **A counter reading and not a nanosecond one**, because `clock::nanos_since_boot`'s
/// multiply-and-divide is out of line and this is read on every timer tick of
/// every CPU.
static AT_TSC: AtomicU64 = AtomicU64::new(0);
static AT_COUNTER: AtomicU64 = AtomicU64::new(0);

/// Whether a CPU has taken the expiry. One machine, one seal, one reset.
static FIRED: AtomicBool = AtomicBool::new(false);
Expand All @@ -79,18 +79,18 @@ pub fn claim_the_reset() -> bool {
/// expiry would seal a `WEDGED` record over it. Disarms rather than latching a
/// second flag, so [`poll`] stays one relaxed load.
pub fn stand_down() {
AT_TSC.store(0, Relaxed);
AT_COUNTER.store(0, Relaxed);
}

/// Whether this boot runs under a bound at all — this one or
/// [`crate::hardlockup`]'s, since one parameter arms both.
///
/// **What the idle path asks before it re-arms a one-shot.** Both bounds rest on
/// some CPU taking a timer interrupt, and a CPU woken by an IPI and held in Ring
/// 0 takes none; on a boot that named no bound that re-arm is a local APIC read and
/// two writes per wake that buy nothing.
/// 0 takes none; on a boot that named no bound that re-arm is timer
/// register traffic on every wake that buys nothing.
pub fn armed() -> bool {
AT_TSC.load(Relaxed) != 0
AT_COUNTER.load(Relaxed) != 0
}

/// The last phase [`reached`] was told about, as an index into [`PHASES`].
Expand Down Expand Up @@ -163,16 +163,16 @@ pub fn claim(cmdline: &str) {
}
}

/// Turn the bound into a deadline, once the TSC period is known.
/// Turn the bound into a deadline, once the counter's period is known.
///
/// Called after `clock::init` and the LAPIC timer's arm, which are the two
/// Called after `clock::init` and the timer's arm, which are the two
/// things [`poll`] needs: a period to convert with, and something that fires.
pub fn start() {
let ms = BOUND_MS.load(Relaxed);
if ms == 0 {
return;
}
AT_TSC.store(crate::clock::tsc_deadline(ms.saturating_mul(1_000_000)), Relaxed);
AT_COUNTER.store(crate::clock::counter_deadline(ms.saturating_mul(1_000_000)), Relaxed);
log!(
"boot deadline: {ms} ms, after which this kernel seals a WEDGED record and writes the \
reset register itself"
Expand All @@ -185,7 +185,7 @@ pub fn start() {
}

pub fn poll() {
past(AT_TSC.load(Relaxed))
past(AT_COUNTER.load(Relaxed))
}

fn past(at: u64) {
Expand All @@ -205,7 +205,7 @@ static KERNEL_PC: [AtomicU64; MAX_CPUS] = [const { AtomicU64::new(0) }; MAX_CPUS
/// `extern "C"` because the Ring 0 half of the x86 timer entry calls it from
/// naked assembly, where the ABI is written out rather than inferred.
pub extern "C" fn poll_in_kernel(pc: u64) {
let at = AT_TSC.load(Relaxed);
let at = AT_COUNTER.load(Relaxed);
if at != 0 {
KERNEL_PC[crate::arch::percpu::cpu_id() as usize].store(pc, Relaxed);
}
Expand Down
4 changes: 2 additions & 2 deletions kernel/src/drivers/xhci/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ const MAX_HID_FAILURES: u8 = 8;

/// How long the driver waits on any one command or transfer.
///
/// Not proof of a dead device: under TCG the TSC tracks host wall-clock, so a live device on an oversubscribed host can breach this once; only three breaches in one command are treated as a device fact.
/// Not proof of a dead device: under TCG the CPU's counter tracks host wall-clock, so a live device on an oversubscribed host can breach this once; only three breaches in one command are treated as a device fact.
///
/// USB_TIMEOUT_NS and `block::OPERATION` are the same 2 s; the two must stay equal or the budget math (`Scsi::Budget` → `BlockError::BudgetExpired`) is wrong.
const USB_TIMEOUT_NS: u64 = 2_000_000_000;
Expand Down Expand Up @@ -1581,7 +1581,7 @@ fn lock_settles() -> bool {
/// machine nobody can turn off. Not fair — a competitor taking a ticket wins —
/// which is why both callers say what they do without it.
fn take_within(bound: u64) -> Option<OwedGuard<'static, Vec<XhciController>>> {
let until = crate::clock::tsc_deadline(bound);
let until = crate::clock::counter_deadline(bound);
loop {
if let Some(guard) = XHCI.try_lock() {
return Some(guard);
Expand Down
2 changes: 1 addition & 1 deletion kernel/src/drivers/xhci/stop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ impl Live {
/// Stop every controller this kernel drives, and say what that did.
///
/// The one entry, taken by the orderly shutdown and by a panic alike. Takes no
/// lock, allocates nothing, and every wait in it is bounded against the TSC.
/// lock, allocates nothing, and every wait in it is bounded against the CPU's counter.
fn stop_all(said: &mut dyn fmt::Write) {
let mut tally = Stopped::NOTHING;
tally.disks = DISKS.load(Ordering::Relaxed);
Expand Down
Loading