All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/2] perf/x86/amd/uncore: Add Hygon uncore PMU and JSON events
@ 2026-07-08 13:13 Qi Liu
  2026-07-08 13:13 ` [PATCH v3 1/2] perf/x86/amd/uncore: Add Hygon uncore PMU support Qi Liu
  2026-07-08 13:13 ` [PATCH v3 2/2] perf vendor events hygon: Add Hygon Data Fabric PMU events and metrics Qi Liu
  0 siblings, 2 replies; 5+ messages in thread
From: Qi Liu @ 2026-07-08 13:13 UTC (permalink / raw)
  To: peterz, mingo, namhyung, alexander.shishkin, jolsa, sandipan.das,
	ravi.bangoria, x86
  Cc: linux-perf-users, linux-kernel, lijing, liuqi

This series adds initial uncore PMU support for Hygon Family 18h
processors.

Hygon uncore PMUs use a programming model similar to AMD's, but
differ in MSR base addresses, event encodings, and counter allocation
semantics. Rather than embedding Hygon support in the AMD uncore
driver with vendor checks, a dedicated driver is introduced to keep
both drivers clean and independently maintainable.

The new driver provides hygon_df and hygon_df_iod PMUs covering the
Data Fabric on both the CPU die and the IO Die, together with perf
JSON events and metrics for three processor models. A per-context
hrtimer handles counter reads, and CPU hotplug callbacks manage
context lifecycle.

Changes since RFC:
  - Address sashiko bot review.
Changes since v1:
  - Rename the helper namespace to amd_uncore_common_* to avoid
    an generic uncore_common_* prefix, and to keep it distinct
    from the existing amd_uncore_* names.
Changes since v2:
  - Drop the common helpers refactoring. Keep the Hygon driver
    self-contained instead, avoiding unnecessary changes in the AMD
    uncore driver.

Qi Liu (2):
  perf/x86/amd/uncore: Add Hygon uncore PMU support
  perf vendor events hygon: Add Hygon Data Fabric PMU events and metrics

 arch/x86/events/Kconfig                       |  11 +
 arch/x86/events/amd/Makefile                  |   2 +
 arch/x86/events/amd/hygon_uncore.c            | 863 ++++++++++++++++++
 arch/x86/events/amd/uncore.c                  |  13 +-
 arch/x86/include/asm/msr-index.h              |   2 +
 arch/x86/include/asm/perf_event.h             |  20 +
 include/linux/cpuhotplug.h                    |   3 +
 tools/perf/pmu-events/Build                   |   2 +-
 .../arch/x86/hygonm1/data-fabric.json         |  87 ++
 .../arch/x86/hygonm4/data-fabric.json         | 122 +++
 .../arch/x86/hygonm6/data-fabric.json         | 154 ++++
 tools/perf/pmu-events/arch/x86/mapfile.csv    |   3 +
 tools/perf/pmu-events/jevents.py              |   2 +
 13 files changed, 1274 insertions(+), 10 deletions(-)
 create mode 100644 arch/x86/events/amd/hygon_uncore.c
 create mode 100644 tools/perf/pmu-events/arch/x86/hygonm1/data-fabric.json
 create mode 100644 tools/perf/pmu-events/arch/x86/hygonm4/data-fabric.json
 create mode 100644 tools/perf/pmu-events/arch/x86/hygonm6/data-fabric.json

-- 
2.34.1



^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 1/2] perf/x86/amd/uncore: Add Hygon uncore PMU support
  2026-07-08 13:13 [PATCH v3 0/2] perf/x86/amd/uncore: Add Hygon uncore PMU and JSON events Qi Liu
