rstubs/arch/int/
ioapic.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
//! Abstraction of the I/O APIC that is used for management of external interrupts.

use core::mem::size_of;
use core::sync::atomic::{AtomicPtr, AtomicU8, Ordering};

use bitfield_struct::bitfield;

use crate::arch::io::VolatileUpdate;

/// The different devices that can send interrupts
#[derive(Debug, Copy, Clone)]
#[allow(unused)]
pub enum Device {
    /// Programmable Interrupt Timer
    Timer = 0,
    /// Keyboard
    Keyboard = 1,
    /// First serial interface
    Com1 = 4,
    /// Second serial interface
    Com2 = 3,
    /// Floppy device
    Floppy = 6,
    /// Printer
    LPT1 = 7,
    /// Real time clock
    RealTimeClock = 8,
    /// Mouse
    PS2Mouse = 12,
    /// First hard disk
    IDE1 = 14,
    /// Second hard disk
    IDE2 = 15,
}

/// The I/O APIC's Core component is the IO-redirection table.
/// This table is used to configure a flexible mapping between
/// the interrupt number and the external interruption.
/// Entries within this table have a width of 64 bit.
pub struct IoApic {
    /// Base address of the IOAPIC registers.
    ///
    /// It contains two memory-mapped registers: `IOREGSEL` at `base` and
    /// `IOWIN` at `base + 0x10`.
    pub base: AtomicPtr<u32>,
    overrides: [AtomicU8; 16],
}

/// The shared IOAPIC instance.
pub static IOAPIC: IoApic = IoApic::new();

impl IoApic {
    pub const DEFAULT_BASE: usize = 0xfec0_0000;
    pub const SLOT_MAX: u8 = 24;

    /// Create a new instance that has to be initialize with [Self::init].
    pub const fn new() -> Self {
        // Identity mapping (no redirections)
        let mut i = 0;
        let mut overrides = [const { AtomicU8::new(0) }; 16];
        while i < 16 {
            overrides[i] = AtomicU8::new(i as _);
            i += 1;
        }
        Self {
            base: AtomicPtr::new(Self::DEFAULT_BASE as _),
            overrides,
        }
    }

    /// Override a slot id, redirecting it
    pub fn set_override(&self, source: usize, global: u8) {
        if source < self.overrides.len() {
            self.overrides[source].store(global, Ordering::Relaxed);
        }
    }

    /// Initializes the I/O APIC.
    ///
    /// This function will initialize the I/O APIC by initializing the
    /// IO-redirection table with sane default values.
    /// The default interrupt-vector number is chosen such that,
    /// in case the interrupt is issued, the panic handler is executed.
    /// In the beginning, all external interrupts are disabled within the I/O APIC.
    /// Apart from the redirection table, the `id`
    /// (read from the system description tables during boot) needs to be passed in.
    pub fn init(&self, id: u8, panic_vector: u8) {
        // Set io apic id
        self.update(Identification::ADDR, |v: Identification| v.with_id(id));

        let reg = self.read::<Version>(Version::ADDR);
        serial!(
            "ioapic version: {:#x}, mre: {}",
            reg.version(),
            reg.max_redirection_entry()
        );

        for i in 0..Self::SLOT_MAX {
            self.config_raw(i, panic_vector, false);
        }
    }

    /// Creates a mapping between an interrupt vector and an external interrupt.
    pub fn config(&self, device: Device, vector: u8, trigger_level: bool) {
        let slot = self.slot(device);
        self.config_raw(slot, vector, trigger_level);
    }

    /// Configures the redirection slots with:
    /// - lowest priority delivery to all cores
    /// - high polarity
    /// - masked (you can enable the keyboard later)
    /// - tigger level and vector based on the arguments
    ///
    /// > Note: Do not overwrite any reserved bits of the registers (read-modify-write).
    fn config_raw(&self, slot: u8, vector: u8, trigger_level: bool) {
        let reg_l = self
            .read::<SlotL>(red_l(slot))
            .with_vector(vector)
            .with_delivery_mode(1) // lowest pri
            .with_destination_mode(true) // logical
            .with_polarity(false) // high
            .with_trigger_mode(trigger_level) // edge or level triggered
            .with_mask(true);
        let reg_h = self.read::<SlotH>(red_h(slot)).with_destination(0b1);

        self.write(red_l(slot), reg_l);
        self.write(red_h(slot), reg_h);
    }

    /// Enables or disables interrupts for the given `device`.
    pub fn enable(&self, device: Device, enabled: bool) {
        let slot = self.slot(device);
        self.update(red_l(slot), |v: SlotL| v.with_mask(!enabled));
    }

    /// Returns whether interrupts for the `device` are active.
    pub fn enabled(&self, device: Device) -> bool {
        let slot = self.slot(device);
        !self.read::<SlotL>(red_l(slot)).mask()
    }

