dev-arm: Refactor GenericTimer
authorAdrian Herrera <adrian.herrera@arm.com>
Wed, 30 Oct 2019 10:03:52 +0000 (10:03 +0000)
committerGiacomo Travaglini <giacomo.travaglini@arm.com>
Tue, 10 Mar 2020 13:53:13 +0000 (13:53 +0000)
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>
src/dev/arm/GenericTimer.py [new file with mode: 0644]
src/dev/arm/RealView.py
src/dev/arm/SConscript
src/dev/arm/generic_timer.cc
src/dev/arm/generic_timer.hh
src/dev/arm/generic_timer_miscregs_types.hh [new file with mode: 0644]

diff --git a/src/dev/arm/GenericTimer.py b/src/dev/arm/GenericTimer.py
new file mode 100644 (file)
index 0000000..077b6f6
--- /dev/null
@@ -0,0 +1,191 @@
+# Copyright (c) 2009-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.
+
+from m5.SimObject import SimObject
+from m5.objects.Device import PioDevice
+from m5.params import Param, MaxAddr, NULL, VectorParam
+from m5.proxy import Parent
+from m5.util import fatal
+from m5.util.fdthelper import FdtNode, FdtProperty, FdtPropertyWords, FdtState
+
+class SystemCounter(SimObject):
+    """
+Shared by both PE-implementations and memory-mapped timers. It provides a
+uniform view of system time through its counter value.
+
+Reference:
+    Arm ARM (ARM DDI 0487E.a)
+    D11.1.2 - The system counter
+    """
+
+    type = 'SystemCounter'
+    cxx_header = "dev/arm/generic_timer.hh"
+
+    # Maximum of 1004 frequency entries, including end marker
+    freqs = VectorParam.UInt32([0x01800000], "Frequencies available for the "
+        "system counter (in Hz). First element is the base frequency, "
+        "following are alternative lower ones which must be exact divisors")
+
+    def generateDtb(self):
+        if not self.freqs:
+            fatal("No counter frequency to expose in DTB")
+        return FdtPropertyWords("clock-frequency", [self.freqs[0]])
+
+class GenericTimer(SimObject):
+    """
+Architected timers per PE in the system. Each of them provides a physical
+counter, a virtual counter and several timers accessible from different
+exception levels and security states.
+
+Reference:
+    Arm ARM (ARM DDI 0487E.a)
+    D11.2 - The AArch64 view of the Generic Timer
+    G6.2  - The AArch32 view of the Generic Timer
+    """
+
+    type = 'GenericTimer'
+    cxx_header = "dev/arm/generic_timer.hh"
+
+    _freq_in_dtb = False
+
+    system = Param.ArmSystem(Parent.any, "system")
+
+    counter = Param.SystemCounter(Parent.any, "Global system counter")
+
+    int_phys_s = Param.ArmPPI("Physical (S) timer interrupt")
+    int_phys_ns = Param.ArmPPI("Physical (NS) timer interrupt")
+    int_virt = Param.ArmPPI("Virtual timer interrupt")
+    int_hyp = Param.ArmPPI("Hypervisor timer interrupt")
+
+    def generateDeviceTree(self, state):
+        node = FdtNode("timer")
+
+        node.appendCompatible(["arm,cortex-a15-timer",
+                               "arm,armv7-timer",
+                               "arm,armv8-timer"])
+        node.append(FdtPropertyWords("interrupts", [
+            1, int(self.int_phys_s.num) - 16, 0xf08,
+            1, int(self.int_phys_ns.num) - 16, 0xf08,
+            1, int(self.int_virt.num) - 16, 0xf08,
+            1, int(self.int_hyp.num) - 16, 0xf08,
+        ]))
+
+        if self._freq_in_dtb:
+            node.append(self.counter.unproxy(self).generateDtb())
+
+        yield node
+
+class GenericTimerFrame(PioDevice):
+    """
+Memory-mapped timer frame implementation. Controlled from GenericTimerMem,
+may be used by peripherals without a system register interface.
+
+Reference:
+    Arm ARM (ARM DDI 0487E.a)
+    I2.3.2 - The CNTBaseN and CNTEL0BaseN frames
+    """
+
+    type = 'GenericTimerFrame'
+    cxx_header = "dev/arm/generic_timer.hh"
+
+    _frame_num = 0
+
+    counter = Param.SystemCounter(Parent.any, "Global system counter")
+
+    cnt_base = Param.Addr("CNTBase register frame base")
+    cnt_el0_base = Param.Addr(MaxAddr, "CNTEL0Base register frame base")
+
+    int_phys = Param.ArmSPI("Physical Interrupt")
+    int_virt = Param.ArmSPI("Virtual Interrupt")
+
+    def generateDeviceTree(self, state):
+        node = FdtNode("frame@{:08x}".format(self.cnt_base.value))
+        node.append(FdtPropertyWords("frame-number", self._frame_num))
+        ints = [0, int(self.int_phys.num) - 32, 4]
+        if self.int_virt != NULL:
+            ints.extend([0, int(self.int_virt.num) - 32, 4])
+        node.append(FdtPropertyWords("interrupts", ints))
+        reg = state.addrCells(self.cnt_base) + state.sizeCells(0x1000)
+        if self.cnt_el0_base.value != MaxAddr:
+            reg.extend(state.addrCells(self.cnt_el0_base)
+                       + state.sizeCells(0x1000))
+        node.append(FdtPropertyWords("reg", reg))
+
+        return node
+
+class GenericTimerMem(PioDevice):
+    """
+System level implementation. It provides three main components:
+- Memory-mapped counter module: controls the system timer through the
+  CNTControlBase frame, and provides its value through the CNTReadBase frame
+- Memory-mapped timer control module: controls the memory-mapped timers
+- Memory-mapped timers: implementations of the GenericTimer for system
+  peripherals
+
+Reference:
+    Arm ARM (ARM DDI 0487E.a)
+    I2 - System Level Implementation of the Generic Timer
+    """
+
+    type = 'GenericTimerMem'
+    cxx_header = "dev/arm/generic_timer.hh"
+
+    _freq_in_dtb = False
+
+    counter = Param.SystemCounter(Parent.any, "Global system counter")
+
+    cnt_control_base = Param.Addr("CNTControlBase register frame base")
+    cnt_read_base = Param.Addr("CNTReadBase register frame base")
+    cnt_ctl_base = Param.Addr("CNTCTLBase register frame base")
+
+    # Maximum of 8 timer frames
+    frames = VectorParam.GenericTimerFrame([], "Memory-mapped timer frames")
+
+    def generateDeviceTree(self, state):
+        node = self.generateBasicPioDeviceNode(state, "timer",
+            self.cnt_ctl_base, 0x1000)
+        node.appendCompatible(["arm,armv7-timer-mem"])
+        node.append(state.addrCellsProperty())
+        node.append(state.sizeCellsProperty())
+        node.append(FdtProperty("ranges"))
+
+        if self._freq_in_dtb:
+            node.append(self.counter.unproxy(self).generateDtb())
+
+        for i, frame in enumerate(self.frames):
+            frame._frame_num = i
+            node.append(frame.generateDeviceTree(state))
+
+        yield node
index db47ed6c82c2879a0881400e809f9fa94de32f6d..4fabcc3bdd5db1b20a9aaec33966d029678f5364 100644 (file)
@@ -51,6 +51,7 @@ from m5.objects.Platform import Platform
 from m5.objects.Terminal import Terminal
 from m5.objects.Uart import Uart
 from m5.objects.SimpleMemory import SimpleMemory
