Files
gem5/src/dev/arm/generic_timer.cc
Adrian Herrera 81fc073768 dev-arm: Refactor GenericTimer
The GenericTimer specification includes a global component for
a universal view of time: the System Counter.

If both per-PE architected and memory-mapped timers are instantiated
in a system, they must both share the same counter. SystemCounter is
promoted to be an independent SimObject, which is now shared by
implementations.

The SystemCounter may be controlled/accessed through the memory-mapped
counter module in the system level implementation. This provides
control (CNTControlBase) and status (CNTReadBase) register frames. The
counter module is now implemented as part of GenericTimerMem.

Frequency changes occur through writes to an active CNTFID or to
CNTCR.EN as per the architecture. Low-high and high-low transitions are
delayed until suitable thresholds, where the counter value is a divisor
of the increment given the new frequency.
Due to changes in frequency, timers need to be notifies to be
rescheduled their counter limit events based on CompareValue/TimerValue.
A new SystemCounterListener interface is provided to achieve
correctness.

CNTFRQ is no longer able to modify the global frequency. PEs may
use this to modify their register view of the former, but they should
not affect the global value. These two should be consistent.

With frequency changes, counter value needs to be stored to track
contributions from different frequency epochs. This is now handled
on epoch change, counter disable and register access.

References to all GenericTimer model components are now provided as
part of the documentation.

VExpress_GEM5_Base is updated with the new model configuration.

Change-Id: I9a991836cacd84a5bc09e5d5275191fcae9ed84b
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/25306
Reviewed-by: Nikos Nikoleris <nikos.nikoleris@arm.com>
Maintainer: Giacomo Travaglini <giacomo.travaglini@arm.com>
Tested-by: kokoro <noreply+kokoro@google.com>
2020-03-10 13:53:13 +00:00

1532 lines
44 KiB
C++