    /// Get the slot for the device (respecting redirections from the ACPI Tables).
    fn slot(&self, device: Device) -> u8 {
        if let Some(o) = self.overrides.get(device as usize) {
            o.load(Ordering::Relaxed)
        } else {
            device as _
        }
    }

    /// Write a value to a register.
    ///
    /// Access to the actual IOAPIC registers can be obtained by performing
    /// the following steps:
    ///
    /// 1. Write the number of the IOAPIC register to the address stored in `IOREGSEL`
    /// 2. Read the value from / write the value to the address referred to by `IOWIN`.
    fn write<T: Register>(&self, addr: u32, reg: T) {
        debug_assert!(size_of::<T>() == 4);
        let base = self.base.load(Ordering::Acquire);
        unsafe {
            base.write_volatile(addr);
            base.add(0x10 / size_of::<u32>()).write_volatile(reg.into());
        }
    }

    /// Read a value from a register.
    fn read<T: Register>(&self, addr: u32) -> T {
        debug_assert!(size_of::<T>() == 4);
        let base = self.base.load(Ordering::Acquire);
        unsafe {
            base.write_volatile(addr);
            base.add(0x10 / size_of::<u32>()).read_volatile().into()
        }
    }

    /// Read-modify-update the value of a register.
    fn update<T: Register>(&self, addr: u32, f: impl FnOnce(T) -> T) {
        debug_assert!(size_of::<T>() == 4);
        let base = self.base.load(Ordering::Acquire);
        unsafe {
            base.write_volatile(addr);
            base.add(0x10 / size_of::<u32>())
                .update_volatile(|v| f(v.into()).into());
        }
    }
}

/// Returns the redirection table entry address for the `slot` (lower half).
const fn red_l(slot: u8) -> u32 {
    2 * (slot as u32) + 0x10
}

/// Returns the redirection table entry address for the `slot` (upper half).
const fn red_h(slot: u8) -> u32 {
    2 * (slot as u32) + 0x11
}

/// An APIC register that can be read/written to IOWIN.
trait Register: From<u32> + Into<u32> {}

/// I/O APIC identification
#[bitfield(u32)]
struct Identification {
    #[bits(24)]
    _p: (),
    #[bits(4)]
    id: u8,
    #[bits(4)]
    _p: (),
}
impl Identification {
    const ADDR: u32 = 0;
}
impl Register for Identification {}

/// I/O APIC version information
#[bitfield(u32)]
struct Version {
    /// Version number
    version: u8,
    _p: u8,
    /// Maximum number of supported redirection entries
    max_redirection_entry: u8,
    _p: u8,
}
impl Version {
    const ADDR: u32 = 1;
}
impl Register for Version {}

/// Entry in the redirection table.
/// Lower Half.
#[bitfield(u32)]
struct SlotL {
    /// Interrupt vector in the "Interrupt Descriptor Table (IDT)" will be
    /// activated when the corresponding external interrupt triggers.
    vector: u8,
    /// The delivery mode denotes the way the interrupts will be delivered
    /// to the local CPU cores, respectively to their local APICs.
    /// - 0: Fixed (send to all cores from the destination mask)
    /// - 1: Lowest priority (send to the lowest priority core from destination mask)
    /// - 2-7: System management / initialization / external interrupts...
    #[bits(3)]
    delivery_mode: u8,
    /// The destination mode defines how the value stored in destination will be interpreted.
    /// Local (true) or physical destination.
    destination_mode: bool,
    /// Delivery status holds whether the interrupt delivery is pending (true) or idle.
    delivery_pending: bool,
    /// The polarity denotes when an interrupt should be issued. This can be low (true) or high.
    polarity: bool,
    /// The remote IRR bit indicates whether the local APIC(s) accept the level interrupt.
    /// The LAPIC EOI sets this read-only flag to zero.
    remote_irr: bool,
    /// The trigger mode states whether the interrupt signaling is level (true) or edge triggered.
    trigger_mode: bool,
    /// Mask (disables) or unmask interrupts for a particular, external source.
    mask: bool,
    #[bits(15)]
    _p: (),
}
impl Register for SlotL {}

/// Entry in the redirection table.
/// Higher Half.
#[bitfield(u32)]
struct SlotH {
    #[bits(24)]
    _p: (),
    /// Interrupt destination.
    ///
    /// The meaning of destination depends on the destination mode:
    /// For the logical destination mode, destination holds a bit mask made up
    /// of the cores that are candidates for receiving the interrupt.
    /// In the single-core case, this value is `1`, in the multi-core case,
    /// the `n` low-order bits needs to be set (with `n` being the number of
    /// CPU cores).
    /// Setting the `n` low-order bits marks all available cores as candidates
    /// for receiving interrupts and thereby balancing the number of interrupts
    /// between the cores.
    destination: u8,
}
impl Register for SlotH {}