1// Copyright 2016 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7#include <zircon/compiler.h>
8
9#include <acpica/acpi.h>
10
11#include <assert.h>
12#include <err.h>
13#include <trace.h>
14
15#include <lk/init.h>
16
17#include <arch/x86/apic.h>
18#include <platform/pc/acpi.h>
19#include <zircon/types.h>
20
21#define LOCAL_TRACE 0
22
23#define ACPI_MAX_INIT_TABLES 32
24static ACPI_TABLE_DESC acpi_tables[ACPI_MAX_INIT_TABLES];
25static bool acpi_initialized = false;
26
27static ACPI_STATUS acpi_set_apic_irq_mode(void);
28
29/**
30 * @brief  Initialize early-access ACPI tables
31 *
32 * This function enables *only* the ACPICA Table Manager subsystem.
33 * The rest of the ACPI subsystem will remain uninitialized.
34 */
35void platform_init_acpi_tables(uint level) {
36    DEBUG_ASSERT(!acpi_initialized);
37
38    ACPI_STATUS status;
39    status = AcpiInitializeTables(acpi_tables, ACPI_MAX_INIT_TABLES, FALSE);
40
41    if (status == AE_NOT_FOUND) {
42        TRACEF("WARNING: could not find ACPI tables\n");
43        return;
44    } else if (status == AE_NO_MEMORY) {
45        TRACEF("WARNING: could not initialize ACPI tables\n");
46        return;
47    } else if (status != AE_OK) {
48        TRACEF("WARNING: could not initialize ACPI tables for unknown reason\n");
49        return;
50    }
51
52    acpi_initialized = true;
53    LTRACEF("ACPI tables initialized\n");
54}
55
56/* initialize ACPI tables as soon as we have a working VM */
57LK_INIT_HOOK(acpi_tables, &platform_init_acpi_tables, LK_INIT_LEVEL_VM + 1);
58
59static zx_status_t acpi_get_madt_record_limits(uintptr_t* start, uintptr_t* end) {
60    ACPI_TABLE_HEADER* table = NULL;
61    ACPI_STATUS status = AcpiGetTable((char*)ACPI_SIG_MADT, 1, &table);
62    if (status != AE_OK) {
63        TRACEF("could not find MADT\n");
64        return ZX_ERR_NOT_FOUND;
65    }
66    ACPI_TABLE_MADT* madt = (ACPI_TABLE_MADT*)table;
67    uintptr_t records_start = ((uintptr_t)madt) + sizeof(*madt);
68    uintptr_t records_end = ((uintptr_t)madt) + madt->Header.Length;
69    if (records_start >= records_end) {
70        TRACEF("MADT wraps around address space\n");
71        return ZX_ERR_INTERNAL;
72    }
73    // Shouldn't be too many records
74    if (madt->Header.Length > 4096) {
75        TRACEF("MADT suspiciously long: %u\n", madt->Header.Length);
76        return ZX_ERR_INTERNAL;
77    }
78    *start = records_start;
79    *end = records_end;
80    return ZX_OK;
81}
82
83/* @brief Enumerate all functioning CPUs and their APIC IDs
84 *
85 * If apic_ids is NULL, just returns the number of logical processors
86 * via num_cpus.
87 *
88 * @param apic_ids Array to write found APIC ids into.
89 * @param len Length of apic_ids.
90 * @param num_cpus Output for the number of logical processors detected.
91 *
92 * @return ZX_OK on success. Note that if len < *num_cpus, not all
93 *         logical apic_ids will be returned.
94 */
95zx_status_t platform_enumerate_cpus(
96    uint32_t* apic_ids,
97    uint32_t len,
98    uint32_t* num_cpus) {
99    if (num_cpus == NULL) {
100        return ZX_ERR_INVALID_ARGS;
101    }
102
103    uintptr_t records_start, records_end;
104    zx_status_t status = acpi_get_madt_record_limits(&records_start, &records_end);
105    if (status != ZX_OK) {
106        return status;
107    }
108    uint32_t count = 0;
109    uintptr_t addr;
110    ACPI_SUBTABLE_HEADER* record_hdr;
111    for (addr = records_start; addr < records_end; addr += record_hdr->Length) {
112        record_hdr = (ACPI_SUBTABLE_HEADER*)addr;
113        switch (record_hdr->Type) {
114        case ACPI_MADT_TYPE_LOCAL_APIC: {
115            ACPI_MADT_LOCAL_APIC* lapic = (ACPI_MADT_LOCAL_APIC*)record_hdr;
116            if (!(lapic->LapicFlags & ACPI_MADT_ENABLED)) {
117                LTRACEF("Skipping disabled processor %02x\n", lapic->Id);
118                continue;
119            }
120            if (apic_ids != NULL && count < len) {
121                apic_ids[count] = lapic->Id;
122            }
123            count++;
124            break;
125        }
126        }
127    }
128    if (addr != records_end) {
129        TRACEF("malformed MADT\n");
130        return ZX_ERR_INTERNAL;
131    }
132    *num_cpus = count;
133    return ZX_OK;
134}
135
136/* @brief Enumerate all IO APICs
137 *
138 * If io_apics is NULL, just returns the number of IO APICs
139 * via num_io_apics.
140 *
141 * @param io_apics Array to populate descriptors into.
142 * @param len Length of io_apics.
143 * @param num_io_apics Number of IO apics found
144 *
145 * @return ZX_OK on success. Note that if len < *num_io_apics, not all
146 *         IO APICs will be returned.
147 */
148zx_status_t platform_enumerate_io_apics(
149    struct io_apic_descriptor* io_apics,
150    uint32_t len,
151    uint32_t* num_io_apics) {
152    if (num_io_apics == NULL) {
153        return ZX_ERR_INVALID_ARGS;
154    }
155
156    uintptr_t records_start, records_end;
157    zx_status_t status = acpi_get_madt_record_limits(&records_start, &records_end);
158    if (status != ZX_OK) {
159        return status;
160    }
161
162    uint32_t count = 0;
163    uintptr_t addr;
164    for (addr = records_start; addr < records_end;) {
165        ACPI_SUBTABLE_HEADER* record_hdr = (ACPI_SUBTABLE_HEADER*)addr;
166        switch (record_hdr->Type) {
167        case ACPI_MADT_TYPE_IO_APIC: {
168            ACPI_MADT_IO_APIC* io_apic = (ACPI_MADT_IO_APIC*)record_hdr;
169            if (io_apics != NULL && count < len) {
170                io_apics[count].apic_id = io_apic->Id;
171                io_apics[count].paddr = io_apic->Address;
172                io_apics[count].global_irq_base = io_apic->GlobalIrqBase;
173            }
174            count++;
175            break;
176        }
177        }
178
179        addr += record_hdr->Length;
180    }
181    if (addr != records_end) {
182        TRACEF("malformed MADT\n");
183        return ZX_ERR_INVALID_ARGS;
184    }
185    *num_io_apics = count;
186    return ZX_OK;
187}
188
189/* @brief Enumerate all interrupt source overrides
190 *
191 * If isos is NULL, just returns the number of ISOs via num_isos.
192 *
193 * @param isos Array to populate overrides into.
194 * @param len Length of isos.
195 * @param num_isos Number of ISOs found
196 *
197 * @return ZX_OK on success. Note that if len < *num_isos, not all
198 *         ISOs will be returned.
199 */
200zx_status_t platform_enumerate_interrupt_source_overrides(
201    struct io_apic_isa_override* isos,
202    uint32_t len,
203    uint32_t* num_isos) {
204    if (num_isos == NULL) {
205        return ZX_ERR_INVALID_ARGS;
206    }
207
208    uintptr_t records_start, records_end;
209    zx_status_t status = acpi_get_madt_record_limits(&records_start, &records_end);
210    if (status != ZX_OK) {
211        return status;
212    }
213
214    uint32_t count = 0;
215    uintptr_t addr;
216    for (addr = records_start; addr < records_end;) {
217        ACPI_SUBTABLE_HEADER* record_hdr = (ACPI_SUBTABLE_HEADER*)addr;
218        switch (record_hdr->Type) {
219        case ACPI_MADT_TYPE_INTERRUPT_OVERRIDE: {
220            ACPI_MADT_INTERRUPT_OVERRIDE* iso =
221                (ACPI_MADT_INTERRUPT_OVERRIDE*)record_hdr;
222            if (isos != NULL && count < len) {
223                ASSERT(iso->Bus == 0); // 0 means ISA, ISOs are only ever for ISA IRQs
224                isos[count].isa_irq = iso->SourceIrq;
225                isos[count].remapped = true;
226                isos[count].global_irq = iso->GlobalIrq;
227
228                uint32_t flags = iso->IntiFlags;
229                uint32_t polarity = flags & ACPI_MADT_POLARITY_MASK;
230                uint32_t trigger = flags & ACPI_MADT_TRIGGER_MASK;
231
232                // Conforms below means conforms to the bus spec.  ISA is
233                // edge triggered and active high.
234                switch (polarity) {
235                case ACPI_MADT_POLARITY_CONFORMS:
236                case ACPI_MADT_POLARITY_ACTIVE_HIGH:
237                    isos[count].pol = IRQ_POLARITY_ACTIVE_HIGH;
238                    break;
239                case ACPI_MADT_POLARITY_ACTIVE_LOW:
240                    isos[count].pol = IRQ_POLARITY_ACTIVE_LOW;
241                    break;
242                default:
243                    panic("Unknown IRQ polarity in override: %u\n",
244                          polarity);
245                }
246
247                switch (trigger) {
248                case ACPI_MADT_TRIGGER_CONFORMS:
249                case ACPI_MADT_TRIGGER_EDGE:
250                    isos[count].tm = IRQ_TRIGGER_MODE_EDGE;
251                    break;
252                case ACPI_MADT_TRIGGER_LEVEL:
253                    isos[count].tm = IRQ_TRIGGER_MODE_LEVEL;
254                    break;
255                default:
256                    panic("Unknown IRQ trigger in override: %u\n",
257                          trigger);
258                }
259            }
260            count++;
261            break;
262        }
263        }
264
265        addr += record_hdr->Length;
266    }
267    if (addr != records_end) {
268        TRACEF("malformed MADT\n");
269        return ZX_ERR_INVALID_ARGS;
270    }
271    *num_isos = count;
272    return ZX_OK;
273}
274
275/* @brief Return information about the High Precision Event Timer, if present.
276 *
277 * @param hpet Descriptor to populate
278 *
279 * @return ZX_OK on success.
280 */
281zx_status_t platform_find_hpet(struct acpi_hpet_descriptor* hpet) {
282    ACPI_TABLE_HEADER* table = NULL;
283    ACPI_STATUS status = AcpiGetTable((char*)ACPI_SIG_HPET, 1, &table);
284    if (status != AE_OK) {
285        TRACEF("could not find HPET\n");
286        return ZX_ERR_NOT_FOUND;
287    }
288    ACPI_TABLE_HPET* hpet_tbl = (ACPI_TABLE_HPET*)table;
289    if (hpet_tbl->Header.Length != sizeof(ACPI_TABLE_HPET)) {
290        TRACEF("Unexpected HPET table length\n");
291        return ZX_ERR_NOT_FOUND;
292    }
293
294    hpet->minimum_tick = hpet_tbl->MinimumTick;
295    hpet->sequence = hpet_tbl->Sequence;
296    hpet->address = hpet_tbl->Address.Address;
297    switch (hpet_tbl->Address.SpaceId) {
298    case ACPI_ADR_SPACE_SYSTEM_IO:
299        hpet->port_io = true;
300        break;
301    case ACPI_ADR_SPACE_SYSTEM_MEMORY:
302        hpet->port_io = false;
303        break;
304    default:
305        return ZX_ERR_NOT_SUPPORTED;
306    }
307
308    return ZX_OK;
309}
310