/* * 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 #include #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) : MemObject(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(®s, 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(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(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(*reinterpret_cast(reg_ptr)); break; case sizeof(uint64_t): pkt->setLE(*reinterpret_cast(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() : pkt->getLE()); switch (offset) { case offsetof(SMMURegs, cr0): assert(pkt->getSize() == sizeof(uint32_t)); regs.cr0 = regs.cr0ack = pkt->getLE(); 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(regs.data + offset) = pkt->getLE(); 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(regs.data + offset) = pkt->getLE(); } break; case offsetof(SMMURegs, cmdq_prod): assert(pkt->getSize() == sizeof(uint32_t)); *reinterpret_cast(regs.data + offset) = pkt->getLE(); schedule(processCommandsEvent, nextCycle()); break; case offsetof(SMMURegs, strtab_base): case offsetof(SMMURegs, eventq_irq_cfg0): assert(pkt->getSize() == sizeof(uint64_t)); *reinterpret_cast(regs.data + offset) = pkt->getLE(); 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(regs.data + offset) = pkt->getLE(); regs.cmdq_cons = 0; regs.cmdq_prod = 0; } break; case offsetof(SMMURegs, eventq_base): assert(pkt->getSize() == sizeof(uint64_t)); *reinterpret_cast(regs.data + offset) = pkt->getLE(); regs.eventq_cons = 0; regs.eventq_prod = 0; break; case offsetof(SMMURegs, priq_base): assert(pkt->getSize() == sizeof(uint64_t)); *reinterpret_cast(regs.data + offset) = pkt->getLE(); 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() { MemObject::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 MemObject::getPort(name, id); } } SMMUv3* SMMUv3Params::create() { return new SMMUv3(this); }