1use self::spec::IO_APIC_VERSION;
10use self::spec::IOAPIC_DEVICE_MMIO_REGION_SIZE;
11use self::spec::IndexRegister;
12use self::spec::IoApicId;
13use self::spec::IoApicVersion;
14use self::spec::REDIRECTION_WRITE_MASK;
15use self::spec::RedirectionEntry;
16use crate::ioapic::spec::IOAPIC_DEVICE_MMIO_REGION_MASK;
17use crate::ioapic::spec::Register;
18use chipset_device::ChipsetDevice;
19use chipset_device::interrupt::HandleEoi;
20use chipset_device::interrupt::LineInterruptTarget;
21use chipset_device::io::IoError;
22use chipset_device::io::IoResult;
23use chipset_device::mmio::MmioIntercept;
24use inspect::Inspect;
25use inspect::InspectMut;
26use inspect_counters::Counter;
27use std::fmt;
28use std::fmt::Debug;
29use std::ops::RangeInclusive;
30use vmcore::device_state::ChangeDeviceState;
31use x86defs::apic::DeliveryMode;
32use x86defs::msi::MsiAddress;
33use x86defs::msi::MsiData;
34
35pub const IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS: u64 = 0xfec00000;
36
37mod spec {
38 use bitfield_struct::bitfield;
39 use inspect::Inspect;
40 use open_enum::open_enum;
41
42 pub const IO_APIC_VERSION: u8 = 0x11;
44
45 open_enum! {
46 pub enum Register: u64 {
47 INDEX = 0,
48 DATA = 0x10,
49 }
50 }
51
52 open_enum! {
53 #[derive(Inspect)]
54 #[inspect(debug)]
55 pub enum IndexRegister: u8 {
56 ID = 0,
57 VERSION = 1,
58 ARBITRATION_ID = 2,
59 REDIRECTION_TABLE_START = 0x10,
60 }
61 }
62
63 pub const IOAPIC_DEVICE_MMIO_REGION_SIZE: u64 = 0x20;
64 pub const IOAPIC_DEVICE_MMIO_REGION_MASK: u64 = IOAPIC_DEVICE_MMIO_REGION_SIZE - 1;
65
66 #[derive(Inspect)]
67 #[bitfield(u64)]
68 #[rustfmt::skip]
69 pub struct RedirectionEntry {
70 #[bits(8)] pub vector: u8,
71 #[bits(3)] pub delivery_mode: u8,
72 #[bits(1)] pub destination_mode_logical: bool,
73 #[bits(1)] pub delivery_status: bool,
74 #[bits(1)] pub active_low: bool,
75 #[bits(1)] pub remote_irr: bool,
76 #[bits(1)] pub trigger_mode_level: bool,
77 #[bits(1)] pub masked: bool,
78 #[bits(15)] _unused: u32,
79 #[bits(16)] _unused2: u32,
80 #[bits(8)] pub extended_destination: u8,
81 #[bits(8)] pub destination: u8,
82 }
83
84 pub const REDIRECTION_WRITE_MASK: u64 = RedirectionEntry::new()
86 .with_vector(0xff)
87 .with_delivery_mode(0x7)
88 .with_destination_mode_logical(true)
89 .with_active_low(true)
90 .with_trigger_mode_level(true)
91 .with_masked(true)
92 .with_destination(0xff)
93 .with_extended_destination(0xff)
94 .0;
95
96 #[bitfield(u32)]
97 pub struct IoApicId {
98 #[bits(24)]
99 _reserved: u32,
100 #[bits(4)]
101 pub id: u8,
102 #[bits(4)]
103 _reserved2: u32,
104 }
105
106 #[bitfield(u32)]
107 pub struct IoApicVersion {
108 pub version: u8,
109 _reserved: u8,
110 pub max_entry: u8,
111 _reserved2: u8,
112 }
113}
114
115impl RedirectionEntry {
116 fn init() -> Self {
117 Self::new()
118 .with_destination_mode_logical(true)
119 .with_masked(true)
120 }
121
122 fn as_msi(&self) -> Option<(u64, u32)> {
124 if self.masked() {
125 return None;
126 }
127
128 let address = MsiAddress::new()
131 .with_address(x86defs::msi::MSI_ADDRESS)
132 .with_destination(self.destination())
133 .with_extended_destination(self.extended_destination())
134 .with_destination_mode_logical(self.destination_mode_logical())
135 .with_redirection_hint(self.delivery_mode() == DeliveryMode::LOWEST_PRIORITY.0);
136
137 let data = MsiData::new()
138 .with_assert(true)
139 .with_destination_mode_logical(self.destination_mode_logical())
140 .with_delivery_mode(self.delivery_mode())
141 .with_trigger_mode_level(self.trigger_mode_level())
142 .with_vector(self.vector());
143
144 Some((u32::from(address).into(), data.into()))
145 }
146}
147
148#[derive(Debug, Inspect)]
149struct IrqEntry {
150 #[inspect(flatten)]
151 redirection: RedirectionEntry,
152 line_level: bool,
153 #[inspect(skip)]
154 registered_request: Option<(u64, u32)>,
155}
156
157impl IrqEntry {
158 fn assert(&mut self, routing: &dyn IoApicRouting, stats: &mut IoApicStats, n: u8) {
159 let old_level = std::mem::replace(&mut self.line_level, true);
160 self.evaluate(routing, stats, n, !old_level);
161 }
162
163 fn deassert(&mut self) {
164 self.line_level = false;
165 }
168
169 fn eoi(&mut self, vector: u32, routing: &dyn IoApicRouting, stats: &mut IoApicStats, n: u8) {
170 if self.redirection.vector() as u32 == vector {
171 self.redirection.set_remote_irr(false);
174 self.evaluate(routing, stats, n, false);
175 }
176 }
177
178 fn evaluate(
179 &mut self,
180 routing: &dyn IoApicRouting,
181 stats: &mut IoApicStats,
182 n: u8,
183 edge: bool,
184 ) {
185 let is_level = self.redirection.trigger_mode_level()
187 && matches!(
188 DeliveryMode(self.redirection.delivery_mode()),
189 DeliveryMode::FIXED | DeliveryMode::LOWEST_PRIORITY
190 );
191
192 if self.redirection.masked()
195 || (is_level && (!self.line_level || self.redirection.remote_irr()))
196 || (!is_level && !edge)
197 {
198 return;
199 }
200
201 self.redirection.set_remote_irr(is_level);
203 stats.interrupts.increment();
204 stats.interrupts_per_irq[n as usize].increment();
205 routing.assert(n);
206 }
207}
208
209#[derive(Debug, InspectMut)]
210pub struct IoApicDevice {
211 #[inspect(skip)]
213 valid_lines: [RangeInclusive<u32>; 1],
214
215 #[inspect(skip)]
217 routing: Box<dyn IoApicRouting>,
218
219 stats: IoApicStats,
221
222 #[inspect(with = r#"|x| inspect::iter_by_index(x.iter())"#)]
224 irqs: Box<[IrqEntry]>,
225 id: u8,
226 index: IndexRegister,
227}
228
229#[derive(Debug, Inspect)]
230struct IoApicStats {
231 #[inspect(iter_by_index)]
232 interrupts_per_irq: Vec<Counter>,
233 interrupts: Counter,
234}
235
236pub trait IoApicRouting: Send + Sync {
238 fn assert(&self, irq: u8);
240 fn set_route(&self, irq: u8, request: Option<(u64, u32)>);
243}
244
245impl Debug for dyn IoApicRouting {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 f.pad("IoApicRouting")
248 }
249}
250
251impl IoApicDevice {
252 pub fn new(num_entries: u8, routing: Box<dyn IoApicRouting>) -> Self {
253 let irqs = (0..num_entries)
254 .map(|_| IrqEntry {
255 redirection: RedirectionEntry::init(),
256 line_level: false,
257 registered_request: None,
258 })
259 .collect();
260
261 IoApicDevice {
262 valid_lines: [0..=num_entries as u32 - 1],
263 routing,
264
265 id: 0,
266 irqs,
267 index: IndexRegister::ID,
268 stats: IoApicStats {
269 interrupts_per_irq: (0..num_entries).map(|_| Counter::new()).collect(),
270 interrupts: Counter::new(),
271 },
272 }
273 }
274
275 fn read_redirection_register(&self, index: u8) -> u32 {
276 let mut value = self
277 .irqs
278 .get(index as usize / 2)
279 .map_or(0, |irq| irq.redirection.into());
280 if index & 1 == 1 {
281 value >>= 32;
282 }
283 value as u32
284 }
285
286 fn write_redirection_register(&mut self, index: u8, val: u32) {
287 let n = index as usize / 2;
288 if let Some(irq) = self.irqs.get_mut(n) {
289 let mut val = val as u64;
290 let mut redirection = u64::from(irq.redirection);
291 if index & 1 == 1 {
292 redirection &= 0xffffffff;
293 val <<= 32;
294 } else {
295 redirection &= !0xffffffff;
296 }
297 redirection |= val & REDIRECTION_WRITE_MASK;
298 irq.redirection = redirection.into();
299
300 tracing::debug!(n, entry = ?irq.redirection, "new redirection entry");
301
302 let request = irq.redirection.as_msi();
303 if request != irq.registered_request {
304 self.routing.set_route(n as u8, request);
305 irq.registered_request = request;
306 }
307
308 irq.evaluate(self.routing.as_ref(), &mut self.stats, n as u8, false);
310 }
311 }
312
313 fn read_register(&self, index: IndexRegister) -> u32 {
314 match index {
315 IndexRegister::ID => IoApicId::new().with_id(self.id).into(),
316 IndexRegister::VERSION => IoApicVersion::new()
317 .with_version(IO_APIC_VERSION)
318 .with_max_entry((self.irqs.len() - 1) as u8)
319 .into(),
320 IndexRegister::ARBITRATION_ID => 0,
321 _ if self.index >= IndexRegister::REDIRECTION_TABLE_START => {
322 self.read_redirection_register(index.0 - IndexRegister::REDIRECTION_TABLE_START.0)
323 }
324 _ => {
325 tracelimit::warn_ratelimited!(?index, "unsupported register index read");
326 !0
327 }
328 }
329 }
330
331 fn write_register(&mut self, index: IndexRegister, val: u32) {
332 match index {
333 IndexRegister::ID => self.id = IoApicId::from(val).id(),
334 IndexRegister::VERSION | IndexRegister::ARBITRATION_ID => {
335 tracing::debug!(?index, val, "ignoring write to read-only register");
336 }
337 _ if self.index >= IndexRegister::REDIRECTION_TABLE_START => {
338 self.write_redirection_register(
339 index.0 - IndexRegister::REDIRECTION_TABLE_START.0,
340 val,
341 );
342 }
343 _ => {
344 tracelimit::warn_ratelimited!(?index, "unsupported register index write");
345 }
346 }
347 }
348}
349
350impl LineInterruptTarget for IoApicDevice {
351 fn set_irq(&mut self, n: u32, high: bool) {
352 if let Some(irq) = self.irqs.get_mut(n as usize) {
353 if high {
354 irq.assert(self.routing.as_ref(), &mut self.stats, n as u8);
355 } else {
356 irq.deassert();
357 }
358 }
359 }
360
361 fn valid_lines(&self) -> &[RangeInclusive<u32>] {
362 &self.valid_lines
363 }
364}
365
366impl HandleEoi for IoApicDevice {
367 fn handle_eoi(&mut self, irq_to_end: u32) {
369 for (index, irq) in self.irqs.iter_mut().enumerate() {
370 irq.eoi(
371 irq_to_end,
372 self.routing.as_ref(),
373 &mut self.stats,
374 index as u8,
375 );
376 }
377 }
378}
379
380impl ChangeDeviceState for IoApicDevice {
381 fn start(&mut self) {}
382
383 async fn stop(&mut self) {}
384
385 async fn reset(&mut self) {
386 let Self {
387 valid_lines: _,
388 routing: _,
389 irqs,
390 id,
391 index,
392 stats: _,
393 } = self;
394 *id = 0;
395 *index = IndexRegister::ID;
396 for (n, irq) in irqs.iter_mut().enumerate() {
397 irq.redirection = RedirectionEntry::init();
398 self.routing.set_route(n as u8, None);
399 irq.registered_request = None;
400 }
401 }
402}
403
404impl ChipsetDevice for IoApicDevice {
405 fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
406 Some(self)
407 }
408
409 fn supports_line_interrupt_target(&mut self) -> Option<&mut dyn LineInterruptTarget> {
410 Some(self)
411 }
412
413 fn supports_handle_eoi(&mut self) -> Option<&mut dyn HandleEoi> {
414 Some(self)
415 }
416}
417
418mod save_restore {
419 use super::IoApicDevice;
420 use super::spec::IndexRegister;
421 use super::spec::RedirectionEntry;
422 use thiserror::Error;
423 use vmcore::save_restore::RestoreError;
424 use vmcore::save_restore::SaveError;
425 use vmcore::save_restore::SaveRestore;
426
427 mod state {
428 use mesh::payload::Protobuf;
429 use vmcore::save_restore::SavedStateRoot;
430
431 #[derive(Clone, Debug, Default, Protobuf, SavedStateRoot)]
432 #[mesh(package = "chipset.ioapic")]
433 pub struct SavedState {
434 #[mesh(1)]
435 pub(super) id: u8,
436 #[mesh(2)]
437 pub(super) index: u8,
438 #[mesh(3)]
439 pub(super) redirection_entries: Vec<u64>,
440 }
441 }
442
443 #[derive(Error, Debug)]
444 #[error("wrong number of redirection entries")]
445 struct WrongNumberOfRedirectionEntries;
446
447 impl SaveRestore for IoApicDevice {
448 type SavedState = state::SavedState;
449
450 fn save(&mut self) -> Result<state::SavedState, SaveError> {
451 let Self {
452 valid_lines: _,
453 routing: _,
454 irqs,
455 id,
456 index,
457 stats: _,
458 } = &self;
459
460 Ok(state::SavedState {
461 redirection_entries: irqs.iter().map(|irq| irq.redirection.into()).collect(),
462 index: index.0,
463 id: *id,
464 })
465 }
466
467 fn restore(&mut self, state: state::SavedState) -> Result<(), RestoreError> {
468 let state::SavedState {
469 redirection_entries,
470 id,
471 index,
472 } = state;
473 if redirection_entries.len() != self.irqs.len() {
474 return Err(RestoreError::Other(WrongNumberOfRedirectionEntries.into()));
475 }
476 for (n, (state, irq)) in redirection_entries
477 .into_iter()
478 .zip(self.irqs.iter_mut())
479 .enumerate()
480 {
481 irq.redirection = RedirectionEntry::from(state);
482 let request = irq.redirection.as_msi();
483 self.routing.set_route(n as u8, request);
484 irq.registered_request = request;
485 }
486 self.id = id;
487 self.index = IndexRegister(index);
488 Ok(())
489 }
490 }
491}
492
493impl MmioIntercept for IoApicDevice {
494 fn mmio_read(&mut self, address: u64, data: &mut [u8]) -> IoResult {
495 assert_eq!(
496 address & !IOAPIC_DEVICE_MMIO_REGION_MASK,
497 IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS
498 );
499
500 let v = match Register(address & IOAPIC_DEVICE_MMIO_REGION_MASK) {
501 Register::INDEX => self.index.0.into(),
502 Register::DATA => self.read_register(self.index),
503 _ => return IoResult::Err(IoError::InvalidRegister),
504 };
505
506 let n = data.len().min(size_of_val(&v));
508 data.copy_from_slice(&v.to_ne_bytes()[..n]);
509 IoResult::Ok
510 }
511
512 fn mmio_write(&mut self, address: u64, data: &[u8]) -> IoResult {
513 assert_eq!(
514 address & !IOAPIC_DEVICE_MMIO_REGION_MASK,
515 IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS
516 );
517
518 match Register(address & IOAPIC_DEVICE_MMIO_REGION_MASK) {
519 Register::INDEX => self.index = IndexRegister(data[0]),
520 Register::DATA => {
521 let Ok(data) = data.try_into() else {
523 return IoResult::Err(IoError::InvalidAccessSize);
524 };
525 let data = u32::from_ne_bytes(data);
526 self.write_register(self.index, data);
527 }
528 _ => return IoResult::Err(IoError::InvalidRegister),
529 }
530
531 IoResult::Ok
532 }
533
534 fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u64>)] {
535 &[(
536 "mmio",
537 IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS
538 ..=IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS + IOAPIC_DEVICE_MMIO_REGION_SIZE - 1,
539 )]
540 }
541}