/*
 * 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.hh"

#include <cstddef>
#include <cstring>

#include "base/bitfield.hh"
#include "base/cast.hh"
#include "base/logging.hh"
#include "base/trace.hh"
#include "base/types.hh"
#include "debug/Checkpoint.hh"
#include "debug/SMMUv3.hh"
#include "dev/arm/smmu_v3_transl.hh"
#include "mem/packet_access.hh"
#include "sim/system.hh"

SMMUv3::SMMUv3(SMMUv3Params *params) :
    ClockedObject(params),
    system(*params->system),
    masterId(params->system->getMasterId(this)),
    masterPort(name() + ".master", *this),
    masterTableWalkPort(name() + ".master_walker", *this),
    controlPort(name() + ".control", *this, params->reg_map),
    tlb(params->tlb_entries, params->tlb_assoc, params->tlb_policy),
    configCache(params->cfg_entries, params->cfg_assoc, params->cfg_policy),
    ipaCache(params->ipa_entries, params->ipa_assoc, params->ipa_policy),
    walkCache({ { params->walk_S1L0, params->walk_S1L1,
                  params->walk_S1L2, params->walk_S1L3,
                  params->walk_S2L0, params->walk_S2L1,
                  params->walk_S2L2, params->walk_S2L3 } },
              params->walk_assoc, params->walk_policy),
    tlbEnable(params->tlb_enable),
    configCacheEnable(params->cfg_enable),
    ipaCacheEnable(params->ipa_enable),
    walkCacheEnable(params->walk_enable),
    tableWalkPortEnable(false),
    walkCacheNonfinalEnable(params->wc_nonfinal_enable),
    walkCacheS1Levels(params->wc_s1_levels),
    walkCacheS2Levels(params->wc_s2_levels),
    masterPortWidth(params->master_port_width),
    tlbSem(params->tlb_slots),
    ifcSmmuSem(1),
    smmuIfcSem(1),
    configSem(params->cfg_slots),
    ipaSem(params->ipa_slots),
    walkSem(params->walk_slots),
    masterPortSem(1),
    transSem(params->xlate_slots),
    ptwSem(params->ptw_slots),
    cycleSem(1),
    tlbLat(params->tlb_lat),
    ifcSmmuLat(params->ifc_smmu_lat),
    smmuIfcLat(params->smmu_ifc_lat),
    configLat(params->cfg_lat),
    ipaLat(params->ipa_lat),
    walkLat(params->walk_lat),
    slaveInterfaces(params->slave_interfaces),
    commandExecutor(name() + ".cmd_exec", *this),
    regsMap(params->reg_map),
    processCommandsEvent(this)
{
    fatal_if(regsMap.size() != SMMU_REG_SIZE,
        "Invalid register map size: %#x different than SMMU_REG_SIZE = %#x\n",
        regsMap.size(), SMMU_REG_SIZE);

    // Init smmu registers to 0
    memset(&regs, 0, sizeof(regs));

    // Setup RO ID registers
    regs.idr0 = params->smmu_idr0;
    regs.idr1 = params->smmu_idr1;
    regs.idr2 = params->smmu_idr2;
    regs.idr3 = params->smmu_idr3;
    regs.idr4 = params->smmu_idr4;
    regs.idr5 = params->smmu_idr5;
    regs.iidr = params->smmu_iidr;
    regs.aidr = params->smmu_aidr;

    // TODO: At the moment it possible to set the ID registers to hold
    // any possible value. It would be nice to have a sanity check here
    // at construction time in case some idx registers are programmed to
    // store an unallowed values or if the are configuration conflicts.
    warn("SMMUv3 IDx register values unchecked\n");

    for (auto ifc : slaveInterfaces)
        ifc->setSMMU(this);
}

bool
SMMUv3::masterRecvTimingResp(PacketPtr pkt)
{
    DPRINTF(SMMUv3, "[t] master resp addr=%#x size=%#x\n",
        pkt->getAddr(), pkt->getSize());

    // @todo: We need to pay for this and not just zero it out
    pkt->headerDelay = pkt->payloadDelay = 0;

    SMMUProcess *proc =
        safe_cast<SMMUProcess *>(pkt->popSenderState());

    runProcessTiming(proc, pkt);

    return true;
}

void
SMMUv3::masterRecvReqRetry()
{
    assert(!packetsToRetry.empty());

    while (!packetsToRetry.empty()) {
        SMMUAction a = packetsToRetry.front();

        assert(a.type==ACTION_SEND_REQ || a.type==ACTION_SEND_REQ_FINAL);

        DPRINTF(SMMUv3, "[t] master retr addr=%#x size=%#x\n",
            a.pkt->getAddr(), a.pkt->getSize());

        if (!masterPort.sendTimingReq(a.pkt))
            break;

        packetsToRetry.pop();

        /*
         * ACTION_SEND_REQ_FINAL means that we have just forwarded the packet
         * on the master interface; this means that we no longer hold on to
         * that transaction and therefore can accept a new one.
         * If the slave port was stalled then unstall it (send retry).
         */
        if (a.type == ACTION_SEND_REQ_FINAL)
            scheduleSlaveRetries();
    }
}

