The GICv5 CPU interface implements support for PE-Private Peripheral
Interrupts (PPI), that are handled (enabled/prioritized/delivered)
entirely within the CPU interface hardware.
To enable PPI interrupts, implement the baseline GICv5 host kernel
driver infrastructure required to handle interrupts on a GICv5 system.
Add the exception handling code path and definitions for GICv5
instructions.
Add GICv5 PPI handling code as a specific IRQ domain to:
- Set-up PPI priority
- Manage PPI configuration and state
- Manage IRQ flow handler
- IRQs allocation/free
- Hook-up a PPI specific IRQchip to provide the relevant methods
PPI IRQ priority is chosen as the minimum allowed priority by the
system design (after probing the number of priority bits implemented
by the CPU interface).
Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
MAINTAINERS | 2 +
arch/arm64/include/asm/sysreg.h | 19 ++
drivers/irqchip/Kconfig | 5 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-gic-v5.c | 460 +++++++++++++++++++++++++++++++++++++
include/linux/irqchip/arm-gic-v5.h | 16 ++
6 files changed, 503 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index d51efac8f9aa21629a0486977fdc76a2eaf5c52f..14d25cd8cd323b8f61b6523784ee65d63f6c1924 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1907,6 +1907,8 @@ M: Marc Zyngier <maz@kernel.org>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Maintained
F: Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5*.yaml
+F: drivers/irqchip/irq-gic-v5*.[ch]
+F: include/linux/irqchip/arm-gic-v5.h
ARM HDLCD DRM DRIVER
M: Liviu Dudau <liviu.dudau@arm.com>
diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index e7734f90bb723bfbd8be99f16dd6d6fdc7fa57e8..9d28d408f9c6df24526dd8ecbf3c7d920246b22d 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -1079,6 +1079,25 @@
#define GCS_CAP(x) ((((unsigned long)x) & GCS_CAP_ADDR_MASK) | \
GCS_CAP_VALID_TOKEN)
+/*
+ * Definitions for GICv5 instructions
+ */
+#define GICV5_OP_GIC_CDDI sys_insn(1, 0, 12, 2, 0)
+#define GICV5_OP_GIC_CDEOI sys_insn(1, 0, 12, 1, 7)
+#define GICV5_OP_GICR_CDIA sys_insn(1, 0, 12, 3, 0)
+
+/* Shift and mask definitions for GIC CDDI */
+#define GICV5_GIC_CDDI_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDDI_ID_MASK GENMASK_ULL(23, 0)
+
+/* Shift and mask definitions for GICR CDIA */
+#define GICV5_GIC_CDIA_VALID_MASK BIT_ULL(32)
+#define GICV5_GIC_CDIA_VALID(r) FIELD_GET(GICV5_GIC_CDIA_VALID_MASK, r)
+#define GICV5_GIC_CDIA_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDIA_ID_MASK GENMASK_ULL(23, 0)
+
+#define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_##insn)
+#define gic_insn(v, insn) write_sysreg_s(v, GICV5_OP_GIC_##insn)
#define ARM64_FEATURE_FIELD_BITS 4
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 08bb3b031f23093311cf2f0918ad43e575b581d1..0f268f35b78531775aa233bfc362bfe119a68275 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -54,6 +54,11 @@ 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
+
config ARM_NVIC
bool
select IRQ_DOMAIN_HIERARCHY
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 365bcea9a61ff89e2cb41034125b3fc8cd494d81..3f8225bba5f0f9ce5dbb629b6d4782eacf85da44 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -35,6 +35,7 @@ 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_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
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-v5.c b/drivers/irqchip/irq-gic-v5.c
new file mode 100644
index 0000000000000000000000000000000000000000..a50982e5d98816d88e4fca37cc0ac31684fb6c76
--- /dev/null
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
+ */
+
+#define pr_fmt(fmt) "GICv5: " fmt
+
+#include <linux/irqdomain.h>
+#include <linux/wordpart.h>
+
+#include <linux/irqchip.h>
+#include <linux/irqchip/arm-gic-v5.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 {
+ struct fwnode_handle *fwnode;
+ struct irq_domain *ppi_domain;
+};
+
+static struct gicv5_chip_data gicv5_global_data __read_mostly;
+
+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_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_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_hwirq_eoi(u32 hwirq_id, u8 hwirq_type)
+{
+ u64 cddi = 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)
+{
+ gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_PPI);
+}
+
+enum {
+ PPI_PENDING,
+ PPI_ACTIVE,
+ PPI_HM
+};
+
+static __always_inline u64 read_ppi_sysreg_s(unsigned int irq,
+ const unsigned int 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 unsigned int 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 *val)
+{
+ u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
+
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ *val = !!(read_ppi_sysreg_s(d->hwirq, PPI_PENDING) & hwirq_id_bit);
+ return 0;
+ case IRQCHIP_STATE_ACTIVE:
+ *val = !!(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_ppi_irq_set_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool val)
+{
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ write_ppi_sysreg_s(d->hwirq, val, PPI_PENDING);
+ return 0;
+ case IRQCHIP_STATE_ACTIVE:
+ write_ppi_sysreg_s(d->hwirq, val, PPI_ACTIVE);
+ return 0;
+ default:
+ pr_debug("Unexpected PPI irqchip state\n");
+ return -EINVAL;
+ }
+}
+
+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 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,
+ .flags = IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static int gicv5_irq_ppi_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 < 3)
+ return -EINVAL;
+
+ if (fwspec->param[0] != GICV5_HWIRQ_TYPE_PPI)
+ return -EINVAL;
+
+ *hwirq = fwspec->param[1];
+
+ /*
+ * 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;
+
+ return 0;
+}
+
+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 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;
+ 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_GIC_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 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 0;
+}
+
+static void __init gicv5_free_domains(void)
+{
+ if (gicv5_global_data.ppi_domain)
+ irq_domain_remove(gicv5_global_data.ppi_domain);
+
+ gicv5_global_data.ppi_domain = NULL;
+}
+
+static int __init gicv5_init_domains(struct fwnode_handle *handle)
+{
+ 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;
+ 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:
+ pri_bits = 4;
+ break;
+ case ICC_IDR0_EL1_PRI_BITS_5BITS:
+ pri_bits = 5;
+ break;
+ default:
+ pr_err("Unexpected ICC_IDR0_EL1_PRI_BITS value, default to 4");
+ pri_bits = 4;
+ break;
+ }
+}
+
+static int __init gicv5_of_init(struct device_node *node, struct device_node *parent)
+{
+ int ret = gicv5_init_domains(&node->fwnode);
+ if (ret)
+ return ret;
+
+ gicv5_set_cpuif_pribits();
+
+ ret = gicv5_starting_cpu(smp_processor_id());
+ if (ret)
+ goto out_dom;
+
+ ret = set_handle_irq(gicv5_handle_irq);
+ if (ret)
+ goto out_int;
+
+ return 0;
+out_int:
+ gicv5_cpu_disable_interrupts();
+out_dom:
+ gicv5_free_domains();
+
+ return ret;
+}
+IRQCHIP_DECLARE(gic_v5, "arm,gic-v5", gicv5_of_init);
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
new file mode 100644
index 0000000000000000000000000000000000000000..4ff0ba64d9840c3844671f7850bb3d81ba2eb1b6
--- /dev/null
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 ARM Limited, All Rights Reserved.
+ */
+#ifndef __LINUX_IRQCHIP_ARM_GIC_V5_H
+#define __LINUX_IRQCHIP_ARM_GIC_V5_H
+
+#include <asm/sysreg.h>
+
+#define GICV5_HWIRQ_ID GENMASK(23, 0)
+#define GICV5_HWIRQ_TYPE GENMASK(31, 29)
+#define GICV5_HWIRQ_INTID GENMASK_ULL(31, 0)
+
+#define GICV5_HWIRQ_TYPE_PPI UL(0x1)
+
+#endif
--
2.48.0
On Tue, 13 May 2025 19:48:13 +0200
Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> The GICv5 CPU interface implements support for PE-Private Peripheral
> Interrupts (PPI), that are handled (enabled/prioritized/delivered)
> entirely within the CPU interface hardware.
>
> To enable PPI interrupts, implement the baseline GICv5 host kernel
> driver infrastructure required to handle interrupts on a GICv5 system.
>
> Add the exception handling code path and definitions for GICv5
> instructions.
>
> Add GICv5 PPI handling code as a specific IRQ domain to:
>
> - Set-up PPI priority
> - Manage PPI configuration and state
> - Manage IRQ flow handler
> - IRQs allocation/free
> - Hook-up a PPI specific IRQchip to provide the relevant methods
>
> PPI IRQ priority is chosen as the minimum allowed priority by the
> system design (after probing the number of priority bits implemented
> by the CPU interface).
>
> Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> Cc: Will Deacon <will@kernel.org>
> Cc: Thomas Gleixner <tglx@linutronix.de>
> Cc: Catalin Marinas <catalin.marinas@arm.com>
> Cc: Marc Zyngier <maz@kernel.org>
A few trivial things inline.
J
> ---
> MAINTAINERS | 2 +
> arch/arm64/include/asm/sysreg.h | 19 ++
> drivers/irqchip/Kconfig | 5 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-gic-v5.c | 460 +++++++++++++++++++++++++++++++++++++
> include/linux/irqchip/arm-gic-v5.h | 16 ++
> 6 files changed, 503 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d51efac8f9aa21629a0486977fdc76a2eaf5c52f..14d25cd8cd323b8f61b6523784ee65d63f6c1924 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
> index e7734f90bb723bfbd8be99f16dd6d6fdc7fa57e8..9d28d408f9c6df24526dd8ecbf3c7d920246b22d 100644
> --- a/arch/arm64/include/asm/sysreg.h
> +++ b/arch/arm64/include/asm/sysreg.h
> @@ -1079,6 +1079,25 @@
>
> #define GCS_CAP(x) ((((unsigned long)x) & GCS_CAP_ADDR_MASK) | \
> GCS_CAP_VALID_TOKEN)
> +/*
> + * Definitions for GICv5 instructions
> + */
> +#define GICV5_OP_GIC_CDDI sys_insn(1, 0, 12, 2, 0)
> +#define GICV5_OP_GIC_CDEOI sys_insn(1, 0, 12, 1, 7)
> +#define GICV5_OP_GICR_CDIA sys_insn(1, 0, 12, 3, 0)
> +
> +/* Shift and mask definitions for GIC CDDI */
Technically just masks (which are shifted) but none the less I wouldn't
expect the comment to say Shift and mask.
> +#define GICV5_GIC_CDDI_TYPE_MASK GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDDI_ID_MASK GENMASK_ULL(23, 0)
> +
> +/* Shift and mask definitions for GICR CDIA */
Likewise.
> +#define GICV5_GIC_CDIA_VALID_MASK BIT_ULL(32)
Maybe
GICV5_GICR_CDIA_VALID(r) etc given the instruction define name.
> +#define GICV5_GIC_CDIA_VALID(r) FIELD_GET(GICV5_GIC_CDIA_VALID_MASK, r)
Personally I rarely see benefit in wrapping FIELD_GET() in another macro
The bare code is only a little shorter and the FIELD_GET() inline keeps things nice
and clear. It's your code though so keep this if you really want to!
> +#define GICV5_GIC_CDIA_TYPE_MASK GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDIA_ID_MASK GENMASK_ULL(23, 0)
> +
> +#define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_##insn)
> +#define gic_insn(v, insn) write_sysreg_s(v, GICV5_OP_GIC_##insn)
>
> #define ARM64_FEATURE_FIELD_BITS 4
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index 08bb3b031f23093311cf2f0918ad43e575b581d1..0f268f35b78531775aa233bfc362bfe119a68275 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -54,6 +54,11 @@ 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
> +
> config ARM_NVIC
> bool
> select IRQ_DOMAIN_HIERARCHY
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 365bcea9a61ff89e2cb41034125b3fc8cd494d81..3f8225bba5f0f9ce5dbb629b6d4782eacf85da44 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -35,6 +35,7 @@ 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_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
> 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-v5.c b/drivers/irqchip/irq-gic-v5.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..a50982e5d98816d88e4fca37cc0ac31684fb6c76
> --- /dev/null
> +++ b/drivers/irqchip/irq-gic-v5.c
> @@ -0,0 +1,460 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
> + */
> +
> +#define pr_fmt(fmt) "GICv5: " fmt
> +
> +#include <linux/irqdomain.h>
> +#include <linux/wordpart.h>
> +
> +#include <linux/irqchip.h>
> +#include <linux/irqchip/arm-gic-v5.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))
> +struct gicv5_chip_data {
> + struct fwnode_handle *fwnode;
> + struct irq_domain *ppi_domain;
> +};
> +
> +static struct gicv5_chip_data gicv5_global_data __read_mostly;
> +enum {
> + PPI_PENDING,
> + PPI_ACTIVE,
> + PPI_HM
> +};
> +
> +static __always_inline u64 read_ppi_sysreg_s(unsigned int irq,
> + const unsigned int which)
Name the enum and use that here rather than an unsigned int?
Might as well give the compiler a hand.
Maybe I'm missing a later use of this that means we can't do that.
This is almost enough combinations to justify a look up table but
I guess the compiler might not figure out how to optimize that.
> +{
> + 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 unsigned int which)
Likewise - nicer with enum perhaps.
> +{
> + 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 *val)
> +{
> + u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
> +
> + switch (which) {
> + case IRQCHIP_STATE_PENDING:
> + *val = !!(read_ppi_sysreg_s(d->hwirq, PPI_PENDING) & hwirq_id_bit);
The !! isn't needed AFAICS but maybe adds a small amount of documentation value if
people don't notice that *val is a bool. I'd call it state as per the
definition as that's kind of more obviously boolean than 'val'.
> + return 0;
> + case IRQCHIP_STATE_ACTIVE:
> + *val = !!(read_ppi_sysreg_s(d->hwirq, PPI_ACTIVE) & hwirq_id_bit);
> + return 0;
> + default:
> + pr_debug("Unexpected PPI irqchip state\n");
> + return -EINVAL;
> + }
> +}
> +
> +static void __exception_irq_entry gicv5_handle_irq(struct pt_regs *regs)
> +{
> + bool valid;
> + u32 hwirq;
> + u64 ia;
> +
> + ia = gicr_insn(CDIA);
> + valid = GICV5_GIC_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);
As below - the GICV5_HWIRQ defines other than this one are going from
hwirq to something the GIC cares about - this one is extracting the
software managed hwirq from the CDIA register.
> +
> + 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);
This might get more complex later, but if not why not squash
to one line? Given the register name is right there, there
isn't a lot of documentation benefit in having cr0 as
the variable name.
> +}
> +
> +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);
Similar to above, I'd squash into single line.
> +}
> +
> +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"))
Alignment off to my eyes. Either a tab or align with !
> + return -ENODEV;
> +
> + gicv5_cpu_enable_interrupts();
> +
> + return 0;
> +}
> +
> diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..4ff0ba64d9840c3844671f7850bb3d81ba2eb1b6
> --- /dev/null
> +++ b/include/linux/irqchip/arm-gic-v5.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 ARM Limited, All Rights Reserved.
> + */
> +#ifndef __LINUX_IRQCHIP_ARM_GIC_V5_H
> +#define __LINUX_IRQCHIP_ARM_GIC_V5_H
> +
> +#include <asm/sysreg.h>
> +
> +#define GICV5_HWIRQ_ID GENMASK(23, 0)
> +#define GICV5_HWIRQ_TYPE GENMASK(31, 29)
> +#define GICV5_HWIRQ_INTID GENMASK_ULL(31, 0)
Maybe some hint as to what these are in from their naming?
First two are from hwirq as defined in the irq domain stuff.
Not the 3rd one if I follow this right.
> +
> +#define GICV5_HWIRQ_TYPE_PPI UL(0x1)
> +
> +#endif
>
On Wed, May 28, 2025 at 03:15:24PM +0100, Jonathan Cameron wrote:
[...]
> > +/* Shift and mask definitions for GIC CDDI */
>
> Technically just masks (which are shifted) but none the less I wouldn't
> expect the comment to say Shift and mask.
Fixed.
>
> > +#define GICV5_GIC_CDDI_TYPE_MASK GENMASK_ULL(31, 29)
> > +#define GICV5_GIC_CDDI_ID_MASK GENMASK_ULL(23, 0)
> > +
> > +/* Shift and mask definitions for GICR CDIA */
>
> Likewise.
>
> > +#define GICV5_GIC_CDIA_VALID_MASK BIT_ULL(32)
>
> Maybe
> GICV5_GICR_CDIA_VALID(r) etc given the instruction define name.
Yes I can do that.
> > +#define GICV5_GIC_CDIA_VALID(r) FIELD_GET(GICV5_GIC_CDIA_VALID_MASK, r)
>
> Personally I rarely see benefit in wrapping FIELD_GET() in another macro
> The bare code is only a little shorter and the FIELD_GET() inline keeps things nice
> and clear. It's your code though so keep this if you really want to!
For these things to be honest I am reluctant to change them, it is
subjective - we may end up arguing forever.
> > +#define GICV5_GIC_CDIA_TYPE_MASK GENMASK_ULL(31, 29)
> > +#define GICV5_GIC_CDIA_ID_MASK GENMASK_ULL(23, 0)
> > +
> > +#define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_##insn)
> > +#define gic_insn(v, insn) write_sysreg_s(v, GICV5_OP_GIC_##insn)
> >
> > #define ARM64_FEATURE_FIELD_BITS 4
> >
> > diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> > index 08bb3b031f23093311cf2f0918ad43e575b581d1..0f268f35b78531775aa233bfc362bfe119a68275 100644
> > --- a/drivers/irqchip/Kconfig
> > +++ b/drivers/irqchip/Kconfig
> > @@ -54,6 +54,11 @@ 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
> > +
> > config ARM_NVIC
> > bool
> > select IRQ_DOMAIN_HIERARCHY
> > diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> > index 365bcea9a61ff89e2cb41034125b3fc8cd494d81..3f8225bba5f0f9ce5dbb629b6d4782eacf85da44 100644
> > --- a/drivers/irqchip/Makefile
> > +++ b/drivers/irqchip/Makefile
> > @@ -35,6 +35,7 @@ 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_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
> > 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-v5.c b/drivers/irqchip/irq-gic-v5.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..a50982e5d98816d88e4fca37cc0ac31684fb6c76
> > --- /dev/null
> > +++ b/drivers/irqchip/irq-gic-v5.c
> > @@ -0,0 +1,460 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
> > + */
> > +
> > +#define pr_fmt(fmt) "GICv5: " fmt
> > +
> > +#include <linux/irqdomain.h>
> > +#include <linux/wordpart.h>
> > +
> > +#include <linux/irqchip.h>
> > +#include <linux/irqchip/arm-gic-v5.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))
>
>
> > +struct gicv5_chip_data {
> > + struct fwnode_handle *fwnode;
> > + struct irq_domain *ppi_domain;
> > +};
> > +
> > +static struct gicv5_chip_data gicv5_global_data __read_mostly;
>
>
> > +enum {
> > + PPI_PENDING,
> > + PPI_ACTIVE,
> > + PPI_HM
> > +};
> > +
> > +static __always_inline u64 read_ppi_sysreg_s(unsigned int irq,
> > + const unsigned int which)
>
> Name the enum and use that here rather than an unsigned int?
> Might as well give the compiler a hand.
> Maybe I'm missing a later use of this that means we can't do that.
>
> This is almost enough combinations to justify a look up table but
> I guess the compiler might not figure out how to optimize that.
Yes, while writing it I initially used a named enum, then switched
to this, does not change anything, function is always inline and either
the compiler manages to remove switch cases or the build fails :)
so at the end of the day a named enum does not change much, I
will change it to that though.
>
> > +{
> > + 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 unsigned int which)
>
> Likewise - nicer with enum perhaps.
Done.
>
> > +{
> > + 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 *val)
> > +{
> > + u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
> > +
> > + switch (which) {
> > + case IRQCHIP_STATE_PENDING:
> > + *val = !!(read_ppi_sysreg_s(d->hwirq, PPI_PENDING) & hwirq_id_bit);
>
> The !! isn't needed AFAICS but maybe adds a small amount of documentation value if
> people don't notice that *val is a bool. I'd call it state as per the
> definition as that's kind of more obviously boolean than 'val'.
Ok for naming it 'state'.
> > + return 0;
> > + case IRQCHIP_STATE_ACTIVE:
> > + *val = !!(read_ppi_sysreg_s(d->hwirq, PPI_ACTIVE) & hwirq_id_bit);
> > + return 0;
> > + default:
> > + pr_debug("Unexpected PPI irqchip state\n");
> > + return -EINVAL;
> > + }
> > +}
>
>
> > +
> > +static void __exception_irq_entry gicv5_handle_irq(struct pt_regs *regs)
> > +{
> > + bool valid;
> > + u32 hwirq;
> > + u64 ia;
> > +
> > + ia = gicr_insn(CDIA);
> > + valid = GICV5_GIC_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);
>
> As below - the GICV5_HWIRQ defines other than this one are going from
> hwirq to something the GIC cares about - this one is extracting the
> software managed hwirq from the CDIA register.
I don't get what you mean. We are extracting the HW INTID from the value
read with CDIA.
>
> > +
> > + 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);
>
> This might get more complex later, but if not why not squash
> to one line? Given the register name is right there, there
> isn't a lot of documentation benefit in having cr0 as
> the variable name.
See above. No rule on the matter - it is subjective so I am
reluctant to change it.
> > +}
> > +
> > +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);
>
> Similar to above, I'd squash into single line.
Ditto.
> > +}
> > +
> > +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"))
>
> Alignment off to my eyes. Either a tab or align with !
Fixed it.
> > + return -ENODEV;
> > +
> > + gicv5_cpu_enable_interrupts();
> > +
> > + return 0;
> > +}
> > +
>
> > diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..4ff0ba64d9840c3844671f7850bb3d81ba2eb1b6
> > --- /dev/null
> > +++ b/include/linux/irqchip/arm-gic-v5.h
> > @@ -0,0 +1,16 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * Copyright (C) 2025 ARM Limited, All Rights Reserved.
> > + */
> > +#ifndef __LINUX_IRQCHIP_ARM_GIC_V5_H
> > +#define __LINUX_IRQCHIP_ARM_GIC_V5_H
> > +
> > +#include <asm/sysreg.h>
> > +
> > +#define GICV5_HWIRQ_ID GENMASK(23, 0)
> > +#define GICV5_HWIRQ_TYPE GENMASK(31, 29)
> > +#define GICV5_HWIRQ_INTID GENMASK_ULL(31, 0)
>
> Maybe some hint as to what these are in from their naming?
>
> First two are from hwirq as defined in the irq domain stuff.
> Not the 3rd one if I follow this right.
The three of them are there to handle the HW GICv5 INTID and its related
fields (Rule R_TJPHS), maybe I should add a comment highlighting this ?
Thanks,
Lorenzo
> > +
> > +#define GICV5_HWIRQ_TYPE_PPI UL(0x1)
> > +
> > +#endif
> >
>
© 2016 - 2026 Red Hat, Inc.