/*
 * Copyright (c) 2013 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.
 *
 * 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: Matt Evans
 */


/** @file
 * Implementiation of a GIC-400 List Register-based VGIC interface.
 * The VGIC is, in this implementation, completely separate from the GIC itself.
 * Only a VIRQ line to the CPU and a PPI line to the GIC (for a HV maintenance IRQ)
 * is required.
 *
 * The mode in which the List Registers may flag (via LR.HW) that a hardware EOI
 * is to be performed is NOT supported.  (This requires tighter integration with
 * the GIC.)
 */

#ifndef __DEV_ARM_VGIC_H__
#define __DEV_ARM_VGIC_H__

#include <algorithm>
#include <array>

#include "base/addr_range.hh"
#include "base/bitunion.hh"
#include "cpu/intr_control.hh"
#include "dev/io_device.hh"
#include "dev/platform.hh"
#include "params/VGic.hh"

class VGic : public PioDevice
{
  private:
    static const int VGIC_CPU_MAX       = 256;
    static const int NUM_LR             = 4;

    static const int GICH_SIZE          = 0x200;
    static const int GICH_REG_SIZE      = 0x2000;

    static const int GICH_HCR           = 0x000;
    static const int GICH_VTR           = 0x004;
    static const int GICH_VMCR          = 0x008;
    static const int GICH_MISR          = 0x010;
    static const int GICH_EISR0         = 0x020;
    static const int GICH_EISR1         = 0x024;
    static const int GICH_ELSR0         = 0x030;
    static const int GICH_ELSR1         = 0x034;
    static const int GICH_APR0          = 0x0f0;
    static const int GICH_LR0           = 0x100;
    static const int GICH_LR1           = 0x104;
    static const int GICH_LR2           = 0x108;
    static const int GICH_LR3           = 0x10c;

    static const int GICV_SIZE          = 0x2000;
    static const int GICV_CTLR          = 0x000;
    static const int GICV_PMR           = 0x004;
    static const int GICV_BPR           = 0x008;
    static const int GICV_IAR           = 0x00c;
    static const int GICV_EOIR          = 0x010;
    static const int GICV_RPR           = 0x014;
    static const int GICV_HPPIR         = 0x018;
    static const int GICV_ABPR          = 0x01c;
    static const int GICV_AIAR          = 0x020;
    static const int GICV_AEOIR         = 0x024;
    static const int GICV_AHPPIR        = 0x028;
    static const int GICV_APR0          = 0x0d0;
    static const int GICV_IIDR          = 0x0fc;
    static const int GICV_DIR           = 0x1000;

    static const uint32_t LR_PENDING    = 1;
    static const uint32_t LR_ACTIVE     = 2;

    /** Event definition to post interrupt to CPU after a delay
    */
    class PostVIntEvent : public Event
    {
      private:
        uint32_t cpu;
        Platform *platform;
      public:
        PostVIntEvent( uint32_t c, Platform* p)
            : cpu(c), platform(p)
        { }
        void process() { platform->intrctrl->post(cpu, ArmISA::INT_VIRT_IRQ, 0);}
        const char *description() const { return "Post VInterrupt to CPU"; }
    };

    PostVIntEvent *postVIntEvent[VGIC_CPU_MAX];
    bool        maintIntPosted[VGIC_CPU_MAX];
    bool        vIntPosted[VGIC_CPU_MAX];

    Platform *platform;
    BaseGic *gic;

    Addr vcpuAddr;
    Addr hvAddr;
    Tick pioDelay;
    int maintInt;

    BitUnion32(ListReg)
    Bitfield<31> HW;
    Bitfield<30> Grp1;
    Bitfield<29,28> State;
    Bitfield<27,23> Priority;
    Bitfield<19> EOI;
    Bitfield<12,10> CpuID;
    Bitfield<9,0> VirtualID;
    EndBitUnion(ListReg)

    BitUnion32(HCR)
    Bitfield<31,27> EOICount;
    Bitfield<7> VGrp1DIE;
    Bitfield<6> VGrp1EIE;
    Bitfield<5> VGrp0DIE;
    Bitfield<4> VGrp0EIE;
    Bitfield<3> NPIE;
    Bitfield<2> LRENPIE;
    Bitfield<1> UIE;
    Bitfield<0> En;
    EndBitUnion(HCR)

