/*
 * Copyright (c) 2010, 2013, 2015-2017 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.
 *
 * Authors: Ali Saidi
 *          Prakash Ramrakhyani
 */

#include "dev/arm/gic_pl390.hh"

#include "base/trace.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"

const AddrRange Pl390::GICD_IGROUPR   (0x080, 0x0ff);
const AddrRange Pl390::GICD_ISENABLER (0x100, 0x17f);
const AddrRange Pl390::GICD_ICENABLER (0x180, 0x1ff);
const AddrRange Pl390::GICD_ISPENDR   (0x200, 0x27f);
const AddrRange Pl390::GICD_ICPENDR   (0x280, 0x2ff);
const AddrRange Pl390::GICD_ISACTIVER (0x300, 0x37f);
const AddrRange Pl390::GICD_ICACTIVER (0x380, 0x3ff);
const AddrRange Pl390::GICD_IPRIORITYR(0x400, 0x7ff);
const AddrRange Pl390::GICD_ITARGETSR (0x800, 0xbff);
const AddrRange Pl390::GICD_ICFGR     (0xc00, 0xcff);

Pl390::Pl390(const Params *p)
    : BaseGic(p),
      distRange(RangeSize(p->dist_addr, DIST_SIZE)),
      cpuRange(RangeSize(p->cpu_addr, 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 {},
      intPriority {}, cpuTarget {}, intConfig {},
      cpuSgiPending {}, cpuSgiActive {},
      cpuSgiPendingExt {}, cpuSgiActiveExt {},
      cpuPpiPending {}, cpuPpiActive {},
      irqEnable(false),
      pendingDelayedInterrupts(0)
{
    for (int x = 0; x < CPU_MAX; x++) {
        iccrpr[x] = 0xff;
        cpuEnabled[x] = false;
        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");
    }
    DPRINTF(Interrupt, "cpuEnabled[0]=%d cpuEnabled[1]=%d\n", cpuEnabled[0],
            cpuEnabled[1]);

    gem5ExtensionsEnabled = false;
}

Pl390::~Pl390()
{
    for (int x = 0; x < CPU_MAX; x++)
        delete postIntEvent[x];
}

Tick
Pl390::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
Pl390::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
Pl390::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->set<uint8_t>(resp);
        break;
      case 2:
        pkt->set<uint16_t>(resp);
        break;
      case 4:
        pkt->set<uint32_t>(resp);
        break;
      default:
        panic("Invalid size while reading Distributor regs in GIC: %d\n",
               pkt->getSize());
    }

    pkt->makeAtomicResponse();
    return distPioDelay;
}

uint32_t
Pl390::readDistributor(ContextID ctx, Addr daddr, size_t resp_sz)
{
    if (GICD_IGROUPR.contains(daddr)) {
        return 0; // unimplemented; RAZ (read as zero)
    }

    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;
        assert(ix < 64);
        /** @todo software generated interrupts and PPIs
         * can't be configured in some ways */
        return intConfig[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->numRunningContexts() - 1) << 5) |
                (itLines/INT_BITS_MAX -1) |
                (haveGem5Extensions ? 0x100 : 0x0));
      case GICD_PIDR0:
        //ARM defined DevID
        return (GICD_400_PIDR_VALUE & 0xFF);
      case GICD_PIDR1:
        return ((GICD_400_PIDR_VALUE >> 8) & 0xFF);
      case GICD_PIDR2:
        return ((GICD_400_PIDR_VALUE >> 16) & 0xFF);
      case GICD_PIDR3:
        return ((GICD_400_PIDR_VALUE >> 24) & 0xFF);
      case GICD_IIDR:
         /* revision id is resorted to 1 and variant to 0*/
        return GICD_400_IIDR_VALUE;
      default:
        panic("Tried to read Gic distributor at offset %#x\n", daddr);
        break;
    }
}

