]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
0b05ac6e BH |
2 | /* |
3 | * Copyright 2011 IBM Corporation. | |
0b05ac6e | 4 | */ |
a1d0d98d | 5 | |
0b05ac6e BH |
6 | #include <linux/types.h> |
7 | #include <linux/kernel.h> | |
8 | #include <linux/irq.h> | |
9 | #include <linux/smp.h> | |
10 | #include <linux/interrupt.h> | |
11 | #include <linux/init.h> | |
12 | #include <linux/cpu.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/spinlock.h> | |
371fefd6 | 15 | #include <linux/module.h> |
0b05ac6e BH |
16 | |
17 | #include <asm/prom.h> | |
18 | #include <asm/io.h> | |
19 | #include <asm/smp.h> | |
20 | #include <asm/irq.h> | |
21 | #include <asm/errno.h> | |
22 | #include <asm/xics.h> | |
371fefd6 | 23 | #include <asm/kvm_ppc.h> |
d4e58e59 | 24 | #include <asm/dbell.h> |
0b05ac6e BH |
25 | |
26 | struct icp_ipl { | |
27 | union { | |
28 | u32 word; | |
29 | u8 bytes[4]; | |
30 | } xirr_poll; | |
31 | union { | |
32 | u32 word; | |
33 | u8 bytes[4]; | |
34 | } xirr; | |
35 | u32 dummy; | |
36 | union { | |
37 | u32 word; | |
38 | u8 bytes[4]; | |
39 | } qirr; | |
40 | u32 link_a; | |
41 | u32 link_b; | |
42 | u32 link_c; | |
43 | }; | |
44 | ||
45 | static struct icp_ipl __iomem *icp_native_regs[NR_CPUS]; | |
46 | ||
47 | static inline unsigned int icp_native_get_xirr(void) | |
48 | { | |
49 | int cpu = smp_processor_id(); | |
54695c30 BH |
50 | unsigned int xirr; |
51 | ||
52 | /* Handled an interrupt latched by KVM */ | |
53 | xirr = kvmppc_get_xics_latch(); | |
54 | if (xirr) | |
55 | return xirr; | |
0b05ac6e BH |
56 | |
57 | return in_be32(&icp_native_regs[cpu]->xirr.word); | |
58 | } | |
59 | ||
60 | static inline void icp_native_set_xirr(unsigned int value) | |
61 | { | |
62 | int cpu = smp_processor_id(); | |
63 | ||
64 | out_be32(&icp_native_regs[cpu]->xirr.word, value); | |
65 | } | |
66 | ||
67 | static inline void icp_native_set_cppr(u8 value) | |
68 | { | |
69 | int cpu = smp_processor_id(); | |
70 | ||
71 | out_8(&icp_native_regs[cpu]->xirr.bytes[0], value); | |
72 | } | |
73 | ||
74 | static inline void icp_native_set_qirr(int n_cpu, u8 value) | |
75 | { | |
76 | out_8(&icp_native_regs[n_cpu]->qirr.bytes[0], value); | |
77 | } | |
78 | ||
79 | static void icp_native_set_cpu_priority(unsigned char cppr) | |
80 | { | |
81 | xics_set_base_cppr(cppr); | |
82 | icp_native_set_cppr(cppr); | |
83 | iosync(); | |
84 | } | |
85 | ||
137436c9 | 86 | void icp_native_eoi(struct irq_data *d) |
0b05ac6e | 87 | { |
476eb491 | 88 | unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d); |
0b05ac6e BH |
89 | |
90 | iosync(); | |
91 | icp_native_set_xirr((xics_pop_cppr() << 24) | hw_irq); | |
92 | } | |
93 | ||
94 | static void icp_native_teardown_cpu(void) | |
95 | { | |
96 | int cpu = smp_processor_id(); | |
97 | ||
98 | /* Clear any pending IPI */ | |
99 | icp_native_set_qirr(cpu, 0xff); | |
100 | } | |
101 | ||
102 | static void icp_native_flush_ipi(void) | |
103 | { | |
104 | /* We take the ipi irq but and never return so we | |
105 | * need to EOI the IPI, but want to leave our priority 0 | |
106 | * | |
107 | * should we check all the other interrupts too? | |
108 | * should we be flagging idle loop instead? | |
109 | * or creating some task to be scheduled? | |
110 | */ | |
111 | ||
112 | icp_native_set_xirr((0x00 << 24) | XICS_IPI); | |
113 | } | |
114 | ||
115 | static unsigned int icp_native_get_irq(void) | |
116 | { | |
117 | unsigned int xirr = icp_native_get_xirr(); | |
118 | unsigned int vec = xirr & 0x00ffffff; | |
119 | unsigned int irq; | |
120 | ||
121 | if (vec == XICS_IRQ_SPURIOUS) | |
ef24ba70 | 122 | return 0; |
0b05ac6e | 123 | |
d6b0d1f7 | 124 | irq = irq_find_mapping(xics_host, vec); |
ef24ba70 | 125 | if (likely(irq)) { |
0b05ac6e BH |
126 | xics_push_cppr(vec); |
127 | return irq; | |
128 | } | |
129 | ||
130 | /* We don't have a linux mapping, so have rtas mask it. */ | |
131 | xics_mask_unknown_vec(vec); | |
132 | ||
133 | /* We might learn about it later, so EOI it */ | |
134 | icp_native_set_xirr(xirr); | |
135 | ||
ef24ba70 | 136 | return 0; |
0b05ac6e BH |
137 | } |
138 | ||
139 | #ifdef CONFIG_SMP | |
140 | ||
b866cc21 | 141 | static void icp_native_cause_ipi(int cpu) |
0b05ac6e | 142 | { |
54695c30 | 143 | kvmppc_set_host_ipi(cpu, 1); |
3609d819 | 144 | icp_native_set_qirr(cpu, IPI_PRIORITY); |
0b05ac6e BH |
145 | } |
146 | ||
ec13e9b6 SW |
147 | #ifdef CONFIG_KVM_BOOK3S_HV_POSSIBLE |
148 | void icp_native_cause_ipi_rm(int cpu) | |
149 | { | |
150 | /* | |
151 | * Currently not used to send IPIs to another CPU | |
152 | * on the same core. Only caller is KVM real mode. | |
153 | * Need the physical address of the XICS to be | |
154 | * previously saved in kvm_hstate in the paca. | |
155 | */ | |
d381d7ca | 156 | void __iomem *xics_phys; |
ec13e9b6 SW |
157 | |
158 | /* | |
159 | * Just like the cause_ipi functions, it is required to | |
d381d7ca | 160 | * include a full barrier before causing the IPI. |
ec13e9b6 | 161 | */ |
d2e60075 | 162 | xics_phys = paca_ptrs[cpu]->kvm_hstate.xics_phys; |
d381d7ca BH |
163 | mb(); |
164 | __raw_rm_writeb(IPI_PRIORITY, xics_phys + XICS_MFRR); | |
ec13e9b6 SW |
165 | } |
166 | #endif | |
167 | ||
d6a4f709 PM |
168 | /* |
169 | * Called when an interrupt is received on an off-line CPU to | |
170 | * clear the interrupt, so that the CPU can go back to nap mode. | |
171 | */ | |
172 | void icp_native_flush_interrupt(void) | |
173 | { | |
174 | unsigned int xirr = icp_native_get_xirr(); | |
175 | unsigned int vec = xirr & 0x00ffffff; | |
176 | ||
177 | if (vec == XICS_IRQ_SPURIOUS) | |
178 | return; | |
179 | if (vec == XICS_IPI) { | |
180 | /* Clear pending IPI */ | |
181 | int cpu = smp_processor_id(); | |
182 | kvmppc_set_host_ipi(cpu, 0); | |
183 | icp_native_set_qirr(cpu, 0xff); | |
184 | } else { | |
185 | pr_err("XICS: hw interrupt 0x%x to offline cpu, disabling\n", | |
186 | vec); | |
187 | xics_mask_unknown_vec(vec); | |
188 | } | |
189 | /* EOI the interrupt */ | |
190 | icp_native_set_xirr(xirr); | |
191 | } | |
192 | ||
371fefd6 PM |
193 | void xics_wake_cpu(int cpu) |
194 | { | |
195 | icp_native_set_qirr(cpu, IPI_PRIORITY); | |
196 | } | |
197 | EXPORT_SYMBOL_GPL(xics_wake_cpu); | |
198 | ||
0b05ac6e BH |
199 | static irqreturn_t icp_native_ipi_action(int irq, void *dev_id) |
200 | { | |
201 | int cpu = smp_processor_id(); | |
202 | ||
54695c30 | 203 | kvmppc_set_host_ipi(cpu, 0); |
0b05ac6e BH |
204 | icp_native_set_qirr(cpu, 0xff); |
205 | ||
23d72bfd | 206 | return smp_ipi_demux(); |
0b05ac6e BH |
207 | } |
208 | ||
209 | #endif /* CONFIG_SMP */ | |
210 | ||
211 | static int __init icp_native_map_one_cpu(int hw_id, unsigned long addr, | |
212 | unsigned long size) | |
213 | { | |
214 | char *rname; | |
215 | int i, cpu = -1; | |
216 | ||
217 | /* This may look gross but it's good enough for now, we don't quite | |
218 | * have a hard -> linux processor id matching. | |
219 | */ | |
220 | for_each_possible_cpu(i) { | |
221 | if (!cpu_present(i)) | |
222 | continue; | |
223 | if (hw_id == get_hard_smp_processor_id(i)) { | |
224 | cpu = i; | |
225 | break; | |
226 | } | |
227 | } | |
228 | ||
229 | /* Fail, skip that CPU. Don't print, it's normal, some XICS come up | |
230 | * with way more entries in there than you have CPUs | |
231 | */ | |
232 | if (cpu == -1) | |
233 | return 0; | |
234 | ||
235 | rname = kasprintf(GFP_KERNEL, "CPU %d [0x%x] Interrupt Presentation", | |
236 | cpu, hw_id); | |
237 | ||
238 | if (!request_mem_region(addr, size, rname)) { | |
f2c2cbcc JP |
239 | pr_warn("icp_native: Could not reserve ICP MMIO for CPU %d, interrupt server #0x%x\n", |
240 | cpu, hw_id); | |
0b05ac6e BH |
241 | return -EBUSY; |
242 | } | |
243 | ||
244 | icp_native_regs[cpu] = ioremap(addr, size); | |
371fefd6 | 245 | kvmppc_set_xics_phys(cpu, addr); |
0b05ac6e | 246 | if (!icp_native_regs[cpu]) { |
f2c2cbcc JP |
247 | pr_warn("icp_native: Failed ioremap for CPU %d, interrupt server #0x%x, addr %#lx\n", |
248 | cpu, hw_id, addr); | |
0b05ac6e BH |
249 | release_mem_region(addr, size); |
250 | return -ENOMEM; | |
251 | } | |
252 | return 0; | |
253 | } | |
254 | ||
255 | static int __init icp_native_init_one_node(struct device_node *np, | |
256 | unsigned int *indx) | |
257 | { | |
258 | unsigned int ilen; | |
6f7aba7b | 259 | const __be32 *ireg; |
0b05ac6e BH |
260 | int i; |
261 | int reg_tuple_size; | |
262 | int num_servers = 0; | |
263 | ||
264 | /* This code does the theorically broken assumption that the interrupt | |
265 | * server numbers are the same as the hard CPU numbers. | |
266 | * This happens to be the case so far but we are playing with fire... | |
267 | * should be fixed one of these days. -BenH. | |
268 | */ | |
269 | ireg = of_get_property(np, "ibm,interrupt-server-ranges", &ilen); | |
270 | ||
271 | /* Do that ever happen ? we'll know soon enough... but even good'old | |
272 | * f80 does have that property .. | |
273 | */ | |
274 | WARN_ON((ireg == NULL) || (ilen != 2*sizeof(u32))); | |
275 | ||
276 | if (ireg) { | |
277 | *indx = of_read_number(ireg, 1); | |
278 | if (ilen >= 2*sizeof(u32)) | |
279 | num_servers = of_read_number(ireg + 1, 1); | |
280 | } | |
281 | ||
282 | ireg = of_get_property(np, "reg", &ilen); | |
283 | if (!ireg) { | |
284 | pr_err("icp_native: Can't find interrupt reg property"); | |
285 | return -1; | |
286 | } | |
287 | ||
288 | reg_tuple_size = (of_n_addr_cells(np) + of_n_size_cells(np)) * 4; | |
289 | if (((ilen % reg_tuple_size) != 0) | |
290 | || (num_servers && (num_servers != (ilen / reg_tuple_size)))) { | |
291 | pr_err("icp_native: ICP reg len (%d) != num servers (%d)", | |
292 | ilen / reg_tuple_size, num_servers); | |
293 | return -1; | |
294 | } | |
295 | ||
296 | for (i = 0; i < (ilen / reg_tuple_size); i++) { | |
297 | struct resource r; | |
298 | int err; | |
299 | ||
300 | err = of_address_to_resource(np, i, &r); | |
301 | if (err) { | |
302 | pr_err("icp_native: Could not translate ICP MMIO" | |
303 | " for interrupt server 0x%x (%d)\n", *indx, err); | |
304 | return -1; | |
305 | } | |
306 | ||
28f65c11 | 307 | if (icp_native_map_one_cpu(*indx, r.start, resource_size(&r))) |
0b05ac6e BH |
308 | return -1; |
309 | ||
310 | (*indx)++; | |
311 | } | |
312 | return 0; | |
313 | } | |
314 | ||
315 | static const struct icp_ops icp_native_ops = { | |
316 | .get_irq = icp_native_get_irq, | |
317 | .eoi = icp_native_eoi, | |
318 | .set_priority = icp_native_set_cpu_priority, | |
319 | .teardown_cpu = icp_native_teardown_cpu, | |
320 | .flush_ipi = icp_native_flush_ipi, | |
321 | #ifdef CONFIG_SMP | |
322 | .ipi_action = icp_native_ipi_action, | |
23d72bfd | 323 | .cause_ipi = icp_native_cause_ipi, |
0b05ac6e BH |
324 | #endif |
325 | }; | |
326 | ||
cf01a404 | 327 | int __init icp_native_init(void) |
0b05ac6e BH |
328 | { |
329 | struct device_node *np; | |
330 | u32 indx = 0; | |
331 | int found = 0; | |
332 | ||
333 | for_each_compatible_node(np, NULL, "ibm,ppc-xicp") | |
334 | if (icp_native_init_one_node(np, &indx) == 0) | |
335 | found = 1; | |
336 | if (!found) { | |
337 | for_each_node_by_type(np, | |
338 | "PowerPC-External-Interrupt-Presentation") { | |
339 | if (icp_native_init_one_node(np, &indx) == 0) | |
340 | found = 1; | |
341 | } | |
342 | } | |
343 | ||
344 | if (found == 0) | |
345 | return -ENODEV; | |
346 | ||
347 | icp_ops = &icp_native_ops; | |
348 | ||
349 | return 0; | |
350 | } |