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

irqchip: Add per-cpu interrupt partitioning library

We've unfortunately started seeing a situation where percpu interrupts
are partitioned in the system: one arbitrary set of CPUs has an
interrupt connected to a type of device, while another disjoint
set of CPUs has the same interrupt connected to another type of device.

This makes it impossible to have a device driver requesting this interrupt
using the current percpu-interrupt abstraction, as the same interrupt number
is now potentially claimed by at least two drivers, and we forbid interrupt
sharing on per-cpu interrupt.

A solution to this is to turn things upside down. Let's assume that our
system describes all the possible partitions for a given interrupt, and
give each of them a unique identifier. It is then possible to create
a namespace where the affinity identifier itself is a form of interrupt
number. At this point, it becomes easy to implement a set of partitions
as a cascaded irqchip, each affinity identifier being the HW irq.

This allows us to keep a number of nice properties:
- Each partition results in a separate percpu-interrupt (with a restrictied
affinity), which keeps drivers happy.
- Because the underlying interrupt is still per-cpu, the overhead of
the indirection can be kept pretty minimal.
- The core code can ignore most of that crap.

For that purpose, we implement a small library that deals with some of
the boilerplate code, relying on platform-specific drivers to provide
a description of the affinity sets and a set of callbacks.

Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: devicetree@vger.kernel.org
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: Will Deacon <will.deacon@arm.com>
Cc: Rob Herring <robh+dt@kernel.org>
Link: http://lkml.kernel.org/r/1460365075-7316-4-git-send-email-marc.zyngier@arm.com
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>

authored by

Marc Zyngier and committed by
Thomas Gleixner
9e2c986c 222df54f

