| // 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 = 32; |
| |
| /// 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>, |
| #[property(rename = "hpet-offset-saved", default = true)] |
| hpet_offset_saved: bool, |
| |
| 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 index in 0..self.num_timers { |
| let tn_regs = &mut regs.tn_regs[index]; |
| |
| tn_regs.update_cmp64(cnt); |
| tn_regs.last = CLOCK_VIRTUAL.get_ns() - NANOSECONDS_PER_SECOND; |
| } |
| |
| // Recalculate the offset between the main counter and guest time |
| if !self.hpet_offset_saved { |
| self.hpet_offset_migration |
| .set(ticks_to_ns(regs.counter) - CLOCK_VIRTUAL.get_ns()); |
| } |
| 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() && self.hpet_offset_saved |
| } |
| |
| 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 {} |