bool
SMMUv3::masterTableWalkRecvTimingResp(PacketPtr pkt)
{
    DPRINTF(SMMUv3, "[t] master HWTW resp addr=%#x size=%#x\n",
        pkt->getAddr(), pkt->getSize());

    // @todo: We need to pay for this and not just zero it out
    pkt->headerDelay = pkt->payloadDelay = 0;

    SMMUProcess *proc =
        safe_cast<SMMUProcess *>(pkt->popSenderState());

    runProcessTiming(proc, pkt);

    return true;
}

void
SMMUv3::masterTableWalkRecvReqRetry()
{
    assert(tableWalkPortEnable);
    assert(!packetsTableWalkToRetry.empty());

    while (!packetsTableWalkToRetry.empty()) {
        SMMUAction a = packetsTableWalkToRetry.front();

        assert(a.type==ACTION_SEND_REQ);

        DPRINTF(SMMUv3, "[t] master HWTW retr addr=%#x size=%#x\n",
            a.pkt->getAddr(), a.pkt->getSize());

        if (!masterTableWalkPort.sendTimingReq(a.pkt))
            break;

        packetsTableWalkToRetry.pop();
    }
}

void
SMMUv3::scheduleSlaveRetries()
{
    for (auto ifc : slaveInterfaces) {
        ifc->scheduleDeviceRetry();
    }
}

SMMUAction
SMMUv3::runProcess(SMMUProcess *proc, PacketPtr pkt)
{
    if (system.isAtomicMode()) {
        return runProcessAtomic(proc, pkt);
    } else if (system.isTimingMode()) {
        return runProcessTiming(proc, pkt);
    } else {
        panic("Not in timing or atomic mode!");
    }
}

SMMUAction
SMMUv3::runProcessAtomic(SMMUProcess *proc, PacketPtr pkt)
{
    SMMUAction action;
    Tick delay = 0;
    bool finished = false;

    do {
        action = proc->run(pkt);

        switch (action.type) {
            case ACTION_SEND_REQ:
                // Send an MMU initiated request on the table walk port if it is
                // enabled. Otherwise, fall through and handle same as the final
                // ACTION_SEND_REQ_FINAL request.
                if (tableWalkPortEnable) {
                    delay += masterTableWalkPort.sendAtomic(action.pkt);
                    pkt = action.pkt;
                    break;
                }
                M5_FALLTHROUGH;
            case ACTION_SEND_REQ_FINAL:
                delay += masterPort.sendAtomic(action.pkt);
                pkt = action.pkt;
                break;

            case ACTION_SEND_RESP:
            case ACTION_SEND_RESP_ATS:
            case ACTION_SLEEP:
                finished = true;
                break;

            case ACTION_DELAY:
                delay += action.delay;
                break;

            case ACTION_TERMINATE:
                panic("ACTION_TERMINATE in atomic mode\n");

            default:
                panic("Unknown action\n");
        }
    } while (!finished);

    action.delay = delay;

    return action;
}

