hal_x86_64/interrupt/apic/
ioapic.rs

1use super::{PinPolarity, TriggerMode};
2use crate::{
3    cpu::FeatureNotSupported,
4    interrupt::IsaInterrupt,
5    mm::{self, page, size::Size4Kb, PhysPage, VirtPage},
6};
7use hal_core::PAddr;
8use mycelium_util::{
9    bits::{bitfield, enum_from_bits},
10    sync::blocking::Mutex,
11};
12use volatile::Volatile;
13
14#[derive(Debug)]
15pub struct IoApicSet {
16    ioapics: alloc::vec::Vec<Mutex<IoApic>>,
17    isa_map: [IsaOverride; 16],
18}
19
20#[derive(Debug)]
21#[must_use]
22pub struct IoApic {
23    registers: Volatile<&'static mut MmioRegisters>,
24}
25
26bitfield! {
27    pub struct RedirectionEntry<u64> {
28        pub const VECTOR: u8;
29        pub const DELIVERY: DeliveryMode;
30        /// Destination mode.
31        ///
32        /// Physical (0) or logical (1). If this is physical mode, then bits
33        /// 56-59 should contain an APIC ID. If this is logical mode, then those
34        /// bits contain a set of processors.
35        pub const DEST_MODE: DestinationMode;
36        /// Set if this interrupt is going to be sent, but the APIC is busy. Read only.
37        pub const QUEUED: bool;
38        pub const POLARITY: PinPolarity;
39        /// Remote IRR.
40        ///
41        /// Used for level triggered interrupts only to show if a local APIC has
42        /// received the interrupt (= 1), or has sent an EOI (= 0). Read only.
43        pub const REMOTE_IRR: bool;
44        pub const TRIGGER: TriggerMode;
45        pub const MASKED: bool;
46        const _RESERVED = 39;
47        /// Destination field.
48        ///
49        /// If the destination mode bit was clear, then the
50        /// lower 4 bits contain the bit APIC ID to sent the interrupt to. If
51        /// the bit was set, the upper 4 bits also contain a set of processors.
52        /// (See below)
53        pub const DESTINATION: u8;
54    }
55}
56
57enum_from_bits! {
58    #[derive(Debug, PartialEq, Eq)]
59    pub enum DestinationMode<u8> {
60        Physical = 0,
61        Logical = 1,
62    }
63}
64
65enum_from_bits! {
66    #[derive(Debug, PartialEq, Eq)]
67    pub enum DeliveryMode<u8> {
68        /// Normal interrupt delivery.
69        Normal = 0b000,
70        /// Lowest priority.
71        LowPriority = 0b001,
72        /// System Management Interrupt (SMI).
73        SystemManagement = 0b010,
74        /// Non-Maskable Interrupt (NMI).
75        NonMaskable = 0b100,
76        /// "INIT" (what does this mean? i don't know!)
77        Init = 0b101,
78        /// External interrupt.
79        External = 0b111,
80    }
81}
82
83/// Memory-mapped IOAPIC registers
84#[derive(Copy, Clone, Debug)]
85#[repr(C)]
86struct MmioRegisters {
87    /// Selects the address to read/write from
88    address: u32,
89    _pad: [u32; 3],
90    /// The data to read/write
91    data: u32,
92}
93
94#[derive(Copy, Clone, Debug)]
95struct IsaOverride {
96    apic: u8,
97    vec: u8,
98}
99
100// === impl IoApicSet ===
101
102impl IoApicSet {
103    pub fn new(
104        madt: &acpi::platform::interrupt::Apic,
105        frame_alloc: &impl hal_core::mem::page::Alloc<mm::size::Size4Kb>,
106        pagectrl: &mut crate::mm::PageCtrl,
107        isa_base: u8,
108    ) -> Self {
109        // The ACPI Multiple APIC Descriptor Table (MADT) tells us where to find
110        // the I/O APICs, as well as information about how the ISA standard
111        // interrupts are routed to I/O APIC inputs on this system.
112        //
113        // See: https://wiki.osdev.org/MADT
114
115        // There may be multiple I/O APICs in the system, so we'll collect them
116        // all into a `Vec`. We're also going to build a table of how the ISA
117        // standard interrupts are mapped across those I/O APICs, so we can look
118        // up which one a particular interrupt lives on.
119        let n_ioapics = madt.io_apics.len();
120        tracing::trace!(?madt.io_apics, "found {n_ioapics} IO APICs", );
121        assert_ne!(
122            n_ioapics, 0,
123            "why would a computer have the APIC interrupt model but not \
124             have any IO APICs???"
125        );
126        let mut this = IoApicSet {
127            ioapics: alloc::vec::Vec::with_capacity(n_ioapics),
128            isa_map: [IsaOverride { apic: 0, vec: 0 }; 16],
129        };
130
131        for (n, ioapic) in madt.io_apics.iter().enumerate() {
132            let addr = PAddr::from_u64(ioapic.address as u64);
133            tracing::debug!(ioapic.paddr = ?addr, "IOAPIC {n}");
134            this.ioapics
135                .push(Mutex::new(IoApic::new(addr, pagectrl, frame_alloc)));
136        }
137
138        // Okay, so here's where it gets ~*weird*~.
139        //
140        // On the Platonic Ideal Normal Computer, the ISA PC interrupts would be
141        // mapped to I/O APIC input pins by number, so ISA IRQ 0 would go to pin
142        // 0 on I/O APIC 0, and so on.
143        //
144        // However, motherboard manufacturers can do whatever they like when
145        // routing these interrupts, so they could go to different pins,
146        // potentially on different I/O APICs (if the system has more than one).
147        // Also, some of these interrupts might have different pin polarity or
148        // edge/level-triggered-iness than what we might expect.
149        //
150        // Fortunately, ACPI is here to help (statements dreamed up by the
151        // utterly deranged).
152        //
153        // The MADT includes a list of "interrupt source overrides" that
154        // describe any ISA interrupts that are not mapped to I/O APIC pins in
155        // numeric order. Each entry in the interrupt source overrides list will
156        // contain:
157        // - an ISA IRQ number (naturally),
158        // - the "global system interrupt", which is NOT the IDT vector for that
159        //   interrupt but instead the I/O APIC input pin number that the IRQ is
160        //   routed to,
161        // - the polarity and trigger mode of the interrupt pin, if these are
162        //   different from the default bus trigger mode.
163        //
164        // For each of the 16 ISA interrupt vectors, we'll configure the
165        // redirection entry in the appropriate I/O APIC and record which I/O
166        // APIC (and which pin on that I/O APIC) the interrupt is routed to. We
167        // do this by first checking if the MADT contains an override matching
168        // that ISA interrupt, using that if so, and if not, falling back to the
169        // ISA interrupt number.
170        //
171        // Yes, this is a big pile of nested loops. But, consider the following:
172        //
173        // - the MADT override entries can probably come in any order, if the
174        //   motherboard firmware chooses to be maximally perverse, so we have
175        //   to scan the whole list of them to find out if each ISA interrupt is
176        //   overridden.
177        // - there are only ever 16 ISA interrupts, so the outer loop iterates
178        //   exactly 16 times; and there can't be *more* overrides than there
179        //   are ISA interrupts (and there's generally substantially fewer).
180        //   similarly, if there's more than 1-8 I/O APICs, you probably have
181        //   some kind of really weird computer and should tell me about it
182        //   because i bet it's awesome.
183        //   so, neither inner loop actually loops that many times.
184        // - finally, we only do this once on boot, so who cares?
185
186        let base_entry = RedirectionEntry::new()
187            .with(RedirectionEntry::DELIVERY, DeliveryMode::Normal)
188            .with(RedirectionEntry::REMOTE_IRR, false)
189            .with(RedirectionEntry::MASKED, true)
190            .with(RedirectionEntry::DESTINATION, 0xff);
191        for irq in IsaInterrupt::ALL {
192            // Assume the IRQ is mapped to the I/O APIC pin corresponding to
193            // that ISA IRQ number, and is active-high and edge-triggered.
194            let mut global_system_interrupt = irq as u8;
195            let mut polarity = PinPolarity::High;
196            let mut trigger = TriggerMode::Edge;
197            // Is there an override for this IRQ? If there is, clobber the
198            // assumed defaults with "whatever the override says".
199            if let Some(src_override) = madt
200                .interrupt_source_overrides
201                .iter()
202                .find(|o| o.isa_source == irq as u8)
203            {
204                // put the defaults through an extruder that maybe messes with
205                // them.
206                use acpi::platform::interrupt::{
207                    Polarity as AcpiPolarity, TriggerMode as AcpiTriggerMode,
208                };
209                tracing::debug!(
210                    ?irq,
211                    ?src_override.global_system_interrupt,
212                    ?src_override.polarity,
213                    ?src_override.trigger_mode,
214                    "ISA interrupt {irq:?} is overridden by MADT"
215                );
216                match src_override.polarity {
217                    AcpiPolarity::ActiveHigh => polarity = PinPolarity::High,
218                    AcpiPolarity::ActiveLow => polarity = PinPolarity::Low,
219                    // TODO(eliza): if the MADT override entry says that the pin
220                    // polarity is "same as bus", we should probably actually
221                    // make it be the same as the bus, instead of just assuming
222                    // that it's active high. But...just assuming that "same as
223                    // bus" means active high seems to basically work so far...
224                    AcpiPolarity::SameAsBus => {}
225                }
226                match src_override.trigger_mode {
227                    AcpiTriggerMode::Edge => trigger = TriggerMode::Edge,
228                    AcpiTriggerMode::Level => trigger = TriggerMode::Level,
229                    // TODO(eliza): As above, when the MADT says this is "same
230                    // as bus", we should make it be the same as the bus instead
231                    // of going "ITS EDGE TRIGGERED LOL LMAO" which is what
232                    // we're currently doing. But, just like above, this Seems
233                    // To Work?
234                    AcpiTriggerMode::SameAsBus => {}
235                }
236                global_system_interrupt = src_override.global_system_interrupt.try_into().expect(
237                    "if this exceeds u8::MAX, what the fuck! \
238                        that's bigger than the entire IDT...",
239                );
240            }
241            // Now, scan to find which I/O APIC this IRQ corresponds to. if the
242            // system only has one I/O APIC, this will always be 0, but we gotta
243            // handle systems with more than one. So, we'll this by traversing
244            // the list of I/O APICs, seeing if the GSI number is less than the
245            // max number of IRQs handled by that I/O APIC, and if it is, we'll
246            // stick it in there. If not, keep searching for the next one,
247            // subtracting the max number of interrupts handled by the I/O APIC
248            // we just looked at.
249            let mut entry_idx = global_system_interrupt;
250            let mut got_him = false;
251            'apic_scan: for (apic_idx, apic) in this.ioapics.iter_mut().enumerate() {
252                let apic = apic.get_mut();
253                let max_entries = apic.max_entries();
254                if entry_idx > max_entries {
255                    entry_idx -= max_entries;
256                    continue;
257                }
258
259                // Ladies and gentlemen...we got him!
260                got_him = true;
261                tracing::debug!(
262                    ?irq,
263                    ?global_system_interrupt,
264                    ?apic_idx,
265                    ?entry_idx,
266                    "found IOAPIC for ISA interrupt"
267                );
268                let entry = base_entry
269                    .with(RedirectionEntry::POLARITY, polarity)
270                    .with(RedirectionEntry::TRIGGER, trigger)
271                    .with(RedirectionEntry::VECTOR, isa_base + irq as u8);
272                apic.set_entry(entry_idx, entry);
273                this.isa_map[irq as usize] = IsaOverride {
274                    apic: apic_idx as u8,
275                    vec: entry_idx,
276                };
277                break 'apic_scan;
278            }
279
280            assert!(
281                got_him,
282                "somehow, we didn't find an I/O APIC for MADT global system \
283                 interrupt {global_system_interrupt} (ISA IRQ {irq:?})!\n \
284                 this probably means the MADT is corrupted somehow, or maybe \
285                 your motherboard is just super weird? i have no idea what to \
286                 do in this situation, so i guess i'll die."
287            );
288        }
289
290        this
291    }
292
293    fn for_isa_irq(&self, irq: IsaInterrupt) -> (&Mutex<IoApic>, u8) {
294        let isa_override = self.isa_map[irq as usize];
295        (&self.ioapics[isa_override.apic as usize], isa_override.vec)
296    }
297
298    pub fn set_isa_masked(&self, irq: IsaInterrupt, masked: bool) {
299        let (ioapic, vec) = self.for_isa_irq(irq);
300        ioapic.with_lock(|ioapic| ioapic.set_masked(vec, masked));
301    }
302}
303
304// === impl IoApic ===
305
306impl IoApic {
307    const REDIRECTION_ENTRY_BASE: u32 = 0x10;
308
309    /// Try to construct an `IoApic`.
310    ///
311    /// # Arguments
312    ///
313    /// - `base_addr`: The [`PAddr`] of the I/O APIC's memory-mapped register
314    ///   page.
315    /// - `pagectrl`: a [page mapper](page::Map) used to ensure that the MMIO
316    ///   register page is mapped and writable.
317    /// - `frame_alloc`: a [frame allocator](page::Alloc) used to allocate page
318    ///   frame(s) while mapping the MMIO register page.
319    ///
320    /// # Returns
321    /// - `Some(IoApic)` if this CPU supports the APIC interrupt model.
322    /// - `None` if this CPU does not support APIC interrupt handling.
323    pub fn try_new<A>(
324        base_paddr: PAddr,
325        pagectrl: &mut impl page::Map<Size4Kb, A>,
326        frame_alloc: &A,
327    ) -> Result<Self, FeatureNotSupported>
328    where
329        A: page::Alloc<Size4Kb>,
330    {
331        if !super::is_supported() {
332            tracing::warn!("tried to construct an IO APIC, but the CPU does not support the APIC interrupt model");
333            return Err(FeatureNotSupported::new("APIC interrupt model"));
334        }
335
336        let base = mm::kernel_vaddr_of(base_paddr);
337        tracing::debug!(?base, ?base_paddr, "found I/O APIC base address");
338
339        unsafe {
340            // ensure the I/O APIC's MMIO page is mapped and writable.
341            let virt = VirtPage::<Size4Kb>::containing_fixed(base);
342            let phys = PhysPage::<Size4Kb>::containing_fixed(base_paddr);
343            tracing::debug!(?virt, ?phys, "mapping I/O APIC MMIO page...");
344            pagectrl
345                .map_page(virt, phys, frame_alloc)
346                .set_writable(true)
347                .commit();
348            tracing::debug!("mapped I/O APIC MMIO page!");
349        }
350
351        let registers = unsafe { Volatile::new(&mut *base.as_mut_ptr::<MmioRegisters>()) };
352        let mut ioapic = Self { registers };
353        tracing::info!(
354            addr = ?base,
355            id = ioapic.id(),
356            version = ioapic.version(),
357            max_entries = ioapic.max_entries(),
358            "I/O APIC enabled"
359        );
360        Ok(ioapic)
361    }
362
363    #[inline]
364    pub fn new<A>(addr: PAddr, pagectrl: &mut impl page::Map<Size4Kb, A>, frame_alloc: &A) -> Self
365    where
366        A: page::Alloc<Size4Kb>,
367    {
368        Self::try_new(addr, pagectrl, frame_alloc).unwrap()
369    }
370
371    /// Returns the IO APIC's ID.
372    #[must_use]
373    pub fn id(&mut self) -> u8 {
374        let val = self.read(0);
375        (val >> 24) as u8
376    }
377
378    /// Returns the IO APIC's version.
379    #[must_use]
380    pub fn version(&mut self) -> u8 {
381        self.read(0x1) as u8
382    }
383
384    /// Returns the maximum number of redirection entries.
385    #[must_use]
386    pub fn max_entries(&mut self) -> u8 {
387        (self.read(0x1) >> 16) as u8
388    }
389
390    #[must_use]
391    pub fn entry(&mut self, irq: u8) -> RedirectionEntry {
392        let register_low = self
393            .entry_offset(irq)
394            .expect("IRQ number exceeds max redirection entries");
395        self.entry_raw(register_low)
396    }
397
398    pub fn set_entry(&mut self, irq: u8, entry: RedirectionEntry) {
399        tracing::debug!(irq, ?entry, "setting IOAPIC redirection entry");
400        let register_low = self
401            .entry_offset(irq)
402            .expect("IRQ number exceeds max redirection entries");
403        let bits = entry.bits();
404        let low = bits as u32;
405        let high = (bits >> 32) as u32;
406        self.write(register_low, low);
407        self.write(register_low + 1, high);
408    }
409
410    /// Convenience function to mask/unmask an IRQ.
411    pub fn set_masked(&mut self, irq: u8, masked: bool) {
412        tracing::debug!(irq, masked, "IoApic::set_masked");
413        self.update_entry(irq, |entry| entry.with(RedirectionEntry::MASKED, masked))
414    }
415
416    pub fn update_entry(
417        &mut self,
418        irq: u8,
419        update: impl FnOnce(RedirectionEntry) -> RedirectionEntry,
420    ) {
421        let register_low = self
422            .entry_offset(irq)
423            .expect("IRQ number exceeds max redirection entries");
424        let entry = self.entry_raw(register_low);
425        let new_entry = update(entry);
426        self.set_entry_raw(register_low, new_entry);
427    }
428
429    fn entry_offset(&mut self, irq: u8) -> Option<u32> {
430        let max_entries = self.max_entries();
431        if irq > max_entries {
432            tracing::warn!("tried to access redirection entry {irq}, but the IO APIC only supports supports up to {max_entries}");
433            return None;
434        }
435
436        Some(Self::REDIRECTION_ENTRY_BASE + irq as u32 * 2)
437    }
438
439    #[inline]
440    fn entry_raw(&mut self, register_low: u32) -> RedirectionEntry {
441        let low = self.read(register_low);
442        let high = self.read(register_low + 1);
443        RedirectionEntry::from_bits(((high as u64) << 32) | low as u64)
444    }
445
446    #[inline]
447    fn set_entry_raw(&mut self, register_low: u32, entry: RedirectionEntry) {
448        let bits = entry.bits();
449        let low = bits as u32;
450        let high = (bits >> 32) as u32;
451        self.write(register_low, low);
452        self.write(register_low + 1, high);
453    }
454
455    #[must_use]
456    fn read(&mut self, offset: u32) -> u32 {
457        self.set_offset(offset);
458        self.registers.map_mut(|ioapic| &mut ioapic.data).read()
459    }
460
461    fn write(&mut self, offset: u32, value: u32) {
462        self.set_offset(offset);
463        self.registers
464            .map_mut(|ioapic| &mut ioapic.data)
465            .write(value)
466    }
467
468    fn set_offset(&mut self, offset: u32) {
469        assert!(offset <= 0xff, "invalid IOAPIC register offset {offset:#x}",);
470        self.registers
471            .map_mut(|ioapic| &mut ioapic.address)
472            .write(offset);
473    }
474}
475
476// === impl DeliveryMode ===
477
478impl Default for DeliveryMode {
479    fn default() -> Self {
480        Self::Normal
481    }
482}
483
484#[cfg(test)]
485mod test {
486    use super::*;
487
488    #[test]
489    fn redirection_entry_is_valid() {
490        RedirectionEntry::assert_valid();
491
492        let entry = RedirectionEntry::new()
493            .with(RedirectionEntry::DELIVERY, DeliveryMode::Normal)
494            .with(RedirectionEntry::POLARITY, PinPolarity::High)
495            .with(RedirectionEntry::TRIGGER, TriggerMode::Edge)
496            .with(RedirectionEntry::MASKED, true)
497            .with(RedirectionEntry::DESTINATION, 0xff)
498            .with(RedirectionEntry::VECTOR, 0x30);
499        println!("{entry}");
500    }
501
502    #[test]
503    fn redirection_entry_offsets() {
504        assert_eq!(
505            RedirectionEntry::DELIVERY.least_significant_index(),
506            8,
507            "delivery"
508        );
509        assert_eq!(
510            RedirectionEntry::DEST_MODE.least_significant_index(),
511            11,
512            "destination mode"
513        );
514        assert_eq!(
515            RedirectionEntry::QUEUED.least_significant_index(),
516            12,
517            "queued"
518        );
519        assert_eq!(
520            RedirectionEntry::POLARITY.least_significant_index(),
521            13,
522            "pin polarity"
523        );
524        assert_eq!(
525            RedirectionEntry::REMOTE_IRR.least_significant_index(),
526            14,
527            "remote IRR"
528        );
529        assert_eq!(
530            RedirectionEntry::TRIGGER.least_significant_index(),
531            15,
532            "trigger mode"
533        );
534        assert_eq!(
535            RedirectionEntry::MASKED.least_significant_index(),
536            16,
537            "masked"
538        );
539        assert_eq!(
540            RedirectionEntry::DESTINATION.least_significant_index(),
541            56,
542            "destination field"
543        );
544    }
545
546    #[test]
547    fn offsetof() {
548        let mmregs = MmioRegisters {
549            address: 0,
550            _pad: [0, 0, 0],
551            data: 0,
552        };
553        let addrof = core::ptr::addr_of!(mmregs.data);
554        assert_eq!(
555            addrof as *const () as usize,
556            (&mmregs as *const _ as usize) + 0x10
557        )
558    }
559}