@ 2026-07-08 13:13 ` Qi Liu
  2026-07-08 13:27   ` sashiko-bot
  2026-07-08 13:13 ` [PATCH v3 2/2] perf vendor events hygon: Add Hygon Data Fabric PMU events and metrics Qi Liu
  1 sibling, 1 reply; 5+ messages in thread
From: Qi Liu @ 2026-07-08 13:13 UTC (permalink / raw)
  To: peterz, mingo, namhyung, alexander.shishkin, jolsa, sandipan.das,
	ravi.bangoria, x86
  Cc: linux-perf-users, linux-kernel, lijing, liuqi, Zhenglang Hu

Add uncore PMU driver for Hygon Family 18h processors, supporting two
PMU types:

  - hygon_df:     Data Fabric counters shared across CPUs within the
                  same DF instance, identified via Node ID.

  - hygon_df_iod: Data Fabric counters on the IO Die (IOD) for
                  monitoring memory access metrics.

A per-context hrtimer periodically reads the counters since uncore
PMUs do not generate interrupts. CPU hotplug callbacks handle context
creation, migration, and teardown.

Keeping Hygon support in a separate driver avoids accumulating vendor
checks in uncore.c as more Hygon-specific features are introduced.
Remove the now-unnecessary Hygon checks from the AMD uncore driver.

Signed-off-by: Qi Liu <liuqi@hygon.cn>
Tested-by: Zhenglang Hu <huzhenglang@hygon.cn>
---
 arch/x86/events/Kconfig            |  11 +
 arch/x86/events/amd/Makefile       |   2 +
 arch/x86/events/amd/hygon_uncore.c | 863 +++++++++++++++++++++++++++++
 arch/x86/events/amd/uncore.c       |  13 +-
 arch/x86/include/asm/msr-index.h   |   2 +
 arch/x86/include/asm/perf_event.h  |  20 +
 include/linux/cpuhotplug.h         |   3 +
 7 files changed, 905 insertions(+), 9 deletions(-)
 create mode 100644 arch/x86/events/amd/hygon_uncore.c

diff --git a/arch/x86/events/Kconfig b/arch/x86/events/Kconfig
index dabdf3d7bf84..cc8387236b95 100644
--- a/arch/x86/events/Kconfig
+++ b/arch/x86/events/Kconfig
@@ -45,6 +45,17 @@ config PERF_EVENTS_AMD_UNCORE
 	  To compile this driver as a module, choose M here: the
 	  module will be called 'amd-uncore'.
 
+config PERF_EVENTS_HYGON_UNCORE
+	tristate "Hygon Uncore performance events"
+	depends on PERF_EVENTS && CPU_SUP_HYGON
+	default y
+	help
+	  Include support for Hygon uncore performance events for use with
+	  e.g., perf stat -e hygon_df/.../.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called 'hygon-uncore'.
+
 config PERF_EVENTS_AMD_BRS
 	depends on PERF_EVENTS && CPU_SUP_AMD
 	bool "AMD Zen3 Branch Sampling support"
diff --git a/arch/x86/events/amd/Makefile b/arch/x86/events/amd/Makefile
index 527d947eb76b..c373fd1d1525 100644
--- a/arch/x86/events/amd/Makefile
+++ b/arch/x86/events/amd/Makefile
@@ -5,6 +5,8 @@ obj-$(CONFIG_PERF_EVENTS_AMD_POWER)	+= power.o
 obj-$(CONFIG_X86_LOCAL_APIC)		+= ibs.o
 obj-$(CONFIG_PERF_EVENTS_AMD_UNCORE)	+= amd-uncore.o
 amd-uncore-objs				:= uncore.o
+obj-$(CONFIG_PERF_EVENTS_HYGON_UNCORE)	+= hygon-uncore.o
+hygon-uncore-objs			:= hygon_uncore.o
 ifdef CONFIG_AMD_IOMMU
 obj-$(CONFIG_CPU_SUP_AMD)		+= iommu.o
 endif
diff --git a/arch/x86/events/amd/hygon_uncore.c b/arch/x86/events/amd/hygon_uncore.c
new file mode 100644
index 000000000000..6c60c2392674
--- /dev/null
+++ b/arch/x86/events/amd/hygon_uncore.c
@@ -0,0 +1,863 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Chengdu Haiguang IC Design Co., Ltd.
+ *
+ */
+
+#include <linux/cpu.h>
+#include <linux/cpumask.h>
+#include <linux/cpufeature.h>
+#include <linux/hrtimer.h>
+#include <linux/init.h>
+#include <linux/perf_event.h>
+#include <linux/percpu.h>
+#include <linux/slab.h>
+#include <linux/smp.h>
+#include <linux/types.h>
+
+#include <asm/amd/nb.h>
+#include <asm/cpuid/api.h>
+#include <asm/perf_event.h>
+#include <asm/msr.h>
+
+#define NUM_COUNTERS_DF		4
+#define NUM_COUNTERS_MAX	8
+
+#define UNCORE_NAME_LEN		16
+#define COUNTER_SHIFT		16
+
+#undef pr_fmt
+#define pr_fmt(fmt)	"hygon_uncore: " fmt
+
+struct hygon_uncore_ctx {
+	int refcnt;
+	int cpu;
+	struct perf_event **events;
+	unsigned long active_mask[BITS_TO_LONGS(NUM_COUNTERS_MAX)];
+	int nr_active;
+	struct hrtimer hrtimer;
+	u64 hrtimer_duration;
+};
+
+struct hygon_uncore_pmu {
+	char name[UNCORE_NAME_LEN];
+	int type;
+	int num_counters;
+	u32 msr_base;
+	cpumask_t active_mask;
+	struct pmu pmu;
+	struct hygon_uncore_ctx * __percpu *ctx;
+};
+
+enum {
+	HYGON_UNCORE_TYPE_DF,
+	HYGON_UNCORE_TYPE_DF_IOD,
+	HYGON_UNCORE_TYPE_MAX
+};
+
+union hygon_uncore_info {
+	struct {
+		u64	num_iods:8;	/* number of iods of each package */
+		u64	num_pmcs:8;	/* number of counters */
+		u64	cid:8;		/* context id */
+	} split;
+	u64		full;
+};
+
+struct hygon_uncore {
+	union hygon_uncore_info  __percpu *info;
+	struct hygon_uncore_pmu pmu;
+	bool pmu_registered;
+	bool init_done;
+	void (*scan)(struct hygon_uncore *uncore, unsigned int cpu);
+	int  (*init)(struct hygon_uncore *uncore, unsigned int cpu);
+	void (*move)(struct hygon_uncore *uncore, unsigned int cpu);
+	void (*free)(struct hygon_uncore *uncore, unsigned int cpu);
+};
+static struct hygon_uncore uncores[];
+
+/* Interval for hrtimer, defaults to 60000 milliseconds */
+static unsigned int update_interval = 60 * MSEC_PER_SEC;
+module_param(update_interval, uint, 0444);
+
+static struct hygon_uncore_pmu *event_to_hygon_uncore_pmu(struct perf_event *event)
+{
+	return container_of(event->pmu, struct hygon_uncore_pmu, pmu);
+}
+
+static __always_inline bool is_uncore_df_iod_event(struct hygon_uncore_pmu *pmu)
+{
+	return pmu->type == HYGON_UNCORE_TYPE_DF_IOD;
+}
+
+static __always_inline int hygon_uncore_ctx_num_pmcs(struct hygon_uncore *uncore, unsigned int cpu)
+{
+	union hygon_uncore_info *info = per_cpu_ptr(uncore->info, cpu);
+
+	return info->split.num_pmcs;
+}
+
+static __always_inline int hygon_uncore_ctx_cid(struct hygon_uncore *uncore, unsigned int cpu)
+{
+	union hygon_uncore_info *info = per_cpu_ptr(uncore->info, cpu);
+
+	return info->split.cid;
+}
+
+static __always_inline int hygon_uncore_ctx_num_iods(struct hygon_uncore *uncore, unsigned int cpu)
+{
+	union hygon_uncore_info *info = per_cpu_ptr(uncore->info, cpu);
+
+	return info->split.num_iods;
+}
+
+static enum hrtimer_restart hygon_uncore_hrtimer(struct hrtimer *hrtimer)
+{
+	struct hygon_uncore_ctx *ctx;
+	struct perf_event *event;
+	int bit;
+
+	ctx = container_of(hrtimer, struct hygon_uncore_ctx, hrtimer);
+
+	if (!ctx->nr_active || ctx->cpu != smp_processor_id())
+		return HRTIMER_NORESTART;
+
+	for_each_set_bit(bit, ctx->active_mask, NUM_COUNTERS_MAX) {
+		event = ctx->events[bit];
+		event->pmu->read(event);
+	}
+
+	hrtimer_forward_now(hrtimer, ns_to_ktime(ctx->hrtimer_duration));
+	return HRTIMER_RESTART;
+}
+
+static void hygon_uncore_start_hrtimer(struct hygon_uncore_ctx *ctx)
+{
+	hrtimer_start(&ctx->hrtimer, ns_to_ktime(ctx->hrtimer_duration),
+		      HRTIMER_MODE_REL_PINNED_HARD);
+}
+
+static void hygon_uncore_cancel_hrtimer(struct hygon_uncore_ctx *ctx)
+{
+	hrtimer_cancel(&ctx->hrtimer);
+}
+
+static void hygon_uncore_init_hrtimer(struct hygon_uncore_ctx *ctx)
+{
+	hrtimer_setup(&ctx->hrtimer, hygon_uncore_hrtimer, CLOCK_MONOTONIC,
+		      HRTIMER_MODE_REL_HARD);
+}
+
+static void hygon_uncore_read(struct perf_event *event)
+{
+	struct hw_perf_event *hwc = &event->hw;
+	u64 prev, cur;
+	s64 delta;
+
+	prev = local64_read(&hwc->prev_count);
+	rdmsrq(hwc->event_base, cur);
+	local64_set(&hwc->prev_count, cur);
+	delta = (cur << COUNTER_SHIFT) - (prev << COUNTER_SHIFT);
+	delta >>= COUNTER_SHIFT;
+	local64_add(delta, &event->count);
+}
+
+static void hygon_uncore_start(struct perf_event *event, int flags)
+{
+	struct hygon_uncore_pmu *pmu = event_to_hygon_uncore_pmu(event);
+	struct hygon_uncore_ctx *ctx = *per_cpu_ptr(pmu->ctx, event->cpu);
+	struct hw_perf_event *hwc = &event->hw;
+
+	if (!ctx->nr_active++)
+		hygon_uncore_start_hrtimer(ctx);
+
+	if (flags & PERF_EF_RELOAD)
+		wrmsrq(hwc->event_base, (u64)local64_read(&hwc->prev_count));
+
+	hwc->state = 0;
+	__set_bit(hwc->idx, ctx->active_mask);
+	wrmsrq(hwc->config_base, (hwc->config | ARCH_PERFMON_EVENTSEL_ENABLE));
+	perf_event_update_userpage(event);
+}
+
+static void hygon_uncore_stop(struct perf_event *event, int flags)
+{
+	struct hygon_uncore_pmu *pmu = event_to_hygon_uncore_pmu(event);
+	struct hygon_uncore_ctx *ctx = *per_cpu_ptr(pmu->ctx, event->cpu);
+	struct hw_perf_event *hwc = &event->hw;
+
+	wrmsrq(hwc->config_base, hwc->config);
+	hwc->state |= PERF_HES_STOPPED;
+
+	if ((flags & PERF_EF_UPDATE) && !(hwc->state & PERF_HES_UPTODATE)) {
+		event->pmu->read(event);
+		hwc->state |= PERF_HES_UPTODATE;
+	}
+
+	if (!--ctx->nr_active)
+		hygon_uncore_cancel_hrtimer(ctx);
+
+	__clear_bit(hwc->idx, ctx->active_mask);
+}
+
+static int hygon_uncore_add(struct perf_event *event, int flags)
+{
+	struct hygon_uncore_pmu *pmu = event_to_hygon_uncore_pmu(event);
+	struct hygon_uncore_ctx *ctx = *per_cpu_ptr(pmu->ctx, event->cpu);
+	struct hw_perf_event *hwc = &event->hw;
+	int i, iod_idx;
+
+	if (!ctx)
+		return -ENODEV;
+
+	if (hwc->idx != -1 && ctx->events[hwc->idx] == event)
+		goto out;
+
+	for (i = 0; i < pmu->num_counters; i++) {
+		if (ctx->events[i] == event) {
+			hwc->idx = i;
+			goto out;
+		}
+	}
+
+	hwc->idx = -1;
+	if (is_uncore_df_iod_event(pmu)) {
+		iod_idx = event->attr.config1;
+		for (i = iod_idx * NUM_COUNTERS_DF; i < (iod_idx + 1) * NUM_COUNTERS_DF; i++) {
+			struct perf_event *tmp = NULL;
+
+			if (try_cmpxchg(&ctx->events[i], &tmp, event)) {
+				hwc->idx = i;
+				break;
+			}
+		}
+	} else {
+		for (i = 0; i < pmu->num_counters; i++) {
+			struct perf_event *tmp = NULL;
+
+			if (try_cmpxchg(&ctx->events[i], &tmp, event)) {
+				hwc->idx = i;
+				break;
+			}
+		}
+	}
+out:
+	if (hwc->idx == -1)
+		return -EBUSY;
+
+	hwc->config_base = pmu->msr_base + (2 * hwc->idx);
+	hwc->event_base = pmu->msr_base + 1 + (2 * hwc->idx);
+	hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED;
+	if (flags & PERF_EF_START)
+		event->pmu->start(event, PERF_EF_RELOAD);
+
+	return 0;
+}
+
+static void hygon_uncore_del(struct perf_event *event, int flags)
+{
+	struct hygon_uncore_pmu *pmu = event_to_hygon_uncore_pmu(event);
+	struct hygon_uncore_ctx *ctx = *per_cpu_ptr(pmu->ctx, event->cpu);
+	struct hw_perf_event *hwc = &event->hw;
+	int i;
+
+	if (!ctx)
+		return;
+
+	event->pmu->stop(event, PERF_EF_UPDATE);
+	for (i = 0; i < pmu->num_counters; i++) {
+		struct perf_event *tmp = event;
+
+		if (try_cmpxchg(&ctx->events[i], &tmp, NULL))
+			break;
+	}
+
+	hwc->idx = -1;
+}
+
+static int hygon_uncore_event_init(struct perf_event *event)
+{
+	struct hw_perf_event *hwc = &event->hw;
+	struct hygon_uncore_pmu *pmu;
+	struct hygon_uncore_ctx *ctx;
+
+	if (event->attr.type != event->pmu->type)
+		return -ENOENT;
+
+	if (event->cpu < 0)
+		return -EINVAL;
+
+	pmu = event_to_hygon_uncore_pmu(event);
+	ctx = *per_cpu_ptr(pmu->ctx, event->cpu);
+	if (!ctx)
+		return -ENODEV;
+
+	event->cpu = ctx->cpu;
+	hwc->config = event->attr.config;
+	hwc->idx = -1;
+
+	return 0;
+}
+
+static int hygon_uncore_df_event_init(struct perf_event *event)
+{
+	int ret = hygon_uncore_event_init(event);
+	struct hw_perf_event *hwc = &event->hw;
+	struct hygon_uncore_pmu *pmu;
+	struct hygon_uncore *uncore;
+	u64 event_mask = HYGON_F18H_RAW_EVENT_MASK_DF;
+
+	if (ret)
+		return ret;
+
+	pmu = event_to_hygon_uncore_pmu(event);
+	uncore = &uncores[pmu->type];
+	if (is_uncore_df_iod_event(pmu) &&
+	    (event->attr.config1 >= hygon_uncore_ctx_num_iods(uncore, event->cpu)))
+		return -EINVAL;
+
+	if (boot_cpu_data.x86_model == 0x4 ||
+	    boot_cpu_data.x86_model == 0x5)
+		event_mask = HYGON_F18H_M4H_RAW_EVENT_MASK_DF;
+	else if (boot_cpu_data.x86_model >= 0x6 &&
+		 boot_cpu_data.x86_model <= 0x18)
+		event_mask = HYGON_F18H_M6H_RAW_EVENT_MASK_DF;
+
+	hwc->config = event->attr.config & event_mask;
+	return 0;
+}
+
+static ssize_t cpumask_show(struct device *dev,
+			    struct device_attribute *attr,
+			    char *buf)
+{
+	struct pmu *ptr = dev_get_drvdata(dev);
+	struct hygon_uncore_pmu *pmu = container_of(ptr, struct hygon_uncore_pmu, pmu);
+
+	return cpumap_print_to_pagebuf(true, buf, &pmu->active_mask);
+}
+static DEVICE_ATTR_RO(cpumask);
+
+static struct attribute *hygon_uncore_attrs[] = {
+	&dev_attr_cpumask.attr,
+	NULL,
+};
+
+static struct attribute_group hygon_uncore_attr_group = {
+	.attrs = hygon_uncore_attrs,
+};
+
+#define DEFINE_UNCORE_FORMAT_ATTR(_var, _name, _format)			\
+static ssize_t __uncore_##_var##_show(struct device *dev,		\
+				struct device_attribute *attr,		\
+				char *page)				\
+{									\
+	BUILD_BUG_ON(sizeof(_format) >= PAGE_SIZE);			\
+	return sprintf(page, _format "\n");				\
+}									\
+static struct device_attribute format_attr_##_var =			\
+	__ATTR(_name, 0444, __uncore_##_var##_show, NULL)
+
+DEFINE_UNCORE_FORMAT_ATTR(event,	event,		"config:0-5");
+DEFINE_UNCORE_FORMAT_ATTR(umask8,	umask,		"config:8-15");
+DEFINE_UNCORE_FORMAT_ATTR(umask10,	umask,		"config:8-17");		/* F18h M4h DF */
+DEFINE_UNCORE_FORMAT_ATTR(umask12,	umask,		"config:8-19");		/* F18h M6h DF */
+DEFINE_UNCORE_FORMAT_ATTR(constid,	constid,	"config:6-7,32-35,61-62");
+DEFINE_UNCORE_FORMAT_ATTR(iod,		iod,		"config1:0-1");
+
+static struct attribute *hygon_uncore_df_umask_attr(void)
+{
+	if (boot_cpu_data.x86_model == 0x4 ||
+	    boot_cpu_data.x86_model == 0x5)
+		return &format_attr_umask10.attr;
+
+	if (boot_cpu_data.x86_model >= 0x6 &&
+	    boot_cpu_data.x86_model <= 0x18)
+		return &format_attr_umask12.attr;
+
+	return &format_attr_umask8.attr;
+}
+
+static struct attribute *hygon_uncore_df_format_attr[] = {
+	&format_attr_event.attr,	/* event */
+	&format_attr_umask8.attr,	/* umask */
+	&format_attr_constid.attr,	/* constid */
+	NULL,
+};
+
+static struct attribute_group hygon_uncore_df_format_group = {
+	.name = "format",
+	.attrs = hygon_uncore_df_format_attr,
+};
+
+static const struct attribute_group *hygon_uncore_df_attr_groups[] = {
+	&hygon_uncore_attr_group,
+	&hygon_uncore_df_format_group,
+	NULL,
+};
+
+static struct attribute *hygon_uncore_df_iod_format_attr[] = {
+	&format_attr_event.attr,	/* event */
+	&format_attr_umask10.attr,	/* umask */
+	&format_attr_constid.attr,	/* constid */
+	&format_attr_iod.attr,		/* iod */
+	NULL,
+};
+
+static struct attribute_group hygon_uncore_df_iod_format_group = {
+	.name = "format",
+	.attrs = hygon_uncore_df_iod_format_attr,
+};
+
+static const struct attribute_group *hygon_uncore_df_iod_attr_groups[] = {
+	&hygon_uncore_attr_group,
+	&hygon_uncore_df_iod_format_group,
+	NULL,
+};
+
+static int hygon_uncore_cpu_starting(unsigned int cpu)
+{
+	struct hygon_uncore *uncore;
+	int i;
+
+	for (i = 0; i < HYGON_UNCORE_TYPE_MAX; i++) {
+		uncore = &uncores[i];
+		uncore->scan(uncore, cpu);
+	}
+
+	return 0;
+}
+
+static int hygon_uncore_cpu_dead(unsigned int cpu)
+{
+	struct hygon_uncore *uncore;
+	int i;
+
+	for (i = 0; i < HYGON_UNCORE_TYPE_MAX; i++) {
+		uncore = &uncores[i];
+		uncore->free(uncore, cpu);
+	}
+
+	return 0;
+}
+
+static int hygon_uncore_cpu_online(unsigned int cpu)
+{
+	struct hygon_uncore *uncore;
+	int i;
+
+	for (i = 0; i < HYGON_UNCORE_TYPE_MAX; i++) {
+		uncore = &uncores[i];
+		if (uncore->init(uncore, cpu))
+			break;
+	}
+
+	return 0;
+}
+
+static int hygon_uncore_cpu_down_prepare(unsigned int cpu)
+{
+	struct hygon_uncore *uncore;
+	int i;
+
+	for (i = 0; i < HYGON_UNCORE_TYPE_MAX; i++) {
+		uncore = &uncores[i];
+		uncore->move(uncore, cpu);
+	}
+
+	return 0;
+}
+
+static void hygon_uncore_ctx_free(struct hygon_uncore *uncore, unsigned int cpu)
+{
+	struct hygon_uncore_pmu *pmu = &uncore->pmu;
+	struct hygon_uncore_ctx *ctx;
+
+	if (!uncore->init_done || !uncore->pmu_registered)
+		return;
+
+	if (!pmu->ctx)
+		return;
+
+	ctx = *per_cpu_ptr(pmu->ctx, cpu);
+	if (!ctx)
+		return;
+
+	if (cpu == ctx->cpu)
+		cpumask_clear_cpu(cpu, &pmu->active_mask);
+
+	if (!--ctx->refcnt) {
+		hygon_uncore_cancel_hrtimer(ctx);
+		kfree(ctx->events);
+		kfree(ctx);
+	}
+
+	*per_cpu_ptr(pmu->ctx, cpu) = NULL;
+}
+
+static void hygon_uncore_ctx_move(struct hygon_uncore *uncore, unsigned int cpu)
+{
+	struct hygon_uncore_pmu *pmu = &uncore->pmu;
+	struct hygon_uncore_ctx *curr, *next;
+	int i;
+
+	if (!uncore->init_done || !uncore->pmu_registered)
+		return;
+
+	if (!pmu->ctx)
+		return;
+
+	curr = *per_cpu_ptr(pmu->ctx, cpu);
+	if (!curr)
+		return;
+
+	for_each_online_cpu(i) {
+		next = *per_cpu_ptr(pmu->ctx, i);
+		if (!next || cpu == i)
+			continue;
+
+		if (curr == next) {
+			perf_pmu_migrate_context(&pmu->pmu, cpu, i);
+			cpumask_clear_cpu(cpu, &pmu->active_mask);
+			cpumask_set_cpu(i, &pmu->active_mask);
+			next->cpu = i;
+			break;
+		}
+	}
+}
+
+static int hygon_uncore_ctx_init(struct hygon_uncore *uncore, unsigned int cpu)
+{
+	struct hygon_uncore_ctx *curr, *prev;
+	struct hygon_uncore_pmu *pmu = &uncore->pmu;
+	int node, cid, i;
+
+	if (!uncore->init_done || !uncore->pmu_registered)
+		return 0;
+
+	if (!pmu->ctx)
+		return 0;
+
+	cid = hygon_uncore_ctx_cid(uncore, cpu);
+	*per_cpu_ptr(pmu->ctx, cpu) = NULL;
+	curr = NULL;
+
+	for_each_online_cpu(i) {
+		if (cpu == i)
+			continue;
+
+		prev = *per_cpu_ptr(pmu->ctx, i);
+		if (!prev)
+			continue;
+		if (cid == hygon_uncore_ctx_cid(uncore, i)) {
+			curr = prev;
+			break;
+		}
+	}
+
+	if (!curr) {
+		node = cpu_to_node(cpu);
+		curr = kzalloc_node(sizeof(*curr), GFP_KERNEL, node);
+		if (!curr)
+			goto fail;
+
+		curr->cpu = cpu;
+		curr->events = kzalloc_node(sizeof(*curr->events) *
+					    pmu->num_counters,
+					    GFP_KERNEL, node);
+		if (!curr->events) {
+			kfree(curr);
+			goto fail;
+		}
+
+		cpumask_set_cpu(cpu, &pmu->active_mask);
+
+		hygon_uncore_init_hrtimer(curr);
+		curr->hrtimer_duration = (u64)update_interval * NSEC_PER_MSEC;
+	}
+
+	curr->refcnt++;
+	*per_cpu_ptr(pmu->ctx, cpu) = curr;
+
+	return 0;
+
+fail:
+	hygon_uncore_ctx_free(uncore, cpu);
+
+	return -ENOMEM;
+}
+
+static void hygon_uncore_df_ctx_scan(struct hygon_uncore *uncore, unsigned int cpu)
+{
+	union hygon_uncore_info info = {};
+
+	if (!boot_cpu_has(X86_FEATURE_PERFCTR_NB))
+		return;
+
+	info.split.num_iods = 0;
+	info.split.num_pmcs = NUM_COUNTERS_DF;
+	info.split.cid = topology_amd_node_id(cpu);
+
+	*per_cpu_ptr(uncore->info, cpu) = info;
+}
+
+static
+int hygon_uncore_df_ctx_init(struct hygon_uncore *uncore, unsigned int cpu)
+{
+	struct hygon_uncore_pmu *pmu = &uncore->pmu;
+	int num_counters;
+
+	if (uncore->init_done)
+		return hygon_uncore_ctx_init(uncore, cpu);
+
+	num_counters = hygon_uncore_ctx_num_pmcs(uncore, cpu);
+	if (!num_counters)
+		goto done;
+
+	strscpy(pmu->name, "hygon_df", sizeof(pmu->name));
+	pmu->num_counters = num_counters;
+	pmu->msr_base = MSR_HYGON_F18H_DF_CTL;
+	pmu->type = HYGON_UNCORE_TYPE_DF;
+
+	hygon_uncore_df_format_attr[1] = hygon_uncore_df_umask_attr();
+
+	pmu->ctx = alloc_percpu(struct hygon_uncore_ctx *);
+	if (!pmu->ctx)
+		goto done;
+
+	pmu->pmu = (struct pmu) {
+		.task_ctx_nr	= perf_invalid_context,
+		.attr_groups	= hygon_uncore_df_attr_groups,
+		.name		= pmu->name,
+		.event_init	= hygon_uncore_df_event_init,
+		.add		= hygon_uncore_add,
+		.del		= hygon_uncore_del,
+		.start		= hygon_uncore_start,
+		.stop		= hygon_uncore_stop,
+		.read		= hygon_uncore_read,
+		.capabilities	= PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_NO_INTERRUPT,
+		.module		= THIS_MODULE,
+	};
+
+	if (perf_pmu_register(&pmu->pmu, pmu->pmu.name, -1)) {
+		free_percpu(pmu->ctx);
+		pmu->ctx = NULL;
+		goto done;
+	}
+	uncore->pmu_registered = true;
+
+done:
+	uncore->init_done = true;
+	return hygon_uncore_ctx_init(uncore, cpu);
+}
+
+static
+void hygon_uncore_df_iod_ctx_scan(struct hygon_uncore *uncore, unsigned int cpu)
+{
+	union hygon_uncore_info info = {};
+	int num_packages, num_cdds, iods_per_package;
+
+	if (!boot_cpu_has(X86_FEATURE_PERFCTR_NB))
+		return;
+
+	if (boot_cpu_data.x86_model < 0x4 || boot_cpu_data.x86_model == 0x6)
+		return;
+
+	num_packages = topology_max_packages();
+	num_cdds = topology_max_dies_per_package() * num_packages;
+	iods_per_package = (amd_nb_num() - num_cdds) / num_packages;
+
+	/* Hardware does not support more than 2 IODs per package. */
+	if (iods_per_package <= 0 || iods_per_package > 2)
+		return;
+
+	info.split.cid = topology_physical_package_id(cpu);
+	info.split.num_iods = iods_per_package;
+	info.split.num_pmcs = NUM_COUNTERS_DF * iods_per_package;
+
+	*per_cpu_ptr(uncore->info, cpu) = info;
+}
+
+static
+int hygon_uncore_df_iod_ctx_init(struct hygon_uncore *uncore, unsigned int cpu)
+{
+	struct hygon_uncore_pmu *pmu = &uncore->pmu;
+	int num_counters;
+
+	if (uncore->init_done)
+		return hygon_uncore_ctx_init(uncore, cpu);
+
+	num_counters = hygon_uncore_ctx_num_pmcs(uncore, cpu);
+	if (!num_counters)
+		goto done;
+
+	strscpy(pmu->name, "hygon_df_iod", sizeof(pmu->name));
+	pmu->num_counters = num_counters;
+	pmu->msr_base = MSR_HYGON_F18H_DF_IOD_CTL;
+	pmu->type = HYGON_UNCORE_TYPE_DF_IOD;
+
+	hygon_uncore_df_iod_format_attr[1] = hygon_uncore_df_umask_attr();
+
+	pmu->ctx = alloc_percpu(struct hygon_uncore_ctx *);
+	if (!pmu->ctx)
+		goto done;
+
+	pmu->pmu = (struct pmu) {
+		.task_ctx_nr	= perf_invalid_context,
+		.attr_groups	= hygon_uncore_df_iod_attr_groups,
+		.name		= pmu->name,
+		.event_init	= hygon_uncore_df_event_init,
+		.add		= hygon_uncore_add,
+		.del		= hygon_uncore_del,
+		.start		= hygon_uncore_start,
+		.stop		= hygon_uncore_stop,
+		.read		= hygon_uncore_read,
+		.capabilities	= PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_NO_INTERRUPT,
+		.module		= THIS_MODULE,
+	};
+
+	if (perf_pmu_register(&pmu->pmu, pmu->pmu.name, -1)) {
+		free_percpu(pmu->ctx);
+		pmu->ctx = NULL;
+		goto done;
+	}
+	uncore->pmu_registered = true;
+done:
+	uncore->init_done = true;
+	return hygon_uncore_ctx_init(uncore, cpu);
+}
+
+static struct hygon_uncore uncores[HYGON_UNCORE_TYPE_MAX] = {
+	/* HYGON DF */
+	{
+		.scan = hygon_uncore_df_ctx_scan,
+		.init = hygon_uncore_df_ctx_init,
+		.move = hygon_uncore_ctx_move,
+		.free = hygon_uncore_ctx_free,
+	},
+	/* HYGON DF IOD */
+	{
+		.scan = hygon_uncore_df_iod_ctx_scan,
+		.init = hygon_uncore_df_iod_ctx_init,
+		.move = hygon_uncore_ctx_move,
+		.free = hygon_uncore_ctx_free,
+	}
+};
+
+static void hygon_uncore_pmu_cleanup(void)
+{
+	struct hygon_uncore_ctx *ctx;
+	struct hygon_uncore *uncore;
+	int i, cpu;
+
+	for (i = 0; i < HYGON_UNCORE_TYPE_MAX; i++) {
+		uncore = &uncores[i];
+
+		/* Unregister PMU before freeing its contexts. */
+		if (uncore->pmu_registered) {
+			perf_pmu_unregister(&uncore->pmu.pmu);
+			uncore->pmu_registered = false;
+		}
+
+		if (!uncore->pmu.ctx)
+			continue;
+
+		/* Shared ctx objects are released through their refcnt. */
+		for_each_possible_cpu(cpu) {
+			ctx = *per_cpu_ptr(uncore->pmu.ctx, cpu);
+			if (!ctx)
+				continue;
+
+			if (!--ctx->refcnt) {
+				hygon_uncore_cancel_hrtimer(ctx);
+				kfree(ctx->events);
+				kfree(ctx);
+			}
+			*per_cpu_ptr(uncore->pmu.ctx, cpu) = NULL;
+		}
+
+		free_percpu(uncore->pmu.ctx);
+		uncore->pmu.ctx = NULL;
+	}
+}
+
+static void hygon_uncore_info_cleanup(void)
+{
+	struct hygon_uncore *uncore;
+	int i;
+
+	for (i = 0; i < HYGON_UNCORE_TYPE_MAX; i++) {
+		uncore = &uncores[i];
+		if (uncore->info) {
+			free_percpu(uncore->info);
+			uncore->info = NULL;
+		}
+	}
+}
+
+static int __init hygon_uncore_init(void)
+{
+	struct hygon_uncore *uncore;
+	int ret = -ENODEV;
+	int i;
+
+	if (boot_cpu_data.x86_vendor != X86_VENDOR_HYGON)
+		return -ENODEV;
+
+	if (!boot_cpu_has(X86_FEATURE_TOPOEXT))
+		return -ENODEV;
+
+	for (i = 0; i < HYGON_UNCORE_TYPE_MAX; i++) {
+		uncore = &uncores[i];
+
+		uncore->info = alloc_percpu(union hygon_uncore_info);
+		if (!uncore->info) {
+			ret = -ENOMEM;
+			goto fail;
+		}
+	}
+
+	ret = cpuhp_setup_state(CPUHP_PERF_X86_HYGON_UNCORE_PREP,
+				"perf/x86/hygon/uncore:prepare",
+				NULL, hygon_uncore_cpu_dead);
+	if (ret)
+		goto fail;
+
+	ret = cpuhp_setup_state(CPUHP_AP_PERF_X86_HYGON_UNCORE_STARTING,
+				"perf/x86/hygon/uncore:starting",
+				hygon_uncore_cpu_starting, NULL);
+	if (ret)
+		goto fail_prep;
+
+	ret = cpuhp_setup_state(CPUHP_AP_PERF_X86_HYGON_UNCORE_ONLINE,
+				"perf/x86/hygon/uncore:online",
+				hygon_uncore_cpu_online,
+				hygon_uncore_cpu_down_prepare);
+	if (ret)
+		goto fail_start;
+
+	return 0;
+
+fail_start:
+	cpuhp_remove_state(CPUHP_AP_PERF_X86_HYGON_UNCORE_STARTING);
+fail_prep:
+	cpuhp_remove_state(CPUHP_PERF_X86_HYGON_UNCORE_PREP);
+fail:
+	hygon_uncore_info_cleanup();
+	return ret;
+}
+
+static void __exit hygon_uncore_exit(void)
+{
+	cpuhp_remove_state(CPUHP_AP_PERF_X86_HYGON_UNCORE_ONLINE);
+	cpuhp_remove_state(CPUHP_AP_PERF_X86_HYGON_UNCORE_STARTING);
+	cpuhp_remove_state(CPUHP_PERF_X86_HYGON_UNCORE_PREP);
+
+	hygon_uncore_pmu_cleanup();
+	hygon_uncore_info_cleanup();
+}
+module_init(hygon_uncore_init);
+module_exit(hygon_uncore_exit);
+
+MODULE_DESCRIPTION("Hygon Uncore Driver");
+MODULE_LICENSE("GPL");
diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c
index dbc00b6dd69e..3f8e40abba73 100644
--- a/arch/x86/events/amd/uncore.c
+++ b/arch/x86/events/amd/uncore.c
@@ -620,7 +620,7 @@ static int amd_uncore_cpu_online(unsigned int cpu)
 	for (i = 0; i < UNCORE_TYPE_MAX; i++) {
 		uncore = &uncores[i];
 		if (uncore->init(uncore, cpu))
-			break;
+			continue;
 	}
 
 	return 0;
@@ -775,9 +775,7 @@ int amd_uncore_df_ctx_init(struct amd_uncore *uncore, unsigned int cpu)
 		goto done;
 	}
 
-	pr_info("%d %s%s counters detected\n", pmu->num_counters,
-		boot_cpu_data.x86_vendor == X86_VENDOR_HYGON ?  "HYGON " : "",
-		pmu->pmu.name);
+	pr_info("%d %s counters detected\n", pmu->num_counters, pmu->pmu.name);
 
 	uncore->num_pmus = 1;
 
@@ -911,9 +909,7 @@ int amd_uncore_l3_ctx_init(struct amd_uncore *uncore, unsigned int cpu)
 		goto done;
 	}
 
-	pr_info("%d %s%s counters detected\n", pmu->num_counters,
-		boot_cpu_data.x86_vendor == X86_VENDOR_HYGON ?  "HYGON " : "",
-		pmu->pmu.name);
+	pr_info("%d %s counters detected\n", pmu->num_counters, pmu->pmu.name);
 
 	uncore->num_pmus = 1;
 
@@ -1120,8 +1116,7 @@ static int __init amd_uncore_init(void)
 	int ret = -ENODEV;
 	int i;
 
-	if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD &&
-	    boot_cpu_data.x86_vendor != X86_VENDOR_HYGON)
+	if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
 		return -ENODEV;
 
 	if (!boot_cpu_has(X86_FEATURE_TOPOEXT))
diff --git a/arch/x86/include/asm/msr-index.h b/arch/x86/include/asm/msr-index.h
index 18c4be75e927..d20338f0b2b2 100644
--- a/arch/x86/include/asm/msr-index.h
+++ b/arch/x86/include/asm/msr-index.h
@@ -851,6 +851,8 @@
 #define MSR_F15H_PTSC			0xc0010280
 #define MSR_F15H_IC_CFG			0xc0011021
 #define MSR_F15H_EX_CFG			0xc001102c
+#define MSR_HYGON_F18H_DF_CTL		0xc0010240
+#define MSR_HYGON_F18H_DF_IOD_CTL	0xc0010250
 
 /* Fam 10h MSRs */
 #define MSR_FAM10H_MMIO_CONF_BASE	0xc0010058
diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h
index 1eb13673e889..7c8c00ad56e9 100644
--- a/arch/x86/include/asm/perf_event.h
+++ b/arch/x86/include/asm/perf_event.h
@@ -124,6 +124,26 @@
 	(AMD64_PERFMON_V2_EVENTSEL_EVENT_NB	|	\
 	 AMD64_PERFMON_V2_EVENTSEL_UMASK_NB)
 
+#define HYGON_F18H_EVENTSEL_EVENT_DF		0x0000003FULL
+#define HYGON_F18H_EVENTSEL_UMASK_DF		0x0000FF00ULL
+#define HYGON_F18H_M4H_EVENTSEL_UMASK_DF	0x0003FF00ULL
+#define HYGON_F18H_M6H_EVENTSEL_UMASK_DF	0x000FFF00ULL
+#define HYGON_F18H_EVENTSEL_CONSTID_DF			\
+	(0x000000C0ULL | GENMASK_ULL(35, 32) | GENMASK_ULL(62, 61))
+
+#define HYGON_F18H_RAW_EVENT_MASK_DF			\
+	(HYGON_F18H_EVENTSEL_EVENT_DF		|	\
+	 HYGON_F18H_EVENTSEL_UMASK_DF		|	\
+	 HYGON_F18H_EVENTSEL_CONSTID_DF)
+#define HYGON_F18H_M4H_RAW_EVENT_MASK_DF		\
+	(HYGON_F18H_EVENTSEL_EVENT_DF		|	\
+	 HYGON_F18H_M4H_EVENTSEL_UMASK_DF	|	\
+	 HYGON_F18H_EVENTSEL_CONSTID_DF)
+#define HYGON_F18H_M6H_RAW_EVENT_MASK_DF		\
+	(HYGON_F18H_EVENTSEL_EVENT_DF		|	\
+	 HYGON_F18H_M6H_EVENTSEL_UMASK_DF	|	\
+	 HYGON_F18H_EVENTSEL_CONSTID_DF)
+
 #define AMD64_PERFMON_V2_ENABLE_UMC			BIT_ULL(31)
 #define AMD64_PERFMON_V2_EVENTSEL_EVENT_UMC		GENMASK_ULL(7, 0)
 #define AMD64_PERFMON_V2_EVENTSEL_RDWRMASK_UMC		GENMASK_ULL(9, 8)
diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
index 0fb3a2a62eb0..bccdb87a0c05 100644
--- a/include/linux/cpuhotplug.h
+++ b/include/linux/cpuhotplug.h
@@ -62,6 +62,7 @@ enum cpuhp_state {
 	CPUHP_CREATE_THREADS,
 	CPUHP_PERF_X86_PREPARE,
 	CPUHP_PERF_X86_AMD_UNCORE_PREP,
+	CPUHP_PERF_X86_HYGON_UNCORE_PREP,
 	CPUHP_PERF_POWER,
 	CPUHP_PERF_SUPERH,
 	CPUHP_X86_HPET_DEAD,
@@ -148,6 +149,7 @@ enum cpuhp_state {
 	CPUHP_AP_IRQ_RISCV_SBI_IPI_STARTING,
 	CPUHP_AP_ARM_MVEBU_COHERENCY,
 	CPUHP_AP_PERF_X86_AMD_UNCORE_STARTING,
+	CPUHP_AP_PERF_X86_HYGON_UNCORE_STARTING,
 	CPUHP_AP_PERF_X86_STARTING,
 	CPUHP_AP_PERF_X86_AMD_IBS_STARTING,
 	CPUHP_AP_PERF_XTENSA_STARTING,
@@ -205,6 +207,7 @@ enum cpuhp_state {
 	CPUHP_AP_PERF_X86_ONLINE,
 	CPUHP_AP_PERF_X86_UNCORE_ONLINE,
 	CPUHP_AP_PERF_X86_AMD_UNCORE_ONLINE,
+	CPUHP_AP_PERF_X86_HYGON_UNCORE_ONLINE,
 	CPUHP_AP_PERF_X86_AMD_POWER_ONLINE,
 	CPUHP_AP_PERF_S390_CF_ONLINE,
 	CPUHP_AP_PERF_S390_SF_ONLINE,
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 2/2] perf vendor events hygon: Add Hygon Data Fabric PMU events and metrics
  2026-07-08 13:13 [PATCH v3 0/2] perf/x86/amd/uncore: Add Hygon uncore PMU and JSON events Qi Liu
  2026-07-08 13:13 ` [PATCH v3 1/2] perf/x86/amd/uncore: Add Hygon uncore PMU support Qi Liu
