1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include <mos/mm/paging/paging.h>
4#include <mos/mm/physical/pmm.h>
5#include <mos/platform/platform.h>
6#include <mos/syslog/printk.h>
7#include <mos/x86/acpi/madt.h>
8#include <mos/x86/interrupt/apic.h>
9#include <mos/x86/x86_interrupt.h>
10#include <mos/x86/x86_platform.h>
11
12// +-------+ +-------+ +-------+
13// | | | | | |
14// | CPU 0 | | CPU 1 | | CPU 2 | ...
15// | | | | | |
16// +-------+ +-------+ +-------+
17// | LAPIC | | LAPIC | | LAPIC | ...
18// +---+---+ +---+---+ +---+---+
19// | | |
20// | +------+ |
21// +---+ | +-----------------+
22// | | |
23// | | |
24// +--v--v--v---+
25// | I/O APIC | <- Interrupts are sent to this
26// +------------+
27
28#define IOAPIC_REG_ID 0x00
29#define IOAPIC_REG_VERSION 0x01
30#define IOAPIC_REG_ARB_ID 0x02
31#define IOAPIC_REG_REDIR_TABLE(n) (0x10 + 2 * (n))
32
33typedef struct
34{
35 u8 interrupt_vec : 8;
36 u8 delivery_mode : 3; // 0 = Normal, 1 = Low Priority, 2 = SMI, 4 = NMI, 5 = INIT, 7 = External
37 bool destination_mode : 1; // 0 = Physical, 1 = Logical
38 bool pending : 1; // 1 = if this interrupt is going to be sent, but the APIC is busy
39 ioapic_polarity_t polarity : 1; // 0 = Active High, 1 = Active Low
40 bool something : 1; // 0 = a local APIC has sent an EOI, 1 = a local APIC has received the interrupt
41 ioapic_trigger_mode_t trigger_mode : 1; // 0 = Edge, 1 = Level
42 bool mask : 1; // 0 = Enabled, 1 = Disabled
43 u64 reserved : 39;
44 struct
45 {
46 u8 target_apic_id : 4;
47 u8 something_else : 4;
48 } __packed destination;
49} __packed ioapic_redirection_entry_t;
50
51MOS_STATIC_ASSERT(sizeof(ioapic_redirection_entry_t) == sizeof(u64), "ioapic_register_1 is not 64 bits");
52
53static u32 volatile *ioapic = NULL;
54
55should_inline u32 ioapic_read(u32 reg)
56{
57 ioapic[0] = reg & 0xff;
58 return ioapic[4];
59}
60
61should_inline void ioapic_write(u32 reg, u32 value)
62{
63 ioapic[0] = reg & 0xff;
64 ioapic[4] = value;
65}
66
67should_inline void ioapic_write_redirection_entry(u32 irq, ioapic_redirection_entry_t entry)
68{
69 union
70 {
71 u64 value;
72 ioapic_redirection_entry_t entry;
73 } __packed u = { .entry = entry };
74
75 ioapic_write(IOAPIC_REG_REDIR_TABLE(irq), value: u.value & 0xffffffff);
76 ioapic_write(IOAPIC_REG_REDIR_TABLE(irq) + 1, value: u.value >> 32);
77}
78
79should_inline ioapic_redirection_entry_t ioapic_read_redirection_entry(u32 irq)
80{
81 union
82 {
83 u64 value;
84 ioapic_redirection_entry_t entry;
85 } __packed u = { 0 };
86
87 u.value = ioapic_read(IOAPIC_REG_REDIR_TABLE(irq));
88 u.value |= (u64) ioapic_read(IOAPIC_REG_REDIR_TABLE(irq) + 1) << 32;
89
90 return u.entry;
91}
92
93void ioapic_init(void)
94{
95 MOS_ASSERT_X(x86_ioapic_phyaddr != 0, "ioapic: no ioapic found in madt");
96 if (!pmm_find_reserved_region(needle: x86_ioapic_phyaddr))
97 {
98 pr_info("reserving ioapic address");
99 pmm_reserve_address(x86_ioapic_phyaddr);
100 }
101
102 ioapic = (u32 volatile *) pa_va(x86_ioapic_phyaddr);
103 const u32 ioapic_id = ioapic_read(IOAPIC_REG_ID) >> 24 & 0xf; // get the 24-27 bits
104
105 const union
106 {
107 u32 value;
108 struct
109 {
110 u32 version : 8; // 0 - 7
111 u32 reserved : 8;
112 u32 max_entries : 8; // 16 - 23
113 u32 reserved2 : 8;
114 } __packed;
115 } version = { .value = ioapic_read(IOAPIC_REG_VERSION) };
116
117 const u32 arb_id = ioapic_read(IOAPIC_REG_ARB_ID) >> 24 & 0xf; // get the 24-27 bits
118
119 pr_dinfo2(x86_ioapic, "max IRQs: %d, id: %d, version: %d, arb: %d", version.max_entries + 1, ioapic_id, version.version, arb_id);
120
121 for (int i = 0; i < version.max_entries + 1; i++)
122 ioapic_disable(irq: i);
123}
124
125void ioapic_enable_with_mode(u32 irq, u32 lapic_id, ioapic_trigger_mode_t trigger_mode, ioapic_polarity_t polarity)
126{
127 pr_dinfo2(x86_ioapic, "enable irq %d, cpu lapic-id: %d, trigger_mode %d, polarity %d", irq, lapic_id, trigger_mode, polarity);
128
129 ioapic_redirection_entry_t entry = { 0 };
130 entry.interrupt_vec = irq + ISR_MAX_COUNT; // the vector number received by the CPU
131 entry.polarity = polarity;
132 entry.trigger_mode = trigger_mode;
133 entry.destination.target_apic_id = lapic_id;
134
135 const u32 irq_overridden = x86_ioapic_get_irq_override(irq); // the irq number received by the ioapic "pin"
136 ioapic_write_redirection_entry(irq: irq_overridden, entry);
137}
138
139void ioapic_disable(u32 irq)
140{
141 ioapic_redirection_entry_t entry = { 0 };
142 entry.interrupt_vec = irq + ISR_MAX_COUNT;
143 entry.mask = true;
144 ioapic_write_redirection_entry(irq, entry);
145}
146