dev-arm: Refactor GenericTimer
[gem5.git] / src / dev / arm / generic_timer.cc
index 33a9fc72d01890c443bf0c4b55260c36d0ee2a88..6e19a3a25860e24f3722bbf85cf69bbcdf904e9c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2015, 2017-2018, 2019 ARM Limited
+ * Copyright (c) 2013, 2015, 2017-2018,2020 ARM Limited
  * All rights reserved.
  *
  * The license below extends only to copyright in the software and shall
 
 #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"
-
-SystemCounter::SystemCounter(std::vector<uint32_t> &freqs)
-    : _freqTable(freqs),
-      _resetTick(0),
-      _regCntkctl(0)
+#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");
@@ -57,47 +69,180 @@ SystemCounter::SystemCounter(std::vector<uint32_t> &freqs)
         "SystemCounter::SystemCounter: Architecture states a maximum of 1004 "
         "frequency table entries, limit surpassed\n");
     // Set the active frequency to be the base
-    _freq = freqs.front();
+    _freq = _freqTable.front();
     _period = (1.0 / _freq) * SimClock::Frequency;
 }
 
 void
-SystemCounter::setFreq(uint32_t freq)
+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()
 {
-    if (_freq != 0) {
-        // Altering the frequency after boot shouldn't be done in practice.
-        warn_once("The frequency of the system counter has already been set");
+    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();
     }
-    _freq = freq;
-    _period = (1.0 / freq) * SimClock::Frequency;
-    _resetTick = curTick();
+}
+
+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
 {
-    SERIALIZE_SCALAR(_regCntkctl);
-    SERIALIZE_SCALAR(_regCnthctl);
+    DPRINTF(Timer, "SystemCounter::serialize: Serializing\n");
+    SERIALIZE_SCALAR(_enabled);
     SERIALIZE_SCALAR(_freq);
-    SERIALIZE_SCALAR(_resetTick);
+    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)
 {
-    // We didn't handle CNTKCTL in this class before, assume it's zero
-    // if it isn't present.
-    if (!UNSERIALIZE_OPT_SCALAR(_regCntkctl))
-        _regCntkctl = 0;
-    if (!UNSERIALIZE_OPT_SCALAR(_regCnthctl))
-        _regCnthctl = 0;
+    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;
-    UNSERIALIZE_SCALAR(_resetTick);
 }
 
