#ifndef __DEV_ARM_CSS_SCMI_PLATFORM_H__
#define __DEV_ARM_CSS_SCMI_PLATFORM_H__

#include "dev/arm/css/scmi_protocols.hh"
#include "dev/arm/css/scp.hh"
#include "dev/dma_device.hh"
#include "mem/mem_object.hh"
#include "params/ScmiPlatform.hh"

class Doorbell;

namespace SCMI
{

class Platform;

static const uint8_t PROTOCOL_MAX = 6;

enum ProtocolID : uint8_t
{
    BASE = 0x10,
    START = 0x11,
    POWER_DOMAIN = START,
    SYSTEM_POWER = 0x12,
    PERFORMANCE_DOMAIN = 0x13,
    CLOCK = 0x14,
    SENSOR = 0x15,
    END = SENSOR
};

enum class MessageType
{
    COMMANDS = 0,
    DELAYED_RESPONSES = 2,
    NOTIFICATIONS = 3
};

BitUnion32(MessageHeader)
    Bitfield<27,18> token;
    Bitfield<17,10> protocolId;
    Bitfield<9,8> messageType;
    Bitfield<7,0> messageId;
EndBitUnion(MessageHeader)

union Payload
{
    struct
    {
        int32_t status;
    } invalidCommand;

    struct
    {
        int32_t status;
        uint32_t version;
    } baseProtocolVersion;

    struct
    {
        int32_t status;
        uint32_t attributes;
    } baseProtocolAttributes;

    struct
    {
        union
        {
            int32_t status;
            uint32_t messageId;
        };
        uint32_t attributes;
    } baseProtocolMessageAttributes;

    struct
    {
        int32_t status;
        uint8_t vendorIdentifier[Protocol::MAX_STRING_SIZE + 1];
    } baseDiscoverVendor;

    struct
    {
        int32_t status;
        uint8_t vendorIdentifier[Protocol::MAX_STRING_SIZE + 1];
    } baseDiscoverSubVendor;

    struct
    {
        int32_t status;
        uint32_t implementationVersion;
    } baseDiscoverImplementationVersion;

    struct
    {
        union
        {
            uint32_t skip;
            int32_t status;
        };
        uint32_t numProtocols;
        uint32_t protocols[(PROTOCOL_MAX - 1)/ 4];
    } baseDiscoverListProtocols;

    struct
    {
        union
        {
            uint32_t agentId;
            int32_t status;
        };
        uint8_t name[Protocol::MAX_STRING_SIZE + 1];
    } baseDiscoverAgent;

    int32_t status;
};

struct Message
{
    uint32_t reserved0;
    uint32_t channelStatus;
    uint64_t reserved1;
    uint32_t mailboxFlags;
    uint32_t length;
    uint32_t header;
    Payload payload;
};

class VirtualChannel : public SimObject
{
  public:
    VirtualChannel(const ScmiChannelParams &p)
      : SimObject(p),
        msgBuffer(), pendingMessage(false), shmem(p.shmem_range),
        physID(p.phys_id), virtID(p.virt_id),
        doorbell(p.doorbell)
    {}

    void
    setPlatform(Platform *_platform)
    {
        platform = _platform;
    }

    Message msgBuffer;
    bool pendingMessage;

    const AddrRange shmem;

    const uint32_t physID;
    const uint32_t virtID;

    DmaPort *dmaPort;
    Doorbell *doorbell;
    Platform *platform;

  private:
    static const int dmaSize = 8; 
};

class AgentChannel : public VirtualChannel
{
  public:
    AgentChannel(const ScmiChannelParams &p);

    void initiateRead();

    void readStatus();
    void readLength();
    void readMessage();
    void handleMessage();

    EventFunctionWrapper readLengthEvent;
    EventFunctionWrapper readMessageEvent;
    EventFunctionWrapper handleMessageEvent;
};

class PlatformChannel : public VirtualChannel
{
  public:
    PlatformChannel(const ScmiChannelParams &p);

    void writeBackMessage(const Message &msg);
    void notifyAgent();
    void clearDoorbell();
    void complete();

    EventFunctionWrapper clearDoorbellEvent;
    EventFunctionWrapper notifyAgentEvent;
    EventFunctionWrapper completeEvent;

