/*
 * Copyright (c) 2013, 2018-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: Stan Czerniawski
 */

#include "dev/arm/smmu_v3_proc.hh"

#include "dev/arm/smmu_v3.hh"
#include "sim/system.hh"

SMMUProcess::SMMUProcess(const std::string &name, SMMUv3 &_smmu) :
    coroutine(NULL),
    myName(name),
    smmu(_smmu)
{}

SMMUProcess::~SMMUProcess()
{
    delete coroutine;
}

void
SMMUProcess::wakeup()
{
    smmu.runProcess(this, NULL);
}

void
SMMUProcess::reinit()
{
    delete coroutine;
    coroutine = new Coroutine(
        std::bind(&SMMUProcess::main, this, std::placeholders::_1));
}

void
SMMUProcess::doRead(Yield &yield, Addr addr, void *ptr, size_t size)
{
    doSemaphoreDown(yield, smmu.masterPortSem);
    doDelay(yield, Cycles(1)); // request - assume 1 cycle
    doSemaphoreUp(smmu.masterPortSem);

    SMMUAction a;
    a.type = ACTION_SEND_REQ;

    RequestPtr req = std::make_shared<Request>(
        addr, size, 0, smmu.masterId);

    req->taskId(ContextSwitchTaskId::DMA);

    a.pkt = new Packet(req, MemCmd::ReadReq);
    a.pkt->dataStatic(ptr);

    a.delay = 0;

    PacketPtr pkt = yield(a).get();

    assert(pkt);
    // >= because we may get the whole cache line
    assert(pkt->getSize() >= size);

    delete pkt;
}

void
SMMUProcess::doWrite(Yield &yield, Addr addr, const void *ptr, size_t size)
{
    unsigned nbeats = (size + (smmu.masterPortWidth-1)) / smmu.masterPortWidth;

    doSemaphoreDown(yield, smmu.masterPortSem);
    doDelay(yield, Cycles(nbeats));
    doSemaphoreUp(smmu.masterPortSem);


    SMMUAction a;
    a.type = ACTION_SEND_REQ;

    RequestPtr req = std::make_shared<Request>(
        addr, size, 0, smmu.masterId);

    req->taskId(ContextSwitchTaskId::DMA);

    a.pkt = new Packet(req, MemCmd::WriteReq);
    a.pkt->dataStatic(ptr);

    PacketPtr pkt = yield(a).get();

    delete pkt;
}

void
SMMUProcess::doDelay(Yield &yield, Cycles cycles)
{
    if (smmu.system.isTimingMode())
        scheduleWakeup(smmu.clockEdge(cycles));

    SMMUAction a;
    a.type = ACTION_DELAY;
    a.delay = cycles * smmu.clockPeriod();
    yield(a);
}

void
SMMUProcess::doSleep(Yield &yield)
{
    SMMUAction a;
    a.type = ACTION_SLEEP;
    yield(a);
}

void
SMMUProcess::doSemaphoreDown(Yield &yield, SMMUSemaphore &sem)
{
    while (sem.count == 0) {
        sem.queue.push(this);
        doSleep(yield);
    }

    sem.count--;
    return;
}

void
SMMUProcess::doSemaphoreUp(SMMUSemaphore &sem)
{
    sem.count++;
    if (!sem.queue.empty()) {
        SMMUProcess *next_proc = sem.queue.front();
        sem.queue.pop();

        // Schedule event in the current tick instead of
        // calling the function directly to avoid overflowing
        // the stack in this coroutine.
        next_proc->scheduleWakeup(curTick());
    }
}

void
SMMUProcess::doWaitForSignal(Yield &yield, SMMUSignal &sig)
{
    sig.waiting.push_back(this);
    doSleep(yield);
}

void
SMMUProcess::doBroadcastSignal(SMMUSignal &sig)
{
    if (!sig.waiting.empty()) {
        for (auto it : sig.waiting) {
            // Schedule event in the current tick instead of
            // calling the function directly to avoid overflowing
            // the stack in this coroutine.
            it->scheduleWakeup(curTick());
        }

        sig.waiting.clear();
    }
}

void
SMMUProcess::scheduleWakeup(Tick when)
{
    auto *ep = new EventWrapper<
        SMMUProcess, &SMMUProcess::wakeup> (this, true);

    smmu.schedule(ep, when);
}

SMMUAction
SMMUProcess::run(PacketPtr pkt)
{
    assert(coroutine != NULL);
    assert(*coroutine);
    return (*coroutine)(pkt).get();
}