blob: ac25c057a1b2edf505fe4e4c4fd35416cbbee9fa [file] [log] [blame] [edit]
/*
* Copyright (c) 2013, 2015, 2017-2018,2020,2022 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/generic_timer.hh"
#include <cmath>
#include <string_view>
#include "arch/arm/page_size.hh"
#include "arch/arm/system.hh"
#include "arch/arm/utility.hh"
#include "base/logging.hh"
#include "base/trace.hh"
#include "config/kvm_isa.hh"
#include "config/use_kvm.hh"
#include "cpu/base.hh"
#include "cpu/kvm/vm.hh"
#include "debug/Timer.hh"
#include "dev/arm/base_gic.hh"
#include "mem/packet_access.hh"
#include "params/GenericTimer.hh"
#include "params/GenericTimerFrame.hh"
#include "params/GenericTimerMem.hh"
#include "params/SystemCounter.hh"
#include "sim/core.hh"
#include "sim/cur_tick.hh"
namespace gem5
{
using namespace ArmISA;
SystemCounter::SystemCounter(const SystemCounterParams &p)
: SimObject(p),
_enabled(true),
_value(0),
_increment(1),
_freqTable(p.freqs),
_activeFreqEntry(0),
_updateTick(0),
_freqUpdateEvent([this]{ freqUpdateCallback(); }, name()),
_nextFreqEntry(0)
{
fatal_if(_freqTable.empty(), "SystemCounter::SystemCounter: Base "
"frequency not provided\n");
// Store the table end marker as a 32-bit zero word
_freqTable.push_back(0);
fatal_if(_freqTable.size() > MAX_FREQ_ENTRIES,
"SystemCounter::SystemCounter: Architecture states a maximum of 1004 "
"frequency table entries, limit surpassed\n");
// Set the active frequency to be the base
_freq = _freqTable.front();
_period = (1.0 / _freq) * sim_clock::Frequency;
}
void
SystemCounter::validateCounterRef(SystemCounter *const sys_cnt)
{
fatal_if(!sys_cnt, "SystemCounter::validateCounterRef: No valid system "
"counter, can't instantiate system timers\n");
}
void
SystemCounter::enable()
{
DPRINTF(Timer, "SystemCounter::enable: Counter enabled\n");
_enabled = true;
updateTick();
}
void
SystemCounter::disable()
{
DPRINTF(Timer, "SystemCounter::disable: Counter disabled\n");
updateValue();
_enabled = false;
}
uint64_t
SystemCounter::value()
{
if (_enabled)
updateValue();
return _value;
}
void
SystemCounter::updateValue()
{
uint64_t new_value =
_value + ((curTick() - _updateTick) / _period) * _increment;
if (new_value > _value) {
_value = new_value;
updateTick();
}
}
void
SystemCounter::setValue(uint64_t new_value)
{
if (_enabled)
warn("Explicit value set with counter enabled, UNKNOWNN result\n");
_value = new_value;
updateTick();
notifyListeners();
}
Tick
SystemCounter::whenValue(uint64_t cur_val, uint64_t target_val) const
{
Tick when = curTick();
if (target_val > cur_val) {
uint64_t num_cycles =
std::ceil((target_val - cur_val) / ((double) _increment));
// Take into account current cycle remaining ticks
Tick rem_ticks = _period - (curTick() % _period);
if (rem_ticks < _period) {
when += rem_ticks;
num_cycles -= 1;
}
when += num_cycles * _period;
}
return when;
}
Tick
SystemCounter::whenValue(uint64_t target_val)
{
return whenValue(value(), target_val);
}
void
SystemCounter::updateTick()
{
_updateTick = curTick() - (curTick() % _period);
}
void
SystemCounter::freqUpdateSchedule(size_t new_freq_entry)
{
if (new_freq_entry < _freqTable.size()) {
auto &new_freq = _freqTable[new_freq_entry];
if (new_freq != _freq) {
_nextFreqEntry = new_freq_entry;
// Wait until the value for which the lowest frequency increment
// is a exact divisor. This covers both high to low and low to
// high transitions
uint64_t new_incr = _freqTable[0] / new_freq;
uint64_t target_val = value();
target_val += target_val % std::max(_increment, new_incr);
reschedule(_freqUpdateEvent, whenValue(target_val), true);
}
}
}
void
SystemCounter::freqUpdateCallback()
{
DPRINTF(Timer, "SystemCounter::freqUpdateCallback: Changing counter "
"frequency\n");
if (_enabled)
updateValue();
_activeFreqEntry = _nextFreqEntry;
_freq = _freqTable[_activeFreqEntry];
_increment = _freqTable[0] / _freq;
_period = (1.0 / _freq) * sim_clock::Frequency;
notifyListeners();
}
void
SystemCounter::registerListener(SystemCounterListener *listener)
{
_listeners.push_back(listener);
}
void
SystemCounter::notifyListeners() const
{
for (auto &listener : _listeners)
listener->notify();
}
void
SystemCounter::serialize(CheckpointOut &cp) const
{
DPRINTF(Timer, "SystemCounter::serialize: Serializing\n");
SERIALIZE_SCALAR(_enabled);
SERIALIZE_SCALAR(_freq);
SERIALIZE_SCALAR(_value);
SERIALIZE_SCALAR(_increment);
SERIALIZE_CONTAINER(_freqTable);
SERIALIZE_SCALAR(_activeFreqEntry);
SERIALIZE_SCALAR(_updateTick);
bool pending_freq_update = _freqUpdateEvent.scheduled();
SERIALIZE_SCALAR(pending_freq_update);
if (pending_freq_update) {
Tick when_freq_update = _freqUpdateEvent.when();
SERIALIZE_SCALAR(when_freq_update);
}
SERIALIZE_SCALAR(_nextFreqEntry);
}
void
SystemCounter::unserialize(CheckpointIn &cp)
{
DPRINTF(Timer, "SystemCounter::unserialize: Unserializing\n");
UNSERIALIZE_SCALAR(_enabled);
UNSERIALIZE_SCALAR(_freq);
UNSERIALIZE_SCALAR(_value);
UNSERIALIZE_SCALAR(_increment);
UNSERIALIZE_CONTAINER(_freqTable);
UNSERIALIZE_SCALAR(_activeFreqEntry);
UNSERIALIZE_SCALAR(_updateTick);
bool pending_freq_update;
UNSERIALIZE_SCALAR(pending_freq_update);
if (pending_freq_update) {
Tick when_freq_update;
UNSERIALIZE_SCALAR(when_freq_update);
reschedule(_freqUpdateEvent, when_freq_update, true);
}
UNSERIALIZE_SCALAR(_nextFreqEntry);
_period = (1.0 / _freq) * sim_clock::Frequency;
}
ArchTimer::ArchTimer(const std::string &name,
SimObject &parent,
SystemCounter &sysctr,
ArmInterruptPin *interrupt)
: _name(name), _parent(parent), _systemCounter(sysctr),
_interrupt(interrupt),
_control(0), _counterLimit(0), _offset(0),
_counterLimitReachedEvent([this]{ counterLimitReached(); }, name)
{
_systemCounter.registerListener(this);
}
void
ArchTimer::counterLimitReached()
{
if (!_control.enable)
return;
DPRINTF(Timer, "Counter limit reached\n");
_control.istatus = 1;
if (!_control.imask) {
if (scheduleEvents()) {
DPRINTF(Timer, "Causing interrupt\n");
_interrupt->raise();
} else {
DPRINTF(Timer, "Kvm mode; skipping simulated interrupt\n");
}
}
}
void
ArchTimer::updateCounter()
{
if (_counterLimitReachedEvent.scheduled())
_parent.deschedule(_counterLimitReachedEvent);
if (value() >= _counterLimit) {
counterLimitReached();
} else {
// Clear the interurpt when timers conditions are not met
if (_interrupt->active()) {
DPRINTF(Timer, "Clearing interrupt\n");
_interrupt->clear();
}
_control.istatus = 0;
if (scheduleEvents()) {
_parent.schedule(_counterLimitReachedEvent,
whenValue(_counterLimit));
}
}
}
void
ArchTimer::setCompareValue(uint64_t val)
{
_counterLimit = val;
updateCounter();
}
void
ArchTimer::setTimerValue(uint32_t val)
{
setCompareValue(value() + sext<32>(val));
}
void
ArchTimer::setControl(uint32_t val)
{
ArchTimerCtrl old_ctl = _control, new_ctl = val;
_control.enable = new_ctl.enable;
_control.imask = new_ctl.imask;
_control.istatus = old_ctl.istatus;
// Timer unmasked or enabled
if ((old_ctl.imask && !new_ctl.imask) ||
(!old_ctl.enable && new_ctl.enable))
updateCounter();
// Timer masked or disabled
else if ((!old_ctl.imask && new_ctl.imask) ||
(old_ctl.enable && !new_ctl.enable)) {
if (_interrupt->active()) {
DPRINTF(Timer, "Clearing interrupt\n");
// We are clearing the interrupt but we are not
// setting istatus to 0 as we are doing
// in the updateCounter.
// istatus signals that Timer conditions are met.
// It shouldn't depend on masking.
// if enable is zero. istatus is unknown.
_interrupt->clear();
}
}
}
void
ArchTimer::setOffset(uint64_t val)
{
_offset = val;
updateCounter();
}
uint64_t
ArchTimer::value() const
{
return _systemCounter.value() - _offset;
}
void
ArchTimer::notify()
{
updateCounter();
}
void
ArchTimer::serialize(CheckpointOut &cp) const
{
paramOut(cp, "control_serial", _control);
SERIALIZE_SCALAR(_counterLimit);
SERIALIZE_SCALAR(_offset);
}
void
ArchTimer::unserialize(CheckpointIn &cp)
{
paramIn(cp, "control_serial", _control);
// We didn't serialize an offset before we added support for the
// virtual timer. Consider it optional to maintain backwards
// compatibility.
if (!UNSERIALIZE_OPT_SCALAR(_offset))
_offset = 0;
// We no longer schedule an event here because we may enter KVM
// emulation. The event creation is delayed until drainResume().
}
DrainState
ArchTimer::drain()
{
if (_counterLimitReachedEvent.scheduled())
_parent.deschedule(_counterLimitReachedEvent);
return DrainState::Drained;
}
void
ArchTimer::drainResume()
{
updateCounter();
}
bool
ArchTimerKvm::scheduleEvents()
{
if constexpr (USE_KVM &&
std::string_view(KVM_ISA) == std::string_view("arm")) {
auto *vm = system.getKvmVM();
return !vm || !vm->validEnvironment();
} else {
return true;
}
}
GenericTimer::GenericTimer(const GenericTimerParams &p)
: SimObject(p),
systemCounter(*p.counter),
system(*p.system)
{
SystemCounter::validateCounterRef(p.counter);
fatal_if(!p.system, "GenericTimer::GenericTimer: No system specified, "
"can't instantiate architected timers\n");
system.setGenericTimer(this);
}
void
GenericTimer::serialize(CheckpointOut &cp) const
{
paramOut(cp, "cpu_count", timers.size());
for (int i = 0; i < timers.size(); ++i) {
const CoreTimers &core(*timers[i]);
core.serializeSection(cp, csprintf("pe_implementation%d", i));
}
}
void
GenericTimer::unserialize(CheckpointIn &cp)
{
// Try to unserialize the CPU count. Old versions of the timer
// model assumed a 8 CPUs, so we fall back to that if the field
// isn't present.
static const unsigned OLD_CPU_MAX = 8;
unsigned cpu_count;
if (!UNSERIALIZE_OPT_SCALAR(cpu_count)) {
warn("Checkpoint does not contain CPU count, assuming %i CPUs\n",
OLD_CPU_MAX);
cpu_count = OLD_CPU_MAX;
}
// We cannot assert for equality here because CPU timers are dynamically
// created on the first miscreg access. Therefore, if we take the checkpoint
// before any timer registers have been accessed, the number of counters
// is actually smaller than the total number of CPUs.
if (cpu_count > system.threads.size()) {
fatal("The simulated system has been initialized with %d CPUs, "
"but the Generic Timer checkpoint expects %d CPUs. Consider "
"restoring the checkpoint specifying %d CPUs.",
system.threads.size(), cpu_count, cpu_count);
}
for (int i = 0; i < cpu_count; ++i) {
CoreTimers &core(getTimers(i));
core.unserializeSection(cp, csprintf("pe_implementation%d", i));
}
}
GenericTimer::CoreTimers &
GenericTimer::getTimers(int cpu_id)
{
if (cpu_id >= timers.size())
createTimers(cpu_id + 1);
return *timers[cpu_id];
}
void
GenericTimer::createTimers(unsigned cpus)
{
assert(timers.size() < cpus);
auto &p = params();
const unsigned old_cpu_count(timers.size());
timers.resize(cpus);
for (unsigned i = old_cpu_count; i < cpus; ++i) {
ThreadContext *tc = system.threads[i];
timers[i].reset(
new CoreTimers(*this, system, i,
p.int_el3_phys->get(tc),
p.int_el1_phys->get(tc),
p.int_el1_virt->get(tc),
p.int_el2_ns_phys->get(tc),
p.int_el2_ns_virt->get(tc),
p.int_el2_s_phys->get(tc),
p.int_el2_s_virt->get(tc)));
}
}
void
GenericTimer::handleStream(CoreTimers::EventStream *ev_stream,
ArchTimer *timer, RegVal old_cnt_ctl, RegVal cnt_ctl)
{
uint64_t evnten = bits(cnt_ctl, 2);
uint64_t old_evnten = bits(old_cnt_ctl, 2);
uint8_t old_trans_to = ev_stream->transitionTo;
uint8_t old_trans_bit = ev_stream->transitionBit;
ev_stream->transitionTo = !bits(cnt_ctl, 3);
ev_stream->transitionBit = bits(cnt_ctl, 7, 4);
// Reschedule the Event Stream if enabled and any change in
// configuration
if (evnten && ((old_evnten != evnten) ||
(old_trans_to != ev_stream->transitionTo) ||
(old_trans_bit != ev_stream->transitionBit))) {
Tick when = timer->whenValue(
ev_stream->eventTargetValue(timer->value()));
reschedule(ev_stream->event, when, true);
} else if (old_evnten && !evnten) {
// Event Stream generation disabled
if (ev_stream->event.scheduled())
deschedule(ev_stream->event);
}
}
void
GenericTimer::setMiscReg(int reg, unsigned cpu, RegVal val)
{
CoreTimers &core(getTimers(cpu));
switch (reg) {
case MISCREG_CNTFRQ:
case MISCREG_CNTFRQ_EL0:
core.cntfrq = val;
warn_if(core.cntfrq != systemCounter.freq(), "CNTFRQ configured freq "
"does not match the system counter freq\n");
return;
case MISCREG_CNTKCTL:
case MISCREG_CNTKCTL_EL1:
{
RegVal old_cnt_ctl = core.cntkctl;
core.cntkctl = val;
ArchTimer *timer = &core.virtEL1;
CoreTimers::EventStream *ev_stream = &core.virtEvStream;
handleStream(ev_stream, timer, old_cnt_ctl, val);
return;
}
case MISCREG_CNTHCTL:
case MISCREG_CNTHCTL_EL2:
{
RegVal old_cnt_ctl = core.cnthctl;
core.cnthctl = val;
ArchTimer *timer = &core.physEL1;
CoreTimers::EventStream *ev_stream = &core.physEvStream;
handleStream(ev_stream, timer, old_cnt_ctl, val);
return;
}
// EL1 physical timer
case MISCREG_CNTP_CVAL_NS:
case MISCREG_CNTP_CVAL_EL0:
core.physEL1.setCompareValue(val);
return;
case MISCREG_CNTP_TVAL_NS:
case MISCREG_CNTP_TVAL_EL0:
core.physEL1.setTimerValue(val);
return;
case MISCREG_CNTP_CTL_NS:
case MISCREG_CNTP_CTL_EL0:
core.physEL1.setControl(val);
return;
// Count registers
case MISCREG_CNTPCT:
case MISCREG_CNTPCT_EL0:
case MISCREG_CNTVCT:
case MISCREG_CNTVCT_EL0:
warn("Ignoring write to read only count register: %s\n",
miscRegName[reg]);
return;
// EL1 virtual timer
case MISCREG_CNTVOFF:
case MISCREG_CNTVOFF_EL2:
core.virtEL1.setOffset(val);
return;
case MISCREG_CNTV_CVAL:
case MISCREG_CNTV_CVAL_EL0:
core.virtEL1.setCompareValue(val);
return;
case MISCREG_CNTV_TVAL:
case MISCREG_CNTV_TVAL_EL0:
core.virtEL1.setTimerValue(val);
return;
case MISCREG_CNTV_CTL:
case MISCREG_CNTV_CTL_EL0:
core.virtEL1.setControl(val);
return;
// EL3 physical timer
case MISCREG_CNTP_CTL_S:
case MISCREG_CNTPS_CTL_EL1:
core.physEL3.setControl(val);
return;
case MISCREG_CNTP_CVAL_S:
case MISCREG_CNTPS_CVAL_EL1:
core.physEL3.setCompareValue(val);
return;
case MISCREG_CNTP_TVAL_S:
case MISCREG_CNTPS_TVAL_EL1:
core.physEL3.setTimerValue(val);
return;
// EL2 Non-secure physical timer
case MISCREG_CNTHP_CTL:
case MISCREG_CNTHP_CTL_EL2:
core.physNsEL2.setControl(val);
return;
case MISCREG_CNTHP_CVAL:
case MISCREG_CNTHP_CVAL_EL2:
core.physNsEL2.setCompareValue(val);
return;
case MISCREG_CNTHP_TVAL:
case MISCREG_CNTHP_TVAL_EL2:
core.physNsEL2.setTimerValue(val);
return;
// EL2 Non-secure virtual timer
case MISCREG_CNTHV_CTL_EL2:
core.virtNsEL2.setControl(val);
return;
case MISCREG_CNTHV_CVAL_EL2:
core.virtNsEL2.setCompareValue(val);
return;
case MISCREG_CNTHV_TVAL_EL2:
core.virtNsEL2.setTimerValue(val);
return;
// EL2 Secure physical timer
case MISCREG_CNTHPS_CTL_EL2:
core.physSEL2.setControl(val);
return;
case MISCREG_CNTHPS_CVAL_EL2:
core.physSEL2.setCompareValue(val);
return;
case MISCREG_CNTHPS_TVAL_EL2:
core.physSEL2.setTimerValue(val);
return;
// EL2 Secure virtual timer
case MISCREG_CNTHVS_CTL_EL2:
core.virtSEL2.setControl(val);
return;
case MISCREG_CNTHVS_CVAL_EL2:
core.virtSEL2.setCompareValue(val);
return;
case MISCREG_CNTHVS_TVAL_EL2:
core.virtSEL2.setTimerValue(val);
return;
default:
warn("Writing to unknown register: %s\n", miscRegName[reg]);
return;
}
}
RegVal
GenericTimer::readMiscReg(int reg, unsigned cpu)
{
CoreTimers &core(getTimers(cpu));
switch (reg) {
case MISCREG_CNTFRQ:
case MISCREG_CNTFRQ_EL0:
return core.cntfrq;
case MISCREG_CNTKCTL:
case MISCREG_CNTKCTL_EL1:
return core.cntkctl & 0x00000000ffffffff;
case MISCREG_CNTHCTL:
case MISCREG_CNTHCTL_EL2:
return core.cnthctl & 0x00000000ffffffff;
// EL1 physical timer
case MISCREG_CNTP_CVAL_NS:
case MISCREG_CNTP_CVAL_EL0:
return core.physEL1.compareValue();
case MISCREG_CNTP_TVAL_NS:
case MISCREG_CNTP_TVAL_EL0:
return core.physEL1.timerValue();
case MISCREG_CNTP_CTL_EL0:
case MISCREG_CNTP_CTL_NS:
return core.physEL1.control();
case MISCREG_CNTPCT:
case MISCREG_CNTPCT_EL0:
return core.physEL1.value();
// EL1 virtual timer
case MISCREG_CNTVCT:
case MISCREG_CNTVCT_EL0:
return core.virtEL1.value();
case MISCREG_CNTVOFF:
case MISCREG_CNTVOFF_EL2:
return core.virtEL1.offset();
case MISCREG_CNTV_CVAL:
case MISCREG_CNTV_CVAL_EL0:
return core.virtEL1.compareValue();
case MISCREG_CNTV_TVAL:
case MISCREG_CNTV_TVAL_EL0:
return core.virtEL1.timerValue();
case MISCREG_CNTV_CTL:
case MISCREG_CNTV_CTL_EL0:
return core.virtEL1.control();
// EL3 physical timer
case MISCREG_CNTP_CTL_S:
case MISCREG_CNTPS_CTL_EL1:
return core.physEL3.control();
case MISCREG_CNTP_CVAL_S:
case MISCREG_CNTPS_CVAL_EL1:
return core.physEL3.compareValue();
case MISCREG_CNTP_TVAL_S:
case MISCREG_CNTPS_TVAL_EL1:
return core.physEL3.timerValue();
// EL2 Non-secure physical timer
case MISCREG_CNTHP_CTL:
case MISCREG_CNTHP_CTL_EL2:
return core.physNsEL2.control();
case MISCREG_CNTHP_CVAL:
case MISCREG_CNTHP_CVAL_EL2:
return core.physNsEL2.compareValue();
case MISCREG_CNTHP_TVAL:
case MISCREG_CNTHP_TVAL_EL2:
return core.physNsEL2.timerValue();
// EL2 Non-secure virtual timer
case MISCREG_CNTHV_CTL_EL2:
return core.virtNsEL2.control();
case MISCREG_CNTHV_CVAL_EL2:
return core.virtNsEL2.compareValue();
case MISCREG_CNTHV_TVAL_EL2:
return core.virtNsEL2.timerValue();
// EL2 Secure physical timer
case MISCREG_CNTHPS_CTL_EL2:
return core.physSEL2.control();
case MISCREG_CNTHPS_CVAL_EL2:
return core.physSEL2.compareValue();
case MISCREG_CNTHPS_TVAL_EL2:
return core.physSEL2.timerValue();
// EL2 Secure virtual timer
case MISCREG_CNTHVS_CTL_EL2:
return core.virtSEL2.control();
case MISCREG_CNTHVS_CVAL_EL2:
return core.virtSEL2.compareValue();
case MISCREG_CNTHVS_TVAL_EL2:
return core.virtSEL2.timerValue();
default:
warn("Reading from unknown register: %s\n", miscRegName[reg]);
return 0;
}
}
GenericTimer::CoreTimers::CoreTimers(GenericTimer &_parent,
ArmSystem &system, unsigned cpu,
ArmInterruptPin *irq_el3_phys, ArmInterruptPin *irq_el1_phys,
ArmInterruptPin *irq_el1_virt, ArmInterruptPin *irq_el2_ns_phys,
ArmInterruptPin *irq_el2_ns_virt, ArmInterruptPin *irq_el2_s_phys,
ArmInterruptPin *irq_el2_s_virt)
: parent(_parent),
cntfrq(parent.params().cntfrq),
cntkctl(0), cnthctl(0),
threadContext(system.threads[cpu]),
irqPhysEL3(irq_el3_phys),
irqPhysEL1(irq_el1_phys),
irqVirtEL1(irq_el1_virt),
irqPhysNsEL2(irq_el2_ns_phys),
irqVirtNsEL2(irq_el2_ns_virt),
irqPhysSEL2(irq_el2_s_phys),
irqVirtSEL2(irq_el2_s_virt),
physEL3(csprintf("%s.el3_phys_timer%d", parent.name(), cpu),
system, parent, parent.systemCounter,
irq_el3_phys),
physEL1(csprintf("%s.el1_phys_timer%d", parent.name(), cpu),
system, parent, parent.systemCounter,
irq_el1_phys),
virtEL1(csprintf("%s.el1_virt_timer%d", parent.name(), cpu),
system, parent, parent.systemCounter,
irq_el1_virt),
physNsEL2(csprintf("%s.el2_ns_phys_timer%d", parent.name(), cpu),
system, parent, parent.systemCounter,
irq_el2_ns_phys),
virtNsEL2(csprintf("%s.el2_ns_virt_timer%d", parent.name(), cpu),
system, parent, parent.systemCounter,
irq_el2_ns_virt),
physSEL2(csprintf("%s.el2_s_phys_timer%d", parent.name(), cpu),
system, parent, parent.systemCounter,
irq_el2_s_phys),
virtSEL2(csprintf("%s.el2_s_virt_timer%d", parent.name(), cpu),
system, parent, parent.systemCounter,
irq_el2_s_virt),
physEvStream{
EventFunctionWrapper([this]{ physEventStreamCallback(); },
csprintf("%s.phys_event_gen%d", parent.name(), cpu)), 0, 0
},
virtEvStream{
EventFunctionWrapper([this]{ virtEventStreamCallback(); },
csprintf("%s.virt_event_gen%d", parent.name(), cpu)), 0, 0
}
{
}
void
GenericTimer::CoreTimers::physEventStreamCallback()
{
eventStreamCallback();
schedNextEvent(physEvStream, physEL1);
}
void
GenericTimer::CoreTimers::virtEventStreamCallback()
{
eventStreamCallback();
schedNextEvent(virtEvStream, virtEL1);
}
void
GenericTimer::CoreTimers::eventStreamCallback() const
{
sendEvent(threadContext);
threadContext->getCpuPtr()->wakeup(threadContext->threadId());
}
void
GenericTimer::CoreTimers::schedNextEvent(EventStream &ev_stream,
ArchTimer &timer)
{
parent.reschedule(ev_stream.event, timer.whenValue(
ev_stream.eventTargetValue(timer.value())), true);
}
void
GenericTimer::CoreTimers::notify()
{
schedNextEvent(virtEvStream, virtEL1);
schedNextEvent(physEvStream, physEL1);
}
void
GenericTimer::CoreTimers::serialize(CheckpointOut &cp) const
{
SERIALIZE_SCALAR(cntfrq);
SERIALIZE_SCALAR(cntkctl);
SERIALIZE_SCALAR(cnthctl);
const bool phys_ev_scheduled = physEvStream.event.scheduled();
SERIALIZE_SCALAR(phys_ev_scheduled);
if (phys_ev_scheduled) {
const Tick phys_ev_when = physEvStream.event.when();
SERIALIZE_SCALAR(phys_ev_when);
}
SERIALIZE_SCALAR(physEvStream.transitionTo);
SERIALIZE_SCALAR(physEvStream.transitionBit);
const bool virt_ev_scheduled = virtEvStream.event.scheduled();
SERIALIZE_SCALAR(virt_ev_scheduled);
if (virt_ev_scheduled) {
const Tick virt_ev_when = virtEvStream.event.when();
SERIALIZE_SCALAR(virt_ev_when);
}
SERIALIZE_SCALAR(virtEvStream.transitionTo);
SERIALIZE_SCALAR(virtEvStream.transitionBit);
physEL3.serializeSection(cp, "phys_el3_timer");
physEL1.serializeSection(cp, "phys_el1_timer");
virtEL1.serializeSection(cp, "virt_el1_timer");
physNsEL2.serializeSection(cp, "phys_ns_el2_timer");
virtNsEL2.serializeSection(cp, "virt_ns_el2_timer");
physSEL2.serializeSection(cp, "phys_s_el2_timer");
virtSEL2.serializeSection(cp, "virt_s_el2_timer");
}
void
GenericTimer::CoreTimers::unserialize(CheckpointIn &cp)
{
UNSERIALIZE_SCALAR(cntfrq);
UNSERIALIZE_SCALAR(cntkctl);
UNSERIALIZE_SCALAR(cnthctl);
bool phys_ev_scheduled;
UNSERIALIZE_SCALAR(phys_ev_scheduled);
if (phys_ev_scheduled) {
Tick phys_ev_when;
UNSERIALIZE_SCALAR(phys_ev_when);
parent.reschedule(physEvStream.event, phys_ev_when, true);
}
UNSERIALIZE_SCALAR(physEvStream.transitionTo);
UNSERIALIZE_SCALAR(physEvStream.transitionBit);
bool virt_ev_scheduled;
UNSERIALIZE_SCALAR(virt_ev_scheduled);
if (virt_ev_scheduled) {
Tick virt_ev_when;
UNSERIALIZE_SCALAR(virt_ev_when);
parent.reschedule(virtEvStream.event, virt_ev_when, true);
}
UNSERIALIZE_SCALAR(virtEvStream.transitionTo);
UNSERIALIZE_SCALAR(virtEvStream.transitionBit);
physEL3.unserializeSection(cp, "phys_el3_timer");
physEL1.unserializeSection(cp, "phys_el1_timer");
virtEL1.unserializeSection(cp, "virt_el1_timer");
physNsEL2.unserializeSection(cp, "phys_ns_el2_timer");
virtNsEL2.unserializeSection(cp, "virt_ns_el2_timer");
physSEL2.unserializeSection(cp, "phys_s_el2_timer");
virtSEL2.unserializeSection(cp, "virt_s_el2_timer");
}
void
GenericTimerISA::setMiscReg(int reg, RegVal val)
{
DPRINTF(Timer, "Setting %s := 0x%x\n", miscRegName[reg], val);
parent.setMiscReg(reg, cpu, val);
}
RegVal
GenericTimerISA::readMiscReg(int reg)
{
RegVal value = parent.readMiscReg(reg, cpu);
DPRINTF(Timer, "Reading %s as 0x%x\n", miscRegName[reg], value);
return value;
}
GenericTimerFrame::GenericTimerFrame(const GenericTimerFrameParams &p)
: PioDevice(p),
timerRange(RangeSize(p.cnt_base, ArmSystem::PageBytes)),
addrRanges({timerRange}),
systemCounter(*p.counter),
physTimer(csprintf("%s.phys_timer", name()),
*this, systemCounter, p.int_phys->get()),
virtTimer(csprintf("%s.virt_timer", name()),
*this, systemCounter,
p.int_virt->get()),
accessBits(0x3f),
system(*dynamic_cast<ArmSystem *>(sys))
{
SystemCounter::validateCounterRef(p.counter);
// Expose optional CNTEL0Base register frame
if (p.cnt_el0_base != MaxAddr) {
timerEl0Range = RangeSize(p.cnt_el0_base, ArmSystem::PageBytes);
accessBitsEl0 = 0x303;
addrRanges.push_back(timerEl0Range);
}
for (auto &range : addrRanges)
GenericTimerMem::validateFrameRange(range);
}
void
GenericTimerFrame::serialize(CheckpointOut &cp) const
{
SERIALIZE_SCALAR(accessBits);
if (hasEl0View())
SERIALIZE_SCALAR(accessBitsEl0);
SERIALIZE_SCALAR(nonSecureAccess);
physTimer.serializeSection(cp, "phys_timer");
virtTimer.serializeSection(cp, "virt_timer");
}
void
GenericTimerFrame::unserialize(CheckpointIn &cp)
{
UNSERIALIZE_SCALAR(accessBits);
if (hasEl0View())
UNSERIALIZE_SCALAR(accessBitsEl0);
UNSERIALIZE_SCALAR(nonSecureAccess);
physTimer.unserializeSection(cp, "phys_timer");
virtTimer.unserializeSection(cp, "virt_timer");
}
uint64_t
GenericTimerFrame::getVirtOffset() const
{
return virtTimer.offset();
}
void
GenericTimerFrame::setVirtOffset(uint64_t new_offset)
{
virtTimer.setOffset(new_offset);
}
bool
GenericTimerFrame::hasEl0View() const
{
return timerEl0Range.valid();
}
uint8_t
GenericTimerFrame::getAccessBits() const
{
return accessBits;
}
void
GenericTimerFrame::setAccessBits(uint8_t data)
{
accessBits = data & 0x3f;
}
bool
GenericTimerFrame::hasNonSecureAccess() const
{
return nonSecureAccess;
}
void
GenericTimerFrame::setNonSecureAccess()
{
nonSecureAccess = true;
}
bool
GenericTimerFrame::hasReadableVoff() const
{
return accessBits.rvoff;
}
AddrRangeList
GenericTimerFrame::getAddrRanges() const
{
return addrRanges;
}
Tick
GenericTimerFrame::read(PacketPtr pkt)
{
const Addr addr = pkt->getAddr();
const size_t size = pkt->getSize();
const bool is_sec = pkt->isSecure();
panic_if(size != 4 && size != 8,
"GenericTimerFrame::read: Invalid size %i\n", size);
bool to_el0 = false;
uint64_t resp = 0;
Addr offset = 0;
if (timerRange.contains(addr)) {
offset = addr - timerRange.start();
} else if (hasEl0View() && timerEl0Range.contains(addr)) {
offset = addr - timerEl0Range.start();
to_el0 = true;
} else {
panic("GenericTimerFrame::read: Invalid address: 0x%x\n", addr);
}
resp = timerRead(offset, size, is_sec, to_el0);
DPRINTF(Timer, "GenericTimerFrame::read: 0x%x<-0x%x(%i) [S = %u]\n", resp,
addr, size, is_sec);
pkt->setUintX(resp, ByteOrder::little);
pkt->makeResponse();
return 0;
}
Tick
GenericTimerFrame::write(PacketPtr pkt)
{
const Addr addr = pkt->getAddr();
const size_t size = pkt->getSize();
const bool is_sec = pkt->isSecure();
panic_if(size != 4 && size != 8,
"GenericTimerFrame::write: Invalid size %i\n", size);
bool to_el0 = false;
const uint64_t data = pkt->getUintX(ByteOrder::little);
Addr offset = 0;
if (timerRange.contains(addr)) {
offset = addr - timerRange.start();
} else if (hasEl0View() && timerEl0Range.contains(addr)) {
offset = addr - timerEl0Range.start();
to_el0 = true;
} else {
panic("GenericTimerFrame::write: Invalid address: 0x%x\n", addr);
}
timerWrite(offset, size, data, is_sec, to_el0);
DPRINTF(Timer, "GenericTimerFrame::write: 0x%x->0x%x(%i) [S = %u]\n", data,
addr, size, is_sec);
pkt->makeResponse();
return 0;
}
uint64_t
GenericTimerFrame::timerRead(Addr addr, size_t size, bool is_sec,
bool to_el0) const
{
if (!GenericTimerMem::validateAccessPerm(system, is_sec) &&
!nonSecureAccess)
return 0;
switch (addr) {
case TIMER_CNTPCT_LO:
if (!accessBits.rpct || (to_el0 && !accessBitsEl0.pcten))
return 0;
else
return physTimer.value();
case TIMER_CNTPCT_HI:
if (!accessBits.rpct || (to_el0 && !accessBitsEl0.pcten))
return 0;
else
return physTimer.value() >> 32;
case TIMER_CNTFRQ:
if ((!accessBits.rfrq) ||
(to_el0 && (!accessBitsEl0.pcten && !accessBitsEl0.vcten)))
return 0;
else
return systemCounter.freq();
case TIMER_CNTEL0ACR:
if (!hasEl0View() || to_el0)
return 0;
else
return accessBitsEl0;
case TIMER_CNTP_CVAL_LO:
if (!accessBits.rwpt || (to_el0 && !accessBitsEl0.pten))
return 0;
else
return physTimer.compareValue();
case TIMER_CNTP_CVAL_HI:
if (!accessBits.rwpt || (to_el0 && !accessBitsEl0.pten))
return 0;
else
return physTimer.compareValue() >> 32;
case TIMER_CNTP_TVAL:
if (!accessBits.rwpt || (to_el0 && !accessBitsEl0.pten))
return 0;
else
return physTimer.timerValue();
case TIMER_CNTP_CTL:
if (!accessBits.rwpt || (to_el0 && !accessBitsEl0.pten))
return 0;
else
return physTimer.control();
case TIMER_CNTVCT_LO:
if (!accessBits.rvct || (to_el0 && !accessBitsEl0.vcten))
return 0;
else
return virtTimer.value();
case TIMER_CNTVCT_HI:
if (!accessBits.rvct || (to_el0 && !accessBitsEl0.vcten))
return 0;
else
return virtTimer.value() >> 32;
case TIMER_CNTVOFF_LO:
if (!accessBits.rvoff || (to_el0))
return 0;
else
return virtTimer.offset();
case TIMER_CNTVOFF_HI:
if (!accessBits.rvoff || (to_el0))
return 0;
else
return virtTimer.offset() >> 32;
case TIMER_CNTV_CVAL_LO:
if (!accessBits.rwvt || (to_el0 && !accessBitsEl0.vten))
return 0;
else
return virtTimer.compareValue();
case TIMER_CNTV_CVAL_HI:
if (!accessBits.rwvt || (to_el0 && !accessBitsEl0.vten))
return 0;
else
return virtTimer.compareValue() >> 32;
case TIMER_CNTV_TVAL:
if (!accessBits.rwvt || (to_el0 && !accessBitsEl0.vten))
return 0;
else
return virtTimer.timerValue();
case TIMER_CNTV_CTL:
if (!accessBits.rwvt || (to_el0 && !accessBitsEl0.vten))
return 0;
else
return virtTimer.control();
default:
warn("GenericTimerFrame::timerRead: Unexpected address (0x%x:%i), "
"assuming RAZ\n", addr, size);
return 0;
}
}
void
GenericTimerFrame::timerWrite(Addr addr, size_t size, uint64_t data,
bool is_sec, bool to_el0)
{
if (!GenericTimerMem::validateAccessPerm(system, is_sec) &&
!nonSecureAccess)
return;
switch (addr) {
case TIMER_CNTPCT_LO ... TIMER_CNTPCT_HI:
warn("GenericTimerFrame::timerWrite: RO reg (0x%x) [CNTPCT]\n",
addr);
return;
case TIMER_CNTFRQ:
warn("GenericTimerFrame::timerWrite: RO reg (0x%x) [CNTFRQ]\n",
addr);
return;
case TIMER_CNTEL0ACR:
if (!hasEl0View() || to_el0)
return;
insertBits(accessBitsEl0, 9, 8, data);
insertBits(accessBitsEl0, 1, 0, data);
return;
case TIMER_CNTP_CVAL_LO:
if ((!accessBits.rwpt) || (to_el0 && !accessBitsEl0.pten))
return;
data = size == 4 ? insertBits(physTimer.compareValue(),
31, 0, data) : data;
physTimer.setCompareValue(data);
return;
case TIMER_CNTP_CVAL_HI:
if ((!accessBits.rwpt) || (to_el0 && !accessBitsEl0.pten))
return;
data = insertBits(physTimer.compareValue(), 63, 32, data);
physTimer.setCompareValue(data);
return;
case TIMER_CNTP_TVAL:
if ((!accessBits.rwpt) || (to_el0 && !accessBitsEl0.pten))
return;
physTimer.setTimerValue(data);
return;
case TIMER_CNTP_CTL:
if ((!accessBits.rwpt) || (to_el0 && !accessBitsEl0.pten))
return;
physTimer.setControl(data);
return;
case TIMER_CNTVCT_LO ... TIMER_CNTVCT_HI:
warn("GenericTimerFrame::timerWrite: RO reg (0x%x) [CNTVCT]\n",
addr);
return;
case TIMER_CNTVOFF_LO ... TIMER_CNTVOFF_HI:
warn("GenericTimerFrame::timerWrite: RO reg (0x%x) [CNTVOFF]\n",
addr);
return;
case TIMER_CNTV_CVAL_LO:
if ((!accessBits.rwvt) || (to_el0 && !accessBitsEl0.vten))
return;
data = size == 4 ? insertBits(virtTimer.compareValue(),
31, 0, data) : data;
virtTimer.setCompareValue(data);
return;
case TIMER_CNTV_CVAL_HI:
if ((!accessBits.rwvt) || (to_el0 && !accessBitsEl0.vten))
return;
data = insertBits(virtTimer.compareValue(), 63, 32, data);
virtTimer.setCompareValue(data);
return;
case TIMER_CNTV_TVAL:
if ((!accessBits.rwvt) || (to_el0 && !accessBitsEl0.vten))
return;
virtTimer.setTimerValue(data);
return;
case TIMER_CNTV_CTL:
if ((!accessBits.rwvt) || (to_el0 && !accessBitsEl0.vten))
return;
virtTimer.setControl(data);
return;
default:
warn("GenericTimerFrame::timerWrite: Unexpected address (0x%x:%i), "
"assuming WI\n", addr, size);
}
}
GenericTimerMem::GenericTimerMem(const GenericTimerMemParams &p)
: PioDevice(p),
counterCtrlRange(RangeSize(p.cnt_control_base, ArmSystem::PageBytes)),
counterStatusRange(RangeSize(p.cnt_read_base, ArmSystem::PageBytes)),
timerCtrlRange(RangeSize(p.cnt_ctl_base, ArmSystem::PageBytes)),
cnttidr(0x0),
addrRanges{counterCtrlRange, counterStatusRange, timerCtrlRange},
systemCounter(*p.counter),
frames(p.frames),
system(*dynamic_cast<ArmSystem *>(sys))
{
SystemCounter::validateCounterRef(p.counter);
for (auto &range : addrRanges)
GenericTimerMem::validateFrameRange(range);
fatal_if(frames.size() > MAX_TIMER_FRAMES,
"GenericTimerMem::GenericTimerMem: Architecture states a maximum of "
"8 memory-mapped timer frames, limit surpassed\n");
// Initialize CNTTIDR with each frame's features
for (int i = 0; i < frames.size(); i++) {
uint32_t features = 0x1;
features |= 0x2;
if (frames[i]->hasEl0View())
features |= 0x4;
features <<= i * 4;
replaceBits(cnttidr, (i + 1) * 4 - 1, i * 4, features);
}
}
void
GenericTimerMem::validateFrameRange(const AddrRange &range)
{
fatal_if(range.start() % ArmSystem::PageBytes,
"GenericTimerMem::validateFrameRange: Architecture states each "
"register frame should be in a separate memory page, specified "
"range base address [0x%x] is not compliant\n");
}
bool
GenericTimerMem::validateAccessPerm(ArmSystem &sys, bool is_sec)
{
return !sys.has(ArmExtension::SECURITY) || is_sec;
}
AddrRangeList
GenericTimerMem::getAddrRanges() const
{
return addrRanges;
}
Tick
GenericTimerMem::read(PacketPtr pkt)
{
const Addr addr = pkt->getAddr();
const size_t size = pkt->getSize();
const bool is_sec = pkt->isSecure();
panic_if(size != 4 && size != 8,
"GenericTimerMem::read: Invalid size %i\n", size);
uint64_t resp = 0;
if (counterCtrlRange.contains(addr))
resp = counterCtrlRead(addr - counterCtrlRange.start(), size, is_sec);
else if (counterStatusRange.contains(addr))
resp = counterStatusRead(addr - counterStatusRange.start(), size);
else if (timerCtrlRange.contains(addr))
resp = timerCtrlRead(addr - timerCtrlRange.start(), size, is_sec);
else
panic("GenericTimerMem::read: Invalid address: 0x%x\n", addr);
DPRINTF(Timer, "GenericTimerMem::read: 0x%x<-0x%x(%i) [S = %u]\n", resp,
addr, size, is_sec);
pkt->setUintX(resp, ByteOrder::little);
pkt->makeResponse();
return 0;
}
Tick
GenericTimerMem::write(PacketPtr pkt)
{
const Addr addr = pkt->getAddr();
const size_t size = pkt->getSize();
const bool is_sec = pkt->isSecure();
panic_if(size != 4 && size != 8,
"GenericTimerMem::write: Invalid size %i\n", size);
const uint64_t data = pkt->getUintX(ByteOrder::little);
if (counterCtrlRange.contains(addr))
counterCtrlWrite(addr - counterCtrlRange.start(), size, data, is_sec);
else if (counterStatusRange.contains(addr))
counterStatusWrite(addr - counterStatusRange.start(), size, data);
else if (timerCtrlRange.contains(addr))
timerCtrlWrite(addr - timerCtrlRange.start(), size, data, is_sec);
else
panic("GenericTimerMem::write: Invalid address: 0x%x\n", addr);
DPRINTF(Timer, "GenericTimerMem::write: 0x%x->0x%x(%i) [S = %u]\n", data,
addr, size, is_sec);
pkt->makeResponse();
return 0;
}
uint64_t
GenericTimerMem::counterCtrlRead(Addr addr, size_t size, bool is_sec) const
{
if (!GenericTimerMem::validateAccessPerm(system, is_sec))
return 0;
switch (addr) {
case COUNTER_CTRL_CNTCR:
{
CNTCR cntcr = 0;
cntcr.en = systemCounter.enabled();
cntcr.fcreq = systemCounter.activeFreqEntry();
return cntcr;
}
case COUNTER_CTRL_CNTSR:
{
CNTSR cntsr = 0;
cntsr.fcack = systemCounter.activeFreqEntry();
return cntsr;
}
case COUNTER_CTRL_CNTCV_LO: return systemCounter.value();
case COUNTER_CTRL_CNTCV_HI: return systemCounter.value() >> 32;
case COUNTER_CTRL_CNTSCR: return 0;
case COUNTER_CTRL_CNTID: return 0;
default:
{
auto &freq_table = systemCounter.freqTable();
for (int i = 0; i < (freq_table.size() - 1); i++) {
Addr offset = COUNTER_CTRL_CNTFID + (i * 0x4);
if (addr == offset)
return freq_table[i];
}
warn("GenericTimerMem::counterCtrlRead: Unexpected address "
"(0x%x:%i), assuming RAZ\n", addr, size);
return 0;
}
}
}
void
GenericTimerMem::counterCtrlWrite(Addr addr, size_t size, uint64_t data,
bool is_sec)
{
if (!GenericTimerMem::validateAccessPerm(system, is_sec))
return;
switch (addr) {
case COUNTER_CTRL_CNTCR:
{
CNTCR val = data;
if (!systemCounter.enabled() && val.en)
systemCounter.enable();
else if (systemCounter.enabled() && !val.en)
systemCounter.disable();
if (val.hdbg)
warn("GenericTimerMem::counterCtrlWrite: Halt-on-debug is not "
"supported\n");
if (val.scen)
warn("GenericTimerMem::counterCtrlWrite: Counter Scaling is not "
"supported\n");
if (val.fcreq != systemCounter.activeFreqEntry())
systemCounter.freqUpdateSchedule(val.fcreq);
return;
}
case COUNTER_CTRL_CNTSR:
warn("GenericTimerMem::counterCtrlWrite: RO reg (0x%x) [CNTSR]\n",
addr);
return;
case COUNTER_CTRL_CNTCV_LO:
data = size == 4 ? insertBits(systemCounter.value(), 31, 0, data)
: data;
systemCounter.setValue(data);
return;
case COUNTER_CTRL_CNTCV_HI:
data = insertBits(systemCounter.value(), 63, 32, data);
systemCounter.setValue(data);
return;
case COUNTER_CTRL_CNTSCR:
return;
case COUNTER_CTRL_CNTID:
warn("GenericTimerMem::counterCtrlWrite: RO reg (0x%x) [CNTID]\n",
addr);
return;
default:
{
auto &freq_table = systemCounter.freqTable();
for (int i = 0; i < (freq_table.size() - 1); i++) {
Addr offset = COUNTER_CTRL_CNTFID + (i * 0x4);
if (addr == offset) {
freq_table[i] = data;
// This is changing the currently selected frequency
if (i == systemCounter.activeFreqEntry()) {
// We've changed the frequency in the table entry,
// however the counter will still work with the
// current one until transition is completed
systemCounter.freqUpdateSchedule(i);
}
return;
}
}
warn("GenericTimerMem::counterCtrlWrite: Unexpected address "
"(0x%x:%i), assuming WI\n", addr, size);
}
}
}
uint64_t
GenericTimerMem::counterStatusRead(Addr addr, size_t size) const
{
switch (addr) {
case COUNTER_STATUS_CNTCV_LO: return systemCounter.value();
case COUNTER_STATUS_CNTCV_HI: return systemCounter.value() >> 32;
default:
warn("GenericTimerMem::counterStatusRead: Unexpected address "
"(0x%x:%i), assuming RAZ\n", addr, size);
return 0;
}
}
void
GenericTimerMem::counterStatusWrite(Addr addr, size_t size, uint64_t data)
{
switch (addr) {
case COUNTER_STATUS_CNTCV_LO ... COUNTER_STATUS_CNTCV_HI:
warn("GenericTimerMem::counterStatusWrite: RO reg (0x%x) [CNTCV]\n",
addr);
return;
default:
warn("GenericTimerMem::counterStatusWrite: Unexpected address "
"(0x%x:%i), assuming WI\n", addr, size);
}
}
uint64_t
GenericTimerMem::timerCtrlRead(Addr addr, size_t size, bool is_sec) const
{
switch (addr) {
case TIMER_CTRL_CNTFRQ:
if (!GenericTimerMem::validateAccessPerm(system, is_sec)) return 0;
return systemCounter.freq();
case TIMER_CTRL_CNTNSAR:
{
if (!GenericTimerMem::validateAccessPerm(system, is_sec)) return 0;
uint32_t cntnsar = 0x0;
for (int i = 0; i < frames.size(); i++) {
if (frames[i]->hasNonSecureAccess())
cntnsar |= 0x1 << i;
}
return cntnsar;
}
case TIMER_CTRL_CNTTIDR: return cnttidr;
default:
for (int i = 0; i < frames.size(); i++) {
Addr cntacr_off = TIMER_CTRL_CNTACR + (i * 0x4);
Addr cntvoff_lo_off = TIMER_CTRL_CNTVOFF_LO + (i * 0x4);
Addr cntvoff_hi_off = TIMER_CTRL_CNTVOFF_HI + (i * 0x4);
// CNTNSAR.NS determines if CNTACR/CNTVOFF are accessible from
// normal world
bool hit = addr == cntacr_off || addr == cntvoff_lo_off ||
addr == cntvoff_hi_off;
bool has_access =
GenericTimerMem::validateAccessPerm(system, is_sec) ||
frames[i]->hasNonSecureAccess();
if (hit && !has_access) return 0;
if (addr == cntacr_off)
return frames[i]->getAccessBits();
if (addr == cntvoff_lo_off || addr == cntvoff_hi_off) {
return addr == cntvoff_lo_off ? frames[i]->getVirtOffset()
: frames[i]->getVirtOffset() >> 32;
}
}
warn("GenericTimerMem::timerCtrlRead: Unexpected address (0x%x:%i), "
"assuming RAZ\n", addr, size);
return 0;
}
}
void
GenericTimerMem::timerCtrlWrite(Addr addr, size_t size, uint64_t data,
bool is_sec)
{
switch (addr) {
case TIMER_CTRL_CNTFRQ:
if (!GenericTimerMem::validateAccessPerm(system, is_sec)) return;
warn_if(data != systemCounter.freq(),
"GenericTimerMem::timerCtrlWrite: CNTFRQ configured freq "
"does not match the counter freq, ignoring\n");
return;
case TIMER_CTRL_CNTNSAR:
if (!GenericTimerMem::validateAccessPerm(system, is_sec)) return;
for (int i = 0; i < frames.size(); i++) {
// Check if the CNTNSAR.NS bit is set for this frame
if (data & (0x1 << i))
frames[i]->setNonSecureAccess();
}
return;
case TIMER_CTRL_CNTTIDR:
warn("GenericTimerMem::timerCtrlWrite: RO reg (0x%x) [CNTTIDR]\n",
addr);
return;
default:
for (int i = 0; i < frames.size(); i++) {
Addr cntacr_off = TIMER_CTRL_CNTACR + (i * 0x4);
Addr cntvoff_lo_off = TIMER_CTRL_CNTVOFF_LO + (i * 0x4);
Addr cntvoff_hi_off = TIMER_CTRL_CNTVOFF_HI + (i * 0x4);
// CNTNSAR.NS determines if CNTACR/CNTVOFF are accessible from
// normal world
bool hit = addr == cntacr_off || addr == cntvoff_lo_off ||
addr == cntvoff_hi_off;
bool has_access =
GenericTimerMem::validateAccessPerm(system, is_sec) ||
frames[i]->hasNonSecureAccess();
if (hit && !has_access) return;
if (addr == cntacr_off) {
frames[i]->setAccessBits(data);
return;
}
if (addr == cntvoff_lo_off || addr == cntvoff_hi_off) {
if (addr == cntvoff_lo_off)
data = size == 4 ? insertBits(frames[i]->getVirtOffset(),
31, 0, data) : data;
else
data = insertBits(frames[i]->getVirtOffset(),
63, 32, data);
frames[i]->setVirtOffset(data);
return;
}
}
warn("GenericTimerMem::timerCtrlWrite: Unexpected address "
"(0x%x:%i), assuming WI\n", addr, size);
}
}
} // namespace gem5