/*
* Copyright (c) 2013, 2015, 2017-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/generic_timer.hh"
#include <cmath>
#include "arch/arm/system.hh"
#include "arch/arm/utility.hh"
#include "cpu/base.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"
SystemCounter::SystemCounter(SystemCounterParams *const 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) * SimClock::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) * SimClock::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) * SimClock::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 {
_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 enabled
if (!old_ctl.enable && new_ctl.enable)
updateCounter();
// Timer disabled
else if (old_ctl.enable && !new_ctl.enable)
_control.istatus = 0;
}
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();
}
GenericTimer::GenericTimer(GenericTimerParams *const 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);
}
const GenericTimerParams *
GenericTimer::params() const
{
return dynamic_cast<const GenericTimerParams *>(_params);
}
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;
}
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 = static_cast<const GenericTimerParams *>(_params);
const unsigned old_cpu_count(timers.size());
timers.resize(cpus);
for (unsigned i = old_cpu_count; i < cpus; ++i) {
ThreadContext *tc = system.getThreadContext(i);
timers[i].reset(
new CoreTimers(*this, system, i,
p->int_phys_s->get(tc),
p->int_phys_ns->get(tc),
p->int_virt->get(tc),
p->int_hyp->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));
ThreadContext *tc = system.getThreadContext(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:
{
if (ELIsInHost(tc, currEL(tc))) {
tc->setMiscReg(MISCREG_CNTHCTL_EL2, val);
return;
}
RegVal old_cnt_ctl = core.cntkctl;
core.cntkctl = val;
ArchTimer *timer = &core.virt;
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.physNS;
CoreTimers::EventStream *ev_stream = &core.physEvStream;
handleStream(ev_stream, timer, old_cnt_ctl, val);
return;
}
// Physical timer (NS)
case MISCREG_CNTP_CVAL_NS:
case MISCREG_CNTP_CVAL_EL0:
core.physNS.setCompareValue(val);
return;
case MISCREG_CNTP_TVAL_NS:
case MISCREG_CNTP_TVAL_EL0:
core.physNS.setTimerValue(val);
return;
case MISCREG_CNTP_CTL_NS:
case MISCREG_CNTP_CTL_EL0:
core.physNS.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;
// Virtual timer
case MISCREG_CNTVOFF:
case MISCREG_CNTVOFF_EL2:
core.virt.setOffset(val);
return;
case MISCREG_CNTV_CVAL:
case MISCREG_CNTV_CVAL_EL0:
core.virt.setCompareValue(val);
return;
case MISCREG_CNTV_TVAL:
case MISCREG_CNTV_TVAL_EL0:
core.virt.setTimerValue(val);
return;
case MISCREG_CNTV_CTL:
case MISCREG_CNTV_CTL_EL0:
core.virt.setControl(val);
return;
// Physical timer (S)
case MISCREG_CNTP_CTL_S:
case MISCREG_CNTPS_CTL_EL1:
core.physS.setControl(val);
return;
case MISCREG_CNTP_CVAL_S:
case MISCREG_CNTPS_CVAL_EL1:
core.physS.setCompareValue(val);
return;
case MISCREG_CNTP_TVAL_S:
case MISCREG_CNTPS_TVAL_EL1:
core.physS.setTimerValue(val);
return;
// Hyp phys. timer, non-secure
case MISCREG_CNTHP_CTL:
case MISCREG_CNTHP_CTL_EL2:
core.hyp.setControl(val);
return;
case MISCREG_CNTHP_CVAL:
case MISCREG_CNTHP_CVAL_EL2:
core.hyp.setCompareValue(val);
return;
case MISCREG_CNTHP_TVAL:
case MISCREG_CNTHP_TVAL_EL2:
core.hyp.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;
// Physical timer
case MISCREG_CNTP_CVAL_NS:
case MISCREG_CNTP_CVAL_EL0:
return core.physNS.compareValue();
case MISCREG_CNTP_TVAL_NS:
case MISCREG_CNTP_TVAL_EL0:
return core.physNS.timerValue();
case MISCREG_CNTP_CTL_EL0:
case MISCREG_CNTP_CTL_NS:
return core.physNS.control();
case MISCREG_CNTPCT:
case MISCREG_CNTPCT_EL0:
return core.physNS.value();
// Virtual timer
case MISCREG_CNTVCT:
case MISCREG_CNTVCT_EL0:
return core.virt.value();
case MISCREG_CNTVOFF:
case MISCREG_CNTVOFF_EL2:
return core.virt.offset();
case MISCREG_CNTV_CVAL:
case MISCREG_CNTV_CVAL_EL0:
return core.virt.compareValue();
case MISCREG_CNTV_TVAL:
case MISCREG_CNTV_TVAL_EL0:
return core.virt.timerValue();
case MISCREG_CNTV_CTL:
case MISCREG_CNTV_CTL_EL0:
return core.virt.control();
// PL1 phys. timer, secure
case MISCREG_CNTP_CTL_S:
case MISCREG_CNTPS_CTL_EL1:
return core.physS.control();
case MISCREG_CNTP_CVAL_S:
case MISCREG_CNTPS_CVAL_EL1:
return core.physS.compareValue();
case MISCREG_CNTP_TVAL_S:
case MISCREG_CNTPS_TVAL_EL1:
return core.physS.timerValue();
// HYP phys. timer (NS)
case MISCREG_CNTHP_CTL:
case MISCREG_CNTHP_CTL_EL2:
return core.hyp.control();
case MISCREG_CNTHP_CVAL:
case MISCREG_CNTHP_CVAL_EL2:
return core.hyp.compareValue();
case MISCREG_CNTHP_TVAL:
case MISCREG_CNTHP_TVAL_EL2:
return core.hyp.timerValue();
default:
warn("Reading from unknown register: %s\n", miscRegName[reg]);
return 0;
}
}
void
GenericTimer::CoreTimers::physEventStreamCallback()
{
eventStreamCallback();
schedNextEvent(physEvStream, physNS);
}
void
GenericTimer::CoreTimers::virtEventStreamCallback()
{
eventStreamCallback();
schedNextEvent(virtEvStream, virt);
}
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, virt);
schedNextEvent(physEvStream, physNS);
}
void
GenericTimer::CoreTimers::serialize(CheckpointOut &cp) const
{
physS.serializeSection(cp, "phys_s_timer");
physNS.serializeSection(cp, "phys_ns_timer");
virt.serializeSection(cp, "virt_timer");
hyp.serializeSection(cp, "hyp_timer");
SERIALIZE_SCALAR(cntfrq);
SERIALIZE_SCALAR(cntkctl);
SERIALIZE_SCALAR(cnthctl);
bool ev_scheduled = physEvStream.event.scheduled();
SERIALIZE_SCALAR(ev_scheduled);
if (ev_scheduled)
SERIALIZE_SCALAR(physEvStream.event.when());
SERIALIZE_SCALAR(physEvStream.transitionTo);
SERIALIZE_SCALAR(physEvStream.transitionBit);
ev_scheduled = virtEvStream.event.scheduled();
SERIALIZE_SCALAR(ev_scheduled);
if (ev_scheduled)
SERIALIZE_SCALAR(virtEvStream.event.when());
SERIALIZE_SCALAR(virtEvStream.transitionTo);
SERIALIZE_SCALAR(virtEvStream.transitionBit);
}
void
GenericTimer::CoreTimers::unserialize(CheckpointIn &cp)
{
physS.unserializeSection(cp, "phys_s_timer");
physNS.unserializeSection(cp, "phys_ns_timer");
virt.unserializeSection(cp, "virt_timer");
hyp.unserializeSection(cp, "hyp_timer");
UNSERIALIZE_SCALAR(cntfrq);
UNSERIALIZE_SCALAR(cntkctl);
UNSERIALIZE_SCALAR(cnthctl);
bool ev_scheduled;
Tick when;
UNSERIALIZE_SCALAR(ev_scheduled);
if (ev_scheduled) {
UNSERIALIZE_SCALAR(when);
parent.reschedule(physEvStream.event, when, true);
}
UNSERIALIZE_SCALAR(physEvStream.transitionTo);
UNSERIALIZE_SCALAR(physEvStream.transitionBit);
UNSERIALIZE_SCALAR(ev_scheduled);
if (ev_scheduled) {
UNSERIALIZE_SCALAR(when);
parent.reschedule(virtEvStream.event, when, true);
}
UNSERIALIZE_SCALAR(virtEvStream.transitionTo);
UNSERIALIZE_SCALAR(virtEvStream.transitionBit);
}
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(GenericTimerFrameParams *const p)
: PioDevice(p),
timerRange(RangeSize(p->cnt_base, sys->getPageBytes())),
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, sys->getPageBytes());
accessBitsEl0 = 0x303;
addrRanges.push_back(timerEl0Range);
}
for (auto &range : addrRanges)
GenericTimerMem::validateFrameRange(range);
}
void
GenericTimerFrame::serialize(CheckpointOut &cp) const
{
physTimer.serializeSection(cp, "phys_timer");
virtTimer.serializeSection(cp, "virt_timer");
SERIALIZE_SCALAR(accessBits);
if (hasEl0View())
SERIALIZE_SCALAR(accessBitsEl0);
SERIALIZE_SCALAR(nonSecureAccess);
}
void
GenericTimerFrame::unserialize(CheckpointIn &cp)
{
physTimer.unserializeSection(cp, "phys_timer");
virtTimer.unserializeSection(cp, "virt_timer");
UNSERIALIZE_SCALAR(accessBits);
if (hasEl0View())
UNSERIALIZE_SCALAR(accessBitsEl0);
UNSERIALIZE_SCALAR(nonSecureAccess);
}
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, LittleEndianByteOrder);
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(LittleEndianByteOrder);
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(GenericTimerMemParams *const p)
: PioDevice(p),
counterCtrlRange(RangeSize(p->cnt_control_base, sys->getPageBytes())),
counterStatusRange(RangeSize(p->cnt_read_base, sys->getPageBytes())),
timerCtrlRange(RangeSize(p->cnt_ctl_base, sys->getPageBytes())),
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() % TheISA::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.haveSecurity() || 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, LittleEndianByteOrder);
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(LittleEndianByteOrder);
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);
}
}
SystemCounter *
SystemCounterParams::create()
{
return new SystemCounter(this);
}
GenericTimer *
GenericTimerParams::create()
{
return new GenericTimer(this);
}
GenericTimerFrame *
GenericTimerFrameParams::create()
{
return new GenericTimerFrame(this);
}
GenericTimerMem *
GenericTimerMemParams::create()
{
return new GenericTimerMem(this);
}