From 003267cb94e21d762eb72d6977d84f44f1705bb7 Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Thu, 11 Jun 2026 09:00:26 -0700 Subject: [PATCH 01/42] perf/x86/intel/uncore: Fix PCI PMU cleanup on setup failure When uncore_pci_pmu_register() fails, pmu->boxes[die] is set to NULL before returning. In the uncore_pci_remove() path, this causes uncore_pci_pmu_unregister() to be skipped entirely, leaking pmu->activeboxes. In the uncore_bus_notify() path, uncore_pci_pmu_unregister() may still be called and must exit early when pmu->boxes[die] is NULL to avoid a NULL pointer dereference, and to ensure activeboxes is only decremented for a previously active box. Additionally, since pci_get_drvdata() returns NULL on registration failure, uncore_pci_remove() can no longer treat NULL drvdata as an indicator of an auxiliary PCI device. Remove the associated WARN_ON_ONCE(). Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://sashiko.dev/#/patchset/20260512233048.9577-1-zide.chen@intel.com?part=1 Link: https://patch.msgid.link/20260611160033.66760-2-zide.chen@intel.com --- arch/x86/events/intel/uncore.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index 7857959c6e82..b69b6a21d46b 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -1183,6 +1183,7 @@ static int uncore_pci_pmu_register(struct pci_dev *pdev, /* First active box registers the pmu */ ret = uncore_pmu_register(pmu); if (ret) { + atomic_dec(&pmu->activeboxes); pmu->boxes[die] = NULL; uncore_box_exit(box); kfree(box); @@ -1248,6 +1249,9 @@ static void uncore_pci_pmu_unregister(struct intel_uncore_pmu *pmu, int die) { struct intel_uncore_box *box = pmu->boxes[die]; + if (!box) + return; + pmu->boxes[die] = NULL; if (atomic_dec_return(&pmu->activeboxes) == 0) uncore_pmu_unregister(pmu); @@ -1272,7 +1276,6 @@ static void uncore_pci_remove(struct pci_dev *pdev) break; } } - WARN_ON_ONCE(i >= UNCORE_EXTRA_PCI_DEV_MAX); return; } From 7d3a9ff98898b3521eb5d7a3daf703b383f7935a Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Thu, 11 Jun 2026 09:00:27 -0700 Subject: [PATCH 02/42] perf/x86/intel/uncore: Fix refcnt and other cleanups Fix typo UNCORE_BOX_FLAG_INITIATED to UNCORE_BOX_FLAG_INITIALIZED. Rename the 'id' parameter in uncore_box_{ref,unref}() to 'die' to reflect its actual meaning and be consistent with other functions. box->refcnt is incremented in the PCI PMU register path but has never been checked or decremented. Although for PCI PMUs box->refcnt effectively tracks only a single user, add atomic_dec_return() in the PCI PMU unregister path to make the reference counting complete and consistent. Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260611160033.66760-3-zide.chen@intel.com --- arch/x86/events/intel/uncore.c | 16 +++++++++------- arch/x86/events/intel/uncore.h | 6 +++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index b69b6a21d46b..21c8ed1628cb 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -1255,8 +1255,10 @@ static void uncore_pci_pmu_unregister(struct intel_uncore_pmu *pmu, int die) pmu->boxes[die] = NULL; if (atomic_dec_return(&pmu->activeboxes) == 0) uncore_pmu_unregister(pmu); - uncore_box_exit(box); - kfree(box); + if (atomic_dec_return(&box->refcnt) == 0) { + uncore_box_exit(box); + kfree(box); + } } static void uncore_pci_remove(struct pci_dev *pdev) @@ -1518,7 +1520,7 @@ static void uncore_change_context(struct intel_uncore_type **uncores, uncore_change_type_ctx(*uncores, old_cpu, new_cpu); } -static void uncore_box_unref(struct intel_uncore_type **types, int id) +static void uncore_box_unref(struct intel_uncore_type **types, int die) { struct intel_uncore_type *type; struct intel_uncore_pmu *pmu; @@ -1529,7 +1531,7 @@ static void uncore_box_unref(struct intel_uncore_type **types, int id) type = *types; pmu = type->pmus; for (i = 0; i < type->num_boxes; i++, pmu++) { - box = pmu->boxes[id]; + box = pmu->boxes[die]; if (box && box->cpu >= 0 && atomic_dec_return(&box->refcnt) == 0) uncore_box_exit(box); } @@ -1604,14 +1606,14 @@ static int allocate_boxes(struct intel_uncore_type **types, } static int uncore_box_ref(struct intel_uncore_type **types, - int id, unsigned int cpu) + int die, unsigned int cpu) { struct intel_uncore_type *type; struct intel_uncore_pmu *pmu; struct intel_uncore_box *box; int i, ret; - ret = allocate_boxes(types, id, cpu); + ret = allocate_boxes(types, die, cpu); if (ret) return ret; @@ -1619,7 +1621,7 @@ static int uncore_box_ref(struct intel_uncore_type **types, type = *types; pmu = type->pmus; for (i = 0; i < type->num_boxes; i++, pmu++) { - box = pmu->boxes[id]; + box = pmu->boxes[die]; if (box && box->cpu >= 0 && atomic_inc_return(&box->refcnt) == 1) uncore_box_init(box); } diff --git a/arch/x86/events/intel/uncore.h b/arch/x86/events/intel/uncore.h index c2e5ccb1d72c..bad5d8dec8e0 100644 --- a/arch/x86/events/intel/uncore.h +++ b/arch/x86/events/intel/uncore.h @@ -185,7 +185,7 @@ struct intel_uncore_box { #define CFL_UNC_CBO_7_PERFEVTSEL0 0xf70 #define CFL_UNC_CBO_7_PER_CTR0 0xf76 -#define UNCORE_BOX_FLAG_INITIATED 0 +#define UNCORE_BOX_FLAG_INITIALIZED 0 /* event config registers are 8-byte apart */ #define UNCORE_BOX_FLAG_CTL_OFFS8 1 /* CFL 8th CBOX has different MSR space */ @@ -559,7 +559,7 @@ static inline u64 uncore_read_counter(struct intel_uncore_box *box, static inline void uncore_box_init(struct intel_uncore_box *box) { - if (!test_and_set_bit(UNCORE_BOX_FLAG_INITIATED, &box->flags)) { + if (!test_and_set_bit(UNCORE_BOX_FLAG_INITIALIZED, &box->flags)) { if (box->pmu->type->ops->init_box) box->pmu->type->ops->init_box(box); } @@ -567,7 +567,7 @@ static inline void uncore_box_init(struct intel_uncore_box *box) static inline void uncore_box_exit(struct intel_uncore_box *box) { - if (test_and_clear_bit(UNCORE_BOX_FLAG_INITIATED, &box->flags)) { + if (test_and_clear_bit(UNCORE_BOX_FLAG_INITIALIZED, &box->flags)) { if (box->pmu->type->ops->exit_box) box->pmu->type->ops->exit_box(box); } From cbbc25209ce34f1baeec615553b93904a7a5d8cd Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Thu, 11 Jun 2026 09:00:28 -0700 Subject: [PATCH 03/42] perf/x86/intel/uncore: Let init_box() callback report failures The init_box() callback currently returns void, so initialization failures are silently ignored and the box is still marked initialized. Change the callback to return int so platform code can report errors back to the common uncore layer. Update uncore_box_init() to set the initialized flag only when init_box() succeeds. Because box->refcnt guarantees that at most one CPU calls uncore_box_init() for a given box at a time, plain __set_bit() is safe for the initialized flag without atomic overhead. Convert all init_box() implementations to return 0 on success or a negative error code on failure. This is a prerequisite for propagating initialization errors to the caller so they can be handled properly. Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Ian Rogers Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260611160033.66760-4-zide.chen@intel.com --- arch/x86/events/intel/uncore.h | 16 +++-- arch/x86/events/intel/uncore_discovery.c | 21 ++++--- arch/x86/events/intel/uncore_discovery.h | 6 +- arch/x86/events/intel/uncore_nhmex.c | 3 +- arch/x86/events/intel/uncore_snb.c | 80 +++++++++++++++--------- arch/x86/events/intel/uncore_snbep.c | 77 ++++++++++++++--------- 6 files changed, 126 insertions(+), 77 deletions(-) diff --git a/arch/x86/events/intel/uncore.h b/arch/x86/events/intel/uncore.h index bad5d8dec8e0..d732b87be0a9 100644 --- a/arch/x86/events/intel/uncore.h +++ b/arch/x86/events/intel/uncore.h @@ -129,7 +129,7 @@ struct intel_uncore_type { #define events_group attr_groups[2] struct intel_uncore_ops { - void (*init_box)(struct intel_uncore_box *); + int (*init_box)(struct intel_uncore_box *); void (*exit_box)(struct intel_uncore_box *); void (*disable_box)(struct intel_uncore_box *); void (*enable_box)(struct intel_uncore_box *); @@ -557,12 +557,18 @@ static inline u64 uncore_read_counter(struct intel_uncore_box *box, return box->pmu->type->ops->read_counter(box, event); } -static inline void uncore_box_init(struct intel_uncore_box *box) +static inline int uncore_box_init(struct intel_uncore_box *box) { - if (!test_and_set_bit(UNCORE_BOX_FLAG_INITIALIZED, &box->flags)) { - if (box->pmu->type->ops->init_box) - box->pmu->type->ops->init_box(box); + int ret = 0; + + if (!test_bit(UNCORE_BOX_FLAG_INITIALIZED, &box->flags) && + box->pmu->type->ops->init_box) { + ret = box->pmu->type->ops->init_box(box); + if (!ret) + __set_bit(UNCORE_BOX_FLAG_INITIALIZED, &box->flags); } + + return ret; } static inline void uncore_box_exit(struct intel_uncore_box *box) diff --git a/arch/x86/events/intel/uncore_discovery.c b/arch/x86/events/intel/uncore_discovery.c index e50776222256..0a22edf4d509 100644 --- a/arch/x86/events/intel/uncore_discovery.c +++ b/arch/x86/events/intel/uncore_discovery.c @@ -489,14 +489,15 @@ static u64 intel_generic_uncore_box_ctl(struct intel_uncore_box *box) return unit->addr; } -void intel_generic_uncore_msr_init_box(struct intel_uncore_box *box) +int intel_generic_uncore_msr_init_box(struct intel_uncore_box *box) { u64 box_ctl = intel_generic_uncore_box_ctl(box); if (!box_ctl) - return; + return -ENODEV; wrmsrq(box_ctl, GENERIC_PMON_BOX_CTL_INT); + return 0; } void intel_generic_uncore_msr_disable_box(struct intel_uncore_box *box) @@ -578,15 +579,16 @@ static inline int intel_pci_uncore_box_ctl(struct intel_uncore_box *box) return UNCORE_DISCOVERY_PCI_BOX_CTRL(intel_generic_uncore_box_ctl(box)); } -void intel_generic_uncore_pci_init_box(struct intel_uncore_box *box) +int intel_generic_uncore_pci_init_box(struct intel_uncore_box *box) { int box_ctl = intel_pci_uncore_box_ctl(box); if (!box_ctl) - return; + return -ENODEV; __set_bit(UNCORE_BOX_FLAG_CTL_OFFS8, &box->flags); - pci_write_config_dword(box->pci_dev, box_ctl, GENERIC_PMON_BOX_CTL_INT); + return pci_write_config_dword(box->pci_dev, box_ctl, + GENERIC_PMON_BOX_CTL_INT); } void intel_generic_uncore_pci_disable_box(struct intel_uncore_box *box) @@ -648,7 +650,7 @@ static struct intel_uncore_ops generic_uncore_pci_ops = { #define UNCORE_GENERIC_MMIO_SIZE 0x4000 -void intel_generic_uncore_mmio_init_box(struct intel_uncore_box *box) +int intel_generic_uncore_mmio_init_box(struct intel_uncore_box *box) { static struct intel_uncore_discovery_unit *unit; struct intel_uncore_type *type = box->pmu->type; @@ -658,13 +660,13 @@ void intel_generic_uncore_mmio_init_box(struct intel_uncore_box *box) if (!unit) { pr_warn("Uncore type %d id %d: Cannot find box control address.\n", type->type_id, box->pmu->pmu_idx); - return; + return -ENODEV; } if (!unit->addr) { pr_warn("Uncore type %d box %d: Invalid box control address.\n", type->type_id, unit->id); - return; + return -ENODEV; } addr = unit->addr; @@ -672,10 +674,11 @@ void intel_generic_uncore_mmio_init_box(struct intel_uncore_box *box) if (!box->io_addr) { pr_warn("Uncore type %d box %d: ioremap error for 0x%llx.\n", type->type_id, unit->id, (unsigned long long)addr); - return; + return -ENOMEM; } writel(GENERIC_PMON_BOX_CTL_INT, box->io_addr); + return 0; } void intel_generic_uncore_mmio_disable_box(struct intel_uncore_box *box) diff --git a/arch/x86/events/intel/uncore_discovery.h b/arch/x86/events/intel/uncore_discovery.h index e1330342b92e..142e1b56cfc2 100644 --- a/arch/x86/events/intel/uncore_discovery.h +++ b/arch/x86/events/intel/uncore_discovery.h @@ -148,11 +148,11 @@ void intel_uncore_generic_uncore_cpu_init(void); int intel_uncore_generic_uncore_pci_init(void); void intel_uncore_generic_uncore_mmio_init(void); -void intel_generic_uncore_msr_init_box(struct intel_uncore_box *box); +int intel_generic_uncore_msr_init_box(struct intel_uncore_box *box); void intel_generic_uncore_msr_disable_box(struct intel_uncore_box *box); void intel_generic_uncore_msr_enable_box(struct intel_uncore_box *box); -void intel_generic_uncore_mmio_init_box(struct intel_uncore_box *box); +int intel_generic_uncore_mmio_init_box(struct intel_uncore_box *box); void intel_generic_uncore_mmio_disable_box(struct intel_uncore_box *box); void intel_generic_uncore_mmio_enable_box(struct intel_uncore_box *box); void intel_generic_uncore_mmio_disable_event(struct intel_uncore_box *box, @@ -160,7 +160,7 @@ void intel_generic_uncore_mmio_disable_event(struct intel_uncore_box *box, void intel_generic_uncore_mmio_enable_event(struct intel_uncore_box *box, struct perf_event *event); -void intel_generic_uncore_pci_init_box(struct intel_uncore_box *box); +int intel_generic_uncore_pci_init_box(struct intel_uncore_box *box); void intel_generic_uncore_pci_disable_box(struct intel_uncore_box *box); void intel_generic_uncore_pci_enable_box(struct intel_uncore_box *box); void intel_generic_uncore_pci_disable_event(struct intel_uncore_box *box, diff --git a/arch/x86/events/intel/uncore_nhmex.c b/arch/x86/events/intel/uncore_nhmex.c index 8962e7cb21e3..7a6855281102 100644 --- a/arch/x86/events/intel/uncore_nhmex.c +++ b/arch/x86/events/intel/uncore_nhmex.c @@ -199,9 +199,10 @@ DEFINE_UNCORE_FORMAT_ATTR(counter, counter, "config:6-7"); DEFINE_UNCORE_FORMAT_ATTR(match, match, "config1:0-63"); DEFINE_UNCORE_FORMAT_ATTR(mask, mask, "config2:0-63"); -static void nhmex_uncore_msr_init_box(struct intel_uncore_box *box) +static int nhmex_uncore_msr_init_box(struct intel_uncore_box *box) { wrmsrq(NHMEX_U_MSR_PMON_GLOBAL_CTL, NHMEX_U_PMON_GLOBAL_EN_ALL); + return 0; } static void nhmex_uncore_msr_exit_box(struct intel_uncore_box *box) diff --git a/arch/x86/events/intel/uncore_snb.c b/arch/x86/events/intel/uncore_snb.c index edddd4f9ab5f..c5347920541c 100644 --- a/arch/x86/events/intel/uncore_snb.c +++ b/arch/x86/events/intel/uncore_snb.c @@ -295,12 +295,14 @@ static void snb_uncore_msr_disable_event(struct intel_uncore_box *box, struct pe wrmsrq(event->hw.config_base, 0); } -static void snb_uncore_msr_init_box(struct intel_uncore_box *box) +static int snb_uncore_msr_init_box(struct intel_uncore_box *box) { if (box->pmu->pmu_idx == 0) { wrmsrq(SNB_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN | SNB_UNC_GLOBAL_CTL_CORE_ALL); } + + return 0; } static void snb_uncore_msr_enable_box(struct intel_uncore_box *box) @@ -394,7 +396,7 @@ void snb_uncore_cpu_init(void) snb_uncore_cbox.num_boxes = topology_num_cores_per_package(); } -static void skl_uncore_msr_init_box(struct intel_uncore_box *box) +static int skl_uncore_msr_init_box(struct intel_uncore_box *box) { if (box->pmu->pmu_idx == 0) { wrmsrq(SKL_UNC_PERF_GLOBAL_CTL, @@ -404,6 +406,8 @@ static void skl_uncore_msr_init_box(struct intel_uncore_box *box) /* The 8th CBOX has different MSR space */ if (box->pmu->pmu_idx == 7) __set_bit(UNCORE_BOX_FLAG_CFL8_CBOX_MSR_OFFS, &box->flags); + + return 0; } static void skl_uncore_msr_enable_box(struct intel_uncore_box *box) @@ -547,10 +551,12 @@ static struct intel_uncore_type *tgl_msr_uncores[] = { NULL, }; -static void rkl_uncore_msr_init_box(struct intel_uncore_box *box) +static int rkl_uncore_msr_init_box(struct intel_uncore_box *box) { if (box->pmu->pmu_idx == 0) wrmsrq(SKL_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN); + + return 0; } void tgl_uncore_cpu_init(void) @@ -707,9 +713,10 @@ static struct intel_uncore_type mtl_uncore_hac_cbox = { .format_group = &adl_uncore_format_group, }; -static void mtl_uncore_msr_init_box(struct intel_uncore_box *box) +static int mtl_uncore_msr_init_box(struct intel_uncore_box *box) { wrmsrq(uncore_msr_box_ctl(box), SNB_UNC_GLOBAL_CTL_EN); + return 0; } static struct intel_uncore_ops mtl_uncore_msr_ops = { @@ -773,10 +780,12 @@ static struct intel_uncore_type *lnl_msr_uncores[] = { #define LNL_UNC_MSR_GLOBAL_CTL 0x240e -static void lnl_uncore_msr_init_box(struct intel_uncore_box *box) +static int lnl_uncore_msr_init_box(struct intel_uncore_box *box) { if (box->pmu->pmu_idx == 0) wrmsrq(LNL_UNC_MSR_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN); + + return 0; } static struct intel_uncore_ops lnl_uncore_msr_ops = { @@ -874,7 +883,7 @@ static const struct attribute_group snb_uncore_imc_format_group = { .attrs = snb_uncore_imc_formats_attr, }; -static void snb_uncore_imc_init_box(struct intel_uncore_box *box) +static int snb_uncore_imc_init_box(struct intel_uncore_box *box) { struct intel_uncore_type *type = box->pmu->type; struct pci_dev *pdev = box->pci_dev; @@ -893,10 +902,13 @@ static void snb_uncore_imc_init_box(struct intel_uncore_box *box) addr &= ~(PAGE_SIZE - 1); box->io_addr = ioremap(addr, type->mmio_map_size); - if (!box->io_addr) + if (!box->io_addr) { pr_warn("perf uncore: Failed to ioremap for %s.\n", type->name); + return -ENOMEM; + } box->hrtimer_duration = UNCORE_SNB_IMC_HRTIMER_INTERVAL; + return 0; } static void snb_uncore_imc_enable_box(struct intel_uncore_box *box) @@ -1532,7 +1544,7 @@ static struct pci_dev *tgl_uncore_get_mc_dev(void) #define TGL_UNCORE_MMIO_IMC_MEM_OFFSET 0x10000 #define TGL_UNCORE_PCI_IMC_MAP_SIZE 0xe000 -static void +static int uncore_get_box_mmio_addr(struct intel_uncore_box *box, unsigned int base_offset, int bar_offset, int step) @@ -1541,19 +1553,20 @@ uncore_get_box_mmio_addr(struct intel_uncore_box *box, struct intel_uncore_pmu *pmu = box->pmu; struct intel_uncore_type *type = pmu->type; resource_size_t addr; + int ret = 0; u32 bar; if (!pdev) { pr_warn("perf uncore: Cannot find matched IMC device.\n"); - return; + return -ENODEV; } pci_read_config_dword(pdev, bar_offset, &bar); if (!(bar & BIT(0))) { pr_warn("perf uncore: BAR 0x%x is disabled. Failed to map %s counters.\n", bar_offset, type->name); - pci_dev_put(pdev); - return; + ret = -ENODEV; + goto out; } bar &= ~BIT(0); addr = (resource_size_t)(bar + step * pmu->pmu_idx); @@ -1565,23 +1578,26 @@ uncore_get_box_mmio_addr(struct intel_uncore_box *box, addr += base_offset; box->io_addr = ioremap(addr, type->mmio_map_size); - if (!box->io_addr) + if (!box->io_addr) { + ret = -ENOMEM; pr_warn("perf uncore: Failed to ioremap for %s.\n", type->name); - + } +out: pci_dev_put(pdev); + return ret; } -static void __uncore_imc_init_box(struct intel_uncore_box *box, +static int __uncore_imc_init_box(struct intel_uncore_box *box, unsigned int base_offset) { - uncore_get_box_mmio_addr(box, base_offset, + return uncore_get_box_mmio_addr(box, base_offset, SNB_UNCORE_PCI_IMC_BAR_OFFSET, TGL_UNCORE_MMIO_IMC_MEM_OFFSET); } -static void tgl_uncore_imc_freerunning_init_box(struct intel_uncore_box *box) +static int tgl_uncore_imc_freerunning_init_box(struct intel_uncore_box *box) { - __uncore_imc_init_box(box, 0); + return __uncore_imc_init_box(box, 0); } static struct intel_uncore_ops tgl_uncore_imc_freerunning_ops = { @@ -1648,13 +1664,15 @@ void tgl_uncore_mmio_init(void) #define ADL_UNCORE_IMC_CTL_INT (ADL_UNCORE_IMC_CTL_RST_CTRL | \ ADL_UNCORE_IMC_CTL_RST_CTRS) -static void adl_uncore_imc_init_box(struct intel_uncore_box *box) +static int adl_uncore_imc_init_box(struct intel_uncore_box *box) { - __uncore_imc_init_box(box, ADL_UNCORE_IMC_BASE); + int ret = __uncore_imc_init_box(box, ADL_UNCORE_IMC_BASE); /* The global control in MC1 can control both MCs. */ - if (box->io_addr && (box->pmu->pmu_idx == 1)) + if (!ret && (box->pmu->pmu_idx == 1)) writel(ADL_UNCORE_IMC_CTL_INT, box->io_addr + ADL_UNCORE_IMC_GLOBAL_CTL); + + return ret; } static void adl_uncore_mmio_disable_box(struct intel_uncore_box *box) @@ -1731,9 +1749,9 @@ static struct freerunning_counters adl_uncore_imc_freerunning[] = { [ADL_MMIO_UNCORE_IMC_DATA_WRITE] = { 0xA0, 0x0, 0x0, 1, 64 }, }; -static void adl_uncore_imc_freerunning_init_box(struct intel_uncore_box *box) +static int adl_uncore_imc_freerunning_init_box(struct intel_uncore_box *box) { - __uncore_imc_init_box(box, ADL_UNCORE_IMC_FREERUNNING_BASE); + return __uncore_imc_init_box(box, ADL_UNCORE_IMC_FREERUNNING_BASE); } static struct intel_uncore_ops adl_uncore_imc_freerunning_ops = { @@ -1803,9 +1821,9 @@ static const struct attribute_group lnl_uncore_format_group = { .attrs = lnl_uncore_formats_attr, }; -static void lnl_uncore_hbo_init_box(struct intel_uncore_box *box) +static int lnl_uncore_hbo_init_box(struct intel_uncore_box *box) { - uncore_get_box_mmio_addr(box, LNL_UNCORE_HBO_BASE, + return uncore_get_box_mmio_addr(box, LNL_UNCORE_HBO_BASE, LNL_UNCORE_PCI_SAFBAR_OFFSET, LNL_UNCORE_HBO_OFFSET); } @@ -1829,14 +1847,16 @@ static struct intel_uncore_type lnl_uncore_hbo = { .format_group = &lnl_uncore_format_group, }; -static void lnl_uncore_sncu_init_box(struct intel_uncore_box *box) +static int lnl_uncore_sncu_init_box(struct intel_uncore_box *box) { - uncore_get_box_mmio_addr(box, LNL_UNCORE_SNCU_BASE, + int ret = uncore_get_box_mmio_addr(box, LNL_UNCORE_SNCU_BASE, LNL_UNCORE_PCI_SAFBAR_OFFSET, 0); - if (box->io_addr) + if (!ret) writel(ADL_UNCORE_IMC_CTL_INT, box->io_addr + LNL_UNCORE_GLOBAL_CTL); + + return ret; } static struct intel_uncore_ops lnl_uncore_sncu_ops = { @@ -1887,13 +1907,15 @@ static struct intel_uncore_type ptl_uncore_imc = { .mmio_map_size = 0xf00, }; -static void ptl_uncore_sncu_init_box(struct intel_uncore_box *box) +static int ptl_uncore_sncu_init_box(struct intel_uncore_box *box) { - intel_generic_uncore_mmio_init_box(box); + int ret = intel_generic_uncore_mmio_init_box(box); /* Clear the global freeze bit */ if (box->io_addr) writel(0, box->io_addr + PTL_UNCORE_GLOBAL_CTL_OFFSET); + + return ret; } static struct intel_uncore_ops ptl_uncore_sncu_ops = { diff --git a/arch/x86/events/intel/uncore_snbep.c b/arch/x86/events/intel/uncore_snbep.c index 334dc384b5b9..a97cd029db36 100644 --- a/arch/x86/events/intel/uncore_snbep.c +++ b/arch/x86/events/intel/uncore_snbep.c @@ -627,12 +627,12 @@ static u64 snbep_uncore_pci_read_counter(struct intel_uncore_box *box, struct pe return count; } -static void snbep_uncore_pci_init_box(struct intel_uncore_box *box) +static int snbep_uncore_pci_init_box(struct intel_uncore_box *box) { struct pci_dev *pdev = box->pci_dev; int box_ctl = uncore_pci_box_ctl(box); - pci_write_config_dword(pdev, box_ctl, SNBEP_PMON_BOX_CTL_INT); + return pci_write_config_dword(pdev, box_ctl, SNBEP_PMON_BOX_CTL_INT); } static void snbep_uncore_msr_disable_box(struct intel_uncore_box *box) @@ -680,12 +680,14 @@ static void snbep_uncore_msr_disable_event(struct intel_uncore_box *box, wrmsrq(hwc->config_base, hwc->config); } -static void snbep_uncore_msr_init_box(struct intel_uncore_box *box) +static int snbep_uncore_msr_init_box(struct intel_uncore_box *box) { unsigned msr = uncore_msr_box_ctl(box); if (msr) wrmsrq(msr, SNBEP_PMON_BOX_CTL_INT); + + return 0; } static struct attribute *snbep_uncore_formats_attr[] = { @@ -1507,18 +1509,21 @@ int snbep_uncore_pci_init(void) /* end of Sandy Bridge-EP uncore support */ /* IvyTown uncore support */ -static void ivbep_uncore_msr_init_box(struct intel_uncore_box *box) +static int ivbep_uncore_msr_init_box(struct intel_uncore_box *box) { unsigned msr = uncore_msr_box_ctl(box); if (msr) wrmsrq(msr, IVBEP_PMON_BOX_CTL_INT); + + return 0; } -static void ivbep_uncore_pci_init_box(struct intel_uncore_box *box) +static int ivbep_uncore_pci_init_box(struct intel_uncore_box *box) { struct pci_dev *pdev = box->pci_dev; - pci_write_config_dword(pdev, SNBEP_PCI_PMON_BOX_CTL, IVBEP_PMON_BOX_CTL_INT); + return pci_write_config_dword(pdev, SNBEP_PCI_PMON_BOX_CTL, + IVBEP_PMON_BOX_CTL_INT); } #define IVBEP_UNCORE_MSR_OPS_COMMON_INIT() \ @@ -2784,7 +2789,7 @@ static struct intel_uncore_type hswep_uncore_cbox = { /* * Write SBOX Initialization register bit by bit to avoid spurious #GPs */ -static void hswep_uncore_sbox_msr_init_box(struct intel_uncore_box *box) +static int hswep_uncore_sbox_msr_init_box(struct intel_uncore_box *box) { unsigned msr = uncore_msr_box_ctl(box); @@ -2798,6 +2803,8 @@ static void hswep_uncore_sbox_msr_init_box(struct intel_uncore_box *box) wrmsrq(msr, flags); } } + + return 0; } static struct intel_uncore_ops hswep_uncore_sbox_msr_ops = { @@ -4162,12 +4169,13 @@ static const struct attribute_group skx_upi_uncore_format_group = { .attrs = skx_upi_uncore_formats_attr, }; -static void skx_upi_uncore_pci_init_box(struct intel_uncore_box *box) +static int skx_upi_uncore_pci_init_box(struct intel_uncore_box *box) { struct pci_dev *pdev = box->pci_dev; __set_bit(UNCORE_BOX_FLAG_CTL_OFFS8, &box->flags); - pci_write_config_dword(pdev, SKX_UPI_PCI_PMON_BOX_CTL, IVBEP_PMON_BOX_CTL_INT); + return pci_write_config_dword(pdev, SKX_UPI_PCI_PMON_BOX_CTL, + IVBEP_PMON_BOX_CTL_INT); } static struct intel_uncore_ops skx_upi_uncore_pci_ops = { @@ -4323,12 +4331,13 @@ static struct intel_uncore_type skx_uncore_upi = { .cleanup_mapping = skx_upi_cleanup_mapping, }; -static void skx_m2m_uncore_pci_init_box(struct intel_uncore_box *box) +static int skx_m2m_uncore_pci_init_box(struct intel_uncore_box *box) { struct pci_dev *pdev = box->pci_dev; __set_bit(UNCORE_BOX_FLAG_CTL_OFFS8, &box->flags); - pci_write_config_dword(pdev, SKX_M2M_PCI_PMON_BOX_CTL, IVBEP_PMON_BOX_CTL_INT); + return pci_write_config_dword(pdev, SKX_M2M_PCI_PMON_BOX_CTL, + IVBEP_PMON_BOX_CTL_INT); } static struct intel_uncore_ops skx_m2m_uncore_pci_ops = { @@ -4831,13 +4840,13 @@ void snr_uncore_cpu_init(void) uncore_msr_uncores = snr_msr_uncores; } -static void snr_m2m_uncore_pci_init_box(struct intel_uncore_box *box) +static int snr_m2m_uncore_pci_init_box(struct intel_uncore_box *box) { struct pci_dev *pdev = box->pci_dev; int box_ctl = uncore_pci_box_ctl(box); __set_bit(UNCORE_BOX_FLAG_CTL_OFFS8, &box->flags); - pci_write_config_dword(pdev, box_ctl, IVBEP_PMON_BOX_CTL_INT); + return pci_write_config_dword(pdev, box_ctl, IVBEP_PMON_BOX_CTL_INT); } static struct intel_uncore_ops snr_m2m_uncore_pci_ops = { @@ -5010,17 +5019,22 @@ static int snr_uncore_mmio_map(struct intel_uncore_box *box, return 0; } -static void __snr_uncore_mmio_init_box(struct intel_uncore_box *box, +static int __snr_uncore_mmio_init_box(struct intel_uncore_box *box, unsigned int box_ctl, int mem_offset, unsigned int device) { - if (!snr_uncore_mmio_map(box, box_ctl, mem_offset, device)) + int ret; + + ret = snr_uncore_mmio_map(box, box_ctl, mem_offset, device); + if (!ret) writel(IVBEP_PMON_BOX_CTL_INT, box->io_addr); + + return ret; } -static void snr_uncore_mmio_init_box(struct intel_uncore_box *box) +static int snr_uncore_mmio_init_box(struct intel_uncore_box *box) { - __snr_uncore_mmio_init_box(box, uncore_mmio_box_ctl(box), + return __snr_uncore_mmio_init_box(box, uncore_mmio_box_ctl(box), SNR_IMC_MMIO_MEM0_OFFSET, SNR_MC_DEVICE_ID); } @@ -5637,14 +5651,14 @@ int icx_uncore_pci_init(void) return 0; } -static void icx_uncore_imc_init_box(struct intel_uncore_box *box) +static int icx_uncore_imc_init_box(struct intel_uncore_box *box) { unsigned int box_ctl = box->pmu->type->box_ctl + box->pmu->type->mmio_offset * (box->pmu->pmu_idx % ICX_NUMBER_IMC_CHN); int mem_offset = (box->pmu->pmu_idx / ICX_NUMBER_IMC_CHN) * ICX_IMC_MEM_STRIDE + SNR_IMC_MMIO_MEM0_OFFSET; - __snr_uncore_mmio_init_box(box, box_ctl, mem_offset, + return __snr_uncore_mmio_init_box(box, box_ctl, mem_offset, SNR_MC_DEVICE_ID); } @@ -5701,12 +5715,12 @@ static struct uncore_event_desc icx_uncore_imc_freerunning_events[] = { { /* end: all zeroes */ }, }; -static void icx_uncore_imc_freerunning_init_box(struct intel_uncore_box *box) +static int icx_uncore_imc_freerunning_init_box(struct intel_uncore_box *box) { int mem_offset = box->pmu->pmu_idx * ICX_IMC_MEM_STRIDE + SNR_IMC_MMIO_MEM0_OFFSET; - snr_uncore_mmio_map(box, uncore_mmio_box_ctl(box), + return snr_uncore_mmio_map(box, uncore_mmio_box_ctl(box), mem_offset, SNR_MC_DEVICE_ID); } @@ -6003,10 +6017,10 @@ static struct intel_uncore_type spr_uncore_mdf = { .name = "mdf", }; -static void spr_uncore_mmio_offs8_init_box(struct intel_uncore_box *box) +static int spr_uncore_mmio_offs8_init_box(struct intel_uncore_box *box) { __set_bit(UNCORE_BOX_FLAG_CTL_OFFS8, &box->flags); - intel_generic_uncore_mmio_init_box(box); + return intel_generic_uncore_mmio_init_box(box); } static struct intel_uncore_ops spr_uncore_mmio_offs8_ops = { @@ -6187,12 +6201,11 @@ static struct uncore_event_desc spr_uncore_imc_freerunning_events[] = { #define SPR_MC_DEVICE_ID 0x3251 -static void spr_uncore_imc_freerunning_init_box(struct intel_uncore_box *box) +static int spr_uncore_imc_freerunning_init_box(struct intel_uncore_box *box) { int mem_offset = box->pmu->pmu_idx * ICX_IMC_MEM_STRIDE + SNR_IMC_MMIO_MEM0_OFFSET; - - snr_uncore_mmio_map(box, uncore_mmio_box_ctl(box), - mem_offset, SPR_MC_DEVICE_ID); + return snr_uncore_mmio_map(box, uncore_mmio_box_ctl(box), + mem_offset, SPR_MC_DEVICE_ID); } static struct intel_uncore_ops spr_uncore_imc_freerunning_ops = { @@ -6881,20 +6894,24 @@ static unsigned int dmr_iio_freerunning_box_offsets[] = { 0x0, 0x8000, 0x18000, 0x20000 }; -static void dmr_uncore_freerunning_init_box(struct intel_uncore_box *box) +static int dmr_uncore_freerunning_init_box(struct intel_uncore_box *box) { struct intel_uncore_type *type = box->pmu->type; u64 mmio_base; if (box->pmu->pmu_idx >= type->num_boxes) - return; + return -ENODEV; mmio_base = DMR_IMH1_HIOP_MMIO_BASE; mmio_base += dmr_iio_freerunning_box_offsets[box->pmu->pmu_idx]; box->io_addr = ioremap(mmio_base, type->mmio_map_size); - if (!box->io_addr) + if (!box->io_addr) { pr_warn("perf uncore: Failed to ioremap for %s.\n", type->name); + return -ENOMEM; + } + + return 0; } static struct intel_uncore_ops dmr_uncore_freerunning_ops = { From 3012af7df3430788eddd30b3c6654d0a0a5f06c6 Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Thu, 11 Jun 2026 09:00:29 -0700 Subject: [PATCH 04/42] perf/x86/intel/uncore: Keep PCI PMUs working when MMIO/MSR setup fails uncore_event_cpu_online() returns -ENOMEM early when both the MSR and MMIO box allocations fail. This also aborts PCI uncore setup, even though PCI PMUs are independent of the MSR/MMIO paths. Remove the early return so PCI uncore setup always runs regardless of whether MSR or MMIO box allocation succeeds. Fixes: 3da04b8a00dd ("perf/x86/intel/uncore: Support MMIO type uncore blocks") Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Ian Rogers Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260611160033.66760-5-zide.chen@intel.com --- arch/x86/events/intel/uncore.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index 21c8ed1628cb..eae335df7634 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -1636,8 +1636,6 @@ static int uncore_event_cpu_online(unsigned int cpu) die = topology_logical_die_id(cpu); msr_ret = uncore_box_ref(uncore_msr_uncores, die, cpu); mmio_ret = uncore_box_ref(uncore_mmio_uncores, die, cpu); - if (msr_ret && mmio_ret) - return -ENOMEM; /* * Check if there is an online cpu in the package From ae7ca8796ddac708db592c5a68555414c451afcc Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Thu, 11 Jun 2026 09:00:30 -0700 Subject: [PATCH 05/42] perf/x86/intel/uncore: Factor out box setup code The PCI uncore PMU path already implements a lazy registration model: the PMU is registered when the first active box appears and unregistered when the last active box is removed. Factor this registration management into a shared helper, so the same code can be reused by the MSR and MMIO paths in later changes. No functional change intended. Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Ian Rogers Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260611160033.66760-6-zide.chen@intel.com --- arch/x86/events/intel/uncore.c | 40 ++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index eae335df7634..06ef89f6ccc2 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -1148,6 +1148,29 @@ uncore_pci_find_dev_pmu(struct pci_dev *pdev, const struct pci_device_id *ids) return pmu; } +static int uncore_box_setup(struct intel_uncore_pmu *pmu, + struct intel_uncore_box *box) +{ + int ret; + + uncore_box_init(box); + + /* First active box registers the pmu. */ + if (atomic_inc_return(&pmu->activeboxes) > 1) + return 0; + + ret = uncore_pmu_register(pmu); + if (ret) { + atomic_dec(&pmu->activeboxes); + goto err; + } + + return 0; +err: + uncore_box_exit(box); + return ret; +} + /* * Register the PMU for a PCI device * @pdev: The PCI device. @@ -1174,20 +1197,13 @@ static int uncore_pci_pmu_register(struct pci_dev *pdev, box->dieid = die; box->pci_dev = pdev; box->pmu = pmu; - uncore_box_init(box); - pmu->boxes[die] = box; - if (atomic_inc_return(&pmu->activeboxes) > 1) - return 0; - - /* First active box registers the pmu */ - ret = uncore_pmu_register(pmu); - if (ret) { - atomic_dec(&pmu->activeboxes); - pmu->boxes[die] = NULL; - uncore_box_exit(box); + ret = uncore_box_setup(pmu, box); + if (!ret) + pmu->boxes[die] = box; + else kfree(box); - } + return ret; } From 30c0a1095652275768a5de67188ff888d1f5d190 Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Thu, 11 Jun 2026 09:00:31 -0700 Subject: [PATCH 06/42] perf/x86/intel/uncore: Introduce PMU flags and broken state Replace the boolean 'registered' field in intel_uncore_pmu with an unsigned long 'flags' field, and add a PMU_BROKEN flag to track box setup failures. The broken flag is sticky, meaning it is cleared only by a module reload or system reboot. Broken PMUs are skipped in the CPU hotplug and box allocation paths. When any box fails to initialize, the PMU is marked broken. Broken PMUs reject new event assignments and skip future box setup attempts. If the PMU was already registered, it remains so to avoid disrupting in-flight events on other boxes. Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260611160033.66760-7-zide.chen@intel.com --- arch/x86/events/intel/uncore.c | 44 ++++++++++++++++++++++-------- arch/x86/events/intel/uncore.h | 13 ++++++++- arch/x86/events/intel/uncore_snb.c | 2 +- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index 06ef89f6ccc2..feb8c3b0076b 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -757,7 +757,7 @@ static int uncore_pmu_event_init(struct perf_event *event) pmu = uncore_event_to_pmu(event); /* no device found for this pmu */ - if (!pmu->registered) + if (!uncore_pmu_available(pmu)) return -ENOENT; /* Sampling not supported yet */ @@ -953,16 +953,18 @@ static int uncore_pmu_register(struct intel_uncore_pmu *pmu) ret = perf_pmu_register(&pmu->pmu, pmu->name, -1); if (!ret) - pmu->registered = true; + uncore_pmu_set_registered(pmu); return ret; } static void uncore_pmu_unregister(struct intel_uncore_pmu *pmu) { - if (!pmu->registered) + if (!uncore_pmu_registered(pmu)) return; perf_pmu_unregister(&pmu->pmu); - pmu->registered = false; + + /* Keep PMU_BROKEN_BIT sticky. */ + uncore_pmu_clear_registered(pmu); } static void uncore_free_boxes(struct intel_uncore_pmu *pmu) @@ -1153,7 +1155,12 @@ static int uncore_box_setup(struct intel_uncore_pmu *pmu, { int ret; - uncore_box_init(box); + if (uncore_pmu_broken(pmu)) + return -ENODEV; + + ret = uncore_box_init(box); + if (ret) + goto err; /* First active box registers the pmu. */ if (atomic_inc_return(&pmu->activeboxes) > 1) @@ -1167,6 +1174,16 @@ static int uncore_box_setup(struct intel_uncore_pmu *pmu, return 0; err: + /* + * If any box fails, mark the per-package PMU as broken regardless of + * whether it was registered or not. + * + * Don't decrement refcnt to avoid other in-die CPUs from trying to set + * up the PMU box again. + * + * Don't kfree box; MSR and MMIO boxes are freed at module exit only. + */ + uncore_pmu_set_broken(pmu); uncore_box_exit(box); return ret; } @@ -1190,8 +1207,10 @@ static int uncore_pci_pmu_register(struct pci_dev *pdev, return -EINVAL; box = uncore_alloc_box(type, NUMA_NO_NODE); - if (!box) + if (!box) { + uncore_pmu_set_broken(pmu); return -ENOMEM; + } atomic_inc(&box->refcnt); box->dieid = die; @@ -1507,7 +1526,8 @@ static void uncore_change_type_ctx(struct intel_uncore_type *type, int old_cpu, if (old_cpu < 0) { WARN_ON_ONCE(box->cpu != -1); - if (uncore_die_has_box(type, die, pmu->pmu_idx)) { + if (uncore_die_has_box(type, die, pmu->pmu_idx) && + !uncore_pmu_broken(pmu)) { box->cpu = new_cpu; cpumask_set_cpu(new_cpu, &pmu->cpu_mask); } @@ -1515,12 +1535,14 @@ static void uncore_change_type_ctx(struct intel_uncore_type *type, int old_cpu, } WARN_ON_ONCE(box->cpu != -1 && box->cpu != old_cpu); - box->cpu = -1; cpumask_clear_cpu(old_cpu, &pmu->cpu_mask); - if (new_cpu < 0) + if (new_cpu < 0) { + box->cpu = -1; continue; + } - if (!uncore_die_has_box(type, die, pmu->pmu_idx)) + /* An inactive box doesn't need migration. */ + if (box->cpu == -1) continue; uncore_pmu_cancel_hrtimer(box); perf_pmu_migrate_context(&pmu->pmu, old_cpu, new_cpu); @@ -1596,7 +1618,7 @@ static int allocate_boxes(struct intel_uncore_type **types, type = *types; pmu = type->pmus; for (i = 0; i < type->num_boxes; i++, pmu++) { - if (pmu->boxes[die]) + if (pmu->boxes[die] || uncore_pmu_broken(pmu)) continue; box = uncore_alloc_box(type, cpu_to_node(cpu)); if (!box) diff --git a/arch/x86/events/intel/uncore.h b/arch/x86/events/intel/uncore.h index d732b87be0a9..0adb477d9708 100644 --- a/arch/x86/events/intel/uncore.h +++ b/arch/x86/events/intel/uncore.h @@ -146,13 +146,24 @@ struct intel_uncore_pmu { struct pmu pmu; char name[UNCORE_PMU_NAME_LEN]; int pmu_idx; - bool registered; + unsigned long flags; atomic_t activeboxes; cpumask_t cpu_mask; struct intel_uncore_type *type; struct intel_uncore_box **boxes; }; +#define PMU_REGISTERED_BIT 0 +#define PMU_BROKEN_BIT 1 + +#define uncore_pmu_registered(pmu) test_bit(PMU_REGISTERED_BIT, &(pmu)->flags) +#define uncore_pmu_broken(pmu) test_bit(PMU_BROKEN_BIT, &(pmu)->flags) +#define uncore_pmu_available(pmu) (uncore_pmu_registered(pmu) && \ + !uncore_pmu_broken(pmu)) +#define uncore_pmu_set_registered(pmu) set_bit(PMU_REGISTERED_BIT, &(pmu)->flags) +#define uncore_pmu_set_broken(pmu) set_bit(PMU_BROKEN_BIT, &(pmu)->flags) +#define uncore_pmu_clear_registered(pmu) clear_bit(PMU_REGISTERED_BIT, &(pmu)->flags) + struct intel_uncore_extra_reg { raw_spinlock_t lock; u64 config, config1, config2; diff --git a/arch/x86/events/intel/uncore_snb.c b/arch/x86/events/intel/uncore_snb.c index c5347920541c..055131c508ff 100644 --- a/arch/x86/events/intel/uncore_snb.c +++ b/arch/x86/events/intel/uncore_snb.c @@ -940,7 +940,7 @@ static int snb_uncore_imc_event_init(struct perf_event *event) pmu = uncore_event_to_pmu(event); /* no device found for this pmu */ - if (!pmu->registered) + if (!uncore_pmu_available(pmu)) return -ENOENT; /* Sampling not supported yet */ From 174f0582e38abe03b88e15f04bfe58490f88cb19 Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Thu, 11 Jun 2026 09:00:32 -0700 Subject: [PATCH 07/42] perf/x86/intel/uncore: Fix uncore_box ref/unref ordering In uncore_event_cpu_online(), uncore_box_ref() was called before uncore_change_context(). uncore_box_ref() gates on box->cpu >= 0, but box->cpu is still -1 at that point because uncore_change_context() has not run yet. As a result, the box is never initialized on the first CPU to come online in a die, leaving it permanently uninitialized in the single-CPU-per-die case. Thus, box->refcnt is one count below the true value, and in the CPU offline path, the box will be torn down on the second-to-last CPU. In uncore_event_cpu_offline(), uncore_box_unref() was called after uncore_change_context(), so box->cpu is already -1 when the collector CPU goes offline, which prevents it from tearing down the box. Fix by swapping the call order in both paths so that uncore_box_{ref,unref}() runs at the point where box->cpu reflects the correct context. Move allocate_boxes() out of uncore_box_ref() to enable this reordering. Fixes: c74443d92f68 ("perf/x86/uncore: Support per PMU cpumask") Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Ian Rogers Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260611160033.66760-8-zide.chen@intel.com --- arch/x86/events/intel/uncore.c | 50 ++++++++++++++++------------------ 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index feb8c3b0076b..b9ac2f7d31ca 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -1580,9 +1580,15 @@ static int uncore_event_cpu_offline(unsigned int cpu) { int die, target; + /* Clear the references */ + die = topology_logical_die_id(cpu); + uncore_box_unref(uncore_msr_uncores, die); + uncore_box_unref(uncore_mmio_uncores, die); + /* Check if exiting cpu is used for collecting uncore events */ if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask)) - goto unref; + return 0; + /* Find a new cpu to collect uncore events */ target = cpumask_any_but(topology_die_cpumask(cpu), cpu); @@ -1595,16 +1601,10 @@ static int uncore_event_cpu_offline(unsigned int cpu) uncore_change_context(uncore_msr_uncores, cpu, target); uncore_change_context(uncore_mmio_uncores, cpu, target); uncore_change_context(uncore_pci_uncores, cpu, target); - -unref: - /* Clear the references */ - die = topology_logical_die_id(cpu); - uncore_box_unref(uncore_msr_uncores, die); - uncore_box_unref(uncore_mmio_uncores, die); return 0; } -static int allocate_boxes(struct intel_uncore_type **types, +static void allocate_boxes(struct intel_uncore_type **types, unsigned int die, unsigned int cpu) { struct intel_uncore_box *box, *tmp; @@ -1621,8 +1621,10 @@ static int allocate_boxes(struct intel_uncore_type **types, if (pmu->boxes[die] || uncore_pmu_broken(pmu)) continue; box = uncore_alloc_box(type, cpu_to_node(cpu)); - if (!box) + if (!box) { + uncore_pmu_set_broken(pmu); goto cleanup; + } box->pmu = pmu; box->dieid = die; list_add(&box->active_list, &allocated); @@ -1633,14 +1635,13 @@ static int allocate_boxes(struct intel_uncore_type **types, list_del_init(&box->active_list); box->pmu->boxes[die] = box; } - return 0; + return; cleanup: list_for_each_entry_safe(box, tmp, &allocated, active_list) { list_del_init(&box->active_list); kfree(box); } - return -ENOMEM; } static int uncore_box_ref(struct intel_uncore_type **types, @@ -1649,11 +1650,7 @@ static int uncore_box_ref(struct intel_uncore_type **types, struct intel_uncore_type *type; struct intel_uncore_pmu *pmu; struct intel_uncore_box *box; - int i, ret; - - ret = allocate_boxes(types, die, cpu); - if (ret) - return ret; + int i; for (; *types; types++) { type = *types; @@ -1669,27 +1666,26 @@ static int uncore_box_ref(struct intel_uncore_type **types, static int uncore_event_cpu_online(unsigned int cpu) { - int die, target, msr_ret, mmio_ret; + int die, target; die = topology_logical_die_id(cpu); - msr_ret = uncore_box_ref(uncore_msr_uncores, die, cpu); - mmio_ret = uncore_box_ref(uncore_mmio_uncores, die, cpu); + allocate_boxes(uncore_msr_uncores, die, cpu); + allocate_boxes(uncore_mmio_uncores, die, cpu); /* * Check if there is an online cpu in the package * which collects uncore events already. */ target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu)); - if (target < nr_cpu_ids) - return 0; - - cpumask_set_cpu(cpu, &uncore_cpu_mask); - - if (!msr_ret) + if (target >= nr_cpu_ids) { + cpumask_set_cpu(cpu, &uncore_cpu_mask); uncore_change_context(uncore_msr_uncores, -1, cpu); - if (!mmio_ret) uncore_change_context(uncore_mmio_uncores, -1, cpu); - uncore_change_context(uncore_pci_uncores, -1, cpu); + uncore_change_context(uncore_pci_uncores, -1, cpu); + } + + uncore_box_ref(uncore_msr_uncores, die, cpu); + uncore_box_ref(uncore_mmio_uncores, die, cpu); return 0; } From b25813b17944b4df532246cddae82201fb880481 Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Thu, 11 Jun 2026 09:00:33 -0700 Subject: [PATCH 08/42] perf/x86/intel/uncore: Implement lazy setup for MSR/MMIO PMUs MSR and MMIO uncore PMUs are currently registered at module init time and appear in sysfs even when no PMU boxes are functional. Apply the same lazy registration model used by PCI uncore PMUs: the PMU is registered when the first box is successfully initialized, and unregistered when the last box exits. If a box fails to initialize on a subsequent die, the PMU is marked broken but remains registered to avoid disrupting any in-flight perf events. Box allocation and freeing remain at module init/exit time to avoid repeated kfree/alloc cycles across CPU offline/online events. Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260611160033.66760-9-zide.chen@intel.com --- arch/x86/events/intel/uncore.c | 78 +++++++--------------------------- arch/x86/events/intel/uncore.h | 6 +++ 2 files changed, 22 insertions(+), 62 deletions(-) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index b9ac2f7d31ca..9f8a80c9dcb6 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -1570,8 +1570,13 @@ static void uncore_box_unref(struct intel_uncore_type **types, int die) pmu = type->pmus; for (i = 0; i < type->num_boxes; i++, pmu++) { box = pmu->boxes[die]; - if (box && box->cpu >= 0 && atomic_dec_return(&box->refcnt) == 0) + if (box && box->cpu >= 0 && + atomic_dec_return(&box->refcnt) == 0) { + if (uncore_box_active(box) && + atomic_dec_return(&pmu->activeboxes) == 0) + uncore_pmu_unregister(pmu); uncore_box_exit(box); + } } } } @@ -1658,7 +1663,7 @@ static int uncore_box_ref(struct intel_uncore_type **types, for (i = 0; i < type->num_boxes; i++, pmu++) { box = pmu->boxes[die]; if (box && box->cpu >= 0 && atomic_inc_return(&box->refcnt) == 1) - uncore_box_init(box); + uncore_box_setup(pmu, box); } } return 0; @@ -1689,67 +1694,12 @@ static int uncore_event_cpu_online(unsigned int cpu) return 0; } -static int __init type_pmu_register(struct intel_uncore_type *type) +static int __init uncore_pmu_types_init(struct intel_uncore_type **types) { - int i, ret; - - for (i = 0; i < type->num_boxes; i++) { - ret = uncore_pmu_register(&type->pmus[i]); - if (ret) - return ret; - } - return 0; -} - -static int __init uncore_msr_pmus_register(void) -{ - struct intel_uncore_type **types = uncore_msr_uncores; - int ret; - - for (; *types; types++) { - ret = type_pmu_register(*types); - if (ret) - return ret; - } - return 0; -} - -static int __init uncore_cpu_init(void) -{ - int ret; - - ret = uncore_types_init(uncore_msr_uncores); + int ret = uncore_types_init(types); if (ret) - goto err; + uncore_types_exit(types); - ret = uncore_msr_pmus_register(); - if (ret) - goto err; - return 0; -err: - uncore_types_exit(uncore_msr_uncores); - uncore_msr_uncores = empty_uncore; - return ret; -} - -static int __init uncore_mmio_init(void) -{ - struct intel_uncore_type **types = uncore_mmio_uncores; - int ret; - - ret = uncore_types_init(types); - if (ret) - goto err; - - for (; *types; types++) { - ret = type_pmu_register(*types); - if (ret) - goto err; - } - return 0; -err: - uncore_types_exit(uncore_mmio_uncores); - uncore_mmio_uncores = empty_uncore; return ret; } @@ -2050,12 +2000,16 @@ static int __init intel_uncore_init(void) if (uncore_init->cpu_init) { uncore_init->cpu_init(); - cret = uncore_cpu_init(); + cret = uncore_pmu_types_init(uncore_msr_uncores); + if (cret) + uncore_msr_uncores = empty_uncore; } if (uncore_init->mmio_init) { uncore_init->mmio_init(); - mret = uncore_mmio_init(); + mret = uncore_pmu_types_init(uncore_mmio_uncores); + if (mret) + uncore_mmio_uncores = empty_uncore; } if (cret && pret && mret) { diff --git a/arch/x86/events/intel/uncore.h b/arch/x86/events/intel/uncore.h index 0adb477d9708..c8dfa2d21bfd 100644 --- a/arch/x86/events/intel/uncore.h +++ b/arch/x86/events/intel/uncore.h @@ -568,6 +568,12 @@ static inline u64 uncore_read_counter(struct intel_uncore_box *box, return box->pmu->type->ops->read_counter(box, event); } +static inline bool uncore_box_active(struct intel_uncore_box *box) +{ + return (!box->pmu->type->ops->init_box || + test_bit(UNCORE_BOX_FLAG_INITIALIZED, &box->flags)); +} + static inline int uncore_box_init(struct intel_uncore_box *box) { int ret = 0; From 8767b4d73018bd3143f4c55b672064fad292f11b Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Tue, 16 Jun 2026 12:46:47 +0800 Subject: [PATCH 09/42] perf/x86/intel: Remove anythread_deprecated bit from perf_capabilities AnyThread mode deprecation is enumerated by CPUID.0AH:EDX[15] instead of PERF_CAPABILITIES MSR. It's not a good practice to define a bit to represent "anythread deprecation" in perf_capabilities. It leads to the anythread_deprecated bit could be overwritten by the real value of PERF_CAPABILITIES MSR, just like the below code in update_pmu_cap() does. if (!intel_pmu_broken_perf_cap()) { /* Perf Metric (Bit 15) and PEBS via PT (Bit 16) are hybrid enumeration */ rdmsrq(MSR_IA32_PERF_CAPABILITIES, hybrid(pmu, intel_cap).capabilities); } It leads to the anythread_deprecated bit is cleared to 0 and the "any" attribute is incorrectly shown in the /sys/devices/cpu/format/ folder on these support Perfmon v6 platforms, like Clearwater Forest. $ grep . /sys/devices/cpu/format/* /sys/devices/cpu/format/acr_mask:config2:0-63 /sys/devices/cpu/format/any:config:21 /sys/devices/cpu/format/cmask:config:24-31 So remove the anythread_deprecated bit from perf_capabilities structure and directly depends on CPUID.0AH:EDX[15] to judge if anythread is deprecated. Fixes: cadbaa039b99 ("perf/x86/intel: Make anythread filter support conditional") Reported-by: Namhyung Kim Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Zide Chen Reviewed-by: Thomas Falcon Acked-by: Namhyung Kim Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260616044654.3468742-2-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 10 +++------- arch/x86/events/perf_event.h | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 2b35483e2b70..465c414f145d 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -7947,12 +7947,6 @@ __init int intel_pmu_init(void) x86_add_quirk(intel_arch_events_quirk); /* Install first, so it runs last */ - if (version >= 5) { - x86_pmu.intel_cap.anythread_deprecated = edx.split.anythread_deprecated; - if (x86_pmu.intel_cap.anythread_deprecated) - pr_cont(" AnyThread deprecated, "); - } - /* The perf side of core PMU is ready to support the mediated vPMU. */ x86_get_pmu(smp_processor_id())->capabilities |= PERF_PMU_CAP_MEDIATED_VPMU; @@ -8829,8 +8823,10 @@ __init int intel_pmu_init(void) &x86_pmu.intel_ctrl); /* AnyThread may be deprecated on arch perfmon v5 or later */ - if (x86_pmu.intel_cap.anythread_deprecated) + if (version >= 5 && edx.split.anythread_deprecated) { x86_pmu.format_attrs = intel_arch_formats_attr; + pr_cont("AnyThread deprecated, "); + } intel_pmu_check_event_constraints_all(NULL); diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index eae24bb35dc1..5902a297daa1 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -668,7 +668,7 @@ union perf_capabilities { u64 perf_metrics:1; u64 pebs_output_pt_available:1; u64 pebs_timing_info:1; - u64 anythread_deprecated:1; + u64 __reserved:1; u64 rdpmc_metrics_clear:1; }; u64 capabilities; From 3c4ec9b2a5db56b60127bfaf933ecdeea7a1f10a Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Tue, 16 Jun 2026 12:46:48 +0800 Subject: [PATCH 10/42] perf/x86/intel: Keep cap_user_rdpmc in sync with RDPMC user-disable state After introducing the RDPMC user disable feature, user-space RDPMC may return 0 instead of the actual event count. This creates an inconsistency with cap_user_rdpmc, where cap_user_rdpmc is set, but user-space RDPMC only returns 0. To accurately represent the user-space RDPMC capability, update cap_user_rdpmc (depending on PERF_EVENT_FLAG_USER_READ_CNT) according to the RDPMC user disable state. If RDPMC user disable is enabled, cap_user_rdpmc is updated to false eventually, allowing user-space programs to fall back to the read() syscall to obtain the real event count. Because PERF_EVENT_FLAG_USER_READ_CNT is evaluated in x86_pmu_event_init(), move intel_pmu_update_rdpmc_user_disable() earlier into intel_pmu_hw_config(). This ensures that the user-disable state is updated before updating PERF_EVENT_FLAG_USER_READ_CNT. Note that since event->ctx is not yet assigned at this stage, use the PERF_ATTACH_TASK flag to detect whether the event is task-attached. While at it, fix the indentation of x86_pmu_has_rdpmc_user_disable() to adhere to the kernel coding style. Fixes: 59af95e028d4 ("perf/x86/intel: Add support for rdpmc user disable feature") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260616044654.3468742-3-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 3 ++- arch/x86/events/intel/core.c | 6 +++--- arch/x86/events/perf_event.h | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index d1af33d96d0a..af0b67ffb43d 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -2539,7 +2539,8 @@ static int x86_pmu_event_init(struct perf_event *event) } if (READ_ONCE(x86_pmu.attr_rdpmc) && - !(event->hw.flags & PERF_X86_EVENT_LARGE_PEBS)) + !(event->hw.flags & PERF_X86_EVENT_LARGE_PEBS) && + !(event->hw.config & ARCH_PERFMON_EVENTSEL_RDPMC_USER_DISABLE)) event->hw.flags |= PERF_EVENT_FLAG_USER_READ_CNT; return err; diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 465c414f145d..5116b15438a2 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -3533,7 +3533,7 @@ static void intel_pmu_update_rdpmc_user_disable(struct perf_event *event) */ if (x86_pmu.attr_rdpmc == X86_USER_RDPMC_ALWAYS_ENABLE || (x86_pmu.attr_rdpmc == X86_USER_RDPMC_CONDITIONAL_ENABLE && - event->ctx->task)) + (event->attach_state & PERF_ATTACH_TASK))) event->hw.config &= ~ARCH_PERFMON_EVENTSEL_RDPMC_USER_DISABLE; else event->hw.config |= ARCH_PERFMON_EVENTSEL_RDPMC_USER_DISABLE; @@ -3547,8 +3547,6 @@ static void intel_pmu_enable_event(struct perf_event *event) struct hw_perf_event *hwc = &event->hw; int idx = hwc->idx; - intel_pmu_update_rdpmc_user_disable(event); - if (unlikely(event->attr.precise_ip)) static_call(x86_pmu_pebs_enable)(event); @@ -5147,6 +5145,8 @@ static int intel_pmu_hw_config(struct perf_event *event) leader->hw.flags |= PERF_X86_EVENT_ACR; } + intel_pmu_update_rdpmc_user_disable(event); + if ((event->attr.type == PERF_TYPE_HARDWARE) || (event->attr.type == PERF_TYPE_HW_CACHE)) return 0; diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 5902a297daa1..a8afea8d38f0 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1344,7 +1344,7 @@ static inline u64 x86_pmu_get_event_config(struct perf_event *event) static inline bool x86_pmu_has_rdpmc_user_disable(struct pmu *pmu) { return !!(hybrid(pmu, config_mask) & - ARCH_PERFMON_EVENTSEL_RDPMC_USER_DISABLE); + ARCH_PERFMON_EVENTSEL_RDPMC_USER_DISABLE); } extern struct event_constraint emptyconstraint; From 170cc6b02e3d5203ccaa49ffea1ee5a7ab08c885 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Tue, 16 Jun 2026 12:46:49 +0800 Subject: [PATCH 11/42] perf/x86/intel: Fallback to sw branch type decoding if no hw decoding intel_pmu_lbr_filter() currently assumes arch-LBR provides hardware branch-type decoding and skips software decoding on that path. However, arch-LBR may not always expose branch-type information. In that case, treating entries as hardware-decoded can misclassify sampled branches (for example, defaulting to JCC), which breaks branch-type filtering results. Fix this by using software branch-type decoding when hardware branch-type decoding is unavailable (that is, when x86_lbr_type is not enabled). This keeps branch classification and filtering behavior correct across arch-LBR configurations. Since x86_lbr_type is only set to true when arch-LBR is supported, it's unnecessary to check if the CPU has the arch-LBR feature. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260616044654.3468742-4-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/lbr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/events/intel/lbr.c b/arch/x86/events/intel/lbr.c index cae2e02fe6cc..688d1dfacbb1 100644 --- a/arch/x86/events/intel/lbr.c +++ b/arch/x86/events/intel/lbr.c @@ -1232,7 +1232,7 @@ intel_pmu_lbr_filter(struct cpu_hw_events *cpuc) * Doesn't support OTHER_BRANCH decoding for now. * OTHER_BRANCH branch type still rely on software decoding. */ - if (static_cpu_has(X86_FEATURE_ARCH_LBR) && + if (static_branch_likely(&x86_lbr_type) && type <= ARCH_LBR_BR_TYPE_KNOWN_MAX) { to_plm = kernel_ip(to) ? X86_BR_KERNEL : X86_BR_USER; type = arch_lbr_br_type_map[type] | to_plm; From e2b0575900ff72aa82748af96e7bd564ade5157a Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Tue, 16 Jun 2026 12:46:50 +0800 Subject: [PATCH 12/42] perf/x86/intel: Fix kernel address leakages in LBR stack Before Arch LBR gained CPL filtering support, a user-only branch stack could still contain kernel addresses. As a result, kernel branch records may be exposed to user space even when PERF_SAMPLE_BRANCH_USER is requested. For example, on Intel Tiger Lake, the following command can still report SYSRET/ERET entries with kernel-space from addresses: $ ./perf record -e cycles:p -o - --branch-filter any,save_type,u -- \ ./perf bench syscall basic --loop 1000 | \ ./perf script -i - --fields brstack|tr ' ' '\n'| \ grep -E '0x[89a-f][0-9a-f]{15}' Total time: 0.000 [sec] 0.219000 usecs/op 4,566,210 ops/sec [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.551 MB - ] 0xffffffff93c001c8/0x7f12a2b1d647/P/-/-/16959/SYSRET/- 0xffffffff93c001c8/0x7f12a2b1d5c2/P/-/-/17535/SYSRET/- 0xffffffff93c01928/0x7f12a2861000/P/-/-/6719/ERET/- 0xffffffff93c01928/0x7f12a297a000/P/-/-/8575/ERET/- The problem is that intel_pmu_lbr_filter() does not fully validate the privilege level of sampled entries. It filters some mismatches based on the branch type and the to address, but it does not reject entries whose from address violates the requested branch privilege filter. Fix this by extending software filtering to validate both from and to addresses against br_sel. Any LBR entry contains kernel address does not match the requested user filter is dropped. This prevents kernel addresses from appearing in user-only branch stacks. Fixes: 47125db27e47 ("perf/x86/intel/lbr: Support Architectural LBR") Reported-by: Ian Rogers Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260616044654.3468742-5-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/lbr.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/arch/x86/events/intel/lbr.c b/arch/x86/events/intel/lbr.c index 688d1dfacbb1..f8fadb0b16a4 100644 --- a/arch/x86/events/intel/lbr.c +++ b/arch/x86/events/intel/lbr.c @@ -1213,7 +1213,7 @@ intel_pmu_lbr_filter(struct cpu_hw_events *cpuc) { u64 from, to; int br_sel = cpuc->br_sel; - int i, j, type, to_plm; + int i, j, type, from_plm, to_plm; bool compress = false; /* if sampling all branches, then nothing to filter */ @@ -1245,8 +1245,14 @@ intel_pmu_lbr_filter(struct cpu_hw_events *cpuc) type |= X86_BR_NO_TX; } - /* if type does not correspond, then discard */ - if (type == X86_BR_NONE || (br_sel & type) != type) { + from_plm = kernel_ip(from) ? X86_BR_KERNEL : X86_BR_USER; + /* + * If type does not correspond, then discard. + * Specifically reject entries whose from address is in + * kernel space when only X86_BR_USER is requested. + */ + if (type == X86_BR_NONE || (br_sel & type) != type || + (!(br_sel & X86_BR_KERNEL) && (from_plm & X86_BR_KERNEL))) { cpuc->lbr_entries[i].from = 0; compress = true; } From 01c153956b4436ead05a529dae56abc0ef58beac Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Tue, 16 Jun 2026 12:46:51 +0800 Subject: [PATCH 13/42] perf/x86/intel: Validate the return value of intel_pmu_init_hybrid() The intel_pmu_init_hybrid() function allocates memory for the x86_pmu.hybrid_pmu[] array. If this allocation fails under memory pressure, hybrid PMU initialization will fail. Currently, the caller does not check the return value of intel_pmu_init_hybrid(). This can lead to a null-pointer dereference or invalid memory access when attempting to use the uninitialized array, potentially triggering a system panic. Fix this by validating the return value of intel_pmu_init_hybrid(). Additionally, reset x86_pmu.num_hybrid_pmus to 0 on failure, and defer intel_pmu_arch_lbr_init() until after hybrid PMU initialization succeeds. This reordering avoids the need to explicitly destroy the kmem cache if the memory allocation fails. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260616044654.3468742-6-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 44 ++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 5116b15438a2..b39c6ce0efb5 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -7679,8 +7679,10 @@ static __always_inline int intel_pmu_init_hybrid(enum hybrid_pmu_type pmus) x86_pmu.num_hybrid_pmus = hweight_long(pmus_mask); x86_pmu.hybrid_pmu = kzalloc_objs(struct x86_hybrid_pmu, x86_pmu.num_hybrid_pmus); - if (!x86_pmu.hybrid_pmu) + if (!x86_pmu.hybrid_pmu) { + x86_pmu.num_hybrid_pmus = 0; return -ENOMEM; + } static_branch_enable(&perf_is_hybrid); x86_pmu.filter = intel_pmu_filter; @@ -7863,14 +7865,14 @@ __init int intel_pmu_init(void) struct attribute **td_attr = &empty_attrs; struct attribute **mem_attr = &empty_attrs; struct attribute **tsx_attr = &empty_attrs; + struct x86_hybrid_pmu *pmu; + unsigned int fixed_mask; union cpuid10_edx edx; union cpuid10_eax eax; union cpuid10_ebx ebx; - unsigned int fixed_mask; + int version, i, ret; bool pmem = false; - int version, i; char *name; - struct x86_hybrid_pmu *pmu; /* Architectural Perfmon was introduced starting with Core "Yonah" */ if (!cpu_has(&boot_cpu_data, X86_FEATURE_ARCH_PERFMON)) { @@ -7940,9 +7942,6 @@ __init int intel_pmu_init(void) x86_pmu.lbr_read = intel_pmu_lbr_read_32; } - if (boot_cpu_has(X86_FEATURE_ARCH_LBR)) - intel_pmu_arch_lbr_init(); - intel_pebs_init(); x86_add_quirk(intel_arch_events_quirk); /* Install first, so it runs last */ @@ -8540,7 +8539,9 @@ __init int intel_pmu_init(void) * * Initialize the common PerfMon capabilities here. */ - intel_pmu_init_hybrid(hybrid_big_small); + ret = intel_pmu_init_hybrid(hybrid_big_small); + if (ret) + return ret; x86_pmu.pebs_latency_data = grt_latency_data; x86_pmu.get_event_constraints = adl_get_event_constraints; @@ -8598,7 +8599,9 @@ __init int intel_pmu_init(void) case INTEL_METEORLAKE: case INTEL_METEORLAKE_L: case INTEL_ARROWLAKE_U: - intel_pmu_init_hybrid(hybrid_big_small); + ret = intel_pmu_init_hybrid(hybrid_big_small); + if (ret) + return ret; x86_pmu.pebs_latency_data = cmt_latency_data; x86_pmu.get_event_constraints = mtl_get_event_constraints; @@ -8629,7 +8632,9 @@ __init int intel_pmu_init(void) pr_cont("Pantherlake Hybrid events, "); name = "pantherlake_hybrid"; - intel_pmu_init_hybrid(hybrid_big_small); + ret = intel_pmu_init_hybrid(hybrid_big_small); + if (ret) + return ret; /* Initialize big core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; @@ -8644,7 +8649,9 @@ __init int intel_pmu_init(void) pr_cont("Arrowlake Hybrid events, "); name = "arrowlake_hybrid"; - intel_pmu_init_hybrid(hybrid_big_small); + ret = intel_pmu_init_hybrid(hybrid_big_small); + if (ret) + return ret; /* Initialize big core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; @@ -8661,7 +8668,9 @@ __init int intel_pmu_init(void) pr_cont("Lunarlake Hybrid events, "); name = "lunarlake_hybrid"; - intel_pmu_init_hybrid(hybrid_big_small); + ret = intel_pmu_init_hybrid(hybrid_big_small); + if (ret) + return ret; /* Initialize big core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; @@ -8686,7 +8695,9 @@ __init int intel_pmu_init(void) break; case INTEL_ARROWLAKE_H: - intel_pmu_init_hybrid(hybrid_big_small_tiny); + ret = intel_pmu_init_hybrid(hybrid_big_small_tiny); + if (ret) + return ret; x86_pmu.pebs_latency_data = arl_h_latency_data; x86_pmu.get_event_constraints = arl_h_get_event_constraints; @@ -8721,7 +8732,9 @@ __init int intel_pmu_init(void) case INTEL_NOVALAKE_L: pr_cont("Novalake Hybrid events, "); name = "novalake_hybrid"; - intel_pmu_init_hybrid(hybrid_big_small); + ret = intel_pmu_init_hybrid(hybrid_big_small); + if (ret) + return ret; x86_pmu.pebs_latency_data = nvl_latency_data; x86_pmu.get_event_constraints = mtl_get_event_constraints; @@ -8828,6 +8841,9 @@ __init int intel_pmu_init(void) pr_cont("AnyThread deprecated, "); } + if (boot_cpu_has(X86_FEATURE_ARCH_LBR)) + intel_pmu_arch_lbr_init(); + intel_pmu_check_event_constraints_all(NULL); /* From a6b5fbc33172509fbe991358d718617a8e33ea7e Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Tue, 16 Jun 2026 12:46:52 +0800 Subject: [PATCH 14/42] perf/x86/intel: Drop fixed-counter PEBS constraints for baseline PEBS On Sapphire Rapids (SPR) guests where pebs_baseline is not advertised, running command $ ./perf record -e cpu/event=0x00,umask=0x01,i\ name=INST_RETIRED.PREC_DIST/p -c 10000 sleep 1 can trigger: unchecked MSR access error: WRMSR to 0x3f1 ... in\ intel_pmu_pebs_enable_all() This occurs because SPR-specific PEBS constraints allow fixed-counter scheduling (for example, INST_RETIRED.PREC_DIST on fixed counter 0). In guests lacking pebs_baseline, KVM does not support PEBS sampling on fixed counters, so enabling such events reaches an invalid MSR programming path. Starting with Icelake, regardless of whether Extended PEBS or Architectural PEBS is in use, all counters (including fixed counters) support PEBS sampling, and the PMU_FL_PEBS_ALL flag is set by default. As long as PMU_FL_PEBS_ALL is set, constraint lookup automatically falls back to the corresponding non-PEBS constraints if no matching entry is found in the PEBS constraints table. Since non-PEBS event constraints already contain the same fixed-counter constraints, it is safe to remove these fixed-counter entries from the PEBS constraints table. The fallback mechanism will ensure that fixed PEBS events are scheduled onto the correct fixed counters. So, directly drop the fixed-counter entries from the PEBS constraint table. Without pebs_baseline, these fixed-counter PEBS events will now resolve to empty constraints and will not be scheduled or enabled, thereby avoiding the warning and bypassing the broken guest PEBS path. Reported-by: Yi Lai Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260616044654.3468742-7-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 91a093d8cf2e..e86e4ba91e1b 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1444,10 +1444,6 @@ struct event_constraint intel_skl_pebs_event_constraints[] = { }; struct event_constraint intel_icl_pebs_event_constraints[] = { - INTEL_FLAGS_UEVENT_CONSTRAINT(0x01c0, 0x100000000ULL), /* old INST_RETIRED.PREC_DIST */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x0100, 0x100000000ULL), /* INST_RETIRED.PREC_DIST */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x0400, 0x800000000ULL), /* SLOTS */ - INTEL_PLD_CONSTRAINT(0x1cd, 0xff), /* MEM_TRANS_RETIRED.LOAD_LATENCY */ INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x11d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_LOADS */ INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x12d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_STORES */ @@ -1470,9 +1466,6 @@ struct event_constraint intel_icl_pebs_event_constraints[] = { }; struct event_constraint intel_glc_pebs_event_constraints[] = { - INTEL_FLAGS_UEVENT_CONSTRAINT(0x100, 0x100000000ULL), /* INST_RETIRED.PREC_DIST */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x0400, 0x800000000ULL), - INTEL_FLAGS_EVENT_CONSTRAINT(0xc0, 0xfe), INTEL_PLD_CONSTRAINT(0x1cd, 0xfe), INTEL_PSD_CONSTRAINT(0x2cd, 0x1), @@ -1497,9 +1490,6 @@ struct event_constraint intel_glc_pebs_event_constraints[] = { }; struct event_constraint intel_lnc_pebs_event_constraints[] = { - INTEL_FLAGS_UEVENT_CONSTRAINT(0x100, 0x100000000ULL), /* INST_RETIRED.PREC_DIST */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x0400, 0x800000000ULL), - INTEL_FLAGS_UEVENT_CONSTRAINT(0x012a, 0x1), /* OCR.* events */ INTEL_FLAGS_UEVENT_CONSTRAINT(0x012b, 0x1), /* OCR.* events */ @@ -1531,9 +1521,6 @@ struct event_constraint intel_lnc_pebs_event_constraints[] = { }; struct event_constraint intel_pnc_pebs_event_constraints[] = { - INTEL_FLAGS_UEVENT_CONSTRAINT(0x100, 0x100000000ULL), /* INST_RETIRED.PREC_DIST */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x0400, 0x800000000ULL), - INTEL_HYBRID_LDLAT_CONSTRAINT(0x1cd, 0xfc), INTEL_HYBRID_STLAT_CONSTRAINT(0x2cd, 0x3), INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x11d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_LOADS */ From 166f10836a653dfa280d4335603b52f685b8b1ef Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Tue, 16 Jun 2026 12:46:53 +0800 Subject: [PATCH 15/42] perf/core: Fix kernel register info leak via hardware skid An unprivileged hardware perf event using exclude_kernel=1 can leak kernel register data to user space via PERF_SAMPLE_REGS_INTR or PERF_SAMPLE_IP. Due to hardware skid, a PMI may trigger after the CPU has already entered kernel space (Ring 0), bypassing the perf_allow_kernel() privilege barrier. This security vulnerability is severely exacerbated by upcoming support for SIMD register sampling via XSAVES, which could expose sensitive kernel FPU states (such as active cryptographic keys). Fix this by ensuring that sampled register data is dropped if the event's exclude_kernel attribute is set but the PMI catches the CPU in kernel mode. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/all/20260529085613.CCAFB1F00893@smtp.kernel.org/ Link: https://patch.msgid.link/20260616044654.3468742-8-dapeng1.mi@linux.intel.com --- kernel/events/core.c | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 954c36e28101..dd3bd9cf1986 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7791,10 +7791,20 @@ unsigned long perf_misc_flags(struct perf_event *event, unsigned long perf_instruction_pointer(struct perf_event *event, struct pt_regs *regs) { - if (should_sample_guest(event)) - return perf_guest_get_ip(); + /* + * Hardware skid can lead to a scenario where a PMI is + * delivered after the CPU has already entered kernel mode. + * In that case, user-space sampling must not expose kernel + * register state. + */ + if (should_sample_guest(event)) { + return event->attr.exclude_kernel && + !(perf_guest_state() & PERF_GUEST_USER) ? + 0 : perf_guest_get_ip(); + } - return perf_arch_instruction_pointer(regs); + return event->attr.exclude_kernel && !user_mode(regs) ? + 0 : perf_arch_instruction_pointer(regs); } static void @@ -7828,10 +7838,22 @@ static void perf_sample_regs_user(struct perf_regs *regs_user, } static void perf_sample_regs_intr(struct perf_regs *regs_intr, - struct pt_regs *regs) + struct pt_regs *regs, + bool exclude_kernel) { - regs_intr->regs = regs; - regs_intr->abi = perf_reg_abi(current); + /* + * Hardware skid can lead to a scenario where a PMI is + * delivered after the CPU has already entered kernel mode. + * In that case, user-space sampling must not expose kernel + * register state. + */ + if (exclude_kernel && !user_mode(regs)) { + regs_intr->abi = PERF_SAMPLE_REGS_ABI_NONE; + regs_intr->regs = NULL; + } else { + regs_intr->regs = regs; + regs_intr->abi = perf_reg_abi(current); + } } @@ -8722,7 +8744,8 @@ void perf_prepare_sample(struct perf_sample_data *data, /* regs dump ABI info */ int size = sizeof(u64); - perf_sample_regs_intr(&data->regs_intr, regs); + perf_sample_regs_intr(&data->regs_intr, regs, + event->attr.exclude_kernel); if (data->regs_intr.regs) { u64 mask = event->attr.sample_regs_intr; From a4573a3838ae4fc73b70019cfa1dac9aaea7cc2f Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Tue, 16 Jun 2026 12:46:54 +0800 Subject: [PATCH 16/42] perf/core: Check kernel access when kernel callchains are requested perf_event_open() currently gates perf_allow_kernel() only on !attr.exclude_kernel. However, users can still request kernel callchain collection with attr.exclude_callchain_kernel == 0 even when attr.exclude_kernel == 1. That still requires kernel profiling privilege, but the existing check does not enforce it. Update the permission check to call perf_allow_kernel() when either kernel sampling is requested or kernel callchains are requested. This keeps permission checks aligned with requested data and prevents unprivileged use of kernel callchain capture. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260616044654.3468742-9-dapeng1.mi@linux.intel.com --- kernel/events/core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index dd3bd9cf1986..0f29d17a600d 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -13932,7 +13932,9 @@ SYSCALL_DEFINE5(perf_event_open, if (err) return err; - if (!attr.exclude_kernel) { + if (!attr.exclude_kernel || + ((attr.sample_type & PERF_SAMPLE_CALLCHAIN) && + !attr.exclude_callchain_kernel)) { err = perf_allow_kernel(); if (err) return err; From 38af0dd6a266057002eacb170c08298ea912fb0a Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Fri, 3 Jul 2026 13:49:06 +0200 Subject: [PATCH 17/42] uprobes/x86: Remove struct uprobe_trampoline object Removing struct uprobe_trampoline object and it's tracking code, because it's not needed. We can do same thing directly on top of struct vm_area_struct objects. This makes the code simpler and allows easy propagation of the trampoline vma object into child process in following change. Note the original code called destroy_uprobe_trampoline if the optimiation failed, but it only freed the struct uprobe_trampoline object, not the vma. The new vma leak is fixed in following change. Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Acked-by: Andrii Nakryiko Link: https://patch.msgid.link/20260703114917.238144-3-jolsa@kernel.org --- arch/x86/kernel/uprobes.c | 106 ++++++++------------------------------ include/linux/uprobes.h | 5 -- kernel/events/uprobes.c | 10 ---- kernel/fork.c | 1 - 4 files changed, 22 insertions(+), 100 deletions(-) diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c index 3af979fb41d3..a76203a33a7b 100644 --- a/arch/x86/kernel/uprobes.c +++ b/arch/x86/kernel/uprobes.c @@ -631,11 +631,6 @@ static struct vm_special_mapping tramp_mapping = { .pages = tramp_mapping_pages, }; -struct uprobe_trampoline { - struct hlist_node node; - unsigned long vaddr; -}; - static bool is_reachable_by_call(unsigned long vtramp, unsigned long vaddr) { long delta = (long)(vaddr + 5 - vtramp); @@ -682,83 +677,28 @@ static unsigned long find_nearest_trampoline(unsigned long vaddr) return high_tramp; } -static struct uprobe_trampoline *create_uprobe_trampoline(unsigned long vaddr) +static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsigned long vaddr) { - struct pt_regs *regs = task_pt_regs(current); - struct mm_struct *mm = current->mm; - struct uprobe_trampoline *tramp; + VMA_ITERATOR(vmi, mm, 0); struct vm_area_struct *vma; - if (!user_64bit_mode(regs)) - return NULL; + if (vaddr > TASK_SIZE || vaddr < PAGE_SIZE) + return ERR_PTR(-EINVAL); + + for_each_vma(vmi, vma) { + if (!vma_is_special_mapping(vma, &tramp_mapping)) + continue; + if (is_reachable_by_call(vma->vm_start, vaddr)) + return vma; + } vaddr = find_nearest_trampoline(vaddr); if (IS_ERR_VALUE(vaddr)) - return NULL; + return ERR_PTR(vaddr); - tramp = kzalloc_obj(*tramp); - if (unlikely(!tramp)) - return NULL; - - tramp->vaddr = vaddr; - vma = _install_special_mapping(mm, tramp->vaddr, PAGE_SIZE, + return _install_special_mapping(mm, vaddr, PAGE_SIZE, VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_DONTCOPY|VM_IO, &tramp_mapping); - if (IS_ERR(vma)) { - kfree(tramp); - return NULL; - } - return tramp; -} - -static struct uprobe_trampoline *get_uprobe_trampoline(unsigned long vaddr, bool *new) -{ - struct uprobes_state *state = ¤t->mm->uprobes_state; - struct uprobe_trampoline *tramp = NULL; - - if (vaddr > TASK_SIZE || vaddr < PAGE_SIZE) - return NULL; - - hlist_for_each_entry(tramp, &state->head_tramps, node) { - if (is_reachable_by_call(tramp->vaddr, vaddr)) { - *new = false; - return tramp; - } - } - - tramp = create_uprobe_trampoline(vaddr); - if (!tramp) - return NULL; - - *new = true; - hlist_add_head(&tramp->node, &state->head_tramps); - return tramp; -} - -static void destroy_uprobe_trampoline(struct uprobe_trampoline *tramp) -{ - /* - * We do not unmap and release uprobe trampoline page itself, - * because there's no easy way to make sure none of the threads - * is still inside the trampoline. - */ - hlist_del(&tramp->node); - kfree(tramp); -} - -void arch_uprobe_init_state(struct mm_struct *mm) -{ - INIT_HLIST_HEAD(&mm->uprobes_state.head_tramps); -} - -void arch_uprobe_clear_state(struct mm_struct *mm) -{ - struct uprobes_state *state = &mm->uprobes_state; - struct uprobe_trampoline *tramp; - struct hlist_node *n; - - hlist_for_each_entry_safe(tramp, n, &state->head_tramps, node) - destroy_uprobe_trampoline(tramp); } static bool __in_uprobe_trampoline(struct mm_struct *mm, unsigned long ip) @@ -1111,21 +1051,19 @@ int set_orig_insn(struct arch_uprobe *auprobe, struct vm_area_struct *vma, static int __arch_uprobe_optimize(struct arch_uprobe *auprobe, struct mm_struct *mm, unsigned long vaddr) { - struct uprobe_trampoline *tramp; - struct vm_area_struct *vma; - bool new = false; - int err = 0; + struct pt_regs *regs = task_pt_regs(current); + struct vm_area_struct *vma, *tramp; + int ret; + if (!user_64bit_mode(regs)) + return -EINVAL; vma = find_vma(mm, vaddr); if (!vma) return -EINVAL; - tramp = get_uprobe_trampoline(vaddr, &new); - if (!tramp) - return -EINVAL; - err = swbp_optimize(auprobe, vma, vaddr, tramp->vaddr); - if (WARN_ON_ONCE(err) && new) - destroy_uprobe_trampoline(tramp); - return err; + tramp = get_uprobe_trampoline(mm, vaddr); + if (IS_ERR(tramp)) + return PTR_ERR(tramp); + return WARN_ON_ONCE(swbp_optimize(auprobe, vma, vaddr, tramp->vm_start)); } void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr) diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h index f548fea2adec..18be159bbc34 100644 --- a/include/linux/uprobes.h +++ b/include/linux/uprobes.h @@ -186,9 +186,6 @@ struct xol_area; struct uprobes_state { struct xol_area *xol_area; -#ifdef CONFIG_X86_64 - struct hlist_head head_tramps; -#endif }; typedef int (*uprobe_write_verify_t)(struct page *page, unsigned long vaddr, @@ -238,8 +235,6 @@ extern void uprobe_handle_trampoline(struct pt_regs *regs); extern void *arch_uretprobe_trampoline(unsigned long *psize); extern unsigned long uprobe_get_trampoline_vaddr(void); extern void uprobe_copy_from_page(struct page *page, unsigned long vaddr, void *dst, int len); -extern void arch_uprobe_clear_state(struct mm_struct *mm); -extern void arch_uprobe_init_state(struct mm_struct *mm); extern void handle_syscall_uprobe(struct pt_regs *regs, unsigned long bp_vaddr); extern void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr); extern unsigned long arch_uprobe_get_xol_area(void); diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index 4084e926e284..b5c516168f84 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -1806,14 +1806,6 @@ static struct xol_area *get_xol_area(void) return area; } -void __weak arch_uprobe_clear_state(struct mm_struct *mm) -{ -} - -void __weak arch_uprobe_init_state(struct mm_struct *mm) -{ -} - /* * uprobe_clear_state - Free the area allocated for slots. */ @@ -1825,8 +1817,6 @@ void uprobe_clear_state(struct mm_struct *mm) delayed_uprobe_remove(NULL, mm); mutex_unlock(&delayed_uprobe_lock); - arch_uprobe_clear_state(mm); - if (!area) return; diff --git a/kernel/fork.c b/kernel/fork.c index 13e38e89a1f3..00b52c7314d1 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1064,7 +1064,6 @@ static void mm_init_uprobes_state(struct mm_struct *mm) { #ifdef CONFIG_UPROBES mm->uprobes_state.xol_area = NULL; - arch_uprobe_init_state(mm); #endif } From 07c308eb2bcfe4727ba669e1ba6f5b0ba7d2696e Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Fri, 3 Jul 2026 13:49:07 +0200 Subject: [PATCH 18/42] uprobes/x86: Do not leak trampoline vma mapping on optimization failure In case the optimization fails, we leak new-ly created trampoline vma mapping (in case we just created it), let's unmap it. Fixes: ba2bfc97b462 ("uprobes/x86: Add support to optimize uprobes") Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Link: https://patch.msgid.link/20260703114917.238144-4-jolsa@kernel.org --- arch/x86/kernel/uprobes.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c index a76203a33a7b..7f820560e6f8 100644 --- a/arch/x86/kernel/uprobes.c +++ b/arch/x86/kernel/uprobes.c @@ -677,11 +677,14 @@ static unsigned long find_nearest_trampoline(unsigned long vaddr) return high_tramp; } -static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsigned long vaddr) +static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsigned long vaddr, + bool *new_mapping) { VMA_ITERATOR(vmi, mm, 0); struct vm_area_struct *vma; + *new_mapping = false; + if (vaddr > TASK_SIZE || vaddr < PAGE_SIZE) return ERR_PTR(-EINVAL); @@ -696,6 +699,7 @@ static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsign if (IS_ERR_VALUE(vaddr)) return ERR_PTR(vaddr); + *new_mapping = true; return _install_special_mapping(mm, vaddr, PAGE_SIZE, VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_DONTCOPY|VM_IO, &tramp_mapping); @@ -1053,6 +1057,7 @@ static int __arch_uprobe_optimize(struct arch_uprobe *auprobe, struct mm_struct { struct pt_regs *regs = task_pt_regs(current); struct vm_area_struct *vma, *tramp; + bool new_mapping; int ret; if (!user_64bit_mode(regs)) @@ -1060,10 +1065,13 @@ static int __arch_uprobe_optimize(struct arch_uprobe *auprobe, struct mm_struct vma = find_vma(mm, vaddr); if (!vma) return -EINVAL; - tramp = get_uprobe_trampoline(mm, vaddr); + tramp = get_uprobe_trampoline(mm, vaddr, &new_mapping); if (IS_ERR(tramp)) return PTR_ERR(tramp); - return WARN_ON_ONCE(swbp_optimize(auprobe, vma, vaddr, tramp->vm_start)); + ret = swbp_optimize(auprobe, vma, vaddr, tramp->vm_start); + if (WARN_ON_ONCE(ret) && new_mapping) + WARN_ON_ONCE(do_munmap(mm, tramp->vm_start, PAGE_SIZE, NULL)); + return ret; } void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr) From d9a48e77f6fe0c11d3cdfcbbcbf4a98ec402e55a Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Fri, 3 Jul 2026 13:49:08 +0200 Subject: [PATCH 19/42] uprobes/x86: Allow to copy uprobe trampolines on fork When we do fork or clone without CLONE_VM the new process won't have uprobe trampoline vma objects and at the same time it will have optimized code calling that trampoline and crash. Fixing this by allowing vma uprobe trampoline objects to be copied on fork to the new process. Fixes: ba2bfc97b462 ("uprobes/x86: Add support to optimize uprobes") Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Link: https://patch.msgid.link/20260703114917.238144-5-jolsa@kernel.org --- arch/x86/kernel/uprobes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c index 7f820560e6f8..ba7ca62f9605 100644 --- a/arch/x86/kernel/uprobes.c +++ b/arch/x86/kernel/uprobes.c @@ -701,7 +701,7 @@ static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsign *new_mapping = true; return _install_special_mapping(mm, vaddr, PAGE_SIZE, - VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_DONTCOPY|VM_IO, + VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_IO, &tramp_mapping); } From 554ba38456dad8053a1a80afe6ae6da9eff745cc Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Fri, 3 Jul 2026 13:49:09 +0200 Subject: [PATCH 20/42] uprobes/x86: Move optimized uprobe from nop5 to nop10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Andrii reported an issue with optimized uprobes [1] that can clobber redzone area with call instruction storing return address on stack where user code may keep temporary data without adjusting rsp. Fixing this by moving the optimized uprobes on top of 10-bytes nop instruction, so we can squeeze another instruction to escape the redzone area before doing the call, like: lea -0x80(%rsp), %rsp call tramp Note the lea instruction is used to adjust the rsp register without changing the flags. We use nop10 and following transformation to optimized instructions above and back as suggested by Peterz [2]. Optimize path (int3_update_optimize): 1) Initial state after set_swbp() installed the uprobe: cc 2e 0f 1f 84 00 00 00 00 00 From offset 0 this is INT3 followed by the tail of the original 10-byte NOP. After a previous unoptimization bytes 5..9 may still contain the old call instruction, which remains valid for threads already there. 2) Rewrite the LEA tail and call displacement: cc [8d 64 24 80 e8 d0 d1 d2 d3] From offset 0 this traps on the uprobe INT3. Bytes 1..9 are not executable entry points while byte 0 is trapped. 3) Publish the first LEA byte: [48] 8d 64 24 80 e8 d0 d1 d2 d3 From offset 0 this is: lea -0x80(%rsp), %rsp call Unoptimize path (int3_update_unoptimize): 1) Initial optimized state: 48 8d 64 24 80 e8 d0 d1 d2 d3 Same as 3) above. 2) Trap new entries before restoring the NOP bytes: [cc] 8d 64 24 80 e8 d0 d1 d2 d3 From offset 0 this traps. A thread that had already executed the LEA can still reach the intact CALL at offset 5. 3) Restore bytes 1..4 of the original NOP while keeping byte 0 trapped and byte 5 as CALL. cc [2e 0f 1f 84] e8 d0 d1 d2 d3 From offset 0 this still traps. Offset 5 is still the CALL for any thread that was already past the first LEA byte. 4) Publish the first byte of the original NOP: [66] 2e 0f 1f 84 e8 d0 d1 d2 d3 From offset 0 this is the restored 10-byte NOP; the CALL opcode and displacement are now only NOP operands. Offset 5 still decodes as CALL for a thread that was already there. Tthere is only a single target uprobe-trampoline for the given nop10 instruction address, so the CALL instruction will not be changed across unoptimization/optimization cycles. Therefore, any task that is preempted at the CALL instruction is guaranteed to observe that CALL and not anything else. Note as explained in [2] we need to use following nop10: PF1 PF2 ESC NOPL MOD SIB DISP32 NOP10: 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 -- cs nopw 0x00000000(%rax,%rax,1) which means we need to allow 0x2e prefix which maps to INAT_PFX_CS attribute in is_prefix_bad function. Also changing the uprobe syscall error when called out of uprobe trampoline to -EPROTO, so we are able to detect the fixed kernel. The optimized uprobe performance stays the same: uprobe-nop : 3.129 ± 0.013M/s uprobe-push : 3.045 ± 0.006M/s uprobe-ret : 1.095 ± 0.004M/s --> uprobe-nop10 : 7.170 ± 0.020M/s uretprobe-nop : 2.143 ± 0.021M/s uretprobe-push : 2.090 ± 0.000M/s uretprobe-ret : 0.942 ± 0.000M/s --> uretprobe-nop10: 3.381 ± 0.003M/s usdt-nop : 3.245 ± 0.004M/s --> usdt-nop10 : 7.256 ± 0.023M/s [1] https://lore.kernel.org/bpf/20260509003146.976844-1-andrii@kernel.org/ [2] https://lore.kernel.org/bpf/20260518104306.GU3102624@noisy.programming.kicks-ass.net/#t Closes: https://lore.kernel.org/bpf/20260509003146.976844-1-andrii@kernel.org/ Fixes: ba2bfc97b462 ("uprobes/x86: Add support to optimize uprobes") Reported-by: Andrii Nakryiko Assisted-by: Codex:GPT-5.5 Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Link: https://patch.msgid.link/20260703114917.238144-6-jolsa@kernel.org --- arch/x86/kernel/uprobes.c | 294 ++++++++++++++++++++++++++++---------- 1 file changed, 217 insertions(+), 77 deletions(-) diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c index ba7ca62f9605..65a2de82ecd2 100644 --- a/arch/x86/kernel/uprobes.c +++ b/arch/x86/kernel/uprobes.c @@ -276,15 +276,9 @@ static bool is_prefix_bad(struct insn *insn) return false; } -static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn, bool x86_64) +static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn) { - enum insn_mode m = x86_64 ? INSN_MODE_64 : INSN_MODE_32; u32 volatile *good_insns; - int ret; - - ret = insn_decode(insn, auprobe->insn, sizeof(auprobe->insn), m); - if (ret < 0) - return -ENOEXEC; if (is_prefix_bad(insn)) return -ENOTSUPP; @@ -293,7 +287,7 @@ static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn, bool if (insn_masking_exception(insn)) return -ENOTSUPP; - if (x86_64) + if (insn->x86_64) good_insns = good_insns_64; else good_insns = good_insns_32; @@ -631,9 +625,29 @@ static struct vm_special_mapping tramp_mapping = { .pages = tramp_mapping_pages, }; + +#define LEA_INSN_SIZE 5 +#define OPT_INSN_SIZE (LEA_INSN_SIZE + CALL_INSN_SIZE) +#define REDZONE_SIZE 0x80 + +static const u8 lea_rsp[] = { 0x48, 0x8d, 0x64, 0x24, 0x80 }; + +static bool is_opt_insns(const uprobe_opcode_t *insn) +{ + return !memcmp(insn, lea_rsp, LEA_INSN_SIZE) && + insn[LEA_INSN_SIZE] == CALL_INSN_OPCODE; +} + +static bool is_swbp_opt_insns(uprobe_opcode_t *insn) +{ + return is_swbp_insn(&insn[0]) && + !memcmp(&insn[1], &lea_rsp[1], LEA_INSN_SIZE - 1) && + insn[LEA_INSN_SIZE] == CALL_INSN_OPCODE; +} + static bool is_reachable_by_call(unsigned long vtramp, unsigned long vaddr) { - long delta = (long)(vaddr + 5 - vtramp); + long delta = (long)(vaddr + OPT_INSN_SIZE - vtramp); return delta >= INT_MIN && delta <= INT_MAX; } @@ -646,7 +660,7 @@ static unsigned long find_nearest_trampoline(unsigned long vaddr) }; unsigned long low_limit, high_limit; unsigned long low_tramp, high_tramp; - unsigned long call_end = vaddr + 5; + unsigned long call_end = vaddr + OPT_INSN_SIZE; if (check_add_overflow(call_end, INT_MIN, &low_limit)) low_limit = PAGE_SIZE; @@ -754,7 +768,7 @@ SYSCALL_DEFINE0(uprobe) /* Allow execution only from uprobe trampolines. */ if (!in_uprobe_trampoline(regs->ip)) - return -ENXIO; + return -EPROTO; err = copy_from_user(&args, (void __user *)regs->sp, sizeof(args)); if (err) @@ -770,8 +784,8 @@ SYSCALL_DEFINE0(uprobe) regs->ax = args.ax; regs->r11 = args.r11; regs->cx = args.cx; - regs->ip = args.retaddr - 5; - regs->sp += sizeof(args); + regs->ip = args.retaddr - OPT_INSN_SIZE; + regs->sp += sizeof(args) + REDZONE_SIZE; regs->orig_ax = -1; sp = regs->sp; @@ -788,12 +802,12 @@ SYSCALL_DEFINE0(uprobe) */ if (regs->sp != sp) { /* skip the trampoline call */ - if (args.retaddr - 5 == regs->ip) - regs->ip += 5; + if (args.retaddr - OPT_INSN_SIZE == regs->ip) + regs->ip += OPT_INSN_SIZE; return regs->ax; } - regs->sp -= sizeof(args); + regs->sp -= sizeof(args) + REDZONE_SIZE; /* for the case uprobe_consumer has changed ax/r11/cx */ args.ax = regs->ax; @@ -801,7 +815,7 @@ SYSCALL_DEFINE0(uprobe) args.cx = regs->cx; /* keep return address unless we are instructed otherwise */ - if (args.retaddr - 5 != regs->ip) + if (args.retaddr - OPT_INSN_SIZE != regs->ip) args.retaddr = regs->ip; if (shstk_push(args.retaddr) == -EFAULT) @@ -835,7 +849,7 @@ asm ( "pop %rax\n" "pop %r11\n" "pop %rcx\n" - "ret\n" + "ret $" __stringify(REDZONE_SIZE) "\n" "int3\n" ".balign " __stringify(PAGE_SIZE) "\n" ".popsection\n" @@ -853,7 +867,8 @@ late_initcall(arch_uprobes_init); enum { EXPECT_SWBP, - EXPECT_CALL, + EXPECT_OPTIMIZED, + EXPECT_SWBP_OPTIMIZED, }; struct write_opcode_ctx { @@ -861,30 +876,29 @@ struct write_opcode_ctx { int expect; }; -static int is_call_insn(uprobe_opcode_t *insn) -{ - return *insn == CALL_INSN_OPCODE; -} - /* - * Verification callback used by int3_update uprobe_write calls to make sure - * the underlying instruction is as expected - either int3 or call. + * Verification callback used by uprobe_write calls to make sure the underlying + * instruction is in the expected stage of the INT3 update sequence. */ static int verify_insn(struct page *page, unsigned long vaddr, uprobe_opcode_t *new_opcode, int nbytes, void *data) { struct write_opcode_ctx *ctx = data; - uprobe_opcode_t old_opcode[5]; + uprobe_opcode_t old_opcode[OPT_INSN_SIZE]; - uprobe_copy_from_page(page, ctx->base, (uprobe_opcode_t *) &old_opcode, 5); + uprobe_copy_from_page(page, ctx->base, old_opcode, OPT_INSN_SIZE); switch (ctx->expect) { case EXPECT_SWBP: if (is_swbp_insn(&old_opcode[0])) return 1; break; - case EXPECT_CALL: - if (is_call_insn(&old_opcode[0])) + case EXPECT_OPTIMIZED: + if (is_opt_insns(&old_opcode[0])) + return 1; + break; + case EXPECT_SWBP_OPTIMIZED: + if (is_swbp_opt_insns(&old_opcode[0])) return 1; break; } @@ -893,48 +907,55 @@ static int verify_insn(struct page *page, unsigned long vaddr, uprobe_opcode_t * } /* - * Modify multi-byte instructions by using INT3 breakpoints on SMP. + * Modify the optimized instruction by using INT3 breakpoints on SMP. * We completely avoid using stop_machine() here, and achieve the * synchronization using INT3 breakpoints and SMP cross-calls. * (borrowed comment from smp_text_poke_batch_finish) * - * The way it is done: - * - Add an INT3 trap to the address that will be patched - * - SMP sync all CPUs - * - Update all but the first byte of the patched range - * - SMP sync all CPUs - * - Replace the first byte (INT3) by the first byte of the replacing opcode - * - SMP sync all CPUs + * For optimization (int3_update_optimize): + * 1) Start with the uprobe INT3 trap already installed + * 2) Update everything but the first byte + * 3) Replace the first INT3 by the first byte of the LEA instruction + * + * For unoptimization (int3_update_unoptimize): + * 1) Start with the optimized uprobe lea/call instructions + * 2) Add an INT3 trap to the address that will be patched + * 3) Restore the NOP bytes before the call opcode + * 4) Replace the first INT3 by the first byte of the NOP instruction + * + * Note that unoptimization deliberately keeps the call opcode and displacement + * in bytes 5..9. Those bytes become operands of the restored 10-byte NOP. + * + * Since there is only a single target uprobe-trampoline for the given nop10 + * instruction address, the CALL instruction will not be changed across + * unoptimization/optimization cycles. + * Therefore, any task that is preempted at the CALL instruction is guaranteed + * to observe that CALL and not anything else. */ -static int int3_update(struct arch_uprobe *auprobe, struct vm_area_struct *vma, - unsigned long vaddr, char *insn, bool optimize) +static int int3_update_optimize(struct arch_uprobe *auprobe, struct vm_area_struct *vma, + unsigned long vaddr, uprobe_opcode_t *insn) { - uprobe_opcode_t int3 = UPROBE_SWBP_INSN; struct write_opcode_ctx ctx = { .base = vaddr, }; int err; /* - * Write int3 trap. + * 1) Initial state after set_swbp() installed the uprobe: + * cc 2e 0f 1f 84 00 00 00 00 00 * - * The swbp_optimize path comes with breakpoint already installed, - * so we can skip this step for optimize == true. + * After a previous unoptimization bytes 5..9 may still contain the + * old call instruction, which remains valid for threads already there. */ - if (!optimize) { - ctx.expect = EXPECT_CALL; - err = uprobe_write(auprobe, vma, vaddr, &int3, 1, verify_insn, - true /* is_register */, false /* do_update_ref_ctr */, - &ctx); - if (err) - return err; - } - smp_text_poke_sync_each_cpu(); - /* Write all but the first byte of the patched range. */ + /* + * 2) Rewrite the LEA tail and call displacement: + * cc [8d 64 24 80 e8 d0 d1 d2 d3] + */ ctx.expect = EXPECT_SWBP; - err = uprobe_write(auprobe, vma, vaddr + 1, insn + 1, 4, verify_insn, + err = uprobe_write(auprobe, vma, vaddr + 1, insn + 1, + OPT_INSN_SIZE - 1, verify_insn, true /* is_register */, false /* do_update_ref_ctr */, &ctx); if (err) @@ -943,13 +964,98 @@ static int int3_update(struct arch_uprobe *auprobe, struct vm_area_struct *vma, smp_text_poke_sync_each_cpu(); /* - * Write first byte. + * 3) Publish the first LEA byte: + * [48] 8d 64 24 80 e8 d0 d1 d2 d3 * - * The swbp_unoptimize needs to finish uprobe removal together - * with ref_ctr update, using uprobe_write with proper flags. + * From offset 0 this is: + * lea -0x80(%rsp), %rsp + * call */ + ctx.expect = EXPECT_SWBP_OPTIMIZED; err = uprobe_write(auprobe, vma, vaddr, insn, 1, verify_insn, - optimize /* is_register */, !optimize /* do_update_ref_ctr */, + true /* is_register */, false /* do_update_ref_ctr */, + &ctx); + if (err) + goto error; + + smp_text_poke_sync_each_cpu(); + return 0; + +error: + /* + * In all intermediate states byte 0 is INT3, so EXPECT_SWBP covers every + * case. Restore NOP bytes 1..4, but keep the valid CALL at bytes 5..9 + * for a thread that had already executed the LEA before a previous + * unoptimization. + */ + ctx.expect = EXPECT_SWBP; + uprobe_write(auprobe, vma, vaddr + 1, auprobe->insn + 1, + LEA_INSN_SIZE - 1, verify_insn, true, false, &ctx); + smp_text_poke_sync_each_cpu(); + return err; +} + +static int int3_update_unoptimize(struct arch_uprobe *auprobe, struct vm_area_struct *vma, + unsigned long vaddr, uprobe_opcode_t *insn) +{ + uprobe_opcode_t int3 = UPROBE_SWBP_INSN; + struct write_opcode_ctx ctx = { + .base = vaddr, + .expect = EXPECT_OPTIMIZED, + }; + int err; + + /* + * Note the first two uprobe_write calls use is_register=true, because they + * are intermediate patching states while the probe is still active, so + * we force the exclusive anonymous page for the update. + * Also we use do_update_ref_ctr=false because refctr was already updated by + * the initial int3 install. + * + * The last uprobe_write to nop10 instruction is called with is_register=false + * and do_update_ref_ctr=true to trigger the refctr update and to instruct + * uprobe_write to zap the anonymous page if it now matches the file page. + * + * 1) Initial optimized state: + * 48 8d 64 24 80 e8 d0 d1 d2 d3 + * + * 2) Trap new entries before restoring the NOP bytes: + * [cc] 8d 64 24 80 e8 d0 d1 d2 d3 + */ + err = uprobe_write(auprobe, vma, vaddr, &int3, 1, verify_insn, + true /* is_register */, false /* do_update_ref_ctr */, + &ctx); + if (err) + return err; + + smp_text_poke_sync_each_cpu(); + + /* + * 3) Restore bytes 1..4 of the original NOP while keeping byte 0 trapped + * and byte 5 as CALL: + * cc [2e 0f 1f 84] e8 d0 d1 d2 d3 + */ + ctx.expect = EXPECT_SWBP_OPTIMIZED; + err = uprobe_write(auprobe, vma, vaddr + 1, insn + 1, + LEA_INSN_SIZE - 1, verify_insn, + true /* is_register */, false /* do_update_ref_ctr */, + &ctx); + if (err) + return err; + + smp_text_poke_sync_each_cpu(); + + /* + * 4) Publish the first byte of the original NOP: + * [66] 2e 0f 1f 84 e8 d0 d1 d2 d3 + * + * From offset 0 this is the restored 10-byte NOP; the CALL opcode and + * displacement are now only NOP operands. Offset 5 still decodes as + * CALL for a thread that was already there. + */ + ctx.expect = EXPECT_SWBP; + err = uprobe_write(auprobe, vma, vaddr, insn, 1, verify_insn, + false /* is_register */, true /* do_update_ref_ctr */, &ctx); if (err) return err; @@ -961,17 +1067,25 @@ static int int3_update(struct arch_uprobe *auprobe, struct vm_area_struct *vma, static int swbp_optimize(struct arch_uprobe *auprobe, struct vm_area_struct *vma, unsigned long vaddr, unsigned long tramp) { - u8 call[5]; + u8 insn[OPT_INSN_SIZE], *call = &insn[LEA_INSN_SIZE]; - __text_gen_insn(call, CALL_INSN_OPCODE, (const void *) vaddr, + /* + * We have nop10 instruction (with first byte overwritten to int3), + * changing it to: + * lea -0x80(%rsp), %rsp + * call tramp + */ + memcpy(insn, lea_rsp, LEA_INSN_SIZE); + __text_gen_insn(call, CALL_INSN_OPCODE, + (const void *) (vaddr + LEA_INSN_SIZE), (const void *) tramp, CALL_INSN_SIZE); - return int3_update(auprobe, vma, vaddr, call, true /* optimize */); + return int3_update_optimize(auprobe, vma, vaddr, insn); } static int swbp_unoptimize(struct arch_uprobe *auprobe, struct vm_area_struct *vma, unsigned long vaddr) { - return int3_update(auprobe, vma, vaddr, auprobe->insn, false /* optimize */); + return int3_update_unoptimize(auprobe, vma, vaddr, auprobe->insn); } static int copy_from_vaddr(struct mm_struct *mm, unsigned long vaddr, void *dst, int len) @@ -993,19 +1107,19 @@ static bool __is_optimized(struct mm_struct *mm, uprobe_opcode_t *insn, unsigned struct __packed __arch_relative_insn { u8 op; s32 raddr; - } *call = (struct __arch_relative_insn *) insn; + } *call = (struct __arch_relative_insn *)(insn + LEA_INSN_SIZE); - if (!is_call_insn(insn)) + if (!is_opt_insns(insn)) return false; - return __in_uprobe_trampoline(mm, vaddr + 5 + call->raddr); + return __in_uprobe_trampoline(mm, vaddr + OPT_INSN_SIZE + call->raddr); } static int is_optimized(struct mm_struct *mm, unsigned long vaddr) { - uprobe_opcode_t insn[5]; + uprobe_opcode_t insn[OPT_INSN_SIZE]; int err; - err = copy_from_vaddr(mm, vaddr, &insn, 5); + err = copy_from_vaddr(mm, vaddr, &insn, OPT_INSN_SIZE); if (err) return err; return __is_optimized(mm, (uprobe_opcode_t *)&insn, vaddr); @@ -1077,7 +1191,7 @@ static int __arch_uprobe_optimize(struct arch_uprobe *auprobe, struct mm_struct void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr) { struct mm_struct *mm = current->mm; - uprobe_opcode_t insn[5]; + uprobe_opcode_t insn[OPT_INSN_SIZE]; if (!should_optimize(auprobe)) return; @@ -1088,7 +1202,7 @@ void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr) * Check if some other thread already optimized the uprobe for us, * if it's the case just go away silently. */ - if (copy_from_vaddr(mm, vaddr, &insn, 5)) + if (copy_from_vaddr(mm, vaddr, &insn, OPT_INSN_SIZE)) goto unlock; if (!is_swbp_insn((uprobe_opcode_t*) &insn)) goto unlock; @@ -1104,16 +1218,32 @@ void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr) mmap_write_unlock(mm); } +static bool is_optimizable_nop10(struct insn *insn) +{ + static const u8 nop10_prefix[] = { + 0x66, 0x2e, 0x0f, 0x1f, 0x84 + }; + + /* + * Restrict this to the 10-byte NOP form whose last 5 bytes are + * SIB/displacement operands. Unoptimization keeps the call opcode and + * displacement in those bytes, so other NOP encodings are not safe. + */ + return insn->length == OPT_INSN_SIZE && + insn_is_nop(insn) && + !memcmp(insn->kaddr, nop10_prefix, ARRAY_SIZE(nop10_prefix)); +} + static bool can_optimize(struct insn *insn, unsigned long vaddr) { - if (!insn->x86_64 || insn->length != 5) + if (!insn->x86_64) return false; - if (!insn_is_nop(insn)) + if (!is_optimizable_nop10(insn)) return false; /* We can't do cross page atomic writes yet. */ - return PAGE_SIZE - (vaddr & ~PAGE_MASK) >= 5; + return PAGE_SIZE - (vaddr & ~PAGE_MASK) >= OPT_INSN_SIZE; } #else /* 32-bit: */ /* @@ -1495,16 +1625,26 @@ static int push_setup_xol_ops(struct arch_uprobe *auprobe, struct insn *insn) */ int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm, unsigned long addr) { + enum insn_mode m = is_64bit_mm(mm) ? INSN_MODE_64 : INSN_MODE_32; u8 fix_ip_or_call = UPROBE_FIX_IP; struct insn insn; int ret; - ret = uprobe_init_insn(auprobe, &insn, is_64bit_mm(mm)); - if (ret) - return ret; + ret = insn_decode(&insn, auprobe->insn, sizeof(auprobe->insn), m); + if (ret < 0) + return -ENOEXEC; - if (can_optimize(&insn, addr)) + /* + * No need to check instruction in uprobe_init_insn in case we + * are on top of optimizable nop10. + */ + if (can_optimize(&insn, addr)) { set_bit(ARCH_UPROBE_FLAG_CAN_OPTIMIZE, &auprobe->flags); + } else { + ret = uprobe_init_insn(auprobe, &insn); + if (ret) + return ret; + } ret = branch_setup_xol_ops(auprobe, &insn); if (ret != -ENOSYS) From ee2862439e5c8763cee84e910706fdc5b97bc879 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Fri, 3 Jul 2026 13:49:10 +0200 Subject: [PATCH 21/42] libbpf: Change has_nop_combo to work on top of nop10 We now expect nop combo with 10 bytes nop instead of 5 bytes nop, fixing has_nop_combo to reflect that. Fixes: 41a5c7df4466 ("libbpf: Add support to detect nop,nop5 instructions combo for usdt probe") Fixes: 554ba38456da ("uprobes/x86: Move optimized uprobe from nop5 to nop10") Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Jakub Sitnicki Acked-by: Andrii Nakryiko Link: https://patch.msgid.link/20260703114917.238144-7-jolsa@kernel.org --- tools/lib/bpf/usdt.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/lib/bpf/usdt.c b/tools/lib/bpf/usdt.c index 57fb82bb81b5..d2ecd3daab96 100644 --- a/tools/lib/bpf/usdt.c +++ b/tools/lib/bpf/usdt.c @@ -305,7 +305,7 @@ struct usdt_manager *usdt_manager_new(struct bpf_object *obj) /* * Detect kernel support for uprobe() syscall, it's presence means we can - * take advantage of faster nop5 uprobe handling. + * take advantage of faster nop10 uprobe handling. * Added in: 56101b69c919 ("uprobes/x86: Add uprobe syscall to speed up uprobe") */ man->has_uprobe_syscall = kernel_supports(obj, FEAT_UPROBE_SYSCALL); @@ -604,14 +604,14 @@ static int parse_usdt_spec(struct usdt_spec *spec, const struct usdt_note *note, #if defined(__x86_64__) static bool has_nop_combo(int fd, long off) { - unsigned char nop_combo[6] = { - 0x90, 0x0f, 0x1f, 0x44, 0x00, 0x00 /* nop,nop5 */ + unsigned char nop_combo[11] = { + 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, }; - unsigned char buf[6]; + unsigned char buf[11]; - if (pread(fd, buf, 6, off) != 6) + if (pread(fd, buf, 11, off) != 11) return false; - return memcmp(buf, nop_combo, 6) == 0; + return memcmp(buf, nop_combo, 11) == 0; } #else static bool has_nop_combo(int fd, long off) @@ -822,8 +822,8 @@ static int collect_usdt_targets(struct usdt_manager *man, struct elf_fd *elf_fd, memset(target, 0, sizeof(*target)); /* - * We have uprobe syscall and usdt with nop,nop5 instructions combo, - * so we can place the uprobe directly on nop5 (+1) and get this probe + * We have uprobe syscall and usdt with nop,nop10 instructions combo, + * so we can place the uprobe directly on nop10 (+1) and get this probe * optimized. */ if (man->has_uprobe_syscall && has_nop_combo(elf_fd->fd, usdt_rel_ip)) { From 8cae54c586084c81ab80f6ab020192eb2ce7aa0b Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Fri, 3 Jul 2026 13:49:11 +0200 Subject: [PATCH 22/42] libbpf: Detect uprobe syscall with new error In the previous optimized uprobe fix we changed the syscall error used for its detection from ENXIO to EPROTO. Changing related probe_uprobe_syscall detection check. Fixes: 05738da0efa1 ("libbpf: Add uprobe syscall feature detection") Fixes: 554ba38456da ("uprobes/x86: Move optimized uprobe from nop5 to nop10") Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Acked-by: Andrii Nakryiko Link: https://patch.msgid.link/20260703114917.238144-8-jolsa@kernel.org --- tools/lib/bpf/features.c | 4 ++-- tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/lib/bpf/features.c b/tools/lib/bpf/features.c index b7e388f99d0b..e5641fa60163 100644 --- a/tools/lib/bpf/features.c +++ b/tools/lib/bpf/features.c @@ -577,10 +577,10 @@ static int probe_ldimm64_full_range_off(int token_fd) static int probe_uprobe_syscall(int token_fd) { /* - * If kernel supports uprobe() syscall, it will return -ENXIO when called + * If kernel supports uprobe() syscall, it will return -EPROTO when called * from the outside of a kernel-generated uprobe trampoline. */ - return syscall(__NR_uprobe) < 0 && errno == ENXIO; + return syscall(__NR_uprobe) < 0 && errno == EPROTO; } #else static int probe_uprobe_syscall(int token_fd) diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c index 955a37751b52..c944136252c6 100644 --- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c @@ -762,7 +762,7 @@ static void test_uprobe_error(void) long err = syscall(__NR_uprobe); ASSERT_EQ(err, -1, "error"); - ASSERT_EQ(errno, ENXIO, "errno"); + ASSERT_EQ(errno, EPROTO, "errno"); } static void __test_uprobe_syscall(void) From 6d91200bcbb52020f33a26f9e92ba309b804fdba Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Fri, 3 Jul 2026 13:49:12 +0200 Subject: [PATCH 23/42] selftests/bpf: Emit nop,nop10 instructions combo for x86_64 arch Syncing latest usdt.h change [1]. Now that we have nop10 optimization support in kernel, let's emit nop,nop10 for usdt probe. We leave it up to the library to use desirable nop instruction. [1] https://github.com/libbpf/usdt/commit/9018f82577d1dad7ed628d9efdaffc09f8f2241b Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Jakub Sitnicki Link: https://patch.msgid.link/20260703114917.238144-9-jolsa@kernel.org --- tools/testing/selftests/bpf/usdt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/usdt.h b/tools/testing/selftests/bpf/usdt.h index c71e21df38b3..75687f50f4e2 100644 --- a/tools/testing/selftests/bpf/usdt.h +++ b/tools/testing/selftests/bpf/usdt.h @@ -313,7 +313,7 @@ struct usdt_sema { volatile unsigned short active; }; #if defined(__ia64__) || defined(__s390__) || defined(__s390x__) #define USDT_NOP nop 0 #elif defined(__x86_64__) -#define USDT_NOP .byte 0x90, 0x0f, 0x1f, 0x44, 0x00, 0x0 /* nop, nop5 */ +#define USDT_NOP .byte 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 /* nop, nop10 */ #else #define USDT_NOP nop #endif From ec0596a0208318d66be7ae946c8964c41dccaa8d Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Fri, 3 Jul 2026 13:49:13 +0200 Subject: [PATCH 24/42] selftests/bpf: Change uprobe syscall tests to use nop10 Optimized uprobes are now on top of 10-bytes nop instructions, reflect that in existing tests. Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Jakub Sitnicki Link: https://patch.msgid.link/20260703114917.238144-10-jolsa@kernel.org --- .../selftests/bpf/benchs/bench_trigger.c | 2 +- .../selftests/bpf/prog_tests/uprobe_syscall.c | 30 +++++++++++-------- tools/testing/selftests/bpf/prog_tests/usdt.c | 25 +++++++++------- tools/testing/selftests/bpf/usdt_2.c | 2 +- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/tools/testing/selftests/bpf/benchs/bench_trigger.c b/tools/testing/selftests/bpf/benchs/bench_trigger.c index 2f22ec61667b..a60b8173cdc4 100644 --- a/tools/testing/selftests/bpf/benchs/bench_trigger.c +++ b/tools/testing/selftests/bpf/benchs/bench_trigger.c @@ -398,7 +398,7 @@ static void *uprobe_producer_ret(void *input) #ifdef __x86_64__ __nocf_check __weak void uprobe_target_nop5(void) { - asm volatile (".byte 0x0f, 0x1f, 0x44, 0x00, 0x00"); + asm volatile (".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00"); } static void *uprobe_producer_nop5(void *input) diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c index c944136252c6..ba50071ace40 100644 --- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c @@ -17,7 +17,7 @@ #include "uprobe_syscall_executed.skel.h" #include "bpf/libbpf_internal.h" -#define USDT_NOP .byte 0x0f, 0x1f, 0x44, 0x00, 0x00 +#define USDT_NOP .byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 #include "usdt.h" #pragma GCC diagnostic ignored "-Wattributes" @@ -26,7 +26,7 @@ __attribute__((aligned(16))) __nocf_check __weak __naked unsigned long uprobe_regs_trigger(void) { asm volatile ( - ".byte 0x0f, 0x1f, 0x44, 0x00, 0x00\n" /* nop5 */ + ".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00\n" /* nop10 */ "movq $0xdeadbeef, %rax\n" "ret\n" ); @@ -345,9 +345,9 @@ static void test_uretprobe_syscall_call(void) __attribute__((aligned(16))) __nocf_check __weak __naked void uprobe_test(void) { - asm volatile (" \n" - ".byte 0x0f, 0x1f, 0x44, 0x00, 0x00 \n" - "ret \n" + asm volatile ( + ".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00\n" /* nop10 */ + "ret\n" ); } @@ -388,14 +388,15 @@ static int find_uprobes_trampoline(void *tramp_addr) return ret; } -static unsigned char nop5[5] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 }; +static unsigned char nop10[10] = { 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static unsigned char lea_rsp[5] = { 0x48, 0x8d, 0x64, 0x24, 0x80 }; -static void *find_nop5(void *fn) +static void *find_nop10(void *fn) { int i; - for (i = 0; i < 10; i++) { - if (!memcmp(nop5, fn + i, 5)) + for (i = 0; i < 128; i++) { + if (!memcmp(nop10, fn + i, 10)) return fn + i; } return NULL; @@ -420,7 +421,8 @@ static void *check_attach(struct uprobe_syscall_executed *skel, trigger_t trigge ASSERT_EQ(skel->bss->executed, executed, "executed"); /* .. and check the trampoline is as expected. */ - call = (struct __arch_relative_insn *) addr; + ASSERT_OK(memcmp(addr, lea_rsp, 5), "lea_rsp"); + call = (struct __arch_relative_insn *)(addr + 5); tramp = (void *) (call + 1) + call->raddr; ASSERT_EQ(call->op, 0xe8, "call"); ASSERT_OK(find_uprobes_trampoline(tramp), "uprobes_trampoline"); @@ -430,9 +432,11 @@ static void *check_attach(struct uprobe_syscall_executed *skel, trigger_t trigge static void check_detach(void *addr, void *tramp) { + static const unsigned char nop10_prefix[] = { 0x66, 0x2e, 0x0f, 0x1f, 0x84 }; + /* [uprobes_trampoline] stays after detach */ ASSERT_OK(find_uprobes_trampoline(tramp), "uprobes_trampoline"); - ASSERT_OK(memcmp(addr, nop5, 5), "nop5"); + ASSERT_OK(memcmp(addr, nop10_prefix, 5), "nop10_prefix"); } static void check(struct uprobe_syscall_executed *skel, struct bpf_link *link, @@ -568,8 +572,8 @@ static void test_uprobe_usdt(void) void *addr; errno = 0; - addr = find_nop5(usdt_test); - if (!ASSERT_OK_PTR(addr, "find_nop5")) + addr = find_nop10(usdt_test); + if (!ASSERT_OK_PTR(addr, "find_nop10")) return; skel = uprobe_syscall_executed__open_and_load(); diff --git a/tools/testing/selftests/bpf/prog_tests/usdt.c b/tools/testing/selftests/bpf/prog_tests/usdt.c index 69759b27794d..fda3a298ccfc 100644 --- a/tools/testing/selftests/bpf/prog_tests/usdt.c +++ b/tools/testing/selftests/bpf/prog_tests/usdt.c @@ -252,7 +252,7 @@ extern void usdt_1(void); extern void usdt_2(void); static unsigned char nop1[1] = { 0x90 }; -static unsigned char nop1_nop5_combo[6] = { 0x90, 0x0f, 0x1f, 0x44, 0x00, 0x00 }; +static unsigned char nop1_nop10_combo[11] = { 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 }; static void *find_instr(void *fn, unsigned char *instr, size_t cnt) { @@ -271,17 +271,17 @@ static void subtest_optimized_attach(void) __u8 *addr_1, *addr_2; /* usdt_1 USDT probe has single nop instruction */ - addr_1 = find_instr(usdt_1, nop1_nop5_combo, 6); - if (!ASSERT_NULL(addr_1, "usdt_1_find_nop1_nop5_combo")) + addr_1 = find_instr(usdt_1, nop1_nop10_combo, 11); + if (!ASSERT_NULL(addr_1, "usdt_1_find_nop1_nop10_combo")) return; addr_1 = find_instr(usdt_1, nop1, 1); if (!ASSERT_OK_PTR(addr_1, "usdt_1_find_nop1")) return; - /* usdt_2 USDT probe has nop,nop5 instructions combo */ - addr_2 = find_instr(usdt_2, nop1_nop5_combo, 6); - if (!ASSERT_OK_PTR(addr_2, "usdt_2_find_nop1_nop5_combo")) + /* usdt_2 USDT probe has nop,nop10 instructions combo */ + addr_2 = find_instr(usdt_2, nop1_nop10_combo, 11); + if (!ASSERT_OK_PTR(addr_2, "usdt_2_find_nop1_nop10_combo")) return; skel = test_usdt__open_and_load(); @@ -309,12 +309,12 @@ static void subtest_optimized_attach(void) bpf_link__destroy(skel->links.usdt_executed); - /* we expect the nop5 ip */ + /* we expect the nop10 ip */ skel->bss->expected_ip = (unsigned long) addr_2 + 1; /* * Attach program on top of usdt_2 which is probe defined on top - * of nop1,nop5 combo, so the probe gets optimized on top of nop5. + * of nop1,nop10 combo, so the probe gets optimized on top of nop10. */ skel->links.usdt_executed = bpf_program__attach_usdt(skel->progs.usdt_executed, 0 /*self*/, "/proc/self/exe", @@ -328,8 +328,13 @@ static void subtest_optimized_attach(void) /* nop stays on addr_2 address */ ASSERT_EQ(*addr_2, 0x90, "nop"); - /* call is on addr_2 + 1 address */ - ASSERT_EQ(*(addr_2 + 1), 0xe8, "call"); + /* + * lea -0x80(%rsp), %rsp + * call ... + */ + static unsigned char expected[] = { 0x48, 0x8d, 0x64, 0x24, 0x80, 0xe8 }; + + ASSERT_MEMEQ(addr_2 + 1, expected, sizeof(expected), "lea_and_call"); ASSERT_EQ(skel->bss->executed, 4, "executed"); cleanup: diff --git a/tools/testing/selftests/bpf/usdt_2.c b/tools/testing/selftests/bpf/usdt_2.c index 789883aaca4c..b359b389f6c0 100644 --- a/tools/testing/selftests/bpf/usdt_2.c +++ b/tools/testing/selftests/bpf/usdt_2.c @@ -3,7 +3,7 @@ #if defined(__x86_64__) /* - * Include usdt.h with default nop,nop5 instructions combo. + * Include usdt.h with default nop,nop10 instructions combo. */ #include "usdt.h" From b2cf7c41e4f12f76f6f7b6a21194367cf8d3e7b9 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Fri, 3 Jul 2026 13:49:14 +0200 Subject: [PATCH 25/42] selftests/bpf: Change uprobe/usdt trigger bench code to use nop10 Changing uprobe/usdt trigger bench code to use nop10 instead of nop5. Also changing run_bench_uprobes.sh to use nop10 triggers. Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Jakub Sitnicki Link: https://patch.msgid.link/20260703114917.238144-11-jolsa@kernel.org --- tools/testing/selftests/bpf/bench.c | 20 +++++------ .../selftests/bpf/benchs/bench_trigger.c | 36 +++++++++---------- .../selftests/bpf/benchs/run_bench_uprobes.sh | 2 +- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tools/testing/selftests/bpf/bench.c b/tools/testing/selftests/bpf/bench.c index 3d9d2cd7764b..c4a3a6b3eb83 100644 --- a/tools/testing/selftests/bpf/bench.c +++ b/tools/testing/selftests/bpf/bench.c @@ -539,12 +539,12 @@ extern const struct bench bench_trig_uretprobe_multi_push; extern const struct bench bench_trig_uprobe_multi_ret; extern const struct bench bench_trig_uretprobe_multi_ret; #ifdef __x86_64__ -extern const struct bench bench_trig_uprobe_nop5; -extern const struct bench bench_trig_uretprobe_nop5; -extern const struct bench bench_trig_uprobe_multi_nop5; -extern const struct bench bench_trig_uretprobe_multi_nop5; +extern const struct bench bench_trig_uprobe_nop10; +extern const struct bench bench_trig_uretprobe_nop10; +extern const struct bench bench_trig_uprobe_multi_nop10; +extern const struct bench bench_trig_uretprobe_multi_nop10; extern const struct bench bench_trig_usdt_nop; -extern const struct bench bench_trig_usdt_nop5; +extern const struct bench bench_trig_usdt_nop10; #endif extern const struct bench bench_rb_libbpf; @@ -622,12 +622,12 @@ static const struct bench *benchs[] = { &bench_trig_uprobe_multi_ret, &bench_trig_uretprobe_multi_ret, #ifdef __x86_64__ - &bench_trig_uprobe_nop5, - &bench_trig_uretprobe_nop5, - &bench_trig_uprobe_multi_nop5, - &bench_trig_uretprobe_multi_nop5, + &bench_trig_uprobe_nop10, + &bench_trig_uretprobe_nop10, + &bench_trig_uprobe_multi_nop10, + &bench_trig_uretprobe_multi_nop10, &bench_trig_usdt_nop, - &bench_trig_usdt_nop5, + &bench_trig_usdt_nop10, #endif /* ringbuf/perfbuf benchmarks */ &bench_rb_libbpf, diff --git a/tools/testing/selftests/bpf/benchs/bench_trigger.c b/tools/testing/selftests/bpf/benchs/bench_trigger.c index a60b8173cdc4..61513efc167a 100644 --- a/tools/testing/selftests/bpf/benchs/bench_trigger.c +++ b/tools/testing/selftests/bpf/benchs/bench_trigger.c @@ -396,15 +396,15 @@ static void *uprobe_producer_ret(void *input) } #ifdef __x86_64__ -__nocf_check __weak void uprobe_target_nop5(void) +__nocf_check __weak void uprobe_target_nop10(void) { asm volatile (".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00"); } -static void *uprobe_producer_nop5(void *input) +static void *uprobe_producer_nop10(void *input) { while (true) - uprobe_target_nop5(); + uprobe_target_nop10(); return NULL; } @@ -418,7 +418,7 @@ static void *uprobe_producer_usdt_nop(void *input) return NULL; } -static void *uprobe_producer_usdt_nop5(void *input) +static void *uprobe_producer_usdt_nop10(void *input) { while (true) usdt_2(); @@ -542,24 +542,24 @@ static void uretprobe_multi_ret_setup(void) } #ifdef __x86_64__ -static void uprobe_nop5_setup(void) +static void uprobe_nop10_setup(void) { - usetup(false, false /* !use_multi */, &uprobe_target_nop5); + usetup(false, false /* !use_multi */, &uprobe_target_nop10); } -static void uretprobe_nop5_setup(void) +static void uretprobe_nop10_setup(void) { - usetup(true, false /* !use_multi */, &uprobe_target_nop5); + usetup(true, false /* !use_multi */, &uprobe_target_nop10); } -static void uprobe_multi_nop5_setup(void) +static void uprobe_multi_nop10_setup(void) { - usetup(false, true /* use_multi */, &uprobe_target_nop5); + usetup(false, true /* use_multi */, &uprobe_target_nop10); } -static void uretprobe_multi_nop5_setup(void) +static void uretprobe_multi_nop10_setup(void) { - usetup(true, true /* use_multi */, &uprobe_target_nop5); + usetup(true, true /* use_multi */, &uprobe_target_nop10); } static void usdt_setup(const char *name) @@ -598,7 +598,7 @@ static void usdt_nop_setup(void) usdt_setup("usdt_1"); } -static void usdt_nop5_setup(void) +static void usdt_nop10_setup(void) { usdt_setup("usdt_2"); } @@ -665,10 +665,10 @@ BENCH_TRIG_USERMODE(uretprobe_multi_nop, nop, "uretprobe-multi-nop"); BENCH_TRIG_USERMODE(uretprobe_multi_push, push, "uretprobe-multi-push"); BENCH_TRIG_USERMODE(uretprobe_multi_ret, ret, "uretprobe-multi-ret"); #ifdef __x86_64__ -BENCH_TRIG_USERMODE(uprobe_nop5, nop5, "uprobe-nop5"); -BENCH_TRIG_USERMODE(uretprobe_nop5, nop5, "uretprobe-nop5"); -BENCH_TRIG_USERMODE(uprobe_multi_nop5, nop5, "uprobe-multi-nop5"); -BENCH_TRIG_USERMODE(uretprobe_multi_nop5, nop5, "uretprobe-multi-nop5"); +BENCH_TRIG_USERMODE(uprobe_nop10, nop10, "uprobe-nop10"); +BENCH_TRIG_USERMODE(uretprobe_nop10, nop10, "uretprobe-nop10"); +BENCH_TRIG_USERMODE(uprobe_multi_nop10, nop10, "uprobe-multi-nop10"); +BENCH_TRIG_USERMODE(uretprobe_multi_nop10, nop10, "uretprobe-multi-nop10"); BENCH_TRIG_USERMODE(usdt_nop, usdt_nop, "usdt-nop"); -BENCH_TRIG_USERMODE(usdt_nop5, usdt_nop5, "usdt-nop5"); +BENCH_TRIG_USERMODE(usdt_nop10, usdt_nop10, "usdt-nop10"); #endif diff --git a/tools/testing/selftests/bpf/benchs/run_bench_uprobes.sh b/tools/testing/selftests/bpf/benchs/run_bench_uprobes.sh index 9ec59423b949..e490b337e960 100755 --- a/tools/testing/selftests/bpf/benchs/run_bench_uprobes.sh +++ b/tools/testing/selftests/bpf/benchs/run_bench_uprobes.sh @@ -2,7 +2,7 @@ set -eufo pipefail -for i in usermode-count syscall-count {uprobe,uretprobe}-{nop,push,ret,nop5} usdt-nop usdt-nop5 +for i in usermode-count syscall-count {uprobe,uretprobe}-{nop,push,ret,nop10} usdt-nop usdt-nop10 do summary=$(sudo ./bench -w2 -d5 -a trig-$i | tail -n1 | cut -d'(' -f1 | cut -d' ' -f3-) printf "%-15s: %s\n" $i "$summary" From 28d57db3e8d4e687b7ece4b047429908f123908e Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Fri, 3 Jul 2026 13:49:15 +0200 Subject: [PATCH 26/42] selftests/bpf: Add reattach tests for uprobe syscall Adding reattach tests for uprobe syscall tests to make sure we can re-attach and optimize same uprobe multiple times. Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260703114917.238144-12-jolsa@kernel.org --- .../selftests/bpf/prog_tests/uprobe_syscall.c | 130 ++++++++++++++++-- 1 file changed, 120 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c index ba50071ace40..7711018f8acd 100644 --- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c @@ -404,6 +404,16 @@ static void *find_nop10(void *fn) typedef void (__attribute__((nocf_check)) *trigger_t)(void); +static void check_attach_notrigger(struct uprobe_syscall_executed *skel, + void *addr, int executed) +{ + unsigned char *op = addr; + + /* Make sure bpf program was not executed. */ + ASSERT_EQ(skel->bss->executed, executed, "executed"); + ASSERT_EQ(*op, 0xcc, "int3"); +} + static void *check_attach(struct uprobe_syscall_executed *skel, trigger_t trigger, void *addr, int executed) { @@ -430,23 +440,26 @@ static void *check_attach(struct uprobe_syscall_executed *skel, trigger_t trigge return tramp; } -static void check_detach(void *addr, void *tramp) +static bool check_detach(void *addr, void *tramp) { static const unsigned char nop10_prefix[] = { 0x66, 0x2e, 0x0f, 0x1f, 0x84 }; + bool ok = true; /* [uprobes_trampoline] stays after detach */ - ASSERT_OK(find_uprobes_trampoline(tramp), "uprobes_trampoline"); - ASSERT_OK(memcmp(addr, nop10_prefix, 5), "nop10_prefix"); + ok &= ASSERT_OK(find_uprobes_trampoline(tramp), "uprobes_trampoline"); + ok &= ASSERT_OK(memcmp(addr, nop10_prefix, 5), "nop10_prefix"); + return ok; } -static void check(struct uprobe_syscall_executed *skel, struct bpf_link *link, - trigger_t trigger, void *addr, int executed) +static void *check(struct uprobe_syscall_executed *skel, struct bpf_link *link, + trigger_t trigger, void *addr, int executed) { void *tramp; tramp = check_attach(skel, trigger, addr, executed); bpf_link__destroy(link); check_detach(addr, tramp); + return tramp; } static void test_uprobe_legacy(void) @@ -457,6 +470,7 @@ static void test_uprobe_legacy(void) ); struct bpf_link *link; unsigned long offset; + void *tramp; offset = get_uprobe_offset(&uprobe_test); if (!ASSERT_GE(offset, 0, "get_uprobe_offset")) @@ -474,7 +488,30 @@ static void test_uprobe_legacy(void) if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_opts")) goto cleanup; - check(skel, link, uprobe_test, uprobe_test, 2); + tramp = check(skel, link, uprobe_test, uprobe_test, 2); + + /* reattach and detach without triggering optimization */ + link = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe, + 0, "/proc/self/exe", offset, NULL); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_opts")) + goto cleanup; + + check_attach_notrigger(skel, uprobe_test, 2); + + bpf_link__destroy(link); + if (!check_detach(uprobe_test, tramp)) + goto cleanup; + + uprobe_test(); + ASSERT_EQ(skel->bss->executed, 2, "executed_no_probe"); + + /* reattach with triggering optimization */ + link = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe, + 0, "/proc/self/exe", offset, NULL); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_opts")) + goto cleanup; + + check(skel, link, uprobe_test, uprobe_test, 4); /* uretprobe */ skel->bss->executed = 0; @@ -496,6 +533,7 @@ static void test_uprobe_multi(void) LIBBPF_OPTS(bpf_uprobe_multi_opts, opts); struct bpf_link *link; unsigned long offset; + void *tramp; offset = get_uprobe_offset(&uprobe_test); if (!ASSERT_GE(offset, 0, "get_uprobe_offset")) @@ -516,7 +554,30 @@ static void test_uprobe_multi(void) if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) goto cleanup; - check(skel, link, uprobe_test, uprobe_test, 2); + tramp = check(skel, link, uprobe_test, uprobe_test, 2); + + /* reattach and detach without triggering optimization */ + link = bpf_program__attach_uprobe_multi(skel->progs.test_uprobe_multi, + 0, "/proc/self/exe", NULL, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) + goto cleanup; + + check_attach_notrigger(skel, uprobe_test, 2); + + bpf_link__destroy(link); + if (!check_detach(uprobe_test, tramp)) + goto cleanup; + + uprobe_test(); + ASSERT_EQ(skel->bss->executed, 2, "executed_no_probe"); + + /* reattach with triggering optimization */ + link = bpf_program__attach_uprobe_multi(skel->progs.test_uprobe_multi, + 0, "/proc/self/exe", NULL, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) + goto cleanup; + + check(skel, link, uprobe_test, uprobe_test, 4); /* uretprobe.multi */ skel->bss->executed = 0; @@ -540,6 +601,7 @@ static void test_uprobe_session(void) ); struct bpf_link *link; unsigned long offset; + void *tramp; offset = get_uprobe_offset(&uprobe_test); if (!ASSERT_GE(offset, 0, "get_uprobe_offset")) @@ -559,7 +621,30 @@ static void test_uprobe_session(void) if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) goto cleanup; - check(skel, link, uprobe_test, uprobe_test, 4); + tramp = check(skel, link, uprobe_test, uprobe_test, 4); + + /* reattach and detach without triggering optimization */ + link = bpf_program__attach_uprobe_multi(skel->progs.test_uprobe_session, + 0, "/proc/self/exe", NULL, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) + goto cleanup; + + check_attach_notrigger(skel, uprobe_test, 4); + + bpf_link__destroy(link); + if (!check_detach(uprobe_test, tramp)) + goto cleanup; + + uprobe_test(); + ASSERT_EQ(skel->bss->executed, 4, "executed_no_probe"); + + /* reattach with triggering optimization */ + link = bpf_program__attach_uprobe_multi(skel->progs.test_uprobe_session, + 0, "/proc/self/exe", NULL, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) + goto cleanup; + + check(skel, link, uprobe_test, uprobe_test, 8); cleanup: uprobe_syscall_executed__destroy(skel); @@ -569,7 +654,7 @@ static void test_uprobe_usdt(void) { struct uprobe_syscall_executed *skel; struct bpf_link *link; - void *addr; + void *addr, *tramp; errno = 0; addr = find_nop10(usdt_test); @@ -588,7 +673,32 @@ static void test_uprobe_usdt(void) if (!ASSERT_OK_PTR(link, "bpf_program__attach_usdt")) goto cleanup; - check(skel, link, usdt_test, addr, 2); + tramp = check(skel, link, usdt_test, addr, 2); + + /* reattach and detach without triggering optimization */ + link = bpf_program__attach_usdt(skel->progs.test_usdt, + -1 /* all PIDs */, "/proc/self/exe", + "optimized_uprobe", "usdt", NULL); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_usdt")) + goto cleanup; + + check_attach_notrigger(skel, addr, 2); + + bpf_link__destroy(link); + if (!check_detach(addr, tramp)) + goto cleanup; + + usdt_test(); + ASSERT_EQ(skel->bss->executed, 2, "executed_no_probe"); + + /* reattach with triggering optimization */ + link = bpf_program__attach_usdt(skel->progs.test_usdt, + -1 /* all PIDs */, "/proc/self/exe", + "optimized_uprobe", "usdt", NULL); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_usdt")) + goto cleanup; + + check(skel, link, usdt_test, addr, 4); cleanup: uprobe_syscall_executed__destroy(skel); From eccf368562bb2f79d2cded2030cfdce91afc9080 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Fri, 3 Jul 2026 13:49:16 +0200 Subject: [PATCH 27/42] selftests/bpf: Add tests for uprobe nop10 red zone clobbering The uprobe nop5 optimization used to replace a 5-byte NOP with a 5-byte CALL to a trampoline. The CALL pushes a return address onto the stack at [rsp-8], clobbering whatever was stored there. On x86-64, the red zone is the 128 bytes below rsp that user code may use for temporary storage without adjusting rsp. Compilers can place USDT argument operands there, generating specs like "8@-8(%rbp)" when rbp == rsp. With the CALL-based optimization, the return address overwrites that argument before the BPF-side USDT argument fetch runs. Add two tests for this case. The uprobe_syscall subtest stores known values at -8(%rsp), -16(%rsp), and -24(%rsp), executes an optimized nop10 uprobe, and verifies the red-zone data is still intact. The USDT subtest triggers a probe in a function where the compiler places three USDT operands in the red zone and verifies that all 10 optimized invocations deliver the expected argument values to BPF. On an unfixed kernel, the first hit goes through the INT3 path and later hits use the optimized CALL path, so the red-zone checks fail after optimization. [ updates to use nop10 ] Signed-off-by: Andrii Nakryiko Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Jakub Sitnicki Link: https://patch.msgid.link/20260703114917.238144-13-jolsa@kernel.org --- .../selftests/bpf/prog_tests/uprobe_syscall.c | 77 +++++++++++++++++++ tools/testing/selftests/bpf/prog_tests/usdt.c | 49 ++++++++++++ tools/testing/selftests/bpf/progs/test_usdt.c | 25 ++++++ tools/testing/selftests/bpf/usdt_2.c | 13 ++++ 4 files changed, 164 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c index 7711018f8acd..ff07e5df9a65 100644 --- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c @@ -357,6 +357,50 @@ __nocf_check __weak void usdt_test(void) USDT(optimized_uprobe, usdt); } +/* + * Assembly-level red zone clobbering test. Stores known values in the + * red zone (below RSP), executes a nop10 (uprobe site), and checks that + * the values survived. Returns 0 if intact, 1 if clobbered. + * + * The nop5 optimization used CALL (which pushes a return address to + * [rsp-8]), the value at -8(%rsp) was overwritten. The nop10 optimization + * should escape that by moving stackpointer below the redzone before + * doing the CALL. + * + * Align the code at 64 bytes, to make sure nop10 is not on page boundary. + */ +__attribute__((aligned(64))) +__nocf_check __weak __naked unsigned long uprobe_red_zone_test(void) +{ + asm volatile ( + "movabs $0x1111111111111111, %%rax\n" + "movq %%rax, -8(%%rsp)\n" + "movabs $0x2222222222222222, %%rax\n" + "movq %%rax, -16(%%rsp)\n" + "movabs $0x3333333333333333, %%rax\n" + "movq %%rax, -24(%%rsp)\n" + + ".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00\n" /* nop10: uprobe site */ + + "movabs $0x1111111111111111, %%rax\n" + "cmpq %%rax, -8(%%rsp)\n" + "jne 1f\n" + "movabs $0x2222222222222222, %%rax\n" + "cmpq %%rax, -16(%%rsp)\n" + "jne 1f\n" + "movabs $0x3333333333333333, %%rax\n" + "cmpq %%rax, -24(%%rsp)\n" + "jne 1f\n" + + "xorl %%eax, %%eax\n" + "retq\n" + "1:\n" + "movl $1, %%eax\n" + "retq\n" + ::: "rax", "memory" + ); +} + static int find_uprobes_trampoline(void *tramp_addr) { void *start, *end; @@ -871,6 +915,37 @@ static void test_uprobe_race(void) #define __NR_uprobe 336 #endif +static void test_uprobe_red_zone(void) +{ + struct uprobe_syscall_executed *skel; + struct bpf_link *link; + void *nop10_addr; + size_t offset; + int i; + + nop10_addr = find_nop10(uprobe_red_zone_test); + if (!ASSERT_NEQ(nop10_addr, NULL, "find_nop10")) + return; + + skel = uprobe_syscall_executed__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + + offset = get_uprobe_offset(nop10_addr); + link = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe, + 0, "/proc/self/exe", offset, NULL); + if (!ASSERT_OK_PTR(link, "attach_uprobe")) + goto cleanup; + + for (i = 0; i < 10; i++) + ASSERT_EQ(uprobe_red_zone_test(), 0, "red_zone_intact"); + + bpf_link__destroy(link); + +cleanup: + uprobe_syscall_executed__destroy(skel); +} + static void test_uprobe_error(void) { long err = syscall(__NR_uprobe); @@ -897,6 +972,8 @@ static void __test_uprobe_syscall(void) test_uprobe_usdt(); if (test__start_subtest("uprobe_race")) test_uprobe_race(); + if (test__start_subtest("uprobe_red_zone")) + test_uprobe_red_zone(); if (test__start_subtest("uprobe_error")) test_uprobe_error(); if (test__start_subtest("uprobe_regs_equal")) diff --git a/tools/testing/selftests/bpf/prog_tests/usdt.c b/tools/testing/selftests/bpf/prog_tests/usdt.c index fda3a298ccfc..8004c9568ffa 100644 --- a/tools/testing/selftests/bpf/prog_tests/usdt.c +++ b/tools/testing/selftests/bpf/prog_tests/usdt.c @@ -250,6 +250,7 @@ static void subtest_basic_usdt(bool optimized) #ifdef __x86_64__ extern void usdt_1(void); extern void usdt_2(void); +extern void usdt_red_zone_trigger(void); static unsigned char nop1[1] = { 0x90 }; static unsigned char nop1_nop10_combo[11] = { 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 }; @@ -340,6 +341,52 @@ static void subtest_optimized_attach(void) cleanup: test_usdt__destroy(skel); } + +/* + * Test that USDT arguments survive nop10 optimization in a function where + * the compiler places operands in the red zone. + * + * Signal handlers are prone to having the compiler place USDT argument + * operands in the red zone (below rsp). + * + * The nop5 optimization used CALL (which pushes a return address to + * [rsp-8]), the value at -8(%rsp) was overwritten. The nop10 optimization + * should escape that by moving stackpointer below the redzone before + * doing the CALL. + */ +static void subtest_optimized_red_zone(void) +{ + struct test_usdt *skel; + int i; + + skel = test_usdt__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + + skel->bss->expected_arg[0] = 0xDEADBEEF; + skel->bss->expected_arg[1] = 0xCAFEBABE; + skel->bss->expected_arg[2] = 0xFEEDFACE; + skel->bss->expected_pid = getpid(); + + skel->links.usdt_check_arg = bpf_program__attach_usdt( + skel->progs.usdt_check_arg, 0, "/proc/self/exe", + "optimized_attach", "usdt_red_zone", NULL); + if (!ASSERT_OK_PTR(skel->links.usdt_check_arg, "attach_usdt_red_zone")) + goto cleanup; + + for (i = 0; i < 10; i++) + usdt_red_zone_trigger(); + + ASSERT_EQ(skel->bss->arg_total, 10, "arg_total"); + ASSERT_EQ(skel->bss->arg_bad, 0, "arg_bad"); + ASSERT_EQ(skel->bss->arg_last[0], 0xDEADBEEF, "arg_last_1"); + ASSERT_EQ(skel->bss->arg_last[1], 0xCAFEBABE, "arg_last_2"); + ASSERT_EQ(skel->bss->arg_last[2], 0xFEEDFACE, "arg_last_3"); + +cleanup: + test_usdt__destroy(skel); +} + #endif unsigned short test_usdt_100_semaphore SEC(".probes"); @@ -613,6 +660,8 @@ void test_usdt(void) subtest_basic_usdt(true); if (test__start_subtest("optimized_attach")) subtest_optimized_attach(); + if (test__start_subtest("optimized_red_zone")) + subtest_optimized_red_zone(); #endif if (test__start_subtest("multispec")) subtest_multispec_usdt(); diff --git a/tools/testing/selftests/bpf/progs/test_usdt.c b/tools/testing/selftests/bpf/progs/test_usdt.c index f00cb52874e0..0ee78fb050a1 100644 --- a/tools/testing/selftests/bpf/progs/test_usdt.c +++ b/tools/testing/selftests/bpf/progs/test_usdt.c @@ -149,5 +149,30 @@ int usdt_executed(struct pt_regs *ctx) executed++; return 0; } + +int arg_total; +int arg_bad; +long arg_last[3]; +long expected_arg[3]; +int expected_pid; + +SEC("usdt") +int BPF_USDT(usdt_check_arg, long arg1, long arg2, long arg3) +{ + if (expected_pid != (bpf_get_current_pid_tgid() >> 32)) + return 0; + + __sync_fetch_and_add(&arg_total, 1); + arg_last[0] = arg1; + arg_last[1] = arg2; + arg_last[2] = arg3; + + if (arg1 != expected_arg[0] || + arg2 != expected_arg[1] || + arg3 != expected_arg[2]) + __sync_fetch_and_add(&arg_bad, 1); + + return 0; +} #endif char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/usdt_2.c b/tools/testing/selftests/bpf/usdt_2.c index b359b389f6c0..5e38f8605b02 100644 --- a/tools/testing/selftests/bpf/usdt_2.c +++ b/tools/testing/selftests/bpf/usdt_2.c @@ -13,4 +13,17 @@ void usdt_2(void) USDT(optimized_attach, usdt_2); } +static volatile unsigned long usdt_red_zone_arg1 = 0xDEADBEEF; +static volatile unsigned long usdt_red_zone_arg2 = 0xCAFEBABE; +static volatile unsigned long usdt_red_zone_arg3 = 0xFEEDFACE; + +void __attribute__((noinline)) usdt_red_zone_trigger(void) +{ + unsigned long a1 = usdt_red_zone_arg1; + unsigned long a2 = usdt_red_zone_arg2; + unsigned long a3 = usdt_red_zone_arg3; + + USDT(optimized_attach, usdt_red_zone, a1, a2, a3); +} + #endif From d7ebbae57de3f37ff42b6f67f40cc67827b23c4f Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Fri, 3 Jul 2026 13:49:17 +0200 Subject: [PATCH 28/42] selftests/bpf: Add tests for forked/cloned optimized uprobes Adding tests for forked/cloned optimized uprobes and make sure the child can properly execute optimized probe for both fork (dups mm) and clone with CLONE_VM. Signed-off-by: Jiri Olsa Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Jakub Sitnicki Link: https://patch.msgid.link/20260703114917.238144-14-jolsa@kernel.org --- .../selftests/bpf/prog_tests/uprobe_syscall.c | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c index ff07e5df9a65..e193206fc5d2 100644 --- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c @@ -4,6 +4,8 @@ #ifdef __x86_64__ +#define _GNU_SOURCE +#include #include #include #include @@ -13,6 +15,7 @@ #include #include #include +#include #include "uprobe_syscall.skel.h" #include "uprobe_syscall_executed.skel.h" #include "bpf/libbpf_internal.h" @@ -954,6 +957,87 @@ static void test_uprobe_error(void) ASSERT_EQ(errno, EPROTO, "errno"); } +__attribute__((aligned(16))) +__nocf_check __weak __naked void uprobe_fork_test(void) +{ + asm volatile ( + ".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00\n" /* nop10 */ + "ret\n" + ); +} + +static noreturn int child_func(void *arg) +{ + struct uprobe_syscall_executed *skel = arg; + + /* Make sure the child's probe is still there and optimized.. */ + if (memcmp(uprobe_fork_test, lea_rsp, sizeof(lea_rsp))) + _exit(1); + + skel->bss->pid = getpid(); + + /* .. and it executes properly. */ + uprobe_fork_test(); + + if (skel->bss->executed != 3) + _exit(2); + + _exit(0); +} + +static void test_uprobe_fork_optimized(bool clone_vm) +{ + struct uprobe_syscall_executed *skel = NULL; + unsigned long offset; + int pid, status, err; + char stack[65535]; + + offset = get_uprobe_offset(&uprobe_fork_test); + if (!ASSERT_GE(offset, 0, "get_uprobe_offset")) + return; + + skel = uprobe_syscall_executed__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + goto cleanup; + + skel->links.test_uprobe = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe, + -1, "/proc/self/exe", offset, NULL); + if (!ASSERT_OK_PTR(skel->links.test_uprobe, "attach_uprobe")) + goto cleanup; + + skel->bss->pid = getpid(); + + /* Trigger optimization of uprobe in uprobe_fork_test. */ + uprobe_fork_test(); + uprobe_fork_test(); + + /* Make sure it got optimied. */ + if (!ASSERT_OK(memcmp(uprobe_fork_test, lea_rsp, sizeof(lea_rsp)), "optimized")) + goto cleanup; + + if (clone_vm) { + pid = clone(child_func, stack + sizeof(stack), CLONE_VM|SIGCHLD, skel); + if (!ASSERT_GT(pid, 0, "clone")) + goto cleanup; + } else { + pid = fork(); + if (!ASSERT_GE(pid, 0, "fork")) + goto cleanup; + if (pid == 0) + child_func(skel); + } + + /* Wait for the child and verify it exited properly with 0. */ + err = waitpid(pid, &status, 0); + if (ASSERT_EQ(err, pid, "waitpid")) { + ASSERT_EQ(WIFEXITED(status), 1, "child_exited"); + ASSERT_EQ(WEXITSTATUS(status), 0, "child_exit_code"); + } + +cleanup: + uprobe_syscall_executed__destroy(skel); +} + static void __test_uprobe_syscall(void) { if (test__start_subtest("uretprobe_regs_equal")) @@ -974,6 +1058,10 @@ static void __test_uprobe_syscall(void) test_uprobe_race(); if (test__start_subtest("uprobe_red_zone")) test_uprobe_red_zone(); + if (test__start_subtest("uprobe_optimized_fork")) + test_uprobe_fork_optimized(false); + if (test__start_subtest("uprobe_optimized_clone_vm")) + test_uprobe_fork_optimized(true); if (test__start_subtest("uprobe_error")) test_uprobe_error(); if (test__start_subtest("uprobe_regs_equal")) From edda9051e267b7390c7ce24b1b71434414ad156e Mon Sep 17 00:00:00 2001 From: Sandipan Das Date: Wed, 1 Jul 2026 11:12:00 +0530 Subject: [PATCH 29/42] perf/x86/amd/uncore: Add group validation The amd_uncore driver currently does not validate event groups and allows creation of groups with more events than the number of available hardware counters. Because of this, pmu->event_init() succeeds but counter assignment fails later in pmu->add() which returns -EBUSY once all counters are exhausted. Address this by introducing group validation in the pmu->event_init() path. Since the uncore PMUs have no per-event constraints and all counters of a PMU are interchangeable, validation is reduced to just counting the group members that target a PMU and ensuring that they fit within the available set of counters. Fixes: c43ca5091a37 ("perf/x86/amd: Add support for AMD NB and L2I "uncore" counters") Signed-off-by: Sandipan Das Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/750877d66e208603c3047f13eed6399625d43969.1782884387.git.sandipan.das@amd.com --- arch/x86/events/amd/uncore.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c index dbc00b6dd69e..222dfab9225f 100644 --- a/arch/x86/events/amd/uncore.c +++ b/arch/x86/events/amd/uncore.c @@ -265,6 +265,29 @@ static void amd_uncore_del(struct perf_event *event, int flags) hwc->idx = -1; } +static bool amd_uncore_group_valid(struct perf_event *event) +{ + struct amd_uncore_pmu *pmu = event_to_amd_uncore_pmu(event); + struct perf_event *leader = event->group_leader; + struct perf_event *sibling; + int counters = 0; + + if (leader->pmu == event->pmu) + counters++; + + for_each_sibling_event(sibling, leader) { + if (sibling->pmu == event->pmu && + sibling->state > PERF_EVENT_STATE_OFF) + counters++; + } + + /* + * When pmu->event_init() is called, the event is yet to be linked to + * its leader's sibling list, so it is counted separately + */ + return (counters + 1) <= pmu->num_counters; +} + static int amd_uncore_event_init(struct perf_event *event) { struct amd_uncore_pmu *pmu; @@ -282,6 +305,14 @@ static int amd_uncore_event_init(struct perf_event *event) if (!ctx) return -ENODEV; + /* + * Ensure that all events in a group can be scheduled together so that + * a failure can be reported at perf_event_open() time rather than + * silently at pmu->add() time when no free counter is found + */ + if (event->group_leader != event && !amd_uncore_group_valid(event)) + return -EINVAL; + /* * NB and Last level cache counters (MSRs) are shared across all cores * that share the same NB / Last level cache. On family 16h and below, From cfd364903af769934ea92d5e80c9b8e20f7b48d2 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 30 Jul 2026 16:34:25 -0700 Subject: [PATCH 30/42] perf/x86/intel/pt: Drop kernel-doc for deleted struct members @lost and @topa_index are no longer part of struct pt_buffer so delete their comment lines. Warning: ../arch/x86/events/intel/pt.h:86 Excess struct member 'lost' description in 'pt_buffer' Warning: ../arch/x86/events/intel/pt.h:86 Excess struct member 'topa_index' description in 'pt_buffer' Signed-off-by: Randy Dunlap Signed-off-by: Ingo Molnar Cc: Peter Zijlstra Link: https://patch.msgid.link/20260730233429.285788-2-rdunlap@infradead.org --- arch/x86/events/intel/pt.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/x86/events/intel/pt.h b/arch/x86/events/intel/pt.h index 2ac36250b656..700e1a915d25 100644 --- a/arch/x86/events/intel/pt.h +++ b/arch/x86/events/intel/pt.h @@ -58,7 +58,6 @@ struct pt_pmu { * @cur_idx: current output region's index within @cur table * @output_off: offset within the current output region * @data_size: running total of the amount of data in this buffer - * @lost: if data was lost/truncated * @head: logical write offset inside the buffer * @snapshot: if this is for a snapshot/overwrite counter * @single: use Single Range Output instead of ToPA @@ -68,7 +67,6 @@ struct pt_pmu { * @stop_te: STOP topa entry pointer * @intr_te: INT topa entry pointer * @data_pages: array of pages from perf - * @topa_index: table of topa entries indexed by page offset */ struct pt_buffer { struct list_head tables; From fa8a2d55139fb2e0b09e68c34b730adac92d18d6 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 6 Jul 2026 10:27:41 -0700 Subject: [PATCH 31/42] srcu: Add lock guard for srcu_fast_updown flavor Add a guard(srcu_fast_updown) definition for scoped SRCU-fast-updown read-side critical sections, following the existing pattern of guard(srcu) and guard(srcu_fast). Signed-off-by: Puranjay Mohan Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Paul E. McKenney Reviewed-by: Oleg Nesterov Link: https://patch.msgid.link/20260706172744.3920417-2-puranjay@kernel.org --- include/linux/srcu.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/linux/srcu.h b/include/linux/srcu.h index a54ce9e808b9..72c86d3b23f2 100644 --- a/include/linux/srcu.h +++ b/include/linux/srcu.h @@ -638,4 +638,11 @@ DEFINE_LOCK_GUARD_1(srcu_fast_notrace, struct srcu_struct, DECLARE_LOCK_GUARD_1_ATTRS(srcu_fast_notrace, __acquires_shared(_T), __releases_shared(*(struct srcu_struct **)_T)) #define class_srcu_fast_notrace_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(srcu_fast_notrace, _T) +DEFINE_LOCK_GUARD_1(srcu_fast_updown, struct srcu_struct, + _T->scp = srcu_read_lock_fast_updown(_T->lock), + srcu_read_unlock_fast_updown(_T->lock, _T->scp), + struct srcu_ctr __percpu *scp) +DECLARE_LOCK_GUARD_1_ATTRS(srcu_fast_updown, __acquires_shared(_T), __releases_shared(*(struct srcu_struct **)_T)) +#define class_srcu_fast_updown_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(srcu_fast_updown, _T) + #endif From 36c8b02c3fe9ba56d6f11ec055cd879bc49871f7 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 6 Jul 2026 10:27:42 -0700 Subject: [PATCH 32/42] uprobes: Switch uretprobes_srcu to SRCU-fast-updown uretprobes_srcu currently uses normal SRCU, which issues two smp_mb() per read lock/unlock pair. This overhead is paid on every uretprobe hit. Switch to SRCU-fast-updown, which eliminates the per-reader memory barriers by moving the ordering cost to the grace-period side (synchronize_rcu() instead of smp_mb()). This is acceptable because grace periods (uprobe unregistration) are infrequent compared to reader-side uretprobe hits. The updown flavor is required because the SRCU read lock is taken in prepare_uretprobe() when a return instance is created and is held until that return instance is finalized. The traced thread returns to user space in between, so the lock is inherently released in a different context from where it was acquired: on the normal return path via uprobe_handle_trampoline() -> hprobe_finalize(), or from ri_timer() (expiry) or dup_utask() (fork) via hprobe_expire(). srcu_down_read_fast() / srcu_up_read_fast() are designed for this acquire-here / release-elsewhere pattern and, unlike the same-context srcu_read_lock_fast() variant, do not carry the lockdep read-side tracking that would warn on it. The short, same-context SRCU sections in ri_timer() and dup_utask() (which guard the uprobe against reuse across the hprobe_expire() cmpxchg) instead use guard(srcu_fast_updown) for proper lockdep coverage. Signed-off-by: Puranjay Mohan Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Acked-by: Andrii Nakryiko Link: https://patch.msgid.link/20260706172744.3920417-3-puranjay@kernel.org --- include/linux/uprobes.h | 5 +++-- kernel/events/uprobes.c | 29 +++++++++++++++++------------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h index 18be159bbc34..d34dbc0fbbfe 100644 --- a/include/linux/uprobes.h +++ b/include/linux/uprobes.h @@ -25,6 +25,7 @@ struct mm_struct; struct inode; struct notifier_block; struct page; +struct srcu_ctr; /* * Allowed return values from uprobe consumer's handler callback @@ -106,7 +107,7 @@ enum hprobe_state { * underlying uprobe is not guaranteed anymore. __UPROBE_DEAD is just an * internal marker and is handled transparently by hprobe_fetch() helper. * - * When uprobe is SRCU-protected, we also record srcu_idx value, necessary for + * When uprobe is SRCU-protected, we also record srcu_scp value, necessary for * SRCU unlocking. * * See hprobe_expire() and hprobe_fetch() for details of race-free uprobe @@ -115,7 +116,7 @@ enum hprobe_state { */ struct hprobe { enum hprobe_state state; - int srcu_idx; + struct srcu_ctr __percpu *srcu_scp; struct uprobe *uprobe; }; diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index e4f526c9bfbf..a18529ab2b87 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -54,7 +54,7 @@ static struct mutex uprobes_mmap_mutex[UPROBES_HASH_SZ]; DEFINE_STATIC_PERCPU_RWSEM(dup_mmap_sem); /* Covers return_instance's uprobe lifetime. */ -DEFINE_STATIC_SRCU(uretprobes_srcu); +DEFINE_STATIC_SRCU_FAST_UPDOWN(uretprobes_srcu); /* Have a copy of original instruction */ #define UPROBE_COPY_INSN 0 @@ -707,12 +707,13 @@ static void put_uprobe(struct uprobe *uprobe) } /* Initialize hprobe as SRCU-protected "leased" uprobe */ -static void hprobe_init_leased(struct hprobe *hprobe, struct uprobe *uprobe, int srcu_idx) +static void hprobe_init_leased(struct hprobe *hprobe, struct uprobe *uprobe, + struct srcu_ctr __percpu *srcu_scp) { WARN_ON(!uprobe); hprobe->state = HPROBE_LEASED; hprobe->uprobe = uprobe; - hprobe->srcu_idx = srcu_idx; + hprobe->srcu_scp = srcu_scp; } /* Initialize hprobe as refcounted ("stable") uprobe (uprobe can be NULL). */ @@ -720,7 +721,7 @@ static void hprobe_init_stable(struct hprobe *hprobe, struct uprobe *uprobe) { hprobe->state = uprobe ? HPROBE_STABLE : HPROBE_GONE; hprobe->uprobe = uprobe; - hprobe->srcu_idx = -1; + hprobe->srcu_scp = NULL; } /* @@ -757,7 +758,7 @@ static void hprobe_finalize(struct hprobe *hprobe, enum hprobe_state hstate) { switch (hstate) { case HPROBE_LEASED: - __srcu_read_unlock(&uretprobes_srcu, hprobe->srcu_idx); + srcu_up_read_fast(&uretprobes_srcu, hprobe->srcu_scp); break; case HPROBE_STABLE: put_uprobe(hprobe->uprobe); @@ -829,7 +830,7 @@ static struct uprobe *hprobe_expire(struct hprobe *hprobe, bool get) */ if (try_cmpxchg(&hprobe->state, &hstate, uprobe ? HPROBE_STABLE : HPROBE_GONE)) { /* We won the race, we are the ones to unlock SRCU */ - __srcu_read_unlock(&uretprobes_srcu, hprobe->srcu_idx); + srcu_up_read_fast(&uretprobes_srcu, hprobe->srcu_scp); return get && uprobe ? get_uprobe(uprobe) : uprobe; } @@ -2035,7 +2036,7 @@ static void ri_timer(struct timer_list *timer) struct return_instance *ri; /* SRCU protects uprobe from reuse for the cmpxchg() inside hprobe_expire(). */ - guard(srcu)(&uretprobes_srcu); + guard(srcu_fast_updown)(&uretprobes_srcu); /* RCU protects return_instance from freeing. */ guard(rcu)(); @@ -2132,7 +2133,7 @@ static int dup_utask(struct task_struct *t, struct uprobe_task *o_utask) t->utask = n_utask; /* protect uprobes from freeing, we'll need try_get_uprobe() them */ - guard(srcu)(&uretprobes_srcu); + guard(srcu_fast_updown)(&uretprobes_srcu); p = &n_utask->return_instances; for (o = o_utask->return_instances; o; o = o->next) { @@ -2244,8 +2245,8 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs, { struct uprobe_task *utask = current->utask; unsigned long orig_ret_vaddr, trampoline_vaddr; + struct srcu_ctr __percpu *srcu_scp; bool chained; - int srcu_idx; if (!get_xol_area()) goto free; @@ -2283,8 +2284,12 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs, orig_ret_vaddr = utask->return_instances->orig_ret_vaddr; } - /* __srcu_read_lock() because SRCU lock survives switch to user space */ - srcu_idx = __srcu_read_lock(&uretprobes_srcu); + /* + * Use srcu_down_read_fast() because the SRCU lock survives a switch to + * user space and can be unlocked from a different context by ri_timer() + * or dup_utask(). + */ + srcu_scp = srcu_down_read_fast(&uretprobes_srcu); ri->func = instruction_pointer(regs); ri->stack = user_stack_pointer(regs); @@ -2293,7 +2298,7 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs, utask->depth++; - hprobe_init_leased(&ri->hprobe, uprobe, srcu_idx); + hprobe_init_leased(&ri->hprobe, uprobe, srcu_scp); ri->next = utask->return_instances; rcu_assign_pointer(utask->return_instances, ri); From c6df517796189723ffbdd7679206c97d3642c2ef Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 21 Jul 2026 10:02:52 +0300 Subject: [PATCH 33/42] perf/x86/intel/pt: Factor out pt_config_enable() pt_config() enables tracing by allowing NMIs and pause/resume, issuing the necessary barriers, and calling pt_config_start(). A later change needs to re-enable tracing on a (re-)start path without repeating the full pt_config() setup (filters, RTIT_CTL, buffer configuration). Factor that enabling sequence out into a new helper, pt_config_enable(), so it can be called on its own. No functional change intended. Signed-off-by: Adrian Hunter Signed-off-by: Peter Zijlstra (Intel) Tested-by: Yi Lai Link: https://patch.msgid.link/20260721070254.13557-2-adrian.hunter@intel.com --- arch/x86/events/intel/pt.c | 41 ++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c index b5726b50e77d..dc1be7f6e04b 100644 --- a/arch/x86/events/intel/pt.c +++ b/arch/x86/events/intel/pt.c @@ -502,6 +502,29 @@ static u64 pt_config_filters(struct perf_event *event) return rtit_ctl; } +static void pt_config_enable(struct perf_event *event) +{ + struct pt *pt = this_cpu_ptr(&pt_ctx); + + /* + * Allow resume before starting so as not to overwrite a value set by a + * PMI. + */ + barrier(); + WRITE_ONCE(pt->resume_allowed, 1); + /* Configuration is complete, it is now OK to handle an NMI */ + barrier(); + WRITE_ONCE(pt->handle_nmi, 1); + barrier(); + pt_config_start(event); + barrier(); + /* + * Allow pause after starting so its pt_config_stop() doesn't race with + * pt_config_start(). + */ + WRITE_ONCE(pt->pause_allowed, 1); +} + static void pt_config(struct perf_event *event) { struct pt *pt = this_cpu_ptr(&pt_ctx); @@ -541,23 +564,7 @@ static void pt_config(struct perf_event *event) event->hw.aux_config = reg; - /* - * Allow resume before starting so as not to overwrite a value set by a - * PMI. - */ - barrier(); - WRITE_ONCE(pt->resume_allowed, 1); - /* Configuration is complete, it is now OK to handle an NMI */ - barrier(); - WRITE_ONCE(pt->handle_nmi, 1); - barrier(); - pt_config_start(event); - barrier(); - /* - * Allow pause after starting so its pt_config_stop() doesn't race with - * pt_config_start(). - */ - WRITE_ONCE(pt->pause_allowed, 1); + pt_config_enable(event); } static void pt_config_stop(struct perf_event *event) From 265bb4ef75fa657f4957d72087a6a246b89d5f0d Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 21 Jul 2026 10:02:53 +0300 Subject: [PATCH 34/42] perf/x86/intel/pt: Use bitwise access for PERF_HES_STOPPED The Intel PT driver reads and writes event->hw.state as a whole value, assuming it is either 0 or PERF_HES_STOPPED. That is true today, but a subsequent fix needs to also track an open AUX output buffer using the PERF_HES_UPTODATE bit of the same field. When more than one bit can be set, whole-value assignments would overwrite the other bits and whole-value comparisons would fail to match. Convert all accesses to set, clear and test the PERF_HES_STOPPED bit individually, in preparation for that change. No functional change intended: event->hw.state currently only ever holds 0 or PERF_HES_STOPPED, so the bitwise forms are equivalent. Signed-off-by: Adrian Hunter Signed-off-by: Peter Zijlstra (Intel) Tested-by: Yi Lai Link: https://patch.msgid.link/20260721070254.13557-3-adrian.hunter@intel.com --- arch/x86/events/intel/pt.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c index dc1be7f6e04b..2163e5ccdc04 100644 --- a/arch/x86/events/intel/pt.c +++ b/arch/x86/events/intel/pt.c @@ -1540,12 +1540,12 @@ void intel_pt_interrupt(void) perf_aux_output_end(&pt->handle, local_xchg(&buf->data_size, 0)); - if (!event->hw.state) { + if (!(event->hw.state & PERF_HES_STOPPED)) { int ret; buf = perf_aux_output_begin(&pt->handle, event); if (!buf) { - event->hw.state = PERF_HES_STOPPED; + event->hw.state |= PERF_HES_STOPPED; WRITE_ONCE(pt->resume_allowed, 0); return; } @@ -1639,7 +1639,7 @@ static void pt_event_start(struct perf_event *event, int mode) goto fail_end_stop; } - hwc->state = 0; + hwc->state &= ~PERF_HES_STOPPED; pt_config_buffer(buf); pt_config(event); @@ -1649,7 +1649,7 @@ static void pt_event_start(struct perf_event *event, int mode) fail_end_stop: perf_aux_output_end(&pt->handle, 0); fail_stop: - hwc->state = PERF_HES_STOPPED; + hwc->state |= PERF_HES_STOPPED; } static void pt_event_stop(struct perf_event *event, int mode) @@ -1680,10 +1680,10 @@ static void pt_event_stop(struct perf_event *event, int mode) pt_config_stop(event); - if (event->hw.state == PERF_HES_STOPPED) + if (event->hw.state & PERF_HES_STOPPED) return; - event->hw.state = PERF_HES_STOPPED; + event->hw.state |= PERF_HES_STOPPED; if (mode & PERF_EF_UPDATE) { struct pt_buffer *buf = perf_get_aux(&pt->handle); @@ -1778,10 +1778,10 @@ static int pt_event_add(struct perf_event *event, int mode) if (mode & PERF_EF_START) { pt_event_start(event, 0); ret = -EINVAL; - if (hwc->state == PERF_HES_STOPPED) + if (hwc->state & PERF_HES_STOPPED) goto fail; } else { - hwc->state = PERF_HES_STOPPED; + hwc->state |= PERF_HES_STOPPED; } ret = 0; From 2e17bf3a469a41457a3bc31b1f8fd66b6ce94a6d Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 21 Jul 2026 10:02:54 +0300 Subject: [PATCH 35/42] perf/x86/intel/pt: Fix stop/start with no update If pt_event_stop() is called without PERF_EF_UPDATE flag, then perf_aux_output_end() is not called. A subsequent call to pt_event_start() will call perf_aux_output_begin() again which violates the rule against nesting and triggers a WARNING in perf_aux_output_begin(). Originally, pt_event_stop() was never called without PERF_EF_UPDATE, because the only code paths to do so are from event overflow, and Intel PT does not do that. However the introduction of group throttling by commit 9734e25fbf5ae ("perf: Fix the throttle logic for a group") meant that an Intel PT event could be throttled if it was part of a group. Throttling calls PMU ->stop() / ->start() callbacks without flags. An example is when AUX area sampling is used. The following commands hit the issue: echo 10000 > /proc/sys/kernel/perf_event_max_sample_rate perf record -F32000 --aux-sample -e '{intel_pt//u,cycles:u}' \ -- bash -c 'for i in `seq 1 100000` ; do true ; done' Use PERF_HES_UPTODATE to track whether perf_aux_output_begin() and perf_aux_output_end() are balanced. A cleared PERF_HES_UPTODATE bit indicates that an AUX output context is still open. Amend pt_event_start() / pt_event_stop() accordingly so that begin/end stay balanced: - In non-snapshot mode, stop() always closes the buffer (the buffer may have run out of space, and that accounting is done by the update), so a following start() opens a fresh one as before. - In snapshot/overwrite mode, stop() without PERF_EF_UPDATE leaves the buffer open so that pt_event_snapshot_aux() can still copy from it, and start() then only re-enables tracing instead of calling perf_aux_output_begin() again. Note that pt_event_del() calls pt_event_stop() with PERF_EF_UPDATE flag set (as is required by the documentation), so a final call to perf_aux_output_end() is assured. Fixes: 52ca9ced3f707 ("perf/x86/intel/pt: Add Intel PT PMU driver") Signed-off-by: Adrian Hunter Signed-off-by: Peter Zijlstra (Intel) Tested-by: Yi Lai Link: https://patch.msgid.link/20260721070254.13557-4-adrian.hunter@intel.com --- arch/x86/events/intel/pt.c | 45 ++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c index 2163e5ccdc04..5754cd405562 100644 --- a/arch/x86/events/intel/pt.c +++ b/arch/x86/events/intel/pt.c @@ -1540,6 +1540,8 @@ void intel_pt_interrupt(void) perf_aux_output_end(&pt->handle, local_xchg(&buf->data_size, 0)); + event->hw.state |= PERF_HES_UPTODATE; + if (!(event->hw.state & PERF_HES_STOPPED)) { int ret; @@ -1561,6 +1563,8 @@ void intel_pt_interrupt(void) pt_config_buffer(buf); pt_config_start(event); + + event->hw.state &= ~PERF_HES_UPTODATE; } } @@ -1629,6 +1633,18 @@ static void pt_event_start(struct perf_event *event, int mode) return; } + /* + * Re-start subsequent to a call to pt_event_stop() without the + * PERF_EF_UPDATE flag. Absence of PERF_HES_UPTODATE indicates that + * perf_aux_output_begin() has already been called. This path can + * come about only in snapshot/overwrite mode - see pt_event_stop(). + */ + if (!(hwc->state & PERF_HES_UPTODATE)) { + hwc->state &= ~PERF_HES_STOPPED; + pt_config_enable(event); + return; + } + buf = perf_aux_output_begin(&pt->handle, event); if (!buf) goto fail_stop; @@ -1639,7 +1655,7 @@ static void pt_event_start(struct perf_event *event, int mode) goto fail_end_stop; } - hwc->state &= ~PERF_HES_STOPPED; + hwc->state &= ~(PERF_HES_STOPPED | PERF_HES_UPTODATE); pt_config_buffer(buf); pt_config(event); @@ -1649,12 +1665,13 @@ static void pt_event_start(struct perf_event *event, int mode) fail_end_stop: perf_aux_output_end(&pt->handle, 0); fail_stop: - hwc->state |= PERF_HES_STOPPED; + hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE; } static void pt_event_stop(struct perf_event *event, int mode) { struct pt *pt = this_cpu_ptr(&pt_ctx); + struct pt_buffer *buf; if (mode & PERF_EF_PAUSE) { if (READ_ONCE(pt->pause_allowed)) @@ -1680,17 +1697,24 @@ static void pt_event_stop(struct perf_event *event, int mode) pt_config_stop(event); - if (event->hw.state & PERF_HES_STOPPED) - return; - event->hw.state |= PERF_HES_STOPPED; - if (mode & PERF_EF_UPDATE) { - struct pt_buffer *buf = perf_get_aux(&pt->handle); + if (event->hw.state & PERF_HES_UPTODATE) + return; - if (!buf) - return; + buf = perf_get_aux(&pt->handle); + if (!buf) + return; + /* + * When not in snapshot/overwrite mode, there is a possibility that the + * buffer has run out of space. The accounting for that is handled by + * the update, so always update in that case. Snapshot/overwrite mode is + * treated differently to allow for pt_event_snapshot_aux() which can + * still get called if the AUX-sampling event is not stopped until after + * PT is stopped. + */ + if ((mode & PERF_EF_UPDATE) || !buf->snapshot) { if (WARN_ON_ONCE(pt->handle.event != event)) return; @@ -1705,6 +1729,7 @@ static void pt_event_stop(struct perf_event *event, int mode) local_xchg(&buf->data_size, buf->nr_pages << PAGE_SHIFT); perf_aux_output_end(&pt->handle, local_xchg(&buf->data_size, 0)); + event->hw.state |= PERF_HES_UPTODATE; } } @@ -1775,6 +1800,8 @@ static int pt_event_add(struct perf_event *event, int mode) if (pt->handle.event) goto fail; + event->hw.state |= PERF_HES_UPTODATE; + if (mode & PERF_EF_START) { pt_event_start(event, 0); ret = -EINVAL; From 91787047b1dc52b1fee68b07f79af70aae5fce47 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 17 Jul 2026 16:03:35 +0800 Subject: [PATCH 36/42] perf/x86: Unregister PMI handler on PMU init failure Fix an NMI handler leak in init_hw_perf_events(). When PMU initialization fails after register_nmi_handler(), the error path exits without calling unregister_nmi_handler(), leaving a stale NMI_LOCAL "PMI" handler registered. Add the missing call before clearing x86_pmu state. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Thomas Falcon Reviewed-by: Zide Chen Link: https://patch.msgid.link/20260717080342.1879573-2-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index af0b67ffb43d..872d07a5fa80 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -2219,7 +2219,7 @@ static int __init init_hw_perf_events(void) err = cpuhp_setup_state(CPUHP_PERF_X86_PREPARE, "perf/x86:prepare", x86_pmu_prepare_cpu, x86_pmu_dead_cpu); if (err) - return err; + goto pmi_unregister; err = cpuhp_setup_state(CPUHP_AP_PERF_X86_STARTING, "perf/x86:starting", x86_pmu_starting_cpu, @@ -2273,6 +2273,8 @@ static int __init init_hw_perf_events(void) cpuhp_remove_state(CPUHP_AP_PERF_X86_STARTING); out: cpuhp_remove_state(CPUHP_PERF_X86_PREPARE); +pmi_unregister: + unregister_nmi_handler(NMI_LOCAL, "PMI"); out_bad_pmu: memset(&x86_pmu, 0, sizeof(x86_pmu)); return err; From 96746e731e1626545e343974ca4673739dceb31b Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 17 Jul 2026 16:03:36 +0800 Subject: [PATCH 37/42] perf/x86: Free hybrid state on PMU init failure If PMU initialization fails, for example in check_hw_exists(), hybrid state can be left partially initialized: x86_pmu.hybrid_pmu is not freed and perf_is_hybrid remains set. This can leak memory and leave stale hybrid state reachable after a failed init path. Add x86_pmu_free_hybrid() and use it on PMU init failure paths so all hybrid-related state is consistently reset. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Thomas Falcon Reviewed-by: Zide Chen Link: https://patch.msgid.link/20260717080342.1879573-3-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 872d07a5fa80..6c63b27e11e6 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -2130,6 +2130,17 @@ void x86_pmu_show_pmu_cap(struct pmu *pmu) pr_info("... global_ctrl mask: %016llx\n", hybrid(pmu, intel_ctrl)); } +static void x86_pmu_free_hybrid(void) +{ + if (!x86_pmu.hybrid_pmu) + return; + + static_branch_disable(&perf_is_hybrid); + kfree(x86_pmu.hybrid_pmu); + x86_pmu.hybrid_pmu = NULL; + x86_pmu.num_hybrid_pmus = 0; +} + static int __init init_hw_perf_events(void) { struct x86_pmu_quirk *quirk; @@ -2258,9 +2269,6 @@ static int __init init_hw_perf_events(void) for (j = 0; j < i; j++) perf_pmu_unregister(&x86_pmu.hybrid_pmu[j].pmu); pr_warn("Failed to register hybrid PMUs\n"); - kfree(x86_pmu.hybrid_pmu); - x86_pmu.hybrid_pmu = NULL; - x86_pmu.num_hybrid_pmus = 0; goto out2; } } @@ -2276,6 +2284,7 @@ static int __init init_hw_perf_events(void) pmi_unregister: unregister_nmi_handler(NMI_LOCAL, "PMI"); out_bad_pmu: + x86_pmu_free_hybrid(); memset(&x86_pmu, 0, sizeof(x86_pmu)); return err; } From 278a3731c9d08bc6ea489c1987c6b7a7020f5d2b Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 17 Jul 2026 16:03:37 +0800 Subject: [PATCH 38/42] perf/x86: Guard intel_pmu_cpu_dead() against invalid hybrid PMU casts In failure paths, cpuc->pmu can still point to the global static pmu instead of an embedded x86_hybrid_pmu::pmu. Calling hybrid_pmu() on that pointer causes an invalid container conversion and may lead to out-of-bounds access. This can happen in at least two cases: - init_hybrid_pmu() fails check_hw_exists() and leaves cpuc->pmu as-is. - CPU hotplug fails between CPUHP_PERF_X86_PREPARE and CPUHP_AP_PERF_X86_STARTING, and rollback invokes intel_pmu_cpu_dead(). Fix both paths by: - Clear cpuc->pmu to NULL when check_hw_exists() fails. - Validat that cpuc->pmu is not the global static pmu before calling hybrid_pmu() in intel_pmu_cpu_dead(). A new helper x86_get_static_pmu() is added to get the global static pmu. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Thomas Falcon Reviewed-by: Zide Chen Link: https://patch.msgid.link/20260717080342.1879573-4-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 5 +++++ arch/x86/events/intel/core.c | 7 +++++-- arch/x86/events/perf_event.h | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 6c63b27e11e6..a02f303a9151 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -790,6 +790,11 @@ int is_x86_event(struct perf_event *event) return false; } +inline struct pmu *x86_get_static_pmu(void) +{ + return &pmu; +} + struct pmu *x86_get_pmu(unsigned int cpu) { struct cpu_hw_events *cpuc = &per_cpu(cpu_hw_events, cpu); diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index b39c6ce0efb5..a991fc4f1575 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -6329,8 +6329,10 @@ static bool init_hybrid_pmu(int cpu) intel_pmu_check_hybrid_pmus(pmu); - if (!check_hw_exists(&pmu->pmu, pmu->cntr_mask, pmu->fixed_cntr_mask)) + if (!check_hw_exists(&pmu->pmu, pmu->cntr_mask, pmu->fixed_cntr_mask)) { + cpuc->pmu = NULL; return false; + } pr_info("%s PMU driver: ", pmu->name); @@ -6475,11 +6477,12 @@ void intel_cpuc_finish(struct cpu_hw_events *cpuc) static void intel_pmu_cpu_dead(int cpu) { struct cpu_hw_events *cpuc = &per_cpu(cpu_hw_events, cpu); + struct pmu *pmu = x86_get_static_pmu(); release_arch_pebs_buf_on_cpu(cpu); intel_cpuc_finish(cpuc); - if (is_hybrid() && cpuc->pmu) + if (is_hybrid() && cpuc->pmu && cpuc->pmu != pmu) cpumask_clear_cpu(cpu, &hybrid_pmu(cpuc->pmu)->supported_cpus); } diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index a8afea8d38f0..01ae287cde16 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1161,6 +1161,7 @@ static struct perf_pmu_format_hybrid_attr format_attr_hybrid_##_name = {\ .pmu_type = _pmu, \ } +struct pmu *x86_get_static_pmu(void); struct pmu *x86_get_pmu(unsigned int cpu); extern struct x86_pmu x86_pmu __read_mostly; From 85182f902afda28ca7ae3408b72f31640a5cfc73 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 17 Jul 2026 16:03:38 +0800 Subject: [PATCH 39/42] perf/x86/intel: Unwind cpuc state if PEBS buffer setup fails intel_pmu_cpu_prepare() allocates per-CPU perf state first and then sets up the arch PEBS buffer. If alloc_arch_pebs_buf_on_cpu() fails, the previously allocated cpuc resources are left behind. Make the failure path call intel_cpuc_finish(cpuc) to release the per-CPU state allocated by intel_cpuc_prepare(). Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Thomas Falcon Reviewed-by: Zide Chen Link: https://patch.msgid.link/20260717080342.1879573-5-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index a991fc4f1575..b47d2f00ac13 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -5924,13 +5924,20 @@ int intel_cpuc_prepare(struct cpu_hw_events *cpuc, int cpu) static int intel_pmu_cpu_prepare(int cpu) { + struct cpu_hw_events *cpuc = &per_cpu(cpu_hw_events, cpu); int ret; - ret = intel_cpuc_prepare(&per_cpu(cpu_hw_events, cpu), cpu); + ret = intel_cpuc_prepare(cpuc, cpu); if (ret) return ret; - return alloc_arch_pebs_buf_on_cpu(cpu); + ret = alloc_arch_pebs_buf_on_cpu(cpu); + if (ret) { + intel_cpuc_finish(cpuc); + return ret; + } + + return 0; } static void flip_smm_bit(void *data) From 3ba87c5bf11bea80e56abe17730085bc962ecfbe Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 17 Jul 2026 16:03:39 +0800 Subject: [PATCH 40/42] perf/x86: Remove stale fixed counter helper and fix hybrid PMU access On hybrid systems, init_hw_perf_events() can call check_hw_exists() with the global PMU pointer after perf_is_hybrid is set. In that case, fixed_counter_disabled() uses hybrid() on a non-hybrid PMU object, so the intel_ctrl access is taken from the wrong layout and can read out of bounds. fixed_counter_disabled() was added in commit 32451614da2a ("perf/x86/intel: Support CPUID 10.ECX to disable fixed counters"), when fixed counters were tracked via num_fixed_counters. Today fixed counters are represented by fixed_cntr_mask, so this helper is obsolete. Remove fixed_counter_disabled() and its callers, and rely directly on the fixed-counter bitmask. With the helper gone, check_hw_exists() no longer needs a PMU argument, so drop that parameter as well. This removes the invalid hybrid access and closes the out-of-bounds read risk. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Thomas Falcon Reviewed-by: Zide Chen Link: https://patch.msgid.link/20260717080342.1879573-6-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 8 ++------ arch/x86/events/intel/core.c | 4 +--- arch/x86/events/perf_event.h | 9 +-------- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index a02f303a9151..143a6e735d9e 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -265,7 +265,7 @@ static void release_pmc_hardware(void) {} #endif -bool check_hw_exists(struct pmu *pmu, unsigned long *cntr_mask, +bool check_hw_exists(unsigned long *cntr_mask, unsigned long *fixed_cntr_mask) { u64 val, val_fail = -1, val_new= ~0; @@ -297,8 +297,6 @@ bool check_hw_exists(struct pmu *pmu, unsigned long *cntr_mask, if (ret) goto msr_fail; for_each_set_bit(i, fixed_cntr_mask, X86_PMC_IDX_MAX) { - if (fixed_counter_disabled(i, pmu)) - continue; if (val & (0x03ULL << i*4)) { bios_fail = 1; val_fail = val; @@ -1618,8 +1616,6 @@ void perf_event_print_debug(void) cpu, idx, prev_left); } for_each_set_bit(idx, fixed_cntr_mask, X86_PMC_IDX_MAX) { - if (fixed_counter_disabled(idx, cpuc->pmu)) - continue; rdmsrq(x86_pmu_fixed_ctr_addr(idx), pmc_count); pr_info("CPU#%d: fixed-PMC%d count: %016llx\n", @@ -2180,7 +2176,7 @@ static int __init init_hw_perf_events(void) pmu_check_apic(); /* sanity check that the hardware exists or is emulated */ - if (!check_hw_exists(&pmu, x86_pmu.cntr_mask, x86_pmu.fixed_cntr_mask)) + if (!check_hw_exists(x86_pmu.cntr_mask, x86_pmu.fixed_cntr_mask)) goto out_bad_pmu; pr_cont("%s PMU driver.\n", x86_pmu.name); diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index b47d2f00ac13..c418176065f6 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -3713,8 +3713,6 @@ static void intel_pmu_reset(void) wrmsrq_safe(x86_pmu_event_addr(idx), 0ull); } for_each_set_bit(idx, fixed_cntr_mask, INTEL_PMC_MAX_FIXED) { - if (fixed_counter_disabled(idx, cpuc->pmu)) - continue; wrmsrq_safe(x86_pmu_fixed_ctr_addr(idx), 0ull); } @@ -6336,7 +6334,7 @@ static bool init_hybrid_pmu(int cpu) intel_pmu_check_hybrid_pmus(pmu); - if (!check_hw_exists(&pmu->pmu, pmu->cntr_mask, pmu->fixed_cntr_mask)) { + if (!check_hw_exists(pmu->cntr_mask, pmu->fixed_cntr_mask)) { cpuc->pmu = NULL; return false; } diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 01ae287cde16..cc9cfaae4f01 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1243,7 +1243,7 @@ static inline int x86_pmu_rdpmc_index(int index) return x86_pmu.rdpmc_index ? x86_pmu.rdpmc_index(index) : index; } -bool check_hw_exists(struct pmu *pmu, unsigned long *cntr_mask, +bool check_hw_exists(unsigned long *cntr_mask, unsigned long *fixed_cntr_mask); int x86_add_exclusive(unsigned int what); @@ -1456,13 +1456,6 @@ ssize_t events_hybrid_sysfs_show(struct device *dev, struct device_attribute *attr, char *page); -static inline bool fixed_counter_disabled(int i, struct pmu *pmu) -{ - u64 intel_ctrl = hybrid(pmu, intel_ctrl); - - return !(intel_ctrl >> (i + INTEL_PMC_IDX_FIXED)); -} - #ifdef CONFIG_CPU_SUP_AMD int amd_pmu_init(void); From dd384d661f06e3725aad3a932a53e0ff81fb8805 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 17 Jul 2026 16:03:40 +0800 Subject: [PATCH 41/42] perf/x86/intel: Fix intel_cap handling on hybrid PMUs intel_cap (IA32_PERF_CAPABILITIES) updates are currently tied to X86_FEATURE_ARCH_PERFMON_EXT, but these are independent feature paths. As a result, hybrid PMU capability state can be updated under the wrong condition. Also, intel_pmu_broken_perf_cap() is too narrow. Per RPL018, the missing PERF_METRICS_AVAILABLE bit affects both Raptor Lake and Meteor Lake parts, not only the currently covered subset. Move intel_cap updates out of the ARCH_PERFMON_EXT-gated path, extend intel_pmu_broken_perf_cap() coverage to both RPL and MTL families, and introduce intel_update_pmu_caps() to centralize PMU capability updates. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Thomas Falcon Reviewed-by: Zide Chen Link: https://patch.msgid.link/20260717080342.1879573-7-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 40 ++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index c418176065f6..f6ee1819864e 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -6147,8 +6147,14 @@ static void intel_pmu_check_extra_regs(struct extra_reg *extra_regs); static inline bool intel_pmu_broken_perf_cap(void) { - /* The Perf Metric (Bit 15) is always cleared */ - if (boot_cpu_data.x86_vfm == INTEL_METEORLAKE || + /* + * The Perf Metric (Bit 15) is always cleared on P-core of + * RPL and MTL. Details can be found in RPL018 erratum. + */ + if (boot_cpu_data.x86_vfm == INTEL_RAPTORLAKE || + boot_cpu_data.x86_vfm == INTEL_RAPTORLAKE_P || + boot_cpu_data.x86_vfm == INTEL_RAPTORLAKE_S || + boot_cpu_data.x86_vfm == INTEL_METEORLAKE || boot_cpu_data.x86_vfm == INTEL_METEORLAKE_L) return true; @@ -6183,7 +6189,7 @@ static inline void __intel_update_large_pebs_flags(struct pmu *pmu) #define counter_mask(_gp, _fixed) ((_gp) | ((u64)(_fixed) << INTEL_PMC_IDX_FIXED)) -static void update_pmu_cap(struct pmu *pmu) +static void update_pmu_cap_from_perfmonext(struct pmu *pmu) { unsigned int eax, ebx, ecx, edx; union cpuid35_eax eax_0; @@ -6241,10 +6247,24 @@ static void update_pmu_cap(struct pmu *pmu) WARN_ON(x86_pmu.arch_pebs == 1); x86_pmu.arch_pebs = 0; } +} - if (!intel_pmu_broken_perf_cap()) { - /* Perf Metric (Bit 15) and PEBS via PT (Bit 16) are hybrid enumeration */ - rdmsrq(MSR_IA32_PERF_CAPABILITIES, hybrid(pmu, intel_cap).capabilities); +static void intel_update_pmu_caps(struct pmu *pmu) +{ + if (this_cpu_has(X86_FEATURE_ARCH_PERFMON_EXT)) + update_pmu_cap_from_perfmonext(pmu); + + if (is_hybrid() && this_cpu_has(X86_FEATURE_PDCM)) { + rdmsrq(MSR_IA32_PERF_CAPABILITIES, + hybrid(pmu, intel_cap).capabilities); + + /* + * Restore perf_metrics on platforms with broken + * perf_capablities. + */ + if (intel_pmu_broken_perf_cap() && + hybrid_pmu(pmu)->pmu_type == hybrid_big) + hybrid(pmu, intel_cap).perf_metrics = 1; } } @@ -6329,9 +6349,7 @@ static bool init_hybrid_pmu(int cpu) if (!cpumask_empty(&pmu->supported_cpus)) goto end; - if (this_cpu_has(X86_FEATURE_ARCH_PERFMON_EXT)) - update_pmu_cap(&pmu->pmu); - + intel_update_pmu_caps(&pmu->pmu); intel_pmu_check_hybrid_pmus(pmu); if (!check_hw_exists(pmu->cntr_mask, pmu->fixed_cntr_mask)) { @@ -8828,8 +8846,8 @@ __init int intel_pmu_init(void) * from the leaf 0xa. The core specific update will be done later * when a new type is online. */ - if (!is_hybrid() && boot_cpu_has(X86_FEATURE_ARCH_PERFMON_EXT)) - update_pmu_cap(NULL); + if (!is_hybrid()) + intel_update_pmu_caps(NULL); if (x86_pmu.arch_pebs) { static_call_update(intel_pmu_disable_event_ext, From 917d558b151cad5b05991e5eaee22efab33525ca Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 17 Jul 2026 16:03:41 +0800 Subject: [PATCH 42/42] perf/x86: Optimize ACR handling in match_prev_assignment() match_prev_assignment() currently forces a mismatch for ACR events, so ACR counter indices are reprogrammed on every scheduling pass. That causes avoidable overhead because disable and enable paths must touch multiple MSRs. The previous ACR assignment is already cached in acr_cfg_b[]. Use that state to compare the newly computed ACR counter indices in hwc->config1 against the cached value in acr_cfg_b[hwc->idx]. If they match, skip unnecessary disable and enable work. Also tighten is_acr_self_reload_event() so it first verifies the event is an ACR event before testing for the self-reload case. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Thomas Falcon Reviewed-by: Zide Chen Link: https://patch.msgid.link/20260717080342.1879573-8-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 13 ++++++++++++- arch/x86/events/perf_event.h | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 143a6e735d9e..8b3ea0adb965 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -1297,6 +1297,17 @@ int x86_perf_rdpmc_index(struct perf_event *event) return event->hw.event_base_rdpmc; } +static inline bool acr_match_prev_indices(struct perf_event *event, + struct cpu_hw_events *cpuc) +{ + struct hw_perf_event *hwc = &event->hw; + + if (!is_acr_event_group(event)) + return true; + /* ACR counter indices don't change. */ + return hwc->config1 == cpuc->acr_cfg_b[hwc->idx]; +} + static inline int match_prev_assignment(struct perf_event *event, struct cpu_hw_events *cpuc, int i) @@ -1306,7 +1317,7 @@ static inline int match_prev_assignment(struct perf_event *event, return hwc->idx == cpuc->assign[i] && hwc->last_cpu == smp_processor_id() && hwc->last_tag == cpuc->tags[i] && - !is_acr_event_group(event); + acr_match_prev_indices(event, cpuc); } static void x86_pmu_start(struct perf_event *event, int flags); diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index cc9cfaae4f01..fa381110f7a7 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -141,7 +141,7 @@ static inline bool is_acr_self_reload_event(struct perf_event *event) { struct hw_perf_event *hwc = &event->hw; - if (hwc->idx < 0) + if (hwc->idx < 0 || !is_acr_event_group(event)) return false; return test_bit(hwc->idx, (unsigned long *)&hwc->config1);