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