@ 2026-07-08 13:13 ` Qi Liu
  1 sibling, 0 replies; 5+ messages in thread
From: Qi Liu @ 2026-07-08 13:13 UTC (permalink / raw)
  To: peterz, mingo, namhyung, alexander.shishkin, jolsa, sandipan.das,
	ravi.bangoria, x86
  Cc: linux-perf-users, linux-kernel, lijing, liuqi

Add perf JSON event definitions and metrics for the Hygon Data Fabric (DF)
uncore PMU.

Signed-off-by: Qi Liu <liuqi@hygon.cn>
---
 tools/perf/pmu-events/Build                   |   2 +-
 .../arch/x86/hygonm1/data-fabric.json         |  87 ++++++++++
 .../arch/x86/hygonm4/data-fabric.json         | 122 ++++++++++++++
 .../arch/x86/hygonm6/data-fabric.json         | 154 ++++++++++++++++++
 tools/perf/pmu-events/arch/x86/mapfile.csv    |   3 +
 tools/perf/pmu-events/jevents.py              |   2 +
 6 files changed, 369 insertions(+), 1 deletion(-)
 create mode 100644 tools/perf/pmu-events/arch/x86/hygonm1/data-fabric.json
 create mode 100644 tools/perf/pmu-events/arch/x86/hygonm4/data-fabric.json
 create mode 100644 tools/perf/pmu-events/arch/x86/hygonm6/data-fabric.json

diff --git a/tools/perf/pmu-events/Build b/tools/perf/pmu-events/Build
index 372773b998e4..79a9b4cad202 100644
--- a/tools/perf/pmu-events/Build
+++ b/tools/perf/pmu-events/Build
@@ -95,7 +95,7 @@ endif
 
 ifeq ($(JEVENTS_ARCH),$(filter $(JEVENTS_ARCH),x86 all))
 # Generate Intel Json
-INTELS := $(shell ls -d pmu-events/arch/x86/*|grep -v amdzen|grep -v mapfile.csv)
+INTELS := $(shell ls -d pmu-events/arch/x86/*|grep -v amdzen|grep -v hygon|grep -v mapfile.csv)
 INTEL_METRICS = $(foreach x,$(INTELS),$(OUTPUT)$(x)/extra-metrics.json)
 INTEL_METRICGROUPS = $(foreach x,$(INTELS),$(OUTPUT)$(x)/extra-metricgroups.json)
 GEN_JSON += $(INTEL_METRICS) $(INTEL_METRICGROUPS)
diff --git a/tools/perf/pmu-events/arch/x86/hygonm1/data-fabric.json b/tools/perf/pmu-events/arch/x86/hygonm1/data-fabric.json
new file mode 100644
index 000000000000..99c243bd9ba0
--- /dev/null
+++ b/tools/perf/pmu-events/arch/x86/hygonm1/data-fabric.json
@@ -0,0 +1,87 @@
+[
+  {
+    "EventName": "cpu_read_requests_cs_0",
+    "BriefDescription": "The number of CPU read requests to memory: Data (64B): CS0",
+    "EventCode": "0x2",
+    "UMask": "0x07",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_read_requests_cs_1",
+    "BriefDescription": "The number of CPU read requests to memory: Data (64B): CS1",
+    "EventCode": "0x2",
+    "UMask": "0x07",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_write_requests_cs_0",
+    "BriefDescription": "The number of CPU write requests to memory: Data (64B): CS0",
+    "EventCode": "0x2",
+    "UMask": "0x08",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_write_requests_cs_1",
+    "BriefDescription": "The number of CPU write requests to memory: Data (64B): CS1",
+    "EventCode": "0x2",
+    "UMask": "0x08",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_read_requests_cs_0",
+    "BriefDescription": "The number of inbound I/O read requests to memory: Data (32B or 64B): CS0",
+    "EventCode": "0x3",
+    "UMask": "0x0F",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_read_requests_cs_1",
+    "BriefDescription": "The number of inbound I/O read requests to memory: Data (32B or 64B): CS1",
+    "EventCode": "0x3",
+    "UMask": "0x0F",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_write_requests_cs_0",
+    "BriefDescription": "The number of inbound I/O write requests to memory: Data (32B or 64B): CS0",
+    "EventCode": "0x3",
+    "UMask": "0x70",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_write_requests_cs_1",
+    "BriefDescription": "The number of inbound I/O write requests to memory: Data (32B or 64B): CS1",
+    "EventCode": "0x3",
+    "UMask": "0x70",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "MetricName": "cpu_memory_bandwidth",
+    "MetricExpr": "(cpu_read_requests_cs_0 + cpu_read_requests_cs_1 + cpu_write_requests_cs_0 + cpu_write_requests_cs_1) * 64 / (duration_time * 1e6)",
+    "BriefDescription": "Total CPU memory access bandwidth",
+    "MetricGroup": "DF_Memory_BW",
+    "ScaleUnit": "1MB/s"
+  },
+  {
+    "MetricName": "io_memory_max_bandwidth",
+    "MetricExpr": "(io_read_requests_cs_0 + io_read_requests_cs_1 + io_write_requests_cs_0 + io_write_requests_cs_1) * 64 / (duration_time * 1e6)",
+    "BriefDescription": "Maximum inbound I/O memory access bandwidth (upper bound, assuming 64B per request. Actual inbound I/O requests may be 32B or 64B)",
+    "MetricGroup": "DF_Memory_BW",
+    "ScaleUnit": "1MB/s"
+  },
+  {
+    "MetricName": "io_memory_min_bandwidth",
+    "MetricExpr": "(io_read_requests_cs_0 + io_read_requests_cs_1 + io_write_requests_cs_0 + io_write_requests_cs_1) * 32 / (duration_time * 1e6)",
+    "BriefDescription": "Minimum inbound I/O memory access bandwidth (lower bound, assuming 32B per request. Actual inbound I/O requests may be 32B or 64B)",
+    "MetricGroup": "DF_Memory_BW",
+    "ScaleUnit": "1MB/s"
+  }
+]
diff --git a/tools/perf/pmu-events/arch/x86/hygonm4/data-fabric.json b/tools/perf/pmu-events/arch/x86/hygonm4/data-fabric.json
new file mode 100644
index 000000000000..f32dcea7061c
--- /dev/null
+++ b/tools/perf/pmu-events/arch/x86/hygonm4/data-fabric.json
@@ -0,0 +1,122 @@
+[
+  {
+    "EventName": "cpu_read_requests_cs_0",
+    "BriefDescription": "The number of CPU read requests to memory: Data (64B): CS0",
+    "EventCode": "0x2",
+    "UMask": "0x07",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_read_requests_cs_1",
+    "BriefDescription": "The number of CPU read requests to memory: Data (64B): CS1",
+    "EventCode": "0x2",
+    "UMask": "0x07",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_read_requests_cs_2",
+    "BriefDescription": "The number of CPU read requests to memory: Data (64B): CS2",
+    "EventCode": "0x2",
+    "UMask": "0x07",
+    "ConstId": "0x2",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_write_requests_cs_0",
+    "BriefDescription": "The number of CPU write requests to memory: Data (64B): CS0",
+    "EventCode": "0x2",
+    "UMask": "0x08",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_write_requests_cs_1",
+    "BriefDescription": "The number of CPU write requests to memory: Data (64B): CS1",
+    "EventCode": "0x2",
+    "UMask": "0x08",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_write_requests_cs_2",
+    "BriefDescription": "The number of CPU write requests to memory: Data (64B): CS2",
+    "EventCode": "0x2",
+    "UMask": "0x08",
+    "ConstId": "0x2",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_read_requests_cs_0",
+    "BriefDescription": "The number of inbound I/O read requests to memory: Data (32B or 64B): CS0",
+    "EventCode": "0x3",
+    "UMask": "0x0F",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_read_requests_cs_1",
+    "BriefDescription": "The number of inbound I/O read requests to memory: Data (32B or 64B): CS1",
+    "EventCode": "0x3",
+    "UMask": "0x0F",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_read_requests_cs_2",
+    "BriefDescription": "The number of inbound I/O read requests to memory: Data (32B or 64B): CS2",
+    "EventCode": "0x3",
+    "UMask": "0x0F",
+    "ConstId": "0x2",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_write_requests_cs_0",
+    "BriefDescription": "The number of inbound I/O write requests to memory: Data (32B or 64B): CS0",
+    "EventCode": "0x3",
+    "UMask": "0x70",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_write_requests_cs_1",
+    "BriefDescription": "The number of inbound I/O write requests to memory: Data (32B or 64B): CS1",
+    "EventCode": "0x3",
+    "UMask": "0x70",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_write_requests_cs_2",
+    "BriefDescription": "The number of inbound I/O write requests to memory: Data (32B or 64B): CS2",
+    "EventCode": "0x3",
+    "UMask": "0x70",
+    "ConstId": "0x2",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "MetricName": "cpu_memory_bandwidth",
+    "MetricExpr": "(cpu_read_requests_cs_0 + cpu_read_requests_cs_1 + cpu_read_requests_cs_2 + cpu_write_requests_cs_0 + cpu_write_requests_cs_1 + cpu_write_requests_cs_2) * 64 / (duration_time * 1e6)",
+    "MetricConstraint": "NO_GROUP_EVENTS",
+    "BriefDescription": "Total CPU memory access bandwidth",
+    "MetricGroup": "DF_Memory_BW",
+    "ScaleUnit": "1MB/s"
+  },
+  {
+    "MetricName": "io_memory_max_bandwidth",
+    "MetricExpr": "(io_read_requests_cs_0 + io_read_requests_cs_1 + io_read_requests_cs_2 + io_write_requests_cs_0 + io_write_requests_cs_1 + io_write_requests_cs_2) * 64 / (duration_time * 1e6)",
+    "MetricConstraint": "NO_GROUP_EVENTS",
+    "BriefDescription": "Maximum inbound I/O memory access bandwidth (upper bound, assuming 64B per request. Actual inbound I/O requests may be 32B or 64B)",
+    "MetricGroup": "DF_Memory_BW",
+    "ScaleUnit": "1MB/s"
+  },
+  {
+    "MetricName": "io_memory_min_bandwidth",
+    "MetricExpr": "(io_read_requests_cs_0 + io_read_requests_cs_1 + io_read_requests_cs_2 + io_write_requests_cs_0 + io_write_requests_cs_1 + io_write_requests_cs_2) * 32 / (duration_time * 1e6)",
+    "MetricConstraint": "NO_GROUP_EVENTS",
+    "BriefDescription": "Minimum inbound I/O memory access bandwidth (lower bound, assuming 32B per request. Actual inbound I/O requests may be 32B or 64B)",
+    "MetricGroup": "DF_Memory_BW",
+    "ScaleUnit": "1MB/s"
+  }
+]
diff --git a/tools/perf/pmu-events/arch/x86/hygonm6/data-fabric.json b/tools/perf/pmu-events/arch/x86/hygonm6/data-fabric.json
new file mode 100644
index 000000000000..bff5c0959d82
--- /dev/null
+++ b/tools/perf/pmu-events/arch/x86/hygonm6/data-fabric.json
@@ -0,0 +1,154 @@
+[
+  {
+    "EventName": "cpu_read_requests_cs_0",
+    "BriefDescription": "The number of CPU read requests to memory: Data (64B): CS0",
+    "EventCode": "0x2",
+    "UMask": "0x07",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_read_requests_cs_1",
+    "BriefDescription": "The number of CPU read requests to memory: Data (64B): CS1",
+    "EventCode": "0x2",
+    "UMask": "0x07",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_read_requests_cs_2",
+    "BriefDescription": "The number of CPU read requests to memory: Data (64B): CS2",
+    "EventCode": "0x2",
+    "UMask": "0x07",
+    "ConstId": "0x2",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_read_requests_cs_3",
+    "BriefDescription": "The number of CPU read requests to memory: Data (64B): CS3",
+    "EventCode": "0x2",
+    "UMask": "0x07",
+    "ConstId": "0x3",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_write_requests_cs_0",
+    "BriefDescription": "The number of CPU write requests to memory: Data (64B): CS0",
+    "EventCode": "0x2",
+    "UMask": "0x08",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_write_requests_cs_1",
+    "BriefDescription": "The number of CPU write requests to memory: Data (64B): CS1",
+    "EventCode": "0x2",
+    "UMask": "0x08",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_write_requests_cs_2",
+    "BriefDescription": "The number of CPU write requests to memory: Data (64B): CS2",
+    "EventCode": "0x2",
+    "UMask": "0x08",
+    "ConstId": "0x2",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "cpu_write_requests_cs_3",
+    "BriefDescription": "The number of CPU write requests to memory: Data (64B): CS3",
+    "EventCode": "0x2",
+    "UMask": "0x08",
+    "ConstId": "0x3",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_read_requests_cs_0",
+    "BriefDescription": "The number of inbound I/O read requests to memory: Data (32B or 64B): CS0",
+    "EventCode": "0x3",
+    "UMask": "0x0F",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_read_requests_cs_1",
+    "BriefDescription": "The number of inbound I/O read requests to memory: Data (32B or 64B): CS1",
+    "EventCode": "0x3",
+    "UMask": "0x0F",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_read_requests_cs_2",
+    "BriefDescription": "The number of inbound I/O read requests to memory: Data (32B or 64B): CS2",
+    "EventCode": "0x3",
+    "UMask": "0x0F",
+    "ConstId": "0x2",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_read_requests_cs_3",
+    "BriefDescription": "The number of inbound I/O read requests to memory: Data (32B or 64B): CS3",
+    "EventCode": "0x3",
+    "UMask": "0x0F",
+    "ConstId": "0x3",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_write_requests_cs_0",
+    "BriefDescription": "The number of inbound I/O write requests to memory: Data (32B or 64B): CS0",
+    "EventCode": "0x3",
+    "UMask": "0x70",
+    "ConstId": "0x0",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_write_requests_cs_1",
+    "BriefDescription": "The number of inbound I/O write requests to memory: Data (32B or 64B): CS1",
+    "EventCode": "0x3",
+    "UMask": "0x70",
+    "ConstId": "0x1",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_write_requests_cs_2",
+    "BriefDescription": "The number of inbound I/O write requests to memory: Data (32B or 64B): CS2",
+    "EventCode": "0x3",
+    "UMask": "0x70",
+    "ConstId": "0x2",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "EventName": "io_write_requests_cs_3",
+    "BriefDescription": "The number of inbound I/O write requests to memory: Data (32B or 64B): CS3",
+    "EventCode": "0x3",
+    "UMask": "0x70",
+    "ConstId": "0x3",
+    "Unit": "HYGON_DF"
+  },
+  {
+    "MetricName": "cpu_memory_bandwidth",
+    "MetricExpr": "(cpu_read_requests_cs_0 + cpu_read_requests_cs_1 + cpu_read_requests_cs_2 + cpu_read_requests_cs_3 + cpu_write_requests_cs_0 + cpu_write_requests_cs_1 + cpu_write_requests_cs_2 + cpu_write_requests_cs_3) * 64 / (duration_time * 1e6)",
+    "MetricConstraint": "NO_GROUP_EVENTS",
+    "BriefDescription": "Total CPU memory access bandwidth",
+    "MetricGroup": "DF_Memory_BW",
+    "ScaleUnit": "1MB/s"
+  },
+  {
+    "MetricName": "io_memory_max_bandwidth",
+    "MetricExpr": "(io_read_requests_cs_0 + io_read_requests_cs_1 + io_read_requests_cs_2 + io_read_requests_cs_3 + io_write_requests_cs_0 + io_write_requests_cs_1 + io_write_requests_cs_2 + io_write_requests_cs_3) * 64 / (duration_time * 1e6)",
+    "MetricConstraint": "NO_GROUP_EVENTS",
+    "BriefDescription": "Maximum inbound I/O memory access bandwidth (upper bound, assuming 64B per request. Actual inbound I/O requests may be 32B or 64B)",
+    "MetricGroup": "DF_Memory_BW",
+    "ScaleUnit": "1MB/s"
+  },
+  {
+    "MetricName": "io_memory_min_bandwidth",
+    "MetricExpr": "(io_read_requests_cs_0 + io_read_requests_cs_1 + io_read_requests_cs_2 + io_read_requests_cs_3 + io_write_requests_cs_0 + io_write_requests_cs_1 + io_write_requests_cs_2 + io_write_requests_cs_3) * 32 / (duration_time * 1e6)",
+    "MetricConstraint": "NO_GROUP_EVENTS",
+    "BriefDescription": "Minimum inbound I/O memory access bandwidth (lower bound, assuming 32B per request. Actual inbound I/O requests may be 32B or 64B)",
+    "MetricGroup": "DF_Memory_BW",
+    "ScaleUnit": "1MB/s"
+  }
+]
diff --git a/tools/perf/pmu-events/arch/x86/mapfile.csv b/tools/perf/pmu-events/arch/x86/mapfile.csv
index 50a403b429b1..ba17117245d1 100644
--- a/tools/perf/pmu-events/arch/x86/mapfile.csv
+++ b/tools/perf/pmu-events/arch/x86/mapfile.csv
@@ -45,3 +45,6 @@ AuthenticAMD-25-([245][[:xdigit:]]|[[:xdigit:]]),v1,amdzen3,core
 AuthenticAMD-25-[[:xdigit:]]+,v1,amdzen4,core
 AuthenticAMD-26-([12467][[:xdigit:]]|[[:xdigit:]]),v1,amdzen5,core
 AuthenticAMD-26-[[:xdigit:]]+,v1,amdzen6,core
+HygonGenuine-24-[0123],v1,hygonm1,core
+HygonGenuine-24-[45],v1,hygonm4,core
+HygonGenuine-24-(1[012345678]|[6789ABCDEF]),v1,hygonm6,core
diff --git a/tools/perf/pmu-events/jevents.py b/tools/perf/pmu-events/jevents.py
index 376dc2d24162..63ae3a763a4f 100755
--- a/tools/perf/pmu-events/jevents.py
+++ b/tools/perf/pmu-events/jevents.py
@@ -294,6 +294,7 @@ class JsonEvent:
           'L3PMC': 'amd_l3',
           'DFPMC': 'amd_df',
           'UMCPMC': 'amd_umc',
+          'HYGON_DF': 'hygon_df',
           'cpu_core': 'cpu_core',
           'cpu_atom': 'cpu_atom',
           'ali_drw': 'ali_drw',
@@ -390,6 +391,7 @@ class JsonEvent:
         ('Invert', 'inv='),
         ('SampleAfterValue', 'period='),
         ('UMask', 'umask='),
+        ('ConstId', 'constid='),
         ('NodeType', 'type='),
         ('RdWrMask', 'rdwrmask='),
         ('EnAllCores', 'enallcores='),
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 1/2] perf/x86/amd/uncore: Add Hygon uncore PMU support
  2026-07-08 13:13 ` [PATCH v3 1/2] perf/x86/amd/uncore: Add Hygon uncore PMU support Qi Liu
