As the VMSA is shared between the CPU MMU and the SMMU, we move the PageTableOps data structures to the arch/arm/pagetable.hh/cc sources. Both MMUs will make use of them Change-Id: I3a1113f6ef56f8d879aff2df50a01037baca82ff Signed-off-by: Giacomo Travaglini <giacomo.travaglini@arm.com> Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/51672 Tested-by: kokoro <noreply+kokoro@google.com>
810 lines
26 KiB
C++
810 lines
26 KiB
C++
/*
|
|
* Copyright (c) 2013, 2018-2020 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.hh"
|
|
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
|
|
#include "base/bitfield.hh"
|
|
#include "base/cast.hh"
|
|
#include "base/compiler.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"
|
|
|
|
namespace gem5
|
|
{
|
|
|
|
SMMUv3::SMMUv3(const SMMUv3Params ¶ms) :
|
|
ClockedObject(params),
|
|
system(*params.system),
|
|
requestorId(params.system->getRequestorId(this)),
|
|
requestPort(name() + ".request", *this),
|
|
tableWalkPort(name() + ".walker", *this),
|
|
controlPort(name() + ".control", *this, params.reg_map),
|
|
irqInterfaceEnable(params.irq_interface_enable),
|
|
tlb(params.tlb_entries, params.tlb_assoc, params.tlb_policy, this),
|
|
configCache(params.cfg_entries, params.cfg_assoc, params.cfg_policy, this),
|
|
ipaCache(params.ipa_entries, params.ipa_assoc, params.ipa_policy, this),
|
|
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, this),
|
|
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),
|
|
requestPortWidth(params.request_port_width),
|
|
tlbSem(params.tlb_slots),
|
|
ifcSmmuSem(1),
|
|
smmuIfcSem(1),
|
|
configSem(params.cfg_slots),
|
|
ipaSem(params.ipa_slots),
|
|
walkSem(params.walk_slots),
|
|
requestPortSem(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),
|
|
stats(this),
|
|
deviceInterfaces(params.device_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 : deviceInterfaces)
|
|
ifc->setSMMU(this);
|
|
}
|
|
|
|
bool
|
|
SMMUv3::recvTimingResp(PacketPtr pkt)
|
|
{
|
|
DPRINTF(SMMUv3, "[t] requestor resp addr=%#x size=%#x\n",
|
|
pkt->getAddr(), pkt->getSize());
|
|
|
|
// @todo: We need to pay for this and not just zero it out
|
|
pkt->headerDelay = pkt->payloadDelay = 0;
|
|
|
|
SMMUProcess *proc =
|
|
safe_cast<SMMUProcess *>(pkt->popSenderState());
|
|
|
|
runProcessTiming(proc, pkt);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
SMMUv3::recvReqRetry()
|
|
{
|
|
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] requestor retr addr=%#x size=%#x\n",
|
|
a.pkt->getAddr(), a.pkt->getSize());
|
|
|
|
if (!requestPort.sendTimingReq(a.pkt))
|
|
break;
|
|
|
|
packetsToRetry.pop();
|
|
|
|
/*
|
|
* ACTION_SEND_REQ_FINAL means that we have just forwarded the packet
|
|
* on the requestor interface; this means that we no longer hold on to
|
|
* that transaction and therefore can accept a new one.
|
|
* If the response port was stalled then unstall it (send retry).
|
|
*/
|
|
if (a.type == ACTION_SEND_REQ_FINAL)
|
|
scheduleDeviceRetries();
|
|
}
|
|
}
|
|
|
|
bool
|
|
SMMUv3::tableWalkRecvTimingResp(PacketPtr pkt)
|
|
{
|
|
DPRINTF(SMMUv3, "[t] requestor HWTW resp addr=%#x size=%#x\n",
|
|
pkt->getAddr(), pkt->getSize());
|
|
|
|
// @todo: We need to pay for this and not just zero it out
|
|
pkt->headerDelay = pkt->payloadDelay = 0;
|
|
|
|
SMMUProcess *proc =
|
|
safe_cast<SMMUProcess *>(pkt->popSenderState());
|
|
|
|
runProcessTiming(proc, pkt);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
SMMUv3::tableWalkRecvReqRetry()
|
|
{
|
|
assert(tableWalkPortEnable);
|
|
assert(!packetsTableWalkToRetry.empty());
|
|
|
|
while (!packetsTableWalkToRetry.empty()) {
|
|
SMMUAction a = packetsTableWalkToRetry.front();
|
|
|
|
assert(a.type==ACTION_SEND_REQ);
|
|
|
|
DPRINTF(SMMUv3, "[t] requestor HWTW retr addr=%#x size=%#x\n",
|
|
a.pkt->getAddr(), a.pkt->getSize());
|
|
|
|
if (!tableWalkPort.sendTimingReq(a.pkt))
|
|
break;
|
|
|
|
packetsTableWalkToRetry.pop();
|
|
}
|
|
}
|
|
|
|
void
|
|
SMMUv3::scheduleDeviceRetries()
|
|
{
|
|
for (auto ifc : deviceInterfaces) {
|
|
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 += tableWalkPort.sendAtomic(action.pkt);
|
|
pkt = action.pkt;
|
|
break;
|
|
}
|
|
[[fallthrough]];
|
|
case ACTION_SEND_REQ_FINAL:
|
|
delay += requestPort.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] requestor HWTW req addr=%#x size=%#x\n",
|
|
action.pkt->getAddr(), action.pkt->getSize());
|
|
|
|
if (packetsTableWalkToRetry.empty()
|
|
&& tableWalkPort.sendTimingReq(action.pkt)) {
|
|
scheduleDeviceRetries();
|
|
} else {
|
|
DPRINTF(SMMUv3, "[t] requestor HWTW req needs retry,"
|
|
" qlen=%d\n", packetsTableWalkToRetry.size());
|
|
packetsTableWalkToRetry.push(action);
|
|
}
|
|
|
|
break;
|
|
}
|
|
[[fallthrough]];
|
|
case ACTION_SEND_REQ_FINAL:
|
|
action.pkt->pushSenderState(proc);
|
|
|
|
DPRINTF(SMMUv3, "[t] requestor req addr=%#x size=%#x\n",
|
|
action.pkt->getAddr(), action.pkt->getSize());
|
|
|
|
if (packetsToRetry.empty() &&
|
|
requestPort.sendTimingReq(action.pkt)) {
|
|
scheduleDeviceRetries();
|
|
} else {
|
|
DPRINTF(SMMUv3, "[t] requestor 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] responder 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 responder 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 dev_interface : deviceInterfaces) {
|
|
dev_interface->microTLB->invalidateSID(cmd.dw0.sid);
|
|
dev_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 dev_interface : deviceInterfaces) {
|
|
dev_interface->microTLB->invalidateAll();
|
|
dev_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 dev_interface : deviceInterfaces) {
|
|
dev_interface->microTLB->invalidateSID(sid);
|
|
dev_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 dev_interface : deviceInterfaces) {
|
|
dev_interface->microTLB->invalidateSSID(
|
|
cmd.dw0.sid, cmd.dw0.ssid);
|
|
dev_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 dev_interface : deviceInterfaces) {
|
|
dev_interface->microTLB->invalidateSID(cmd.dw0.sid);
|
|
dev_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 dev_interface : deviceInterfaces) {
|
|
dev_interface->microTLB->invalidateVMID(cmd.dw0.vmid);
|
|
dev_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 dev_interface : deviceInterfaces) {
|
|
dev_interface->microTLB->invalidateASID(
|
|
cmd.dw0.asid, cmd.dw0.vmid);
|
|
dev_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 dev_interface : deviceInterfaces) {
|
|
dev_interface->microTLB->invalidateVAA(
|
|
addr, cmd.dw0.vmid);
|
|
dev_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 dev_interface : deviceInterfaces) {
|
|
dev_interface->microTLB->invalidateVA(
|
|
addr, cmd.dw0.asid, cmd.dw0.vmid);
|
|
dev_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 dev_interface : deviceInterfaces) {
|
|
dev_interface->microTLB->invalidateVMID(cmd.dw0.vmid);
|
|
dev_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 dev_interface : deviceInterfaces) {
|
|
dev_interface->microTLB->invalidateAll();
|
|
dev_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;
|
|
}
|
|
}
|
|
|
|
Tick
|
|
SMMUv3::readControl(PacketPtr pkt)
|
|
{
|
|
DPRINTF(SMMUv3, "readControl: addr=%08x size=%d\n",
|
|
pkt->getAddr(), pkt->getSize());
|
|
|
|
int offset = pkt->getAddr() - regsMap.start();
|
|
assert(offset >= 0 && offset < SMMU_REG_SIZE);
|
|
|
|
if (inSecureBlock(offset)) {
|
|
warn("smmu: secure registers (0x%x) are not implemented\n",
|
|
offset);
|
|
}
|
|
|
|
auto reg_ptr = regs.data + offset;
|
|
|
|
switch (pkt->getSize()) {
|
|
case sizeof(uint32_t):
|
|
pkt->setLE<uint32_t>(*reinterpret_cast<uint32_t *>(reg_ptr));
|
|
break;
|
|
case sizeof(uint64_t):
|
|
pkt->setLE<uint64_t>(*reinterpret_cast<uint64_t *>(reg_ptr));
|
|
break;
|
|
default:
|
|
panic("smmu: unallowed access size: %d bytes\n", pkt->getSize());
|
|
break;
|
|
}
|
|
|
|
pkt->makeAtomicResponse();
|
|
|
|
return 0;
|
|
}
|
|
|
|
Tick
|
|
SMMUv3::writeControl(PacketPtr pkt)
|
|
{
|
|
int offset = pkt->getAddr() - regsMap.start();
|
|
assert(offset >= 0 && offset < SMMU_REG_SIZE);
|
|
|
|
DPRINTF(SMMUv3, "writeControl: addr=%08x size=%d data=%16x\n",
|
|
pkt->getAddr(), pkt->getSize(),
|
|
pkt->getSize() == sizeof(uint64_t) ?
|
|
pkt->getLE<uint64_t>() : pkt->getLE<uint32_t>());
|
|
|
|
switch (offset) {
|
|
case offsetof(SMMURegs, cr0):
|
|
assert(pkt->getSize() == sizeof(uint32_t));
|
|
regs.cr0 = regs.cr0ack = pkt->getLE<uint32_t>();
|
|
break;
|
|
case offsetof(SMMURegs, irq_ctrl):
|
|
assert(pkt->getSize() == sizeof(uint32_t));
|
|
if (irqInterfaceEnable) {
|
|
warn("SMMUv3::%s No support for interrupt sources", __func__);
|
|
regs.irq_ctrl = regs.irq_ctrlack = pkt->getLE<uint32_t>();
|
|
}
|
|
break;
|
|
|
|
case offsetof(SMMURegs, cr1):
|
|
case offsetof(SMMURegs, cr2):
|
|
case offsetof(SMMURegs, strtab_base_cfg):
|
|
case offsetof(SMMURegs, eventq_cons):
|
|
case offsetof(SMMURegs, eventq_irq_cfg1):
|
|
case offsetof(SMMURegs, priq_cons):
|
|
assert(pkt->getSize() == sizeof(uint32_t));
|
|
*reinterpret_cast<uint32_t *>(regs.data + offset) =
|
|
pkt->getLE<uint32_t>();
|
|
break;
|
|
|
|
case offsetof(SMMURegs, cmdq_cons):
|
|
assert(pkt->getSize() == sizeof(uint32_t));
|
|
if (regs.cr0 & CR0_CMDQEN_MASK) {
|
|
warn("CMDQ is enabled: ignoring write to CMDQ_CONS\n");
|
|
} else {
|
|
*reinterpret_cast<uint32_t *>(regs.data + offset) =
|
|
pkt->getLE<uint32_t>();
|
|
}
|
|
break;
|
|
|
|
case offsetof(SMMURegs, cmdq_prod):
|
|
assert(pkt->getSize() == sizeof(uint32_t));
|
|
*reinterpret_cast<uint32_t *>(regs.data + offset) =
|
|
pkt->getLE<uint32_t>();
|
|
schedule(processCommandsEvent, nextCycle());
|
|
break;
|
|
|
|
case offsetof(SMMURegs, strtab_base):
|
|
case offsetof(SMMURegs, eventq_irq_cfg0):
|
|
assert(pkt->getSize() == sizeof(uint64_t));
|
|
*reinterpret_cast<uint64_t *>(regs.data + offset) =
|
|
pkt->getLE<uint64_t>();
|
|
break;
|
|
|
|
case offsetof(SMMURegs, cmdq_base):
|
|
assert(pkt->getSize() == sizeof(uint64_t));
|
|
if (regs.cr0 & CR0_CMDQEN_MASK) {
|
|
warn("CMDQ is enabled: ignoring write to CMDQ_BASE\n");
|
|
} else {
|
|
*reinterpret_cast<uint64_t *>(regs.data + offset) =
|
|
pkt->getLE<uint64_t>();
|
|
regs.cmdq_cons = 0;
|
|
regs.cmdq_prod = 0;
|
|
}
|
|
break;
|
|
|
|
case offsetof(SMMURegs, eventq_base):
|
|
assert(pkt->getSize() == sizeof(uint64_t));
|
|
*reinterpret_cast<uint64_t *>(regs.data + offset) =
|
|
pkt->getLE<uint64_t>();
|
|
regs.eventq_cons = 0;
|
|
regs.eventq_prod = 0;
|
|
break;
|
|
|
|
case offsetof(SMMURegs, priq_base):
|
|
assert(pkt->getSize() == sizeof(uint64_t));
|
|
*reinterpret_cast<uint64_t *>(regs.data + offset) =
|
|
pkt->getLE<uint64_t>();
|
|
regs.priq_cons = 0;
|
|
regs.priq_prod = 0;
|
|
break;
|
|
|
|
default:
|
|
if (inSecureBlock(offset)) {
|
|
warn("smmu: secure registers (0x%x) are not implemented\n",
|
|
offset);
|
|
} else {
|
|
warn("smmu: write to read-only/undefined register at 0x%x\n",
|
|
offset);
|
|
}
|
|
}
|
|
|
|
pkt->makeAtomicResponse();
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
SMMUv3::inSecureBlock(uint32_t offs) const
|
|
{
|
|
if (offs >= offsetof(SMMURegs, _secure_regs) && offs < SMMU_SECURE_SZ)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void
|
|
SMMUv3::init()
|
|
{
|
|
// make sure both sides are connected and have the same block size
|
|
if (!requestPort.isConnected())
|
|
fatal("Request port is not connected.\n");
|
|
|
|
// If the second request port is connected for the table walks, enable
|
|
// the mode to send table walks through this port instead
|
|
if (tableWalkPort.isConnected())
|
|
tableWalkPortEnable = true;
|
|
|
|
// notify the request side of our address ranges
|
|
for (auto ifc : deviceInterfaces) {
|
|
ifc->sendRange();
|
|
}
|
|
|
|
if (controlPort.isConnected())
|
|
controlPort.sendRangeChange();
|
|
}
|
|
|
|
SMMUv3::SMMUv3Stats::SMMUv3Stats(statistics::Group *parent)
|
|
: statistics::Group(parent),
|
|
ADD_STAT(steL1Fetches, statistics::units::Count::get(), "STE L1 fetches"),
|
|
ADD_STAT(steFetches, statistics::units::Count::get(), "STE fetches"),
|
|
ADD_STAT(cdL1Fetches, statistics::units::Count::get(), "CD L1 fetches"),
|
|
ADD_STAT(cdFetches, statistics::units::Count::get(), "CD fetches"),
|
|
ADD_STAT(translationTimeDist, statistics::units::Tick::get(),
|
|
"Time to translate address"),
|
|
ADD_STAT(ptwTimeDist, statistics::units::Tick::get(),
|
|
"Time to walk page tables")
|
|
{
|
|
using namespace statistics;
|
|
|
|
steL1Fetches
|
|
.flags(pdf);
|
|
|
|
steFetches
|
|
.flags(pdf);
|
|
|
|
cdL1Fetches
|
|
.flags(pdf);
|
|
|
|
cdFetches
|
|
.flags(pdf);
|
|
|
|
translationTimeDist
|
|
.init(0, 2000000, 2000)
|
|
.flags(pdf);
|
|
|
|
ptwTimeDist
|
|
.init(0, 2000000, 2000)
|
|
.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 == "request") {
|
|
return requestPort;
|
|
} else if (name == "walker") {
|
|
return tableWalkPort;
|
|
} else if (name == "control") {
|
|
return controlPort;
|
|
} else {
|
|
return ClockedObject::getPort(name, id);
|
|
}
|
|
}
|
|
|
|
} // namespace gem5
|