/* * QEMU Loongson Local I/O interrupt controller. * * Copyright (c) 2020 Huacai Chen * Copyright (c) 2020 Jiaxun Yang * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "qemu/osdep.h" #include "qemu/module.h" #include "qemu/log.h" #include "hw/irq.h" #include "hw/qdev-properties.h" #include "hw/intc/loongson_liointc.h" #define NUM_IRQS 32 #define NUM_CORES 4 #define NUM_IPS 4 #define NUM_PARENTS (NUM_CORES * NUM_IPS) #define PARENT_COREx_IPy(x, y) (NUM_IPS * x + y) #define R_MAPPER_START 0x0 #define R_MAPPER_END 0x20 #define R_ISR R_MAPPER_END #define R_IEN 0x24 #define R_IEN_SET 0x28 #define R_IEN_CLR 0x2c #define R_ISR_SIZE 0x8 #define R_START 0x40 #define R_END (R_START + R_ISR_SIZE * NUM_CORES) struct loongson_liointc { SysBusDevice parent_obj; MemoryRegion mmio; qemu_irq parent_irq[NUM_PARENTS]; uint8_t mapper[NUM_IRQS]; /* 0:3 for core, 4:7 for IP */ uint32_t isr; uint32_t ien; uint32_t per_core_isr[NUM_CORES]; /* state of the interrupt input pins */ uint32_t pin_state; bool parent_state[NUM_PARENTS]; }; static void update_irq(struct loongson_liointc *p) { uint32_t irq, core, ip; uint32_t per_ip_isr[NUM_IPS] = {0}; /* level triggered interrupt */ p->isr = p->pin_state; /* Clear disabled IRQs */ p->isr &= p->ien; /* Clear per_core_isr */ for (core = 0; core < NUM_CORES; core++) { p->per_core_isr[core] = 0; } /* Update per_core_isr and per_ip_isr */ for (irq = 0; irq < NUM_IRQS; irq++) { if (!(p->isr & (1 << irq))) { continue; } for (core = 0; core < NUM_CORES; core++) { if ((p->mapper[irq] & (1 << core))) { p->per_core_isr[core] |= (1 << irq); } } for (ip = 0; ip < NUM_IPS; ip++) { if ((p->mapper[irq] & (1 << (ip + 4)))) { per_ip_isr[ip] |= (1 << irq); } } } /* Emit IRQ to parent! */ for (core = 0; core < NUM_CORES; core++) { for (ip = 0; ip < NUM_IPS; ip++) { int parent = PARENT_COREx_IPy(core, ip); if (p->parent_state[parent] != (!!p->per_core_isr[core] && !!per_ip_isr[ip])) { p->parent_state[parent] = !p->parent_state[parent]; qemu_set_irq(p->parent_irq[parent], p->parent_state[parent]); } } } } static uint64_t liointc_read(void *opaque, hwaddr addr, unsigned int size) { struct loongson_liointc *p = opaque; uint32_t r = 0; /* Mapper is 1 byte */ if (size == 1 && addr < R_MAPPER_END) { r = p->mapper[addr]; goto out; } /* Rest are 4 bytes */ if (size != 4 || (addr % 4)) { goto out; } if (addr >= R_START && addr < R_END) { hwaddr offset = addr - R_START; int core = offset / R_ISR_SIZE; if (offset % R_ISR_SIZE) { goto out; } r = p->per_core_isr[core]; goto out; } switch (addr) { case R_ISR: r = p->isr; break; case R_IEN: r = p->ien; break; default: break; } out: qemu_log_mask(CPU_LOG_INT, "%s: size=%d, addr=%"HWADDR_PRIx", val=%x\n", __func__, size, addr, r); return r; } static void liointc_write(void *opaque, hwaddr addr, uint64_t val64, unsigned int size) { struct loongson_liointc *p = opaque; uint32_t value = val64; qemu_log_mask(CPU_LOG_INT, "%s: size=%d, addr=%"HWADDR_PRIx", val=%x\n", __func__, size, addr, value); /* Mapper is 1 byte */ if (size == 1 && addr < R_MAPPER_END) { p->mapper[addr] = value; goto out; } /* Rest are 4 bytes */ if (size != 4 || (addr % 4)) { goto out; } if (addr >= R_START && addr < R_END) { hwaddr offset = addr - R_START; int core = offset / R_ISR_SIZE; if (offset % R_ISR_SIZE) { goto out; } p->per_core_isr[core] = value; goto out; } switch (addr) { case R_IEN_SET: p->ien |= value; break; case R_IEN_CLR: p->ien &= ~value; break; default: break; } out: update_irq(p); } static const MemoryRegionOps pic_ops = { .read = liointc_read, .write = liointc_write, .endianness = DEVICE_NATIVE_ENDIAN, .valid = { .min_access_size = 1, .max_access_size = 4 } }; static void irq_handler(void *opaque, int irq, int level) { struct loongson_liointc *p = opaque; p->pin_state &= ~(1 << irq); p->pin_state |= level << irq; update_irq(p); } static void loongson_liointc_init(Object *obj) { struct loongson_liointc *p = LOONGSON_LIOINTC(obj); int i; qdev_init_gpio_in(DEVICE(obj), irq_handler, 32); for (i = 0; i < NUM_PARENTS; i++) { sysbus_init_irq(SYS_BUS_DEVICE(obj), &p->parent_irq[i]); } memory_region_init_io(&p->mmio, obj, &pic_ops, p, TYPE_LOONGSON_LIOINTC, R_END); sysbus_init_mmio(SYS_BUS_DEVICE(obj), &p->mmio); } static const TypeInfo loongson_liointc_info = { .name = TYPE_LOONGSON_LIOINTC, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(struct loongson_liointc), .instance_init = loongson_liointc_init, }; static void loongson_liointc_register_types(void) { type_register_static(&loongson_liointc_info); } type_init(loongson_liointc_register_types)