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