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