#include "../../include/acpi/acpi.h" #include "../../include/io/serial.h" #include "../../include/io/ports.h" #include "../../include/memory/pmm.h" #include static acpi_rsdp2_t *rsdp; static acpi_xsdt_t *xsdt; static acpi_rsdt_t *rsdt; static uint8_t s5_slp_typa = 0; static uint8_t s5_slp_typb = 0; static bool s5_parsed = false; static inline void *phys_to_virt(uintptr_t phys) { return (void *)(phys + pmm_get_hhdm_offset()); } static bool checksum(void *base, size_t len) { uint8_t sum = 0; uint8_t *b = base; for (size_t i = 0; i < len; i++) sum += b[i]; return sum == 0; } static bool parse_s5_from_aml(const uint8_t *aml, size_t aml_len) { if (!aml || aml_len < 40) return false; for (size_t i = 0; i + 20 < aml_len; i++) { if (aml[i] == '_' && aml[i+1] == 'S' && aml[i+2] == '5' && aml[i+3] == '_') { size_t pos = i + 4; for (size_t j = pos; j < pos + 4 && j < aml_len; j++) { if (aml[j] == 0x12) { size_t pkg = j + 1; if (pkg >= aml_len) continue; uint8_t lead = aml[pkg]; size_t pkg_len_bytes = 1; if (lead & 0xC0) { pkg_len_bytes = (lead >> 6) + 1; } pkg += pkg_len_bytes; if (pkg >= aml_len) continue; pkg++; if (pkg >= aml_len) continue; if (aml[pkg] == 0x0A) { pkg++; if (pkg >= aml_len) continue; s5_slp_typa = aml[pkg]; pkg++; } else if (aml[pkg] == 0x00) { s5_slp_typa = 0; pkg++; } else if (aml[pkg] == 0x01) { s5_slp_typa = 1; pkg++; } else { s5_slp_typa = aml[pkg]; pkg++; } if (pkg >= aml_len) continue; if (aml[pkg] == 0x0A) { pkg++; if (pkg >= aml_len) continue; s5_slp_typb = aml[pkg]; } else if (aml[pkg] == 0x00) { s5_slp_typb = 0; } else if (aml[pkg] == 0x01) { s5_slp_typb = 1; } else { s5_slp_typb = aml[pkg]; } serial_printf("ACPI: parsed S5 from AML: SLP_TYPa=%u SLP_TYPb=%u\n", s5_slp_typa, s5_slp_typb); return true; } } } } return false; } static void parse_s5(void) { if (s5_parsed) return; s5_parsed = true; acpi_fadt_t *fadt = (acpi_fadt_t *)acpi_find_table("FACP", 0); if (!fadt) { serial_writestring("ACPI: no FADT, cannot parse S5\n"); return; } uintptr_t dsdt_phys = 0; if (fadt->header.length >= 148 && fadt->x_dsdt) dsdt_phys = (uintptr_t)fadt->x_dsdt; if (!dsdt_phys) dsdt_phys = (uintptr_t)fadt->dsdt; if (!dsdt_phys) { serial_writestring("ACPI: no DSDT address in FADT\n"); return; } acpi_sdt_header_t *dsdt = phys_to_virt(dsdt_phys); if (memcmp(dsdt->signature, "DSDT", 4) != 0) { serial_writestring("ACPI: invalid DSDT signature\n"); return; } serial_printf("ACPI: DSDT at phys=0x%llx length=%u\n", (unsigned long long)dsdt_phys, dsdt->length); const uint8_t *aml = (const uint8_t *)dsdt + sizeof(acpi_sdt_header_t); size_t aml_len = dsdt->length - sizeof(acpi_sdt_header_t); if (parse_s5_from_aml(aml, aml_len)) return; for (uint64_t idx = 0; ; idx++) { acpi_sdt_header_t *ssdt = acpi_find_table("SSDT", idx); if (!ssdt) break; const uint8_t *s_aml = (const uint8_t *)ssdt + sizeof(acpi_sdt_header_t); size_t s_len = ssdt->length - sizeof(acpi_sdt_header_t); if (parse_s5_from_aml(s_aml, s_len)) return; } serial_writestring("ACPI: S5 object not found in DSDT/SSDT, using defaults\n"); s5_slp_typa = 5; s5_slp_typb = 0; } static void acpi_enable_mode(acpi_fadt_t *fadt) { if (!fadt->smi_command_port || !fadt->acpi_enable) return; uint16_t pm1a_val = inw(fadt->pm1a_control_block); if (pm1a_val & 1) { serial_writestring("ACPI: already in ACPI mode\n"); return; } serial_writestring("ACPI: enabling ACPI mode...\n"); outb((uint16_t)fadt->smi_command_port, fadt->acpi_enable); for (int i = 0; i < 3000; i++) { pm1a_val = inw(fadt->pm1a_control_block); if (pm1a_val & 1) { serial_writestring("ACPI: ACPI mode enabled\n"); return; } for (volatile int j = 0; j < 10000; j++) asm volatile("pause"); } serial_writestring("ACPI: WARNING - timeout enabling ACPI mode\n"); } void acpi_init(void) { if (!rsdp_request.response) { serial_writestring("ACPI: no RSDP\n"); return; } rsdp = (acpi_rsdp2_t *)rsdp_request.response->address; if (!checksum(&rsdp->rsdp_v1, sizeof(acpi_rsdp_t))) { serial_writestring("ACPI: bad RSDP checksum\n"); return; } if (rsdp->rsdp_v1.revision >= 2 && rsdp->xsdt_address) { xsdt = phys_to_virt(rsdp->xsdt_address); if (!checksum(xsdt, xsdt->header.length)) xsdt = NULL; } if (!xsdt && rsdp->rsdp_v1.rsdt_address) { rsdt = phys_to_virt(rsdp->rsdp_v1.rsdt_address); if (!checksum(rsdt, rsdt->header.length)) rsdt = NULL; } if (!xsdt && !rsdt) return; parse_s5(); } void *acpi_find_table(const char *sig, uint64_t index) { uint64_t count = 0; if (xsdt) { size_t n = (xsdt->header.length - sizeof(acpi_sdt_header_t)) / 8; for (size_t i = 0; i < n; i++) { acpi_sdt_header_t *h = phys_to_virt(xsdt->sdt_pointers[i]); if (!memcmp(h->signature, sig, 4)) { if (count++ == index) return h; } } } else if (rsdt) { size_t n = (rsdt->header.length - sizeof(acpi_sdt_header_t)) / 4; for (size_t i = 0; i < n; i++) { acpi_sdt_header_t *h = phys_to_virt(rsdt->sdt_pointers[i]); if (!memcmp(h->signature, sig, 4)) { if (count++ == index) return h; } } } return NULL; } void acpi_print_tables(void) { serial_writestring("ACPI tables:\n"); for (uint64_t i = 0;; i++) { acpi_sdt_header_t *h = acpi_find_table("APIC", i); if (!h) break; serial_writestring(" - APIC (MADT)\n"); } for (uint64_t i = 0;; i++) { acpi_sdt_header_t *h = acpi_find_table("HPET", i); if (!h) break; serial_writestring(" - HPET\n"); } for (uint64_t i = 0;; i++) { acpi_sdt_header_t *h = acpi_find_table("MCFG", i); if (!h) break; serial_writestring(" - MCFG (PCIe)\n"); } for (uint64_t i = 0;; i++) { acpi_sdt_header_t *h = acpi_find_table("FACP", i); if (!h) break; serial_writestring(" - FACP (FADT)\n"); } } static void acpi_write_gas(const acpi_gas_t *gas, uint64_t value) { if (gas->address == 0) return; switch (gas->address_space_id) { case 0x00: { volatile uint8_t *mmio = phys_to_virt(gas->address); switch (gas->register_bit_width) { case 8: *(volatile uint8_t *)mmio = (uint8_t)value; break; case 16: *(volatile uint16_t *)mmio = (uint16_t)value; break; case 32: *(volatile uint32_t *)mmio = (uint32_t)value; break; case 64: *(volatile uint64_t *)mmio = value; break; } break; } case 0x01: { uint16_t port = (uint16_t)gas->address; switch (gas->register_bit_width) { case 8: outb(port, (uint8_t)value); break; case 16: outw(port, (uint16_t)value); break; case 32: outl(port, (uint32_t)value); break; } break; } } } void acpi_shutdown(void) { acpi_fadt_t *fadt = (acpi_fadt_t *)acpi_find_table("FACP", 0); if (!fadt) { serial_writestring("ACPI shutdown: FADT not found\n"); goto fallback; } acpi_enable_mode(fadt); parse_s5(); serial_printf("ACPI shutdown: SLP_TYPa=%u SLP_TYPb=%u\n", s5_slp_typa, s5_slp_typb); serial_printf("ACPI shutdown: PM1a_CNT=0x%x PM1b_CNT=0x%x\n", fadt->pm1a_control_block, fadt->pm1b_control_block); asm volatile("cli"); uint16_t pm1a_value = (s5_slp_typa << 10) | (1 << 13); outw(fadt->pm1a_control_block, pm1a_value); if (fadt->pm1b_control_block) { uint16_t pm1b_value = (s5_slp_typb << 10) | (1 << 13); outw(fadt->pm1b_control_block, pm1b_value); } for (volatile int i = 0; i < 100000; i++) asm volatile("pause"); if (fadt->header.length >= 244) { if (fadt->x_pm1a_control_block.address) { serial_writestring("ACPI shutdown: trying extended PM1a...\n"); acpi_write_gas(&fadt->x_pm1a_control_block, pm1a_value); } if (fadt->x_pm1b_control_block.address) { uint16_t pm1b_value = (s5_slp_typb << 10) | (1 << 13); acpi_write_gas(&fadt->x_pm1b_control_block, pm1b_value); } for (volatile int i = 0; i < 100000; i++) asm volatile("pause"); } fallback: serial_writestring("ACPI shutdown: trying QEMU/Bochs port 0x604...\n"); outw(0x604, 0x2000); for (volatile int i = 0; i < 100000; i++) asm volatile("pause"); serial_writestring("ACPI shutdown: trying VirtualBox port 0x4004...\n"); outw(0x4004, 0x3400); for (volatile int i = 0; i < 100000; i++) asm volatile("pause"); serial_writestring("ACPI shutdown: trying legacy APM...\n"); outw(0xB004, 0x2000); serial_writestring("ACPI shutdown: all methods failed, halting CPU\n"); for (;;) asm volatile("cli; hlt"); } void acpi_reboot(void) { acpi_fadt_t *fadt = (acpi_fadt_t *)acpi_find_table("FACP", 0); asm volatile("cli"); if (fadt && fadt->header.length >= 129) { if ((fadt->flags & (1 << 10)) && fadt->reset_reg.address) { serial_printf("ACPI reboot: using FADT reset register " "(space=%u addr=0x%llx val=0x%x)\n", fadt->reset_reg.address_space_id, (unsigned long long)fadt->reset_reg.address, fadt->reset_value); acpi_write_gas(&fadt->reset_reg, fadt->reset_value); for (volatile int i = 0; i < 100000; i++) asm volatile("pause"); } } serial_writestring("ACPI reboot: trying 8042 keyboard controller reset...\n"); for (int i = 0; i < 10000; i++) { if (!(inb(0x64) & 0x02)) break; for (volatile int j = 0; j < 100; j++) asm volatile("pause"); } outb(0x64, 0xFE); for (volatile int i = 0; i < 100000; i++) asm volatile("pause"); serial_writestring("ACPI reboot: triggering triple fault...\n"); struct { uint16_t limit; uint64_t base; } __attribute__((packed)) null_idt = { 0, 0 }; asm volatile( "lidt %0\n" "int $3\n" :: "m"(null_idt) ); for (;;) asm volatile("cli; hlt"); }