xref: /openbmc/qemu/hw/intc/loongarch_extioi.c (revision b14df228)
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 "hw/irq.h"
12 #include "hw/sysbus.h"
13 #include "hw/loongarch/virt.h"
14 #include "hw/qdev-properties.h"
15 #include "exec/address-spaces.h"
16 #include "hw/intc/loongarch_extioi.h"
17 #include "migration/vmstate.h"
18 #include "trace.h"
19 
20 
21 static void extioi_update_irq(LoongArchExtIOI *s, int irq, int level)
22 {
23     int ipnum, cpu, found, irq_index, irq_mask;
24 
25     ipnum = s->sw_ipmap[irq / 32];
26     cpu = s->sw_coremap[irq];
27     irq_index = irq / 32;
28     irq_mask = 1 << (irq & 0x1f);
29 
30     if (level) {
31         /* if not enable return false */
32         if (((s->enable[irq_index]) & irq_mask) == 0) {
33             return;
34         }
35         s->coreisr[cpu][irq_index] |= irq_mask;
36         found = find_first_bit(s->sw_isr[cpu][ipnum], EXTIOI_IRQS);
37         set_bit(irq, s->sw_isr[cpu][ipnum]);
38         if (found < EXTIOI_IRQS) {
39             /* other irq is handling, need not update parent irq level */
40             return;
41         }
42     } else {
43         s->coreisr[cpu][irq_index] &= ~irq_mask;
44         clear_bit(irq, s->sw_isr[cpu][ipnum]);
45         found = find_first_bit(s->sw_isr[cpu][ipnum], EXTIOI_IRQS);
46         if (found < EXTIOI_IRQS) {
47             /* other irq is handling, need not update parent irq level */
48             return;
49         }
50     }
51     qemu_set_irq(s->parent_irq[cpu][ipnum], level);
52 }
53 
54 static void extioi_setirq(void *opaque, int irq, int level)
55 {
56     LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
57     trace_loongarch_extioi_setirq(irq, level);
58     if (level) {
59         /*
60          * s->isr should be used in vmstate structure,
61          * but it not support 'unsigned long',
62          * so we have to switch it.
63          */
64         set_bit(irq, (unsigned long *)s->isr);
65     } else {
66         clear_bit(irq, (unsigned long *)s->isr);
67     }
68     extioi_update_irq(s, irq, level);
69 }
70 
71 static uint64_t extioi_readw(void *opaque, hwaddr addr, unsigned size)
72 {
73     LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
74     unsigned long offset = addr & 0xffff;
75     uint32_t index, cpu, ret = 0;
76 
77     switch (offset) {
78     case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END - 1:
79         index = (offset - EXTIOI_NODETYPE_START) >> 2;
80         ret = s->nodetype[index];
81         break;
82     case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END - 1:
83         index = (offset - EXTIOI_IPMAP_START) >> 2;
84         ret = s->ipmap[index];
85         break;
86     case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END - 1:
87         index = (offset - EXTIOI_ENABLE_START) >> 2;
88         ret = s->enable[index];
89         break;
90     case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END - 1:
91         index = (offset - EXTIOI_BOUNCE_START) >> 2;
92         ret = s->bounce[index];
93         break;
94     case EXTIOI_COREISR_START ... EXTIOI_COREISR_END - 1:
95         index = ((offset - EXTIOI_COREISR_START) & 0x1f) >> 2;
96         cpu = ((offset - EXTIOI_COREISR_START) >> 8) & 0x3;
97         ret = s->coreisr[cpu][index];
98         break;
99     case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END - 1:
100         index = (offset - EXTIOI_COREMAP_START) >> 2;
101         ret = s->coremap[index];
102         break;
103     default:
104         break;
105     }
106 
107     trace_loongarch_extioi_readw(addr, ret);
108     return ret;
109 }
110 
111 static inline void extioi_enable_irq(LoongArchExtIOI *s, int index,\
112                                      uint32_t mask, int level)
113 {
114     uint32_t val;
115     int irq;
116 
117     val = mask & s->isr[index];
118     irq = ctz32(val);
119     while (irq != 32) {
120         /*
121          * enable bit change from 0 to 1,
122          * need to update irq by pending bits
123          */
124         extioi_update_irq(s, irq + index * 32, level);
125         val &= ~(1 << irq);
126         irq = ctz32(val);
127     }
128 }
129 
130 static void extioi_writew(void *opaque, hwaddr addr,
131                           uint64_t val, unsigned size)
132 {
133     LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
134     int i, cpu, index, old_data, irq;
135     uint32_t offset;
136 
137     trace_loongarch_extioi_writew(addr, val);
138     offset = addr & 0xffff;
139 
140     switch (offset) {
141     case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END - 1:
142         index = (offset - EXTIOI_NODETYPE_START) >> 2;
143         s->nodetype[index] = val;
144         break;
145     case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END - 1:
146         /*
147          * ipmap cannot be set at runtime, can be set only at the beginning
148          * of intr driver, need not update upper irq level
149          */
150         index = (offset - EXTIOI_IPMAP_START) >> 2;
151         s->ipmap[index] = val;
152         /*
153          * loongarch only support little endian,
154          * so we paresd the value with little endian.
155          */
156         val = cpu_to_le64(val);
157         for (i = 0; i < 4; i++) {
158             uint8_t ipnum;
159             ipnum = val & 0xff;
160             ipnum = ctz32(ipnum);
161             ipnum = (ipnum >= 4) ? 0 : ipnum;
162             s->sw_ipmap[index * 4 + i] = ipnum;
163             val = val >> 8;
164         }
165 
166         break;
167     case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END - 1:
168         index = (offset - EXTIOI_ENABLE_START) >> 2;
169         old_data = s->enable[index];
170         s->enable[index] = val;
171 
172         /* unmask irq */
173         val = s->enable[index] & ~old_data;
174         extioi_enable_irq(s, index, val, 1);
175 
176         /* mask irq */
177         val = ~s->enable[index] & old_data;
178         extioi_enable_irq(s, index, val, 0);
179         break;
180     case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END - 1:
181         /* do not emulate hw bounced irq routing */
182         index = (offset - EXTIOI_BOUNCE_START) >> 2;
183         s->bounce[index] = val;
184         break;
185     case EXTIOI_COREISR_START ... EXTIOI_COREISR_END - 1:
186         index = ((offset - EXTIOI_COREISR_START) & 0x1f) >> 2;
187         cpu = ((offset - EXTIOI_COREISR_START) >> 8) & 0x3;
188         old_data = s->coreisr[cpu][index];
189         s->coreisr[cpu][index] = old_data & ~val;
190         /* write 1 to clear interrrupt */
191         old_data &= val;
192         irq = ctz32(old_data);
193         while (irq != 32) {
194             extioi_update_irq(s, irq + index * 32, 0);
195             old_data &= ~(1 << irq);
196             irq = ctz32(old_data);
197         }
198         break;
199     case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END - 1:
200         irq = offset - EXTIOI_COREMAP_START;
201         index = irq / 4;
202         s->coremap[index] = val;
203         /*
204          * loongarch only support little endian,
205          * so we paresd the value with little endian.
206          */
207         val = cpu_to_le64(val);
208 
209         for (i = 0; i < 4; i++) {
210             cpu = val & 0xff;
211             cpu = ctz32(cpu);
212             cpu = (cpu >= 4) ? 0 : cpu;
213             val = val >> 8;
214 
215             if (s->sw_coremap[irq + i] == cpu) {
216                 continue;
217             }
218 
219             if (test_bit(irq, (unsigned long *)s->isr)) {
220                 /*
221                  * lower irq at old cpu and raise irq at new cpu
222                  */
223                 extioi_update_irq(s, irq + i, 0);
224                 s->sw_coremap[irq + i] = cpu;
225                 extioi_update_irq(s, irq + i, 1);
226             } else {
227                 s->sw_coremap[irq + i] = cpu;
228             }
229         }
230         break;
231     default:
232         break;
233     }
234 }
235 
236 static const MemoryRegionOps extioi_ops = {
237     .read = extioi_readw,
238     .write = extioi_writew,
239     .impl.min_access_size = 4,
240     .impl.max_access_size = 4,
241     .valid.min_access_size = 4,
242     .valid.max_access_size = 8,
243     .endianness = DEVICE_LITTLE_ENDIAN,
244 };
245 
246 static const VMStateDescription vmstate_loongarch_extioi = {
247     .name = TYPE_LOONGARCH_EXTIOI,
248     .version_id = 1,
249     .minimum_version_id = 1,
250     .fields = (VMStateField[]) {
251         VMSTATE_UINT32_ARRAY(bounce, LoongArchExtIOI, EXTIOI_IRQS_GROUP_COUNT),
252         VMSTATE_UINT32_2DARRAY(coreisr, LoongArchExtIOI, LOONGARCH_MAX_VCPUS,
253                                EXTIOI_IRQS_GROUP_COUNT),
254         VMSTATE_UINT32_ARRAY(nodetype, LoongArchExtIOI,
255                              EXTIOI_IRQS_NODETYPE_COUNT / 2),
256         VMSTATE_UINT32_ARRAY(enable, LoongArchExtIOI, EXTIOI_IRQS / 32),
257         VMSTATE_UINT32_ARRAY(isr, LoongArchExtIOI, EXTIOI_IRQS / 32),
258         VMSTATE_UINT32_ARRAY(ipmap, LoongArchExtIOI, EXTIOI_IRQS_IPMAP_SIZE / 4),
259         VMSTATE_UINT32_ARRAY(coremap, LoongArchExtIOI, EXTIOI_IRQS / 4),
260         VMSTATE_UINT8_ARRAY(sw_ipmap, LoongArchExtIOI, EXTIOI_IRQS_IPMAP_SIZE),
261         VMSTATE_UINT8_ARRAY(sw_coremap, LoongArchExtIOI, EXTIOI_IRQS),
262 
263         VMSTATE_END_OF_LIST()
264     }
265 };
266 
267 static void loongarch_extioi_instance_init(Object *obj)
268 {
269     SysBusDevice *dev = SYS_BUS_DEVICE(obj);
270     LoongArchExtIOI *s = LOONGARCH_EXTIOI(obj);
271     int i, cpu, pin;
272 
273     for (i = 0; i < EXTIOI_IRQS; i++) {
274         sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq[i]);
275     }
276 
277     qdev_init_gpio_in(DEVICE(obj), extioi_setirq, EXTIOI_IRQS);
278 
279     for (cpu = 0; cpu < LOONGARCH_MAX_VCPUS; cpu++) {
280         memory_region_init_io(&s->extioi_iocsr_mem[cpu], OBJECT(s), &extioi_ops,
281                               s, "extioi_iocsr", 0x900);
282         sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->extioi_iocsr_mem[cpu]);
283         for (pin = 0; pin < LS3A_INTC_IP; pin++) {
284             qdev_init_gpio_out(DEVICE(obj), &s->parent_irq[cpu][pin], 1);
285         }
286     }
287     memory_region_init_io(&s->extioi_system_mem, OBJECT(s), &extioi_ops,
288                           s, "extioi_system_mem", 0x900);
289     sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->extioi_system_mem);
290 }
291 
292 static void loongarch_extioi_class_init(ObjectClass *klass, void *data)
293 {
294     DeviceClass *dc = DEVICE_CLASS(klass);
295 
296     dc->vmsd = &vmstate_loongarch_extioi;
297 }
298 
299 static const TypeInfo loongarch_extioi_info = {
300     .name          = TYPE_LOONGARCH_EXTIOI,
301     .parent        = TYPE_SYS_BUS_DEVICE,
302     .instance_init = loongarch_extioi_instance_init,
303     .instance_size = sizeof(struct LoongArchExtIOI),
304     .class_init    = loongarch_extioi_class_init,
305 };
306 
307 static void loongarch_extioi_register_types(void)
308 {
309     type_register_static(&loongarch_extioi_info);
310 }
311 
312 type_init(loongarch_extioi_register_types)
313