513 lines
16 KiB
C
513 lines
16 KiB
C
#if defined (__x86_64__) || defined (__i386__)
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdbool.h>
|
|
#include <sys/lapic.h>
|
|
#include <sys/cpu.h>
|
|
#include <lib/misc.h>
|
|
#include <lib/acpi.h>
|
|
#include <mm/pmm.h>
|
|
|
|
#define LAPIC_REG_LVT_CMCI 0x2f0
|
|
#define LAPIC_REG_LVT_TIMER 0x320
|
|
#define LAPIC_REG_LVT_THERMAL 0x330
|
|
#define LAPIC_REG_LVT_PMC 0x340
|
|
#define LAPIC_REG_LVT_LINT0 0x350
|
|
#define LAPIC_REG_LVT_LINT1 0x360
|
|
#define LAPIC_REG_LVT_ERROR 0x370
|
|
#define LAPIC_REG_SVR 0x0f0
|
|
#define LAPIC_REG_TPR 0x080
|
|
#define LAPIC_REG_VERSION 0x030
|
|
|
|
static uint32_t pending_lint0 = UINT32_MAX; // no override
|
|
static uint32_t pending_lint1 = UINT32_MAX; // no override
|
|
|
|
static uint32_t lapic_madt_nmi_flags_to_lvt(uint16_t flags) {
|
|
uint32_t lvt = 0x10400; // masked + NMI delivery mode
|
|
|
|
// Polarity: bits 1:0 of flags
|
|
uint8_t polarity = flags & 0x3;
|
|
if (polarity == 0x3) {
|
|
lvt |= (1 << 13); // active low
|
|
}
|
|
// 0b00 (conforms) and 0b01 (active high) leave bit 13 clear
|
|
|
|
// Trigger mode: bits 3:2 of flags
|
|
uint8_t trigger = (flags >> 2) & 0x3;
|
|
if (trigger == 0x3) {
|
|
lvt |= (1 << 15); // level triggered
|
|
}
|
|
// 0b00 (conforms) and 0b01 (edge) leave bit 15 clear
|
|
|
|
return lvt;
|
|
}
|
|
|
|
void lapic_prep_lint(struct madt *madt, uint32_t acpi_uid, bool x2apic) {
|
|
pending_lint0 = UINT32_MAX; // no override
|
|
pending_lint1 = UINT32_MAX; // no override
|
|
|
|
// Walk MADT entries looking for NMI entries
|
|
for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin;
|
|
(uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length;
|
|
madt_ptr += *(madt_ptr + 1)) {
|
|
if (*(madt_ptr + 1) == 0
|
|
|| (uintptr_t)madt_ptr + *(madt_ptr + 1) > (uintptr_t)madt + madt->header.length) {
|
|
break;
|
|
}
|
|
switch (*madt_ptr) {
|
|
case 4: {
|
|
// Local APIC NMI
|
|
if (*(madt_ptr + 1) < sizeof(struct madt_lapic_nmi)) {
|
|
continue;
|
|
}
|
|
|
|
struct madt_lapic_nmi *nmi = (void *)madt_ptr;
|
|
|
|
// Match all processors (0xff) or specific UID.
|
|
if (nmi->acpi_processor_uid != 0xff
|
|
&& (acpi_uid > 0xfe || nmi->acpi_processor_uid != acpi_uid)) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t lvt = lapic_madt_nmi_flags_to_lvt(nmi->flags);
|
|
if (nmi->lint == 0) {
|
|
pending_lint0 = lvt;
|
|
} else if (nmi->lint == 1) {
|
|
pending_lint1 = lvt;
|
|
}
|
|
continue;
|
|
}
|
|
case 0x0a: {
|
|
// Local x2APIC NMI
|
|
if (!x2apic) {
|
|
continue;
|
|
}
|
|
if (*(madt_ptr + 1) < sizeof(struct madt_x2apic_nmi)) {
|
|
continue;
|
|
}
|
|
|
|
struct madt_x2apic_nmi *nmi = (void *)madt_ptr;
|
|
|
|
// Match all processors (0xffffffff) or specific UID
|
|
if (nmi->acpi_processor_uid != 0xffffffff && nmi->acpi_processor_uid != acpi_uid) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t lvt = lapic_madt_nmi_flags_to_lvt(nmi->flags);
|
|
if (nmi->lint == 0) {
|
|
pending_lint0 = lvt;
|
|
} else if (nmi->lint == 1) {
|
|
pending_lint1 = lvt;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool lvt_should_mask(uint32_t lvt) {
|
|
switch ((lvt >> 8) & 7) {
|
|
case 0b000: // Fixed
|
|
case 0b001: // Lowest Priority
|
|
case 0b100: // NMI
|
|
case 0b111: // ExtINT
|
|
return true;
|
|
default: // SMI, INIT, Reserved
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void lapic_configure_handoff_state(void) {
|
|
bool is_x2 = !!(rdmsr(0x1b) & (1 << 10));
|
|
|
|
uint32_t max_lvt;
|
|
if (is_x2) {
|
|
max_lvt = (x2apic_read(LAPIC_REG_VERSION) >> 16) & 0xff;
|
|
} else {
|
|
max_lvt = (lapic_read(LAPIC_REG_VERSION) >> 16) & 0xff;
|
|
}
|
|
|
|
uint32_t lvt;
|
|
|
|
if (is_x2) {
|
|
x2apic_write(LAPIC_REG_SVR, 0x1ff);
|
|
x2apic_write(LAPIC_REG_TPR, 0);
|
|
if (max_lvt >= 6) {
|
|
lvt = x2apic_read(LAPIC_REG_LVT_CMCI);
|
|
if (lvt_should_mask(lvt)) {
|
|
x2apic_write(LAPIC_REG_LVT_CMCI, lvt | (1 << 16));
|
|
}
|
|
}
|
|
lvt = x2apic_read(LAPIC_REG_LVT_TIMER);
|
|
if (lvt_should_mask(lvt)) {
|
|
x2apic_write(LAPIC_REG_LVT_TIMER, lvt | (1 << 16));
|
|
}
|
|
if (max_lvt >= 5) {
|
|
lvt = x2apic_read(LAPIC_REG_LVT_THERMAL);
|
|
if (lvt_should_mask(lvt)) {
|
|
x2apic_write(LAPIC_REG_LVT_THERMAL, lvt | (1 << 16));
|
|
}
|
|
}
|
|
if (max_lvt >= 4) {
|
|
lvt = x2apic_read(LAPIC_REG_LVT_PMC);
|
|
if (lvt_should_mask(lvt)) {
|
|
x2apic_write(LAPIC_REG_LVT_PMC, lvt | (1 << 16));
|
|
}
|
|
}
|
|
lvt = x2apic_read(LAPIC_REG_LVT_ERROR);
|
|
if (lvt_should_mask(lvt)) {
|
|
x2apic_write(LAPIC_REG_LVT_ERROR, lvt | (1 << 16));
|
|
}
|
|
lvt = x2apic_read(LAPIC_REG_LVT_LINT0);
|
|
if (lvt_should_mask(lvt)) {
|
|
x2apic_write(LAPIC_REG_LVT_LINT0, pending_lint0 != UINT32_MAX ? pending_lint0 : lvt | (1 << 16));
|
|
}
|
|
lvt = x2apic_read(LAPIC_REG_LVT_LINT1);
|
|
if (lvt_should_mask(lvt)) {
|
|
x2apic_write(LAPIC_REG_LVT_LINT1, pending_lint1 != UINT32_MAX ? pending_lint1 : lvt | (1 << 16));
|
|
}
|
|
} else {
|
|
lapic_write(LAPIC_REG_SVR, 0x1ff);
|
|
lapic_write(LAPIC_REG_TPR, 0);
|
|
if (max_lvt >= 6) {
|
|
lvt = lapic_read(LAPIC_REG_LVT_CMCI);
|
|
if (lvt_should_mask(lvt)) {
|
|
lapic_write(LAPIC_REG_LVT_CMCI, lvt | (1 << 16));
|
|
}
|
|
}
|
|
lvt = lapic_read(LAPIC_REG_LVT_TIMER);
|
|
if (lvt_should_mask(lvt)) {
|
|
lapic_write(LAPIC_REG_LVT_TIMER, lvt | (1 << 16));
|
|
}
|
|
if (max_lvt >= 5) {
|
|
lvt = lapic_read(LAPIC_REG_LVT_THERMAL);
|
|
if (lvt_should_mask(lvt)) {
|
|
lapic_write(LAPIC_REG_LVT_THERMAL, lvt | (1 << 16));
|
|
}
|
|
}
|
|
if (max_lvt >= 4) {
|
|
lvt = lapic_read(LAPIC_REG_LVT_PMC);
|
|
if (lvt_should_mask(lvt)) {
|
|
lapic_write(LAPIC_REG_LVT_PMC, lvt | (1 << 16));
|
|
}
|
|
}
|
|
lvt = lapic_read(LAPIC_REG_LVT_ERROR);
|
|
if (lvt_should_mask(lvt)) {
|
|
lapic_write(LAPIC_REG_LVT_ERROR, lvt | (1 << 16));
|
|
}
|
|
lvt = lapic_read(LAPIC_REG_LVT_LINT0);
|
|
if (lvt_should_mask(lvt)) {
|
|
lapic_write(LAPIC_REG_LVT_LINT0, pending_lint0 != UINT32_MAX ? pending_lint0 : lvt | (1 << 16));
|
|
}
|
|
lvt = lapic_read(LAPIC_REG_LVT_LINT1);
|
|
if (lvt_should_mask(lvt)) {
|
|
lapic_write(LAPIC_REG_LVT_LINT1, pending_lint1 != UINT32_MAX ? pending_lint1 : lvt | (1 << 16));
|
|
}
|
|
}
|
|
}
|
|
|
|
void lapic_configure_bsp(void) {
|
|
struct madt *madt = acpi_get_table("APIC", 0);
|
|
if (madt == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Detect x2APIC from MSR
|
|
bool is_x2 = !!(rdmsr(0x1b) & (1 << 10));
|
|
|
|
// Find the BSP entry by matching LAPIC ID
|
|
uint32_t bsp_lapic_id;
|
|
if (is_x2) {
|
|
bsp_lapic_id = x2apic_read(LAPIC_REG_ID);
|
|
} else {
|
|
bsp_lapic_id = lapic_read(LAPIC_REG_ID) >> 24;
|
|
}
|
|
|
|
uint32_t bsp_acpi_uid = 0;
|
|
|
|
for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin;
|
|
(uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length;
|
|
madt_ptr += *(madt_ptr + 1)) {
|
|
if (*(madt_ptr + 1) == 0
|
|
|| (uintptr_t)madt_ptr + *(madt_ptr + 1) > (uintptr_t)madt + madt->header.length) {
|
|
break;
|
|
}
|
|
switch (*madt_ptr) {
|
|
case 0: {
|
|
if (*(madt_ptr + 1) < sizeof(struct madt_lapic)) {
|
|
continue;
|
|
}
|
|
struct madt_lapic *lapic = (void *)madt_ptr;
|
|
if (lapic->lapic_id == bsp_lapic_id) {
|
|
bsp_acpi_uid = lapic->acpi_processor_uid;
|
|
goto found;
|
|
}
|
|
continue;
|
|
}
|
|
case 9: {
|
|
if (!is_x2) {
|
|
continue;
|
|
}
|
|
if (*(madt_ptr + 1) < sizeof(struct madt_x2apic)) {
|
|
continue;
|
|
}
|
|
struct madt_x2apic *x2lapic = (void *)madt_ptr;
|
|
if (x2lapic->x2apic_id == bsp_lapic_id) {
|
|
bsp_acpi_uid = x2lapic->acpi_processor_uid;
|
|
goto found;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
found:
|
|
lapic_prep_lint(madt, bsp_acpi_uid, is_x2);
|
|
lapic_configure_handoff_state();
|
|
}
|
|
|
|
struct dmar {
|
|
struct sdt header;
|
|
uint8_t host_address_width;
|
|
uint8_t flags;
|
|
uint8_t reserved[10];
|
|
symbol remapping_structures;
|
|
} __attribute__((packed));
|
|
|
|
bool lapic_check(void) {
|
|
uint32_t eax, ebx, ecx, edx;
|
|
if (!cpuid(1, 0, &eax, &ebx, &ecx, &edx))
|
|
return false;
|
|
|
|
if (!(edx & (1 << 9)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t lapic_read(uint32_t reg) {
|
|
size_t lapic_mmio_base = (size_t)(rdmsr(0x1b) & 0xfffff000);
|
|
return mmind(lapic_mmio_base + reg);
|
|
}
|
|
|
|
void lapic_write(uint32_t reg, uint32_t data) {
|
|
size_t lapic_mmio_base = (size_t)(rdmsr(0x1b) & 0xfffff000);
|
|
mmoutd(lapic_mmio_base + reg, data);
|
|
}
|
|
|
|
void lapic_icr_wait(void) {
|
|
for (int i = 0; i < 1000000; i++) {
|
|
if (!(lapic_read(LAPIC_REG_ICR0) & (1 << 12))) {
|
|
return;
|
|
}
|
|
asm volatile ("pause");
|
|
}
|
|
}
|
|
|
|
bool x2apic_check(void) {
|
|
uint32_t eax, ebx, ecx, edx;
|
|
if (!cpuid(1, 0, &eax, &ebx, &ecx, &edx))
|
|
return false;
|
|
|
|
if (!(ecx & (1 << 21)))
|
|
return false;
|
|
|
|
// According to the Intel VT-d spec, we're required
|
|
// to check if bit 0 and 1 of the flags field of the
|
|
// DMAR ACPI table are set, and if they are, we should
|
|
// not report x2APIC capabilities.
|
|
struct dmar *dmar = acpi_get_table("DMAR", 0);
|
|
if (!dmar)
|
|
return true;
|
|
|
|
if ((dmar->flags & (1 << 0)) && (dmar->flags & (1 << 1)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool x2apic_mode = false;
|
|
|
|
bool x2apic_enable(void) {
|
|
if (!x2apic_check())
|
|
return false;
|
|
|
|
uint64_t ia32_apic_base = rdmsr(0x1b);
|
|
ia32_apic_base |= (1 << 10);
|
|
wrmsr(0x1b, ia32_apic_base);
|
|
|
|
x2apic_mode = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool x2apic_disable(void) {
|
|
uint64_t msr = rdmsr(0x1b);
|
|
if (!(msr & (1 << 10)))
|
|
return true;
|
|
|
|
// Check for LEGACY_XAPIC_DISABLED (Intel Meteor Lake+).
|
|
// CPUID.07H.0:EDX[29] enumerates IA32_ARCH_CAPABILITIES MSR (0x10A).
|
|
// IA32_ARCH_CAPABILITIES bit 21 = XAPIC_DISABLE feature supported.
|
|
// IA32_XAPIC_DISABLE_STATUS MSR (0xBD) bit 0 = xAPIC permanently disabled.
|
|
uint32_t eax, ebx, ecx, edx;
|
|
if (cpuid(7, 0, &eax, &ebx, &ecx, &edx) && (edx & (1 << 29))) {
|
|
uint64_t arch_caps = rdmsr(0x10a);
|
|
if (arch_caps & (1 << 21)) {
|
|
if (rdmsr(0xbd) & 1) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save LAPIC state; clearing EN resets all registers except APIC ID.
|
|
uint32_t max_lvt = (x2apic_read(LAPIC_REG_VERSION) >> 16) & 0xff;
|
|
uint32_t saved_svr = x2apic_read(LAPIC_REG_SVR);
|
|
uint32_t saved_tpr = x2apic_read(LAPIC_REG_TPR);
|
|
uint32_t saved_timer = x2apic_read(LAPIC_REG_LVT_TIMER);
|
|
uint32_t saved_lint0 = x2apic_read(LAPIC_REG_LVT_LINT0);
|
|
uint32_t saved_lint1 = x2apic_read(LAPIC_REG_LVT_LINT1);
|
|
uint32_t saved_error = x2apic_read(LAPIC_REG_LVT_ERROR);
|
|
uint32_t saved_pmc = max_lvt >= 4 ? x2apic_read(LAPIC_REG_LVT_PMC) : 0;
|
|
uint32_t saved_thermal = max_lvt >= 5 ? x2apic_read(LAPIC_REG_LVT_THERMAL) : 0;
|
|
uint32_t saved_cmci = max_lvt >= 6 ? x2apic_read(LAPIC_REG_LVT_CMCI) : 0;
|
|
|
|
// Transition x2APIC -> disabled -> xAPIC.
|
|
// Direct x2APIC -> xAPIC is an invalid transition (#GP).
|
|
msr &= ~((1ULL << 11) | (1ULL << 10));
|
|
wrmsr(0x1b, msr);
|
|
|
|
msr |= (1ULL << 11);
|
|
wrmsr(0x1b, msr);
|
|
|
|
x2apic_mode = false;
|
|
|
|
// Restore LAPIC state. SVR is restored last to re-enable the APIC.
|
|
lapic_write(LAPIC_REG_TPR, saved_tpr);
|
|
lapic_write(LAPIC_REG_LVT_TIMER, saved_timer);
|
|
lapic_write(LAPIC_REG_LVT_LINT0, saved_lint0);
|
|
lapic_write(LAPIC_REG_LVT_LINT1, saved_lint1);
|
|
lapic_write(LAPIC_REG_LVT_ERROR, saved_error);
|
|
if (max_lvt >= 4) lapic_write(LAPIC_REG_LVT_PMC, saved_pmc);
|
|
if (max_lvt >= 5) lapic_write(LAPIC_REG_LVT_THERMAL, saved_thermal);
|
|
if (max_lvt >= 6) lapic_write(LAPIC_REG_LVT_CMCI, saved_cmci);
|
|
lapic_write(LAPIC_REG_SVR, saved_svr);
|
|
|
|
return true;
|
|
}
|
|
|
|
void lapic_eoi(void) {
|
|
if (!x2apic_mode) {
|
|
lapic_write(0xb0, 0);
|
|
} else {
|
|
x2apic_write(0xb0, 0);
|
|
}
|
|
}
|
|
|
|
uint64_t x2apic_read(uint32_t reg) {
|
|
return rdmsr(0x800 + (reg >> 4));
|
|
}
|
|
|
|
void x2apic_write(uint32_t reg, uint64_t data) {
|
|
wrmsr(0x800 + (reg >> 4), data);
|
|
}
|
|
|
|
static struct madt_io_apic **io_apics = NULL;
|
|
static size_t max_io_apics = 0;
|
|
|
|
void init_io_apics(void) {
|
|
static bool already_inited = false;
|
|
if (already_inited) {
|
|
return;
|
|
}
|
|
|
|
struct madt *madt = acpi_get_table("APIC", 0);
|
|
|
|
if (madt == NULL) {
|
|
goto out;
|
|
}
|
|
|
|
for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin;
|
|
(uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length;
|
|
madt_ptr += *(madt_ptr + 1)) {
|
|
if (*(madt_ptr + 1) == 0
|
|
|| (uintptr_t)madt_ptr + *(madt_ptr + 1) > (uintptr_t)madt + madt->header.length) {
|
|
break;
|
|
}
|
|
switch (*madt_ptr) {
|
|
case 1: {
|
|
if (*(madt_ptr + 1) < sizeof(struct madt_io_apic))
|
|
continue;
|
|
max_io_apics++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
io_apics = ext_mem_alloc_counted(max_io_apics, sizeof(struct madt_io_apic *));
|
|
max_io_apics = 0;
|
|
|
|
for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin;
|
|
(uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length;
|
|
madt_ptr += *(madt_ptr + 1)) {
|
|
if (*(madt_ptr + 1) == 0
|
|
|| (uintptr_t)madt_ptr + *(madt_ptr + 1) > (uintptr_t)madt + madt->header.length) {
|
|
break;
|
|
}
|
|
switch (*madt_ptr) {
|
|
case 1: {
|
|
if (*(madt_ptr + 1) < sizeof(struct madt_io_apic))
|
|
continue;
|
|
io_apics[max_io_apics++] = (void *)madt_ptr;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
already_inited = true;
|
|
}
|
|
|
|
uint32_t io_apic_read(size_t io_apic, uint32_t reg) {
|
|
uintptr_t base = (uintptr_t)io_apics[io_apic]->address;
|
|
mmoutd(base, reg);
|
|
return mmind(base + 16);
|
|
}
|
|
|
|
void io_apic_write(size_t io_apic, uint32_t reg, uint32_t value) {
|
|
uintptr_t base = (uintptr_t)io_apics[io_apic]->address;
|
|
mmoutd(base, reg);
|
|
mmoutd(base + 16, value);
|
|
}
|
|
|
|
uint32_t io_apic_gsi_count(size_t io_apic) {
|
|
return ((io_apic_read(io_apic, 1) & 0xff0000) >> 16) + 1;
|
|
}
|
|
|
|
void io_apic_mask_all(bool mask_nmi_and_extint) {
|
|
for (size_t i = 0; i < max_io_apics; i++) {
|
|
uint32_t gsi_count = io_apic_gsi_count(i);
|
|
for (uint32_t j = 0; j < gsi_count; j++) {
|
|
uintptr_t ioredtbl = j * 2 + 16;
|
|
switch ((io_apic_read(i, ioredtbl) >> 8) & 0b111) {
|
|
case 0b000: // Fixed
|
|
case 0b001: // Lowest Priority
|
|
break;
|
|
case 0b100: // NMI
|
|
case 0b111: // ExtINT
|
|
if (!mask_nmi_and_extint) {
|
|
continue;
|
|
}
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
io_apic_write(i, ioredtbl, io_apic_read(i, ioredtbl) | (1 << 16));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|