Tick
Pl390::readCpu(PacketPtr pkt)
{
    const Addr daddr = pkt->getAddr() - cpuRange.start();

    assert(pkt->req->hasContextId());
    const ContextID ctx = pkt->req->contextId();
    assert(ctx < sys->numRunningContexts());

    DPRINTF(GIC, "gic cpu read register %#x cpu context: %d\n", daddr,
            ctx);

    pkt->set<uint32_t>(readCpu(ctx, daddr));

    pkt->makeAtomicResponse();
    return cpuPioDelay;
}

uint32_t
Pl390::readCpu(ContextID ctx, Addr daddr)
{
    switch(daddr) {
      case GICC_IIDR:
        return GICC_400_IIDR_VALUE;
      case GICC_CTLR:
        return cpuEnabled[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->numRunningContexts(); 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 = ULL(1) << (ctx + 8 * iar.cpu_id);
                    cpuSgiActive[iar.ack_id] |= sgi_num;
                    cpuSgiPending[iar.ack_id] &= ~sgi_num;
                } else {
                    uint64_t sgi_num = ULL(1) << 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();
                cpuPpiPending[ctx] &= ~int_num;

            } else {
                uint32_t int_num = 1 << intNumToBit(cpuHighestInt[ctx]);
                getActiveInt(ctx, intNumToWord(cpuHighestInt[ctx])) |= int_num;
                updateRunPri();
                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);
            platform->intrctrl->clear(ctx, ArmISA::INT_IRQ, 0);
            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
Pl390::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();

    uint32_t pkt_data M5_VAR_USED;
    switch (data_sz)
    {
      case 1:
        pkt_data = pkt->get<uint8_t>();
        break;
      case 2:
        pkt_data = pkt->get<uint16_t>();
        break;
      case 4:
        pkt_data = pkt->get<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
Pl390::writeDistributor(ContextID ctx, Addr daddr, uint32_t data,
                        size_t data_sz)
{
    if (GICD_IGROUPR.contains(daddr)) {
        return; // unimplemented; WI (writes ignored)
    }

    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;
        assert(ix < INT_BITS_MAX*2);
        intConfig[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
Pl390::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->get<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
Pl390::writeCpu(ContextID ctx, Addr daddr, uint32_t data)
{
    switch(daddr) {
      case GICC_CTLR:
        cpuEnabled[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 = ULL(1) << (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))
                panic("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;
      }
      default:
        panic("Tried to write Gic cpu at offset %#x\n", daddr);
        break;
    }
    if (cpuEnabled[ctx]) updateIntState(-1);
}

Pl390::BankedRegs&
Pl390::getBankedRegs(ContextID ctx) {
    if (bankedRegs.size() <= ctx)
        bankedRegs.resize(ctx + 1);

    if (!bankedRegs[ctx])
        bankedRegs[ctx] = new BankedRegs;
    return *bankedRegs[ctx];
}

void
Pl390::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->numContexts(); 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->numContexts(); 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->numContexts(); 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
Pl390::genSwiMask(int cpu)
{
    if (cpu > sys->numContexts())
        panic("Invalid CPU ID\n");
    return ULL(0x0101010101010101) << cpu;
}

uint8_t
Pl390::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
Pl390::updateIntState(int hint)
{
    for (int cpu = 0; cpu < sys->numContexts(); 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 = 0; ppi < PPI_MAX; ppi++) {
            if (cpuPpiPending[cpu] & (1 << ppi))
                if (highest_pri > getIntPriority(cpu, SGI_MAX + ppi)) {
                    highest_pri = getIntPriority(cpu, SGI_MAX + ppi);
                    highest_int = SGI_MAX + ppi;
                }
            }
        }

        bool mp_sys = sys->numRunningContexts() > 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;
                        }
                }
            }
        }

        cpuHighestInt[cpu] = highest_int;

        if (highest_int == SPURIOUS_INT)
            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);
            postInt(cpu, curTick() + intLatency);
        }
    }
}

