blob: 0ff5bd7498bdbd85944fb5d999f015f5178b1874 [file] [log] [blame]
/*
* Copyright (c) 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.
*
* Copyright 2019 Google, Inc.
*
* 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/arm/fastmodel/iris/thread_context.hh"
#include <utility>
#include "arch/arm/fastmodel/iris/cpu.hh"
#include "arch/arm/system.hh"
#include "arch/arm/utility.hh"
#include "iris/detail/IrisCppAdapter.h"
#include "iris/detail/IrisObjects.h"
#include "mem/se_translating_port_proxy.hh"
#include "mem/translating_port_proxy.hh"
#include "sim/pseudo_inst.hh"
namespace gem5
{
namespace Iris
{
void
ThreadContext::initFromIrisInstance(const ResourceMap &resources)
{
bool enabled = false;
call().perInstanceExecution_getState(_instId, enabled);
_status = enabled ? Active : Suspended;
suspend();
call().memory_getMemorySpaces(_instId, memorySpaces);
call().memory_getUsefulAddressTranslations(_instId, translations);
typedef ThreadContext Self;
iris::EventSourceInfo evSrcInfo;
client.registerEventCallback<Self, &Self::breakpointHit>(
this, "ec_IRIS_BREAKPOINT_HIT",
"Handle hitting a breakpoint", "Iris::ThreadContext");
call().event_getEventSource(_instId, evSrcInfo, "IRIS_BREAKPOINT_HIT");
call().eventStream_create(_instId, breakpointEventStreamId,
evSrcInfo.evSrcId, client.getInstId());
for (auto it = bps.begin(); it != bps.end(); it++)
installBp(it);
client.registerEventCallback<Self, &Self::semihostingEvent>(
this, "ec_IRIS_SEMIHOSTING_CALL_EXTENSION",
"Handle a semihosting call", "Iris::ThreadContext");
call().event_getEventSource(_instId, evSrcInfo,
"IRIS_SEMIHOSTING_CALL_EXTENSION");
call().eventStream_create(_instId, semihostingEventStreamId,
evSrcInfo.evSrcId, client.getInstId(),
// Set all arguments to their defaults, except syncEc which is
// changed to true.
nullptr, "", false, 0, nullptr, false, false, true);
}
iris::ResourceId
ThreadContext::extractResourceId(
const ResourceMap &resources, const std::string &name)
{
return resources.at(name).rscId;
}
void
ThreadContext::extractResourceMap(
ResourceIds &ids, const ResourceMap &resources,
const IdxNameMap &idx_names)
{
for (const auto &idx_name: idx_names) {
int idx = idx_name.first;
const std::string &name = idx_name.second;
if (idx >= ids.size())
ids.resize(idx + 1, iris::IRIS_UINT64_MAX);
ids[idx] = extractResourceId(resources, name);
}
}
void
ThreadContext::maintainStepping()
{
Tick now = 0;
while (true) {
if (comInstEventQueue.empty()) {
// Set to 0 to deactivate stepping.
call().step_setup(_instId, 0, "instruction");
break;
}
Tick next = comInstEventQueue.nextTick();
if (!now)
now = getCurrentInstCount();
if (next <= now) {
comInstEventQueue.serviceEvents(now);
// Start over now that comInstEventQueue has likely changed.
continue;
}
// Set to the number of instructions still to step through.
Tick remaining = next - now;
call().step_setup(_instId, remaining, "instruction");
break;
}
}
ThreadContext::BpInfoIt
ThreadContext::getOrAllocBp(Addr pc)
{
auto pc_it = bps.find(pc);
if (pc_it != bps.end())
return pc_it;
auto res = bps.emplace(std::make_pair(pc, new BpInfo(pc)));
panic_if(!res.second, "Inserting breakpoint failed.");
return res.first;
}
void
ThreadContext::installBp(BpInfoIt it)
{
Addr pc = it->second->pc;
const auto &space_ids = getBpSpaceIds();
for (auto sid: space_ids) {
BpId id;
call().breakpoint_set_code(_instId, id, pc, sid, 0, true);
it->second->ids.push_back(id);
}
}
void
ThreadContext::uninstallBp(BpInfoIt it)
{
for (auto id: it->second->ids)
call().breakpoint_delete(_instId, id);
it->second->clearIds();
}
void
ThreadContext::delBp(BpInfoIt it)
{
panic_if(!it->second->empty(),
"BP info still had events associated with it.");
if (it->second->validIds())
uninstallBp(it);
bps.erase(it);
}
iris::IrisErrorCode
ThreadContext::instanceRegistryChanged(
uint64_t esId, const iris::IrisValueMap &fields, uint64_t time,
uint64_t sInstId, bool syncEc, std::string &error_message_out)
{
const std::string &event = fields.at("EVENT").getString();
const iris::InstanceId id = fields.at("INST_ID").getU64();
const std::string &name = fields.at("INST_NAME").getString();
if (name != "component." + _irisPath)
return iris::E_ok;
if (event == "added")
_instId = id;
else if (event == "removed")
_instId = iris::IRIS_UINT64_MAX;
else
panic("Unrecognized event type %s", event);
return iris::E_ok;
}
iris::IrisErrorCode
ThreadContext::phaseInitLeave(
uint64_t esId, const iris::IrisValueMap &fields, uint64_t time,
uint64_t sInstId, bool syncEc, std::string &error_message_out)
{
std::vector<iris::ResourceInfo> resources;
call().resource_getList(_instId, resources);
std::map<iris::ResourceId, const iris::ResourceInfo *>
idToResource;
for (const auto &resource: resources) {
idToResource[resource.rscId] = &resource;
}
ResourceMap resourceMap;
for (const auto &resource: resources) {
std::string name = resource.name;
iris::ResourceId parentId = resource.parentRscId;
while (parentId != iris::IRIS_UINT64_MAX) {
const auto *parent = idToResource[parentId];
name = parent->name + "." + name;
parentId = parent->parentRscId;
}
resourceMap[name] = resource;
}
initFromIrisInstance(resourceMap);
return iris::E_ok;
}
iris::IrisErrorCode
ThreadContext::simulationTimeEvent(
uint64_t esId, const iris::IrisValueMap &fields, uint64_t time,
uint64_t sInstId, bool syncEc, std::string &error_message_out)
{
if (fields.at("RUNNING").getAsBool()) {
// If this is just simulation time starting up, don't do anything.
return iris::E_ok;
}
// If simulation time has stopped for any reason, IRIS helpfully clears
// all stepping counters and we need to set them back. We might also need
// to service events based on the current number of executed instructions.
maintainStepping();
// Restart simulation time to make sure things progress once we give
// control back.
call().simulationTime_run(iris::IrisInstIdSimulationEngine);
return iris::E_ok;
}
iris::IrisErrorCode
ThreadContext::breakpointHit(
uint64_t esId, const iris::IrisValueMap &fields, uint64_t time,
uint64_t sInstId, bool syncEc, std::string &error_message_out)
{
Addr pc = fields.at("PC").getU64();
auto it = getOrAllocBp(pc);
std::shared_ptr<BpInfo::EventList> events = it->second->events;
auto e_it = events->begin();
while (e_it != events->end()) {
PCEvent *e = *e_it;
// Advance e_it here since e might remove itself from the list.
e_it++;
e->process(this);
}
return iris::E_ok;
}
iris::IrisErrorCode
ThreadContext::semihostingEvent(
uint64_t esId, const iris::IrisValueMap &fields, uint64_t time,
uint64_t sInstId, bool syncEc, std::string &error_message_out)
{
if (ArmSystem::callSemihosting(this, true)) {
// Stop execution in case an exit of the sim loop was scheduled. We
// don't want to keep executing instructions in the mean time.
call().perInstanceExecution_setState(_instId, false);
// Schedule an event to resume execution right after any exit has
// had a chance to happen.
if (!enableAfterPseudoEvent->scheduled())
getCpuPtr()->schedule(enableAfterPseudoEvent, curTick());
call().semihosting_return(_instId, readIntReg(0));
} else {
call().semihosting_notImplemented(_instId);
}
return iris::E_ok;
}
ThreadContext::ThreadContext(
gem5::BaseCPU *cpu, int id, System *system, gem5::BaseMMU *mmu,
BaseISA *isa, iris::IrisConnectionInterface *iris_if,
const std::string &iris_path) :
_cpu(cpu), _threadId(id), _system(system), _mmu(mmu), _isa(isa),
_irisPath(iris_path), vecRegs(ArmISA::NumVecRegs),
vecPredRegs(ArmISA::NumVecPredRegs),
comInstEventQueue("instruction-based event queue"),
client(iris_if, "client." + iris_path)
{
iris::InstanceInfo info;
auto ret_code = noThrow().instanceRegistry_getInstanceInfoByName(
info, "component." + iris_path);
if (ret_code == iris::E_ok) {
// The iris instance registry already new about this path.
_instId = info.instId;
} else {
// This path doesn't (yet) exist. Set the ID to something invalid.
_instId = iris::IRIS_UINT64_MAX;
}
typedef ThreadContext Self;
iris::EventSourceInfo evSrcInfo;
client.registerEventCallback<Self, &Self::instanceRegistryChanged>(
this, "ec_IRIS_INSTANCE_REGISTRY_CHANGED",
"Install the iris instance ID", "Iris::ThreadContext");
call().event_getEventSource(iris::IrisInstIdGlobalInstance, evSrcInfo,
"IRIS_INSTANCE_REGISTRY_CHANGED");
regEventStreamId = iris::IRIS_UINT64_MAX;
static const std::vector<std::string> fields =
{ "EVENT", "INST_ID", "INST_NAME" };
call().eventStream_create(iris::IrisInstIdGlobalInstance, regEventStreamId,
evSrcInfo.evSrcId, client.getInstId(), &fields);
client.registerEventCallback<Self, &Self::phaseInitLeave>(
this, "ec_IRIS_SIM_PHASE_INIT_LEAVE",
"Initialize register contexts", "Iris::ThreadContext");
call().event_getEventSource(iris::IrisInstIdSimulationEngine, evSrcInfo,
"IRIS_SIM_PHASE_INIT_LEAVE");
initEventStreamId = iris::IRIS_UINT64_MAX;
call().eventStream_create(
iris::IrisInstIdSimulationEngine, initEventStreamId,
evSrcInfo.evSrcId, client.getInstId());
client.registerEventCallback<Self, &Self::simulationTimeEvent>(
this, "ec_IRIS_SIMULATION_TIME_EVENT",
"Handle simulation time stopping for breakpoints or stepping",
"Iris::ThreadContext");
call().event_getEventSource(iris::IrisInstIdSimulationEngine, evSrcInfo,
"IRIS_SIMULATION_TIME_EVENT");
timeEventStreamId = iris::IRIS_UINT64_MAX;
call().eventStream_create(
iris::IrisInstIdSimulationEngine, timeEventStreamId,
evSrcInfo.evSrcId, client.getInstId());
breakpointEventStreamId = iris::IRIS_UINT64_MAX;
semihostingEventStreamId = iris::IRIS_UINT64_MAX;
auto enable_lambda = [this]{
call().perInstanceExecution_setState(_instId, true);
};
enableAfterPseudoEvent = new EventFunctionWrapper(
enable_lambda, "resume after pseudo inst",
false, Event::Sim_Exit_Pri + 1);
}
ThreadContext::~ThreadContext()
{
call().eventStream_destroy(
iris::IrisInstIdSimulationEngine, initEventStreamId);
initEventStreamId = iris::IRIS_UINT64_MAX;
client.unregisterEventCallback("ec_IRIS_SIM_PHASE_INIT_LEAVE");
call().eventStream_destroy(
iris::IrisInstIdGlobalInstance, regEventStreamId);
regEventStreamId = iris::IRIS_UINT64_MAX;
client.unregisterEventCallback("ec_IRIS_INSTANCE_REGISTRY_CHANGED");
call().eventStream_destroy(
iris::IrisInstIdGlobalInstance, timeEventStreamId);
timeEventStreamId = iris::IRIS_UINT64_MAX;
client.unregisterEventCallback("ec_IRIS_SIMULATION_TIME_EVENT");
if (enableAfterPseudoEvent->scheduled())
getCpuPtr()->deschedule(enableAfterPseudoEvent);
delete enableAfterPseudoEvent;
}
bool
ThreadContext::schedule(PCEvent *e)
{
auto it = getOrAllocBp(e->pc());
it->second->events->push_back(e);
if (_instId != iris::IRIS_UINT64_MAX && !it->second->validIds())
installBp(it);
return true;
}
bool
ThreadContext::remove(PCEvent *e)
{
auto it = getOrAllocBp(e->pc());
it->second->events->remove(e);
if (it->second->empty())
delBp(it);
return true;
}
bool
ThreadContext::translateAddress(Addr &paddr, iris::MemorySpaceId p_space,
Addr vaddr, iris::MemorySpaceId v_space)
{
iris::MemoryAddressTranslationResult result;
auto ret = noThrow().memory_translateAddress(
_instId, result, v_space, vaddr, p_space);
if (ret != iris::E_ok) {
// Check if there was a legal translation between these two spaces.
// If so, something else went wrong.
for (auto &trans: translations)
if (trans.inSpaceId == v_space && trans.outSpaceId == p_space)
return false;
panic("No legal translation IRIS address translation found.");
}
if (result.address.empty())
return false;
if (result.address.size() > 1) {
warn("Multiple mappings for address %#x.", vaddr);
return false;
}
paddr = result.address[0];
return true;
}
void
ThreadContext::scheduleInstCountEvent(Event *event, Tick count)
{
Tick now = getCurrentInstCount();
comInstEventQueue.schedule(event, count);
if (count <= now)
call().simulationTime_stop(iris::IrisInstIdSimulationEngine);
else
maintainStepping();
}
void
ThreadContext::descheduleInstCountEvent(Event *event)
{
comInstEventQueue.deschedule(event);
maintainStepping();
}
Tick
ThreadContext::getCurrentInstCount()
{
uint64_t count;
auto ret = call().step_getStepCounterValue(_instId, count, "instruction");
panic_if(ret != iris::E_ok, "Failed to get instruction count.");
return count;
}
void
ThreadContext::initMemProxies(gem5::ThreadContext *tc)
{
assert(!virtProxy);
if (FullSystem) {
virtProxy.reset(new TranslatingPortProxy(tc));
} else {
virtProxy.reset(new SETranslatingPortProxy(this,
SETranslatingPortProxy::NextPage));
}
}
void
ThreadContext::sendFunctional(PacketPtr pkt)
{
auto *iris_cpu = dynamic_cast<Iris::BaseCPU *>(getCpuPtr());
assert(iris_cpu);
iris_cpu->evs_base_cpu->sendFunc(pkt);
}
ThreadContext::Status
ThreadContext::status() const
{
return _status;
}
void
ThreadContext::setStatus(Status new_status)
{
if (enableAfterPseudoEvent->scheduled())
getCpuPtr()->deschedule(enableAfterPseudoEvent);
if (new_status == Active) {
if (_status != Active)
call().perInstanceExecution_setState(_instId, true);
} else {
if (_status == Active)
call().perInstanceExecution_setState(_instId, false);
}
_status = new_status;
}
ArmISA::PCState
ThreadContext::pcState() const
{
ArmISA::CPSR cpsr = readMiscRegNoEffect(ArmISA::MISCREG_CPSR);
ArmISA::PCState pc;
pc.thumb(cpsr.t);
pc.nextThumb(pc.thumb());
pc.jazelle(cpsr.j);
pc.nextJazelle(cpsr.j);
pc.aarch64(!cpsr.width);
pc.nextAArch64(!cpsr.width);
pc.illegalExec(false);
pc.itstate(ArmISA::itState(cpsr));
pc.nextItstate(0);
iris::ResourceReadResult result;
call().resource_read(_instId, result, pcRscId);
Addr addr = result.data.at(0);
if (cpsr.width && cpsr.t)
addr = addr & ~0x1;
pc.set(addr);
return pc;
}
void
ThreadContext::pcState(const ArmISA::PCState &val)
{
Addr pc = val.pc();
ArmISA::CPSR cpsr = readMiscRegNoEffect(ArmISA::MISCREG_CPSR);
if (cpsr.width && cpsr.t)
pc = pc | 0x1;
iris::ResourceWriteResult result;
call().resource_write(_instId, result, pcRscId, pc);
}
Addr
ThreadContext::instAddr() const
{
return pcState().instAddr();
}
Addr
ThreadContext::nextInstAddr() const
{
return pcState().nextInstAddr();
}
RegVal
ThreadContext::readMiscRegNoEffect(RegIndex misc_reg) const
{
iris::ResourceReadResult result;
call().resource_read(_instId, result, miscRegIds.at(misc_reg));
return result.data.at(0);
}
void
ThreadContext::setMiscRegNoEffect(RegIndex misc_reg, const RegVal val)
{
iris::ResourceWriteResult result;
call().resource_write(_instId, result, miscRegIds.at(misc_reg), val);
}
RegVal
ThreadContext::readIntReg(RegIndex reg_idx) const
{
ArmISA::CPSR cpsr = readMiscRegNoEffect(ArmISA::MISCREG_CPSR);
iris::ResourceReadResult result;
if (cpsr.width)
call().resource_read(_instId, result, intReg32Ids.at(reg_idx));
else
call().resource_read(_instId, result, intReg64Ids.at(reg_idx));
return result.data.at(0);
}
void
ThreadContext::setIntReg(RegIndex reg_idx, RegVal val)
{
ArmISA::CPSR cpsr = readMiscRegNoEffect(ArmISA::MISCREG_CPSR);
iris::ResourceWriteResult result;
if (cpsr.width)
call().resource_write(_instId, result, intReg32Ids.at(reg_idx), val);
else
call().resource_write(_instId, result, intReg64Ids.at(reg_idx), val);
}
/*
* The 64 bit version of registers gives us a pre-flattened view of the reg
* file, no matter what mode we're in or if we're currently 32 or 64 bit.
*/
RegVal
ThreadContext::readIntRegFlat(RegIndex idx) const
{
if (idx >= flattenedIntIds.size())
return 0;
iris::ResourceId res_id = flattenedIntIds.at(idx);
if (res_id == iris::IRIS_UINT64_MAX)
return 0;
iris::ResourceReadResult result;
call().resource_read(_instId, result, res_id);
return result.data.at(0);
}
void
ThreadContext::setIntRegFlat(RegIndex idx, uint64_t val)
{
iris::ResourceId res_id =
(idx >= flattenedIntIds.size()) ? iris::IRIS_UINT64_MAX :
flattenedIntIds.at(idx);
panic_if(res_id == iris::IRIS_UINT64_MAX,
"Int reg %d is not supported by fast model.", idx);
iris::ResourceWriteResult result;
call().resource_write(_instId, result, flattenedIntIds.at(idx), val);
}
RegVal
ThreadContext::readCCRegFlat(RegIndex idx) const
{
if (idx >= ccRegIds.size())
return 0;
iris::ResourceReadResult result;
call().resource_read(_instId, result, ccRegIds.at(idx));
return result.data.at(0);
}
void
ThreadContext::setCCRegFlat(RegIndex idx, RegVal val)
{
panic_if(idx >= ccRegIds.size(),
"CC reg %d is not supported by fast model.", idx);
iris::ResourceWriteResult result;
call().resource_write(_instId, result, ccRegIds.at(idx), val);
}
const ArmISA::VecRegContainer &
ThreadContext::readVecReg(const RegId &reg_id) const
{
const RegIndex idx = reg_id.index();
ArmISA::VecRegContainer &reg = vecRegs.at(idx);
reg.zero();
// Ignore accesses to registers which aren't architected. gem5 defines a
// few extra registers which it uses internally in the implementation of
// some instructions.
if (idx >= vecRegIds.size())
return reg;
iris::ResourceReadResult result;
call().resource_read(_instId, result, vecRegIds.at(idx));
size_t data_size = result.data.size() * (sizeof(*result.data.data()));
size_t size = std::min(data_size, reg.size());
memcpy(reg.as<uint8_t>(), (void *)result.data.data(), size);
return reg;
}
const ArmISA::VecRegContainer &
ThreadContext::readVecRegFlat(RegIndex idx) const
{
return readVecReg(RegId(VecRegClass, idx));
}
const ArmISA::VecPredRegContainer &
ThreadContext::readVecPredReg(const RegId &reg_id) const
{
RegIndex idx = reg_id.index();
ArmISA::VecPredRegContainer &reg = vecPredRegs.at(idx);
reg.reset();
if (idx >= vecPredRegIds.size())
return reg;
iris::ResourceReadResult result;
call().resource_read(_instId, result, vecPredRegIds.at(idx));
size_t offset = 0;
size_t num_bits = reg.NUM_BITS;
uint8_t *bytes = (uint8_t *)result.data.data();
while (num_bits > 8) {
reg.setBits(offset, 8, *bytes);
offset += 8;
num_bits -= 8;
bytes++;
}
if (num_bits)
reg.setBits(offset, num_bits, *bytes);
return reg;
}
const ArmISA::VecPredRegContainer &
ThreadContext::readVecPredRegFlat(RegIndex idx) const
{
return readVecPredReg(RegId(VecPredRegClass, idx));
}
} // namespace Iris
} // namespace gem5