How can I make power shutdown by ACPI?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
gamingjam60
Posts: 21
Joined: Sat Aug 24, 2024 10:06 pm
Libera.chat IRC: gamingjam60
Location: India
GitHub: https://github.com/baponkar
Contact:

How can I make power shutdown by ACPI?

Post by gamingjam60 »

I want to shutdown a x86 architecture based 64 bit under developing OS by ACPI, So I maintain following steps
  • Finding ACPI RSDP table
  • Validate with Sign. & Checksum
  • Find RSDT/XSDT
  • Find FADT
  • Parse FADT to get pm1a_control & pm1b_control
  • define two variables #define SLP_EN (1 << 13) & #define S5_SLEEP_TYPA (5 << 10)
  • send poweroff command by outw(pm1a_control, S5_SLEEP_TYPA | SLP_EN);
I am getting rsdp address from limine bootloader by

Code: Select all

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
}
Validate the found acpi table by follwoing

Code: Select all

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");
    }
}
Finding FADT by following code

Code: Select all

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_poweroff code

Code: Select all

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.

viewtopic.php?t=16990 tutorials is not understandable for me. It will be helpful if you please share some solution.

The full code is present in GitHub .
Klakap
Member
Member
Posts: 314
Joined: Sat Mar 10, 2018 10:16 am

Re: How can I make power shutdown by ACPI?

Post by Klakap »

gamingjam60 wrote: Sun Feb 02, 2025 6:10 am #define S5_SLEEP_TYPA (5 << 10)
This is not correct. You should parse correct S5 value for pm1a and pm1b ports from DSDT.

And also this method for shutting down will not always work correctly on real hardware. To ensure correct transition to sleep state, you need as first to call _PTS (prepare to sleep) method written in AML, and then you can safely set S5 shutdown state by registers.
User avatar
gamingjam60
Posts: 21
Joined: Sat Aug 24, 2024 10:06 pm
Libera.chat IRC: gamingjam60
Location: India
GitHub: https://github.com/baponkar
Contact:

Re: How can I make power shutdown by ACPI?

Post by gamingjam60 »

Thankyou for your comment. You may right I will try to use those values by parsing DSDT.
The code is working 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

Code: Select all

void outw(uint16_t port, uint16_t value) {
    asm volatile ("outw %0, %1" : : "a"(value), "Nd"(port));
}
User avatar
GoingNuts
Posts: 5
Joined: Fri Jun 17, 2022 7:36 pm
Libera.chat IRC: GoingNuts

Re: How can I make power shutdown by ACPI?

Post by GoingNuts »

gamingjam60 wrote: Sun Feb 02, 2025 7:56 am Thankyou for your comment. You may right I will try to use those values by parsing DSDT.
The code is working 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

Code: Select all

void outw(uint16_t port, uint16_t value) {
    asm volatile ("outw %0, %1" : : "a"(value), "Nd"(port));
}
When I first wrote ACPI ShutDown in 2022 (Windows 10 and whichever QEMU latest version then) a note was made that it couldn't be run on QEMU for reasons that its RSDP format plus Version=0; but worked under real hardware & VirtualBox. It worked some time later under a different version of QEMU in 2023 and still functional under the current environment (Windows 11, QEMU emulator version 9.1.0 (v9.1.0-12064-gc658eebf44))
Post Reply