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 pub const DEST_MODE: DestinationMode;
36 pub const QUEUED: bool;
38 pub const POLARITY: PinPolarity;
39 pub const REMOTE_IRR: bool;
44 pub const TRIGGER: TriggerMode;
45 pub const MASKED: bool;
46 const _RESERVED = 39;
47 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 = 0b000,
70 LowPriority = 0b001,
72 SystemManagement = 0b010,
74 NonMaskable = 0b100,
76 Init = 0b101,
78 External = 0b111,
80 }
81}
82
83#[derive(Copy, Clone, Debug)]
85#[repr(C)]
86struct MmioRegisters {
87 address: u32,
89 _pad: [u32; 3],
90 data: u32,
92}
93
94#[derive(Copy, Clone, Debug)]
95struct IsaOverride {
96 apic: u8,
97 vec: u8,
98}
99
100impl 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 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 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 let mut global_system_interrupt = irq as u8;
195 let mut polarity = PinPolarity::High;
196 let mut trigger = TriggerMode::Edge;
197 if let Some(src_override) = madt
200 .interrupt_source_overrides
201 .iter()
202 .find(|o| o.isa_source == irq as u8)
203 {
204 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 AcpiPolarity::SameAsBus => {}
225 }
226 match src_override.trigger_mode {
227 AcpiTriggerMode::Edge => trigger = TriggerMode::Edge,
228 AcpiTriggerMode::Level => trigger = TriggerMode::Level,
229 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 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 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
304impl IoApic {
307 const REDIRECTION_ENTRY_BASE: u32 = 0x10;
308
309 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 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 #[must_use]
373 pub fn id(&mut self) -> u8 {
374 let val = self.read(0);
375 (val >> 24) as u8
376 }
377
378 #[must_use]
380 pub fn version(&mut self) -> u8 {
381 self.read(0x1) as u8
382 }
383
384 #[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 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
476impl 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}