| /* |
| * Copyright (c) 2011-2014, 2017-2019 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 "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(nullptr) |
| { |
| DPRINTF(PMUVerbose, "Initializing the PMU.\n"); |
| |
| if (maximumCounterCount > 31) { |
| fatal("The PMU can only accept 31 counters, %d counters requested.\n", |
| maximumCounterCount); |
| } |
| |
| warn_if(!p->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()); |
| auto pmu_params = static_cast<const ArmPMUParams *>(params()); |
| |
| if (pmu_params->interrupt) |
| interrupt = pmu_params->interrupt->get(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<RegularEvent*>(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_EL0: |
| 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(currEL(cpsr)); |
| 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) |
| { |
| if (!resetValue) { |
| value = 0; |
| resetValue = true; |
| } |
| 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); |
| } |