SMMUAction
SMMUv3::runProcessTiming(SMMUProcess *proc, PacketPtr pkt)
{
    SMMUAction action = proc->run(pkt);

    switch (action.type) {
        case ACTION_SEND_REQ:
            // Send an MMU initiated request on the table walk port if it is
            // enabled. Otherwise, fall through and handle same as the final
            // ACTION_SEND_REQ_FINAL request.
            if (tableWalkPortEnable) {
                action.pkt->pushSenderState(proc);

                DPRINTF(SMMUv3, "[t] master HWTW req  addr=%#x size=%#x\n",
                        action.pkt->getAddr(), action.pkt->getSize());

                if (packetsTableWalkToRetry.empty()
                        && masterTableWalkPort.sendTimingReq(action.pkt)) {
                    scheduleSlaveRetries();
                } else {
                    DPRINTF(SMMUv3, "[t] master HWTW req  needs retry,"
                            " qlen=%d\n", packetsTableWalkToRetry.size());
                    packetsTableWalkToRetry.push(action);
                }

                break;
            }
            M5_FALLTHROUGH;
        case ACTION_SEND_REQ_FINAL:
            action.pkt->pushSenderState(proc);

            DPRINTF(SMMUv3, "[t] master req  addr=%#x size=%#x\n",
                    action.pkt->getAddr(), action.pkt->getSize());

            if (packetsToRetry.empty() && masterPort.sendTimingReq(action.pkt)) {
                scheduleSlaveRetries();
            } else {
                DPRINTF(SMMUv3, "[t] master req  needs retry, qlen=%d\n",
                        packetsToRetry.size());
                packetsToRetry.push(action);
            }

            break;

        case ACTION_SEND_RESP:
            // @todo: We need to pay for this and not just zero it out
            action.pkt->headerDelay = action.pkt->payloadDelay = 0;

            DPRINTF(SMMUv3, "[t] slave resp addr=%#x size=%#x\n",
                    action.pkt->getAddr(),
                    action.pkt->getSize());

            assert(action.ifc);
            action.ifc->schedTimingResp(action.pkt);

            delete proc;
            break;

        case ACTION_SEND_RESP_ATS:
            // @todo: We need to pay for this and not just zero it out
            action.pkt->headerDelay = action.pkt->payloadDelay = 0;

            DPRINTF(SMMUv3, "[t] ATS slave resp addr=%#x size=%#x\n",
                    action.pkt->getAddr(), action.pkt->getSize());

            assert(action.ifc);
            action.ifc->schedAtsTimingResp(action.pkt);

            delete proc;
            break;

        case ACTION_DELAY:
        case ACTION_SLEEP:
            break;

        case ACTION_TERMINATE:
            delete proc;
            break;

        default:
            panic("Unknown action\n");
    }

    return action;
}

void
SMMUv3::processCommands()
{
    DPRINTF(SMMUv3, "processCommands()\n");

    if (system.isAtomicMode()) {
        SMMUAction a = runProcessAtomic(&commandExecutor, NULL);
        (void) a;
    } else if (system.isTimingMode()) {
        if (!commandExecutor.isBusy())
            runProcessTiming(&commandExecutor, NULL);
    } else {
        panic("Not in timing or atomic mode!");
    }
}