+319
+3
drivers/irqchip/Kconfig
··· 244 244 config MVEBU_ODMI 245 245 bool 246 246 select GENERIC_MSI_IRQ_DOMAIN 247 + 248 + config PARTITION_PERCPU 249 + bool
+1
drivers/irqchip/Makefile
··· 27 27 obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o 28 28 obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-common.o 29 29 obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-pci-msi.o irq-gic-v3-its-platform-msi.o 30 + obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o 30 31 obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o 31 32 obj-$(CONFIG_ARM_NVIC) += irq-nvic.o 32 33 obj-$(CONFIG_ARM_VIC) += irq-vic.o
+256
drivers/irqchip/irq-partition-percpu.c
··· 1 + /* 2 + * Copyright (C) 2016 ARM Limited, All Rights Reserved. 3 + * Author: Marc Zyngier <marc.zyngier@arm.com> 4 + * 5 + * This program is free software; you can redistribute it and/or modify 6 + * it under the terms of the GNU General Public License version 2 as 7 + * published by the Free Software Foundation. 8 + * 9 + * This program is distributed in the hope that it will be useful, 10 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 + * GNU General Public License for more details. 13 + * 14 + * You should have received a copy of the GNU General Public License 15 + * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 + */ 17 + 18 + #include <linux/bitops.h> 19 + #include <linux/interrupt.h> 20 + #include <linux/irqchip.h> 21 + #include <linux/irqchip/chained_irq.h> 22 + #include <linux/irqchip/irq-partition-percpu.h> 23 + #include <linux/irqdomain.h> 24 + #include <linux/seq_file.h> 25 + #include <linux/slab.h> 26 + 27 + struct partition_desc { 28 + int nr_parts; 29 + struct partition_affinity *parts; 30 + struct irq_domain *domain; 31 + struct irq_desc *chained_desc; 32 + unsigned long *bitmap; 33 + struct irq_domain_ops ops; 34 + }; 35 + 36 + static bool partition_check_cpu(struct partition_desc *part, 37 + unsigned int cpu, unsigned int hwirq) 38 + { 39 + return cpumask_test_cpu(cpu, &part->parts[hwirq].mask); 40 + } 41 + 42 + static void partition_irq_mask(struct irq_data *d) 43 + { 44 + struct partition_desc *part = irq_data_get_irq_chip_data(d); 45 + struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 46 + struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 47 + 48 + if (partition_check_cpu(part, smp_processor_id(), d->hwirq) && 49 + chip->irq_mask) 50 + chip->irq_mask(data); 51 + } 52 + 53 + static void partition_irq_unmask(struct irq_data *d) 54 + { 55 + struct partition_desc *part = irq_data_get_irq_chip_data(d); 56 + struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 57 + struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 58 + 59 + if (partition_check_cpu(part, smp_processor_id(), d->hwirq) && 60 + chip->irq_unmask) 61 + chip->irq_unmask(data); 62 + } 63 + 64 + static int partition_irq_set_irqchip_state(struct irq_data *d, 65 + enum irqchip_irq_state which, 66 + bool val) 67 + { 68 + struct partition_desc *part = irq_data_get_irq_chip_data(d); 69 + struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 70 + struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 71 + 72 + if (partition_check_cpu(part, smp_processor_id(), d->hwirq) && 73 + chip->irq_set_irqchip_state) 74 + return chip->irq_set_irqchip_state(data, which, val); 75 + 76 + return -EINVAL; 77 + } 78 + 79 + static int partition_irq_get_irqchip_state(struct irq_data *d, 80 + enum irqchip_irq_state which, 81 + bool *val) 82 + { 83 + struct partition_desc *part = irq_data_get_irq_chip_data(d); 84 + struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 85 + struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 86 + 87 + if (partition_check_cpu(part, smp_processor_id(), d->hwirq) && 88 + chip->irq_get_irqchip_state) 89 + return chip->irq_get_irqchip_state(data, which, val); 90 + 91 + return -EINVAL; 92 + } 93 + 94 + static int partition_irq_set_type(struct irq_data *d, unsigned int type) 95 + { 96 + struct partition_desc *part = irq_data_get_irq_chip_data(d); 97 + struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 98 + struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 99 + 100 + if (chip->irq_set_type) 101 + return chip->irq_set_type(data, type); 102 + 103 + return -EINVAL; 104 + } 105 + 106 + static void partition_irq_print_chip(struct irq_data *d, struct seq_file *p) 107 + { 108 + struct partition_desc *part = irq_data_get_irq_chip_data(d); 109 + struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 110 + struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 111 + 112 + seq_printf(p, " %5s-%lu", chip->name, data->hwirq); 113 + } 114 + 115 + static struct irq_chip partition_irq_chip = { 116 + .irq_mask = partition_irq_mask, 117 + .irq_unmask = partition_irq_unmask, 118 + .irq_set_type = partition_irq_set_type, 119 + .irq_get_irqchip_state = partition_irq_get_irqchip_state, 120 + .irq_set_irqchip_state = partition_irq_set_irqchip_state, 121 + .irq_print_chip = partition_irq_print_chip, 122 + }; 123 + 124 + static void partition_handle_irq(struct irq_desc *desc) 125 + { 126 + struct partition_desc *part = irq_desc_get_handler_data(desc); 127 + struct irq_chip *chip = irq_desc_get_chip(desc); 128 + int cpu = smp_processor_id(); 129 + int hwirq; 130 + 131 + chained_irq_enter(chip, desc); 132 + 133 + for_each_set_bit(hwirq, part->bitmap, part->nr_parts) { 134 + if (partition_check_cpu(part, cpu, hwirq)) 135 + break; 136 + } 137 + 138 + if (unlikely(hwirq == part->nr_parts)) { 139 + handle_bad_irq(desc); 140 + } else { 141 + unsigned int irq; 142 + irq = irq_find_mapping(part->domain, hwirq); 143 + generic_handle_irq(irq); 144 + } 145 + 146 + chained_irq_exit(chip, desc); 147 + } 148 + 149 + static int partition_domain_alloc(struct irq_domain *domain, unsigned int virq, 150 + unsigned int nr_irqs, void *arg) 151 + { 152 + int ret; 153 + irq_hw_number_t hwirq; 154 + unsigned int type; 155 + struct irq_fwspec *fwspec = arg; 156 + struct partition_desc *part; 157 + 158 + BUG_ON(nr_irqs != 1); 159 + ret = domain->ops->translate(domain, fwspec, &hwirq, &type); 160 + if (ret) 161 + return ret; 162 + 163 + part = domain->host_data; 164 + 165 + set_bit(hwirq, part->bitmap); 166 + irq_set_chained_handler_and_data(irq_desc_get_irq(part->chained_desc), 167 + partition_handle_irq, part); 168 + irq_set_percpu_devid_partition(virq, &part->parts[hwirq].mask); 169 + irq_domain_set_info(domain, virq, hwirq, &partition_irq_chip, part, 170 + handle_percpu_devid_irq, NULL, NULL); 171 + irq_set_status_flags(virq, IRQ_NOAUTOEN); 172 + 173 + return 0; 174 + } 175 + 176 + static void partition_domain_free(struct irq_domain *domain, unsigned int virq, 177 + unsigned int nr_irqs) 178 + { 179 + struct irq_data *d; 180 + 181 + BUG_ON(nr_irqs != 1); 182 + 183 + d = irq_domain_get_irq_data(domain, virq); 184 + irq_set_handler(virq, NULL); 185 + irq_domain_reset_irq_data(d); 186 + } 187 + 188 + int partition_translate_id(struct partition_desc *desc, void *partition_id) 189 + { 190 + struct partition_affinity *part = NULL; 191 + int i; 192 + 193 + for (i = 0; i < desc->nr_parts; i++) { 194 + if (desc->parts[i].partition_id == partition_id) { 195 + part = &desc->parts[i]; 196 + break; 197 + } 198 + } 199 + 200 + if (WARN_ON(!part)) { 201 + pr_err("Failed to find partition\n"); 202 + return -EINVAL; 203 + } 204 + 205 + return i; 206 + } 207 + 208 + struct partition_desc *partition_create_desc(struct fwnode_handle *fwnode, 209 + struct partition_affinity *parts, 210 + int nr_parts, 211 + int chained_irq, 212 + const struct irq_domain_ops *ops) 213 + { 214 + struct partition_desc *desc; 215 + struct irq_domain *d; 216 + 217 + BUG_ON(!ops->select || !ops->translate); 218 + 219 + desc = kzalloc(sizeof(*desc), GFP_KERNEL); 220 + if (!desc) 221 + return NULL; 222 + 223 + desc->ops = *ops; 224 + desc->ops.free = partition_domain_free; 225 + desc->ops.alloc = partition_domain_alloc; 226 + 227 + d = irq_domain_create_linear(fwnode, nr_parts, &desc->ops, desc); 228 + if (!d) 229 + goto out; 230 + desc->domain = d; 231 + 232 + desc->bitmap = kzalloc(sizeof(long) * BITS_TO_LONGS(nr_parts), 233 + GFP_KERNEL); 234 + if (WARN_ON(!desc->bitmap)) 235 + goto out; 236 + 237 + desc->chained_desc = irq_to_desc(chained_irq); 238 + desc->nr_parts = nr_parts; 239 + desc->parts = parts; 240 + 241 + return desc; 242 + out: 243 + if (d) 244 + irq_domain_remove(d); 245 + kfree(desc); 246 + 247 + return NULL; 248 + } 249 + 250 + struct irq_domain *partition_get_domain(struct partition_desc *dsc) 251 + { 252 + if (dsc) 253 + return dsc->domain; 254 + 255 + return NULL; 256 + }
+59
include/linux/irqchip/irq-partition-percpu.h
··· 1 + /* 2 + * Copyright (C) 2016 ARM Limited, All Rights Reserved. 3 + * Author: Marc Zyngier <marc.zyngier@arm.com> 4 + * 5 + * This program is free software; you can redistribute it and/or modify 6 + * it under the terms of the GNU General Public License version 2 as 7 + * published by the Free Software Foundation. 8 + * 9 + * This program is distributed in the hope that it will be useful, 10 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 + * GNU General Public License for more details. 13 + * 14 + * You should have received a copy of the GNU General Public License 15 + * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 + */ 17 + 18 + #include <linux/fwnode.h> 19 + #include <linux/cpumask.h> 20 + #include <linux/irqdomain.h> 21 + 22 + struct partition_affinity { 23 + cpumask_t mask; 24 + void *partition_id; 25 + }; 26 + 27 + struct partition_desc; 28 + 29 + #ifdef CONFIG_PARTITION_PERCPU 30 + int partition_translate_id(struct partition_desc *desc, void *partition_id); 31 + struct partition_desc *partition_create_desc(struct fwnode_handle *fwnode, 32 + struct partition_affinity *parts, 33 + int nr_parts, 34 + int chained_irq, 35 + const struct irq_domain_ops *ops); 36 + struct irq_domain *partition_get_domain(struct partition_desc *dsc); 37 + #else 38 + static inline int partition_translate_id(struct partition_desc *desc, 39 + void *partition_id) 40 + { 41 + return -EINVAL; 42 + } 43 + 44 + static inline 45 + struct partition_desc *partition_create_desc(struct fwnode_handle *fwnode, 46 + struct partition_affinity *parts, 47 + int nr_parts, 48 + int chained_irq, 49 + const struct irq_domain_ops *ops) 50 + { 51 + return NULL; 52 + } 53 + 54 + static inline 55 + struct irq_domain *partition_get_domain(struct partition_desc *dsc) 56 + { 57 + return NULL; 58 + } 59 + #endif