Skip to main content

chipset/ioapic/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! IO-APIC emulator.
5//!
6//! Currently this supports IO-APIC version 0x11, from the PIIX4 era. This
7//! version does not support an EOI register (unlike version 0x20 and newer).
8
9use 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    /// The version reported by the Intel 82093AA.
45    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    /// The bits of the redirection entry that can be set by the guest.
87    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    /// Converts the entry to MSI format.
125    fn as_msi(&self) -> Option<(u64, u32)> {
126        if self.masked() {
127            return None;
128        }
129
130        // Copy the redirection entry's bits to the MSI as defined in the Intel
131        // ICHx datasheets.
132        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        // No need to evaluate; this interrupt definitely doesn't need to be
168        // delivered now.
169    }
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            // Clear remote IRR to allow further interrupts (if level
174            // triggered).
175            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        // Only some delivery modes support level trigger mode.
188        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        // Masked edge-triggered interrupts are lost. Masked level-triggered
195        // interrupts are reevaluated when the interrupt is unmasked.
196        if self.redirection.masked()
197            || (is_level && (!self.line_level || self.redirection.remote_irr()))
198            || (!is_level && !edge)
199        {
200            return;
201        }
202
203        // Remote IRR tracks whether a level-triggered interrupt has been EOIed yet.
204        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    // Static configuration
214    #[inspect(skip)]
215    valid_lines: [RangeInclusive<u32>; 1],
216
217    // Runtime glue
218    #[inspect(skip)]
219    routing: Box<dyn IoApicRouting>,
220
221    // Runtime book-keeping
222    stats: IoApicStats,
223
224    // Volatile state
225    #[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            // Reevaluate in case this unmasked a level-triggered interrupt.
296            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    /// reSearch query: `IoApicEmulator::NotifyEoi`
355    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        // Allow any size read.
494        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                // Only allow 4-byte writes.
509                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}