| /* |
| * Copyright (c) 2001-2005 The Regents of The University of Michigan |
| * Copyright (c) 2007 MIPS Technologies, Inc. |
| * Copyright (c) 2020 Barkhausen Institut |
| * Copyright (c) 2021 Huawei International |
| * All rights reserved. |
| * |
| * 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 "arch/riscv/tlb.hh" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "arch/riscv/faults.hh" |
| #include "arch/riscv/mmu.hh" |
| #include "arch/riscv/pagetable.hh" |
| #include "arch/riscv/pagetable_walker.hh" |
| #include "arch/riscv/pma_checker.hh" |
| #include "arch/riscv/pmp.hh" |
| #include "arch/riscv/pra_constants.hh" |
| #include "arch/riscv/utility.hh" |
| #include "base/inifile.hh" |
| #include "base/str.hh" |
| #include "base/trace.hh" |
| #include "cpu/thread_context.hh" |
| #include "debug/TLB.hh" |
| #include "debug/TLBVerbose.hh" |
| #include "mem/page_table.hh" |
| #include "params/RiscvTLB.hh" |
| #include "sim/full_system.hh" |
| #include "sim/process.hh" |
| #include "sim/system.hh" |
| |
| namespace gem5 |
| { |
| |
| using namespace RiscvISA; |
| |
| /////////////////////////////////////////////////////////////////////// |
| // |
| // RISC-V TLB |
| // |
| |
| static Addr |
| buildKey(Addr vpn, uint16_t asid) |
| { |
| return (static_cast<Addr>(asid) << 48) | vpn; |
| } |
| |
| TLB::TLB(const Params &p) : |
| BaseTLB(p), size(p.size), tlb(size), |
| lruSeq(0), stats(this), pma(p.pma_checker), |
| pmp(p.pmp) |
| { |
| for (size_t x = 0; x < size; x++) { |
| tlb[x].trieHandle = NULL; |
| freeList.push_back(&tlb[x]); |
| } |
| |
| walker = p.walker; |
| walker->setTLB(this); |
| } |
| |
| Walker * |
| TLB::getWalker() |
| { |
| return walker; |
| } |
| |
| void |
| TLB::evictLRU() |
| { |
| // Find the entry with the lowest (and hence least recently updated) |
| // sequence number. |
| |
| size_t lru = 0; |
| for (size_t i = 1; i < size; i++) { |
| if (tlb[i].lruSeq < tlb[lru].lruSeq) |
| lru = i; |
| } |
| |
| remove(lru); |
| } |
| |
| TlbEntry * |
| TLB::lookup(Addr vpn, uint16_t asid, BaseMMU::Mode mode, bool hidden) |
| { |
| TlbEntry *entry = trie.lookup(buildKey(vpn, asid)); |
| |
| if (!hidden) { |
| if (entry) |
| entry->lruSeq = nextSeq(); |
| |
| if (mode == BaseMMU::Write) |
| stats.writeAccesses++; |
| else |
| stats.readAccesses++; |
| |
| if (!entry) { |
| if (mode == BaseMMU::Write) |
| stats.writeMisses++; |
| else |
| stats.readMisses++; |
| } |
| else { |
| if (mode == BaseMMU::Write) |
| stats.writeHits++; |
| else |
| stats.readHits++; |
| } |
| |
| DPRINTF(TLBVerbose, "lookup(vpn=%#x, asid=%#x): %s ppn %#x\n", |
| vpn, asid, entry ? "hit" : "miss", entry ? entry->paddr : 0); |
| } |
| |
| return entry; |
| } |
| |
| TlbEntry * |
| TLB::insert(Addr vpn, const TlbEntry &entry) |
| { |
| DPRINTF(TLB, "insert(vpn=%#x, asid=%#x): ppn=%#x pte=%#x size=%#x\n", |
| vpn, entry.asid, entry.paddr, entry.pte, entry.size()); |
| |
| // If somebody beat us to it, just use that existing entry. |
| TlbEntry *newEntry = lookup(vpn, entry.asid, BaseMMU::Read, true); |
| if (newEntry) { |
| // update PTE flags (maybe we set the dirty/writable flag) |
| newEntry->pte = entry.pte; |
| assert(newEntry->vaddr == vpn); |
| return newEntry; |
| } |
| |
| if (freeList.empty()) |
| evictLRU(); |
| |
| newEntry = freeList.front(); |
| freeList.pop_front(); |
| |
| Addr key = buildKey(vpn, entry.asid); |
| *newEntry = entry; |
| newEntry->lruSeq = nextSeq(); |
| newEntry->vaddr = vpn; |
| newEntry->trieHandle = |
| trie.insert(key, TlbEntryTrie::MaxBits - entry.logBytes, newEntry); |
| return newEntry; |
| } |
| |
| void |
| TLB::demapPage(Addr vpn, uint64_t asid) |
| { |
| asid &= 0xFFFF; |
| |
| if (vpn == 0 && asid == 0) |
| flushAll(); |
| else { |
| DPRINTF(TLB, "flush(vpn=%#x, asid=%#x)\n", vpn, asid); |
| if (vpn != 0 && asid != 0) { |
| TlbEntry *newEntry = lookup(vpn, asid, BaseMMU::Read, true); |
| if (newEntry) |
| remove(newEntry - tlb.data()); |
| } |
| else { |
| for (size_t i = 0; i < size; i++) { |
| if (tlb[i].trieHandle) { |
| Addr mask = ~(tlb[i].size() - 1); |
| if ((vpn == 0 || (vpn & mask) == tlb[i].vaddr) && |
| (asid == 0 || tlb[i].asid == asid)) |
| remove(i); |
| } |
| } |
| } |
| } |
| } |
| |
| void |
| TLB::flushAll() |
| { |
| DPRINTF(TLB, "flushAll()\n"); |
| for (size_t i = 0; i < size; i++) { |
| if (tlb[i].trieHandle) |
| remove(i); |
| } |
| } |
| |
| void |
| TLB::remove(size_t idx) |
| { |
| DPRINTF(TLB, "remove(vpn=%#x, asid=%#x): ppn=%#x pte=%#x size=%#x\n", |
| tlb[idx].vaddr, tlb[idx].asid, tlb[idx].paddr, tlb[idx].pte, |
| tlb[idx].size()); |
| |
| assert(tlb[idx].trieHandle); |
| trie.remove(tlb[idx].trieHandle); |
| tlb[idx].trieHandle = NULL; |
| freeList.push_back(&tlb[idx]); |
| } |
| |
| Fault |
| TLB::checkPermissions(STATUS status, PrivilegeMode pmode, Addr vaddr, |
| BaseMMU::Mode mode, PTESv39 pte) |
| { |
| Fault fault = NoFault; |
| |
| if (mode == BaseMMU::Read && !pte.r) { |
| DPRINTF(TLB, "PTE has no read perm, raising PF\n"); |
| fault = createPagefault(vaddr, mode); |
| } |
| else if (mode == BaseMMU::Write && !pte.w) { |
| DPRINTF(TLB, "PTE has no write perm, raising PF\n"); |
| fault = createPagefault(vaddr, mode); |
| } |
| else if (mode == BaseMMU::Execute && !pte.x) { |
| DPRINTF(TLB, "PTE has no exec perm, raising PF\n"); |
| fault = createPagefault(vaddr, mode); |
| } |
| |
| if (fault == NoFault) { |
| // check pte.u |
| if (pmode == PrivilegeMode::PRV_U && !pte.u) { |
| DPRINTF(TLB, "PTE is not user accessible, raising PF\n"); |
| fault = createPagefault(vaddr, mode); |
| } |
| else if (pmode == PrivilegeMode::PRV_S && pte.u && status.sum == 0) { |
| DPRINTF(TLB, "PTE is only user accessible, raising PF\n"); |
| fault = createPagefault(vaddr, mode); |
| } |
| } |
| |
| return fault; |
| } |
| |
| Fault |
| TLB::createPagefault(Addr vaddr, BaseMMU::Mode mode) |
| { |
| ExceptionCode code; |
| if (mode == BaseMMU::Read) |
| code = ExceptionCode::LOAD_PAGE; |
| else if (mode == BaseMMU::Write) |
| code = ExceptionCode::STORE_PAGE; |
| else |
| code = ExceptionCode::INST_PAGE; |
| return std::make_shared<AddressFault>(vaddr, code); |
| } |
| |
| Addr |
| TLB::translateWithTLB(Addr vaddr, uint16_t asid, BaseMMU::Mode mode) |
| { |
| TlbEntry *e = lookup(vaddr, asid, mode, false); |
| assert(e != nullptr); |
| return e->paddr << PageShift | (vaddr & mask(e->logBytes)); |
| } |
| |
| Fault |
| TLB::doTranslate(const RequestPtr &req, ThreadContext *tc, |
| BaseMMU::Translation *translation, BaseMMU::Mode mode, |
| bool &delayed) |
| { |
| delayed = false; |
| |
| Addr vaddr = Addr(sext<VADDR_BITS>(req->getVaddr())); |
| SATP satp = tc->readMiscReg(MISCREG_SATP); |
| |
| TlbEntry *e = lookup(vaddr, satp.asid, mode, false); |
| if (!e) { |
| Fault fault = walker->start(tc, translation, req, mode); |
| if (translation != nullptr || fault != NoFault) { |
| // This gets ignored in atomic mode. |
| delayed = true; |
| return fault; |
| } |
| e = lookup(vaddr, satp.asid, mode, false); |
| assert(e != nullptr); |
| } |
| |
| STATUS status = tc->readMiscReg(MISCREG_STATUS); |
| PrivilegeMode pmode = getMemPriv(tc, mode); |
| Fault fault = checkPermissions(status, pmode, vaddr, mode, e->pte); |
| if (fault != NoFault) { |
| // if we want to write and it isn't writable, do a page table walk |
| // again to update the dirty flag. |
| if (mode == BaseMMU::Write && !e->pte.w) { |
| DPRINTF(TLB, "Dirty bit not set, repeating PT walk\n"); |
| fault = walker->start(tc, translation, req, mode); |
| if (translation != nullptr || fault != NoFault) { |
| delayed = true; |
| return fault; |
| } |
| } |
| if (fault != NoFault) |
| return fault; |
| } |
| |
| Addr paddr = e->paddr << PageShift | (vaddr & mask(e->logBytes)); |
| DPRINTF(TLBVerbose, "translate(vpn=%#x, asid=%#x): %#x\n", |
| vaddr, satp.asid, paddr); |
| req->setPaddr(paddr); |
| |
| return NoFault; |
| } |
| |
| PrivilegeMode |
| TLB::getMemPriv(ThreadContext *tc, BaseMMU::Mode mode) |
| { |
| STATUS status = (STATUS)tc->readMiscReg(MISCREG_STATUS); |
| PrivilegeMode pmode = (PrivilegeMode)tc->readMiscReg(MISCREG_PRV); |
| if (mode != BaseMMU::Execute && status.mprv == 1) |
| pmode = (PrivilegeMode)(RegVal)status.mpp; |
| return pmode; |
| } |
| |
| Fault |
| TLB::translate(const RequestPtr &req, ThreadContext *tc, |
| BaseMMU::Translation *translation, BaseMMU::Mode mode, |
| bool &delayed) |
| { |
| delayed = false; |
| |
| if (FullSystem) { |
| PrivilegeMode pmode = getMemPriv(tc, mode); |
| SATP satp = tc->readMiscReg(MISCREG_SATP); |
| if (pmode == PrivilegeMode::PRV_M || satp.mode == AddrXlateMode::BARE) |
| req->setFlags(Request::PHYSICAL); |
| |
| Fault fault; |
| if (req->getFlags() & Request::PHYSICAL) { |
| /** |
| * we simply set the virtual address to physical address |
| */ |
| req->setPaddr(req->getVaddr()); |
| fault = NoFault; |
| } else { |
| fault = doTranslate(req, tc, translation, mode, delayed); |
| } |
| |
| // according to the RISC-V tests, negative physical addresses trigger |
| // an illegal address exception. |
| // TODO where is that written in the manual? |
| if (!delayed && fault == NoFault && bits(req->getPaddr(), 63)) { |
| ExceptionCode code; |
| if (mode == BaseMMU::Read) |
| code = ExceptionCode::LOAD_ACCESS; |
| else if (mode == BaseMMU::Write) |
| code = ExceptionCode::STORE_ACCESS; |
| else |
| code = ExceptionCode::INST_ACCESS; |
| fault = std::make_shared<AddressFault>(req->getVaddr(), code); |
| } |
| |
| if (!delayed && fault == NoFault) { |
| pma->check(req); |
| |
| // do pmp check if any checking condition is met. |
| // timingFault will be NoFault if pmp checks are |
| // passed, otherwise an address fault will be returned. |
| fault = pmp->pmpCheck(req, mode, pmode, tc); |
| } |
| |
| return fault; |
| } else { |
| // In the O3 CPU model, sometimes a memory access will be speculatively |
| // executed along a branch that will end up not being taken where the |
| // address is invalid. In that case, return a fault rather than trying |
| // to translate it (which will cause a panic). Since RISC-V allows |
| // unaligned memory accesses, this should only happen if the request's |
| // length is long enough to wrap around from the end of the memory to |
| // the start. |
| assert(req->getSize() > 0); |
| if (req->getVaddr() + req->getSize() - 1 < req->getVaddr()) |
| return std::make_shared<GenericPageTableFault>(req->getVaddr()); |
| |
| Process * p = tc->getProcessPtr(); |
| |
| Fault fault = p->pTable->translate(req); |
| if (fault != NoFault) |
| return fault; |
| |
| return NoFault; |
| } |
| } |
| |
| Fault |
| TLB::translateAtomic(const RequestPtr &req, ThreadContext *tc, |
| BaseMMU::Mode mode) |
| { |
| bool delayed; |
| return translate(req, tc, nullptr, mode, delayed); |
| } |
| |
| void |
| TLB::translateTiming(const RequestPtr &req, ThreadContext *tc, |
| BaseMMU::Translation *translation, BaseMMU::Mode mode) |
| { |
| bool delayed; |
| assert(translation); |
| Fault fault = translate(req, tc, translation, mode, delayed); |
| if (!delayed) |
| translation->finish(fault, req, tc, mode); |
| else |
| translation->markDelayed(); |
| } |
| |
| Fault |
| TLB::translateFunctional(const RequestPtr &req, ThreadContext *tc, |
| BaseMMU::Mode mode) |
| { |
| const Addr vaddr = req->getVaddr(); |
| Addr paddr = vaddr; |
| |
| if (FullSystem) { |
| MMU *mmu = static_cast<MMU *>(tc->getMMUPtr()); |
| |
| PrivilegeMode pmode = mmu->getMemPriv(tc, mode); |
| SATP satp = tc->readMiscReg(MISCREG_SATP); |
| if (pmode != PrivilegeMode::PRV_M && |
| satp.mode != AddrXlateMode::BARE) { |
| Walker *walker = mmu->getDataWalker(); |
| unsigned logBytes; |
| Fault fault = walker->startFunctional( |
| tc, paddr, logBytes, mode); |
| if (fault != NoFault) |
| return fault; |
| |
| Addr masked_addr = vaddr & mask(logBytes); |
| paddr |= masked_addr; |
| } |
| } |
| else { |
| Process *process = tc->getProcessPtr(); |
| const auto *pte = process->pTable->lookup(vaddr); |
| |
| if (!pte && mode != BaseMMU::Execute) { |
| // Check if we just need to grow the stack. |
| if (process->fixupFault(vaddr)) { |
| // If we did, lookup the entry for the new page. |
| pte = process->pTable->lookup(vaddr); |
| } |
| } |
| |
| if (!pte) |
| return std::make_shared<GenericPageTableFault>(req->getVaddr()); |
| |
| paddr = pte->paddr | process->pTable->pageOffset(vaddr); |
| } |
| |
| DPRINTF(TLB, "Translated (functional) %#x -> %#x.\n", vaddr, paddr); |
| req->setPaddr(paddr); |
| return NoFault; |
| } |
| |
| Fault |
| TLB::finalizePhysical(const RequestPtr &req, |
| ThreadContext *tc, BaseMMU::Mode mode) const |
| { |
| return NoFault; |
| } |
| |
| void |
| TLB::serialize(CheckpointOut &cp) const |
| { |
| // Only store the entries in use. |
| uint32_t _size = size - freeList.size(); |
| SERIALIZE_SCALAR(_size); |
| SERIALIZE_SCALAR(lruSeq); |
| |
| uint32_t _count = 0; |
| for (uint32_t x = 0; x < size; x++) { |
| if (tlb[x].trieHandle != NULL) |
| tlb[x].serializeSection(cp, csprintf("Entry%d", _count++)); |
| } |
| } |
| |
| void |
| TLB::unserialize(CheckpointIn &cp) |
| { |
| // Do not allow to restore with a smaller tlb. |
| uint32_t _size; |
| UNSERIALIZE_SCALAR(_size); |
| if (_size > size) { |
| fatal("TLB size less than the one in checkpoint!"); |
| } |
| |
| UNSERIALIZE_SCALAR(lruSeq); |
| |
| for (uint32_t x = 0; x < _size; x++) { |
| TlbEntry *newEntry = freeList.front(); |
| freeList.pop_front(); |
| |
| newEntry->unserializeSection(cp, csprintf("Entry%d", x)); |
| Addr key = buildKey(newEntry->vaddr, newEntry->asid); |
| newEntry->trieHandle = trie.insert(key, |
| TlbEntryTrie::MaxBits - newEntry->logBytes, newEntry); |
| } |
| } |
| |
| TLB::TlbStats::TlbStats(statistics::Group *parent) |
| : statistics::Group(parent), |
| ADD_STAT(readHits, statistics::units::Count::get(), "read hits"), |
| ADD_STAT(readMisses, statistics::units::Count::get(), "read misses"), |
| ADD_STAT(readAccesses, statistics::units::Count::get(), "read accesses"), |
| ADD_STAT(writeHits, statistics::units::Count::get(), "write hits"), |
| ADD_STAT(writeMisses, statistics::units::Count::get(), "write misses"), |
| ADD_STAT(writeAccesses, statistics::units::Count::get(), "write accesses"), |
| ADD_STAT(hits, statistics::units::Count::get(), |
| "Total TLB (read and write) hits", readHits + writeHits), |
| ADD_STAT(misses, statistics::units::Count::get(), |
| "Total TLB (read and write) misses", readMisses + writeMisses), |
| ADD_STAT(accesses, statistics::units::Count::get(), |
| "Total TLB (read and write) accesses", |
| readAccesses + writeAccesses) |
| { |
| } |
| |
| Port * |
| TLB::getTableWalkerPort() |
| { |
| return &walker->getPort("port"); |
| } |
| |
| } // namespace gem5 |