void
SMMUv3::processCommand(const SMMUCommand &cmd)
{
    switch (cmd.dw0.type) {
        case CMD_PRF_CONFIG:
            DPRINTF(SMMUv3, "CMD_PREFETCH_CONFIG - ignored\n");
            break;

        case CMD_PRF_ADDR:
            DPRINTF(SMMUv3, "CMD_PREFETCH_ADDR - ignored\n");
            break;

        case CMD_CFGI_STE: {
            DPRINTF(SMMUv3, "CMD_CFGI_STE sid=%#x\n", cmd.dw0.sid);
            configCache.invalidateSID(cmd.dw0.sid);

            for (auto slave_interface : slaveInterfaces) {
                slave_interface->microTLB->invalidateSID(cmd.dw0.sid);
                slave_interface->mainTLB->invalidateSID(cmd.dw0.sid);
            }
            break;
        }

        case CMD_CFGI_STE_RANGE: {
            const auto range = cmd.dw1.range;
            if (range == 31) {
                // CMD_CFGI_ALL is an alias of CMD_CFGI_STE_RANGE with
                // range = 31
                DPRINTF(SMMUv3, "CMD_CFGI_ALL\n");
                configCache.invalidateAll();

                for (auto slave_interface : slaveInterfaces) {
                    slave_interface->microTLB->invalidateAll();
                    slave_interface->mainTLB->invalidateAll();
                }
            } else {
                DPRINTF(SMMUv3, "CMD_CFGI_STE_RANGE\n");
                const auto start_sid = cmd.dw0.sid & ~((1 << (range + 1)) - 1);
                const auto end_sid = start_sid + (1 << (range + 1)) - 1;
                for (auto sid = start_sid; sid <= end_sid; sid++) {
                    configCache.invalidateSID(sid);

                    for (auto slave_interface : slaveInterfaces) {
                        slave_interface->microTLB->invalidateSID(sid);
                        slave_interface->mainTLB->invalidateSID(sid);
                    }
                }
            }
            break;
        }

        case CMD_CFGI_CD: {
            DPRINTF(SMMUv3, "CMD_CFGI_CD sid=%#x ssid=%#x\n",
                    cmd.dw0.sid, cmd.dw0.ssid);
            configCache.invalidateSSID(cmd.dw0.sid, cmd.dw0.ssid);

            for (auto slave_interface : slaveInterfaces) {
                slave_interface->microTLB->invalidateSSID(
                    cmd.dw0.sid, cmd.dw0.ssid);
                slave_interface->mainTLB->invalidateSSID(
                    cmd.dw0.sid, cmd.dw0.ssid);
            }
            break;
        }

        case CMD_CFGI_CD_ALL: {
            DPRINTF(SMMUv3, "CMD_CFGI_CD_ALL sid=%#x\n", cmd.dw0.sid);
            configCache.invalidateSID(cmd.dw0.sid);

            for (auto slave_interface : slaveInterfaces) {
                slave_interface->microTLB->invalidateSID(cmd.dw0.sid);
                slave_interface->mainTLB->invalidateSID(cmd.dw0.sid);
            }
            break;
        }

        case CMD_TLBI_NH_ALL: {
            DPRINTF(SMMUv3, "CMD_TLBI_NH_ALL vmid=%#x\n", cmd.dw0.vmid);
            for (auto slave_interface : slaveInterfaces) {
                slave_interface->microTLB->invalidateVMID(cmd.dw0.vmid);
                slave_interface->mainTLB->invalidateVMID(cmd.dw0.vmid);
            }
            tlb.invalidateVMID(cmd.dw0.vmid);
            walkCache.invalidateVMID(cmd.dw0.vmid);
            break;
        }

        case CMD_TLBI_NH_ASID: {
            DPRINTF(SMMUv3, "CMD_TLBI_NH_ASID asid=%#x vmid=%#x\n",
                    cmd.dw0.asid, cmd.dw0.vmid);
            for (auto slave_interface : slaveInterfaces) {
                slave_interface->microTLB->invalidateASID(
                    cmd.dw0.asid, cmd.dw0.vmid);
                slave_interface->mainTLB->invalidateASID(
                    cmd.dw0.asid, cmd.dw0.vmid);
            }
            tlb.invalidateASID(cmd.dw0.asid, cmd.dw0.vmid);
            walkCache.invalidateASID(cmd.dw0.asid, cmd.dw0.vmid);
            break;
        }

        case CMD_TLBI_NH_VAA: {
            const Addr addr = cmd.addr();
            DPRINTF(SMMUv3, "CMD_TLBI_NH_VAA va=%#08x vmid=%#x\n",
                    addr, cmd.dw0.vmid);
            for (auto slave_interface : slaveInterfaces) {
                slave_interface->microTLB->invalidateVAA(
                    addr, cmd.dw0.vmid);
                slave_interface->mainTLB->invalidateVAA(
                    addr, cmd.dw0.vmid);
            }
            tlb.invalidateVAA(addr, cmd.dw0.vmid);
            const bool leaf_only = cmd.dw1.leaf ? true : false;
            walkCache.invalidateVAA(addr, cmd.dw0.vmid, leaf_only);
            break;
        }

        case CMD_TLBI_NH_VA: {
            const Addr addr = cmd.addr();
            DPRINTF(SMMUv3, "CMD_TLBI_NH_VA va=%#08x asid=%#x vmid=%#x\n",
                    addr, cmd.dw0.asid, cmd.dw0.vmid);
            for (auto slave_interface : slaveInterfaces) {
                slave_interface->microTLB->invalidateVA(
                    addr, cmd.dw0.asid, cmd.dw0.vmid);
                slave_interface->mainTLB->invalidateVA(
                    addr, cmd.dw0.asid, cmd.dw0.vmid);
            }
            tlb.invalidateVA(addr, cmd.dw0.asid, cmd.dw0.vmid);
            const bool leaf_only = cmd.dw1.leaf ? true : false;
            walkCache.invalidateVA(addr, cmd.dw0.asid, cmd.dw0.vmid,
                                   leaf_only);
            break;
        }

        case CMD_TLBI_S2_IPA: {
            const Addr addr = cmd.addr();
            DPRINTF(SMMUv3, "CMD_TLBI_S2_IPA ipa=%#08x vmid=%#x\n",
                    addr, cmd.dw0.vmid);
            // This does not invalidate TLBs containing
            // combined Stage1 + Stage2 translations, as per the spec.
            ipaCache.invalidateIPA(addr, cmd.dw0.vmid);

            if (!cmd.dw1.leaf)
                walkCache.invalidateVMID(cmd.dw0.vmid);
            break;
        }

        case CMD_TLBI_S12_VMALL: {
            DPRINTF(SMMUv3, "CMD_TLBI_S12_VMALL vmid=%#x\n", cmd.dw0.vmid);
            for (auto slave_interface : slaveInterfaces) {
                slave_interface->microTLB->invalidateVMID(cmd.dw0.vmid);
                slave_interface->mainTLB->invalidateVMID(cmd.dw0.vmid);
            }
            tlb.invalidateVMID(cmd.dw0.vmid);
            ipaCache.invalidateVMID(cmd.dw0.vmid);
            walkCache.invalidateVMID(cmd.dw0.vmid);
            break;
        }

        case CMD_TLBI_NSNH_ALL: {
            DPRINTF(SMMUv3, "CMD_TLBI_NSNH_ALL\n");
            for (auto slave_interface : slaveInterfaces) {
                slave_interface->microTLB->invalidateAll();
                slave_interface->mainTLB->invalidateAll();
            }
            tlb.invalidateAll();
            ipaCache.invalidateAll();
            walkCache.invalidateAll();
            break;
        }

        case CMD_RESUME:
            DPRINTF(SMMUv3, "CMD_RESUME\n");
            panic("resume unimplemented");
            break;

        default:
            warn("Unimplemented command %#x\n", cmd.dw0.type);
            break;
    }
}

