mirror of
https://github.com/torvalds/linux.git
synced 2024-11-29 23:51:37 +00:00
ac7473a179
- Core: - Provide a new mechanism to create interrupt domains. The existing interfaces have already too many parameters and it's a pain to expand any of this for new required functionality. The new function takes a pointer to a data structure as argument. The data structure combines all existing parameters and allows for easy extension. The first extension for this is to handle the instantiation of generic interrupt chips at the core level and to allow drivers to provide extra init/exit callbacks. This is necessary to do the full interrupt chip initialization before the new domain is published, so that concurrent usage sites won't see a half initialized interrupt domain. Similar problems exist on teardown. This has turned out to be a real problem due to the deferred and parallel probing which was added in recent years. Handling this at the core level allows to remove quite some accrued boilerplate code in existing drivers and avoids horrible workarounds at the driver level. - The usual small improvements all over the place - Drivers - Add support for LAN966x OIC and RZ/Five SoC - Split the STM ExtI driver into a microcontroller and a SMP version to allow building the latter as a module for multi-platform kernels. - Enable MSI support for Armada 370XP on platforms which do not support IPIs. - The usual small fixes and enhancements all over the place. -----BEGIN PGP SIGNATURE----- iQJHBAABCgAxFiEEQp8+kY+LLUocC4bMphj1TA10mKEFAmaVJbUTHHRnbHhAbGlu dXRyb25peC5kZQAKCRCmGPVMDXSYoXTuD/9Tc9BhY5CW7HQkdPQu2Db1O+esprkQ Uo9lMpTTpPiy9btg4LONzLf4mjbufZpyKBxkRWoZFO0Zj5q4UE9NZYh7EcxrF5Tl CIFJmyteLsYuOyCmPrtSDSovonXjQKYBE3u2LVJNNkwEkhYbYW9sqIKeT8nneLv6 53gd28ESFUEUjHNTblw/eXviweyUKSXc0qyg+3hgZQPMoh9RkdkEPvyaw9Y/s5Ce FelLLxzMqX86dR2TJMLqiaGiMpUu/kl+Yz2m5c77TwA2D68qjhHywbtKtlH7b3C6 LMHu2dMrrKSJrLL8roVIYJdHAd1TKWVdnYhqv9WBHFTu1sDuztpR44mewbo8exUU L2RgVSGYNmeFC3p4wztWYSQfIVa9uOg7+TnJJdh7G0jLIeKM/TbufWqDAJAuoVPL QhGbZ5xNbZJZ8bvhhItjxpRN/kPs44p3mUGyRJBQzm+mDN118bqfmQzhLcwRbfE2 smp73SQzg9alG2rGdNVEqkKmp8zhg2Crx2VCeVdgbeOxWQRet9zLWcp4FfCEUE9e eK3iEi8z+rmwafaf3rsxYdrdIRLaUmcni0v7R/16cJH/Cs7bU3Re8XyGhevo3lsO pJiP5wZDxbckwXNpLm3S/qPDW7vSCnuFPF7QmOvC3a70PsD+E4NKUgiwJuHtn/ZV pFBKzbQgCsowQA== =QCRH -----END PGP SIGNATURE----- Merge tag 'irq-core-2024-07-15' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip Pull interrupt subsystem updates from Thomas Gleixner: "Core: - Provide a new mechanism to create interrupt domains. The existing interfaces have already too many parameters and it's a pain to expand any of this for new required functionality. The new function takes a pointer to a data structure as argument. The data structure combines all existing parameters and allows for easy extension. The first extension for this is to handle the instantiation of generic interrupt chips at the core level and to allow drivers to provide extra init/exit callbacks. This is necessary to do the full interrupt chip initialization before the new domain is published, so that concurrent usage sites won't see a half initialized interrupt domain. Similar problems exist on teardown. This has turned out to be a real problem due to the deferred and parallel probing which was added in recent years. Handling this at the core level allows to remove quite some accrued boilerplate code in existing drivers and avoids horrible workarounds at the driver level. - The usual small improvements all over the place Drivers: - Add support for LAN966x OIC and RZ/Five SoC - Split the STM ExtI driver into a microcontroller and a SMP version to allow building the latter as a module for multi-platform kernels - Enable MSI support for Armada 370XP on platforms which do not support IPIs - The usual small fixes and enhancements all over the place" * tag 'irq-core-2024-07-15' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (59 commits) irqdomain: Fix the kernel-doc and plug it into Documentation genirq: Set IRQF_COND_ONESHOT in request_irq() irqchip/imx-irqsteer: Handle runtime power management correctly irqchip/gic-v3: Pass #redistributor-regions to gic_of_setup_kvm_info() irqchip/bcm2835: Enable SKIP_SET_WAKE and MASK_ON_SUSPEND irqchip/gic-v4: Make sure a VPE is locked when VMAPP is issued irqchip/gic-v4: Substitute vmovp_lock for a per-VM lock irqchip/gic-v4: Always configure affinity on VPE activation Revert "irqchip/dw-apb-ictl: Support building as module" Revert "Loongarch: Support loongarch avec" arm64: Kconfig: Allow build irq-stm32mp-exti driver as module ARM: stm32: Allow build irq-stm32mp-exti driver as module irqchip/stm32mp-exti: Allow building as module irqchip/stm32mp-exti: Rename internal symbols irqchip/stm32-exti: Split MCU and MPU code arm64: Kconfig: Select STM32MP_EXTI on STM32 platforms ARM: stm32: Use different EXTI driver on ARMv7m and ARMv7a irqchip/stm32-exti: Add CONFIG_STM32MP_EXTI irqchip/dw-apb-ictl: Support building as module irqchip/riscv-aplic: Simplify the initialization code ...
430 lines
11 KiB
C
430 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2005 Intel Corporation
|
|
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
|
|
*
|
|
* Alex Chiang <achiang@hp.com>
|
|
* - Unified x86/ia64 implementations
|
|
*
|
|
* I/O APIC hotplug support
|
|
* Yinghai Lu <yinghai@kernel.org>
|
|
* Jiang Liu <jiang.liu@intel.com>
|
|
*/
|
|
#include <linux/export.h>
|
|
#include <linux/acpi.h>
|
|
#include <acpi/processor.h>
|
|
|
|
static struct acpi_table_madt *get_madt_table(void)
|
|
{
|
|
static struct acpi_table_madt *madt;
|
|
static int read_madt;
|
|
|
|
if (!read_madt) {
|
|
if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_MADT, 0,
|
|
(struct acpi_table_header **)&madt)))
|
|
madt = NULL;
|
|
read_madt++;
|
|
}
|
|
|
|
return madt;
|
|
}
|
|
|
|
static int map_lapic_id(struct acpi_subtable_header *entry,
|
|
u32 acpi_id, phys_cpuid_t *apic_id)
|
|
{
|
|
struct acpi_madt_local_apic *lapic =
|
|
container_of(entry, struct acpi_madt_local_apic, header);
|
|
|
|
if (!(lapic->lapic_flags & ACPI_MADT_ENABLED))
|
|
return -ENODEV;
|
|
|
|
if (lapic->processor_id != acpi_id)
|
|
return -EINVAL;
|
|
|
|
*apic_id = lapic->id;
|
|
return 0;
|
|
}
|
|
|
|
static int map_x2apic_id(struct acpi_subtable_header *entry,
|
|
int device_declaration, u32 acpi_id, phys_cpuid_t *apic_id)
|
|
{
|
|
struct acpi_madt_local_x2apic *apic =
|
|
container_of(entry, struct acpi_madt_local_x2apic, header);
|
|
|
|
if (!(apic->lapic_flags & ACPI_MADT_ENABLED))
|
|
return -ENODEV;
|
|
|
|
if (device_declaration && (apic->uid == acpi_id)) {
|
|
*apic_id = apic->local_apic_id;
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int map_lsapic_id(struct acpi_subtable_header *entry,
|
|
int device_declaration, u32 acpi_id, phys_cpuid_t *apic_id)
|
|
{
|
|
struct acpi_madt_local_sapic *lsapic =
|
|
container_of(entry, struct acpi_madt_local_sapic, header);
|
|
|
|
if (!(lsapic->lapic_flags & ACPI_MADT_ENABLED))
|
|
return -ENODEV;
|
|
|
|
if (device_declaration) {
|
|
if ((entry->length < 16) || (lsapic->uid != acpi_id))
|
|
return -EINVAL;
|
|
} else if (lsapic->processor_id != acpi_id)
|
|
return -EINVAL;
|
|
|
|
*apic_id = (lsapic->id << 8) | lsapic->eid;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Retrieve the ARM CPU physical identifier (MPIDR)
|
|
*/
|
|
static int map_gicc_mpidr(struct acpi_subtable_header *entry,
|
|
int device_declaration, u32 acpi_id, phys_cpuid_t *mpidr)
|
|
{
|
|
struct acpi_madt_generic_interrupt *gicc =
|
|
container_of(entry, struct acpi_madt_generic_interrupt, header);
|
|
|
|
if (!(gicc->flags &
|
|
(ACPI_MADT_ENABLED | ACPI_MADT_GICC_ONLINE_CAPABLE)))
|
|
return -ENODEV;
|
|
|
|
/* device_declaration means Device object in DSDT, in the
|
|
* GIC interrupt model, logical processors are required to
|
|
* have a Processor Device object in the DSDT, so we should
|
|
* check device_declaration here
|
|
*/
|
|
if (device_declaration && (gicc->uid == acpi_id)) {
|
|
*mpidr = gicc->arm_mpidr;
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Retrieve the RISC-V hartid for the processor
|
|
*/
|
|
static int map_rintc_hartid(struct acpi_subtable_header *entry,
|
|
int device_declaration, u32 acpi_id,
|
|
phys_cpuid_t *hartid)
|
|
{
|
|
struct acpi_madt_rintc *rintc =
|
|
container_of(entry, struct acpi_madt_rintc, header);
|
|
|
|
if (!(rintc->flags & ACPI_MADT_ENABLED))
|
|
return -ENODEV;
|
|
|
|
/* device_declaration means Device object in DSDT, in the
|
|
* RISC-V, logical processors are required to
|
|
* have a Processor Device object in the DSDT, so we should
|
|
* check device_declaration here
|
|
*/
|
|
if (device_declaration && rintc->uid == acpi_id) {
|
|
*hartid = rintc->hart_id;
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Retrieve LoongArch CPU physical id
|
|
*/
|
|
static int map_core_pic_id(struct acpi_subtable_header *entry,
|
|
int device_declaration, u32 acpi_id, phys_cpuid_t *phys_id)
|
|
{
|
|
struct acpi_madt_core_pic *core_pic =
|
|
container_of(entry, struct acpi_madt_core_pic, header);
|
|
|
|
if (!(core_pic->flags & ACPI_MADT_ENABLED))
|
|
return -ENODEV;
|
|
|
|
/* device_declaration means Device object in DSDT, in LoongArch
|
|
* system, logical processor acpi_id is required in _UID property
|
|
* of DSDT table, so we should check device_declaration here
|
|
*/
|
|
if (device_declaration && (core_pic->processor_id == acpi_id)) {
|
|
*phys_id = core_pic->core_id;
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static phys_cpuid_t map_madt_entry(struct acpi_table_madt *madt,
|
|
int type, u32 acpi_id)
|
|
{
|
|
unsigned long madt_end, entry;
|
|
phys_cpuid_t phys_id = PHYS_CPUID_INVALID; /* CPU hardware ID */
|
|
|
|
if (!madt)
|
|
return phys_id;
|
|
|
|
entry = (unsigned long)madt;
|
|
madt_end = entry + madt->header.length;
|
|
|
|
/* Parse all entries looking for a match. */
|
|
|
|
entry += sizeof(struct acpi_table_madt);
|
|
while (entry + sizeof(struct acpi_subtable_header) < madt_end) {
|
|
struct acpi_subtable_header *header =
|
|
(struct acpi_subtable_header *)entry;
|
|
if (header->type == ACPI_MADT_TYPE_LOCAL_APIC) {
|
|
if (!map_lapic_id(header, acpi_id, &phys_id))
|
|
break;
|
|
} else if (header->type == ACPI_MADT_TYPE_LOCAL_X2APIC) {
|
|
if (!map_x2apic_id(header, type, acpi_id, &phys_id))
|
|
break;
|
|
} else if (header->type == ACPI_MADT_TYPE_LOCAL_SAPIC) {
|
|
if (!map_lsapic_id(header, type, acpi_id, &phys_id))
|
|
break;
|
|
} else if (header->type == ACPI_MADT_TYPE_GENERIC_INTERRUPT) {
|
|
if (!map_gicc_mpidr(header, type, acpi_id, &phys_id))
|
|
break;
|
|
} else if (header->type == ACPI_MADT_TYPE_RINTC) {
|
|
if (!map_rintc_hartid(header, type, acpi_id, &phys_id))
|
|
break;
|
|
} else if (header->type == ACPI_MADT_TYPE_CORE_PIC) {
|
|
if (!map_core_pic_id(header, type, acpi_id, &phys_id))
|
|
break;
|
|
}
|
|
entry += header->length;
|
|
}
|
|
return phys_id;
|
|
}
|
|
|
|
phys_cpuid_t __init acpi_map_madt_entry(u32 acpi_id)
|
|
{
|
|
struct acpi_table_madt *madt = NULL;
|
|
phys_cpuid_t rv;
|
|
|
|
acpi_get_table(ACPI_SIG_MADT, 0,
|
|
(struct acpi_table_header **)&madt);
|
|
if (!madt)
|
|
return PHYS_CPUID_INVALID;
|
|
|
|
rv = map_madt_entry(madt, 1, acpi_id);
|
|
|
|
acpi_put_table((struct acpi_table_header *)madt);
|
|
|
|
return rv;
|
|
}
|
|
|
|
int __init acpi_get_madt_revision(void)
|
|
{
|
|
struct acpi_table_header *madt = NULL;
|
|
int revision;
|
|
|
|
if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_MADT, 0, &madt)))
|
|
return -EINVAL;
|
|
|
|
revision = madt->revision;
|
|
|
|
acpi_put_table(madt);
|
|
|
|
return revision;
|
|
}
|
|
|
|
static phys_cpuid_t map_mat_entry(acpi_handle handle, int type, u32 acpi_id)
|
|
{
|
|
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
union acpi_object *obj;
|
|
struct acpi_subtable_header *header;
|
|
phys_cpuid_t phys_id = PHYS_CPUID_INVALID;
|
|
|
|
if (ACPI_FAILURE(acpi_evaluate_object(handle, "_MAT", NULL, &buffer)))
|
|
goto exit;
|
|
|
|
if (!buffer.length || !buffer.pointer)
|
|
goto exit;
|
|
|
|
obj = buffer.pointer;
|
|
if (obj->type != ACPI_TYPE_BUFFER ||
|
|
obj->buffer.length < sizeof(struct acpi_subtable_header)) {
|
|
goto exit;
|
|
}
|
|
|
|
header = (struct acpi_subtable_header *)obj->buffer.pointer;
|
|
if (header->type == ACPI_MADT_TYPE_LOCAL_APIC)
|
|
map_lapic_id(header, acpi_id, &phys_id);
|
|
else if (header->type == ACPI_MADT_TYPE_LOCAL_SAPIC)
|
|
map_lsapic_id(header, type, acpi_id, &phys_id);
|
|
else if (header->type == ACPI_MADT_TYPE_LOCAL_X2APIC)
|
|
map_x2apic_id(header, type, acpi_id, &phys_id);
|
|
else if (header->type == ACPI_MADT_TYPE_GENERIC_INTERRUPT)
|
|
map_gicc_mpidr(header, type, acpi_id, &phys_id);
|
|
else if (header->type == ACPI_MADT_TYPE_CORE_PIC)
|
|
map_core_pic_id(header, type, acpi_id, &phys_id);
|
|
|
|
exit:
|
|
kfree(buffer.pointer);
|
|
return phys_id;
|
|
}
|
|
|
|
phys_cpuid_t acpi_get_phys_id(acpi_handle handle, int type, u32 acpi_id)
|
|
{
|
|
phys_cpuid_t phys_id;
|
|
|
|
phys_id = map_mat_entry(handle, type, acpi_id);
|
|
if (invalid_phys_cpuid(phys_id))
|
|
phys_id = map_madt_entry(get_madt_table(), type, acpi_id);
|
|
|
|
return phys_id;
|
|
}
|
|
EXPORT_SYMBOL_GPL(acpi_get_phys_id);
|
|
|
|
int acpi_map_cpuid(phys_cpuid_t phys_id, u32 acpi_id)
|
|
{
|
|
#ifdef CONFIG_SMP
|
|
int i;
|
|
#endif
|
|
|
|
if (invalid_phys_cpuid(phys_id)) {
|
|
/*
|
|
* On UP processor, there is no _MAT or MADT table.
|
|
* So above phys_id is always set to PHYS_CPUID_INVALID.
|
|
*
|
|
* BIOS may define multiple CPU handles even for UP processor.
|
|
* For example,
|
|
*
|
|
* Scope (_PR)
|
|
* {
|
|
* Processor (CPU0, 0x00, 0x00000410, 0x06) {}
|
|
* Processor (CPU1, 0x01, 0x00000410, 0x06) {}
|
|
* Processor (CPU2, 0x02, 0x00000410, 0x06) {}
|
|
* Processor (CPU3, 0x03, 0x00000410, 0x06) {}
|
|
* }
|
|
*
|
|
* Ignores phys_id and always returns 0 for the processor
|
|
* handle with acpi id 0 if nr_cpu_ids is 1.
|
|
* This should be the case if SMP tables are not found.
|
|
* Return -EINVAL for other CPU's handle.
|
|
*/
|
|
if (nr_cpu_ids <= 1 && acpi_id == 0)
|
|
return acpi_id;
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
for_each_possible_cpu(i) {
|
|
if (cpu_physical_id(i) == phys_id)
|
|
return i;
|
|
}
|
|
#else
|
|
/* In UP kernel, only processor 0 is valid */
|
|
if (phys_id == 0)
|
|
return phys_id;
|
|
#endif
|
|
return -ENODEV;
|
|
}
|
|
|
|
int acpi_get_cpuid(acpi_handle handle, int type, u32 acpi_id)
|
|
{
|
|
phys_cpuid_t phys_id;
|
|
|
|
phys_id = acpi_get_phys_id(handle, type, acpi_id);
|
|
|
|
return acpi_map_cpuid(phys_id, acpi_id);
|
|
}
|
|
EXPORT_SYMBOL_GPL(acpi_get_cpuid);
|
|
|
|
#ifdef CONFIG_ACPI_HOTPLUG_IOAPIC
|
|
static int get_ioapic_id(struct acpi_subtable_header *entry, u32 gsi_base,
|
|
u64 *phys_addr, int *ioapic_id)
|
|
{
|
|
struct acpi_madt_io_apic *ioapic = (struct acpi_madt_io_apic *)entry;
|
|
|
|
if (ioapic->global_irq_base != gsi_base)
|
|
return 0;
|
|
|
|
*phys_addr = ioapic->address;
|
|
*ioapic_id = ioapic->id;
|
|
return 1;
|
|
}
|
|
|
|
static int parse_madt_ioapic_entry(u32 gsi_base, u64 *phys_addr)
|
|
{
|
|
struct acpi_subtable_header *hdr;
|
|
unsigned long madt_end, entry;
|
|
struct acpi_table_madt *madt;
|
|
int apic_id = -1;
|
|
|
|
madt = get_madt_table();
|
|
if (!madt)
|
|
return apic_id;
|
|
|
|
entry = (unsigned long)madt;
|
|
madt_end = entry + madt->header.length;
|
|
|
|
/* Parse all entries looking for a match. */
|
|
entry += sizeof(struct acpi_table_madt);
|
|
while (entry + sizeof(struct acpi_subtable_header) < madt_end) {
|
|
hdr = (struct acpi_subtable_header *)entry;
|
|
if (hdr->type == ACPI_MADT_TYPE_IO_APIC &&
|
|
get_ioapic_id(hdr, gsi_base, phys_addr, &apic_id))
|
|
break;
|
|
else
|
|
entry += hdr->length;
|
|
}
|
|
|
|
return apic_id;
|
|
}
|
|
|
|
static int parse_mat_ioapic_entry(acpi_handle handle, u32 gsi_base,
|
|
u64 *phys_addr)
|
|
{
|
|
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
struct acpi_subtable_header *header;
|
|
union acpi_object *obj;
|
|
int apic_id = -1;
|
|
|
|
if (ACPI_FAILURE(acpi_evaluate_object(handle, "_MAT", NULL, &buffer)))
|
|
goto exit;
|
|
|
|
if (!buffer.length || !buffer.pointer)
|
|
goto exit;
|
|
|
|
obj = buffer.pointer;
|
|
if (obj->type != ACPI_TYPE_BUFFER ||
|
|
obj->buffer.length < sizeof(struct acpi_subtable_header))
|
|
goto exit;
|
|
|
|
header = (struct acpi_subtable_header *)obj->buffer.pointer;
|
|
if (header->type == ACPI_MADT_TYPE_IO_APIC)
|
|
get_ioapic_id(header, gsi_base, phys_addr, &apic_id);
|
|
|
|
exit:
|
|
kfree(buffer.pointer);
|
|
return apic_id;
|
|
}
|
|
|
|
/**
|
|
* acpi_get_ioapic_id - Get IOAPIC ID and physical address matching @gsi_base
|
|
* @handle: ACPI object for IOAPIC device
|
|
* @gsi_base: GSI base to match with
|
|
* @phys_addr: Pointer to store physical address of matching IOAPIC record
|
|
*
|
|
* Walk resources returned by ACPI_MAT method, then ACPI MADT table, to search
|
|
* for an ACPI IOAPIC record matching @gsi_base.
|
|
* Return IOAPIC id and store physical address in @phys_addr if found a match,
|
|
* otherwise return <0.
|
|
*/
|
|
int acpi_get_ioapic_id(acpi_handle handle, u32 gsi_base, u64 *phys_addr)
|
|
{
|
|
int apic_id;
|
|
|
|
apic_id = parse_mat_ioapic_entry(handle, gsi_base, phys_addr);
|
|
if (apic_id == -1)
|
|
apic_id = parse_madt_ioapic_entry(gsi_base, phys_addr);
|
|
|
|
return apic_id;
|
|
}
|
|
#endif /* CONFIG_ACPI_HOTPLUG_IOAPIC */
|