282 lines
8.0 KiB
Rust

//! x86-64 implementation of a HPET
use core::time::Duration;
use abi::error::Error;
use acpi_lib::hpet::HpetInfo as AcpiHpet;
use device_api::{
interrupt::{InterruptHandler, IrqLevel, IrqOptions, IrqTrigger},
timer::MonotonicTimestampProviderDevice,
Device,
};
use tock_registers::{
interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields, register_structs,
registers::{ReadOnly, ReadWrite},
};
use crate::{
arch::{x86_64::IrqNumber, Architecture, ARCHITECTURE},
mem::device::DeviceMemoryIo,
proc::wait,
sync::IrqSafeSpinlock,
task::tasklet,
};
register_bitfields! {
u64,
GeneralCapabilities [
COUNTER_CLK_PERIOD OFFSET(32) NUMBITS(32) [],
COUNT_SIZE_CAP OFFSET(13) NUMBITS(1) [
Supports64Bit = 1
],
NUM_TIM_CAP OFFSET(8) NUMBITS(5) [],
],
GeneralConfiguration [
LEG_RT_CNF OFFSET(1) NUMBITS(1) [],
ENABLE_CNF OFFSET(0) NUMBITS(1) [],
],
TimerConfigurationCapablities [
TM_INT_TYPE_CNF OFFSET(1) NUMBITS(1) [
EdgeTriggered = 0,
LevelTriggered = 1,
],
TM_INT_ENB_CNF OFFSET(2) NUMBITS(1) [
InterruptEnabled = 1,
InterruptDisabled = 0
],
TM_TYPE_CNF OFFSET(3) NUMBITS(1) [
Periodic = 1,
OneShot = 0
],
TM_PER_INT_CAP OFFSET(4) NUMBITS(1) [
SupportsPeriodic = 1,
],
TM_SIZE_CAP OFFSET(5) NUMBITS(1) [
Timer64Bit = 1,
Timer32Bit = 0,
],
TM_VAL_SET_CNF OFFSET(6) NUMBITS(1) [],
TM_32MODE_CNF OFFSET(8) NUMBITS(1) [],
TM_INT_ROUTE_CNF OFFSET(9) NUMBITS(5) [],
TM_FSB_EN_CNF OFFSET(14) NUMBITS(1) [],
TM_FSB_INT_DEL_CAP OFFSET(15) NUMBITS(1) [],
TM_INT_ROUTE_CAP OFFSET(32) NUMBITS(32) [],
]
}
register_structs! {
#[allow(non_snake_case)]
TimerRegs {
(0x000 => ConfigurationCapability: ReadWrite<u64, TimerConfigurationCapablities::Register>),
(0x008 => Comparator: ReadWrite<u64>),
(0x010 => FsbInterruptRoute: ReadWrite<u64>),
(0x018 => _0),
(0x020 => @END),
}
}
register_structs! {
#[allow(non_snake_case)]
Regs {
(0x000 => GeneralCapabilities: ReadOnly<u64, GeneralCapabilities::Register>),
(0x008 => _0),
(0x010 => GeneralConfiguration: ReadWrite<u64, GeneralConfiguration::Register>),
(0x018 => _1),
(0x020 => GeneralInterruptStatus: ReadWrite<u64>),
(0x028 => _2),
(0x0F0 => MainCounter: ReadWrite<u64>),
(0x0F8 => _3),
(0x100 => Timers: [TimerRegs; 3]),
(0x160 => _4),
(0x400 => @END),
}
}
struct Inner {
regs: DeviceMemoryIo<Regs>,
tim0_period: u64,
tim0_counter: u64,
}
/// HPET timer group interface
pub struct Hpet {
inner: IrqSafeSpinlock<Inner>,
}
impl Inner {
unsafe fn new(base: usize) -> Result<Self, Error> {
// Femtoseconds in a millisecond
const FS_IN_MS: u64 = 1000000000000;
let regs = DeviceMemoryIo::<Regs>::map("hpet", base)?;
// Disable the counter first
regs.GeneralConfiguration
.modify(GeneralConfiguration::ENABLE_CNF::CLEAR);
// Disable legacy routing
regs.GeneralConfiguration
.modify(GeneralConfiguration::LEG_RT_CNF::CLEAR);
// Reset the counter
regs.MainCounter.set(0);
// Disable all comparator interrupts
for i in 0..3 {
regs.Timers[i]
.ConfigurationCapability
.modify(TimerConfigurationCapablities::TM_INT_ENB_CNF::InterruptDisabled);
}
// Calculate the interrupt period
let clk_period = regs
.GeneralCapabilities
.read(GeneralCapabilities::COUNTER_CLK_PERIOD);
// Will give an interrupt interval of 1ms
let tim0_period = FS_IN_MS / clk_period;
// if tim0_period > 0x100000000
// && !tim0
// .ConfigurationCapability
// .matches_all(TimerConfigurationCapablities::TM_SIZE_CAP::Timer64Bit)
// {
// panic!("Period is too large and timer doesn't support 64-bit");
// }
// Enable the main counter
regs.GeneralConfiguration
.modify(GeneralConfiguration::ENABLE_CNF::SET);
Ok(Self {
regs,
tim0_period,
tim0_counter: 0,
})
}
}
impl MonotonicTimestampProviderDevice for Hpet {
fn monotonic_timestamp(&self) -> Result<Duration, Error> {
let inner = self.inner.lock();
let counter = inner.regs.MainCounter.get();
let period = inner.tim0_period;
Ok(Duration::from_millis(counter / period))
}
}
impl InterruptHandler for Hpet {
fn handle_irq(&self) -> bool {
let now = {
let mut inner = self.inner.lock();
inner.regs.GeneralInterruptStatus.set(1);
inner.tim0_counter = inner.tim0_counter.wrapping_add(1);
Duration::from_millis(inner.tim0_counter)
};
wait::tick(now);
tasklet::tick(now);
true
}
}
impl Device for Hpet {
fn display_name(&self) -> &'static str {
"HPET"
}
unsafe fn init_irq(&'static self) -> Result<(), Error> {
// Configure timer 0
let intc = ARCHITECTURE.external_interrupt_controller();
let mut inner = self.inner.lock();
let tim0 = &inner.regs.Timers[0];
// Temporarily disable the main counter
inner
.regs
.GeneralConfiguration
.modify(GeneralConfiguration::ENABLE_CNF::CLEAR);
assert!(tim0
.ConfigurationCapability
.matches_all(TimerConfigurationCapablities::TM_PER_INT_CAP::SupportsPeriodic));
let mut tim0_irq = None;
let tim0_irqs = tim0
.ConfigurationCapability
.read(TimerConfigurationCapablities::TM_INT_ROUTE_CAP);
// Skip most likely legacy routes: IRQ2, IRQ0
for i in 3..32 {
if tim0_irqs & (1 << i) != 0 {
tim0_irq = Some(i);
break;
}
}
let tim0_irq = tim0_irq.expect("Could not pick an IRQ for HPET TIM0");
// Bind and enable the IRQ
let irq = IrqNumber::Gsi(tim0_irq);
intc.register_irq(
irq,
IrqOptions {
level: IrqLevel::ActiveLow,
trigger: IrqTrigger::Level,
},
self,
)?;
intc.enable_irq(irq)?;
// Disable FSB interrupt route and 32 bit mode
tim0.ConfigurationCapability.modify(
TimerConfigurationCapablities::TM_FSB_EN_CNF::CLEAR
+ TimerConfigurationCapablities::TM_32MODE_CNF::CLEAR
+ TimerConfigurationCapablities::TM_VAL_SET_CNF::SET,
);
// Setup interrupt route + edge-triggered
tim0.ConfigurationCapability.modify(
TimerConfigurationCapablities::TM_INT_ROUTE_CNF.val(tim0_irq as u64)
+ TimerConfigurationCapablities::TM_INT_TYPE_CNF::LevelTriggered,
);
// Setup periodic mode
tim0.ConfigurationCapability
.modify(TimerConfigurationCapablities::TM_TYPE_CNF::Periodic);
// Set the comparator
tim0.Comparator.set(inner.tim0_period);
// Enable the timer
tim0.ConfigurationCapability
.modify(TimerConfigurationCapablities::TM_INT_ENB_CNF::InterruptEnabled);
// Reenable the main counter
inner
.regs
.GeneralConfiguration
.modify(GeneralConfiguration::ENABLE_CNF::SET);
Ok(())
}
}
impl Hpet {
/// Creates a HPET instance from its ACPI definition
pub fn from_acpi(info: &AcpiHpet) -> Result<Self, Error> {
infoln!("Initializing HPET:");
infoln!("Address: {:#x}", info.base_address);
let inner = unsafe { Inner::new(info.base_address) }?;
Ok(Self {
inner: IrqSafeSpinlock::new(inner),
})
}
}