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