@ 2026-07-08 13:27   ` sashiko-bot
  2026-07-09  2:09     ` Qi Liu
  0 siblings, 1 reply; 5+ messages in thread
From: sashiko-bot @ 2026-07-08 13:27 UTC (permalink / raw)
  To: Qi Liu; +Cc: linux-perf-users

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Module initialization error path fails to unregister PMUs and leaks per-CPU arrays, leading to a Use-After-Free.
- [Medium] The CPU online callback aborts PMU initialization on the first failure, leaving remaining independent PMUs uninitialized.
- [Medium] The patch removes L3 cache uncore PMU support for Hygon processors without providing a replacement.
- [Medium] The `update_interval` module parameter lacks bounds checking, allowing a value of 0 to cause a CPU-locking interrupt storm.
--

--- Patch [1]: [PATCH v3 1/2] perf/x86/amd/uncore: Add Hygon uncore PMU support ---
commit 451a8d6c91c9ade8b2dbc942f864d5c36dc872c9
Author: Qi Liu <liuqi@hygon.cn>
Subject: perf/x86/amd/uncore: Add Hygon uncore PMU support

This commit adds a new uncore PMU driver specifically for Hygon Family 18h
processors, supporting Data Fabric and IO Die counters. It also removes the
Hygon-specific checks from the AMD uncore driver to keep the vendor
implementations separate.