    BitUnion32(VCTLR)
    Bitfield<9> EOImode;
    Bitfield<4> CPBR;
    Bitfield<3> FIQEn;
    Bitfield<2> AckCtl;
    Bitfield<1> EnGrp1;
    Bitfield<0> En;     // This gets written to enable, not group 1.
    EndBitUnion(VCTLR)

    /* State per CPU.  EVERYTHING should be in this struct and simply replicated
     * N times.
     */
    struct vcpuIntData : public Serializable {
        vcpuIntData()
            : vctrl(0), hcr(0), eisr(0), VMGrp0En(0), VMGrp1En(0),
              VMAckCtl(0), VMFiqEn(0), VMCBPR(0), VEM(0), VMABP(0), VMBP(0),
              VMPriMask(0)
        {
            std::fill(LR.begin(), LR.end(), 0);
        }
        virtual ~vcpuIntData() {}

        std::array<ListReg, NUM_LR> LR;
        VCTLR vctrl;

        HCR hcr;
        uint64_t eisr;

        /* Host info, guest info (should be 100% accessible via GICH_* regs!) */
        uint8_t VMGrp0En;
        uint8_t VMGrp1En;
        uint8_t VMAckCtl;
        uint8_t VMFiqEn;
        uint8_t VMCBPR;
        uint8_t VEM;
        uint8_t VMABP;
        uint8_t VMBP;
        uint8_t VMPriMask;

        void serialize(CheckpointOut &cp) const override;
        void unserialize(CheckpointIn &cp) override;
    };

    struct std::array<vcpuIntData, VGIC_CPU_MAX>  vcpuData;

  public:
   typedef VGicParams Params;
   const Params *
    params() const
    {
        return dynamic_cast<const Params *>(_params);
    }
    VGic(const Params *p);

    AddrRangeList getAddrRanges() const override;

    Tick read(PacketPtr pkt) override;
    Tick write(PacketPtr pkt) override;

    void serialize(CheckpointOut &cp) const override;
    void unserialize(CheckpointIn &cp) override;

  private:
    Tick readVCpu(PacketPtr pkt);
    Tick readCtrl(PacketPtr pkt);

    Tick writeVCpu(PacketPtr pkt);
    Tick writeCtrl(PacketPtr pkt);

    void updateIntState(ContextID ctx_id);
    uint32_t getMISR(struct vcpuIntData *vid);
    void postVInt(uint32_t cpu, Tick when);
    void unPostVInt(uint32_t cpu);
    void postMaintInt(uint32_t cpu);
    void unPostMaintInt(uint32_t cpu);

    unsigned int lrPending(struct vcpuIntData *vid)
    {
        unsigned int pend = 0;
        for (int i = 0; i < NUM_LR; i++) {
            if (vid->LR[i].State & LR_PENDING)
                pend++;
        }
        return pend;
    }
    unsigned int lrValid(struct vcpuIntData *vid)
    {
        unsigned int valid = 0;
        for (int i = 0; i < NUM_LR; i++) {
            if (vid->LR[i].State)
                valid++;
        }
        return valid;
    }

    /** Returns LR index or -1 if none pending */
    int findHighestPendingLR(struct vcpuIntData *vid)
    {
        unsigned int prio = 0xff;
        int p = -1;
        for (int i = 0; i < NUM_LR; i++) {
            if ((vid->LR[i].State & LR_PENDING) && (vid->LR[i].Priority < prio)) {
                p = i;
                prio = vid->LR[i].Priority;
            }
        }
        return p;
    }

    int findLRForVIRQ(struct vcpuIntData *vid, int virq, int vcpu)
    {
        for (int i = 0; i < NUM_LR; i++) {
            if (vid->LR[i].State &&
                vid->LR[i].VirtualID == virq &&
                vid->LR[i].CpuID == vcpu)
                return i;
        }
        return -1;
    }
};

#endif