diff options
-rw-r--r-- | arch/arm64/Kconfig | 1 | ||||
-rw-r--r-- | drivers/irqchip/Kconfig | 6 | ||||
-rw-r--r-- | drivers/irqchip/Makefile | 1 | ||||
-rw-r--r-- | drivers/irqchip/irq-gic-v2m.c | 333 | ||||
-rw-r--r-- | drivers/irqchip/irq-gic.c | 4 | ||||
-rw-r--r-- | include/linux/irqchip/arm-gic.h | 2 |
6 files changed, 347 insertions, 0 deletions
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 1f49c288457b..e06e1a99eafa 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig | |||
@@ -12,6 +12,7 @@ config ARM64 | |||
12 | select ARM_ARCH_TIMER | 12 | select ARM_ARCH_TIMER |
13 | select ARM_GIC | 13 | select ARM_GIC |
14 | select AUDIT_ARCH_COMPAT_GENERIC | 14 | select AUDIT_ARCH_COMPAT_GENERIC |
15 | select ARM_GIC_V2M if PCI_MSI | ||
15 | select ARM_GIC_V3 | 16 | select ARM_GIC_V3 |
16 | select ARM_GIC_V3_ITS if PCI_MSI | 17 | select ARM_GIC_V3_ITS if PCI_MSI |
17 | select BUILDTIME_EXTABLE_SORT | 18 | select BUILDTIME_EXTABLE_SORT |
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index d47fa846763b..e72e23960632 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig | |||
@@ -8,6 +8,12 @@ config ARM_GIC | |||
8 | select IRQ_DOMAIN_HIERARCHY | 8 | select IRQ_DOMAIN_HIERARCHY |
9 | select MULTI_IRQ_HANDLER | 9 | select MULTI_IRQ_HANDLER |
10 | 10 | ||
11 | config ARM_GIC_V2M | ||
12 | bool | ||
13 | depends on ARM_GIC | ||
14 | depends on PCI && PCI_MSI | ||
15 | select PCI_MSI_IRQ_DOMAIN | ||
16 | |||
11 | config GIC_NON_BANKED | 17 | config GIC_NON_BANKED |
12 | bool | 18 | bool |
13 | 19 | ||
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 2029e79c07a9..983634d61b25 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile | |||
@@ -19,6 +19,7 @@ obj-$(CONFIG_ARCH_SUNXI) += irq-sun4i.o | |||
19 | obj-$(CONFIG_ARCH_SUNXI) += irq-sunxi-nmi.o | 19 | obj-$(CONFIG_ARCH_SUNXI) += irq-sunxi-nmi.o |
20 | obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o | 20 | obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o |
21 | obj-$(CONFIG_ARM_GIC) += irq-gic.o irq-gic-common.o | 21 | obj-$(CONFIG_ARM_GIC) += irq-gic.o irq-gic-common.o |
22 | obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o | ||
22 | obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-common.o | 23 | obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-common.o |
23 | obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o | 24 | obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o |
24 | obj-$(CONFIG_ARM_NVIC) += irq-nvic.o | 25 | obj-$(CONFIG_ARM_NVIC) += irq-nvic.o |
diff --git a/drivers/irqchip/irq-gic-v2m.c b/drivers/irqchip/irq-gic-v2m.c new file mode 100644 index 000000000000..fdf706555d72 --- /dev/null +++ b/drivers/irqchip/irq-gic-v2m.c | |||
@@ -0,0 +1,333 @@ | |||
1 | /* | ||
2 | * ARM GIC v2m MSI(-X) support | ||
3 | * Support for Message Signaled Interrupts for systems that | ||
4 | * implement ARM Generic Interrupt Controller: GICv2m. | ||
5 | * | ||
6 | * Copyright (C) 2014 Advanced Micro Devices, Inc. | ||
7 | * Authors: Suravee Suthikulpanit <suravee.suthikulpanit@amd.com> | ||
8 | * Harish Kasiviswanathan <harish.kasiviswanathan@amd.com> | ||
9 | * Brandon Anderson <brandon.anderson@amd.com> | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify it | ||
12 | * under the terms of the GNU General Public License version 2 as published | ||
13 | * by the Free Software Foundation. | ||
14 | */ | ||
15 | |||
16 | #define pr_fmt(fmt) "GICv2m: " fmt | ||
17 | |||
18 | #include <linux/irq.h> | ||
19 | #include <linux/irqdomain.h> | ||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/of_address.h> | ||
22 | #include <linux/of_pci.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/spinlock.h> | ||
25 | |||
26 | /* | ||
27 | * MSI_TYPER: | ||
28 | * [31:26] Reserved | ||
29 | * [25:16] lowest SPI assigned to MSI | ||
30 | * [15:10] Reserved | ||
31 | * [9:0] Numer of SPIs assigned to MSI | ||
32 | */ | ||
33 | #define V2M_MSI_TYPER 0x008 | ||
34 | #define V2M_MSI_TYPER_BASE_SHIFT 16 | ||
35 | #define V2M_MSI_TYPER_BASE_MASK 0x3FF | ||
36 | #define V2M_MSI_TYPER_NUM_MASK 0x3FF | ||
37 | #define V2M_MSI_SETSPI_NS 0x040 | ||
38 | #define V2M_MIN_SPI 32 | ||
39 | #define V2M_MAX_SPI 1019 | ||
40 | |||
41 | #define V2M_MSI_TYPER_BASE_SPI(x) \ | ||
42 | (((x) >> V2M_MSI_TYPER_BASE_SHIFT) & V2M_MSI_TYPER_BASE_MASK) | ||
43 | |||
44 | #define V2M_MSI_TYPER_NUM_SPI(x) ((x) & V2M_MSI_TYPER_NUM_MASK) | ||
45 | |||
46 | struct v2m_data { | ||
47 | spinlock_t msi_cnt_lock; | ||
48 | struct msi_controller mchip; | ||
49 | struct resource res; /* GICv2m resource */ | ||
50 | void __iomem *base; /* GICv2m virt address */ | ||
51 | u32 spi_start; /* The SPI number that MSIs start */ | ||
52 | u32 nr_spis; /* The number of SPIs for MSIs */ | ||
53 | unsigned long *bm; /* MSI vector bitmap */ | ||
54 | struct irq_domain *domain; | ||
55 | }; | ||
56 | |||
57 | static void gicv2m_mask_msi_irq(struct irq_data *d) | ||
58 | { | ||
59 | pci_msi_mask_irq(d); | ||
60 | irq_chip_mask_parent(d); | ||
61 | } | ||
62 | |||
63 | static void gicv2m_unmask_msi_irq(struct irq_data *d) | ||
64 | { | ||
65 | pci_msi_unmask_irq(d); | ||
66 | irq_chip_unmask_parent(d); | ||
67 | } | ||
68 | |||
69 | static struct irq_chip gicv2m_msi_irq_chip = { | ||
70 | .name = "MSI", | ||
71 | .irq_mask = gicv2m_mask_msi_irq, | ||
72 | .irq_unmask = gicv2m_unmask_msi_irq, | ||
73 | .irq_eoi = irq_chip_eoi_parent, | ||
74 | .irq_write_msi_msg = pci_msi_domain_write_msg, | ||
75 | }; | ||
76 | |||
77 | static struct msi_domain_info gicv2m_msi_domain_info = { | ||
78 | .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | | ||
79 | MSI_FLAG_PCI_MSIX), | ||
80 | .chip = &gicv2m_msi_irq_chip, | ||
81 | }; | ||
82 | |||
83 | static int gicv2m_set_affinity(struct irq_data *irq_data, | ||
84 | const struct cpumask *mask, bool force) | ||
85 | { | ||
86 | int ret; | ||
87 | |||
88 | ret = irq_chip_set_affinity_parent(irq_data, mask, force); | ||
89 | if (ret == IRQ_SET_MASK_OK) | ||
90 | ret = IRQ_SET_MASK_OK_DONE; | ||
91 | |||
92 | return ret; | ||
93 | } | ||
94 | |||
95 | static void gicv2m_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) | ||
96 | { | ||
97 | struct v2m_data *v2m = irq_data_get_irq_chip_data(data); | ||
98 | phys_addr_t addr = v2m->res.start + V2M_MSI_SETSPI_NS; | ||
99 | |||
100 | msg->address_hi = (u32) (addr >> 32); | ||
101 | msg->address_lo = (u32) (addr); | ||
102 | msg->data = data->hwirq; | ||
103 | } | ||
104 | |||
105 | static struct irq_chip gicv2m_irq_chip = { | ||
106 | .name = "GICv2m", | ||
107 | .irq_mask = irq_chip_mask_parent, | ||
108 | .irq_unmask = irq_chip_unmask_parent, | ||
109 | .irq_eoi = irq_chip_eoi_parent, | ||
110 | .irq_set_affinity = gicv2m_set_affinity, | ||
111 | .irq_compose_msi_msg = gicv2m_compose_msi_msg, | ||
112 | }; | ||
113 | |||
114 | static int gicv2m_irq_gic_domain_alloc(struct irq_domain *domain, | ||
115 | unsigned int virq, | ||
116 | irq_hw_number_t hwirq) | ||
117 | { | ||
118 | struct of_phandle_args args; | ||
119 | struct irq_data *d; | ||
120 | int err; | ||
121 | |||
122 | args.np = domain->parent->of_node; | ||
123 | args.args_count = 3; | ||
124 | args.args[0] = 0; | ||
125 | args.args[1] = hwirq - 32; | ||
126 | args.args[2] = IRQ_TYPE_EDGE_RISING; | ||
127 | |||
128 | err = irq_domain_alloc_irqs_parent(domain, virq, 1, &args); | ||
129 | if (err) | ||
130 | return err; | ||
131 | |||
132 | /* Configure the interrupt line to be edge */ | ||
133 | d = irq_domain_get_irq_data(domain->parent, virq); | ||
134 | d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING); | ||
135 | return 0; | ||
136 | } | ||
137 | |||
138 | static void gicv2m_unalloc_msi(struct v2m_data *v2m, unsigned int hwirq) | ||
139 | { | ||
140 | int pos; | ||
141 | |||
142 | pos = hwirq - v2m->spi_start; | ||
143 | if (pos < 0 || pos >= v2m->nr_spis) { | ||
144 | pr_err("Failed to teardown msi. Invalid hwirq %d\n", hwirq); | ||
145 | return; | ||
146 | } | ||
147 | |||
148 | spin_lock(&v2m->msi_cnt_lock); | ||
149 | __clear_bit(pos, v2m->bm); | ||
150 | spin_unlock(&v2m->msi_cnt_lock); | ||
151 | } | ||
152 | |||
153 | static int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, | ||
154 | unsigned int nr_irqs, void *args) | ||
155 | { | ||
156 | struct v2m_data *v2m = domain->host_data; | ||
157 | int hwirq, offset, err = 0; | ||
158 | |||
159 | spin_lock(&v2m->msi_cnt_lock); | ||
160 | offset = find_first_zero_bit(v2m->bm, v2m->nr_spis); | ||
161 | if (offset < v2m->nr_spis) | ||
162 | __set_bit(offset, v2m->bm); | ||
163 | else | ||
164 | err = -ENOSPC; | ||
165 | spin_unlock(&v2m->msi_cnt_lock); | ||
166 | |||
167 | if (err) | ||
168 | return err; | ||
169 | |||
170 | hwirq = v2m->spi_start + offset; | ||
171 | |||
172 | err = gicv2m_irq_gic_domain_alloc(domain, virq, hwirq); | ||
173 | if (err) { | ||
174 | gicv2m_unalloc_msi(v2m, hwirq); | ||
175 | return err; | ||
176 | } | ||
177 | |||
178 | irq_domain_set_hwirq_and_chip(domain, virq, hwirq, | ||
179 | &gicv2m_irq_chip, v2m); | ||
180 | |||
181 | return 0; | ||
182 | } | ||
183 | |||
184 | static void gicv2m_irq_domain_free(struct irq_domain *domain, | ||
185 | unsigned int virq, unsigned int nr_irqs) | ||
186 | { | ||
187 | struct irq_data *d = irq_domain_get_irq_data(domain, virq); | ||
188 | struct v2m_data *v2m = irq_data_get_irq_chip_data(d); | ||
189 | |||
190 | BUG_ON(nr_irqs != 1); | ||
191 | gicv2m_unalloc_msi(v2m, d->hwirq); | ||
192 | irq_domain_free_irqs_parent(domain, virq, nr_irqs); | ||
193 | } | ||
194 | |||
195 | static const struct irq_domain_ops gicv2m_domain_ops = { | ||
196 | .alloc = gicv2m_irq_domain_alloc, | ||
197 | .free = gicv2m_irq_domain_free, | ||
198 | }; | ||
199 | |||
200 | static bool is_msi_spi_valid(u32 base, u32 num) | ||
201 | { | ||
202 | if (base < V2M_MIN_SPI) { | ||
203 | pr_err("Invalid MSI base SPI (base:%u)\n", base); | ||
204 | return false; | ||
205 | } | ||
206 | |||
207 | if ((num == 0) || (base + num > V2M_MAX_SPI)) { | ||
208 | pr_err("Number of SPIs (%u) exceed maximum (%u)\n", | ||
209 | num, V2M_MAX_SPI - V2M_MIN_SPI + 1); | ||
210 | return false; | ||
211 | } | ||
212 | |||
213 | return true; | ||
214 | } | ||
215 | |||
216 | static int __init gicv2m_init_one(struct device_node *node, | ||
217 | struct irq_domain *parent) | ||
218 | { | ||
219 | int ret; | ||
220 | struct v2m_data *v2m; | ||
221 | |||
222 | v2m = kzalloc(sizeof(struct v2m_data), GFP_KERNEL); | ||
223 | if (!v2m) { | ||
224 | pr_err("Failed to allocate struct v2m_data.\n"); | ||
225 | return -ENOMEM; | ||
226 | } | ||
227 | |||
228 | ret = of_address_to_resource(node, 0, &v2m->res); | ||
229 | if (ret) { | ||
230 | pr_err("Failed to allocate v2m resource.\n"); | ||
231 | goto err_free_v2m; | ||
232 | } | ||
233 | |||
234 | v2m->base = ioremap(v2m->res.start, resource_size(&v2m->res)); | ||
235 | if (!v2m->base) { | ||
236 | pr_err("Failed to map GICv2m resource\n"); | ||
237 | ret = -ENOMEM; | ||
238 | goto err_free_v2m; | ||
239 | } | ||
240 | |||
241 | if (!of_property_read_u32(node, "arm,msi-base-spi", &v2m->spi_start) && | ||
242 | !of_property_read_u32(node, "arm,msi-num-spis", &v2m->nr_spis)) { | ||
243 | pr_info("Overriding V2M MSI_TYPER (base:%u, num:%u)\n", | ||
244 | v2m->spi_start, v2m->nr_spis); | ||
245 | } else { | ||
246 | u32 typer = readl_relaxed(v2m->base + V2M_MSI_TYPER); | ||
247 | |||
248 | v2m->spi_start = V2M_MSI_TYPER_BASE_SPI(typer); | ||
249 | v2m->nr_spis = V2M_MSI_TYPER_NUM_SPI(typer); | ||
250 | } | ||
251 | |||
252 | if (!is_msi_spi_valid(v2m->spi_start, v2m->nr_spis)) { | ||
253 | ret = -EINVAL; | ||
254 | goto err_iounmap; | ||
255 | } | ||
256 | |||
257 | v2m->bm = kzalloc(sizeof(long) * BITS_TO_LONGS(v2m->nr_spis), | ||
258 | GFP_KERNEL); | ||
259 | if (!v2m->bm) { | ||
260 | ret = -ENOMEM; | ||
261 | goto err_iounmap; | ||
262 | } | ||
263 | |||
264 | v2m->domain = irq_domain_add_tree(NULL, &gicv2m_domain_ops, v2m); | ||
265 | if (!v2m->domain) { | ||
266 | pr_err("Failed to create GICv2m domain\n"); | ||
267 | ret = -ENOMEM; | ||
268 | goto err_free_bm; | ||
269 | } | ||
270 | |||
271 | v2m->domain->parent = parent; | ||
272 | v2m->mchip.of_node = node; | ||
273 | v2m->mchip.domain = pci_msi_create_irq_domain(node, | ||
274 | &gicv2m_msi_domain_info, | ||
275 | v2m->domain); | ||
276 | if (!v2m->mchip.domain) { | ||
277 | pr_err("Failed to create MSI domain\n"); | ||
278 | ret = -ENOMEM; | ||
279 | goto err_free_domains; | ||
280 | } | ||
281 | |||
282 | spin_lock_init(&v2m->msi_cnt_lock); | ||
283 | |||
284 | ret = of_pci_msi_chip_add(&v2m->mchip); | ||
285 | if (ret) { | ||
286 | pr_err("Failed to add msi_chip.\n"); | ||
287 | goto err_free_domains; | ||
288 | } | ||
289 | |||
290 | pr_info("Node %s: range[%#lx:%#lx], SPI[%d:%d]\n", node->name, | ||
291 | (unsigned long)v2m->res.start, (unsigned long)v2m->res.end, | ||
292 | v2m->spi_start, (v2m->spi_start + v2m->nr_spis)); | ||
293 | |||
294 | return 0; | ||
295 | |||
296 | err_free_domains: | ||
297 | if (v2m->mchip.domain) | ||
298 | irq_domain_remove(v2m->mchip.domain); | ||
299 | if (v2m->domain) | ||
300 | irq_domain_remove(v2m->domain); | ||
301 | err_free_bm: | ||
302 | kfree(v2m->bm); | ||
303 | err_iounmap: | ||
304 | iounmap(v2m->base); | ||
305 | err_free_v2m: | ||
306 | kfree(v2m); | ||
307 | return ret; | ||
308 | } | ||
309 | |||
310 | static struct of_device_id gicv2m_device_id[] = { | ||
311 | { .compatible = "arm,gic-v2m-frame", }, | ||
312 | {}, | ||
313 | }; | ||
314 | |||
315 | int __init gicv2m_of_init(struct device_node *node, struct irq_domain *parent) | ||
316 | { | ||
317 | int ret = 0; | ||
318 | struct device_node *child; | ||
319 | |||
320 | for (child = of_find_matching_node(node, gicv2m_device_id); child; | ||
321 | child = of_find_matching_node(child, gicv2m_device_id)) { | ||
322 | if (!of_find_property(child, "msi-controller", NULL)) | ||
323 | continue; | ||
324 | |||
325 | ret = gicv2m_init_one(child, parent); | ||
326 | if (ret) { | ||
327 | of_node_put(node); | ||
328 | break; | ||
329 | } | ||
330 | } | ||
331 | |||
332 | return ret; | ||
333 | } | ||
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index ab6069b09c94..5a71be79786d 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c | |||
@@ -1066,6 +1066,10 @@ gic_of_init(struct device_node *node, struct device_node *parent) | |||
1066 | irq = irq_of_parse_and_map(node, 0); | 1066 | irq = irq_of_parse_and_map(node, 0); |
1067 | gic_cascade_irq(gic_cnt, irq); | 1067 | gic_cascade_irq(gic_cnt, irq); |
1068 | } | 1068 | } |
1069 | |||
1070 | if (IS_ENABLED(CONFIG_ARM_GIC_V2M)) | ||
1071 | gicv2m_of_init(node, gic_data[gic_cnt].domain); | ||
1072 | |||
1069 | gic_cnt++; | 1073 | gic_cnt++; |
1070 | return 0; | 1074 | return 0; |
1071 | } | 1075 | } |
diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 13eed92c7d24..60b09ed58cae 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h | |||
@@ -106,6 +106,8 @@ static inline void gic_init(unsigned int nr, int start, | |||
106 | gic_init_bases(nr, start, dist, cpu, 0, NULL); | 106 | gic_init_bases(nr, start, dist, cpu, 0, NULL); |
107 | } | 107 | } |
108 | 108 | ||
109 | int gicv2m_of_init(struct device_node *node, struct irq_domain *parent); | ||
110 | |||
109 | void gic_send_sgi(unsigned int cpu_id, unsigned int irq); | 111 | void gic_send_sgi(unsigned int cpu_id, unsigned int irq); |
110 | int gic_get_cpu_id(unsigned int cpu); | 112 | int gic_get_cpu_id(unsigned int cpu); |
111 | void gic_migrate_target(unsigned int new_cpu_id); | 113 | void gic_migrate_target(unsigned int new_cpu_id); |