| /* |
| * Copyright (c) 2010, 2013, 2015-2018, 2020 ARM Limited |
| * All rights reserved |
| * |
| * The license below extends only to copyright in the software and shall |
| * not be construed as granting a license to any other intellectual |
| * property including but not limited to intellectual property relating |
| * to a hardware implementation of the functionality of the software |
| * licensed hereunder. You may use the software subject to the license |
| * terms below provided that you ensure that this notice is replicated |
| * unmodified and in its entirety in all distributions of the software, |
| * modified or unmodified, in source code or in binary form. |
| * |
| * Copyright (c) 2005 The Regents of The University of Michigan |
| * 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 copyright holders 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 COPYRIGHT |
| * OWNER OR CONTRIBUTORS 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 "dev/arm/gic_v2.hh" |
| |
| #include "base/compiler.hh" |
| #include "base/trace.hh" |
| #include "cpu/base.hh" |
| #include "debug/Checkpoint.hh" |
| #include "debug/GIC.hh" |
| #include "debug/IPI.hh" |
| #include "debug/Interrupt.hh" |
| #include "mem/packet.hh" |
| #include "mem/packet_access.hh" |
| |
| namespace gem5 |
| { |
| |
| const AddrRange GicV2::GICD_IGROUPR (0x080, 0x100); |
| const AddrRange GicV2::GICD_ISENABLER (0x100, 0x180); |
| const AddrRange GicV2::GICD_ICENABLER (0x180, 0x200); |
| const AddrRange GicV2::GICD_ISPENDR (0x200, 0x280); |
| const AddrRange GicV2::GICD_ICPENDR (0x280, 0x300); |
| const AddrRange GicV2::GICD_ISACTIVER (0x300, 0x380); |
| const AddrRange GicV2::GICD_ICACTIVER (0x380, 0x400); |
| const AddrRange GicV2::GICD_IPRIORITYR(0x400, 0x800); |
| const AddrRange GicV2::GICD_ITARGETSR (0x800, 0xc00); |
| const AddrRange GicV2::GICD_ICFGR (0xc00, 0xd00); |
| |
| GicV2::GicV2(const Params &p) |
| : BaseGic(p), |
| gicdPIDR(p.gicd_pidr), |
| gicdIIDR(p.gicd_iidr), |
| giccIIDR(p.gicc_iidr), |
| distRange(RangeSize(p.dist_addr, DIST_SIZE)), |
| cpuRange(RangeSize(p.cpu_addr, p.cpu_size)), |
| addrRanges{distRange, cpuRange}, |
| distPioDelay(p.dist_pio_delay), |
| cpuPioDelay(p.cpu_pio_delay), intLatency(p.int_latency), |
| enabled(false), haveGem5Extensions(p.gem5_extensions), |
| itLines(p.it_lines), |
| intEnabled {}, pendingInt {}, activeInt {}, intGroup {}, |
| intPriority {}, intConfig {}, cpuTarget {}, |
| cpuSgiPending {}, cpuSgiActive {}, |
| cpuSgiPendingExt {}, cpuSgiActiveExt {}, |
| cpuPpiPending {}, cpuPpiActive {}, |
| pendingDelayedInterrupts(0) |
| { |
| for (int x = 0; x < CPU_MAX; x++) { |
| iccrpr[x] = 0xff; |
| cpuControl[x] = 0; |
| cpuPriority[x] = 0xff; |
| cpuBpr[x] = GICC_BPR_MINIMUM; |
| // Initialize cpu highest int |
| cpuHighestInt[x] = SPURIOUS_INT; |
| postIntEvent[x] = |
| new EventFunctionWrapper([this, x]{ postDelayedInt(x); }, |
| "Post Interrupt to CPU"); |
| postFiqEvent[x] = |
| new EventFunctionWrapper([this, x]{ postDelayedFiq(x); }, |
| "Post FIQ to CPU"); |
| } |
| DPRINTF(Interrupt, "cpuEnabled[0]=%d cpuEnabled[1]=%d\n", cpuEnabled(0), |
| cpuEnabled(1)); |
| |
| gem5ExtensionsEnabled = false; |
| } |
| |
| GicV2::~GicV2() |
| { |
| for (int x = 0; x < CPU_MAX; x++) { |
| delete postIntEvent[x]; |
| delete postFiqEvent[x]; |
| } |
| } |
| |
| Tick |
| GicV2::read(PacketPtr pkt) |
| { |
| const Addr addr = pkt->getAddr(); |
| |
| if (distRange.contains(addr)) |
| return readDistributor(pkt); |
| else if (cpuRange.contains(addr)) |
| return readCpu(pkt); |
| else |
| panic("Read to unknown address %#x\n", pkt->getAddr()); |
| } |
| |
| |
| Tick |
| GicV2::write(PacketPtr pkt) |
| { |
| const Addr addr = pkt->getAddr(); |
| |
| if (distRange.contains(addr)) |
| return writeDistributor(pkt); |
| else if (cpuRange.contains(addr)) |
| return writeCpu(pkt); |
| else |
| panic("Write to unknown address %#x\n", pkt->getAddr()); |
| } |
| |
| Tick |
| GicV2::readDistributor(PacketPtr pkt) |
| { |
| const Addr daddr = pkt->getAddr() - distRange.start(); |
| const ContextID ctx = pkt->req->contextId(); |
| |
| DPRINTF(GIC, "gic distributor read register %#x\n", daddr); |
| |
| const uint32_t resp = readDistributor(ctx, daddr, pkt->getSize()); |
| |
| switch (pkt->getSize()) { |
| case 1: |
| pkt->setLE<uint8_t>(resp); |
| break; |
| case 2: |
| pkt->setLE<uint16_t>(resp); |
| break; |
| case 4: |
| pkt->setLE<uint32_t>(resp); |
| break; |
| default: |
| panic("Invalid size while reading Distributor regs in GIC: %d\n", |
| pkt->getSize()); |
| } |
| |
| pkt->makeAtomicResponse(); |
| return distPioDelay; |
| } |
| |
| uint32_t |
| GicV2::readDistributor(ContextID ctx, Addr daddr, size_t resp_sz) |
| { |
| if (GICD_IGROUPR.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_IGROUPR.start()) >> 2; |
| assert(ix < 32); |
| return getIntGroup(ctx, ix); |
| } |
| |
| if (GICD_ISENABLER.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ISENABLER.start()) >> 2; |
| assert(ix < 32); |
| return getIntEnabled(ctx, ix); |
| } |
| |
| if (GICD_ICENABLER.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ICENABLER.start()) >> 2; |
| assert(ix < 32); |
| return getIntEnabled(ctx, ix); |
| } |
| |
| if (GICD_ISPENDR.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ISPENDR.start()) >> 2; |
| assert(ix < 32); |
| return getPendingInt(ctx, ix); |
| } |
| |
| if (GICD_ICPENDR.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ICPENDR.start()) >> 2; |
| assert(ix < 32); |
| return getPendingInt(ctx, ix); |
| } |
| |
| if (GICD_ISACTIVER.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ISACTIVER.start()) >> 2; |
| assert(ix < 32); |
| return getActiveInt(ctx, ix); |
| } |
| |
| if (GICD_ICACTIVER.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ICACTIVER.start()) >> 2; |
| assert(ix < 32); |
| return getActiveInt(ctx, ix); |
| } |
| |
| if (GICD_IPRIORITYR.contains(daddr)) { |
| Addr int_num = daddr - GICD_IPRIORITYR.start(); |
| assert(int_num < INT_LINES_MAX); |
| DPRINTF(Interrupt, "Reading interrupt priority at int# %#x \n", |
| int_num); |
| |
| switch (resp_sz) { |
| default: // will panic() after return to caller anyway |
| case 1: |
| return getIntPriority(ctx, int_num); |
| case 2: |
| assert((int_num + 1) < INT_LINES_MAX); |
| return (getIntPriority(ctx, int_num) | |
| getIntPriority(ctx, int_num+1) << 8); |
| case 4: |
| assert((int_num + 3) < INT_LINES_MAX); |
| return (getIntPriority(ctx, int_num) | |
| getIntPriority(ctx, int_num+1) << 8 | |
| getIntPriority(ctx, int_num+2) << 16 | |
| getIntPriority(ctx, int_num+3) << 24); |
| } |
| } |
| |
| if (GICD_ITARGETSR.contains(daddr)) { |
| Addr int_num = daddr - GICD_ITARGETSR.start(); |
| DPRINTF(GIC, "Reading processor target register for int# %#x \n", |
| int_num); |
| assert(int_num < INT_LINES_MAX); |
| |
| if (resp_sz == 1) { |
| return getCpuTarget(ctx, int_num); |
| } else { |
| assert(resp_sz == 4); |
| int_num = mbits(int_num, 31, 2); |
| return (getCpuTarget(ctx, int_num) | |
| getCpuTarget(ctx, int_num+1) << 8 | |
| getCpuTarget(ctx, int_num+2) << 16 | |
| getCpuTarget(ctx, int_num+3) << 24) ; |
| } |
| } |
| |
| if (GICD_ICFGR.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ICFGR.start()) >> 2; |
| return getIntConfig(ctx, ix); |
| } |
| |
| switch(daddr) { |
| case GICD_CTLR: |
| return enabled; |
| case GICD_TYPER: |
| /* The 0x100 is a made-up flag to show that gem5 extensions |
| * are available, |
| * write 0x200 to this register to enable it. */ |
| return (((sys->threads.numRunning() - 1) << 5) | |
| (itLines/INT_BITS_MAX -1) | |
| (haveGem5Extensions ? 0x100 : 0x0)); |
| case GICD_PIDR0: |
| //ARM defined DevID |
| return (gicdPIDR & 0xFF); |
| case GICD_PIDR1: |
| return ((gicdPIDR >> 8) & 0xFF); |
| case GICD_PIDR2: |
| return ((gicdPIDR >> 16) & 0xFF); |
| case GICD_PIDR3: |
| return ((gicdPIDR >> 24) & 0xFF); |
| case GICD_IIDR: |
| /* revision id is resorted to 1 and variant to 0*/ |
| return gicdIIDR; |
| default: |
| panic("Tried to read Gic distributor at offset %#x\n", daddr); |
| break; |
| } |
| } |
| |
| Tick |
| GicV2::readCpu(PacketPtr pkt) |
| { |
| const Addr daddr = pkt->getAddr() - cpuRange.start(); |
| |
| assert(pkt->req->hasContextId()); |
| const ContextID ctx = pkt->req->contextId(); |
| assert(ctx < sys->threads.numRunning()); |
| |
| DPRINTF(GIC, "gic cpu read register %#x cpu context: %d\n", daddr, ctx); |
| |
| pkt->setLE<uint32_t>(readCpu(ctx, daddr)); |
| |
| pkt->makeAtomicResponse(); |
| return cpuPioDelay; |
| } |
| |
| uint32_t |
| GicV2::readCpu(ContextID ctx, Addr daddr) |
| { |
| switch(daddr) { |
| case GICC_IIDR: |
| return giccIIDR; |
| case GICC_CTLR: |
| return cpuControl[ctx]; |
| case GICC_PMR: |
| return cpuPriority[ctx]; |
| case GICC_BPR: |
| return cpuBpr[ctx]; |
| case GICC_IAR: |
| if (enabled && cpuEnabled(ctx)) { |
| int active_int = cpuHighestInt[ctx]; |
| IAR iar = 0; |
| iar.ack_id = active_int; |
| iar.cpu_id = 0; |
| if (active_int < SGI_MAX) { |
| // this is a software interrupt from another CPU |
| if (!gem5ExtensionsEnabled) { |
| panic_if(!cpuSgiPending[active_int], |
| "Interrupt %d active but no CPU generated it?\n", |
| active_int); |
| for (int x = 0; x < sys->threads.numRunning(); x++) { |
| // See which CPU generated the interrupt |
| uint8_t cpugen = |
| bits(cpuSgiPending[active_int], 7 + 8 * x, 8 * x); |
| if (cpugen & (1 << ctx)) { |
| iar.cpu_id = x; |
| break; |
| } |
| } |
| uint64_t sgi_num = 1ULL << (ctx + 8 * iar.cpu_id); |
| cpuSgiActive[iar.ack_id] |= sgi_num; |
| cpuSgiPending[iar.ack_id] &= ~sgi_num; |
| } else { |
| uint64_t sgi_num = 1ULL << iar.ack_id; |
| cpuSgiActiveExt[ctx] |= sgi_num; |
| cpuSgiPendingExt[ctx] &= ~sgi_num; |
| } |
| } else if (active_int < (SGI_MAX + PPI_MAX) ) { |
| uint32_t int_num = 1 << (cpuHighestInt[ctx] - SGI_MAX); |
| cpuPpiActive[ctx] |= int_num; |
| updateRunPri(); |
| if (!isLevelSensitive(ctx, active_int)) { |
| cpuPpiPending[ctx] &= ~int_num; |
| } |
| |
| } else { |
| uint32_t int_num = 1 << intNumToBit(cpuHighestInt[ctx]); |
| getActiveInt(ctx, intNumToWord(cpuHighestInt[ctx])) |= int_num; |
| updateRunPri(); |
| if (!isLevelSensitive(ctx, active_int)) { |
| getPendingInt(ctx, intNumToWord(cpuHighestInt[ctx])) |
| &= ~int_num; |
| } |
| } |
| |
| DPRINTF(Interrupt, |
| "CPU %d reading IAR.id=%d IAR.cpu=%d, iar=0x%x\n", |
| ctx, iar.ack_id, iar.cpu_id, iar); |
| cpuHighestInt[ctx] = SPURIOUS_INT; |
| updateIntState(-1); |
| clearInt(ctx, active_int); |
| return iar; |
| } else { |
| return SPURIOUS_INT; |
| } |
| |
| break; |
| case GICC_RPR: |
| return iccrpr[0]; |
| case GICC_HPPIR: |
| panic("Need to implement HPIR"); |
| break; |
| default: |
| panic("Tried to read Gic cpu at offset %#x\n", daddr); |
| break; |
| } |
| } |
| |
| Tick |
| GicV2::writeDistributor(PacketPtr pkt) |
| { |
| const Addr daddr = pkt->getAddr() - distRange.start(); |
| |
| assert(pkt->req->hasContextId()); |
| const ContextID ctx = pkt->req->contextId(); |
| const size_t data_sz = pkt->getSize(); |
| |
| [[maybe_unused]] uint32_t pkt_data; |
| switch (data_sz) |
| { |
| case 1: |
| pkt_data = pkt->getLE<uint8_t>(); |
| break; |
| case 2: |
| pkt_data = pkt->getLE<uint16_t>(); |
| break; |
| case 4: |
| pkt_data = pkt->getLE<uint32_t>(); |
| break; |
| default: |
| panic("Invalid size when writing to priority regs in Gic: %d\n", |
| data_sz); |
| } |
| |
| DPRINTF(GIC, "gic distributor write register %#x size %#x value %#x \n", |
| daddr, data_sz, pkt_data); |
| |
| writeDistributor(ctx, daddr, pkt_data, data_sz); |
| |
| pkt->makeAtomicResponse(); |
| return distPioDelay; |
| } |
| |
| void |
| GicV2::writeDistributor(ContextID ctx, Addr daddr, uint32_t data, |
| size_t data_sz) |
| { |
| if (GICD_IGROUPR.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_IGROUPR.start()) >> 2; |
| assert(ix < 32); |
| getIntGroup(ctx, ix) |= data; |
| return; |
| } |
| |
| if (GICD_ISENABLER.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ISENABLER.start()) >> 2; |
| assert(ix < 32); |
| getIntEnabled(ctx, ix) |= data; |
| return; |
| } |
| |
| if (GICD_ICENABLER.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ICENABLER.start()) >> 2; |
| assert(ix < 32); |
| getIntEnabled(ctx, ix) &= ~data; |
| return; |
| } |
| |
| if (GICD_ISPENDR.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ISPENDR.start()) >> 2; |
| auto mask = data; |
| if (ix == 0) mask &= SGI_MASK; // Don't allow SGIs to be changed |
| getPendingInt(ctx, ix) |= mask; |
| updateIntState(ix); |
| return; |
| } |
| |
| if (GICD_ICPENDR.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ICPENDR.start()) >> 2; |
| auto mask = data; |
| if (ix == 0) mask &= SGI_MASK; // Don't allow SGIs to be changed |
| getPendingInt(ctx, ix) &= ~mask; |
| updateIntState(ix); |
| return; |
| } |
| |
| if (GICD_ISACTIVER.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ISACTIVER.start()) >> 2; |
| getActiveInt(ctx, ix) |= data; |
| return; |
| } |
| |
| if (GICD_ICACTIVER.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ICACTIVER.start()) >> 2; |
| getActiveInt(ctx, ix) &= ~data; |
| return; |
| } |
| |
| if (GICD_IPRIORITYR.contains(daddr)) { |
| Addr int_num = daddr - GICD_IPRIORITYR.start(); |
| switch(data_sz) { |
| case 1: |
| getIntPriority(ctx, int_num) = data; |
| break; |
| case 2: { |
| getIntPriority(ctx, int_num) = bits(data, 7, 0); |
| getIntPriority(ctx, int_num + 1) = bits(data, 15, 8); |
| break; |
| } |
| case 4: { |
| getIntPriority(ctx, int_num) = bits(data, 7, 0); |
| getIntPriority(ctx, int_num + 1) = bits(data, 15, 8); |
| getIntPriority(ctx, int_num + 2) = bits(data, 23, 16); |
| getIntPriority(ctx, int_num + 3) = bits(data, 31, 24); |
| break; |
| } |
| default: |
| panic("Invalid size when writing to priority regs in Gic: %d\n", |
| data_sz); |
| } |
| |
| updateIntState(-1); |
| updateRunPri(); |
| return; |
| } |
| |
| if (GICD_ITARGETSR.contains(daddr)) { |
| Addr int_num = daddr - GICD_ITARGETSR.start(); |
| // Interrupts 0-31 are read only |
| unsigned offset = SGI_MAX + PPI_MAX; |
| if (int_num >= offset) { |
| unsigned ix = int_num - offset; // index into cpuTarget array |
| if (data_sz == 1) { |
| cpuTarget[ix] = data & 0xff; |
| } else { |
| assert (data_sz == 4); |
| cpuTarget[ix] = bits(data, 7, 0); |
| cpuTarget[ix+1] = bits(data, 15, 8); |
| cpuTarget[ix+2] = bits(data, 23, 16); |
| cpuTarget[ix+3] = bits(data, 31, 24); |
| } |
| updateIntState(int_num >> 2); |
| } |
| return; |
| } |
| |
| if (GICD_ICFGR.contains(daddr)) { |
| uint32_t ix = (daddr - GICD_ICFGR.start()) >> 2; |
| // Since the GICD_ICFGR0 is RO (WI), we are discarding the write |
| // if ix = 0 |
| if (ix != 0) |
| getIntConfig(ctx, ix) = data; |
| if (data & NN_CONFIG_MASK) |
| warn("GIC N:N mode selected and not supported at this time\n"); |
| return; |
| } |
| |
| switch(daddr) { |
| case GICD_CTLR: |
| enabled = data; |
| DPRINTF(Interrupt, "Distributor enable flag set to = %d\n", enabled); |
| break; |
| case GICD_TYPER: |
| /* 0x200 is a made-up flag to enable gem5 extension functionality. |
| * This reg is not normally written. |
| */ |
| gem5ExtensionsEnabled = (data & 0x200) && haveGem5Extensions; |
| DPRINTF(GIC, "gem5 extensions %s\n", |
| gem5ExtensionsEnabled ? "enabled" : "disabled"); |
| break; |
| case GICD_SGIR: |
| softInt(ctx, data); |
| break; |
| default: |
| panic("Tried to write Gic distributor at offset %#x\n", daddr); |
| break; |
| } |
| } |
| |
| Tick |
| GicV2::writeCpu(PacketPtr pkt) |
| { |
| const Addr daddr = pkt->getAddr() - cpuRange.start(); |
| |
| assert(pkt->req->hasContextId()); |
| const ContextID ctx = pkt->req->contextId(); |
| const uint32_t data = pkt->getLE<uint32_t>(); |
| |
| DPRINTF(GIC, "gic cpu write register cpu:%d %#x val: %#x\n", |
| ctx, daddr, data); |
| |
| writeCpu(ctx, daddr, data); |
| |
| pkt->makeAtomicResponse(); |
| return cpuPioDelay; |
| } |
| |
| void |
| GicV2::writeCpu(ContextID ctx, Addr daddr, uint32_t data) |
| { |
| switch(daddr) { |
| case GICC_CTLR: |
| cpuControl[ctx] = data; |
| break; |
| case GICC_PMR: |
| cpuPriority[ctx] = data; |
| break; |
| case GICC_BPR: { |
| auto bpr = data & 0x7; |
| if (bpr < GICC_BPR_MINIMUM) |
| bpr = GICC_BPR_MINIMUM; |
| cpuBpr[ctx] = bpr; |
| break; |
| } |
| case GICC_EOIR: { |
| const IAR iar = data; |
| if (iar.ack_id < SGI_MAX) { |
| // Clear out the bit that corresponds to the cleared int |
| uint64_t clr_int = 1ULL << (ctx + 8 * iar.cpu_id); |
| if (!(cpuSgiActive[iar.ack_id] & clr_int) && |
| !(cpuSgiActiveExt[ctx] & (1 << iar.ack_id))) |
| panic("Done handling a SGI that isn't active?\n"); |
| if (gem5ExtensionsEnabled) |
| cpuSgiActiveExt[ctx] &= ~(1 << iar.ack_id); |
| else |
| cpuSgiActive[iar.ack_id] &= ~clr_int; |
| } else if (iar.ack_id < (SGI_MAX + PPI_MAX) ) { |
| uint32_t int_num = 1 << (iar.ack_id - SGI_MAX); |
| if (!(cpuPpiActive[ctx] & int_num)) |
| warn("CPU %d Done handling a PPI interrupt " |
| "that isn't active?\n", ctx); |
| cpuPpiActive[ctx] &= ~int_num; |
| } else { |
| uint32_t int_num = 1 << intNumToBit(iar.ack_id); |
| if (!(getActiveInt(ctx, intNumToWord(iar.ack_id)) & int_num)) |
| warn("Done handling interrupt that isn't active: %d\n", |
| intNumToBit(iar.ack_id)); |
| getActiveInt(ctx, intNumToWord(iar.ack_id)) &= ~int_num; |
| } |
| updateRunPri(); |
| DPRINTF(Interrupt, "CPU %d done handling intr IAR = %d from cpu %d\n", |
| ctx, iar.ack_id, iar.cpu_id); |
| break; |
| } |
| case GICC_APR0: |
| case GICC_APR1: |
| case GICC_APR2: |
| case GICC_APR3: |
| warn("GIC APRn write ignored because not implemented: %#x\n", daddr); |
| break; |
| case GICC_DIR: |
| warn("GIC DIR write ignored because not implemented: %#x\n", daddr); |
| break; |
| default: |
| panic("Tried to write Gic cpu at offset %#x\n", daddr); |
| break; |
| } |
| if (cpuEnabled(ctx)) updateIntState(-1); |
| } |
| |
| GicV2::BankedRegs& |
| GicV2::getBankedRegs(ContextID ctx) { |
| if (bankedRegs.size() <= ctx) |
| bankedRegs.resize(ctx + 1); |
| |
| if (!bankedRegs[ctx]) |
| bankedRegs[ctx] = new BankedRegs; |
| return *bankedRegs[ctx]; |
| } |
| |
| void |
| GicV2::softInt(ContextID ctx, SWI swi) |
| { |
| if (gem5ExtensionsEnabled) { |
| switch (swi.list_type) { |
| case 0: { |
| // interrupt cpus specified |
| int dest = swi.cpu_list; |
| DPRINTF(IPI, "Generating softIRQ from CPU %d for CPU %d\n", |
| ctx, dest); |
| if (cpuEnabled(dest)) { |
| cpuSgiPendingExt[dest] |= (1 << swi.sgi_id); |
| DPRINTF(IPI, "SGI[%d]=%#x\n", dest, |
| cpuSgiPendingExt[dest]); |
| } |
| } break; |
| case 1: { |
| // interrupt all |
| for (int i = 0; i < sys->threads.size(); i++) { |
| DPRINTF(IPI, "Processing CPU %d\n", i); |
| if (!cpuEnabled(i)) |
| continue; |
| cpuSgiPendingExt[i] |= 1 << swi.sgi_id; |
| DPRINTF(IPI, "SGI[%d]=%#x\n", swi.sgi_id, |
| cpuSgiPendingExt[i]); |
| } |
| } break; |
| case 2: { |
| // Interrupt requesting cpu only |
| DPRINTF(IPI, "Generating softIRQ from CPU %d for CPU %d\n", |
| ctx, ctx); |
| if (cpuEnabled(ctx)) { |
| cpuSgiPendingExt[ctx] |= (1 << swi.sgi_id); |
| DPRINTF(IPI, "SGI[%d]=%#x\n", ctx, |
| cpuSgiPendingExt[ctx]); |
| } |
| } break; |
| } |
| } else { |
| switch (swi.list_type) { |
| case 1: |
| // interrupt all |
| uint8_t cpu_list; |
| cpu_list = 0; |
| for (int x = 0; x < sys->threads.size(); x++) |
| cpu_list |= cpuEnabled(x) ? 1 << x : 0; |
| swi.cpu_list = cpu_list; |
| break; |
| case 2: |
| // interrupt requesting cpu only |
| swi.cpu_list = 1 << ctx; |
| break; |
| // else interrupt cpus specified |
| } |
| |
| DPRINTF(IPI, "Generating softIRQ from CPU %d for %#x\n", ctx, |
| swi.cpu_list); |
| for (int i = 0; i < sys->threads.size(); i++) { |
| DPRINTF(IPI, "Processing CPU %d\n", i); |
| if (!cpuEnabled(i)) |
| continue; |
| if (swi.cpu_list & (1 << i)) |
| cpuSgiPending[swi.sgi_id] |= (1 << i) << (8 * ctx); |
| DPRINTF(IPI, "SGI[%d]=%#x\n", swi.sgi_id, |
| cpuSgiPending[swi.sgi_id]); |
| } |
| } |
| updateIntState(-1); |
| } |
| |
| uint64_t |
| GicV2::genSwiMask(int cpu) |
| { |
| panic_if(cpu > sys->threads.size(), "Invalid CPU ID."); |
| return 0x0101010101010101ULL << cpu; |
| } |
| |
| uint8_t |
| GicV2::getCpuPriority(unsigned cpu) |
| { |
| // see Table 3-2 in IHI0048B.b (GICv2) |
| // mask some low-order priority bits per BPR value |
| // NB: the GIC prioritization scheme is upside down: |
| // lower values are higher priority; masking off bits |
| // actually creates a higher priority, not lower. |
| return cpuPriority[cpu] & (0xff00 >> (7 - cpuBpr[cpu])); |
| } |
| |
| void |
| GicV2::updateIntState(int hint) |
| { |
| for (int cpu = 0; cpu < sys->threads.size(); cpu++) { |
| if (!cpuEnabled(cpu)) |
| continue; |
| |
| /*@todo use hint to do less work. */ |
| int highest_int = SPURIOUS_INT; |
| // Priorities below that set in GICC_PMR can be ignored |
| uint8_t highest_pri = getCpuPriority(cpu); |
| |
| // Check SGIs |
| for (int swi = 0; swi < SGI_MAX; swi++) { |
| if (!cpuSgiPending[swi] && !cpuSgiPendingExt[cpu]) |
| continue; |
| if ((cpuSgiPending[swi] & genSwiMask(cpu)) || |
| (cpuSgiPendingExt[cpu] & (1 << swi))) |
| if (highest_pri > getIntPriority(cpu, swi)) { |
| highest_pri = getIntPriority(cpu, swi); |
| highest_int = swi; |
| } |
| } |
| |
| // Check PPIs |
| if (cpuPpiPending[cpu]) { |
| for (int ppi_idx = 0, int_num = SGI_MAX; |
| int_num < PPI_MAX + SGI_MAX; |
| ppi_idx++, int_num++) { |
| |
| const bool ppi_pending = bits(cpuPpiPending[cpu], ppi_idx); |
| const bool ppi_enabled = bits(getIntEnabled(cpu, 0), int_num); |
| const bool higher_priority = |
| highest_pri > getIntPriority(cpu, int_num); |
| |
| if (ppi_pending && ppi_enabled && higher_priority) { |
| highest_pri = getIntPriority(cpu, int_num); |
| highest_int = int_num; |
| } |
| } |
| } |
| |
| bool mp_sys = sys->threads.numRunning() > 1; |
| // Check other ints |
| for (int x = 0; x < (itLines/INT_BITS_MAX); x++) { |
| if (getIntEnabled(cpu, x) & getPendingInt(cpu, x)) { |
| for (int y = 0; y < INT_BITS_MAX; y++) { |
| uint32_t int_nm = x * INT_BITS_MAX + y; |
| DPRINTF(GIC, "Checking for interrupt# %d \n",int_nm); |
| /* Set current pending int as highest int for current cpu |
| if the interrupt's priority higher than current priority |
| and if current cpu is the target (for mp configs only) |
| */ |
| if ((bits(getIntEnabled(cpu, x), y) |
| &bits(getPendingInt(cpu, x), y)) && |
| (getIntPriority(cpu, int_nm) < highest_pri)) |
| if ((!mp_sys) || |
| (gem5ExtensionsEnabled |
| ? (getCpuTarget(cpu, int_nm) == cpu) |
| : (getCpuTarget(cpu, int_nm) & (1 << cpu)))) { |
| highest_pri = getIntPriority(cpu, int_nm); |
| highest_int = int_nm; |
| } |
| } |
| } |
| } |
| |
| uint32_t prev_highest = cpuHighestInt[cpu]; |
| cpuHighestInt[cpu] = highest_int; |
| |
| if (highest_int == SPURIOUS_INT) { |
| if (isLevelSensitive(cpu, prev_highest)) { |
| |
| DPRINTF(Interrupt, "Clear IRQ for cpu%d\n", cpu); |
| clearInt(cpu, prev_highest); |
| } |
| continue; |
| } |
| |
| /* @todo make this work for more than one cpu, need to handle 1:N, N:N |
| * models */ |
| if (enabled && cpuEnabled(cpu) && |
| (highest_pri < getCpuPriority(cpu)) && |
| !(getActiveInt(cpu, intNumToWord(highest_int)) |
| & (1 << intNumToBit(highest_int)))) { |
| |
| DPRINTF(Interrupt, "Posting interrupt %d to cpu%d\n", highest_int, |
| cpu); |
| |
| if (isFiq(cpu, highest_int)) { |
| postFiq(cpu, curTick() + intLatency); |
| } else { |
| postInt(cpu, curTick() + intLatency); |
| } |
| } |
| } |
| } |
| |
| void |
| GicV2::updateRunPri() |
| { |
| for (int cpu = 0; cpu < sys->threads.size(); cpu++) { |
| if (!cpuEnabled(cpu)) |
| continue; |
| uint8_t maxPriority = 0xff; |
| for (int i = 0; i < itLines; i++) { |
| if (i < SGI_MAX) { |
| if (((cpuSgiActive[i] & genSwiMask(cpu)) || |
| (cpuSgiActiveExt[cpu] & (1 << i))) && |
| (getIntPriority(cpu, i) < maxPriority)) |
| maxPriority = getIntPriority(cpu, i); |
| } else if (i < (SGI_MAX + PPI_MAX)) { |
| if ((cpuPpiActive[cpu] & ( 1 << (i - SGI_MAX))) && |
| (getIntPriority(cpu, i) < maxPriority)) |
| maxPriority = getIntPriority(cpu, i); |
| |
| } else { |
| if (getActiveInt(cpu, intNumToWord(i)) |
| & (1 << intNumToBit(i))) |
| if (getIntPriority(cpu, i) < maxPriority) |
| maxPriority = getIntPriority(cpu, i); |
| } |
| } |
| iccrpr[cpu] = maxPriority; |
| } |
| } |
| |
| void |
| GicV2::sendInt(uint32_t num) |
| { |
| uint8_t target = getCpuTarget(0, num); |
| DPRINTF(Interrupt, "Received Interrupt number %d, cpuTarget %#x: \n", |
| num, target); |
| if ((target & (target - 1)) && !gem5ExtensionsEnabled) |
| panic("Multiple targets for peripheral interrupts is not supported\n"); |
| panic_if(num < SGI_MAX + PPI_MAX, |
| "sentInt() must only be used for interrupts 32 and higher"); |
| getPendingInt(target, intNumToWord(num)) |= 1 << intNumToBit(num); |
| updateIntState(intNumToWord(num)); |
| } |
| |
| void |
| GicV2::sendPPInt(uint32_t num, uint32_t cpu) |
| { |
| DPRINTF(Interrupt, "Received PPI %d, cpuTarget %#x: \n", |
| num, cpu); |
| cpuPpiPending[cpu] |= 1 << (num - SGI_MAX); |
| updateIntState(intNumToWord(num)); |
| } |
| |
| void |
| GicV2::clearInt(uint32_t num) |
| { |
| if (isLevelSensitive(0, num)) { |
| uint8_t target = getCpuTarget(0, num); |
| |
| DPRINTF(Interrupt, |
| "Received Clear interrupt number %d, cpuTarget %#x:\n", |
| num, target); |
| |
| getPendingInt(target, intNumToWord(num)) &= ~(1 << intNumToBit(num)); |
| updateIntState(intNumToWord(num)); |
| } else { |
| /* Nothing to do : |
| * Edge-triggered interrupt remain pending until software |
| * writes GICD_ICPENDR or reads GICC_IAR */ |
| } |
| } |
| |
| void |
| GicV2::clearPPInt(uint32_t num, uint32_t cpu) |
| { |
| if (isLevelSensitive(cpu, num)) { |
| DPRINTF(Interrupt, "Clearing PPI %d, cpuTarget %#x: \n", |
| num, cpu); |
| cpuPpiPending[cpu] &= ~(1 << (num - SGI_MAX)); |
| updateIntState(intNumToWord(num)); |
| } else { |
| /* Nothing to do : |
| * Edge-triggered interrupt remain pending until software |
| * writes GICD_ICPENDR or reads GICC_IAR */ |
| } |
| } |
| |
| void |
| GicV2::clearInt(ContextID ctx, uint32_t int_num) |
| { |
| auto tc = sys->threads[ctx]; |
| if (isFiq(ctx, int_num)) { |
| tc->getCpuPtr()->clearInterrupt(tc->threadId(), ArmISA::INT_FIQ, 0); |
| } else { |
| tc->getCpuPtr()->clearInterrupt(tc->threadId(), ArmISA::INT_IRQ, 0); |
| } |
| } |
| |
| void |
| GicV2::postInt(uint32_t cpu, Tick when) |
| { |
| if (!(postIntEvent[cpu]->scheduled())) { |
| ++pendingDelayedInterrupts; |
| eventq->schedule(postIntEvent[cpu], when); |
| } |
| } |
| |
| void |
| GicV2::postDelayedInt(uint32_t cpu) |
| { |
| auto tc = sys->threads[cpu]; |
| tc->getCpuPtr()->postInterrupt(tc->threadId(), ArmISA::INT_IRQ, 0); |
| --pendingDelayedInterrupts; |
| assert(pendingDelayedInterrupts >= 0); |
| if (pendingDelayedInterrupts == 0) |
| signalDrainDone(); |
| } |
| |
| void |
| GicV2::postFiq(uint32_t cpu, Tick when) |
| { |
| if (!(postFiqEvent[cpu]->scheduled())) { |
| ++pendingDelayedInterrupts; |
| eventq->schedule(postFiqEvent[cpu], when); |
| } |
| } |
| |
| bool |
| GicV2::supportsVersion(GicVersion version) |
| { |
| return version == GicVersion::GIC_V2; |
| } |
| |
| void |
| GicV2::postDelayedFiq(uint32_t cpu) |
| { |
| auto tc = sys->threads[cpu]; |
| tc->getCpuPtr()->postInterrupt(tc->threadId(), ArmISA::INT_FIQ, 0); |
| --pendingDelayedInterrupts; |
| assert(pendingDelayedInterrupts >= 0); |
| if (pendingDelayedInterrupts == 0) |
| signalDrainDone(); |
| } |
| |
| DrainState |
| GicV2::drain() |
| { |
| if (pendingDelayedInterrupts == 0) { |
| return DrainState::Drained; |
| } else { |
| return DrainState::Draining; |
| } |
| } |
| |
| |
| void |
| GicV2::drainResume() |
| { |
| // There may be pending interrupts if checkpointed from Kvm; post them. |
| updateIntState(-1); |
| } |
| |
| void |
| GicV2::serialize(CheckpointOut &cp) const |
| { |
| DPRINTF(Checkpoint, "Serializing Arm GIC\n"); |
| |
| SERIALIZE_SCALAR(enabled); |
| SERIALIZE_SCALAR(itLines); |
| SERIALIZE_ARRAY(intEnabled, INT_BITS_MAX-1); |
| SERIALIZE_ARRAY(pendingInt, INT_BITS_MAX-1); |
| SERIALIZE_ARRAY(activeInt, INT_BITS_MAX-1); |
| SERIALIZE_ARRAY(intGroup, INT_BITS_MAX-1); |
| SERIALIZE_ARRAY(iccrpr, CPU_MAX); |
| SERIALIZE_ARRAY(intPriority, GLOBAL_INT_LINES); |
| SERIALIZE_ARRAY(cpuTarget, GLOBAL_INT_LINES); |
| SERIALIZE_ARRAY(intConfig, INT_BITS_MAX * 2 - 2); |
| SERIALIZE_ARRAY(cpuControl, CPU_MAX); |
| SERIALIZE_ARRAY(cpuPriority, CPU_MAX); |
| SERIALIZE_ARRAY(cpuBpr, CPU_MAX); |
| SERIALIZE_ARRAY(cpuHighestInt, CPU_MAX); |
| SERIALIZE_ARRAY(cpuSgiActive, SGI_MAX); |
| SERIALIZE_ARRAY(cpuSgiPending, SGI_MAX); |
| SERIALIZE_ARRAY(cpuSgiActiveExt, CPU_MAX); |
| SERIALIZE_ARRAY(cpuSgiPendingExt, CPU_MAX); |
| SERIALIZE_ARRAY(cpuPpiActive, CPU_MAX); |
| SERIALIZE_ARRAY(cpuPpiPending, CPU_MAX); |
| SERIALIZE_SCALAR(gem5ExtensionsEnabled); |
| |
| for (uint32_t i=0; i < bankedRegs.size(); ++i) { |
| if (!bankedRegs[i]) |
| continue; |
| bankedRegs[i]->serializeSection(cp, csprintf("bankedRegs%i", i)); |
| } |
| } |
| |
| void |
| GicV2::BankedRegs::serialize(CheckpointOut &cp) const |
| { |
| SERIALIZE_SCALAR(intEnabled); |
| SERIALIZE_SCALAR(pendingInt); |
| SERIALIZE_SCALAR(activeInt); |
| SERIALIZE_SCALAR(intGroup); |
| SERIALIZE_ARRAY(intConfig, 2); |
| SERIALIZE_ARRAY(intPriority, SGI_MAX + PPI_MAX); |
| } |
| |
| void |
| GicV2::unserialize(CheckpointIn &cp) |
| { |
| DPRINTF(Checkpoint, "Unserializing Arm GIC\n"); |
| |
| UNSERIALIZE_SCALAR(enabled); |
| UNSERIALIZE_SCALAR(itLines); |
| UNSERIALIZE_ARRAY(intEnabled, INT_BITS_MAX-1); |
| UNSERIALIZE_ARRAY(pendingInt, INT_BITS_MAX-1); |
| UNSERIALIZE_ARRAY(activeInt, INT_BITS_MAX-1); |
| UNSERIALIZE_ARRAY(intGroup, INT_BITS_MAX-1); |
| UNSERIALIZE_ARRAY(iccrpr, CPU_MAX); |
| UNSERIALIZE_ARRAY(intPriority, GLOBAL_INT_LINES); |
| UNSERIALIZE_ARRAY(cpuTarget, GLOBAL_INT_LINES); |
| UNSERIALIZE_ARRAY(intConfig, INT_BITS_MAX * 2 - 2); |
| UNSERIALIZE_ARRAY(cpuControl, CPU_MAX); |
| UNSERIALIZE_ARRAY(cpuPriority, CPU_MAX); |
| UNSERIALIZE_ARRAY(cpuBpr, CPU_MAX); |
| UNSERIALIZE_ARRAY(cpuHighestInt, CPU_MAX); |
| UNSERIALIZE_ARRAY(cpuSgiActive, SGI_MAX); |
| UNSERIALIZE_ARRAY(cpuSgiPending, SGI_MAX); |
| UNSERIALIZE_ARRAY(cpuSgiActiveExt, CPU_MAX); |
| UNSERIALIZE_ARRAY(cpuSgiPendingExt, CPU_MAX); |
| UNSERIALIZE_ARRAY(cpuPpiActive, CPU_MAX); |
| UNSERIALIZE_ARRAY(cpuPpiPending, CPU_MAX); |
| |
| // Handle checkpoints from before we drained the GIC to prevent |
| // in-flight interrupts. |
| if (cp.entryExists(Serializable::currentSection(), "interrupt_time")) { |
| Tick interrupt_time[CPU_MAX]; |
| UNSERIALIZE_ARRAY(interrupt_time, CPU_MAX); |
| |
| for (uint32_t cpu = 0; cpu < CPU_MAX; cpu++) { |
| if (interrupt_time[cpu]) |
| schedule(postIntEvent[cpu], interrupt_time[cpu]); |
| } |
| } |
| |
| if (!UNSERIALIZE_OPT_SCALAR(gem5ExtensionsEnabled)) |
| gem5ExtensionsEnabled = false; |
| |
| for (uint32_t i=0; i < CPU_MAX; ++i) { |
| ScopedCheckpointSection sec(cp, csprintf("bankedRegs%i", i)); |
| if (cp.sectionExists(Serializable::currentSection())) { |
| getBankedRegs(i).unserialize(cp); |
| } |
| } |
| } |
| |
| void |
| GicV2::BankedRegs::unserialize(CheckpointIn &cp) |
| { |
| UNSERIALIZE_SCALAR(intEnabled); |
| UNSERIALIZE_SCALAR(pendingInt); |
| UNSERIALIZE_SCALAR(activeInt); |
| UNSERIALIZE_SCALAR(intGroup); |
| UNSERIALIZE_ARRAY(intConfig, 2); |
| UNSERIALIZE_ARRAY(intPriority, SGI_MAX + PPI_MAX); |
| } |
| |
| } // namespace gem5 |