// Copyright 2016 The Fuchsia Authors // // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT #include #include #include #include #include #include #include #include #include #define LOCAL_TRACE 0 #define ACPI_MAX_INIT_TABLES 32 static ACPI_TABLE_DESC acpi_tables[ACPI_MAX_INIT_TABLES]; static bool acpi_initialized = false; static ACPI_STATUS acpi_set_apic_irq_mode(void); /** * @brief Initialize early-access ACPI tables * * This function enables *only* the ACPICA Table Manager subsystem. * The rest of the ACPI subsystem will remain uninitialized. */ void platform_init_acpi_tables(uint level) { DEBUG_ASSERT(!acpi_initialized); ACPI_STATUS status; status = AcpiInitializeTables(acpi_tables, ACPI_MAX_INIT_TABLES, FALSE); if (status == AE_NOT_FOUND) { TRACEF("WARNING: could not find ACPI tables\n"); return; } else if (status == AE_NO_MEMORY) { TRACEF("WARNING: could not initialize ACPI tables\n"); return; } else if (status != AE_OK) { TRACEF("WARNING: could not initialize ACPI tables for unknown reason\n"); return; } acpi_initialized = true; LTRACEF("ACPI tables initialized\n"); } /* initialize ACPI tables as soon as we have a working VM */ LK_INIT_HOOK(acpi_tables, &platform_init_acpi_tables, LK_INIT_LEVEL_VM + 1); static zx_status_t acpi_get_madt_record_limits(uintptr_t* start, uintptr_t* end) { ACPI_TABLE_HEADER* table = NULL; ACPI_STATUS status = AcpiGetTable((char*)ACPI_SIG_MADT, 1, &table); if (status != AE_OK) { TRACEF("could not find MADT\n"); return ZX_ERR_NOT_FOUND; } ACPI_TABLE_MADT* madt = (ACPI_TABLE_MADT*)table; uintptr_t records_start = ((uintptr_t)madt) + sizeof(*madt); uintptr_t records_end = ((uintptr_t)madt) + madt->Header.Length; if (records_start >= records_end) { TRACEF("MADT wraps around address space\n"); return ZX_ERR_INTERNAL; } // Shouldn't be too many records if (madt->Header.Length > 4096) { TRACEF("MADT suspiciously long: %u\n", madt->Header.Length); return ZX_ERR_INTERNAL; } *start = records_start; *end = records_end; return ZX_OK; } /* @brief Enumerate all functioning CPUs and their APIC IDs * * If apic_ids is NULL, just returns the number of logical processors * via num_cpus. * * @param apic_ids Array to write found APIC ids into. * @param len Length of apic_ids. * @param num_cpus Output for the number of logical processors detected. * * @return ZX_OK on success. Note that if len < *num_cpus, not all * logical apic_ids will be returned. */ zx_status_t platform_enumerate_cpus( uint32_t* apic_ids, uint32_t len, uint32_t* num_cpus) { if (num_cpus == NULL) { return ZX_ERR_INVALID_ARGS; } uintptr_t records_start, records_end; zx_status_t status = acpi_get_madt_record_limits(&records_start, &records_end); if (status != ZX_OK) { return status; } uint32_t count = 0; uintptr_t addr; ACPI_SUBTABLE_HEADER* record_hdr; for (addr = records_start; addr < records_end; addr += record_hdr->Length) { record_hdr = (ACPI_SUBTABLE_HEADER*)addr; switch (record_hdr->Type) { case ACPI_MADT_TYPE_LOCAL_APIC: { ACPI_MADT_LOCAL_APIC* lapic = (ACPI_MADT_LOCAL_APIC*)record_hdr; if (!(lapic->LapicFlags & ACPI_MADT_ENABLED)) { LTRACEF("Skipping disabled processor %02x\n", lapic->Id); continue; } if (apic_ids != NULL && count < len) { apic_ids[count] = lapic->Id; } count++; break; } } } if (addr != records_end) { TRACEF("malformed MADT\n"); return ZX_ERR_INTERNAL; } *num_cpus = count; return ZX_OK; } /* @brief Enumerate all IO APICs * * If io_apics is NULL, just returns the number of IO APICs * via num_io_apics. * * @param io_apics Array to populate descriptors into. * @param len Length of io_apics. * @param num_io_apics Number of IO apics found * * @return ZX_OK on success. Note that if len < *num_io_apics, not all * IO APICs will be returned. */ zx_status_t platform_enumerate_io_apics( struct io_apic_descriptor* io_apics, uint32_t len, uint32_t* num_io_apics) { if (num_io_apics == NULL) { return ZX_ERR_INVALID_ARGS; } uintptr_t records_start, records_end; zx_status_t status = acpi_get_madt_record_limits(&records_start, &records_end); if (status != ZX_OK) { return status; } uint32_t count = 0; uintptr_t addr; for (addr = records_start; addr < records_end;) { ACPI_SUBTABLE_HEADER* record_hdr = (ACPI_SUBTABLE_HEADER*)addr; switch (record_hdr->Type) { case ACPI_MADT_TYPE_IO_APIC: { ACPI_MADT_IO_APIC* io_apic = (ACPI_MADT_IO_APIC*)record_hdr; if (io_apics != NULL && count < len) { io_apics[count].apic_id = io_apic->Id; io_apics[count].paddr = io_apic->Address; io_apics[count].global_irq_base = io_apic->GlobalIrqBase; } count++; break; } } addr += record_hdr->Length; } if (addr != records_end) { TRACEF("malformed MADT\n"); return ZX_ERR_INVALID_ARGS; } *num_io_apics = count; return ZX_OK; } /* @brief Enumerate all interrupt source overrides * * If isos is NULL, just returns the number of ISOs via num_isos. * * @param isos Array to populate overrides into. * @param len Length of isos. * @param num_isos Number of ISOs found * * @return ZX_OK on success. Note that if len < *num_isos, not all * ISOs will be returned. */ zx_status_t platform_enumerate_interrupt_source_overrides( struct io_apic_isa_override* isos, uint32_t len, uint32_t* num_isos) { if (num_isos == NULL) { return ZX_ERR_INVALID_ARGS; } uintptr_t records_start, records_end; zx_status_t status = acpi_get_madt_record_limits(&records_start, &records_end); if (status != ZX_OK) { return status; } uint32_t count = 0; uintptr_t addr; for (addr = records_start; addr < records_end;) { ACPI_SUBTABLE_HEADER* record_hdr = (ACPI_SUBTABLE_HEADER*)addr; switch (record_hdr->Type) { case ACPI_MADT_TYPE_INTERRUPT_OVERRIDE: { ACPI_MADT_INTERRUPT_OVERRIDE* iso = (ACPI_MADT_INTERRUPT_OVERRIDE*)record_hdr; if (isos != NULL && count < len) { ASSERT(iso->Bus == 0); // 0 means ISA, ISOs are only ever for ISA IRQs isos[count].isa_irq = iso->SourceIrq; isos[count].remapped = true; isos[count].global_irq = iso->GlobalIrq; uint32_t flags = iso->IntiFlags; uint32_t polarity = flags & ACPI_MADT_POLARITY_MASK; uint32_t trigger = flags & ACPI_MADT_TRIGGER_MASK; // Conforms below means conforms to the bus spec. ISA is // edge triggered and active high. switch (polarity) { case ACPI_MADT_POLARITY_CONFORMS: case ACPI_MADT_POLARITY_ACTIVE_HIGH: isos[count].pol = IRQ_POLARITY_ACTIVE_HIGH; break; case ACPI_MADT_POLARITY_ACTIVE_LOW: isos[count].pol = IRQ_POLARITY_ACTIVE_LOW; break; default: panic("Unknown IRQ polarity in override: %u\n", polarity); } switch (trigger) { case ACPI_MADT_TRIGGER_CONFORMS: case ACPI_MADT_TRIGGER_EDGE: isos[count].tm = IRQ_TRIGGER_MODE_EDGE; break; case ACPI_MADT_TRIGGER_LEVEL: isos[count].tm = IRQ_TRIGGER_MODE_LEVEL; break; default: panic("Unknown IRQ trigger in override: %u\n", trigger); } } count++; break; } } addr += record_hdr->Length; } if (addr != records_end) { TRACEF("malformed MADT\n"); return ZX_ERR_INVALID_ARGS; } *num_isos = count; return ZX_OK; } /* @brief Return information about the High Precision Event Timer, if present. * * @param hpet Descriptor to populate * * @return ZX_OK on success. */ zx_status_t platform_find_hpet(struct acpi_hpet_descriptor* hpet) { ACPI_TABLE_HEADER* table = NULL; ACPI_STATUS status = AcpiGetTable((char*)ACPI_SIG_HPET, 1, &table); if (status != AE_OK) { TRACEF("could not find HPET\n"); return ZX_ERR_NOT_FOUND; } ACPI_TABLE_HPET* hpet_tbl = (ACPI_TABLE_HPET*)table; if (hpet_tbl->Header.Length != sizeof(ACPI_TABLE_HPET)) { TRACEF("Unexpected HPET table length\n"); return ZX_ERR_NOT_FOUND; } hpet->minimum_tick = hpet_tbl->MinimumTick; hpet->sequence = hpet_tbl->Sequence; hpet->address = hpet_tbl->Address.Address; switch (hpet_tbl->Address.SpaceId) { case ACPI_ADR_SPACE_SYSTEM_IO: hpet->port_io = true; break; case ACPI_ADR_SPACE_SYSTEM_MEMORY: hpet->port_io = false; break; default: return ZX_ERR_NOT_SUPPORTED; } return ZX_OK; }