From: Tomasz Nowicki tn@semihalf.com
From the top level prospective this self-probe infrastructure works
in the similar way as OF, however there are some internal differences.
For DT, the init fn is called once it finds compatible strings, but for ACPI there are no such thing. Instead, we initialize irqchips using static MADT table. Doing that, we need to relay on subtables presence which define details to corresponding IRQ controller. Also, for some subtable we can do additional matching, like for GIC distributor version. See patch for more detailed information.
This mechanism can also be used for clock declare and may also works on x86 for some table parsing too.
Signed-off-by: Tomasz Nowicki tn@semihalf.com Signed-off-by: Hanjun Guo hanjun.guo@linaro.org --- drivers/acpi/Makefile | 2 + drivers/acpi/irq.c | 110 ++++++++++++++++++++++++++++++++++++++ include/asm-generic/vmlinux.lds.h | 13 +++++ include/linux/acpi.h | 18 +++++++ include/linux/irqchip.h | 13 +++++ include/linux/mod_devicetable.h | 9 ++++ 6 files changed, 165 insertions(+) create mode 100644 drivers/acpi/irq.c
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 8321430..599f1df 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -27,6 +27,8 @@ acpi-$(CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT) += sleep.o acpi-y += device_pm.o acpi-$(CONFIG_ACPI_SLEEP) += proc.o
+# IRQ controller probe +acpi-y += irq.o
# # ACPI Bus and Device Drivers diff --git a/drivers/acpi/irq.c b/drivers/acpi/irq.c new file mode 100644 index 0000000..4216b34 --- /dev/null +++ b/drivers/acpi/irq.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015, Linaro Ltd. + * Author: Tomasz Nowicki tomasz.nowicki@linaro.org + * Author: Hanjun Guo hanjun.guo@linaro.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <linux/acpi.h> +#include <linux/init.h> + +/* + * This special acpi_table_id is the sentinel at the end of the + * acpi_table_id[] array of all irqchips. It is automatically placed at + * the end of the array by the linker, thanks to being part of a + * special section. + */ +static const struct acpi_table_id +irqchip_acpi_match_end __used __section(__irqchip_acpi_table_end); +extern struct acpi_table_id __irqchip_acpi_table[]; +static struct acpi_table_id *iterator; + +static int __init +acpi_match_gic_redist(struct acpi_subtable_header *header, + const unsigned long end) +{ + return 0; +} + +static bool __init +acpi_gic_redist_is_present(void) +{ + int count; + + count = acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_REDISTRIBUTOR, + acpi_match_gic_redist, 0); + return count > 0; +} + +static int __init +acpi_match_madt_subtable(struct acpi_subtable_header *header, + const unsigned long end) +{ + struct acpi_madt_generic_distributor *dist; + u8 gic_version = ACPI_MADT_GIC_VERSION_NONE; + + /* Found appropriated subtable, now try to do additional matching */ + switch (header->type) { + case ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR: + + dist = (struct acpi_madt_generic_distributor *)header; + gic_version = dist->version; + + /* + * This is for backward compatibility with ACPI 5.1, + * which has no gic_version field. + */ + if (gic_version == ACPI_MADT_GIC_VERSION_NONE) { + /* It is GICv3/v4 if redistributor is present */ + if (acpi_gic_redist_is_present()) + gic_version = ACPI_MADT_GIC_VERSION_V3; + else + gic_version = ACPI_MADT_GIC_VERSION_V2; + + return 0; + } + + /* + * GICv4 has meaning to KVM, for host IRQ controller we can + * treat it as GICv3 to avoid another IRQCHIP_ACPI_DECLARE + * entry. + */ + if (gic_version == ACPI_MADT_GIC_VERSION_V4) + gic_version = ACPI_MADT_GIC_VERSION_V3; + + if (gic_version == iterator->driver_data) + return 0; + + return -AE_NOT_FOUND; + } + + /* No additional matching for the rest of subtable types for now */ + return 0; +} + +void __init acpi_irqchip_init(void) +{ + + if (acpi_disabled) + return; + + for (iterator = __irqchip_acpi_table; iterator->id[0]; iterator++) { + if (acpi_table_parse_madt(iterator->type, + acpi_match_madt_subtable, 0) <= 0) + continue; /* No match or invalid subtables */ + + acpi_table_parse(ACPI_SIG_MADT, + (acpi_tbl_table_handler)iterator->handler); + } +} diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 8bd374d..625776c 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -181,6 +181,18 @@ #define CPUIDLE_METHOD_OF_TABLES() OF_TABLE(CONFIG_CPU_IDLE, cpuidle_method) #define EARLYCON_OF_TABLES() OF_TABLE(CONFIG_SERIAL_EARLYCON, earlycon)
+#ifdef CONFIG_ACPI +#define ACPI_TABLE(name) \ + . = ALIGN(8); \ + VMLINUX_SYMBOL(__##name##_acpi_table) = .; \ + *(__##name##_acpi_table) \ + *(__##name##_acpi_table_end) + +#define IRQCHIP_ACPI_MATCH_TABLE() ACPI_TABLE(irqchip) +#else +#define IRQCHIP_ACPI_MATCH_TABLE() +#endif + #define KERNEL_DTB() \ STRUCT_ALIGN(); \ VMLINUX_SYMBOL(__dtb_start) = .; \ @@ -516,6 +528,7 @@ CPUIDLE_METHOD_OF_TABLES() \ KERNEL_DTB() \ IRQCHIP_OF_MATCH_TABLE() \ + IRQCHIP_ACPI_MATCH_TABLE() \ EARLYCON_TABLE() \ EARLYCON_OF_TABLES()
diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 0820cb1..a30b969 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -829,4 +829,22 @@ static inline struct acpi_device *acpi_get_next_child(struct device *dev,
#endif
+#ifdef CONFIG_ACPI +#define ACPI_DECLARE(table, name, table_id, subtable, data, fn) \ + static const struct acpi_table_id __acpi_table_##name \ + __used __section(__##table##_acpi_table) \ + = { .id = table_id, \ + .type = subtable, \ + .handler = (void *)fn, \ + .driver_data = data } +#else +#define ACPI_DECLARE(table, name, table_id, subtable, data, fn) \ + static const struct acpi_table_id __acpi_table_##name \ + __attribute__((unused)) \ + = { .id = table_id, \ + .type = subtable, \ + .handler = (void *)fn, \ + .driver_data = data } +#endif + #endif /*_LINUX_ACPI_H*/ diff --git a/include/linux/irqchip.h b/include/linux/irqchip.h index 6388873..a7573fd 100644 --- a/include/linux/irqchip.h +++ b/include/linux/irqchip.h @@ -11,6 +11,7 @@ #ifndef _LINUX_IRQCHIP_H #define _LINUX_IRQCHIP_H
+#include <linux/acpi.h> #include <linux/of.h>
/* @@ -25,6 +26,18 @@ */ #define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
+/* + * This macro must be used by the different ARM GIC drivers to declare + * the association between their version and their initialization function. + * + * @name: name that must be unique accross all IRQCHIP_ACPI_DECLARE of the + * same file. + * @gic_version: version of GIC + * @fn: initialization function + */ +#define IRQCHIP_ACPI_DECLARE(name, subtable, version, fn) \ + ACPI_DECLARE(irqchip, name, ACPI_SIG_MADT, subtable, version, fn) + #ifdef CONFIG_IRQCHIP void irqchip_init(void); #else diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 34f25b7..ebedaa7 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -193,6 +193,15 @@ struct acpi_device_id { __u32 cls_msk; };
+#define ACPI_TABLE_ID_LEN 5 + +struct acpi_table_id { + __u8 id[ACPI_TABLE_ID_LEN]; + __u8 type; + const void *handler; + kernel_ulong_t driver_data; +}; + #define PNP_ID_LEN 8 #define PNP_MAX_DEVICES 8