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