+from m5.objects.GenericTimer import *
 from m5.objects.Gic import *
 from m5.objects.EnergyCtrl import EnergyCtrl
 from m5.objects.ClockedObject import ClockedObject
@@ -437,49 +438,6 @@ class CpuLocalTimer(BasicPioDevice):
     int_timer = Param.ArmPPI("Interrrupt used per-cpu to GIC")
     int_watchdog = Param.ArmPPI("Interrupt for per-cpu watchdog to GIC")
 
-class GenericTimer(ClockedObject):
-    type = 'GenericTimer'
-    cxx_header = "dev/arm/generic_timer.hh"
-    system = Param.ArmSystem(Parent.any, "system")
-    int_phys_s = Param.ArmPPI("Physical (S) timer interrupt")
-    int_phys_ns = Param.ArmPPI("Physical (NS) timer interrupt")
-    int_virt = Param.ArmPPI("Virtual timer interrupt")
-    int_hyp = Param.ArmPPI("Hypervisor timer interrupt")
-
-    freqs = VectorParam.UInt32([0x01800000], "Frequencies available for the "
-        "system counter (in Hz). First element is the base frequency, "
-        "following are alternative lower ones which must be exact divisors")
-
-    def generateDeviceTree(self, state):
-        node = FdtNode("timer")
-
-        node.appendCompatible(["arm,cortex-a15-timer",
-                               "arm,armv7-timer",
-                               "arm,armv8-timer"])
-        node.append(FdtPropertyWords("interrupts", [
-            1, int(self.int_phys_s.num) - 16, 0xf08,
-            1, int(self.int_phys_ns.num) - 16, 0xf08,
-            1, int(self.int_virt.num) - 16, 0xf08,
-            1, int(self.int_hyp.num) - 16, 0xf08,
-        ]))
-        clock = state.phandle(self.clk_domain.unproxy(self))
-        node.append(FdtPropertyWords("clocks", clock))
-
-        yield node
-
-class GenericTimerMem(PioDevice):
-    type = 'GenericTimerMem'
-    cxx_header = "dev/arm/generic_timer.hh"
-
-    base = Param.Addr(0, "Base address")
-
-    int_phys = Param.ArmSPI("Physical Interrupt")
-    int_virt = Param.ArmSPI("Virtual Interrupt")
-
-    freqs = VectorParam.UInt32([0x01800000], "Frequencies available for the "
-        "system counter (in Hz). First element is the base frequency, "
-        "following are alternative lower ones which must be exact divisors")
-
 class PL031(AmbaIntDevice):
     type = 'PL031'
     cxx_header = "dev/arm/rtc_pl031.hh"
