[PATCH 2/2] perf: arm_pmuv3: Don't use PMCCNTR_EL0 on SMT cores
James Clark
james.clark at linaro.org
Tue Aug 12 03:31:14 PDT 2025
On 12/08/2025 11:14 am, Yicong Yang wrote:
> On 2025/8/12 18:00, James Clark wrote:
>>
>>
>> On 12/08/2025 9:08 am, Yicong Yang wrote:
>>> From: Yicong Yang <yangyicong at hisilicon.com>
>>>
>>> CPU_CYCLES is expected to count the logical CPU (PE) clock. Currently it's
>>> preferred to use PMCCNTR_EL0 for counting CPU_CYCLES, but it'll count
>>> processor clock rather than the PE clock (ARM DDI0487 L.b D13.1.3) if
>>> one of the SMT siblings is not idle on a multi-threaded implementation.
>>> So don't use it on SMT cores.
>>>
>>> When counting cycles on SMT CPU 2-3 and CPU 3 is idle, without this
>>> patch we'll get:
>>> [root at client1 tmp]# perf stat -e cycles -A -C 2-3 -- stress-ng -c 1
>>> --taskset 2 --timeout 1
>>> [...]
>>> Performance counter stats for 'CPU(s) 2-3':
>>>
>>> CPU2 2880457316 cycles
>>> CPU3 2880459810 cycles
>>> 1.254688470 seconds time elapsed
>>>
>>> With this patch the idle state of CPU3 is observed as expected:
>>> [root at client1 ~]# perf stat -e cycles -A -C 2-3 -- stress-ng -c 1
>>> --taskset 2 --timeout 1
>>> [...]
>>> Performance counter stats for 'CPU(s) 2-3':
>>>
>>> CPU2 2558580492 cycles
>>> CPU3 305749 cycles
>>> 1.113626410 seconds time elapsed
>>>
>>> Signed-off-by: Yicong Yang <yangyicong at hisilicon.com>
>>> ---
>>> drivers/perf/arm_pmuv3.c | 9 +++++++++
>>> 1 file changed, 9 insertions(+)
>>>
>>> diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c
>>> index 95c899d07df5..ed3149632b71 100644
>>> --- a/drivers/perf/arm_pmuv3.c
>>> +++ b/drivers/perf/arm_pmuv3.c
>>> @@ -1002,6 +1002,15 @@ static bool armv8pmu_can_use_pmccntr(struct pmu_hw_events *cpuc,
>>> if (has_branch_stack(event))
>>> return false;
>>> + /*
>>> + * The PMCCNTR_EL0 increments from the processor clock rather than
>>> + * the PE clock (ARM DDI0487 L.b D13.1.3) which means it'll continue
>>> + * counting on a WFI PE if one of its SMT silbing is not idle on a
>>> + * multi-threaded implementation. So don't use it on SMT cores.
>>> + */
>>> + if (cpumask_weight(topology_sibling_cpumask(smp_processor_id())) > 1)
>>> + return false;
>>> +
>>
>> Isn't this something that's static to the PMU? If all CPUs in each PMU are always the same then this doesn't need to be probed every time and can be set once.
>>
> we can make use of PMCCNTR_EL0 if the SMT is runtime disabled, e.g. by /sys/devices/system/cpu/smt/control
> if set this at probe time then we permanently lose the chance to use PMCCNTR_EL0.
>
Is that a valuable usecase though? I don't actually know the answer to
this. How common is disabling SMT on SMT cores and then also using PMU
events in a way that you would miss having the extra cycles counter,
despite not minding that you didn't have it when SMT was enabled?
And would it correctly handle enabling and disabling SMT after the event
has already started? Feels like it wouldn't if you start the event with
it disabled and it puts it onto PMCCNTR_EL0 then you enable it and the
counts become wrong again.
>
>> Also you can't call smp_processor_id() from here because this is also called in armpmu_event_init() -> __hw_perf_event_init() -> validate_group() before the event is actually scheduled on a CPU. With CONFIG_DEBUG_PREEMPT you'd see the error.
>
> ok, will use raw_smp_processor_id() instead. it won't affect the validation checking in pmu::event_init().
> in pmu::add() the cpu id is always stable so it'll also be fine.
>
> Thanks.
>
>
That feels a bit wrong but I suppose it will work. Maybe the real
problem is that validation is doing too much. I know it's to re-use
code, but then we're doing things as part of the validation that don't
make sense. That can confuse the reader or it's just wasted effort. Also
using raw removes the safety check which might mean it gets refactored
into somewhere were it isn't valid to call it in the future.
More information about the linux-arm-kernel
mailing list