:: limine / common / sys / iommu.c 5.8 KB raw

1
#if defined (__x86_64__) || defined (__i386__)
2
3
#include <stdint.h>
4
#include <stddef.h>
5
#include <stdbool.h>
6
#include <sys/iommu.h>
7
#include <sys/cpu.h>
8
#include <lib/acpi.h>
9
#include <lib/libc.h>
10
11
// Intel VT-d registers
12
#define VTD_GCMD_REG  0x18
13
#define VTD_GSTS_REG  0x1C
14
15
// GSTS/GCMD bit positions
16
#define VTD_GSTS_TES  (1u << 31)  // Translation Enable Status
17
#define VTD_GSTS_QIES (1u << 26)  // Queued Invalidation Enable Status
18
#define VTD_GSTS_IRES (1u << 25)  // Interrupt Remapping Enable Status
19
20
// Mask to clear one-shot command bits when reading GSTS for use as GCMD base.
21
// One-shot bits auto-clear after hardware processes them and must not be
22
// carried over from GSTS into GCMD writes:
23
//   Bit 30: SRTP (Set Root Table Pointer)
24
//   Bit 29: SFL  (Set Fault Log)
25
//   Bit 27: WBF  (Write Buffer Flush)
26
//   Bit 24: SIRTP (Set Interrupt Remap Table Pointer)
27
// All other bits (TE, EAFL, QIE, IRE, CFI) are persistent toggles.
28
#define VTD_GCMD_ONESHOT_MASK 0x96FFFFFF
29
30
static void vtd_disable_unit(uintptr_t reg_base) {
31
    uint32_t sts = mmind(reg_base + VTD_GSTS_REG);
32
33
    // Disable DMA translation first (most urgent: prevents stale lookups)
34
    if (sts & VTD_GSTS_TES) {
35
        uint32_t gcmd = (sts & VTD_GCMD_ONESHOT_MASK) & ~VTD_GSTS_TES;
36
        mmoutd(reg_base + VTD_GCMD_REG, gcmd);
37
38
        while ((sts = mmind(reg_base + VTD_GSTS_REG)) & VTD_GSTS_TES) {
39
            asm volatile ("pause");
40
        }
41
    }
42
43
    // Disable interrupt remapping (depends on QIE, so disable before QIE)
44
    if (sts & VTD_GSTS_IRES) {
45
        uint32_t gcmd = (sts & VTD_GCMD_ONESHOT_MASK) & ~VTD_GSTS_IRES;
46
        mmoutd(reg_base + VTD_GCMD_REG, gcmd);
47
48
        while ((sts = mmind(reg_base + VTD_GSTS_REG)) & VTD_GSTS_IRES) {
49
            asm volatile ("pause");
50
        }
51
    }
52
53
    // Disable queued invalidation last (was prerequisite for IRE)
54
    if (sts & VTD_GSTS_QIES) {
55
        uint32_t gcmd = (sts & VTD_GCMD_ONESHOT_MASK) & ~VTD_GSTS_QIES;
56
        mmoutd(reg_base + VTD_GCMD_REG, gcmd);
57
58
        while ((sts = mmind(reg_base + VTD_GSTS_REG)) & VTD_GSTS_QIES) {
59
            asm volatile ("pause");
60
        }
61
    }
62
}
63
64
static void vtd_disable_all(void) {
65
    struct sdt *dmar = acpi_get_table("DMAR", 0);
66
    if (dmar == NULL) {
67
        return;
68
    }
69
70
    // DMAR header is 48 bytes: 36 (SDT) + 1 (width) + 1 (flags) + 10 (reserved)
71
    uint8_t *ptr = (uint8_t *)dmar + 48;
72
    uint8_t *end = (uint8_t *)dmar + dmar->length;
73
74
    while (ptr + 4 <= end) {
75
        uint16_t type   = *(uint16_t *)(ptr + 0);
76
        uint16_t length = *(uint16_t *)(ptr + 2);
77
78
        if (length < 4 || ptr + length > end) {
79
            break;
80
        }
81
82
        // Type 0 = DRHD (DMA Remapping Hardware Unit Definition)
83
        if (type == 0 && length >= 16) {
84
            uint64_t reg_base;
85
            memcpy(&reg_base, ptr + 8, sizeof(reg_base));
86
            vtd_disable_unit((uintptr_t)reg_base);
87
        }
88
89
        ptr += length;
90
    }
91
}
92
93
// AMD IOMMU control register (64-bit at offset 0x18)
94
#define AMDVI_CONTROL_REG     0x18
95
#define AMDVI_CONTROL_EN      (1u << 0)
96
#define AMDVI_CONTROL_EVT_LOG (1u << 2)
97
#define AMDVI_CONTROL_EVT_INT (1u << 3)
98
#define AMDVI_CONTROL_CMDBUF  (1u << 12)
99
#define AMDVI_CONTROL_PPR_LOG (1u << 13)
100
#define AMDVI_CONTROL_PPR_INT (1u << 14)
101
#define AMDVI_CONTROL_PPR     (1u << 15)
102
#define AMDVI_CONTROL_GA_LOG  (1u << 28)
103
#define AMDVI_CONTROL_GA_INT  (1u << 29)
104
105
// AMD IOMMU status register (64-bit at offset 0x2020)
106
#define AMDVI_STATUS_REG         0x2020
107
#define AMDVI_STATUS_EVTLOG_RUN  (1u << 3)
108
#define AMDVI_STATUS_CMDBUF_RUN  (1u << 4)
109
#define AMDVI_STATUS_PPRLOG_RUN  (1u << 7)
110
#define AMDVI_STATUS_GALOG_RUN   (1u << 8)
111
112
static void amdvi_disable_unit(uintptr_t mmio_base) {
113
    // Read low 32 bits of the 64-bit control register
114
    uint32_t ctrl_lo = mmind(mmio_base + AMDVI_CONTROL_REG);
115
116
    if (!(ctrl_lo & AMDVI_CONTROL_EN)) {
117
        return; // IOMMU not enabled
118
    }
119
120
    // Disable command buffer, logs and their interrupts before the master
121
    // enable. Clearing IommuEn while sub-features are still live can leave
122
    // queued descriptors and in-flight DMA in an undefined state.
123
    ctrl_lo &= ~(AMDVI_CONTROL_CMDBUF |
124
                 AMDVI_CONTROL_EVT_LOG | AMDVI_CONTROL_EVT_INT |
125
                 AMDVI_CONTROL_GA_LOG  | AMDVI_CONTROL_GA_INT  |
126
                 AMDVI_CONTROL_PPR_LOG | AMDVI_CONTROL_PPR_INT |
127
                 AMDVI_CONTROL_PPR);
128
    mmoutd(mmio_base + AMDVI_CONTROL_REG, ctrl_lo);
129
130
    // The *Run bits are level-sensitive and only drop to 0 once the engine
131
    // has actually drained, so wait for them before continuing.
132
    const uint32_t run_mask = AMDVI_STATUS_CMDBUF_RUN | AMDVI_STATUS_EVTLOG_RUN |
133
                              AMDVI_STATUS_PPRLOG_RUN | AMDVI_STATUS_GALOG_RUN;
134
    while (mmind(mmio_base + AMDVI_STATUS_REG) & run_mask) {
135
        asm volatile ("pause");
136
    }
137
138
    ctrl_lo &= ~AMDVI_CONTROL_EN;
139
    mmoutd(mmio_base + AMDVI_CONTROL_REG, ctrl_lo);
140
}
141
142
static void amdvi_disable_all(void) {
143
    struct sdt *ivrs = acpi_get_table("IVRS", 0);
144
    if (ivrs == NULL) {
145
        return;
146
    }
147
148
    // IVRS header is 48 bytes: 36 (SDT) + 4 (IVinfo) + 8 (reserved)
149
    uint8_t *ptr = (uint8_t *)ivrs + 48;
150
    uint8_t *end = (uint8_t *)ivrs + ivrs->length;
151
152
    while (ptr + 4 <= end) {
153
        uint8_t  type   = *(uint8_t *)(ptr + 0);
154
        uint16_t length = *(uint16_t *)(ptr + 2);
155
156
        if (length < 4 || ptr + length > end) {
157
            break;
158
        }
159
160
        // IVHD types: 0x10 (basic), 0x11 (extended), 0x40 (extended, newer)
161
        if ((type == 0x10 || type == 0x11 || type == 0x40) && length >= 16) {
162
            uint64_t mmio_base;
163
            memcpy(&mmio_base, ptr + 8, sizeof(mmio_base));
164
            amdvi_disable_unit((uintptr_t)mmio_base);
165
        }
166
167
        ptr += length;
168
    }
169
}
170
171
void iommu_disable_all(void) {
172
    vtd_disable_all();
173
    amdvi_disable_all();
174
}
175
176
#endif
tab: 248 wrap: offon