  protected:
    uint32_t agentDoorbellVal;
    uint32_t platformDoorbellVal;
};

class Communication : public SimObject
{
  public:
    Communication(const ScmiCommunicationParams &p)
      : SimObject(p), platformChan(p.platform_channel),
        agentChan(p.agent_channel)
    {}

    PlatformChannel *platformChan;
    AgentChannel *agentChan;
};

class Platform : public Scp
{
  public:
    using ProtocolList = std::unordered_map<uint8_t, Protocol *>;

    PARAMS(ScmiPlatform);
    Platform(const Params &p);
    ~Platform();

    void handleMessage(AgentChannel *ch, Message &msg);

    uint32_t numAgents() const { return agents.size(); }

    const char*
    getAgent(unsigned index) const
    {
        return agents[index].c_str();
    }

    uint32_t numProtocols() const { return protocols.size() - 1; }

    Port& getPort(const std::string &if_name, PortID idx) override;

    void raiseInterrupt(const Doorbell *doorbell) override;
    void clearInterrupt(const Doorbell *doorbell) override;

    static uint32_t
    protocolID(const Message &msg)
    {
        return bits(msg.header, 17, 10);
    }

    static uint32_t
    messageID(const Message &msg)
    {
        return bits(msg.header, 7, 0);
    }

    static uint32_t
    messageType(const Message &msg)
    {
        return bits(msg.header, 9, 8);
    }

    const ProtocolList&
    protocolList() const
    {
        return protocols;
    }

    AgentChannel* find(PlatformChannel* platform) const;
    PlatformChannel* find(AgentChannel* agent) const;

  private:
    std::vector<Communication *> comms;
    const std::vector<std::string> agents;

    ProtocolList protocols;

    DmaPort dmaPort;
};

} 

#endif
/*
 * Copyright (c) 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.
 *
 * 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.
 */

#ifndef __DEV_ARM_CSS_SCMI_PLATFORM_H__
#define __DEV_ARM_CSS_SCMI_PLATFORM_H__

#include "dev/arm/css/scmi_protocols.hh"
#include "dev/arm/css/scp.hh"
#include "dev/dma_device.hh"
#include "mem/mem_object.hh"
#include "params/ScmiPlatform.hh"

class Doorbell;

namespace SCMI
{

class Platform;

// Maximum number of protocols defined by the SCMI specification
static const uint8_t PROTOCOL_MAX = 6;

enum ProtocolID : uint8_t
{
    BASE = 0x10,
    START = 0x11,
    POWER_DOMAIN = START,
    SYSTEM_POWER = 0x12,
    PERFORMANCE_DOMAIN = 0x13,
    CLOCK = 0x14,
    SENSOR = 0x15,
    END = SENSOR
};

enum class MessageType
{
    COMMANDS = 0,
    DELAYED_RESPONSES = 2,
    NOTIFICATIONS = 3
};

BitUnion32(MessageHeader)
    Bitfield<27,18> token;
    Bitfield<17,10> protocolId;
    Bitfield<9,8> messageType;
    Bitfield<7,0> messageId;
EndBitUnion(MessageHeader)

union Payload
{
    struct
    {
        int32_t status;
    } invalidCommand;

    struct
    {
        int32_t status;
        uint32_t version;
    } baseProtocolVersion;

    struct
    {
        int32_t status;
        uint32_t attributes;
    } baseProtocolAttributes;

    struct
    {
        union
        {
            int32_t status;
            uint32_t messageId;
        };
        uint32_t attributes;
    } baseProtocolMessageAttributes;

    struct
    {
        int32_t status;
        uint8_t vendorIdentifier[Protocol::MAX_STRING_SIZE + 1];
    } baseDiscoverVendor;

    struct
    {
        int32_t status;
        uint8_t vendorIdentifier[Protocol::MAX_STRING_SIZE + 1];
    } baseDiscoverSubVendor;

    struct
    {
        int32_t status;
        uint32_t implementationVersion;
    } baseDiscoverImplementationVersion;

    struct
    {
        union
        {
            uint32_t skip;
            int32_t status;
        };
        uint32_t numProtocols;
        uint32_t protocols[(PROTOCOL_MAX - 1)/ 4];
    } baseDiscoverListProtocols;

    struct
    {
        union
        {
            uint32_t agentId;
            int32_t status;
        };
        uint8_t name[Protocol::MAX_STRING_SIZE + 1];
    } baseDiscoverAgent;