> diff --git a/arch/x86/events/amd/hygon_uncore.c b/arch/x86/events/amd/hygon_uncore.c
> new file mode 100644
> --- /dev/null
> +++ b/arch/x86/events/amd/hygon_uncore.c
> @@ -0,0 +1,863 @@

[ ... ]

> +/* Interval for hrtimer, defaults to 60000 milliseconds */
> +static unsigned int update_interval = 60 * MSEC_PER_SEC;
> +module_param(update_interval, uint, 0444);

[Severity: Medium]
Could setting this to 0 cause an interrupt storm? If a privileged user loads
the module with update_interval=0, hrtimer_duration becomes 0. When this is
passed to hrtimer_forward_now(), it is clamped to the minimum resolution,
causing the high-resolution timer to fire continuously in a loop. Should
there be a minimum bounds check here to prevent a CPU lockup?

[ ... ]

> +static int hygon_uncore_cpu_online(unsigned int cpu)
> +{
> +	struct hygon_uncore *uncore;
> +	int i;
> +
> +	for (i = 0; i < HYGON_UNCORE_TYPE_MAX; i++) {
> +		uncore = &uncores[i];
> +		if (uncore->init(uncore, cpu))
> +			break;

[Severity: Medium]
Should this be a continue instead of a break? If the first PMU type (e.g.,
hygon_df) fails to initialize, this break aborts the loop, which prevents
any subsequent independent PMUs (like hygon_df_iod) from initializing on
this CPU. The same patch corrects this exact logic flaw in
amd_uncore_cpu_online(), but it seems to have been reintroduced here.

> +	}
> +
> +	return 0;
> +}

