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
use super::TABLE_BASE;
use bitflags::bitflags;
use core::convert::{TryFrom, TryInto};
use core::fmt;

pub const IRQ_MODE_MASK: u32 = 0x0000_0700;

/// IOAPIC interrupt modes.
#[derive(Debug)]
#[repr(u8)]
pub enum IrqMode {
    /// Asserts the INTR signal on all allowed processors.
    Fixed = 0b000,
    /// Asserts the INTR signal on the lowest priority processor allowed.
    LowestPriority = 0b001,
    /// System management interrupt.
    /// Requires edge-triggering.
    SystemManagement = 0b010,
    /// Asserts the NMI signal on all allowed processors.
    /// Requires edge-triggering.
    NonMaskable = 0b100,
    /// Asserts the INIT signal on all allowed processors.
    /// Requires edge-triggering.
    Init = 0b101,
    /// Asserts the INTR signal as a signal that originated in an
    /// externally-connected interrupt controller.
    /// Requires edge-triggering.
    External = 0b111,
}

impl IrqMode {
    pub(super) fn as_u32(self) -> u32 {
        (self as u32) << 8
    }
}

impl TryFrom<u32> for IrqMode {
    type Error = u32;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match (value & IRQ_MODE_MASK) >> 8 {
            0b000 => Ok(IrqMode::Fixed),
            0b001 => Ok(IrqMode::LowestPriority),
            0b010 => Ok(IrqMode::SystemManagement),
            0b100 => Ok(IrqMode::NonMaskable),
            0b101 => Ok(IrqMode::Init),
            0b111 => Ok(IrqMode::External),
            other => Err(other),
        }
    }
}

bitflags! {
    /// Redirection table entry flags.
    pub struct IrqFlags: u32 {
        /// Logical destination mode (vs physical)
        const LOGICAL_DEST = 1 << 11;
        /// Delivery status: send pending (vs idle, readonly)
        const SEND_PENDING = 1 << 12;
        /// Low-polarity interrupt signal (vs high-polarity)
        const LOW_ACTIVE = 1 << 13;
        /// Remote IRR (readonly)
        const REMOTE_IRR = 1 << 14;
        /// Level-triggered interrupt (vs edge-triggered)
        const LEVEL_TRIGGERED = 1 << 15;
        /// Masked interrupt (vs unmasked)
        const MASKED = 1 << 16;
    }
}

/// Redirection table entry.
#[derive(Default)]
pub struct RedirectionTableEntry {
    low: u32,
    high: u32,
}

impl RedirectionTableEntry {
    pub(crate) fn from_raw(low: u32, high: u32) -> Self {
        Self { low, high }
    }

    pub(crate) fn into_raw(self) -> (u32, u32) {
        (self.low, self.high)
    }

    /// Returns the interrupt vector.
    pub fn vector(&self) -> u8 {
        (self.low & 0xff) as u8
    }

    /// Sets the interrupt vector to `vector`.
    pub fn set_vector(&mut self, vector: u8) {
        self.low = self.low & !0xff | vector as u32
    }

    /// Returns the interrupt delivery mode.
    pub fn mode(&self) -> IrqMode {
        self.low.try_into().unwrap()
    }

    /// Sets the interrupt delivery mode to `mode`.
    pub fn set_mode(&mut self, mode: IrqMode) {
        self.low = self.low & !IRQ_MODE_MASK | mode.as_u32()
    }

    /// Returns the redirection table entry flags.
    pub fn flags(&self) -> IrqFlags {
        IrqFlags::from_bits_truncate(self.low)
    }

    /// Sets the redirection table entry flags to `flags`.
    pub fn set_flags(&mut self, flags: IrqFlags) {
        let ro_flags = IrqFlags::SEND_PENDING | IrqFlags::REMOTE_IRR;
        self.low = self.low & !(IrqFlags::all() - ro_flags).bits()
            | (flags - ro_flags).bits()
    }

    /// Returns the destination field.
    pub fn dest(&self) -> u8 {
        (self.high >> 24) as u8
    }

    /// Sets the destination field to `dest`.
    pub fn set_dest(&mut self, dest: u8) {
        self.high = (dest as u32) << 24;
    }
}

// Gets the lower segment selector for `irq`
pub fn lo(irq: u8) -> u32 {
    TABLE_BASE + (2 * u32::from(irq))
}

// Gets the upper segment selector for `irq`
pub fn hi(irq: u8) -> u32 {
    lo(irq) + 1
}

impl fmt::Debug for RedirectionTableEntry {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("RedirectionTableEntry")
            .field("vector", &self.vector())
            .field("mode", &self.mode())
            .field("flags", &self.flags())
            .field("dest", &self.dest())
            .finish()
    }
}