void
Pl390::updateRunPri()
{
    for (int cpu = 0; cpu < sys->numContexts(); 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
Pl390::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
Pl390::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
Pl390::clearInt(uint32_t number)
{
    /* @todo assume edge triggered only at the moment. Nothing to do. */
}

void
Pl390::clearPPInt(uint32_t num, uint32_t cpu)
{
    DPRINTF(Interrupt, "Clearing PPI %d, cpuTarget %#x: \n",
            num, cpu);
    cpuPpiPending[cpu] &= ~(1 << (num - SGI_MAX));
    updateIntState(intNumToWord(num));
}

void
Pl390::postInt(uint32_t cpu, Tick when)
{
    if (!(postIntEvent[cpu]->scheduled())) {
        ++pendingDelayedInterrupts;
        eventq->schedule(postIntEvent[cpu], when);
    }
}

void
Pl390::postDelayedInt(uint32_t cpu)
{
    platform->intrctrl->post(cpu, ArmISA::INT_IRQ, 0);
    --pendingDelayedInterrupts;
    assert(pendingDelayedInterrupts >= 0);
    if (pendingDelayedInterrupts == 0)
        signalDrainDone();
}

DrainState
Pl390::drain()
{
    if (pendingDelayedInterrupts == 0) {
        return DrainState::Drained;
    } else {
        return DrainState::Draining;
    }
}


void
Pl390::drainResume()
{
    // There may be pending interrupts if checkpointed from Kvm; post them.
    updateIntState(-1);
}

void
Pl390::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(iccrpr, CPU_MAX);
    SERIALIZE_ARRAY(intPriority, GLOBAL_INT_LINES);
    SERIALIZE_ARRAY(cpuTarget, GLOBAL_INT_LINES);
    SERIALIZE_ARRAY(intConfig, INT_BITS_MAX * 2);
    SERIALIZE_ARRAY(cpuEnabled, 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(irqEnable);
    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
Pl390::BankedRegs::serialize(CheckpointOut &cp) const
{
    SERIALIZE_SCALAR(intEnabled);
    SERIALIZE_SCALAR(pendingInt);
    SERIALIZE_SCALAR(activeInt);
    SERIALIZE_ARRAY(intPriority, SGI_MAX + PPI_MAX);
}

void
Pl390::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(iccrpr, CPU_MAX);
    UNSERIALIZE_ARRAY(intPriority, GLOBAL_INT_LINES);
    UNSERIALIZE_ARRAY(cpuTarget, GLOBAL_INT_LINES);
    UNSERIALIZE_ARRAY(intConfig, INT_BITS_MAX * 2);
    UNSERIALIZE_ARRAY(cpuEnabled, 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);
    UNSERIALIZE_SCALAR(irqEnable);

    // 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
Pl390::BankedRegs::unserialize(CheckpointIn &cp)
{
    UNSERIALIZE_SCALAR(intEnabled);
    UNSERIALIZE_SCALAR(pendingInt);
    UNSERIALIZE_SCALAR(activeInt);
    UNSERIALIZE_ARRAY(intPriority, SGI_MAX + PPI_MAX);
}

Pl390 *
Pl390Params::create()
{
    return new Pl390(this);
}

/* Functions for debugging and testing */
void
Pl390::driveSPI(uint32_t spiVect)
{
    DPRINTF(GIC, "Received SPI Vector:%x Enable: %d\n", spiVect, irqEnable);
    getPendingInt(0, 1) |= spiVect;
    if (irqEnable && enabled) {
        updateIntState(-1);
    }
}

void
Pl390::driveIrqEn( bool state)
{
    irqEnable = state;
    DPRINTF(GIC, " Enabling Irq\n");
    updateIntState(-1);
}

void
Pl390::driveLegIRQ(bool state)
{
    if (irqEnable && !(!enabled && cpuEnabled[0])) {
        if (state) {
            DPRINTF(GIC, "Driving Legacy Irq\n");
            platform->intrctrl->post(0, ArmISA::INT_IRQ, 0);
        }
        else platform->intrctrl->clear(0, ArmISA::INT_IRQ, 0);
    }
}

void
Pl390::driveLegFIQ(bool state)
{
    if (state)
        platform->intrctrl->post(0, ArmISA::INT_FIQ, 0);
    else platform->intrctrl->clear(0, ArmISA::INT_FIQ, 0);
}