Files
gem5/src/dev/arm/smmu_v3.cc
Gabe Black 91d83cc8a1 misc: Standardize the way create() constructs SimObjects.
The create() method on Params structs usually instantiate SimObjects
using a constructor which takes the Params struct as a parameter
somehow. There has been a lot of needless variation in how that was
done, making it annoying to pass Params down to base classes. Some of
the different forms were:

const Params &
Params &
Params *
const Params *
Params const*

This change goes through and fixes up every constructor and every
create() method to use the const Params & form. We use a reference
because the Params struct should never be null. We use const because
neither the create method nor the consuming object should modify the
record of the parameters as they came in from the config. That would
make consuming them not idempotent, and make it impossible to tell what
the actual simulation configuration was since it would change from any
user visible form (config script, config.ini, dot pdf output).

Change-Id: I77453cba52fdcfd5f4eec92dfb0bddb5a9945f31
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/35938
Reviewed-by: Gabe Black <gabeblack@google.com>
Reviewed-by: Daniel Carvalho <odanrc@yahoo.com.br>
Maintainer: Gabe Black <gabeblack@google.com>
Tested-by: kokoro <noreply+kokoro@google.com>
2020-10-14 12:06:44 +00:00

835 lines
26 KiB
C++

/*
* 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.hh"
#include <cstddef>
#include <cstring>
#include "base/bitfield.hh"
#include "base/cast.hh"
#include "base/logging.hh"
#include "base/trace.hh"
#include "base/types.hh"
#include "debug/Checkpoint.hh"
#include "debug/SMMUv3.hh"
#include "dev/arm/smmu_v3_transl.hh"
#include "mem/packet_access.hh"
#include "sim/system.hh"
SMMUv3::SMMUv3(const SMMUv3Params &params) :
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),
tlb(params.tlb_entries, params.tlb_assoc, params.tlb_policy),
configCache(params.cfg_entries, params.cfg_assoc, params.cfg_policy),
ipaCache(params.ipa_entries, params.ipa_assoc, params.ipa_policy),
walkCache({ { params.walk_S1L0, params.walk_S1L1,
params.walk_S1L2, params.walk_S1L3,
params.walk_S2L0, params.walk_S2L1,
params.walk_S2L2, params.walk_S2L3 } },
params.walk_assoc, params.walk_policy),
tlbEnable(params.tlb_enable),
configCacheEnable(params.cfg_enable),
ipaCacheEnable(params.ipa_enable),
walkCacheEnable(params.walk_enable),
tableWalkPortEnable(false),
walkCacheNonfinalEnable(params.wc_nonfinal_enable),
walkCacheS1Levels(params.wc_s1_levels),
walkCacheS2Levels(params.wc_s2_levels),
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),
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(&regs, 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;
}
M5_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;
}
M5_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;
}
}
const PageTableOps*
SMMUv3::getPageTableOps(uint8_t trans_granule)
{
static V8PageTableOps4k ptOps4k;
static V8PageTableOps16k ptOps16k;
static V8PageTableOps64k ptOps64k;
switch (trans_granule) {
case TRANS_GRANULE_4K: return &ptOps4k;
case TRANS_GRANULE_16K: return &ptOps16k;
case TRANS_GRANULE_64K: return &ptOps64k;
default:
panic("Unknown translation granule size %d", trans_granule);
}
}
Tick
SMMUv3::readControl(PacketPtr pkt)
{
DPRINTF(SMMUv3, "readControl: addr=%08x size=%d\n",
pkt->getAddr(), pkt->getSize());
int offset = pkt->getAddr() - regsMap.start();
assert(offset >= 0 && offset < SMMU_REG_SIZE);
if (inSecureBlock(offset)) {
warn("smmu: secure registers (0x%x) are not implemented\n",
offset);
}
auto reg_ptr = regs.data + offset;
switch (pkt->getSize()) {
case sizeof(uint32_t):
pkt->setLE<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, 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();
}
void
SMMUv3::regStats()
{
ClockedObject::regStats();
using namespace Stats;
for (size_t i = 0; i < deviceInterfaces.size(); i++) {
deviceInterfaces[i]->microTLB->regStats(
csprintf("%s.utlb%d", name(), i));
deviceInterfaces[i]->mainTLB->regStats(
csprintf("%s.maintlb%d", name(), i));
}
tlb.regStats(name() + ".tlb");
configCache.regStats(name() + ".cfg");
ipaCache.regStats(name() + ".ipa");
walkCache.regStats(name() + ".walk");
steL1Fetches
.name(name() + ".steL1Fetches")
.desc("STE L1 fetches")
.flags(pdf);
steFetches
.name(name() + ".steFetches")
.desc("STE fetches")
.flags(pdf);
cdL1Fetches
.name(name() + ".cdL1Fetches")
.desc("CD L1 fetches")
.flags(pdf);
cdFetches
.name(name() + ".cdFetches")
.desc("CD fetches")
.flags(pdf);
translationTimeDist
.init(0, 2000000, 2000)
.name(name() + ".translationTimeDist")
.desc("Time to translate address")
.flags(pdf);
ptwTimeDist
.init(0, 2000000, 2000)
.name(name() + ".ptwTimeDist")
.desc("Time to walk page tables")
.flags(pdf);
}
DrainState
SMMUv3::drain()
{
// Wait until the Command Executor is not busy
if (commandExecutor.isBusy()) {
return DrainState::Draining;
}
return DrainState::Drained;
}
void
SMMUv3::serialize(CheckpointOut &cp) const
{
DPRINTF(Checkpoint, "Serializing SMMUv3\n");
SERIALIZE_ARRAY(regs.data, sizeof(regs.data) / sizeof(regs.data[0]));
}
void
SMMUv3::unserialize(CheckpointIn &cp)
{
DPRINTF(Checkpoint, "Unserializing SMMUv3\n");
UNSERIALIZE_ARRAY(regs.data, sizeof(regs.data) / sizeof(regs.data[0]));
}
Port&
SMMUv3::getPort(const std::string &name, PortID id)
{
if (name == "request") {
return requestPort;
} else if (name == "walker") {
return tableWalkPort;
} else if (name == "control") {
return controlPort;
} else {
return ClockedObject::getPort(name, id);
}
}
SMMUv3*
SMMUv3Params::create() const
{
return new SMMUv3(*this);
}