/*
 * Copyright (c) 2019 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: Giacomo Travaglini
 */

#ifndef __DEV_ARM_GICV3_ITS_H__
#define __DEV_ARM_GICV3_ITS_H__

#include <queue>

#include "base/coroutine.hh"
#include "dev/dma_device.hh"
#include "params/Gicv3Its.hh"

class Gicv3;
class Gicv3Redistributor;
class ItsProcess;
class ItsTranslation;
class ItsCommand;

enum class ItsActionType
{
    INITIAL_NOP,
    SEND_REQ,
    TERMINATE,
};

struct ItsAction
{
    ItsActionType type;
    PacketPtr pkt;
    Tick delay;
};

/**
 * GICv3 ITS module. This class is just modelling a pio device with its
 * memory mapped registers. Most of the ITS functionalities are
 * implemented as processes (ItsProcess) objects, like ItsTranslation or
 * ItsCommand.
 * Main job of Gicv3Its is to spawn those processes upon receival of packets.
 */
class Gicv3Its : public BasicPioDevice
{
    friend class ::ItsProcess;
    friend class ::ItsTranslation;
    friend class ::ItsCommand;
  public:
    class DataPort : public MasterPort
    {
      protected:
        Gicv3Its &its;

      public:
        DataPort(const std::string &_name, Gicv3Its &_its) :
            MasterPort(_name, &_its),
            its(_its)
        {}

        virtual ~DataPort() {}

        bool recvTimingResp(PacketPtr pkt) { return its.recvTimingResp(pkt); }
        void recvReqRetry() { return its.recvReqRetry(); }
    };

    DataPort dmaPort;

    Port & getPort(const std::string &if_name, PortID idx) override;
    bool recvTimingResp(PacketPtr pkt);
    void recvReqRetry();

    Gicv3Its(const Gicv3ItsParams *params);

    void setGIC(Gicv3 *_gic);

    static const uint32_t itsControl = 0x0;
    static const uint32_t itsTranslate = 0x10000;

    // Address range part of Control frame
    static const AddrRange GITS_BASER;

    static const uint32_t NUM_BASER_REGS = 8;

    enum : Addr
    {
        // Control frame
        GITS_CTLR    = itsControl + 0x0000,
        GITS_IIDR    = itsControl + 0x0004,
        GITS_TYPER   = itsControl + 0x0008,
        GITS_CBASER  = itsControl + 0x0080,
        GITS_CWRITER = itsControl + 0x0088,
        GITS_CREADR  = itsControl + 0x0090,

        // Translation frame
        GITS_TRANSLATER = itsTranslate + 0x0040
    };

    AddrRangeList getAddrRanges() const override;

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

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

    void translate(PacketPtr pkt);

    BitUnion32(CTLR)
        Bitfield<31> quiescent;
        Bitfield<7, 4> itsNumber;
        Bitfield<1> imDe;
        Bitfield<0> enabled;
    EndBitUnion(CTLR)

    // Command read/write, (CREADR, CWRITER)
    BitUnion64(CRDWR)
        Bitfield<19, 5> offset;
        Bitfield<0> retry;
        Bitfield<0> stalled;
    EndBitUnion(CRDWR)

    BitUnion64(CBASER)
        Bitfield<63> valid;
        Bitfield<61, 59> innerCache;
        Bitfield<55, 53> outerCache;
        Bitfield<51, 12> physAddr;
        Bitfield<11, 10> shareability;
        Bitfield<7, 0> size;
    EndBitUnion(CBASER)

    BitUnion64(BASER)
        Bitfield<63> valid;
        Bitfield<62> indirect;
        Bitfield<61, 59> innerCache;
        Bitfield<58, 56> type;
        Bitfield<55, 53> outerCache;
        Bitfield<52, 48> entrySize;
        Bitfield<47, 12> physAddr;
        Bitfield<11, 10> shareability;
        Bitfield<9, 8> pageSize;
        Bitfield<7, 0> size;
    EndBitUnion(BASER)

    BitUnion64(TYPER)
        Bitfield<37> vmovp;
        Bitfield<36> cil;
        Bitfield<35, 32> cidBits;
        Bitfield<31, 24> hcc;
        Bitfield<19> pta;
        Bitfield<18> seis;
        Bitfield<17, 13> devBits;
        Bitfield<12, 8> idBits;
        Bitfield<7, 4> ittEntrySize;
        Bitfield<2> cct;
        Bitfield<1> _virtual;
        Bitfield<0> physical;
    EndBitUnion(TYPER)

    CTLR     gitsControl;
    TYPER    gitsTyper;
    CBASER   gitsCbaser;
    CRDWR    gitsCreadr;
    CRDWR    gitsCwriter;
    uint32_t gitsIidr;
    uint32_t gitsTranslater;

    std::vector<BASER> tableBases;

