개요

ACPI (Advanced Configuration and Power Interface)는 Power Management를 하기 위한 주변장치의 일종이다. ACPI는 운영체제가 어느정도의 파워가 지금 공급되고 있는지, CPU Fan speed, thermal zone, battery level과 같은 정보를 가져올 수 있게 한다. 또한 ACPI는 SMP, NUMA와 같은 Multiprocessing 정보또한 OS에게 제공한다. 이러한 정보는 MP floating table에도 존재하지만, Modern OS는 대부분 ACPI에서 먼저 SMP정보를 가져온다.

ACPI 접근

ACPI는 BIOS 메모리에 저장되어 있다. EBDA

BIOS메모리는 메모리 영역중 BIOS가 초기화 하는 부분이며, 0x40e, 혹은 0xffffff 아래에 ACPI가 저장되어 있다.

ACPI는 두가지의 부분으로 구성되어 있는데, 첫 번쨰 부분은 OS가 부트과정에서 CPU configuration을 위해서 사용되는 정보가 저장되어 있는 부분과 (CPUs, APIC정보, NUMA 메모리 구조...) ACPI환경을 구성하고 있는 AML코드가 저장된 부분으로 구성된다. ACPI를 이용하기 위해서는 OS는 반드시 RSDP에 접근하여 정보를 가져와야 한다.

RSDP가 발견되고, Verfication이 되면, RSDP (root system description pointer)은 RSDT(root system description table)에 대한 포인터를 가지고 있다. (XSDT _ eXtended System description table 는 최신 하드웨어에 내장됨) RSDT나 XSDT에는 해당하는 정보가 들어 있게 된다. XSDT는 RSDP를 호환할 수 있으며 RSDP의 rev필드에 0이면 rsdp1.0 아니면 XSDT를 사용하는 rsdp2.0이다.

코드

static struct acpi_rsdt* global_rsdt_pointer = NULL;

static inline int check_rsdp(uint8_t *rsdp) {
        uint8_t check = 0;
        size_t size = sizeof(struct acpi_rsdp);

        if(!strncmp(rsdp, "RSD PTR ", 8)) {
                while(size--) check += (int8_t) *rsdp++;
                return check;
        }

        return -1;
}

static struct acpi_rsdp * get_rsdt() {
        // Get Extended BIOS data area pointer address
        uintptr_t ebda_pa = *(uint16_t *)P2V(0x40e);
        uint8_t *ebda = (uint8_t *)P2V(ebda_pa);
        uint8_t *ebda_cursor = ebda;

        // Get virtual address of EBDA
        printf("0x%x\n", ebda);

        while(ebda_cursor <= ebda + 0x400) {
                if(!check_rsdp(ebda_cursor)) {
                        return (struct acpi_rsdp *)ebda_cursor;
                }

                ebda_cursor += 0x10;
        }

        ebda_cursor = (uint8_t *)0x000e0000;
        while(ebda_cursor < P2V(0x000fffff)) {
                if(!check_rsdp(ebda_cursor)) {
                        return (struct acpi_rsdp *)ebda_cursor;
                }

                ebda_cursor += 0x10;
        }

        return NULL;
}

void acpi_init() {
        struct acpi_rsdp* rsdp = get_rsdt();
        global_rsdt_pointer = (struct acpi_rsdt *)P2V(rsdp->rsdt & 0xffffffff);

        // This rsdt area is marked as 0x1 in multiboot2 mma flags.
        // Thus memory_init ignore this area as an allocation point.
        // Also physical memory manager ignore this area for managing.
        // So we have to set VMM to use this area.
        uint64_t addr = (uint64_t)P2V(global_rsdt_pointer);
        vmm_set_page(kernel_P4, addr, rsdp->rsdt, PAGE_PRESENT);
        vmm_activate(kernel_P4);
}

struct acpi_rsdt *acpi_get_rsdt(char signature[4]) {
        struct acpi_rsdt* rsdt = global_rsdt_pointer;

        if(!rsdt) {
                return NULL;
        }

        int entries = (rsdt->header.length - sizeof(rsdt->header)) / sizeof(uint32_t);

        for(int i = 0; i < entries; ++i) {
                struct acpi_rsdt_header *hdr = (struct acpi_rsdt_header *)P2V(rsdt->sdt[i]);

                if(!strncmp(hdr->signature, signature, 4)) {
                        return hdr;
                }
        }

        return NULL;
}