1 | /* |
2 | * SiFive PLIC (Platform Level Interrupt Controller) |
3 | * |
4 | * Copyright (c) 2017 SiFive, Inc. |
5 | * |
6 | * This provides a parameterizable interrupt controller based on SiFive's PLIC. |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify it |
9 | * under the terms and conditions of the GNU General Public License, |
10 | * version 2 or later, as published by the Free Software Foundation. |
11 | * |
12 | * This program is distributed in the hope it will be useful, but WITHOUT |
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
15 | * more details. |
16 | * |
17 | * You should have received a copy of the GNU General Public License along with |
18 | * this program. If not, see <http://www.gnu.org/licenses/>. |
19 | */ |
20 | |
21 | #include "qemu/osdep.h" |
22 | #include "qemu/log.h" |
23 | #include "qemu/module.h" |
24 | #include "qemu/error-report.h" |
25 | #include "hw/sysbus.h" |
26 | #include "hw/pci/msi.h" |
27 | #include "hw/boards.h" |
28 | #include "hw/qdev-properties.h" |
29 | #include "target/riscv/cpu.h" |
30 | #include "sysemu/sysemu.h" |
31 | #include "hw/riscv/sifive_plic.h" |
32 | |
33 | #define RISCV_DEBUG_PLIC 0 |
34 | |
35 | static PLICMode char_to_mode(char c) |
36 | { |
37 | switch (c) { |
38 | case 'U': return PLICMode_U; |
39 | case 'S': return PLICMode_S; |
40 | case 'H': return PLICMode_H; |
41 | case 'M': return PLICMode_M; |
42 | default: |
43 | error_report("plic: invalid mode '%c'" , c); |
44 | exit(1); |
45 | } |
46 | } |
47 | |
48 | static char mode_to_char(PLICMode m) |
49 | { |
50 | switch (m) { |
51 | case PLICMode_U: return 'U'; |
52 | case PLICMode_S: return 'S'; |
53 | case PLICMode_H: return 'H'; |
54 | case PLICMode_M: return 'M'; |
55 | default: return '?'; |
56 | } |
57 | } |
58 | |
59 | static void sifive_plic_print_state(SiFivePLICState *plic) |
60 | { |
61 | int i; |
62 | int addrid; |
63 | |
64 | /* pending */ |
65 | qemu_log("pending : " ); |
66 | for (i = plic->bitfield_words - 1; i >= 0; i--) { |
67 | qemu_log("%08x" , plic->pending[i]); |
68 | } |
69 | qemu_log("\n" ); |
70 | |
71 | /* pending */ |
72 | qemu_log("claimed : " ); |
73 | for (i = plic->bitfield_words - 1; i >= 0; i--) { |
74 | qemu_log("%08x" , plic->claimed[i]); |
75 | } |
76 | qemu_log("\n" ); |
77 | |
78 | for (addrid = 0; addrid < plic->num_addrs; addrid++) { |
79 | qemu_log("hart%d-%c enable: " , |
80 | plic->addr_config[addrid].hartid, |
81 | mode_to_char(plic->addr_config[addrid].mode)); |
82 | for (i = plic->bitfield_words - 1; i >= 0; i--) { |
83 | qemu_log("%08x" , plic->enable[addrid * plic->bitfield_words + i]); |
84 | } |
85 | qemu_log("\n" ); |
86 | } |
87 | } |
88 | |
89 | static uint32_t atomic_set_masked(uint32_t *a, uint32_t mask, uint32_t value) |
90 | { |
91 | uint32_t old, new, cmp = atomic_read(a); |
92 | |
93 | do { |
94 | old = cmp; |
95 | new = (old & ~mask) | (value & mask); |
96 | cmp = atomic_cmpxchg(a, old, new); |
97 | } while (old != cmp); |
98 | |
99 | return old; |
100 | } |
101 | |
102 | static void sifive_plic_set_pending(SiFivePLICState *plic, int irq, bool level) |
103 | { |
104 | atomic_set_masked(&plic->pending[irq >> 5], 1 << (irq & 31), -!!level); |
105 | } |
106 | |
107 | static void sifive_plic_set_claimed(SiFivePLICState *plic, int irq, bool level) |
108 | { |
109 | atomic_set_masked(&plic->claimed[irq >> 5], 1 << (irq & 31), -!!level); |
110 | } |
111 | |
112 | static int sifive_plic_irqs_pending(SiFivePLICState *plic, uint32_t addrid) |
113 | { |
114 | int i, j; |
115 | for (i = 0; i < plic->bitfield_words; i++) { |
116 | uint32_t pending_enabled_not_claimed = |
117 | (plic->pending[i] & ~plic->claimed[i]) & |
118 | plic->enable[addrid * plic->bitfield_words + i]; |
119 | if (!pending_enabled_not_claimed) { |
120 | continue; |
121 | } |
122 | for (j = 0; j < 32; j++) { |
123 | int irq = (i << 5) + j; |
124 | uint32_t prio = plic->source_priority[irq]; |
125 | int enabled = pending_enabled_not_claimed & (1 << j); |
126 | if (enabled && prio > plic->target_priority[addrid]) { |
127 | return 1; |
128 | } |
129 | } |
130 | } |
131 | return 0; |
132 | } |
133 | |
134 | static void sifive_plic_update(SiFivePLICState *plic) |
135 | { |
136 | int addrid; |
137 | |
138 | /* raise irq on harts where this irq is enabled */ |
139 | for (addrid = 0; addrid < plic->num_addrs; addrid++) { |
140 | uint32_t hartid = plic->addr_config[addrid].hartid; |
141 | PLICMode mode = plic->addr_config[addrid].mode; |
142 | CPUState *cpu = qemu_get_cpu(hartid); |
143 | CPURISCVState *env = cpu ? cpu->env_ptr : NULL; |
144 | if (!env) { |
145 | continue; |
146 | } |
147 | int level = sifive_plic_irqs_pending(plic, addrid); |
148 | switch (mode) { |
149 | case PLICMode_M: |
150 | riscv_cpu_update_mip(RISCV_CPU(cpu), MIP_MEIP, BOOL_TO_MASK(level)); |
151 | break; |
152 | case PLICMode_S: |
153 | riscv_cpu_update_mip(RISCV_CPU(cpu), MIP_SEIP, BOOL_TO_MASK(level)); |
154 | break; |
155 | default: |
156 | break; |
157 | } |
158 | } |
159 | |
160 | if (RISCV_DEBUG_PLIC) { |
161 | sifive_plic_print_state(plic); |
162 | } |
163 | } |
164 | |
165 | void sifive_plic_raise_irq(SiFivePLICState *plic, uint32_t irq) |
166 | { |
167 | sifive_plic_set_pending(plic, irq, true); |
168 | sifive_plic_update(plic); |
169 | } |
170 | |
171 | void sifive_plic_lower_irq(SiFivePLICState *plic, uint32_t irq) |
172 | { |
173 | sifive_plic_set_pending(plic, irq, false); |
174 | sifive_plic_update(plic); |
175 | } |
176 | |
177 | static uint32_t sifive_plic_claim(SiFivePLICState *plic, uint32_t addrid) |
178 | { |
179 | int i, j; |
180 | for (i = 0; i < plic->bitfield_words; i++) { |
181 | uint32_t pending_enabled_not_claimed = |
182 | (plic->pending[i] & ~plic->claimed[i]) & |
183 | plic->enable[addrid * plic->bitfield_words + i]; |
184 | if (!pending_enabled_not_claimed) { |
185 | continue; |
186 | } |
187 | for (j = 0; j < 32; j++) { |
188 | int irq = (i << 5) + j; |
189 | uint32_t prio = plic->source_priority[irq]; |
190 | int enabled = pending_enabled_not_claimed & (1 << j); |
191 | if (enabled && prio > plic->target_priority[addrid]) { |
192 | sifive_plic_set_pending(plic, irq, false); |
193 | sifive_plic_set_claimed(plic, irq, true); |
194 | return irq; |
195 | } |
196 | } |
197 | } |
198 | return 0; |
199 | } |
200 | |
201 | static uint64_t sifive_plic_read(void *opaque, hwaddr addr, unsigned size) |
202 | { |
203 | SiFivePLICState *plic = opaque; |
204 | |
205 | /* writes must be 4 byte words */ |
206 | if ((addr & 0x3) != 0) { |
207 | goto err; |
208 | } |
209 | |
210 | if (addr >= plic->priority_base && /* 4 bytes per source */ |
211 | addr < plic->priority_base + (plic->num_sources << 2)) |
212 | { |
213 | uint32_t irq = ((addr - plic->priority_base) >> 2) + 1; |
214 | if (RISCV_DEBUG_PLIC) { |
215 | qemu_log("plic: read priority: irq=%d priority=%d\n" , |
216 | irq, plic->source_priority[irq]); |
217 | } |
218 | return plic->source_priority[irq]; |
219 | } else if (addr >= plic->pending_base && /* 1 bit per source */ |
220 | addr < plic->pending_base + (plic->num_sources >> 3)) |
221 | { |
222 | uint32_t word = (addr - plic->pending_base) >> 2; |
223 | if (RISCV_DEBUG_PLIC) { |
224 | qemu_log("plic: read pending: word=%d value=%d\n" , |
225 | word, plic->pending[word]); |
226 | } |
227 | return plic->pending[word]; |
228 | } else if (addr >= plic->enable_base && /* 1 bit per source */ |
229 | addr < plic->enable_base + plic->num_addrs * plic->enable_stride) |
230 | { |
231 | uint32_t addrid = (addr - plic->enable_base) / plic->enable_stride; |
232 | uint32_t wordid = (addr & (plic->enable_stride - 1)) >> 2; |
233 | if (wordid < plic->bitfield_words) { |
234 | if (RISCV_DEBUG_PLIC) { |
235 | qemu_log("plic: read enable: hart%d-%c word=%d value=%x\n" , |
236 | plic->addr_config[addrid].hartid, |
237 | mode_to_char(plic->addr_config[addrid].mode), wordid, |
238 | plic->enable[addrid * plic->bitfield_words + wordid]); |
239 | } |
240 | return plic->enable[addrid * plic->bitfield_words + wordid]; |
241 | } |
242 | } else if (addr >= plic->context_base && /* 1 bit per source */ |
243 | addr < plic->context_base + plic->num_addrs * plic->context_stride) |
244 | { |
245 | uint32_t addrid = (addr - plic->context_base) / plic->context_stride; |
246 | uint32_t contextid = (addr & (plic->context_stride - 1)); |
247 | if (contextid == 0) { |
248 | if (RISCV_DEBUG_PLIC) { |
249 | qemu_log("plic: read priority: hart%d-%c priority=%x\n" , |
250 | plic->addr_config[addrid].hartid, |
251 | mode_to_char(plic->addr_config[addrid].mode), |
252 | plic->target_priority[addrid]); |
253 | } |
254 | return plic->target_priority[addrid]; |
255 | } else if (contextid == 4) { |
256 | uint32_t value = sifive_plic_claim(plic, addrid); |
257 | if (RISCV_DEBUG_PLIC) { |
258 | qemu_log("plic: read claim: hart%d-%c irq=%x\n" , |
259 | plic->addr_config[addrid].hartid, |
260 | mode_to_char(plic->addr_config[addrid].mode), |
261 | value); |
262 | sifive_plic_print_state(plic); |
263 | } |
264 | return value; |
265 | } |
266 | } |
267 | |
268 | err: |
269 | qemu_log_mask(LOG_GUEST_ERROR, |
270 | "%s: Invalid register read 0x%" HWADDR_PRIx "\n" , |
271 | __func__, addr); |
272 | return 0; |
273 | } |
274 | |
275 | static void sifive_plic_write(void *opaque, hwaddr addr, uint64_t value, |
276 | unsigned size) |
277 | { |
278 | SiFivePLICState *plic = opaque; |
279 | |
280 | /* writes must be 4 byte words */ |
281 | if ((addr & 0x3) != 0) { |
282 | goto err; |
283 | } |
284 | |
285 | if (addr >= plic->priority_base && /* 4 bytes per source */ |
286 | addr < plic->priority_base + (plic->num_sources << 2)) |
287 | { |
288 | uint32_t irq = ((addr - plic->priority_base) >> 2) + 1; |
289 | plic->source_priority[irq] = value & 7; |
290 | if (RISCV_DEBUG_PLIC) { |
291 | qemu_log("plic: write priority: irq=%d priority=%d\n" , |
292 | irq, plic->source_priority[irq]); |
293 | } |
294 | return; |
295 | } else if (addr >= plic->pending_base && /* 1 bit per source */ |
296 | addr < plic->pending_base + (plic->num_sources >> 3)) |
297 | { |
298 | qemu_log_mask(LOG_GUEST_ERROR, |
299 | "%s: invalid pending write: 0x%" HWADDR_PRIx "" , |
300 | __func__, addr); |
301 | return; |
302 | } else if (addr >= plic->enable_base && /* 1 bit per source */ |
303 | addr < plic->enable_base + plic->num_addrs * plic->enable_stride) |
304 | { |
305 | uint32_t addrid = (addr - plic->enable_base) / plic->enable_stride; |
306 | uint32_t wordid = (addr & (plic->enable_stride - 1)) >> 2; |
307 | if (wordid < plic->bitfield_words) { |
308 | plic->enable[addrid * plic->bitfield_words + wordid] = value; |
309 | if (RISCV_DEBUG_PLIC) { |
310 | qemu_log("plic: write enable: hart%d-%c word=%d value=%x\n" , |
311 | plic->addr_config[addrid].hartid, |
312 | mode_to_char(plic->addr_config[addrid].mode), wordid, |
313 | plic->enable[addrid * plic->bitfield_words + wordid]); |
314 | } |
315 | return; |
316 | } |
317 | } else if (addr >= plic->context_base && /* 4 bytes per reg */ |
318 | addr < plic->context_base + plic->num_addrs * plic->context_stride) |
319 | { |
320 | uint32_t addrid = (addr - plic->context_base) / plic->context_stride; |
321 | uint32_t contextid = (addr & (plic->context_stride - 1)); |
322 | if (contextid == 0) { |
323 | if (RISCV_DEBUG_PLIC) { |
324 | qemu_log("plic: write priority: hart%d-%c priority=%x\n" , |
325 | plic->addr_config[addrid].hartid, |
326 | mode_to_char(plic->addr_config[addrid].mode), |
327 | plic->target_priority[addrid]); |
328 | } |
329 | if (value <= plic->num_priorities) { |
330 | plic->target_priority[addrid] = value; |
331 | sifive_plic_update(plic); |
332 | } |
333 | return; |
334 | } else if (contextid == 4) { |
335 | if (RISCV_DEBUG_PLIC) { |
336 | qemu_log("plic: write claim: hart%d-%c irq=%x\n" , |
337 | plic->addr_config[addrid].hartid, |
338 | mode_to_char(plic->addr_config[addrid].mode), |
339 | (uint32_t)value); |
340 | } |
341 | if (value < plic->num_sources) { |
342 | sifive_plic_set_claimed(plic, value, false); |
343 | sifive_plic_update(plic); |
344 | } |
345 | return; |
346 | } |
347 | } |
348 | |
349 | err: |
350 | qemu_log_mask(LOG_GUEST_ERROR, |
351 | "%s: Invalid register write 0x%" HWADDR_PRIx "\n" , |
352 | __func__, addr); |
353 | } |
354 | |
355 | static const MemoryRegionOps sifive_plic_ops = { |
356 | .read = sifive_plic_read, |
357 | .write = sifive_plic_write, |
358 | .endianness = DEVICE_LITTLE_ENDIAN, |
359 | .valid = { |
360 | .min_access_size = 4, |
361 | .max_access_size = 4 |
362 | } |
363 | }; |
364 | |
365 | static Property sifive_plic_properties[] = { |
366 | DEFINE_PROP_STRING("hart-config" , SiFivePLICState, hart_config), |
367 | DEFINE_PROP_UINT32("num-sources" , SiFivePLICState, num_sources, 0), |
368 | DEFINE_PROP_UINT32("num-priorities" , SiFivePLICState, num_priorities, 0), |
369 | DEFINE_PROP_UINT32("priority-base" , SiFivePLICState, priority_base, 0), |
370 | DEFINE_PROP_UINT32("pending-base" , SiFivePLICState, pending_base, 0), |
371 | DEFINE_PROP_UINT32("enable-base" , SiFivePLICState, enable_base, 0), |
372 | DEFINE_PROP_UINT32("enable-stride" , SiFivePLICState, enable_stride, 0), |
373 | DEFINE_PROP_UINT32("context-base" , SiFivePLICState, context_base, 0), |
374 | DEFINE_PROP_UINT32("context-stride" , SiFivePLICState, context_stride, 0), |
375 | DEFINE_PROP_UINT32("aperture-size" , SiFivePLICState, aperture_size, 0), |
376 | DEFINE_PROP_END_OF_LIST(), |
377 | }; |
378 | |
379 | /* |
380 | * parse PLIC hart/mode address offset config |
381 | * |
382 | * "M" 1 hart with M mode |
383 | * "MS,MS" 2 harts, 0-1 with M and S mode |
384 | * "M,MS,MS,MS,MS" 5 harts, 0 with M mode, 1-5 with M and S mode |
385 | */ |
386 | static void parse_hart_config(SiFivePLICState *plic) |
387 | { |
388 | int addrid, hartid, modes; |
389 | const char *p; |
390 | char c; |
391 | |
392 | /* count and validate hart/mode combinations */ |
393 | addrid = 0, hartid = 0, modes = 0; |
394 | p = plic->hart_config; |
395 | while ((c = *p++)) { |
396 | if (c == ',') { |
397 | addrid += ctpop8(modes); |
398 | modes = 0; |
399 | hartid++; |
400 | } else { |
401 | int m = 1 << char_to_mode(c); |
402 | if (modes == (modes | m)) { |
403 | error_report("plic: duplicate mode '%c' in config: %s" , |
404 | c, plic->hart_config); |
405 | exit(1); |
406 | } |
407 | modes |= m; |
408 | } |
409 | } |
410 | if (modes) { |
411 | addrid += ctpop8(modes); |
412 | } |
413 | hartid++; |
414 | |
415 | /* store hart/mode combinations */ |
416 | plic->num_addrs = addrid; |
417 | plic->addr_config = g_new(PLICAddr, plic->num_addrs); |
418 | addrid = 0, hartid = 0; |
419 | p = plic->hart_config; |
420 | while ((c = *p++)) { |
421 | if (c == ',') { |
422 | hartid++; |
423 | } else { |
424 | plic->addr_config[addrid].addrid = addrid; |
425 | plic->addr_config[addrid].hartid = hartid; |
426 | plic->addr_config[addrid].mode = char_to_mode(c); |
427 | addrid++; |
428 | } |
429 | } |
430 | } |
431 | |
432 | static void sifive_plic_irq_request(void *opaque, int irq, int level) |
433 | { |
434 | SiFivePLICState *plic = opaque; |
435 | if (RISCV_DEBUG_PLIC) { |
436 | qemu_log("sifive_plic_irq_request: irq=%d level=%d\n" , irq, level); |
437 | } |
438 | sifive_plic_set_pending(plic, irq, level > 0); |
439 | sifive_plic_update(plic); |
440 | } |
441 | |
442 | static void sifive_plic_realize(DeviceState *dev, Error **errp) |
443 | { |
444 | MachineState *ms = MACHINE(qdev_get_machine()); |
445 | unsigned int smp_cpus = ms->smp.cpus; |
446 | SiFivePLICState *plic = SIFIVE_PLIC(dev); |
447 | int i; |
448 | |
449 | memory_region_init_io(&plic->mmio, OBJECT(dev), &sifive_plic_ops, plic, |
450 | TYPE_SIFIVE_PLIC, plic->aperture_size); |
451 | parse_hart_config(plic); |
452 | plic->bitfield_words = (plic->num_sources + 31) >> 5; |
453 | plic->source_priority = g_new0(uint32_t, plic->num_sources); |
454 | plic->target_priority = g_new(uint32_t, plic->num_addrs); |
455 | plic->pending = g_new0(uint32_t, plic->bitfield_words); |
456 | plic->claimed = g_new0(uint32_t, plic->bitfield_words); |
457 | plic->enable = g_new0(uint32_t, plic->bitfield_words * plic->num_addrs); |
458 | sysbus_init_mmio(SYS_BUS_DEVICE(dev), &plic->mmio); |
459 | qdev_init_gpio_in(dev, sifive_plic_irq_request, plic->num_sources); |
460 | |
461 | /* We can't allow the supervisor to control SEIP as this would allow the |
462 | * supervisor to clear a pending external interrupt which will result in |
463 | * lost a interrupt in the case a PLIC is attached. The SEIP bit must be |
464 | * hardware controlled when a PLIC is attached. |
465 | */ |
466 | for (i = 0; i < smp_cpus; i++) { |
467 | RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i)); |
468 | if (riscv_cpu_claim_interrupts(cpu, MIP_SEIP) < 0) { |
469 | error_report("SEIP already claimed" ); |
470 | exit(1); |
471 | } |
472 | } |
473 | |
474 | msi_nonbroken = true; |
475 | } |
476 | |
477 | static void sifive_plic_class_init(ObjectClass *klass, void *data) |
478 | { |
479 | DeviceClass *dc = DEVICE_CLASS(klass); |
480 | |
481 | dc->props = sifive_plic_properties; |
482 | dc->realize = sifive_plic_realize; |
483 | } |
484 | |
485 | static const TypeInfo sifive_plic_info = { |
486 | .name = TYPE_SIFIVE_PLIC, |
487 | .parent = TYPE_SYS_BUS_DEVICE, |
488 | .instance_size = sizeof(SiFivePLICState), |
489 | .class_init = sifive_plic_class_init, |
490 | }; |
491 | |
492 | static void sifive_plic_register_types(void) |
493 | { |
494 | type_register_static(&sifive_plic_info); |
495 | } |
496 | |
497 | type_init(sifive_plic_register_types) |
498 | |
499 | /* |
500 | * Create PLIC device. |
501 | */ |
502 | DeviceState *sifive_plic_create(hwaddr addr, char *hart_config, |
503 | uint32_t num_sources, uint32_t num_priorities, |
504 | uint32_t priority_base, uint32_t pending_base, |
505 | uint32_t enable_base, uint32_t enable_stride, |
506 | uint32_t context_base, uint32_t context_stride, |
507 | uint32_t aperture_size) |
508 | { |
509 | DeviceState *dev = qdev_create(NULL, TYPE_SIFIVE_PLIC); |
510 | assert(enable_stride == (enable_stride & -enable_stride)); |
511 | assert(context_stride == (context_stride & -context_stride)); |
512 | qdev_prop_set_string(dev, "hart-config" , hart_config); |
513 | qdev_prop_set_uint32(dev, "num-sources" , num_sources); |
514 | qdev_prop_set_uint32(dev, "num-priorities" , num_priorities); |
515 | qdev_prop_set_uint32(dev, "priority-base" , priority_base); |
516 | qdev_prop_set_uint32(dev, "pending-base" , pending_base); |
517 | qdev_prop_set_uint32(dev, "enable-base" , enable_base); |
518 | qdev_prop_set_uint32(dev, "enable-stride" , enable_stride); |
519 | qdev_prop_set_uint32(dev, "context-base" , context_base); |
520 | qdev_prop_set_uint32(dev, "context-stride" , context_stride); |
521 | qdev_prop_set_uint32(dev, "aperture-size" , aperture_size); |
522 | qdev_init_nofail(dev); |
523 | sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); |
524 | return dev; |
525 | } |
526 | |