const PageTableOps*
SMMUv3::getPageTableOps(uint8_t trans_granule)
{
    static V8PageTableOps4k  ptOps4k;
    static V8PageTableOps16k ptOps16k;
    static V8PageTableOps64k ptOps64k;

    switch (trans_granule) {
    case TRANS_GRANULE_4K:  return &ptOps4k;
    case TRANS_GRANULE_16K: return &ptOps16k;
    case TRANS_GRANULE_64K: return &ptOps64k;
    default:
        panic("Unknown translation granule size %d", trans_granule);
    }
}

Tick
SMMUv3::readControl(PacketPtr pkt)
{
    DPRINTF(SMMUv3, "readControl:  addr=%08x size=%d\n",
            pkt->getAddr(), pkt->getSize());

    int offset = pkt->getAddr() - regsMap.start();
    assert(offset >= 0 && offset < SMMU_REG_SIZE);

    if (inSecureBlock(offset)) {
        warn("smmu: secure registers (0x%x) are not implemented\n",
             offset);
    }

    auto reg_ptr = regs.data + offset;

    switch (pkt->getSize()) {
      case sizeof(uint32_t):
        pkt->setLE<uint32_t>(*reinterpret_cast<uint32_t *>(reg_ptr));
        break;
      case sizeof(uint64_t):
        pkt->setLE<uint64_t>(*reinterpret_cast<uint64_t *>(reg_ptr));
        break;
      default:
        panic("smmu: unallowed access size: %d bytes\n", pkt->getSize());
        break;
    }

    pkt->makeAtomicResponse();

    return 0;
}

