* [RFC PATCH 1/5] mm/damon/vaddr: support prep_probes
2026-09-05 20:26 [RFC PATCH 0/5] mm/damon/vaddr: support {prep,apply}_probes SJ Park
@ 2026-09-05 20:26 ` SJ Park
2026-09-05 20:26 ` [RFC PATCH 2/5] mm/damon/paddr: move probe filter handling to ops-common SJ Park
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: SJ Park @ 2026-09-05 20:26 UTC (permalink / raw)
Cc: SJ Park, Andrew Morton, damon, linux-kernel, linux-mm
DAMON virtual address space operation set (vaddr) is not supporting
prep_probes. Add the support.
Signed-off-by: SJ Park <sj@kernel.org>
---
mm/damon/vaddr.c | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index af9e1b82454cc..20f4784f17817 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -483,6 +483,45 @@ static unsigned int damon_va_check_accesses(struct damon_ctx *ctx)
return max_nr_accesses;
}
+static void damon_va_prep_probe_region(struct damon_ctx *ctx,
+ struct mm_struct *mm, struct damon_region *r,
+ struct damon_probe *probe)
+{
+ struct damon_prep *p;
+
+ damon_for_each_prep(p, probe) {
+ switch (p->action) {
+ case DAMON_PREP_SET_PGIDLE:
+ damon_va_mkold(mm, r->sampling_addr);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void damon_va_prep_probes(struct damon_ctx *ctx, bool set_samples)
+{
+ struct damon_target *t;
+ struct mm_struct *mm;
+ struct damon_region *r;
+ struct damon_probe *p;
+
+ damon_for_each_target(t, ctx) {
+ mm = damon_get_mm(t);
+ if (!mm)
+ continue;
+ damon_for_each_region(r, t) {
+ if (set_samples)
+ r->sampling_addr = damon_rand(ctx, r->ar.start,
+ r->ar.end);
+ damon_for_each_probe(p, ctx)
+ damon_va_prep_probe_region(ctx, mm, r, p);
+ }
+ mmput(mm);
+ }
+}
+
static bool damos_va_filter_young_match(struct damos_filter *filter,
struct folio *folio, struct vm_area_struct *vma,
unsigned long addr, pte_t *ptep, pmd_t *pmdp)
@@ -911,6 +950,7 @@ static int __init damon_va_initcall(void)
.update = damon_va_update,
.prepare_access_checks = damon_va_prepare_access_checks,
.check_accesses = damon_va_check_accesses,
+ .prep_probes = damon_va_prep_probes,
.target_valid = damon_va_target_valid,
.cleanup_target = damon_va_cleanup_target,
.apply_scheme = damon_va_apply_scheme,
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread* [RFC PATCH 2/5] mm/damon/paddr: move probe filter handling to ops-common
2026-09-05 20:26 [RFC PATCH 0/5] mm/damon/vaddr: support {prep,apply}_probes SJ Park
2026-09-05 20:26 ` [RFC PATCH 1/5] mm/damon/vaddr: support prep_probes SJ Park
@ 2026-09-05 20:26 ` SJ Park
2026-09-05 20:26 ` [RFC PATCH 3/5] mm/damon/vaddr: support apply_probe SJ Park
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: SJ Park @ 2026-09-05 20:26 UTC (permalink / raw)
Cc: SJ Park, Andrew Morton, damon, linux-kernel, linux-mm
Probe filter matching logic for anon and memcg type filters in DAMON's
physical address space operation set (paddr) can be reused by virtual
address space operation set (vaddr) in future. Prepare the future by
moving the code to ops-common.c
Signed-off-by: SJ Park <sj@kernel.org>
---
mm/damon/ops-common.c | 32 ++++++++++++++++++++++++++++++++
mm/damon/ops-common.h | 2 ++
mm/damon/paddr.c | 23 +----------------------
3 files changed, 35 insertions(+), 22 deletions(-)
diff --git a/mm/damon/ops-common.c b/mm/damon/ops-common.c
index 441d45679eaf2..76b663a4be610 100644
--- a/mm/damon/ops-common.c
+++ b/mm/damon/ops-common.c
@@ -524,3 +524,35 @@ bool damos_ops_has_filter(struct damos *s)
return true;
return false;
}
+
+bool damon_ops_filter_match(struct damon_filter *filter, struct folio *folio)
+{
+ bool matched = false;
+ struct mem_cgroup *memcg;
+
+ switch (filter->type) {
+ case DAMON_FILTER_TYPE_ANON:
+ if (!folio) {
+ matched = false;
+ break;
+ }
+ matched = folio_test_anon(folio);
+ break;
+ case DAMON_FILTER_TYPE_MEMCG:
+ if (!folio) {
+ matched = false;
+ break;
+ }
+ rcu_read_lock();
+ memcg = folio_memcg_check(folio);
+ if (!memcg)
+ matched = false;
+ else
+ matched = filter->memcg_id == mem_cgroup_id(memcg);
+ rcu_read_unlock();
+ break;
+ default:
+ break;
+ }
+ return matched == filter->matching;
+}
diff --git a/mm/damon/ops-common.h b/mm/damon/ops-common.h
index 172f0f17c4a84..ee27058251005 100644
--- a/mm/damon/ops-common.h
+++ b/mm/damon/ops-common.h
@@ -31,3 +31,5 @@ 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);
+
+bool damon_ops_filter_match(struct damon_filter *filter, struct folio *folio);
diff --git a/mm/damon/paddr.c b/mm/damon/paddr.c
index d2173a448d0b0..d7c81829445ba 100644
--- a/mm/damon/paddr.c
+++ b/mm/damon/paddr.c
@@ -143,29 +143,8 @@ static bool damon_pa_filter_match(struct damon_filter *filter,
struct folio *folio)
{
bool matched = false;
- struct mem_cgroup *memcg;
switch (filter->type) {
- case DAMON_FILTER_TYPE_ANON:
- if (!folio) {
- matched = false;
- break;
- }
- matched = folio_test_anon(folio);
- break;
- case DAMON_FILTER_TYPE_MEMCG:
- if (!folio) {
- matched = false;
- break;
- }
- rcu_read_lock();
- memcg = folio_memcg_check(folio);
- if (!memcg)
- matched = false;
- else
- matched = filter->memcg_id == mem_cgroup_id(memcg);
- rcu_read_unlock();
- break;
case DAMON_FILTER_TYPE_PGIDLE_UNSET:
if (!folio)
matched = false;
@@ -173,7 +152,7 @@ static bool damon_pa_filter_match(struct damon_filter *filter,
matched = damon_folio_young(folio);
break;
default:
- break;
+ return damon_ops_filter_match(filter, folio);
}
return matched == filter->matching;
}
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread* [RFC PATCH 3/5] mm/damon/vaddr: support apply_probe
2026-09-05 20:26 [RFC PATCH 0/5] mm/damon/vaddr: support {prep,apply}_probes SJ Park
2026-09-05 20:26 ` [RFC PATCH 1/5] mm/damon/vaddr: support prep_probes SJ Park
2026-09-05 20:26 ` [RFC PATCH 2/5] mm/damon/paddr: move probe filter handling to ops-common SJ Park
@ 2026-09-05 20:26 ` SJ Park
2026-09-05 20:26 ` [RFC PATCH 4/5] mm/damon/vaddr: extend apply_probes() for hugetlb SJ Park
2026-09-05 20:26 ` [RFC PATCH 5/5] mm/damon/vaddr: support pgidle_unset probe filter type SJ Park
4 siblings, 0 replies; 6+ messages in thread
From: SJ Park @ 2026-09-05 20:26 UTC (permalink / raw)
Cc: SJ Park, Andrew Morton, damon, linux-kernel, linux-mm
DAMON virtual address space operation set (vaddr) is not supporting
apply_probe. Add a minimum support. Do not support hugetlb pages and
PGIDLE_UNSET filter type for simplicity.
Signed-off-by: SJ Park <sj@kernel.org>
---
mm/damon/vaddr.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 123 insertions(+)
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index 20f4784f17817..7c548ec0cf6b5 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -522,6 +522,128 @@ static void damon_va_prep_probes(struct damon_ctx *ctx, bool set_samples)
}
}
+static bool damon_va_filter_pass(struct folio *folio, struct damon_probe *p)
+{
+ struct damon_filter *f;
+ bool pass = true;
+
+ damon_for_each_filter(f, p) {
+ if (damon_ops_filter_match(f, folio)) {
+ pass = f->allow;
+ break;
+ }
+ pass = !f->allow;
+ }
+ return pass;
+}
+
+struct damon_va_probe_walk_private {
+ struct damon_ctx *ctx;
+ struct damon_region *r;
+};
+
+static void damon_va_probe_folio(struct damon_ctx *ctx,
+ struct damon_region *r, struct folio *folio)
+{
+ struct damon_probe *probe;
+ int i = 0;
+
+ damon_for_each_probe(probe, ctx) {
+ if (damon_va_filter_pass(folio, probe))
+ r->probe_hits[i]++;
+ i++;
+ }
+}
+
+static int damon_va_probe_pmd_entry(pmd_t *pmd, unsigned long addr,
+ unsigned long next, struct mm_walk *walk)
+{
+ pte_t *pte;
+ pte_t ptent;
+ spinlock_t *ptl;
+ struct folio *folio;
+ struct damon_va_probe_walk_private *priv = walk->private;
+
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+ ptl = pmd_trans_huge_lock(pmd, walk->vma);
+ if (ptl) {
+ pmd_t pmde = pmdp_get(pmd);
+
+ if (!pmd_present(pmde))
+ goto huge_out;
+ folio = vm_normal_folio_pmd(walk->vma, addr, pmde);
+ if (!folio)
+ goto huge_out;
+ damon_va_probe_folio(priv->ctx, priv->r, folio);
+
+huge_out:
+ spin_unlock(ptl);
+ return 0;
+ }
+#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
+
+ pte = pte_offset_map_lock(walk->mm, pmd, addr, &ptl);
+ if (!pte)
+ return 0;
+ ptent = ptep_get(pte);
+ if (!pte_present(ptent))
+ goto out;
+ folio = vm_normal_folio(walk->vma, addr, ptent);
+ if (!folio)
+ goto out;
+ damon_va_probe_folio(priv->ctx, priv->r, folio);
+
+out:
+ pte_unmap_unlock(pte, ptl);
+ return 0;
+}
+
+static void __damon_va_apply_probes(struct damon_ctx *ctx,
+ struct mm_struct *mm, struct damon_region *r)
+{
+ struct damon_va_probe_walk_private arg = {
+ .ctx = ctx,
+ .r = r,
+ };
+ struct mm_walk_ops damon_probe_walk_ops = {
+ .pmd_entry = damon_va_probe_pmd_entry,
+ .hugetlb_entry = NULL,
+ };
+ unsigned long addr = r->sampling_addr;
+
+ if (!mm)
+ return;
+
+ damon_va_walk_page_range(mm, addr, addr + 1, &damon_probe_walk_ops,
+ &arg);
+}
+
+static unsigned int damon_va_apply_probes(struct damon_ctx *ctx,
+ bool set_samples, bool return_max_wsum)
+{
+ struct damon_target *t;
+ struct mm_struct *mm;
+ struct damon_region *r;
+ unsigned int max_wsum = 0;
+
+ damon_for_each_target(t, ctx) {
+ mm = damon_get_mm(t);
+ damon_for_each_region(r, t) {
+ if (set_samples)
+ r->sampling_addr = damon_rand(ctx, r->ar.start,
+ r->ar.end);
+ __damon_va_apply_probes(ctx, mm, r);
+ if (return_max_wsum)
+ max_wsum = max(damon_probe_hits_wsum(r, false,
+ ctx), max_wsum);
+ }
+ if (mm)
+ mmput(mm);
+ }
+
+ return max_wsum;
+}
+
static bool damos_va_filter_young_match(struct damos_filter *filter,
struct folio *folio, struct vm_area_struct *vma,
unsigned long addr, pte_t *ptep, pmd_t *pmdp)
@@ -951,6 +1073,7 @@ static int __init damon_va_initcall(void)
.prepare_access_checks = damon_va_prepare_access_checks,
.check_accesses = damon_va_check_accesses,
.prep_probes = damon_va_prep_probes,
+ .apply_probes = damon_va_apply_probes,
.target_valid = damon_va_target_valid,
.cleanup_target = damon_va_cleanup_target,
.apply_scheme = damon_va_apply_scheme,
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread* [RFC PATCH 4/5] mm/damon/vaddr: extend apply_probes() for hugetlb
2026-09-05 20:26 [RFC PATCH 0/5] mm/damon/vaddr: support {prep,apply}_probes SJ Park
` (2 preceding siblings ...)
2026-09-05 20:26 ` [RFC PATCH 3/5] mm/damon/vaddr: support apply_probe SJ Park
@ 2026-09-05 20:26 ` SJ Park
2026-09-05 20:26 ` [RFC PATCH 5/5] mm/damon/vaddr: support pgidle_unset probe filter type SJ Park
4 siblings, 0 replies; 6+ messages in thread
From: SJ Park @ 2026-09-05 20:26 UTC (permalink / raw)
Cc: SJ Park, Andrew Morton, damon, linux-kernel, linux-mm
DAMON virtual address space operation set(vaddr) does not support
hugetlb pages in apply_probes. Extend it for hugetlb pages.
Signed-off-by: SJ Park <sj@kernel.org>
---
mm/damon/vaddr.c | 30 +++++++++++++++++++++++++++++-
1 file changed, 29 insertions(+), 1 deletion(-)
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index 7c548ec0cf6b5..45239f05e113b 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -598,6 +598,34 @@ static int damon_va_probe_pmd_entry(pmd_t *pmd, unsigned long addr,
return 0;
}
+#ifdef CONFIG_HUGETLB_PAGE
+static int damon_va_probe_hugetlb_entry(pte_t *pte, unsigned long hmask,
+ unsigned long addr, unsigned long end, struct mm_walk *walk)
+{
+ struct damon_va_probe_walk_private *priv = walk->private;
+ struct hstate *h = hstate_vma(walk->vma);
+ struct folio *folio;
+ spinlock_t *ptl;
+ pte_t entry;
+
+ ptl = huge_pte_lock(h, walk->mm, pte);
+ entry = huge_ptep_get(walk->mm, addr, pte);
+ if (!pte_present(entry))
+ goto out;
+
+ folio = pfn_folio(pte_pfn(entry));
+ folio_get(folio);
+ damon_va_probe_folio(priv->ctx, priv->r, folio);
+ folio_put(folio);
+
+out:
+ spin_unlock(ptl);
+ return 0;
+}
+#else
+#define damon_va_probe_hugetlb_entry NULL
+#endif /* CONFIG_HUGETLB_PAGE */
+
static void __damon_va_apply_probes(struct damon_ctx *ctx,
struct mm_struct *mm, struct damon_region *r)
{
@@ -607,7 +635,7 @@ static void __damon_va_apply_probes(struct damon_ctx *ctx,
};
struct mm_walk_ops damon_probe_walk_ops = {
.pmd_entry = damon_va_probe_pmd_entry,
- .hugetlb_entry = NULL,
+ .hugetlb_entry = damon_va_probe_hugetlb_entry,
};
unsigned long addr = r->sampling_addr;
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread* [RFC PATCH 5/5] mm/damon/vaddr: support pgidle_unset probe filter type
2026-09-05 20:26 [RFC PATCH 0/5] mm/damon/vaddr: support {prep,apply}_probes SJ Park
` (3 preceding siblings ...)
2026-09-05 20:26 ` [RFC PATCH 4/5] mm/damon/vaddr: extend apply_probes() for hugetlb SJ Park
@ 2026-09-05 20:26 ` SJ Park
4 siblings, 0 replies; 6+ messages in thread
From: SJ Park @ 2026-09-05 20:26 UTC (permalink / raw)
Cc: SJ Park, Andrew Morton, damon, linux-kernel, linux-mm
DAMON virtual address space operation set (vaddr) does not support
pgidle_unset probe filter type. Add the support.
Signed-off-by: SJ Park <sj@kernel.org>
---
mm/damon/vaddr.c | 55 ++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 48 insertions(+), 7 deletions(-)
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index 45239f05e113b..9a38dc89a156e 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -522,13 +522,49 @@ static void damon_va_prep_probes(struct damon_ctx *ctx, bool set_samples)
}
}
-static bool damon_va_filter_pass(struct folio *folio, struct damon_probe *p)
+static bool damon_va_young_addr(struct folio *folio, pte_t *pte, pmd_t *pmd,
+ struct mm_struct *mm, unsigned long addr)
+{
+ bool young = false;
+
+ if (pte)
+ young = pte_young(*pte);
+ else if (pmd)
+ young = pmd_young(*pmd);
+ young = young || !folio_test_idle(folio) ||
+ mmu_notifier_test_young(mm, addr);
+ return young;
+}
+
+static bool damon_va_filter_match(struct damon_filter *filter,
+ struct folio *folio, pte_t *pte, pmd_t *pmd,
+ struct mm_struct *mm, unsigned long addr)
+{
+ bool matched = false;
+
+ switch (filter->type) {
+ case DAMON_FILTER_TYPE_PGIDLE_UNSET:
+ if (!folio)
+ matched = false;
+ else
+ matched = damon_va_young_addr(folio, pte, pmd, mm,
+ addr);
+ break;
+ default:
+ return damon_ops_filter_match(filter, folio);
+ }
+ return matched == filter->matching;
+}
+
+static bool damon_va_filter_pass(struct folio *folio, struct damon_probe *p,
+ pte_t *pte, pmd_t *pmd, struct mm_struct *mm,
+ unsigned long addr)
{
struct damon_filter *f;
bool pass = true;
damon_for_each_filter(f, p) {
- if (damon_ops_filter_match(f, folio)) {
+ if (damon_va_filter_match(f, folio, pte, pmd, mm, addr)) {
pass = f->allow;
break;
}
@@ -543,13 +579,15 @@ struct damon_va_probe_walk_private {
};
static void damon_va_probe_folio(struct damon_ctx *ctx,
- struct damon_region *r, struct folio *folio)
+ struct damon_region *r, struct folio *folio,
+ pte_t *pte, pmd_t *pmd, struct mm_struct *mm)
{
struct damon_probe *probe;
int i = 0;
damon_for_each_probe(probe, ctx) {
- if (damon_va_filter_pass(folio, probe))
+ if (damon_va_filter_pass(folio, probe, pte, pmd, mm,
+ r->sampling_addr))
r->probe_hits[i]++;
i++;
}
@@ -574,7 +612,8 @@ static int damon_va_probe_pmd_entry(pmd_t *pmd, unsigned long addr,
folio = vm_normal_folio_pmd(walk->vma, addr, pmde);
if (!folio)
goto huge_out;
- damon_va_probe_folio(priv->ctx, priv->r, folio);
+ damon_va_probe_folio(priv->ctx, priv->r, folio, NULL, &pmde,
+ walk->vma->vm_mm);
huge_out:
spin_unlock(ptl);
@@ -591,7 +630,8 @@ static int damon_va_probe_pmd_entry(pmd_t *pmd, unsigned long addr,
folio = vm_normal_folio(walk->vma, addr, ptent);
if (!folio)
goto out;
- damon_va_probe_folio(priv->ctx, priv->r, folio);
+ damon_va_probe_folio(priv->ctx, priv->r, folio, &ptent, NULL,
+ walk->vma->vm_mm);
out:
pte_unmap_unlock(pte, ptl);
@@ -615,7 +655,8 @@ static int damon_va_probe_hugetlb_entry(pte_t *pte, unsigned long hmask,
folio = pfn_folio(pte_pfn(entry));
folio_get(folio);
- damon_va_probe_folio(priv->ctx, priv->r, folio);
+ damon_va_probe_folio(priv->ctx, priv->r, folio, &entry, NULL,
+ walk->vma->vm_mm);
folio_put(folio);
out:
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread