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

irq_domain: Add support for base irq and hwirq in legacy mappings

Add support for a legacy mapping where irq = (hwirq - first_hwirq + first_irq)
so that a controller driver can allocate a fixed range of irq_descs and use
a simple calculation to translate back and forth between linux and hw irq
numbers. This is needed to use an irq_domain with many of the ARM interrupt
controller drivers that manage their own irq_desc allocations. Ultimately
the goal is to migrate those drivers to use the linear revmap, but doing it
this way allows each driver to be converted separately which makes the
migration path easier.

This patch generalizes the IRQ_DOMAIN_MAP_LEGACY method to use
(first_irq-first_hwirq) as the offset between hwirq and linux irq number,
and adds checks to make sure that the hwirq number does not exceed range
assigned to the controller.

Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Milton Miller <miltonm@bga.com>
Tested-by: Olof Johansson <olof@lixom.net>

+85 -38
-3
arch/powerpc/include/asm/irq.h
··· 36 36 /* Total number of virq in the platform */ 37 37 #define NR_IRQS CONFIG_NR_IRQS 38 38 39 - /* Number of irqs reserved for the legacy controller */ 40 - #define NUM_ISA_INTERRUPTS 16 41 - 42 39 /* Same thing, used by the generic IRQ code */ 43 40 #define NR_IRQS_LEGACY NUM_ISA_INTERRUPTS 44 41
+1 -1
arch/powerpc/sysdev/i8259.c
··· 263 263 raw_spin_unlock_irqrestore(&i8259_lock, flags); 264 264 265 265 /* create a legacy host */ 266 - i8259_host = irq_domain_add_legacy(node, &i8259_host_ops, NULL); 266 + i8259_host = irq_domain_add_legacy_isa(node, &i8259_host_ops, NULL); 267 267 if (i8259_host == NULL) { 268 268 printk(KERN_ERR "i8259: failed to allocate irq host !\n"); 269 269 return;
+1 -1
arch/powerpc/sysdev/tsi108_pci.c
··· 419 419 { 420 420 DBG("Tsi108_pci_int_init: initializing PCI interrupts\n"); 421 421 422 - pci_irq_host = irq_domain_add_legacy(node, &pci_irq_domain_ops, NULL); 422 + pci_irq_host = irq_domain_add_legacy_isa(node, &pci_irq_domain_ops, NULL); 423 423 if (pci_irq_host == NULL) { 424 424 printk(KERN_ERR "pci_irq_host: failed to allocate irq domain!\n"); 425 425 return;
+19 -1
include/linux/irqdomain.h
··· 39 39 struct irq_domain; 40 40 struct of_device_id; 41 41 42 + /* Number of irqs reserved for a legacy isa controller */ 43 + #define NUM_ISA_INTERRUPTS 16 44 + 42 45 /* This type is the placeholder for a hardware interrupt number. It has to 43 46 * be big enough to enclose whatever representation is used by a given 44 47 * platform. ··· 101 98 union { 102 99 struct { 103 100 unsigned int size; 101 + unsigned int first_irq; 102 + irq_hw_number_t first_hwirq; 103 + } legacy; 104 + struct { 105 + unsigned int size; 104 106 unsigned int *revmap; 105 107 } linear; 106 108 struct radix_tree_root tree; ··· 125 117 #ifdef CONFIG_IRQ_DOMAIN 126 118 #ifdef CONFIG_PPC 127 119 struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, 120 + unsigned int size, 121 + unsigned int first_irq, 122 + irq_hw_number_t first_hwirq, 128 123 struct irq_domain_ops *ops, 129 124 void *host_data); 130 125 struct irq_domain *irq_domain_add_linear(struct device_node *of_node, ··· 141 130 struct irq_domain_ops *ops, 142 131 void *host_data); 143 132 144 - 145 133 extern struct irq_domain *irq_find_host(struct device_node *node); 146 134 extern void irq_set_default_host(struct irq_domain *host); 147 135 extern void irq_set_virq_count(unsigned int count); 148 136 137 + static inline struct irq_domain *irq_domain_add_legacy_isa( 138 + struct device_node *of_node, 139 + struct irq_domain_ops *ops, 140 + void *host_data) 141 + { 142 + return irq_domain_add_legacy(of_node, NUM_ISA_INTERRUPTS, 0, 0, ops, 143 + host_data); 144 + } 149 145 150 146 extern unsigned int irq_create_mapping(struct irq_domain *host, 151 147 irq_hw_number_t hwirq);
+64 -32
kernel/irq/irqdomain.c
··· 13 13 #include <linux/smp.h> 14 14 #include <linux/fs.h> 15 15 16 - #define IRQ_DOMAIN_MAP_LEGACY 0 /* legacy 8259, gets irqs 1..15 */ 16 + #define IRQ_DOMAIN_MAP_LEGACY 0 /* driver allocated fixed range of irqs. 17 + * ie. legacy 8259, gets irqs 1..15 */ 17 18 #define IRQ_DOMAIN_MAP_NOMAP 1 /* no fast reverse mapping */ 18 19 #define IRQ_DOMAIN_MAP_LINEAR 2 /* linear map of interrupts */ 19 20 #define IRQ_DOMAIN_MAP_TREE 3 /* radix tree */ ··· 75 74 domain->revmap_type, domain); 76 75 } 77 76 77 + static unsigned int irq_domain_legacy_revmap(struct irq_domain *domain, 78 + irq_hw_number_t hwirq) 79 + { 80 + irq_hw_number_t first_hwirq = domain->revmap_data.legacy.first_hwirq; 81 + int size = domain->revmap_data.legacy.size; 82 + 83 + if (WARN_ON(hwirq < first_hwirq || hwirq >= first_hwirq + size)) 84 + return 0; 85 + return hwirq - first_hwirq + domain->revmap_data.legacy.first_irq; 86 + } 87 + 78 88 /** 79 89 * irq_domain_add_legacy() - Allocate and register a legacy revmap irq_domain. 80 90 * @of_node: pointer to interrupt controller's device tree node. 91 + * @size: total number of irqs in legacy mapping 92 + * @first_irq: first number of irq block assigned to the domain 93 + * @first_hwirq: first hwirq number to use for the translation. Should normally 94 + * be '0', but a positive integer can be used if the effective 95 + * hwirqs numbering does not begin at zero. 81 96 * @ops: map/unmap domain callbacks 82 97 * @host_data: Controller private data pointer 83 98 * ··· 102 85 * a legacy controller). 103 86 */ 104 87 struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, 88 + unsigned int size, 89 + unsigned int first_irq, 90 + irq_hw_number_t first_hwirq, 105 91 struct irq_domain_ops *ops, 106 92 void *host_data) 107 93 { 108 - struct irq_domain *domain, *h; 94 + struct irq_domain *domain; 109 95 unsigned int i; 110 96 111 97 domain = irq_domain_alloc(of_node, IRQ_DOMAIN_MAP_LEGACY, ops, host_data); 112 98 if (!domain) 113 99 return NULL; 114 100 101 + domain->revmap_data.legacy.first_irq = first_irq; 102 + domain->revmap_data.legacy.first_hwirq = first_hwirq; 103 + domain->revmap_data.legacy.size = size; 104 + 115 105 mutex_lock(&irq_domain_mutex); 116 - /* Make sure only one legacy controller can be created */ 117 - list_for_each_entry(h, &irq_domain_list, link) { 118 - if (WARN_ON(h->revmap_type == IRQ_DOMAIN_MAP_LEGACY)) { 106 + /* Verify that all the irqs are available */ 107 + for (i = 0; i < size; i++) { 108 + int irq = first_irq + i; 109 + struct irq_data *irq_data = irq_get_irq_data(irq); 110 + 111 + if (WARN_ON(!irq_data || irq_data->domain)) { 119 112 mutex_unlock(&irq_domain_mutex); 120 113 of_node_put(domain->of_node); 121 114 kfree(domain); 122 115 return NULL; 123 116 } 124 117 } 125 - list_add(&domain->link, &irq_domain_list); 118 + 119 + /* Claim all of the irqs before registering a legacy domain */ 120 + for (i = 0; i < size; i++) { 121 + struct irq_data *irq_data = irq_get_irq_data(first_irq + i); 122 + irq_data->hwirq = first_hwirq + i; 123 + irq_data->domain = domain; 124 + } 126 125 mutex_unlock(&irq_domain_mutex); 127 126 128 - /* setup us as the domain for all legacy interrupts */ 129 - for (i = 1; i < NUM_ISA_INTERRUPTS; i++) { 130 - struct irq_data *irq_data = irq_get_irq_data(i); 131 - irq_data->hwirq = i; 132 - irq_data->domain = domain; 127 + for (i = 0; i < size; i++) { 128 + int irq = first_irq + i; 129 + int hwirq = first_hwirq + i; 130 + 131 + /* IRQ0 gets ignored */ 132 + if (!irq) 133 + continue; 133 134 134 135 /* Legacy flags are left to default at this point, 135 136 * one can then use irq_create_mapping() to 136 137 * explicitly change them 137 138 */ 138 - ops->map(domain, i, i); 139 + ops->map(domain, irq, hwirq); 139 140 140 141 /* Clear norequest flags */ 141 - irq_clear_status_flags(i, IRQ_NOREQUEST); 142 + irq_clear_status_flags(irq, IRQ_NOREQUEST); 142 143 } 144 + 145 + irq_domain_add(domain); 143 146 return domain; 144 147 } 145 148 ··· 375 338 } 376 339 377 340 /* Get a virtual interrupt number */ 378 - if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) { 379 - /* Handle legacy */ 380 - virq = (unsigned int)hwirq; 381 - if (virq == 0 || virq >= NUM_ISA_INTERRUPTS) 382 - return 0; 383 - return virq; 384 - } else { 385 - /* Allocate a virtual interrupt number */ 386 - hint = hwirq % irq_virq_count; 387 - if (hint == 0) 388 - hint++; 389 - virq = irq_alloc_desc_from(hint, 0); 390 - if (!virq) 391 - virq = irq_alloc_desc_from(1, 0); 392 - if (!virq) { 393 - pr_debug("irq: -> virq allocation failed\n"); 394 - return 0; 395 - } 341 + if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) 342 + return irq_domain_legacy_revmap(domain, hwirq); 343 + 344 + /* Allocate a virtual interrupt number */ 345 + hint = hwirq % irq_virq_count; 346 + if (hint == 0) 347 + hint++; 348 + virq = irq_alloc_desc_from(hint, 0); 349 + if (!virq) 350 + virq = irq_alloc_desc_from(1, 0); 351 + if (!virq) { 352 + pr_debug("irq: -> virq allocation failed\n"); 353 + return 0; 396 354 } 397 355 398 356 if (irq_setup_virq(domain, virq, hwirq)) { ··· 515 483 516 484 /* legacy -> bail early */ 517 485 if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) 518 - return hwirq; 486 + return irq_domain_legacy_revmap(domain, hwirq); 519 487 520 488 /* Slow path does a linear search of the map */ 521 489 if (hint == 0)