diff options
Diffstat (limited to 'hw/xtensa/mx_pic.c')
-rw-r--r-- | hw/xtensa/mx_pic.c | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/hw/xtensa/mx_pic.c b/hw/xtensa/mx_pic.c new file mode 100644 index 0000000000..7075db9d4b --- /dev/null +++ b/hw/xtensa/mx_pic.c @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2013 - 2019, Max Filippov, Open Source and Linux Lab. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Open Source and Linux Lab nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "qemu/osdep.h" +#include "hw/hw.h" +#include "hw/xtensa/mx_pic.h" +#include "qemu/log.h" + +#define MX_MAX_CPU 32 +#define MX_MAX_IRQ 32 + +#define MIROUT 0x0 +#define MIPICAUSE 0x100 +#define MIPISET 0x140 +#define MIENG 0x180 +#define MIENGSET 0x184 +#define MIASG 0x188 +#define MIASGSET 0x18c +#define MIPIPART 0x190 +#define SYSCFGID 0x1a0 +#define MPSCORE 0x200 +#define CCON 0x220 + +struct XtensaMxPic { + unsigned n_cpu; + unsigned n_irq; + + uint32_t ext_irq_state; + uint32_t mieng; + uint32_t miasg; + uint32_t mirout[MX_MAX_IRQ]; + uint32_t mipipart; + uint32_t runstall; + + qemu_irq *irq_inputs; + struct XtensaMxPicCpu { + XtensaMxPic *mx; + qemu_irq *irq; + qemu_irq runstall; + uint32_t mipicause; + uint32_t mirout_cache; + uint32_t irq_state_cache; + uint32_t ccon; + MemoryRegion reg; + } cpu[MX_MAX_CPU]; +}; + +static uint64_t xtensa_mx_pic_ext_reg_read(void *opaque, hwaddr offset, + unsigned size) +{ + struct XtensaMxPicCpu *mx_cpu = opaque; + struct XtensaMxPic *mx = mx_cpu->mx; + + if (offset < MIROUT + MX_MAX_IRQ) { + return mx->mirout[offset - MIROUT]; + } else if (offset >= MIPICAUSE && offset < MIPICAUSE + MX_MAX_CPU) { + return mx->cpu[offset - MIPICAUSE].mipicause; + } else { + switch (offset) { + case MIENG: + return mx->mieng; + + case MIASG: + return mx->miasg; + + case MIPIPART: + return mx->mipipart; + + case SYSCFGID: + return ((mx->n_cpu - 1) << 18) | (mx_cpu - mx->cpu); + + case MPSCORE: + return mx->runstall; + + case CCON: + return mx_cpu->ccon; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "unknown RER in MX PIC range: 0x%08x\n", + (uint32_t)offset); + return 0; + } + } +} + +static uint32_t xtensa_mx_pic_get_ipi_for_cpu(const XtensaMxPic *mx, + unsigned cpu) +{ + uint32_t mipicause = mx->cpu[cpu].mipicause; + uint32_t mipipart = mx->mipipart; + + return (((mipicause & 1) << (mipipart & 3)) | + ((mipicause & 0x000e) != 0) << ((mipipart >> 2) & 3) | + ((mipicause & 0x00f0) != 0) << ((mipipart >> 4) & 3) | + ((mipicause & 0xff00) != 0) << ((mipipart >> 6) & 3)) & 0x7; +} + +static uint32_t xtensa_mx_pic_get_ext_irq_for_cpu(const XtensaMxPic *mx, + unsigned cpu) +{ + return ((((mx->ext_irq_state & mx->mieng) | mx->miasg) & + mx->cpu[cpu].mirout_cache) << 2) | + xtensa_mx_pic_get_ipi_for_cpu(mx, cpu); +} + +static void xtensa_mx_pic_update_cpu(XtensaMxPic *mx, unsigned cpu) +{ + uint32_t irq = xtensa_mx_pic_get_ext_irq_for_cpu(mx, cpu); + uint32_t changed_irq = mx->cpu[cpu].irq_state_cache ^ irq; + unsigned i; + + qemu_log_mask(CPU_LOG_INT, "%s: CPU %d, irq: %08x, changed_irq: %08x\n", + __func__, cpu, irq, changed_irq); + mx->cpu[cpu].irq_state_cache = irq; + for (i = 0; changed_irq; ++i) { + uint32_t mask = 1u << i; + + if (changed_irq & mask) { + changed_irq ^= mask; + qemu_set_irq(mx->cpu[cpu].irq[i], irq & mask); + } + } +} + +static void xtensa_mx_pic_update_all(XtensaMxPic *mx) +{ + unsigned cpu; + + for (cpu = 0; cpu < mx->n_cpu; ++cpu) { + xtensa_mx_pic_update_cpu(mx, cpu); + } +} + +static void xtensa_mx_pic_ext_reg_write(void *opaque, hwaddr offset, + uint64_t v, unsigned size) +{ + struct XtensaMxPicCpu *mx_cpu = opaque; + struct XtensaMxPic *mx = mx_cpu->mx; + unsigned cpu; + + if (offset < MIROUT + mx->n_irq) { + mx->mirout[offset - MIROUT] = v; + for (cpu = 0; cpu < mx->n_cpu; ++cpu) { + uint32_t mask = 1u << (offset - MIROUT); + + if (!(mx->cpu[cpu].mirout_cache & mask) != !(v & (1u << cpu))) { + mx->cpu[cpu].mirout_cache ^= mask; + xtensa_mx_pic_update_cpu(mx, cpu); + } + } + } else if (offset >= MIPICAUSE && offset < MIPICAUSE + mx->n_cpu) { + cpu = offset - MIPICAUSE; + mx->cpu[cpu].mipicause &= ~v; + xtensa_mx_pic_update_cpu(mx, cpu); + } else if (offset >= MIPISET && offset < MIPISET + 16) { + for (cpu = 0; cpu < mx->n_cpu; ++cpu) { + if (v & (1u << cpu)) { + mx->cpu[cpu].mipicause |= 1u << (offset - MIPISET); + xtensa_mx_pic_update_cpu(mx, cpu); + } + } + } else { + uint32_t change = 0; + uint32_t oldv, newv; + const char *name = "???"; + + switch (offset) { + case MIENG: + change = mx->mieng & v; + oldv = mx->mieng; + mx->mieng &= ~v; + newv = mx->mieng; + name = "MIENG"; + break; + + case MIENGSET: + change = ~mx->mieng & v; + oldv = mx->mieng; + mx->mieng |= v; + newv = mx->mieng; + name = "MIENG"; + break; + + case MIASG: + change = mx->miasg & v; + oldv = mx->miasg; + mx->miasg &= ~v; + newv = mx->miasg; + name = "MIASG"; + break; + + case MIASGSET: + change = ~mx->miasg & v; + oldv = mx->miasg; + mx->miasg |= v; + newv = mx->miasg; + name = "MIASG"; + break; + + case MIPIPART: + change = mx->mipipart ^ v; + oldv = mx->mipipart; + mx->mipipart = v; + newv = mx->mipipart; + name = "MIPIPART"; + break; + + case MPSCORE: + change = mx->runstall ^ v; + oldv = mx->runstall; + mx->runstall = v; + newv = mx->runstall; + name = "RUNSTALL"; + for (cpu = 0; cpu < mx->n_cpu; ++cpu) { + if (change & (1u << cpu)) { + qemu_set_irq(mx->cpu[cpu].runstall, v & (1u << cpu)); + } + } + break; + + case CCON: + mx_cpu->ccon = v & 0x1; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "unknown WER in MX PIC range: 0x%08x = 0x%08x\n", + (uint32_t)offset, (uint32_t)v); + break; + } + if (change) { + qemu_log_mask(CPU_LOG_INT, + "%s: %s changed by CPU %d: %08x -> %08x\n", + __func__, name, (int)(mx_cpu - mx->cpu), + oldv, newv); + xtensa_mx_pic_update_all(mx); + } + } +} + +static const MemoryRegionOps xtensa_mx_pic_ops = { + .read = xtensa_mx_pic_ext_reg_read, + .write = xtensa_mx_pic_ext_reg_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .unaligned = true, + }, +}; + +MemoryRegion *xtensa_mx_pic_register_cpu(XtensaMxPic *mx, + qemu_irq *irq, + qemu_irq runstall) +{ + struct XtensaMxPicCpu *mx_cpu = mx->cpu + mx->n_cpu; + + mx_cpu->mx = mx; + mx_cpu->irq = irq; + mx_cpu->runstall = runstall; + + memory_region_init_io(&mx_cpu->reg, NULL, &xtensa_mx_pic_ops, mx_cpu, + "mx_pic", 0x280); + + ++mx->n_cpu; + return &mx_cpu->reg; +} + +static void xtensa_mx_pic_set_irq(void *opaque, int irq, int active) +{ + XtensaMxPic *mx = opaque; + + if (irq < mx->n_irq) { + uint32_t old_irq_state = mx->ext_irq_state; + + if (active) { + mx->ext_irq_state |= 1u << irq; + } else { + mx->ext_irq_state &= ~(1u << irq); + } + if (old_irq_state != mx->ext_irq_state) { + qemu_log_mask(CPU_LOG_INT, + "%s: IRQ %d, active: %d, ext_irq_state: %08x -> %08x\n", + __func__, irq, active, + old_irq_state, mx->ext_irq_state); + xtensa_mx_pic_update_all(mx); + } + } else { + qemu_log_mask(LOG_GUEST_ERROR, "%s: IRQ %d out of range\n", + __func__, irq); + } +} + +XtensaMxPic *xtensa_mx_pic_init(unsigned n_irq) +{ + XtensaMxPic *mx = calloc(1, sizeof(XtensaMxPic)); + + mx->n_irq = n_irq + 1; + mx->irq_inputs = qemu_allocate_irqs(xtensa_mx_pic_set_irq, mx, + mx->n_irq); + return mx; +} + +void xtensa_mx_pic_reset(void *opaque) +{ + XtensaMxPic *mx = opaque; + unsigned i; + + mx->ext_irq_state = 0; + mx->mieng = mx->n_irq < 32 ? (1u << mx->n_irq) - 1 : ~0u; + mx->miasg = 0; + mx->mipipart = 0; + for (i = 0; i < mx->n_irq; ++i) { + mx->mirout[i] = 1; + } + for (i = 0; i < mx->n_cpu; ++i) { + mx->cpu[i].mipicause = 0; + mx->cpu[i].mirout_cache = i ? 0 : mx->mieng; + mx->cpu[i].irq_state_cache = 0; + mx->cpu[i].ccon = 0; + } + mx->runstall = (1u << mx->n_cpu) - 2; + for (i = 0; i < mx->n_cpu; ++i) { + qemu_set_irq(mx->cpu[i].runstall, i > 0); + } +} + +qemu_irq *xtensa_mx_pic_get_extints(XtensaMxPic *mx) +{ + return mx->irq_inputs + 1; +} |