/* * 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_transl.hh" #include "debug/SMMUv3.hh" #include "debug/SMMUv3Hazard.hh" #include "dev/arm/amba.hh" #include "dev/arm/smmu_v3.hh" #include "sim/system.hh" SMMUTranslRequest SMMUTranslRequest::fromPacket(PacketPtr pkt, bool ats) { SMMUTranslRequest req; req.addr = pkt->getAddr(); req.size = pkt->getSize(); req.sid = pkt->req->streamId(); req.ssid = pkt->req->hasSubstreamId() ? pkt->req->substreamId() : 0; req.isWrite = pkt->isWrite(); req.isPrefetch = false; req.isAtsRequest = ats; req.pkt = pkt; return req; } SMMUTranslRequest SMMUTranslRequest::prefetch(Addr addr, uint32_t sid, uint32_t ssid) { SMMUTranslRequest req; req.addr = addr; req.size = 0; req.sid = sid; req.ssid = ssid; req.isWrite = false; req.isPrefetch = true; req.isAtsRequest = false; req.pkt = NULL; return req; } SMMUTranslationProcess::SMMUTranslationProcess(const std::string &name, SMMUv3 &_smmu, SMMUv3SlaveInterface &_ifc) : SMMUProcess(name, _smmu), ifc(_ifc) { // Decrease number of pending translation slots on the slave interface assert(ifc.xlateSlotsRemaining > 0); ifc.xlateSlotsRemaining--; ifc.pendingMemAccesses++; reinit(); } SMMUTranslationProcess::~SMMUTranslationProcess() { // Increase number of pending translation slots on the slave interface assert(ifc.pendingMemAccesses > 0); ifc.pendingMemAccesses--; // If no more SMMU memory accesses are pending, // signal SMMU Slave Interface as drained if (ifc.pendingMemAccesses == 0) { ifc.signalDrainDone(); } } void SMMUTranslationProcess::beginTransaction(const SMMUTranslRequest &req) { request = req; reinit(); } void SMMUTranslationProcess::resumeTransaction() { assert(smmu.system.isTimingMode()); assert(!"Stalls are broken"); Tick resumeTick = curTick(); (void) resumeTick; DPRINTF(SMMUv3, "Resume at tick = %d. Fault duration = %d (%.3fus)\n", resumeTick, resumeTick-faultTick, (resumeTick-faultTick) / 1e6); beginTransaction(request); smmu.runProcessTiming(this, request.pkt); } void SMMUTranslationProcess::main(Yield &yield) { // Hack: // The coroutine starts running as soon as it's created. // But we need to wait for request data esp. in atomic mode. SMMUAction a; a.type = ACTION_INITIAL_NOP; a.pkt = NULL; yield(a); const Addr next4k = (request.addr + 0x1000ULL) & ~0xfffULL; if ((request.addr + request.size) > next4k) panic("Transaction crosses 4k boundary (addr=%#x size=%#x)!\n", request.addr, request.size); unsigned numSlaveBeats = request.isWrite ? (request.size + (ifc.portWidth - 1)) / ifc.portWidth : 1; doSemaphoreDown(yield, ifc.slavePortSem); doDelay(yield, Cycles(numSlaveBeats)); doSemaphoreUp(ifc.slavePortSem); recvTick = curTick(); if (!(smmu.regs.cr0 & CR0_SMMUEN_MASK)) { // SMMU disabled doDelay(yield, Cycles(1)); completeTransaction(yield, bypass(request.addr)); return; } TranslResult tr; bool wasPrefetched = false; if (request.isPrefetch) { // Abort prefetch if: // - there's already a transaction looking up the same 4k page, OR // - requested address is already in the TLB. if (hazard4kCheck() || ifcTLBLookup(yield, tr, wasPrefetched)) completePrefetch(yield); // this never returns hazard4kRegister(); tr = smmuTranslation(yield); if (tr.fault == FAULT_NONE) ifcTLBUpdate(yield, tr); hazard4kRelease(); completePrefetch(yield); } else { hazardIdRegister(); if (!microTLBLookup(yield, tr)) { bool hit = ifcTLBLookup(yield, tr, wasPrefetched); if (!hit) { while (!hit && hazard4kCheck()) { hazard4kHold(yield); hit = ifcTLBLookup(yield, tr, wasPrefetched); } } // Issue prefetch if: // - there was a TLB hit and the entry was prefetched, OR // - TLB miss was successfully serviced if (hit) { if (wasPrefetched) issuePrefetch(next4k); } else { hazard4kRegister(); tr = smmuTranslation(yield); if (tr.fault == FAULT_NONE) { ifcTLBUpdate(yield, tr); issuePrefetch(next4k); } hazard4kRelease(); } if (tr.fault == FAULT_NONE) microTLBUpdate(yield, tr); } hazardIdHold(yield); hazardIdRelease(); if (tr.fault != FAULT_NONE) panic("fault\n"); completeTransaction(yield, tr); } } SMMUTranslationProcess::TranslResult SMMUTranslationProcess::bypass(Addr addr) const { TranslResult tr; tr.fault = FAULT_NONE; tr.addr = addr; tr.addrMask = 0; tr.writable = 1; return tr; } SMMUTranslationProcess::TranslResult SMMUTranslationProcess::smmuTranslation(Yield &yield) { TranslResult tr; // Need SMMU credit to proceed doSemaphoreDown(yield, smmu.transSem); // Simulate pipelined IFC->SMMU link doSemaphoreDown(yield, smmu.ifcSmmuSem); doDelay(yield, Cycles(1)); // serialize transactions doSemaphoreUp(smmu.ifcSmmuSem); doDelay(yield, smmu.ifcSmmuLat - Cycles(1)); // remaining pipeline delay bool haveConfig = true; if (!configCacheLookup(yield, context)) { if(findConfig(yield, context, tr)) { configCacheUpdate(yield, context); } else { haveConfig = false; } } if (haveConfig && !smmuTLBLookup(yield, tr)) { // SMMU main TLB miss // Need PTW slot to proceed doSemaphoreDown(yield, smmu.ptwSem); // Page table walk Tick ptwStartTick = curTick(); if (context.stage1Enable) { tr = translateStage1And2(yield, request.addr); } else if (context.stage2Enable) { tr = translateStage2(yield, request.addr, true); } else { tr = bypass(request.addr); } if (context.stage1Enable || context.stage2Enable) smmu.ptwTimeDist.sample(curTick() - ptwStartTick); // Free PTW slot doSemaphoreUp(smmu.ptwSem); if (tr.fault == FAULT_NONE) smmuTLBUpdate(yield, tr); } // Simulate pipelined SMMU->SLAVE INTERFACE link doSemaphoreDown(yield, smmu.smmuIfcSem); doDelay(yield, Cycles(1)); // serialize transactions doSemaphoreUp(smmu.smmuIfcSem); doDelay(yield, smmu.smmuIfcLat - Cycles(1)); // remaining pipeline delay // return SMMU credit doSemaphoreUp(smmu.transSem); return tr; } bool SMMUTranslationProcess::microTLBLookup(Yield &yield, TranslResult &tr) { if (!ifc.microTLBEnable) return false; doSemaphoreDown(yield, ifc.microTLBSem); doDelay(yield, ifc.microTLBLat); const SMMUTLB::Entry *e = ifc.microTLB->lookup(request.sid, request.ssid, request.addr); doSemaphoreUp(ifc.microTLBSem); if (!e) { DPRINTF(SMMUv3, "micro TLB miss vaddr=%#x sid=%#x ssid=%#x\n", request.addr, request.sid, request.ssid); return false; } DPRINTF(SMMUv3, "micro TLB hit vaddr=%#x amask=%#x sid=%#x ssid=%#x paddr=%#x\n", request.addr, e->vaMask, request.sid, request.ssid, e->pa); tr.fault = FAULT_NONE; tr.addr = e->pa + (request.addr & ~e->vaMask);; tr.addrMask = e->vaMask; tr.writable = e->permissions; return true; } bool SMMUTranslationProcess::ifcTLBLookup(Yield &yield, TranslResult &tr, bool &wasPrefetched) { if (!ifc.mainTLBEnable) return false; doSemaphoreDown(yield, ifc.mainTLBSem); doDelay(yield, ifc.mainTLBLat); const SMMUTLB::Entry *e = ifc.mainTLB->lookup(request.sid, request.ssid, request.addr); doSemaphoreUp(ifc.mainTLBSem); if (!e) { DPRINTF(SMMUv3, "SLAVE Interface TLB miss vaddr=%#x sid=%#x ssid=%#x\n", request.addr, request.sid, request.ssid); return false; } DPRINTF(SMMUv3, "SLAVE Interface TLB hit vaddr=%#x amask=%#x sid=%#x ssid=%#x " "paddr=%#x\n", request.addr, e->vaMask, request.sid, request.ssid, e->pa); tr.fault = FAULT_NONE; tr.addr = e->pa + (request.addr & ~e->vaMask);; tr.addrMask = e->vaMask; tr.writable = e->permissions; wasPrefetched = e->prefetched; return true; } bool SMMUTranslationProcess::smmuTLBLookup(Yield &yield, TranslResult &tr) { if (!smmu.tlbEnable) return false; doSemaphoreDown(yield, smmu.tlbSem); doDelay(yield, smmu.tlbLat); const ARMArchTLB::Entry *e = smmu.tlb.lookup(request.addr, context.asid, context.vmid); doSemaphoreUp(smmu.tlbSem); if (!e) { DPRINTF(SMMUv3, "SMMU TLB miss vaddr=%#x asid=%#x vmid=%#x\n", request.addr, context.asid, context.vmid); return false; } DPRINTF(SMMUv3, "SMMU TLB hit vaddr=%#x amask=%#x asid=%#x vmid=%#x paddr=%#x\n", request.addr, e->vaMask, context.asid, context.vmid, e->pa); tr.fault = FAULT_NONE; tr.addr = e->pa + (request.addr & ~e->vaMask);; tr.addrMask = e->vaMask; tr.writable = e->permissions; return true; } void SMMUTranslationProcess::microTLBUpdate(Yield &yield, const TranslResult &tr) { assert(tr.fault == FAULT_NONE); if (!ifc.microTLBEnable) return; SMMUTLB::Entry e; e.valid = true; e.prefetched = false; e.sid = request.sid; e.ssid = request.ssid; e.vaMask = tr.addrMask; e.va = request.addr & e.vaMask; e.pa = tr.addr & e.vaMask; e.permissions = tr.writable; e.asid = context.asid; e.vmid = context.vmid; doSemaphoreDown(yield, ifc.microTLBSem); DPRINTF(SMMUv3, "micro TLB upd vaddr=%#x amask=%#x paddr=%#x sid=%#x ssid=%#x\n", e.va, e.vaMask, e.pa, e.sid, e.ssid); ifc.microTLB->store(e, SMMUTLB::ALLOC_ANY_WAY); doSemaphoreUp(ifc.microTLBSem); } void SMMUTranslationProcess::ifcTLBUpdate(Yield &yield, const TranslResult &tr) { assert(tr.fault == FAULT_NONE); if (!ifc.mainTLBEnable) return; SMMUTLB::Entry e; e.valid = true; e.prefetched = request.isPrefetch; e.sid = request.sid; e.ssid = request.ssid; e.vaMask = tr.addrMask; e.va = request.addr & e.vaMask; e.pa = tr.addr & e.vaMask; e.permissions = tr.writable; e.asid = context.asid; e.vmid = context.vmid; SMMUTLB::AllocPolicy alloc = SMMUTLB::ALLOC_ANY_WAY; if (ifc.prefetchEnable && ifc.prefetchReserveLastWay) alloc = request.isPrefetch ? SMMUTLB::ALLOC_LAST_WAY : SMMUTLB::ALLOC_ANY_BUT_LAST_WAY; doSemaphoreDown(yield, ifc.mainTLBSem); DPRINTF(SMMUv3, "SLAVE Interface upd vaddr=%#x amask=%#x paddr=%#x sid=%#x " "ssid=%#x\n", e.va, e.vaMask, e.pa, e.sid, e.ssid); ifc.mainTLB->store(e, alloc); doSemaphoreUp(ifc.mainTLBSem); } void SMMUTranslationProcess::smmuTLBUpdate(Yield &yield, const TranslResult &tr) { assert(tr.fault == FAULT_NONE); if (!smmu.tlbEnable) return; ARMArchTLB::Entry e; e.valid = true; e.vaMask = tr.addrMask; e.va = request.addr & e.vaMask; e.asid = context.asid; e.vmid = context.vmid; e.pa = tr.addr & e.vaMask; e.permissions = tr.writable; doSemaphoreDown(yield, smmu.tlbSem); DPRINTF(SMMUv3, "SMMU TLB upd vaddr=%#x amask=%#x paddr=%#x asid=%#x vmid=%#x\n", e.va, e.vaMask, e.pa, e.asid, e.vmid); smmu.tlb.store(e); doSemaphoreUp(smmu.tlbSem); } bool SMMUTranslationProcess::configCacheLookup(Yield &yield, TranslContext &tc) { if (!smmu.configCacheEnable) return false; doSemaphoreDown(yield, smmu.configSem); doDelay(yield, smmu.configLat); const ConfigCache::Entry *e = smmu.configCache.lookup(request.sid, request.ssid); doSemaphoreUp(smmu.configSem); if (!e) { DPRINTF(SMMUv3, "Config miss sid=%#x ssid=%#x\n", request.sid, request.ssid); return false; } DPRINTF(SMMUv3, "Config hit sid=%#x ssid=%#x ttb=%#08x asid=%#x\n", request.sid, request.ssid, e->ttb0, e->asid); tc.stage1Enable = e->stage1_en; tc.stage2Enable = e->stage2_en; tc.ttb0 = e->ttb0; tc.ttb1 = e->ttb1; tc.asid = e->asid; tc.httb = e->httb; tc.vmid = e->vmid; tc.stage1TranslGranule = e->stage1_tg; tc.stage2TranslGranule = e->stage2_tg; tc.t0sz = e->t0sz; tc.s2t0sz = e->s2t0sz; return true; } void SMMUTranslationProcess::configCacheUpdate(Yield &yield, const TranslContext &tc) { if (!smmu.configCacheEnable) return; ConfigCache::Entry e; e.valid = true; e.sid = request.sid; e.ssid = request.ssid; e.stage1_en = tc.stage1Enable; e.stage2_en = tc.stage2Enable; e.ttb0 = tc.ttb0; e.ttb1 = tc.ttb1; e.asid = tc.asid; e.httb = tc.httb; e.vmid = tc.vmid; e.stage1_tg = tc.stage1TranslGranule; e.stage2_tg = tc.stage2TranslGranule; e.t0sz = tc.t0sz; e.s2t0sz = tc.s2t0sz; doSemaphoreDown(yield, smmu.configSem); DPRINTF(SMMUv3, "Config upd sid=%#x ssid=%#x\n", e.sid, e.ssid); smmu.configCache.store(e); doSemaphoreUp(smmu.configSem); } bool SMMUTranslationProcess::findConfig(Yield &yield, TranslContext &tc, TranslResult &tr) { tc.stage1Enable = false; tc.stage2Enable = false; StreamTableEntry ste; doReadSTE(yield, ste, request.sid); switch (ste.dw0.config) { case STE_CONFIG_BYPASS: break; case STE_CONFIG_STAGE1_ONLY: tc.stage1Enable = true; break; case STE_CONFIG_STAGE2_ONLY: tc.stage2Enable = true; break; case STE_CONFIG_STAGE1_AND_2: tc.stage1Enable = true; tc.stage2Enable = true; break; default: panic("Bad or unimplemented STE config %d\n", ste.dw0.config); } // Establish stage 2 context first since // Context Descriptors can be in IPA space. if (tc.stage2Enable) { tc.httb = ste.dw3.s2ttb << STE_S2TTB_SHIFT; tc.vmid = ste.dw2.s2vmid; tc.stage2TranslGranule = ste.dw2.s2tg; tc.s2t0sz = ste.dw2.s2t0sz; } else { tc.httb = 0xdeadbeef; tc.vmid = 0; tc.stage2TranslGranule = TRANS_GRANULE_INVALID; tc.s2t0sz = 0; } // Now fetch stage 1 config. if (context.stage1Enable) { ContextDescriptor cd; doReadCD(yield, cd, ste, request.sid, request.ssid); tc.ttb0 = cd.dw1.ttb0 << CD_TTB_SHIFT; tc.ttb1 = cd.dw2.ttb1 << CD_TTB_SHIFT; tc.asid = cd.dw0.asid; tc.stage1TranslGranule = cd.dw0.tg0; tc.t0sz = cd.dw0.t0sz; } else { tc.ttb0 = 0xcafebabe; tc.ttb1 = 0xcafed00d; tc.asid = 0; tc.stage1TranslGranule = TRANS_GRANULE_INVALID; tc.t0sz = 0; } return true; } void SMMUTranslationProcess::walkCacheLookup( Yield &yield, const WalkCache::Entry *&walkEntry, Addr addr, uint16_t asid, uint16_t vmid, unsigned stage, unsigned level) { const char *indent = stage==2 ? " " : ""; (void) indent; // this is only used in DPRINTFs const PageTableOps *pt_ops = stage == 1 ? smmu.getPageTableOps(context.stage1TranslGranule) : smmu.getPageTableOps(context.stage2TranslGranule); unsigned walkCacheLevels = smmu.walkCacheEnable ? (stage == 1 ? smmu.walkCacheS1Levels : smmu.walkCacheS2Levels) : 0; if ((1 << level) & walkCacheLevels) { doSemaphoreDown(yield, smmu.walkSem); doDelay(yield, smmu.walkLat); walkEntry = smmu.walkCache.lookup(addr, pt_ops->walkMask(level), asid, vmid, stage, level); if (walkEntry) { DPRINTF(SMMUv3, "%sWalkCache hit va=%#x asid=%#x vmid=%#x " "base=%#x (S%d, L%d)\n", indent, addr, asid, vmid, walkEntry->pa, stage, level); } else { DPRINTF(SMMUv3, "%sWalkCache miss va=%#x asid=%#x vmid=%#x " "(S%d, L%d)\n", indent, addr, asid, vmid, stage, level); } doSemaphoreUp(smmu.walkSem); } } void SMMUTranslationProcess::walkCacheUpdate(Yield &yield, Addr va, Addr vaMask, Addr pa, unsigned stage, unsigned level, bool leaf, uint8_t permissions) { unsigned walkCacheLevels = stage == 1 ? smmu.walkCacheS1Levels : smmu.walkCacheS2Levels; if (smmu.walkCacheEnable && ((1<lastLevel(); level++) { Addr pte_addr = walkPtr + pt_ops->index(addr, level); DPRINTF(SMMUv3, "Fetching S1 L%d PTE from pa=%#08x\n", level, pte_addr); doReadPTE(yield, addr, pte_addr, &pte, 1, level); DPRINTF(SMMUv3, "Got S1 L%d PTE=%#x from pa=%#08x\n", level, pte, pte_addr); doSemaphoreDown(yield, smmu.cycleSem); doDelay(yield, Cycles(1)); doSemaphoreUp(smmu.cycleSem); bool valid = pt_ops->isValid(pte, level); bool leaf = pt_ops->isLeaf(pte, level); if (!valid) { DPRINTF(SMMUv3, "S1 PTE not valid - fault\n"); TranslResult tr; tr.fault = FAULT_TRANSLATION; return tr; } if (valid && leaf && request.isWrite && !pt_ops->isWritable(pte, level, false)) { DPRINTF(SMMUv3, "S1 page not writable - fault\n"); TranslResult tr; tr.fault = FAULT_PERMISSION; return tr; } walkPtr = pt_ops->nextLevelPointer(pte, level); if (leaf) break; if (context.stage2Enable) { TranslResult s2tr = translateStage2(yield, walkPtr, false); if (s2tr.fault != FAULT_NONE) return s2tr; walkPtr = s2tr.addr; } walkCacheUpdate(yield, addr, pt_ops->walkMask(level), walkPtr, 1, level, leaf, 0); } TranslResult tr; tr.fault = FAULT_NONE; tr.addrMask = pt_ops->pageMask(pte, level); tr.addr = walkPtr + (addr & ~tr.addrMask); tr.writable = pt_ops->isWritable(pte, level, false); if (context.stage2Enable) { TranslResult s2tr = translateStage2(yield, tr.addr, true); if (s2tr.fault != FAULT_NONE) return s2tr; tr = combineTranslations(tr, s2tr); } walkCacheUpdate(yield, addr, tr.addrMask, tr.addr, 1, level, true, tr.writable); return tr; } SMMUTranslationProcess::TranslResult SMMUTranslationProcess::walkStage2(Yield &yield, Addr addr, bool final_tr, const PageTableOps *pt_ops, unsigned level, Addr walkPtr) { PageTableOps::pte_t pte; doSemaphoreDown(yield, smmu.cycleSem); doDelay(yield, Cycles(1)); doSemaphoreUp(smmu.cycleSem); for (; level <= pt_ops->lastLevel(); level++) { Addr pte_addr = walkPtr + pt_ops->index(addr, level); DPRINTF(SMMUv3, " Fetching S2 L%d PTE from pa=%#08x\n", level, pte_addr); doReadPTE(yield, addr, pte_addr, &pte, 2, level); DPRINTF(SMMUv3, " Got S2 L%d PTE=%#x from pa=%#08x\n", level, pte, pte_addr); doSemaphoreDown(yield, smmu.cycleSem); doDelay(yield, Cycles(1)); doSemaphoreUp(smmu.cycleSem); bool valid = pt_ops->isValid(pte, level); bool leaf = pt_ops->isLeaf(pte, level); if (!valid) { DPRINTF(SMMUv3, " S2 PTE not valid - fault\n"); TranslResult tr; tr.fault = FAULT_TRANSLATION; return tr; } if (valid && leaf && request.isWrite && !pt_ops->isWritable(pte, level, true)) { DPRINTF(SMMUv3, " S2 PTE not writable = fault\n"); TranslResult tr; tr.fault = FAULT_PERMISSION; return tr; } walkPtr = pt_ops->nextLevelPointer(pte, level); if (final_tr || smmu.walkCacheNonfinalEnable) walkCacheUpdate(yield, addr, pt_ops->walkMask(level), walkPtr, 2, level, leaf, leaf ? pt_ops->isWritable(pte, level, true) : 0); if (leaf) break; } TranslResult tr; tr.fault = FAULT_NONE; tr.addrMask = pt_ops->pageMask(pte, level); tr.addr = walkPtr + (addr & ~tr.addrMask); tr.writable = pt_ops->isWritable(pte, level, true); return tr; } SMMUTranslationProcess::TranslResult SMMUTranslationProcess::translateStage1And2(Yield &yield, Addr addr) { const PageTableOps *pt_ops = smmu.getPageTableOps(context.stage1TranslGranule); const WalkCache::Entry *walk_ep = NULL; unsigned level; // Level here is actually (level+1) so we can count down // to 0 using unsigned int. for (level = pt_ops->lastLevel() + 1; level > pt_ops->firstLevel(context.t0sz); level--) { walkCacheLookup(yield, walk_ep, addr, context.asid, context.vmid, 1, level-1); if (walk_ep) break; } // Correct level (see above). level -= 1; TranslResult tr; if (walk_ep) { if (walk_ep->leaf) { tr.fault = FAULT_NONE; tr.addr = walk_ep->pa + (addr & ~walk_ep->vaMask); tr.addrMask = walk_ep->vaMask; tr.writable = walk_ep->permissions; } else { tr = walkStage1And2(yield, addr, pt_ops, level+1, walk_ep->pa); } } else { Addr table_addr = context.ttb0; if (context.stage2Enable) { TranslResult s2tr = translateStage2(yield, table_addr, false); if (s2tr.fault != FAULT_NONE) return s2tr; table_addr = s2tr.addr; } tr = walkStage1And2(yield, addr, pt_ops, pt_ops->firstLevel(context.t0sz), table_addr); } if (tr.fault == FAULT_NONE) DPRINTF(SMMUv3, "Translated vaddr %#x to paddr %#x\n", addr, tr.addr); return tr; } SMMUTranslationProcess::TranslResult SMMUTranslationProcess::translateStage2(Yield &yield, Addr addr, bool final_tr) { const PageTableOps *pt_ops = smmu.getPageTableOps(context.stage2TranslGranule); const IPACache::Entry *ipa_ep = NULL; if (smmu.ipaCacheEnable) { doSemaphoreDown(yield, smmu.ipaSem); doDelay(yield, smmu.ipaLat); ipa_ep = smmu.ipaCache.lookup(addr, context.vmid); doSemaphoreUp(smmu.ipaSem); } if (ipa_ep) { TranslResult tr; tr.fault = FAULT_NONE; tr.addr = ipa_ep->pa + (addr & ~ipa_ep->ipaMask); tr.addrMask = ipa_ep->ipaMask; tr.writable = ipa_ep->permissions; DPRINTF(SMMUv3, " IPACache hit ipa=%#x vmid=%#x pa=%#x\n", addr, context.vmid, tr.addr); return tr; } else if (smmu.ipaCacheEnable) { DPRINTF(SMMUv3, " IPACache miss ipa=%#x vmid=%#x\n", addr, context.vmid); } const WalkCache::Entry *walk_ep = NULL; unsigned level = pt_ops->firstLevel(context.s2t0sz); if (final_tr || smmu.walkCacheNonfinalEnable) { // Level here is actually (level+1) so we can count down // to 0 using unsigned int. for (level = pt_ops->lastLevel() + 1; level > pt_ops->firstLevel(context.s2t0sz); level--) { walkCacheLookup(yield, walk_ep, addr, 0, context.vmid, 2, level-1); if (walk_ep) break; } // Correct level (see above). level -= 1; } TranslResult tr; if (walk_ep) { if (walk_ep->leaf) { tr.fault = FAULT_NONE; tr.addr = walk_ep->pa + (addr & ~walk_ep->vaMask); tr.addrMask = walk_ep->vaMask; tr.writable = walk_ep->permissions; } else { tr = walkStage2(yield, addr, final_tr, pt_ops, level + 1, walk_ep->pa); } } else { tr = walkStage2(yield, addr, final_tr, pt_ops, pt_ops->firstLevel(context.s2t0sz), context.httb); } if (tr.fault == FAULT_NONE) DPRINTF(SMMUv3, " Translated %saddr %#x to paddr %#x\n", context.stage1Enable ? "ip" : "v", addr, tr.addr); if (smmu.ipaCacheEnable) { IPACache::Entry e; e.valid = true; e.ipaMask = tr.addrMask; e.ipa = addr & e.ipaMask; e.pa = tr.addr & tr.addrMask; e.permissions = tr.writable; e.vmid = context.vmid; doSemaphoreDown(yield, smmu.ipaSem); smmu.ipaCache.store(e); doSemaphoreUp(smmu.ipaSem); } return tr; } SMMUTranslationProcess::TranslResult SMMUTranslationProcess::combineTranslations(const TranslResult &s1tr, const TranslResult &s2tr) const { if (s2tr.fault != FAULT_NONE) return s2tr; assert(s1tr.fault == FAULT_NONE); TranslResult tr; tr.fault = FAULT_NONE; tr.addr = s2tr.addr; tr.addrMask = s1tr.addrMask | s2tr.addrMask; tr.writable = s1tr.writable & s2tr.writable; return tr; } bool SMMUTranslationProcess::hazard4kCheck() { Addr addr4k = request.addr & ~0xfffULL; for (auto it = ifc.duplicateReqs.begin(); it != ifc.duplicateReqs.end(); ++it) { Addr other4k = (*it)->request.addr & ~0xfffULL; if (addr4k == other4k) return true; } return false; } void SMMUTranslationProcess::hazard4kRegister() { DPRINTF(SMMUv3Hazard, "4kReg: p=%p a4k=%#x\n", this, request.addr & ~0xfffULL); ifc.duplicateReqs.push_back(this); } void SMMUTranslationProcess::hazard4kHold(Yield &yield) { Addr addr4k = request.addr & ~0xfffULL; bool found_hazard; do { found_hazard = false; for (auto it = ifc.duplicateReqs.begin(); it!=ifc.duplicateReqs.end() && *it!=this; ++it) { Addr other4k = (*it)->request.addr & ~0xfffULL; DPRINTF(SMMUv3Hazard, "4kHold: p=%p a4k=%#x Q: p=%p a4k=%#x\n", this, addr4k, *it, other4k); if (addr4k == other4k) { DPRINTF(SMMUv3Hazard, "4kHold: p=%p a4k=%#x WAIT on p=%p a4k=%#x\n", this, addr4k, *it, other4k); doWaitForSignal(yield, ifc.duplicateReqRemoved); DPRINTF(SMMUv3Hazard, "4kHold: p=%p a4k=%#x RESUME\n", this, addr4k); // This is to avoid checking *it!=this after doWaitForSignal() // since it could have been deleted. found_hazard = true; break; } } } while (found_hazard); } void SMMUTranslationProcess::hazard4kRelease() { DPRINTF(SMMUv3Hazard, "4kRel: p=%p a4k=%#x\n", this, request.addr & ~0xfffULL); std::list::iterator it; for (it = ifc.duplicateReqs.begin(); it != ifc.duplicateReqs.end(); ++it) if (*it == this) break; if (it == ifc.duplicateReqs.end()) panic("hazard4kRelease: request not found"); ifc.duplicateReqs.erase(it); doBroadcastSignal(ifc.duplicateReqRemoved); } void SMMUTranslationProcess::hazardIdRegister() { auto orderId = AMBA::orderId(request.pkt); DPRINTF(SMMUv3Hazard, "IdReg: p=%p oid=%d\n", this, orderId); assert(orderId < SMMU_MAX_TRANS_ID); std::list &depReqs = request.isWrite ? ifc.dependentWrites[orderId] : ifc.dependentReads[orderId]; depReqs.push_back(this); } void SMMUTranslationProcess::hazardIdHold(Yield &yield) { auto orderId = AMBA::orderId(request.pkt); DPRINTF(SMMUv3Hazard, "IdHold: p=%p oid=%d\n", this, orderId); std::list &depReqs = request.isWrite ? ifc.dependentWrites[orderId] : ifc.dependentReads[orderId]; std::list::iterator it; bool found_hazard; do { found_hazard = false; for (auto it = depReqs.begin(); it!=depReqs.end() && *it!=this; ++it) { DPRINTF(SMMUv3Hazard, "IdHold: p=%p oid=%d Q: %p\n", this, orderId, *it); if (AMBA::orderId((*it)->request.pkt) == orderId) { DPRINTF(SMMUv3Hazard, "IdHold: p=%p oid=%d WAIT on=%p\n", this, orderId, *it); doWaitForSignal(yield, ifc.dependentReqRemoved); DPRINTF(SMMUv3Hazard, "IdHold: p=%p oid=%d RESUME\n", this, orderId); // This is to avoid checking *it!=this after doWaitForSignal() // since it could have been deleted. found_hazard = true; break; } } } while (found_hazard); } void SMMUTranslationProcess::hazardIdRelease() { auto orderId = AMBA::orderId(request.pkt); DPRINTF(SMMUv3Hazard, "IdRel: p=%p oid=%d\n", this, orderId); std::list &depReqs = request.isWrite ? ifc.dependentWrites[orderId] : ifc.dependentReads[orderId]; std::list::iterator it; for (it = depReqs.begin(); it != depReqs.end(); ++it) { if (*it == this) break; } if (it == depReqs.end()) panic("hazardIdRelease: request not found"); depReqs.erase(it); doBroadcastSignal(ifc.dependentReqRemoved); } void SMMUTranslationProcess::issuePrefetch(Addr addr) { if (!smmu.system.isTimingMode()) return; if (!ifc.prefetchEnable || ifc.xlateSlotsRemaining == 0) return; std::string proc_name = csprintf("%sprf", name()); SMMUTranslationProcess *proc = new SMMUTranslationProcess(proc_name, smmu, ifc); proc->beginTransaction( SMMUTranslRequest::prefetch(addr, request.sid, request.ssid)); proc->scheduleWakeup(smmu.clockEdge(Cycles(1))); } void SMMUTranslationProcess::completeTransaction(Yield &yield, const TranslResult &tr) { assert(tr.fault == FAULT_NONE); unsigned numMasterBeats = request.isWrite ? (request.size + (smmu.masterPortWidth-1)) / smmu.masterPortWidth : 1; doSemaphoreDown(yield, smmu.masterPortSem); doDelay(yield, Cycles(numMasterBeats)); doSemaphoreUp(smmu.masterPortSem); smmu.translationTimeDist.sample(curTick() - recvTick); ifc.xlateSlotsRemaining++; if (!request.isAtsRequest && request.isWrite) ifc.wrBufSlotsRemaining += (request.size + (ifc.portWidth-1)) / ifc.portWidth; smmu.scheduleSlaveRetries(); SMMUAction a; if (request.isAtsRequest) { a.type = ACTION_SEND_RESP_ATS; if (smmu.system.isAtomicMode()) { request.pkt->makeAtomicResponse(); } else if (smmu.system.isTimingMode()) { request.pkt->makeTimingResponse(); } else { panic("Not in atomic or timing mode"); } } else { a.type = ACTION_SEND_REQ_FINAL; a.ifc = &ifc; } a.pkt = request.pkt; a.delay = 0; a.pkt->setAddr(tr.addr); a.pkt->req->setPaddr(tr.addr); yield(a); if (!request.isAtsRequest) { PacketPtr pkt = yield.get(); pkt->setAddr(request.addr); a.type = ACTION_SEND_RESP; a.pkt = pkt; a.ifc = &ifc; a.delay = 0; yield(a); } } void SMMUTranslationProcess::completePrefetch(Yield &yield) { ifc.xlateSlotsRemaining++; SMMUAction a; a.type = ACTION_TERMINATE; a.pkt = NULL; a.ifc = &ifc; a.delay = 0; yield(a); } void SMMUTranslationProcess::sendEvent(Yield &yield, const SMMUEvent &ev) { int sizeMask = mask(smmu.regs.eventq_base & Q_BASE_SIZE_MASK); if (((smmu.regs.eventq_prod+1) & sizeMask) == (smmu.regs.eventq_cons & sizeMask)) panic("Event queue full - aborting\n"); Addr event_addr = (smmu.regs.eventq_base & Q_BASE_ADDR_MASK) + (smmu.regs.eventq_prod & sizeMask) * sizeof(ev); DPRINTF(SMMUv3, "Sending event to addr=%#08x (pos=%d): type=%#x stag=%#x " "flags=%#x sid=%#x ssid=%#x va=%#08x ipa=%#x\n", event_addr, smmu.regs.eventq_prod, ev.type, ev.stag, ev.flags, ev.streamId, ev.substreamId, ev.va, ev.ipa); // This deliberately resets the overflow field in eventq_prod! smmu.regs.eventq_prod = (smmu.regs.eventq_prod + 1) & sizeMask; doWrite(yield, event_addr, &ev, sizeof(ev)); if (!(smmu.regs.eventq_irq_cfg0 & E_BASE_ENABLE_MASK)) panic("eventq msi not enabled\n"); doWrite(yield, smmu.regs.eventq_irq_cfg0 & E_BASE_ADDR_MASK, &smmu.regs.eventq_irq_cfg1, sizeof(smmu.regs.eventq_irq_cfg1)); } void SMMUTranslationProcess::doReadSTE(Yield &yield, StreamTableEntry &ste, uint32_t sid) { unsigned max_sid = 1 << (smmu.regs.strtab_base_cfg & ST_CFG_SIZE_MASK); if (sid >= max_sid) panic("SID %#x out of range, max=%#x", sid, max_sid); Addr ste_addr; if ((smmu.regs.strtab_base_cfg & ST_CFG_FMT_MASK) == ST_CFG_FMT_2LEVEL) { unsigned split = (smmu.regs.strtab_base_cfg & ST_CFG_SPLIT_MASK) >> ST_CFG_SPLIT_SHIFT; if (split!= 7 && split!=8 && split!=16) panic("Invalid stream table split %d", split); uint64_t l2_ptr; uint64_t l2_addr = (smmu.regs.strtab_base & VMT_BASE_ADDR_MASK) + bits(sid, 32, split) * sizeof(l2_ptr); DPRINTF(SMMUv3, "Read L1STE at %#x\n", l2_addr); doReadConfig(yield, l2_addr, &l2_ptr, sizeof(l2_ptr), sid, 0); DPRINTF(SMMUv3, "Got L1STE L1 at %#x: 0x%016x\n", l2_addr, l2_ptr); unsigned span = l2_ptr & ST_L2_SPAN_MASK; if (span == 0) panic("Invalid level 1 stream table descriptor"); unsigned index = bits(sid, split-1, 0); if (index >= (1 << span)) panic("StreamID %d out of level 1 descriptor range %d", sid, 1<= max_ssid) panic("SSID %#x out of range, max=%#x", ssid, max_ssid); if (ste.dw0.s1fmt==STAGE1_CFG_2L_4K || ste.dw0.s1fmt==STAGE1_CFG_2L_64K) { unsigned split = ste.dw0.s1fmt==STAGE1_CFG_2L_4K ? 7 : 11; uint64_t l2_ptr; uint64_t l2_addr = (ste.dw0.s1ctxptr << ST_CD_ADDR_SHIFT) + bits(ssid, 24, split) * sizeof(l2_ptr); if (context.stage2Enable) l2_addr = translateStage2(yield, l2_addr, false).addr; DPRINTF(SMMUv3, "Read L1CD at %#x\n", l2_addr); doReadConfig(yield, l2_addr, &l2_ptr, sizeof(l2_ptr), sid, ssid); DPRINTF(SMMUv3, "Got L1CD at %#x: 0x%016x\n", l2_addr, l2_ptr); cd_addr = l2_ptr + bits(ssid, split-1, 0) * sizeof(cd); smmu.cdL1Fetches++; } else if (ste.dw0.s1fmt == STAGE1_CFG_1L) { cd_addr = (ste.dw0.s1ctxptr << ST_CD_ADDR_SHIFT) + ssid*sizeof(cd); } } if (context.stage2Enable) cd_addr = translateStage2(yield, cd_addr, false).addr; DPRINTF(SMMUv3, "Read CD at %#x\n", cd_addr); doReadConfig(yield, cd_addr, &cd, sizeof(cd), sid, ssid); DPRINTF(SMMUv3, "Got CD at %#x [0]: 0x%016x\n", cd_addr, cd.dw0); DPRINTF(SMMUv3, " CD at %#x [1]: 0x%016x\n", cd_addr, cd.dw1); DPRINTF(SMMUv3, " CD at %#x [2]: 0x%016x\n", cd_addr, cd.dw2); DPRINTF(SMMUv3, " CD at %#x [3]: 0x%016x\n", cd_addr, cd.mair); DPRINTF(SMMUv3, " CD at %#x [4]: 0x%016x\n", cd_addr, cd.amair); DPRINTF(SMMUv3, " CD at %#x [5]: 0x%016x\n", cd_addr, cd._pad[0]); DPRINTF(SMMUv3, " CD at %#x [6]: 0x%016x\n", cd_addr, cd._pad[1]); DPRINTF(SMMUv3, " CD at %#x [7]: 0x%016x\n", cd_addr, cd._pad[2]); if (!cd.dw0.valid) panic("CD @ %#x not valid\n", cd_addr); smmu.cdFetches++; } void SMMUTranslationProcess::doReadConfig(Yield &yield, Addr addr, void *ptr, size_t size, uint32_t sid, uint32_t ssid) { doRead(yield, addr, ptr, size); } void SMMUTranslationProcess::doReadPTE(Yield &yield, Addr va, Addr addr, void *ptr, unsigned stage, unsigned level) { size_t pte_size = sizeof(PageTableOps::pte_t); Addr mask = pte_size - 1; Addr base = addr & ~mask; doRead(yield, base, ptr, pte_size); }