    /**
     * Returns TRUE if the eventID supplied has bits above the implemented
     * size or above the itt_range
     */
    bool idOutOfRange(uint32_t event_id, uint8_t itt_range) const;

    /**
     * Returns TRUE if the value supplied has bits above the implemented range
     * or if the value supplied exceeds the maximum configured size in the
     * appropriate GITS_BASER<n>
     */
    bool deviceOutOfRange(uint32_t device_id) const;

    /**
     * Returns TRUE if the value (size) supplied exceeds the maximum
     * allowed by GITS_TYPER.ID_bits. Size is the parameter which is
     * passed to the ITS via the MAPD command and is stored in the
     * DTE.ittRange field.
     */
    bool sizeOutOfRange(uint32_t size) const;

    /**
     * Returns TRUE if the value supplied has bits above the implemented range
     * or if the value exceeds the total number of collections supported in
     * hardware and external memory
     */
    bool collectionOutOfRange(uint32_t collection_id) const;

    /**
     * Returns TRUE if the value supplied is larger than that permitted by
     * GICD_TYPER.IDbits or not in the LPI range and is not 1023
     */
    bool lpiOutOfRange(uint32_t intid) const;

  private: // Command
    void checkCommandQueue();
    void incrementReadPointer();

  public: // TableWalk
    BitUnion64(DTE)
        Bitfield<57, 53> ittRange;
        Bitfield<52, 1> ittAddress;
        Bitfield<0> valid;
    EndBitUnion(DTE)

    BitUnion64(ITTE)
        Bitfield<59, 46> vpeid;
        Bitfield<45, 30> icid;
        Bitfield<29, 16> intNumHyp;
        Bitfield<15, 2> intNum;
        Bitfield<1> intType;
        Bitfield<0> valid;
    EndBitUnion(ITTE)

    BitUnion64(CTE)
        Bitfield<40, 1> rdBase;
        Bitfield<0> valid;
    EndBitUnion(CTE)

    enum InterruptType
    {
        VIRTUAL_INTERRUPT = 0,
        PHYSICAL_INTERRUPT = 1
    };

  private:
    Gicv3Redistributor* getRedistributor(uint64_t rd_base);
    Gicv3Redistributor* getRedistributor(CTE cte)
    {
        return getRedistributor(cte.rdBase);
    }

    ItsAction runProcess(ItsProcess *proc, PacketPtr pkt);
    ItsAction runProcessTiming(ItsProcess *proc, PacketPtr pkt);
    ItsAction runProcessAtomic(ItsProcess *proc, PacketPtr pkt);

    enum ItsTables
    {
        DEVICE_TABLE = 1,
        VPE_TABLE = 2,
        TRANSLATION_TABLE = 3,
        COLLECTION_TABLE = 4
    };

    enum PageSize
    {
        SIZE_4K,
        SIZE_16K,
        SIZE_64K
    };

    Addr pageAddress(enum ItsTables table);

    void moveAllPendingState(
        Gicv3Redistributor *rd1, Gicv3Redistributor *rd2);

  private:
    std::queue<ItsAction> packetsToRetry;
    uint32_t masterId;
    Gicv3 *gic;
    EventFunctionWrapper commandEvent;

    bool pendingCommands;
    uint32_t pendingTranslations;
};

/**
 * ItsProcess is a base coroutine wrapper which is spawned by
 * the Gicv3Its module when the latter needs to perform different
 * actions, like translating a peripheral's MSI into an LPI
 * (See derived ItsTranslation) or processing a Command from the
 * ITS queue (ItsCommand).
 * The action to take is implemented by the method:
 *
 * virtual void main(Yield &yield) = 0;
 * It's inheriting from Packet::SenderState since the generic process
 * will be stopped (we are using coroutines) and sent with the packet
 * to memory when doing table walks.
 * When Gicv3Its receives a response, it will resume the coroutine from
 * the point it stopped when yielding.
 */
class ItsProcess : public Packet::SenderState
{
  public:
    using DTE = Gicv3Its::DTE;
    using ITTE = Gicv3Its::ITTE;
    using CTE = Gicv3Its::CTE;
    using Coroutine = m5::Coroutine<PacketPtr, ItsAction>;
    using Yield = Coroutine::CallerType;

    ItsProcess(Gicv3Its &_its);
    virtual ~ItsProcess();

    /** Returns the Gicv3Its name. Mainly used for DPRINTS */
    const std::string name() const;

    ItsAction run(PacketPtr pkt);

  protected:
    void reinit();
    virtual void main(Yield &yield) = 0;

    void writeDeviceTable(Yield &yield, uint32_t device_id, DTE dte);

    void writeIrqTranslationTable(
        Yield &yield, const Addr itt_base, uint32_t event_id, ITTE itte);

    void writeIrqCollectionTable(
        Yield &yield, uint32_t collection_id, CTE cte);

    uint64_t readDeviceTable(
        Yield &yield, uint32_t device_id);

    uint64_t readIrqTranslationTable(
        Yield &yield, const Addr itt_base, uint32_t event_id);

