diff options
author | Marc Zyngier <marc.zyngier@arm.com> | 2018-05-08 08:14:36 -0400 |
---|---|---|
committer | Thomas Gleixner <tglx@linutronix.de> | 2018-05-13 09:59:01 -0400 |
commit | 505287525c24d5c78b662fd73721ad9900b91fcc (patch) | |
tree | b12a24720a5963ba2b64c2bc169845f6a36c7fec | |
parent | b2425b51bed7437c08c11ce71bb0f308f4516451 (diff) |
irqchip/gic-v3: Add support for Message Based Interrupts as an MSI controller
GICv3 offers the possibility to signal SPIs using a pair of doorbells
(SETPI, CLRSPI) under the name of Message Based Interrupts (MBI).
They can be used as either traditional (edge) MSIs, or the more exotic
level-triggered flavour.
Let's implement support for platform MSI, which is the original intent
for this feature.
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Cc: Miquel Raynal <miquel.raynal@bootlin.com>
Link: https://lkml.kernel.org/r/20180508121438.11301-8-marc.zyngier@arm.com
-rw-r--r-- | drivers/irqchip/Makefile | 2 | ||||
-rw-r--r-- | drivers/irqchip/irq-gic-v3-mbi.c | 275 | ||||
-rw-r--r-- | drivers/irqchip/irq-gic-v3.c | 6 | ||||
-rw-r--r-- | include/linux/irqchip/arm-gic-v3.h | 1 |
4 files changed, 283 insertions, 1 deletions
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 5ed465ab1c76..15f268f646bf 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile | |||
@@ -27,7 +27,7 @@ obj-$(CONFIG_ARM_GIC) += irq-gic.o irq-gic-common.o | |||
27 | obj-$(CONFIG_ARM_GIC_PM) += irq-gic-pm.o | 27 | obj-$(CONFIG_ARM_GIC_PM) += irq-gic-pm.o |
28 | obj-$(CONFIG_ARCH_REALVIEW) += irq-gic-realview.o | 28 | obj-$(CONFIG_ARCH_REALVIEW) += irq-gic-realview.o |
29 | obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o | 29 | obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o |
30 | obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-common.o | 30 | obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o |
31 | obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-platform-msi.o irq-gic-v4.o | 31 | obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-platform-msi.o irq-gic-v4.o |
32 | obj-$(CONFIG_ARM_GIC_V3_ITS_PCI) += irq-gic-v3-its-pci-msi.o | 32 | obj-$(CONFIG_ARM_GIC_V3_ITS_PCI) += irq-gic-v3-its-pci-msi.o |
33 | obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o | 33 | obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o |
diff --git a/drivers/irqchip/irq-gic-v3-mbi.c b/drivers/irqchip/irq-gic-v3-mbi.c new file mode 100644 index 000000000000..2b3b767050aa --- /dev/null +++ b/drivers/irqchip/irq-gic-v3-mbi.c | |||
@@ -0,0 +1,275 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Copyright (C) 2018 ARM Limited, All Rights Reserved. | ||
4 | * Author: Marc Zyngier <marc.zyngier@arm.com> | ||
5 | */ | ||
6 | |||
7 | #define pr_fmt(fmt) "GICv3: " fmt | ||
8 | |||
9 | #include <linux/dma-iommu.h> | ||
10 | #include <linux/irq.h> | ||
11 | #include <linux/irqdomain.h> | ||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/msi.h> | ||
14 | #include <linux/of_address.h> | ||
15 | #include <linux/slab.h> | ||
16 | #include <linux/spinlock.h> | ||
17 | |||
18 | #include <linux/irqchip/arm-gic-v3.h> | ||
19 | |||
20 | struct mbi_range { | ||
21 | u32 spi_start; | ||
22 | u32 nr_spis; | ||
23 | unsigned long *bm; | ||
24 | }; | ||
25 | |||
26 | static struct mutex mbi_lock; | ||
27 | static phys_addr_t mbi_phys_base; | ||
28 | static struct mbi_range *mbi_ranges; | ||
29 | static unsigned int mbi_range_nr; | ||
30 | |||
31 | static struct irq_chip mbi_irq_chip = { | ||
32 | .name = "MBI", | ||
33 | .irq_mask = irq_chip_mask_parent, | ||
34 | .irq_unmask = irq_chip_unmask_parent, | ||
35 | .irq_eoi = irq_chip_eoi_parent, | ||
36 | .irq_set_type = irq_chip_set_type_parent, | ||
37 | .irq_set_affinity = irq_chip_set_affinity_parent, | ||
38 | }; | ||
39 | |||
40 | static int mbi_irq_gic_domain_alloc(struct irq_domain *domain, | ||
41 | unsigned int virq, | ||
42 | irq_hw_number_t hwirq) | ||
43 | { | ||
44 | struct irq_fwspec fwspec; | ||
45 | struct irq_data *d; | ||
46 | int err; | ||
47 | |||
48 | /* | ||
49 | * Using ACPI? There is no MBI support in the spec, you | ||
50 | * shouldn't even be here. | ||
51 | */ | ||
52 | if (!is_of_node(domain->parent->fwnode)) | ||
53 | return -EINVAL; | ||
54 | |||
55 | /* | ||
56 | * Let's default to edge. This is consistent with traditional | ||
57 | * MSIs, and systems requiring level signaling will just | ||
58 | * enforce the trigger on their own. | ||
59 | */ | ||
60 | fwspec.fwnode = domain->parent->fwnode; | ||
61 | fwspec.param_count = 3; | ||
62 | fwspec.param[0] = 0; | ||
63 | fwspec.param[1] = hwirq - 32; | ||
64 | fwspec.param[2] = IRQ_TYPE_EDGE_RISING; | ||
65 | |||
66 | err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); | ||
67 | if (err) | ||
68 | return err; | ||
69 | |||
70 | d = irq_domain_get_irq_data(domain->parent, virq); | ||
71 | return d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING); | ||
72 | } | ||
73 | |||
74 | static void mbi_free_msi(struct mbi_range *mbi, unsigned int hwirq, | ||
75 | int nr_irqs) | ||
76 | { | ||
77 | mutex_lock(&mbi_lock); | ||
78 | bitmap_release_region(mbi->bm, hwirq - mbi->spi_start, | ||
79 | get_count_order(nr_irqs)); | ||
80 | mutex_unlock(&mbi_lock); | ||
81 | } | ||
82 | |||
83 | static int mbi_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, | ||
84 | unsigned int nr_irqs, void *args) | ||
85 | { | ||
86 | struct mbi_range *mbi = NULL; | ||
87 | int hwirq, offset, i, err = 0; | ||
88 | |||
89 | mutex_lock(&mbi_lock); | ||
90 | for (i = 0; i < mbi_range_nr; i++) { | ||
91 | offset = bitmap_find_free_region(mbi_ranges[i].bm, | ||
92 | mbi_ranges[i].nr_spis, | ||
93 | get_count_order(nr_irqs)); | ||
94 | if (offset >= 0) { | ||
95 | mbi = &mbi_ranges[i]; | ||
96 | break; | ||
97 | } | ||
98 | } | ||
99 | mutex_unlock(&mbi_lock); | ||
100 | |||
101 | if (!mbi) | ||
102 | return -ENOSPC; | ||
103 | |||
104 | hwirq = mbi->spi_start + offset; | ||
105 | |||
106 | for (i = 0; i < nr_irqs; i++) { | ||
107 | err = mbi_irq_gic_domain_alloc(domain, virq + i, hwirq + i); | ||
108 | if (err) | ||
109 | goto fail; | ||
110 | |||
111 | irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, | ||
112 | &mbi_irq_chip, mbi); | ||
113 | } | ||
114 | |||
115 | return 0; | ||
116 | |||
117 | fail: | ||
118 | irq_domain_free_irqs_parent(domain, virq, nr_irqs); | ||
119 | mbi_free_msi(mbi, hwirq, nr_irqs); | ||
120 | return err; | ||
121 | } | ||
122 | |||
123 | static void mbi_irq_domain_free(struct irq_domain *domain, | ||
124 | unsigned int virq, unsigned int nr_irqs) | ||
125 | { | ||
126 | struct irq_data *d = irq_domain_get_irq_data(domain, virq); | ||
127 | struct mbi_range *mbi = irq_data_get_irq_chip_data(d); | ||
128 | |||
129 | mbi_free_msi(mbi, d->hwirq, nr_irqs); | ||
130 | irq_domain_free_irqs_parent(domain, virq, nr_irqs); | ||
131 | } | ||
132 | |||
133 | static const struct irq_domain_ops mbi_domain_ops = { | ||
134 | .alloc = mbi_irq_domain_alloc, | ||
135 | .free = mbi_irq_domain_free, | ||
136 | }; | ||
137 | |||
138 | static void mbi_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) | ||
139 | { | ||
140 | msg[0].address_hi = upper_32_bits(mbi_phys_base + GICD_SETSPI_NSR); | ||
141 | msg[0].address_lo = lower_32_bits(mbi_phys_base + GICD_SETSPI_NSR); | ||
142 | msg[0].data = data->parent_data->hwirq; | ||
143 | |||
144 | iommu_dma_map_msi_msg(data->irq, msg); | ||
145 | } | ||
146 | |||
147 | static void mbi_compose_mbi_msg(struct irq_data *data, struct msi_msg *msg) | ||
148 | { | ||
149 | mbi_compose_msi_msg(data, msg); | ||
150 | |||
151 | msg[1].address_hi = upper_32_bits(mbi_phys_base + GICD_CLRSPI_NSR); | ||
152 | msg[1].address_lo = lower_32_bits(mbi_phys_base + GICD_CLRSPI_NSR); | ||
153 | msg[1].data = data->parent_data->hwirq; | ||
154 | |||
155 | iommu_dma_map_msi_msg(data->irq, &msg[1]); | ||
156 | } | ||
157 | |||
158 | /* Platform-MSI specific irqchip */ | ||
159 | static struct irq_chip mbi_pmsi_irq_chip = { | ||
160 | .name = "pMSI", | ||
161 | .irq_set_type = irq_chip_set_type_parent, | ||
162 | .irq_compose_msi_msg = mbi_compose_mbi_msg, | ||
163 | .flags = IRQCHIP_SUPPORTS_LEVEL_MSI, | ||
164 | }; | ||
165 | |||
166 | static struct msi_domain_ops mbi_pmsi_ops = { | ||
167 | }; | ||
168 | |||
169 | static struct msi_domain_info mbi_pmsi_domain_info = { | ||
170 | .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | | ||
171 | MSI_FLAG_LEVEL_CAPABLE), | ||
172 | .ops = &mbi_pmsi_ops, | ||
173 | .chip = &mbi_pmsi_irq_chip, | ||
174 | }; | ||
175 | |||
176 | static int mbi_allocate_domains(struct irq_domain *parent) | ||
177 | { | ||
178 | struct irq_domain *nexus_domain, *plat_domain; | ||
179 | |||
180 | nexus_domain = irq_domain_create_tree(parent->fwnode, | ||
181 | &mbi_domain_ops, NULL); | ||
182 | if (!nexus_domain) | ||
183 | return -ENOMEM; | ||
184 | |||
185 | irq_domain_update_bus_token(nexus_domain, DOMAIN_BUS_NEXUS); | ||
186 | nexus_domain->parent = parent; | ||
187 | |||
188 | plat_domain = platform_msi_create_irq_domain(parent->fwnode, | ||
189 | &mbi_pmsi_domain_info, | ||
190 | nexus_domain); | ||
191 | |||
192 | if (!plat_domain) { | ||
193 | irq_domain_remove(plat_domain); | ||
194 | irq_domain_remove(nexus_domain); | ||
195 | return -ENOMEM; | ||
196 | } | ||
197 | |||
198 | return 0; | ||
199 | } | ||
200 | |||
201 | int __init mbi_init(struct fwnode_handle *fwnode, struct irq_domain *parent) | ||
202 | { | ||
203 | struct device_node *np; | ||
204 | const __be32 *reg; | ||
205 | int ret, n; | ||
206 | |||
207 | np = to_of_node(fwnode); | ||
208 | |||
209 | if (!of_property_read_bool(np, "msi-controller")) | ||
210 | return 0; | ||
211 | |||
212 | n = of_property_count_elems_of_size(np, "mbi-ranges", sizeof(u32)); | ||
213 | if (n <= 0 || n % 2) | ||
214 | return -EINVAL; | ||
215 | |||
216 | mbi_range_nr = n / 2; | ||
217 | mbi_ranges = kcalloc(mbi_range_nr, sizeof(*mbi_ranges), GFP_KERNEL); | ||
218 | if (!mbi_ranges) | ||
219 | return -ENOMEM; | ||
220 | |||
221 | for (n = 0; n < mbi_range_nr; n++) { | ||
222 | ret = of_property_read_u32_index(np, "mbi-ranges", n * 2, | ||
223 | &mbi_ranges[n].spi_start); | ||
224 | if (ret) | ||
225 | goto err_free_mbi; | ||
226 | ret = of_property_read_u32_index(np, "mbi-ranges", n * 2 + 1, | ||
227 | &mbi_ranges[n].nr_spis); | ||
228 | if (ret) | ||
229 | goto err_free_mbi; | ||
230 | |||
231 | mbi_ranges[n].bm = kcalloc(BITS_TO_LONGS(mbi_ranges[n].nr_spis), | ||
232 | sizeof(long), GFP_KERNEL); | ||
233 | if (!mbi_ranges[n].bm) { | ||
234 | ret = -ENOMEM; | ||
235 | goto err_free_mbi; | ||
236 | } | ||
237 | pr_info("MBI range [%d:%d]\n", mbi_ranges[n].spi_start, | ||
238 | mbi_ranges[n].spi_start + mbi_ranges[n].nr_spis - 1); | ||
239 | } | ||
240 | |||
241 | reg = of_get_property(np, "mbi-alias", NULL); | ||
242 | if (reg) { | ||
243 | mbi_phys_base = of_translate_address(np, reg); | ||
244 | if (mbi_phys_base == OF_BAD_ADDR) { | ||
245 | ret = -ENXIO; | ||
246 | goto err_free_mbi; | ||
247 | } | ||
248 | } else { | ||
249 | struct resource res; | ||
250 | |||
251 | if (of_address_to_resource(np, 0, &res)) { | ||
252 | ret = -ENXIO; | ||
253 | goto err_free_mbi; | ||
254 | } | ||
255 | |||
256 | mbi_phys_base = res.start; | ||
257 | } | ||
258 | |||
259 | pr_info("Using MBI frame %pa\n", &mbi_phys_base); | ||
260 | |||
261 | ret = mbi_allocate_domains(parent); | ||
262 | if (ret) | ||
263 | goto err_free_mbi; | ||
264 | |||
265 | return 0; | ||
266 | |||
267 | err_free_mbi: | ||
268 | if (mbi_ranges) { | ||
269 | for (n = 0; n < mbi_range_nr; n++) | ||
270 | kfree(mbi_ranges[n].bm); | ||
271 | kfree(mbi_ranges); | ||
272 | } | ||
273 | |||
274 | return ret; | ||
275 | } | ||
diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 94164d7b87a6..5a67ec084588 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c | |||
@@ -1113,6 +1113,12 @@ static int __init gic_init_bases(void __iomem *dist_base, | |||
1113 | pr_info("Distributor has %sRange Selector support\n", | 1113 | pr_info("Distributor has %sRange Selector support\n", |
1114 | gic_data.has_rss ? "" : "no "); | 1114 | gic_data.has_rss ? "" : "no "); |
1115 | 1115 | ||
1116 | if (typer & GICD_TYPER_MBIS) { | ||
1117 | err = mbi_init(handle, gic_data.domain); | ||
1118 | if (err) | ||
1119 | pr_err("Failed to initialize MBIs\n"); | ||
1120 | } | ||
1121 | |||
1116 | set_handle_irq(gic_handle_irq); | 1122 | set_handle_irq(gic_handle_irq); |
1117 | 1123 | ||
1118 | gic_update_vlpi_properties(); | 1124 | gic_update_vlpi_properties(); |
diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h index f5af3b594e6e..cbb872c1b607 100644 --- a/include/linux/irqchip/arm-gic-v3.h +++ b/include/linux/irqchip/arm-gic-v3.h | |||
@@ -587,6 +587,7 @@ struct fwnode_handle; | |||
587 | int its_cpu_init(void); | 587 | int its_cpu_init(void); |
588 | int its_init(struct fwnode_handle *handle, struct rdists *rdists, | 588 | int its_init(struct fwnode_handle *handle, struct rdists *rdists, |
589 | struct irq_domain *domain); | 589 | struct irq_domain *domain); |
590 | int mbi_init(struct fwnode_handle *fwnode, struct irq_domain *parent); | ||
590 | 591 | ||
591 | static inline bool gic_enable_sre(void) | 592 | static inline bool gic_enable_sre(void) |
592 | { | 593 | { |