[ ... ]

> +fail_start:
> +	cpuhp_remove_state(CPUHP_AP_PERF_X86_HYGON_UNCORE_STARTING);
> +fail_prep:
> +	cpuhp_remove_state(CPUHP_PERF_X86_HYGON_UNCORE_PREP);
> +fail:
> +	hygon_uncore_info_cleanup();
> +	return ret;
> +}

[Severity: High]
Does this error path leak the registered PMUs? If cpuhp_setup_state()
fails for the ONLINE state, we remove the previous hotplug states but it
looks like hygon_uncore_pmu_cleanup() is never called.

Any PMUs successfully registered during the initial CPU online callbacks
would remain registered, and their per-CPU arrays would not be freed. When
the module unloads, could this lead to a use-after-free if the perf
subsystem tries to access the freed module memory?

[ ... ]

> diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c
> index dbc00b6dd69ee..3f8e40abba736 100644
> --- a/arch/x86/events/amd/uncore.c
> +++ b/arch/x86/events/amd/uncore.c

[ ... ]

> @@ -1120,8 +1116,7 @@ static int __init amd_uncore_init(void)
>  	int ret = -ENODEV;
>  	int i;
>  
> -	if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD &&
> -	    boot_cpu_data.x86_vendor != X86_VENDOR_HYGON)
> +	if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
>  		return -ENODEV;

