summaryrefslogtreecommitdiff
path: root/kernel/src/arch/i686/acpi.c
blob: 78075581e31a316882aebf73aa851052a033f4e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <panic.h>
#include <string.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <arch/i686/acpi.h>
#include <arch/i686/asm.h>

extern uintptr_t KERNEL_MAPPING;

static struct RootSystemDescriptionTable *rsdt;
static struct FixedACPIDescriptionTable *fadt;

static uint16_t SLP_TYPa;
static uint16_t SLP_TYPb;
static uint16_t SLP_EN;
static uint16_t SCI_EN;

static bool checksum(uint8_t *data, size_t len) {
    unsigned char sum = 0;
    for (size_t i = 0; i < len; i++)
        sum += data[i];
    return sum == 0;
}

static void *find_fadt(void) {
    int entries = (rsdt->header.length - sizeof(rsdt->header)) / 4;
 
    for (int i = 0; i < entries; i++) {
        uintptr_t sdt_ptr = rsdt->sdt_table[i];
        struct SystemDescriptionTableHeader *h = (void*) sdt_ptr;
        if (!strncmp(h->signature, "FACP", 4))
            return (void *) h;
    }
 
    return NULL;
}

static void read_s5_addr(void) {
    uintptr_t ptr = fadt->dsdt;
    char *s5_addr = (void*) (ptr + 36);
    
    int dsdt_len = *((int*) (ptr+1)) - 36;
    while (0 < dsdt_len--) {
       if ( memcmp(s5_addr, "_S5_", 4) == 0)
          break;
       s5_addr++;
    }

    if (dsdt_len > 0) {
        // check for valid AML structure
        if ( ( *(s5_addr-1) == 0x08 || ( *(s5_addr-2) == 0x08 && *(s5_addr-1) == '\\') ) && *(s5_addr+4) == 0x12 ) {
            s5_addr += 5;
            s5_addr += ((*s5_addr &0xC0)>>6) +2;   // calculate PkgLength size

            if (*s5_addr == 0x0A)
               s5_addr++;   // skip byteprefix
            SLP_TYPa = *(s5_addr)<<10;
            s5_addr++;

            if (*s5_addr == 0x0A)
               s5_addr++;   // skip byteprefix
            SLP_TYPb = *(s5_addr)<<10;

            SLP_EN = 1<<13;
            SCI_EN = 1;

        } else {
            panic("\\_S5 parse error.");
        }
    } else {
        panic("\\_S5 not present.");
    }
}

void acpi_init(void *ptr) {

    struct RootSystemDescriptionPointer *rsdp = ptr;
    if (!checksum((uint8_t*) rsdp, sizeof(struct RootSystemDescriptionPointer))) {
        panic("RSDP checksum failed to validate");
    }

    uintptr_t rsdt_ptr = rsdp->rsdt_address;
    rsdt = (void *) rsdt_ptr;
    if (!checksum((uint8_t*) &rsdt->header, rsdt->header.length)) {
        panic("RSDT checksum failed to validate");
    }

    fadt = find_fadt();
    if (fadt == NULL) {
        panic("Could not find FADT");
    }

    if (!checksum((uint8_t*) &fadt->header, fadt->header.length)) {
        panic("FADT checksum failed to validate");
    }

    read_s5_addr();

    outb(fadt->smi_command_port,fadt->acpi_enable);
}

void acpi_poweroff(void) {
    outw((unsigned int) fadt->pm1_a_control_block, SLP_TYPb | SLP_EN);
    panic("failed to shutdown");
}