Tick
SMMUv3::writeControl(PacketPtr pkt)
{
    int offset = pkt->getAddr() - regsMap.start();
    assert(offset >= 0 && offset < SMMU_REG_SIZE);

    DPRINTF(SMMUv3, "writeControl: addr=%08x size=%d data=%16x\n",
            pkt->getAddr(), pkt->getSize(),
            pkt->getSize() == sizeof(uint64_t) ?
            pkt->getLE<uint64_t>() : pkt->getLE<uint32_t>());

    switch (offset) {
        case offsetof(SMMURegs, cr0):
            assert(pkt->getSize() == sizeof(uint32_t));
            regs.cr0 = regs.cr0ack = pkt->getLE<uint32_t>();
            break;

        case offsetof(SMMURegs, cr1):
        case offsetof(SMMURegs, cr2):
        case offsetof(SMMURegs, strtab_base_cfg):
        case offsetof(SMMURegs, eventq_cons):
        case offsetof(SMMURegs, eventq_irq_cfg1):
        case offsetof(SMMURegs, priq_cons):
            assert(pkt->getSize() == sizeof(uint32_t));
            *reinterpret_cast<uint32_t *>(regs.data + offset) =
                pkt->getLE<uint32_t>();
            break;

        case offsetof(SMMURegs, cmdq_cons):
            assert(pkt->getSize() == sizeof(uint32_t));
            if (regs.cr0 & CR0_CMDQEN_MASK) {
                warn("CMDQ is enabled: ignoring write to CMDQ_CONS\n");
            } else {
                *reinterpret_cast<uint32_t *>(regs.data + offset) =
                    pkt->getLE<uint32_t>();
            }
            break;

        case offsetof(SMMURegs, cmdq_prod):
            assert(pkt->getSize() == sizeof(uint32_t));
            *reinterpret_cast<uint32_t *>(regs.data + offset) =
                pkt->getLE<uint32_t>();
            schedule(processCommandsEvent, nextCycle());
            break;

        case offsetof(SMMURegs, strtab_base):
        case offsetof(SMMURegs, eventq_irq_cfg0):
            assert(pkt->getSize() == sizeof(uint64_t));
            *reinterpret_cast<uint64_t *>(regs.data + offset) =
                pkt->getLE<uint64_t>();
            break;

        case offsetof(SMMURegs, cmdq_base):
            assert(pkt->getSize() == sizeof(uint64_t));
            if (regs.cr0 & CR0_CMDQEN_MASK) {
                warn("CMDQ is enabled: ignoring write to CMDQ_BASE\n");
            } else {
                *reinterpret_cast<uint64_t *>(regs.data + offset) =
                    pkt->getLE<uint64_t>();
                regs.cmdq_cons = 0;
                regs.cmdq_prod = 0;
            }
            break;

        case offsetof(SMMURegs, eventq_base):
            assert(pkt->getSize() == sizeof(uint64_t));
            *reinterpret_cast<uint64_t *>(regs.data + offset) =
                pkt->getLE<uint64_t>();
            regs.eventq_cons = 0;
            regs.eventq_prod = 0;
            break;

        case offsetof(SMMURegs, priq_base):
            assert(pkt->getSize() == sizeof(uint64_t));
            *reinterpret_cast<uint64_t *>(regs.data + offset) =
                pkt->getLE<uint64_t>();
            regs.priq_cons = 0;
            regs.priq_prod = 0;
            break;

        default:
            if (inSecureBlock(offset)) {
                warn("smmu: secure registers (0x%x) are not implemented\n",
                     offset);
            } else {
                warn("smmu: write to read-only/undefined register at 0x%x\n",
                     offset);
            }
    }

    pkt->makeAtomicResponse();

    return 0;
}

