282 lines
8.0 KiB
Rust
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),
|
|
})
|
|
}
|
|
}
|