@@ -732,6 +690,7 @@ class VExpress_EMM(RealView):
         conf_base=0x30000000, conf_size='256MB', conf_device_bits=16,
         pci_pio_base=0)
 
+    sys_counter = SystemCounter()
     generic_timer = GenericTimer(int_phys_s=ArmPPI(num=29),
                                  int_phys_ns=ArmPPI(num=30),
                                  int_virt=ArmPPI(num=27),
@@ -901,7 +860,14 @@ Memory map:
        0x1c170000-0x1c17ffff: RTC
 
    0x20000000-0x3fffffff: On-chip peripherals:
+       0x2a430000-0x2a43ffff: System Counter (control)
        0x2a490000-0x2a49ffff: Trusted Watchdog (SP805)
+       0x2a800000-0x2a800fff: System Counter (read)
+       0x2a810000-0x2a810fff: System Timer (control)
+
+       0x2a820000-0x2a820fff: System Timer (frame 0)
+       0x2a830000-0x2a830fff: System Timer (frame 1)
+
        0x2b000000-0x2b00ffff: HDLCD
 
        0x2b060000-0x2b060fff: System Watchdog (SP805)
@@ -946,6 +912,8 @@ Interrupts:
         47   : Reserved (Ethernet)
         48   : Reserved (USB)
         56   : Trusted Watchdog (SP805)
+        57   : System timer0 (phys)
+        58   : System timer1 (phys)
     95-255: On-chip interrupt sources (we use these for
             gem5-specific devices, SPIs)
          74    : VirtIO (gem5/FM extension)
@@ -991,19 +959,29 @@ Interrupts:
     # Trusted Watchdog, SP805
     trusted_watchdog = Sp805(pio_addr=0x2a490000, int_num=56)
 
+    sys_counter = SystemCounter()
     generic_timer = GenericTimer(int_phys_s=ArmPPI(num=29),
                                  int_phys_ns=ArmPPI(num=30),
                                  int_virt=ArmPPI(num=27),
                                  int_hyp=ArmPPI(num=26))
+    generic_timer_mem = GenericTimerMem(cnt_control_base=0x2a430000,
+                                        cnt_read_base=0x2a800000,
+                                        cnt_ctl_base=0x2a810000,
+                                        frames=[
+            GenericTimerFrame(cnt_base=0x2a820000,
+                int_phys=ArmSPI(num=57), int_virt=ArmSPI(num=133)),
+            GenericTimerFrame(cnt_base=0x2a830000,
+                int_phys=ArmSPI(num=58), int_virt=ArmSPI(num=134))
+    ])
 
     system_watchdog = Sp805(pio_addr=0x2b060000, int_num=130)
 
     def _on_chip_devices(self):
         return [
-            self.generic_timer,
+            self.generic_timer_mem,
             self.trusted_watchdog,
             self.system_watchdog
-        ]
+        ] + self.generic_timer_mem.frames
 
     def _on_chip_memory(self):
         memories = [
index d865c67f48a3b8c38dc197556c5f975e1d3a7779..b264d05cfdb3b123de155f9eb5694668da211cb3 100644 (file)
@@ -41,6 +41,7 @@ if env['TARGET_ISA'] == 'arm':
     SimObject('AbstractNVM.py')
     SimObject('Display.py')
     SimObject('FlashDevice.py')
+    SimObject('GenericTimer.py')
     SimObject('Gic.py')
     SimObject('RealView.py')
     SimObject('SMMUv3.py')
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()
 {
index 8da0467a0205d79d085cf1b3c5510bdf77afc1f4..b74b19a16da43d79b064fea8998cf491f5f1a6ba 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 "arch/arm/isa_device.hh"
 #include "arch/arm/system.hh"
-#include "base/bitunion.hh"
 #include "dev/arm/base_gic.hh"
+#include "dev/arm/generic_timer_miscregs_types.hh"
 #include "sim/core.hh"
 #include "sim/sim_object.hh"
 
 /// @file
 /// This module implements the global system counter and the local per-CPU
-/// architected timers as specified by the ARM Generic Timer extension (ARM
-/// ARM, Issue C, Chapter 17).
+/// architected timers as specified by the ARM Generic Timer extension:
+/// Arm ARM (ARM DDI 0487E.a)
+///     D11.1.2 - The system counter
+///     D11.2 - The AArch64 view of the Generic Timer
+///     G6.2  - The AArch32 view of the Generic Timer
+///     I2 - System Level Implementation of the Generic Timer
 
 class Checkpoint;
+class SystemCounterParams;
 class GenericTimerParams;
+class GenericTimerFrameParams;
 class GenericTimerMemParams;
 
-/// Global system counter.  It is shared by the architected timers.
-/// @todo: implement memory-mapped controls
-class SystemCounter : public Serializable
+/// Abstract class for elements whose events depend on the counting speed
+/// of the System Counter
+class SystemCounterListener : public Serializable
+{
+  public:
+    /// Called from the SystemCounter when a change in counting speed occurred
+    /// Events should be rescheduled properly inside this member function
+    virtual void notify(void) = 0;
+};
+
+/// Global system counter.  It is shared by the architected and memory-mapped
+/// timers.
+class SystemCounter : public SimObject
 {
   protected:
+    /// Indicates if the counter is enabled
+    bool _enabled;
     /// Counter frequency (as specified by CNTFRQ).
     uint32_t _freq;
+    /// Counter value (as specified in CNTCV).
+    uint64_t _value;
+    /// Value increment in each counter cycle
+    uint64_t _increment;
     /// Frequency modes table with all possible frequencies for the counter
     std::vector<uint32_t> _freqTable;
+    /// Currently selected entry in the table, its contents should match _freq
+    size_t _activeFreqEntry;
     /// Cached copy of the counter period (inverse of the frequency).
     Tick _period;
-    /// Tick when the counter was reset.
-    Tick _resetTick;
+    /// Counter cycle start Tick when the counter status affecting
+    /// its value has been updated
+    Tick _updateTick;
 
-    /// Kernel event stream control register
-    uint32_t _regCntkctl;
-    /// Hypervisor event stream control register
-    uint32_t _regCnthctl;
+    /// Listeners to changes in counting speed
+    std::vector<SystemCounterListener *> _listeners;
 
     /// Maximum architectural number of frequency table entries
     static constexpr size_t MAX_FREQ_ENTRIES = 1004;
 
   public:
-    SystemCounter(std::vector<uint32_t> &freqs);
+    SystemCounter(SystemCounterParams *const p);
 
-    /// Returns the current value of the physical counter.
-    uint64_t value() const
-    {
-        if (_freq == 0)
-            return 0;  // Counter is still off.
-        return (curTick() - _resetTick) / _period;
-    }
+    /// Validates a System Counter reference
+    /// @param sys_cnt System counter reference to validate
+    static void validateCounterRef(SystemCounter *sys_cnt);
 
+    /// Indicates if the counter is enabled.
+    bool enabled() const { return _enabled; }
     /// Returns the counter frequency.
     uint32_t freq() const { return _freq; }
-    /// Sets the counter frequency.
-    /// @param freq frequency in Hz.
-    void setFreq(uint32_t freq);
-
+    /// Updates and returns the counter value.
+    uint64_t value();
+    /// Returns the value increment
+    uint64_t increment() const { return _increment; }
+    /// Returns a reference to the frequency modes table.
+    std::vector<uint32_t>& freqTable() { return _freqTable; }
+    /// Returns the currently active frequency table entry.
+    size_t activeFreqEntry() const { return _activeFreqEntry; }
     /// Returns the counter period.
     Tick period() const { return _period; }
 
-    void setKernelControl(uint32_t val) { _regCntkctl = val; }
-    uint32_t getKernelControl() { return _regCntkctl; }
+    /// Enables the counter after a CNTCR.EN == 1
+    void enable();
+    /// Disables the counter after a CNTCR.EN == 0
+    void disable();
+
+    /// Schedules a counter frequency update after a CNTCR.FCREQ == 1
+    /// This complies with frequency transitions as per the architecture
+    /// @param new_freq_entry Index in CNTFID of the new freq
+    void freqUpdateSchedule(size_t new_freq_entry);
 
-    void setHypControl(uint32_t val) { _regCnthctl = val; }
-    uint32_t getHypControl() { return _regCnthctl; }
+    /// Sets the value explicitly from writes to CNTCR.CNTCV
+    void setValue(uint64_t new_value);
+
+    /// Called from System Counter Listeners to register
+    void registerListener(SystemCounterListener *listener);
+
+    /// Returns the tick at which a certain counter value is reached
+    Tick whenValue(uint64_t target_val);
+    Tick whenValue(uint64_t cur_val, uint64_t target_val) const;
 
     void serialize(CheckpointOut &cp) const override;
     void unserialize(CheckpointIn &cp) override;
@@ -108,10 +147,25 @@ class SystemCounter : public Serializable
   private:
     // Disable copying
     SystemCounter(const SystemCounter &c);
+
+    /// Frequency update event handling
+    EventFunctionWrapper _freqUpdateEvent;
+    size_t _nextFreqEntry;
+    /// Callback for the frequency update
+    void freqUpdateCallback();
+
+    /// Updates the counter value.
+    void updateValue(void);
+
+    /// Updates the update tick, normalizes to the lower cycle start tick
+    void updateTick(void);
+
+    /// Notifies counting speed changes to listeners
+    void notifyListeners(void) const;
 };
 
 /// Per-CPU architected timer.
-class ArchTimer : public Serializable, public Drainable
+class ArchTimer : public SystemCounterListener, public Drainable
 {
   protected:
     /// Control register.
@@ -178,6 +232,11 @@ class ArchTimer : public Serializable, public Drainable
 
     /// Returns the value of the counter which this timer relies on.
     uint64_t value() const;
+    Tick whenValue(uint64_t target_val) {
+        return _systemCounter.whenValue(value(), target_val);
+    }
+
+    void notify(void) override;
 
     // Serializable
     void serialize(CheckpointOut &cp) const override;
@@ -214,12 +273,12 @@ class ArchTimerKvm : public ArchTimer
     }
 };
 
-class GenericTimer : public ClockedObject
+class GenericTimer : public SimObject
 {
   public:
     const GenericTimerParams * params() const;
 
-    GenericTimer(GenericTimerParams *p);
+    GenericTimer(GenericTimerParams *const p);
 
     void serialize(CheckpointOut &cp) const override;
     void unserialize(CheckpointIn &cp) override;
@@ -229,11 +288,15 @@ class GenericTimer : public ClockedObject
     RegVal readMiscReg(int misc_reg, unsigned cpu);
 
   protected:
-    struct CoreTimers {
-        CoreTimers(GenericTimer &parent, ArmSystem &system, unsigned cpu,
+    class CoreTimers : public SystemCounterListener
+    {
+      public:
+        CoreTimers(GenericTimer &_parent, ArmSystem &system, unsigned cpu,
                    ArmInterruptPin *_irqPhysS, ArmInterruptPin *_irqPhysNS,
                    ArmInterruptPin *_irqVirt, ArmInterruptPin *_irqHyp)
-            : irqPhysS(_irqPhysS),
+            : parent(_parent),
+              threadContext(system.getThreadContext(cpu)),
+              irqPhysS(_irqPhysS),
               irqPhysNS(_irqPhysNS),
               irqVirt(_irqVirt),
               irqHyp(_irqHyp),
@@ -250,8 +313,33 @@ class GenericTimer : public ClockedObject
                    _irqVirt),
               hyp(csprintf("%s.hyp_timer%d", parent.name(), cpu),
                    system, parent, parent.systemCounter,
-                   _irqHyp)
-        {}
+                   _irqHyp),
+              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
+              }
+        {
+            cntfrq = 0x01800000;
+        }
+
+        /// System counter frequency as visible from this core
+        uint32_t cntfrq;
+
+        /// Kernel control register
+        CNTKCTL cntkctl;
+
+        /// Hypervisor control register
+        CNTHCTL cnthctl;
+
+        /// Generic Timer parent reference
+        GenericTimer &parent;
+
+        /// Thread (HW) context associated to this PE implementation
+        ThreadContext *threadContext;
 
         ArmInterruptPin const *irqPhysS;
         ArmInterruptPin const *irqPhysNS;
@@ -263,6 +351,39 @@ class GenericTimer : public ClockedObject
         ArchTimerKvm virt;
         ArchTimerKvm hyp;
 
+        // Event Stream. Events are generated based on a configurable
+        // transitionBit over the counter value. transitionTo indicates
+        // the transition direction (0->1 or 1->0)
+        struct EventStream
+        {
+            EventFunctionWrapper event;
+            uint8_t transitionTo;
+            uint8_t transitionBit;
+
+            uint64_t
+            eventTargetValue(uint64_t val) const
+            {
+                uint64_t bit_val = bits(val, transitionBit);
+                uint64_t ret_val = mbits(val, 63, transitionBit);
+                uint64_t incr_val = 1 << transitionBit;
+                if (bit_val == transitionTo)
+                    incr_val *= 2;
+                return ret_val + incr_val;
+            }
+        };
+
+        EventStream physEvStream;
+        EventStream virtEvStream;
+        void physEventStreamCallback();
+        void virtEventStreamCallback();
+        void eventStreamCallback() const;
+        void schedNextEvent(EventStream &ev_stream, ArchTimer &timer);
+
+        void notify(void) override;
+
+        void serialize(CheckpointOut &cp) const override;
+        void unserialize(CheckpointIn &cp) override;
+
       private:
         // Disable copying
         CoreTimers(const CoreTimers &c);
@@ -271,8 +392,8 @@ class GenericTimer : public ClockedObject
     CoreTimers &getTimers(int cpu_id);
     void createTimers(unsigned cpus);
 
-    /// System counter.
-    SystemCounter systemCounter;
+    /// System counter reference.
+    SystemCounter &systemCounter;
 
     /// Per-CPU physical architected timers.
     std::vector<std::unique_ptr<CoreTimers>> timers;
@@ -280,6 +401,9 @@ class GenericTimer : public ClockedObject
   protected: // Configuration
     /// ARM system containing this timer
     ArmSystem &system;
+
+    void handleStream(CoreTimers::EventStream *ev_stream,
+        ArchTimer *timer, RegVal old_cnt_ctl, RegVal cnt_ctl);
 };
 
 class GenericTimerISA : public ArmISA::BaseISADevice
@@ -296,61 +420,190 @@ class GenericTimerISA : public ArmISA::BaseISADevice
     unsigned cpu;
 };
 