    uint64_t readIrqCollectionTable(Yield &yield, uint32_t collection_id);

    void doRead(Yield &yield, Addr addr, void *ptr, size_t size);
    void doWrite(Yield &yield, Addr addr, void *ptr, size_t size);
    void terminate(Yield &yield);

  protected:
    Gicv3Its &its;

  private:
    std::unique_ptr<Coroutine> coroutine;
};

/**
 * An ItsTranslation is created whenever a peripheral writes a message in
 * GITS_TRANSLATER (MSI). In this case main will simply do the table walks
 * until it gets a redistributor and an INTID. It will then raise the
 * LPI interrupt to the target redistributor.
 */
class ItsTranslation : public ItsProcess
{
  public:
    ItsTranslation(Gicv3Its &_its);
    ~ItsTranslation();

  protected:
    void main(Yield &yield) override;

    std::pair<uint32_t, Gicv3Redistributor *>
    translateLPI(Yield &yield, uint32_t device_id, uint32_t event_id);
};

/**
 * An ItsCommand is created whenever there is a new command in the command
 * queue. Only one command can be executed per time.
 * main will firstly read the command from memory and then it will process
 * it.
 */
class ItsCommand : public ItsProcess
{
  public:
    union CommandEntry
    {
        struct
        {
            uint32_t type;
            uint32_t deviceId;
            uint32_t eventId;
            uint32_t pintId;

            uint32_t data[4];
        };
        uint64_t raw[4];
    };

    enum CommandType : uint32_t
    {
        CLEAR = 0x04,
        DISCARD = 0x0F,
        INT = 0x03,
        INV = 0x0C,
        INVALL = 0x0D,
        MAPC = 0x09,
        MAPD = 0x08,
        MAPI = 0x0B,
        MAPTI = 0x0A,
        MOVALL = 0x0E,
        MOVI = 0x01,
        SYNC = 0x05,
        VINVALL = 0x2D,
        VMAPI = 0x2B,
        VMAPP = 0x29,
        VMAPTI = 0x2A,
        VMOVI = 0x21,
        VMOVP = 0x22,
        VSYNC = 0x25
    };

    ItsCommand(Gicv3Its &_its);
    ~ItsCommand();

  protected:
    /**
     * Dispatch entry is a metadata struct which contains information about
     * the command (like the name) and the function object implementing
     * the command.
     */
    struct DispatchEntry
    {
        using ExecFn = std::function<void(ItsCommand*, Yield&, CommandEntry&)>;

        DispatchEntry(std::string _name, ExecFn _exec)
          : name(_name), exec(_exec)
        {}

        std::string name;
        ExecFn exec;
    };

    using DispatchTable = std::unordered_map<
        std::underlying_type<enum CommandType>::type, DispatchEntry>;

    static DispatchTable cmdDispatcher;

    static std::string commandName(uint32_t cmd);

    void main(Yield &yield) override;

    void readCommand(Yield &yield, CommandEntry &command);
    void processCommand(Yield &yield, CommandEntry &command);

    // Commands
    void clear(Yield &yield, CommandEntry &command);
    void discard(Yield &yield, CommandEntry &command);
    void mapc(Yield &yield, CommandEntry &command);
    void mapd(Yield &yield, CommandEntry &command);
    void mapi(Yield &yield, CommandEntry &command);
    void mapti(Yield &yield, CommandEntry &command);
    void movall(Yield &yield, CommandEntry &command);
    void movi(Yield &yield, CommandEntry &command);
    void sync(Yield &yield, CommandEntry &command);
    void doInt(Yield &yield, CommandEntry &command);
    void inv(Yield &yield, CommandEntry &command);
    void invall(Yield &yield, CommandEntry &command);
    void vinvall(Yield &yield, CommandEntry &command);
    void vmapi(Yield &yield, CommandEntry &command);
    void vmapp(Yield &yield, CommandEntry &command);
    void vmapti(Yield &yield, CommandEntry &command);
    void vmovi(Yield &yield, CommandEntry &command);
    void vmovp(Yield &yield, CommandEntry &command);
    void vsync(Yield &yield, CommandEntry &command);

  protected: // Helpers
    bool idOutOfRange(CommandEntry &command, DTE dte) const
    {
        return its.idOutOfRange(command.eventId, dte.ittRange);
    }

    bool deviceOutOfRange(CommandEntry &command) const
    {
        return its.deviceOutOfRange(command.deviceId);
    }

    bool sizeOutOfRange(CommandEntry &command) const
    {
        const auto size = bits(command.raw[1], 4, 0);
        const auto valid = bits(command.raw[2], 63);
        if (valid)
            return its.sizeOutOfRange(size);
        else
            return false;
    }

    bool collectionOutOfRange(CommandEntry &command) const
    {
        return its.collectionOutOfRange(bits(command.raw[2], 15, 0));
    }
};

#endif