Next Generation WASM Microkernel Operating System
at trap_handler 441 lines 15 kB view raw
1// Copyright 2025. Jonas Kruckenberg 2// 3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or 4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or 5// http://opensource.org/licenses/MIT>, at your option. This file may not be 6// copied, modified, or distributed except according to those terms. 7 8mod entry; 9mod wheel; 10 11use crate::time::{Clock, NANOS_PER_SEC, TimeError, max_duration}; 12use crate::{loom::sync::atomic::Ordering, time::Instant}; 13use cordyceps::List; 14use core::{pin::Pin, ptr::NonNull, task::Poll, time::Duration}; 15use spin::Mutex; 16use util::loom_const_fn; 17use wheel::Wheel; 18 19pub(in crate::time) use entry::Entry; 20 21#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 22pub struct Ticks(pub u64); 23 24#[derive(Copy, Clone, Debug)] 25pub struct Deadline { 26 ticks: Ticks, 27 slot: usize, 28 wheel: usize, 29} 30 31#[derive(Debug)] 32pub struct Timer { 33 clock: Clock, 34 tick_duration: Duration, 35 tick_duration_nanos: u64, 36 tick_ratio: u64, 37 core: Mutex<Core>, 38} 39 40#[derive(Debug)] 41struct Core { 42 /// The ticks that have elapsed since the wheel started. 43 now: Ticks, 44 /// Timer wheels 45 /// 46 /// Each timer has 6 wheels with 64 slots, giving each wheel a precision multiplier 47 /// of `64^x` where `x` is the wheel level: 48 /// 49 /// Levels: 50 /// - 64^0 slots 51 /// - 64^1 slots 52 /// - 64^2 slots 53 /// - 64^3 slots 54 /// - 64^4 slots 55 /// - 64^5 slots 56 /// 57 /// For example, a timer constructed from the default QEMU RISC-V CPU timebase frequency of `10_000_000 hz` 58 /// and therefore a `tick_duration` of `100 ns` (10000000 hz => 1/10000000 s => 0,0000001 s => 100ns) 59 /// will have the following wheel configuration: 60 /// 61 /// | wheel | multiplier | | 62 /// |-------|------------|-------------------------------| 63 /// | 0 | 64^0 | 100 ns slots / ~6 µs range | 64 /// | 1 | 64^1 | ~6 µs slots / ~4 ms range | 65 /// | 2 | 64^2 | ~4 ms slots / ~262 ms range | 66 /// | 3 | 64^3 | ~262 ms slots / ~17 sec range | 67 /// | 4 | 64^4 | 17 sec range / ~18 min range | 68 /// | 5 | 64^5 | 18 min range / ~19 hr range | 69 /// 70 /// As you can see, such a timer configuration can track time up to 19 hours into the future with 71 /// a precision of 100 nanoseconds. Quite high precision at the cost of a quite maximum timeout 72 /// duration. 73 /// 74 /// Here is another example of a "lower precision" clock configuration where the `tick_duration` 75 /// is `1 ms`: 76 /// 77 /// | wheel | multiplier | | 78 /// |-------|------------|-------------------------------| 79 /// | 0 | 64^0 | 1 ms slots / 64 ms range | 80 /// | 1 | 64^1 | 64 ms slots / ~ 4 sec range | 81 /// | 2 | 64^2 | ~ 4 sec slots / ~ 4 min range | 82 /// | 3 | 64^3 | ~ 4 min slots / ~ 4 hr range | 83 /// | 4 | 64^4 | ~ 4 hr slots / ~ 12 day range | 84 /// | 5 | 64^5 | ~ 12 day slots / ~ 2 yr range | 85 /// 86 /// As you can see with this configuration we are able to track time up to 2 years into the future 87 /// with a precision of 1 millisecond which should be an acceptable tradeoff for most applications. 88 wheels: [Wheel; Core::WHEELS], 89} 90 91// === impl Deadline === 92 93impl Deadline { 94 pub fn as_ticks(&self) -> Ticks { 95 self.ticks 96 } 97 98 pub fn as_instant(&self, timer: &Timer) -> Instant { 99 Instant::from_ticks(timer, self.ticks) 100 } 101} 102 103// === impl Timer === 104 105impl Timer { 106 loom_const_fn! { 107 pub const fn new(tick_duration: Duration, clock: Clock) -> Self { 108 let tick_duration_nanos = duration_to_nanos(tick_duration); 109 110 let tick_ratio = tick_duration_nanos / duration_to_nanos(clock.tick_duration()); 111 112 Self { 113 clock, 114 tick_duration, 115 tick_duration_nanos, 116 tick_ratio, 117 core: Mutex::new(Core::new()), 118 } 119 } 120 } 121 122 /// Returns the current `now` timestamp, in [`Ticks`] of this timer's base 123 /// tick duration. 124 pub fn now(&self) -> Ticks { 125 Ticks(self.clock.now() / self.tick_ratio) 126 } 127 128 /// Returns the hardware clock backing this timer. 129 pub fn clock(&self) -> &Clock { 130 &self.clock 131 } 132 133 /// Returns the [`Duration`] of one tick of this Timer. 134 #[must_use] 135 pub const fn tick_duration(&self) -> Duration { 136 self.tick_duration 137 } 138 139 /// Returns the maximum duration of this Timer. 140 #[must_use] 141 pub fn max_duration(&self) -> Duration { 142 max_duration(self.tick_duration()) 143 } 144 145 /// Convert the given raw [`Ticks`] into a [`Duration`] using this timers 146 /// internal tick duration. 147 /// 148 /// # Panics 149 /// 150 /// This method panics if the conversion from the given [`Ticks`] would overflow. 151 pub fn ticks_to_duration(&self, ticks: Ticks) -> Duration { 152 Duration::from_nanos(ticks.0 * self.tick_duration_nanos) 153 } 154 155 /// Convert the given [`Duration`] into a raw [`Ticks`] using this timers 156 /// internal tick duration. 157 /// 158 /// # Errors 159 /// 160 /// This method returns a `[TimeError::DurationTooLong`] if the conversion from the given [`Duration`] 161 /// into the 64-bits [`Ticks`] would overflow. 162 pub fn duration_to_ticks(&self, duration: Duration) -> Result<Ticks, TimeError> { 163 let duration_nanos = 164 checked_duration_to_nanos(duration).ok_or(TimeError::DurationTooLong { 165 requested: duration, 166 max: self.max_duration(), 167 })?; 168 169 Ok(Ticks(duration_nanos / self.tick_duration_nanos)) 170 } 171 172 /// Advance the timer to the current time, waking any ready tasks. 173 /// 174 /// The return value indicates the number of tasks woken during this turn 175 /// as well as the next deadline (if any) at which new tasks will become ready. 176 /// 177 /// It is a good idea for the caller to wait until that deadline is reached. 178 pub fn turn(&self) -> (usize, Option<Deadline>) { 179 let mut core = self.core.lock(); 180 self.turn_locked(&mut core) 181 } 182 183 /// Try to advance the timer to the current time, waking any ready tasks. 184 /// 185 /// This method *does not* block when the inner timer mutex lock cannot be acquired, 186 /// making it suitable to call in interrupt handlers. 187 /// 188 /// The return value indicates the number of tasks woken during this turn 189 /// as well as the next deadline (if any) at which new tasks will become ready. 190 /// 191 /// It is a good idea for the caller to wait until that deadline is reached. 192 pub fn try_turn(&self) -> Option<(usize, Option<Deadline>)> { 193 let mut core = self.core.try_lock()?; 194 Some(self.turn_locked(&mut core)) 195 } 196 197 fn turn_locked(&self, core: &mut Core) -> (usize, Option<Deadline>) { 198 let mut now = self.now(); 199 tracing::info!(now = ?now, "turn_locked"); 200 201 if now < core.now { 202 tracing::warn!("time went backwards!"); 203 now = core.now; 204 } 205 206 let mut expired = 0; 207 loop { 208 let (_expired, next_deadline) = core.poll(now); 209 expired += _expired; 210 if let Some(next) = next_deadline { 211 now = self.now(); 212 if now >= next.as_ticks() { 213 // we've advanced past the next deadline, so we need to 214 // advance again. 215 continue; 216 } 217 } 218 219 return (expired, next_deadline); 220 } 221 } 222 223 /// Schedule a wakeup using this timers hardware [`Clock`]. 224 /// 225 /// If the provided argument is `Some()` the clock is instructed to schedule a wakeup 226 /// at that deadline, if `None` is provided, a deadline *maximally far in the future* is chosen. 227 pub fn schedule_wakeup(&self, maybe_next_deadline: Option<Deadline>) { 228 if let Some(next_deadline) = maybe_next_deadline { 229 let virt = next_deadline.as_ticks(); 230 let phys = virt.0 * self.tick_ratio; 231 self.clock.schedule_wakeup(phys); 232 } else { 233 self.clock.schedule_wakeup(u64::MAX); 234 } 235 } 236 237 pub(super) fn cancel(&self, entry: Pin<&mut Entry>) { 238 let mut core = self.core.lock(); 239 core.cancel(entry); 240 241 self.schedule_wakeup(core.next_deadline()); 242 } 243 244 pub(super) fn register(&self, entry: Pin<&mut Entry>) -> Poll<()> { 245 let mut core = self.core.lock(); 246 let poll = core.register(entry); 247 248 self.schedule_wakeup(core.next_deadline()); 249 250 poll 251 } 252} 253 254// === impl Core === 255 256impl Core { 257 const WHEELS: usize = Wheel::BITS; 258 const MAX_SLEEP_TICKS: u64 = (1 << (Wheel::BITS * Self::WHEELS)) - 1; 259 260 #[inline] 261 const fn new() -> Self { 262 Self { 263 now: Ticks(0), 264 #[cfg(not(loom))] 265 wheels: [ 266 Wheel::new(0), 267 Wheel::new(1), 268 Wheel::new(2), 269 Wheel::new(3), 270 Wheel::new(4), 271 Wheel::new(5), 272 ], 273 #[cfg(loom)] 274 wheels: [Wheel::new(0), Wheel::new(1)], 275 } 276 } 277 278 fn poll(&mut self, now: Ticks) -> (usize, Option<Deadline>) { 279 // sleeps that need to be rescheduled on lower-level wheels need to be 280 // processed after we have finished turning the wheel, to avoid looping 281 // infinitely. 282 let mut pending_reschedule = List::<Entry>::new(); 283 284 let mut expired = 0; 285 286 // we will stop looping if the next deadline is after `now`, but we 287 // still need to be able to return it. 288 let mut next_deadline = self.next_deadline(); 289 while let Some(deadline) = next_deadline { 290 // if the deadline is in the future we don't need to continue 291 if deadline.ticks > now { 292 break; 293 } 294 295 // Note that we need to take _all_ of the entries off the list before 296 // processing any of them. This is important because it's possible that 297 // those entries might need to be reinserted into the same slot. 298 // 299 // This happens only on the highest level, when an entry is inserted 300 // more than MAX_DURATION into the future. When this happens, we wrap 301 // around, and process some entries a multiple of MAX_DURATION before 302 // they actually need to be dropped down a level. We then reinsert them 303 // back into the same position; we must make sure we don't then process 304 // those entries again, or we'll end up in an infinite loop. 305 let entries = self.wheels[deadline.wheel].take_slot(deadline.slot); 306 for entry in entries { 307 // Safety: upon registering the caller promised the entry is valid 308 let entry_deadline = unsafe { entry.as_ref().deadline }; 309 310 if entry_deadline > now { 311 // this timer was on the top-level wheel and needs to be 312 // rescheduled on a lower-level wheel, rather than firing now. 313 debug_assert_ne!( 314 deadline.wheel, 0, 315 "if a timer is being rescheduled, it must not have been on the lowest-level wheel" 316 ); 317 tracing::trace!( 318 "rescheduling entry {entry:?} because deadline {entry_deadline:?} is later than now {now:?}" 319 ); 320 // this timer will need to be rescheduled. 321 pending_reschedule.push_front(entry); 322 } else { 323 // otherwise, fire the timer 324 // Safety: upon registering the caller promised the entry is valid 325 unsafe { 326 expired += 1; 327 entry.as_ref().fire(); 328 } 329 } 330 } 331 332 self.now = deadline.ticks; 333 next_deadline = self.next_deadline(); 334 } 335 336 self.now = now; 337 338 let any = !pending_reschedule.is_empty(); 339 340 for entry in pending_reschedule { 341 // Safety: upon registering the caller promised the entry is valid 342 let entry_deadline = unsafe { entry.as_ref().deadline }; 343 344 debug_assert!(entry_deadline > self.now); 345 debug_assert_ne!(entry_deadline, Ticks(0)); 346 self.insert_at(entry_deadline, entry); 347 } 348 349 if any { 350 next_deadline = self.next_deadline(); 351 } 352 353 (expired, next_deadline) 354 } 355 356 fn next_deadline(&self) -> Option<Deadline> { 357 self.wheels.iter().find_map(|wheel| { 358 let next_deadline = wheel.next_deadline(self.now)?; 359 Some(next_deadline) 360 }) 361 } 362 363 fn cancel(&mut self, entry: Pin<&mut Entry>) { 364 let deadline = entry.deadline; 365 tracing::trace!("canceling entry={entry:?};now={:?}", self.now); 366 let wheel = self.wheel_index(deadline); 367 self.wheels[wheel].remove(deadline, entry); 368 } 369 370 fn register(&mut self, entry: Pin<&mut Entry>) -> Poll<()> { 371 let deadline = { 372 tracing::trace!("registering entry={entry:?};now={:?}", self.now); 373 374 if entry.deadline <= self.now { 375 tracing::trace!("timer already completed, firing immediately"); 376 entry.fire(); 377 return Poll::Ready(()); 378 } 379 380 let _did_link = entry.is_registered.compare_exchange( 381 false, 382 true, 383 Ordering::AcqRel, 384 Ordering::Acquire, 385 ); 386 debug_assert!( 387 _did_link.is_ok(), 388 "tried to register a sleep that was already registered" 389 ); 390 391 entry.deadline 392 }; 393 394 // Safety: TODO 395 let ptr = unsafe { NonNull::from(Pin::into_inner_unchecked(entry)) }; 396 397 self.insert_at(deadline, ptr); 398 Poll::Pending 399 } 400 401 fn insert_at(&mut self, deadline: Ticks, ptr: NonNull<Entry>) { 402 let wheel = self.wheel_index(deadline); 403 tracing::trace!("inserting entry={ptr:?};deadline={deadline:?}"); 404 self.wheels[wheel].insert(deadline, ptr); 405 } 406 407 #[inline] 408 fn wheel_index(&self, ticks: Ticks) -> usize { 409 wheel_index(self.now, ticks) 410 } 411} 412 413fn wheel_index(now: Ticks, ticks: Ticks) -> usize { 414 const WHEEL_MASK: u64 = (1 << Wheel::BITS) - 1; 415 416 // mask out the bits representing the index in the wheel 417 let mut wheel_indices = now.0 ^ ticks.0 | WHEEL_MASK; 418 419 // put sleeps over the max duration in the top level wheel 420 if wheel_indices >= Core::MAX_SLEEP_TICKS { 421 wheel_indices = Core::MAX_SLEEP_TICKS - 1; 422 } 423 424 let zeros = wheel_indices.leading_zeros(); 425 let rest = u64::BITS - 1 - zeros; 426 427 rest as usize / Core::WHEELS 428} 429 430#[inline] 431const fn duration_to_nanos(duration: Duration) -> u64 { 432 duration.as_secs() * NANOS_PER_SEC + duration.subsec_nanos() as u64 433} 434 435#[inline] 436fn checked_duration_to_nanos(duration: Duration) -> Option<u64> { 437 duration 438 .as_secs() 439 .checked_mul(NANOS_PER_SEC)? 440 .checked_add(u64::from(duration.subsec_nanos())) 441}