-
-
 ArchTimer::ArchTimer(const std::string &name,
                      SimObject &parent,
                      SystemCounter &sysctr,
@@ -107,17 +252,17 @@ ArchTimer::ArchTimer(const std::string &name,
       _control(0), _counterLimit(0), _offset(0),
       _counterLimitReachedEvent([this]{ counterLimitReached(); }, name)
 {
+    _systemCounter.registerListener(this);
 }
 
 void
 ArchTimer::counterLimitReached()
 {
-    _control.istatus = 1;
-
     if (!_control.enable)
         return;
 
     DPRINTF(Timer, "Counter limit reached\n");
+    _control.istatus = 1;
     if (!_control.imask) {
         if (scheduleEvents()) {
             DPRINTF(Timer, "Causing interrupt\n");
@@ -138,9 +283,8 @@ ArchTimer::updateCounter()
     } else {
         _control.istatus = 0;
         if (scheduleEvents()) {
-            const auto period(_systemCounter.period());
             _parent.schedule(_counterLimitReachedEvent,
-                 curTick() + (_counterLimit - value()) * period);
+                             whenValue(_counterLimit));
         }
     }
 }
@@ -161,19 +305,16 @@ ArchTimer::setTimerValue(uint32_t val)
 void
 ArchTimer::setControl(uint32_t val)
 {
-    ArchTimerCtrl new_ctl = val;
-    if ((new_ctl.enable && !new_ctl.imask) &&
-        !(_control.enable && !_control.imask)) {
-        // Re-evalute the timer condition
-        if (_counterLimit >= value()) {
-            _control.istatus = 1;
-
-            DPRINTF(Timer, "Causing interrupt in control\n");
-            //_interrupt.send();
-        }
-    }
+    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
@@ -189,6 +330,12 @@ ArchTimer::value() const
     return _systemCounter.value() - _offset;
 }
 
+void
+ArchTimer::notify()
+{
+    updateCounter();
+}
+
 void
 ArchTimer::serialize(CheckpointOut &cp) const
 {
@@ -226,12 +373,14 @@ ArchTimer::drainResume()
     updateCounter();
 }
 
-GenericTimer::GenericTimer(GenericTimerParams *p)
-    : ClockedObject(p),
-      systemCounter(p->freqs),
+GenericTimer::GenericTimer(GenericTimerParams *const p)
+    : SimObject(p),
+      systemCounter(*p->counter),
       system(*p->system)
 {
-    fatal_if(!p->system, "No system specified, can't instantiate timer.\n");
+    SystemCounter::validateCounterRef(p->counter);
+    fatal_if(!p->system, "GenericTimer::GenericTimer: No system specified, "
+             "can't instantiate architected timers\n");
     system.setGenericTimer(this);
 }
 
@@ -246,25 +395,15 @@ GenericTimer::serialize(CheckpointOut &cp) const
 {
     paramOut(cp, "cpu_count", timers.size());
 
-    systemCounter.serializeSection(cp, "sys_counter");
-
     for (int i = 0; i < timers.size(); ++i) {
         const CoreTimers &core(*timers[i]);
-
-        // This should really be phys_timerN, but we are stuck with
-        // arch_timer for backwards compatibility.
-        core.physNS.serializeSection(cp, csprintf("arch_timer%d", i));
-        core.physS.serializeSection(cp, csprintf("phys_s_timer%d", i));
-        core.virt.serializeSection(cp, csprintf("virt_timer%d", i));
-        core.hyp.serializeSection(cp, csprintf("hyp_timer%d", i));
+        core.serializeSection(cp, csprintf("pe_implementation%d", i));
     }
 }
 
 void
 GenericTimer::unserialize(CheckpointIn &cp)
 {
-    systemCounter.unserializeSection(cp, "sys_counter");
-
     // 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.
@@ -278,16 +417,10 @@ GenericTimer::unserialize(CheckpointIn &cp)
 
     for (int i = 0; i < cpu_count; ++i) {
         CoreTimers &core(getTimers(i));
-        // This should really be phys_timerN, but we are stuck with
-        // arch_timer for backwards compatibility.
-        core.physNS.unserializeSection(cp, csprintf("arch_timer%d", i));
-        core.physS.unserializeSection(cp, csprintf("phys_s_timer%d", i));
-        core.virt.unserializeSection(cp, csprintf("virt_timer%d", i));
-        core.hyp.unserializeSection(cp, csprintf("hyp_timer%d", i));
+        core.unserializeSection(cp, csprintf("pe_implementation%d", i));
     }
 }
 
-
 GenericTimer::CoreTimers &
 GenericTimer::getTimers(int cpu_id)
 {
@@ -318,28 +451,73 @@ GenericTimer::createTimers(unsigned cpus)
     }
 }
 
+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:
-        systemCounter.setFreq(val);
+        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:
-        systemCounter.setKernelControl(val);
-        return;
+      {
+        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:
-        systemCounter.setHypControl(val);
-        return;
+      {
+        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:
@@ -433,16 +611,13 @@ GenericTimer::readMiscReg(int reg, unsigned cpu)
     switch (reg) {
       case MISCREG_CNTFRQ:
       case MISCREG_CNTFRQ_EL0:
-        return systemCounter.freq();
-
+        return core.cntfrq;
       case MISCREG_CNTKCTL:
       case MISCREG_CNTKCTL_EL1:
-        return systemCounter.getKernelControl();
-
+        return core.cntkctl & 0x00000000ffffffff;
       case MISCREG_CNTHCTL:
       case MISCREG_CNTHCTL_EL2:
-        return systemCounter.getHypControl();
-
+        return core.cnthctl & 0x00000000ffffffff;
       // Physical timer
       case MISCREG_CNTP_CVAL_NS:
       case MISCREG_CNTP_CVAL_EL0:
@@ -514,6 +689,97 @@ GenericTimer::readMiscReg(int reg, unsigned cpu)
     }
 }
 
+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)
@@ -530,341 +796,734 @@ GenericTimerISA::readMiscReg(int reg)
     return value;
 }
 
