blob: 1d5e8bb00fab76365535813c62d3f7ff0debb986 [file] [log] [blame]
/*
* Copyright (c) 2013, 2015, 2017 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.
*
* Authors: Giacomo Gabrielli
* Andreas Sandberg
*/
#include "dev/arm/generic_timer.hh"
#include "arch/arm/system.hh"
#include "debug/Timer.hh"
#include "dev/arm/base_gic.hh"
#include "mem/packet_access.hh"
#include "params/GenericTimer.hh"
#include "params/GenericTimerMem.hh"
SystemCounter::SystemCounter()
: _freq(0), _period(0), _resetTick(0), _regCntkctl(0)
{
setFreq(0x01800000);
}
void
SystemCounter::setFreq(uint32_t freq)
{
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");
}
_freq = freq;
_period = (1.0 / freq) * SimClock::Frequency;
_resetTick = curTick();
}
void
SystemCounter::serialize(CheckpointOut &cp) const
{
SERIALIZE_SCALAR(_regCntkctl);
SERIALIZE_SCALAR(_regCnthctl);
SERIALIZE_SCALAR(_freq);
SERIALIZE_SCALAR(_period);
SERIALIZE_SCALAR(_resetTick);
}
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;
UNSERIALIZE_SCALAR(_freq);
UNSERIALIZE_SCALAR(_period);
UNSERIALIZE_SCALAR(_resetTick);
}
ArchTimer::ArchTimer(const std::string &name,
SimObject &parent,
SystemCounter &sysctr,
const Interrupt &interrupt)
: _name(name), _parent(parent), _systemCounter(sysctr),
_interrupt(interrupt),
_control(0), _counterLimit(0), _offset(0),
_counterLimitReachedEvent([this]{ counterLimitReached(); }, name)
{
}
void
ArchTimer::counterLimitReached()
{
_control.istatus = 1;
if (!_control.enable)
return;
DPRINTF(Timer, "Counter limit reached\n");
if (!_control.imask) {
if (scheduleEvents()) {
DPRINTF(Timer, "Causing interrupt\n");
_interrupt.send();
} else {
DPRINTF(Timer, "Kvm mode; skipping simulated interrupt\n");
}
}
}
void
ArchTimer::updateCounter()
{
if (_counterLimitReachedEvent.scheduled())
_parent.deschedule(_counterLimitReachedEvent);
if (value() >= _counterLimit) {
counterLimitReached();
} else {
_control.istatus = 0;
if (scheduleEvents()) {
const auto period(_systemCounter.period());
_parent.schedule(_counterLimitReachedEvent,
curTick() + (_counterLimit - value()) * period);
}
}
}
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 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();
}
}
_control.enable = new_ctl.enable;
_control.imask = new_ctl.imask;
}
void
ArchTimer::setOffset(uint64_t val)
{
_offset = val;
updateCounter();
}
uint64_t
ArchTimer::value() const
{
return _systemCounter.value() - _offset;
}
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();
}
void
ArchTimer::Interrupt::send()
{
if (_ppi) {
_gic.sendPPInt(_irq, _cpu);
} else {
_gic.sendInt(_irq);
}
}
void
ArchTimer::Interrupt::clear()
{
if (_ppi) {
_gic.clearPPInt(_irq, _cpu);
} else {
_gic.clearInt(_irq);
}
}
GenericTimer::GenericTimer(GenericTimerParams *p)
: ClockedObject(p),
system(*p->system),
gic(p->gic),
irqPhysS(p->int_phys_s),
irqPhysNS(p->int_phys_ns),
irqVirt(p->int_virt),
irqHyp(p->int_hyp)
{
fatal_if(!p->system, "No system specified, can't instantiate timer.\n");
system.setGenericTimer(this);
}
void
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));
}
}
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.
static const unsigned OLD_CPU_MAX = 8;
unsigned cpu_count;
if (!UNSERIALIZE_OPT_SCALAR(cpu_count)) {
warn("Checkpoint does not contain CPU count, assuming %i CPUs\n",
OLD_CPU_MAX);
cpu_count = OLD_CPU_MAX;
}
for (int i = 0; i < cpu_count; ++i) {
CoreTimers &core(getTimers(i));
// 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));
}
}
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);
const unsigned old_cpu_count(timers.size());
timers.resize(cpus);
for (unsigned i = old_cpu_count; i < cpus; ++i) {
timers[i].reset(
new CoreTimers(*this, system, i,
irqPhysS, irqPhysNS, irqVirt, irqHyp));
}
}
void
GenericTimer::setMiscReg(int reg, unsigned cpu, MiscReg val)
{
CoreTimers &core(getTimers(cpu));
switch (reg) {
case MISCREG_CNTFRQ:
case MISCREG_CNTFRQ_EL0:
systemCounter.setFreq(val);
return;
case MISCREG_CNTKCTL:
case MISCREG_CNTKCTL_EL1:
systemCounter.setKernelControl(val);
return;
case MISCREG_CNTHCTL:
case MISCREG_CNTHCTL_EL2:
systemCounter.setHypControl(val);
return;
// Physical timer (NS)
case MISCREG_CNTP_CVAL_NS:
case MISCREG_CNTP_CVAL_EL0:
core.physNS.setCompareValue(val);
return;
case MISCREG_CNTP_TVAL_NS:
case MISCREG_CNTP_TVAL_EL0:
core.physNS.setTimerValue(val);
return;
case MISCREG_CNTP_CTL_NS:
case MISCREG_CNTP_CTL_EL0:
core.physNS.setControl(val);
return;
// Count registers
case MISCREG_CNTPCT:
case MISCREG_CNTPCT_EL0:
case MISCREG_CNTVCT:
case MISCREG_CNTVCT_EL0:
warn("Ignoring write to read only count register: %s\n",
miscRegName[reg]);
return;
// Virtual timer
case MISCREG_CNTVOFF:
case MISCREG_CNTVOFF_EL2:
core.virt.setOffset(val);
return;
case MISCREG_CNTV_CVAL:
case MISCREG_CNTV_CVAL_EL0:
core.virt.setCompareValue(val);
return;
case MISCREG_CNTV_TVAL:
case MISCREG_CNTV_TVAL_EL0:
core.virt.setTimerValue(val);
return;
case MISCREG_CNTV_CTL:
case MISCREG_CNTV_CTL_EL0:
core.virt.setControl(val);
return;
// Physical timer (S)
case MISCREG_CNTP_CTL_S:
case MISCREG_CNTPS_CTL_EL1:
core.physS.setControl(val);
return;
case MISCREG_CNTP_CVAL_S:
case MISCREG_CNTPS_CVAL_EL1:
core.physS.setCompareValue(val);
return;
case MISCREG_CNTP_TVAL_S:
case MISCREG_CNTPS_TVAL_EL1:
core.physS.setTimerValue(val);
return;
// Hyp phys. timer, non-secure
case MISCREG_CNTHP_CTL:
case MISCREG_CNTHP_CTL_EL2:
core.hyp.setControl(val);
return;
case MISCREG_CNTHP_CVAL:
case MISCREG_CNTHP_CVAL_EL2:
core.hyp.setCompareValue(val);
return;
case MISCREG_CNTHP_TVAL:
case MISCREG_CNTHP_TVAL_EL2:
core.hyp.setTimerValue(val);
return;
default:
warn("Writing to unknown register: %s\n", miscRegName[reg]);
return;
}
}
MiscReg
GenericTimer::readMiscReg(int reg, unsigned cpu)
{
CoreTimers &core(getTimers(cpu));
switch (reg) {
case MISCREG_CNTFRQ:
case MISCREG_CNTFRQ_EL0:
return systemCounter.freq();
case MISCREG_CNTKCTL:
case MISCREG_CNTKCTL_EL1:
return systemCounter.getKernelControl();
case MISCREG_CNTHCTL:
case MISCREG_CNTHCTL_EL2:
return systemCounter.getHypControl();
// Physical timer
case MISCREG_CNTP_CVAL_NS:
case MISCREG_CNTP_CVAL_EL0:
return core.physNS.compareValue();
case MISCREG_CNTP_TVAL_NS:
case MISCREG_CNTP_TVAL_EL0:
return core.physNS.timerValue();
case MISCREG_CNTP_CTL_EL0:
case MISCREG_CNTP_CTL_NS:
return core.physNS.control();
case MISCREG_CNTPCT:
case MISCREG_CNTPCT_EL0:
return core.physNS.value();
// Virtual timer
case MISCREG_CNTVCT:
case MISCREG_CNTVCT_EL0:
return core.virt.value();
case MISCREG_CNTVOFF:
case MISCREG_CNTVOFF_EL2:
return core.virt.offset();
case MISCREG_CNTV_CVAL:
case MISCREG_CNTV_CVAL_EL0:
return core.virt.compareValue();
case MISCREG_CNTV_TVAL:
case MISCREG_CNTV_TVAL_EL0:
return core.virt.timerValue();
case MISCREG_CNTV_CTL:
case MISCREG_CNTV_CTL_EL0:
return core.virt.control();
// PL1 phys. timer, secure
case MISCREG_CNTP_CTL_S:
case MISCREG_CNTPS_CTL_EL1:
return core.physS.control();
case MISCREG_CNTP_CVAL_S:
case MISCREG_CNTPS_CVAL_EL1:
return core.physS.compareValue();
case MISCREG_CNTP_TVAL_S:
case MISCREG_CNTPS_TVAL_EL1:
return core.physS.timerValue();
// HYP phys. timer (NS)
case MISCREG_CNTHP_CTL:
case MISCREG_CNTHP_CTL_EL2:
return core.hyp.control();
case MISCREG_CNTHP_CVAL:
case MISCREG_CNTHP_CVAL_EL2:
return core.hyp.compareValue();
case MISCREG_CNTHP_TVAL:
case MISCREG_CNTHP_TVAL_EL2:
return core.hyp.timerValue();
default:
warn("Reading from unknown register: %s\n", miscRegName[reg]);
return 0;
}
}
GenericTimerMem::GenericTimerMem(GenericTimerMemParams *p)
: PioDevice(p),
ctrlRange(RangeSize(p->base, TheISA::PageBytes)),
timerRange(RangeSize(p->base + TheISA::PageBytes, TheISA::PageBytes)),
addrRanges{ctrlRange, timerRange},
systemCounter(),
physTimer(csprintf("%s.phys_timer0", name()),
*this, systemCounter,
ArchTimer::Interrupt(*p->gic, p->int_phys)),
virtTimer(csprintf("%s.virt_timer0", name()),
*this, systemCounter,
ArchTimer::Interrupt(*p->gic, p->int_virt))
{
}
void
GenericTimerMem::serialize(CheckpointOut &cp) const
{
paramOut(cp, "timer_count", 1);
systemCounter.serializeSection(cp, "sys_counter");
physTimer.serializeSection(cp, "phys_timer0");
virtTimer.serializeSection(cp, "virt_timer0");
}
void
GenericTimerMem::unserialize(CheckpointIn &cp)
{
systemCounter.unserializeSection(cp, "sys_counter");
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");
physTimer.unserializeSection(cp, "phys_timer0");
virtTimer.unserializeSection(cp, "virt_timer0");
}
Tick
GenericTimerMem::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);
} else {
panic("Invalid address: 0x%x\n", addr);
}
DPRINTF(Timer, "Read 0x%x <- 0x%x(%i)\n", value, addr, size);
if (size == 8) {
pkt->set<uint64_t>(value);
} else if (size == 4) {
pkt->set<uint32_t>(value);
} else {
panic("Unexpected access size: %i\n", size);
}
return 0;
}
Tick
GenericTimerMem::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->get<uint64_t>() : pkt->get<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);
} else {
panic("Invalid address: 0x%x\n", addr);
}
pkt->makeResponse();
return 0;
}
uint64_t
GenericTimerMem::ctrlRead(Addr addr, size_t size) const
{
if (size == 4) {
switch (addr) {
case CTRL_CNTFRQ:
return systemCounter.freq();
case CTRL_CNTTIDR:
return 0x3; // Frame 0 implemented with virtual timers
case CTRL_CNTNSAR:
case CTRL_CNTACR_BASE:
warn("Reading from unimplemented control register (0x%x)\n", addr);
return 0;
case CTRL_CNTVOFF_LO_BASE:
return virtTimer.offset();
case CTRL_CNTVOFF_HI_BASE:
return virtTimer.offset() >> 32;
default:
warn("Unexpected address (0x%x:%i), assuming RAZ\n", addr, size);
return 0;
}
} else if (size == 8) {
switch (addr) {
case CTRL_CNTVOFF_LO_BASE:
return virtTimer.offset();
default:
warn("Unexpected address (0x%x:%i), assuming RAZ\n", addr, size);
return 0;
}
} else {
panic("Invalid access size: %i\n", size);
}
}
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 CTRL_CNTVOFF_LO_BASE:
virtTimer.setOffset(
insertBits(virtTimer.offset(), 31, 0, value));
return;
case CTRL_CNTVOFF_HI_BASE:
virtTimer.setOffset(
insertBits(virtTimer.offset(), 63, 32, value));
return;
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;
default:
warn("Ignoring write to unexpected address (0x%x:%i)\n",
addr, size);
return;
}
} else {
panic("Invalid access size: %i\n", size);
}
}
uint64_t
GenericTimerMem::timerRead(Addr addr, size_t size) const
{
if (size == 4) {
switch (addr) {
case TIMER_CNTPCT_LO:
return physTimer.value();
case TIMER_CNTPCT_HI:
return physTimer.value() >> 32;
case TIMER_CNTVCT_LO:
return virtTimer.value();
case TIMER_CNTVCT_HI:
return virtTimer.value() >> 32;
case TIMER_CNTFRQ:
return systemCounter.freq();
case TIMER_CNTEL0ACR:
warn("Read from unimplemented timer register (0x%x)\n", addr);
return 0;
case CTRL_CNTVOFF_LO_BASE:
return virtTimer.offset();
case CTRL_CNTVOFF_HI_BASE:
return virtTimer.offset() >> 32;
case TIMER_CNTP_CVAL_LO:
return physTimer.compareValue();
case TIMER_CNTP_CVAL_HI:
return physTimer.compareValue() >> 32;
case TIMER_CNTP_TVAL:
return physTimer.timerValue();
case TIMER_CNTP_CTL:
return physTimer.control();
case TIMER_CNTV_CVAL_LO:
return virtTimer.compareValue();
case TIMER_CNTV_CVAL_HI:
return virtTimer.compareValue() >> 32;
case TIMER_CNTV_TVAL:
return virtTimer.timerValue();
case TIMER_CNTV_CTL:
return virtTimer.control();
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();
case TIMER_CNTVCT_LO:
return virtTimer.value();
case CTRL_CNTVOFF_LO_BASE:
return virtTimer.offset();
case TIMER_CNTP_CVAL_LO:
return physTimer.compareValue();
case TIMER_CNTV_CVAL_LO:
return virtTimer.compareValue();
default:
warn("Unexpected address (0x%x:%i), assuming RAZ\n", addr, size);
return 0;
}
} else {
panic("Invalid access size: %i\n", size);
}
}
void
GenericTimerMem::timerWrite(Addr addr, size_t size, uint64_t value)
{
if (size == 4) {
switch (addr) {
case TIMER_CNTEL0ACR:
warn("Unimplemented timer register (0x%x)\n", addr);
return;
case TIMER_CNTP_CVAL_LO:
physTimer.setCompareValue(
insertBits(physTimer.compareValue(), 31, 0, value));
return;
case TIMER_CNTP_CVAL_HI:
physTimer.setCompareValue(
insertBits(physTimer.compareValue(), 63, 32, value));
return;
case TIMER_CNTP_TVAL:
physTimer.setTimerValue(value);
return;
case TIMER_CNTP_CTL:
physTimer.setControl(value);
return;
case TIMER_CNTV_CVAL_LO:
virtTimer.setCompareValue(
insertBits(virtTimer.compareValue(), 31, 0, value));
return;
case TIMER_CNTV_CVAL_HI:
virtTimer.setCompareValue(
insertBits(virtTimer.compareValue(), 63, 32, value));
return;
case TIMER_CNTV_TVAL:
virtTimer.setTimerValue(value);
return;
case TIMER_CNTV_CTL:
virtTimer.setControl(value);
return;
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);
case TIMER_CNTV_CVAL_LO:
return virtTimer.setCompareValue(value);
default:
warn("Unexpected address (0x%x:%i), ignoring write\n", addr, size);
return;
}
} else {
panic("Invalid access size: %i\n", size);
}
}
GenericTimer *
GenericTimerParams::create()
{
return new GenericTimer(this);
}
GenericTimerMem *
GenericTimerMemParams::create()
{
return new GenericTimerMem(this);
}