Re: [RESEND PATCH v4 02/11] perf/cxl: Clear stale event fields before reprogramming a counter

[email protected] Wed, 05 Aug 2026 16:12:52 +0000
Newsgroups org.kernel.vger.linux-cxl,org.kernel.vger.linux-perf-users
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Incorrect 64-bit write (writeq) to a 32-bit hardware filter configuration register.
- [High] Incorrect PMU counter overflow handling logic leads to massive overcounting and Undefined Behavior (UB) via out-of-bounds bitwise shift.
--

--- Patch [2]: [RESEND PATCH v4 02/11] perf/cxl: Clear stale event fields before reprogramming a counter ---
commit 215e20dd2e223d030442e8b7a911140cd3ebb0bf
Author: Dave Jiang <[email protected]>

perf/cxl: Clear stale event fields before reprogramming a counter

This patch fixes an issue in cxl_pmu_event_start() where previous event
settings were accumulated instead of cleared. It uses FIELD_MODIFY() to
ensure event group ID, mask, edge, and invert selections replace the old
values, preventing counters from using stale configuration.

Link: https://sashiko.dev/#/patchset/[email protected]?part=1

> diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c
> index ff01b658e1b45..b16e2e4090a39 100644
> --- a/drivers/perf/cxl_pmu.c
> +++ b/drivers/perf/cxl_pmu.c
> @@ -646,17 +646,17 @@ static void cxl_pmu_event_start(struct perf_event *event, int flags)
>  	cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_INT_ON_OVRFLW, 1);
>  	cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_FREEZE_ON_OVRFLW, 1);
>  	cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_ENABLE, 1);
> -	cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_EDGE,
> -			  cxl_pmu_config1_get_edge(event) ? 1 : 0);
> -	cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_INVERT,
> -			  cxl_pmu_config1_get_invert(event) ? 1 : 0);
> +	FIELD_MODIFY(CXL_PMU_COUNTER_CFG_EDGE, &cfg,
> +		     cxl_pmu_config1_get_edge(event) ? 1 : 0);
> +	FIELD_MODIFY(CXL_PMU_COUNTER_CFG_INVERT, &cfg,
> +		     cxl_pmu_config1_get_invert(event) ? 1 : 0);
>  
>  	/* Fixed purpose counters have next two fields RO */
>  	if (test_bit(hwc->idx, info->conf_counter_bm)) {
> -		cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_EVENT_GRP_ID_IDX_MSK,
> -				  hwc->event_base);
> -		cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_EVENTS_MSK,
> -				  cxl_pmu_config_get_mask(event));
> +		FIELD_MODIFY(CXL_PMU_COUNTER_CFG_EVENT_GRP_ID_IDX_MSK, &cfg,
> +			     hwc->event_base);
> +		FIELD_MODIFY(CXL_PMU_COUNTER_CFG_EVENTS_MSK, &cfg,
> +			     cxl_pmu_config_get_mask(event));
>  	}
>  	cfg &= ~CXL_PMU_COUNTER_CFG_THRESHOLD_MSK;
>  	/*

[Severity: High]
This isn't a bug introduced by this patch, but while looking at
cxl_pmu_event_start(), does this writeq() overwrite the configuration
for the adjacent filter?

drivers/perf/cxl_pmu.c:cxl_pmu_event_start() {
    ...
	if (info->filter_hdm) {
		if (cxl_pmu_config1_hdm_filter_en(event))
			cfg = cxl_pmu_config2_get_hdm_decoder(event);
		else
			cfg = GENMASK(31, 0); /* No filtering if 0xFFFF_FFFF */
		writeq(cfg, base + CXL_PMU_FILTER_CFG_REG(hwc->idx, 0));
	}
    ...
}

CXL_PMU_FILTER_CFG_REG has a 4-byte stride, making it a 32-bit hardware
register. Using the 64-bit writeq() here spills into filter 1. If filter 0
is disabled (cfg = 0xFFFFFFFF), writeq() writes 0x00000000 to filter 1,
which would inadvertently enable filter 1 with a 0-match condition.


[Severity: High]
This is a pre-existing issue, but I also noticed a potential undefined
behavior in the overflow handling logic nearby:

drivers/perf/cxl_pmu.c:__cxl_pmu_read() {
    ...
	delta = (new_cnt - prev_cnt) & GENMASK_ULL(info->counter_width - 1, 0);
	if (overflow && delta < GENMASK_ULL(info->counter_width - 1, 0))
		delta += (1UL << info->counter_width);

	local64_add(delta, &event->count);
}

Since delta already accounts for one wraparound via the bitwise AND when
new_cnt is less than prev_cnt, does unconditionally adding 2^width on
overflow cause massive overcounting? 

Also, can (1UL << info->counter_width) cause an out-of-bounds bitwise
shift resulting in undefined behavior? The CXL specification allows
counter_width to be 64, which would shift beyond the width of an unsigned
long, and on 32-bit architectures this could happen for a width of 32 or
greater.

-- 
Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=2