summaryrefslogtreecommitdiff
path: root/drivers/irqchip
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/irqchip')
-rw-r--r--drivers/irqchip/Kconfig12
-rw-r--r--drivers/irqchip/Makefile5
-rw-r--r--drivers/irqchip/irq-gic-common.h2
-rw-r--r--drivers/irqchip/irq-gic-its-msi-parent.c (renamed from drivers/irqchip/irq-gic-v3-its-msi-parent.c)168
-rw-r--r--drivers/irqchip/irq-gic-its-msi-parent.h12
-rw-r--r--drivers/irqchip/irq-gic-v3-its.c1
-rw-r--r--drivers/irqchip/irq-gic-v4.c4
-rw-r--r--drivers/irqchip/irq-gic-v5-irs.c822
-rw-r--r--drivers/irqchip/irq-gic-v5-its.c1228
-rw-r--r--drivers/irqchip/irq-gic-v5-iwb.c284
-rw-r--r--drivers/irqchip/irq-gic-v5.c1137
-rw-r--r--drivers/irqchip/irq-gic.c2
-rw-r--r--drivers/irqchip/irq-msi-lib.c5
13 files changed, 3674 insertions, 8 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 94a41c7d430e..39a6ae1d574b 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -41,10 +41,14 @@ config ARM_GIC_V3
select HAVE_ARM_SMCCC_DISCOVERY
select IRQ_MSI_IOMMU
+config ARM_GIC_ITS_PARENT
+ bool
+
config ARM_GIC_V3_ITS
bool
select GENERIC_MSI_IRQ
select IRQ_MSI_LIB
+ select ARM_GIC_ITS_PARENT
default ARM_GIC_V3
select IRQ_MSI_IOMMU
@@ -54,6 +58,14 @@ config ARM_GIC_V3_ITS_FSL_MC
depends on FSL_MC_BUS
default ARM_GIC_V3_ITS
+config ARM_GIC_V5
+ bool
+ select IRQ_DOMAIN_HIERARCHY
+ select GENERIC_IRQ_EFFECTIVE_AFF_MASK
+ select GENERIC_MSI_IRQ
+ select IRQ_MSI_LIB
+ select ARM_GIC_ITS_PARENT
+
config ARM_NVIC
bool
select IRQ_DOMAIN_HIERARCHY
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 0458d6c5d161..93e3ced023bb 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -33,9 +33,12 @@ obj-$(CONFIG_ARCH_REALVIEW) += irq-gic-realview.o
obj-$(CONFIG_IRQ_MSI_LIB) += irq-msi-lib.o
obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o
obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o
-obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o
+obj-$(CONFIG_ARM_GIC_ITS_PARENT) += irq-gic-its-msi-parent.o
+obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o
obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
+obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o irq-gic-v5-its.o \
+ irq-gic-v5-iwb.o
obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
obj-$(CONFIG_ARM_VIC) += irq-vic.o
diff --git a/drivers/irqchip/irq-gic-common.h b/drivers/irqchip/irq-gic-common.h
index 020ecdf16901..710cab61d919 100644
--- a/drivers/irqchip/irq-gic-common.h
+++ b/drivers/irqchip/irq-gic-common.h
@@ -29,8 +29,6 @@ void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
void gic_enable_of_quirks(const struct device_node *np,
const struct gic_quirk *quirks, void *data);
-extern const struct msi_parent_ops gic_v3_its_msi_parent_ops;
-
#define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING (1 << 0)
#define RDIST_FLAGS_RD_TABLES_PREALLOCATED (1 << 1)
#define RDIST_FLAGS_FORCE_NON_SHAREABLE (1 << 2)
diff --git a/drivers/irqchip/irq-gic-v3-its-msi-parent.c b/drivers/irqchip/irq-gic-its-msi-parent.c
index a5e110ffdd88..eb1473f1448a 100644
--- a/drivers/irqchip/irq-gic-v3-its-msi-parent.c
+++ b/drivers/irqchip/irq-gic-its-msi-parent.c
@@ -5,9 +5,10 @@
// Copyright (C) 2022 Intel
#include <linux/acpi_iort.h>
+#include <linux/of_address.h>
#include <linux/pci.h>
-#include "irq-gic-common.h"
+#include "irq-gic-its-msi-parent.h"
#include <linux/irqchip/irq-msi-lib.h>
#define ITS_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
@@ -18,6 +19,23 @@
MSI_FLAG_PCI_MSIX | \
MSI_FLAG_MULTI_PCI_MSI)
+static int its_translate_frame_address(struct device_node *msi_node, phys_addr_t *pa)
+{
+ struct resource res;
+ int ret;
+
+ ret = of_property_match_string(msi_node, "reg-names", "ns-translate");
+ if (ret < 0)
+ return ret;
+
+ ret = of_address_to_resource(msi_node, ret, &res);
+ if (ret)
+ return ret;
+
+ *pa = res.start;
+ return 0;
+}
+
#ifdef CONFIG_PCI_MSI
static int its_pci_msi_vec_count(struct pci_dev *pdev, void *data)
{
@@ -82,8 +100,46 @@ static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
msi_info = msi_get_domain_info(domain->parent);
return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
}
+
+static int its_v5_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
+ int nvec, msi_alloc_info_t *info)
+{
+ struct device_node *msi_node = NULL;
+ struct msi_domain_info *msi_info;
+ struct pci_dev *pdev;
+ phys_addr_t pa;
+ u32 rid;
+ int ret;
+
+ if (!dev_is_pci(dev))
+ return -EINVAL;
+
+ pdev = to_pci_dev(dev);
+
+ rid = pci_msi_map_rid_ctlr_node(pdev, &msi_node);
+ if (!msi_node)
+ return -ENODEV;
+
+ ret = its_translate_frame_address(msi_node, &pa);
+ if (ret)
+ return -ENODEV;
+
+ of_node_put(msi_node);
+
+ /* ITS specific DeviceID */
+ info->scratchpad[0].ul = rid;
+ /* ITS translate frame physical address */
+ info->scratchpad[1].ul = pa;
+
+ /* Always allocate power of two vectors */
+ nvec = roundup_pow_of_two(nvec);
+
+ msi_info = msi_get_domain_info(domain->parent);
+ return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
+}
#else /* CONFIG_PCI_MSI */
#define its_pci_msi_prepare NULL
+#define its_v5_pci_msi_prepare NULL
#endif /* !CONFIG_PCI_MSI */
static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev,
@@ -118,6 +174,53 @@ static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev,
return ret;
}
+static int of_v5_pmsi_get_msi_info(struct irq_domain *domain, struct device *dev,
+ u32 *dev_id, phys_addr_t *pa)
+{
+ int ret, index = 0;
+ /*
+ * Retrieve the DeviceID and the ITS translate frame node pointer
+ * out of the msi-parent property.
+ */
+ do {
+ struct of_phandle_args args;
+
+ ret = of_parse_phandle_with_args(dev->of_node,
+ "msi-parent", "#msi-cells",
+ index, &args);
+ if (ret)
+ break;
+ /*
+ * The IRQ domain fwnode is the msi controller parent
+ * in GICv5 (where the msi controller nodes are the
+ * ITS translate frames).
+ */
+ if (args.np->parent == irq_domain_get_of_node(domain)) {
+ if (WARN_ON(args.args_count != 1))
+ return -EINVAL;
+ *dev_id = args.args[0];
+
+ ret = its_translate_frame_address(args.np, pa);
+ if (ret)
+ return -ENODEV;
+ break;
+ }
+ index++;
+ } while (!ret);
+
+ if (ret) {
+ struct device_node *np = NULL;
+
+ ret = of_map_id(dev->of_node, dev->id, "msi-map", "msi-map-mask", &np, dev_id);
+ if (np) {
+ ret = its_translate_frame_address(np, pa);
+ of_node_put(np);
+ }
+ }
+
+ return ret;
+}
+
int __weak iort_pmsi_get_dev_id(struct device *dev, u32 *dev_id)
{
return -1;
@@ -148,6 +251,33 @@ static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev,
dev, nvec, info);
}
+static int its_v5_pmsi_prepare(struct irq_domain *domain, struct device *dev,
+ int nvec, msi_alloc_info_t *info)
+{
+ struct msi_domain_info *msi_info;
+ phys_addr_t pa;
+ u32 dev_id;
+ int ret;
+
+ if (!dev->of_node)
+ return -ENODEV;
+
+ ret = of_v5_pmsi_get_msi_info(domain->parent, dev, &dev_id, &pa);
+ if (ret)
+ return ret;
+
+ /* ITS specific DeviceID */
+ info->scratchpad[0].ul = dev_id;
+ /* ITS translate frame physical address */
+ info->scratchpad[1].ul = pa;
+
+ /* Allocate always as a power of 2 */
+ nvec = roundup_pow_of_two(nvec);
+
+ msi_info = msi_get_domain_info(domain->parent);
+ return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
+}
+
static void its_msi_teardown(struct irq_domain *domain, msi_alloc_info_t *info)
{
struct msi_domain_info *msi_info;
@@ -199,6 +329,32 @@ static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
return true;
}
+static bool its_v5_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
+ struct irq_domain *real_parent, struct msi_domain_info *info)
+{
+ if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
+ return false;
+
+ switch (info->bus_token) {
+ case DOMAIN_BUS_PCI_DEVICE_MSI:
+ case DOMAIN_BUS_PCI_DEVICE_MSIX:
+ info->ops->msi_prepare = its_v5_pci_msi_prepare;
+ info->ops->msi_teardown = its_msi_teardown;
+ break;
+ case DOMAIN_BUS_DEVICE_MSI:
+ case DOMAIN_BUS_WIRED_TO_MSI:
+ info->ops->msi_prepare = its_v5_pmsi_prepare;
+ info->ops->msi_teardown = its_msi_teardown;
+ break;
+ default:
+ /* Confused. How did the lib return true? */
+ WARN_ON_ONCE(1);
+ return false;
+ }
+
+ return true;
+}
+
const struct msi_parent_ops gic_v3_its_msi_parent_ops = {
.supported_flags = ITS_MSI_FLAGS_SUPPORTED,
.required_flags = ITS_MSI_FLAGS_REQUIRED,
@@ -208,3 +364,13 @@ const struct msi_parent_ops gic_v3_its_msi_parent_ops = {
.prefix = "ITS-",
.init_dev_msi_info = its_init_dev_msi_info,
};
+
+const struct msi_parent_ops gic_v5_its_msi_parent_ops = {
+ .supported_flags = ITS_MSI_FLAGS_SUPPORTED,
+ .required_flags = ITS_MSI_FLAGS_REQUIRED,
+ .chip_flags = MSI_CHIP_FLAG_SET_EOI,
+ .bus_select_token = DOMAIN_BUS_NEXUS,
+ .bus_select_mask = MATCH_PCI_MSI | MATCH_PLATFORM_MSI,
+ .prefix = "ITS-v5-",
+ .init_dev_msi_info = its_v5_init_dev_msi_info,
+};
diff --git a/drivers/irqchip/irq-gic-its-msi-parent.h b/drivers/irqchip/irq-gic-its-msi-parent.h
new file mode 100644
index 000000000000..df016f347337
--- /dev/null
+++ b/drivers/irqchip/irq-gic-its-msi-parent.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 ARM Limited, All Rights Reserved.
+ */
+
+#ifndef _IRQ_GIC_ITS_MSI_PARENT_H
+#define _IRQ_GIC_ITS_MSI_PARENT_H
+
+extern const struct msi_parent_ops gic_v3_its_msi_parent_ops;
+extern const struct msi_parent_ops gic_v5_its_msi_parent_ops;
+
+#endif /* _IRQ_GIC_ITS_MSI_PARENT_H */
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
index d54fa0638dc4..467cb78435a9 100644
--- a/drivers/irqchip/irq-gic-v3-its.c
+++ b/drivers/irqchip/irq-gic-v3-its.c
@@ -41,6 +41,7 @@
#include <asm/exception.h>
#include "irq-gic-common.h"
+#include "irq-gic-its-msi-parent.h"
#include <linux/irqchip/irq-msi-lib.h>
#define ITS_FLAGS_CMDQ_NEEDS_FLUSHING (1ULL << 0)
diff --git a/drivers/irqchip/irq-gic-v4.c b/drivers/irqchip/irq-gic-v4.c
index 58c28895f8c4..8455b4a5fbb0 100644
--- a/drivers/irqchip/irq-gic-v4.c
+++ b/drivers/irqchip/irq-gic-v4.c
@@ -342,10 +342,10 @@ int its_get_vlpi(int irq, struct its_vlpi_map *map)
return irq_set_vcpu_affinity(irq, &info);
}
-int its_unmap_vlpi(int irq)
+void its_unmap_vlpi(int irq)
{
irq_clear_status_flags(irq, IRQ_DISABLE_UNLAZY);
- return irq_set_vcpu_affinity(irq, NULL);
+ WARN_ON_ONCE(irq_set_vcpu_affinity(irq, NULL));
}
int its_prop_update_vlpi(int irq, u8 config, bool inv)
diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
new file mode 100644
index 000000000000..f845415f9143
--- /dev/null
+++ b/drivers/irqchip/irq-gic-v5-irs.c
@@ -0,0 +1,822 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
+ */
+
+#define pr_fmt(fmt) "GICv5 IRS: " fmt
+
+#include <linux/log2.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include <linux/irqchip.h>
+#include <linux/irqchip/arm-gic-v5.h>
+
+/*
+ * Hardcoded ID_BITS limit for systems supporting only a 1-level IST
+ * table. Systems supporting only a 1-level IST table aren't expected
+ * to require more than 2^12 LPIs. Tweak as required.
+ */
+#define LPI_ID_BITS_LINEAR 12
+
+#define IRS_FLAGS_NON_COHERENT BIT(0)
+
+static DEFINE_PER_CPU_READ_MOSTLY(struct gicv5_irs_chip_data *, per_cpu_irs_data);
+static LIST_HEAD(irs_nodes);
+
+static u32 irs_readl_relaxed(struct gicv5_irs_chip_data *irs_data,
+ const u32 reg_offset)
+{
+ return readl_relaxed(irs_data->irs_base + reg_offset);
+}
+
+static void irs_writel_relaxed(struct gicv5_irs_chip_data *irs_data,
+ const u32 val, const u32 reg_offset)
+{
+ writel_relaxed(val, irs_data->irs_base + reg_offset);
+}
+
+static u64 irs_readq_relaxed(struct gicv5_irs_chip_data *irs_data,
+ const u32 reg_offset)
+{
+ return readq_relaxed(irs_data->irs_base + reg_offset);
+}
+
+static void irs_writeq_relaxed(struct gicv5_irs_chip_data *irs_data,
+ const u64 val, const u32 reg_offset)
+{
+ writeq_relaxed(val, irs_data->irs_base + reg_offset);
+}
+
+/*
+ * The polling wait (in gicv5_wait_for_op_s_atomic()) on a GIC register
+ * provides the memory barriers (through MMIO accessors)
+ * required to synchronize CPU and GIC access to IST memory.
+ */
+static int gicv5_irs_ist_synchronise(struct gicv5_irs_chip_data *irs_data)
+{
+ return gicv5_wait_for_op_atomic(irs_data->irs_base, GICV5_IRS_IST_STATUSR,
+ GICV5_IRS_IST_STATUSR_IDLE, NULL);
+}
+
+static int __init gicv5_irs_init_ist_linear(struct gicv5_irs_chip_data *irs_data,
+ unsigned int lpi_id_bits,
+ unsigned int istsz)
+{
+ size_t l2istsz;
+ u32 n, cfgr;
+ void *ist;
+ u64 baser;
+ int ret;
+
+ /* Taken from GICv5 specifications 10.2.1.13 IRS_IST_BASER */
+ n = max(5, lpi_id_bits + 1 + istsz);
+
+ l2istsz = BIT(n + 1);
+ /*
+ * Check memory requirements. For a linear IST we cap the
+ * number of ID bits to a value that should never exceed
+ * kmalloc interface memory allocation limits, so this
+ * check is really belt and braces.
+ */
+ if (l2istsz > KMALLOC_MAX_SIZE) {
+ u8 lpi_id_cap = ilog2(KMALLOC_MAX_SIZE) - 2 + istsz;
+
+ pr_warn("Limiting LPI ID bits from %u to %u\n",
+ lpi_id_bits, lpi_id_cap);
+ lpi_id_bits = lpi_id_cap;
+ l2istsz = KMALLOC_MAX_SIZE;
+ }
+
+ ist = kzalloc(l2istsz, GFP_KERNEL);
+ if (!ist)
+ return -ENOMEM;
+
+ if (irs_data->flags & IRS_FLAGS_NON_COHERENT)
+ dcache_clean_inval_poc((unsigned long)ist,
+ (unsigned long)ist + l2istsz);
+ else
+ dsb(ishst);
+
+ cfgr = FIELD_PREP(GICV5_IRS_IST_CFGR_STRUCTURE,
+ GICV5_IRS_IST_CFGR_STRUCTURE_LINEAR) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, istsz) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_L2SZ,
+ GICV5_IRS_IST_CFGR_L2SZ_4K) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, lpi_id_bits);
+ irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_IST_CFGR);
+
+ gicv5_global_data.ist.l2 = false;
+
+ baser = (virt_to_phys(ist) & GICV5_IRS_IST_BASER_ADDR_MASK) |
+ FIELD_PREP(GICV5_IRS_IST_BASER_VALID, 0x1);
+ irs_writeq_relaxed(irs_data, baser, GICV5_IRS_IST_BASER);
+
+ ret = gicv5_irs_ist_synchronise(irs_data);
+ if (ret) {
+ kfree(ist);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __init gicv5_irs_init_ist_two_level(struct gicv5_irs_chip_data *irs_data,
+ unsigned int lpi_id_bits,
+ unsigned int istsz,
+ unsigned int l2sz)
+{
+ __le64 *l1ist;
+ u32 cfgr, n;
+ size_t l1sz;
+ u64 baser;
+ int ret;
+
+ /* Taken from GICv5 specifications 10.2.1.13 IRS_IST_BASER */
+ n = max(5, lpi_id_bits - ((10 - istsz) + (2 * l2sz)) + 2);
+
+ l1sz = BIT(n + 1);
+
+ l1ist = kzalloc(l1sz, GFP_KERNEL);
+ if (!l1ist)
+ return -ENOMEM;
+
+ if (irs_data->flags & IRS_FLAGS_NON_COHERENT)
+ dcache_clean_inval_poc((unsigned long)l1ist,
+ (unsigned long)l1ist + l1sz);
+ else
+ dsb(ishst);
+
+ cfgr = FIELD_PREP(GICV5_IRS_IST_CFGR_STRUCTURE,
+ GICV5_IRS_IST_CFGR_STRUCTURE_TWO_LEVEL) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, istsz) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_L2SZ, l2sz) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, lpi_id_bits);
+ irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_IST_CFGR);
+
+ /*
+ * The L2SZ determine bits required at L2 level. Number of bytes
+ * required by metadata is reported through istsz - the number of bits
+ * covered by L2 entries scales accordingly.
+ */
+ gicv5_global_data.ist.l2_size = BIT(11 + (2 * l2sz) + 1);
+ gicv5_global_data.ist.l2_bits = (10 - istsz) + (2 * l2sz);
+ gicv5_global_data.ist.l1ist_addr = l1ist;
+ gicv5_global_data.ist.l2 = true;
+
+ baser = (virt_to_phys(l1ist) & GICV5_IRS_IST_BASER_ADDR_MASK) |
+ FIELD_PREP(GICV5_IRS_IST_BASER_VALID, 0x1);
+ irs_writeq_relaxed(irs_data, baser, GICV5_IRS_IST_BASER);
+
+ ret = gicv5_irs_ist_synchronise(irs_data);
+ if (ret) {
+ kfree(l1ist);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Alloc L2 IST entries on demand.
+ *
+ * Locking/serialization is guaranteed by irqdomain core code by
+ * taking the hierarchical domain struct irq_domain.root->mutex.
+ */
+int gicv5_irs_iste_alloc(const u32 lpi)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ unsigned int index;
+ u32 l2istr, l2bits;
+ __le64 *l1ist;
+ size_t l2size;
+ void *l2ist;
+ int ret;
+
+ if (!gicv5_global_data.ist.l2)
+ return 0;
+
+ irs_data = per_cpu(per_cpu_irs_data, smp_processor_id());
+ if (!irs_data)
+ return -ENOENT;
+
+ l2size = gicv5_global_data.ist.l2_size;
+ l2bits = gicv5_global_data.ist.l2_bits;
+ l1ist = gicv5_global_data.ist.l1ist_addr;
+ index = lpi >> l2bits;
+
+ if (FIELD_GET(GICV5_ISTL1E_VALID, le64_to_cpu(l1ist[index])))
+ return 0;
+
+ l2ist = kzalloc(l2size, GFP_KERNEL);
+ if (!l2ist)
+ return -ENOMEM;
+
+ l1ist[index] = cpu_to_le64(virt_to_phys(l2ist) & GICV5_ISTL1E_L2_ADDR_MASK);
+
+ if (irs_data->flags & IRS_FLAGS_NON_COHERENT) {
+ dcache_clean_inval_poc((unsigned long)l2ist,
+ (unsigned long)l2ist + l2size);
+ dcache_clean_poc((unsigned long)(l1ist + index),
+ (unsigned long)(l1ist + index) + sizeof(*l1ist));
+ } else {
+ dsb(ishst);
+ }
+
+ l2istr = FIELD_PREP(GICV5_IRS_MAP_L2_ISTR_ID, lpi);
+ irs_writel_relaxed(irs_data, l2istr, GICV5_IRS_MAP_L2_ISTR);
+
+ ret = gicv5_irs_ist_synchronise(irs_data);
+ if (ret) {
+ l1ist[index] = 0;
+ kfree(l2ist);
+ return ret;
+ }
+
+ /*
+ * Make sure we invalidate the cache line pulled before the IRS
+ * had a chance to update the L1 entry and mark it valid.
+ */
+ if (irs_data->flags & IRS_FLAGS_NON_COHERENT) {
+ /*
+ * gicv5_irs_ist_synchronise() includes memory
+ * barriers (MMIO accessors) required to guarantee that the
+ * following dcache invalidation is not executed before the
+ * IST mapping operation has completed.
+ */
+ dcache_inval_poc((unsigned long)(l1ist + index),
+ (unsigned long)(l1ist + index) + sizeof(*l1ist));
+ }
+
+ return 0;
+}
+
+/*
+ * Try to match the L2 IST size to the pagesize, and if this is not possible
+ * pick the smallest supported L2 size in order to minimise the requirement for
+ * physically contiguous blocks of memory as page-sized allocations are
+ * guaranteed to be physically contiguous, and are by definition the easiest to
+ * find.
+ *
+ * Fall back to the smallest supported size (in the event that the pagesize
+ * itself is not supported) again serves to make it easier to find physically
+ * contiguous blocks of memory.
+ */
+static unsigned int gicv5_irs_l2_sz(u32 idr2)
+{
+ switch (PAGE_SIZE) {
+ case SZ_64K:
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2))
+ return GICV5_IRS_IST_CFGR_L2SZ_64K;
+ fallthrough;
+ case SZ_4K:
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2))
+ return GICV5_IRS_IST_CFGR_L2SZ_4K;
+ fallthrough;
+ case SZ_16K:
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
+ return GICV5_IRS_IST_CFGR_L2SZ_16K;
+ break;
+ }
+
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2))
+ return GICV5_IRS_IST_CFGR_L2SZ_4K;
+
+ return GICV5_IRS_IST_CFGR_L2SZ_64K;
+}
+
+static int __init gicv5_irs_init_ist(struct gicv5_irs_chip_data *irs_data)
+{
+ u32 lpi_id_bits, idr2_id_bits, idr2_min_lpi_id_bits, l2_iste_sz, l2sz;
+ u32 l2_iste_sz_split, idr2;
+ bool two_levels, istmd;
+ u64 baser;
+ int ret;
+
+ baser = irs_readq_relaxed(irs_data, GICV5_IRS_IST_BASER);
+ if (FIELD_GET(GICV5_IRS_IST_BASER_VALID, baser)) {
+ pr_err("IST is marked as valid already; cannot allocate\n");
+ return -EPERM;
+ }
+
+ idr2 = irs_readl_relaxed(irs_data, GICV5_IRS_IDR2);
+ two_levels = !!FIELD_GET(GICV5_IRS_IDR2_IST_LEVELS, idr2);
+
+ idr2_id_bits = FIELD_GET(GICV5_IRS_IDR2_ID_BITS, idr2);
+ idr2_min_lpi_id_bits = FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, idr2);
+
+ /*
+ * For two level tables we are always supporting the maximum allowed
+ * number of IDs.
+ *
+ * For 1-level tables, we should support a number of bits that
+ * is >= min_lpi_id_bits but cap it to LPI_ID_BITS_LINEAR lest
+ * the level 1-table gets too large and its memory allocation
+ * may fail.
+ */
+ if (two_levels) {
+ lpi_id_bits = idr2_id_bits;
+ } else {
+ lpi_id_bits = max(LPI_ID_BITS_LINEAR, idr2_min_lpi_id_bits);
+ lpi_id_bits = min(lpi_id_bits, idr2_id_bits);
+ }
+
+ /*
+ * Cap the ID bits according to the CPUIF supported ID bits
+ */
+ lpi_id_bits = min(lpi_id_bits, gicv5_global_data.cpuif_id_bits);
+
+ if (two_levels)
+ l2sz = gicv5_irs_l2_sz(idr2);
+
+ istmd = !!FIELD_GET(GICV5_IRS_IDR2_ISTMD, idr2);
+
+ l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_4;
+
+ if (istmd) {
+ l2_iste_sz_split = FIELD_GET(GICV5_IRS_IDR2_ISTMD_SZ, idr2);
+
+ if (lpi_id_bits < l2_iste_sz_split)
+ l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_8;
+ else
+ l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_16;
+ }
+
+ /*
+ * Follow GICv5 specification recommendation to opt in for two
+ * level tables (ref: 10.2.1.14 IRS_IST_CFGR).
+ */
+ if (two_levels && (lpi_id_bits > ((10 - l2_iste_sz) + (2 * l2sz)))) {
+ ret = gicv5_irs_init_ist_two_level(irs_data, lpi_id_bits,
+ l2_iste_sz, l2sz);
+ } else {
+ ret = gicv5_irs_init_ist_linear(irs_data, lpi_id_bits,
+ l2_iste_sz);
+ }
+ if (ret)
+ return ret;
+
+ gicv5_init_lpis(BIT(lpi_id_bits));
+
+ return 0;
+}
+
+struct iaffid_entry {
+ u16 iaffid;
+ bool valid;
+};
+
+static DEFINE_PER_CPU(struct iaffid_entry, cpu_iaffid);
+
+int gicv5_irs_cpu_to_iaffid(int cpuid, u16 *iaffid)
+{
+ if (!per_cpu(cpu_iaffid, cpuid).valid) {
+ pr_err("IAFFID for CPU %d has not been initialised\n", cpuid);
+ return -ENODEV;
+ }
+
+ *iaffid = per_cpu(cpu_iaffid, cpuid).iaffid;
+
+ return 0;
+}
+
+struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ u32 min, max;
+
+ list_for_each_entry(irs_data, &irs_nodes, entry) {
+ if (!irs_data->spi_range)
+ continue;
+
+ min = irs_data->spi_min;
+ max = irs_data->spi_min + irs_data->spi_range - 1;
+ if (spi_id >= min && spi_id <= max)
+ return irs_data;
+ }
+
+ return NULL;
+}
+
+static int gicv5_irs_wait_for_spi_op(struct gicv5_irs_chip_data *irs_data)
+{
+ u32 statusr;
+ int ret;
+
+ ret = gicv5_wait_for_op_atomic(irs_data->irs_base, GICV5_IRS_SPI_STATUSR,
+ GICV5_IRS_SPI_STATUSR_IDLE, &statusr);
+ if (ret)
+ return ret;
+
+ return !!FIELD_GET(GICV5_IRS_SPI_STATUSR_V, statusr) ? 0 : -EIO;
+}
+
+static int gicv5_irs_wait_for_irs_pe(struct gicv5_irs_chip_data *irs_data,
+ bool selr)
+{
+ bool valid = true;
+ u32 statusr;
+ int ret;
+
+ ret = gicv5_wait_for_op_atomic(irs_data->irs_base, GICV5_IRS_PE_STATUSR,
+ GICV5_IRS_PE_STATUSR_IDLE, &statusr);
+ if (ret)
+ return ret;
+
+ if (selr)
+ valid = !!FIELD_GET(GICV5_IRS_PE_STATUSR_V, statusr);
+
+ return valid ? 0 : -EIO;
+}
+
+static int gicv5_irs_wait_for_pe_selr(struct gicv5_irs_chip_data *irs_data)
+{
+ return gicv5_irs_wait_for_irs_pe(irs_data, true);
+}
+
+static int gicv5_irs_wait_for_pe_cr0(struct gicv5_irs_chip_data *irs_data)
+{
+ return gicv5_irs_wait_for_irs_pe(irs_data, false);
+}
+
+int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gicv5_irs_chip_data *irs_data = d->chip_data;
+ u32 selr, cfgr;
+ bool level;
+ int ret;
+
+ /*
+ * There is no distinction between HIGH/LOW for level IRQs
+ * and RISING/FALLING for edge IRQs in the architecture,
+ * hence consider them equivalent.
+ */
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ level = false;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ case IRQ_TYPE_LEVEL_LOW:
+ level = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(raw_spinlock)(&irs_data->spi_config_lock);
+
+ selr = FIELD_PREP(GICV5_IRS_SPI_SELR_ID, d->hwirq);
+ irs_writel_relaxed(irs_data, selr, GICV5_IRS_SPI_SELR);
+ ret = gicv5_irs_wait_for_spi_op(irs_data);
+ if (ret)
+ return ret;
+
+ cfgr = FIELD_PREP(GICV5_IRS_SPI_CFGR_TM, level);
+ irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_SPI_CFGR);
+
+ return gicv5_irs_wait_for_spi_op(irs_data);
+}
+
+static int gicv5_irs_wait_for_idle(struct gicv5_irs_chip_data *irs_data)
+{
+ return gicv5_wait_for_op_atomic(irs_data->irs_base, GICV5_IRS_CR0,
+ GICV5_IRS_CR0_IDLE, NULL);
+}
+
+void gicv5_irs_syncr(void)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ u32 syncr;
+
+ irs_data = list_first_entry_or_null(&irs_nodes, struct gicv5_irs_chip_data, entry);
+ if (WARN_ON_ONCE(!irs_data))
+ return;
+
+ syncr = FIELD_PREP(GICV5_IRS_SYNCR_SYNC, 1);
+ irs_writel_relaxed(irs_data, syncr, GICV5_IRS_SYNCR);
+
+ gicv5_wait_for_op(irs_data->irs_base, GICV5_IRS_SYNC_STATUSR,
+ GICV5_IRS_SYNC_STATUSR_IDLE);
+}
+
+int gicv5_irs_register_cpu(int cpuid)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ u32 selr, cr0;
+ u16 iaffid;
+ int ret;
+
+ ret = gicv5_irs_cpu_to_iaffid(cpuid, &iaffid);
+ if (ret) {
+ pr_err("IAFFID for CPU %d has not been initialised\n", cpuid);
+ return ret;
+ }
+
+ irs_data = per_cpu(per_cpu_irs_data, cpuid);
+ if (!irs_data) {
+ pr_err("No IRS associated with CPU %u\n", cpuid);
+ return -ENXIO;
+ }
+
+ selr = FIELD_PREP(GICV5_IRS_PE_SELR_IAFFID, iaffid);
+ irs_writel_relaxed(irs_data, selr, GICV5_IRS_PE_SELR);
+
+ ret = gicv5_irs_wait_for_pe_selr(irs_data);
+ if (ret) {
+ pr_err("IAFFID 0x%x used in IRS_PE_SELR is invalid\n", iaffid);
+ return -ENXIO;
+ }
+
+ cr0 = FIELD_PREP(GICV5_IRS_PE_CR0_DPS, 0x1);
+ irs_writel_relaxed(irs_data, cr0, GICV5_IRS_PE_CR0);
+
+ ret = gicv5_irs_wait_for_pe_cr0(irs_data);
+ if (ret)
+ return ret;
+
+ pr_debug("CPU %d enabled PE IAFFID 0x%x\n", cpuid, iaffid);
+
+ return 0;
+}
+
+static void __init gicv5_irs_init_bases(struct gicv5_irs_chip_data *irs_data,
+ void __iomem *irs_base,
+ struct fwnode_handle *handle)
+{
+ struct device_node *np = to_of_node(handle);
+ u32 cr0, cr1;
+
+ irs_data->fwnode = handle;
+ irs_data->irs_base = irs_base;
+
+ if (of_property_read_bool(np, "dma-noncoherent")) {
+ /*
+ * A non-coherent IRS implies that some cache levels cannot be
+ * used coherently by the cores and GIC. Our only option is to mark
+ * memory attributes for the GIC as non-cacheable; by default,
+ * non-cacheable memory attributes imply outer-shareable
+ * shareability, the value written into IRS_CR1_SH is ignored.
+ */
+ cr1 = FIELD_PREP(GICV5_IRS_CR1_VPED_WA, GICV5_NO_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VPED_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMD_WA, GICV5_NO_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMD_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VPET_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMT_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IST_WA, GICV5_NO_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IST_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IC, GICV5_NON_CACHE) |
+ FIELD_PREP(GICV5_IRS_CR1_OC, GICV5_NON_CACHE);
+ irs_data->flags |= IRS_FLAGS_NON_COHERENT;
+ } else {
+ cr1 = FIELD_PREP(GICV5_IRS_CR1_VPED_WA, GICV5_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VPED_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMD_WA, GICV5_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMD_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VPET_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMT_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IST_WA, GICV5_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IST_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IC, GICV5_WB_CACHE) |
+ FIELD_PREP(GICV5_IRS_CR1_OC, GICV5_WB_CACHE) |
+ FIELD_PREP(GICV5_IRS_CR1_SH, GICV5_INNER_SHARE);
+ }
+
+ irs_writel_relaxed(irs_data, cr1, GICV5_IRS_CR1);
+
+ cr0 = FIELD_PREP(GICV5_IRS_CR0_IRSEN, 0x1);
+ irs_writel_relaxed(irs_data, cr0, GICV5_IRS_CR0);
+ gicv5_irs_wait_for_idle(irs_data);
+}
+
+static int __init gicv5_irs_of_init_affinity(struct device_node *node,
+ struct gicv5_irs_chip_data *irs_data,
+ u8 iaffid_bits)
+{
+ /*
+ * Detect IAFFID<->CPU mappings from the device tree and
+ * record IRS<->CPU topology information.
+ */
+ u16 iaffid_mask = GENMASK(iaffid_bits - 1, 0);
+ int ret, i, ncpus, niaffids;
+
+ ncpus = of_count_phandle_with_args(node, "cpus", NULL);
+ if (ncpus < 0)
+ return -EINVAL;
+
+ niaffids = of_property_count_elems_of_size(node, "arm,iaffids",
+ sizeof(u16));
+ if (niaffids != ncpus)
+ return -EINVAL;
+
+ u16 *iaffids __free(kfree) = kcalloc(niaffids, sizeof(*iaffids), GFP_KERNEL);
+ if (!iaffids)
+ return -ENOMEM;
+
+ ret = of_property_read_u16_array(node, "arm,iaffids", iaffids, niaffids);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ncpus; i++) {
+ struct device_node *cpu_node;
+ int cpu;
+
+ cpu_node = of_parse_phandle(node, "cpus", i);
+ if (WARN_ON(!cpu_node))
+ continue;
+
+ cpu = of_cpu_node_to_id(cpu_node);
+ of_node_put(cpu_node);
+ if (WARN_ON(cpu < 0))
+ continue;
+
+ if (iaffids[i] & ~iaffid_mask) {
+ pr_warn("CPU %d iaffid 0x%x exceeds IRS iaffid bits\n",
+ cpu, iaffids[i]);
+ continue;
+ }
+
+ per_cpu(cpu_iaffid, cpu).iaffid = iaffids[i];
+ per_cpu(cpu_iaffid, cpu).valid = true;
+
+ /* We also know that the CPU is connected to this IRS */
+ per_cpu(per_cpu_irs_data, cpu) = irs_data;
+ }
+
+ return ret;
+}
+
+static void irs_setup_pri_bits(u32 idr1)
+{
+ switch (FIELD_GET(GICV5_IRS_IDR1_PRIORITY_BITS, idr1)) {
+ case GICV5_IRS_IDR1_PRIORITY_BITS_1BITS:
+ gicv5_global_data.irs_pri_bits = 1;
+ break;
+ case GICV5_IRS_IDR1_PRIORITY_BITS_2BITS:
+ gicv5_global_data.irs_pri_bits = 2;
+ break;
+ case GICV5_IRS_IDR1_PRIORITY_BITS_3BITS:
+ gicv5_global_data.irs_pri_bits = 3;
+ break;
+ case GICV5_IRS_IDR1_PRIORITY_BITS_4BITS:
+ gicv5_global_data.irs_pri_bits = 4;
+ break;
+ case GICV5_IRS_IDR1_PRIORITY_BITS_5BITS:
+ gicv5_global_data.irs_pri_bits = 5;
+ break;
+ default:
+ pr_warn("Detected wrong IDR priority bits value 0x%lx\n",
+ FIELD_GET(GICV5_IRS_IDR1_PRIORITY_BITS, idr1));
+ gicv5_global_data.irs_pri_bits = 1;
+ break;
+ }
+}
+
+static int __init gicv5_irs_init(struct device_node *node)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ void __iomem *irs_base;
+ u32 idr, spi_count;
+ u8 iaffid_bits;
+ int ret;
+
+ irs_data = kzalloc(sizeof(*irs_data), GFP_KERNEL);
+ if (!irs_data)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&irs_data->spi_config_lock);
+
+ ret = of_property_match_string(node, "reg-names", "ns-config");
+ if (ret < 0) {
+ pr_err("%pOF: ns-config reg-name not present\n", node);
+ goto out_err;
+ }
+
+ irs_base = of_io_request_and_map(node, ret, of_node_full_name(node));
+ if (IS_ERR(irs_base)) {
+ pr_err("%pOF: unable to map GICv5 IRS registers\n", node);
+ ret = PTR_ERR(irs_base);
+ goto out_err;
+ }
+
+ gicv5_irs_init_bases(irs_data, irs_base, &node->fwnode);
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
+ iaffid_bits = FIELD_GET(GICV5_IRS_IDR1_IAFFID_BITS, idr) + 1;
+
+ ret = gicv5_irs_of_init_affinity(node, irs_data, iaffid_bits);
+ if (ret) {
+ pr_err("Failed to parse CPU IAFFIDs from the device tree!\n");
+ goto out_iomem;
+ }
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR2);
+ if (WARN(!FIELD_GET(GICV5_IRS_IDR2_LPI, idr),
+ "LPI support not available - no IPIs, can't proceed\n")) {
+ ret = -ENODEV;
+ goto out_iomem;
+ }
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR7);
+ irs_data->spi_min = FIELD_GET(GICV5_IRS_IDR7_SPI_BASE, idr);
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR6);
+ irs_data->spi_range = FIELD_GET(GICV5_IRS_IDR6_SPI_IRS_RANGE, idr);
+
+ if (irs_data->spi_range) {
+ pr_info("%s detected SPI range [%u-%u]\n",
+ of_node_full_name(node),
+ irs_data->spi_min,
+ irs_data->spi_min +
+ irs_data->spi_range - 1);
+ }
+
+ /*
+ * Do the global setting only on the first IRS.
+ * Global properties (iaffid_bits, global spi count) are guaranteed to
+ * be consistent across IRSes by the architecture.
+ */
+ if (list_empty(&irs_nodes)) {
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
+ irs_setup_pri_bits(idr);
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR5);
+
+ spi_count = FIELD_GET(GICV5_IRS_IDR5_SPI_RANGE, idr);
+ gicv5_global_data.global_spi_count = spi_count;
+
+ gicv5_init_lpi_domain();
+
+ pr_debug("Detected %u SPIs globally\n", spi_count);
+ }
+
+ list_add_tail(&irs_data->entry, &irs_nodes);
+
+ return 0;
+
+out_iomem:
+ iounmap(irs_base);
+out_err:
+ kfree(irs_data);
+ return ret;
+}
+
+void __init gicv5_irs_remove(void)
+{
+ struct gicv5_irs_chip_data *irs_data, *tmp_data;
+
+ gicv5_free_lpi_domain();
+ gicv5_deinit_lpis();
+
+ list_for_each_entry_safe(irs_data, tmp_data, &irs_nodes, entry) {
+ iounmap(irs_data->irs_base);
+ list_del(&irs_data->entry);
+ kfree(irs_data);
+ }
+}
+
+int __init gicv5_irs_enable(void)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ int ret;
+
+ irs_data = list_first_entry_or_null(&irs_nodes,
+ struct gicv5_irs_chip_data, entry);
+ if (!irs_data)
+ return -ENODEV;
+
+ ret = gicv5_irs_init_ist(irs_data);
+ if (ret) {
+ pr_err("Failed to init IST\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+void __init gicv5_irs_its_probe(void)
+{
+ struct gicv5_irs_chip_data *irs_data;
+
+ list_for_each_entry(irs_data, &irs_nodes, entry)
+ gicv5_its_of_probe(to_of_node(irs_data->fwnode));
+}
+
+int __init gicv5_irs_of_probe(struct device_node *parent)
+{
+ struct device_node *np;
+ int ret;
+
+ for_each_available_child_of_node(parent, np) {
+ if (!of_device_is_compatible(np, "arm,gic-v5-irs"))
+ continue;
+
+ ret = gicv5_irs_init(np);
+ if (ret)
+ pr_err("Failed to init IRS %s\n", np->full_name);
+ }
+
+ return list_empty(&irs_nodes) ? -ENODEV : 0;
+}
diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
new file mode 100644
index 000000000000..340640fdbdf6
--- /dev/null
+++ b/drivers/irqchip/irq-gic-v5-its.c
@@ -0,0 +1,1228 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
+ */
+
+#define pr_fmt(fmt) "GICv5 ITS: " fmt
+
+#include <linux/bitmap.h>
+#include <linux/iommu.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/msi.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+
+#include <linux/irqchip.h>
+#include <linux/irqchip/arm-gic-v5.h>
+#include <linux/irqchip/irq-msi-lib.h>
+
+#include "irq-gic-its-msi-parent.h"
+
+#define ITS_FLAGS_NON_COHERENT BIT(0)
+
+struct gicv5_its_chip_data {
+ struct xarray its_devices;
+ struct mutex dev_alloc_lock;
+ struct fwnode_handle *fwnode;
+ struct gicv5_its_devtab_cfg devtab_cfgr;
+ void __iomem *its_base;
+ u32 flags;
+ unsigned int msi_domain_flags;
+};
+
+struct gicv5_its_dev {
+ struct gicv5_its_chip_data *its_node;
+ struct gicv5_its_itt_cfg itt_cfg;
+ unsigned long *event_map;
+ u32 device_id;
+ u32 num_events;
+ phys_addr_t its_trans_phys_base;
+};
+
+static u32 its_readl_relaxed(struct gicv5_its_chip_data *its_node, const u32 reg_offset)
+{
+ return readl_relaxed(its_node->its_base + reg_offset);
+}
+
+static void its_writel_relaxed(struct gicv5_its_chip_data *its_node, const u32 val,
+ const u32 reg_offset)
+{
+ writel_relaxed(val, its_node->its_base + reg_offset);
+}
+
+static void its_writeq_relaxed(struct gicv5_its_chip_data *its_node, const u64 val,
+ const u32 reg_offset)
+{
+ writeq_relaxed(val, its_node->its_base + reg_offset);
+}
+
+static void gicv5_its_dcache_clean(struct gicv5_its_chip_data *its, void *start,
+ size_t sz)
+{
+ void *end = start + sz;
+
+ if (its->flags & ITS_FLAGS_NON_COHERENT)
+ dcache_clean_inval_poc((unsigned long)start, (unsigned long)end);
+ else
+ dsb(ishst);
+}
+
+static void its_write_table_entry(struct gicv5_its_chip_data *its, __le64 *entry,
+ u64 val)
+{
+ WRITE_ONCE(*entry, cpu_to_le64(val));
+ gicv5_its_dcache_clean(its, entry, sizeof(*entry));
+}
+
+#define devtab_cfgr_field(its, f) \
+ FIELD_GET(GICV5_ITS_DT_CFGR_##f, (its)->devtab_cfgr.cfgr)
+
+static int gicv5_its_cache_sync(struct gicv5_its_chip_data *its)
+{
+ return gicv5_wait_for_op_atomic(its->its_base, GICV5_ITS_STATUSR,
+ GICV5_ITS_STATUSR_IDLE, NULL);
+}
+
+static void gicv5_its_syncr(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev)
+{
+ u64 syncr;
+
+ syncr = FIELD_PREP(GICV5_ITS_SYNCR_SYNC, 1) |
+ FIELD_PREP(GICV5_ITS_SYNCR_DEVICEID, its_dev->device_id);
+
+ its_writeq_relaxed(its, syncr, GICV5_ITS_SYNCR);
+
+ gicv5_wait_for_op(its->its_base, GICV5_ITS_SYNC_STATUSR, GICV5_ITS_SYNC_STATUSR_IDLE);
+}
+
+/* Number of bits required for each L2 {device/interrupt translation} table size */
+#define ITS_L2SZ_64K_L2_BITS 13
+#define ITS_L2SZ_16K_L2_BITS 11
+#define ITS_L2SZ_4K_L2_BITS 9
+
+static unsigned int gicv5_its_l2sz_to_l2_bits(unsigned int sz)
+{
+ switch (sz) {
+ case GICV5_ITS_DT_ITT_CFGR_L2SZ_64k:
+ return ITS_L2SZ_64K_L2_BITS;
+ case GICV5_ITS_DT_ITT_CFGR_L2SZ_16k:
+ return ITS_L2SZ_16K_L2_BITS;
+ case GICV5_ITS_DT_ITT_CFGR_L2SZ_4k:
+ default:
+ return ITS_L2SZ_4K_L2_BITS;
+ }
+}
+
+static int gicv5_its_itt_cache_inv(struct gicv5_its_chip_data *its, u32 device_id,
+ u16 event_id)
+{
+ u32 eventr, eidr;
+ u64 didr;
+
+ didr = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id);
+ eidr = FIELD_PREP(GICV5_ITS_EIDR_EVENTID, event_id);
+ eventr = FIELD_PREP(GICV5_ITS_INV_EVENTR_I, 0x1);
+
+ its_writeq_relaxed(its, didr, GICV5_ITS_DIDR);
+ its_writel_relaxed(its, eidr, GICV5_ITS_EIDR);
+ its_writel_relaxed(its, eventr, GICV5_ITS_INV_EVENTR);
+
+ return gicv5_its_cache_sync(its);
+}
+
+static void gicv5_its_free_itt_linear(struct gicv5_its_dev *its_dev)
+{
+ kfree(its_dev->itt_cfg.linear.itt);
+}
+
+static void gicv5_its_free_itt_two_level(struct gicv5_its_dev *its_dev)
+{
+ unsigned int i, num_ents = its_dev->itt_cfg.l2.num_l1_ents;
+
+ for (i = 0; i < num_ents; i++)
+ kfree(its_dev->itt_cfg.l2.l2ptrs[i]);
+
+ kfree(its_dev->itt_cfg.l2.l2ptrs);
+ kfree(its_dev->itt_cfg.l2.l1itt);
+}
+
+static void gicv5_its_free_itt(struct gicv5_its_dev *its_dev)
+{
+ if (!its_dev->itt_cfg.l2itt)
+ gicv5_its_free_itt_linear(its_dev);
+ else
+ gicv5_its_free_itt_two_level(its_dev);
+}
+
+static int gicv5_its_create_itt_linear(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev,
+ unsigned int event_id_bits)
+{
+ unsigned int num_ents = BIT(event_id_bits);
+ __le64 *itt;
+
+ itt = kcalloc(num_ents, sizeof(*itt), GFP_KERNEL);
+ if (!itt)
+ return -ENOMEM;
+
+ its_dev->itt_cfg.linear.itt = itt;
+ its_dev->itt_cfg.linear.num_ents = num_ents;
+ its_dev->itt_cfg.l2itt = false;
+ its_dev->itt_cfg.event_id_bits = event_id_bits;
+
+ gicv5_its_dcache_clean(its, itt, num_ents * sizeof(*itt));
+
+ return 0;
+}
+
+/*
+ * Allocate a two-level ITT. All ITT entries are allocated in one go, unlike
+ * with the device table. Span may be used to limit the second level table
+ * size, where possible.
+ */
+static int gicv5_its_create_itt_two_level(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev,
+ unsigned int event_id_bits,
+ unsigned int itt_l2sz,
+ unsigned int num_events)
+{
+ unsigned int l1_bits, l2_bits, span, events_per_l2_table;
+ unsigned int i, complete_tables, final_span, num_ents;
+ __le64 *itt_l1, *itt_l2, **l2ptrs;
+ int ret;
+ u64 val;
+
+ ret = gicv5_its_l2sz_to_l2_bits(itt_l2sz);
+ if (ret >= event_id_bits) {
+ pr_debug("Incorrect l2sz (0x%x) for %u EventID bits. Cannot allocate ITT\n",
+ itt_l2sz, event_id_bits);
+ return -EINVAL;
+ }
+
+ l2_bits = ret;
+
+ l1_bits = event_id_bits - l2_bits;
+
+ num_ents = BIT(l1_bits);
+
+ itt_l1 = kcalloc(num_ents, sizeof(*itt_l1), GFP_KERNEL);
+ if (!itt_l1)
+ return -ENOMEM;
+
+ l2ptrs = kcalloc(num_ents, sizeof(*l2ptrs), GFP_KERNEL);
+ if (!l2ptrs) {
+ kfree(itt_l1);
+ return -ENOMEM;
+ }
+
+ its_dev->itt_cfg.l2.l2ptrs = l2ptrs;
+
+ its_dev->itt_cfg.l2.l2sz = itt_l2sz;
+ its_dev->itt_cfg.l2.l1itt = itt_l1;
+ its_dev->itt_cfg.l2.num_l1_ents = num_ents;
+ its_dev->itt_cfg.l2itt = true;
+ its_dev->itt_cfg.event_id_bits = event_id_bits;
+
+ /*
+ * Need to determine how many entries there are per L2 - this is based
+ * on the number of bits in the table.
+ */
+ events_per_l2_table = BIT(l2_bits);
+ complete_tables = num_events / events_per_l2_table;
+ final_span = order_base_2(num_events % events_per_l2_table);
+
+ for (i = 0; i < num_ents; i++) {
+ size_t l2sz;
+
+ span = i == complete_tables ? final_span : l2_bits;
+
+ itt_l2 = kcalloc(BIT(span), sizeof(*itt_l2), GFP_KERNEL);
+ if (!itt_l2) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+
+ its_dev->itt_cfg.l2.l2ptrs[i] = itt_l2;
+
+ l2sz = BIT(span) * sizeof(*itt_l2);
+
+ gicv5_its_dcache_clean(its, itt_l2, l2sz);
+
+ val = (virt_to_phys(itt_l2) & GICV5_ITTL1E_L2_ADDR_MASK) |
+ FIELD_PREP(GICV5_ITTL1E_SPAN, span) |
+ FIELD_PREP(GICV5_ITTL1E_VALID, 0x1);
+
+ WRITE_ONCE(itt_l1[i], cpu_to_le64(val));
+ }
+
+ gicv5_its_dcache_clean(its, itt_l1, num_ents * sizeof(*itt_l1));
+
+ return 0;
+
+out_free:
+ for (i = i - 1; i >= 0; i--)
+ kfree(its_dev->itt_cfg.l2.l2ptrs[i]);
+
+ kfree(its_dev->itt_cfg.l2.l2ptrs);
+ kfree(itt_l1);
+ return ret;
+}
+
+/*
+ * Function to check whether the device table or ITT table support
+ * a two-level table and if so depending on the number of id_bits
+ * requested, determine whether a two-level table is required.
+ *
+ * Return the 2-level size value if a two level table is deemed
+ * necessary.
+ */
+static bool gicv5_its_l2sz_two_level(bool devtab, u32 its_idr1, u8 id_bits, u8 *sz)
+{
+ unsigned int l2_bits, l2_sz;
+
+ if (devtab && !FIELD_GET(GICV5_ITS_IDR1_DT_LEVELS, its_idr1))
+ return false;
+
+ if (!devtab && !FIELD_GET(GICV5_ITS_IDR1_ITT_LEVELS, its_idr1))
+ return false;
+
+ /*
+ * Pick an L2 size that matches the pagesize; if a match
+ * is not found, go for the smallest supported l2 size granule.
+ *
+ * This ensures that we will always be able to allocate
+ * contiguous memory at L2.
+ */
+ switch (PAGE_SIZE) {
+ case SZ_64K:
+ if (GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(its_idr1)) {
+ l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_64k;
+ break;
+ }
+ fallthrough;
+ case SZ_4K:
+ if (GICV5_ITS_IDR1_L2SZ_SUPPORT_4KB(its_idr1)) {
+ l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_4k;
+ break;
+ }
+ fallthrough;
+ case SZ_16K:
+ if (GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(its_idr1)) {
+ l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_16k;
+ break;
+ }
+ if (GICV5_ITS_IDR1_L2SZ_SUPPORT_4KB(its_idr1)) {
+ l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_4k;
+ break;
+ }
+ if (GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(its_idr1)) {
+ l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_64k;
+ break;
+ }
+
+ l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_4k;
+ break;
+ }
+
+ l2_bits = gicv5_its_l2sz_to_l2_bits(l2_sz);
+
+ if (l2_bits > id_bits)
+ return false;
+
+ *sz = l2_sz;
+
+ return true;
+}
+
+static __le64 *gicv5_its_device_get_itte_ref(struct gicv5_its_dev *its_dev,
+ u16 event_id)
+{
+ unsigned int l1_idx, l2_idx, l2_bits;
+ __le64 *l2_itt;
+
+ if (!its_dev->itt_cfg.l2itt) {
+ __le64 *itt = its_dev->itt_cfg.linear.itt;
+
+ return &itt[event_id];
+ }
+
+ l2_bits = gicv5_its_l2sz_to_l2_bits(its_dev->itt_cfg.l2.l2sz);
+ l1_idx = event_id >> l2_bits;
+ l2_idx = event_id & GENMASK(l2_bits - 1, 0);
+ l2_itt = its_dev->itt_cfg.l2.l2ptrs[l1_idx];
+
+ return &l2_itt[l2_idx];
+}
+
+static int gicv5_its_device_cache_inv(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev)
+{
+ u32 devicer;
+ u64 didr;
+
+ didr = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, its_dev->device_id);
+ devicer = FIELD_PREP(GICV5_ITS_INV_DEVICER_I, 0x1) |
+ FIELD_PREP(GICV5_ITS_INV_DEVICER_EVENTID_BITS,
+ its_dev->itt_cfg.event_id_bits) |
+ FIELD_PREP(GICV5_ITS_INV_DEVICER_L1, 0x0);
+ its_writeq_relaxed(its, didr, GICV5_ITS_DIDR);
+ its_writel_relaxed(its, devicer, GICV5_ITS_INV_DEVICER);
+
+ return gicv5_its_cache_sync(its);
+}
+
+/*
+ * Allocate a level 2 device table entry, update L1 parent to reference it.
+ * Only used for 2-level device tables, and it is called on demand.
+ */
+static int gicv5_its_alloc_l2_devtab(struct gicv5_its_chip_data *its,
+ unsigned int l1_index)
+{
+ __le64 *l2devtab, *l1devtab = its->devtab_cfgr.l2.l1devtab;
+ u8 span, l2sz, l2_bits;
+ u64 l1dte;
+
+ if (FIELD_GET(GICV5_DTL1E_VALID, le64_to_cpu(l1devtab[l1_index])))
+ return 0;
+
+ span = FIELD_GET(GICV5_DTL1E_SPAN, le64_to_cpu(l1devtab[l1_index]));
+ l2sz = devtab_cfgr_field(its, L2SZ);
+
+ l2_bits = gicv5_its_l2sz_to_l2_bits(l2sz);
+
+ /*
+ * Span allows us to create a smaller L2 device table.
+ * If it is too large, use the number of allowed L2 bits.
+ */
+ if (span > l2_bits)
+ span = l2_bits;
+
+ l2devtab = kcalloc(BIT(span), sizeof(*l2devtab), GFP_KERNEL);
+ if (!l2devtab)
+ return -ENOMEM;
+
+ its->devtab_cfgr.l2.l2ptrs[l1_index] = l2devtab;
+
+ l1dte = FIELD_PREP(GICV5_DTL1E_SPAN, span) |
+ (virt_to_phys(l2devtab) & GICV5_DTL1E_L2_ADDR_MASK) |
+ FIELD_PREP(GICV5_DTL1E_VALID, 0x1);
+ its_write_table_entry(its, &l1devtab[l1_index], l1dte);
+
+ return 0;
+}
+
+static __le64 *gicv5_its_devtab_get_dte_ref(struct gicv5_its_chip_data *its,
+ u32 device_id, bool alloc)
+{
+ u8 str = devtab_cfgr_field(its, STRUCTURE);
+ unsigned int l2sz, l2_bits, l1_idx, l2_idx;
+ __le64 *l2devtab;
+ int ret;
+
+ if (str == GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR) {
+ l2devtab = its->devtab_cfgr.linear.devtab;
+ return &l2devtab[device_id];
+ }
+
+ l2sz = devtab_cfgr_field(its, L2SZ);
+ l2_bits = gicv5_its_l2sz_to_l2_bits(l2sz);
+ l1_idx = device_id >> l2_bits;
+ l2_idx = device_id & GENMASK(l2_bits - 1, 0);
+
+ if (alloc) {
+ /*
+ * Allocate a new L2 device table here before
+ * continuing. We make the assumption that the span in
+ * the L1 table has been set correctly, and blindly use
+ * that value.
+ */
+ ret = gicv5_its_alloc_l2_devtab(its, l1_idx);
+ if (ret)
+ return NULL;
+ }
+
+ l2devtab = its->devtab_cfgr.l2.l2ptrs[l1_idx];
+ return &l2devtab[l2_idx];
+}
+
+/*
+ * Register a new device in the device table. Allocate an ITT and
+ * program the L2DTE entry according to the ITT structure that
+ * was chosen.
+ */
+static int gicv5_its_device_register(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev)
+{
+ u8 event_id_bits, device_id_bits, itt_struct, itt_l2sz;
+ phys_addr_t itt_phys_base;
+ bool two_level_itt;
+ u32 idr1, idr2;
+ __le64 *dte;
+ u64 val;
+ int ret;
+
+ device_id_bits = devtab_cfgr_field(its, DEVICEID_BITS);
+
+ if (its_dev->device_id >= BIT(device_id_bits)) {
+ pr_err("Supplied DeviceID (%u) outside of Device Table range (%u)!",
+ its_dev->device_id, (u32)GENMASK(device_id_bits - 1, 0));
+ return -EINVAL;
+ }
+
+ dte = gicv5_its_devtab_get_dte_ref(its, its_dev->device_id, true);
+ if (!dte)
+ return -ENOMEM;
+
+ if (FIELD_GET(GICV5_DTL2E_VALID, le64_to_cpu(*dte)))
+ return -EBUSY;
+
+ /*
+ * Determine how many bits we need, validate those against the max.
+ * Based on these, determine if we should go for a 1- or 2-level ITT.
+ */
+ event_id_bits = order_base_2(its_dev->num_events);
+
+ idr2 = its_readl_relaxed(its, GICV5_ITS_IDR2);
+
+ if (event_id_bits > FIELD_GET(GICV5_ITS_IDR2_EVENTID_BITS, idr2)) {
+ pr_err("Required EventID bits (%u) larger than supported bits (%u)!",
+ event_id_bits,
+ (u8)FIELD_GET(GICV5_ITS_IDR2_EVENTID_BITS, idr2));
+ return -EINVAL;
+ }
+
+ idr1 = its_readl_relaxed(its, GICV5_ITS_IDR1);
+
+ /*
+ * L2 ITT size is programmed into the L2DTE regardless of
+ * whether a two-level or linear ITT is built, init it.
+ */
+ itt_l2sz = 0;
+
+ two_level_itt = gicv5_its_l2sz_two_level(false, idr1, event_id_bits,
+ &itt_l2sz);
+ if (two_level_itt)
+ ret = gicv5_its_create_itt_two_level(its, its_dev, event_id_bits,
+ itt_l2sz,
+ its_dev->num_events);
+ else
+ ret = gicv5_its_create_itt_linear(its, its_dev, event_id_bits);
+ if (ret)
+ return ret;
+
+ itt_phys_base = two_level_itt ? virt_to_phys(its_dev->itt_cfg.l2.l1itt) :
+ virt_to_phys(its_dev->itt_cfg.linear.itt);
+
+ itt_struct = two_level_itt ? GICV5_ITS_DT_ITT_CFGR_STRUCTURE_TWO_LEVEL :
+ GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR;
+
+ val = FIELD_PREP(GICV5_DTL2E_EVENT_ID_BITS, event_id_bits) |
+ FIELD_PREP(GICV5_DTL2E_ITT_STRUCTURE, itt_struct) |
+ (itt_phys_base & GICV5_DTL2E_ITT_ADDR_MASK) |
+ FIELD_PREP(GICV5_DTL2E_ITT_L2SZ, itt_l2sz) |
+ FIELD_PREP(GICV5_DTL2E_VALID, 0x1);
+
+ its_write_table_entry(its, dte, val);
+
+ ret = gicv5_its_device_cache_inv(its, its_dev);
+ if (ret) {
+ its_write_table_entry(its, dte, 0);
+ gicv5_its_free_itt(its_dev);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Unregister a device in the device table. Lookup the device by ID, free the
+ * corresponding ITT, mark the device as invalid in the device table.
+ */
+static int gicv5_its_device_unregister(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev)
+{
+ __le64 *dte;
+
+ dte = gicv5_its_devtab_get_dte_ref(its, its_dev->device_id, false);
+
+ if (!FIELD_GET(GICV5_DTL2E_VALID, le64_to_cpu(*dte))) {
+ pr_debug("Device table entry for DeviceID 0x%x is not valid. Nothing to clean up!",
+ its_dev->device_id);
+ return -EINVAL;
+ }
+
+ /* Zero everything - make it clear that this is an invalid entry */
+ its_write_table_entry(its, dte, 0);
+
+ gicv5_its_free_itt(its_dev);
+
+ return gicv5_its_device_cache_inv(its, its_dev);
+}
+
+/*
+ * Allocate a 1-level device table. All entries are allocated, but marked
+ * invalid.
+ */
+static int gicv5_its_alloc_devtab_linear(struct gicv5_its_chip_data *its,
+ u8 device_id_bits)
+{
+ __le64 *devtab;
+ size_t sz;
+ u64 baser;
+ u32 cfgr;
+
+ /*
+ * We expect a GICv5 implementation requiring a large number of
+ * deviceID bits to support a 2-level device table. If that's not
+ * the case, cap the number of deviceIDs supported according to the
+ * kmalloc limits so that the system can chug along with a linear
+ * device table.
+ */
+ sz = BIT_ULL(device_id_bits) * sizeof(*devtab);
+ if (sz > KMALLOC_MAX_SIZE) {
+ u8 device_id_cap = ilog2(KMALLOC_MAX_SIZE/sizeof(*devtab));
+
+ pr_warn("Limiting device ID bits from %u to %u\n",
+ device_id_bits, device_id_cap);
+ device_id_bits = device_id_cap;
+ }
+
+ devtab = kcalloc(BIT(device_id_bits), sizeof(*devtab), GFP_KERNEL);
+ if (!devtab)
+ return -ENOMEM;
+
+ gicv5_its_dcache_clean(its, devtab, sz);
+
+ cfgr = FIELD_PREP(GICV5_ITS_DT_CFGR_STRUCTURE,
+ GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR) |
+ FIELD_PREP(GICV5_ITS_DT_CFGR_L2SZ, 0) |
+ FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS, device_id_bits);
+ its_writel_relaxed(its, cfgr, GICV5_ITS_DT_CFGR);
+
+ baser = virt_to_phys(devtab) & GICV5_ITS_DT_BASER_ADDR_MASK;
+ its_writeq_relaxed(its, baser, GICV5_ITS_DT_BASER);
+
+ its->devtab_cfgr.cfgr = cfgr;
+ its->devtab_cfgr.linear.devtab = devtab;
+
+ return 0;
+}
+
+/*
+ * Allocate a 2-level device table. L2 entries are not allocated,
+ * they are allocated on-demand.
+ */
+static int gicv5_its_alloc_devtab_two_level(struct gicv5_its_chip_data *its,
+ u8 device_id_bits,
+ u8 devtab_l2sz)
+{
+ unsigned int l1_bits, l2_bits, i;
+ __le64 *l1devtab, **l2ptrs;
+ size_t l1_sz;
+ u64 baser;
+ u32 cfgr;
+
+ l2_bits = gicv5_its_l2sz_to_l2_bits(devtab_l2sz);
+
+ l1_bits = device_id_bits - l2_bits;
+ l1_sz = BIT(l1_bits) * sizeof(*l1devtab);
+ /*
+ * With 2-level device table support it is highly unlikely
+ * that we are not able to allocate the required amount of
+ * device table memory to cover deviceID space; cap the
+ * deviceID space if we encounter such set-up.
+ * If this ever becomes a problem we could revisit the policy
+ * behind level 2 size selection to reduce level-1 deviceID bits.
+ */
+ if (l1_sz > KMALLOC_MAX_SIZE) {
+ l1_bits = ilog2(KMALLOC_MAX_SIZE/sizeof(*l1devtab));
+
+ pr_warn("Limiting device ID bits from %u to %u\n",
+ device_id_bits, l1_bits + l2_bits);
+ device_id_bits = l1_bits + l2_bits;
+ l1_sz = KMALLOC_MAX_SIZE;
+ }
+
+ l1devtab = kcalloc(BIT(l1_bits), sizeof(*l1devtab), GFP_KERNEL);
+ if (!l1devtab)
+ return -ENOMEM;
+
+ l2ptrs = kcalloc(BIT(l1_bits), sizeof(*l2ptrs), GFP_KERNEL);
+ if (!l2ptrs) {
+ kfree(l1devtab);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < BIT(l1_bits); i++)
+ l1devtab[i] = cpu_to_le64(FIELD_PREP(GICV5_DTL1E_SPAN, l2_bits));
+
+ gicv5_its_dcache_clean(its, l1devtab, l1_sz);
+
+ cfgr = FIELD_PREP(GICV5_ITS_DT_CFGR_STRUCTURE,
+ GICV5_ITS_DT_ITT_CFGR_STRUCTURE_TWO_LEVEL) |
+ FIELD_PREP(GICV5_ITS_DT_CFGR_L2SZ, devtab_l2sz) |
+ FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS, device_id_bits);
+ its_writel_relaxed(its, cfgr, GICV5_ITS_DT_CFGR);
+
+ baser = virt_to_phys(l1devtab) & GICV5_ITS_DT_BASER_ADDR_MASK;
+ its_writeq_relaxed(its, baser, GICV5_ITS_DT_BASER);
+
+ its->devtab_cfgr.cfgr = cfgr;
+ its->devtab_cfgr.l2.l1devtab = l1devtab;
+ its->devtab_cfgr.l2.l2ptrs = l2ptrs;
+
+ return 0;
+}
+
+/*
+ * Initialise the device table as either 1- or 2-level depending on what is
+ * supported by the hardware.
+ */
+static int gicv5_its_init_devtab(struct gicv5_its_chip_data *its)
+{
+ u8 device_id_bits, devtab_l2sz;
+ bool two_level_devtab;
+ u32 idr1;
+
+ idr1 = its_readl_relaxed(its, GICV5_ITS_IDR1);
+
+ device_id_bits = FIELD_GET(GICV5_ITS_IDR1_DEVICEID_BITS, idr1);
+ two_level_devtab = gicv5_its_l2sz_two_level(true, idr1, device_id_bits,
+ &devtab_l2sz);
+ if (two_level_devtab)
+ return gicv5_its_alloc_devtab_two_level(its, device_id_bits,
+ devtab_l2sz);
+ else
+ return gicv5_its_alloc_devtab_linear(its, device_id_bits);
+}
+
+static void gicv5_its_deinit_devtab(struct gicv5_its_chip_data *its)
+{
+ u8 str = devtab_cfgr_field(its, STRUCTURE);
+
+ if (str == GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR) {
+ kfree(its->devtab_cfgr.linear.devtab);
+ } else {
+ kfree(its->devtab_cfgr.l2.l1devtab);
+ kfree(its->devtab_cfgr.l2.l2ptrs);
+ }
+}
+
+static void gicv5_its_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
+{
+ struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
+ u64 addr = its_dev->its_trans_phys_base;
+
+ msg->data = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
+ msi_msg_set_addr(irq_data_get_msi_desc(d), msg, addr);
+}
+
+static const struct irq_chip gicv5_its_irq_chip = {
+ .name = "GICv5-ITS-MSI",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .irq_compose_msi_msg = gicv5_its_compose_msi_msg,
+};
+
+static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *its,
+ u32 device_id)
+{
+ struct gicv5_its_dev *dev = xa_load(&its->its_devices, device_id);
+
+ return dev ? dev : ERR_PTR(-ENODEV);
+}
+
+static struct gicv5_its_dev *gicv5_its_alloc_device(struct gicv5_its_chip_data *its, int nvec,
+ u32 dev_id)
+{
+ struct gicv5_its_dev *its_dev;
+ void *entry;
+ int ret;
+
+ its_dev = gicv5_its_find_device(its, dev_id);
+ if (!IS_ERR(its_dev)) {
+ pr_err("A device with this DeviceID (0x%x) has already been registered.\n",
+ dev_id);
+
+ return ERR_PTR(-EBUSY);
+ }
+
+ its_dev = kzalloc(sizeof(*its_dev), GFP_KERNEL);
+ if (!its_dev)
+ return ERR_PTR(-ENOMEM);
+
+ its_dev->device_id = dev_id;
+ its_dev->num_events = nvec;
+
+ ret = gicv5_its_device_register(its, its_dev);
+ if (ret) {
+ pr_err("Failed to register the device\n");
+ goto out_dev_free;
+ }
+
+ gicv5_its_device_cache_inv(its, its_dev);
+
+ its_dev->its_node = its;
+
+ its_dev->event_map = (unsigned long *)bitmap_zalloc(its_dev->num_events, GFP_KERNEL);
+ if (!its_dev->event_map) {
+ ret = -ENOMEM;
+ goto out_unregister;
+ }
+
+ entry = xa_store(&its->its_devices, dev_id, its_dev, GFP_KERNEL);
+ if (xa_is_err(entry)) {
+ ret = xa_err(entry);
+ goto out_bitmap_free;
+ }
+
+ return its_dev;
+
+out_bitmap_free:
+ bitmap_free(its_dev->event_map);
+out_unregister:
+ gicv5_its_device_unregister(its, its_dev);
+out_dev_free:
+ kfree(its_dev);
+ return ERR_PTR(ret);
+}
+
+static int gicv5_its_msi_prepare(struct irq_domain *domain, struct device *dev,
+ int nvec, msi_alloc_info_t *info)
+{
+ u32 dev_id = info->scratchpad[0].ul;
+ struct msi_domain_info *msi_info;
+ struct gicv5_its_chip_data *its;
+ struct gicv5_its_dev *its_dev;
+
+ msi_info = msi_get_domain_info(domain);
+ its = msi_info->data;
+
+ guard(mutex)(&its->dev_alloc_lock);
+
+ its_dev = gicv5_its_alloc_device(its, nvec, dev_id);
+ if (IS_ERR(its_dev))
+ return PTR_ERR(its_dev);
+
+ its_dev->its_trans_phys_base = info->scratchpad[1].ul;
+ info->scratchpad[0].ptr = its_dev;
+
+ return 0;
+}
+
+static void gicv5_its_msi_teardown(struct irq_domain *domain, msi_alloc_info_t *info)
+{
+ struct gicv5_its_dev *its_dev = info->scratchpad[0].ptr;
+ struct msi_domain_info *msi_info;
+ struct gicv5_its_chip_data *its;
+
+ msi_info = msi_get_domain_info(domain);
+ its = msi_info->data;
+
+ guard(mutex)(&its->dev_alloc_lock);
+
+ if (WARN_ON_ONCE(!bitmap_empty(its_dev->event_map, its_dev->num_events)))
+ return;
+
+ xa_erase(&its->its_devices, its_dev->device_id);
+ bitmap_free(its_dev->event_map);
+ gicv5_its_device_unregister(its, its_dev);
+ kfree(its_dev);
+}
+
+static struct msi_domain_ops gicv5_its_msi_domain_ops = {
+ .msi_prepare = gicv5_its_msi_prepare,
+ .msi_teardown = gicv5_its_msi_teardown,
+};
+
+static int gicv5_its_map_event(struct gicv5_its_dev *its_dev, u16 event_id, u32 lpi)
+{
+ struct gicv5_its_chip_data *its = its_dev->its_node;
+ u64 itt_entry;
+ __le64 *itte;
+
+ itte = gicv5_its_device_get_itte_ref(its_dev, event_id);
+
+ if (FIELD_GET(GICV5_ITTL2E_VALID, *itte))
+ return -EEXIST;
+
+ itt_entry = FIELD_PREP(GICV5_ITTL2E_LPI_ID, lpi) |
+ FIELD_PREP(GICV5_ITTL2E_VALID, 0x1);
+
+ its_write_table_entry(its, itte, itt_entry);
+
+ gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
+
+ return 0;
+}
+
+static void gicv5_its_unmap_event(struct gicv5_its_dev *its_dev, u16 event_id)
+{
+ struct gicv5_its_chip_data *its = its_dev->its_node;
+ u64 itte_val;
+ __le64 *itte;
+
+ itte = gicv5_its_device_get_itte_ref(its_dev, event_id);
+
+ itte_val = le64_to_cpu(*itte);
+ itte_val &= ~GICV5_ITTL2E_VALID;
+
+ its_write_table_entry(its, itte, itte_val);
+
+ gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
+}
+
+static int gicv5_its_alloc_eventid(struct gicv5_its_dev *its_dev, msi_alloc_info_t *info,
+ unsigned int nr_irqs, u32 *eventid)
+{
+ int event_id_base;
+
+ if (!(info->flags & MSI_ALLOC_FLAGS_FIXED_MSG_DATA)) {
+ event_id_base = bitmap_find_free_region(its_dev->event_map,
+ its_dev->num_events,
+ get_count_order(nr_irqs));
+ if (event_id_base < 0)
+ return event_id_base;
+ } else {
+ /*
+ * We want to have a fixed EventID mapped for hardcoded
+ * message data allocations.
+ */
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return -EINVAL;
+
+ event_id_base = info->hwirq;
+
+ if (event_id_base >= its_dev->num_events) {
+ pr_err("EventID ouside of ITT range; cannot allocate an ITT entry!\n");
+
+ return -EINVAL;
+ }
+
+ if (test_and_set_bit(event_id_base, its_dev->event_map)) {
+ pr_warn("Can't reserve event_id bitmap\n");
+ return -EINVAL;
+
+ }
+ }
+
+ *eventid = event_id_base;
+
+ return 0;
+}
+
+static void gicv5_its_free_eventid(struct gicv5_its_dev *its_dev, u32 event_id_base,
+ unsigned int nr_irqs)
+{
+ bitmap_release_region(its_dev->event_map, event_id_base,
+ get_count_order(nr_irqs));
+}
+
+static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ u32 device_id, event_id_base, lpi;
+ struct gicv5_its_dev *its_dev;
+ msi_alloc_info_t *info = arg;
+ irq_hw_number_t hwirq;
+ struct irq_data *irqd;
+ int ret, i;
+
+ its_dev = info->scratchpad[0].ptr;
+
+ ret = gicv5_its_alloc_eventid(its_dev, info, nr_irqs, &event_id_base);
+ if (ret)
+ return ret;
+
+ ret = iommu_dma_prepare_msi(info->desc, its_dev->its_trans_phys_base);
+ if (ret)
+ goto out_eventid;
+
+ device_id = its_dev->device_id;
+
+ for (i = 0; i < nr_irqs; i++) {
+ lpi = gicv5_alloc_lpi();
+ if (ret < 0) {
+ pr_debug("Failed to find free LPI!\n");
+ goto out_eventid;
+ }
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi);
+ if (ret)
+ goto out_free_lpi;
+
+ /*
+ * Store eventid and deviceid into the hwirq for later use.
+ *
+ * hwirq = event_id << 32 | device_id
+ */
+ hwirq = FIELD_PREP(GICV5_ITS_HWIRQ_DEVICE_ID, device_id) |
+ FIELD_PREP(GICV5_ITS_HWIRQ_EVENT_ID, (u64)event_id_base + i);
+ irq_domain_set_info(domain, virq + i, hwirq,
+ &gicv5_its_irq_chip, its_dev,
+ handle_fasteoi_irq, NULL, NULL);
+
+ irqd = irq_get_irq_data(virq + i);
+ irqd_set_single_target(irqd);
+ irqd_set_affinity_on_activate(irqd);
+ irqd_set_resend_when_in_progress(irqd);
+ }
+
+ return 0;
+
+out_free_lpi:
+ gicv5_free_lpi(lpi);
+out_eventid:
+ gicv5_its_free_eventid(its_dev, event_id_base, nr_irqs);
+ return ret;
+}
+
+static void gicv5_its_irq_domain_free(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq);
+ struct gicv5_its_chip_data *its;
+ struct gicv5_its_dev *its_dev;
+ u16 event_id_base;
+ unsigned int i;
+
+ its_dev = irq_data_get_irq_chip_data(d);
+ its = its_dev->its_node;
+
+ event_id_base = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
+
+ bitmap_release_region(its_dev->event_map, event_id_base,
+ get_count_order(nr_irqs));
+
+ /* Hierarchically free irq data */
+ for (i = 0; i < nr_irqs; i++) {
+ d = irq_domain_get_irq_data(domain, virq + i);
+
+ gicv5_free_lpi(d->parent_data->hwirq);
+ irq_domain_reset_irq_data(d);
+ irq_domain_free_irqs_parent(domain, virq + i, 1);
+ }
+
+ gicv5_its_syncr(its, its_dev);
+ gicv5_irs_syncr();
+}
+
+static int gicv5_its_irq_domain_activate(struct irq_domain *domain, struct irq_data *d,
+ bool reserve)
+{
+ struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
+ u16 event_id;
+ u32 lpi;
+
+ event_id = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
+ lpi = d->parent_data->hwirq;
+
+ return gicv5_its_map_event(its_dev, event_id, lpi);
+}
+
+static void gicv5_its_irq_domain_deactivate(struct irq_domain *domain,
+ struct irq_data *d)
+{
+ struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
+ u16 event_id;
+
+ event_id = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
+
+ gicv5_its_unmap_event(its_dev, event_id);
+}
+
+static const struct irq_domain_ops gicv5_its_irq_domain_ops = {
+ .alloc = gicv5_its_irq_domain_alloc,
+ .free = gicv5_its_irq_domain_free,
+ .activate = gicv5_its_irq_domain_activate,
+ .deactivate = gicv5_its_irq_domain_deactivate,
+ .select = msi_lib_irq_domain_select,
+};
+
+static int gicv5_its_write_cr0(struct gicv5_its_chip_data *its, bool enable)
+{
+ u32 cr0 = FIELD_PREP(GICV5_ITS_CR0_ITSEN, enable);
+
+ its_writel_relaxed(its, cr0, GICV5_ITS_CR0);
+ return gicv5_wait_for_op_atomic(its->its_base, GICV5_ITS_CR0,
+ GICV5_ITS_CR0_IDLE, NULL);
+}
+
+static int gicv5_its_enable(struct gicv5_its_chip_data *its)
+{
+ return gicv5_its_write_cr0(its, true);
+}
+
+static int gicv5_its_disable(struct gicv5_its_chip_data *its)
+{
+ return gicv5_its_write_cr0(its, false);
+}
+
+static void gicv5_its_print_info(struct gicv5_its_chip_data *its_node)
+{
+ bool devtab_linear;
+ u8 device_id_bits;
+ u8 str;
+
+ device_id_bits = devtab_cfgr_field(its_node, DEVICEID_BITS);
+
+ str = devtab_cfgr_field(its_node, STRUCTURE);
+ devtab_linear = (str == GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR);
+
+ pr_info("ITS %s enabled using %s device table device_id_bits %u\n",
+ fwnode_get_name(its_node->fwnode),
+ devtab_linear ? "linear" : "2-level",
+ device_id_bits);
+}
+
+static int gicv5_its_init_domain(struct gicv5_its_chip_data *its, struct irq_domain *parent)
+{
+ struct irq_domain_info dom_info = {
+ .fwnode = its->fwnode,
+ .ops = &gicv5_its_irq_domain_ops,
+ .domain_flags = its->msi_domain_flags,
+ .parent = parent,
+ };
+ struct msi_domain_info *info;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->ops = &gicv5_its_msi_domain_ops;
+ info->data = its;
+ dom_info.host_data = info;
+
+ if (!msi_create_parent_irq_domain(&dom_info, &gic_v5_its_msi_parent_ops)) {
+ kfree(info);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int __init gicv5_its_init_bases(void __iomem *its_base, struct fwnode_handle *handle,
+ struct irq_domain *parent_domain)
+{
+ struct device_node *np = to_of_node(handle);
+ struct gicv5_its_chip_data *its_node;
+ u32 cr0, cr1;
+ bool enabled;
+ int ret;
+
+ its_node = kzalloc(sizeof(*its_node), GFP_KERNEL);
+ if (!its_node)
+ return -ENOMEM;
+
+ mutex_init(&its_node->dev_alloc_lock);
+ xa_init(&its_node->its_devices);
+ its_node->fwnode = handle;
+ its_node->its_base = its_base;
+ its_node->msi_domain_flags = IRQ_DOMAIN_FLAG_ISOLATED_MSI |
+ IRQ_DOMAIN_FLAG_FWNODE_PARENT;
+
+ cr0 = its_readl_relaxed(its_node, GICV5_ITS_CR0);
+ enabled = FIELD_GET(GICV5_ITS_CR0_ITSEN, cr0);
+ if (WARN(enabled, "ITS %s enabled, disabling it before proceeding\n", np->full_name)) {
+ ret = gicv5_its_disable(its_node);
+ if (ret)
+ goto out_free_node;
+ }
+
+ if (of_property_read_bool(np, "dma-noncoherent")) {
+ /*
+ * A non-coherent ITS implies that some cache levels cannot be
+ * used coherently by the cores and GIC. Our only option is to mark
+ * memory attributes for the GIC as non-cacheable; by default,
+ * non-cacheable memory attributes imply outer-shareable
+ * shareability, the value written into ITS_CR1_SH is ignored.
+ */
+ cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_NON_CACHE) |
+ FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_NON_CACHE);
+ its_node->flags |= ITS_FLAGS_NON_COHERENT;
+ } else {
+ cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_WB_CACHE) |
+ FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_WB_CACHE) |
+ FIELD_PREP(GICV5_ITS_CR1_SH, GICV5_INNER_SHARE);
+ }
+
+ its_writel_relaxed(its_node, cr1, GICV5_ITS_CR1);
+
+ ret = gicv5_its_init_devtab(its_node);
+ if (ret)
+ goto out_free_node;
+
+ ret = gicv5_its_enable(its_node);
+ if (ret)
+ goto out_free_devtab;
+
+ ret = gicv5_its_init_domain(its_node, parent_domain);
+ if (ret)
+ goto out_disable_its;
+
+ gicv5_its_print_info(its_node);
+
+ return 0;
+
+out_disable_its:
+ gicv5_its_disable(its_node);
+out_free_devtab:
+ gicv5_its_deinit_devtab(its_node);
+out_free_node:
+ kfree(its_node);
+ return ret;
+}
+
+static int __init gicv5_its_init(struct device_node *node)
+{
+ void __iomem *its_base;
+ int ret, idx;
+
+ idx = of_property_match_string(node, "reg-names", "ns-config");
+ if (idx < 0) {
+ pr_err("%pOF: ns-config reg-name not present\n", node);
+ return -ENODEV;
+ }
+
+ its_base = of_io_request_and_map(node, idx, of_node_full_name(node));
+ if (IS_ERR(its_base)) {
+ pr_err("%pOF: unable to map GICv5 ITS_CONFIG_FRAME\n", node);
+ return PTR_ERR(its_base);
+ }
+
+ ret = gicv5_its_init_bases(its_base, of_fwnode_handle(node),
+ gicv5_global_data.lpi_domain);
+ if (ret)
+ goto out_unmap;
+
+ return 0;
+
+out_unmap:
+ iounmap(its_base);
+ return ret;
+}
+
+void __init gicv5_its_of_probe(struct device_node *parent)
+{
+ struct device_node *np;
+
+ for_each_available_child_of_node(parent, np) {
+ if (!of_device_is_compatible(np, "arm,gic-v5-its"))
+ continue;
+
+ if (gicv5_its_init(np))
+ pr_err("Failed to init ITS %s\n", np->full_name);
+ }
+}
diff --git a/drivers/irqchip/irq-gic-v5-iwb.c b/drivers/irqchip/irq-gic-v5-iwb.c
new file mode 100644
index 000000000000..ed72fbdd4900
--- /dev/null
+++ b/drivers/irqchip/irq-gic-v5-iwb.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
+ */
+#define pr_fmt(fmt) "GICv5 IWB: " fmt
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/msi.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+
+#include <linux/irqchip.h>
+#include <linux/irqchip/arm-gic-v5.h>
+
+struct gicv5_iwb_chip_data {
+ void __iomem *iwb_base;
+ u16 nr_regs;
+};
+
+static u32 iwb_readl_relaxed(struct gicv5_iwb_chip_data *iwb_node, const u32 reg_offset)
+{
+ return readl_relaxed(iwb_node->iwb_base + reg_offset);
+}
+
+static void iwb_writel_relaxed(struct gicv5_iwb_chip_data *iwb_node, const u32 val,
+ const u32 reg_offset)
+{
+ writel_relaxed(val, iwb_node->iwb_base + reg_offset);
+}
+
+static int gicv5_iwb_wait_for_wenabler(struct gicv5_iwb_chip_data *iwb_node)
+{
+ return gicv5_wait_for_op_atomic(iwb_node->iwb_base, GICV5_IWB_WENABLE_STATUSR,
+ GICV5_IWB_WENABLE_STATUSR_IDLE, NULL);
+}
+
+static int __gicv5_iwb_set_wire_enable(struct gicv5_iwb_chip_data *iwb_node,
+ u32 iwb_wire, bool enable)
+{
+ u32 n = iwb_wire / 32;
+ u8 i = iwb_wire % 32;
+ u32 val;
+
+ if (n >= iwb_node->nr_regs) {
+ pr_err("IWB_WENABLER<n> is invalid for n=%u\n", n);
+ return -EINVAL;
+ }
+
+ /*
+ * Enable IWB wire/pin at this point
+ * Note: This is not the same as enabling the interrupt
+ */
+ val = iwb_readl_relaxed(iwb_node, GICV5_IWB_WENABLER + (4 * n));
+ if (enable)
+ val |= BIT(i);
+ else
+ val &= ~BIT(i);
+ iwb_writel_relaxed(iwb_node, val, GICV5_IWB_WENABLER + (4 * n));
+
+ return gicv5_iwb_wait_for_wenabler(iwb_node);
+}
+
+static int gicv5_iwb_enable_wire(struct gicv5_iwb_chip_data *iwb_node,
+ u32 iwb_wire)
+{
+ return __gicv5_iwb_set_wire_enable(iwb_node, iwb_wire, true);
+}
+
+static int gicv5_iwb_disable_wire(struct gicv5_iwb_chip_data *iwb_node,
+ u32 iwb_wire)
+{
+ return __gicv5_iwb_set_wire_enable(iwb_node, iwb_wire, false);
+}
+
+static void gicv5_iwb_irq_disable(struct irq_data *d)
+{
+ struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d);
+
+ gicv5_iwb_disable_wire(iwb_node, d->hwirq);
+ irq_chip_disable_parent(d);
+}
+
+static void gicv5_iwb_irq_enable(struct irq_data *d)
+{
+ struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d);
+
+ gicv5_iwb_enable_wire(iwb_node, d->hwirq);
+ irq_chip_enable_parent(d);
+}
+
+static int gicv5_iwb_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d);
+ u32 iwb_wire, n, wtmr;
+ u8 i;
+
+ iwb_wire = d->hwirq;
+ i = iwb_wire % 32;
+ n = iwb_wire / 32;
+
+ if (n >= iwb_node->nr_regs) {
+ pr_err_once("reg %u out of range\n", n);
+ return -EINVAL;
+ }
+
+ wtmr = iwb_readl_relaxed(iwb_node, GICV5_IWB_WTMR + (4 * n));
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ case IRQ_TYPE_LEVEL_LOW:
+ wtmr |= BIT(i);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ wtmr &= ~BIT(i);
+ break;
+ default:
+ pr_debug("unexpected wire trigger mode");
+ return -EINVAL;
+ }
+
+ iwb_writel_relaxed(iwb_node, wtmr, GICV5_IWB_WTMR + (4 * n));
+
+ return 0;
+}
+
+static void gicv5_iwb_domain_set_desc(msi_alloc_info_t *alloc_info, struct msi_desc *desc)
+{
+ alloc_info->desc = desc;
+ alloc_info->hwirq = (u32)desc->data.icookie.value;
+}
+
+static int gicv5_iwb_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
+ irq_hw_number_t *hwirq,
+ unsigned int *type)
+{
+ if (!is_of_node(fwspec->fwnode))
+ return -EINVAL;
+
+ if (fwspec->param_count < 2)
+ return -EINVAL;
+
+ /*
+ * param[0] is be the wire
+ * param[1] is the interrupt type
+ */
+ *hwirq = fwspec->param[0];
+ *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
+
+ return 0;
+}
+
+static void gicv5_iwb_write_msi_msg(struct irq_data *d, struct msi_msg *msg) {}
+
+static const struct msi_domain_template iwb_msi_template = {
+ .chip = {
+ .name = "GICv5-IWB",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_enable = gicv5_iwb_irq_enable,
+ .irq_disable = gicv5_iwb_irq_disable,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_type = gicv5_iwb_set_type,
+ .irq_write_msi_msg = gicv5_iwb_write_msi_msg,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .flags = IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND,
+ },
+
+ .ops = {
+ .set_desc = gicv5_iwb_domain_set_desc,
+ .msi_translate = gicv5_iwb_irq_domain_translate,
+ },
+
+ .info = {
+ .bus_token = DOMAIN_BUS_WIRED_TO_MSI,
+ .flags = MSI_FLAG_USE_DEV_FWNODE,
+ },
+
+ .alloc_info = {
+ .flags = MSI_ALLOC_FLAGS_FIXED_MSG_DATA,
+ },
+};
+
+static bool gicv5_iwb_create_device_domain(struct device *dev, unsigned int size,
+ struct gicv5_iwb_chip_data *iwb_node)
+{
+ if (WARN_ON_ONCE(!dev->msi.domain))
+ return false;
+
+ return msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN,
+ &iwb_msi_template, size,
+ NULL, iwb_node);
+}
+
+static struct gicv5_iwb_chip_data *
+gicv5_iwb_init_bases(void __iomem *iwb_base, struct platform_device *pdev)
+{
+ u32 nr_wires, idr0, cr0;
+ unsigned int n;
+ int ret;
+
+ struct gicv5_iwb_chip_data *iwb_node __free(kfree) = kzalloc(sizeof(*iwb_node),
+ GFP_KERNEL);
+ if (!iwb_node)
+ return ERR_PTR(-ENOMEM);
+
+ iwb_node->iwb_base = iwb_base;
+
+ idr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_IDR0);
+ nr_wires = (FIELD_GET(GICV5_IWB_IDR0_IW_RANGE, idr0) + 1) * 32;
+
+ cr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_CR0);
+ if (!FIELD_GET(GICV5_IWB_CR0_IWBEN, cr0)) {
+ dev_err(&pdev->dev, "IWB must be enabled in firmware\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ iwb_node->nr_regs = FIELD_GET(GICV5_IWB_IDR0_IW_RANGE, idr0) + 1;
+
+ for (n = 0; n < iwb_node->nr_regs; n++)
+ iwb_writel_relaxed(iwb_node, 0, GICV5_IWB_WENABLER + (sizeof(u32) * n));
+
+ ret = gicv5_iwb_wait_for_wenabler(iwb_node);
+ if (ret)
+ return ERR_PTR(ret);
+
+ if (!gicv5_iwb_create_device_domain(&pdev->dev, nr_wires, iwb_node))
+ return ERR_PTR(-ENOMEM);
+
+ return_ptr(iwb_node);
+}
+
+static int gicv5_iwb_device_probe(struct platform_device *pdev)
+{
+ struct gicv5_iwb_chip_data *iwb_node;
+ void __iomem *iwb_base;
+ struct resource *res;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EINVAL;
+
+ iwb_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ if (!iwb_base) {
+ dev_err(&pdev->dev, "failed to ioremap %pR\n", res);
+ return -ENOMEM;
+ }
+
+ iwb_node = gicv5_iwb_init_bases(iwb_base, pdev);
+ if (IS_ERR(iwb_node)) {
+ ret = PTR_ERR(iwb_node);
+ goto out_unmap;
+ }
+
+ return 0;
+
+out_unmap:
+ iounmap(iwb_base);
+ return ret;
+}
+
+static const struct of_device_id gicv5_iwb_of_match[] = {
+ { .compatible = "arm,gic-v5-iwb" },
+ { /* END */ }
+};
+MODULE_DEVICE_TABLE(of, gicv5_iwb_of_match);
+
+static struct platform_driver gicv5_iwb_platform_driver = {
+ .driver = {
+ .name = "GICv5 IWB",
+ .of_match_table = gicv5_iwb_of_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = gicv5_iwb_device_probe,
+};
+
+module_platform_driver(gicv5_iwb_platform_driver);
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
new file mode 100644
index 000000000000..4bd224f359a7
--- /dev/null
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -0,0 +1,1137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
+ */
+
+#define pr_fmt(fmt) "GICv5: " fmt
+
+#include <linux/cpuhotplug.h>
+#include <linux/idr.h>
+#include <linux/irqdomain.h>
+#include <linux/slab.h>
+#include <linux/wordpart.h>
+
+#include <linux/irqchip.h>
+#include <linux/irqchip/arm-gic-v5.h>
+#include <linux/irqchip/arm-vgic-info.h>
+
+#include <asm/cpufeature.h>
+#include <asm/exception.h>
+
+static u8 pri_bits __ro_after_init = 5;
+
+#define GICV5_IRQ_PRI_MASK 0x1f
+#define GICV5_IRQ_PRI_MI (GICV5_IRQ_PRI_MASK & GENMASK(4, 5 - pri_bits))
+
+#define PPI_NR 128
+
+static bool gicv5_cpuif_has_gcie(void)
+{
+ return this_cpu_has_cap(ARM64_HAS_GICV5_CPUIF);
+}
+
+struct gicv5_chip_data gicv5_global_data __read_mostly;
+
+static DEFINE_IDA(lpi_ida);
+static u32 num_lpis __ro_after_init;
+
+void __init gicv5_init_lpis(u32 lpis)
+{
+ num_lpis = lpis;
+}
+
+void __init gicv5_deinit_lpis(void)
+{
+ num_lpis = 0;
+}
+
+static int alloc_lpi(void)
+{
+ if (!num_lpis)
+ return -ENOSPC;
+
+ return ida_alloc_max(&lpi_ida, num_lpis - 1, GFP_KERNEL);
+}
+
+static void release_lpi(u32 lpi)
+{
+ ida_free(&lpi_ida, lpi);
+}
+
+int gicv5_alloc_lpi(void)
+{
+ return alloc_lpi();
+}
+
+void gicv5_free_lpi(u32 lpi)
+{
+ release_lpi(lpi);
+}
+
+static void gicv5_ppi_priority_init(void)
+{
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR0_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR1_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR2_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR3_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR4_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR5_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR6_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR7_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR8_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR9_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR10_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR11_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR12_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR13_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR14_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR15_EL1);
+
+ /*
+ * Context syncronization required to make sure system register writes
+ * effects are synchronised.
+ */
+ isb();
+}
+
+static void gicv5_hwirq_init(irq_hw_number_t hwirq, u8 priority, u8 hwirq_type)
+{
+ u64 cdpri, cdaff;
+ u16 iaffid;
+ int ret;
+
+ if (hwirq_type == GICV5_HWIRQ_TYPE_LPI || hwirq_type == GICV5_HWIRQ_TYPE_SPI) {
+ cdpri = FIELD_PREP(GICV5_GIC_CDPRI_PRIORITY_MASK, priority) |
+ FIELD_PREP(GICV5_GIC_CDPRI_TYPE_MASK, hwirq_type) |
+ FIELD_PREP(GICV5_GIC_CDPRI_ID_MASK, hwirq);
+ gic_insn(cdpri, CDPRI);
+
+ ret = gicv5_irs_cpu_to_iaffid(smp_processor_id(), &iaffid);
+
+ if (WARN_ON_ONCE(ret))
+ return;
+
+ cdaff = FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, iaffid) |
+ FIELD_PREP(GICV5_GIC_CDAFF_TYPE_MASK, hwirq_type) |
+ FIELD_PREP(GICV5_GIC_CDAFF_ID_MASK, hwirq);
+ gic_insn(cdaff, CDAFF);
+ }
+}
+
+static void gicv5_ppi_irq_mask(struct irq_data *d)
+{
+ u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
+
+ if (d->hwirq < 64)
+ sysreg_clear_set_s(SYS_ICC_PPI_ENABLER0_EL1, hwirq_id_bit, 0);
+ else
+ sysreg_clear_set_s(SYS_ICC_PPI_ENABLER1_EL1, hwirq_id_bit, 0);
+
+ /*
+ * We must ensure that the disable takes effect immediately to
+ * guarantee that the lazy-disabled IRQ mechanism works.
+ * A context synchronization event is required to guarantee it.
+ * Reference: I_ZLTKB/R_YRGMH GICv5 specification - section 2.9.1.
+ */
+ isb();
+}
+
+static void gicv5_iri_irq_mask(struct irq_data *d, u8 hwirq_type)
+{
+ u64 cddis;
+
+ cddis = FIELD_PREP(GICV5_GIC_CDDIS_ID_MASK, d->hwirq) |
+ FIELD_PREP(GICV5_GIC_CDDIS_TYPE_MASK, hwirq_type);
+
+ gic_insn(cddis, CDDIS);
+ /*
+ * We must make sure that GIC CDDIS write effects are propagated
+ * immediately to make sure the disable takes effect to guarantee
+ * that the lazy-disabled IRQ mechanism works.
+ * Rule R_XCLJC states that the effects of a GIC system instruction
+ * complete in finite time.
+ * The GSB ensures completion of the GIC instruction and prevents
+ * loads, stores and GIC instructions from executing part of their
+ * functionality before the GSB SYS.
+ */
+ gsb_sys();
+}
+
+static void gicv5_spi_irq_mask(struct irq_data *d)
+{
+ gicv5_iri_irq_mask(d, GICV5_HWIRQ_TYPE_SPI);
+}
+
+static void gicv5_lpi_irq_mask(struct irq_data *d)
+{
+ gicv5_iri_irq_mask(d, GICV5_HWIRQ_TYPE_LPI);
+}
+
+static void gicv5_ppi_irq_unmask(struct irq_data *d)
+{
+ u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
+
+ if (d->hwirq < 64)
+ sysreg_clear_set_s(SYS_ICC_PPI_ENABLER0_EL1, 0, hwirq_id_bit);
+ else
+ sysreg_clear_set_s(SYS_ICC_PPI_ENABLER1_EL1, 0, hwirq_id_bit);
+ /*
+ * We must ensure that the enable takes effect in finite time - a
+ * context synchronization event is required to guarantee it, we
+ * can not take for granted that would happen (eg a core going straight
+ * into idle after enabling a PPI).
+ * Reference: I_ZLTKB/R_YRGMH GICv5 specification - section 2.9.1.
+ */
+ isb();
+}
+
+static void gicv5_iri_irq_unmask(struct irq_data *d, u8 hwirq_type)
+{
+ u64 cden;
+
+ cden = FIELD_PREP(GICV5_GIC_CDEN_ID_MASK, d->hwirq) |
+ FIELD_PREP(GICV5_GIC_CDEN_TYPE_MASK, hwirq_type);
+ /*
+ * Rule R_XCLJC states that the effects of a GIC system instruction
+ * complete in finite time and that's the only requirement when
+ * unmasking an SPI/LPI IRQ.
+ */
+ gic_insn(cden, CDEN);
+}
+
+static void gicv5_spi_irq_unmask(struct irq_data *d)
+{
+ gicv5_iri_irq_unmask(d, GICV5_HWIRQ_TYPE_SPI);
+}
+
+static void gicv5_lpi_irq_unmask(struct irq_data *d)
+{
+ gicv5_iri_irq_unmask(d, GICV5_HWIRQ_TYPE_LPI);
+}
+
+static void gicv5_hwirq_eoi(u32 hwirq_id, u8 hwirq_type)
+{
+ u64 cddi;
+
+ cddi = FIELD_PREP(GICV5_GIC_CDDI_ID_MASK, hwirq_id) |
+ FIELD_PREP(GICV5_GIC_CDDI_TYPE_MASK, hwirq_type);
+
+ gic_insn(cddi, CDDI);
+
+ gic_insn(0, CDEOI);
+}
+
+static void gicv5_ppi_irq_eoi(struct irq_data *d)
+{
+ /* Skip deactivate for forwarded PPI interrupts */
+ if (irqd_is_forwarded_to_vcpu(d)) {
+ gic_insn(0, CDEOI);
+ return;
+ }
+
+ gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_PPI);
+}
+
+static void gicv5_spi_irq_eoi(struct irq_data *d)
+{
+ gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_SPI);
+}
+
+static void gicv5_lpi_irq_eoi(struct irq_data *d)
+{
+ gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_LPI);
+}
+
+static int gicv5_iri_irq_set_affinity(struct irq_data *d,
+ const struct cpumask *mask_val,
+ bool force, u8 hwirq_type)
+{
+ int ret, cpuid;
+ u16 iaffid;
+ u64 cdaff;
+
+ if (force)
+ cpuid = cpumask_first(mask_val);
+ else
+ cpuid = cpumask_any_and(mask_val, cpu_online_mask);
+
+ ret = gicv5_irs_cpu_to_iaffid(cpuid, &iaffid);
+ if (ret)
+ return ret;
+
+ cdaff = FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, iaffid) |
+ FIELD_PREP(GICV5_GIC_CDAFF_TYPE_MASK, hwirq_type) |
+ FIELD_PREP(GICV5_GIC_CDAFF_ID_MASK, d->hwirq);
+ gic_insn(cdaff, CDAFF);
+
+ irq_data_update_effective_affinity(d, cpumask_of(cpuid));
+
+ return IRQ_SET_MASK_OK_DONE;
+}
+
+static int gicv5_spi_irq_set_affinity(struct irq_data *d,
+ const struct cpumask *mask_val,
+ bool force)
+{
+ return gicv5_iri_irq_set_affinity(d, mask_val, force,
+ GICV5_HWIRQ_TYPE_SPI);
+}
+
+static int gicv5_lpi_irq_set_affinity(struct irq_data *d,
+ const struct cpumask *mask_val,
+ bool force)
+{
+ return gicv5_iri_irq_set_affinity(d, mask_val, force,
+ GICV5_HWIRQ_TYPE_LPI);
+}
+
+enum ppi_reg {
+ PPI_PENDING,
+ PPI_ACTIVE,
+ PPI_HM
+};
+
+static __always_inline u64 read_ppi_sysreg_s(unsigned int irq,
+ const enum ppi_reg which)
+{
+ switch (which) {
+ case PPI_PENDING:
+ return irq < 64 ? read_sysreg_s(SYS_ICC_PPI_SPENDR0_EL1) :
+ read_sysreg_s(SYS_ICC_PPI_SPENDR1_EL1);
+ case PPI_ACTIVE:
+ return irq < 64 ? read_sysreg_s(SYS_ICC_PPI_SACTIVER0_EL1) :
+ read_sysreg_s(SYS_ICC_PPI_SACTIVER1_EL1);
+ case PPI_HM:
+ return irq < 64 ? read_sysreg_s(SYS_ICC_PPI_HMR0_EL1) :
+ read_sysreg_s(SYS_ICC_PPI_HMR1_EL1);
+ default:
+ BUILD_BUG_ON(1);
+ }
+}
+
+static __always_inline void write_ppi_sysreg_s(unsigned int irq, bool set,
+ const enum ppi_reg which)
+{
+ u64 bit = BIT_ULL(irq % 64);
+
+ switch (which) {
+ case PPI_PENDING:
+ if (set) {
+ if (irq < 64)
+ write_sysreg_s(bit, SYS_ICC_PPI_SPENDR0_EL1);
+ else
+ write_sysreg_s(bit, SYS_ICC_PPI_SPENDR1_EL1);
+ } else {
+ if (irq < 64)
+ write_sysreg_s(bit, SYS_ICC_PPI_CPENDR0_EL1);
+ else
+ write_sysreg_s(bit, SYS_ICC_PPI_CPENDR1_EL1);
+ }
+ return;
+ case PPI_ACTIVE:
+ if (set) {
+ if (irq < 64)
+ write_sysreg_s(bit, SYS_ICC_PPI_SACTIVER0_EL1);
+ else
+ write_sysreg_s(bit, SYS_ICC_PPI_SACTIVER1_EL1);
+ } else {
+ if (irq < 64)
+ write_sysreg_s(bit, SYS_ICC_PPI_CACTIVER0_EL1);
+ else
+ write_sysreg_s(bit, SYS_ICC_PPI_CACTIVER1_EL1);
+ }
+ return;
+ default:
+ BUILD_BUG_ON(1);
+ }
+}
+
+static int gicv5_ppi_irq_get_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool *state)
+{
+ u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
+
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ *state = !!(read_ppi_sysreg_s(d->hwirq, PPI_PENDING) & hwirq_id_bit);
+ return 0;
+ case IRQCHIP_STATE_ACTIVE:
+ *state = !!(read_ppi_sysreg_s(d->hwirq, PPI_ACTIVE) & hwirq_id_bit);
+ return 0;
+ default:
+ pr_debug("Unexpected PPI irqchip state\n");
+ return -EINVAL;
+ }
+}
+
+static int gicv5_iri_irq_get_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool *state, u8 hwirq_type)
+{
+ u64 icsr, cdrcfg;
+
+ cdrcfg = d->hwirq | FIELD_PREP(GICV5_GIC_CDRCFG_TYPE_MASK, hwirq_type);
+
+ gic_insn(cdrcfg, CDRCFG);
+ isb();
+ icsr = read_sysreg_s(SYS_ICC_ICSR_EL1);
+
+ if (FIELD_GET(ICC_ICSR_EL1_F, icsr)) {
+ pr_err("ICSR_EL1 is invalid\n");
+ return -EINVAL;
+ }
+
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ *state = !!(FIELD_GET(ICC_ICSR_EL1_Pending, icsr));
+ return 0;
+
+ case IRQCHIP_STATE_ACTIVE:
+ *state = !!(FIELD_GET(ICC_ICSR_EL1_Active, icsr));
+ return 0;
+
+ default:
+ pr_debug("Unexpected irqchip_irq_state\n");
+ return -EINVAL;
+ }
+}
+
+static int gicv5_spi_irq_get_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool *state)
+{
+ return gicv5_iri_irq_get_irqchip_state(d, which, state,
+ GICV5_HWIRQ_TYPE_SPI);
+}
+
+static int gicv5_lpi_irq_get_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool *state)
+{
+ return gicv5_iri_irq_get_irqchip_state(d, which, state,
+ GICV5_HWIRQ_TYPE_LPI);
+}
+
+static int gicv5_ppi_irq_set_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool state)
+{
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ write_ppi_sysreg_s(d->hwirq, state, PPI_PENDING);
+ return 0;
+ case IRQCHIP_STATE_ACTIVE:
+ write_ppi_sysreg_s(d->hwirq, state, PPI_ACTIVE);
+ return 0;
+ default:
+ pr_debug("Unexpected PPI irqchip state\n");
+ return -EINVAL;
+ }
+}
+
+static void gicv5_iri_irq_write_pending_state(struct irq_data *d, bool state,
+ u8 hwirq_type)
+{
+ u64 cdpend;
+
+ cdpend = FIELD_PREP(GICV5_GIC_CDPEND_TYPE_MASK, hwirq_type) |
+ FIELD_PREP(GICV5_GIC_CDPEND_ID_MASK, d->hwirq) |
+ FIELD_PREP(GICV5_GIC_CDPEND_PENDING_MASK, state);
+
+ gic_insn(cdpend, CDPEND);
+}
+
+static void gicv5_spi_irq_write_pending_state(struct irq_data *d, bool state)
+{
+ gicv5_iri_irq_write_pending_state(d, state, GICV5_HWIRQ_TYPE_SPI);
+}
+
+static void gicv5_lpi_irq_write_pending_state(struct irq_data *d, bool state)
+{
+ gicv5_iri_irq_write_pending_state(d, state, GICV5_HWIRQ_TYPE_LPI);
+}
+
+static int gicv5_spi_irq_set_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool state)
+{
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ gicv5_spi_irq_write_pending_state(d, state);
+ break;
+ default:
+ pr_debug("Unexpected irqchip_irq_state\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int gicv5_lpi_irq_set_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool state)
+{
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ gicv5_lpi_irq_write_pending_state(d, state);
+ break;
+
+ default:
+ pr_debug("Unexpected irqchip_irq_state\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int gicv5_spi_irq_retrigger(struct irq_data *data)
+{
+ return !gicv5_spi_irq_set_irqchip_state(data, IRQCHIP_STATE_PENDING,
+ true);
+}
+
+static int gicv5_lpi_irq_retrigger(struct irq_data *data)
+{
+ return !gicv5_lpi_irq_set_irqchip_state(data, IRQCHIP_STATE_PENDING,
+ true);
+}
+
+static void gicv5_ipi_send_single(struct irq_data *d, unsigned int cpu)
+{
+ /* Mark the LPI pending */
+ irq_chip_retrigger_hierarchy(d);
+}
+
+static bool gicv5_ppi_irq_is_level(irq_hw_number_t hwirq)
+{
+ u64 bit = BIT_ULL(hwirq % 64);
+
+ return !!(read_ppi_sysreg_s(hwirq, PPI_HM) & bit);
+}
+
+static int gicv5_ppi_irq_set_vcpu_affinity(struct irq_data *d, void *vcpu)
+{
+ if (vcpu)
+ irqd_set_forwarded_to_vcpu(d);
+ else
+ irqd_clr_forwarded_to_vcpu(d);
+
+ return 0;
+}
+
+static const struct irq_chip gicv5_ppi_irq_chip = {
+ .name = "GICv5-PPI",
+ .irq_mask = gicv5_ppi_irq_mask,
+ .irq_unmask = gicv5_ppi_irq_unmask,
+ .irq_eoi = gicv5_ppi_irq_eoi,
+ .irq_get_irqchip_state = gicv5_ppi_irq_get_irqchip_state,
+ .irq_set_irqchip_state = gicv5_ppi_irq_set_irqchip_state,
+ .irq_set_vcpu_affinity = gicv5_ppi_irq_set_vcpu_affinity,
+ .flags = IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static const struct irq_chip gicv5_spi_irq_chip = {
+ .name = "GICv5-SPI",
+ .irq_mask = gicv5_spi_irq_mask,
+ .irq_unmask = gicv5_spi_irq_unmask,
+ .irq_eoi = gicv5_spi_irq_eoi,
+ .irq_set_type = gicv5_spi_irq_set_type,
+ .irq_set_affinity = gicv5_spi_irq_set_affinity,
+ .irq_retrigger = gicv5_spi_irq_retrigger,
+ .irq_get_irqchip_state = gicv5_spi_irq_get_irqchip_state,
+ .irq_set_irqchip_state = gicv5_spi_irq_set_irqchip_state,
+ .flags = IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static const struct irq_chip gicv5_lpi_irq_chip = {
+ .name = "GICv5-LPI",
+ .irq_mask = gicv5_lpi_irq_mask,
+ .irq_unmask = gicv5_lpi_irq_unmask,
+ .irq_eoi = gicv5_lpi_irq_eoi,
+ .irq_set_affinity = gicv5_lpi_irq_set_affinity,
+ .irq_retrigger = gicv5_lpi_irq_retrigger,
+ .irq_get_irqchip_state = gicv5_lpi_irq_get_irqchip_state,
+ .irq_set_irqchip_state = gicv5_lpi_irq_set_irqchip_state,
+ .flags = IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static const struct irq_chip gicv5_ipi_irq_chip = {
+ .name = "GICv5-IPI",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .ipi_send_single = gicv5_ipi_send_single,
+ .flags = IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static __always_inline int gicv5_irq_domain_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ irq_hw_number_t *hwirq,
+ unsigned int *type,
+ const u8 hwirq_type)
+{
+ if (!is_of_node(fwspec->fwnode))
+ return -EINVAL;
+
+ if (fwspec->param_count < 3)
+ return -EINVAL;
+
+ if (fwspec->param[0] != hwirq_type)
+ return -EINVAL;
+
+ *hwirq = fwspec->param[1];
+
+ switch (hwirq_type) {
+ case GICV5_HWIRQ_TYPE_PPI:
+ /*
+ * Handling mode is hardcoded for PPIs, set the type using
+ * HW reported value.
+ */
+ *type = gicv5_ppi_irq_is_level(*hwirq) ? IRQ_TYPE_LEVEL_LOW :
+ IRQ_TYPE_EDGE_RISING;
+ break;
+ case GICV5_HWIRQ_TYPE_SPI:
+ *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
+ break;
+ default:
+ BUILD_BUG_ON(1);
+ }
+
+ return 0;
+}
+
+static int gicv5_irq_ppi_domain_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ irq_hw_number_t *hwirq,
+ unsigned int *type)
+{
+ return gicv5_irq_domain_translate(d, fwspec, hwirq, type,
+ GICV5_HWIRQ_TYPE_PPI);
+}
+
+static int gicv5_irq_ppi_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ unsigned int type = IRQ_TYPE_NONE;
+ struct irq_fwspec *fwspec = arg;
+ irq_hw_number_t hwirq;
+ int ret;
+
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return -EINVAL;
+
+ ret = gicv5_irq_ppi_domain_translate(domain, fwspec, &hwirq, &type);
+ if (ret)
+ return ret;
+
+ if (type & IRQ_TYPE_LEVEL_MASK)
+ irq_set_status_flags(virq, IRQ_LEVEL);
+
+ irq_set_percpu_devid(virq);
+ irq_domain_set_info(domain, virq, hwirq, &gicv5_ppi_irq_chip, NULL,
+ handle_percpu_devid_irq, NULL, NULL);
+
+ return 0;
+}
+
+static void gicv5_irq_domain_free(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct irq_data *d;
+
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return;
+
+ d = irq_domain_get_irq_data(domain, virq);
+
+ irq_set_handler(virq, NULL);
+ irq_domain_reset_irq_data(d);
+}
+
+static int gicv5_irq_ppi_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
+ enum irq_domain_bus_token bus_token)
+{
+ if (fwspec->fwnode != d->fwnode)
+ return 0;
+
+ if (fwspec->param[0] != GICV5_HWIRQ_TYPE_PPI)
+ return 0;
+
+ return (d == gicv5_global_data.ppi_domain);
+}
+
+static const struct irq_domain_ops gicv5_irq_ppi_domain_ops = {
+ .translate = gicv5_irq_ppi_domain_translate,
+ .alloc = gicv5_irq_ppi_domain_alloc,
+ .free = gicv5_irq_domain_free,
+ .select = gicv5_irq_ppi_domain_select
+};
+
+static int gicv5_irq_spi_domain_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ irq_hw_number_t *hwirq,
+ unsigned int *type)
+{
+ return gicv5_irq_domain_translate(d, fwspec, hwirq, type,
+ GICV5_HWIRQ_TYPE_SPI);
+}
+
+static int gicv5_irq_spi_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct gicv5_irs_chip_data *chip_data;
+ unsigned int type = IRQ_TYPE_NONE;
+ struct irq_fwspec *fwspec = arg;
+ struct irq_data *irqd;
+ irq_hw_number_t hwirq;
+ int ret;
+
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return -EINVAL;
+
+ ret = gicv5_irq_spi_domain_translate(domain, fwspec, &hwirq, &type);
+ if (ret)
+ return ret;
+
+ irqd = irq_desc_get_irq_data(irq_to_desc(virq));
+ chip_data = gicv5_irs_lookup_by_spi_id(hwirq);
+
+ irq_domain_set_info(domain, virq, hwirq, &gicv5_spi_irq_chip, chip_data,
+ handle_fasteoi_irq, NULL, NULL);
+ irq_set_probe(virq);
+ irqd_set_single_target(irqd);
+
+ gicv5_hwirq_init(hwirq, GICV5_IRQ_PRI_MI, GICV5_HWIRQ_TYPE_SPI);
+
+ return 0;
+}
+
+static int gicv5_irq_spi_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
+ enum irq_domain_bus_token bus_token)
+{
+ if (fwspec->fwnode != d->fwnode)
+ return 0;
+
+ if (fwspec->param[0] != GICV5_HWIRQ_TYPE_SPI)
+ return 0;
+
+ return (d == gicv5_global_data.spi_domain);
+}
+
+static const struct irq_domain_ops gicv5_irq_spi_domain_ops = {
+ .translate = gicv5_irq_spi_domain_translate,
+ .alloc = gicv5_irq_spi_domain_alloc,
+ .free = gicv5_irq_domain_free,
+ .select = gicv5_irq_spi_domain_select
+};
+
+static void gicv5_lpi_config_reset(struct irq_data *d)
+{
+ u64 cdhm;
+
+ /*
+ * Reset LPIs handling mode to edge by default and clear pending
+ * state to make sure we start the LPI with a clean state from
+ * previous incarnations.
+ */
+ cdhm = FIELD_PREP(GICV5_GIC_CDHM_HM_MASK, 0) |
+ FIELD_PREP(GICV5_GIC_CDHM_TYPE_MASK, GICV5_HWIRQ_TYPE_LPI) |
+ FIELD_PREP(GICV5_GIC_CDHM_ID_MASK, d->hwirq);
+ gic_insn(cdhm, CDHM);
+
+ gicv5_lpi_irq_write_pending_state(d, false);
+}
+
+static int gicv5_irq_lpi_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ irq_hw_number_t hwirq;
+ struct irq_data *irqd;
+ u32 *lpi = arg;
+ int ret;
+
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return -EINVAL;
+
+ hwirq = *lpi;
+
+ irqd = irq_domain_get_irq_data(domain, virq);
+
+ irq_domain_set_info(domain, virq, hwirq, &gicv5_lpi_irq_chip, NULL,
+ handle_fasteoi_irq, NULL, NULL);
+ irqd_set_single_target(irqd);
+
+ ret = gicv5_irs_iste_alloc(hwirq);
+ if (ret < 0)
+ return ret;
+
+ gicv5_hwirq_init(hwirq, GICV5_IRQ_PRI_MI, GICV5_HWIRQ_TYPE_LPI);
+ gicv5_lpi_config_reset(irqd);
+
+ return 0;
+}
+
+static const struct irq_domain_ops gicv5_irq_lpi_domain_ops = {
+ .alloc = gicv5_irq_lpi_domain_alloc,
+ .free = gicv5_irq_domain_free,
+};
+
+void __init gicv5_init_lpi_domain(void)
+{
+ struct irq_domain *d;
+
+ d = irq_domain_create_tree(NULL, &gicv5_irq_lpi_domain_ops, NULL);
+ gicv5_global_data.lpi_domain = d;
+}
+
+void __init gicv5_free_lpi_domain(void)
+{
+ irq_domain_remove(gicv5_global_data.lpi_domain);
+ gicv5_global_data.lpi_domain = NULL;
+}
+
+static int gicv5_irq_ipi_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct irq_data *irqd;
+ int ret, i;
+ u32 lpi;
+
+ for (i = 0; i < nr_irqs; i++) {
+ ret = gicv5_alloc_lpi();
+ if (ret < 0)
+ return ret;
+
+ lpi = ret;
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi);
+ if (ret) {
+ gicv5_free_lpi(lpi);
+ return ret;
+ }
+
+ irqd = irq_domain_get_irq_data(domain, virq + i);
+
+ irq_domain_set_hwirq_and_chip(domain, virq + i, i,
+ &gicv5_ipi_irq_chip, NULL);
+
+ irqd_set_single_target(irqd);
+
+ irq_set_handler(virq + i, handle_percpu_irq);
+ }
+
+ return 0;
+}
+
+static void gicv5_irq_ipi_domain_free(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct irq_data *d;
+ unsigned int i;
+
+ for (i = 0; i < nr_irqs; i++) {
+ d = irq_domain_get_irq_data(domain, virq + i);
+
+ if (!d)
+ return;
+
+ gicv5_free_lpi(d->parent_data->hwirq);
+
+ irq_set_handler(virq + i, NULL);
+ irq_domain_reset_irq_data(d);
+ irq_domain_free_irqs_parent(domain, virq + i, 1);
+ }
+}
+
+static const struct irq_domain_ops gicv5_irq_ipi_domain_ops = {
+ .alloc = gicv5_irq_ipi_domain_alloc,
+ .free = gicv5_irq_ipi_domain_free,
+};
+
+static void handle_irq_per_domain(u32 hwirq)
+{
+ u8 hwirq_type = FIELD_GET(GICV5_HWIRQ_TYPE, hwirq);
+ u32 hwirq_id = FIELD_GET(GICV5_HWIRQ_ID, hwirq);
+ struct irq_domain *domain;
+
+ switch (hwirq_type) {
+ case GICV5_HWIRQ_TYPE_PPI:
+ domain = gicv5_global_data.ppi_domain;
+ break;
+ case GICV5_HWIRQ_TYPE_SPI:
+ domain = gicv5_global_data.spi_domain;
+ break;
+ case GICV5_HWIRQ_TYPE_LPI:
+ domain = gicv5_global_data.lpi_domain;
+ break;
+ default:
+ pr_err_once("Unknown IRQ type, bail out\n");
+ return;
+ }
+
+ if (generic_handle_domain_irq(domain, hwirq_id)) {
+ pr_err_once("Could not handle, hwirq = 0x%x", hwirq_id);
+ gicv5_hwirq_eoi(hwirq_id, hwirq_type);
+ }
+}
+
+static void __exception_irq_entry gicv5_handle_irq(struct pt_regs *regs)
+{
+ bool valid;
+ u32 hwirq;
+ u64 ia;
+
+ ia = gicr_insn(CDIA);
+ valid = GICV5_GICR_CDIA_VALID(ia);
+
+ if (!valid)
+ return;
+
+ /*
+ * Ensure that the CDIA instruction effects (ie IRQ activation) are
+ * completed before handling the interrupt.
+ */
+ gsb_ack();
+
+ /*
+ * Ensure instruction ordering between an acknowledgment and subsequent
+ * instructions in the IRQ handler using an ISB.
+ */
+ isb();
+
+ hwirq = FIELD_GET(GICV5_HWIRQ_INTID, ia);
+
+ handle_irq_per_domain(hwirq);
+}
+
+static void gicv5_cpu_disable_interrupts(void)
+{
+ u64 cr0;
+
+ cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 0);
+ write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
+}
+
+static void gicv5_cpu_enable_interrupts(void)
+{
+ u64 cr0, pcr;
+
+ write_sysreg_s(0, SYS_ICC_PPI_ENABLER0_EL1);
+ write_sysreg_s(0, SYS_ICC_PPI_ENABLER1_EL1);
+
+ gicv5_ppi_priority_init();
+
+ pcr = FIELD_PREP(ICC_PCR_EL1_PRIORITY, GICV5_IRQ_PRI_MI);
+ write_sysreg_s(pcr, SYS_ICC_PCR_EL1);
+
+ cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 1);
+ write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
+}
+
+static int base_ipi_virq;
+
+static int gicv5_starting_cpu(unsigned int cpu)
+{
+ if (WARN(!gicv5_cpuif_has_gcie(),
+ "GICv5 system components present but CPU does not have FEAT_GCIE"))
+ return -ENODEV;
+
+ gicv5_cpu_enable_interrupts();
+
+ return gicv5_irs_register_cpu(cpu);
+}
+
+static void __init gicv5_smp_init(void)
+{
+ unsigned int num_ipis = GICV5_IPIS_PER_CPU * nr_cpu_ids;
+
+ cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
+ "irqchip/arm/gicv5:starting",
+ gicv5_starting_cpu, NULL);
+
+ base_ipi_virq = irq_domain_alloc_irqs(gicv5_global_data.ipi_domain,
+ num_ipis, NUMA_NO_NODE, NULL);
+ if (WARN(base_ipi_virq <= 0, "IPI IRQ allocation was not successful"))
+ return;
+
+ set_smp_ipi_range_percpu(base_ipi_virq, GICV5_IPIS_PER_CPU, nr_cpu_ids);
+}
+
+static void __init gicv5_free_domains(void)
+{
+ if (gicv5_global_data.ppi_domain)
+ irq_domain_remove(gicv5_global_data.ppi_domain);
+ if (gicv5_global_data.spi_domain)
+ irq_domain_remove(gicv5_global_data.spi_domain);
+ if (gicv5_global_data.ipi_domain)
+ irq_domain_remove(gicv5_global_data.ipi_domain);
+
+ gicv5_global_data.ppi_domain = NULL;
+ gicv5_global_data.spi_domain = NULL;
+ gicv5_global_data.ipi_domain = NULL;
+}
+
+static int __init gicv5_init_domains(struct fwnode_handle *handle)
+{
+ u32 spi_count = gicv5_global_data.global_spi_count;
+ struct irq_domain *d;
+
+ d = irq_domain_create_linear(handle, PPI_NR, &gicv5_irq_ppi_domain_ops, NULL);
+ if (!d)
+ return -ENOMEM;
+
+ irq_domain_update_bus_token(d, DOMAIN_BUS_WIRED);
+ gicv5_global_data.ppi_domain = d;
+
+ if (spi_count) {
+ d = irq_domain_create_linear(handle, spi_count,
+ &gicv5_irq_spi_domain_ops, NULL);
+
+ if (!d) {
+ gicv5_free_domains();
+ return -ENOMEM;
+ }
+
+ gicv5_global_data.spi_domain = d;
+ irq_domain_update_bus_token(d, DOMAIN_BUS_WIRED);
+ }
+
+ if (!WARN(!gicv5_global_data.lpi_domain,
+ "LPI domain uninitialized, can't set up IPIs")) {
+ d = irq_domain_create_hierarchy(gicv5_global_data.lpi_domain,
+ 0, GICV5_IPIS_PER_CPU * nr_cpu_ids,
+ NULL, &gicv5_irq_ipi_domain_ops,
+ NULL);
+
+ if (!d) {
+ gicv5_free_domains();
+ return -ENOMEM;
+ }
+ gicv5_global_data.ipi_domain = d;
+ }
+ gicv5_global_data.fwnode = handle;
+
+ return 0;
+}
+
+static void gicv5_set_cpuif_pribits(void)
+{
+ u64 icc_idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
+
+ switch (FIELD_GET(ICC_IDR0_EL1_PRI_BITS, icc_idr0)) {
+ case ICC_IDR0_EL1_PRI_BITS_4BITS:
+ gicv5_global_data.cpuif_pri_bits = 4;
+ break;
+ case ICC_IDR0_EL1_PRI_BITS_5BITS:
+ gicv5_global_data.cpuif_pri_bits = 5;
+ break;
+ default:
+ pr_err("Unexpected ICC_IDR0_EL1_PRI_BITS value, default to 4");
+ gicv5_global_data.cpuif_pri_bits = 4;
+ break;
+ }
+}
+
+static void gicv5_set_cpuif_idbits(void)
+{
+ u32 icc_idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
+
+ switch (FIELD_GET(ICC_IDR0_EL1_ID_BITS, icc_idr0)) {
+ case ICC_IDR0_EL1_ID_BITS_16BITS:
+ gicv5_global_data.cpuif_id_bits = 16;
+ break;
+ case ICC_IDR0_EL1_ID_BITS_24BITS:
+ gicv5_global_data.cpuif_id_bits = 24;
+ break;
+ default:
+ pr_err("Unexpected ICC_IDR0_EL1_ID_BITS value, default to 16");
+ gicv5_global_data.cpuif_id_bits = 16;
+ break;
+ }
+}
+
+#ifdef CONFIG_KVM
+static struct gic_kvm_info gic_v5_kvm_info __initdata;
+
+static bool __init gicv5_cpuif_has_gcie_legacy(void)
+{
+ u64 idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
+ return !!FIELD_GET(ICC_IDR0_EL1_GCIE_LEGACY, idr0);
+}
+
+static void __init gic_of_setup_kvm_info(struct device_node *node)
+{
+ gic_v5_kvm_info.type = GIC_V5;
+ gic_v5_kvm_info.has_gcie_v3_compat = gicv5_cpuif_has_gcie_legacy();
+
+ /* GIC Virtual CPU interface maintenance interrupt */
+ gic_v5_kvm_info.no_maint_irq_mask = false;
+ gic_v5_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
+ if (!gic_v5_kvm_info.maint_irq) {
+ pr_warn("cannot find GICv5 virtual CPU interface maintenance interrupt\n");
+ return;
+ }
+
+ vgic_set_kvm_info(&gic_v5_kvm_info);
+}
+#else
+static inline void __init gic_of_setup_kvm_info(struct device_node *node)
+{
+}
+#endif // CONFIG_KVM
+
+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_domains(of_fwnode_handle(node));
+ if (ret)
+ goto out_irs;
+
+ gicv5_set_cpuif_pribits();
+ gicv5_set_cpuif_idbits();
+
+ pri_bits = min_not_zero(gicv5_global_data.cpuif_pri_bits,
+ gicv5_global_data.irs_pri_bits);
+
+ ret = gicv5_starting_cpu(smp_processor_id());
+ if (ret)
+ goto out_dom;
+
+ ret = set_handle_irq(gicv5_handle_irq);
+ if (ret)
+ goto out_int;
+
+ ret = gicv5_irs_enable();
+ if (ret)
+ goto out_int;
+
+ 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();
+out_irs:
+ gicv5_irs_remove();
+
+ return ret;
+}
+IRQCHIP_DECLARE(gic_v5, "arm,gic-v5", gicv5_of_init);
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c
index 6503573557fd..1269ab8eb726 100644
--- a/drivers/irqchip/irq-gic.c
+++ b/drivers/irqchip/irq-gic.c
@@ -54,7 +54,7 @@
static void gic_check_cpu_features(void)
{
- WARN_TAINT_ONCE(this_cpu_has_cap(ARM64_HAS_GIC_CPUIF_SYSREGS),
+ WARN_TAINT_ONCE(this_cpu_has_cap(ARM64_HAS_GICV3_CPUIF),
TAINT_CPU_OUT_OF_SPEC,
"GICv3 system registers enabled, broken firmware!\n");
}
diff --git a/drivers/irqchip/irq-msi-lib.c b/drivers/irqchip/irq-msi-lib.c
index 246c30205af4..454c7f16dd4d 100644
--- a/drivers/irqchip/irq-msi-lib.c
+++ b/drivers/irqchip/irq-msi-lib.c
@@ -133,11 +133,14 @@ int msi_lib_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
{
const struct msi_parent_ops *ops = d->msi_parent_ops;
u32 busmask = BIT(bus_token);
+ struct fwnode_handle *fwh;
if (!ops)
return 0;
- if (fwspec->fwnode != d->fwnode || fwspec->param_count != 0)
+ fwh = d->flags & IRQ_DOMAIN_FLAG_FWNODE_PARENT ? fwnode_get_parent(fwspec->fwnode)
+ : fwspec->fwnode;
+ if (fwh != d->fwnode || fwspec->param_count != 0)
return 0;
/* Handle pure domain searches */