bool
SMMUv3::inSecureBlock(uint32_t offs) const
{
    if (offs >= offsetof(SMMURegs, _secure_regs) && offs < SMMU_SECURE_SZ)
        return true;
    else
        return false;
}

void
SMMUv3::init()
{
    // make sure both sides are connected and have the same block size
    if (!masterPort.isConnected())
        fatal("Master port is not connected.\n");

    // If the second master port is connected for the table walks, enable
    // the mode to send table walks through this port instead
    if (masterTableWalkPort.isConnected())
        tableWalkPortEnable = true;

    // notify the master side  of our address ranges
    for (auto ifc : slaveInterfaces) {
        ifc->sendRange();
    }

    if (controlPort.isConnected())
        controlPort.sendRangeChange();
}

void
SMMUv3::regStats()
{
    ClockedObject::regStats();

    using namespace Stats;

    for (size_t i = 0; i < slaveInterfaces.size(); i++) {
        slaveInterfaces[i]->microTLB->regStats(
            csprintf("%s.utlb%d", name(), i));
        slaveInterfaces[i]->mainTLB->regStats(
            csprintf("%s.maintlb%d", name(), i));
    }

    tlb.regStats(name() + ".tlb");
    configCache.regStats(name() + ".cfg");
    ipaCache.regStats(name() + ".ipa");
    walkCache.regStats(name() + ".walk");

    steL1Fetches
        .name(name() + ".steL1Fetches")
        .desc("STE L1 fetches")
        .flags(pdf);

    steFetches
        .name(name() + ".steFetches")
        .desc("STE fetches")
        .flags(pdf);

    cdL1Fetches
        .name(name() + ".cdL1Fetches")
        .desc("CD L1 fetches")
        .flags(pdf);

    cdFetches
        .name(name() + ".cdFetches")
        .desc("CD fetches")
        .flags(pdf);

    translationTimeDist
        .init(0, 2000000, 2000)
        .name(name() + ".translationTimeDist")
        .desc("Time to translate address")
        .flags(pdf);

    ptwTimeDist
        .init(0, 2000000, 2000)
        .name(name() + ".ptwTimeDist")
        .desc("Time to walk page tables")
        .flags(pdf);
}

DrainState
SMMUv3::drain()
{
    // Wait until the Command Executor is not busy
    if (commandExecutor.isBusy()) {
        return DrainState::Draining;
    }
    return DrainState::Drained;
}

void
SMMUv3::serialize(CheckpointOut &cp) const
{
    DPRINTF(Checkpoint, "Serializing SMMUv3\n");

    SERIALIZE_ARRAY(regs.data, sizeof(regs.data) / sizeof(regs.data[0]));
}

void
SMMUv3::unserialize(CheckpointIn &cp)
{
    DPRINTF(Checkpoint, "Unserializing SMMUv3\n");

    UNSERIALIZE_ARRAY(regs.data, sizeof(regs.data) / sizeof(regs.data[0]));
}

Port&
SMMUv3::getPort(const std::string &name, PortID id)
{
    if (name == "master") {
        return masterPort;
    } else if (name == "master_walker") {
        return masterTableWalkPort;
    } else if (name == "control") {
        return controlPort;
    } else {
        return ClockedObject::getPort(name, id);
    }
}

SMMUv3*
SMMUv3Params::create()
{
    return new SMMUv3(this);
}