| /* |
| * 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. |
| */ |
| |
| #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, SMMUv3DeviceInterface &_ifc) |
| : |
| SMMUProcess(name, _smmu), |
| ifc(_ifc) |
| { |
| // Decrease number of pending translation slots on the device interface |
| assert(ifc.xlateSlotsRemaining > 0); |
| ifc.xlateSlotsRemaining--; |
| |
| ifc.pendingMemAccesses++; |
| reinit(); |
| } |
| |
| SMMUTranslationProcess::~SMMUTranslationProcess() |
| { |
| // Increase number of pending translation slots on the device interface |
| assert(ifc.pendingMemAccesses > 0); |
| ifc.pendingMemAccesses--; |
| |
| // If no more SMMU memory accesses are pending, |
| // signal SMMU Device 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 numResponderBeats = request.isWrite ? |
| (request.size + (ifc.portWidth - 1)) / ifc.portWidth : 1; |
| |
| doSemaphoreDown(yield, ifc.devicePortSem); |
| doDelay(yield, Cycles(numResponderBeats)); |
| doSemaphoreUp(ifc.devicePortSem); |
| |
| |
| 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("Translation Fault (addr=%#x, size=%#x, sid=%d, ssid=%d, " |
| "isWrite=%d, isPrefetch=%d, isAtsRequest=%d)\n", |
| request.addr, request.size, request.sid, request.ssid, |
| request.isWrite, request.isPrefetch, request.isAtsRequest); |
| |
| 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->RESPONSE 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, |
| "RESPONSE Interface TLB miss vaddr=%#x sid=%#x ssid=%#x\n", |
| request.addr, request.sid, request.ssid); |
| |
| return false; |
| } |
| |
| DPRINTF(SMMUv3, |
| "RESPONSE 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, |
| "RESPONSE 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<<level) & walkCacheLevels)) { |
| WalkCache::Entry e; |
| e.valid = true; |
| e.va = va; |
| e.vaMask = vaMask; |
| e.asid = stage==1 ? context.asid : 0; |
| e.vmid = context.vmid; |
| e.stage = stage; |
| e.level = level; |
| e.leaf = leaf; |
| e.pa = pa; |
| e.permissions = permissions; |
| |
| doSemaphoreDown(yield, smmu.walkSem); |
| |
| DPRINTF(SMMUv3, "%sWalkCache upd va=%#x mask=%#x asid=%#x vmid=%#x " |
| "tpa=%#x leaf=%s (S%d, L%d)\n", |
| e.stage==2 ? " " : "", |
| e.va, e.vaMask, e.asid, e.vmid, |
| e.pa, e.leaf, e.stage, e.level); |
| |
| smmu.walkCache.store(e); |
| |
| doSemaphoreUp(smmu.walkSem); |
| } |
| } |
| |
| /* |
| * Please note: |
| * This does not deal with the case where stage 1 page size |
| * is larger than stage 2 page size. |
| */ |
| SMMUTranslationProcess::TranslResult |
| SMMUTranslationProcess::walkStage1And2(Yield &yield, Addr addr, |
| const PageTableOps *pt_ops, |
| unsigned level, Addr walkPtr) |
| { |
| PageTableOps::pte_t pte = 0; |
| |
| 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 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, walkPtr, |
| 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<SMMUTranslationProcess *>::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<SMMUTranslationProcess *> &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<SMMUTranslationProcess *> &depReqs = |
| request.isWrite ? |
| ifc.dependentWrites[orderId] : ifc.dependentReads[orderId]; |
| std::list<SMMUTranslationProcess *>::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<SMMUTranslationProcess *> &depReqs = |
| request.isWrite ? |
| ifc.dependentWrites[orderId] : ifc.dependentReads[orderId]; |
| std::list<SMMUTranslationProcess *>::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 numRequestorBeats = request.isWrite ? |
| (request.size + (smmu.requestPortWidth-1)) |
| / smmu.requestPortWidth : |
| 1; |
| |
| doSemaphoreDown(yield, smmu.requestPortSem); |
| doDelay(yield, Cycles(numRequestorBeats)); |
| doSemaphoreUp(smmu.requestPortSem); |
| |
| |
| smmu.translationTimeDist.sample(curTick() - recvTick); |
| ifc.xlateSlotsRemaining++; |
| if (!request.isAtsRequest && request.isWrite) |
| ifc.wrBufSlotsRemaining += |
| (request.size + (ifc.portWidth-1)) / ifc.portWidth; |
| |
| smmu.scheduleDeviceRetries(); |
| |
| |
| 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<<span); |
| |
| ste_addr = (l2_ptr & ST_L2_ADDR_MASK) + index * sizeof(ste); |
| |
| smmu.steL1Fetches++; |
| } else if ((smmu.regs.strtab_base_cfg & ST_CFG_FMT_MASK) == ST_CFG_FMT_LINEAR) { |
| ste_addr = |
| (smmu.regs.strtab_base & VMT_BASE_ADDR_MASK) + sid * sizeof(ste); |
| } else { |
| panic("Invalid stream table format"); |
| } |
| |
| DPRINTF(SMMUv3, "Read STE at %#x\n", ste_addr); |
| |
| doReadConfig(yield, ste_addr, &ste, sizeof(ste), sid, 0); |
| |
| DPRINTF(SMMUv3, "Got STE at %#x [0]: 0x%016x\n", ste_addr, ste.dw0); |
| DPRINTF(SMMUv3, " STE at %#x [1]: 0x%016x\n", ste_addr, ste.dw1); |
| DPRINTF(SMMUv3, " STE at %#x [2]: 0x%016x\n", ste_addr, ste.dw2); |
| DPRINTF(SMMUv3, " STE at %#x [3]: 0x%016x\n", ste_addr, ste.dw3); |
| DPRINTF(SMMUv3, " STE at %#x [4]: 0x%016x\n", ste_addr, ste._pad[0]); |
| DPRINTF(SMMUv3, " STE at %#x [5]: 0x%016x\n", ste_addr, ste._pad[1]); |
| DPRINTF(SMMUv3, " STE at %#x [6]: 0x%016x\n", ste_addr, ste._pad[2]); |
| DPRINTF(SMMUv3, " STE at %#x [7]: 0x%016x\n", ste_addr, ste._pad[3]); |
| |
| if (!ste.dw0.valid) |
| panic("STE @ %#x not valid\n", ste_addr); |
| |
| smmu.steFetches++; |
| } |
| |
| void |
| SMMUTranslationProcess::doReadCD(Yield &yield, |
| ContextDescriptor &cd, |
| const StreamTableEntry &ste, |
| uint32_t sid, uint32_t ssid) |
| { |
| Addr cd_addr = 0; |
| |
| if (ste.dw0.s1cdmax == 0) { |
| cd_addr = ste.dw0.s1ctxptr << ST_CD_ADDR_SHIFT; |
| } else { |
| unsigned max_ssid = 1 << ste.dw0.s1cdmax; |
| if (ssid >= 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); |
| } |