baponkar
baponkar

Reputation: 456

How can I make power shutdown by ACPI?

I want to shutdown a x86 architecture based 64 bit under developing OS by ACPI, So I maintain following steps

  1. Finding ACPI RSDP table
  2. Validate with Sign. & Checksum
  3. Find RSDT/XSDT
  4. Find FADT
  5. Parse FADT to get pm1a_control & pm1b_control
  6. define two variables #define SLP_EN (1 << 13) & #define S5_SLEEP_TYPA (5 << 10)
  7. send poweroff command by outw(pm1a_control, S5_SLEEP_TYPA | SLP_EN);

Where

I am getting rsdp address from limine bootloader

void *find_acpi_table() {

    if (!rsdp_request.response || !rsdp_request.response->address) {
        printf("ACPI is not available\n");
        return NULL; // ACPI is not available
    }

    rsdp_t *rsdp = (rsdp_t *) rsdp_request.response->address;
    
    if (rsdp->revision >= 2) {
        rsdp_ext_t *rsdp_ext = (rsdp_ext_t *)rsdp;
        return (void *)(uintptr_t)rsdp_ext; // Use XSDT for 64-bit systems
    }

    return (void *)(uintptr_t)rsdp; // Use RSDT for ACPI 1.0
}
void validate_acpi_table(void *table_addr){
    rsdp_t *rsdp = (rsdp_t *) table_addr;
    if(rsdp){
        uint64_t acpi_version = (rsdp->revision >= 2) ? 2 : 1;
        if(!memcmp(rsdp->signature, "RSD PTR ", 8)){
            uint8_t sum = 0;
            uint8_t *ptr = (uint8_t *) rsdp;
            for (int i = 0; i < 20; i++) {
                sum += ptr[i];
            }
            if((sum % 256) == 0){
                printf("ACPI %d.0 is signature and checksum validated\n", acpi_version);
            }else{
                printf("ACPI %d.0 is not checksum  validated\n", acpi_version);
            }
        }else{
            printf("ACPI %d.0 is not signature  validated\n", acpi_version);
        }

    }else{
        printf("ACPI Table not found\n");
    }
}
void find_fadt(void *table_addr) {
    rsdp_t *rsdp = (rsdp_t *) table_addr;
    rsdt_t *rsdt = (rsdt_t *) rsdp->rsdt_address;

    rsdp_ext_t *rsdp_ext = (rsdp->revision >= 2) ? (rsdp_ext_t *) table_addr : 0;;
    xsdt_t *xsdt = (rsdp->revision >= 2) ? (xsdt_t *)rsdp_ext->xsdt_address : 0;

    acpi_header_t header = (rsdp->revision >= 2) ? xsdt->header : rsdt->header;

    int entry_size = (rsdp->revision >= 2) ? sizeof(uint64_t) : sizeof(uint32_t);
    int entry_count = (header.length - sizeof(acpi_header_t)) / entry_size;

    uint32_t *entries_32 = (uint32_t *) rsdt->entries;
    uint64_t *entries_64 = (uint64_t *) xsdt->entries;
    void *entries = (rsdp->revision >= 2) ? (void *)entries_64 : (void *)entries_32;

    for (int i = 0; i < entry_count; i++) {
        acpi_header_t *entry = (acpi_header_t *)(uintptr_t)((rsdp->revision >= 2) ? ((uint64_t *)entries)[i] : ((uint32_t *)entries)[i]);
         if (!memcmp(entry->signature, "FACP", 4)) {
            fadt = (fadt_t *) entry;
        }
    }
}

And finally acpi_powroff


void acpi_poweroff() {
    if (!fadt) {
        printf("FADT not found, ACPI shutdown unavailable!\n");
        return;
    }

    // Enable ACPI first (if needed)
    if(!is_acpi_enabled()){
        acpi_enable();
    }

    uint32_t pm1a_control = 0;

    pm1a_control = (fadt->header.revision >= 2 && fadt->X_PM1aControlBlock.Address) ? (uint32_t)fadt->X_PM1aControlBlock.Address : fadt->PM1aControlBlock;

    uint32_t pm1b_control = fadt->PM1bControlBlock;

    if (!pm1a_control) {
        printf("PM1a Control Block not found!\n");
        return;
    }

    printf("Sending ACPI shutdown command: outw(%x, %x)\n", pm1a_control, S5_SLEEP_TYPA | SLP_EN);

    // Shutdown by setting SLP_EN (bit 13) with S5 sleep type (bits 10-12)
    outw(pm1a_control, S5_SLEEP_TYPA | SLP_EN);
    if(pm1b_control) outw(pm1b_control, S5_SLEEP_TYPA | SLP_EN);

    // If ACPI fails, use fallback methods
    printf("ACPI Shutdown failed, halting system!\n");
    while (1) {
        __asm__ volatile ("hlt");
    }
}

Unfortunately acpi_poweroff is not making power shut even I have tested in real machine. THe debug in Qemu is showing pm1a_control = 0x604 and S5_SLEEP_TYPA | SLP_EN = 3400.

This tutorials is not understandable for me. It will be helpful if you please share some solution.

The full code is present in GitHub .

Upvotes: 1

Views: 59

Answers (1)

baponkar
baponkar

Reputation: 456

The code is perfect for real machine and virtualbox although acpi_poweroff() is not working for QEmu(I don't know the reason!). My outw function was wrong so it was not working. Now fixed outw function is

void outw(uint16_t port, uint16_t value) {
    asm volatile ("outw %0, %1" : : "a"(value), "Nd"(port));
}

Upvotes: 0

Related Questions