| // Copyright (C) 2024 Intel Corporation. | |
| // Author(s): Zhao Liu <zhao1.liu@intel.com> | |
| // SPDX-License-Identifier: GPL-2.0-or-later | |
| use std::{ | |
| ffi::CStr, | |
| mem::MaybeUninit, | |
| pin::Pin, | |
| ptr::{addr_of_mut, null_mut, NonNull}, | |
| slice::from_ref, | |
| }; | |
| use bql::prelude::*; | |
| use common::prelude::*; | |
| use hwcore::prelude::*; | |
| use migration::{self, prelude::*, ToMigrationStateShared}; | |
| use qom::prelude::*; | |
| use system::{ | |
| bindings::{address_space_memory, address_space_stl_le}, | |
| prelude::*, | |
| MEMTXATTRS_UNSPECIFIED, | |
| }; | |
| use util::prelude::*; | |
| use crate::fw_cfg::HPETFwConfig; | |
| ::trace::include_trace!("hw_timer"); | |
| /// Register space for each timer block (`HPET_BASE` is defined in hpet.h). | |
| const HPET_REG_SPACE_LEN: u64 = 0x400; // 1024 bytes | |
| /// Minimum recommended hardware implementation. | |
| const HPET_MIN_TIMERS: usize = 3; | |
| /// Maximum timers in each timer block. | |
| const HPET_MAX_TIMERS: usize = 24; | |
| /// Flags that HPETState.flags supports. | |
| const HPET_FLAG_MSI_SUPPORT_SHIFT: usize = 0; | |
| const HPET_NUM_IRQ_ROUTES: usize = 32; | |
| const HPET_LEGACY_PIT_INT: u32 = 0; // HPET_LEGACY_RTC_INT isn't defined here. | |
| const RTC_ISA_IRQ: usize = 8; | |
| const HPET_CLK_PERIOD: u64 = 10; // 10 ns | |
| const FS_PER_NS: u64 = 1000000; // 1000000 femtoseconds == 1 ns | |
| /// Revision ID (bits 0:7). Revision 1 is implemented (refer to v1.0a spec). | |
| const HPET_CAP_REV_ID_VALUE: u64 = 0x1; | |
| const HPET_CAP_REV_ID_SHIFT: usize = 0; | |
| /// Number of Timers (bits 8:12) | |
| const HPET_CAP_NUM_TIM_SHIFT: usize = 8; | |
| /// Counter Size (bit 13) | |
| const HPET_CAP_COUNT_SIZE_CAP_SHIFT: usize = 13; | |
| /// Legacy Replacement Route Capable (bit 15) | |
| const HPET_CAP_LEG_RT_CAP_SHIFT: usize = 15; | |
| /// Vendor ID (bits 16:31) | |
| const HPET_CAP_VENDER_ID_VALUE: u64 = 0x8086; | |
| const HPET_CAP_VENDER_ID_SHIFT: usize = 16; | |
| /// Main Counter Tick Period (bits 32:63) | |
| const HPET_CAP_CNT_CLK_PERIOD_SHIFT: usize = 32; | |
| /// Overall Enable (bit 0) | |
| const HPET_CFG_ENABLE_SHIFT: usize = 0; | |
| /// Legacy Replacement Route (bit 1) | |
| const HPET_CFG_LEG_RT_SHIFT: usize = 1; | |
| /// Other bits are reserved. | |
| const HPET_CFG_WRITE_MASK: u64 = 0x003; | |
| /// bit 0, 7, and bits 16:31 are reserved. | |
| /// bit 4, 5, 15, and bits 32:64 are read-only. | |
| const HPET_TN_CFG_WRITE_MASK: u64 = 0x7f4e; | |
| /// Timer N Interrupt Type (bit 1) | |
| const HPET_TN_CFG_INT_TYPE_SHIFT: usize = 1; | |
| /// Timer N Interrupt Enable (bit 2) | |
| const HPET_TN_CFG_INT_ENABLE_SHIFT: usize = 2; | |
| /// Timer N Type (Periodic enabled or not, bit 3) | |
| const HPET_TN_CFG_PERIODIC_SHIFT: usize = 3; | |
| /// Timer N Periodic Interrupt Capable (support Periodic or not, bit 4) | |
| const HPET_TN_CFG_PERIODIC_CAP_SHIFT: usize = 4; | |
| /// Timer N Size (timer size is 64-bits or 32 bits, bit 5) | |
| const HPET_TN_CFG_SIZE_CAP_SHIFT: usize = 5; | |
| /// Timer N Value Set (bit 6) | |
| const HPET_TN_CFG_SETVAL_SHIFT: usize = 6; | |
| /// Timer N 32-bit Mode (bit 8) | |
| const HPET_TN_CFG_32BIT_SHIFT: usize = 8; | |
| /// Timer N Interrupt Rout (bits 9:13) | |
| const HPET_TN_CFG_INT_ROUTE_MASK: u64 = 0x3e00; | |
| const HPET_TN_CFG_INT_ROUTE_SHIFT: usize = 9; | |
| /// Timer N FSB Interrupt Enable (bit 14) | |
| const HPET_TN_CFG_FSB_ENABLE_SHIFT: usize = 14; | |
| /// Timer N FSB Interrupt Delivery (bit 15) | |
| const HPET_TN_CFG_FSB_CAP_SHIFT: usize = 15; | |
| /// Timer N Interrupt Routing Capability (bits 32:63) | |
| const HPET_TN_CFG_INT_ROUTE_CAP_SHIFT: usize = 32; | |
| #[derive(common::TryInto)] | |
| #[repr(u64)] | |
| #[allow(non_camel_case_types)] | |
| /// Timer register enumerations, masked by 0x18 | |
| enum TimerRegister { | |
| /// Timer N Configuration and Capability Register | |
| CFG = 0, | |
| /// Timer N Comparator Value Register | |
| CMP = 8, | |
| /// Timer N FSB Interrupt Route Register | |
| ROUTE = 16, | |
| } | |
| #[derive(common::TryInto)] | |
| #[repr(u64)] | |
| #[allow(non_camel_case_types)] | |
| /// Global register enumerations | |
| enum GlobalRegister { | |
| /// General Capabilities and ID Register | |
| CAP = 0, | |
| /// General Configuration Register | |
| CFG = 0x10, | |
| /// General Interrupt Status Register | |
| INT_STATUS = 0x20, | |
| /// Main Counter Value Register | |
| COUNTER = 0xF0, | |
| } | |
| enum DecodedRegister<'a> { | |
| /// Global register in the range from `0` to `0xff` | |
| Global(GlobalRegister), | |
| /// Register in the timer block `0x100`...`0x3ff` | |
| Timer(&'a HPETTimer, TimerRegister), | |
| /// Invalid address | |
| #[allow(dead_code)] | |
| Unknown(hwaddr), | |
| } | |
| struct HPETAddrDecode<'a> { | |
| shift: u32, | |
| len: u32, | |
| target: DecodedRegister<'a>, | |
| } | |
| const fn hpet_next_wrap(cur_tick: u64) -> u64 { | |
| (cur_tick | 0xffffffff) + 1 | |
| } | |
| const fn hpet_time_after(a: u64, b: u64) -> bool { | |
| ((b - a) as i64) < 0 | |
| } | |
| const fn ticks_to_ns(value: u64) -> u64 { | |
| value * HPET_CLK_PERIOD | |
| } | |
| const fn ns_to_ticks(value: u64) -> u64 { | |
| value / HPET_CLK_PERIOD | |
| } | |
| // Avoid touching the bits that cannot be written. | |
| const fn hpet_fixup_reg(new: u64, old: u64, mask: u64) -> u64 { | |
| (new & mask) | (old & !mask) | |
| } | |
| const fn activating_bit(old: u64, new: u64, shift: usize) -> bool { | |
| let mask: u64 = 1 << shift; | |
| (old & mask == 0) && (new & mask != 0) | |
| } | |
| const fn deactivating_bit(old: u64, new: u64, shift: usize) -> bool { | |
| let mask: u64 = 1 << shift; | |
| (old & mask != 0) && (new & mask == 0) | |
| } | |
| fn timer_handler(t: &HPETTimer) { | |
| // SFAETY: state field is valid after timer initialization. | |
| let hpet_regs = &unsafe { t.state.as_ref() }.regs; | |
| t.callback(&mut hpet_regs.borrow_mut()) | |
| } | |
| #[derive(Debug, Default)] | |
| pub struct HPETTimerRegisters { | |
| // Memory-mapped, software visible timer registers | |
| /// Timer N Configuration and Capability Register | |
| config: u64, | |
| /// Timer N Comparator Value Register | |
| cmp: u64, | |
| /// Timer N FSB Interrupt Route Register | |
| fsb: u64, | |
| // Hidden register state | |
| /// comparator (extended to counter width) | |
| cmp64: u64, | |
| /// Last value written to comparator | |
| period: u64, | |
| /// timer pop will indicate wrap for one-shot 32-bit | |
| /// mode. Next pop will be actual timer expiration. | |
| wrap_flag: bool, | |
| /// last value armed, to avoid timer storms | |
| last: u64, | |
| } | |
| impl HPETTimerRegisters { | |
| /// calculate next value of the general counter that matches the | |
| /// target (either entirely, or the low 32-bit only depending on | |
| /// the timer mode). | |
| fn update_cmp64(&mut self, cur_tick: u64) { | |
| self.cmp64 = if self.is_32bit_mod() { | |
| let mut result: u64 = cur_tick.deposit(0, 32, self.cmp); | |
| if result < cur_tick { | |
| result += 0x100000000; | |
| } | |
| result | |
| } else { | |
| self.cmp | |
| } | |
| } | |
| const fn is_fsb_route_enabled(&self) -> bool { | |
| self.config & (1 << HPET_TN_CFG_FSB_ENABLE_SHIFT) != 0 | |
| } | |
| const fn is_periodic(&self) -> bool { | |
| self.config & (1 << HPET_TN_CFG_PERIODIC_SHIFT) != 0 | |
| } | |
| const fn is_int_enabled(&self) -> bool { | |
| self.config & (1 << HPET_TN_CFG_INT_ENABLE_SHIFT) != 0 | |
| } | |
| const fn is_32bit_mod(&self) -> bool { | |
| self.config & (1 << HPET_TN_CFG_32BIT_SHIFT) != 0 | |
| } | |
| const fn is_valset_enabled(&self) -> bool { | |
| self.config & (1 << HPET_TN_CFG_SETVAL_SHIFT) != 0 | |
| } | |
| /// True if timer interrupt is level triggered; otherwise, edge triggered. | |
| const fn is_int_level_triggered(&self) -> bool { | |
| self.config & (1 << HPET_TN_CFG_INT_TYPE_SHIFT) != 0 | |
| } | |
| const fn clear_valset(&mut self) { | |
| self.config &= !(1 << HPET_TN_CFG_SETVAL_SHIFT); | |
| } | |
| const fn get_individual_route(&self) -> usize { | |
| ((self.config & HPET_TN_CFG_INT_ROUTE_MASK) >> HPET_TN_CFG_INT_ROUTE_SHIFT) as usize | |
| } | |
| } | |
| /// HPET Timer Abstraction | |
| #[derive(Debug)] | |
| pub struct HPETTimer { | |
| /// timer N index within the timer block (`HPETState`) | |
| #[doc(alias = "tn")] | |
| index: u8, | |
| qemu_timer: Timer, | |
| /// timer block abstraction containing this timer | |
| state: NonNull<HPETState>, | |
| } | |
| // SAFETY: Sync is not automatically derived due to the `state` field, | |
| // which is always dereferenced to a shared reference. | |
| unsafe impl Sync for HPETTimer {} | |
| impl HPETTimer { | |
| fn new(index: u8, state: *const HPETState) -> HPETTimer { | |
| HPETTimer { | |
| index, | |
| // SAFETY: the HPETTimer will only be used after the timer | |
| // is initialized below. | |
| qemu_timer: unsafe { Timer::new() }, | |
| state: NonNull::new(state.cast_mut()).unwrap(), | |
| } | |
| } | |
| fn init_timer(timer: Pin<&mut Self>) { | |
| Timer::init_full( | |
| timer, | |
| None, | |
| CLOCK_VIRTUAL, | |
| Timer::NS, | |
| 0, | |
| timer_handler, | |
| |t| &mut t.qemu_timer, | |
| ); | |
| } | |
| fn get_state(&self) -> &HPETState { | |
| // SAFETY: | |
| // the pointer is convertible to a reference | |
| unsafe { self.state.as_ref() } | |
| } | |
| fn is_int_active(&self, regs: &HPETRegisters) -> bool { | |
| regs.is_timer_int_active(self.index.into()) | |
| } | |
| fn get_int_route(&self, regs: &HPETRegisters) -> usize { | |
| if self.index <= 1 && regs.is_legacy_mode() { | |
| // If LegacyReplacement Route bit is set, HPET specification requires | |
| // timer0 be routed to IRQ0 in NON-APIC or IRQ2 in the I/O APIC, | |
| // timer1 be routed to IRQ8 in NON-APIC or IRQ8 in the I/O APIC. | |
| // | |
| // If the LegacyReplacement Route bit is set, the individual routing | |
| // bits for timers 0 and 1 (APIC or FSB) will have no impact. | |
| // | |
| // FIXME: Consider I/O APIC case. | |
| if self.index == 0 { | |
| 0 | |
| } else { | |
| RTC_ISA_IRQ | |
| } | |
| } else { | |
| // (If the LegacyReplacement Route bit is set) Timer 2-n will be | |
| // routed as per the routing in the timer n config registers. | |
| // ... | |
| // If the LegacyReplacement Route bit is not set, the individual | |
| // routing bits for each of the timers are used. | |
| regs.tn_regs[self.index as usize].get_individual_route() | |
| } | |
| } | |
| fn set_irq(&self, regs: &HPETRegisters, set: bool) { | |
| let tn_regs = ®s.tn_regs[self.index as usize]; | |
| let route = self.get_int_route(regs); | |
| if set && tn_regs.is_int_enabled() && regs.is_hpet_enabled() { | |
| if tn_regs.is_fsb_route_enabled() { | |
| // SAFETY: | |
| // the parameters are valid. | |
| unsafe { | |
| address_space_stl_le( | |
| addr_of_mut!(address_space_memory), | |
| tn_regs.fsb >> 32, // Timer N FSB int addr | |
| tn_regs.fsb as u32, // Timer N FSB int value, truncate! | |
| MEMTXATTRS_UNSPECIFIED, | |
| null_mut(), | |
| ); | |
| } | |
| } else if tn_regs.is_int_level_triggered() { | |
| self.get_state().irqs[route].raise(); | |
| } else { | |
| self.get_state().irqs[route].pulse(); | |
| } | |
| } else if !tn_regs.is_fsb_route_enabled() { | |
| self.get_state().irqs[route].lower(); | |
| } | |
| } | |
| fn update_irq(&self, regs: &mut HPETRegisters, set: bool) { | |
| // If Timer N Interrupt Enable bit is 0, "the timer will | |
| // still operate and generate appropriate status bits, but | |
| // will not cause an interrupt" | |
| regs.int_status = regs.int_status.deposit( | |
| self.index.into(), | |
| 1, | |
| u64::from(set && regs.tn_regs[self.index as usize].is_int_level_triggered()), | |
| ); | |
| self.set_irq(regs, set); | |
| } | |
| fn arm_timer(&self, regs: &mut HPETRegisters, tick: u64) { | |
| let mut ns = regs.get_ns(tick); | |
| let tn_regs = &mut regs.tn_regs[self.index as usize]; | |
| // Clamp period to reasonable min value (1 us) | |
| if tn_regs.is_periodic() && ns - tn_regs.last < 1000 { | |
| ns = tn_regs.last + 1000; | |
| } | |
| tn_regs.last = ns; | |
| self.qemu_timer.modify(tn_regs.last); | |
| } | |
| fn set_timer(&self, regs: &mut HPETRegisters) { | |
| let cur_tick: u64 = regs.get_ticks(); | |
| let tn_regs = &mut regs.tn_regs[self.index as usize]; | |
| tn_regs.wrap_flag = false; | |
| tn_regs.update_cmp64(cur_tick); | |
| let mut next_tick: u64 = tn_regs.cmp64; | |
| if tn_regs.is_32bit_mod() { | |
| // HPET spec says in one-shot 32-bit mode, generate an interrupt when | |
| // counter wraps in addition to an interrupt with comparator match. | |
| if !tn_regs.is_periodic() && tn_regs.cmp64 > hpet_next_wrap(cur_tick) { | |
| tn_regs.wrap_flag = true; | |
| next_tick = hpet_next_wrap(cur_tick); | |
| } | |
| } | |
| self.arm_timer(regs, next_tick); | |
| } | |
| fn del_timer(&self, regs: &mut HPETRegisters) { | |
| // Just remove the timer from the timer_list without destroying | |
| // this timer instance. | |
| self.qemu_timer.delete(); | |
| if self.is_int_active(regs) { | |
| // For level-triggered interrupt, this leaves interrupt status | |
| // register set but lowers irq. | |
| self.update_irq(regs, true); | |
| } | |
| } | |
| fn prepare_tn_cfg_reg_new( | |
| &self, | |
| regs: &mut HPETRegisters, | |
| shift: u32, | |
| len: u32, | |
| val: u64, | |
| ) -> (u64, u64) { | |
| trace::trace_hpet_ram_write_tn_cfg((shift / 8).try_into().unwrap()); | |
| let tn_regs = ®s.tn_regs[self.index as usize]; | |
| let old_val: u64 = tn_regs.config; | |
| let mut new_val: u64 = old_val.deposit(shift, len, val); | |
| new_val = hpet_fixup_reg(new_val, old_val, HPET_TN_CFG_WRITE_MASK); | |
| // Switch level-type interrupt to edge-type. | |
| if deactivating_bit(old_val, new_val, HPET_TN_CFG_INT_TYPE_SHIFT) { | |
| // Do this before changing timer.regs.config; otherwise, if | |
| // HPET_TN_FSB is set, update_irq will not lower the qemu_irq. | |
| self.update_irq(regs, false); | |
| } | |
| (new_val, old_val) | |
| } | |
| /// Configuration and Capability Register | |
| fn set_tn_cfg_reg(&self, regs: &mut HPETRegisters, shift: u32, len: u32, val: u64) { | |
| // Factor out a prepare_tn_cfg_reg_new() to better handle immutable scope. | |
| let (new_val, old_val) = self.prepare_tn_cfg_reg_new(regs, shift, len, val); | |
| regs.tn_regs[self.index as usize].config = new_val; | |
| if activating_bit(old_val, new_val, HPET_TN_CFG_INT_ENABLE_SHIFT) | |
| && self.is_int_active(regs) | |
| { | |
| self.update_irq(regs, true); | |
| } | |
| let tn_regs = &mut regs.tn_regs[self.index as usize]; | |
| if tn_regs.is_32bit_mod() { | |
| tn_regs.cmp = u64::from(tn_regs.cmp as u32); // truncate! | |
| tn_regs.period = u64::from(tn_regs.period as u32); // truncate! | |
| } | |
| if regs.is_hpet_enabled() { | |
| self.set_timer(regs); | |
| } | |
| } | |
| /// Comparator Value Register | |
| fn set_tn_cmp_reg(&self, regs: &mut HPETRegisters, shift: u32, len: u32, val: u64) { | |
| let tn_regs = &mut regs.tn_regs[self.index as usize]; | |
| let mut length = len; | |
| let mut value = val; | |
| if tn_regs.is_32bit_mod() { | |
| // High 32-bits are zero, leave them untouched. | |
| if shift != 0 { | |
| trace::trace_hpet_ram_write_invalid_tn_cmp(); | |
| return; | |
| } | |
| length = 64; | |
| value = u64::from(value as u32); // truncate! | |
| } | |
| trace::trace_hpet_ram_write_tn_cmp((shift / 8).try_into().unwrap()); | |
| if !tn_regs.is_periodic() || tn_regs.is_valset_enabled() { | |
| tn_regs.cmp = tn_regs.cmp.deposit(shift, length, value); | |
| } | |
| if tn_regs.is_periodic() { | |
| tn_regs.period = tn_regs.period.deposit(shift, length, value); | |
| } | |
| tn_regs.clear_valset(); | |
| if regs.is_hpet_enabled() { | |
| self.set_timer(regs); | |
| } | |
| } | |
| /// FSB Interrupt Route Register | |
| fn set_tn_fsb_route_reg(&self, regs: &mut HPETRegisters, shift: u32, len: u32, val: u64) { | |
| let tn_regs = &mut regs.tn_regs[self.index as usize]; | |
| tn_regs.fsb = tn_regs.fsb.deposit(shift, len, val); | |
| } | |
| fn reset(&self, regs: &mut HPETRegisters) { | |
| self.del_timer(regs); | |
| let tn_regs = &mut regs.tn_regs[self.index as usize]; | |
| tn_regs.cmp = u64::MAX; // Comparator Match Registers reset to all 1's. | |
| tn_regs.config = (1 << HPET_TN_CFG_PERIODIC_CAP_SHIFT) | (1 << HPET_TN_CFG_SIZE_CAP_SHIFT); | |
| if self.get_state().has_msi_flag() { | |
| tn_regs.config |= 1 << HPET_TN_CFG_FSB_CAP_SHIFT; | |
| } | |
| // advertise availability of ioapic int | |
| tn_regs.config |= | |
| (u64::from(self.get_state().int_route_cap)) << HPET_TN_CFG_INT_ROUTE_CAP_SHIFT; | |
| tn_regs.period = 0; | |
| tn_regs.wrap_flag = false; | |
| } | |
| /// timer expiration callback | |
| fn callback(&self, regs: &mut HPETRegisters) { | |
| let cur_tick: u64 = regs.get_ticks(); | |
| let tn_regs = &mut regs.tn_regs[self.index as usize]; | |
| let next_tick = if tn_regs.is_periodic() && tn_regs.period != 0 { | |
| while hpet_time_after(cur_tick, tn_regs.cmp64) { | |
| tn_regs.cmp64 += tn_regs.period; | |
| } | |
| if tn_regs.is_32bit_mod() { | |
| tn_regs.cmp = u64::from(tn_regs.cmp64 as u32); // truncate! | |
| } else { | |
| tn_regs.cmp = tn_regs.cmp64; | |
| } | |
| Some(tn_regs.cmp64) | |
| } else { | |
| tn_regs.wrap_flag.then_some(tn_regs.cmp64) | |
| }; | |
| tn_regs.wrap_flag = false; | |
| if let Some(tick) = next_tick { | |
| self.arm_timer(regs, tick); | |
| } | |
| self.update_irq(regs, true); | |
| } | |
| fn read(&self, target: TimerRegister, regs: &HPETRegisters) -> u64 { | |
| let tn_regs = ®s.tn_regs[self.index as usize]; | |
| use TimerRegister::*; | |
| match target { | |
| CFG => tn_regs.config, // including interrupt capabilities | |
| CMP => tn_regs.cmp, // comparator register | |
| ROUTE => tn_regs.fsb, | |
| } | |
| } | |
| fn write( | |
| &self, | |
| target: TimerRegister, | |
| regs: &mut HPETRegisters, | |
| value: u64, | |
| shift: u32, | |
| len: u32, | |
| ) { | |
| use TimerRegister::*; | |
| trace::trace_hpet_ram_write_timer_id(self.index); | |
| match target { | |
| CFG => self.set_tn_cfg_reg(regs, shift, len, value), | |
| CMP => self.set_tn_cmp_reg(regs, shift, len, value), | |
| ROUTE => self.set_tn_fsb_route_reg(regs, shift, len, value), | |
| } | |
| } | |
| } | |
| #[derive(Default, ToMigrationState)] | |
| pub struct HPETRegisters { | |
| // HPET block Registers: Memory-mapped, software visible registers | |
| /// General Capabilities and ID Register | |
| /// | |
| /// Constant and therefore not migrated. | |
| #[migration_state(omit)] | |
| capability: u64, | |
| /// General Configuration Register | |
| config: u64, | |
| /// General Interrupt Status Register | |
| #[doc(alias = "isr")] | |
| int_status: u64, | |
| /// Main Counter Value Register | |
| #[doc(alias = "hpet_counter")] | |
| counter: u64, | |
| /// HPET Timer N Registers | |
| /// | |
| /// Migrated as part of `Migratable<HPETTimer>` | |
| #[migration_state(omit)] | |
| tn_regs: [HPETTimerRegisters; HPET_MAX_TIMERS], | |
| /// Offset of main counter relative to qemu clock. | |
| /// | |
| /// Migrated as a subsection and therefore snapshotted into [`HPETState`] | |
| #[migration_state(omit)] | |
| pub hpet_offset: u64, | |
| } | |
| impl HPETRegisters { | |
| fn get_ticks(&self) -> u64 { | |
| ns_to_ticks(CLOCK_VIRTUAL.get_ns() + self.hpet_offset) | |
| } | |
| fn get_ns(&self, tick: u64) -> u64 { | |
| ticks_to_ns(tick) - self.hpet_offset | |
| } | |
| fn is_legacy_mode(&self) -> bool { | |
| self.config & (1 << HPET_CFG_LEG_RT_SHIFT) != 0 | |
| } | |
| fn is_hpet_enabled(&self) -> bool { | |
| self.config & (1 << HPET_CFG_ENABLE_SHIFT) != 0 | |
| } | |
| fn is_timer_int_active(&self, index: usize) -> bool { | |
| self.int_status & (1 << index) != 0 | |
| } | |
| } | |
| /// HPET Event Timer Block Abstraction | |
| #[repr(C)] | |
| #[derive(qom::Object, hwcore::Device)] | |
| pub struct HPETState { | |
| parent_obj: ParentField<SysBusDevice>, | |
| iomem: MemoryRegion, | |
| regs: Migratable<BqlRefCell<HPETRegisters>>, | |
| // Internal state | |
| /// Capabilities that QEMU HPET supports. | |
| /// bit 0: MSI (or FSB) support. | |
| #[property(rename = "msi", bit = HPET_FLAG_MSI_SUPPORT_SHIFT, default = false)] | |
| flags: u32, | |
| hpet_offset_migration: BqlCell<u64>, | |
| irqs: [InterruptSource; HPET_NUM_IRQ_ROUTES], | |
| rtc_irq_level: BqlCell<u32>, | |
| pit_enabled: InterruptSource, | |
| /// Interrupt Routing Capability. | |
| /// This field indicates to which interrupts in the I/O (x) APIC | |
| /// the timers' interrupt can be routed, and is encoded in the | |
| /// bits 32:64 of timer N's config register: | |
| #[doc(alias = "intcap")] | |
| #[property(rename = "hpet-intcap", default = 0)] | |
| int_route_cap: u32, | |
| /// HPET timer array managed by this timer block. | |
| #[doc(alias = "timer")] | |
| timers: [Migratable<HPETTimer>; HPET_MAX_TIMERS], | |
| #[property(rename = "timers", default = HPET_MIN_TIMERS)] | |
| num_timers: usize, | |
| num_timers_save: BqlCell<u8>, | |
| /// Instance id (HPET timer block ID). | |
| hpet_id: BqlCell<usize>, | |
| } | |
| impl HPETState { | |
| const fn has_msi_flag(&self) -> bool { | |
| self.flags & (1 << HPET_FLAG_MSI_SUPPORT_SHIFT) != 0 | |
| } | |
| fn handle_legacy_irq(&self, irq: u32, level: u32) { | |
| let regs = self.regs.borrow(); | |
| if irq == HPET_LEGACY_PIT_INT { | |
| if !regs.is_legacy_mode() { | |
| self.irqs[0].set(level != 0); | |
| } | |
| } else { | |
| self.rtc_irq_level.set(level); | |
| if !regs.is_legacy_mode() { | |
| self.irqs[RTC_ISA_IRQ].set(level != 0); | |
| } | |
| } | |
| } | |
| fn init_timers(this: &mut MaybeUninit<Self>) { | |
| let state = this.as_ptr(); | |
| for index in 0..HPET_MAX_TIMERS { | |
| let mut timer = uninit_field_mut!(*this, timers[index]); | |
| // Initialize in two steps, to avoid calling Timer::init_full on a | |
| // temporary that can be moved. | |
| let timer = timer.write(Migratable::new(HPETTimer::new( | |
| index.try_into().unwrap(), | |
| state, | |
| ))); | |
| // SAFETY: HPETState is pinned | |
| let timer = unsafe { Pin::new_unchecked(&mut **timer) }; | |
| HPETTimer::init_timer(timer); | |
| } | |
| } | |
| /// General Configuration Register | |
| fn set_cfg_reg(&self, regs: &mut HPETRegisters, shift: u32, len: u32, val: u64) { | |
| let old_val = regs.config; | |
| let mut new_val = old_val.deposit(shift, len, val); | |
| new_val = hpet_fixup_reg(new_val, old_val, HPET_CFG_WRITE_MASK); | |
| regs.config = new_val; | |
| if activating_bit(old_val, new_val, HPET_CFG_ENABLE_SHIFT) { | |
| // Enable main counter and interrupt generation. | |
| regs.hpet_offset = ticks_to_ns(regs.counter) - CLOCK_VIRTUAL.get_ns(); | |
| for t in self.timers.iter().take(self.num_timers) { | |
| let id = t.index as usize; | |
| let tn_regs = ®s.tn_regs[id]; | |
| if tn_regs.is_int_enabled() && t.is_int_active(regs) { | |
| t.update_irq(regs, true); | |
| } | |
| t.set_timer(regs); | |
| } | |
| } else if deactivating_bit(old_val, new_val, HPET_CFG_ENABLE_SHIFT) { | |
| // Halt main counter and disable interrupt generation. | |
| regs.counter = regs.get_ticks(); | |
| for t in self.timers.iter().take(self.num_timers) { | |
| t.del_timer(regs); | |
| } | |
| } | |
| // i8254 and RTC output pins are disabled when HPET is in legacy mode | |
| if activating_bit(old_val, new_val, HPET_CFG_LEG_RT_SHIFT) { | |
| self.pit_enabled.set(false); | |
| self.irqs[0].lower(); | |
| self.irqs[RTC_ISA_IRQ].lower(); | |
| } else if deactivating_bit(old_val, new_val, HPET_CFG_LEG_RT_SHIFT) { | |
| self.irqs[0].lower(); | |
| self.pit_enabled.set(true); | |
| self.irqs[RTC_ISA_IRQ].set(self.rtc_irq_level.get() != 0); | |
| } | |
| } | |
| /// General Interrupt Status Register: Read/Write Clear | |
| fn set_int_status_reg(&self, regs: &mut HPETRegisters, shift: u32, _len: u32, val: u64) { | |
| let new_val = val << shift; | |
| let cleared = new_val & regs.int_status; | |
| for t in self.timers.iter().take(self.num_timers) { | |
| if cleared & (1 << t.index) != 0 { | |
| t.update_irq(regs, false); | |
| } | |
| } | |
| } | |
| /// Main Counter Value Register | |
| fn set_counter_reg(&self, regs: &mut HPETRegisters, shift: u32, len: u32, val: u64) { | |
| if regs.is_hpet_enabled() { | |
| // HPET spec says that writes to this register should only be | |
| // done while the counter is halted. So this is an undefined | |
| // behavior. There's no need to forbid it, but when HPET is | |
| // enabled, the changed counter value will not affect the | |
| // tick count (i.e., the previously calculated offset will | |
| // not be changed as well). | |
| trace::trace_hpet_ram_write_counter_write_while_enabled(); | |
| } | |
| regs.counter = regs.counter.deposit(shift, len, val); | |
| } | |
| unsafe fn init(mut this: ParentInit<Self>) { | |
| static HPET_RAM_OPS: MemoryRegionOps<HPETState> = | |
| MemoryRegionOpsBuilder::<HPETState>::new() | |
| .read(&HPETState::read) | |
| .write(&HPETState::write) | |
| .little_endian() | |
| .valid_sizes(4, 8) | |
| .impl_sizes(4, 8) | |
| .build(); | |
| MemoryRegion::init_io( | |
| &mut uninit_field_mut!(*this, iomem), | |
| &HPET_RAM_OPS, | |
| "hpet", | |
| HPET_REG_SPACE_LEN, | |
| ); | |
| // Only consider members with more complex structures. C has already | |
| // initialized memory to all zeros - simple types (bool/u32/usize) can | |
| // rely on this without explicit initialization. | |
| uninit_field_mut!(*this, regs).write(Default::default()); | |
| uninit_field_mut!(*this, hpet_offset_migration).write(Default::default()); | |
| // Set null_mut for now and post_init() will fill it. | |
| uninit_field_mut!(*this, irqs).write(Default::default()); | |
| uninit_field_mut!(*this, rtc_irq_level).write(Default::default()); | |
| uninit_field_mut!(*this, pit_enabled).write(Default::default()); | |
| uninit_field_mut!(*this, num_timers_save).write(Default::default()); | |
| uninit_field_mut!(*this, hpet_id).write(Default::default()); | |
| Self::init_timers(&mut this); | |
| } | |
| fn post_init(&self) { | |
| self.init_mmio(&self.iomem); | |
| for irq in self.irqs.iter() { | |
| self.init_irq(irq); | |
| } | |
| } | |
| fn realize(&self) -> util::Result<()> { | |
| ensure!( | |
| (HPET_MIN_TIMERS..=HPET_MAX_TIMERS).contains(&self.num_timers), | |
| "hpet.num_timers must be between {HPET_MIN_TIMERS} and {HPET_MAX_TIMERS}" | |
| ); | |
| ensure!( | |
| self.int_route_cap != 0, | |
| "hpet.hpet-intcap property not initialized" | |
| ); | |
| self.hpet_id.set(HPETFwConfig::assign_hpet_id()?); | |
| // 64-bit General Capabilities and ID Register; LegacyReplacementRoute. | |
| self.regs.borrow_mut().capability = HPET_CAP_REV_ID_VALUE << HPET_CAP_REV_ID_SHIFT | | |
| 1 << HPET_CAP_COUNT_SIZE_CAP_SHIFT | | |
| 1 << HPET_CAP_LEG_RT_CAP_SHIFT | | |
| HPET_CAP_VENDER_ID_VALUE << HPET_CAP_VENDER_ID_SHIFT | | |
| ((self.num_timers - 1) as u64) << HPET_CAP_NUM_TIM_SHIFT | // indicate the last timer | |
| (HPET_CLK_PERIOD * FS_PER_NS) << HPET_CAP_CNT_CLK_PERIOD_SHIFT; // 10 ns | |
| self.init_gpio_in(2, HPETState::handle_legacy_irq); | |
| self.init_gpio_out(from_ref(&self.pit_enabled)); | |
| Ok(()) | |
| } | |
| fn reset_hold(&self, _type: ResetType) { | |
| let mut regs = self.regs.borrow_mut(); | |
| for t in self.timers.iter().take(self.num_timers) { | |
| t.reset(&mut regs); | |
| } | |
| regs.counter = 0; | |
| regs.config = 0; | |
| regs.hpet_offset = 0; | |
| HPETFwConfig::update_hpet_cfg( | |
| self.hpet_id.get(), | |
| regs.capability as u32, | |
| self.mmio_addr(0).unwrap(), | |
| ); | |
| // pit_enabled.set(true) will call irq handler and access regs | |
| // again. We cannot borrow BqlRefCell twice at once. Minimize the | |
| // scope of regs to ensure it will be dropped before irq callback. | |
| drop(regs); | |
| self.pit_enabled.set(true); | |
| // to document that the RTC lowers its output on reset as well | |
| self.rtc_irq_level.set(0); | |
| } | |
| fn decode(&self, mut addr: hwaddr, size: u32) -> HPETAddrDecode<'_> { | |
| let shift = ((addr & 4) * 8) as u32; | |
| let len = std::cmp::min(size * 8, 64 - shift); | |
| addr &= !4; | |
| let target = if (0..=0xff).contains(&addr) { | |
| GlobalRegister::try_from(addr).map(DecodedRegister::Global) | |
| } else { | |
| let timer_id: usize = ((addr - 0x100) / 0x20) as usize; | |
| if timer_id < self.num_timers { | |
| TimerRegister::try_from(addr & 0x18) | |
| .map(|target| DecodedRegister::Timer(&self.timers[timer_id], target)) | |
| } else { | |
| trace::trace_hpet_timer_id_out_of_range(timer_id.try_into().unwrap()); | |
| Err(addr) | |
| } | |
| }; | |
| // `target` is now a Result<DecodedRegister, hwaddr> | |
| // convert the Err case into DecodedRegister as well | |
| let target = target.unwrap_or_else(DecodedRegister::Unknown); | |
| HPETAddrDecode { shift, len, target } | |
| } | |
| fn read(&self, addr: hwaddr, size: u32) -> u64 { | |
| trace::trace_hpet_ram_read(addr); | |
| let HPETAddrDecode { shift, target, .. } = self.decode(addr, size); | |
| let regs = &self.regs.borrow(); | |
| use DecodedRegister::*; | |
| use GlobalRegister::*; | |
| (match target { | |
| Timer(t, tn_target) => t.read(tn_target, regs), | |
| Global(CAP) => regs.capability, /* including HPET_PERIOD 0x004 */ | |
| Global(CFG) => regs.config, | |
| Global(INT_STATUS) => regs.int_status, | |
| Global(COUNTER) => { | |
| let cur_tick = if regs.is_hpet_enabled() { | |
| regs.get_ticks() | |
| } else { | |
| regs.counter | |
| }; | |
| trace::trace_hpet_ram_read_reading_counter((addr & 4) as u8, cur_tick); | |
| cur_tick | |
| } | |
| Unknown(_) => { | |
| trace::trace_hpet_ram_read_invalid(); | |
| 0 | |
| } | |
| }) >> shift | |
| } | |
| fn write(&self, addr: hwaddr, value: u64, size: u32) { | |
| let HPETAddrDecode { shift, len, target } = self.decode(addr, size); | |
| let mut regs = self.regs.borrow_mut(); | |
| trace::trace_hpet_ram_write(addr, value); | |
| use DecodedRegister::*; | |
| use GlobalRegister::*; | |
| match target { | |
| Timer(t, tn_target) => t.write(tn_target, &mut regs, value, shift, len), | |
| Global(CAP) => {} // General Capabilities and ID Register: Read Only | |
| Global(CFG) => self.set_cfg_reg(&mut regs, shift, len, value), | |
| Global(INT_STATUS) => self.set_int_status_reg(&mut regs, shift, len, value), | |
| Global(COUNTER) => self.set_counter_reg(&mut regs, shift, len, value), | |
| Unknown(_) => trace::trace_hpet_ram_write_invalid(), | |
| } | |
| } | |
| fn pre_save(&self) -> Result<(), migration::Infallible> { | |
| let mut regs = self.regs.borrow_mut(); | |
| self.hpet_offset_migration.set(regs.hpet_offset); | |
| if regs.is_hpet_enabled() { | |
| regs.counter = regs.get_ticks(); | |
| } | |
| /* | |
| * The number of timers must match on source and destination, but it was | |
| * also added to the migration stream. Check that it matches the value | |
| * that was configured. | |
| */ | |
| self.num_timers_save.set(self.num_timers as u8); | |
| Ok(()) | |
| } | |
| fn post_load(&self, _version_id: u8) -> Result<(), migration::Infallible> { | |
| let mut regs = self.regs.borrow_mut(); | |
| let cnt = regs.counter; | |
| for tn_regs in regs.tn_regs.iter_mut().take(self.num_timers) { | |
| tn_regs.update_cmp64(cnt); | |
| tn_regs.last = CLOCK_VIRTUAL.get_ns() - NANOSECONDS_PER_SECOND; | |
| } | |
| regs.hpet_offset = self.hpet_offset_migration.get(); | |
| Ok(()) | |
| } | |
| fn is_rtc_irq_level_needed(&self) -> bool { | |
| self.rtc_irq_level.get() != 0 | |
| } | |
| fn is_offset_needed(&self) -> bool { | |
| self.regs.borrow().is_hpet_enabled() | |
| } | |
| fn validate_num_timers(&self, _version_id: u8) -> bool { | |
| self.num_timers == self.num_timers_save.get().into() | |
| } | |
| } | |
| qom_isa!(HPETState: SysBusDevice, DeviceState, Object); | |
| unsafe impl ObjectType for HPETState { | |
| // No need for HPETClass. Just like OBJECT_DECLARE_SIMPLE_TYPE in C. | |
| type Class = <SysBusDevice as ObjectType>::Class; | |
| const TYPE_NAME: &'static CStr = crate::TYPE_HPET; | |
| } | |
| impl ObjectImpl for HPETState { | |
| type ParentType = SysBusDevice; | |
| const INSTANCE_INIT: Option<unsafe fn(ParentInit<Self>)> = Some(Self::init); | |
| const INSTANCE_POST_INIT: Option<fn(&Self)> = Some(Self::post_init); | |
| const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>; | |
| } | |
| static VMSTATE_HPET_RTC_IRQ_LEVEL: VMStateDescription<HPETState> = | |
| VMStateDescriptionBuilder::<HPETState>::new() | |
| .name(c"hpet/rtc_irq_level") | |
| .version_id(1) | |
| .minimum_version_id(1) | |
| .needed(&HPETState::is_rtc_irq_level_needed) | |
| .fields(vmstate_fields! { | |
| vmstate_of!(HPETState, rtc_irq_level), | |
| }) | |
| .build(); | |
| static VMSTATE_HPET_OFFSET: VMStateDescription<HPETState> = | |
| VMStateDescriptionBuilder::<HPETState>::new() | |
| .name(c"hpet/offset") | |
| .version_id(1) | |
| .minimum_version_id(1) | |
| .needed(&HPETState::is_offset_needed) | |
| .fields(vmstate_fields! { | |
| vmstate_of!(HPETState, hpet_offset_migration), | |
| }) | |
| .build(); | |
| #[derive(Default)] | |
| pub struct HPETTimerMigration { | |
| index: u8, | |
| config: u64, | |
| cmp: u64, | |
| fsb: u64, | |
| period: u64, | |
| wrap_flag: u8, | |
| qemu_timer: i64, | |
| } | |
| impl ToMigrationState for HPETTimer { | |
| type Migrated = HPETTimerMigration; | |
| fn snapshot_migration_state( | |
| &self, | |
| target: &mut Self::Migrated, | |
| ) -> Result<(), migration::InvalidError> { | |
| let state = self.get_state(); | |
| let regs = state.regs.borrow_mut(); | |
| let tn_regs = ®s.tn_regs[self.index as usize]; | |
| target.index = self.index; | |
| target.config = tn_regs.config; | |
| target.cmp = tn_regs.cmp; | |
| target.fsb = tn_regs.fsb; | |
| target.period = tn_regs.period; | |
| target.wrap_flag = u8::from(tn_regs.wrap_flag); | |
| self.qemu_timer | |
| .snapshot_migration_state(&mut target.qemu_timer)?; | |
| Ok(()) | |
| } | |
| fn restore_migrated_state_mut( | |
| &mut self, | |
| source: Self::Migrated, | |
| version_id: u8, | |
| ) -> Result<(), migration::InvalidError> { | |
| self.restore_migrated_state(source, version_id) | |
| } | |
| } | |
| impl ToMigrationStateShared for HPETTimer { | |
| fn restore_migrated_state( | |
| &self, | |
| source: Self::Migrated, | |
| version_id: u8, | |
| ) -> Result<(), migration::InvalidError> { | |
| let state = self.get_state(); | |
| let mut regs = state.regs.borrow_mut(); | |
| let tn_regs = &mut regs.tn_regs[self.index as usize]; | |
| tn_regs.config = source.config; | |
| tn_regs.cmp = source.cmp; | |
| tn_regs.fsb = source.fsb; | |
| tn_regs.period = source.period; | |
| tn_regs.wrap_flag = source.wrap_flag != 0; | |
| self.qemu_timer | |
| .restore_migrated_state(source.qemu_timer, version_id)?; | |
| Ok(()) | |
| } | |
| } | |
| const VMSTATE_HPET_TIMER: VMStateDescription<HPETTimerMigration> = | |
| VMStateDescriptionBuilder::<HPETTimerMigration>::new() | |
| .name(c"hpet_timer") | |
| .version_id(1) | |
| .minimum_version_id(1) | |
| .fields(vmstate_fields! { | |
| vmstate_of!(HPETTimerMigration, index), | |
| vmstate_of!(HPETTimerMigration, config), | |
| vmstate_of!(HPETTimerMigration, cmp), | |
| vmstate_of!(HPETTimerMigration, fsb), | |
| vmstate_of!(HPETTimerMigration, period), | |
| vmstate_of!(HPETTimerMigration, wrap_flag), | |
| vmstate_of!(HPETTimerMigration, qemu_timer), | |
| }) | |
| .build(); | |
| impl_vmstate_struct!(HPETTimerMigration, VMSTATE_HPET_TIMER); | |
| const VALIDATE_TIMERS_NAME: &CStr = c"num_timers must match"; | |
| // HPETRegistersMigration is generated by ToMigrationState macro. | |
| impl_vmstate_struct!( | |
| HPETRegistersMigration, | |
| VMStateDescriptionBuilder::<HPETRegistersMigration>::new() | |
| .name(c"hpet/regs") | |
| .version_id(2) | |
| .minimum_version_id(2) | |
| .fields(vmstate_fields! { | |
| vmstate_of!(HPETRegistersMigration, config), | |
| vmstate_of!(HPETRegistersMigration, int_status), | |
| vmstate_of!(HPETRegistersMigration, counter), | |
| }) | |
| .build() | |
| ); | |
| const VMSTATE_HPET: VMStateDescription<HPETState> = | |
| VMStateDescriptionBuilder::<HPETState>::new() | |
| .name(c"hpet") | |
| .version_id(2) | |
| .minimum_version_id(2) | |
| .pre_save(&HPETState::pre_save) | |
| .post_load(&HPETState::post_load) | |
| .fields(vmstate_fields! { | |
| vmstate_of!(HPETState, regs), | |
| vmstate_of!(HPETState, num_timers_save), | |
| vmstate_validate!(HPETState, VALIDATE_TIMERS_NAME, HPETState::validate_num_timers), | |
| vmstate_of!(HPETState, timers[0 .. num_timers_save], HPETState::validate_num_timers).with_version_id(0), | |
| }) | |
| .subsections(vmstate_subsections!( | |
| VMSTATE_HPET_RTC_IRQ_LEVEL, | |
| VMSTATE_HPET_OFFSET, | |
| )) | |
| .build(); | |
| impl DeviceImpl for HPETState { | |
| const VMSTATE: Option<VMStateDescription<Self>> = Some(VMSTATE_HPET); | |
| const REALIZE: Option<fn(&Self) -> util::Result<()>> = Some(Self::realize); | |
| } | |
| impl ResettablePhasesImpl for HPETState { | |
| const HOLD: Option<fn(&Self, ResetType)> = Some(Self::reset_hold); | |
| } | |
| impl SysBusDeviceImpl for HPETState {} |