boredos_mirror/src/drivers/acpi.c

220 lines
6.3 KiB
C

#include <stdint.h>
#include <stddef.h>
#include "acpi_structures.h"
#include "acpi.h"
#include "../sys/idt.h"
#include "../core/limine.h"
#include "../core/panic.h"
#include "../core/platform.h"
#include "../core/kconsole.h"
#define MAX_ISO 16
static struct acpi_rsdp *acpi_rsdp = NULL;
static struct acpi_madt *acpi_madt = NULL;
static fadt_t *acpi_fadt = NULL; // my header file sucks ill make this nicer but its fine
static struct {
uint8_t source;
uint32_t gsi;
uint16_t flags;
} iso_table[MAX_ISO];
static uint8_t iso_count = 0;
/*
Each ACPI table contains an 8-bit checksum field.
When all bytes in the table are added together (including the checksum byte),
the lower 8 bits of the total sum must be zero for it to pass.
*/
static int acpi_checksum(void *ptr, size_t len) {
uint8_t sum = 0;
uint8_t *p = ptr;
for (size_t i = 0; i < len; i++)
sum += p[i];
return sum == 0;
}
static void *acpi_get_rsdp(void){
extern volatile struct limine_rsdp_request acpi_rsdp_request;
if (!acpi_rsdp_request.response)
kernel_panic(NULL, "ACPI RSDP not provided by the Bootloader");
if (!acpi_rsdp_request.response->address)
kernel_panic(NULL, "ACPI Invalid RSDP address provided by Bootloader");
return acpi_rsdp_request.response->address;
}
static struct acpi_sdt *acpi_get_sdt(const char signature[4]) {
if (acpi_rsdp->revision >= 2 && acpi_rsdp->xsdt_address) {
struct acpi_xsdt *acpi_xsdt = (struct acpi_xsdt *)p2v(acpi_rsdp->xsdt_address);
if (acpi_checksum(acpi_xsdt, acpi_xsdt->header.length)) {
size_t entries = (acpi_xsdt->header.length - sizeof(struct acpi_sdt)) / 8;
for (size_t i = 0; i < entries; i++) {
struct acpi_sdt *tbl = (struct acpi_sdt *)p2v(acpi_xsdt->tables[i]);
if (!tbl) continue;
if (!memcmp(tbl->signature, signature, 4))
return tbl;
}
}
}
// RSDT fallback
if (!acpi_rsdp->rsdt_address)
return NULL;
struct acpi_sdt *acpi_rsdt = (struct acpi_sdt *)p2v(acpi_rsdp->rsdt_address);
if (!acpi_checksum(acpi_rsdt, acpi_rsdt->length))
return NULL;
uint32_t *tables = (uint32_t *)((uint8_t *)acpi_rsdt + sizeof(struct acpi_sdt));
size_t entries = (acpi_rsdt->length - sizeof(struct acpi_sdt)) / 4;
for (size_t i = 0; i < entries; i++) {
struct acpi_sdt *tbl = (struct acpi_sdt *)p2v(tables[i]);
if (!tbl) continue;
if (!memcmp(tbl->signature, signature, 4))
return tbl;
}
return NULL;
}
static uint16_t SLP_TYPa = 0;
static uint16_t SLP_TYPb = 0;
void acpi_parse_s5(void) {
if (!acpi_fadt || !acpi_fadt->dsdt) return;
char *dsdt = (char *)p2v((uintptr_t)acpi_fadt->dsdt);
if (memcmp(dsdt, "DSDT", 4) != 0) return;
uint32_t dsdt_len = *(uint32_t*)(dsdt + 4);
char *ptr = dsdt + 36;
char *end = dsdt + dsdt_len;
while (ptr < end) {
if (memcmp(ptr, "_S5_", 4) == 0) {
ptr += 4;
if (*ptr == 0x12) {
ptr += 3;
if (*ptr == 0x0A) ptr++;
SLP_TYPa = (*(uint8_t*)ptr) << 10;
ptr++;
if (*ptr == 0x0A) ptr++;
SLP_TYPb = (*(uint8_t*)ptr) << 10;
return;
}
}
ptr++;
}
}
__attribute__((noreturn))
void acpi_shutdown(void) {
if (SLP_TYPa == 0) acpi_parse_s5();
if (SLP_TYPa == 0) SLP_TYPa = (5 << 10);
outw((uint16_t)acpi_fadt->pm1a_cnt_blk, SLP_TYPa | SLP_EN);
if (acpi_fadt->pm1b_cnt_blk != 0) {
outw((uint16_t)acpi_fadt->pm1b_cnt_blk, SLP_TYPb | SLP_EN);
}
//virtulizers last just incase these have some sort of unintended effect on real hw
outw(0xB004, 0x2000); // bochs
outw(0x4004, 0x3400); // vbox
outw(0x604, 0x2000); // QEMU
outw(0x600, 0x34); // Cloud Hypervisor
asm volatile("cli");
for(;;) asm volatile("hlt");
}
int acpi_init(void){
acpi_rsdp = acpi_get_rsdp();
if (!acpi_rsdp)
kernel_panic(NULL, "ACPI does not provide a required RSDP");
size_t rsdp_len = acpi_rsdp->revision >= 2 ? acpi_rsdp->length : 20;
if (!acpi_checksum(acpi_rsdp, rsdp_len))
kernel_panic(NULL, "bad RSDP checksum");
acpi_fadt = (struct acpi_fadt *)acpi_get_sdt("FACP");
if (!acpi_fadt)
kernel_panic(NULL, "FADT not found");
if (!acpi_checksum(acpi_fadt, acpi_fadt->header.length))
kernel_panic(NULL, "bad FADT checksum");
if (acpi_fadt->smi_cmd && acpi_fadt->acpi_enable) {
outb(acpi_fadt->smi_cmd, acpi_fadt->acpi_enable);
int timeout = 1000000;
while (!(inw(acpi_fadt->pm1a_cnt_blk) & 1) && timeout-- > 0)
asm("pause");
if (timeout <= 0) {
kernel_panic(NULL, "Enable timeout");
}
}
acpi_madt = (struct acpi_madt *)acpi_get_sdt("APIC");
if (!acpi_madt)
kernel_panic(NULL, "MADT not found");
if (!acpi_checksum(acpi_madt, acpi_madt->header.length))
kernel_panic(NULL, "bad MADT checksum");
uint8_t *ptr = acpi_madt->entries;
uint8_t *end = (uint8_t *)acpi_madt + acpi_madt->header.length;
while (ptr < end) {
struct madt_entry_header *h = (void *)ptr;
if (h->length < sizeof(struct madt_entry_header))
break;
if (ptr + h->length > end)
break;
switch (h->type) {
case 2: {
if (iso_count < MAX_ISO) {
struct madt_iso *iso = (void *)ptr;
iso_table[iso_count].source = iso->source;
iso_table[iso_count].gsi = iso->gsi;
iso_table[iso_count].flags = iso->flags;
iso_count++;
}
break;
}
default:
break;
}
ptr += h->length;
}
return 0;
}
uint32_t acpi_irq_to_gsi(uint32_t irq) {
for (size_t i = 0; i < iso_count; i++) {
if (iso_table[i].source == irq)
return iso_table[i].gsi;
}
return irq;
}
uint16_t acpi_irq_flags(uint32_t irq) {
for (size_t i = 0; i < iso_count; i++) {
if (iso_table[i].source == irq)
return iso_table[i].flags;
}
return 0;
}