[Severity: Medium]
Does this intentionally drop L3 cache uncore support for Hygon? By
removing Hygon support here in amd_uncore.c, the previous L3 cache
counters are no longer available. Looking at the new hygon_uncore.c, it
only implements the Data Fabric and IO Die PMUs, meaning L3 PMU support
appears to be lost completely.

>  	if (!boot_cpu_has(X86_FEATURE_TOPOEXT))
>  		return -ENODEV;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260708131315.13766-1-liuqi@hygon.cn?part=1

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 1/2] perf/x86/amd/uncore: Add Hygon uncore PMU support
  2026-07-08 13:27   ` sashiko-bot
@ 2026-07-09  2:09     ` Qi Liu
  0 siblings, 0 replies; 5+ messages in thread
From: Qi Liu @ 2026-07-09  2:09 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: linux-perf-users

Hi,

On 7/8/2026 9:27 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [High] Module initialization error path fails to unregister PMUs and leaks per-CPU arrays, leading to a Use-After-Free.
> - [Medium] The CPU online callback aborts PMU initialization on the first failure, leaving remaining independent PMUs uninitialized.
> - [Medium] The patch removes L3 cache uncore PMU support for Hygon processors without providing a replacement.
> - [Medium] The `update_interval` module parameter lacks bounds checking, allowing a value of 0 to cause a CPU-locking interrupt storm.
> --
> 
> --- Patch [1]: [PATCH v3 1/2] perf/x86/amd/uncore: Add Hygon uncore PMU support ---
> commit 451a8d6c91c9ade8b2dbc942f864d5c36dc872c9
> Author: Qi Liu <liuqi@hygon.cn>
> Subject: perf/x86/amd/uncore: Add Hygon uncore PMU support
> 
> This commit adds a new uncore PMU driver specifically for Hygon Family 18h
> processors, supporting Data Fabric and IO Die counters. It also removes the
> Hygon-specific checks from the AMD uncore driver to keep the vendor
> implementations separate.
> 
>> diff --git a/arch/x86/events/amd/hygon_uncore.c b/arch/x86/events/amd/hygon_uncore.c
>> new file mode 100644
>> --- /dev/null
>> +++ b/arch/x86/events/amd/hygon_uncore.c
>> @@ -0,0 +1,863 @@
> 
> [ ... ]
> 
>> +/* Interval for hrtimer, defaults to 60000 milliseconds */
>> +static unsigned int update_interval = 60 * MSEC_PER_SEC;
>> +module_param(update_interval, uint, 0444);
> 
> [Severity: Medium]
> Could setting this to 0 cause an interrupt storm? If a privileged user loads
> the module with update_interval=0, hrtimer_duration becomes 0. When this is
> passed to hrtimer_forward_now(), it is clamped to the minimum resolution,
> causing the high-resolution timer to fire continuously in a loop. Should
> there be a minimum bounds check here to prevent a CPU lockup?
will fix this.>
> [ ... ]
> 
>> +static int hygon_uncore_cpu_online(unsigned int cpu)
>> +{
>> +	struct hygon_uncore *uncore;
>> +	int i;
>> +
>> +	for (i = 0; i < HYGON_UNCORE_TYPE_MAX; i++) {
>> +		uncore = &uncores[i];
>> +		if (uncore->init(uncore, cpu))
>> +			break;
> 
> [Severity: Medium]
> Should this be a continue instead of a break? If the first PMU type (e.g.,
> hygon_df) fails to initialize, this break aborts the loop, which prevents
> any subsequent independent PMUs (like hygon_df_iod) from initializing on
> this CPU. The same patch corrects this exact logic flaw in
> amd_uncore_cpu_online(), but it seems to have been reintroduced here.
> 
will fix this.>> +	}
>> +
>> +	return 0;
>> +}
> 
> [ ... ]
> 
>> +fail_start:
>> +	cpuhp_remove_state(CPUHP_AP_PERF_X86_HYGON_UNCORE_STARTING);
>> +fail_prep:
>> +	cpuhp_remove_state(CPUHP_PERF_X86_HYGON_UNCORE_PREP);
>> +fail:
>> +	hygon_uncore_info_cleanup();
>> +	return ret;
>> +}
> 
> [Severity: High]
> Does this error path leak the registered PMUs? If cpuhp_setup_state()
> fails for the ONLINE state, we remove the previous hotplug states but it
> looks like hygon_uncore_pmu_cleanup() is never called.
> 
> Any PMUs successfully registered during the initial CPU online callbacks
> would remain registered, and their per-CPU arrays would not be freed. When
> the module unloads, could this lead to a use-after-free if the perf
> subsystem tries to access the freed module memory?
> 
hygon_uncore_cpu_online() always returns 0, so cpuhp_setup_state() for the 
ONLINE state will never fail due to a callback error. The only failure mode is 
that the state slot itself cannot be registered — in which case the startup 
callbacks were never invoked and no PMUs were ever registered.

