1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 /*
3 * Loongson 3A5000 ext interrupt controller emulation
4 *
5 * Copyright (C) 2021 Loongson Technology Corporation Limited
6 */
7
8 #include "qemu/osdep.h"
9 #include "qemu/module.h"
10 #include "qemu/log.h"
11 #include "qapi/error.h"
12 #include "hw/irq.h"
13 #include "hw/loongarch/virt.h"
14 #include "system/address-spaces.h"
15 #include "hw/intc/loongarch_extioi.h"
16 #include "trace.h"
17
extioi_get_index_from_archid(LoongArchExtIOICommonState * s,uint64_t arch_id)18 static int extioi_get_index_from_archid(LoongArchExtIOICommonState *s,
19 uint64_t arch_id)
20 {
21 int i;
22
23 for (i = 0; i < s->num_cpu; i++) {
24 if (s->cpu[i].arch_id == arch_id) {
25 break;
26 }
27 }
28
29 if ((i < s->num_cpu) && s->cpu[i].cpu) {
30 return i;
31 }
32
33 return -1;
34 }
35
extioi_update_irq(LoongArchExtIOICommonState * s,int irq,int level)36 static void extioi_update_irq(LoongArchExtIOICommonState *s, int irq, int level)
37 {
38 int ipnum, cpu, found, irq_index, irq_mask;
39
40 ipnum = s->sw_ipmap[irq / 32];
41 cpu = s->sw_coremap[irq];
42 irq_index = irq / 32;
43 irq_mask = 1 << (irq & 0x1f);
44
45 if (level) {
46 /* if not enable return false */
47 if (((s->enable[irq_index]) & irq_mask) == 0) {
48 return;
49 }
50 s->cpu[cpu].coreisr[irq_index] |= irq_mask;
51 found = find_first_bit(s->cpu[cpu].sw_isr[ipnum], EXTIOI_IRQS);
52 set_bit(irq, s->cpu[cpu].sw_isr[ipnum]);
53 if (found < EXTIOI_IRQS) {
54 /* other irq is handling, need not update parent irq level */
55 return;
56 }
57 } else {
58 s->cpu[cpu].coreisr[irq_index] &= ~irq_mask;
59 clear_bit(irq, s->cpu[cpu].sw_isr[ipnum]);
60 found = find_first_bit(s->cpu[cpu].sw_isr[ipnum], EXTIOI_IRQS);
61 if (found < EXTIOI_IRQS) {
62 /* other irq is handling, need not update parent irq level */
63 return;
64 }
65 }
66 qemu_set_irq(s->cpu[cpu].parent_irq[ipnum], level);
67 }
68
extioi_setirq(void * opaque,int irq,int level)69 static void extioi_setirq(void *opaque, int irq, int level)
70 {
71 LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(opaque);
72 trace_loongarch_extioi_setirq(irq, level);
73 if (level) {
74 set_bit32(irq, s->isr);
75 } else {
76 clear_bit32(irq, s->isr);
77 }
78 extioi_update_irq(s, irq, level);
79 }
80
extioi_readw(void * opaque,hwaddr addr,uint64_t * data,unsigned size,MemTxAttrs attrs)81 static MemTxResult extioi_readw(void *opaque, hwaddr addr, uint64_t *data,
82 unsigned size, MemTxAttrs attrs)
83 {
84 LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(opaque);
85 unsigned long offset = addr & 0xffff;
86 uint32_t index, cpu;
87
88 switch (offset) {
89 case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END - 1:
90 index = (offset - EXTIOI_NODETYPE_START) >> 2;
91 *data = s->nodetype[index];
92 break;
93 case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END - 1:
94 index = (offset - EXTIOI_IPMAP_START) >> 2;
95 *data = s->ipmap[index];
96 break;
97 case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END - 1:
98 index = (offset - EXTIOI_ENABLE_START) >> 2;
99 *data = s->enable[index];
100 break;
101 case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END - 1:
102 index = (offset - EXTIOI_BOUNCE_START) >> 2;
103 *data = s->bounce[index];
104 break;
105 case EXTIOI_COREISR_START ... EXTIOI_COREISR_END - 1:
106 index = (offset - EXTIOI_COREISR_START) >> 2;
107 /* using attrs to get current cpu index */
108 cpu = attrs.requester_id;
109 *data = s->cpu[cpu].coreisr[index];
110 break;
111 case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END - 1:
112 index = (offset - EXTIOI_COREMAP_START) >> 2;
113 *data = s->coremap[index];
114 break;
115 default:
116 break;
117 }
118
119 trace_loongarch_extioi_readw(addr, *data);
120 return MEMTX_OK;
121 }
122
extioi_enable_irq(LoongArchExtIOICommonState * s,int index,uint32_t mask,int level)123 static inline void extioi_enable_irq(LoongArchExtIOICommonState *s, int index,\
124 uint32_t mask, int level)
125 {
126 uint32_t val;
127 int irq;
128
129 val = mask & s->isr[index];
130 irq = ctz32(val);
131 while (irq != 32) {
132 /*
133 * enable bit change from 0 to 1,
134 * need to update irq by pending bits
135 */
136 extioi_update_irq(s, irq + index * 32, level);
137 val &= ~(1 << irq);
138 irq = ctz32(val);
139 }
140 }
141
extioi_update_sw_coremap(LoongArchExtIOICommonState * s,int irq,uint64_t val,bool notify)142 static inline void extioi_update_sw_coremap(LoongArchExtIOICommonState *s,
143 int irq, uint64_t val, bool notify)
144 {
145 int i, cpu, cpuid;
146
147 /*
148 * loongarch only support little endian,
149 * so we paresd the value with little endian.
150 */
151 val = cpu_to_le64(val);
152
153 for (i = 0; i < 4; i++) {
154 cpuid = val & 0xff;
155 val = val >> 8;
156
157 if (!(s->status & BIT(EXTIOI_ENABLE_CPU_ENCODE))) {
158 cpuid = ctz32(cpuid);
159 cpuid = (cpuid >= 4) ? 0 : cpuid;
160 }
161
162 cpu = extioi_get_index_from_archid(s, cpuid);
163 if (cpu < 0) {
164 continue;
165 }
166
167 if (s->sw_coremap[irq + i] == cpu) {
168 continue;
169 }
170
171 if (notify && test_bit32(irq + i, s->isr)) {
172 /*
173 * lower irq at old cpu and raise irq at new cpu
174 */
175 extioi_update_irq(s, irq + i, 0);
176 s->sw_coremap[irq + i] = cpu;
177 extioi_update_irq(s, irq + i, 1);
178 } else {
179 s->sw_coremap[irq + i] = cpu;
180 }
181 }
182 }
183
extioi_update_sw_ipmap(LoongArchExtIOICommonState * s,int index,uint64_t val)184 static inline void extioi_update_sw_ipmap(LoongArchExtIOICommonState *s,
185 int index, uint64_t val)
186 {
187 int i;
188 uint8_t ipnum;
189
190 /*
191 * loongarch only support little endian,
192 * so we paresd the value with little endian.
193 */
194 val = cpu_to_le64(val);
195 for (i = 0; i < 4; i++) {
196 ipnum = val & 0xff;
197 ipnum = ctz32(ipnum);
198 ipnum = (ipnum >= 4) ? 0 : ipnum;
199 s->sw_ipmap[index * 4 + i] = ipnum;
200 val = val >> 8;
201 }
202 }
203
extioi_writew(void * opaque,hwaddr addr,uint64_t val,unsigned size,MemTxAttrs attrs)204 static MemTxResult extioi_writew(void *opaque, hwaddr addr,
205 uint64_t val, unsigned size,
206 MemTxAttrs attrs)
207 {
208 LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(opaque);
209 int cpu, index, old_data, irq;
210 uint32_t offset;
211
212 trace_loongarch_extioi_writew(addr, val);
213 offset = addr & 0xffff;
214
215 switch (offset) {
216 case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END - 1:
217 index = (offset - EXTIOI_NODETYPE_START) >> 2;
218 s->nodetype[index] = val;
219 break;
220 case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END - 1:
221 /*
222 * ipmap cannot be set at runtime, can be set only at the beginning
223 * of intr driver, need not update upper irq level
224 */
225 index = (offset - EXTIOI_IPMAP_START) >> 2;
226 s->ipmap[index] = val;
227 extioi_update_sw_ipmap(s, index, val);
228 break;
229 case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END - 1:
230 index = (offset - EXTIOI_ENABLE_START) >> 2;
231 old_data = s->enable[index];
232 s->enable[index] = val;
233
234 /* unmask irq */
235 val = s->enable[index] & ~old_data;
236 extioi_enable_irq(s, index, val, 1);
237
238 /* mask irq */
239 val = ~s->enable[index] & old_data;
240 extioi_enable_irq(s, index, val, 0);
241 break;
242 case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END - 1:
243 /* do not emulate hw bounced irq routing */
244 index = (offset - EXTIOI_BOUNCE_START) >> 2;
245 s->bounce[index] = val;
246 break;
247 case EXTIOI_COREISR_START ... EXTIOI_COREISR_END - 1:
248 index = (offset - EXTIOI_COREISR_START) >> 2;
249 /* using attrs to get current cpu index */
250 cpu = attrs.requester_id;
251 old_data = s->cpu[cpu].coreisr[index];
252 s->cpu[cpu].coreisr[index] = old_data & ~val;
253 /* write 1 to clear interrupt */
254 old_data &= val;
255 irq = ctz32(old_data);
256 while (irq != 32) {
257 extioi_update_irq(s, irq + index * 32, 0);
258 old_data &= ~(1 << irq);
259 irq = ctz32(old_data);
260 }
261 break;
262 case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END - 1:
263 irq = offset - EXTIOI_COREMAP_START;
264 index = irq / 4;
265 s->coremap[index] = val;
266
267 extioi_update_sw_coremap(s, irq, val, true);
268 break;
269 default:
270 break;
271 }
272 return MEMTX_OK;
273 }
274
275 static const MemoryRegionOps extioi_ops = {
276 .read_with_attrs = extioi_readw,
277 .write_with_attrs = extioi_writew,
278 .impl.min_access_size = 4,
279 .impl.max_access_size = 4,
280 .valid.min_access_size = 4,
281 .valid.max_access_size = 8,
282 .endianness = DEVICE_LITTLE_ENDIAN,
283 };
284
extioi_virt_readw(void * opaque,hwaddr addr,uint64_t * data,unsigned size,MemTxAttrs attrs)285 static MemTxResult extioi_virt_readw(void *opaque, hwaddr addr, uint64_t *data,
286 unsigned size, MemTxAttrs attrs)
287 {
288 LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(opaque);
289
290 switch (addr) {
291 case EXTIOI_VIRT_FEATURES:
292 *data = s->features;
293 break;
294 case EXTIOI_VIRT_CONFIG:
295 *data = s->status;
296 break;
297 default:
298 g_assert_not_reached();
299 }
300
301 return MEMTX_OK;
302 }
303
extioi_virt_writew(void * opaque,hwaddr addr,uint64_t val,unsigned size,MemTxAttrs attrs)304 static MemTxResult extioi_virt_writew(void *opaque, hwaddr addr,
305 uint64_t val, unsigned size,
306 MemTxAttrs attrs)
307 {
308 LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(opaque);
309
310 switch (addr) {
311 case EXTIOI_VIRT_FEATURES:
312 return MEMTX_ACCESS_ERROR;
313
314 case EXTIOI_VIRT_CONFIG:
315 /*
316 * extioi features can only be set at disabled status
317 */
318 if ((s->status & BIT(EXTIOI_ENABLE)) && val) {
319 return MEMTX_ACCESS_ERROR;
320 }
321
322 s->status = val & s->features;
323 break;
324 default:
325 g_assert_not_reached();
326 }
327 return MEMTX_OK;
328 }
329
330 static const MemoryRegionOps extioi_virt_ops = {
331 .read_with_attrs = extioi_virt_readw,
332 .write_with_attrs = extioi_virt_writew,
333 .impl.min_access_size = 4,
334 .impl.max_access_size = 4,
335 .valid.min_access_size = 4,
336 .valid.max_access_size = 8,
337 .endianness = DEVICE_LITTLE_ENDIAN,
338 };
339
loongarch_extioi_realize(DeviceState * dev,Error ** errp)340 static void loongarch_extioi_realize(DeviceState *dev, Error **errp)
341 {
342 LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(dev);
343 LoongArchExtIOIClass *lec = LOONGARCH_EXTIOI_GET_CLASS(dev);
344 SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
345 Error *local_err = NULL;
346 int i;
347
348 lec->parent_realize(dev, &local_err);
349 if (local_err) {
350 error_propagate(errp, local_err);
351 return;
352 }
353
354 for (i = 0; i < EXTIOI_IRQS; i++) {
355 sysbus_init_irq(sbd, &s->irq[i]);
356 }
357
358 qdev_init_gpio_in(dev, extioi_setirq, EXTIOI_IRQS);
359 memory_region_init_io(&s->extioi_system_mem, OBJECT(s), &extioi_ops,
360 s, "extioi_system_mem", 0x900);
361 sysbus_init_mmio(sbd, &s->extioi_system_mem);
362
363 if (s->features & BIT(EXTIOI_HAS_VIRT_EXTENSION)) {
364 memory_region_init_io(&s->virt_extend, OBJECT(s), &extioi_virt_ops,
365 s, "extioi_virt", EXTIOI_VIRT_SIZE);
366 sysbus_init_mmio(sbd, &s->virt_extend);
367 s->features |= EXTIOI_VIRT_HAS_FEATURES;
368 } else {
369 s->status |= BIT(EXTIOI_ENABLE);
370 }
371 }
372
loongarch_extioi_unrealize(DeviceState * dev)373 static void loongarch_extioi_unrealize(DeviceState *dev)
374 {
375 LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(dev);
376
377 g_free(s->cpu);
378 }
379
loongarch_extioi_reset_hold(Object * obj,ResetType type)380 static void loongarch_extioi_reset_hold(Object *obj, ResetType type)
381 {
382 LoongArchExtIOIClass *lec = LOONGARCH_EXTIOI_GET_CLASS(obj);
383
384 if (lec->parent_phases.hold) {
385 lec->parent_phases.hold(obj, type);
386 }
387 }
388
vmstate_extioi_post_load(void * opaque,int version_id)389 static int vmstate_extioi_post_load(void *opaque, int version_id)
390 {
391 LoongArchExtIOICommonState *s = LOONGARCH_EXTIOI_COMMON(opaque);
392 int i, start_irq;
393
394 for (i = 0; i < (EXTIOI_IRQS / 4); i++) {
395 start_irq = i * 4;
396 extioi_update_sw_coremap(s, start_irq, s->coremap[i], false);
397 }
398
399 for (i = 0; i < (EXTIOI_IRQS_IPMAP_SIZE / 4); i++) {
400 extioi_update_sw_ipmap(s, i, s->ipmap[i]);
401 }
402
403 return 0;
404 }
405
loongarch_extioi_class_init(ObjectClass * klass,const void * data)406 static void loongarch_extioi_class_init(ObjectClass *klass, const void *data)
407 {
408 DeviceClass *dc = DEVICE_CLASS(klass);
409 LoongArchExtIOIClass *lec = LOONGARCH_EXTIOI_CLASS(klass);
410 LoongArchExtIOICommonClass *lecc = LOONGARCH_EXTIOI_COMMON_CLASS(klass);
411 ResettableClass *rc = RESETTABLE_CLASS(klass);
412
413 device_class_set_parent_realize(dc, loongarch_extioi_realize,
414 &lec->parent_realize);
415 device_class_set_parent_unrealize(dc, loongarch_extioi_unrealize,
416 &lec->parent_unrealize);
417 resettable_class_set_parent_phases(rc, NULL, loongarch_extioi_reset_hold,
418 NULL, &lec->parent_phases);
419 lecc->post_load = vmstate_extioi_post_load;
420 }
421
422 static const TypeInfo loongarch_extioi_types[] = {
423 {
424 .name = TYPE_LOONGARCH_EXTIOI,
425 .parent = TYPE_LOONGARCH_EXTIOI_COMMON,
426 .instance_size = sizeof(LoongArchExtIOIState),
427 .class_size = sizeof(LoongArchExtIOIClass),
428 .class_init = loongarch_extioi_class_init,
429 }
430 };
431
432 DEFINE_TYPES(loongarch_extioi_types)
433