diff options
author | Marc Zyngier <marc.zyngier@arm.com> | 2018-02-06 13:55:33 -0500 |
---|---|---|
committer | Marc Zyngier <marc.zyngier@arm.com> | 2018-02-16 08:47:58 -0500 |
commit | de337ee301422756dff43d6c60fbb0400c1235e9 (patch) | |
tree | e9b697ef630c31e9ce5aafc336ed650678408cae | |
parent | 95a2562590c2f64a0398183f978d5cf3db6d0284 (diff) |
irqchip/gic-v2m: Add PCI Multi-MSI support
We'd never implemented Multi-MSI support with GICv2m, because
it is weird and clunky, and you'd think people would rather use
MSI-X.
Turns out there is still plenty of devices out there that rely
on Multi-MSI. Oh well, let's teach that trick to the v2m widget,
it is not a big deal anyway.
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
-rw-r--r-- | drivers/irqchip/irq-gic-v2m.c | 46 |
1 files changed, 22 insertions, 24 deletions
diff --git a/drivers/irqchip/irq-gic-v2m.c b/drivers/irqchip/irq-gic-v2m.c index 993a8426a453..1ff38aff9f29 100644 --- a/drivers/irqchip/irq-gic-v2m.c +++ b/drivers/irqchip/irq-gic-v2m.c | |||
@@ -94,7 +94,7 @@ static struct irq_chip gicv2m_msi_irq_chip = { | |||
94 | 94 | ||
95 | static struct msi_domain_info gicv2m_msi_domain_info = { | 95 | static struct msi_domain_info gicv2m_msi_domain_info = { |
96 | .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | | 96 | .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | |
97 | MSI_FLAG_PCI_MSIX), | 97 | MSI_FLAG_PCI_MSIX | MSI_FLAG_MULTI_PCI_MSI), |
98 | .chip = &gicv2m_msi_irq_chip, | 98 | .chip = &gicv2m_msi_irq_chip, |
99 | }; | 99 | }; |
100 | 100 | ||
@@ -155,18 +155,12 @@ static int gicv2m_irq_gic_domain_alloc(struct irq_domain *domain, | |||
155 | return 0; | 155 | return 0; |
156 | } | 156 | } |
157 | 157 | ||
158 | static void gicv2m_unalloc_msi(struct v2m_data *v2m, unsigned int hwirq) | 158 | static void gicv2m_unalloc_msi(struct v2m_data *v2m, unsigned int hwirq, |
159 | int nr_irqs) | ||
159 | { | 160 | { |
160 | int pos; | ||
161 | |||
162 | pos = hwirq - v2m->spi_start; | ||
163 | if (pos < 0 || pos >= v2m->nr_spis) { | ||
164 | pr_err("Failed to teardown msi. Invalid hwirq %d\n", hwirq); | ||
165 | return; | ||
166 | } | ||
167 | |||
168 | spin_lock(&v2m_lock); | 161 | spin_lock(&v2m_lock); |
169 | __clear_bit(pos, v2m->bm); | 162 | bitmap_release_region(v2m->bm, hwirq - v2m->spi_start, |
163 | get_count_order(nr_irqs)); | ||
170 | spin_unlock(&v2m_lock); | 164 | spin_unlock(&v2m_lock); |
171 | } | 165 | } |
172 | 166 | ||
@@ -174,13 +168,13 @@ static int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, | |||
174 | unsigned int nr_irqs, void *args) | 168 | unsigned int nr_irqs, void *args) |
175 | { | 169 | { |
176 | struct v2m_data *v2m = NULL, *tmp; | 170 | struct v2m_data *v2m = NULL, *tmp; |
177 | int hwirq, offset, err = 0; | 171 | int hwirq, offset, i, err = 0; |
178 | 172 | ||
179 | spin_lock(&v2m_lock); | 173 | spin_lock(&v2m_lock); |
180 | list_for_each_entry(tmp, &v2m_nodes, entry) { | 174 | list_for_each_entry(tmp, &v2m_nodes, entry) { |
181 | offset = find_first_zero_bit(tmp->bm, tmp->nr_spis); | 175 | offset = bitmap_find_free_region(tmp->bm, tmp->nr_spis, |
182 | if (offset < tmp->nr_spis) { | 176 | get_count_order(nr_irqs)); |
183 | __set_bit(offset, tmp->bm); | 177 | if (offset >= 0) { |
184 | v2m = tmp; | 178 | v2m = tmp; |
185 | break; | 179 | break; |
186 | } | 180 | } |
@@ -192,16 +186,21 @@ static int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, | |||
192 | 186 | ||
193 | hwirq = v2m->spi_start + offset; | 187 | hwirq = v2m->spi_start + offset; |
194 | 188 | ||
195 | err = gicv2m_irq_gic_domain_alloc(domain, virq, hwirq); | 189 | for (i = 0; i < nr_irqs; i++) { |
196 | if (err) { | 190 | err = gicv2m_irq_gic_domain_alloc(domain, virq + i, hwirq + i); |
197 | gicv2m_unalloc_msi(v2m, hwirq); | 191 | if (err) |
198 | return err; | 192 | goto fail; |
199 | } | ||
200 | 193 | ||
201 | irq_domain_set_hwirq_and_chip(domain, virq, hwirq, | 194 | irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, |
202 | &gicv2m_irq_chip, v2m); | 195 | &gicv2m_irq_chip, v2m); |
196 | } | ||
203 | 197 | ||
204 | return 0; | 198 | return 0; |
199 | |||
200 | fail: | ||
201 | irq_domain_free_irqs_parent(domain, virq, nr_irqs); | ||
202 | gicv2m_unalloc_msi(v2m, hwirq, get_count_order(nr_irqs)); | ||
203 | return err; | ||
205 | } | 204 | } |
206 | 205 | ||
207 | static void gicv2m_irq_domain_free(struct irq_domain *domain, | 206 | static void gicv2m_irq_domain_free(struct irq_domain *domain, |
@@ -210,8 +209,7 @@ static void gicv2m_irq_domain_free(struct irq_domain *domain, | |||
210 | struct irq_data *d = irq_domain_get_irq_data(domain, virq); | 209 | struct irq_data *d = irq_domain_get_irq_data(domain, virq); |
211 | struct v2m_data *v2m = irq_data_get_irq_chip_data(d); | 210 | struct v2m_data *v2m = irq_data_get_irq_chip_data(d); |
212 | 211 | ||
213 | BUG_ON(nr_irqs != 1); | 212 | gicv2m_unalloc_msi(v2m, d->hwirq, nr_irqs); |
214 | gicv2m_unalloc_msi(v2m, d->hwirq); | ||
215 | irq_domain_free_irqs_parent(domain, virq, nr_irqs); | 213 | irq_domain_free_irqs_parent(domain, virq, nr_irqs); |
216 | } | 214 | } |
217 | 215 | ||