aboutsummaryrefslogtreecommitdiff
path: root/kernel/main/acpi.c
blob: cb0f221efe8a6ce28ec0e5bb52ee7251d41e6b3c (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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#include "main/acpi.h"

#include "boot/config.h"
#include "mm/page.h"
#include "types.h"
#include "util/debug.h"
#include "util/string.h"

#define XSDT_SIGNATURE (*(uint32_t *)"XSDT")
#define RSDT_SIGNATURE (*(uint32_t *)"RSDT")
#define FACP_SIGNATURE (*(uint32_t *)"FACP")
#define DSDT_SIGNATURE (*(uint32_t *)"DSDT")

#define RSDP_ALIGN 16

#define EBDA_MIN_PADDR 0x80000
#define EBDA_MAX_PADDR 0xa0000
#define EBDA_PTR_LOC_PADDR 0x040e

#define EBDA_MIN (PHYS_OFFSET + EBDA_MIN_PADDR)
#define EBDA_MAX (PHYS_OFFSET + EBDA_MAX_PADDR)
#define EBDA_PTR_LOC (PHYS_OFFSET + EBDA_PTR_LOC_PADDR)

static const uint8_t rsdp_sig[8] = {'R', 'S', 'D', ' ', 'P', 'T', 'R', ' '};

typedef struct rsdp
{
    uint8_t rp_sign[8];
    uint8_t rp_checksum;
    uint8_t rp_oemid[6];
    uint8_t rp_rev;
    uint32_t rp_addr;
} packed rsdp_t;

typedef struct rsdp_20
{
    rsdp_t rsdp;
    uint32_t length;
    uint64_t xsdt_addr;
    uint8_t ext_checksum;
    uint8_t reserved[3];
} packed rsdp_20_t;

typedef struct rsd_table
{
    acpi_header_t rt_header;
    uint64_t rt_other[];
} packed rsd_table_t;

static uint8_t __acpi_checksum(const uint8_t *buf, long size)
{
    uint8_t sum = 0;
    for (long i = 0; i < size; i++)
        sum += buf[i];
    return sum;
}

static rsdp_20_t *__rsdp_search_range(uintptr_t start, uintptr_t end)
{
    uintptr_t rsdp_candidate = start;
    while (rsdp_candidate <= end - sizeof(struct rsdp))
    {
        if (memcmp((void *)rsdp_candidate, rsdp_sig, sizeof(rsdp_sig)) == 0 &&
            __acpi_checksum((uint8_t *)rsdp_candidate, sizeof(rsdp_20_t)) ==
                0)
        {
            return (rsdp_20_t *)rsdp_candidate;
        }
        rsdp_candidate += RSDP_ALIGN;
    }
    return NULL;
}

static void *__rsdp_search()
{
    // detect the location of the EBDA from the BIOS data section
    uintptr_t ebda =
        ((uintptr_t) * (uint16_t *)EBDA_PTR_LOC << 4) + PHYS_OFFSET;
    rsdp_20_t *rsdp = 0;
    if (ebda >= EBDA_MIN && ebda <= EBDA_MAX && ebda % RSDP_ALIGN == 0)
    {
        // check only if it's valid
        rsdp = __rsdp_search_range(ebda, EBDA_MAX);
    }
    if (!rsdp)
    {
        // darmanio: unsure where these magic constants came from...
        rsdp =
            __rsdp_search_range(PHYS_OFFSET + 0xe0000, PHYS_OFFSET + 0x100000);
    }
    return rsdp;
}

static rsdp_20_t *rsd_ptr = NULL;
static rsd_table_t *rsd_table = NULL;

static rsd_table_t *_acpi_load_table(uintptr_t paddr)
{
    page_mark_reserved(PAGE_ALIGN_DOWN(paddr));
    return (rsd_table_t *)(PHYS_OFFSET + paddr);
}

void acpi_init()
{
    if (rsd_ptr == NULL)
    {
        rsd_ptr = __rsdp_search();
        KASSERT(rsd_ptr && "Could not find the ACPI Root Descriptor Table.");

        rsd_table = _acpi_load_table(rsd_ptr->xsdt_addr);
        KASSERT(XSDT_SIGNATURE == rsd_table->rt_header.ah_sign);
        if (__acpi_checksum((void *)rsd_table, rsd_table->rt_header.ah_size))
        {
            panic("Weenix only supports ACPI 2.0 or higher");
        }

        dbgq(DBG_CORE, "--- ACPI INIT ---\n");
        dbgq(DBG_CORE, "rsdp addr:  %p\n", rsd_ptr);
        dbgq(DBG_CORE, "rsdt addr:  %p\n", rsd_table);
        dbgq(DBG_CORE, "rev:        %i\n", (int)rsd_ptr->rsdp.rp_rev);

        rsd_ptr->rsdp.rp_oemid[5] = 0;
        dbgq(DBG_CORE, "oem:        %s\n", (char *)rsd_ptr->rsdp.rp_oemid);

        // search for all tables listed in the RSDT and checksum them
        dbgq(DBG_CORE, "ents:\t");
        size_t headers =
            (rsd_table->rt_header.ah_size - sizeof(rsd_table->rt_header)) /
            sizeof(rsd_table->rt_other[0]);

        for (size_t i = 0; i < headers; ++i)
        {
            acpi_header_t *header =
                &_acpi_load_table(rsd_table->rt_other[i])->rt_header;
            rsd_table->rt_other[i] = (uintptr_t)header;

            dbgq(DBG_CORE, "%.4s ", (char *)&header->ah_sign);
            KASSERT(0 == __acpi_checksum((void *)header, header->ah_size));
        }
        dbgq(DBG_CORE, "\n");
    }
}

void *acpi_table(uint32_t signature, int index)
{
    KASSERT(index >= 0);

    size_t headers =
        (rsd_table->rt_header.ah_size - sizeof(rsd_table->rt_header)) /
        sizeof(rsd_table->rt_other[0]);

    for (size_t i = 0; i < headers; ++i)
    {
        acpi_header_t *header = (acpi_header_t *)rsd_table->rt_other[i];
        if (header->ah_sign == signature && 0 == index--)
        {
            return header;
        }
    }
    return NULL;
}