Skip to content

Commit

Permalink
feat(hal-x86_64): add LocalApic::interrupt_in (#505)
Browse files Browse the repository at this point in the history
This is a rudimentary first pass on #498 adding a method to `LocalApic`
to trigger a single oneshot timer interrupt after a given duration. It
would be nice to also have a full-on blocking sleep method, like we do
for the PIT.
  • Loading branch information
hawkw committed Jan 5, 2025
1 parent e8554b9 commit 3f367f8
Showing 1 changed file with 55 additions and 14 deletions.
69 changes: 55 additions & 14 deletions hal-x86_64/src/interrupt/apic/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,35 @@ impl LocalApic {
None
}

pub fn interrupt_in(&self, duration: Duration, vector: u8) -> Result<(), TimerError> {
let TimerCalibration {
frequency_hz,
divisor,
} = self
.timer_calibration
.get()
.ok_or(TimerError::NotCalibrated)?;
let duration_ms: u32 = duration.as_millis().try_into().map_err(|_| {
InvalidDuration::new(
duration,
"local APIC oneshot timer duration exceeds a `u32`",
)
})?;
let ticks = duration_ms
.checked_mul(frequency_hz / 1000)
.ok_or_else(|| {
InvalidDuration::new(
duration,
"local APIC oneshot timer duration requires a number of ticks that exceed a `u32`",
)
})?;
unsafe {
self.configure_timer(divisor, TimerMode::OneShot, vector, ticks);
}

Ok(())
}

#[tracing::instrument(
level = tracing::Level::DEBUG,
name = "LocalApic::start_periodic_timer",
Expand Down Expand Up @@ -399,21 +428,8 @@ impl LocalApic {
)
})?;

// Set the divisor configuration, update the LVT entry, and set the
// initial count. Per the OSDev Wiki, we must do this in this specific
// order (divisor, then unmask the LVT entry, then set the initial
// count), or else we may miss IRQs or have other issues. I'm not sure
// why this is, but see:
// https://wiki.osdev.org/APIC_Timer#Enabling_APIC_Timer
unsafe {
self.write_register(register::TIMER_DIVISOR, divisor);
self.register(register::LVT_TIMER).update(|lvt_timer| {
lvt_timer
.set(LvtTimer::VECTOR, vector)
.set(LvtTimer::MODE, register::TimerMode::Periodic)
.set(LvtTimer::MASKED, false);
});
self.write_register(register::TIMER_INITIAL_COUNT, ticks_per_interval);
self.configure_timer(divisor, TimerMode::Periodic, vector, ticks_per_interval);
}

tracing::info!(
Expand All @@ -427,6 +443,31 @@ impl LocalApic {
Ok(())
}

#[inline(always)]
unsafe fn configure_timer(
&self,
divisor: TimerDivisor,
mode: TimerMode,
vector: u8,
initial_count: u32,
) {
// Set the divisor configuration, update the LVT entry, and set the
// initial count. Per the OSDev Wiki, we must do this in this specific
// order (divisor, then unmask the LVT entry, then set the initial
// count), or else we may miss IRQs or have other issues. I'm not sure
// why this is, but see:
// https://wiki.osdev.org/APIC_Timer#Enabling_APIC_Timer
self.register(register::TIMER_DIVISOR).write(divisor);
self.register(register::LVT_TIMER).update(|lvt_timer| {
lvt_timer
.set(LvtTimer::VECTOR, vector)
.set(LvtTimer::MODE, mode)
.set(LvtTimer::MASKED, false);
});
self.register(register::TIMER_INITIAL_COUNT)
.write(initial_count)
}

/// Sends an End of Interrupt (EOI) to the local APIC.
///
/// This should be called by an interrupt handler after handling a local
Expand Down

0 comments on commit 3f367f8

Please sign in to comment.