    int32_t status;
};

struct Message
{
    uint32_t reserved0;
    uint32_t channelStatus;
    uint64_t reserved1;
    uint32_t mailboxFlags;
    uint32_t length;
    uint32_t header;
    Payload payload;
};

/**
 * Generic communication channel between the Agent and the Platform
 */
class VirtualChannel : public SimObject
{
  public:
    VirtualChannel(const ScmiChannelParams &p)
      : SimObject(p),
        msgBuffer(), pendingMessage(false), shmem(p.shmem_range),
        physID(p.phys_id), virtID(p.virt_id),
        doorbell(p.doorbell)
    {}

    /** Set a pointer to the SCMI platform */
    void
    setPlatform(Platform *_platform)
    {
        platform = _platform;
    }

    Message msgBuffer;
    bool pendingMessage;

    const AddrRange shmem;

    const uint32_t physID;
    const uint32_t virtID;

    DmaPort *dmaPort;
    Doorbell *doorbell;
    Platform *platform;

  private:
    static const int dmaSize = 8; // 64 bits
};

/**
 * This is a Agent to Platform channel (The agent is the initiator)
 */
class AgentChannel : public VirtualChannel
{
  public:
    AgentChannel(const ScmiChannelParams &p);

    void initiateRead();

    void readStatus();
    void readLength();
    void readMessage();
    void handleMessage();

    EventFunctionWrapper readLengthEvent;
    EventFunctionWrapper readMessageEvent;
    EventFunctionWrapper handleMessageEvent;
};

/**
 * This is a Platform to Agent channel (The platform is the initiator)
 */
class PlatformChannel : public VirtualChannel
{
  public:
    PlatformChannel(const ScmiChannelParams &p);

    void writeBackMessage(const Message &msg);
    void notifyAgent();
    void clearDoorbell();
    void complete();

    EventFunctionWrapper clearDoorbellEvent;
    EventFunctionWrapper notifyAgentEvent;
    EventFunctionWrapper completeEvent;

  protected:
    uint32_t agentDoorbellVal;
    uint32_t platformDoorbellVal;
};

/**
 * The SCMI Communication class models a bidirectional
 * communication between the SCMI platform and the agent.
 * As such it has a ScmiAgentChannel and a ScmiPlatformChannel
 * object as members.
 */
class Communication : public SimObject
{
  public:
    Communication(const ScmiCommunicationParams &p)
      : SimObject(p), platformChan(p.platform_channel),
        agentChan(p.agent_channel)
    {}

    PlatformChannel *platformChan;
    AgentChannel *agentChan;
};

class Platform : public Scp
{
  public:
    using ProtocolList = std::unordered_map<uint8_t, Protocol *>;

    PARAMS(ScmiPlatform);
    Platform(const Params &p);
    ~Platform();

    void handleMessage(AgentChannel *ch, Message &msg);

    /** Returns the number of agents in the system */
    uint32_t numAgents() const { return agents.size(); }

    /** Returns the name of an agent given an index */
    const char*
    getAgent(unsigned index) const
    {
        return agents[index].c_str();
    }

    /**
     * Returns the number of protocols implemented, except for
     * the base protocol
     */
    uint32_t numProtocols() const { return protocols.size() - 1; }

    Port& getPort(const std::string &if_name, PortID idx) override;

    void raiseInterrupt(const Doorbell *doorbell) override;
    void clearInterrupt(const Doorbell *doorbell) override;

    static uint32_t
    protocolID(const Message &msg)
    {
        return bits(msg.header, 17, 10);
    }

    static uint32_t
    messageID(const Message &msg)
    {
        return bits(msg.header, 7, 0);
    }

    static uint32_t
    messageType(const Message &msg)
    {
        return bits(msg.header, 9, 8);
    }

    const ProtocolList&
    protocolList() const
    {
        return protocols;
    }

    AgentChannel* find(PlatformChannel* platform) const;
    PlatformChannel* find(AgentChannel* agent) const;

  private:
    std::vector<Communication *> comms;
    const std::vector<std::string> agents;

    ProtocolList protocols;

    DmaPort dmaPort;
};

} // namespace SCMI

#endif // __DEV_ARM_CSS_SCMI_PLATFORM_H__