-GenericTimerMem::GenericTimerMem(GenericTimerMemParams *p)
+GenericTimerFrame::GenericTimerFrame(GenericTimerFrameParams *const p)
     : PioDevice(p),
-      ctrlRange(RangeSize(p->base, sys->getPageBytes())),
-      timerRange(RangeSize(p->base + sys->getPageBytes(),
-                  sys->getPageBytes())),
-      addrRanges{ctrlRange, timerRange},
-      systemCounter(p->freqs),
-      physTimer(csprintf("%s.phys_timer0", name()),
+      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_phys->get()),
-      virtTimer(csprintf("%s.virt_timer0", name()),
-                *this, systemCounter,
-                p->int_virt->get())
+                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
-GenericTimerMem::serialize(CheckpointOut &cp) const
+GenericTimerFrame::setVirtOffset(uint64_t new_offset)
 {
-    paramOut(cp, "timer_count", 1);
+    virtTimer.setOffset(new_offset);
+}
 
-    systemCounter.serializeSection(cp, "sys_counter");
+bool
+GenericTimerFrame::hasEl0View() const
+{
+    return timerEl0Range.valid();
+}
+
+uint8_t
+GenericTimerFrame::getAccessBits() const
+{
+    return accessBits;
+}
+
+void
+GenericTimerFrame::setAccessBits(uint8_t data)
+{
+    accessBits = data & 0x3f;
+}
 
-    physTimer.serializeSection(cp, "phys_timer0");
-    virtTimer.serializeSection(cp, "virt_timer0");
+bool
+GenericTimerFrame::hasNonSecureAccess() const
+{
+    return nonSecureAccess;
 }
 
 void
-GenericTimerMem::unserialize(CheckpointIn &cp)
+GenericTimerFrame::setNonSecureAccess()
 {
-    systemCounter.unserializeSection(cp, "sys_counter");
+    nonSecureAccess = true;
+}
 
-    unsigned timer_count;
-    UNSERIALIZE_SCALAR(timer_count);
-    // The timer count variable is just here for future versions where
-    // we support more than one set of timers.
-    if (timer_count != 1)
-        panic("Incompatible checkpoint: Only one set of timers supported");
+bool
+GenericTimerFrame::hasReadableVoff() const
+{
+    return accessBits.rvoff;
+}
 
-    physTimer.unserializeSection(cp, "phys_timer0");
-    virtTimer.unserializeSection(cp, "virt_timer0");
+AddrRangeList
+GenericTimerFrame::getAddrRanges() const
+{
+    return addrRanges;
 }
 
 Tick
