1 /* 2 * QEMU Loongson Local I/O interrupt controler. 3 * 4 * Copyright (c) 2020 Huacai Chen <chenhc@lemote.com> 5 * Copyright (c) 2020 Jiaxun Yang <jiaxun.yang@flygoat.com> 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program. If not, see <https://www.gnu.org/licenses/>. 19 * 20 */ 21 22 #include "qemu/osdep.h" 23 #include "qemu/module.h" 24 #include "qemu/log.h" 25 #include "hw/irq.h" 26 #include "hw/qdev-properties.h" 27 #include "hw/intc/loongson_liointc.h" 28 29 #define NUM_IRQS 32 30 31 #define NUM_CORES 4 32 #define NUM_IPS 4 33 #define NUM_PARENTS (NUM_CORES * NUM_IPS) 34 #define PARENT_COREx_IPy(x, y) (NUM_IPS * x + y) 35 36 #define R_MAPPER_START 0x0 37 #define R_MAPPER_END 0x20 38 #define R_ISR R_MAPPER_END 39 #define R_IEN 0x24 40 #define R_IEN_SET 0x28 41 #define R_IEN_CLR 0x2c 42 #define R_ISR_SIZE 0x8 43 #define R_START 0x40 44 #define R_END (R_START + R_ISR_SIZE * NUM_CORES) 45 46 struct loongson_liointc { 47 SysBusDevice parent_obj; 48 49 MemoryRegion mmio; 50 qemu_irq parent_irq[NUM_PARENTS]; 51 52 uint8_t mapper[NUM_IRQS]; /* 0:3 for core, 4:7 for IP */ 53 uint32_t isr; 54 uint32_t ien; 55 uint32_t per_core_isr[NUM_CORES]; 56 57 /* state of the interrupt input pins */ 58 uint32_t pin_state; 59 bool parent_state[NUM_PARENTS]; 60 }; 61 62 static void update_irq(struct loongson_liointc *p) 63 { 64 uint32_t irq, core, ip; 65 uint32_t per_ip_isr[NUM_IPS] = {0}; 66 67 /* level triggered interrupt */ 68 p->isr = p->pin_state; 69 70 /* Clear disabled IRQs */ 71 p->isr &= p->ien; 72 73 /* Clear per_core_isr */ 74 for (core = 0; core < NUM_CORES; core++) { 75 p->per_core_isr[core] = 0; 76 } 77 78 /* Update per_core_isr and per_ip_isr */ 79 for (irq = 0; irq < NUM_IRQS; irq++) { 80 if (!(p->isr & (1 << irq))) { 81 continue; 82 } 83 84 for (core = 0; core < NUM_CORES; core++) { 85 if ((p->mapper[irq] & (1 << core))) { 86 p->per_core_isr[core] |= (1 << irq); 87 } 88 } 89 90 for (ip = 0; ip < NUM_IPS; ip++) { 91 if ((p->mapper[irq] & (1 << (ip + 4)))) { 92 per_ip_isr[ip] |= (1 << irq); 93 } 94 } 95 } 96 97 /* Emit IRQ to parent! */ 98 for (core = 0; core < NUM_CORES; core++) { 99 for (ip = 0; ip < NUM_IPS; ip++) { 100 int parent = PARENT_COREx_IPy(core, ip); 101 if (p->parent_state[parent] != 102 (!!p->per_core_isr[core] && !!per_ip_isr[ip])) { 103 p->parent_state[parent] = !p->parent_state[parent]; 104 qemu_set_irq(p->parent_irq[parent], p->parent_state[parent]); 105 } 106 } 107 } 108 } 109 110 static uint64_t 111 liointc_read(void *opaque, hwaddr addr, unsigned int size) 112 { 113 struct loongson_liointc *p = opaque; 114 uint32_t r = 0; 115 116 /* Mapper is 1 byte */ 117 if (size == 1 && addr < R_MAPPER_END) { 118 r = p->mapper[addr]; 119 goto out; 120 } 121 122 /* Rest are 4 bytes */ 123 if (size != 4 || (addr % 4)) { 124 goto out; 125 } 126 127 if (addr >= R_START && addr < R_END) { 128 hwaddr offset = addr - R_START; 129 int core = offset / R_ISR_SIZE; 130 131 if (offset % R_ISR_SIZE) { 132 goto out; 133 } 134 r = p->per_core_isr[core]; 135 goto out; 136 } 137 138 switch (addr) { 139 case R_ISR: 140 r = p->isr; 141 break; 142 case R_IEN: 143 r = p->ien; 144 break; 145 default: 146 break; 147 } 148 149 out: 150 qemu_log_mask(CPU_LOG_INT, "%s: size=%d, addr=%"HWADDR_PRIx", val=%x\n", 151 __func__, size, addr, r); 152 return r; 153 } 154 155 static void 156 liointc_write(void *opaque, hwaddr addr, 157 uint64_t val64, unsigned int size) 158 { 159 struct loongson_liointc *p = opaque; 160 uint32_t value = val64; 161 162 qemu_log_mask(CPU_LOG_INT, "%s: size=%d, addr=%"HWADDR_PRIx", val=%x\n", 163 __func__, size, addr, value); 164 165 /* Mapper is 1 byte */ 166 if (size == 1 && addr < R_MAPPER_END) { 167 p->mapper[addr] = value; 168 goto out; 169 } 170 171 /* Rest are 4 bytes */ 172 if (size != 4 || (addr % 4)) { 173 goto out; 174 } 175 176 if (addr >= R_START && addr < R_END) { 177 hwaddr offset = addr - R_START; 178 int core = offset / R_ISR_SIZE; 179 180 if (offset % R_ISR_SIZE) { 181 goto out; 182 } 183 p->per_core_isr[core] = value; 184 goto out; 185 } 186 187 switch (addr) { 188 case R_IEN_SET: 189 p->ien |= value; 190 break; 191 case R_IEN_CLR: 192 p->ien &= ~value; 193 break; 194 default: 195 break; 196 } 197 198 out: 199 update_irq(p); 200 } 201 202 static const MemoryRegionOps pic_ops = { 203 .read = liointc_read, 204 .write = liointc_write, 205 .endianness = DEVICE_NATIVE_ENDIAN, 206 .valid = { 207 .min_access_size = 1, 208 .max_access_size = 4 209 } 210 }; 211 212 static void irq_handler(void *opaque, int irq, int level) 213 { 214 struct loongson_liointc *p = opaque; 215 216 p->pin_state &= ~(1 << irq); 217 p->pin_state |= level << irq; 218 update_irq(p); 219 } 220 221 static void loongson_liointc_init(Object *obj) 222 { 223 struct loongson_liointc *p = LOONGSON_LIOINTC(obj); 224 int i; 225 226 qdev_init_gpio_in(DEVICE(obj), irq_handler, 32); 227 228 for (i = 0; i < NUM_PARENTS; i++) { 229 sysbus_init_irq(SYS_BUS_DEVICE(obj), &p->parent_irq[i]); 230 } 231 232 memory_region_init_io(&p->mmio, obj, &pic_ops, p, 233 TYPE_LOONGSON_LIOINTC, R_END); 234 sysbus_init_mmio(SYS_BUS_DEVICE(obj), &p->mmio); 235 } 236 237 static const TypeInfo loongson_liointc_info = { 238 .name = TYPE_LOONGSON_LIOINTC, 239 .parent = TYPE_SYS_BUS_DEVICE, 240 .instance_size = sizeof(struct loongson_liointc), 241 .instance_init = loongson_liointc_init, 242 }; 243 244 static void loongson_liointc_register_types(void) 245 { 246 type_register_static(&loongson_liointc_info); 247 } 248 249 type_init(loongson_liointc_register_types) 250