Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

irqchip/gic-v5: Add GICv5 IWB support

The GICv5 architecture implements the Interrupt Wire Bridge (IWB) in
order to support wired interrupts that cannot be connected directly
to an IRS and instead uses the ITS to translate a wire event into
an IRQ signal.

Add the wired-to-MSI IWB driver to manage IWB wired interrupts.

An IWB is connected to an ITS and it has its own deviceID for all
interrupt wires that it manages; the IWB input wire number must be
exposed to the ITS as an eventID with a 1:1 mapping.

This eventID is not programmable and therefore requires a new
msi_alloc_info_t flag to make sure the ITS driver does not allocate
an eventid for the wire but rather it uses the msi_alloc_info_t.hwirq
number to gather the ITS eventID.

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>
Reviewed-by: Marc Zyngier <maz@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20250703-gicv5-host-v7-29-12e71f1b3528@kernel.org
Signed-off-by: Marc Zyngier <maz@kernel.org>

authored by

Lorenzo Pieralisi and committed by
Marc Zyngier
695949d8 57d72196

+335 -10
+2 -1
drivers/irqchip/Makefile
··· 37 37 obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o 38 38 obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o 39 39 obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o 40 - obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o irq-gic-v5-its.o 40 + obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o irq-gic-v5-its.o \ 41 + irq-gic-v5-iwb.o 41 42 obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o 42 43 obj-$(CONFIG_ARM_NVIC) += irq-nvic.o 43 44 obj-$(CONFIG_ARM_VIC) += irq-vic.o
+31 -9
drivers/irqchip/irq-gic-v5-its.c
··· 880 880 gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id); 881 881 } 882 882 883 - static int gicv5_its_alloc_eventid(struct gicv5_its_dev *its_dev, 883 + static int gicv5_its_alloc_eventid(struct gicv5_its_dev *its_dev, msi_alloc_info_t *info, 884 884 unsigned int nr_irqs, u32 *eventid) 885 885 { 886 - int ret; 886 + int event_id_base; 887 887 888 - ret = bitmap_find_free_region(its_dev->event_map, 889 - its_dev->num_events, 890 - get_count_order(nr_irqs)); 888 + if (!(info->flags & MSI_ALLOC_FLAGS_FIXED_MSG_DATA)) { 889 + event_id_base = bitmap_find_free_region(its_dev->event_map, 890 + its_dev->num_events, 891 + get_count_order(nr_irqs)); 892 + if (event_id_base < 0) 893 + return event_id_base; 894 + } else { 895 + /* 896 + * We want to have a fixed EventID mapped for hardcoded 897 + * message data allocations. 898 + */ 899 + if (WARN_ON_ONCE(nr_irqs != 1)) 900 + return -EINVAL; 891 901 892 - if (ret < 0) 893 - return ret; 902 + event_id_base = info->hwirq; 894 903 895 - *eventid = ret; 904 + if (event_id_base >= its_dev->num_events) { 905 + pr_err("EventID ouside of ITT range; cannot allocate an ITT entry!\n"); 906 + 907 + return -EINVAL; 908 + } 909 + 910 + if (test_and_set_bit(event_id_base, its_dev->event_map)) { 911 + pr_warn("Can't reserve event_id bitmap\n"); 912 + return -EINVAL; 913 + 914 + } 915 + } 916 + 917 + *eventid = event_id_base; 896 918 897 919 return 0; 898 920 } ··· 938 916 939 917 its_dev = info->scratchpad[0].ptr; 940 918 941 - ret = gicv5_its_alloc_eventid(its_dev, nr_irqs, &event_id_base); 919 + ret = gicv5_its_alloc_eventid(its_dev, info, nr_irqs, &event_id_base); 942 920 if (ret) 943 921 return ret; 944 922
+284
drivers/irqchip/irq-gic-v5-iwb.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved. 4 + */ 5 + #define pr_fmt(fmt) "GICv5 IWB: " fmt 6 + 7 + #include <linux/init.h> 8 + #include <linux/kernel.h> 9 + #include <linux/msi.h> 10 + #include <linux/of.h> 11 + #include <linux/of_address.h> 12 + #include <linux/of_platform.h> 13 + 14 + #include <linux/irqchip.h> 15 + #include <linux/irqchip/arm-gic-v5.h> 16 + 17 + struct gicv5_iwb_chip_data { 18 + void __iomem *iwb_base; 19 + u16 nr_regs; 20 + }; 21 + 22 + static u32 iwb_readl_relaxed(struct gicv5_iwb_chip_data *iwb_node, const u32 reg_offset) 23 + { 24 + return readl_relaxed(iwb_node->iwb_base + reg_offset); 25 + } 26 + 27 + static void iwb_writel_relaxed(struct gicv5_iwb_chip_data *iwb_node, const u32 val, 28 + const u32 reg_offset) 29 + { 30 + writel_relaxed(val, iwb_node->iwb_base + reg_offset); 31 + } 32 + 33 + static int gicv5_iwb_wait_for_wenabler(struct gicv5_iwb_chip_data *iwb_node) 34 + { 35 + return gicv5_wait_for_op_atomic(iwb_node->iwb_base, GICV5_IWB_WENABLE_STATUSR, 36 + GICV5_IWB_WENABLE_STATUSR_IDLE, NULL); 37 + } 38 + 39 + static int __gicv5_iwb_set_wire_enable(struct gicv5_iwb_chip_data *iwb_node, 40 + u32 iwb_wire, bool enable) 41 + { 42 + u32 n = iwb_wire / 32; 43 + u8 i = iwb_wire % 32; 44 + u32 val; 45 + 46 + if (n >= iwb_node->nr_regs) { 47 + pr_err("IWB_WENABLER<n> is invalid for n=%u\n", n); 48 + return -EINVAL; 49 + } 50 + 51 + /* 52 + * Enable IWB wire/pin at this point 53 + * Note: This is not the same as enabling the interrupt 54 + */ 55 + val = iwb_readl_relaxed(iwb_node, GICV5_IWB_WENABLER + (4 * n)); 56 + if (enable) 57 + val |= BIT(i); 58 + else 59 + val &= ~BIT(i); 60 + iwb_writel_relaxed(iwb_node, val, GICV5_IWB_WENABLER + (4 * n)); 61 + 62 + return gicv5_iwb_wait_for_wenabler(iwb_node); 63 + } 64 + 65 + static int gicv5_iwb_enable_wire(struct gicv5_iwb_chip_data *iwb_node, 66 + u32 iwb_wire) 67 + { 68 + return __gicv5_iwb_set_wire_enable(iwb_node, iwb_wire, true); 69 + } 70 + 71 + static int gicv5_iwb_disable_wire(struct gicv5_iwb_chip_data *iwb_node, 72 + u32 iwb_wire) 73 + { 74 + return __gicv5_iwb_set_wire_enable(iwb_node, iwb_wire, false); 75 + } 76 + 77 + static void gicv5_iwb_irq_disable(struct irq_data *d) 78 + { 79 + struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d); 80 + 81 + gicv5_iwb_disable_wire(iwb_node, d->hwirq); 82 + irq_chip_disable_parent(d); 83 + } 84 + 85 + static void gicv5_iwb_irq_enable(struct irq_data *d) 86 + { 87 + struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d); 88 + 89 + gicv5_iwb_enable_wire(iwb_node, d->hwirq); 90 + irq_chip_enable_parent(d); 91 + } 92 + 93 + static int gicv5_iwb_set_type(struct irq_data *d, unsigned int type) 94 + { 95 + struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d); 96 + u32 iwb_wire, n, wtmr; 97 + u8 i; 98 + 99 + iwb_wire = d->hwirq; 100 + i = iwb_wire % 32; 101 + n = iwb_wire / 32; 102 + 103 + if (n >= iwb_node->nr_regs) { 104 + pr_err_once("reg %u out of range\n", n); 105 + return -EINVAL; 106 + } 107 + 108 + wtmr = iwb_readl_relaxed(iwb_node, GICV5_IWB_WTMR + (4 * n)); 109 + 110 + switch (type) { 111 + case IRQ_TYPE_LEVEL_HIGH: 112 + case IRQ_TYPE_LEVEL_LOW: 113 + wtmr |= BIT(i); 114 + break; 115 + case IRQ_TYPE_EDGE_RISING: 116 + case IRQ_TYPE_EDGE_FALLING: 117 + wtmr &= ~BIT(i); 118 + break; 119 + default: 120 + pr_debug("unexpected wire trigger mode"); 121 + return -EINVAL; 122 + } 123 + 124 + iwb_writel_relaxed(iwb_node, wtmr, GICV5_IWB_WTMR + (4 * n)); 125 + 126 + return 0; 127 + } 128 + 129 + static void gicv5_iwb_domain_set_desc(msi_alloc_info_t *alloc_info, struct msi_desc *desc) 130 + { 131 + alloc_info->desc = desc; 132 + alloc_info->hwirq = (u32)desc->data.icookie.value; 133 + } 134 + 135 + static int gicv5_iwb_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, 136 + irq_hw_number_t *hwirq, 137 + unsigned int *type) 138 + { 139 + if (!is_of_node(fwspec->fwnode)) 140 + return -EINVAL; 141 + 142 + if (fwspec->param_count < 2) 143 + return -EINVAL; 144 + 145 + /* 146 + * param[0] is be the wire 147 + * param[1] is the interrupt type 148 + */ 149 + *hwirq = fwspec->param[0]; 150 + *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; 151 + 152 + return 0; 153 + } 154 + 155 + static void gicv5_iwb_write_msi_msg(struct irq_data *d, struct msi_msg *msg) {} 156 + 157 + static const struct msi_domain_template iwb_msi_template = { 158 + .chip = { 159 + .name = "GICv5-IWB", 160 + .irq_mask = irq_chip_mask_parent, 161 + .irq_unmask = irq_chip_unmask_parent, 162 + .irq_enable = gicv5_iwb_irq_enable, 163 + .irq_disable = gicv5_iwb_irq_disable, 164 + .irq_eoi = irq_chip_eoi_parent, 165 + .irq_set_type = gicv5_iwb_set_type, 166 + .irq_write_msi_msg = gicv5_iwb_write_msi_msg, 167 + .irq_set_affinity = irq_chip_set_affinity_parent, 168 + .irq_get_irqchip_state = irq_chip_get_parent_state, 169 + .irq_set_irqchip_state = irq_chip_set_parent_state, 170 + .flags = IRQCHIP_SET_TYPE_MASKED | 171 + IRQCHIP_SKIP_SET_WAKE | 172 + IRQCHIP_MASK_ON_SUSPEND, 173 + }, 174 + 175 + .ops = { 176 + .set_desc = gicv5_iwb_domain_set_desc, 177 + .msi_translate = gicv5_iwb_irq_domain_translate, 178 + }, 179 + 180 + .info = { 181 + .bus_token = DOMAIN_BUS_WIRED_TO_MSI, 182 + .flags = MSI_FLAG_USE_DEV_FWNODE, 183 + }, 184 + 185 + .alloc_info = { 186 + .flags = MSI_ALLOC_FLAGS_FIXED_MSG_DATA, 187 + }, 188 + }; 189 + 190 + static bool gicv5_iwb_create_device_domain(struct device *dev, unsigned int size, 191 + struct gicv5_iwb_chip_data *iwb_node) 192 + { 193 + if (WARN_ON_ONCE(!dev->msi.domain)) 194 + return false; 195 + 196 + return msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN, 197 + &iwb_msi_template, size, 198 + NULL, iwb_node); 199 + } 200 + 201 + static struct gicv5_iwb_chip_data * 202 + gicv5_iwb_init_bases(void __iomem *iwb_base, struct platform_device *pdev) 203 + { 204 + u32 nr_wires, idr0, cr0; 205 + unsigned int n; 206 + int ret; 207 + 208 + struct gicv5_iwb_chip_data *iwb_node __free(kfree) = kzalloc(sizeof(*iwb_node), 209 + GFP_KERNEL); 210 + if (!iwb_node) 211 + return ERR_PTR(-ENOMEM); 212 + 213 + iwb_node->iwb_base = iwb_base; 214 + 215 + idr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_IDR0); 216 + nr_wires = (FIELD_GET(GICV5_IWB_IDR0_IW_RANGE, idr0) + 1) * 32; 217 + 218 + cr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_CR0); 219 + if (!FIELD_GET(GICV5_IWB_CR0_IWBEN, cr0)) { 220 + dev_err(&pdev->dev, "IWB must be enabled in firmware\n"); 221 + return ERR_PTR(-EINVAL); 222 + } 223 + 224 + iwb_node->nr_regs = FIELD_GET(GICV5_IWB_IDR0_IW_RANGE, idr0) + 1; 225 + 226 + for (n = 0; n < iwb_node->nr_regs; n++) 227 + iwb_writel_relaxed(iwb_node, 0, GICV5_IWB_WENABLER + (sizeof(u32) * n)); 228 + 229 + ret = gicv5_iwb_wait_for_wenabler(iwb_node); 230 + if (ret) 231 + return ERR_PTR(ret); 232 + 233 + if (!gicv5_iwb_create_device_domain(&pdev->dev, nr_wires, iwb_node)) 234 + return ERR_PTR(-ENOMEM); 235 + 236 + return_ptr(iwb_node); 237 + } 238 + 239 + static int gicv5_iwb_device_probe(struct platform_device *pdev) 240 + { 241 + struct gicv5_iwb_chip_data *iwb_node; 242 + void __iomem *iwb_base; 243 + struct resource *res; 244 + int ret; 245 + 246 + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 247 + if (!res) 248 + return -EINVAL; 249 + 250 + iwb_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); 251 + if (!iwb_base) { 252 + dev_err(&pdev->dev, "failed to ioremap %pR\n", res); 253 + return -ENOMEM; 254 + } 255 + 256 + iwb_node = gicv5_iwb_init_bases(iwb_base, pdev); 257 + if (IS_ERR(iwb_node)) { 258 + ret = PTR_ERR(iwb_node); 259 + goto out_unmap; 260 + } 261 + 262 + return 0; 263 + 264 + out_unmap: 265 + iounmap(iwb_base); 266 + return ret; 267 + } 268 + 269 + static const struct of_device_id gicv5_iwb_of_match[] = { 270 + { .compatible = "arm,gic-v5-iwb" }, 271 + { /* END */ } 272 + }; 273 + MODULE_DEVICE_TABLE(of, gicv5_iwb_of_match); 274 + 275 + static struct platform_driver gicv5_iwb_platform_driver = { 276 + .driver = { 277 + .name = "GICv5 IWB", 278 + .of_match_table = gicv5_iwb_of_match, 279 + .suppress_bind_attrs = true, 280 + }, 281 + .probe = gicv5_iwb_device_probe, 282 + }; 283 + 284 + module_platform_driver(gicv5_iwb_platform_driver);
+1
include/asm-generic/msi.h
··· 33 33 34 34 /* Device generating MSIs is proxying for another device */ 35 35 #define MSI_ALLOC_FLAGS_PROXY_DEVICE (1UL << 0) 36 + #define MSI_ALLOC_FLAGS_FIXED_MSG_DATA (1UL << 1) 36 37 37 38 #define GENERIC_MSI_DOMAIN_OPS 1 38 39
+17
include/linux/irqchip/arm-gic-v5.h
··· 249 249 #define GICV5_ITS_HWIRQ_EVENT_ID GENMASK_ULL(63, 32) 250 250 251 251 /* 252 + * IWB registers 253 + */ 254 + #define GICV5_IWB_IDR0 0x0000 255 + #define GICV5_IWB_CR0 0x0080 256 + #define GICV5_IWB_WENABLE_STATUSR 0x00c0 257 + #define GICV5_IWB_WENABLER 0x2000 258 + #define GICV5_IWB_WTMR 0x4000 259 + 260 + #define GICV5_IWB_IDR0_INT_DOMS GENMASK(14, 11) 261 + #define GICV5_IWB_IDR0_IW_RANGE GENMASK(10, 0) 262 + 263 + #define GICV5_IWB_CR0_IDLE BIT(1) 264 + #define GICV5_IWB_CR0_IWBEN BIT(0) 265 + 266 + #define GICV5_IWB_WENABLE_STATUSR_IDLE BIT(0) 267 + 268 + /* 252 269 * Global Data structures and functions 253 270 */ 254 271 struct gicv5_chip_data {