#define pr_fmt(fmt) "GICv5 IRS: " fmt
+#include <linux/acpi.h>
#include <linux/kmemleak.h>
#include <linux/log2.h>
#include <linux/of.h>
return list_empty(&irs_nodes) ? -ENODEV : 0;
}
+
+#ifdef CONFIG_ACPI
+
+#define ACPI_GICV5_IRS_MEM_SIZE (SZ_64K)
+static struct gicv5_irs_chip_data *current_irs_data __initdata;
+static int current_irsid __initdata = -1;
+static u8 current_iaffid_bits __initdata;
+
+static int __init gic_acpi_parse_iaffid(union acpi_subtable_headers *header,
+ const unsigned long end)
+{
+ struct acpi_madt_generic_interrupt *gicc = (struct acpi_madt_generic_interrupt *)header;
+ int cpu;
+
+ if (!(gicc->flags & (ACPI_MADT_ENABLED | ACPI_MADT_GICC_ONLINE_CAPABLE)))
+ return 0;
+
+ if (gicc->irs_id != current_irsid)
+ return 0;
+
+ cpu = get_logical_index(gicc->arm_mpidr);
+
+ if (gicc->iaffid & ~GENMASK(current_iaffid_bits - 1, 0)) {
+ pr_warn("CPU %d iaffid 0x%x exceeds IRS iaffid bits\n", cpu, gicc->iaffid);
+ return 0;
+ }
+
+ /* Bind the IAFFID and the CPU */
+ per_cpu(cpu_iaffid, cpu).iaffid = gicc->iaffid;
+ per_cpu(cpu_iaffid, cpu).valid = true;
+ pr_debug("Processed IAFFID %u for CPU%d", per_cpu(cpu_iaffid, cpu).iaffid, cpu);
+
+ /* We also know that the CPU is connected to this IRS */
+ per_cpu(per_cpu_irs_data, cpu) = current_irs_data;
+
+ return 0;
+}
+
+static int __init gicv5_irs_acpi_init_affinity(u32 irsid, struct gicv5_irs_chip_data *irs_data)
+{
+ u32 idr;
+
+ current_irsid = irsid;
+ current_irs_data = irs_data;
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
+ current_iaffid_bits = FIELD_GET(GICV5_IRS_IDR1_IAFFID_BITS, idr) + 1;
+
+ acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_INTERRUPT, gic_acpi_parse_iaffid, 0);
+
+ return 0;
+}
+
+static struct resource * __init gic_request_region(resource_size_t base, resource_size_t size,
+ const char *name)
+{
+ struct resource *r = request_mem_region(base, size, name);
+
+ if (!r)
+ pr_warn_once(FW_BUG "%s region %pa has overlapping address\n", name, &base);
+
+ return r;
+}
+
+static int __init gic_acpi_parse_madt_irs(union acpi_subtable_headers *header,
+ const unsigned long end)
+{
+ struct acpi_madt_gicv5_irs *irs = (struct acpi_madt_gicv5_irs *)header;
+ struct gicv5_irs_chip_data *irs_data;
+ void __iomem *irs_base;
+ struct resource *r;
+ int ret;
+
+ /* Per-IRS data structure */
+ irs_data = kzalloc(sizeof(*irs_data), GFP_KERNEL);
+ if (!irs_data)
+ return -ENOMEM;
+
+ /* This spinlock is used for SPI config changes */
+ raw_spin_lock_init(&irs_data->spi_config_lock);
+
+ r = gic_request_region(irs->config_base_address, ACPI_GICV5_IRS_MEM_SIZE, "GICv5 IRS");
+ if (!r) {
+ ret = -EBUSY;
+ goto out_free;
+ }
+
+ irs_base = ioremap(irs->config_base_address, ACPI_GICV5_IRS_MEM_SIZE);
+ if (!irs_base) {
+ pr_err("Unable to map GIC IRS registers\n");
+ ret = -ENOMEM;
+ goto out_release;
+ }
+
+ gicv5_irs_init_bases(irs_data, irs_base, irs->flags & ACPI_MADT_IRS_NON_COHERENT);
+
+ gicv5_irs_acpi_init_affinity(irs->irs_id, irs_data);
+
+ ret = gicv5_irs_init(irs_data);
+ if (ret)
+ goto out_map;
+
+ if (irs_data->spi_range) {
+ pr_info("%s @%llx detected SPI range [%u-%u]\n", "IRS", irs->config_base_address,
+ irs_data->spi_min,
+ irs_data->spi_min +
+ irs_data->spi_range - 1);
+ }
+
+ return 0;
+
+out_map:
+ iounmap(irs_base);
+out_release:
+ release_mem_region(r->start, resource_size(r));
+out_free:
+ kfree(irs_data);
+ return ret;
+}
+
+int __init gicv5_irs_acpi_probe(void)
+{
+ acpi_table_parse_madt(ACPI_MADT_TYPE_GICV5_IRS, gic_acpi_parse_madt_irs, 0);
+
+ return list_empty(&irs_nodes) ? -ENODEV : 0;
+}
+#endif
unsigned int *type,
const u8 hwirq_type)
{
- if (!is_of_node(fwspec->fwnode))
- return -EINVAL;
+ unsigned int hwirq_trigger;
+ u8 fwspec_irq_type;
- if (fwspec->param_count < 3)
- return -EINVAL;
+ if (is_of_node(fwspec->fwnode)) {
- if (fwspec->param[0] != hwirq_type)
- return -EINVAL;
+ if (fwspec->param_count < 3)
+ return -EINVAL;
+
+ fwspec_irq_type = fwspec->param[0];
+
+ if (fwspec->param[0] != hwirq_type)
+ return -EINVAL;
+
+ *hwirq = fwspec->param[1];
+ hwirq_trigger = fwspec->param[2];
+ }
+
+ if (is_fwnode_irqchip(fwspec->fwnode)) {
+
+ if (fwspec->param_count != 2)
+ return -EINVAL;
- *hwirq = fwspec->param[1];
+ fwspec_irq_type = FIELD_GET(GICV5_HWIRQ_TYPE, fwspec->param[0]);
+
+ if (fwspec_irq_type != hwirq_type)
+ return -EINVAL;
+
+ *hwirq = FIELD_GET(GICV5_HWIRQ_ID, fwspec->param[0]);
+ hwirq_trigger = fwspec->param[1];
+ }
switch (hwirq_type) {
case GICV5_HWIRQ_TYPE_PPI:
IRQ_TYPE_EDGE_RISING;
break;
case GICV5_HWIRQ_TYPE_SPI:
- *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
+ *type = hwirq_trigger & IRQ_TYPE_SENSE_MASK;
break;
default:
BUILD_BUG_ON(1);
static int gicv5_irq_ppi_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
enum irq_domain_bus_token bus_token)
{
+ u32 hwirq_type;
+
if (fwspec->fwnode != d->fwnode)
return 0;
- if (fwspec->param[0] != GICV5_HWIRQ_TYPE_PPI)
+ if (is_of_node(fwspec->fwnode))
+ hwirq_type = fwspec->param[0];
+
+ if (is_fwnode_irqchip(fwspec->fwnode))
+ hwirq_type = FIELD_GET(GICV5_HWIRQ_TYPE, fwspec->param[0]);
+
+ if (hwirq_type != GICV5_HWIRQ_TYPE_PPI)
return 0;
return (d == gicv5_global_data.ppi_domain);
static int gicv5_irq_spi_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
enum irq_domain_bus_token bus_token)
{
+ u32 hwirq_type;
+
if (fwspec->fwnode != d->fwnode)
return 0;
- if (fwspec->param[0] != GICV5_HWIRQ_TYPE_SPI)
+ if (is_of_node(fwspec->fwnode))
+ hwirq_type = fwspec->param[0];
+
+ if (is_fwnode_irqchip(fwspec->fwnode))
+ hwirq_type = FIELD_GET(GICV5_HWIRQ_TYPE, fwspec->param[0]);
+
+ if (hwirq_type != GICV5_HWIRQ_TYPE_SPI)
return 0;
return (d == gicv5_global_data.spi_domain);
}
#endif // CONFIG_KVM
-static int __init gicv5_of_init(struct device_node *node, struct device_node *parent)
+static int __init gicv5_init_common(struct fwnode_handle *parent_domain)
{
- int ret = gicv5_irs_of_probe(node);
+ int ret = gicv5_init_domains(parent_domain);
if (ret)
return ret;
- ret = gicv5_init_domains(of_fwnode_handle(node));
- if (ret)
- goto out_irs;
-
gicv5_set_cpuif_pribits();
gicv5_set_cpuif_idbits();
gicv5_smp_init();
gicv5_irs_its_probe();
-
- gic_of_setup_kvm_info(node);
-
return 0;
out_int:
gicv5_cpu_disable_interrupts();
out_dom:
gicv5_free_domains();
+ return ret;
+}
+
+static int __init gicv5_of_init(struct device_node *node, struct device_node *parent)
+{
+ int ret = gicv5_irs_of_probe(node);
+ if (ret)
+ return ret;
+
+ ret = gicv5_init_common(of_fwnode_handle(node));
+ if (ret)
+ goto out_irs;
+
+ gic_of_setup_kvm_info(node);
+
+ return 0;
out_irs:
gicv5_irs_remove();
return ret;
}
IRQCHIP_DECLARE(gic_v5, "arm,gic-v5", gicv5_of_init);
+
+#ifdef CONFIG_ACPI
+static bool __init acpi_validate_gic_table(struct acpi_subtable_header *header,
+ struct acpi_probe_entry *ape)
+{
+ struct acpi_madt_gicv5_irs *irs = (struct acpi_madt_gicv5_irs *)header;
+
+ return (irs->version == ape->driver_data);
+}
+
+static struct fwnode_handle *gsi_domain_handle;
+
+static struct fwnode_handle *gic_v5_get_gsi_domain_id(u32 gsi)
+{
+ return gsi_domain_handle;
+}
+
+static int __init gic_acpi_init(union acpi_subtable_headers *header, const unsigned long end)
+{
+ struct acpi_madt_gicv5_irs *irs = (struct acpi_madt_gicv5_irs *)header;
+ int ret;
+
+ if (gsi_domain_handle)
+ return 0;
+
+ gsi_domain_handle = irq_domain_alloc_fwnode(&irs->config_base_address);
+ if (!gsi_domain_handle)
+ return -ENOMEM;
+
+ ret = gicv5_irs_acpi_probe();
+ if (ret)
+ goto out_fwnode;
+
+ ret = gicv5_init_common(gsi_domain_handle);
+ if (ret)
+ goto out_irs;
+
+ acpi_set_irq_model(ACPI_IRQ_MODEL_GIC_V5, gic_v5_get_gsi_domain_id);
+
+ return 0;
+
+out_irs:
+ gicv5_irs_remove();
+out_fwnode:
+ irq_domain_free_fwnode(gsi_domain_handle);
+ return ret;
+}
+IRQCHIP_ACPI_DECLARE(gic_v5, ACPI_MADT_TYPE_GICV5_IRS,
+ acpi_validate_gic_table, ACPI_MADT_GIC_VERSION_V5,
+ gic_acpi_init);
+#endif