* [PATCH v6 00/15] userfaultfd: working set tracking for VM guest memory
From: Kiryl Shutsemau (Meta) @ 2026-05-29 17:26 UTC (permalink / raw)
To: akpm, rppt, peterx, david
Cc: ljs, surenb, vbabka, Liam.Howlett, ziy, corbet, skhan, seanjc,
pbonzini, jthoughton, aarcange, sj, usama.arif, linux-mm,
linux-kernel, linux-doc, linux-kselftest, kvm, kernel-team, kas
This series adds userfaultfd support for tracking the working set of
VM guest memory, so a VMM can identify cold pages and evict them to
tiered or remote storage.
v1: https://lore.kernel.org/all/20260427114607.4068647-1-kas@kernel.org/
v2: https://lore.kernel.org/all/cover.1778254670.git.kas@kernel.org/
v3: https://lore.kernel.org/all/20260522133857.552279-1-kirill@shutemov.name/
v4: https://lore.kernel.org/all/20260525113737.1942478-1-kas@kernel.org/
v5: https://lore.kernel.org/all/20260526130509.2748441-1-kirill@shutemov.name/
This series is based on the "userfaultfd/pagemap: pre-existing fixes"
series, posted separately; that series carries the pre-existing
Fixes:/Cc: stable@ patches that used to live at the front of v5 (the
four from v5 plus two more surfaced since).
= Changes since v5 =
- Split the pre-existing fixes out into a separate series; this is
rebased on top. (Lorenzo, Andrew)
- Rework the mk_vma_flags() OOB fix to config-gated per-mode masks
(mk_vma_flags_from_masks()); moved to the fixes series. (Lorenzo)
- New prep patch 04/15: convert the userfaultfd_*() helpers to
vma_test_any_mask(). (Lorenzo)
- 08/15: gup_can_follow_protnone() forces the RWP fault only on
accessible VMAs, fixing a FOLL_FORCE loop on a VM_UFFD_RWP VMA that
was mprotect(PROT_NONE)'d.
uffd-unit-tests 113/113, pagemap_ioctl 117/117.
= Problem =
A VMM managing guest memory needs to:
1. detect which pages are still being touched (working-set
tracking);
2. safely evict cold pages to slower tiered or remote storage;
3. fetch them back on demand when accessed again.
= Approach =
UFFDIO_REGISTER_MODE_RWP is a new userfaultfd registration mode, in
parallel with the existing MODE_MISSING / MODE_WP / MODE_MINOR. It
uses the same mechanism on every backing -- anon, shmem, hugetlbfs:
- PAGE_NONE on the PTE (the same primitive NUMA balancing uses)
makes the page inaccessible while keeping it resident;
- the uffd PTE bit (the one MODE_WP already owns) marks the entry
as "userfaultfd-tracked" so the protnone fault path can tell an
RWP fault apart from an mprotect(PROT_NONE) or NUMA hinting
fault.
VM_UFFD_WP and VM_UFFD_RWP are mutually exclusive per VMA, so the
same PTE bit safely carries both meanings depending on the
registered VMA flag.
In sync mode, the kernel delivers a UFFD_PAGEFAULT_FLAG_RWP message
to the registered handler, and the handler resolves the fault with
UFFDIO_RWPROTECT clearing MODE_RWP. In async mode
(UFFD_FEATURE_RWP_ASYNC), the fault is auto-resolved in-place: the
kernel restores the original PTE permissions and the faulting thread
continues without a userfaultfd message ever being delivered.
Userspace then learns which pages were touched by reading
PAGE_IS_ACCESSED out of PAGEMAP_SCAN -- pages whose uffd bit is
still set were not re-accessed since the last RWP cycle.
UFFDIO_RWPROTECT is the protect/unprotect ioctl, mirroring
UFFDIO_WRITEPROTECT.
UFFDIO_SET_MODE flips RWP_ASYNC <-> sync at runtime under
mmap_write_lock() + vma_start_write(), so a VMM can run in async
mode for detection and switch to sync for race-free eviction without
re-registering the userfaultfd.
= Typical VMM workflow =
/* arm */
UFFDIO_API(features = RWP | RWP_ASYNC)
UFFDIO_REGISTER(MODE_RWP)
/* detection cycle (async) */
UFFDIO_RWPROTECT(range, RWP)
sleep(interval)
/* freeze the cold snapshot before scanning */
UFFDIO_SET_MODE(disable = RWP_ASYNC) /* sync */
PAGEMAP_SCAN(!PAGE_IS_ACCESSED) -> cold pages
/* eviction (sync mode traps races) */
pwrite(cold) + fallocate(FALLOC_FL_PUNCH_HOLE, cold)
UFFDIO_WAKE(cold)
UFFDIO_SET_MODE(enable = RWP_ASYNC) /* resume */
= Series layout =
Patches 1 to 4 are preparatory:
1: decouple protnone helpers from CONFIG_NUMA_BALANCING.
2-3: rename _PAGE_BIT_UFFD_WP, pte_uffd_wp() and friends to drop
the _WP suffix, since the bit now carries WP and RWP meaning
depending on the VMA flag. The SCAN_PTE_UFFD enum's ftrace
output string is intentionally kept as "pte_uffd_wp" so
trace-based tooling does not silently break.
4: convert the userfaultfd_*() flag helpers to vma_test_any_mask().
Patches 5 to 8 add the in-kernel mechanism:
5: VM_UFFD_RWP VMA flag (aliased to VM_NONE until 09/15 introduces
CONFIG_USERFAULTFD_RWP together with the UAPI).
6: MM_CP_UFFD_RWP change_protection() primitive (PAGE_NONE +
uffd bit, plus a RESOLVE counterpart).
7: marker preservation across swap, device-exclusive, migration,
fork, mremap, UFFDIO_MOVE, hugetlb copy, and mprotect().
8: handle VM_UFFD_RWP in khugepaged, rmap, and GUP.
Patches 9 to 13 wire the userspace surface:
9: UFFDIO_REGISTER_MODE_RWP and UFFDIO_RWPROTECT plumbing.
10: RWP fault delivery; turn the UAPI on.
11: PAGE_IS_ACCESSED in PAGEMAP_SCAN.
12: UFFD_FEATURE_RWP_ASYNC.
13: UFFDIO_SET_MODE runtime sync/async toggle.
Patches 14 to 15 are selftests and documentation.
Kiryl Shutsemau (Meta) (15):
mm: decouple protnone helpers from CONFIG_NUMA_BALANCING
mm: rename uffd-wp PTE bit macros to uffd
mm: rename uffd-wp PTE accessors to uffd
userfaultfd: test uffd VMA flags through the vma_flags_t API
mm: add VM_UFFD_RWP VMA flag
mm: add MM_CP_UFFD_RWP change_protection() flag
mm: preserve RWP marker across PTE rewrites
mm: handle VM_UFFD_RWP in khugepaged, rmap, and GUP
userfaultfd: add UFFDIO_REGISTER_MODE_RWP and UFFDIO_RWPROTECT
plumbing
mm/userfaultfd: add RWP fault delivery and expose
UFFDIO_REGISTER_MODE_RWP
mm/pagemap: add PAGE_IS_ACCESSED for RWP tracking
userfaultfd: add UFFD_FEATURE_RWP_ASYNC for async fault resolution
userfaultfd: add UFFDIO_SET_MODE for runtime sync/async toggle
selftests/mm: add userfaultfd RWP tests
Documentation/userfaultfd: document RWP working set tracking
Documentation/admin-guide/mm/pagemap.rst | 13 +-
Documentation/admin-guide/mm/userfaultfd.rst | 253 +++++-
Documentation/filesystems/proc.rst | 1 +
arch/arm64/Kconfig | 1 +
arch/arm64/include/asm/pgtable-prot.h | 8 +-
arch/arm64/include/asm/pgtable.h | 47 +-
arch/loongarch/Kconfig | 1 +
arch/loongarch/include/asm/pgtable.h | 4 +-
arch/powerpc/include/asm/book3s/64/pgtable.h | 8 +-
arch/powerpc/platforms/Kconfig.cputype | 1 +
arch/riscv/Kconfig | 1 +
arch/riscv/include/asm/pgtable-bits.h | 12 +-
arch/riscv/include/asm/pgtable.h | 59 +-
arch/s390/Kconfig | 1 +
arch/s390/include/asm/hugetlb.h | 12 +-
arch/s390/include/asm/pgtable.h | 4 +-
arch/x86/Kconfig | 1 +
arch/x86/include/asm/pgtable.h | 56 +-
arch/x86/include/asm/pgtable_types.h | 16 +-
fs/proc/task_mmu.c | 98 ++-
include/asm-generic/hugetlb.h | 18 +-
include/asm-generic/pgtable_uffd.h | 32 +-
include/linux/huge_mm.h | 7 +
include/linux/leafops.h | 4 +-
include/linux/mm.h | 65 +-
include/linux/mm_inline.h | 4 +-
include/linux/pgtable.h | 32 +-
include/linux/swapops.h | 4 +-
include/linux/userfaultfd_k.h | 89 ++-
include/trace/events/huge_memory.h | 2 +-
include/trace/events/mmflags.h | 7 +
include/uapi/linux/fs.h | 1 +
include/uapi/linux/userfaultfd.h | 54 +-
init/Kconfig | 8 +
mm/Kconfig | 9 +
mm/debug_vm_pgtable.c | 4 +-
mm/huge_memory.c | 159 ++--
mm/hugetlb.c | 158 +++-
mm/internal.h | 4 +-
mm/khugepaged.c | 40 +-
mm/memory.c | 135 +++-
mm/migrate.c | 20 +-
mm/migrate_device.c | 8 +-
mm/mprotect.c | 70 +-
mm/mremap.c | 17 +-
mm/page_table_check.c | 8 +-
mm/rmap.c | 18 +-
mm/swapfile.c | 9 +-
mm/userfaultfd.c | 387 +++++++++-
tools/include/uapi/linux/fs.h | 1 +
tools/testing/selftests/mm/uffd-unit-tests.c | 765 +++++++++++++++++++
51 files changed, 2300 insertions(+), 436 deletions(-)
base-commit: 9110948da327947a01830604020e0548c15b96f1
--
2.54.0
^ permalink raw reply
* [PATCH v3] drm/xe/hwmon: document DG2 fan speed reporting quirk
From: Zhan Wei @ 2026-05-29 17:24 UTC (permalink / raw)
To: matthew.brost, thomas.hellstrom, rodrigo.vivi
Cc: raag.jadav, corbet, skhan, intel-xe, dri-devel, linux-doc,
linux-kernel, Zhan Wei
In-Reply-To: <ahm680G_8mf_cjG9@black.igk.intel.com>
On DG2 the driver always shows two fan channels, because the
FSC_READ_NUM_FANS command does not work on some cards. OEMs decide how
the fans map to tach channels, so two fans can share one tach line.
When that happens, the second channel reads 0 RPM even though the fan
is spinning.
Note this on the fan2_input ABI entry so the steady 0 RPM is not
mistaken for a driver bug.
Signed-off-by: Zhan Wei <zhanwei919@gmail.com>
---
v3:
- Drop the dedicated Documentation/gpu/xe/xe_hwmon.rst doc and the
index.rst hunk; add a short note under the fan2_input entry in the
existing ABI doc instead, per Raag's feedback.
v2: https://lore.kernel.org/intel-xe/20260529135028.20763-1-zhanwei919@gmail.com/
- Drop the code change that reported a single fan on DG2; document the
shared-tach behaviour instead, per review feedback on v1.
v1: https://lore.kernel.org/intel-xe/20260527115311.13398-1-zhanwei919@gmail.com/
Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon b/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
index 55ab45f669ac..0da739d9a816 100644
--- a/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
+++ b/Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
@@ -251,6 +251,13 @@ Description: RO. Fan 2 speed in RPM.
Only supported for particular Intel Xe graphics platforms.
+ On DG2 the driver always shows two fan channels, because the
+ FSC_READ_NUM_FANS command does not work on some cards. OEMs
+ decide how the fans map to tach channels, so two fans can share
+ one tach line. When that happens, the second channel
+ reads 0 RPM even though the fan is spinning. This is normal, not
+ a bug.
+
What: /sys/bus/pci/drivers/xe/.../hwmon/hwmon<i>/fan3_input
Date: March 2025
KernelVersion: 6.16
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v5 10/10] RAS: add firmware-first CPER provider
From: Jonathan Cameron @ 2026-05-29 17:06 UTC (permalink / raw)
To: Ahmed Tiba
Cc: will, xueshuai, saket.dumbre, mchehab, dave, djbw, bp, tony.luck,
guohanjun, lenb, skhan, vishal.l.verma, rafael, corbet, ira.weiny,
dave.jiang, krzk+dt, robh, catalin.marinas, alison.schofield,
conor+dt, linux-arm-kernel, Michael.Zhao2, linux-doc,
linux-kernel, linux-cxl, Dmitry.Lamerov, devicetree, linux-acpi,
linux-edac, acpica-devel
In-Reply-To: <20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-10-2e0500d42642@arm.com>
On Fri, 29 May 2026 10:50:50 +0100
Ahmed Tiba <ahmed.tiba@arm.com> wrote:
> Add a firmware-first CPER provider that reuses the shared
> GHES helpers, wire it into the RAS Kconfig/Makefile and
> document it in the admin guide.
>
> Update MAINTAINERS now that the driver exists.
Sashiko had some interesting comments on this one. Please take a look and
reply to the thread if any look plausible but aren't!
https://sashiko.dev/#/patchset/20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-0-2e0500d42642@arm.com
The irq thread / force_sig() one is something I'd not have
considered. I obviously haven't tested it and so it might be wrong :)
A few things inline.
Jonathan
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
> ---
> Documentation/admin-guide/RAS/main.rst | 18 +++
> MAINTAINERS | 1 +
> drivers/acpi/apei/apei-internal.h | 10 +-
> drivers/acpi/apei/ghes_cper.c | 2 +
> drivers/ras/Kconfig | 11 ++
> drivers/ras/Makefile | 1 +
> drivers/ras/cper-esource.c | 257 +++++++++++++++++++++++++++++++++
> include/acpi/ghes_cper.h | 10 ++
> 8 files changed, 301 insertions(+), 9 deletions(-)
>
> diff --git a/Documentation/admin-guide/RAS/main.rst b/Documentation/admin-guide/RAS/main.rst
> index 5a45db32c49b..84219d25a072 100644
> --- a/Documentation/admin-guide/RAS/main.rst
> +++ b/Documentation/admin-guide/RAS/main.rst
> @@ -205,6 +205,24 @@ Architecture (MCA)\ [#f3]_.
> .. [#f3] For more details about the Machine Check Architecture (MCA),
> please read Documentation/arch/x86/x86_64/machinecheck.rst at the Kernel tree.
>
> +Firmware-first CPER providers
> +-----------------------------
> +
> +Some systems expose Common Platform Error Record (CPER) data
> +through platform firmware instead of ACPI HEST tables.
Given the data doesn't come through HEST but rather in places it points to and
that is via a different type of platform firmware it seems to me this
bit needs a rewrite.
> +Enable ``CONFIG_RAS_CPER_ESOURCE`` to build the ``drivers/ras/cper-esource.c``
> +driver. The current in-tree firmware description uses the
> +``Documentation/devicetree/bindings/firmware/arm,ras-cper.yaml`` binding.
> +The driver reuses the GHES CPER helper object in
> +``drivers/acpi/apei/ghes_cper.c`` so the logging, notifier chains, and
> +memory failure handling match the ACPI GHES behaviour even when
> +ACPI is disabled.
Rewrap. Line lengths appear very random.
> +
> +Once a platform describes a firmware-first provider, both ACPI GHES and the
> +firmware-described driver reuse the same code paths. This keeps the
> +behaviour consistent regardless of whether the error source is described
> +by ACPI tables or another firmware description.
> +
> EDAC - Error Detection And Correction
> *************************************
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8a9714603a7d..c14638cd97f6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22265,6 +22265,7 @@ RAS ERROR STATUS
> M: Ahmed Tiba <ahmed.tiba@arm.com>
> S: Maintained
> F: Documentation/devicetree/bindings/firmware/arm,ras-cper.yaml
> +F: drivers/ras/cper-esource.c
>
> RAS INFRASTRUCTURE
> M: Tony Luck <tony.luck@intel.com>
> diff --git a/drivers/acpi/apei/apei-internal.h b/drivers/acpi/apei/apei-internal.h
> index 77c10a7a7a9f..c16ac541f15b 100644
> --- a/drivers/acpi/apei/apei-internal.h
> +++ b/drivers/acpi/apei/apei-internal.h
> @@ -8,6 +8,7 @@
> #define APEI_INTERNAL_H
>
> #include <linux/acpi.h>
> +#include <acpi/ghes_cper.h>
>
> struct apei_exec_context;
>
> @@ -120,15 +121,6 @@ int apei_exec_collect_resources(struct apei_exec_context *ctx,
> struct dentry;
> struct dentry *apei_get_debugfs_dir(void);
>
> -static inline u32 cper_estatus_len(struct acpi_hest_generic_status *estatus)
As below. I'd not expect to see this moving in this patch given all the precursor
code movement patches.
> -{
> - if (estatus->raw_data_length)
> - return estatus->raw_data_offset + \
> - estatus->raw_data_length;
> - else
> - return sizeof(*estatus) + estatus->data_length;
> -}
> -
> int apei_osc_setup(void);
>
> int einj_get_available_error_type(u32 *type, int einj_action);
> diff --git a/drivers/acpi/apei/ghes_cper.c b/drivers/acpi/apei/ghes_cper.c
> index 0ff9d06eb78f..a7691aa5011c 100644
> --- a/drivers/acpi/apei/ghes_cper.c
> +++ b/drivers/acpi/apei/ghes_cper.c
> @@ -46,7 +46,9 @@
> #include <asm/fixmap.h>
> #include <asm/tlbflush.h>
>
> +#ifdef CONFIG_ACPI_APEI
> #include "apei-internal.h"
Why is this dance needed? Add a comment.
We'd normally expect a header to safe to include if it's not used with
any stubbing done in the header.
> +#endif
>
> ATOMIC_NOTIFIER_HEAD(ghes_report_chain);
>
> diff --git a/drivers/ras/cper-esource.c b/drivers/ras/cper-esource.c
> new file mode 100644
> index 000000000000..83f7a910e50a
> --- /dev/null
> +++ b/drivers/ras/cper-esource.c
> +struct cper_esource {
> + struct device *dev;
> + void __iomem *status;
> + size_t status_len;
> +
> + struct cper_esource_ack ack;
> +
> + struct acpi_hest_generic *generic;
See below - maybe this can just be embedded here.
> + struct acpi_hest_generic_status *estatus;
> +
> + bool sync;
> + int irq;
> +
> + /* Serializes access while firmware and the OS share the status buffer. */
> + spinlock_t lock;
> +};
> +
> +static int cper_esource_copy_status(struct cper_esource *ctx)
> +{
> + memcpy_fromio(ctx->estatus, ctx->status, ctx->status_len);
> + return 0;
Seems an unnecessary helper. In particular if it returns 0 always
no need to do so or to check the return value.
> +}
>
> +static int cper_esource_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct cper_esource *ctx;
> + struct resource *res;
> + int source_id;
> + int rc;
> +
> + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
> + if (!ctx)
> + return -ENOMEM;
> +
> + spin_lock_init(&ctx->lock);
> + ctx->dev = dev;
> + ctx->sync = device_property_read_bool(dev, "arm,sea-notify");
> +
> + ctx->status = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
> + if (IS_ERR(ctx->status))
> + return dev_err_probe(dev, PTR_ERR(ctx->status),
> + "failed to map status region\n");
> +
> + ctx->status_len = resource_size(res);
> + if (!ctx->status_len)
> + return dev_err_probe(dev, -EINVAL, "status region has zero length\n");
> +
> + rc = cper_esource_init_ack(pdev, ctx);
> + if (rc)
> + return rc;
> +
> + rc = cper_esource_init_pool();
> + if (rc)
> + return rc;
> +
> + ctx->estatus = devm_kzalloc(dev, ctx->status_len, GFP_KERNEL);
> + if (!ctx->estatus)
> + return -ENOMEM;
> +
> + ctx->generic = devm_kzalloc(dev, sizeof(*ctx->generic), GFP_KERNEL);
Lifetime of this is shorter than that of containing structure. Why not just
embed it and avoid need to allocate here?
> + if (!ctx->generic)
> + return -ENOMEM;
> +
> + source_id = ida_alloc_min(&cper_esource_source_ids, 1, GFP_KERNEL);
Maybe a comment on what is special about id 0
> + if (source_id < 0)
> + return source_id;
> +
> + ctx->generic->header.type = ACPI_HEST_TYPE_GENERIC_ERROR;
> + ctx->generic->header.source_id = source_id;
> +
> + rc = devm_add_action_or_reset(dev, cper_esource_release_source_id,
> + ctx->generic);
> + if (rc)
> + return rc;
> +
> + ctx->generic->notify.type = ctx->sync ?
> + ACPI_HEST_NOTIFY_SEA : ACPI_HEST_NOTIFY_EXTERNAL;
> + ctx->generic->error_block_length = ctx->status_len;
> +
> + ctx->irq = platform_get_irq(pdev, 0);
> + if (ctx->irq < 0)
> + return ctx->irq;
> +
> + rc = devm_request_threaded_irq(dev, ctx->irq, NULL, cper_esource_irq,
> + IRQF_ONESHOT,
> + dev_name(dev), ctx);
> + if (rc)
> + return dev_err_probe(dev, rc, "failed to request interrupt\n");
> +
> + return 0;
> +}
> +
> +static const struct of_device_id cper_esource_of_match[] = {
> + { .compatible = "arm,ras-cper" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, cper_esource_of_match);
> +
> +static struct platform_driver cper_esource_driver = {
> + .driver = {
> + .name = "cper-esource",
> + .of_match_table = cper_esource_of_match,
> + },
> + .probe = cper_esource_probe,
> +};
Common practice to not have a blank line here. Keeps the
structure and the macro visually closely coupled.
> +
> +module_platform_driver(cper_esource_driver);
> +
> +MODULE_AUTHOR("Ahmed Tiba <ahmed.tiba@arm.com>");
> +MODULE_DESCRIPTION("Firmware-first CPER provider");
> +MODULE_LICENSE("GPL");
> diff --git a/include/acpi/ghes_cper.h b/include/acpi/ghes_cper.h
> index 511b95b50911..a78d4a773129 100644
> --- a/include/acpi/ghes_cper.h
> +++ b/include/acpi/ghes_cper.h
> @@ -80,6 +80,14 @@ static inline bool is_hest_sync_notify(struct ghes *ghes)
> return notify_type == ACPI_HEST_NOTIFY_SEA;
> }
>
> +static inline u32 cper_estatus_len(struct acpi_hest_generic_status *estatus)
I'm surprised to still see code movement in this patch. To me this should have
happened earlier in series.
> +{
> + if (estatus->raw_data_length)
> + return estatus->raw_data_offset + estatus->raw_data_length;
> + else
> + return sizeof(*estatus) + estatus->data_length;
> +}
> +
> struct ghes_vendor_record_entry {
> struct work_struct work;
> int error_severity;
> @@ -108,6 +116,8 @@ int __ghes_read_estatus(struct acpi_hest_generic_status *estatus,
> int ghes_estatus_cached(struct acpi_hest_generic_status *estatus);
> void ghes_estatus_cache_add(struct acpi_hest_generic *generic,
> struct acpi_hest_generic_status *estatus);
> +int ghes_register_vendor_record_notifier(struct notifier_block *nb);
> +void ghes_unregister_vendor_record_notifier(struct notifier_block *nb);
Why are we seeing these here now? Stray from earlier patch?
Which c file are they in at this point?
> void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata,
> int sev);
> int ghes_severity(int severity);
>
^ permalink raw reply
* [RFC PATCH 6/6] mm/damon: add damos_node_eligible_mem_bp tracepoint
From: Ravi Jonnalagadda @ 2026-05-29 16:56 UTC (permalink / raw)
To: sj, akinobu.mita, damon, linux-mm, linux-kernel, linux-doc
Cc: akpm, corbet, bijan311, ajayjoshi, honggyu.kim, yunjeong.mun,
ravis.opensrc
In-Reply-To: <20260529165640.820-1-ravis.opensrc@gmail.com>
Fire a tracepoint at every DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP goal
evaluation, exposing (context, scheme, nid, target_value,
current_value). This gives userspace observability into goal-tracking
without polling sysfs.
The trace_..._enabled() guard avoids the damon_for_each_scheme()
iteration cost when nothing is listening.
Signed-off-by: Ravi Jonnalagadda <ravis.opensrc@gmail.com>
---
include/trace/events/damon.h | 32 ++++++++++++++++++++++++++++++++
mm/damon/core.c | 20 ++++++++++++++++++++
2 files changed, 52 insertions(+)
diff --git a/include/trace/events/damon.h b/include/trace/events/damon.h
index e97e70579a8c8..877627c9a1a18 100644
--- a/include/trace/events/damon.h
+++ b/include/trace/events/damon.h
@@ -91,6 +91,38 @@ TRACE_EVENT(damon_perf_ring_overflow,
TP_printk("cpu=%d", __entry->cpu)
);
+/* Per-tick DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP goal evaluation. */
+TRACE_EVENT(damos_node_eligible_mem_bp,
+
+ TP_PROTO(unsigned int context_idx, unsigned int scheme_idx,
+ int nid,
+ unsigned long target_value, unsigned long current_value),
+
+ TP_ARGS(context_idx, scheme_idx, nid, target_value, current_value),
+
+ TP_STRUCT__entry(
+ __field(unsigned int, context_idx)
+ __field(unsigned int, scheme_idx)
+ __field(int, nid)
+ __field(unsigned long, target_value)
+ __field(unsigned long, current_value)
+ ),
+
+ TP_fast_assign(
+ __entry->context_idx = context_idx;
+ __entry->scheme_idx = scheme_idx;
+ __entry->nid = nid;
+ __entry->target_value = target_value;
+ __entry->current_value = current_value;
+ ),
+
+ TP_printk("ctx_idx=%u scheme_idx=%u nid=%d "
+ "target_value=%lu current_value=%lu",
+ __entry->context_idx, __entry->scheme_idx,
+ __entry->nid,
+ __entry->target_value, __entry->current_value)
+);
+
TRACE_EVENT_CONDITION(damos_before_apply,
TP_PROTO(unsigned int context_idx, unsigned int scheme_idx,
diff --git a/mm/damon/core.c b/mm/damon/core.c
index 1e6966e45144f..609d627e2b33e 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -3203,6 +3203,26 @@ static unsigned long damos_quota_score(struct damon_ctx *c, struct damos *s)
highest_score = max(highest_score,
mult_frac(goal->current_value, 10000,
goal->target_value));
+
+ /*
+ * Per-tick visibility of NODE_ELIGIBLE_MEM_BP goal evaluation
+ * for userspace convergence-detection.
+ */
+ if (goal->metric == DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP &&
+ trace_damos_node_eligible_mem_bp_enabled()) {
+ unsigned int cidx = 0, sidx = 0;
+ struct damos *siter;
+
+ damon_for_each_scheme(siter, c) {
+ if (siter == s)
+ break;
+ sidx++;
+ }
+ trace_damos_node_eligible_mem_bp(cidx, sidx,
+ goal->nid,
+ goal->target_value,
+ goal->current_value);
+ }
}
return highest_score;
--
2.43.0
^ permalink raw reply related
* [RFC PATCH 5/6] mm/damon/vaddr: implement perf-event access check
From: Ravi Jonnalagadda @ 2026-05-29 16:56 UTC (permalink / raw)
To: sj, akinobu.mita, damon, linux-mm, linux-kernel, linux-doc
Cc: akpm, corbet, bijan311, ajayjoshi, honggyu.kim, yunjeong.mun,
ravis.opensrc
In-Reply-To: <20260529165640.820-1-ravis.opensrc@gmail.com>
Add the perf-event backend used by the substrate. Two stateless NMI
overflow handlers are picked at perf_event_create_kernel_counter()
time (paddr- vs vaddr-keyed) and called with context = NULL, so the
NMI fast path never dereferences the per-event struct. Each submits
a damon_access_report into the per-CPU ring.
The vaddr handler drops samples with addr == 0 or addr >= TASK_SIZE.
The paddr handler gates on data->sample_flags & PERF_SAMPLE_PHYS_ADDR
rather than testing data->phys_addr for zero (which would also drop
legitimate page 0). AMD IBS Op only populates phys_addr when
IBS_OP_DATA3.dc_phy_addr_valid is set; gating on sample_flags is the
documented way to detect that. is_write is derived from
data->data_src.mem_op.
cpuhp_setup_state_multi() registers one global state at subsys_initcall;
each damon_perf_event is added as an instance in damon_perf_init() so
cpuhp drives per-CPU event creation and offline-time release. Events
are created with disabled=1 and armed by kdamond_fn() when the
substrate is ready; per-CPU init failures are surfaced via
init_complete / any_cpu_failed so damon_perf_init() rolls back the
cpuhp instance instead of leaving a half-armed event behind.
Signed-off-by: Ravi Jonnalagadda <ravis.opensrc@gmail.com>
---
mm/damon/vaddr.c | 267 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 267 insertions(+)
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index d271476035641..73fcea91afa07 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -7,11 +7,13 @@
#define pr_fmt(fmt) "damon-va: " fmt
+#include <linux/cpuhotplug.h>
#include <linux/highmem.h>
#include <linux/hugetlb.h>
#include <linux/mman.h>
#include <linux/mmu_notifier.h>
#include <linux/page_idle.h>
+#include <linux/perf_event.h>
#include <linux/pagewalk.h>
#include <linux/sched/mm.h>
@@ -957,6 +959,263 @@ static int damon_va_scheme_score(struct damon_ctx *context,
return DAMOS_MAX_SCORE;
}
+#ifdef CONFIG_PERF_EVENTS
+
+#define DAMON_PERF_MAX_RECORDS (1UL << 20)
+#define DAMON_PERF_INIT_RECORDS (1UL << 15)
+
+/*
+ * NMI hot-path: avoid every heap dereference. These handlers carry no
+ * pointer back to the per-event struct -- perf_event_create_kernel_counter
+ * is called with context = NULL. Submission flows into the global
+ * per-CPU SPSC ring (damon_report_access -> kdamond_check_reported_accesses
+ * drains).
+ */
+static void damon_perf_overflow_vaddr(struct perf_event *perf_event,
+ struct perf_sample_data *data, struct pt_regs *regs)
+{
+ struct damon_access_report report;
+
+ if (!data || !data->addr)
+ return;
+
+ /* Drop kernel-VA hits -- only user-space VAs land in damon vaddr regions. */
+ if (data->addr >= TASK_SIZE)
+ return;
+
+ report = (struct damon_access_report){
+ .vaddr = data->addr & PAGE_MASK,
+ .size = PAGE_SIZE,
+ .cpu = smp_processor_id(),
+ .tid = current->pid,
+ .tgid = current->tgid,
+ .is_write = !!(data->data_src.mem_op & PERF_MEM_OP_STORE),
+ };
+ damon_report_access(&report);
+}
+
+static void damon_perf_overflow_paddr(struct perf_event *perf_event,
+ struct perf_sample_data *data, struct pt_regs *regs)
+{
+ struct damon_access_report report;
+
+ if (!data)
+ return;
+
+ /*
+ * AMD IBS Op only populates data->phys_addr when
+ * IBS_OP_DATA3.dc_phy_addr_valid is set; otherwise the field
+ * carries a stale value. Gate on sample_flags rather than testing
+ * phys_addr for zero (which would also drop legitimate page 0).
+ */
+ if (!(data->sample_flags & PERF_SAMPLE_PHYS_ADDR))
+ return;
+
+ report = (struct damon_access_report){
+ .paddr = data->phys_addr & PAGE_MASK,
+ .size = PAGE_SIZE,
+ .cpu = smp_processor_id(),
+ .is_write = !!(data->data_src.mem_op & PERF_MEM_OP_STORE),
+ };
+ damon_report_access(&report);
+}
+
+static enum cpuhp_state damon_perf_cpuhp_state;
+
+static void damon_perf_event_init_attr(struct damon_perf_event *event,
+ struct perf_event_attr *attr)
+{
+ *attr = (struct perf_event_attr) {
+ .size = sizeof(*attr),
+ .type = event->attr.type,
+ .config = event->attr.config,
+ .config1 = event->attr.config1,
+ .config2 = event->attr.config2,
+ .freq = event->attr.freq,
+ .sample_type = PERF_SAMPLE_TIME | PERF_SAMPLE_ADDR |
+ PERF_SAMPLE_PERIOD | PERF_SAMPLE_DATA_SRC |
+ (event->attr.sample_phys_addr ?
+ PERF_SAMPLE_PHYS_ADDR : 0) |
+ (event->attr.sample_weight_struct ?
+ PERF_SAMPLE_WEIGHT_STRUCT : 0),
+ .precise_ip = event->attr.precise_ip,
+ .pinned = 1,
+ .disabled = 1,
+ .wakeup_events = event->attr.wakeup_events,
+ .exclude_kernel = event->attr.exclude_kernel,
+ .exclude_hv = event->attr.exclude_hv,
+ };
+
+ /*
+ * sample_period and sample_freq share storage in the kernel
+ * perf_event_attr (union). Select based on the freq toggle so
+ * frequency-based callers (PEBS) and period-based callers
+ * (AMD IBS Op MaxCnt) both work correctly.
+ */
+ if (event->attr.freq)
+ attr->sample_freq = event->attr.sample_freq;
+ else
+ attr->sample_period = event->attr.sample_period;
+}
+
+static int damon_perf_cpu_online(unsigned int cpu, struct hlist_node *node)
+{
+ struct damon_perf_event *event = hlist_entry(node,
+ struct damon_perf_event, hlist_node);
+ struct damon_perf *perf = event->priv;
+ struct perf_event_attr attr;
+ struct perf_event *perf_event;
+ perf_overflow_handler_t handler;
+
+ if (!perf)
+ return 0;
+
+ damon_perf_event_init_attr(event, &attr);
+
+ /*
+ * Pick a paddr- or vaddr-specific handler at create time so the
+ * NMI fast path is statically branched. Pass NULL as context --
+ * handlers are stateless wrt the per-event struct, so the NMI
+ * fast path performs no per-event heap dereference. Submission
+ * flows into the global per-CPU SPSC ring via damon_report_access().
+ */
+ handler = event->attr.sample_phys_addr ?
+ damon_perf_overflow_paddr : damon_perf_overflow_vaddr;
+
+ perf_event = perf_event_create_kernel_counter(&attr, cpu, NULL,
+ handler, NULL);
+ if (IS_ERR(perf_event)) {
+ pr_warn_ratelimited("damon-perf: cpu %u event create failed: %ld\n",
+ cpu, PTR_ERR(perf_event));
+ if (!event->init_complete)
+ event->any_cpu_failed = true;
+ return 0; /* never block CPU online */
+ }
+ *per_cpu_ptr(perf->event, cpu) = perf_event;
+ /*
+ * Late-online CPU after the substrate is armed: events are created
+ * with attr.disabled = 1 and would otherwise stay quiescent on this
+ * CPU until the next arm walk. Enable here so coverage matches the
+ * already-online CPUs.
+ */
+ if (event->ctx && READ_ONCE(event->ctx->perf_events_active))
+ perf_event_enable(perf_event);
+ return 0;
+}
+
+static int damon_perf_cpu_offline(unsigned int cpu, struct hlist_node *node)
+{
+ struct damon_perf_event *event = hlist_entry(node,
+ struct damon_perf_event, hlist_node);
+ struct damon_perf *perf = event->priv;
+ struct perf_event *perf_event;
+
+ if (!perf)
+ return 0;
+
+ perf_event = per_cpu(*perf->event, cpu);
+ if (perf_event) {
+ perf_event_disable(perf_event);
+ perf_event_release_kernel(perf_event);
+ *per_cpu_ptr(perf->event, cpu) = NULL;
+ }
+ return 0;
+}
+
+void damon_perf_event_arm(struct damon_perf_event *event)
+{
+ struct damon_perf *perf = event->priv;
+ struct perf_event *perf_event;
+ int cpu;
+
+ if (!perf)
+ return;
+
+ for_each_online_cpu(cpu) {
+ perf_event = *per_cpu_ptr(perf->event, cpu);
+ if (perf_event)
+ perf_event_enable(perf_event);
+ }
+}
+
+void damon_perf_event_disarm(struct damon_perf_event *event)
+{
+ struct damon_perf *perf = event->priv;
+ struct perf_event *perf_event;
+ int cpu;
+
+ if (!perf)
+ return;
+
+ for_each_online_cpu(cpu) {
+ perf_event = *per_cpu_ptr(perf->event, cpu);
+ if (perf_event)
+ perf_event_disable(perf_event);
+ }
+}
+
+int damon_perf_init(struct damon_ctx *ctx, struct damon_perf_event *event)
+{
+ struct damon_perf *perf;
+ int err = -ENOMEM;
+
+ perf = kzalloc(sizeof(*perf), GFP_KERNEL);
+ if (!perf)
+ return -ENOMEM;
+
+ perf->event = alloc_percpu(typeof(*perf->event));
+ if (!perf->event)
+ goto free_perf;
+
+ event->priv = perf;
+ event->ctx = ctx;
+ INIT_HLIST_NODE(&event->hlist_node);
+
+ /*
+ * cpuhp_state_add_instance() invokes the online callback synchronously
+ * for every currently-online CPU; late-online CPUs subsequently get
+ * an event automatically and offline CPUs release theirs cleanly.
+ */
+ err = cpuhp_state_add_instance(damon_perf_cpuhp_state,
+ &event->hlist_node);
+ if (err)
+ goto free_event;
+
+ event->init_complete = true;
+ if (event->any_cpu_failed) {
+ cpuhp_state_remove_instance(damon_perf_cpuhp_state,
+ &event->hlist_node);
+ err = -ENODEV;
+ goto free_event;
+ }
+
+ return 0;
+
+free_event:
+ free_percpu(perf->event);
+free_perf:
+ kfree(perf);
+ event->priv = NULL;
+ return err;
+}
+
+void damon_perf_cleanup(struct damon_ctx *ctx, struct damon_perf_event *event)
+{
+ struct damon_perf *perf = event->priv;
+
+ if (!perf)
+ return;
+
+ cpuhp_state_remove_instance(damon_perf_cpuhp_state,
+ &event->hlist_node);
+
+ free_percpu(perf->event);
+ kfree(perf);
+ event->priv = NULL;
+}
+
+#endif /* CONFIG_PERF_EVENTS */
+
static int __init damon_va_initcall(void)
{
struct damon_operations ops = {
@@ -979,6 +1238,14 @@ static int __init damon_va_initcall(void)
ops_fvaddr.init = NULL;
ops_fvaddr.update = NULL;
+#ifdef CONFIG_PERF_EVENTS
+ err = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "damon/perf:online",
+ damon_perf_cpu_online, damon_perf_cpu_offline);
+ if (err < 0)
+ return err;
+ damon_perf_cpuhp_state = err;
+#endif
+
err = damon_register_ops(&ops);
if (err)
return err;
--
2.43.0
^ permalink raw reply related
* [RFC PATCH 4/6] mm/damon/core: per-CPU SPSC ring drain and damon_perf_event lifecycle
From: Ravi Jonnalagadda @ 2026-05-29 16:56 UTC (permalink / raw)
To: sj, akinobu.mita, damon, linux-mm, linux-kernel, linux-doc
Cc: akpm, corbet, bijan311, ajayjoshi, honggyu.kim, yunjeong.mun,
ravis.opensrc
In-Reply-To: <20260529165640.820-1-ravis.opensrc@gmail.com>
Replace the mutex-protected damon_access_reports[] single-buffer with
a per-CPU SPSC ring. The producer (damon_report_access) is called
from NMI by perf overflow handlers; the consumer
(kdamond_check_reported_accesses) runs once per sample tick.
- 256-entry ring per CPU with cache-line-aligned head/tail
- per-CPU damon_report_ring_busy guards against NMI nesting on top
of a process-context producer on the same CPU
- per-CPU damon_ring_pending bit so the consumer iterates only CPUs
that produced samples this tick
- smp_mb between flag clear and head read on the consumer side
pairs with the producer's head-publish ordering
Replace the O(N) per-region scan in kdamond_apply_access_report() with
bsearch over a per-tick per-target snapshot built into a reusable
damon_ctx::drain_snapshot buffer. The pid-based ctx early-reject is
no longer needed: kdamond_apply_access_report() already discriminates
report->vaddr vs report->paddr per ctx.
Wire the damon_perf_event lifecycle: init per attached event when
kdamond starts, teardown when the ctx is destroyed, replayed across
damon_commit_ctx. Add the matching forward decl + drain_snapshot
field on struct damon_ctx.
Signed-off-by: Ravi Jonnalagadda <ravis.opensrc@gmail.com>
---
include/trace/events/damon.h | 17 ++
mm/damon/core.c | 383 ++++++++++++++++++++++++++++++-----
2 files changed, 344 insertions(+), 56 deletions(-)
diff --git a/include/trace/events/damon.h b/include/trace/events/damon.h
index b131bee27cc4a..e97e70579a8c8 100644
--- a/include/trace/events/damon.h
+++ b/include/trace/events/damon.h
@@ -74,6 +74,23 @@ TRACE_EVENT(damos_esz,
__entry->esz)
);
+TRACE_EVENT(damon_perf_ring_overflow,
+
+ TP_PROTO(int cpu),
+
+ TP_ARGS(cpu),
+
+ TP_STRUCT__entry(
+ __field(int, cpu)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu = cpu;
+ ),
+
+ TP_printk("cpu=%d", __entry->cpu)
+);
+
TRACE_EVENT_CONDITION(damos_before_apply,
TP_PROTO(unsigned int context_idx, unsigned int scheme_idx,
diff --git a/mm/damon/core.c b/mm/damon/core.c
index 23311189b589e..1e6966e45144f 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -8,6 +8,7 @@
#define pr_fmt(fmt) "damon: " fmt
#include <linux/damon.h>
+#include <asm/local.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/memcontrol.h>
@@ -24,22 +25,43 @@
#define CREATE_TRACE_POINTS
#include <trace/events/damon.h>
-#define DAMON_ACCESS_REPORTS_CAP 1000
+#define DAMON_REPORT_RING_SIZE 256
+#define DAMON_REPORT_RING_MASK (DAMON_REPORT_RING_SIZE - 1)
+
+/* Per-target region lookup snapshot for the drain loop. */
+struct damon_target_lookup {
+ struct damon_target *t;
+ struct damon_region **regions;
+ unsigned int nr_regions;
+};
+
+struct damon_report_ring {
+ unsigned int head; /* written by producer (NMI) */
+ unsigned int tail /* written by consumer (kdamond) */
+ ____cacheline_aligned_in_smp;
+ struct damon_access_report entries[DAMON_REPORT_RING_SIZE]
+ ____cacheline_aligned_in_smp;
+};
+
+static DEFINE_PER_CPU(struct damon_report_ring, damon_report_rings);
+static DEFINE_PER_CPU(local_t, damon_report_ring_busy);
+/*
+ * Producer (NMI) sets after publishing a report; consumer (kdamond) clears
+ * before draining the corresponding ring. Per-CPU to avoid cross-CPU
+ * cacheline bouncing under sampling load on large systems.
+ */
+static DEFINE_PER_CPU(unsigned long, damon_ring_pending);
static DEFINE_MUTEX(damon_lock);
static int nr_running_ctxs;
static bool running_exclusive_ctxs;
+static struct damon_ctx *damon_perf_owner;
static DEFINE_MUTEX(damon_ops_lock);
static struct damon_operations damon_registered_ops[NR_DAMON_OPS];
static struct kmem_cache *damon_region_cache __ro_after_init;
-static DEFINE_MUTEX(damon_access_reports_lock);
-static struct damon_access_report damon_access_reports[
- DAMON_ACCESS_REPORTS_CAP];
-static int damon_access_reports_len;
-
/* Should be called under damon_ops_lock with id smaller than NR_DAMON_OPS */
static bool __damon_is_registered_ops(enum damon_ops_id id)
{
@@ -805,11 +827,24 @@ struct damon_ctx *damon_new_ctx(void)
INIT_LIST_HEAD(&ctx->adaptive_targets);
INIT_LIST_HEAD(&ctx->schemes);
+ INIT_LIST_HEAD(&ctx->perf_events);
+
prandom_seed_state(&ctx->rnd_state, get_random_u64());
return ctx;
}
+static void damon_perf_destroy(struct damon_ctx *ctx)
+{
+ struct damon_perf_event *event, *next;
+
+ list_for_each_entry_safe(event, next, &ctx->perf_events, list) {
+ damon_perf_cleanup(ctx, event);
+ list_del(&event->list);
+ kfree(event);
+ }
+}
+
static void damon_destroy_targets(struct damon_ctx *ctx)
{
struct damon_target *t, *next_t;
@@ -835,6 +870,11 @@ void damon_destroy_ctx(struct damon_ctx *ctx)
damon_for_each_sample_filter_safe(f, next_f, &ctx->sample_control)
damon_destroy_sample_filter(f, &ctx->sample_control);
+ damon_perf_destroy(ctx);
+
+ kfree(ctx->drain_snapshot.lookups);
+ kfree(ctx->drain_snapshot.region_buf);
+
kfree(ctx);
}
@@ -1694,6 +1734,45 @@ static int damon_commit_sample_control(
return damon_commit_sample_filters(dst, src);
}
+static int damon_commit_perf_events(struct damon_ctx *dst,
+ struct damon_ctx *src)
+{
+ struct damon_perf_event *src_event, *new_event;
+ int err = 0;
+
+ damon_perf_destroy(dst);
+
+ list_for_each_entry(src_event, &src->perf_events, list) {
+ new_event = kzalloc(sizeof(*new_event), GFP_KERNEL);
+ if (!new_event) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ new_event->attr = src_event->attr;
+
+ if (damon_is_running(dst)) {
+ err = damon_perf_init(dst, new_event);
+ if (err) {
+ kfree(new_event);
+ goto out;
+ }
+ /*
+ * Events are created with attr.disabled=1 and only fire while
+ * the kdamond runs. Arm now if we are committing into a
+ * running ctx whose substrate is already armed.
+ */
+ if (dst->perf_events_active)
+ damon_perf_event_arm(new_event);
+ }
+ list_add_tail(&new_event->list, &dst->perf_events);
+ }
+ return 0;
+out:
+ damon_perf_destroy(dst);
+ return err;
+}
+
static int __damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src)
{
int err;
@@ -1742,6 +1821,9 @@ static int __damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src)
return err;
err = damon_commit_sample_control(&dst->sample_control,
&src->sample_control);
+ if (err)
+ return err;
+ err = damon_commit_perf_events(dst, src);
if (err)
return err;
dst->addr_unit = src->addr_unit;
@@ -1929,12 +2011,40 @@ int damon_start(struct damon_ctx **ctxs, int nr_ctxs, bool exclusive)
return -EBUSY;
}
+ /*
+ * The per-CPU PMU events backing the perf-event substrate are a single
+ * shared resource; only one ctx may own them. Reject the start if
+ * another already-running ctx owns the substrate, or if more than one
+ * ctx in this batch wants it.
+ */
+ for (i = 0; i < nr_ctxs; i++) {
+ if (!list_empty(&ctxs[i]->perf_events)) {
+ int j;
+
+ if (damon_perf_owner) {
+ mutex_unlock(&damon_lock);
+ return -EBUSY;
+ }
+ for (j = i + 1; j < nr_ctxs; j++) {
+ if (!list_empty(&ctxs[j]->perf_events)) {
+ mutex_unlock(&damon_lock);
+ return -EBUSY;
+ }
+ }
+ damon_perf_owner = ctxs[i];
+ break;
+ }
+ }
+
for (i = 0; i < nr_ctxs; i++) {
err = __damon_start(ctxs[i]);
if (err)
break;
nr_running_ctxs++;
}
+ if (err && damon_perf_owner &&
+ !damon_perf_owner->kdamond)
+ damon_perf_owner = NULL;
if (exclusive && nr_running_ctxs)
running_exclusive_ctxs = true;
mutex_unlock(&damon_lock);
@@ -2113,29 +2223,47 @@ int damos_walk(struct damon_ctx *ctx, struct damos_walk_control *control)
* damon_report_access() - Report identified access events to DAMON.
* @report: The reporting access information.
*
- * Report access events to DAMON.
+ * Report access events to DAMON via a per-CPU SPSC lockless ring. Producer
+ * is the local CPU (typically NMI from a hardware-sampling backend);
+ * consumer is the kdamond drain in kdamond_check_reported_accesses().
*
- * Context: May sleep.
+ * Context: any (NMI-safe). An NMI nesting on top of a process-context
+ * producer on the same CPU would otherwise stomp the same entries[head]
+ * slot; the busy guard detects and drops in that case.
*
- * NOTE: we may be able to implement this as a lockless queue, and allow any
- * context. As the overhead is unknown, and region-based DAMON logics would
- * guarantee the reports would be not made that frequently, let's start with
- * this simple implementation.
+ * If the ring is full, the sample is dropped and the per-CPU overflow
+ * counter incremented.
*/
void damon_report_access(struct damon_access_report *report)
{
- struct damon_access_report *dst;
+ struct damon_report_ring *ring;
+ unsigned int head, next;
- /* silently fail for races */
- if (!mutex_trylock(&damon_access_reports_lock))
- return;
- dst = &damon_access_reports[damon_access_reports_len++];
- /* just drop all existing reports in favor of simplicity. */
- if (damon_access_reports_len == DAMON_ACCESS_REPORTS_CAP)
- damon_access_reports_len = 0;
- *dst = *report;
- dst->report_jiffies = jiffies;
- mutex_unlock(&damon_access_reports_lock);
+ /* Pin to a CPU so the SPSC invariant holds for preemptible callers. */
+ preempt_disable();
+ if (local_inc_return(this_cpu_ptr(&damon_report_ring_busy)) != 1) {
+ /* NMI nested on a process-context producer; drop. */
+ trace_damon_perf_ring_overflow(smp_processor_id());
+ goto out;
+ }
+
+ ring = this_cpu_ptr(&damon_report_rings);
+ head = ring->head;
+ next = (head + 1) & DAMON_REPORT_RING_MASK;
+
+ if (next == READ_ONCE(ring->tail)) {
+ trace_damon_perf_ring_overflow(smp_processor_id());
+ goto out;
+ }
+
+ ring->entries[head] = *report;
+ ring->entries[head].report_jiffies = jiffies;
+ smp_wmb(); /* publish entry before head advance */
+ WRITE_ONCE(ring->head, next);
+ WRITE_ONCE(*this_cpu_ptr(&damon_ring_pending), 1);
+out:
+ local_dec(this_cpu_ptr(&damon_report_ring_busy));
+ preempt_enable();
}
#ifdef CONFIG_MMU
@@ -2145,7 +2273,8 @@ void damon_report_page_fault(struct vm_fault *vmf, bool huge_pmd)
.vaddr = vmf->address,
.size = 1, /* todo: set appripriately */
.cpu = smp_processor_id(),
- .tid = task_pid_vnr(current),
+ .tid = current->pid,
+ .tgid = task_tgid_nr(current),
.is_write = vmf->flags & FAULT_FLAG_WRITE,
};
@@ -3700,6 +3829,7 @@ static void kdamond_init_ctx(struct damon_ctx *ctx)
unsigned long sample_interval = ctx->attrs.sample_interval ?
ctx->attrs.sample_interval : 1;
struct damos *scheme;
+ struct damon_perf_event *event, *next;
ctx->passed_sample_intervals = 0;
ctx->next_aggregation_sis = ctx->attrs.aggr_interval / sample_interval;
@@ -3713,6 +3843,15 @@ static void kdamond_init_ctx(struct damon_ctx *ctx)
damos_set_next_apply_sis(scheme, ctx);
damos_set_filters_default_reject(scheme);
}
+
+ list_for_each_entry_safe(event, next, &ctx->perf_events, list) {
+ int err = damon_perf_init(ctx, event);
+
+ if (err) {
+ list_del(&event->list);
+ kfree(event);
+ }
+ }
}
static bool damon_sample_filter_matching(struct damon_access_report *report,
@@ -3759,26 +3898,46 @@ static bool damon_sample_filter_out(struct damon_access_report *report,
}
static void kdamond_apply_access_report(struct damon_access_report *report,
- struct damon_target *t, struct damon_ctx *ctx)
+ struct damon_target *t,
+ struct damon_region **regions, unsigned int nr_regions,
+ struct damon_ctx *ctx)
{
struct damon_region *r;
unsigned long addr;
+ int left, right, mid;
- if (damon_sample_filter_out(report, &ctx->sample_control))
- return;
- if (damon_target_has_pid(ctx))
+ if (damon_target_has_pid(ctx)) {
+ if (pid_nr(t->pid) != report->tgid)
+ return;
addr = report->vaddr;
- else
+ } else {
addr = report->paddr;
+ }
- /* todo: make search faster, e.g., binary search? */
- damon_for_each_region(r, t) {
- if (addr < r->ar.start)
- continue;
- if (r->ar.end < addr + report->size)
- continue;
- if (!r->access_reported)
- damon_update_region_access_rate(r, true, &ctx->attrs);
+ /* Binary search the snapshot for the region containing addr. */
+ left = 0;
+ right = nr_regions - 1;
+ r = NULL;
+ while (left <= right) {
+ /* Avoid (left + right) overflow at large nr_regions. */
+ mid = left + (right - left) / 2;
+ if (addr < regions[mid]->ar.start)
+ right = mid - 1;
+ else if (addr >= regions[mid]->ar.end)
+ left = mid + 1;
+ else {
+ r = regions[mid];
+ break;
+ }
+ }
+
+ if (!r)
+ return;
+ /* Reject reports straddling a region boundary. */
+ if (addr + report->size > r->ar.end)
+ return;
+ if (!r->access_reported) {
+ damon_update_region_access_rate(r, true, &ctx->attrs);
r->access_reported = true;
}
}
@@ -3802,28 +3961,120 @@ static unsigned int kdamond_apply_zero_access_report(struct damon_ctx *ctx)
return max_nr_accesses;
}
-static unsigned int kdamond_check_reported_accesses(struct damon_ctx *ctx)
+/*
+ * Build a snapshot of the ctx's targets and their region arrays for use
+ * by the ring drain loop. The snapshot buffer is reused across ticks,
+ * grown via krealloc only when a new high water mark is reached.
+ *
+ * The two-pass walk over adaptive_targets is safe even though
+ * krealloc_array() may sleep: target list mutation is funneled through
+ * damon_call onto the kdamond itself, so no other thread can mutate the
+ * list while kdamond is running this function.
+ */
+static struct damon_target_lookup *damon_build_target_lookup(
+ struct damon_ctx *ctx, unsigned int *nr_targets_out)
{
- int i;
- struct damon_access_report *report;
struct damon_target *t;
+ struct damon_target_lookup *tbl;
+ unsigned int nr_targets = 0, total_regions = 0, ti = 0, ri = 0;
- /* currently damon_access_report supports only physical address */
- if (damon_target_has_pid(ctx))
- return 0;
+ damon_for_each_target(t, ctx) {
+ nr_targets++;
+ total_regions += damon_nr_regions(t);
+ }
- mutex_lock(&damon_access_reports_lock);
- for (i = 0; i < damon_access_reports_len; i++) {
- report = &damon_access_reports[i];
- if (time_before(report->report_jiffies,
- jiffies -
- usecs_to_jiffies(
- ctx->attrs.sample_interval)))
+ if (nr_targets > ctx->drain_snapshot.nr_lookups) {
+ tbl = krealloc_array(ctx->drain_snapshot.lookups,
+ nr_targets, sizeof(*tbl), GFP_KERNEL);
+ if (!tbl)
+ return NULL;
+ ctx->drain_snapshot.lookups = tbl;
+ ctx->drain_snapshot.nr_lookups = nr_targets;
+ }
+ tbl = ctx->drain_snapshot.lookups;
+
+ if (total_regions > ctx->drain_snapshot.region_buf_cap) {
+ struct damon_region **buf;
+
+ buf = krealloc_array(ctx->drain_snapshot.region_buf,
+ total_regions, sizeof(*buf), GFP_KERNEL);
+ if (!buf)
+ return NULL;
+ ctx->drain_snapshot.region_buf = buf;
+ ctx->drain_snapshot.region_buf_cap = total_regions;
+ }
+
+ damon_for_each_target(t, ctx) {
+ struct damon_region *r;
+
+ tbl[ti].t = t;
+ tbl[ti].regions = &ctx->drain_snapshot.region_buf[ri];
+ tbl[ti].nr_regions = damon_nr_regions(t);
+ damon_for_each_region(r, t)
+ ctx->drain_snapshot.region_buf[ri++] = r;
+ ti++;
+ }
+
+ *nr_targets_out = nr_targets;
+ return tbl;
+}
+
+static unsigned int kdamond_check_reported_accesses(struct damon_ctx *ctx)
+{
+ int cpu;
+ struct damon_target_lookup *tbl;
+ unsigned int nr_targets = 0;
+ unsigned int i;
+
+ tbl = damon_build_target_lookup(ctx, &nr_targets);
+ if (!tbl) {
+ pr_warn_ratelimited(
+ "damon: target-lookup alloc failed; ring drain skipped this tick\n");
+ return kdamond_apply_zero_access_report(ctx);
+ }
+
+ for_each_online_cpu(cpu) {
+ struct damon_report_ring *ring;
+ unsigned int head, tail;
+
+ if (!READ_ONCE(*per_cpu_ptr(&damon_ring_pending, cpu)))
continue;
- damon_for_each_target(t, ctx)
- kdamond_apply_access_report(report, t, ctx);
+ ring = per_cpu_ptr(&damon_report_rings, cpu);
+
+ WRITE_ONCE(*per_cpu_ptr(&damon_ring_pending, cpu), 0);
+ /*
+ * Pair with the producer's smp_wmb between entry and head
+ * publish: order our flag clear before the head read so that
+ * a producer publishing between our clear and READ_ONCE(head)
+ * is observed via the flag it re-sets, not lost as a
+ * stale-head drain.
+ */
+ smp_mb();
+ head = READ_ONCE(ring->head);
+ smp_rmb(); /* pair with smp_wmb in producer */
+ tail = ring->tail;
+
+ while (tail != head) {
+ struct damon_access_report *report =
+ &ring->entries[tail];
+
+ if (time_before(report->report_jiffies,
+ jiffies - usecs_to_jiffies(
+ ctx->attrs.sample_interval)))
+ goto next;
+ if (damon_sample_filter_out(report,
+ &ctx->sample_control))
+ goto next;
+ for (i = 0; i < nr_targets; i++)
+ kdamond_apply_access_report(report,
+ tbl[i].t,
+ tbl[i].regions,
+ tbl[i].nr_regions, ctx);
+next:
+ tail = (tail + 1) & DAMON_REPORT_RING_MASK;
+ }
+ WRITE_ONCE(ring->tail, tail);
}
- mutex_unlock(&damon_access_reports_lock);
/* For nr_accesses_bp, absence of access should also be reported. */
return kdamond_apply_zero_access_report(ctx);
}
@@ -3848,6 +4099,14 @@ static int kdamond_fn(void *data)
complete(&ctx->kdamond_started);
kdamond_init_ctx(ctx);
+ if (!list_empty(&ctx->perf_events)) {
+ struct damon_perf_event *event;
+
+ WRITE_ONCE(ctx->perf_events_active, true);
+ list_for_each_entry(event, &ctx->perf_events, list)
+ damon_perf_event_arm(event);
+ }
+
if (ctx->ops.init)
ctx->ops.init(ctx);
ctx->regions_score_histogram = kmalloc_array(DAMOS_MAX_SCORE + 1,
@@ -3871,14 +4130,15 @@ static int kdamond_fn(void *data)
if (kdamond_wait_activation(ctx))
break;
- if (ctx->ops.prepare_access_checks)
+ if (list_empty(&ctx->perf_events) &&
+ ctx->ops.prepare_access_checks)
ctx->ops.prepare_access_checks(ctx);
kdamond_usleep(sample_interval);
ctx->passed_sample_intervals++;
- /* todo: make these non-exclusive */
- if (ctx->sample_control.primitives_enabled.page_fault)
+ if (!list_empty(&ctx->perf_events) ||
+ ctx->sample_control.primitives_enabled.page_fault)
max_nr_accesses = kdamond_check_reported_accesses(ctx);
else if (ctx->ops.check_accesses)
max_nr_accesses = ctx->ops.check_accesses(ctx);
@@ -3965,6 +4225,15 @@ static int kdamond_fn(void *data)
}
}
done:
+ if (ctx->perf_events_active) {
+ struct damon_perf_event *event;
+
+ WRITE_ONCE(ctx->perf_events_active, false);
+ list_for_each_entry(event, &ctx->perf_events, list)
+ damon_perf_event_disarm(event);
+ /* Drain any in-flight reports queued before disarm took effect. */
+ kdamond_check_reported_accesses(ctx);
+ }
damon_destroy_targets(ctx);
kfree(ctx->regions_score_histogram);
@@ -3986,6 +4255,8 @@ static int kdamond_fn(void *data)
nr_running_ctxs--;
if (!nr_running_ctxs && running_exclusive_ctxs)
running_exclusive_ctxs = false;
+ if (damon_perf_owner == ctx)
+ damon_perf_owner = NULL;
mutex_unlock(&damon_lock);
return 0;
--
2.43.0
^ permalink raw reply related
* [RFC PATCH 3/6] mm/damon/sysfs: install perf_events on apply
From: Ravi Jonnalagadda @ 2026-05-29 16:56 UTC (permalink / raw)
To: sj, akinobu.mita, damon, linux-mm, linux-kernel, linux-doc
Cc: akpm, corbet, bijan311, ajayjoshi, honggyu.kim, yunjeong.mun,
ravis.opensrc
In-Reply-To: <20260529165640.820-1-ravis.opensrc@gmail.com>
Call damon_sysfs_add_perf_events() from damon_sysfs_apply_inputs() so
events configured under sample/perf_events/ get attached to the
damon_ctx when the kdamond starts.
Signed-off-by: Ravi Jonnalagadda <ravis.opensrc@gmail.com>
---
mm/damon/sysfs.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c
index 9f71871a249d8..bc4a931fe3f0a 100644
--- a/mm/damon/sysfs.c
+++ b/mm/damon/sysfs.c
@@ -2092,6 +2092,9 @@ static int damon_sysfs_apply_inputs(struct damon_ctx *ctx,
return err;
err = damon_sysfs_set_sample_control(&ctx->sample_control,
sys_ctx->attrs->sample);
+ if (err)
+ return err;
+ err = damon_sysfs_add_perf_events(ctx, sys_ctx->attrs->sample);
if (err)
return err;
err = damon_sysfs_add_targets(ctx, sys_ctx->targets);
--
2.43.0
^ permalink raw reply related
* [RFC PATCH 2/6] mm/damon/sysfs-sample: expose perf_events configuration via sysfs
From: Ravi Jonnalagadda @ 2026-05-29 16:56 UTC (permalink / raw)
To: sj, akinobu.mita, damon, linux-mm, linux-kernel, linux-doc
Cc: akpm, corbet, bijan311, ajayjoshi, honggyu.kim, yunjeong.mun,
ravis.opensrc
In-Reply-To: <20260529165640.820-1-ravis.opensrc@gmail.com>
Add a perf_events/ subdirectory under each context's sample/ directory.
Each numbered entry maps to one damon_perf_event and exposes its raw
PMU attr, addressing flags, and period/delivery knobs.
Defaults match Intel PEBS L3-miss; userspace overrides them for other
PMUs. sample_weight_struct defaults off because PMUs that do not
advertise PERF_SAMPLE_WEIGHT_STRUCT (e.g. AMD IBS Op) reject events
that request it with -EOPNOTSUPP.
Signed-off-by: Ravi Jonnalagadda <ravis.opensrc@gmail.com>
---
mm/damon/sysfs-sample.c | 579 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 579 insertions(+)
diff --git a/mm/damon/sysfs-sample.c b/mm/damon/sysfs-sample.c
index ffc9c85455474..0570d27a47b1c 100644
--- a/mm/damon/sysfs-sample.c
+++ b/mm/damon/sysfs-sample.c
@@ -452,6 +452,520 @@ static const struct kobj_type damon_sysfs_primitives_ktype = {
.default_groups = damon_sysfs_primitives_groups,
};
+/*
+ * perf_event_attr directory
+ */
+
+struct damon_sysfs_perf_event_attr {
+ struct kobject kobj;
+ u32 type;
+ u64 config;
+ u64 config1;
+ u64 config2;
+ bool sample_phys_addr;
+ bool sample_weight_struct;
+ bool exclude_kernel;
+ bool exclude_hv;
+ bool freq;
+ u64 sample_freq;
+ u64 sample_period;
+ u32 wakeup_events;
+ u32 precise_ip;
+};
+
+static struct damon_sysfs_perf_event_attr *
+damon_sysfs_perf_event_attr_alloc(void)
+{
+ struct damon_sysfs_perf_event_attr *attr =
+ kzalloc(sizeof(*attr), GFP_KERNEL);
+
+ if (!attr)
+ return NULL;
+ attr->wakeup_events = 1;
+ attr->precise_ip = 2;
+ attr->freq = true;
+ attr->exclude_kernel = true;
+ attr->exclude_hv = true;
+ return attr;
+}
+
+static ssize_t attr_type_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "0x%x\n", perf_event_attr->type);
+}
+
+static ssize_t attr_type_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou32(buf, 0, &perf_event_attr->type);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t config_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "0x%llx\n", perf_event_attr->config);
+}
+
+static ssize_t config_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou64(buf, 0, &perf_event_attr->config);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t config1_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "0x%llx\n", perf_event_attr->config1);
+}
+
+static ssize_t config1_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou64(buf, 0, &perf_event_attr->config1);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t config2_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "0x%llx\n", perf_event_attr->config2);
+}
+
+static ssize_t config2_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou64(buf, 0, &perf_event_attr->config2);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t sample_phys_addr_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "%d\n", perf_event_attr->sample_phys_addr);
+}
+
+static ssize_t sample_phys_addr_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ bool sample_phys_addr;
+ int err = kstrtobool(buf, &sample_phys_addr);
+
+ if (err)
+ return -EINVAL;
+
+ perf_event_attr->sample_phys_addr = sample_phys_addr;
+ return count;
+}
+
+static ssize_t sample_weight_struct_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "%d\n", perf_event_attr->sample_weight_struct);
+}
+
+static ssize_t sample_weight_struct_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ bool sample_weight_struct;
+ int err = kstrtobool(buf, &sample_weight_struct);
+
+ if (err)
+ return -EINVAL;
+
+ perf_event_attr->sample_weight_struct = sample_weight_struct;
+ return count;
+}
+
+static ssize_t sample_freq_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "%llu\n", perf_event_attr->sample_freq);
+}
+
+static ssize_t sample_freq_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou64(buf, 0, &perf_event_attr->sample_freq);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t wakeup_events_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "%u\n", perf_event_attr->wakeup_events);
+}
+
+static ssize_t wakeup_events_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou32(buf, 0, &perf_event_attr->wakeup_events);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t precise_ip_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "%u\n", perf_event_attr->precise_ip);
+}
+
+static ssize_t precise_ip_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou32(buf, 0, &perf_event_attr->precise_ip);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t freq_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "%d\n", perf_event_attr->freq);
+}
+
+static ssize_t freq_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ bool freq;
+ int err = kstrtobool(buf, &freq);
+
+ if (err)
+ return -EINVAL;
+ perf_event_attr->freq = freq;
+ return count;
+}
+
+static ssize_t sample_period_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "%llu\n", perf_event_attr->sample_period);
+}
+
+static ssize_t sample_period_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou64(buf, 0, &perf_event_attr->sample_period);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t exclude_kernel_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "%d\n", perf_event_attr->exclude_kernel);
+}
+
+static ssize_t exclude_kernel_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ bool v;
+ int err = kstrtobool(buf, &v);
+
+ if (err)
+ return -EINVAL;
+ perf_event_attr->exclude_kernel = v;
+ return count;
+}
+
+static ssize_t exclude_hv_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "%d\n", perf_event_attr->exclude_hv);
+}
+
+static ssize_t exclude_hv_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ bool v;
+ int err = kstrtobool(buf, &v);
+
+ if (err)
+ return -EINVAL;
+ perf_event_attr->exclude_hv = v;
+ return count;
+}
+
+static void damon_sysfs_perf_event_attr_release(struct kobject *kobj)
+{
+ kfree(container_of(kobj, struct damon_sysfs_perf_event_attr, kobj));
+}
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_type_attr =
+ __ATTR(type, 0600, attr_type_show, attr_type_store);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_config_attr =
+ __ATTR_RW_MODE(config, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_config1_attr =
+ __ATTR_RW_MODE(config1, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_config2_attr =
+ __ATTR_RW_MODE(config2, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_sample_phys_addr_attr =
+ __ATTR_RW_MODE(sample_phys_addr, 0600);
+
+static struct kobj_attribute
+ damon_sysfs_perf_event_attr_sample_weight_struct_attr =
+ __ATTR_RW_MODE(sample_weight_struct, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_sample_freq_attr =
+ __ATTR_RW_MODE(sample_freq, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_wakeup_events_attr =
+ __ATTR_RW_MODE(wakeup_events, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_precise_ip_attr =
+ __ATTR_RW_MODE(precise_ip, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_freq_attr =
+ __ATTR_RW_MODE(freq, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_sample_period_attr =
+ __ATTR_RW_MODE(sample_period, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_exclude_kernel_attr =
+ __ATTR_RW_MODE(exclude_kernel, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_exclude_hv_attr =
+ __ATTR_RW_MODE(exclude_hv, 0600);
+
+static struct attribute *damon_sysfs_perf_event_attr_attrs[] = {
+ &damon_sysfs_perf_event_attr_type_attr.attr,
+ &damon_sysfs_perf_event_attr_config_attr.attr,
+ &damon_sysfs_perf_event_attr_config1_attr.attr,
+ &damon_sysfs_perf_event_attr_config2_attr.attr,
+ &damon_sysfs_perf_event_attr_sample_phys_addr_attr.attr,
+ &damon_sysfs_perf_event_attr_sample_weight_struct_attr.attr,
+ &damon_sysfs_perf_event_attr_freq_attr.attr,
+ &damon_sysfs_perf_event_attr_sample_freq_attr.attr,
+ &damon_sysfs_perf_event_attr_sample_period_attr.attr,
+ &damon_sysfs_perf_event_attr_wakeup_events_attr.attr,
+ &damon_sysfs_perf_event_attr_precise_ip_attr.attr,
+ &damon_sysfs_perf_event_attr_exclude_kernel_attr.attr,
+ &damon_sysfs_perf_event_attr_exclude_hv_attr.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(damon_sysfs_perf_event_attr);
+
+static const struct kobj_type damon_sysfs_perf_event_attr_ktype = {
+ .release = damon_sysfs_perf_event_attr_release,
+ .sysfs_ops = &kobj_sysfs_ops,
+ .default_groups = damon_sysfs_perf_event_attr_groups,
+};
+
+/*
+ * perf_events directory
+ */
+
+/*
+ * Cap on the number of perf events per damon_ctx, to bound the sysfs
+ * kobject footprint and prevent unbounded allocations from a careless
+ * write to nr_perf_events.
+ */
+#define DAMON_SYSFS_PERF_EVENTS_MAX 64
+
+struct damon_sysfs_perf_events {
+ struct kobject kobj;
+ struct damon_sysfs_perf_event_attr **attrs_arr;
+ int nr;
+};
+
+static struct damon_sysfs_perf_events *damon_sysfs_perf_events_alloc(void)
+{
+ return kzalloc(sizeof(struct damon_sysfs_perf_events), GFP_KERNEL);
+}
+
+static void damon_sysfs_perf_events_rm_dirs(
+ struct damon_sysfs_perf_events *events)
+{
+ struct damon_sysfs_perf_event_attr **attrs_arr = events->attrs_arr;
+ int i;
+
+ for (i = 0; i < events->nr; i++)
+ kobject_put(&attrs_arr[i]->kobj);
+ events->nr = 0;
+ kfree(attrs_arr);
+ events->attrs_arr = NULL;
+}
+
+static int damon_sysfs_perf_events_add_dirs(
+ struct damon_sysfs_perf_events *events, int nr_events)
+{
+ struct damon_sysfs_perf_event_attr **attrs_arr, *attr;
+ int err, i;
+
+ damon_sysfs_perf_events_rm_dirs(events);
+ if (!nr_events)
+ return 0;
+
+ attrs_arr = kmalloc_array(nr_events, sizeof(*attrs_arr), GFP_KERNEL);
+ if (!attrs_arr)
+ return -ENOMEM;
+ events->attrs_arr = attrs_arr;
+
+ for (i = 0; i < nr_events; i++) {
+ attr = damon_sysfs_perf_event_attr_alloc();
+ if (!attr) {
+ damon_sysfs_perf_events_rm_dirs(events);
+ return -ENOMEM;
+ }
+
+ err = kobject_init_and_add(&attr->kobj,
+ &damon_sysfs_perf_event_attr_ktype, &events->kobj,
+ "%d", i);
+ if (err) {
+ kobject_put(&attr->kobj);
+ damon_sysfs_perf_events_rm_dirs(events);
+ return err;
+ }
+ attrs_arr[i] = attr;
+ events->nr++;
+ }
+ return 0;
+}
+
+static ssize_t nr_perf_events_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_events *events = container_of(kobj,
+ struct damon_sysfs_perf_events, kobj);
+
+ return sysfs_emit(buf, "%d\n", events->nr);
+}
+
+static ssize_t nr_perf_events_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_events *events;
+ int nr, err = kstrtoint(buf, 0, &nr);
+
+ if (err)
+ return err;
+ if (nr < 0 || nr > DAMON_SYSFS_PERF_EVENTS_MAX)
+ return -EINVAL;
+
+ events = container_of(kobj, struct damon_sysfs_perf_events, kobj);
+
+ if (!mutex_trylock(&damon_sysfs_lock))
+ return -EBUSY;
+ err = damon_sysfs_perf_events_add_dirs(events, nr);
+ mutex_unlock(&damon_sysfs_lock);
+ if (err)
+ return err;
+
+ return count;
+}
+
+static void damon_sysfs_perf_events_release(struct kobject *kobj)
+{
+ kfree(container_of(kobj, struct damon_sysfs_perf_events, kobj));
+}
+
+static struct kobj_attribute damon_sysfs_perf_events_nr_attr =
+ __ATTR_RW_MODE(nr_perf_events, 0600);
+
+static struct attribute *damon_sysfs_perf_events_attrs[] = {
+ &damon_sysfs_perf_events_nr_attr.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(damon_sysfs_perf_events);
+
+static const struct kobj_type damon_sysfs_perf_events_ktype = {
+ .release = damon_sysfs_perf_events_release,
+ .sysfs_ops = &kobj_sysfs_ops,
+ .default_groups = damon_sysfs_perf_events_groups,
+};
+
/*
* sample directory
*/
@@ -471,6 +985,7 @@ int damon_sysfs_sample_add_dirs(struct damon_sysfs_sample *sample)
{
struct damon_sysfs_primitives *primitives;
struct damon_sysfs_sample_filters *filters;
+ struct damon_sysfs_perf_events *perf_events;
int err;
primitives = damon_sysfs_primitives_alloc(true, false);
@@ -494,7 +1009,23 @@ int damon_sysfs_sample_add_dirs(struct damon_sysfs_sample *sample)
if (err)
goto put_filters_out;
sample->filters = filters;
+
+ perf_events = damon_sysfs_perf_events_alloc();
+ if (!perf_events) {
+ err = -ENOMEM;
+ goto put_filters_out;
+ }
+ err = kobject_init_and_add(&perf_events->kobj,
+ &damon_sysfs_perf_events_ktype, &sample->kobj,
+ "perf_events");
+ if (err)
+ goto put_perf_events_out;
+ sample->perf_events = perf_events;
+
return 0;
+put_perf_events_out:
+ kobject_put(&perf_events->kobj);
+ sample->perf_events = NULL;
put_filters_out:
kobject_put(&filters->kobj);
sample->filters = NULL;
@@ -512,6 +1043,10 @@ void damon_sysfs_sample_rm_dirs(struct damon_sysfs_sample *sample)
damon_sysfs_sample_filters_rm_dirs(sample->filters);
kobject_put(&sample->filters->kobj);
}
+ if (sample->perf_events) {
+ damon_sysfs_perf_events_rm_dirs(sample->perf_events);
+ kobject_put(&sample->perf_events->kobj);
+ }
}
void damon_sysfs_sample_release(struct kobject *kobj)
@@ -596,3 +1131,47 @@ int damon_sysfs_set_sample_control(
return damon_sysfs_set_sample_filters(control,
sysfs_sample->filters);
}
+
+static int damon_sysfs_add_perf_event(
+ struct damon_sysfs_perf_event_attr *sys_attr,
+ struct damon_ctx *ctx)
+{
+ struct damon_perf_event *event = kzalloc(sizeof(*event), GFP_KERNEL);
+
+ if (!event)
+ return -ENOMEM;
+
+ event->attr.type = sys_attr->type;
+ event->attr.config = sys_attr->config;
+ event->attr.config1 = sys_attr->config1;
+ event->attr.config2 = sys_attr->config2;
+ event->attr.sample_phys_addr = sys_attr->sample_phys_addr;
+ event->attr.sample_weight_struct = sys_attr->sample_weight_struct;
+ event->attr.freq = sys_attr->freq;
+ event->attr.sample_freq = sys_attr->sample_freq;
+ event->attr.sample_period = sys_attr->sample_period;
+ event->attr.wakeup_events = sys_attr->wakeup_events;
+ event->attr.precise_ip = sys_attr->precise_ip;
+ event->attr.exclude_kernel = sys_attr->exclude_kernel;
+ event->attr.exclude_hv = sys_attr->exclude_hv;
+
+ list_add_tail(&event->list, &ctx->perf_events);
+ return 0;
+}
+
+int damon_sysfs_add_perf_events(struct damon_ctx *ctx,
+ struct damon_sysfs_sample *sysfs_sample)
+{
+ struct damon_sysfs_perf_events *events = sysfs_sample->perf_events;
+ int i, err;
+
+ if (!events)
+ return 0;
+
+ for (i = 0; i < events->nr; i++) {
+ err = damon_sysfs_add_perf_event(events->attrs_arr[i], ctx);
+ if (err)
+ return err;
+ }
+ return 0;
+}
--
2.43.0
^ permalink raw reply related
* [RFC PATCH 1/6] mm/damon: add struct damon_perf_event{,_attr} and per-ctx perf_events list
From: Ravi Jonnalagadda @ 2026-05-29 16:56 UTC (permalink / raw)
To: sj, akinobu.mita, damon, linux-mm, linux-kernel, linux-doc
Cc: akpm, corbet, bijan311, ajayjoshi, honggyu.kim, yunjeong.mun,
ravis.opensrc
In-Reply-To: <20260529165640.820-1-ravis.opensrc@gmail.com>
Introduce the substrate types for using perf events as DAMON access
check sources. struct damon_perf_event_attr carries the raw PMU attr
configurable from userspace; struct damon_perf_event is the per-event
entry on a new damon_ctx::perf_events list.
Declare damon_perf_init() and damon_perf_cleanup() in
mm/damon/ops-common.h. When CONFIG_PERF_EVENTS=n they fold to a no-op
returning -ENOSYS.
Suggested-by: Akinobu Mita <akinobu.mita@gmail.com>
Link: https://lore.kernel.org/20260423004211.7037-1-akinobu.mita@gmail.com
Signed-off-by: Ravi Jonnalagadda <ravis.opensrc@gmail.com>
---
include/linux/damon.h | 80 +++++++++++++++++++++++++++++++++++++++++
mm/damon/ops-common.h | 39 ++++++++++++++++++++
mm/damon/sysfs-common.h | 6 ++++
3 files changed, 125 insertions(+)
diff --git a/include/linux/damon.h b/include/linux/damon.h
index c0375035a3a7b..11f1c1071b9ba 100644
--- a/include/linux/damon.h
+++ b/include/linux/damon.h
@@ -123,6 +123,7 @@ struct damon_target {
* @size: The size of the accessed address range.
* @cpu: The id of the CPU that made the access.
* @tid: The task id of the task that made the access.
+ * @tgid: Thread group id of the task that made the access.
* @is_write: Whether the access is write.
*
* Any DAMON API callers that notified access events can report the information
@@ -135,6 +136,7 @@ struct damon_access_report {
unsigned long size;
unsigned int cpu;
pid_t tid;
+ pid_t tgid;
bool is_write;
/* private: */
unsigned long report_jiffies; /* when this report is made */
@@ -501,6 +503,7 @@ struct damos_filter {
};
struct damon_ctx;
+struct damon_target_lookup;
struct damos;
/**
@@ -966,6 +969,67 @@ struct damon_sample_control {
struct list_head sample_filters;
};
+/**
+ * struct damon_perf_event_attr - raw PMU event attr for access check.
+ *
+ * @type: raw PMU event type.
+ * @config: raw PMU event config.
+ * @config1: raw PMU event config1.
+ * @config2: raw PMU event config2.
+ * @sample_phys_addr: whether to set PERF_SAMPLE_PHYS_ADDR in sample_type.
+ * @sample_weight_struct: whether to set PERF_SAMPLE_WEIGHT_STRUCT in
+ * sample_type. PMUs that do not advertise
+ * weight (e.g. AMD IBS Op) reject events with
+ * this flag set, so it must be opt-in.
+ * @exclude_kernel: exclude kernel-mode samples.
+ * @exclude_hv: exclude hypervisor samples.
+ * @freq: when true use @sample_freq, otherwise @sample_period.
+ * @sample_freq: target sample rate when @freq is true.
+ * @sample_period: period (samples-between-overflows) when @freq is false.
+ * @wakeup_events: perf_event_attr.wakeup_events.
+ * @precise_ip: precise sampling skid bound (PEBS-style PMUs).
+ */
+struct damon_perf_event_attr {
+ u32 type;
+ u64 config;
+ u64 config1;
+ u64 config2;
+ bool sample_phys_addr;
+ bool sample_weight_struct;
+ bool exclude_kernel;
+ bool exclude_hv;
+ bool freq;
+ u64 sample_freq;
+ u64 sample_period;
+ u32 wakeup_events;
+ u32 precise_ip;
+};
+
+/**
+ * struct damon_perf_event - perf event for access check.
+ *
+ * @attr: Per-event PMU attribute (configured via sysfs).
+ * @priv: Monitoring operations-specific data.
+ * @list: List head for &damon_ctx->perf_events siblings.
+ * @hlist_node: Tracks this event among cpuhp multi-instance entries.
+ * @init_complete: Set after the synchronous online sweep finishes; gates
+ * @any_cpu_failed writes from late hotplug callbacks.
+ * @any_cpu_failed: Set by the cpuhp online callback if perf_event creation
+ * fails on any CPU during the synchronous initial install.
+ * @ctx: Back-pointer to the owning damon_ctx; the cpu_online callback
+ * reads ctx->perf_events_active to decide whether to enable a
+ * late-onlining CPU's event immediately after create.
+ */
+struct damon_perf_event {
+ struct damon_perf_event_attr attr;
+ void *priv;
+ struct list_head list;
+ struct hlist_node hlist_node;
+ bool init_complete;
+ bool any_cpu_failed;
+ struct damon_ctx *ctx;
+};
+
/**
* struct damon_ctx - Represents a context for each monitoring. This is the
* main interface that allows users to set the attributes and get the results
@@ -991,6 +1055,11 @@ struct damon_sample_control {
* @addr_unit: Scale factor for core to ops address conversion.
* @min_region_sz: Minimum region size.
* @pause: Pause kdamond main loop.
+ * @perf_events: Head of perf events (&damon_perf_event) list.
+ * @perf_events_active: Set while kdamond_fn has the perf events armed.
+ * Cleared in the kdamond_fn done path before the events are
+ * disabled; serves as the gate for damon_commit_perf_events()
+ * and the kdamond_fn drain dispatch.
*/
struct damon_ctx {
struct damon_attrs attrs;
@@ -1046,6 +1115,9 @@ struct damon_ctx {
unsigned long min_region_sz;
bool pause;
+ struct list_head perf_events;
+ bool perf_events_active;
+
/* private: */
/* Head of monitoring targets (&damon_target) list. */
struct list_head adaptive_targets;
@@ -1054,6 +1126,14 @@ struct damon_ctx {
/* Per-ctx PRNG state for damon_rand(); kdamond is the sole consumer. */
struct rnd_state rnd_state;
+
+ /* Reusable drain-loop snapshot buffer (avoids per-tick kmalloc). */
+ struct {
+ struct damon_target_lookup *lookups;
+ unsigned int nr_lookups;
+ struct damon_region **region_buf;
+ unsigned int region_buf_cap;
+ } drain_snapshot;
};
/* Get a random number in [@l, @r) using @ctx's lockless PRNG. */
diff --git a/mm/damon/ops-common.h b/mm/damon/ops-common.h
index 5efa5b5970def..35da400a67ec1 100644
--- a/mm/damon/ops-common.h
+++ b/mm/damon/ops-common.h
@@ -23,3 +23,42 @@ bool damos_folio_filter_match(struct damos_filter *filter, struct folio *folio);
unsigned long damon_migrate_pages(struct list_head *folio_list, int target_nid);
bool damos_ops_has_filter(struct damos *s);
+
+#ifdef CONFIG_PERF_EVENTS
+
+/*
+ * Per-event opaque allocated by damon_perf_init(). The NMI overflow
+ * handler does NOT touch this struct; submission goes through the
+ * shared per-CPU SPSC ring via damon_report_access().
+ */
+struct damon_perf {
+ struct perf_event * __percpu *event;
+};
+
+int damon_perf_init(struct damon_ctx *ctx, struct damon_perf_event *event);
+void damon_perf_cleanup(struct damon_ctx *ctx, struct damon_perf_event *event);
+void damon_perf_event_arm(struct damon_perf_event *event);
+void damon_perf_event_disarm(struct damon_perf_event *event);
+
+#else /* !CONFIG_PERF_EVENTS */
+
+static inline int damon_perf_init(struct damon_ctx *ctx,
+ struct damon_perf_event *event)
+{
+ return -ENOSYS;
+}
+
+static inline void damon_perf_cleanup(struct damon_ctx *ctx,
+ struct damon_perf_event *event)
+{
+}
+
+static inline void damon_perf_event_arm(struct damon_perf_event *event)
+{
+}
+
+static inline void damon_perf_event_disarm(struct damon_perf_event *event)
+{
+}
+
+#endif /* CONFIG_PERF_EVENTS */
diff --git a/mm/damon/sysfs-common.h b/mm/damon/sysfs-common.h
index 25a6c28abdea8..67c7545fd57d0 100644
--- a/mm/damon/sysfs-common.h
+++ b/mm/damon/sysfs-common.h
@@ -66,10 +66,13 @@ int damon_sysfs_memcg_path_to_id(char *memcg_path, u64 *id);
* sample directory
*/
+struct damon_sysfs_perf_events;
+
struct damon_sysfs_sample {
struct kobject kobj;
struct damon_sysfs_primitives *primitives;
struct damon_sysfs_sample_filters *filters;
+ struct damon_sysfs_perf_events *perf_events;
};
struct damon_sysfs_sample *damon_sysfs_sample_alloc(void);
@@ -82,3 +85,6 @@ extern const struct kobj_type damon_sysfs_sample_ktype;
int damon_sysfs_set_sample_control(
struct damon_sample_control *control,
struct damon_sysfs_sample *sysfs_sample);
+
+int damon_sysfs_add_perf_events(struct damon_ctx *ctx,
+ struct damon_sysfs_sample *sysfs_sample);
--
2.43.0
^ permalink raw reply related
* [RFC PATCH 0/6] mm/damon: hardware-sampled access reports
From: Ravi Jonnalagadda @ 2026-05-29 16:56 UTC (permalink / raw)
To: sj, akinobu.mita, damon, linux-mm, linux-kernel, linux-doc
Cc: akpm, corbet, bijan311, ajayjoshi, honggyu.kim, yunjeong.mun,
ravis.opensrc
This series introduces a vendor and PMU-agnostic substrate inside DAMON
that consumes hardware-sampled access reports through the standard
perf-event interface. Userspace selects the PMU through sysfs (raw
type/config knobs), driving either Intel PEBS L3-miss sampling or AMD
IBS Op sampling.
Why a unified perf-event substrate
Earlier hardware-sampled access-monitoring proposal [1] took an AMD IBS
specific module path backend, owning its own probe configuration,
sysfs knobs, and lifecycle.
SeongJae Park has previously highlighted the advantage of Akinobu
Mita's perf-event proposal [2]: let DAMON register kernel-counter perf
events and consume samples from any sampling PMU that perf core knows
about. This series builds on that direction with the changes we
needed to run it cross-vendor:
- a per-CPU lockless ring between the NMI sample handler and the
kdamond drain,
- per-CPU events that follow CPU hotplug cleanly,
- events fire only while the monitor is running -- created disabled,
armed when kdamond starts, disarmed and drained when it stops,
- all-or-nothing init across CPUs: a partial-CPU create failure rolls
the whole event back rather than leaving silent gaps,
- safe handling of vendor sample-validity flags so a stale or
unpopulated address is never mistaken for a valid sample.
What the series adds
Patch 1 introduces the substrate's data types: a per-event
configuration struct and a per-context list to hang them on. A
CONFIG_PERF_EVENTS=n build folds to no-op stubs.
Patch 2 exposes those types through sysfs. Each entry maps to one
perf event and lets userspace pick the PMU and how to sample it: the
raw PMU type/config, addressing flags, and period or frequency. The
defaults are tuned for Intel PEBS; userspace overrides them for other
PMUs.
Patch 3 wires the sysfs apply path so configured events get attached
to the running monitoring context.
Patch 4 is the core of the series. It replaces the mutex-protected
report queue with a per-CPU lockless ring fed from NMI by the perf
overflow handler and drained once per sample tick by the kdamond.
Drained reports are matched to monitored regions by binary search
over a per-tick snapshot. The patch also wires the per-event
lifecycle into kdamond: events arm when the monitor starts, disarm
and drain when it stops, roll back cleanly when per-CPU init fails on
some CPUs, and a second context that asks for the substrate while
it is in use is rejected with -EBUSY.
Patch 5 is the perf-event backend. Two stateless overflow handlers
(one vaddr-keyed, one paddr-keyed) are picked at event creation time
and submit samples into the per-CPU ring. Vendor-specific sample
validity is honored at this layer.
Patch 6 adds a tracepoint at every node_eligible_mem_bp quota-goal
evaluation so userspace can watch goal convergence without polling
sysfs.
Userspace setup model
Userspace selects the sampling PMU by pointing the perf event's
`type` / `config` at it, and chooses the scheme topology that suits
the address space the PMU reports on. No module load or unload step
is involved; `echo on > state` arms the substrate, `echo off > state`
disarms it.
Two configurations were used for validation.
Configuration A: AMD IBS Op, paddr ops, system-wide PULL+PUSH tiering
IBS Op stamps samples with physical addresses, so DAMON reasons over
every backing page in the system regardless of which task or guest
touched it -- the substrate becomes a system-wide tiering controller.
Setup (abridged; `D=/sys/kernel/mm/damon/admin/kdamonds/0`):
echo 1 > /sys/kernel/mm/damon/admin/kdamonds/nr_kdamonds
echo 1 > $D/contexts/nr_contexts
echo paddr > $D/contexts/0/operations
# Two regions, one per NUMA node (DRAM + CXL). PA ranges
# are derived per host from /proc/iomem; omitted here.
echo 1 > $D/contexts/0/targets/nr_targets
echo 2 > $D/contexts/0/targets/0/regions/nr_regions
echo <DRAM_LO> > $D/contexts/0/targets/0/regions/0/start
echo <DRAM_HI> > $D/contexts/0/targets/0/regions/0/end
echo <CXL_LO> > $D/contexts/0/targets/0/regions/1/start
echo <CXL_HI> > $D/contexts/0/targets/0/regions/1/end
# IBS Op event, period-based, paddr-stamped:
PE=$D/contexts/0/monitoring_attrs/sample/perf_events
echo 1 > $PE/nr_perf_events
echo $(cat /sys/bus/event_source/devices/ibs_op/type) > $PE/0/type
echo 0 > $PE/0/config
echo 1 > $PE/0/sample_phys_addr
echo 0 > $PE/0/freq
echo 262144 > $PE/0/sample_period
echo 0 > $PE/0/exclude_kernel
echo 0 > $PE/0/exclude_hv
# PULL scheme: migrate_hot toward DRAM, gated on
# node_eligible_mem_bp(nid=DRAM) goal target_value=TARGET_BP.
# addr filter restricts source to the CXL range.
# PUSH scheme: migrate_hot toward CXL, gated on
# node_eligible_mem_bp(nid=CXL) target_value=10000-TARGET_BP.
# addr filter restricts source to the DRAM range.
# Both schemes are migrate_hot; they converge from opposite
# directions on the same hot working set.
echo on > $D/state
Userspace tunes the steady-state DRAM:CXL split by writing the goal
`target_value`s; DAMON's quota autotuner drives migration intensity
to match.
Workload: a QEMU/KVM guest pinned to one NUMA node, running 32
multichase multiload threads each touching a 4 GiB working set
(~128 GiB aggregate) with the memcpy-libc kernel. The guest sees
a flat single-NUMA layout and has no direct view of the host's
tiering topology, yet its hot pages are migrated to DRAM and cold
pages pushed to CXL by host-side DAMON acting on IBS-stamped
physical addresses -- the application inside the guest benefits
from tiering it never had to be aware of. Validated on AMD Turin
(132-CPU EPYC). The configuration converged to its target ratio
in seconds and remained stable for 7+ hours continuously, with no
perf core auto-throttle and no measurable drift in the achieved
interleave ratio.
Configuration B: Intel PEBS L3-miss, vaddr ops, per-PID weighted-dest
PEBS reports vaddr samples in the context of the running task.
DAMON's vaddr ops monitors a specific PID.
Setup (abridged):
echo 1 > /sys/kernel/mm/damon/admin/kdamonds/nr_kdamonds
echo 1 > $D/contexts/nr_contexts
echo vaddr > $D/contexts/0/operations
echo 1 > $D/contexts/0/targets/nr_targets
echo $PID > $D/contexts/0/targets/0/pid_target
echo 0 > $D/contexts/0/targets/0/regions/nr_regions
# PEBS MEM_LOAD_RETIRED.L3_MISS, frequency-based, vaddr-stamped:
echo 1 > $PE/nr_perf_events
echo 4 > $PE/0/type # PERF_TYPE_RAW
echo 0x20d1 > $PE/0/config # umask=0x20 event=0xd1
echo 0 > $PE/0/sample_phys_addr
echo 1 > $PE/0/freq
echo 5003 > $PE/0/sample_freq
echo 2 > $PE/0/precise_ip
echo 1 > $PE/0/wakeup_events
# Single migrate_hot scheme with two weighted destinations
# (DRAM + CXL). Userspace tunes the steady-state interleave by
# writing dests/{0,1}/weight.
echo on > $D/state
Workload: 32 multichase multiload threads with a 4 GiB working set
each (~128 GiB aggregate) running directly on the host, monitored
by DAMON via the multiload PID. Validated on Intel Granite Rapids
(144-CPU). Convergence is fast and the system is stable.
[1] https://lore.kernel.org/linux-mm/20260516223439.4033-1-ravis.opensrc@gmail.com/
[2] https://lore.kernel.org/20260423004211.7037-1-akinobu.mita@gmail.com
Ravi Jonnalagadda (6):
mm/damon: add struct damon_perf_event{,_attr} and per-ctx perf_events
list
mm/damon/sysfs-sample: expose perf_events configuration via sysfs
mm/damon/sysfs: install perf_events on apply
mm/damon/core: per-CPU SPSC ring drain and damon_perf_event lifecycle
mm/damon/vaddr: implement perf-event access check
mm/damon: add damos_node_eligible_mem_bp tracepoint
include/linux/damon.h | 80 +++++
include/trace/events/damon.h | 49 +++
mm/damon/core.c | 403 ++++++++++++++++++++----
mm/damon/ops-common.h | 39 +++
mm/damon/sysfs-common.h | 6 +
mm/damon/sysfs-sample.c | 579 +++++++++++++++++++++++++++++++++++
mm/damon/sysfs.c | 3 +
mm/damon/vaddr.c | 267 ++++++++++++++++
8 files changed, 1370 insertions(+), 56 deletions(-)
base-commit: 4c8ad15abf15eb480d3ad85f902001e35465ef18
--
2.43.0
^ permalink raw reply
* Re: [PATCH v3 2/2] hwmon: (pmbus/max20860a) Add driver for Analog Devices MAX20860A
From: Pradhan, Sanman @ 2026-05-29 16:51 UTC (permalink / raw)
To: noname.nuno@gmail.com
Cc: linux-hwmon@vger.kernel.org, linux@roeck-us.net, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, corbet@lwn.net,
skhan@linuxfoundation.org, devicetree@vger.kernel.org,
linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
Syed, Arif, Sanman Pradhan
In-Reply-To: <ahlcnlJJnLfmCZNH@nsa>
From: Sanman Pradhan <psanman@juniper.net>
On Fri, May 29, 2026 at 10:31:20 +0100, Nuno Sá wrote:
> > +static struct pmbus_driver_info max20860a_info = {
> > + .pages = 1,
> > + .format[PSC_VOLTAGE_IN] = linear,
> > + .format[PSC_VOLTAGE_OUT] = linear,
> > + .format[PSC_CURRENT_OUT] = linear,
> > + .format[PSC_TEMPERATURE] = linear,
> > + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT |
> > + PMBUS_HAVE_STATUS_VOUT |
> > + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
> > + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 |
> > + PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_STATUS_INPUT,
> > +};
>
> Any reason not to add regulator support? Given that the device seems to
> be a regulator...
Thanks for the review, Nuno.
The driver was submitted as monitoring-only to start with, and I was
planning to add regulator support as a follow-up patch.
I think the DT binding already includes the regulator.yaml $ref
so, in that case adding regulator support later won't require
a binding change.
If you'd prefer regulator support included from the start,
happy to add it in a v4.
Thank you.
Regards,
Sanman Pradhan
^ permalink raw reply
* Re: [PATCH v3 1/2] dt-bindings: hwmon: pmbus: Add Analog Devices MAX20860A
From: Conor Dooley @ 2026-05-29 16:44 UTC (permalink / raw)
To: Pradhan, Sanman
Cc: linux-hwmon@vger.kernel.org, linux@roeck-us.net, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, corbet@lwn.net,
skhan@linuxfoundation.org, devicetree@vger.kernel.org,
linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
Syed, Arif, Sanman Pradhan
In-Reply-To: <20260529001903.625737-2-sanman.pradhan@hpe.com>
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v5 09/10] dt-bindings: firmware: add arm,ras-cper
From: Jonathan Cameron @ 2026-05-29 16:44 UTC (permalink / raw)
To: Ahmed Tiba
Cc: will, xueshuai, saket.dumbre, mchehab, dave, djbw, bp, tony.luck,
guohanjun, lenb, skhan, vishal.l.verma, rafael, corbet, ira.weiny,
dave.jiang, krzk+dt, robh, catalin.marinas, alison.schofield,
conor+dt, linux-arm-kernel, Michael.Zhao2, linux-doc,
linux-kernel, linux-cxl, Dmitry.Lamerov, devicetree, linux-acpi,
linux-edac, acpica-devel
In-Reply-To: <20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-9-2e0500d42642@arm.com>
On Fri, 29 May 2026 10:50:49 +0100
Ahmed Tiba <ahmed.tiba@arm.com> wrote:
> Describe the DeviceTree node that exposes the Arm firmware-first
> CPER provider and hook the file into MAINTAINERS so the
> binding has an owner.
Odd wrap. Pick a length (72 / 75 typical for commit messages) and
stick to it.
Request for references inline + a question on whether we have
to allow for the ack register not existing. I'd rather we required
it if possible.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
> ---
> .../devicetree/bindings/firmware/arm,ras-cper.yaml | 54 ++++++++++++++++++++++
> MAINTAINERS | 5 ++
> 2 files changed, 59 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/firmware/arm,ras-cper.yaml b/Documentation/devicetree/bindings/firmware/arm,ras-cper.yaml
> new file mode 100644
> index 000000000000..3d4de096093f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/firmware/arm,ras-cper.yaml
> @@ -0,0 +1,54 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/firmware/arm,ras-cper.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Arm RAS CPER provider
> +
> +maintainers:
> + - Ahmed Tiba <ahmed.tiba@arm.com>
> +
> +description:
> + Arm Reliability, Availability and Serviceability (RAS) firmware can expose
> + a firmware-first CPER error source directly via DeviceTree. Firmware
> + provides the CPER Generic Error Status block and notifies the OS through
> + an interrupt.
I'd like some spec references in here if possible.
> +
> +properties:
> + compatible:
> + const: arm,ras-cper
> +
> + memory-region:
> + minItems: 1
> + items:
> + - description:
> + CPER Generic Error Status block exposed by firmware.
> + - description:
> + Optional firmware-owned ack buffer used on platforms
> + where firmware needs an explicit "ack" handshake before overwriting
> + the CPER buffer. Firmware watches bit 0 and expects the OS to set it
> + once the current status block has been consumed.
Does the arm spec really make this optional? Can we constraint it to not be
just to make our lives easier? I've never been sure how you would actually
make a working platform without the ack support.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 461a3eed6129..8a9714603a7d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22261,6 +22261,11 @@ M: Alexandre Bounine <alex.bou9@gmail.com>
> S: Maintained
> F: drivers/rapidio/
>
> +RAS ERROR STATUS
> +M: Ahmed Tiba <ahmed.tiba@arm.com>
> +S: Maintained
> +F: Documentation/devicetree/bindings/firmware/arm,ras-cper.yaml
> +
> RAS INFRASTRUCTURE
> M: Tony Luck <tony.luck@intel.com>
> M: Borislav Petkov <bp@alien8.de>
>
^ permalink raw reply
* Re: [PATCH v5 00/10] ACPI: APEI: share GHES CPER helpers and add DT FFH provider
From: Jonathan Cameron @ 2026-05-29 16:36 UTC (permalink / raw)
To: Ahmed Tiba
Cc: will, xueshuai, saket.dumbre, mchehab, dave, djbw, bp, tony.luck,
guohanjun, lenb, skhan, vishal.l.verma, rafael, corbet, ira.weiny,
dave.jiang, krzk+dt, robh, catalin.marinas, alison.schofield,
conor+dt, linux-arm-kernel, Michael.Zhao2, linux-doc,
linux-kernel, linux-cxl, Dmitry.Lamerov, devicetree, linux-acpi,
linux-edac, acpica-devel
In-Reply-To: <20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-0-2e0500d42642@arm.com>
On Fri, 29 May 2026 10:50:40 +0100
Ahmed Tiba <ahmed.tiba@arm.com> wrote:
> This is v5 of the GHES refactor series. Compared to v4, it only updates
> the DT binding to address the latest review comments.
https://sashiko.dev/#/patchset/20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-0-2e0500d42642@arm.com
It is mostly not happy with existing code, but there is one point where it notes
a header that should be there (linux/cleanup.h)
Given you are working with this code at the moment, maybe take a look at some
of the reported issues whilst you are here!
Jonathan
^ permalink raw reply
* Re: [PATCH v5 08/10] ACPI: APEI: share GHES CPER helpers
From: Jonathan Cameron @ 2026-05-29 16:32 UTC (permalink / raw)
To: Ahmed Tiba
Cc: will, xueshuai, saket.dumbre, mchehab, dave, djbw, bp, tony.luck,
guohanjun, lenb, skhan, vishal.l.verma, rafael, corbet, ira.weiny,
dave.jiang, krzk+dt, robh, catalin.marinas, alison.schofield,
conor+dt, linux-arm-kernel, Michael.Zhao2, linux-doc,
linux-kernel, linux-cxl, Dmitry.Lamerov, devicetree, linux-acpi,
linux-edac, acpica-devel
In-Reply-To: <20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-8-2e0500d42642@arm.com>
On Fri, 29 May 2026 10:50:48 +0100
Ahmed Tiba <ahmed.tiba@arm.com> wrote:
> Wire GHES up to the helper routines in ghes_cper.c and remove the local
> copies from ghes.c. This keeps the control flow identical while letting
> the helpers be shared with other firmware-first providers.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
Mostly looks fine. The one bit that rather makes this exercise of breaking
out generic code look dodgy is the ifdefs in the generic file.
I'm haven't looked closely but that to me implies a coupling that should not be
here.
Jonathan
> ---
> drivers/acpi/apei/ghes.c | 416 +--------------------------------------
> drivers/acpi/apei/ghes_cper.c | 438 +++++++++++++++++++++++++++++++++++++++++-
> include/acpi/ghes_cper.h | 20 ++
> 3 files changed, 459 insertions(+), 415 deletions(-)
>
> diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
> index 85be2ebf4d3e..f85b97c4db4c 100644
> --- a/drivers/acpi/apei/ghes.c
> +++ b/drivers/acpi/apei/ghes.c
>
> static void __ghes_panic(struct ghes *ghes,
> diff --git a/drivers/acpi/apei/ghes_cper.c b/drivers/acpi/apei/ghes_cper.c
> index d7a666a163c3..0ff9d06eb78f 100644
> --- a/drivers/acpi/apei/ghes_cper.c
> +++ b/drivers/acpi/apei/ghes_cper.c
> @@ -13,22 +13,32 @@
>
> #include "apei-internal.h"
>
> +ATOMIC_NOTIFIER_HEAD(ghes_report_chain);
> +
> +#ifndef CONFIG_ACPI_APEI
> +void __weak arch_apei_report_mem_error(int sev, struct cper_sec_mem_err *mem_err) { }
> +#endif
This is non obvious enough that the reasoning for a new weak function should be mentioned in
the patch description. Why not stub it in include/acpi/apei.h?
> +
> static struct ghes_estatus_cache __rcu *ghes_estatus_caches[GHES_ESTATUS_CACHES_SIZE];
> static atomic_t ghes_estatus_cache_alloced;
> +void __ghes_print_estatus(const char *pfx,
> + const struct acpi_hest_generic *generic,
> + const struct acpi_hest_generic_status *estatus)
> +{
> + static atomic_t seqno;
> + unsigned int curr_seqno;
> + char pfx_seq[64];
> +
> + if (!pfx) {
> + if (ghes_severity(estatus->error_severity) <=
> + GHES_SEV_CORRECTED)
> + pfx = KERN_WARNING;
> + else
> + pfx = KERN_ERR;
> + }
> + curr_seqno = atomic_inc_return(&seqno);
> + snprintf(pfx_seq, sizeof(pfx_seq), "%s{%u}" HW_ERR, pfx, curr_seqno);
> + printk("%sHardware error from APEI Generic Hardware Error Source: %d\n",
> + pfx_seq, generic->header.source_id);
> + cper_estatus_print(pfx_seq, estatus);
> +}
> +
> +int ghes_print_estatus(const char *pfx,
> + const struct acpi_hest_generic *generic,
> + const struct acpi_hest_generic_status *estatus)
> +{
> + /* Not more than 2 messages every 5 seconds */
> + static DEFINE_RATELIMIT_STATE(ratelimit_corrected, 5 * HZ, 2);
> + static DEFINE_RATELIMIT_STATE(ratelimit_uncorrected, 5 * HZ, 2);
> + struct ratelimit_state *ratelimit;
> +
> + if (ghes_severity(estatus->error_severity) <= GHES_SEV_CORRECTED)
> + ratelimit = &ratelimit_corrected;
> + else
> + ratelimit = &ratelimit_uncorrected;
> + if (__ratelimit(ratelimit)) {
> + __ghes_print_estatus(pfx, generic, estatus);
> + return 1;
> + }
> + return 0;
> +}
> +
> +#ifdef CONFIG_ACPI_APEI
So after the effort to break the the generic stuff we end up with non generic
bits in the broken out file? Is there no way to avoid this?
> static void __iomem *ghes_map(u64 pfn, enum fixed_addresses fixmap_idx)
> {
> phys_addr_t paddr;
> @@ -272,6 +636,7 @@ void ghes_clear_estatus(struct ghes *ghes,
> if (is_hest_type_generic_v2(ghes))
> ghes_ack_error(ghes->generic_v2);
> }
> +#endif /* CONFIG_ACPI_APEI */
> +void ghes_cper_handle_status(struct device *dev,
> + const struct acpi_hest_generic *generic,
> + const struct acpi_hest_generic_status *estatus,
> + bool sync)
> +{
> + int sev, sec_sev;
> + struct acpi_hest_generic_data *gdata;
> + guid_t *sec_type;
> + const guid_t *fru_id = &guid_null;
> + char *fru_text = "";
> + bool queued = false;
> +
> + sev = ghes_severity(estatus->error_severity);
> + apei_estatus_for_each_section(estatus, gdata) {
> + sec_type = (guid_t *)gdata->section_type;
> + sec_sev = ghes_severity(gdata->error_severity);
> + if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID)
> + fru_id = (guid_t *)gdata->fru_id;
> +
> + if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT)
> + fru_text = gdata->fru_text;
> +
> + ghes_log_hwerr(sev, sec_type);
> + if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) {
> + struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata);
> +
> + atomic_notifier_call_chain(&ghes_report_chain, sev, mem_err);
> +
> + arch_apei_report_mem_error(sev, mem_err);
> + queued = ghes_handle_memory_failure(gdata, sev, sync);
> + } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) {
> + ghes_handle_aer(gdata);
> + } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) {
> + queued = ghes_handle_arm_hw_error(gdata, sev, sync);
> + } else if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR)) {
> + struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata);
> +
> + cxl_cper_post_prot_err(prot_err, gdata->error_severity);
> + } else if (guid_equal(sec_type, &CPER_SEC_CXL_GEN_MEDIA_GUID)) {
> + struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata);
> +
> + cxl_cper_post_event(CXL_CPER_EVENT_GEN_MEDIA, rec);
> + } else if (guid_equal(sec_type, &CPER_SEC_CXL_DRAM_GUID)) {
> + struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata);
> +
> + cxl_cper_post_event(CXL_CPER_EVENT_DRAM, rec);
> + } else if (guid_equal(sec_type, &CPER_SEC_CXL_MEM_MODULE_GUID)) {
> + struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata);
> +
> + cxl_cper_post_event(CXL_CPER_EVENT_MEM_MODULE, rec);
> + } else {
> + void *err = acpi_hest_get_payload(gdata);
> +
> + ghes_defer_non_standard_event(gdata, sev);
> + log_non_standard_event(sec_type, fru_id, fru_text,
> + sec_sev, err,
> + gdata->error_data_length);
> + }
> + }
> +
> + /*
> + * If no memory failure work is queued for abnormal synchronous
> + * errors, do a force kill.
> + */
> + if (sync && !queued) {
> + dev_err(dev,
> + HW_ERR GHES_PFX "%s:%d: synchronous unrecoverable error (SIGBUS)\n",
> + current->comm, task_pid_nr(current));
> + force_sig(SIGBUS);
> + }
> +}
Blank line here
> /* Room for 8 entries */
> #define CXL_CPER_PROT_ERR_FIFO_DEPTH 8
> static DEFINE_KFIFO(cxl_cper_prot_err_fifo, struct cxl_cper_prot_err_work_data,
> diff --git a/include/acpi/ghes_cper.h b/include/acpi/ghes_cper.h
> index dd49e9179b63..511b95b50911 100644
> --- a/include/acpi/ghes_cper.h
> +++ b/include/acpi/ghes_cper.h
> @@ -17,6 +17,8 @@
> #define ACPI_APEI_GHES_CPER_H
>
> #include <linux/atomic.h>
> +#include <linux/device.h>
> +#include <linux/notifier.h>
> #include <linux/workqueue.h>
>
> #include <acpi/ghes.h>
> @@ -57,6 +59,7 @@
> ((struct ghes_vendor_record_entry *)(vendor_entry) + 1))
>
> extern struct gen_pool *ghes_estatus_pool;
> +extern struct atomic_notifier_head ghes_report_chain;
>
> static inline bool is_hest_type_generic_v2(struct ghes *ghes)
> {
> @@ -107,6 +110,23 @@ void ghes_estatus_cache_add(struct acpi_hest_generic *generic,
> struct acpi_hest_generic_status *estatus);
> void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata,
> int sev);
> +int ghes_severity(int severity);
> +bool ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata,
> + int sev, bool sync);
> +bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata,
> + int sev, bool sync);
> +void ghes_handle_aer(struct acpi_hest_generic_data *gdata);
> +void ghes_log_hwerr(int sev, guid_t *sec_type);
> +void __ghes_print_estatus(const char *pfx,
> + const struct acpi_hest_generic *generic,
> + const struct acpi_hest_generic_status *estatus);
> +int ghes_print_estatus(const char *pfx,
> + const struct acpi_hest_generic *generic,
> + const struct acpi_hest_generic_status *estatus);
> +void ghes_cper_handle_status(struct device *dev,
> + const struct acpi_hest_generic *generic,
> + const struct acpi_hest_generic_status *estatus,
> + bool sync);
> void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err,
> int severity);
> int cxl_cper_register_prot_err_work(struct work_struct *work);
>
^ permalink raw reply
* Re: [PATCH] MAINTAINERS: update ndesaulniers
From: Justin Stitt @ 2026-05-29 16:28 UTC (permalink / raw)
To: Nick Desaulniers
Cc: Nathan Chancellor, Bill Wendling, Will Deacon, Kees Cook,
Jonathan Corbet, Shuah Khan, Carlos Bilbao, Avadhut Naik,
linux-kernel, workflows, linux-doc, llvm, gosst-kernel,
android-kernel-team, kernel-dynamic-tools
In-Reply-To: <20260528-im_back_baby-v1-1-25d355efdbae@google.com>
Hi,
On Thu, May 28, 2026 at 2:38 PM Nick Desaulniers
<ndesaulniers@google.com> wrote:
>
> I'm coming back. I will return. I will possess your body, and I'll
> make LKML burn.
:0
>
> Signed-off-by: Nick Desaulniers <ndesaulniers@google.com>
> ---
> .mailmap | 1 -
> Documentation/process/embargoed-hardware-issues.rst | 2 +-
> Documentation/translations/sp_SP/process/embargoed-hardware-issues.rst | 2 +-
> MAINTAINERS | 2 +-
> 4 files changed, 3 insertions(+), 4 deletions(-)
>
> diff --git a/.mailmap b/.mailmap
> index a009f73d7ea5..f863781b0102 100644
> --- a/.mailmap
> +++ b/.mailmap
> @@ -634,7 +634,6 @@ Nicholas Piggin <npiggin@gmail.com> <npiggin@kernel.dk>
> Nicholas Piggin <npiggin@gmail.com> <npiggin@suse.de>
> Nicholas Piggin <npiggin@gmail.com> <nickpiggin@yahoo.com.au>
> Nicholas Piggin <npiggin@gmail.com> <piggin@cyberone.com.au>
> -Nick Desaulniers <nick.desaulniers+lkml@gmail.com> <ndesaulniers@google.com>
> Nicolas Ferre <nicolas.ferre@microchip.com> <nicolas.ferre@atmel.com>
> Nicolas Pitre <nico@fluxnic.net> <nicolas.pitre@linaro.org>
> Nicolas Pitre <nico@fluxnic.net> <nico@linaro.org>
> diff --git a/Documentation/process/embargoed-hardware-issues.rst b/Documentation/process/embargoed-hardware-issues.rst
> index 34e00848e0da..d07f16c3c7b8 100644
> --- a/Documentation/process/embargoed-hardware-issues.rst
> +++ b/Documentation/process/embargoed-hardware-issues.rst
> @@ -308,7 +308,7 @@ an involved disclosed party. The current ambassadors list:
>
> Google Kees Cook <keescook@chromium.org>
>
> - LLVM Nick Desaulniers <nick.desaulniers+lkml@gmail.com>
> + LLVM Nick Desaulniers <ndesaulniers@google.com>
> ============= ========================================================
>
> If you want your organization to be added to the ambassadors list, please
> diff --git a/Documentation/translations/sp_SP/process/embargoed-hardware-issues.rst b/Documentation/translations/sp_SP/process/embargoed-hardware-issues.rst
> index 9d444b9c46d3..7d4d694967c7 100644
> --- a/Documentation/translations/sp_SP/process/embargoed-hardware-issues.rst
> +++ b/Documentation/translations/sp_SP/process/embargoed-hardware-issues.rst
> @@ -287,7 +287,7 @@ revelada involucrada. La lista de embajadores actuales:
>
> Google Kees Cook <keescook@chromium.org>
>
> - LLVM Nick Desaulniers <nick.desaulniers+lkml@gmail.com>
> + LLVM Nick Desaulniers <ndesaulniers@google.com>
> ============= ========================================================
>
> Si quiere que su organización se añada a la lista de embajadores, por
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 461a3eed6129..2f06cc2e463c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -6259,7 +6259,7 @@ F: .clang-format
>
> CLANG/LLVM BUILD SUPPORT
> M: Nathan Chancellor <nathan@kernel.org>
> -R: Nick Desaulniers <nick.desaulniers+lkml@gmail.com>
> +R: Nick Desaulniers <ndesaulniers@google.com>
> R: Bill Wendling <morbo@google.com>
> R: Justin Stitt <justinstitt@google.com>
> L: llvm@lists.linux.dev
>
> ---
> base-commit: 8fde5d1d47f69db6082dfa34500c27f8485389a5
> change-id: 20260528-im_back_baby-1ade32dc049e
>
> Best regards,
> --
> Nick Desaulniers <ndesaulniers@google.com>
>
Justin
^ permalink raw reply
* Re: [PATCH v19 00/14] crypto/dmaengine: qce: introduce BAM locking and use DMA for register I/O
From: Eric Biggers @ 2026-05-29 16:22 UTC (permalink / raw)
To: Bartosz Golaszewski
Cc: Vinod Koul, Jonathan Corbet, Thara Gopinath, Herbert Xu,
David S. Miller, Udit Tiwari, Md Sadre Alam, Dmitry Baryshkov,
Manivannan Sadhasivam, Stephan Gerhold, Bjorn Andersson,
Peter Ujfalusi, Michal Simek, Frank Li, Andy Gross,
Neil Armstrong, dmaengine, linux-doc, linux-kernel, linux-arm-msm,
linux-crypto, linux-arm-kernel, brgl, Bartosz Golaszewski,
Dmitry Baryshkov, Konrad Dybcio
In-Reply-To: <20260526-qcom-qce-cmd-descr-v19-0-08472fdcbf4a@oss.qualcomm.com>
On Tue, May 26, 2026 at 03:10:48PM +0200, Bartosz Golaszewski wrote:
> I feel like I fell into the trap of trying to address pre-existing
> issues reported by sashiko and in the process provoking more reports so
> let this be the last iteration where I do this. Vinod can we get this
> queued for v7.2 now and iron out any previously existing problems in
> tree?
>
> Merging strategy: there are build-time dependencies between the crypto
> and DMA patches so the best approach is for Vinod to create an immutable
> branch with the DMA part pulled in by the crypto tree.
>
> This iteration continues to build on top of v12 but uses the BAM's NWD
> bit on data descriptors as suggested by Stephan. To that end, there are
> some more changes like reversing the order of command and data
> descriptors queuedy by the QCE driver.
>
> Currently the QCE crypto driver accesses the crypto engine registers
> directly via CPU. Trust Zone may perform crypto operations simultaneously
> resulting in a race condition. To remedy that, let's introduce support
> for BAM locking/unlocking to the driver. The BAM driver will now wrap
> any existing issued descriptor chains with additional descriptors
> performing the locking when the client starts the transaction
> (dmaengine_issue_pending()). The client wanting to profit from locking
> needs to switch to performing register I/O over DMA and communicate the
> address to which to perform the dummy writes via a call to
> dmaengine_desc_attach_metadata().
>
> In the specific case of the BAM DMA this translates to sending command
> descriptors performing dummy writes with the relevant flags set. The BAM
> will then lock all other pipes not related to the current pipe group, and
> keep handling the current pipe only until it sees the the unlock bit.
>
> In order for the locking to work correctly, we also need to switch to
> using DMA for all register I/O.
>
> On top of this, the series contains some additional tweaks and
> refactoring.
>
> The goal of this is not to improve the performance but to prepare the
> driver for supporting decryption into secure buffers in the future.
>
> Tested with tcrypt.ko, kcapi and cryptsetup.
>
> Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
> Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
None of these fixes are Cc'ed to stable, so stable kernels will remain
vulnerable to these race conditions.
Shouldn't this be preceded by a patch, Cc'ed to stable, that marks the
driver as BROKEN? As discussed in the other thread
(https://lore.kernel.org/linux-crypto/20260515-shikra_qcrypto-v1-0-80f07b345c29@oss.qualcomm.com/T/#u),
none of the current functionality of this driver is actually useful in
Linux. It's just been causing problems.
- Eric
^ permalink raw reply
* Re: [PATCH v5 07/10] ACPI: APEI: introduce GHES helper
From: Jonathan Cameron @ 2026-05-29 16:21 UTC (permalink / raw)
To: Ahmed Tiba
Cc: will, xueshuai, saket.dumbre, mchehab, dave, djbw, bp, tony.luck,
guohanjun, lenb, skhan, vishal.l.verma, rafael, corbet, ira.weiny,
dave.jiang, krzk+dt, robh, catalin.marinas, alison.schofield,
conor+dt, linux-arm-kernel, Michael.Zhao2, linux-doc,
linux-kernel, linux-cxl, Dmitry.Lamerov, devicetree, linux-acpi,
linux-edac, acpica-devel
In-Reply-To: <20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-7-2e0500d42642@arm.com>
On Fri, 29 May 2026 10:50:47 +0100
Ahmed Tiba <ahmed.tiba@arm.com> wrote:
> Add a dedicated GHES_CPER_HELPERS Kconfig entry so the shared helper code
> can be built even when ACPI_APEI_GHES is disabled. Update the build glue
> and headers to depend on the new symbol.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
I guess it doesn't matter too much as in practice all exiting CXL systems
are ACPI based, but is this new symbol sufficient for the
CONFIG_CXL_RAS dependency?
Rest of this looks fine to me.
^ permalink raw reply
* Re: [PATCH v5 06/10] ACPI: APEI: GHES: move CXL CPER helpers
From: Jonathan Cameron @ 2026-05-29 16:16 UTC (permalink / raw)
To: Ahmed Tiba
Cc: will, xueshuai, saket.dumbre, mchehab, dave, djbw, bp, tony.luck,
guohanjun, lenb, skhan, vishal.l.verma, rafael, corbet, ira.weiny,
dave.jiang, krzk+dt, robh, catalin.marinas, alison.schofield,
conor+dt, linux-arm-kernel, Michael.Zhao2, linux-doc,
linux-kernel, linux-cxl, Dmitry.Lamerov, devicetree, linux-acpi,
linux-edac, acpica-devel, Terry Bowman
In-Reply-To: <20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-6-2e0500d42642@arm.com>
On Fri, 29 May 2026 10:50:46 +0100
Ahmed Tiba <ahmed.tiba@arm.com> wrote:
> Move the CXL CPER handling paths out of ghes.c and into ghes_cper.c so the
> helpers can be reused. The code is moved as-is, with the public
> prototypes updated so GHES keeps calling into the new translation unit.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
This is going to clash with the series Terry is working on for CXL error handling.
I think this patch in particular:
https://lore.kernel.org/all/20260505173029.2718246-6-terry.bowman@amd.com/
That series is high priority to land from a CXL point of view. +CC Terry
Subject to that,
Reviewed-by: Jonathan Cameron <jic23@kernel.org>
Patch left so Terry can see it. No comments.
> ---
> drivers/acpi/apei/ghes.c | 132 -----------------------------------------
> drivers/acpi/apei/ghes_cper.c | 134 ++++++++++++++++++++++++++++++++++++++++++
> include/acpi/ghes_cper.h | 11 ++++
> 3 files changed, 145 insertions(+), 132 deletions(-)
>
> diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
> index 81ac51632f21..85be2ebf4d3e 100644
> --- a/drivers/acpi/apei/ghes.c
> +++ b/drivers/acpi/apei/ghes.c
> @@ -383,69 +383,6 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata)
> #endif
> }
>
> -/* Room for 8 entries */
> -#define CXL_CPER_PROT_ERR_FIFO_DEPTH 8
> -static DEFINE_KFIFO(cxl_cper_prot_err_fifo, struct cxl_cper_prot_err_work_data,
> - CXL_CPER_PROT_ERR_FIFO_DEPTH);
> -
> -/* Synchronize schedule_work() with cxl_cper_prot_err_work changes */
> -static DEFINE_SPINLOCK(cxl_cper_prot_err_work_lock);
> -struct work_struct *cxl_cper_prot_err_work;
> -
> -static void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err,
> - int severity)
> -{
> -#ifdef CONFIG_ACPI_APEI_PCIEAER
> - struct cxl_cper_prot_err_work_data wd;
> -
> - if (cxl_cper_sec_prot_err_valid(prot_err))
> - return;
> -
> - guard(spinlock_irqsave)(&cxl_cper_prot_err_work_lock);
> -
> - if (!cxl_cper_prot_err_work)
> - return;
> -
> - if (cxl_cper_setup_prot_err_work_data(&wd, prot_err, severity))
> - return;
> -
> - if (!kfifo_put(&cxl_cper_prot_err_fifo, wd)) {
> - pr_err_ratelimited("CXL CPER kfifo overflow\n");
> - return;
> - }
> -
> - schedule_work(cxl_cper_prot_err_work);
> -#endif
> -}
> -
> -int cxl_cper_register_prot_err_work(struct work_struct *work)
> -{
> - if (cxl_cper_prot_err_work)
> - return -EINVAL;
> -
> - guard(spinlock)(&cxl_cper_prot_err_work_lock);
> - cxl_cper_prot_err_work = work;
> - return 0;
> -}
> -EXPORT_SYMBOL_NS_GPL(cxl_cper_register_prot_err_work, "CXL");
> -
> -int cxl_cper_unregister_prot_err_work(struct work_struct *work)
> -{
> - if (cxl_cper_prot_err_work != work)
> - return -EINVAL;
> -
> - guard(spinlock)(&cxl_cper_prot_err_work_lock);
> - cxl_cper_prot_err_work = NULL;
> - return 0;
> -}
> -EXPORT_SYMBOL_NS_GPL(cxl_cper_unregister_prot_err_work, "CXL");
> -
> -int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data *wd)
> -{
> - return kfifo_get(&cxl_cper_prot_err_fifo, wd);
> -}
> -EXPORT_SYMBOL_NS_GPL(cxl_cper_prot_err_kfifo_get, "CXL");
> -
> static void ghes_vendor_record_notifier_destroy(void *nb)
> {
> ghes_unregister_vendor_record_notifier(nb);
> @@ -464,75 +401,6 @@ int devm_ghes_register_vendor_record_notifier(struct device *dev,
> }
> EXPORT_SYMBOL_GPL(devm_ghes_register_vendor_record_notifier);
>
> -/* Room for 8 entries for each of the 4 event log queues */
> -#define CXL_CPER_FIFO_DEPTH 32
> -DEFINE_KFIFO(cxl_cper_fifo, struct cxl_cper_work_data, CXL_CPER_FIFO_DEPTH);
> -
> -/* Synchronize schedule_work() with cxl_cper_work changes */
> -static DEFINE_SPINLOCK(cxl_cper_work_lock);
> -struct work_struct *cxl_cper_work;
> -
> -static void cxl_cper_post_event(enum cxl_event_type event_type,
> - struct cxl_cper_event_rec *rec)
> -{
> - struct cxl_cper_work_data wd;
> -
> - if (rec->hdr.length <= sizeof(rec->hdr) ||
> - rec->hdr.length > sizeof(*rec)) {
> - pr_err(FW_WARN "CXL CPER Invalid section length (%u)\n",
> - rec->hdr.length);
> - return;
> - }
> -
> - if (!(rec->hdr.validation_bits & CPER_CXL_COMP_EVENT_LOG_VALID)) {
> - pr_err(FW_WARN "CXL CPER invalid event\n");
> - return;
> - }
> -
> - guard(spinlock_irqsave)(&cxl_cper_work_lock);
> -
> - if (!cxl_cper_work)
> - return;
> -
> - wd.event_type = event_type;
> - memcpy(&wd.rec, rec, sizeof(wd.rec));
> -
> - if (!kfifo_put(&cxl_cper_fifo, wd)) {
> - pr_err_ratelimited("CXL CPER kfifo overflow\n");
> - return;
> - }
> -
> - schedule_work(cxl_cper_work);
> -}
> -
> -int cxl_cper_register_work(struct work_struct *work)
> -{
> - if (cxl_cper_work)
> - return -EINVAL;
> -
> - guard(spinlock)(&cxl_cper_work_lock);
> - cxl_cper_work = work;
> - return 0;
> -}
> -EXPORT_SYMBOL_NS_GPL(cxl_cper_register_work, "CXL");
> -
> -int cxl_cper_unregister_work(struct work_struct *work)
> -{
> - if (cxl_cper_work != work)
> - return -EINVAL;
> -
> - guard(spinlock)(&cxl_cper_work_lock);
> - cxl_cper_work = NULL;
> - return 0;
> -}
> -EXPORT_SYMBOL_NS_GPL(cxl_cper_unregister_work, "CXL");
> -
> -int cxl_cper_kfifo_get(struct cxl_cper_work_data *wd)
> -{
> - return kfifo_get(&cxl_cper_fifo, wd);
> -}
> -EXPORT_SYMBOL_NS_GPL(cxl_cper_kfifo_get, "CXL");
> -
> static void ghes_log_hwerr(int sev, guid_t *sec_type)
> {
> if (sev != CPER_SEV_RECOVERABLE)
> diff --git a/drivers/acpi/apei/ghes_cper.c b/drivers/acpi/apei/ghes_cper.c
> index 131980d36064..d7a666a163c3 100644
> --- a/drivers/acpi/apei/ghes_cper.c
> +++ b/drivers/acpi/apei/ghes_cper.c
> @@ -12,10 +12,12 @@
> * Author: Huang Ying <ying.huang@intel.com>
> */
>
> +#include <linux/aer.h>
> #include <linux/err.h>
> #include <linux/genalloc.h>
> #include <linux/irq_work.h>
> #include <linux/io.h>
> +#include <linux/kfifo.h>
> #include <linux/kernel.h>
> #include <linux/list.h>
> #include <linux/math64.h>
> @@ -321,6 +323,138 @@ void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata,
> schedule_work(&entry->work);
> }
>
> +/* Room for 8 entries */
> +#define CXL_CPER_PROT_ERR_FIFO_DEPTH 8
> +static DEFINE_KFIFO(cxl_cper_prot_err_fifo, struct cxl_cper_prot_err_work_data,
> + CXL_CPER_PROT_ERR_FIFO_DEPTH);
> +
> +/* Synchronize schedule_work() with cxl_cper_prot_err_work changes */
> +static DEFINE_SPINLOCK(cxl_cper_prot_err_work_lock);
> +struct work_struct *cxl_cper_prot_err_work;
> +
> +void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err,
> + int severity)
> +{
> +#ifdef CONFIG_ACPI_APEI_PCIEAER
> + struct cxl_cper_prot_err_work_data wd;
> +
> + if (cxl_cper_sec_prot_err_valid(prot_err))
> + return;
> +
> + guard(spinlock_irqsave)(&cxl_cper_prot_err_work_lock);
> +
> + if (!cxl_cper_prot_err_work)
> + return;
> +
> + if (cxl_cper_setup_prot_err_work_data(&wd, prot_err, severity))
> + return;
> +
> + if (!kfifo_put(&cxl_cper_prot_err_fifo, wd)) {
> + pr_err_ratelimited("CXL CPER kfifo overflow\n");
> + return;
> + }
> +
> + schedule_work(cxl_cper_prot_err_work);
> +#endif
> +}
> +
> +int cxl_cper_register_prot_err_work(struct work_struct *work)
> +{
> + if (cxl_cper_prot_err_work)
> + return -EINVAL;
> +
> + guard(spinlock)(&cxl_cper_prot_err_work_lock);
> + cxl_cper_prot_err_work = work;
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_cper_register_prot_err_work, "CXL");
> +
> +int cxl_cper_unregister_prot_err_work(struct work_struct *work)
> +{
> + if (cxl_cper_prot_err_work != work)
> + return -EINVAL;
> +
> + guard(spinlock)(&cxl_cper_prot_err_work_lock);
> + cxl_cper_prot_err_work = NULL;
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_cper_unregister_prot_err_work, "CXL");
> +
> +int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data *wd)
> +{
> + return kfifo_get(&cxl_cper_prot_err_fifo, wd);
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_cper_prot_err_kfifo_get, "CXL");
> +
> +/* Room for 8 entries for each of the 4 event log queues */
> +#define CXL_CPER_FIFO_DEPTH 32
> +static DEFINE_KFIFO(cxl_cper_fifo, struct cxl_cper_work_data, CXL_CPER_FIFO_DEPTH);
> +
> +/* Synchronize schedule_work() with cxl_cper_work changes */
> +static DEFINE_SPINLOCK(cxl_cper_work_lock);
> +struct work_struct *cxl_cper_work;
> +
> +void cxl_cper_post_event(enum cxl_event_type event_type,
> + struct cxl_cper_event_rec *rec)
> +{
> + struct cxl_cper_work_data wd;
> +
> + if (rec->hdr.length <= sizeof(rec->hdr) ||
> + rec->hdr.length > sizeof(*rec)) {
> + pr_err(FW_WARN "CXL CPER Invalid section length (%u)\n",
> + rec->hdr.length);
> + return;
> + }
> +
> + if (!(rec->hdr.validation_bits & CPER_CXL_COMP_EVENT_LOG_VALID)) {
> + pr_err(FW_WARN "CXL CPER invalid event\n");
> + return;
> + }
> +
> + guard(spinlock_irqsave)(&cxl_cper_work_lock);
> +
> + if (!cxl_cper_work)
> + return;
> +
> + wd.event_type = event_type;
> + memcpy(&wd.rec, rec, sizeof(wd.rec));
> +
> + if (!kfifo_put(&cxl_cper_fifo, wd)) {
> + pr_err_ratelimited("CXL CPER kfifo overflow\n");
> + return;
> + }
> +
> + schedule_work(cxl_cper_work);
> +}
> +
> +int cxl_cper_register_work(struct work_struct *work)
> +{
> + if (cxl_cper_work)
> + return -EINVAL;
> +
> + guard(spinlock)(&cxl_cper_work_lock);
> + cxl_cper_work = work;
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_cper_register_work, "CXL");
> +
> +int cxl_cper_unregister_work(struct work_struct *work)
> +{
> + if (cxl_cper_work != work)
> + return -EINVAL;
> +
> + guard(spinlock)(&cxl_cper_work_lock);
> + cxl_cper_work = NULL;
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_cper_unregister_work, "CXL");
> +
> +int cxl_cper_kfifo_get(struct cxl_cper_work_data *wd)
> +{
> + return kfifo_get(&cxl_cper_fifo, wd);
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_cper_kfifo_get, "CXL");
> +
> /*
> * GHES error status reporting throttle, to report more kinds of
> * errors, instead of just most frequently occurred errors.
> diff --git a/include/acpi/ghes_cper.h b/include/acpi/ghes_cper.h
> index 51725f25c516..dd49e9179b63 100644
> --- a/include/acpi/ghes_cper.h
> +++ b/include/acpi/ghes_cper.h
> @@ -20,6 +20,7 @@
> #include <linux/workqueue.h>
>
> #include <acpi/ghes.h>
> +#include <cxl/event.h>
>
> #define GHES_PFX "GHES: "
>
> @@ -106,5 +107,15 @@ void ghes_estatus_cache_add(struct acpi_hest_generic *generic,
> struct acpi_hest_generic_status *estatus);
> void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata,
> int sev);
> +void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err,
> + int severity);
> +int cxl_cper_register_prot_err_work(struct work_struct *work);
> +int cxl_cper_unregister_prot_err_work(struct work_struct *work);
> +int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data *wd);
> +void cxl_cper_post_event(enum cxl_event_type event_type,
> + struct cxl_cper_event_rec *rec);
> +int cxl_cper_register_work(struct work_struct *work);
> +int cxl_cper_unregister_work(struct work_struct *work);
> +int cxl_cper_kfifo_get(struct cxl_cper_work_data *wd);
>
> #endif /* ACPI_APEI_GHES_CPER_H */
>
^ permalink raw reply
* Re: [PATCH v2] drm/xe/hwmon: document DG2 fan speed reporting quirk
From: Raag Jadav @ 2026-05-29 16:12 UTC (permalink / raw)
To: 占wei
Cc: Matthew Brost, Thomas Hellström, Rodrigo Vivi,
Jonathan Corbet, Shuah Khan, intel-xe, dri-devel, linux-doc,
linux-kernel
In-Reply-To: <CA+qUFckr9uC7h4S9xw0JMxGnehXyrQ8HpOYdwoFoMsLk-HM_nw@mail.gmail.com>
On Fri, May 29, 2026 at 10:05:58PM +0800, 占wei wrote:
> +Cc Raag, who authored the fan support and reviewed v1.
>
> Thanks for your help, this v2 drops the code change and documents the
> DG2 shared-tach behaviour instead, per your feedback on v1.
IMO it's a bit verbose to have a dedicated doc for this. Just add a small
comment in the existing ABI doc[1] under fan channel description.
[1] Documentation/ABI/testing/sysfs-driver-intel-xe-hwmon
Raag
> Zhan Wei <zhanwei919@gmail.com> 于2026年5月29日周五 21:50写道:
> >
> > The number of fanN_input attributes on DG2 is hardcoded to two because
> > FSC_READ_NUM_FANS returns an incorrect value on some boards. How the
> > physical fans map onto the tach channels is left to the board vendor:
> > some OEMs route multiple physical fans through a single shared tach
> > line, in which case the unwired channel's pulse counter never
> > accumulates and fanN_input reads a constant 0 RPM.
> >
> > This is expected behaviour for such boards rather than a driver fault,
> > and the driver has no reliable way to distinguish a shared-tach layout
> > from a genuinely silent fan. Document this so the flat DG2 fan count is
> > not mistaken for a bug and "fixed" by lowering it, which would hide a
> > working fan2 on boards that do wire two tach lines.
> >
> > Signed-off-by: Zhan Wei <zhanwei919@gmail.com>
> > ---
> > v1 -> v2: Drop the code change. As pointed out in review, the same PCI
> > device ID ships with both shared-tach (multiple physical fans on one
> > channel) and 1:1 fan wiring, and FSC_READ_NUM_FANS is unreliable on
> > some boards, so the DG2 fan count cannot be lowered without hiding a
> > working fan2 on boards that do wire two tach lines. Document the
> > behaviour instead of changing the reported fan count.
> >
> > v1: https://lore.kernel.org/intel-xe/20260527115311.13398-1-zhanwei919@gmail.com/
> >
> > Documentation/gpu/xe/index.rst | 1 +
> > Documentation/gpu/xe/xe_hwmon.rst | 48 +++++++++++++++++++++++++++++++
> > 2 files changed, 49 insertions(+)
> > create mode 100644 Documentation/gpu/xe/xe_hwmon.rst
> >
> > diff --git a/Documentation/gpu/xe/index.rst b/Documentation/gpu/xe/index.rst
> > index 874ffcb6da3a..3c14cdcaa8a6 100644
> > --- a/Documentation/gpu/xe/index.rst
> > +++ b/Documentation/gpu/xe/index.rst
> > @@ -30,3 +30,4 @@ DG2, etc is provided to prototype the driver.
> > xe-drm-usage-stats.rst
> > xe_configfs
> > xe_gt_stats
> > + xe_hwmon
> > diff --git a/Documentation/gpu/xe/xe_hwmon.rst b/Documentation/gpu/xe/xe_hwmon.rst
> > new file mode 100644
> > index 000000000000..8cd48df59386
> > --- /dev/null
> > +++ b/Documentation/gpu/xe/xe_hwmon.rst
> > @@ -0,0 +1,48 @@
> > +.. SPDX-License-Identifier: (GPL-2.0+ OR MIT)
> > +
> > +=================
> > +Xe HWMON support
> > +=================
> > +
> > +The xe driver exposes hardware monitoring sensors (power, energy,
> > +temperature, voltage and fan speed) through the kernel hwmon subsystem,
> > +typically consumed via ``/sys/class/hwmon/hwmonX/`` or tools such as
> > +``sensors``.
> > +
> > +Fan speed reporting
> > +===================
> > +
> > +Fan speed (``fanN_input``) is reported in RPM and computed from a tach
> > +pulse counter: the driver reads an accumulating pulse register, divides
> > +the delta between two subsequent readings by two pulses per rotation,
> > +and time-averages the result.
> > +
> > +Number of fan channels
> > +-----------------------
> > +
> > +The number of ``fanN_input`` attributes exposed in sysfs is the fan
> > +count returned by the ``FSC_READ_NUM_FANS`` pcode command. On DG2 this
> > +command has been found to return an incorrect value on some boards, so
> > +the driver hardcodes a fan count of two there. As a result up to
> > +``fan1_input`` and ``fan2_input`` are always exposed on DG2 regardless
> > +of how many tach lines are actually wired.
> > +
> > +Zero RPM on DG2 is not necessarily a bug
> > +----------------------------------------
> > +
> > +How physical fans map onto the tach channels is left to the board
> > +vendor. Some OEMs route several physical fans through a single shared
> > +tach line, while others wire each fan to its own channel 1:1. The
> > +driver has no reliable way to tell these layouts apart, and the same PCI
> > +device ID can ship in either configuration.
> > +
> > +When a channel has no tach line driving it, its pulse counter never
> > +accumulates, so the corresponding ``fanN_input`` reads a constant 0 RPM.
> > +On DG2 this is most often seen on ``fan2_input`` for boards that drive
> > +both physical fans from a single tach line. This is expected behaviour
> > +for such boards, not a driver fault, and reflects the board wiring
> > +rather than a missing or stalled fan.
> > +
> > +For this reason the fan count on DG2 is intentionally left at a flat
> > +value rather than tracked per board: there is no driver-visible signal
> > +that distinguishes a shared-tach layout from a genuinely silent fan.
> > --
> > 2.43.0
> >
^ permalink raw reply
* Re: [PATCH v5 05/10] ACPI: APEI: GHES: move vendor record helpers
From: Jonathan Cameron @ 2026-05-29 16:10 UTC (permalink / raw)
To: Ahmed Tiba
Cc: will, xueshuai, saket.dumbre, mchehab, dave, djbw, bp, tony.luck,
guohanjun, lenb, skhan, vishal.l.verma, rafael, corbet, ira.weiny,
dave.jiang, krzk+dt, robh, catalin.marinas, alison.schofield,
conor+dt, linux-arm-kernel, Michael.Zhao2, linux-doc,
linux-kernel, linux-cxl, Dmitry.Lamerov, devicetree, linux-acpi,
linux-edac, acpica-devel
In-Reply-To: <20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-5-2e0500d42642@arm.com>
On Fri, 29 May 2026 10:50:45 +0100
Ahmed Tiba <ahmed.tiba@arm.com> wrote:
> Shift the vendor record workqueue helpers into ghes_cper.c so both GHES
> and future DT-based providers can use the same implementation. The change
> is mechanical and keeps the notifier behavior identical.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
A few questions / comments inline
J
> ---
> drivers/acpi/apei/ghes.c | 86 +++++++++----------------------------------
> drivers/acpi/apei/ghes_cper.c | 55 +++++++++++++++++++++++++++
> include/acpi/ghes_cper.h | 2 +
> 3 files changed, 75 insertions(+), 68 deletions(-)
>
> diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
> index adab7404310e..81ac51632f21 100644
> --- a/drivers/acpi/apei/ghes.c
> +++ b/drivers/acpi/apei/ghes.c
...
> -
> -static void ghes_vendor_record_notifier_destroy(void *nb)
> -{
> - ghes_unregister_vendor_record_notifier(nb);
> -}
> -
> -int devm_ghes_register_vendor_record_notifier(struct device *dev,
> - struct notifier_block *nb)
> -{
> - int ret;
> -
> - ret = ghes_register_vendor_record_notifier(nb);
> - if (ret)
> - return ret;
> -
> - return devm_add_action_or_reset(dev, ghes_vendor_record_notifier_destroy, nb);
> -}
> -EXPORT_SYMBOL_GPL(devm_ghes_register_vendor_record_notifier);
> #define CXL_CPER_PROT_ERR_FIFO_DEPTH 8
> static DEFINE_KFIFO(cxl_cper_prot_err_fifo, struct cxl_cper_prot_err_work_data,
> @@ -514,6 +446,24 @@ int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data *wd)
> }
> EXPORT_SYMBOL_NS_GPL(cxl_cper_prot_err_kfifo_get, "CXL");
>
> +static void ghes_vendor_record_notifier_destroy(void *nb)
> +{
> + ghes_unregister_vendor_record_notifier(nb);
> +}
> +
> +int devm_ghes_register_vendor_record_notifier(struct device *dev,
> + struct notifier_block *nb)
> +{
> + int ret;
> +
> + ret = ghes_register_vendor_record_notifier(nb);
> + if (ret)
> + return ret;
> +
> + return devm_add_action_or_reset(dev, ghes_vendor_record_notifier_destroy, nb);
> +}
> +EXPORT_SYMBOL_GPL(devm_ghes_register_vendor_record_notifier);
> +
Why did these two move inside the file? It is a bit odd to leave the devm calls in
a different place to what they are wrapping. I guess someone argued for that in an
earlier version? (hopefully not me ;)
If the move puts them in an ifdef block then I'd not bother - it's tiny code and
to me doing this is more confusing than just leaving them where they were.
> /* Room for 8 entries for each of the 4 event log queues */
> #define CXL_CPER_FIFO_DEPTH 32
> DEFINE_KFIFO(cxl_cper_fifo, struct cxl_cper_work_data, CXL_CPER_FIFO_DEPTH);
> diff --git a/drivers/acpi/apei/ghes_cper.c b/drivers/acpi/apei/ghes_cper.c
> index 0a117f478afb..131980d36064 100644
> --- a/drivers/acpi/apei/ghes_cper.c
> +++ b/drivers/acpi/apei/ghes_cper.c
> @@ -14,12 +14,17 @@
>
> #include <linux/err.h>
> #include <linux/genalloc.h>
> +#include <linux/irq_work.h>
> #include <linux/io.h>
> #include <linux/kernel.h>
> +#include <linux/list.h>
> #include <linux/math64.h>
> #include <linux/mm.h>
> +#include <linux/notifier.h>
> +#include <linux/llist.h>
> #include <linux/ratelimit.h>
> #include <linux/rcupdate.h>
> +#include <linux/rculist.h>
I'm not seeing anything reason for most of these new includes.
Probably in the wrong patch
> #include <linux/sched/clock.h>
> #include <linux/slab.h>
>
> @@ -266,6 +271,56 @@ void ghes_clear_estatus(struct ghes *ghes,
> ghes_ack_error(ghes->generic_v2);
> }
>
> +static BLOCKING_NOTIFIER_HEAD(vendor_record_notify_list);
> +
> +int ghes_register_vendor_record_notifier(struct notifier_block *nb)
> +{
> + return blocking_notifier_chain_register(&vendor_record_notify_list, nb);
> +}
> +EXPORT_SYMBOL_GPL(ghes_register_vendor_record_notifier);
> +
> +void ghes_unregister_vendor_record_notifier(struct notifier_block *nb)
> +{
> + blocking_notifier_chain_unregister(&vendor_record_notify_list, nb);
> +}
> +EXPORT_SYMBOL_GPL(ghes_unregister_vendor_record_notifier);
> +
> +static void ghes_vendor_record_work_func(struct work_struct *work)
> +{
> + struct ghes_vendor_record_entry *entry;
> + struct acpi_hest_generic_data *gdata;
> + u32 len;
> +
> + entry = container_of(work, struct ghes_vendor_record_entry, work);
> + gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry);
> +
> + blocking_notifier_call_chain(&vendor_record_notify_list,
> + entry->error_severity, gdata);
> +
> + len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata));
> + gen_pool_free(ghes_estatus_pool, (unsigned long)entry, len);
> +}
> +
> +void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata,
> + int sev)
> +{
> + struct acpi_hest_generic_data *copied_gdata;
> + struct ghes_vendor_record_entry *entry;
> + u32 len;
> +
> + len = GHES_VENDOR_ENTRY_LEN(acpi_hest_get_record_size(gdata));
> + entry = (void *)gen_pool_alloc(ghes_estatus_pool, len);
> + if (!entry)
> + return;
> +
> + copied_gdata = GHES_GDATA_FROM_VENDOR_ENTRY(entry);
> + memcpy(copied_gdata, gdata, acpi_hest_get_record_size(gdata));
> + entry->error_severity = sev;
> +
> + INIT_WORK(&entry->work, ghes_vendor_record_work_func);
> + schedule_work(&entry->work);
> +}
> +
> /*
> * GHES error status reporting throttle, to report more kinds of
> * errors, instead of just most frequently occurred errors.
> diff --git a/include/acpi/ghes_cper.h b/include/acpi/ghes_cper.h
> index 1b5dbeca9bb6..51725f25c516 100644
> --- a/include/acpi/ghes_cper.h
> +++ b/include/acpi/ghes_cper.h
> @@ -104,5 +104,7 @@ int __ghes_read_estatus(struct acpi_hest_generic_status *estatus,
> int ghes_estatus_cached(struct acpi_hest_generic_status *estatus);
> void ghes_estatus_cache_add(struct acpi_hest_generic *generic,
> struct acpi_hest_generic_status *estatus);
> +void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata,
> + int sev);
>
> #endif /* ACPI_APEI_GHES_CPER_H */
>
^ permalink raw reply
* Re: [PATCH v5 04/18] mm: skip out-of-range bits in mk_vma_flags()
From: Kiryl Shutsemau @ 2026-05-29 16:09 UTC (permalink / raw)
To: Lorenzo Stoakes
Cc: akpm, rppt, peterx, david, surenb, vbabka, Liam.Howlett, ziy,
corbet, skhan, seanjc, pbonzini, jthoughton, aarcange, sj,
usama.arif, linux-mm, linux-kernel, linux-doc, linux-kselftest,
kvm, kernel-team, stable
In-Reply-To: <ahmQvfNk7S4F0LBj@lucifer>
On Fri, May 29, 2026 at 03:00:14PM +0100, Lorenzo Stoakes wrote:
> > Add VMA_NO_BIT and have DECLARE_VMA_BIT() resolve any bitnum out
> > of range to it. vma_flags_set_flag() drops negative bit values.
> > The ternary collapses at compile time, the runtime check folds
> > away when the bit is in range, and the common path is unchanged.
>
> Hmm are you sure it does?
You were right - I measured it (gcc 15.2, clang 21.1.8, -O2). The
DECLARE_VMA_BIT() ternary is fine, but the "if (bit < 0)" guard does not
reliably fold: with it, clang stops folding __VMA_UFFD_FLAGS to a constant
and gcc keeps a rolled loop; without it, both fold.
So I've dropped VMA_NO_BIT and gone with your config-gated-mask approach
instead: mk_vma_flags_from_masks() plus VMA_UFFD_{MISSING,WP,MINOR,RWP}
masks that collapse to EMPTY_VMA_FLAGS when unavailable, so no out-of-range
bit ever reaches mk_vma_flags(). __VMA_UFFD_FLAGS now folds to a single
constant on both compilers, 32- and 64-bit. Added your Suggested-by.
I also took your "use the new API" hint and added a prep patch converting
the existing userfaultfd_*() helpers to vma_test_any_mask() (Suggested-by
you as well). One deviation: vma_test(vma, VMA_UFFD_RWP_BIT) is itself an
out-of-bounds *read* on 32-bit (test_bit(43, &one_long)), so the helpers
use vma_test_any_mask() with the masks rather than the bit.
> Either way, I think we should break out any fix like this from the series.
Agreed - the OOB fix and the other pre-existing fixes will go as a separate
series with the RWP work rebased on top.
--
Kiryl Shutsemau / Kirill A. Shutemov
^ permalink raw reply
* Re: [PATCH v5 04/10] ACPI: APEI: GHES: move estatus cache helpers
From: Jonathan Cameron @ 2026-05-29 16:03 UTC (permalink / raw)
To: Ahmed Tiba
Cc: will, xueshuai, saket.dumbre, mchehab, dave, djbw, bp, tony.luck,
guohanjun, lenb, skhan, vishal.l.verma, rafael, corbet, ira.weiny,
dave.jiang, krzk+dt, robh, catalin.marinas, alison.schofield,
conor+dt, linux-arm-kernel, Michael.Zhao2, linux-doc,
linux-kernel, linux-cxl, Dmitry.Lamerov, devicetree, linux-acpi,
linux-edac, acpica-devel
In-Reply-To: <20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-4-2e0500d42642@arm.com>
On Fri, 29 May 2026 10:50:44 +0100
Ahmed Tiba <ahmed.tiba@arm.com> wrote:
> Relocate the estatus cache allocation and lookup helpers from ghes.c into
> ghes_cper.c. This code move keeps the logic intact while making the cache
> implementation available to forthcoming users.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
A couple of minor things inline.
With the two I've called out tidied up
Reviewed-by: Jonathan Cameron <jic23@kernel.org>
> diff --git a/drivers/acpi/apei/ghes_cper.c b/drivers/acpi/apei/ghes_cper.c
> index 8080e0f76dac..0a117f478afb 100644
> --- a/drivers/acpi/apei/ghes_cper.c
> +++ b/drivers/acpi/apei/ghes_cper.c
> @@ -13,10 +13,14 @@
> */
>
> #include <linux/err.h>
> +#include <linux/genalloc.h>
> #include <linux/io.h>
> #include <linux/kernel.h>
> +#include <linux/math64.h>
> #include <linux/mm.h>
> #include <linux/ratelimit.h>
> +#include <linux/rcupdate.h>
> +#include <linux/sched/clock.h>
> #include <linux/slab.h>
> +static void ghes_estatus_cache_rcu_free(struct rcu_head *head)
> +{
> + struct ghes_estatus_cache *cache;
> + u32 len;
> +
> + cache = container_of(head, struct ghes_estatus_cache, rcu);
> + len = cper_estatus_len(GHES_ESTATUS_FROM_CACHE(cache));
> + len = GHES_ESTATUS_CACHE_LEN(len);
> + gen_pool_free(ghes_estatus_pool, (unsigned long)cache, len);
> + atomic_dec(&ghes_estatus_cache_alloced);
> +}
> +
> +void
> +ghes_estatus_cache_add(struct acpi_hest_generic *generic,
> + struct acpi_hest_generic_status *estatus)
void ghes_estatus_cache_add(struct acpi_hest_generic *generic,
struct acpi_hest_generic_status *estatus)
is under 80 chars (and how you have it in the header!)
(RB assumes you fix this - or argue against perhaps because of a change in
a future patch)
> +{
> + unsigned long long now, duration, period, max_period = 0;
> + struct ghes_estatus_cache *cache, *new_cache;
> + struct ghes_estatus_cache __rcu *victim;
> + int i, slot = -1, count;
> +
> + new_cache = ghes_estatus_cache_alloc(generic, estatus);
> + if (!new_cache)
> + return;
> +
> + rcu_read_lock();
> + now = sched_clock();
> + for (i = 0; i < GHES_ESTATUS_CACHES_SIZE; i++) {
> + cache = rcu_dereference(ghes_estatus_caches[i]);
> + if (cache == NULL) {
> + slot = i;
> + break;
> + }
> + duration = now - cache->time_in;
> + if (duration >= GHES_ESTATUS_IN_CACHE_MAX_NSEC) {
> + slot = i;
> + break;
> + }
> + count = atomic_read(&cache->count);
> + period = duration;
> + do_div(period, (count + 1));
> + if (period > max_period) {
> + max_period = period;
> + slot = i;
> + }
> + }
> + rcu_read_unlock();
> +
> + if (slot != -1) {
If you even end up doing tidy up of this code, would be nicer to flip
the logic here and do an early return.
if (slot == -1)
return;
Then the rest is much less indented.
No need to do that in this series though as nothing 'wrong' with the
current code as such.
> + /*
> + * Use release semantics to ensure that ghes_estatus_cached()
> + * running on another CPU will see the updated cache fields if
> + * it can see the new value of the pointer.
> + */
> + victim = xchg_release(&ghes_estatus_caches[slot],
> + RCU_INITIALIZER(new_cache));
> +
> + /*
> + * At this point, victim may point to a cached item different
> + * from the one based on which we selected the slot. Instead of
> + * going to the loop again to pick another slot, let's just
> + * drop the other item anyway: this may cause a false cache
> + * miss later on, but that won't cause any problems.
> + */
> + if (victim)
> + call_rcu(&unrcu_pointer(victim)->rcu,
> + ghes_estatus_cache_rcu_free);
> + }
> +}
> diff --git a/include/acpi/ghes_cper.h b/include/acpi/ghes_cper.h
> index 6b7632cfaf66..1b5dbeca9bb6 100644
> --- a/include/acpi/ghes_cper.h
> +++ b/include/acpi/ghes_cper.h
> @@ -16,6 +16,7 @@
> #ifndef ACPI_APEI_GHES_CPER_H
> #define ACPI_APEI_GHES_CPER_H
>
> +#include <linux/atomic.h>
Why? Nothing in in the types used in what is added to the header needs
it - maybe I'm suffering Friday syndrome. Seems like it belongs in another
patch or in a c file rather than the header.
(RB assumes this fixed or argued against)
> #include <linux/workqueue.h>
>
> #include <acpi/ghes.h>
> @@ -54,6 +55,8 @@
> ((struct acpi_hest_generic_data *) \
> ((struct ghes_vendor_record_entry *)(vendor_entry) + 1))
>
> +extern struct gen_pool *ghes_estatus_pool;
> +
> static inline bool is_hest_type_generic_v2(struct ghes *ghes)
> {
> return ghes->generic->header.type == ACPI_HEST_TYPE_GENERIC_ERROR_V2;
> @@ -98,5 +101,8 @@ int __ghes_read_estatus(struct acpi_hest_generic_status *estatus,
> u64 buf_paddr, enum fixed_addresses fixmap_idx,
> size_t buf_len);
> #endif
> +int ghes_estatus_cached(struct acpi_hest_generic_status *estatus);
> +void ghes_estatus_cache_add(struct acpi_hest_generic *generic,
> + struct acpi_hest_generic_status *estatus);
>
> #endif /* ACPI_APEI_GHES_CPER_H */
>
^ permalink raw reply
* Re: [PATCH v5 03/10] ACPI: APEI: GHES: move GHESv2 ack and alloc helpers
From: Jonathan Cameron @ 2026-05-29 15:54 UTC (permalink / raw)
To: Ahmed Tiba
Cc: will, xueshuai, saket.dumbre, mchehab, dave, djbw, bp, tony.luck,
guohanjun, lenb, skhan, vishal.l.verma, rafael, corbet, ira.weiny,
dave.jiang, krzk+dt, robh, catalin.marinas, alison.schofield,
conor+dt, linux-arm-kernel, Michael.Zhao2, linux-doc,
linux-kernel, linux-cxl, Dmitry.Lamerov, devicetree, linux-acpi,
linux-edac, acpica-devel
In-Reply-To: <20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-3-2e0500d42642@arm.com>
On Fri, 29 May 2026 10:50:43 +0100
Ahmed Tiba <ahmed.tiba@arm.com> wrote:
> Move the GHESv2 acknowledgment and error-source allocation helpers from
> ghes.c into ghes_cper.c. This is a mechanical refactor that keeps the
> logic unchanged while making the helpers reusable.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
Reviewed-by: Jonathan Cameron <jic23@kernel.org>
^ permalink raw reply
* Re: [PATCH v5 01/10] ACPI: APEI: GHES: share macros via a private header
From: Jonathan Cameron @ 2026-05-29 15:52 UTC (permalink / raw)
To: Ahmed Tiba
Cc: will, xueshuai, saket.dumbre, mchehab, dave, djbw, bp, tony.luck,
guohanjun, lenb, skhan, vishal.l.verma, rafael, corbet, ira.weiny,
dave.jiang, krzk+dt, robh, catalin.marinas, alison.schofield,
conor+dt, linux-arm-kernel, Michael.Zhao2, linux-doc,
linux-kernel, linux-cxl, Dmitry.Lamerov, devicetree, linux-acpi,
linux-edac, acpica-devel
In-Reply-To: <20260529-topics-ahmtib01-ras_ffh_arm_internal_review-v5-1-2e0500d42642@arm.com>
On Fri, 29 May 2026 10:50:41 +0100
Ahmed Tiba <ahmed.tiba@arm.com> wrote:
> Carve the CPER helper macros out of ghes.c and place them in a private
> header so they can be shared with upcoming helper files. This is a
> mechanical include change with no functional differences.
>
> Signed-off-by: Ahmed Tiba <ahmed.tiba@arm.com>
I'm not sure there is a 'right' way to handle the use of ACPI deifned stuff
outside of ACPI, but this seems reasonable to me.
Reviewed-by: Jonathan Cameron <jic23@kernel.org>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox