x86-64: couldn't get HPET to work on real hw

This commit is contained in:
Mark Poliakov 2023-09-07 11:59:20 +03:00
parent c543576685
commit 3330beedfd
5 changed files with 107 additions and 290 deletions

View File

@ -5,6 +5,8 @@ edition = "2021"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.dev.package.tock-registers]
opt-level = 3
[dependencies]
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
@ -22,9 +24,6 @@ tock-registers = "0.8.1"
cfg-if = "1.0.0"
git-version = "0.3.5"
# bitmap-font = { version = "0.3.0", optional = true }
# embedded-graphics = { version = "0.8.0", optional = true }
log = "0.4.20"
futures-util = { version = "0.3.28", default-features = false, features = ["alloc", "async-await"] }
crossbeam-queue = { version = "0.3.8", default-features = false, features = ["alloc"] }

View File

@ -2,7 +2,7 @@
use core::sync::atomic::Ordering;
use abi::error::Error;
use acpi_lib::{mcfg::Mcfg, AcpiTables, HpetInfo, InterruptModel};
use acpi_lib::{mcfg::Mcfg, AcpiTables, InterruptModel};
use alloc::boxed::Box;
use cpu::Cpu;
use device_api::{
@ -45,7 +45,7 @@ use self::{
acpi::{AcpiAllocator, AcpiHandlerImpl},
apic::ioapic::IoApic,
intrinsics::{IoPort, IoPortAccess},
peripherals::{hpet::Hpet, ps2::PS2Controller},
peripherals::{i8253::I8253, ps2::PS2Controller},
smp::CPU_COUNT,
};
@ -140,7 +140,7 @@ pub struct X86_64 {
combined_terminal: OneTimeInit<CombinedTerminal>,
ioapic: OneTimeInit<IoApic>,
timer: OneTimeInit<Hpet>,
timer: OneTimeInit<I8253>,
}
/// x86-64 architecture implementation
@ -392,9 +392,6 @@ impl X86_64 {
self.ioapic.init(IoApic::from_acpi(&apic_info).unwrap());
let hpet = HpetInfo::new(acpi).unwrap();
self.timer.init(Hpet::from_acpi(&hpet).unwrap());
acpi::init_acpi(acpi).unwrap();
// Enumerate PCIe root devices
@ -442,8 +439,11 @@ impl X86_64 {
if cpu_id == 0 {
self.init_acpi_from_boot_data();
Self::disable_8259();
self.timer.init(I8253::new());
// Initialize debug output as soon as possible
let com1_3 = Box::leak(Box::new(ComPort::new(0x3F8, 0x3E8, IrqNumber::Isa(4))));
debug::add_sink(com1_3.port_a(), LogLevel::Debug);
@ -478,7 +478,7 @@ impl X86_64 {
ps2.init_irq().unwrap();
device::register_device(self.ioapic.get());
device::register_device(self.timer.get());
// device::register_device(self.timer.get());
device::register_device(ps2);
// Initialize devices from PCI bus

View File

@ -1,279 +0,0 @@
//! 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},
device::timer,
mem::device::DeviceMemoryIo,
sync::IrqSafeSpinlock,
};
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;
// 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)
};
timer::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 inner = self.inner.lock();
let tim0 = &inner.regs.Timers[0];
if inner.tim0_period > 0x100000000
&& !tim0
.ConfigurationCapability
.matches_all(TimerConfigurationCapablities::TM_SIZE_CAP::Timer64Bit)
{
panic!("Period is too large and timer doesn't support 64-bit");
}
// 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),
})
}
}

View File

@ -0,0 +1,97 @@
use core::time::Duration;
use abi::error::Error;
use device_api::{interrupt::InterruptHandler, timer::MonotonicTimestampProviderDevice, Device};
use crate::{
arch::{
x86_64::{
intrinsics::{IoPort, IoPortAccess},
IrqNumber,
},
Architecture, ARCHITECTURE,
},
sync::IrqSafeSpinlock,
task::runtime,
};
const FREQUENCY: u32 = 1193180;
const CMD_CH0: u8 = 0 << 6;
const CMD_ACC_WORD: u8 = 3 << 4;
const CMD_MODE_RATE: u8 = 2 << 1;
struct Inner {
ch0_data: IoPort<u8>,
#[allow(unused)]
ch1_data: IoPort<u8>,
#[allow(unused)]
ch2_data: IoPort<u8>,
cmd: IoPort<u8>,
tick: u64,
}
pub struct I8253 {
inner: IrqSafeSpinlock<Inner>,
}
impl MonotonicTimestampProviderDevice for I8253 {
fn monotonic_timestamp(&self) -> Result<Duration, Error> {
let tick = self.inner.lock().tick;
Ok(Duration::from_millis(tick))
}
}
impl InterruptHandler for I8253 {
fn handle_irq(&self) -> bool {
let mut inner = self.inner.lock();
inner.tick += 1;
let now = Duration::from_millis(inner.tick);
drop(inner);
runtime::tick(now);
true
}
}
impl Device for I8253 {
fn display_name(&self) -> &'static str {
"i8253 PIT"
}
unsafe fn init_irq(&'static self) -> Result<(), Error> {
let intc = ARCHITECTURE.external_interrupt_controller();
let inner = self.inner.lock();
let div: u16 = (FREQUENCY / 1000).try_into().unwrap();
inner.cmd.write(CMD_CH0 | CMD_ACC_WORD | CMD_MODE_RATE);
inner.ch0_data.write(div as u8);
inner.ch0_data.write((div >> 8) as u8);
let irq = IrqNumber::Isa(0);
intc.register_irq(irq, Default::default(), self)?;
intc.enable_irq(irq)?;
Ok(())
}
}
impl I8253 {
pub const fn new() -> Self {
Self {
inner: IrqSafeSpinlock::new(Inner {
ch0_data: IoPort::new(0x40),
ch1_data: IoPort::new(0x41),
ch2_data: IoPort::new(0x42),
cmd: IoPort::new(0x43),
tick: 0,
}),
}
}
}

View File

@ -1,5 +1,5 @@
//! x86-64 platform peripheral drivers
pub mod hpet;
pub mod i8253;
pub mod ps2;
pub mod serial;