/* * Copyright (c) 2011-2014, 2017-2018 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: Dam Sunwoo * Matt Horsnell * Andreas Sandberg * Jose Marinho */ #include "arch/arm/pmu.hh" #include "arch/arm/isa.hh" #include "arch/arm/utility.hh" #include "base/trace.hh" #include "cpu/base.hh" #include "debug/Checkpoint.hh" #include "debug/PMUVerbose.hh" #include "dev/arm/base_gic.hh" #include "dev/arm/generic_timer.hh" #include "params/ArmPMU.hh" namespace ArmISA { const RegVal PMU::reg_pmcr_wr_mask = 0x39; PMU::PMU(const ArmPMUParams *p) : SimObject(p), BaseISADevice(), reg_pmcnten(0), reg_pmcr(0), reg_pmselr(0), reg_pminten(0), reg_pmovsr(0), reg_pmceid0(0),reg_pmceid1(0), clock_remainder(0), maximumCounterCount(p->eventCounters), cycleCounter(*this, maximumCounterCount), cycleCounterEventId(p->cycleEventId), swIncrementEvent(nullptr), reg_pmcr_conf(0), interrupt(p->interrupt->get()) { DPRINTF(PMUVerbose, "Initializing the PMU.\n"); if (maximumCounterCount > 31) { fatal("The PMU can only accept 31 counters, %d counters requested.\n", maximumCounterCount); } warn_if(!interrupt, "ARM PMU: No interrupt specified, interrupt " \ "delivery disabled.\n"); /* Setup the performance counter ID registers */ reg_pmcr_conf.imp = 0x41; // ARM Ltd. reg_pmcr_conf.idcode = 0x00; reg_pmcr_conf.n = p->eventCounters; // Setup the hard-coded cycle counter, which is equivalent to // architected counter event type 0x11. cycleCounter.eventId = 0x11; } PMU::~PMU() { } void PMU::setThreadContext(ThreadContext *tc) { DPRINTF(PMUVerbose, "Assigning PMU to ContextID %i.\n", tc->contextId()); if (interrupt) interrupt->setThreadContext(tc); } void PMU::addSoftwareIncrementEvent(unsigned int id) { auto old_event = eventMap.find(id); DPRINTF(PMUVerbose, "PMU: Adding SW increment event with id '0x%x'\n", id); if (swIncrementEvent) { fatal_if(old_event == eventMap.end() || old_event->second != swIncrementEvent, "Trying to add a software increment event with multiple" "IDs. This is not supported.\n"); return; } fatal_if(old_event != eventMap.end(), "An event with id %d has " "been previously defined\n", id); swIncrementEvent = new SWIncrementEvent(); eventMap[id] = swIncrementEvent; registerEvent(id); } void PMU::addEventProbe(unsigned int id, SimObject *obj, const char *probe_name) { DPRINTF(PMUVerbose, "PMU: Adding Probe Driven event with id '0x%x'" "as probe %s:%s\n",id, obj->name(), probe_name); RegularEvent *event = nullptr; auto event_entry = eventMap.find(id); if (event_entry == eventMap.end()) { event = new RegularEvent(); eventMap[id] = event; } else { event = dynamic_cast(event_entry->second); if (!event) { fatal("Event with id %d is not probe driven\n", id); } } event->addMicroarchitectureProbe(obj, probe_name); registerEvent(id); } void PMU::registerEvent(uint32_t id) { // Flag the event as available in the corresponding PMCEID register if it // is an architected event. if (id < 0x20) { reg_pmceid0 |= ((uint64_t)1) << id; } else if (id > 0x20 && id < 0x40) { reg_pmceid1 |= ((uint64_t)1) << (id - 0x20); } else if (id >= 0x4000 && id < 0x4020) { reg_pmceid0 |= ((uint64_t)1) << (id - 0x4000 + 32); } else if (id >= 0x4020 && id < 0x4040) { reg_pmceid1 |= ((uint64_t)1) << (id - 0x4020 + 32); } } void PMU::drainResume() { // Re-attach enabled counters after a resume in case they changed. updateAllCounters(); } void PMU::regProbeListeners() { // at this stage all probe configurations are done // counters can be configured for (uint32_t index = 0; index < maximumCounterCount-1; index++) { counters.emplace_back(*this, index); } PMUEvent *event = getEvent(cycleCounterEventId); panic_if(!event, "core cycle event is not present\n"); cycleCounter.enabled = true; cycleCounter.attach(event); } void PMU::setMiscReg(int misc_reg, RegVal val) { DPRINTF(PMUVerbose, "setMiscReg(%s, 0x%x)\n", miscRegName[unflattenMiscReg(misc_reg)], val); switch (unflattenMiscReg(misc_reg)) { case MISCREG_PMCR_EL0: case MISCREG_PMCR: setControlReg(val); return; case MISCREG_PMCNTENSET_EL0: case MISCREG_PMCNTENSET: reg_pmcnten |= val; updateAllCounters(); return; case MISCREG_PMCNTENCLR_EL0: case MISCREG_PMCNTENCLR: reg_pmcnten &= ~val; updateAllCounters(); return; case MISCREG_PMOVSCLR_EL0: case MISCREG_PMOVSR: setOverflowStatus(reg_pmovsr & ~val); return; case MISCREG_PMSWINC_EL0: case MISCREG_PMSWINC: if (swIncrementEvent) { swIncrementEvent->write(val); } return; case MISCREG_PMCCNTR_EL0: case MISCREG_PMCCNTR: cycleCounter.setValue(val); return; case MISCREG_PMSELR_EL0: case MISCREG_PMSELR: reg_pmselr = val; return; //TODO: implement MISCREF_PMCEID{2,3} case MISCREG_PMCEID0_EL0: case MISCREG_PMCEID0: case MISCREG_PMCEID1_EL0: case MISCREG_PMCEID1: // Ignore writes return; case MISCREG_PMEVTYPER0_EL0...MISCREG_PMEVTYPER5_EL0: setCounterTypeRegister(misc_reg - MISCREG_PMEVTYPER0_EL0, val); return; case MISCREG_PMCCFILTR: case MISCREG_PMCCFILTR_EL0: DPRINTF(PMUVerbose, "Setting PMCCFILTR: 0x%x\n", val); setCounterTypeRegister(PMCCNTR, val); return; case MISCREG_PMXEVTYPER_PMCCFILTR: case MISCREG_PMXEVTYPER_EL0: case MISCREG_PMXEVTYPER: DPRINTF(PMUVerbose, "Setting counter type: " "[PMSELR: 0x%x, PMSELER.sel: 0x%x, EVTYPER: 0x%x]\n", reg_pmselr, reg_pmselr.sel, val); setCounterTypeRegister(reg_pmselr.sel, val); return; case MISCREG_PMEVCNTR0_EL0...MISCREG_PMEVCNTR5_EL0: setCounterValue(misc_reg - MISCREG_PMEVCNTR0_EL0, val); return; case MISCREG_PMXEVCNTR_EL0: case MISCREG_PMXEVCNTR: setCounterValue(reg_pmselr.sel, val); return; case MISCREG_PMUSERENR_EL0: case MISCREG_PMUSERENR: // TODO break; case MISCREG_PMINTENSET_EL1: case MISCREG_PMINTENSET: reg_pminten |= val; return; case MISCREG_PMINTENCLR_EL1: case MISCREG_PMINTENCLR: reg_pminten &= ~val; return; case MISCREG_PMOVSSET_EL0: case MISCREG_PMOVSSET: setOverflowStatus(reg_pmovsr | val); return; default: panic("Unexpected PMU register: %i\n", miscRegName[misc_reg]); } warn("Not doing anything for write to miscreg %s\n", miscRegName[misc_reg]); } RegVal PMU::readMiscReg(int misc_reg) { RegVal val(readMiscRegInt(misc_reg)); DPRINTF(PMUVerbose, "readMiscReg(%s): 0x%x\n", miscRegName[unflattenMiscReg(misc_reg)], val); return val; } RegVal PMU::readMiscRegInt(int misc_reg) { misc_reg = unflattenMiscReg(misc_reg); switch (misc_reg) { case MISCREG_PMCR_EL0: case MISCREG_PMCR: return reg_pmcr_conf | (reg_pmcr & reg_pmcr_wr_mask); case MISCREG_PMCNTENSET_EL0: case MISCREG_PMCNTENCLR_EL0: case MISCREG_PMCNTENSET: case MISCREG_PMCNTENCLR: return reg_pmcnten; case MISCREG_PMOVSCLR_EL0: case MISCREG_PMOVSSET_EL0: case MISCREG_PMOVSR: // Overflow Status Register case MISCREG_PMOVSSET: return reg_pmovsr; case MISCREG_PMSWINC_EL0: case MISCREG_PMSWINC: // Software Increment Register (RAZ) return 0; case MISCREG_PMSELR: return reg_pmselr; case MISCREG_PMCEID0_EL0: return reg_pmceid0; case MISCREG_PMCEID1_EL0: return reg_pmceid1; //TODO: implement MISCREF_PMCEID{2,3} case MISCREG_PMCEID0: // Common Event ID register return reg_pmceid0 & 0xFFFFFFFF; case MISCREG_PMCEID1: // Common Event ID register return reg_pmceid1 & 0xFFFFFFFF; case MISCREG_PMCCNTR_EL0: return cycleCounter.getValue(); case MISCREG_PMCCNTR: return cycleCounter.getValue() & 0xFFFFFFFF; case MISCREG_PMEVTYPER0_EL0...MISCREG_PMEVTYPER5_EL0: return getCounterTypeRegister(misc_reg - MISCREG_PMEVTYPER0_EL0); case MISCREG_PMCCFILTR: case MISCREG_PMCCFILTR_EL0: return getCounterTypeRegister(PMCCNTR); case MISCREG_PMXEVTYPER_PMCCFILTR: case MISCREG_PMXEVTYPER_EL0: case MISCREG_PMXEVTYPER: return getCounterTypeRegister(reg_pmselr.sel); case MISCREG_PMEVCNTR0_EL0...MISCREG_PMEVCNTR5_EL0: { return getCounterValue(misc_reg - MISCREG_PMEVCNTR0_EL0) & 0xFFFFFFFF; } case MISCREG_PMXEVCNTR_EL0: case MISCREG_PMXEVCNTR: return getCounterValue(reg_pmselr.sel) & 0xFFFFFFFF; case MISCREG_PMUSERENR_EL0: case MISCREG_PMUSERENR: // TODO return 0; case MISCREG_PMINTENSET_EL1: case MISCREG_PMINTENCLR_EL1: case MISCREG_PMINTENSET: case MISCREG_PMINTENCLR: return reg_pminten; default: panic("Unexpected PMU register: %i\n", miscRegName[misc_reg]); } warn("Not doing anything for read from miscreg %s\n", miscRegName[misc_reg]); return 0; } void PMU::setControlReg(PMCR_t val) { DPRINTF(PMUVerbose, "Set Control Reg 0x%08x.\n", val); if (val.p) { DPRINTF(PMUVerbose, "PMU reset all events to zero.\n"); resetEventCounts(); } if (val.c) { DPRINTF(PMUVerbose, "PMU reset cycle counter to zero.\n"); cycleCounter.setValue(0); } // Reset the clock remainder if divide by 64-mode is toggled. if (reg_pmcr.d != val.d) clock_remainder = 0; reg_pmcr = val & reg_pmcr_wr_mask; updateAllCounters(); } void PMU::updateAllCounters() { const bool global_enable(reg_pmcr.e); for (int i = 0; i < counters.size(); ++i) { CounterState &ctr(counters[i]); const bool enable(global_enable && (reg_pmcnten & (1 << i))); if (ctr.enabled != enable) { ctr.enabled = enable; updateCounter(ctr); } } const bool ccntr_enable(global_enable && (reg_pmcnten & (1 << PMCCNTR))); if (cycleCounter.enabled != ccntr_enable) { cycleCounter.enabled = ccntr_enable; updateCounter(cycleCounter); } } void PMU::PMUEvent::attachEvent(PMU::CounterState *user) { if (userCounters.empty()) { enable(); } userCounters.insert(user); updateAttachedCounters(); } void PMU::PMUEvent::increment(const uint64_t val) { for (auto& counter: userCounters) { counter->add(val); } } void PMU::PMUEvent::detachEvent(PMU::CounterState *user) { userCounters.erase(user); if (userCounters.empty()) { disable(); } } void PMU::RegularEvent::RegularProbe::notify(const uint64_t &val) { parentEvent->increment(val); } void PMU::RegularEvent::enable() { for (auto& subEvents: microArchitectureEventSet) { attachedProbePointList.emplace_back( new RegularProbe(this, subEvents.first, subEvents.second)); } } void PMU::RegularEvent::disable() { attachedProbePointList.clear(); } bool PMU::CounterState::isFiltered() const { assert(pmu.isa); const PMEVTYPER_t filter(this->filter); const SCR scr(pmu.isa->readMiscRegNoEffect(MISCREG_SCR)); const CPSR cpsr(pmu.isa->readMiscRegNoEffect(MISCREG_CPSR)); const ExceptionLevel el(opModeToEL((OperatingMode)(uint8_t)cpsr.mode)); const bool secure(inSecureState(scr, cpsr)); switch (el) { case EL0: return secure ? filter.u : (filter.u != filter.nsu); case EL1: return secure ? filter.p : (filter.p != filter.nsk); case EL2: return !filter.nsh; case EL3: return filter.p != filter.m; default: panic("Unexpected execution level in PMU::isFiltered.\n"); } } void PMU::CounterState::detach() { if (sourceEvent) { sourceEvent->detachEvent(this); sourceEvent = nullptr; } else { debugCounter("detaching event not currently attached" " to any event\n"); } } void PMU::CounterState::attach(PMUEvent* event) { value = 0; sourceEvent = event; sourceEvent->attachEvent(this); } uint64_t PMU::CounterState::getValue() const { if (sourceEvent) { sourceEvent->updateAttachedCounters(); } else { debugCounter("attempted to get value from a counter without" " an associated event\n"); } return value; } void PMU::CounterState::setValue(uint64_t val) { value = val; resetValue = true; if (sourceEvent) { sourceEvent->updateAttachedCounters(); } else { debugCounter("attempted to set value from a counter without" " an associated event\n"); } } void PMU::updateCounter(CounterState &ctr) { if (!ctr.enabled) { DPRINTF(PMUVerbose, "updateCounter(%i): Disabling counter\n", ctr.getCounterId()); ctr.detach(); } else { DPRINTF(PMUVerbose, "updateCounter(%i): Enable event id 0x%x\n", ctr.getCounterId(), ctr.eventId); auto sourceEvent = eventMap.find(ctr.eventId); if (sourceEvent == eventMap.end()) { warn("Can't enable PMU counter of type '0x%x': " "No such event type.\n", ctr.eventId); } else { ctr.attach(sourceEvent->second); } } } void PMU::resetEventCounts() { for (CounterState &ctr : counters) ctr.setValue(0); } void PMU::setCounterValue(CounterId id, uint64_t val) { if (!isValidCounter(id)) { warn_once("Can't change counter value: Counter %i does not exist.\n", id); return; } CounterState &ctr(getCounter(id)); ctr.setValue(val); } PMU::PMEVTYPER_t PMU::getCounterTypeRegister(CounterId id) const { if (!isValidCounter(id)) return 0; const CounterState &cs(getCounter(id)); PMEVTYPER_t type(cs.filter); type.evtCount = cs.eventId; return type; } void PMU::setCounterTypeRegister(CounterId id, PMEVTYPER_t val) { DPRINTF(PMUVerbose, "Set Event [%d] = 0x%08x\n", id, val); if (!isValidCounter(id)) { warn_once("Can't change counter type: Counter %i does not exist.\n", id); return; } CounterState &ctr(getCounter(id)); const EventTypeId old_event_id(ctr.eventId); ctr.filter = val; // If PMCCNTR Register, do not change event type. PMCCNTR can // count processor cycles only. If we change the event type, we // need to update the probes the counter is using. if (id != PMCCNTR && old_event_id != val.evtCount) { ctr.eventId = val.evtCount; updateCounter(ctr); } } void PMU::setOverflowStatus(RegVal new_val) { const bool int_old = reg_pmovsr != 0; const bool int_new = new_val != 0; reg_pmovsr = new_val; if (int_old && !int_new) { clearInterrupt(); } else if (!int_old && int_new && (reg_pminten & reg_pmovsr)) { raiseInterrupt(); } } void PMU::raiseInterrupt() { if (interrupt) { DPRINTF(PMUVerbose, "Delivering PMU interrupt.\n"); interrupt->raise(); } else { warn_once("Dropping PMU interrupt as no interrupt has " "been specified\n"); } } void PMU::clearInterrupt() { if (interrupt) { DPRINTF(PMUVerbose, "Clearing PMU interrupt.\n"); interrupt->clear(); } else { warn_once("Dropping PMU interrupt as no interrupt has " "been specified\n"); } } void PMU::serialize(CheckpointOut &cp) const { DPRINTF(Checkpoint, "Serializing Arm PMU\n"); SERIALIZE_SCALAR(reg_pmcr); SERIALIZE_SCALAR(reg_pmcnten); SERIALIZE_SCALAR(reg_pmselr); SERIALIZE_SCALAR(reg_pminten); SERIALIZE_SCALAR(reg_pmovsr); SERIALIZE_SCALAR(reg_pmceid0); SERIALIZE_SCALAR(reg_pmceid1); SERIALIZE_SCALAR(clock_remainder); for (size_t i = 0; i < counters.size(); ++i) counters[i].serializeSection(cp, csprintf("counters.%i", i)); cycleCounter.serializeSection(cp, "cycleCounter"); } void PMU::unserialize(CheckpointIn &cp) { DPRINTF(Checkpoint, "Unserializing Arm PMU\n"); UNSERIALIZE_SCALAR(reg_pmcr); UNSERIALIZE_SCALAR(reg_pmcnten); UNSERIALIZE_SCALAR(reg_pmselr); UNSERIALIZE_SCALAR(reg_pminten); UNSERIALIZE_SCALAR(reg_pmovsr); // Old checkpoints used to store the entire PMCEID value in a // single 64-bit entry (reg_pmceid). The register was extended in // ARMv8.1, so we now need to store it as two 64-bit registers. if (!UNSERIALIZE_OPT_SCALAR(reg_pmceid0)) paramIn(cp, "reg_pmceid", reg_pmceid0); if (!UNSERIALIZE_OPT_SCALAR(reg_pmceid1)) reg_pmceid1 = 0; UNSERIALIZE_SCALAR(clock_remainder); for (size_t i = 0; i < counters.size(); ++i) counters[i].unserializeSection(cp, csprintf("counters.%i", i)); cycleCounter.unserializeSection(cp, "cycleCounter"); } PMU::PMUEvent* PMU::getEvent(uint64_t eventId) { auto entry = eventMap.find(eventId); if (entry == eventMap.end()) { warn("event %d does not exist\n", eventId); return nullptr; } else { return entry->second; } } void PMU::CounterState::serialize(CheckpointOut &cp) const { SERIALIZE_SCALAR(eventId); SERIALIZE_SCALAR(value); SERIALIZE_SCALAR(overflow64); } void PMU::CounterState::unserialize(CheckpointIn &cp) { UNSERIALIZE_SCALAR(eventId); UNSERIALIZE_SCALAR(value); UNSERIALIZE_SCALAR(overflow64); } uint64_t PMU::CounterState::add(uint64_t delta) { uint64_t value_until_overflow; if (overflow64) { value_until_overflow = UINT64_MAX - value; } else { value_until_overflow = UINT32_MAX - (uint32_t)value; } if (isFiltered()) return value_until_overflow; if (resetValue) { delta = 0; resetValue = false; } else { value += delta; } if (delta > value_until_overflow) { // overflow situation detected // flag the overflow occurence pmu.reg_pmovsr |= (1 << counterId); // Deliver a PMU interrupt if interrupt delivery is enabled // for this counter. if (pmu.reg_pminten & (1 << counterId)) { pmu.raiseInterrupt(); } return overflow64 ? UINT64_MAX : UINT32_MAX; } return value_until_overflow - delta + 1; } void PMU::SWIncrementEvent::write(uint64_t val) { for (auto& counter: userCounters) { if (val & (0x1 << counter->getCounterId())) { counter->add(1); } } } } // namespace ArmISA ArmISA::PMU * ArmPMUParams::create() { return new ArmISA::PMU(this); }