-GenericTimerMem::read(PacketPtr pkt)
+GenericTimerFrame::read(PacketPtr pkt)
 {
-    const unsigned size(pkt->getSize());
-    const Addr addr(pkt->getAddr());
-    uint64_t value;
-
-    pkt->makeResponse();
-    if (ctrlRange.contains(addr)) {
-        value = ctrlRead(addr - ctrlRange.start(), size);
-    } else if (timerRange.contains(addr)) {
-        value = timerRead(addr - timerRange.start(), size);
+    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("Invalid address: 0x%x\n", addr);
+        panic("GenericTimerFrame::read: Invalid address: 0x%x\n", addr);
     }
 
-    DPRINTF(Timer, "Read 0x%x <- 0x%x(%i)\n", value, addr, size);
+    resp = timerRead(offset, size, is_sec, to_el0);
 
-    if (size == 8) {
-        pkt->setLE<uint64_t>(value);
-    } else if (size == 4) {
-        pkt->setLE<uint32_t>(value);
-    } else {
-        panic("Unexpected access size: %i\n", size);
-    }
+    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
-GenericTimerMem::write(PacketPtr pkt)
+GenericTimerFrame::write(PacketPtr pkt)
 {
-    const unsigned size(pkt->getSize());
-    if (size != 8 && size != 4)
-        panic("Unexpected access size\n");
-
-    const Addr addr(pkt->getAddr());
-    const uint64_t value(size == 8 ?
-                         pkt->getLE<uint64_t>() : pkt->getLE<uint32_t>());
-
-    DPRINTF(Timer, "Write 0x%x -> 0x%x(%i)\n", value, addr, size);
-    if (ctrlRange.contains(addr)) {
-        ctrlWrite(addr - ctrlRange.start(), size, value);
-    } else if (timerRange.contains(addr)) {
-        timerWrite(addr - timerRange.start(), size, value);
+    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("Invalid address: 0x%x\n", addr);
+        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
-GenericTimerMem::ctrlRead(Addr addr, size_t size) const
+GenericTimerFrame::timerRead(Addr addr, size_t size, bool is_sec,
+                             bool to_el0) const
 {
-    if (size == 4) {
-        switch (addr) {
-          case CTRL_CNTFRQ:
+    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 CTRL_CNTTIDR:
-            return 0x3; // Frame 0 implemented with virtual timers
+      case TIMER_CNTEL0ACR:
+        if (!hasEl0View() || to_el0)
+            return 0;
+        else
+            return accessBitsEl0;
 
-          case CTRL_CNTNSAR:
-          case CTRL_CNTACR_BASE:
-            warn("Reading from unimplemented control register (0x%x)\n", addr);
+      case TIMER_CNTP_CVAL_LO:
+        if (!accessBits.rwpt || (to_el0 && !accessBitsEl0.pten))
             return 0;
+        else
+            return physTimer.compareValue();
 
-          case CTRL_CNTVOFF_LO_BASE:
-            return virtTimer.offset();
+      case TIMER_CNTP_CVAL_HI:
+        if (!accessBits.rwpt || (to_el0 && !accessBitsEl0.pten))
+            return 0;
+        else
+            return physTimer.compareValue() >> 32;
 
-          case CTRL_CNTVOFF_HI_BASE:
-            return virtTimer.offset() >> 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();
 
-          default:
-            warn("Unexpected address (0x%x:%i), assuming RAZ\n", addr, size);
+      case TIMER_CNTVCT_LO:
+        if (!accessBits.rvct || (to_el0 && !accessBitsEl0.vcten))
             return 0;
-        }
-    } else if (size == 8) {
-        switch (addr) {
-          case CTRL_CNTVOFF_LO_BASE:
+        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();
 
-          default:
-            warn("Unexpected address (0x%x:%i), assuming RAZ\n", addr, size);
+      case TIMER_CNTVOFF_HI:
+        if (!accessBits.rvoff || (to_el0))
             return 0;
-        }
-    } else {
-        panic("Invalid access size: %i\n", size);
-    }
-}
+        else
+            return virtTimer.offset() >> 32;
 
-void
-GenericTimerMem::ctrlWrite(Addr addr, size_t size, uint64_t value)
-{
-    if (size == 4) {
-        switch (addr) {
-          case CTRL_CNTFRQ:
-          case CTRL_CNTNSAR:
-          case CTRL_CNTTIDR:
-          case CTRL_CNTACR_BASE:
-            warn("Write to unimplemented control register (0x%x)\n", addr);
-            return;
+      case TIMER_CNTV_CVAL_LO:
+        if (!accessBits.rwvt || (to_el0 && !accessBitsEl0.vten))
+            return 0;
+        else
+            return virtTimer.compareValue();
 
-          case CTRL_CNTVOFF_LO_BASE:
-            virtTimer.setOffset(
-                insertBits(virtTimer.offset(), 31, 0, value));
-            return;
+      case TIMER_CNTV_CVAL_HI:
+        if (!accessBits.rwvt || (to_el0 && !accessBitsEl0.vten))
+            return 0;
+        else
+            return virtTimer.compareValue() >> 32;
 
-          case CTRL_CNTVOFF_HI_BASE:
-            virtTimer.setOffset(
-                insertBits(virtTimer.offset(), 63, 32, value));
-            return;
+      case TIMER_CNTV_TVAL:
+        if (!accessBits.rwvt || (to_el0 && !accessBitsEl0.vten))
+            return 0;
+        else
+            return virtTimer.timerValue();
 
-          default:
-            warn("Ignoring write to unexpected address (0x%x:%i)\n",
-                 addr, size);
-            return;
-        }
-    } else if (size == 8) {
-        switch (addr) {
-          case CTRL_CNTVOFF_LO_BASE:
-            virtTimer.setOffset(value);
-            return;
+      case TIMER_CNTV_CTL:
+        if (!accessBits.rwvt || (to_el0 && !accessBitsEl0.vten))
+            return 0;
+        else
+            return virtTimer.control();
 
-          default:
-            warn("Ignoring write to unexpected address (0x%x:%i)\n",
-                 addr, size);
-            return;
-        }
-    } else {
-        panic("Invalid access size: %i\n", size);
+      default:
+        warn("GenericTimerFrame::timerRead: Unexpected address (0x%x:%i), "
+             "assuming RAZ\n", addr, size);
+        return 0;
     }
 }
 
-uint64_t
-GenericTimerMem::timerRead(Addr addr, size_t size) const
+void
+GenericTimerFrame::timerWrite(Addr addr, size_t size, uint64_t data,
+                              bool is_sec, bool to_el0)
 {
-    if (size == 4) {
-        switch (addr) {
-          case TIMER_CNTPCT_LO:
-            return physTimer.value();
+    if (!GenericTimerMem::validateAccessPerm(system, is_sec) &&
+        !nonSecureAccess)
+        return;
 
-          case TIMER_CNTPCT_HI:
-            return physTimer.value() >> 32;
+    switch (addr) {
+      case TIMER_CNTPCT_LO ... TIMER_CNTPCT_HI:
+        warn("GenericTimerFrame::timerWrite: RO reg (0x%x) [CNTPCT]\n",
+             addr);
+        return;
 
-          case TIMER_CNTVCT_LO:
-            return virtTimer.value();
+      case TIMER_CNTFRQ:
+        warn("GenericTimerFrame::timerWrite: RO reg (0x%x) [CNTFRQ]\n",
+             addr);
+        return;
 
-          case TIMER_CNTVCT_HI:
-            return virtTimer.value() >> 32;
+      case TIMER_CNTEL0ACR:
+        if (!hasEl0View() || to_el0)
+            return;
 
-          case TIMER_CNTFRQ:
-            return systemCounter.freq();
+        insertBits(accessBitsEl0, 9, 8, data);
+        insertBits(accessBitsEl0, 1, 0, data);
+        return;
 
-          case TIMER_CNTEL0ACR:
-            warn("Read from unimplemented timer register (0x%x)\n", addr);
-            return 0;
+      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 CTRL_CNTVOFF_LO_BASE:
-            return virtTimer.offset();
+      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 CTRL_CNTVOFF_HI_BASE:
-            return virtTimer.offset() >> 32;
+      case TIMER_CNTP_TVAL:
+        if ((!accessBits.rwpt) || (to_el0 && !accessBitsEl0.pten))
+            return;
+        physTimer.setTimerValue(data);
+        return;
 
-          case TIMER_CNTP_CVAL_LO:
-            return physTimer.compareValue();
+      case TIMER_CNTP_CTL:
+        if ((!accessBits.rwpt) || (to_el0 && !accessBitsEl0.pten))
+            return;
+        physTimer.setControl(data);
+        return;
 
-          case TIMER_CNTP_CVAL_HI:
-            return physTimer.compareValue() >> 32;
+      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_CNTP_TVAL:
-            return physTimer.timerValue();
+      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_CNTP_CTL:
-            return physTimer.control();
+      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_CVAL_LO:
-            return virtTimer.compareValue();
+      case TIMER_CNTV_TVAL:
+        if ((!accessBits.rwvt) || (to_el0 && !accessBitsEl0.vten))
+            return;
+        virtTimer.setTimerValue(data);
+        return;
 
-          case TIMER_CNTV_CVAL_HI:
-            return virtTimer.compareValue() >> 32;
+      case TIMER_CNTV_CTL:
+        if ((!accessBits.rwvt) || (to_el0 && !accessBitsEl0.vten))
+            return;
+        virtTimer.setControl(data);
+        return;
 
-          case TIMER_CNTV_TVAL:
-            return virtTimer.timerValue();
+      default:
+        warn("GenericTimerFrame::timerWrite: Unexpected address (0x%x:%i), "
+             "assuming WI\n", addr, size);
+    }
+}
 
-          case TIMER_CNTV_CTL:
-            return virtTimer.control();
+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);
+    }
+}
 
-          default:
-            warn("Unexpected address (0x%x:%i), assuming RAZ\n", addr, size);
-            return 0;
-        }
-    } else if (size == 8) {
-        switch (addr) {
-          case TIMER_CNTPCT_LO:
-            return physTimer.value();
+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");
+}
 
-          case TIMER_CNTVCT_LO:
-            return virtTimer.value();
+bool
+GenericTimerMem::validateAccessPerm(ArmSystem &sys, bool is_sec)
+{
+    return !sys.haveSecurity() || is_sec;
+}
 
-          case CTRL_CNTVOFF_LO_BASE:
-            return virtTimer.offset();
+AddrRangeList
+GenericTimerMem::getAddrRanges() const
+{
+    return addrRanges;
+}
 
-          case TIMER_CNTP_CVAL_LO:
-            return physTimer.compareValue();
+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;
+}
 
