/*
 * Copyright (c) 2014-2016 ARM Limited
 * All rights reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Authors: Andreas Sandberg
 */

#include "jobslot.hh"

#include <cassert>
#include <cstdlib>

#include "jobcontrol.hh"
#include "gpu.hh"
#include "regutils.hh"

namespace NoMali {

static const Status STATUS_IDLE(Status::CLASS_NOFAULT, 0, 0);
static const Status STATUS_DONE(Status::CLASS_NOFAULT, 0, 1);
static const Status STATUS_ACTIVE(Status::CLASS_NOFAULT, 1, 0);

const std::vector<JobSlot::cmd_t> JobSlot::cmds {
    &JobSlot::cmdNop,                      // JSn_COMMAND_NOP
    &JobSlot::cmdStart,                    // JSn_COMMAND_START
    &JobSlot::cmdSoftStop,                 // JSn_COMMAND_SOFT_STOP
    &JobSlot::cmdHardStop,                 // JSn_COMMAND_HARD_STOP
    &JobSlot::cmdSoftStop0,                // JSn_COMMAND_SOFT_STOP_0
    &JobSlot::cmdHardStop0,                // JSn_COMMAND_HARD_STOP_0
    &JobSlot::cmdSoftStop1,                // JSn_COMMAND_SOFT_STOP_1
    &JobSlot::cmdHardStop1,                // JSn_COMMAND_HARD_STOP_1
};

JobSlot::JobSlot(GPU &_gpu, JobControl &_jc, uint8_t _id)
    : GPUBlock(_gpu, JSn_NO_REGS),
      id(_id),
      jc(_jc)
{
}

JobSlot::JobSlot(JobSlot &&rhs)
    : GPUBlock(std::move(rhs)),
      id(std::move(rhs.id)),
      jc(rhs.jc)
{
}

JobSlot::~JobSlot()
{
}

void
JobSlot::writeReg(RegAddr addr, uint32_t value)
{
    switch (addr.value) {
      case JSn_COMMAND:
        jobCommand(value);
        break;

      case JSn_COMMAND_NEXT:
        regs[addr] = value;
        tryStart();
        break;

      case JSn_HEAD_NEXT_LO:
      case JSn_HEAD_NEXT_HI:
      case JSn_AFFINITY_NEXT_LO:
      case JSn_AFFINITY_NEXT_HI:
      case JSn_CONFIG_NEXT:
        GPUBlock::writeReg(addr, value);
        break;

      default:
        // Ignore writes by default
        break;
    };
}

bool
JobSlot::active() const
{
    return false;
}

bool
JobSlot::activeNext() const
{
    return regs[RegAddr(JSn_COMMAND_NEXT)] == JSn_COMMAND_START;
}

void
JobSlot::tryStart()
{
    // Only actually start something if the next command is start
    if (regs[RegAddr(JSn_COMMAND_NEXT)] != JSn_COMMAND_START )
        return;

    // Reset the status register
    regs[RegAddr(JSn_STATUS)] = STATUS_ACTIVE.value;

    // Transfer the next job configuration to the active job
    // configuration
    regs.set64(RegAddr(JSn_HEAD_LO), regs.get64(RegAddr(JSn_HEAD_NEXT_LO)));
    regs.set64(RegAddr(JSn_TAIL_LO), regs.get64(RegAddr(JSn_HEAD_NEXT_LO)));
    regs.set64(RegAddr(JSn_AFFINITY_LO),
               regs.get64(RegAddr(JSn_AFFINITY_NEXT_LO)));
    regs[RegAddr(JSn_CONFIG)] = regs[RegAddr(JSn_CONFIG_NEXT)];

    // Reset the next job configuration
    regs.set64(RegAddr(JSn_HEAD_NEXT_LO), 0);
    regs[RegAddr(JSn_COMMAND_NEXT)] = 0;

    runJob();
}

void
JobSlot::runJob()
{
    exitJob(STATUS_DONE,
            0); // Time stamp counter value
}

void
JobSlot::exitJob(Status status, uint64_t fault_address)
{
    assert(status.statusClass() == Status::CLASS_NOFAULT ||
           status.statusClass() == Status::CLASS_JOB);

    regs[RegAddr(JSn_STATUS)] = status.value;

    if (status.statusClass() == Status::CLASS_NOFAULT) {
        jc.jobDone(id);
    } else {
        jc.jobFailed(id);
    }
}

void
JobSlot::jobCommand(uint32_t cmd)
{
    if (cmd < cmds.size())
        (this->*cmds[cmd])(cmd);
}

void
JobSlot::cmdNop(uint32_t cmd)
{
    assert(cmd == JSn_COMMAND_NOP);
}

void
JobSlot::cmdStart(uint32_t cmd)
{
    assert(cmd == JSn_COMMAND_START);
    // The JSn_COMMAND_START should never be issued through the
    // JSn_COMMAND register. It should use the JSn_COMMAND_NEXT
    // register instead.
    abort();
}

void
JobSlot::cmdSoftStop(uint32_t cmd)
{
    assert(cmd == JSn_COMMAND_SOFT_STOP ||
           cmd == JSn_COMMAND_SOFT_STOP_0 ||
           cmd == JSn_COMMAND_SOFT_STOP_1);
}

void
JobSlot::cmdHardStop(uint32_t cmd)
{
    assert(cmd == JSn_COMMAND_HARD_STOP ||
           cmd == JSn_COMMAND_HARD_STOP_0 ||
           cmd == JSn_COMMAND_HARD_STOP_1);
}

void
JobSlot::cmdSoftStop0(uint32_t cmd)
{
    if (!(regs[RegAddr(JSn_CONFIG)] & JSn_CONFIG_JOB_CHAIN_FLAG))
        cmdSoftStop(cmd);
}

void
JobSlot::cmdHardStop0(uint32_t cmd)
{
    if (!(regs[RegAddr(JSn_CONFIG)] & JSn_CONFIG_JOB_CHAIN_FLAG))
        cmdHardStop(cmd);
}

void
JobSlot::cmdSoftStop1(uint32_t cmd)
{
    if (regs[RegAddr(JSn_CONFIG)] & JSn_CONFIG_JOB_CHAIN_FLAG)
        cmdSoftStop(cmd);
}

void
JobSlot::cmdHardStop1(uint32_t cmd)
{
    if (regs[RegAddr(JSn_CONFIG)] & JSn_CONFIG_JOB_CHAIN_FLAG)
        cmdHardStop(cmd);
}

}