396 lines
11 KiB
Rust
396 lines
11 KiB
Rust
use core::sync::atomic::{AtomicU64, Ordering};
|
|
|
|
use abi::error::Error;
|
|
use acpi::HpetInfo;
|
|
use alloc::{collections::btree_map::BTreeMap, format, string::String, sync::Arc};
|
|
use device_api::{
|
|
device::Device,
|
|
interrupt::{InterruptHandler, Irq, IrqOptions, IrqTrigger},
|
|
};
|
|
use libk::{device::external_interrupt_controller, task::runtime, time};
|
|
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
|
|
use libk_util::sync::spin_rwlock::IrqSafeRwLock;
|
|
use tock_registers::{
|
|
interfaces::{ReadWriteable, Readable, Writeable},
|
|
register_bitfields, register_structs,
|
|
registers::{ReadOnly, ReadWrite},
|
|
};
|
|
|
|
register_bitfields! {
|
|
u64,
|
|
GENERAL_CAPABILITIES [
|
|
COUNTER_CLK_PERIOD OFFSET(32) NUMBITS(32) [],
|
|
VENDOR_ID OFFSET(16) NUMBITS(16) [],
|
|
COUNT_SIZE_CAP OFFSET(13) NUMBITS(1) [],
|
|
NUM_TIM_CAP OFFSET(8) NUMBITS(4) [],
|
|
REV_ID OFFSET(0) NUMBITS(8) [],
|
|
],
|
|
GENERAL_CONFIG [
|
|
LEG_RT_CNF OFFSET(1) NUMBITS(1) [],
|
|
ENABLE_CNF OFFSET(0) NUMBITS(1) [],
|
|
],
|
|
Tn_CONFIG [
|
|
Tn_INT_ROUTE_CAP OFFSET(32) NUMBITS(32) [],
|
|
Tn_FSB_INT_DEL_CAP OFFSET(15) NUMBITS(1) [],
|
|
Tn_FSB_EN_CNF OFFSET(14) NUMBITS(1) [],
|
|
Tn_INT_ROUTE_CNF OFFSET(9) NUMBITS(5) [],
|
|
Tn_32MODE_CNF OFFSET(8) NUMBITS(1) [],
|
|
Tn_VAL_SET_CNF OFFSET(6) NUMBITS(1) [],
|
|
Tn_SIZE_CAP OFFSET(5) NUMBITS(1) [],
|
|
Tn_PER_INT_CAP OFFSET(4) NUMBITS(1) [],
|
|
Tn_TYPE_CNF OFFSET(3) NUMBITS(1) [
|
|
NonPeriodic = 0,
|
|
Periodic = 1,
|
|
],
|
|
Tn_INT_ENB_CNF OFFSET(2) NUMBITS(1) [],
|
|
Tn_INT_TYPE_CNF OFFSET(1) NUMBITS(1) [
|
|
Edge = 0,
|
|
Level = 1,
|
|
],
|
|
]
|
|
}
|
|
|
|
register_structs! {
|
|
#[allow(non_snake_case)]
|
|
GeneralRegs {
|
|
(0x000 => GENERAL_CAPABILITIES: ReadOnly<u64, GENERAL_CAPABILITIES::Register>),
|
|
(0x008 => _0),
|
|
(0x010 => GENERAL_CONFIG: ReadWrite<u64, GENERAL_CONFIG::Register>),
|
|
(0x018 => _1),
|
|
(0x020 => GENERAL_ISR: ReadWrite<u64>),
|
|
(0x028 => _2),
|
|
(0x0F0 => MAIN_COUNTER: ReadWrite<u64>),
|
|
(0x0F8 => _3),
|
|
(0x100 => @END),
|
|
}
|
|
}
|
|
|
|
register_structs! {
|
|
#[allow(non_snake_case)]
|
|
TimerRegs {
|
|
(0x00 => Tn_CONFIG: ReadWrite<u64, Tn_CONFIG::Register>),
|
|
(0x08 => Tn_COMPARATOR: ReadWrite<u64>),
|
|
(0x10 => Tn_FSB_ROUTE: ReadWrite<u64>),
|
|
(0x18 => _0),
|
|
(0x20 => @END),
|
|
}
|
|
}
|
|
|
|
pub struct HpetTimer {
|
|
name: String,
|
|
parent: Arc<Hpet>,
|
|
regs: IrqSafeRwLock<DeviceMemoryIo<'static, TimerRegs>>,
|
|
system_clock_source: bool,
|
|
period: u64,
|
|
|
|
last_ticks: AtomicU64,
|
|
|
|
periodic: bool,
|
|
bits64: bool,
|
|
}
|
|
|
|
pub struct Hpet {
|
|
base: PhysicalAddress,
|
|
regs: IrqSafeRwLock<DeviceMemoryIo<'static, GeneralRegs>>,
|
|
timers: IrqSafeRwLock<BTreeMap<usize, Arc<HpetTimer>>>,
|
|
|
|
base_frequency: u64,
|
|
base_period: u64,
|
|
}
|
|
|
|
const FS_IN_S: u64 = 10u64.pow(15);
|
|
const FS_IN_NS: u64 = 10u64.pow(6);
|
|
|
|
impl InterruptHandler for HpetTimer {
|
|
fn handle_irq(self: Arc<Self>, _vector: Option<usize>) -> bool {
|
|
let now = self.parent.read_counter();
|
|
let last = self.last_ticks.swap(now, Ordering::Relaxed);
|
|
let delta = now.wrapping_sub(last);
|
|
|
|
if !self.periodic {
|
|
self.regs.write().rearm(now.wrapping_add(self.period));
|
|
}
|
|
|
|
if self.system_clock_source {
|
|
let dt = delta * (self.parent.base_period / FS_IN_NS);
|
|
time::add_nanoseconds(dt);
|
|
runtime::tick();
|
|
}
|
|
|
|
true
|
|
}
|
|
}
|
|
|
|
impl Device for HpetTimer {
|
|
unsafe fn init(self: Arc<Self>) -> Result<(), Error> {
|
|
if self.period > u32::MAX as u64 && !self.bits64 {
|
|
log::error!(
|
|
"HPET period is >32bit and the HPET itself does not support 64bit counters"
|
|
);
|
|
return Err(Error::InvalidArgument);
|
|
}
|
|
|
|
let intc = external_interrupt_controller()?;
|
|
|
|
let regs = self.regs.write();
|
|
|
|
if regs.is_fsb_capable() {
|
|
log::warn!("TODO: HPET supports FSB interrupt delivery, but the kernel doesn't yet");
|
|
}
|
|
|
|
let irq = regs.select_irq().ok_or(Error::InvalidArgument)?;
|
|
|
|
log::info!(
|
|
"Set up {:?}, period={}t, periodic={}, irq={:?}",
|
|
self.name,
|
|
self.period,
|
|
self.periodic,
|
|
irq
|
|
);
|
|
|
|
intc.register_irq(
|
|
irq,
|
|
IrqOptions {
|
|
trigger: IrqTrigger::Edge,
|
|
..Default::default()
|
|
},
|
|
self.clone(),
|
|
)?;
|
|
intc.enable_irq(irq)?;
|
|
let irq = IrqDestination::Irq(irq);
|
|
|
|
let now = self.parent.read_counter();
|
|
|
|
// Pause the timer
|
|
regs.stop();
|
|
if self.periodic {
|
|
regs.configure_periodic(now.wrapping_add(self.period), self.period, irq);
|
|
} else {
|
|
regs.configure_oneshot(now.wrapping_add(self.period), irq);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
|
|
todo!()
|
|
}
|
|
|
|
fn display_name(&self) -> &str {
|
|
self.name.as_str()
|
|
}
|
|
}
|
|
|
|
impl Device for Hpet {
|
|
unsafe fn init(self: Arc<Self>) -> Result<(), Error> {
|
|
let regs = self.regs.write();
|
|
|
|
// Reset the device until at least one timer is created
|
|
regs.stop();
|
|
regs.GENERAL_CONFIG
|
|
.modify(GENERAL_CONFIG::LEG_RT_CNF::CLEAR);
|
|
regs.MAIN_COUNTER.set(0);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn display_name(&self) -> &str {
|
|
"HPET"
|
|
}
|
|
}
|
|
|
|
impl HpetTimer {
|
|
fn create(
|
|
hpet: Arc<Hpet>,
|
|
index: usize,
|
|
frequency: u64,
|
|
system_clock_source: bool,
|
|
) -> Result<Arc<Self>, Error> {
|
|
let base = hpet.base.add(0x100 + 0x20 * index);
|
|
let regs = unsafe { DeviceMemoryIo::<TimerRegs>::map(base, Default::default()) }?;
|
|
|
|
let period = hpet.base_frequency / frequency;
|
|
|
|
let periodic = regs.is_periodic();
|
|
let bits64 = regs.is_64_bit();
|
|
|
|
let timer = Arc::new(HpetTimer {
|
|
name: format!("hpet{index}"),
|
|
regs: IrqSafeRwLock::new(regs),
|
|
parent: hpet,
|
|
last_ticks: AtomicU64::new(0),
|
|
system_clock_source,
|
|
periodic,
|
|
period,
|
|
bits64,
|
|
});
|
|
|
|
Ok(timer)
|
|
}
|
|
}
|
|
|
|
impl Hpet {
|
|
fn new(base: PhysicalAddress) -> Result<Self, Error> {
|
|
let regs = unsafe { DeviceMemoryIo::<GeneralRegs>::map(base, Default::default()) }?;
|
|
let base_period = regs.timer_period();
|
|
let base_frequency = FS_IN_S / base_period;
|
|
Ok(Self {
|
|
base,
|
|
base_period,
|
|
base_frequency,
|
|
regs: IrqSafeRwLock::new(regs),
|
|
timers: IrqSafeRwLock::new(BTreeMap::new()),
|
|
})
|
|
}
|
|
|
|
pub fn setup_from_acpi(acpi: &HpetInfo) -> Result<Arc<Self>, Error> {
|
|
let base = PhysicalAddress::from_usize(acpi.base_address);
|
|
let hpet = Arc::new(Self::new(base)?);
|
|
unsafe { hpet.clone().init() }?;
|
|
Ok(hpet)
|
|
}
|
|
|
|
pub fn setup_timer(
|
|
self: Arc<Self>,
|
|
index: usize,
|
|
frequency: u64,
|
|
system_clock_source: bool,
|
|
) -> Result<Arc<HpetTimer>, Error> {
|
|
let regs = self.regs.write();
|
|
let mut timers = self.timers.write();
|
|
let timer_count = regs.timer_count();
|
|
|
|
if index >= timer_count {
|
|
log::warn!("HPET #{index} does not exist");
|
|
return Err(Error::DoesNotExist);
|
|
}
|
|
|
|
if timers.contains_key(&index) {
|
|
log::warn!("HPET #{index} already configured");
|
|
return Err(Error::AlreadyExists);
|
|
}
|
|
|
|
// Pause the timer
|
|
regs.stop();
|
|
|
|
drop(regs);
|
|
|
|
let timer = HpetTimer::create(self.clone(), index, frequency, system_clock_source);
|
|
let timer = if let Ok(timer) = timer {
|
|
timers.insert(index, timer.clone());
|
|
|
|
match unsafe { timer.clone().init() } {
|
|
Ok(()) => Ok(timer),
|
|
Err(error) => Err(error),
|
|
}
|
|
} else {
|
|
timer
|
|
};
|
|
|
|
let regs = self.regs.write();
|
|
|
|
regs.start();
|
|
|
|
timer
|
|
}
|
|
|
|
fn read_counter(&self) -> u64 {
|
|
self.regs.read().counter()
|
|
}
|
|
}
|
|
|
|
impl TimerRegs {
|
|
fn is_periodic(&self) -> bool {
|
|
self.Tn_CONFIG.matches_all(Tn_CONFIG::Tn_PER_INT_CAP::SET)
|
|
}
|
|
|
|
fn is_64_bit(&self) -> bool {
|
|
self.Tn_CONFIG.matches_all(Tn_CONFIG::Tn_SIZE_CAP::SET)
|
|
}
|
|
|
|
fn is_fsb_capable(&self) -> bool {
|
|
self.Tn_CONFIG
|
|
.matches_all(Tn_CONFIG::Tn_FSB_INT_DEL_CAP::SET)
|
|
}
|
|
|
|
fn select_irq(&self) -> Option<Irq> {
|
|
let mask = self.Tn_CONFIG.read(Tn_CONFIG::Tn_INT_ROUTE_CAP);
|
|
for i in 9..32 {
|
|
if mask & (1 << i) != 0 {
|
|
return Some(Irq::External(i));
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn stop(&self) {
|
|
self.Tn_CONFIG.modify(Tn_CONFIG::Tn_INT_ENB_CNF::CLEAR);
|
|
}
|
|
|
|
fn enable(&self, irq: IrqDestination) {
|
|
let config = match irq {
|
|
IrqDestination::Irq(Irq::External(irq)) => {
|
|
Tn_CONFIG::Tn_INT_ROUTE_CNF.val(irq as u64) + Tn_CONFIG::Tn_INT_TYPE_CNF::Edge
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
self.Tn_CONFIG.modify(config);
|
|
self.Tn_CONFIG.modify(Tn_CONFIG::Tn_INT_ENB_CNF::SET);
|
|
}
|
|
|
|
fn configure_oneshot(&self, compare_value: u64, irq: IrqDestination) {
|
|
self.Tn_CONFIG
|
|
.modify(Tn_CONFIG::Tn_32MODE_CNF::CLEAR + Tn_CONFIG::Tn_TYPE_CNF::NonPeriodic);
|
|
|
|
self.Tn_COMPARATOR.set(compare_value);
|
|
|
|
self.enable(irq);
|
|
}
|
|
|
|
fn configure_periodic(&self, compare_value: u64, period: u64, irq: IrqDestination) {
|
|
self.Tn_CONFIG.modify(
|
|
Tn_CONFIG::Tn_VAL_SET_CNF::SET
|
|
+ Tn_CONFIG::Tn_32MODE_CNF::CLEAR
|
|
+ Tn_CONFIG::Tn_TYPE_CNF::Periodic,
|
|
);
|
|
self.Tn_COMPARATOR.set(compare_value);
|
|
self.Tn_COMPARATOR.set(period);
|
|
|
|
self.enable(irq);
|
|
}
|
|
|
|
fn rearm(&self, compare_value: u64) {
|
|
self.Tn_COMPARATOR.set(compare_value);
|
|
}
|
|
}
|
|
|
|
impl GeneralRegs {
|
|
fn stop(&self) {
|
|
self.GENERAL_CONFIG
|
|
.modify(GENERAL_CONFIG::ENABLE_CNF::CLEAR);
|
|
}
|
|
fn start(&self) {
|
|
self.GENERAL_CONFIG.modify(GENERAL_CONFIG::ENABLE_CNF::SET);
|
|
}
|
|
|
|
fn timer_count(&self) -> usize {
|
|
self.GENERAL_CAPABILITIES
|
|
.read(GENERAL_CAPABILITIES::NUM_TIM_CAP) as usize
|
|
+ 1
|
|
}
|
|
|
|
fn timer_period(&self) -> u64 {
|
|
self.GENERAL_CAPABILITIES
|
|
.read(GENERAL_CAPABILITIES::COUNTER_CLK_PERIOD)
|
|
}
|
|
|
|
fn counter(&self) -> u64 {
|
|
self.MAIN_COUNTER.get()
|
|
}
|
|
}
|
|
|
|
enum IrqDestination {
|
|
Irq(Irq),
|
|
}
|