-class GenericTimerMem : public PioDevice
+class GenericTimerFrame : public PioDevice
 {
   public:
-    GenericTimerMem(GenericTimerMemParams *p);
+    GenericTimerFrame(GenericTimerFrameParams *const p);
 
     void serialize(CheckpointOut &cp) const override;
     void unserialize(CheckpointIn &cp) override;
 
-  public: // PioDevice
-    AddrRangeList getAddrRanges() const override { return addrRanges; }
+    /// Indicates if this frame implements a virtual timer
+    bool hasVirtualTimer() const;
+
+    /// Returns the virtual offset for this frame if a virtual timer is
+    /// implemented
+    uint64_t getVirtOffset() const;
+
+    /// Sets the virtual offset for this frame's virtual timer after
+    /// a write to CNTVOFF
+    void setVirtOffset(uint64_t new_offset);
+
+    /// Indicates if this frame implements a second EL0 view
+    bool hasEl0View() const;
+
+    /// Returns the access bits for this frame
+    uint8_t getAccessBits() const;
+
+    /// Updates the access bits after a write to CNTCTLBase.CNTACR
+    void setAccessBits(uint8_t data);
+
+    /// Indicates if non-secure accesses are allowed to this frame
+    bool hasNonSecureAccess() const;
+
+    /// Allows non-secure accesses after an enabling write to
+    /// CNTCTLBase.CNTNSAR
+    void setNonSecureAccess();
+
+    /// Indicates if CNTVOFF is readable for this frame
+    bool hasReadableVoff() const;
+
+  protected:
+    AddrRangeList getAddrRanges() const override;
     Tick read(PacketPtr pkt) override;
     Tick write(PacketPtr pkt) override;
 
-  protected:
-    uint64_t ctrlRead(Addr addr, size_t size) const;
-    void ctrlWrite(Addr addr, size_t size, uint64_t value);
-
-    uint64_t timerRead(Addr addr, size_t size) const;
-    void timerWrite(Addr addr, size_t size, uint64_t value);
-
-  protected: // Registers
-    static const Addr CTRL_CNTFRQ          = 0x000;
-    static const Addr CTRL_CNTNSAR         = 0x004;
-    static const Addr CTRL_CNTTIDR         = 0x008;
-    static const Addr CTRL_CNTACR_BASE     = 0x040;
-    static const Addr CTRL_CNTVOFF_LO_BASE = 0x080;
-    static const Addr CTRL_CNTVOFF_HI_BASE = 0x084;
-
-    static const Addr TIMER_CNTPCT_LO    = 0x000;
-    static const Addr TIMER_CNTPCT_HI    = 0x004;
-    static const Addr TIMER_CNTVCT_LO    = 0x008;
-    static const Addr TIMER_CNTVCT_HI    = 0x00C;
-    static const Addr TIMER_CNTFRQ       = 0x010;
-    static const Addr TIMER_CNTEL0ACR    = 0x014;
-    static const Addr TIMER_CNTVOFF_LO   = 0x018;
-    static const Addr TIMER_CNTVOFF_HI   = 0x01C;
-    static const Addr TIMER_CNTP_CVAL_LO = 0x020;
-    static const Addr TIMER_CNTP_CVAL_HI = 0x024;
-    static const Addr TIMER_CNTP_TVAL    = 0x028;
-    static const Addr TIMER_CNTP_CTL     = 0x02C;
-    static const Addr TIMER_CNTV_CVAL_LO = 0x030;
-    static const Addr TIMER_CNTV_CVAL_HI = 0x034;
-    static const Addr TIMER_CNTV_TVAL    = 0x038;
-    static const Addr TIMER_CNTV_CTL     = 0x03C;
-
-  protected: // Params
-    const AddrRange ctrlRange;
+  private:
+    /// CNTBase/CNTEL0Base (Memory-mapped timer frame)
+    uint64_t timerRead(Addr addr, size_t size, bool is_sec, bool to_el0) const;
+    void timerWrite(Addr addr, size_t size, uint64_t data, bool is_sec,
+                    bool to_el0);
     const AddrRange timerRange;
-    const AddrRangeList addrRanges;
-
-  protected:
-    /// System counter.
-    SystemCounter systemCounter;
+    AddrRange timerEl0Range;
+
+    static const Addr TIMER_CNTPCT_LO          = 0x00;
+    static const Addr TIMER_CNTPCT_HI          = 0x04;
+    static const Addr TIMER_CNTVCT_LO          = 0x08;
+    static const Addr TIMER_CNTVCT_HI          = 0x0c;
+    static const Addr TIMER_CNTFRQ             = 0x10;
+    static const Addr TIMER_CNTEL0ACR          = 0x14;
+    static const Addr TIMER_CNTVOFF_LO         = 0x18;
+    static const Addr TIMER_CNTVOFF_HI         = 0x1c;
+    static const Addr TIMER_CNTP_CVAL_LO       = 0x20;
+    static const Addr TIMER_CNTP_CVAL_HI       = 0x24;
+    static const Addr TIMER_CNTP_TVAL          = 0x28;
+    static const Addr TIMER_CNTP_CTL           = 0x2c;
+    static const Addr TIMER_CNTV_CVAL_LO       = 0x30;
+    static const Addr TIMER_CNTV_CVAL_HI       = 0x34;
+    static const Addr TIMER_CNTV_TVAL          = 0x38;
+    static const Addr TIMER_CNTV_CTL           = 0x3c;
+
+    /// All MMIO ranges GenericTimerFrame responds to
+    AddrRangeList addrRanges;
+
+    /// System counter reference.
+    SystemCounter &systemCounter;
+
+    /// Physical and virtual timers
     ArchTimer physTimer;
     ArchTimer virtTimer;
+
+    /// Reports access properties of the CNTBase register frame elements
+    BitUnion8(AccessBits)
+        Bitfield<5> rwpt;
+        Bitfield<4> rwvt;
+        Bitfield<3> rvoff;
+        Bitfield<2> rfrq;
+        Bitfield<1> rvct;
+        Bitfield<0> rpct;
+    EndBitUnion(AccessBits)
+    AccessBits accessBits;
+
+    // Reports access properties of the CNTEL0Base register frame elements
+    BitUnion16(AccessBitsEl0)
+        Bitfield<9> pten;
+        Bitfield<8> vten;
+        Bitfield<1> vcten;
+        Bitfield<0> pcten;
+    EndBitUnion(AccessBitsEl0)
+    AccessBitsEl0 accessBitsEl0;
+
+    /// Reports whether non-secure accesses are allowed to this frame
+    bool nonSecureAccess;
+
+    ArmSystem &system;
+};
+
+class GenericTimerMem : public PioDevice
+{
+  public:
+    GenericTimerMem(GenericTimerMemParams *const p);
+
+    /// Validates a Generic Timer register frame address range
+    /// @param base_addr Range of the register frame
+    static void validateFrameRange(const AddrRange &range);
+
+    /// Validates an MMIO access permissions
+    /// @param sys System reference where the acces is being made
+    /// @param is_sec If the access is to secure memory
+    static bool validateAccessPerm(ArmSystem &sys, bool is_sec);
+
+  protected:
+    AddrRangeList getAddrRanges() const override;
+    Tick read(PacketPtr pkt) override;
+    Tick write(PacketPtr pkt) override;
+
+  private:
+    /// CNTControlBase (System counter control frame)
+    uint64_t counterCtrlRead(Addr addr, size_t size, bool is_sec) const;
+    void counterCtrlWrite(Addr addr, size_t size, uint64_t data, bool is_sec);
+    const AddrRange counterCtrlRange;
+
+    BitUnion32(CNTCR)
+        Bitfield<17,8> fcreq;
+        Bitfield<2> scen;
+        Bitfield<1> hdbg;
+        Bitfield<0> en;
+    EndBitUnion(CNTCR)
+
+    BitUnion32(CNTSR)
+        Bitfield<31,8> fcack;
+    EndBitUnion(CNTSR)
+
+    static const Addr COUNTER_CTRL_CNTCR       = 0x00;
+    static const Addr COUNTER_CTRL_CNTSR       = 0x04;
+    static const Addr COUNTER_CTRL_CNTCV_LO    = 0x08;
+    static const Addr COUNTER_CTRL_CNTCV_HI    = 0x0c;
+    static const Addr COUNTER_CTRL_CNTSCR      = 0x10;
+    static const Addr COUNTER_CTRL_CNTID       = 0x1c;
+    static const Addr COUNTER_CTRL_CNTFID      = 0x20;
+
+    /// CNTReadBase (System counter status frame)
+    uint64_t counterStatusRead(Addr addr, size_t size) const;
+    void counterStatusWrite(Addr addr, size_t size, uint64_t data);
+    const AddrRange counterStatusRange;
+
+    static const Addr COUNTER_STATUS_CNTCV_LO  = 0x00;
+    static const Addr COUNTER_STATUS_CNTCV_HI  = 0x04;
+
+    /// CNTCTLBase (Memory-mapped timer global control frame)
+    uint64_t timerCtrlRead(Addr addr, size_t size, bool is_sec) const;
+    void timerCtrlWrite(Addr addr, size_t size, uint64_t data, bool is_sec);
+    const AddrRange timerCtrlRange;
+
+    /// ID register for reporting features of implemented timer frames
+    uint32_t cnttidr;
+
+    static const Addr TIMER_CTRL_CNTFRQ        = 0x00;
+    static const Addr TIMER_CTRL_CNTNSAR       = 0x04;
+    static const Addr TIMER_CTRL_CNTTIDR       = 0x08;
+    static const Addr TIMER_CTRL_CNTACR        = 0x40;
+    static const Addr TIMER_CTRL_CNTVOFF_LO    = 0x80;
+    static const Addr TIMER_CTRL_CNTVOFF_HI    = 0x84;
+
+    /// All MMIO ranges GenericTimerMem responds to
+    const AddrRangeList addrRanges;
+
+    /// System counter reference.
+    SystemCounter &systemCounter;
+
+    /// Maximum architectural number of memory-mapped timer frames
+    static constexpr size_t MAX_TIMER_FRAMES = 8;
+
+    /// Timer frame references
+    std::vector<GenericTimerFrame *> frames;
+
+    ArmSystem &system;
 };
 
 #endif // __DEV_ARM_GENERIC_TIMER_HH__
