use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::{Arc, Weak};
use alloc::vec::Vec;
use core::fmt;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind, SinkPin};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::sched::{AccessKind, LazyHandle};
use crate::core::space::{AccessConstraints, MemAttrs, MemOps, MemResult, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{AtomicU64, LockRank, Mutex, Ordering};
use crate::core::value::{Endian, Width};
use crate::core::wire::{
FanIn, IntAck, IntAckCycle, IntAckResponse, Level, Resolve, WireId, WireSink, WireSource,
};
use crate::machine::realize::Instance;
use crate::machine::validate::ClassSchema;
pub use bus::{ApicBus, Delivery, EoiSink, Message, Shorthand, Target};
pub const CLASS_NAME: &str = "pc.lapic";
const STATE_VERSION: u32 = 1;
pub const REGISTER_WINDOW_LEN: u64 = 0x1000;
pub const DEFAULT_BASE: u64 = 0xfee0_0000;
pub mod bus {
use alloc::sync::{Arc, Weak};
use alloc::vec::Vec;
use core::fmt;
use crate::core::error::Result;
use crate::core::hosts::{HostKind, HostObjects};
use crate::core::props::Props;
use crate::core::sync::{LockRank, Mutex};
pub const KIND: HostKind = HostKind::new("apic-bus");
pub const DEFAULT_NAME: &str = "apic";
pub const BUS_RANK: LockRank = LockRank::new(0x4c60);
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Delivery(pub u8);
impl Delivery {
pub const FIXED: Delivery = Delivery(0b000);
pub const LOWEST: Delivery = Delivery(0b001);
pub const SMI: Delivery = Delivery(0b010);
pub const NMI: Delivery = Delivery(0b100);
pub const INIT: Delivery = Delivery(0b101);
pub const STARTUP: Delivery = Delivery(0b110);
pub const EXTINT: Delivery = Delivery(0b111);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Shorthand {
Dest,
SelfOnly,
All,
AllButSelf,
}
impl Shorthand {
#[must_use]
pub const fn from_bits(bits: u32) -> Shorthand {
match bits & 3 {
1 => Shorthand::SelfOnly,
2 => Shorthand::All,
3 => Shorthand::AllButSelf,
_ => Shorthand::Dest,
}
}
#[must_use]
pub const fn bits(self) -> u32 {
match self {
Shorthand::Dest => 0,
Shorthand::SelfOnly => 1,
Shorthand::All => 2,
Shorthand::AllButSelf => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Message {
pub vector: u8,
pub delivery: Delivery,
pub logical: bool,
pub dest: u8,
pub level_triggered: bool,
pub assert: bool,
}
impl Message {
#[must_use]
pub const fn fixed(vector: u8, dest: u8) -> Message {
Message {
vector,
delivery: Delivery::FIXED,
logical: false,
dest,
level_triggered: false,
assert: true,
}
}
}
pub trait Target: Send + Sync + fmt::Debug {
fn apic_id(&self) -> u8;
fn logical_match(&self, dest: u8) -> bool;
fn arbitration_priority(&self) -> u8;
fn accept(&self, message: Message);
}
pub trait EoiSink: Send + Sync + fmt::Debug {
fn eoi(&self, vector: u8);
}
#[derive(Default)]
pub struct ApicBus {
targets: Mutex<Vec<Weak<dyn Target>>>,
eoi: Mutex<Vec<Weak<dyn EoiSink>>>,
}
impl fmt::Debug for ApicBus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ApicBus")
.field("targets", &self.targets.lock().len())
.field("eoi_sinks", &self.eoi.lock().len())
.finish()
}
}
impl ApicBus {
#[must_use]
pub fn new() -> ApicBus {
ApicBus {
targets: Mutex::with_rank(BUS_RANK, Vec::new()),
eoi: Mutex::with_rank(BUS_RANK, Vec::new()),
}
}
pub fn attach(&self, target: Weak<dyn Target>) {
self.targets.lock().push(target);
}
pub fn attach_eoi(&self, sink: Weak<dyn EoiSink>) {
self.eoi.lock().push(sink);
}
#[must_use]
pub fn len(&self) -> usize {
self.targets
.lock()
.iter()
.filter(|t| t.strong_count() > 0)
.count()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn roster(&self) -> Vec<Arc<dyn Target>> {
self.targets
.lock()
.iter()
.filter_map(Weak::upgrade)
.collect()
}
pub fn deliver(&self, message: Message, from: Option<u8>, shorthand: Shorthand) {
let roster = self.roster();
let mut selected: Vec<&Arc<dyn Target>> = Vec::new();
for target in &roster {
let id = target.apic_id();
let chosen = match shorthand {
Shorthand::SelfOnly => Some(id) == from,
Shorthand::All => true,
Shorthand::AllButSelf => Some(id) != from,
Shorthand::Dest => {
if message.logical {
target.logical_match(message.dest)
} else {
message.dest == 0xff || message.dest == id
}
}
};
if chosen {
selected.push(target);
}
}
if message.delivery == Delivery::LOWEST {
let winner = selected
.iter()
.min_by_key(|t| (t.arbitration_priority(), t.apic_id()))
.copied();
selected.clear();
selected.extend(winner);
}
for target in selected {
target.accept(message);
}
}
pub fn broadcast_eoi(&self, vector: u8) {
let sinks: Vec<Arc<dyn EoiSink>> =
self.eoi.lock().iter().filter_map(Weak::upgrade).collect();
for sink in sinks {
sink.eoi(vector);
}
}
}
pub fn open(hosts: &HostObjects, name: &str) -> Result<Arc<ApicBus>> {
hosts.open(KIND, name, ApicBus::new)
}
pub fn attach(props: &Props, name: &str) -> Result<Arc<ApicBus>> {
props.host(KIND, name, ApicBus::new)
}
}
const REG_STRIDE: u64 = 0x10;
const REG_ID: u64 = 0x020;
const REG_VERSION: u64 = 0x030;
const REG_TPR: u64 = 0x080;
const REG_APR: u64 = 0x090;
const REG_PPR: u64 = 0x0a0;
const REG_EOI: u64 = 0x0b0;
const REG_RRD: u64 = 0x0c0;
const REG_LDR: u64 = 0x0d0;
const REG_DFR: u64 = 0x0e0;
const REG_SVR: u64 = 0x0f0;
const REG_ISR: u64 = 0x100;
const REG_TMR: u64 = 0x180;
const REG_IRR: u64 = 0x200;
const REG_ESR: u64 = 0x280;
const REG_ICR_LOW: u64 = 0x300;
const REG_ICR_HIGH: u64 = 0x310;
const REG_LVT_BASE: u64 = 0x320;
const REG_TIMER_INIT: u64 = 0x380;
const REG_TIMER_CUR: u64 = 0x390;
const REG_TIMER_DIV: u64 = 0x3e0;
const VERSION: u32 = 0x14;
const LVT_COUNT: usize = 6;
const LVT_TIMER: usize = 0;
const LVT_LINT0: usize = 3;
const LVT_LINT1: usize = 4;
const LVT_ERROR: usize = 5;
const LVT_MASK: u32 = 1 << 16;
const LVT_LEVEL: u32 = 1 << 15;
const LVT_REMOTE_IRR: u32 = 1 << 14;
const LVT_ACTIVE_LOW: u32 = 1 << 13;
const LVT_DELIVERY_STATUS: u32 = 1 << 12;
const LVT_TIMER_MODE: u32 = 0b11 << 17;
const TIMER_PERIODIC: u32 = 0b01 << 17;
const LVT_RESET: u32 = LVT_MASK;
const SVR_ENABLE: u32 = 1 << 8;
const APIC_BASE_BSP: u64 = 1 << 8;
const APIC_BASE_ENABLE: u64 = 1 << 11;
const ESR_SEND_ILLEGAL_VECTOR: u32 = 1 << 5;
const ESR_RECV_ILLEGAL_VECTOR: u32 = 1 << 6;
const ESR_ILLEGAL_REGISTER: u32 = 1 << 7;
const FIRST_LEGAL_VECTOR: u8 = 16;
type Bitmap = [u32; 8];
fn bitmap_set(map: &mut Bitmap, vector: u8) {
map[usize::from(vector) >> 5] |= 1 << (vector & 31);
}
fn bitmap_clear(map: &mut Bitmap, vector: u8) {
map[usize::from(vector) >> 5] &= !(1 << (vector & 31));
}
fn bitmap_get(map: &Bitmap, vector: u8) -> bool {
map[usize::from(vector) >> 5] & (1 << (vector & 31)) != 0
}
fn bitmap_highest(map: &Bitmap) -> Option<u8> {
for word in (0..8).rev() {
if map[word] != 0 {
let bit = 31 - map[word].leading_zeros();
return Some((word as u8) * 32 + bit as u8);
}
}
None
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct State {
id: u8,
tpr: u8,
ldr: u8,
dfr: u8,
svr: u32,
esr: u32,
esr_pending: u32,
isr: Bitmap,
tmr: Bitmap,
irr: Bitmap,
lvt: [u32; LVT_COUNT],
icr_low: u32,
icr_high: u32,
timer_initial: u32,
timer_remaining: u64,
timer_divide: u32,
tick: u64,
apic_base: u64,
lint_level: [bool; 2],
extint: bool,
wait_for_sipi: bool,
init_asserted: bool,
startup: Option<u8>,
}
impl Default for State {
fn default() -> State {
State {
id: 0,
tpr: 0,
ldr: 0,
dfr: 0xf,
svr: 0x0000_00ff,
esr: 0,
esr_pending: 0,
isr: [0; 8],
tmr: [0; 8],
irr: [0; 8],
lvt: [LVT_RESET; LVT_COUNT],
icr_low: 0,
icr_high: 0,
timer_initial: 0,
timer_remaining: 0,
timer_divide: 0,
tick: 0,
apic_base: DEFAULT_BASE | APIC_BASE_ENABLE,
lint_level: [false; 2],
extint: false,
wait_for_sipi: false,
init_asserted: false,
startup: None,
}
}
}
impl State {
fn hardware_enabled(&self) -> bool {
self.apic_base & APIC_BASE_ENABLE != 0
}
fn software_enabled(&self) -> bool {
self.svr & SVR_ENABLE != 0
}
fn lvt(&self, index: usize) -> u32 {
if self.software_enabled() {
self.lvt[index]
} else {
self.lvt[index] | LVT_MASK
}
}
fn lvt_active(&self, index: usize) -> bool {
self.lvt(index) & LVT_MASK == 0
}
fn lvt_delivery(&self, index: usize) -> Delivery {
Delivery(((self.lvt[index] >> 8) & 7) as u8)
}
fn lvt_vector(&self, index: usize) -> u8 {
self.lvt[index] as u8
}
fn ppr(&self) -> u8 {
let isrv = bitmap_highest(&self.isr).unwrap_or(0);
if (self.tpr >> 4) >= (isrv >> 4) {
self.tpr
} else {
isrv & 0xf0
}
}
fn apr(&self) -> u8 {
let isrv = bitmap_highest(&self.isr).unwrap_or(0);
let irrv = bitmap_highest(&self.irr).unwrap_or(0);
if (self.tpr >> 4) >= (irrv >> 4) && (self.tpr >> 4) > (isrv >> 4) {
self.tpr
} else {
((isrv >> 4).max(irrv >> 4)) << 4
}
}
fn deliverable(&self) -> Option<u8> {
let vector = bitmap_highest(&self.irr)?;
((vector >> 4) > (self.ppr() >> 4)).then_some(vector)
}
fn extint_pending(&self) -> bool {
self.extint && (!self.hardware_enabled() || self.lvt_active(LVT_LINT0))
}
fn intr(&self) -> bool {
if !self.hardware_enabled() {
return self.lint_level[0];
}
self.extint_pending() || self.deliverable().is_some()
}
fn timer_divisor(&self) -> u64 {
let field = ((self.timer_divide >> 1) & 0b100) | (self.timer_divide & 0b11);
if field == 0b111 { 1 } else { 2 << field }
}
fn timer_current(&self) -> u32 {
let divisor = self.timer_divisor();
u32::try_from(self.timer_remaining.div_ceil(divisor)).unwrap_or(u32::MAX)
}
fn timer_periodic(&self) -> bool {
self.lvt[LVT_TIMER] & LVT_TIMER_MODE == TIMER_PERIODIC
}
fn timer_mode_runs(&self) -> bool {
matches!(self.lvt[LVT_TIMER] & LVT_TIMER_MODE, 0 | TIMER_PERIODIC)
}
fn next_event(&self) -> Option<u64> {
(self.timer_remaining > 0 && self.timer_mode_runs()).then_some(self.timer_remaining)
}
fn timer_step(&mut self, span: u64) -> bool {
if self.timer_remaining == 0 || !self.timer_mode_runs() {
return false;
}
if span < self.timer_remaining {
self.timer_remaining -= span;
return false;
}
let rest = span - self.timer_remaining;
let period = u64::from(self.timer_initial) * self.timer_divisor();
self.timer_remaining = if self.timer_periodic() && period > 0 {
period - (rest % period)
} else {
0
};
true
}
fn error(&mut self, bit: u32) {
let fresh = self.esr_pending & bit == 0;
self.esr_pending |= bit;
if fresh && self.lvt_active(LVT_ERROR) {
let vector = self.lvt_vector(LVT_ERROR);
if vector >= FIRST_LEGAL_VECTOR {
bitmap_set(&mut self.irr, vector);
bitmap_clear(&mut self.tmr, vector);
}
}
}
fn request(&mut self, vector: u8, level_triggered: bool) {
if vector < FIRST_LEGAL_VECTOR {
self.error(ESR_RECV_ILLEGAL_VECTOR);
return;
}
bitmap_set(&mut self.irr, vector);
if level_triggered {
bitmap_set(&mut self.tmr, vector);
} else {
bitmap_clear(&mut self.tmr, vector);
}
}
fn init_reset(&mut self) {
let id = self.id;
let base = self.apic_base;
let pins = self.lint_level;
let tick = self.tick;
*self = State::default();
self.id = id;
self.apic_base = base;
self.lint_level = pins;
self.tick = tick;
}
}
struct Registers {
state: Mutex<State>,
outs: Mutex<Outputs>,
extint_ack: Mutex<Option<Weak<dyn IntAck>>>,
lazy: Mutex<Option<LazyHandle>>,
bus: Arc<ApicBus>,
tick: AtomicU64,
next_event: AtomicU64,
}
#[derive(Debug, Default)]
struct Outputs {
intr: Option<WireSource>,
nmi: Option<WireSource>,
}
impl fmt::Debug for Registers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Registers");
s.field("tick", &self.tick.load(Ordering::Relaxed));
match self.state.try_lock() {
Some(state) => s.field("state", &*state).finish(),
None => s.field("state", &"<in use>").finish(),
}
}
}
#[derive(Debug, Default)]
struct Pending {
intr: bool,
nmi: bool,
send: Option<(Message, Shorthand)>,
eoi: Option<u8>,
}
impl Registers {
fn publish(&self, state: &State) {
self.tick.store(state.tick, Ordering::Relaxed);
let at = match state.next_event() {
Some(d) => state.tick.saturating_add(d),
None => u64::MAX,
};
self.next_event.store(at, Ordering::Relaxed);
}
fn settle(&self, pending: Pending) {
let outs = self.outs.lock().clone_sources();
if let Some(intr) = &outs.0 {
intr.set(Level::from_bool(pending.intr));
}
if pending.nmi
&& let Some(nmi) = &outs.1
{
nmi.set(Level::High);
nmi.set(Level::Low);
}
if let Some(vector) = pending.eoi {
self.bus.broadcast_eoi(vector);
}
if let Some((message, shorthand)) = pending.send {
let from = self.state.lock().id;
self.bus.deliver(message, Some(from), shorthand);
}
}
fn refresh(&self) {
let intr = self.state.lock().intr();
self.settle(Pending {
intr,
..Pending::default()
});
}
fn sync(&self, attrs: MemAttrs) {
let handle = self.lazy.lock().clone();
let Some(handle) = handle else {
return;
};
let kind = if attrs.debug {
AccessKind::Debug
} else {
AccessKind::Guest
};
let _ = handle.sync(kind);
}
fn advance_to(&self, target: u64) {
let pending = {
let mut state = self.state.lock();
if target <= state.tick {
return;
}
let span = target - state.tick;
state.tick = target;
if state.timer_step(span) && state.lvt_active(LVT_TIMER) {
let vector = state.lvt_vector(LVT_TIMER);
state.request(vector, false);
}
self.publish(&state);
Pending {
intr: state.intr(),
..Pending::default()
}
};
self.settle(pending);
}
fn read_register(&self, offset: u64, debug: bool) -> u32 {
let mut state = self.state.lock();
match offset {
REG_ID => u32::from(state.id) << 24,
REG_VERSION => VERSION | ((LVT_COUNT as u32 - 1) << 16),
REG_TPR => u32::from(state.tpr),
REG_APR => u32::from(state.apr()),
REG_PPR => u32::from(state.ppr()),
REG_EOI => 0,
REG_RRD => 0,
REG_LDR => u32::from(state.ldr) << 24,
REG_DFR => (u32::from(state.dfr) << 28) | 0x0fff_ffff,
REG_SVR => state.svr,
REG_ESR => state.esr,
REG_ICR_LOW => state.icr_low,
REG_ICR_HIGH => state.icr_high,
REG_TIMER_INIT => state.timer_initial,
REG_TIMER_CUR => state.timer_current(),
REG_TIMER_DIV => state.timer_divide,
_ if (REG_ISR..REG_ISR + 8 * REG_STRIDE).contains(&offset) => {
state.isr[((offset - REG_ISR) / REG_STRIDE) as usize]
}
_ if (REG_TMR..REG_TMR + 8 * REG_STRIDE).contains(&offset) => {
state.tmr[((offset - REG_TMR) / REG_STRIDE) as usize]
}
_ if (REG_IRR..REG_IRR + 8 * REG_STRIDE).contains(&offset) => {
state.irr[((offset - REG_IRR) / REG_STRIDE) as usize]
}
_ if (REG_LVT_BASE..REG_LVT_BASE + LVT_COUNT as u64 * REG_STRIDE).contains(&offset) => {
state.lvt(((offset - REG_LVT_BASE) / REG_STRIDE) as usize)
}
_ => {
if !debug {
state.error(ESR_ILLEGAL_REGISTER);
}
0
}
}
}
fn write_register(&self, offset: u64, value: u32) {
let pending = {
let mut state = self.state.lock();
let mut pending = Pending::default();
match offset {
REG_ID => state.id = (value >> 24) as u8,
REG_TPR => state.tpr = value as u8,
REG_EOI => {
if let Some(vector) = bitmap_highest(&state.isr) {
bitmap_clear(&mut state.isr, vector);
if bitmap_get(&state.tmr, vector) {
bitmap_clear(&mut state.tmr, vector);
pending.eoi = Some(vector);
}
}
}
REG_LDR => state.ldr = (value >> 24) as u8,
REG_DFR => state.dfr = (value >> 28) as u8,
REG_SVR => {
state.svr = value & 0x0000_13ff;
}
REG_ESR => {
state.esr = core::mem::take(&mut state.esr_pending);
}
REG_ICR_HIGH => state.icr_high = value & 0xff00_0000,
REG_ICR_LOW => {
state.icr_low = value & 0x000c_cfff;
let message = Message {
vector: value as u8,
delivery: Delivery(((value >> 8) & 7) as u8),
logical: value & (1 << 11) != 0,
dest: (state.icr_high >> 24) as u8,
level_triggered: value & (1 << 15) != 0,
assert: value & (1 << 14) != 0,
};
if message.delivery == Delivery::FIXED && message.vector < FIRST_LEGAL_VECTOR {
state.error(ESR_SEND_ILLEGAL_VECTOR);
} else {
pending.send = Some((message, Shorthand::from_bits(value >> 18)));
}
}
REG_TIMER_INIT => {
state.timer_initial = value;
state.timer_remaining = u64::from(value) * state.timer_divisor();
}
REG_TIMER_DIV => state.timer_divide = value & 0b1011,
_ if (REG_LVT_BASE..REG_LVT_BASE + LVT_COUNT as u64 * REG_STRIDE)
.contains(&offset) =>
{
let index = ((offset - REG_LVT_BASE) / REG_STRIDE) as usize;
let keep = state.lvt[index] & LVT_REMOTE_IRR;
let mut written = (value & !(LVT_DELIVERY_STATUS | LVT_REMOTE_IRR)) | keep;
if !state.software_enabled() {
written |= LVT_MASK;
}
state.lvt[index] = written;
if index == LVT_TIMER && !state.timer_mode_runs() {
state.timer_remaining = 0;
}
}
_ => state.error(ESR_ILLEGAL_REGISTER),
}
self.publish(&state);
pending.intr = state.intr();
pending
};
self.settle(pending);
}
fn set_lint(&self, index: usize, high: bool) {
self.sync(MemAttrs::DEFAULT);
let pending = {
let mut state = self.state.lock();
let was = state.lint_level[index];
state.lint_level[index] = high;
let entry = if index == 0 { LVT_LINT0 } else { LVT_LINT1 };
let mut pending = Pending::default();
if !state.hardware_enabled() {
if index == 0 {
state.extint = high;
} else {
pending.nmi = high && !was;
}
} else {
let asserted = high != (state.lvt[entry] & LVT_ACTIVE_LOW != 0);
let was_asserted = was != (state.lvt[entry] & LVT_ACTIVE_LOW != 0);
let level = state.lvt[entry] & LVT_LEVEL != 0;
let edge = asserted && !was_asserted;
match state.lvt_delivery(entry) {
Delivery::EXTINT if index == 0 => state.extint = asserted,
_ if !state.lvt_active(entry) => {}
Delivery::NMI => pending.nmi = edge,
Delivery::FIXED => {
if level {
if asserted {
let v = state.lvt_vector(entry);
state.request(v, true);
}
} else if edge {
let v = state.lvt_vector(entry);
state.request(v, false);
}
}
_ => {}
}
}
pending.intr = state.intr();
pending
};
self.settle(pending);
}
fn accept_message(&self, message: Message) {
let pending = {
let mut state = self.state.lock();
let mut pending = Pending::default();
match message.delivery {
Delivery::FIXED | Delivery::LOWEST => {
state.request(message.vector, message.level_triggered);
}
Delivery::NMI => pending.nmi = true,
Delivery::INIT => {
if message.assert {
state.init_reset();
state.wait_for_sipi = true;
state.init_asserted = true;
} else {
state.init_asserted = false;
}
}
Delivery::STARTUP if state.wait_for_sipi => {
state.wait_for_sipi = false;
state.startup = Some(message.vector);
}
_ => {}
}
self.publish(&state);
pending.intr = state.intr();
pending
};
self.settle(pending);
}
}
impl Outputs {
fn clone_sources(&self) -> (Option<WireSource>, Option<WireSource>) {
(self.intr.clone(), self.nmi.clone())
}
}
impl bus::Target for Registers {
fn apic_id(&self) -> u8 {
self.state.lock().id
}
fn logical_match(&self, dest: u8) -> bool {
let state = self.state.lock();
if state.dfr == 0xf {
state.ldr & dest != 0
} else {
(state.ldr >> 4) == (dest >> 4) && (state.ldr & dest & 0x0f) != 0
}
}
fn arbitration_priority(&self) -> u8 {
self.state.lock().apr()
}
fn accept(&self, message: Message) {
self.accept_message(message);
}
}
impl IntAck for Registers {
fn acknowledge(&self, cycle: IntAckCycle) -> IntAckResponse {
let (answer, delegate) = {
let mut state = self.state.lock();
if state.extint_pending() {
let ack = self.extint_ack.lock().clone();
(None, ack)
} else {
match state.deliverable() {
Some(vector) => {
bitmap_clear(&mut state.irr, vector);
bitmap_set(&mut state.isr, vector);
(Some(u32::from(vector)), None)
}
None => (Some(state.svr & 0xff), None),
}
}
};
let response = match answer {
Some(vector) => IntAckResponse::Vector(vector),
None => delegate
.as_ref()
.and_then(Weak::upgrade)
.map(|ack| ack.acknowledge(cycle))
.filter(|response| response.answered())
.unwrap_or_else(|| IntAckResponse::Vector(self.state.lock().svr & 0xff)),
};
self.refresh();
response
}
}
impl MemOps for Registers {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
let [a, b, c, d] = dst else {
return Err(BusError::BadAccess);
};
if !offset.is_multiple_of(REG_STRIDE) {
return Err(BusError::BadAccess);
}
if !attrs.debug {
self.sync(attrs);
}
if !self.state.lock().hardware_enabled() {
return Err(BusError::BadAccess);
}
let value = self.read_register(offset, attrs.debug);
let bytes = value.to_le_bytes();
*a = bytes[0];
*b = bytes[1];
*c = bytes[2];
*d = bytes[3];
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let [a, b, c, d] = src else {
return Err(BusError::BadAccess);
};
if !offset.is_multiple_of(REG_STRIDE) {
return Err(BusError::BadAccess);
}
if attrs.debug {
return Err(BusError::BadAccess);
}
self.sync(attrs);
if !self.state.lock().hardware_enabled() {
return Err(BusError::BadAccess);
}
self.write_register(offset, u32::from_le_bytes([*a, *b, *c, *d]));
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U32, Endian::Little)
}
}
#[derive(Debug)]
pub struct LocalApic {
regs: Arc<Registers>,
region: RegionRef,
pins: Mutex<Vec<Arc<LintPin>>>,
bsp: bool,
reset_id: u8,
}
#[derive(Debug)]
struct LintPin {
regs: Arc<Registers>,
index: usize,
inputs: FanIn,
}
impl WireSink for LintPin {
fn set_level(&self, src: WireId, _line: u32, level: Level) {
self.inputs.set(src, level);
let high = self.inputs.resolve(Resolve::Or).is_high();
self.regs.set_lint(self.index, high);
}
}
impl LocalApic {
pub fn new(props: &Props) -> Result<LocalApic> {
let mut r = props.reader();
let id = u8::try_from(r.or_range::<u64>("id", 0, 0..=255)?).unwrap_or(0);
let bsp = r.or("bsp", id == 0)?;
let name = r.or_str("bus", bus::DEFAULT_NAME)?.to_string();
r.finish()?;
let bus = bus::attach(props, &name)?;
Ok(LocalApic::with_bus(id, bsp, bus))
}
#[must_use]
pub fn default_device() -> LocalApic {
LocalApic::with_bus(0, true, Arc::new(ApicBus::new()))
}
#[must_use]
pub fn with_bus(id: u8, bsp: bool, bus: Arc<ApicBus>) -> LocalApic {
let mut state = State {
id,
..State::default()
};
if bsp {
state.apic_base |= APIC_BASE_BSP;
}
let regs = Arc::new(Registers {
state: Mutex::with_rank(LockRank::DEVICE, state),
outs: Mutex::with_rank(LockRank::LEAF, Outputs::default()),
extint_ack: Mutex::with_rank(LockRank::LEAF, None),
lazy: Mutex::with_rank(LockRank::LEAF, None),
bus,
tick: AtomicU64::new(0),
next_event: AtomicU64::new(u64::MAX),
});
let region: RegionRef = Arc::new(Region::io(
CLASS_NAME,
REGISTER_WINDOW_LEN,
Arc::clone(®s) as Arc<dyn MemOps>,
));
LocalApic {
regs,
region,
pins: Mutex::with_rank(LockRank::LEAF, Vec::new()),
bsp,
reset_id: id,
}
}
#[must_use]
pub fn bus(&self) -> &Arc<ApicBus> {
&self.regs.bus
}
#[must_use]
pub fn id(&self) -> u8 {
self.regs.state.lock().id
}
#[must_use]
pub fn apic_base(&self) -> u64 {
self.regs.state.lock().apic_base
}
pub fn set_apic_base(&self, value: u64) {
{
let mut state = self.regs.state.lock();
state.apic_base = value;
}
self.regs.refresh();
}
#[must_use]
pub fn intr_asserted(&self) -> bool {
self.regs.state.lock().intr()
}
#[must_use]
pub fn requested(&self) -> [u32; 8] {
self.regs.state.lock().irr
}
#[must_use]
pub fn in_service(&self) -> [u32; 8] {
self.regs.state.lock().isr
}
#[must_use]
pub fn waiting_for_startup(&self) -> bool {
self.regs.state.lock().wait_for_sipi
}
#[must_use]
pub fn init_asserted(&self) -> bool {
self.regs.state.lock().init_asserted
}
pub fn take_startup(&self) -> Option<u8> {
self.regs.state.lock().startup.take()
}
pub fn advance_to(&self, tick: u64) {
self.regs.advance_to(tick);
}
#[must_use]
pub fn tick(&self) -> u64 {
self.regs.tick.load(Ordering::Relaxed)
}
fn lint_number(port: &str) -> Option<usize> {
match port {
"lint0" => Some(0),
"lint1" => Some(1),
_ => None,
}
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "a processor's local APIC, with its timer and the interprocessor interrupt path",
properties: &[
PropertySpec {
name: "id",
kind: ValueKind::Uint,
required: false,
summary: "the APIC ID this part comes out of reset with, 0-255 (default 0)",
},
PropertySpec {
name: "bsp",
kind: ValueKind::Bool,
required: false,
summary: "whether this is the bootstrap processor (default: true for APIC ID 0)",
},
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: false,
summary: "the APIC message bus this part is on (default `apic`)",
},
],
construct: |props| Ok(Box::new(LocalApic::new(props)?)),
};
impl Device for LocalApic {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
self.regs
.bus
.attach(Arc::downgrade(&self.regs) as Weak<dyn Target>);
Ok(())
}
fn reset(&self, _kind: ResetKind) {
let pending = {
let mut state = self.regs.state.lock();
let pins = state.lint_level;
let tick = state.tick;
*state = State::default();
state.id = self.reset_id;
state.lint_level = pins;
state.tick = tick;
if self.bsp {
state.apic_base |= APIC_BASE_BSP;
}
self.regs.publish(&state);
Pending {
intr: state.intr(),
..Pending::default()
}
};
self.regs.settle(pending);
}
fn region(&self, name: &str) -> Option<RegionRef> {
matches!(name, "" | "regs").then(|| Arc::clone(&self.region))
}
fn sink(&self, port: &str, sources: &[WireId]) -> Option<SinkPin> {
let index = LocalApic::lint_number(port)?;
let pin = Arc::new(LintPin {
regs: Arc::clone(&self.regs),
index,
inputs: FanIn::new(sources),
});
self.pins.lock().push(Arc::clone(&pin));
Some(SinkPin {
sink: pin,
line: index as u32,
})
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
let mut outs = self.regs.outs.lock();
match port {
"intr" => outs.intr = Some(source),
"nmi" => outs.nmi = Some(source),
_ => {
return Err(Error::Config {
at: port.to_string(),
message: String::from("a local APIC drives `intr` and `nmi`"),
});
}
}
Ok(())
}
fn announce(&self, port: &str) {
if port == "intr" {
self.regs.refresh();
}
}
fn int_ack(&self, port: &str) -> Option<Arc<dyn IntAck>> {
(port == "intr").then(|| Arc::clone(&self.regs) as Arc<dyn IntAck>)
}
fn attach_int_ack(&self, port: &str, ack: Weak<dyn IntAck>) {
if port == "lint0" {
*self.regs.extint_ack.lock() = Some(ack);
}
}
fn is_lazy(&self) -> bool {
true
}
fn current_tick(&self) -> u64 {
self.regs.tick.load(Ordering::Relaxed)
}
fn advance_to(&self, tick: u64) {
self.regs.advance_to(tick);
}
fn next_event_tick(&self) -> Option<u64> {
match self.regs.next_event.load(Ordering::Relaxed) {
u64::MAX => None,
at => Some(at),
}
}
fn attach_lazy(&self, handle: LazyHandle) {
*self.regs.lazy.lock() = Some(handle);
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = self.regs.state.lock();
w.write_u8(state.id)?;
w.write_u8(state.tpr)?;
w.write_u8(state.ldr)?;
w.write_u8(state.dfr)?;
w.write_u32(state.svr)?;
w.write_u32(state.esr)?;
w.write_u32(state.esr_pending)?;
for map in [&state.isr, &state.tmr, &state.irr] {
for word in map {
w.write_u32(*word)?;
}
}
w.write_seq_len(LVT_COUNT as u64)?;
for entry in state.lvt {
w.write_u32(entry)?;
}
w.write_u32(state.icr_low)?;
w.write_u32(state.icr_high)?;
w.write_u32(state.timer_initial)?;
w.write_u64(state.timer_remaining)?;
w.write_u32(state.timer_divide)?;
w.write_u64(state.tick)?;
w.write_u64(state.apic_base)?;
for level in state.lint_level {
w.write_bool(level)?;
}
for flag in [state.extint, state.wait_for_sipi, state.init_asserted] {
w.write_bool(flag)?;
}
match state.startup {
None => w.write_bool(false)?,
Some(vector) => {
w.write_bool(true)?;
w.write_u8(vector)?;
}
}
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut state = State {
id: r.read_u8()?,
tpr: r.read_u8()?,
ldr: r.read_u8()?,
dfr: r.read_u8()?,
svr: r.read_u32()?,
esr: r.read_u32()?,
esr_pending: r.read_u32()?,
..State::default()
};
for map in [&mut state.isr, &mut state.tmr, &mut state.irr] {
for word in map.iter_mut() {
*word = r.read_u32()?;
}
}
let entries = r.read_seq_len(4)? as usize;
if entries != LVT_COUNT {
return Err(Error::State(format!(
"snapshot has {entries} local vector table entries, this APIC has {LVT_COUNT}"
)));
}
for entry in &mut state.lvt {
*entry = r.read_u32()?;
}
state.icr_low = r.read_u32()?;
state.icr_high = r.read_u32()?;
state.timer_initial = r.read_u32()?;
state.timer_remaining = r.read_u64()?;
state.timer_divide = r.read_u32()?;
state.tick = r.read_u64()?;
state.apic_base = r.read_u64()?;
for level in &mut state.lint_level {
*level = r.read_bool()?;
}
state.extint = r.read_bool()?;
state.wait_for_sipi = r.read_bool()?;
state.init_asserted = r.read_bool()?;
state.startup = if r.read_bool()? {
Some(r.read_u8()?)
} else {
None
};
if state.timer_divide & !0b1011 != 0 {
return Err(Error::State(format!(
"snapshot has an APIC timer divide configuration of {:#x}, which sets a reserved bit",
state.timer_divide
)));
}
{
let mut current = self.regs.state.lock();
*current = state;
self.regs.publish(¤t);
}
self.regs.refresh();
Ok(())
}
}
impl Instance for LocalApic {}
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(LocalApic::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
use crate::machine::validate::{PortDir, PropSchema};
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("id", ValueKind::Uint).range(0, 255))
.prop(PropSchema::new("bsp", ValueKind::Bool))
.prop(PropSchema::new("bus", ValueKind::Str))
.region("")
.region("regs")
.port("intr", PortDir::Out)
.port("nmi", PortDir::Out)
.port("lint0", PortDir::In)
.port("lint1", PortDir::In)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::state::{MachineShape, Migrations, StateReader, StateWriter};
use crate::core::sync::{AtomicU32, Ordering as AtomicOrdering};
use crate::core::wire::{Wire, WireIdAllocator};
const TIMER_VECTOR: u8 = 0x40;
const REG_LVT_TIMER: u64 = REG_LVT_BASE + LVT_TIMER as u64 * REG_STRIDE;
const REG_LVT_LINT0: u64 = REG_LVT_BASE + LVT_LINT0 as u64 * REG_STRIDE;
const REG_LVT_LINT1: u64 = REG_LVT_BASE + LVT_LINT1 as u64 * REG_STRIDE;
#[derive(Debug, Default)]
struct Probe {
level: AtomicU32,
edges: AtomicU32,
}
impl WireSink for Probe {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
self.level
.store(u32::from(level.is_high()), AtomicOrdering::Relaxed);
if level.is_high() {
self.edges.fetch_add(1, AtomicOrdering::Relaxed);
}
}
}
impl Probe {
fn high(&self) -> bool {
self.level.load(AtomicOrdering::Relaxed) != 0
}
fn edges(&self) -> u32 {
self.edges.load(AtomicOrdering::Relaxed)
}
}
#[derive(Debug)]
struct Stub8259 {
vector: u32,
asked: AtomicU32,
}
impl IntAck for Stub8259 {
fn acknowledge(&self, _cycle: IntAckCycle) -> IntAckResponse {
self.asked.fetch_add(1, AtomicOrdering::Relaxed);
IntAckResponse::Vector(self.vector)
}
}
struct Bench {
apic: LocalApic,
lint: Vec<Arc<dyn WireSink>>,
src: WireId,
intr: Arc<Probe>,
nmi: Arc<Probe>,
}
fn bench_on(bus: &Arc<ApicBus>, id: u8, bsp: bool) -> Bench {
let apic = LocalApic::with_bus(id, bsp, Arc::clone(bus));
let ids = WireIdAllocator::new();
let src = ids.alloc();
let lint: Vec<Arc<dyn WireSink>> = ["lint0", "lint1"]
.iter()
.map(|port| apic.sink(port, &[src]).expect("both LINT pins exist").sink)
.collect();
let intr = Arc::new(Probe::default());
let nmi = Arc::new(Probe::default());
for (port, probe) in [("intr", &intr), ("nmi", &nmi)] {
let out = ids.alloc();
let wire = Wire::builder()
.source(out)
.sink(Arc::clone(probe) as Arc<dyn WireSink>, 0)
.build_shared();
apic.connect(port, WireSource::new(wire, out))
.expect("both output pins exist");
}
bus.attach(Arc::downgrade(&apic.regs) as Weak<dyn Target>);
Bench {
apic,
lint,
src,
intr,
nmi,
}
}
fn bench() -> Bench {
bench_on(&Arc::new(ApicBus::new()), 0, true)
}
impl Bench {
fn poke(&self, offset: u64, value: u32) {
self.apic
.regs
.write(offset, &value.to_le_bytes(), MemAttrs::DEFAULT)
.expect("a 32-bit aligned write is legal");
}
fn peek(&self, offset: u64) -> u32 {
self.peek_with(offset, MemAttrs::DEFAULT)
}
fn peek_with(&self, offset: u64, attrs: MemAttrs) -> u32 {
let mut bytes = [0u8; 4];
self.apic
.regs
.read(offset, &mut bytes, attrs)
.expect("a 32-bit aligned read is legal");
u32::from_le_bytes(bytes)
}
fn enable(&self) {
self.poke(REG_SVR, SVR_ENABLE | 0xff);
}
fn drive_lint(&self, index: usize, level: Level) {
self.lint[index].set_level(self.src, index as u32, level);
}
fn ack(&self) -> IntAckResponse {
IntAck::acknowledge(&*self.apic.regs, IntAckCycle::vector_only())
}
}
#[test]
fn the_version_register_says_what_this_part_is() {
let b = bench();
let version = b.peek(REG_VERSION);
assert_eq!(version & 0xff, VERSION, "an integrated APIC");
assert_eq!(
(version >> 16) & 0xff,
LVT_COUNT as u32 - 1,
"six local vector table entries, reported as the highest index"
);
assert_eq!(b.peek(REG_ID) >> 24, 0, "and the ID it was built with");
}
#[test]
fn a_register_is_only_reachable_on_its_own_sixteen_byte_boundary() {
let b = bench();
let mut bytes = [0u8; 4];
assert!(
b.apic
.regs
.read(REG_VERSION + 4, &mut bytes, MemAttrs::DEFAULT)
.is_err()
);
assert!(
b.apic
.regs
.read(REG_VERSION, &mut [0u8; 1], MemAttrs::DEFAULT)
.is_err(),
"and a byte access is not a 32-bit load"
);
}
#[test]
fn the_timer_fires_on_the_tick_the_scheduler_was_told_about() {
let b = bench();
b.enable();
b.poke(REG_LVT_TIMER, u32::from(TIMER_VECTOR));
b.poke(REG_TIMER_DIV, 0b0011);
b.poke(REG_TIMER_INIT, 100);
assert_eq!(Device::next_event_tick(&b.apic), Some(1600));
assert_eq!(b.peek(REG_TIMER_CUR), 100, "and nothing has counted yet");
b.apic.advance_to(1599);
assert_eq!(b.peek(REG_TIMER_CUR), 1, "one count left");
assert!(!b.intr.high(), "and no interrupt yet");
b.apic.advance_to(1600);
assert_eq!(b.peek(REG_TIMER_CUR), 0);
assert!(b.intr.high(), "the count reached zero and INTR went up");
assert_eq!(
Device::next_event_tick(&b.apic),
None,
"a one-shot timer has nothing further to say"
);
assert_eq!(
b.ack(),
IntAckResponse::Vector(u32::from(TIMER_VECTOR)),
"and the acknowledge cycle answers with the vector the LVT names"
);
assert!(!b.intr.high(), "which drops INTR, the request having moved");
}
#[test]
fn the_timers_position_is_a_function_of_its_tick_and_nothing_else() {
let b = bench();
b.enable();
b.poke(REG_TIMER_DIV, 0b1011); b.poke(REG_TIMER_INIT, 5_000);
for _ in 0..1_000 {
assert_eq!(b.peek(REG_TIMER_CUR), 5_000);
}
assert_eq!(b.apic.tick(), 0, "and the device has not moved either");
b.apic.advance_to(1_234);
assert_eq!(b.peek(REG_TIMER_CUR), 5_000 - 1_234);
}
#[test]
fn a_periodic_timer_reloads_and_several_periods_collapse_into_one_request() {
let b = bench();
b.enable();
b.poke(REG_LVT_TIMER, TIMER_PERIODIC | u32::from(TIMER_VECTOR));
b.poke(REG_TIMER_DIV, 0b1011); b.poke(REG_TIMER_INIT, 10);
assert_eq!(Device::next_event_tick(&b.apic), Some(10));
b.apic.advance_to(10);
assert!(b.intr.high());
assert_eq!(
Device::next_event_tick(&b.apic),
Some(20),
"and it re-arms for the next period"
);
b.apic.advance_to(263);
assert_eq!(Device::next_event_tick(&b.apic), Some(270));
assert_eq!(b.peek(REG_TIMER_CUR), 7);
}
#[test]
fn a_masked_timer_still_counts_and_simply_does_not_interrupt() {
let b = bench();
b.enable();
b.poke(REG_LVT_TIMER, LVT_MASK | u32::from(TIMER_VECTOR));
b.poke(REG_TIMER_DIV, 0b1011);
b.poke(REG_TIMER_INIT, 8);
b.apic.advance_to(8);
assert!(!b.intr.high(), "the mask blocks the interrupt");
assert_eq!(b.peek(REG_TIMER_CUR), 0, "but not the counting");
}
#[test]
fn a_debug_read_moves_nothing_and_a_debug_write_is_refused() {
let b = bench();
b.enable();
b.poke(REG_LVT_TIMER, u32::from(TIMER_VECTOR));
b.poke(REG_TIMER_DIV, 0b1011);
b.poke(REG_TIMER_INIT, 4);
b.apic.advance_to(4);
b.ack();
let debug = MemAttrs::DEBUG;
assert_ne!(b.peek_with(REG_ISR + 2 * REG_STRIDE, debug), 0);
assert!(
b.apic
.regs
.write(REG_EOI, &0u32.to_le_bytes(), debug)
.is_err(),
"and there is no harmless write on this part"
);
assert_ne!(
b.peek(REG_ISR + 2 * REG_STRIDE),
0,
"so the in-service bit is still there for the guest"
);
}
#[test]
fn nothing_pending_is_answered_with_the_spurious_vector() {
let b = bench();
b.poke(REG_SVR, SVR_ENABLE | 0xef);
assert_eq!(b.ack(), IntAckResponse::Vector(0xef));
assert_eq!(b.peek(REG_ISR + 7 * REG_STRIDE), 0, "and sets no ISR bit");
}
#[test]
fn the_task_priority_holds_an_interrupt_off_until_it_is_lowered() {
let bus = Arc::new(ApicBus::new());
let b = bench_on(&bus, 0, true);
b.enable();
b.poke(REG_TPR, 0x40);
bus.deliver(Message::fixed(0x44, 0), None, Shorthand::Dest);
assert_ne!(b.peek(REG_IRR + 2 * REG_STRIDE), 0, "the request is held");
assert!(!b.intr.high(), "but not offered");
b.poke(REG_TPR, 0x30);
assert!(b.intr.high(), "and lowering the task priority offers it");
assert_eq!(b.ack(), IntAckResponse::Vector(0x44));
assert_eq!(
b.peek(REG_PPR) & 0xf0,
0x40,
"the in-service vector now sets the processor priority"
);
b.poke(REG_EOI, 0);
assert_eq!(b.peek(REG_PPR) & 0xf0, 0x30, "and the EOI gives it back");
}
#[test]
fn a_vector_below_sixteen_is_refused_and_recorded() {
let bus = Arc::new(ApicBus::new());
let b = bench_on(&bus, 0, true);
b.enable();
bus.deliver(Message::fixed(0x0f, 0), None, Shorthand::Dest);
assert_eq!(b.peek(REG_IRR), 0, "the architecture's own vectors are not");
b.poke(REG_ESR, 0);
assert_eq!(b.peek(REG_ESR), ESR_RECV_ILLEGAL_VECTOR);
}
#[test]
fn an_extint_pin_forwards_the_acknowledge_to_the_controller_behind_it() {
let b = bench();
b.enable();
let pic = Arc::new(Stub8259 {
vector: 0x08,
asked: AtomicU32::new(0),
});
b.apic
.attach_int_ack("lint0", Arc::downgrade(&pic) as Weak<dyn IntAck>);
b.poke(REG_LVT_LINT0, u32::from(Delivery::EXTINT.0) << 8);
assert!(!b.intr.high());
b.drive_lint(0, Level::High);
assert!(b.intr.high(), "the 8259A's INT reaches the processor");
assert_eq!(
b.ack(),
IntAckResponse::Vector(0x08),
"and the vector comes from the 8259A, not from this part"
);
assert_eq!(pic.asked.load(AtomicOrdering::Relaxed), 1);
assert_eq!(b.peek(REG_ISR), 0, "no local in-service bit is set");
}
#[test]
fn a_nmi_lint_delivers_an_edge_and_a_fixed_one_a_vector() {
let b = bench();
b.enable();
b.poke(REG_LVT_LINT1, u32::from(Delivery::NMI.0) << 8);
b.drive_lint(1, Level::High);
assert_eq!(b.nmi.edges(), 1, "one edge on the NMI pin");
b.drive_lint(1, Level::Low);
b.drive_lint(1, Level::High);
assert_eq!(b.nmi.edges(), 2);
b.poke(REG_LVT_LINT0, 0x33);
b.drive_lint(0, Level::High);
assert_eq!(b.ack(), IntAckResponse::Vector(0x33));
}
#[test]
fn a_software_disabled_apic_masks_every_lvt_entry() {
let b = bench();
b.poke(REG_LVT_LINT0, 0x33);
assert_eq!(
b.peek(REG_LVT_LINT0) & LVT_MASK,
LVT_MASK,
"the mask reads back set however it was written (SDM 10.4.7.2)"
);
b.drive_lint(0, Level::High);
assert!(!b.intr.high(), "so nothing is delivered");
b.enable();
b.poke(REG_LVT_LINT0, 0x33);
b.drive_lint(0, Level::Low);
b.drive_lint(0, Level::High);
assert!(b.intr.high(), "and enabling it makes the same pin work");
}
#[test]
fn an_interprocessor_interrupt_reaches_the_apic_it_names() {
let bus = Arc::new(ApicBus::new());
let zero = bench_on(&bus, 0, true);
let one = bench_on(&bus, 1, false);
one.enable();
zero.poke(REG_ICR_HIGH, 1 << 24);
zero.poke(REG_ICR_LOW, 0x51);
assert!(one.intr.high(), "the destination took it");
assert!(!zero.intr.high(), "and the sender did not");
assert_eq!(one.ack(), IntAckResponse::Vector(0x51));
}
#[test]
fn the_shorthands_pick_out_the_sender_and_everyone_else() {
let bus = Arc::new(ApicBus::new());
let zero = bench_on(&bus, 0, true);
let one = bench_on(&bus, 1, false);
zero.enable();
one.enable();
zero.poke(REG_ICR_LOW, (Shorthand::SelfOnly.bits() << 18) | 0x61);
assert!(zero.intr.high());
assert!(!one.intr.high());
assert_eq!(zero.ack(), IntAckResponse::Vector(0x61));
zero.poke(REG_EOI, 0);
zero.poke(REG_ICR_LOW, (Shorthand::AllButSelf.bits() << 18) | 0x62);
assert!(!zero.intr.high());
assert_eq!(one.ack(), IntAckResponse::Vector(0x62));
}
#[test]
fn a_logical_destination_in_the_flat_model_is_a_bitmap() {
let bus = Arc::new(ApicBus::new());
let zero = bench_on(&bus, 0, true);
let one = bench_on(&bus, 1, false);
for (b, ldr) in [(&zero, 0x01u32), (&one, 0x02)] {
b.enable();
b.poke(REG_DFR, 0xf << 28);
b.poke(REG_LDR, ldr << 24);
}
zero.poke(REG_ICR_HIGH, 0x02 << 24);
zero.poke(REG_ICR_LOW, (1 << 11) | 0x71);
assert!(!zero.intr.high());
assert!(one.intr.high());
zero.poke(REG_ICR_HIGH, 0x03 << 24);
zero.poke(REG_ICR_LOW, (1 << 11) | 0x72);
assert!(zero.intr.high());
}
#[test]
fn lowest_priority_picks_the_processor_bidding_least() {
let bus = Arc::new(ApicBus::new());
let zero = bench_on(&bus, 0, true);
let one = bench_on(&bus, 1, false);
zero.enable();
one.enable();
zero.poke(REG_TPR, 0x80);
zero.poke(REG_ICR_HIGH, 0xff << 24);
zero.poke(REG_ICR_LOW, (u32::from(Delivery::LOWEST.0) << 8) | 0x73);
assert!(one.intr.high(), "the idle one takes it");
assert_eq!(zero.peek(REG_IRR + 3 * REG_STRIDE), 0);
}
#[test]
fn init_then_start_up_leaves_a_processor_with_a_page_to_start_at() {
let bus = Arc::new(ApicBus::new());
let bsp = bench_on(&bus, 0, true);
let ap = bench_on(&bus, 1, false);
ap.enable();
ap.poke(REG_TPR, 0x50);
bsp.poke(REG_ICR_HIGH, 1 << 24);
let init = (u32::from(Delivery::INIT.0) << 8) | (1 << 14) | (1 << 15);
bsp.poke(REG_ICR_LOW, init);
assert!(ap.apic.waiting_for_startup(), "the AP is held at INIT");
assert!(ap.apic.init_asserted());
assert_eq!(ap.peek(REG_TPR), 0, "and its APIC came back reset");
assert_eq!(ap.apic.id(), 1, "except for its ID (SDM 10.4.7.1)");
bsp.poke(REG_ICR_LOW, init & !(1 << 14));
assert!(!ap.apic.init_asserted(), "the de-assert drops the line");
assert!(ap.apic.waiting_for_startup(), "but it is still waiting");
bsp.poke(REG_ICR_LOW, (u32::from(Delivery::STARTUP.0) << 8) | 0x08);
assert!(!ap.apic.waiting_for_startup());
assert_eq!(ap.apic.take_startup(), Some(0x08));
assert_eq!(ap.apic.take_startup(), None, "and it is taken once");
bsp.poke(REG_ICR_LOW, (u32::from(Delivery::STARTUP.0) << 8) | 0x08);
assert_eq!(ap.apic.take_startup(), None);
}
#[test]
fn a_snapshot_round_trips_the_whole_part() {
let bus = Arc::new(ApicBus::new());
let saved = bench_on(&bus, 3, false);
saved.enable();
saved.poke(REG_TPR, 0x20);
saved.poke(REG_LDR, 0x08 << 24);
saved.poke(REG_LVT_TIMER, TIMER_PERIODIC | u32::from(TIMER_VECTOR));
saved.poke(REG_TIMER_DIV, 0b0001); saved.poke(REG_TIMER_INIT, 250);
saved.poke(REG_LVT_LINT0, 0x39);
saved.drive_lint(0, Level::High);
saved.apic.advance_to(1_003);
saved.ack();
let mut shape = MachineShape::new();
shape.add_device("lapic", CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("lapic", CLASS.name, CLASS.version).unwrap();
saved.apic.save(&mut chunk).unwrap();
}
let bytes = w.to_vec().unwrap();
let restored = bench_on(&Arc::new(ApicBus::new()), 0, true);
let reader = StateReader::new(&bytes).unwrap();
let chunk = reader
.load("lapic", CLASS.name, CLASS.version, &Migrations::new())
.unwrap();
restored.apic.load(&mut chunk.reader()).unwrap();
let after = restored.apic.regs.state.lock().clone();
let before = saved.apic.regs.state.lock().clone();
assert_eq!(after, before, "every field came back");
assert_eq!(
restored.apic.tick(),
1_003,
"the position in its domain too"
);
assert_eq!(
Device::next_event_tick(&restored.apic),
Device::next_event_tick(&saved.apic),
"so the scheduler is told the same next event"
);
let mut shape = MachineShape::new();
shape.add_device("lapic", CLASS.name).unwrap();
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("lapic", CLASS.name, CLASS.version).unwrap();
restored.apic.save(&mut chunk).unwrap();
}
assert_eq!(w.to_vec().unwrap(), bytes);
}
#[test]
fn a_reset_stops_the_timer_and_drops_intr() {
let b = bench();
b.enable();
b.poke(REG_LVT_TIMER, u32::from(TIMER_VECTOR));
b.poke(REG_TIMER_DIV, 0b1011);
b.poke(REG_TIMER_INIT, 3);
b.apic.advance_to(3);
assert!(b.intr.high());
b.apic.reset(ResetKind::Cold);
assert!(!b.intr.high());
assert_eq!(Device::next_event_tick(&b.apic), None);
assert_eq!(b.peek(REG_IRR + 2 * REG_STRIDE), 0);
}
}