> [ ... ]
> 
>> diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c
>> index dbc00b6dd69ee..3f8e40abba736 100644
>> --- a/arch/x86/events/amd/uncore.c
>> +++ b/arch/x86/events/amd/uncore.c
> 
> [ ... ]
> 
>> @@ -1120,8 +1116,7 @@ static int __init amd_uncore_init(void)
>>   	int ret = -ENODEV;
>>   	int i;
>>   
>> -	if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD &&
>> -	    boot_cpu_data.x86_vendor != X86_VENDOR_HYGON)
>> +	if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
>>   		return -ENODEV;
> 
> [Severity: Medium]
> Does this intentionally drop L3 cache uncore support for Hygon? By
> removing Hygon support here in amd_uncore.c, the previous L3 cache
> counters are no longer available. Looking at the new hygon_uncore.c, it
> only implements the Data Fabric and IO Die PMUs, meaning L3 PMU support
> appears to be lost completely.
> 

Yes, this is intentional. L3 and UMC PMU support for Hygon will be added in 
follow-up series. This series focuses specifically on Data Fabric uncore PMU 
support as the initial step.

Thanks,
Qi>>   	if (!boot_cpu_has(X86_FEATURE_TOPOEXT))
>>   		return -ENODEV;
> 



^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-07-09  2:09 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-08 13:13 [PATCH v3 0/2] perf/x86/amd/uncore: Add Hygon uncore PMU and JSON events Qi Liu
2026-07-08 13:13 ` [PATCH v3 1/2] perf/x86/amd/uncore: Add Hygon uncore PMU support Qi Liu
2026-07-08 13:27   ` sashiko-bot
2026-07-09  2:09     ` Qi Liu
2026-07-08 13:13 ` [PATCH v3 2/2] perf vendor events hygon: Add Hygon Data Fabric PMU events and metrics Qi Liu

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.