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