/* * 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/irq.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; }