diff options
author | Grant Likely <grant.likely@secretlab.ca> | 2012-02-14 16:06:55 -0500 |
---|---|---|
committer | Grant Likely <grant.likely@secretlab.ca> | 2012-02-16 08:11:23 -0500 |
commit | 1bc04f2cf8c2a1feadbd994f50c40bb145bf2989 (patch) | |
tree | 26508a452ca52542ac437f55bb632b48fa607048 | |
parent | a8db8cf0d894df5f1dcfd4bce9894e0dbcc01c96 (diff) |
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>
-rw-r--r-- | arch/powerpc/include/asm/irq.h | 3 | ||||
-rw-r--r-- | arch/powerpc/sysdev/i8259.c | 2 | ||||
-rw-r--r-- | arch/powerpc/sysdev/tsi108_pci.c | 2 | ||||
-rw-r--r-- | include/linux/irqdomain.h | 20 | ||||
-rw-r--r-- | kernel/irq/irqdomain.c | 96 |
5 files changed, 85 insertions, 38 deletions
diff --git a/arch/powerpc/include/asm/irq.h b/arch/powerpc/include/asm/irq.h index 728cc30d04ea..fe0b09dceb7d 100644 --- a/arch/powerpc/include/asm/irq.h +++ b/arch/powerpc/include/asm/irq.h | |||
@@ -36,9 +36,6 @@ extern atomic_t ppc_n_lost_interrupts; | |||
36 | /* Total number of virq in the platform */ | 36 | /* Total number of virq in the platform */ |
37 | #define NR_IRQS CONFIG_NR_IRQS | 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 | /* Same thing, used by the generic IRQ code */ | 39 | /* Same thing, used by the generic IRQ code */ |
43 | #define NR_IRQS_LEGACY NUM_ISA_INTERRUPTS | 40 | #define NR_IRQS_LEGACY NUM_ISA_INTERRUPTS |
44 | 41 | ||
diff --git a/arch/powerpc/sysdev/i8259.c b/arch/powerpc/sysdev/i8259.c index 573a73bd954a..997df6a7ab5d 100644 --- a/arch/powerpc/sysdev/i8259.c +++ b/arch/powerpc/sysdev/i8259.c | |||
@@ -263,7 +263,7 @@ void i8259_init(struct device_node *node, unsigned long intack_addr) | |||
263 | raw_spin_unlock_irqrestore(&i8259_lock, flags); | 263 | raw_spin_unlock_irqrestore(&i8259_lock, flags); |
264 | 264 | ||
265 | /* create a legacy host */ | 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 | if (i8259_host == NULL) { | 267 | if (i8259_host == NULL) { |
268 | printk(KERN_ERR "i8259: failed to allocate irq host !\n"); | 268 | printk(KERN_ERR "i8259: failed to allocate irq host !\n"); |
269 | return; | 269 | return; |
diff --git a/arch/powerpc/sysdev/tsi108_pci.c b/arch/powerpc/sysdev/tsi108_pci.c index 1be26f4b9c96..188012c58f7f 100644 --- a/arch/powerpc/sysdev/tsi108_pci.c +++ b/arch/powerpc/sysdev/tsi108_pci.c | |||
@@ -419,7 +419,7 @@ void __init tsi108_pci_int_init(struct device_node *node) | |||
419 | { | 419 | { |
420 | DBG("Tsi108_pci_int_init: initializing PCI interrupts\n"); | 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 | if (pci_irq_host == NULL) { | 423 | if (pci_irq_host == NULL) { |
424 | printk(KERN_ERR "pci_irq_host: failed to allocate irq domain!\n"); | 424 | printk(KERN_ERR "pci_irq_host: failed to allocate irq domain!\n"); |
425 | return; | 425 | return; |
diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index f95553fa6872..7fef39ed5523 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h | |||
@@ -39,6 +39,9 @@ struct device_node; | |||
39 | struct irq_domain; | 39 | struct irq_domain; |
40 | struct of_device_id; | 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 | /* This type is the placeholder for a hardware interrupt number. It has to | 45 | /* This type is the placeholder for a hardware interrupt number. It has to |
43 | * be big enough to enclose whatever representation is used by a given | 46 | * be big enough to enclose whatever representation is used by a given |
44 | * platform. | 47 | * platform. |
@@ -98,6 +101,11 @@ struct irq_domain { | |||
98 | union { | 101 | union { |
99 | struct { | 102 | struct { |
100 | unsigned int size; | 103 | unsigned int size; |
104 | unsigned int first_irq; | ||
105 | irq_hw_number_t first_hwirq; | ||
106 | } legacy; | ||
107 | struct { | ||
108 | unsigned int size; | ||
101 | unsigned int *revmap; | 109 | unsigned int *revmap; |
102 | } linear; | 110 | } linear; |
103 | struct radix_tree_root tree; | 111 | struct radix_tree_root tree; |
@@ -117,6 +125,9 @@ struct irq_domain { | |||
117 | #ifdef CONFIG_IRQ_DOMAIN | 125 | #ifdef CONFIG_IRQ_DOMAIN |
118 | #ifdef CONFIG_PPC | 126 | #ifdef CONFIG_PPC |
119 | struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, | 127 | struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, |
128 | unsigned int size, | ||
129 | unsigned int first_irq, | ||
130 | irq_hw_number_t first_hwirq, | ||
120 | struct irq_domain_ops *ops, | 131 | struct irq_domain_ops *ops, |
121 | void *host_data); | 132 | void *host_data); |
122 | struct irq_domain *irq_domain_add_linear(struct device_node *of_node, | 133 | struct irq_domain *irq_domain_add_linear(struct device_node *of_node, |
@@ -130,11 +141,18 @@ struct irq_domain *irq_domain_add_tree(struct device_node *of_node, | |||
130 | struct irq_domain_ops *ops, | 141 | struct irq_domain_ops *ops, |
131 | void *host_data); | 142 | void *host_data); |
132 | 143 | ||
133 | |||
134 | extern struct irq_domain *irq_find_host(struct device_node *node); | 144 | extern struct irq_domain *irq_find_host(struct device_node *node); |
135 | extern void irq_set_default_host(struct irq_domain *host); | 145 | extern void irq_set_default_host(struct irq_domain *host); |
136 | extern void irq_set_virq_count(unsigned int count); | 146 | extern void irq_set_virq_count(unsigned int count); |
137 | 147 | ||
148 | static inline struct irq_domain *irq_domain_add_legacy_isa( | ||
149 | struct device_node *of_node, | ||
150 | struct irq_domain_ops *ops, | ||
151 | void *host_data) | ||
152 | { | ||
153 | return irq_domain_add_legacy(of_node, NUM_ISA_INTERRUPTS, 0, 0, ops, | ||
154 | host_data); | ||
155 | } | ||
138 | 156 | ||
139 | extern unsigned int irq_create_mapping(struct irq_domain *host, | 157 | extern unsigned int irq_create_mapping(struct irq_domain *host, |
140 | irq_hw_number_t hwirq); | 158 | irq_hw_number_t hwirq); |
diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index acedba1a2651..c6740d72073e 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c | |||
@@ -13,7 +13,8 @@ | |||
13 | #include <linux/smp.h> | 13 | #include <linux/smp.h> |
14 | #include <linux/fs.h> | 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 | #define IRQ_DOMAIN_MAP_NOMAP 1 /* no fast reverse mapping */ | 18 | #define IRQ_DOMAIN_MAP_NOMAP 1 /* no fast reverse mapping */ |
18 | #define IRQ_DOMAIN_MAP_LINEAR 2 /* linear map of interrupts */ | 19 | #define IRQ_DOMAIN_MAP_LINEAR 2 /* linear map of interrupts */ |
19 | #define IRQ_DOMAIN_MAP_TREE 3 /* radix tree */ | 20 | #define IRQ_DOMAIN_MAP_TREE 3 /* radix tree */ |
@@ -74,9 +75,25 @@ static void irq_domain_add(struct irq_domain *domain) | |||
74 | domain->revmap_type, domain); | 75 | domain->revmap_type, domain); |
75 | } | 76 | } |
76 | 77 | ||
78 | static unsigned int irq_domain_legacy_revmap(struct irq_domain *domain, | ||
79 | irq_hw_number_t hwirq) | ||
80 | { | ||
81 | irq_hw_number_t first_hwirq = domain->revmap_data.legacy.first_hwirq; | ||
82 | int size = domain->revmap_data.legacy.size; | ||
83 | |||
84 | if (WARN_ON(hwirq < first_hwirq || hwirq >= first_hwirq + size)) | ||
85 | return 0; | ||
86 | return hwirq - first_hwirq + domain->revmap_data.legacy.first_irq; | ||
87 | } | ||
88 | |||
77 | /** | 89 | /** |
78 | * irq_domain_add_legacy() - Allocate and register a legacy revmap irq_domain. | 90 | * irq_domain_add_legacy() - Allocate and register a legacy revmap irq_domain. |
79 | * @of_node: pointer to interrupt controller's device tree node. | 91 | * @of_node: pointer to interrupt controller's device tree node. |
92 | * @size: total number of irqs in legacy mapping | ||
93 | * @first_irq: first number of irq block assigned to the domain | ||
94 | * @first_hwirq: first hwirq number to use for the translation. Should normally | ||
95 | * be '0', but a positive integer can be used if the effective | ||
96 | * hwirqs numbering does not begin at zero. | ||
80 | * @ops: map/unmap domain callbacks | 97 | * @ops: map/unmap domain callbacks |
81 | * @host_data: Controller private data pointer | 98 | * @host_data: Controller private data pointer |
82 | * | 99 | * |
@@ -85,44 +102,64 @@ static void irq_domain_add(struct irq_domain *domain) | |||
85 | * a legacy controller). | 102 | * a legacy controller). |
86 | */ | 103 | */ |
87 | struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, | 104 | struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, |
105 | unsigned int size, | ||
106 | unsigned int first_irq, | ||
107 | irq_hw_number_t first_hwirq, | ||
88 | struct irq_domain_ops *ops, | 108 | struct irq_domain_ops *ops, |
89 | void *host_data) | 109 | void *host_data) |
90 | { | 110 | { |
91 | struct irq_domain *domain, *h; | 111 | struct irq_domain *domain; |
92 | unsigned int i; | 112 | unsigned int i; |
93 | 113 | ||
94 | domain = irq_domain_alloc(of_node, IRQ_DOMAIN_MAP_LEGACY, ops, host_data); | 114 | domain = irq_domain_alloc(of_node, IRQ_DOMAIN_MAP_LEGACY, ops, host_data); |
95 | if (!domain) | 115 | if (!domain) |
96 | return NULL; | 116 | return NULL; |
97 | 117 | ||
118 | domain->revmap_data.legacy.first_irq = first_irq; | ||
119 | domain->revmap_data.legacy.first_hwirq = first_hwirq; | ||
120 | domain->revmap_data.legacy.size = size; | ||
121 | |||
98 | mutex_lock(&irq_domain_mutex); | 122 | mutex_lock(&irq_domain_mutex); |
99 | /* Make sure only one legacy controller can be created */ | 123 | /* Verify that all the irqs are available */ |
100 | list_for_each_entry(h, &irq_domain_list, link) { | 124 | for (i = 0; i < size; i++) { |
101 | if (WARN_ON(h->revmap_type == IRQ_DOMAIN_MAP_LEGACY)) { | 125 | int irq = first_irq + i; |
126 | struct irq_data *irq_data = irq_get_irq_data(irq); | ||
127 | |||
128 | if (WARN_ON(!irq_data || irq_data->domain)) { | ||
102 | mutex_unlock(&irq_domain_mutex); | 129 | mutex_unlock(&irq_domain_mutex); |
103 | of_node_put(domain->of_node); | 130 | of_node_put(domain->of_node); |
104 | kfree(domain); | 131 | kfree(domain); |
105 | return NULL; | 132 | return NULL; |
106 | } | 133 | } |
107 | } | 134 | } |
108 | list_add(&domain->link, &irq_domain_list); | ||
109 | mutex_unlock(&irq_domain_mutex); | ||
110 | 135 | ||
111 | /* setup us as the domain for all legacy interrupts */ | 136 | /* Claim all of the irqs before registering a legacy domain */ |
112 | for (i = 1; i < NUM_ISA_INTERRUPTS; i++) { | 137 | for (i = 0; i < size; i++) { |
113 | struct irq_data *irq_data = irq_get_irq_data(i); | 138 | struct irq_data *irq_data = irq_get_irq_data(first_irq + i); |
114 | irq_data->hwirq = i; | 139 | irq_data->hwirq = first_hwirq + i; |
115 | irq_data->domain = domain; | 140 | irq_data->domain = domain; |
141 | } | ||
142 | mutex_unlock(&irq_domain_mutex); | ||
143 | |||
144 | for (i = 0; i < size; i++) { | ||
145 | int irq = first_irq + i; | ||
146 | int hwirq = first_hwirq + i; | ||
147 | |||
148 | /* IRQ0 gets ignored */ | ||
149 | if (!irq) | ||
150 | continue; | ||
116 | 151 | ||
117 | /* Legacy flags are left to default at this point, | 152 | /* Legacy flags are left to default at this point, |
118 | * one can then use irq_create_mapping() to | 153 | * one can then use irq_create_mapping() to |
119 | * explicitly change them | 154 | * explicitly change them |
120 | */ | 155 | */ |
121 | ops->map(domain, i, i); | 156 | ops->map(domain, irq, hwirq); |
122 | 157 | ||
123 | /* Clear norequest flags */ | 158 | /* Clear norequest flags */ |
124 | irq_clear_status_flags(i, IRQ_NOREQUEST); | 159 | irq_clear_status_flags(irq, IRQ_NOREQUEST); |
125 | } | 160 | } |
161 | |||
162 | irq_domain_add(domain); | ||
126 | return domain; | 163 | return domain; |
127 | } | 164 | } |
128 | 165 | ||
@@ -338,24 +375,19 @@ unsigned int irq_create_mapping(struct irq_domain *domain, | |||
338 | } | 375 | } |
339 | 376 | ||
340 | /* Get a virtual interrupt number */ | 377 | /* Get a virtual interrupt number */ |
341 | if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) { | 378 | if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) |
342 | /* Handle legacy */ | 379 | return irq_domain_legacy_revmap(domain, hwirq); |
343 | virq = (unsigned int)hwirq; | 380 | |
344 | if (virq == 0 || virq >= NUM_ISA_INTERRUPTS) | 381 | /* Allocate a virtual interrupt number */ |
345 | return 0; | 382 | hint = hwirq % irq_virq_count; |
346 | return virq; | 383 | if (hint == 0) |
347 | } else { | 384 | hint++; |
348 | /* Allocate a virtual interrupt number */ | 385 | virq = irq_alloc_desc_from(hint, 0); |
349 | hint = hwirq % irq_virq_count; | 386 | if (!virq) |
350 | if (hint == 0) | 387 | virq = irq_alloc_desc_from(1, 0); |
351 | hint++; | 388 | if (!virq) { |
352 | virq = irq_alloc_desc_from(hint, 0); | 389 | pr_debug("irq: -> virq allocation failed\n"); |
353 | if (!virq) | 390 | return 0; |
354 | virq = irq_alloc_desc_from(1, 0); | ||
355 | if (!virq) { | ||
356 | pr_debug("irq: -> virq allocation failed\n"); | ||
357 | return 0; | ||
358 | } | ||
359 | } | 391 | } |
360 | 392 | ||
361 | if (irq_setup_virq(domain, virq, hwirq)) { | 393 | if (irq_setup_virq(domain, virq, hwirq)) { |
@@ -483,7 +515,7 @@ unsigned int irq_find_mapping(struct irq_domain *domain, | |||
483 | 515 | ||
484 | /* legacy -> bail early */ | 516 | /* legacy -> bail early */ |
485 | if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) | 517 | if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) |
486 | return hwirq; | 518 | return irq_domain_legacy_revmap(domain, hwirq); |
487 | 519 | ||
488 | /* Slow path does a linear search of the map */ | 520 | /* Slow path does a linear search of the map */ |
489 | if (hint == 0) | 521 | if (hint == 0) |