chipset/
ioapic.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 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    /// The version reported by the Intel 82093AA.
43    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    /// The bits of the redirection entry that can be set by the guest.
85    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    /// Converts the entry to MSI format.
123    fn as_msi(&self) -> Option<(u64, u32)> {
124        if self.masked() {
125            return None;
126        }
127
128        // Copy the redirection entry's bits to the MSI as defined in the Intel
129        // ICHx datasheets.
130        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        // No need to evaluate; this interrupt definitely doesn't need to be
166        // delivered now.
167    }
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            // Clear remote IRR to allow further interrupts (if level
172            // triggered).
173            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        // Only some delivery modes support level trigger mode.
186        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        // Masked edge-triggered interrupts are lost. Masked level-triggered
193        // interrupts are reevaluated when the interrupt is unmasked.
194        if self.redirection.masked()
195            || (is_level && (!self.line_level || self.redirection.remote_irr()))
196            || (!is_level && !edge)
197        {
198            return;
199        }
200
201        // Remote IRR tracks whether a level-triggered interrupt has been EOIed yet.
202        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    // Static configuration
212    #[inspect(skip)]
213    valid_lines: [RangeInclusive<u32>; 1],
214
215    // Runtime glue
216    #[inspect(skip)]
217    routing: Box<dyn IoApicRouting>,
218
219    // Runtime book-keeping
220    stats: IoApicStats,
221
222    // Volatile state
223    #[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
236/// Trait allowing the IO-APIC device to assert VM interrupts.
237pub trait IoApicRouting: Send + Sync {
238    /// Asserts virtual interrupt line `irq`.
239    fn assert(&self, irq: u8);
240    /// Sets the MSI parameters to use when virtual interrupt line `irq` is
241    /// asserted.
242    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            // Reevaluate in case this unmasked a level-triggered interrupt.
309            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    /// reSearch query: `IoApicEmulator::NotifyEoi`
368    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        // Allow any size read.
507        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                // Only allow 4-byte writes.
522                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}