-          case TIMER_CNTV_CVAL_LO:
-            return virtTimer.compareValue();
+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);
 
-          default:
-            warn("Unexpected address (0x%x:%i), assuming RAZ\n", addr, size);
-            return 0;
+    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];
         }
-    } else {
-        panic("Invalid access size: %i\n", size);
+        warn("GenericTimerMem::counterCtrlRead: Unexpected address "
+             "(0x%x:%i), assuming RAZ\n", addr, size);
+        return 0;
+      }
     }
 }
 
 void
-GenericTimerMem::timerWrite(Addr addr, size_t size, uint64_t value)
+GenericTimerMem::counterCtrlWrite(Addr addr, size_t size, uint64_t data,
+                                  bool is_sec)
 {
-    if (size == 4) {
-        switch (addr) {
-          case TIMER_CNTEL0ACR:
-            warn("Unimplemented timer register (0x%x)\n", addr);
-            return;
+    if (!GenericTimerMem::validateAccessPerm(system, is_sec))
+        return;
 
-          case TIMER_CNTP_CVAL_LO:
-            physTimer.setCompareValue(
-                insertBits(physTimer.compareValue(), 31, 0, value));
-            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 TIMER_CNTP_CVAL_HI:
-            physTimer.setCompareValue(
-                insertBits(physTimer.compareValue(), 63, 32, value));
-            return;
+      case COUNTER_CTRL_CNTSR:
+        warn("GenericTimerMem::counterCtrlWrite: RO reg (0x%x) [CNTSR]\n",
+             addr);
+        return;
 
-          case TIMER_CNTP_TVAL:
-            physTimer.setTimerValue(value);
-            return;
+      case COUNTER_CTRL_CNTCV_LO:
+        data = size == 4 ? insertBits(systemCounter.value(), 31, 0, data)
+                         : data;
+        systemCounter.setValue(data);
+        return;
 
-          case TIMER_CNTP_CTL:
-            physTimer.setControl(value);
-            return;
+      case COUNTER_CTRL_CNTCV_HI:
+        data = insertBits(systemCounter.value(), 63, 32, data);
+        systemCounter.setValue(data);
+        return;
 
-          case TIMER_CNTV_CVAL_LO:
-            virtTimer.setCompareValue(
-                insertBits(virtTimer.compareValue(), 31, 0, value));
-            return;
+      case COUNTER_CTRL_CNTSCR:
+        return;
 
-          case TIMER_CNTV_CVAL_HI:
-            virtTimer.setCompareValue(
-                insertBits(virtTimer.compareValue(), 63, 32, value));
-            return;
+      case COUNTER_CTRL_CNTID:
+        warn("GenericTimerMem::counterCtrlWrite: RO reg (0x%x) [CNTID]\n",
+             addr);
+        return;
 
-          case TIMER_CNTV_TVAL:
-            virtTimer.setTimerValue(value);
-            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);
+      }
+    }
+}
 
-          case TIMER_CNTV_CTL:
-            virtTimer.setControl(value);
-            return;
+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;
+    }
+}
 
-          default:
-            warn("Unexpected address (0x%x:%i), ignoring write\n", addr, size);
-            return;
-        }
-    } else if (size == 8) {
-        switch (addr) {
-          case TIMER_CNTP_CVAL_LO:
-            return physTimer.setCompareValue(value);
+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);
+    }
+}
 
-          case TIMER_CNTV_CVAL_LO:
-            return virtTimer.setCompareValue(value);
+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;
+    }
+}
 
-          default:
-            warn("Unexpected address (0x%x:%i), ignoring write\n", addr, size);
-            return;
+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();
         }
-    } else {
-        panic("Invalid access size: %i\n", size);
+        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()
 {