diff --git a/src/dev/arm/generic_timer_miscregs_types.hh b/src/dev/arm/generic_timer_miscregs_types.hh
new file mode 100644 (file)
index 0000000..98c9f2f
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020 ARM Limited
+ * All rights reserved.
+ *
+ * The license below extends only to copyright in the software and shall
+ * not be construed as granting a license to any other intellectual
+ * property including but not limited to intellectual property relating
+ * to a hardware implementation of the functionality of the software
+ * licensed hereunder.  You may use the software subject to the license
+ * terms below provided that you ensure that this notice is replicated
+ * unmodified and in its entirety in all distributions of the software,
+ * modified or unmodified, in source code or in binary form.
+ *
+ * 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.
+ */
+
+#ifndef __DEV_ARM_GENERIC_TIMER_MISCREGS_TYPES_HH__
+#define __DEV_ARM_GENERIC_TIMER_MISCREGS_TYPES_HH__
+
+#include "base/bitunion.hh"
+
+namespace ArmISA
+{
+    BitUnion64(CNTKCTL)
+        Bitfield<9> el0pten;
+        Bitfield<8> el0vten;
+        Bitfield<7,4> evnti;
+        Bitfield<3> evntdir;
+        Bitfield<2> evnten;
+        Bitfield<1> el0vcten;
+        Bitfield<0> el0pcten;
+    EndBitUnion(CNTKCTL)
+
+    BitUnion64(CNTHCTL)
+        Bitfield<7,4> evnti;
+        Bitfield<3> evntdir;
+        Bitfield<2> evnten;
+        Bitfield<1> el1pcen;
+        Bitfield<0> el1pcten;
+    EndBitUnion(CNTHCTL)
+    // If Armv8.1-VHE && HCR_EL2.E2H == 1
+    BitUnion64(CNTHCTL_E2H)
+        Bitfield<11> el1pten;
+        Bitfield<10> el1pcten;
+        Bitfield<9> el0pten;
+        Bitfield<8> el0vten;
+        Bitfield<7,4> evnti;
+        Bitfield<3> evntdir;
+        Bitfield<2> evnten;
+        Bitfield<1> el0vcten;
+        Bitfield<0> el0pcten;
+    EndBitUnion(CNTHCTL_E2H)
+}
+
+#endif // __DEV_ARM_GENERIC_TIMER_MISCREGS_TYPES_HH__