Files
linux/mm/damon/sysfs.c
Linus Torvalds 8804d970fa Merge tag 'mm-stable-2025-10-01-19-00' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
Pull MM updates from Andrew Morton:

 - "mm, swap: improve cluster scan strategy" from Kairui Song improves
   performance and reduces the failure rate of swap cluster allocation

 - "support large align and nid in Rust allocators" from Vitaly Wool
   permits Rust allocators to set NUMA node and large alignment when
   perforning slub and vmalloc reallocs

 - "mm/damon/vaddr: support stat-purpose DAMOS" from Yueyang Pan extend
   DAMOS_STAT's handling of the DAMON operations sets for virtual
   address spaces for ops-level DAMOS filters

 - "execute PROCMAP_QUERY ioctl under per-vma lock" from Suren
   Baghdasaryan reduces mmap_lock contention during reads of
   /proc/pid/maps

 - "mm/mincore: minor clean up for swap cache checking" from Kairui Song
   performs some cleanup in the swap code

 - "mm: vm_normal_page*() improvements" from David Hildenbrand provides
   code cleanup in the pagemap code

 - "add persistent huge zero folio support" from Pankaj Raghav provides
   a block layer speedup by optionalls making the
   huge_zero_pagepersistent, instead of releasing it when its refcount
   falls to zero

 - "kho: fixes and cleanups" from Mike Rapoport adds a few touchups to
   the recently added Kexec Handover feature

 - "mm: make mm->flags a bitmap and 64-bit on all arches" from Lorenzo
   Stoakes turns mm_struct.flags into a bitmap. To end the constant
   struggle with space shortage on 32-bit conflicting with 64-bit's
   needs

 - "mm/swapfile.c and swap.h cleanup" from Chris Li cleans up some swap
   code

 - "selftests/mm: Fix false positives and skip unsupported tests" from
   Donet Tom fixes a few things in our selftests code

 - "prctl: extend PR_SET_THP_DISABLE to only provide THPs when advised"
   from David Hildenbrand "allows individual processes to opt-out of
   THP=always into THP=madvise, without affecting other workloads on the
   system".

   It's a long story - the [1/N] changelog spells out the considerations

 - "Add and use memdesc_flags_t" from Matthew Wilcox gets us started on
   the memdesc project. Please see

      https://kernelnewbies.org/MatthewWilcox/Memdescs and
      https://blogs.oracle.com/linux/post/introducing-memdesc

 - "Tiny optimization for large read operations" from Chi Zhiling
   improves the efficiency of the pagecache read path

 - "Better split_huge_page_test result check" from Zi Yan improves our
   folio splitting selftest code

 - "test that rmap behaves as expected" from Wei Yang adds some rmap
   selftests

 - "remove write_cache_pages()" from Christoph Hellwig removes that
   function and converts its two remaining callers

 - "selftests/mm: uffd-stress fixes" from Dev Jain fixes some UFFD
   selftests issues

 - "introduce kernel file mapped folios" from Boris Burkov introduces
   the concept of "kernel file pages". Using these permits btrfs to
   account its metadata pages to the root cgroup, rather than to the
   cgroups of random inappropriate tasks

 - "mm/pageblock: improve readability of some pageblock handling" from
   Wei Yang provides some readability improvements to the page allocator
   code

 - "mm/damon: support ARM32 with LPAE" from SeongJae Park teaches DAMON
   to understand arm32 highmem

 - "tools: testing: Use existing atomic.h for vma/maple tests" from
   Brendan Jackman performs some code cleanups and deduplication under
   tools/testing/

 - "maple_tree: Fix testing for 32bit compiles" from Liam Howlett fixes
   a couple of 32-bit issues in tools/testing/radix-tree.c

 - "kasan: unify kasan_enabled() and remove arch-specific
   implementations" from Sabyrzhan Tasbolatov moves KASAN arch-specific
   initialization code into a common arch-neutral implementation

 - "mm: remove zpool" from Johannes Weiner removes zspool - an
   indirection layer which now only redirects to a single thing
   (zsmalloc)

 - "mm: task_stack: Stack handling cleanups" from Pasha Tatashin makes a
   couple of cleanups in the fork code

 - "mm: remove nth_page()" from David Hildenbrand makes rather a lot of
   adjustments at various nth_page() callsites, eventually permitting
   the removal of that undesirable helper function

 - "introduce kasan.write_only option in hw-tags" from Yeoreum Yun
   creates a KASAN read-only mode for ARM, using that architecture's
   memory tagging feature. It is felt that a read-only mode KASAN is
   suitable for use in production systems rather than debug-only

 - "mm: hugetlb: cleanup hugetlb folio allocation" from Kefeng Wang does
   some tidying in the hugetlb folio allocation code

 - "mm: establish const-correctness for pointer parameters" from Max
   Kellermann makes quite a number of the MM API functions more accurate
   about the constness of their arguments. This was getting in the way
   of subsystems (in this case CEPH) when they attempt to improving
   their own const/non-const accuracy

 - "Cleanup free_pages() misuse" from Vishal Moola fixes a number of
   code sites which were confused over when to use free_pages() vs
   __free_pages()

 - "Add Rust abstraction for Maple Trees" from Alice Ryhl makes the
   mapletree code accessible to Rust. Required by nouveau and by its
   forthcoming successor: the new Rust Nova driver

 - "selftests/mm: split_huge_page_test: split_pte_mapped_thp
   improvements" from David Hildenbrand adds a fix and some cleanups to
   the thp selftesting code

 - "mm, swap: introduce swap table as swap cache (phase I)" from Chris
   Li and Kairui Song is the first step along the path to implementing
   "swap tables" - a new approach to swap allocation and state tracking
   which is expected to yield speed and space improvements. This
   patchset itself yields a 5-20% performance benefit in some situations

 - "Some ptdesc cleanups" from Matthew Wilcox utilizes the new memdesc
   layer to clean up the ptdesc code a little

 - "Fix va_high_addr_switch.sh test failure" from Chunyu Hu fixes some
   issues in our 5-level pagetable selftesting code

 - "Minor fixes for memory allocation profiling" from Suren Baghdasaryan
   addresses a couple of minor issues in relatively new memory
   allocation profiling feature

 - "Small cleanups" from Matthew Wilcox has a few cleanups in
   preparation for more memdesc work

 - "mm/damon: add addr_unit for DAMON_LRU_SORT and DAMON_RECLAIM" from
   Quanmin Yan makes some changes to DAMON in furtherance of supporting
   arm highmem

 - "selftests/mm: Add -Wunreachable-code and fix warnings" from Muhammad
   Anjum adds that compiler check to selftests code and fixes the
   fallout, by removing dead code

 - "Improvements to Victim Process Thawing and OOM Reaper Traversal
   Order" from zhongjinji makes a number of improvements in the OOM
   killer: mainly thawing a more appropriate group of victim threads so
   they can release resources

 - "mm/damon: misc fixups and improvements for 6.18" from SeongJae Park
   is a bunch of small and unrelated fixups for DAMON

 - "mm/damon: define and use DAMON initialization check function" from
   SeongJae Park implement reliability and maintainability improvements
   to a recently-added bug fix

 - "mm/damon/stat: expose auto-tuned intervals and non-idle ages" from
   SeongJae Park provides additional transparency to userspace clients
   of the DAMON_STAT information

 - "Expand scope of khugepaged anonymous collapse" from Dev Jain removes
   some constraints on khubepaged's collapsing of anon VMAs. It also
   increases the success rate of MADV_COLLAPSE against an anon vma

 - "mm: do not assume file == vma->vm_file in compat_vma_mmap_prepare()"
   from Lorenzo Stoakes moves us further towards removal of
   file_operations.mmap(). This patchset concentrates upon clearing up
   the treatment of stacked filesystems

 - "mm: Improve mlock tracking for large folios" from Kiryl Shutsemau
   provides some fixes and improvements to mlock's tracking of large
   folios. /proc/meminfo's "Mlocked" field became more accurate

 - "mm/ksm: Fix incorrect accounting of KSM counters during fork" from
   Donet Tom fixes several user-visible KSM stats inaccuracies across
   forks and adds selftest code to verify these counters

 - "mm_slot: fix the usage of mm_slot_entry" from Wei Yang addresses
   some potential but presently benign issues in KSM's mm_slot handling

* tag 'mm-stable-2025-10-01-19-00' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: (372 commits)
  mm: swap: check for stable address space before operating on the VMA
  mm: convert folio_page() back to a macro
  mm/khugepaged: use start_addr/addr for improved readability
  hugetlbfs: skip VMAs without shareable locks in hugetlb_vmdelete_list
  alloc_tag: fix boot failure due to NULL pointer dereference
  mm: silence data-race in update_hiwater_rss
  mm/memory-failure: don't select MEMORY_ISOLATION
  mm/khugepaged: remove definition of struct khugepaged_mm_slot
  mm/ksm: get mm_slot by mm_slot_entry() when slot is !NULL
  hugetlb: increase number of reserving hugepages via cmdline
  selftests/mm: add fork inheritance test for ksm_merging_pages counter
  mm/ksm: fix incorrect KSM counter handling in mm_struct during fork
  drivers/base/node: fix double free in register_one_node()
  mm: remove PMD alignment constraint in execmem_vmalloc()
  mm/memory_hotplug: fix typo 'esecially' -> 'especially'
  mm/rmap: improve mlock tracking for large folios
  mm/filemap: map entire large folio faultaround
  mm/fault: try to map the entire file folio in finish_fault()
  mm/rmap: mlock large folios in try_to_unmap_one()
  mm/rmap: fix a mlock race condition in folio_referenced_one()
  ...
2025-10-02 18:18:33 -07:00

2055 lines
50 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* DAMON sysfs Interface
*
* Copyright (c) 2022 SeongJae Park <sj@kernel.org>
*/
#include <linux/pid.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "sysfs-common.h"
/*
* init region directory
*/
struct damon_sysfs_region {
struct kobject kobj;
struct damon_addr_range ar;
};
static struct damon_sysfs_region *damon_sysfs_region_alloc(void)
{
return kzalloc(sizeof(struct damon_sysfs_region), GFP_KERNEL);
}
static ssize_t start_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct damon_sysfs_region *region = container_of(kobj,
struct damon_sysfs_region, kobj);
return sysfs_emit(buf, "%lu\n", region->ar.start);
}
static ssize_t start_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct damon_sysfs_region *region = container_of(kobj,
struct damon_sysfs_region, kobj);
int err = kstrtoul(buf, 0, &region->ar.start);
return err ? err : count;
}
static ssize_t end_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct damon_sysfs_region *region = container_of(kobj,
struct damon_sysfs_region, kobj);
return sysfs_emit(buf, "%lu\n", region->ar.end);
}
static ssize_t end_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct damon_sysfs_region *region = container_of(kobj,
struct damon_sysfs_region, kobj);
int err = kstrtoul(buf, 0, &region->ar.end);
return err ? err : count;
}
static void damon_sysfs_region_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_region, kobj));
}
static struct kobj_attribute damon_sysfs_region_start_attr =
__ATTR_RW_MODE(start, 0600);
static struct kobj_attribute damon_sysfs_region_end_attr =
__ATTR_RW_MODE(end, 0600);
static struct attribute *damon_sysfs_region_attrs[] = {
&damon_sysfs_region_start_attr.attr,
&damon_sysfs_region_end_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_region);
static const struct kobj_type damon_sysfs_region_ktype = {
.release = damon_sysfs_region_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_region_groups,
};
/*
* init_regions directory
*/
struct damon_sysfs_regions {
struct kobject kobj;
struct damon_sysfs_region **regions_arr;
int nr;
};
static struct damon_sysfs_regions *damon_sysfs_regions_alloc(void)
{
return kzalloc(sizeof(struct damon_sysfs_regions), GFP_KERNEL);
}
static void damon_sysfs_regions_rm_dirs(struct damon_sysfs_regions *regions)
{
struct damon_sysfs_region **regions_arr = regions->regions_arr;
int i;
for (i = 0; i < regions->nr; i++)
kobject_put(&regions_arr[i]->kobj);
regions->nr = 0;
kfree(regions_arr);
regions->regions_arr = NULL;
}
static int damon_sysfs_regions_add_dirs(struct damon_sysfs_regions *regions,
int nr_regions)
{
struct damon_sysfs_region **regions_arr, *region;
int err, i;
damon_sysfs_regions_rm_dirs(regions);
if (!nr_regions)
return 0;
regions_arr = kmalloc_array(nr_regions, sizeof(*regions_arr),
GFP_KERNEL | __GFP_NOWARN);
if (!regions_arr)
return -ENOMEM;
regions->regions_arr = regions_arr;
for (i = 0; i < nr_regions; i++) {
region = damon_sysfs_region_alloc();
if (!region) {
damon_sysfs_regions_rm_dirs(regions);
return -ENOMEM;
}
err = kobject_init_and_add(&region->kobj,
&damon_sysfs_region_ktype, &regions->kobj,
"%d", i);
if (err) {
kobject_put(&region->kobj);
damon_sysfs_regions_rm_dirs(regions);
return err;
}
regions_arr[i] = region;
regions->nr++;
}
return 0;
}
static ssize_t nr_regions_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_regions *regions = container_of(kobj,
struct damon_sysfs_regions, kobj);
return sysfs_emit(buf, "%d\n", regions->nr);
}
static ssize_t nr_regions_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_regions *regions;
int nr, err = kstrtoint(buf, 0, &nr);
if (err)
return err;
if (nr < 0)
return -EINVAL;
regions = container_of(kobj, struct damon_sysfs_regions, kobj);
if (!mutex_trylock(&damon_sysfs_lock))
return -EBUSY;
err = damon_sysfs_regions_add_dirs(regions, nr);
mutex_unlock(&damon_sysfs_lock);
if (err)
return err;
return count;
}
static void damon_sysfs_regions_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_regions, kobj));
}
static struct kobj_attribute damon_sysfs_regions_nr_attr =
__ATTR_RW_MODE(nr_regions, 0600);
static struct attribute *damon_sysfs_regions_attrs[] = {
&damon_sysfs_regions_nr_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_regions);
static const struct kobj_type damon_sysfs_regions_ktype = {
.release = damon_sysfs_regions_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_regions_groups,
};
/*
* target directory
*/
struct damon_sysfs_target {
struct kobject kobj;
struct damon_sysfs_regions *regions;
int pid;
};
static struct damon_sysfs_target *damon_sysfs_target_alloc(void)
{
return kzalloc(sizeof(struct damon_sysfs_target), GFP_KERNEL);
}
static int damon_sysfs_target_add_dirs(struct damon_sysfs_target *target)
{
struct damon_sysfs_regions *regions = damon_sysfs_regions_alloc();
int err;
if (!regions)
return -ENOMEM;
err = kobject_init_and_add(&regions->kobj, &damon_sysfs_regions_ktype,
&target->kobj, "regions");
if (err)
kobject_put(&regions->kobj);
else
target->regions = regions;
return err;
}
static void damon_sysfs_target_rm_dirs(struct damon_sysfs_target *target)
{
damon_sysfs_regions_rm_dirs(target->regions);
kobject_put(&target->regions->kobj);
}
static ssize_t pid_target_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_target *target = container_of(kobj,
struct damon_sysfs_target, kobj);
return sysfs_emit(buf, "%d\n", target->pid);
}
static ssize_t pid_target_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_target *target = container_of(kobj,
struct damon_sysfs_target, kobj);
int err = kstrtoint(buf, 0, &target->pid);
if (err)
return -EINVAL;
return count;
}
static void damon_sysfs_target_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_target, kobj));
}
static struct kobj_attribute damon_sysfs_target_pid_attr =
__ATTR_RW_MODE(pid_target, 0600);
static struct attribute *damon_sysfs_target_attrs[] = {
&damon_sysfs_target_pid_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_target);
static const struct kobj_type damon_sysfs_target_ktype = {
.release = damon_sysfs_target_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_target_groups,
};
/*
* targets directory
*/
struct damon_sysfs_targets {
struct kobject kobj;
struct damon_sysfs_target **targets_arr;
int nr;
};
static struct damon_sysfs_targets *damon_sysfs_targets_alloc(void)
{
return kzalloc(sizeof(struct damon_sysfs_targets), GFP_KERNEL);
}
static void damon_sysfs_targets_rm_dirs(struct damon_sysfs_targets *targets)
{
struct damon_sysfs_target **targets_arr = targets->targets_arr;
int i;
for (i = 0; i < targets->nr; i++) {
damon_sysfs_target_rm_dirs(targets_arr[i]);
kobject_put(&targets_arr[i]->kobj);
}
targets->nr = 0;
kfree(targets_arr);
targets->targets_arr = NULL;
}
static int damon_sysfs_targets_add_dirs(struct damon_sysfs_targets *targets,
int nr_targets)
{
struct damon_sysfs_target **targets_arr, *target;
int err, i;
damon_sysfs_targets_rm_dirs(targets);
if (!nr_targets)
return 0;
targets_arr = kmalloc_array(nr_targets, sizeof(*targets_arr),
GFP_KERNEL | __GFP_NOWARN);
if (!targets_arr)
return -ENOMEM;
targets->targets_arr = targets_arr;
for (i = 0; i < nr_targets; i++) {
target = damon_sysfs_target_alloc();
if (!target) {
damon_sysfs_targets_rm_dirs(targets);
return -ENOMEM;
}
err = kobject_init_and_add(&target->kobj,
&damon_sysfs_target_ktype, &targets->kobj,
"%d", i);
if (err)
goto out;
err = damon_sysfs_target_add_dirs(target);
if (err)
goto out;
targets_arr[i] = target;
targets->nr++;
}
return 0;
out:
damon_sysfs_targets_rm_dirs(targets);
kobject_put(&target->kobj);
return err;
}
static ssize_t nr_targets_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_targets *targets = container_of(kobj,
struct damon_sysfs_targets, kobj);
return sysfs_emit(buf, "%d\n", targets->nr);
}
static ssize_t nr_targets_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_targets *targets;
int nr, err = kstrtoint(buf, 0, &nr);
if (err)
return err;
if (nr < 0)
return -EINVAL;
targets = container_of(kobj, struct damon_sysfs_targets, kobj);
if (!mutex_trylock(&damon_sysfs_lock))
return -EBUSY;
err = damon_sysfs_targets_add_dirs(targets, nr);
mutex_unlock(&damon_sysfs_lock);
if (err)
return err;
return count;
}
static void damon_sysfs_targets_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_targets, kobj));
}
static struct kobj_attribute damon_sysfs_targets_nr_attr =
__ATTR_RW_MODE(nr_targets, 0600);
static struct attribute *damon_sysfs_targets_attrs[] = {
&damon_sysfs_targets_nr_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_targets);
static const struct kobj_type damon_sysfs_targets_ktype = {
.release = damon_sysfs_targets_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_targets_groups,
};
/*
* intervals goal directory
*/
struct damon_sysfs_intervals_goal {
struct kobject kobj;
unsigned long access_bp;
unsigned long aggrs;
unsigned long min_sample_us;
unsigned long max_sample_us;
};
static struct damon_sysfs_intervals_goal *damon_sysfs_intervals_goal_alloc(
unsigned long access_bp, unsigned long aggrs,
unsigned long min_sample_us, unsigned long max_sample_us)
{
struct damon_sysfs_intervals_goal *goal = kmalloc(sizeof(*goal),
GFP_KERNEL);
if (!goal)
return NULL;
goal->kobj = (struct kobject){};
goal->access_bp = access_bp;
goal->aggrs = aggrs;
goal->min_sample_us = min_sample_us;
goal->max_sample_us = max_sample_us;
return goal;
}
static ssize_t access_bp_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_intervals_goal *goal = container_of(kobj,
struct damon_sysfs_intervals_goal, kobj);
return sysfs_emit(buf, "%lu\n", goal->access_bp);
}
static ssize_t access_bp_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_intervals_goal *goal = container_of(kobj,
struct damon_sysfs_intervals_goal, kobj);
unsigned long nr;
int err = kstrtoul(buf, 0, &nr);
if (err)
return err;
goal->access_bp = nr;
return count;
}
static ssize_t aggrs_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_intervals_goal *goal = container_of(kobj,
struct damon_sysfs_intervals_goal, kobj);
return sysfs_emit(buf, "%lu\n", goal->aggrs);
}
static ssize_t aggrs_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_intervals_goal *goal = container_of(kobj,
struct damon_sysfs_intervals_goal, kobj);
unsigned long nr;
int err = kstrtoul(buf, 0, &nr);
if (err)
return err;
goal->aggrs = nr;
return count;
}
static ssize_t min_sample_us_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_intervals_goal *goal = container_of(kobj,
struct damon_sysfs_intervals_goal, kobj);
return sysfs_emit(buf, "%lu\n", goal->min_sample_us);
}
static ssize_t min_sample_us_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_intervals_goal *goal = container_of(kobj,
struct damon_sysfs_intervals_goal, kobj);
unsigned long nr;
int err = kstrtoul(buf, 0, &nr);
if (err)
return err;
goal->min_sample_us = nr;
return count;
}
static ssize_t max_sample_us_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_intervals_goal *goal = container_of(kobj,
struct damon_sysfs_intervals_goal, kobj);
return sysfs_emit(buf, "%lu\n", goal->max_sample_us);
}
static ssize_t max_sample_us_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_intervals_goal *goal = container_of(kobj,
struct damon_sysfs_intervals_goal, kobj);
unsigned long nr;
int err = kstrtoul(buf, 0, &nr);
if (err)
return err;
goal->max_sample_us = nr;
return count;
}
static void damon_sysfs_intervals_goal_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_intervals_goal, kobj));
}
static struct kobj_attribute damon_sysfs_intervals_goal_access_bp_attr =
__ATTR_RW_MODE(access_bp, 0600);
static struct kobj_attribute damon_sysfs_intervals_goal_aggrs_attr =
__ATTR_RW_MODE(aggrs, 0600);
static struct kobj_attribute damon_sysfs_intervals_goal_min_sample_us_attr =
__ATTR_RW_MODE(min_sample_us, 0600);
static struct kobj_attribute damon_sysfs_intervals_goal_max_sample_us_attr =
__ATTR_RW_MODE(max_sample_us, 0600);
static struct attribute *damon_sysfs_intervals_goal_attrs[] = {
&damon_sysfs_intervals_goal_access_bp_attr.attr,
&damon_sysfs_intervals_goal_aggrs_attr.attr,
&damon_sysfs_intervals_goal_min_sample_us_attr.attr,
&damon_sysfs_intervals_goal_max_sample_us_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_intervals_goal);
static const struct kobj_type damon_sysfs_intervals_goal_ktype = {
.release = damon_sysfs_intervals_goal_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_intervals_goal_groups,
};
/*
* intervals directory
*/
struct damon_sysfs_intervals {
struct kobject kobj;
unsigned long sample_us;
unsigned long aggr_us;
unsigned long update_us;
struct damon_sysfs_intervals_goal *intervals_goal;
};
static struct damon_sysfs_intervals *damon_sysfs_intervals_alloc(
unsigned long sample_us, unsigned long aggr_us,
unsigned long update_us)
{
struct damon_sysfs_intervals *intervals = kmalloc(sizeof(*intervals),
GFP_KERNEL);
if (!intervals)
return NULL;
intervals->kobj = (struct kobject){};
intervals->sample_us = sample_us;
intervals->aggr_us = aggr_us;
intervals->update_us = update_us;
return intervals;
}
static int damon_sysfs_intervals_add_dirs(struct damon_sysfs_intervals *intervals)
{
struct damon_sysfs_intervals_goal *goal;
int err;
goal = damon_sysfs_intervals_goal_alloc(0, 0, 0, 0);
if (!goal)
return -ENOMEM;
err = kobject_init_and_add(&goal->kobj,
&damon_sysfs_intervals_goal_ktype, &intervals->kobj,
"intervals_goal");
if (err) {
kobject_put(&goal->kobj);
intervals->intervals_goal = NULL;
return err;
}
intervals->intervals_goal = goal;
return 0;
}
static void damon_sysfs_intervals_rm_dirs(struct damon_sysfs_intervals *intervals)
{
kobject_put(&intervals->intervals_goal->kobj);
}
static ssize_t sample_us_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_intervals *intervals = container_of(kobj,
struct damon_sysfs_intervals, kobj);
return sysfs_emit(buf, "%lu\n", intervals->sample_us);
}
static ssize_t sample_us_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_intervals *intervals = container_of(kobj,
struct damon_sysfs_intervals, kobj);
unsigned long us;
int err = kstrtoul(buf, 0, &us);
if (err)
return err;
intervals->sample_us = us;
return count;
}
static ssize_t aggr_us_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct damon_sysfs_intervals *intervals = container_of(kobj,
struct damon_sysfs_intervals, kobj);
return sysfs_emit(buf, "%lu\n", intervals->aggr_us);
}
static ssize_t aggr_us_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct damon_sysfs_intervals *intervals = container_of(kobj,
struct damon_sysfs_intervals, kobj);
unsigned long us;
int err = kstrtoul(buf, 0, &us);
if (err)
return err;
intervals->aggr_us = us;
return count;
}
static ssize_t update_us_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_intervals *intervals = container_of(kobj,
struct damon_sysfs_intervals, kobj);
return sysfs_emit(buf, "%lu\n", intervals->update_us);
}
static ssize_t update_us_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_intervals *intervals = container_of(kobj,
struct damon_sysfs_intervals, kobj);
unsigned long us;
int err = kstrtoul(buf, 0, &us);
if (err)
return err;
intervals->update_us = us;
return count;
}
static void damon_sysfs_intervals_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_intervals, kobj));
}
static struct kobj_attribute damon_sysfs_intervals_sample_us_attr =
__ATTR_RW_MODE(sample_us, 0600);
static struct kobj_attribute damon_sysfs_intervals_aggr_us_attr =
__ATTR_RW_MODE(aggr_us, 0600);
static struct kobj_attribute damon_sysfs_intervals_update_us_attr =
__ATTR_RW_MODE(update_us, 0600);
static struct attribute *damon_sysfs_intervals_attrs[] = {
&damon_sysfs_intervals_sample_us_attr.attr,
&damon_sysfs_intervals_aggr_us_attr.attr,
&damon_sysfs_intervals_update_us_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_intervals);
static const struct kobj_type damon_sysfs_intervals_ktype = {
.release = damon_sysfs_intervals_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_intervals_groups,
};
/*
* monitoring_attrs directory
*/
struct damon_sysfs_attrs {
struct kobject kobj;
struct damon_sysfs_intervals *intervals;
struct damon_sysfs_ul_range *nr_regions_range;
};
static struct damon_sysfs_attrs *damon_sysfs_attrs_alloc(void)
{
struct damon_sysfs_attrs *attrs = kmalloc(sizeof(*attrs), GFP_KERNEL);
if (!attrs)
return NULL;
attrs->kobj = (struct kobject){};
return attrs;
}
static int damon_sysfs_attrs_add_dirs(struct damon_sysfs_attrs *attrs)
{
struct damon_sysfs_intervals *intervals;
struct damon_sysfs_ul_range *nr_regions_range;
int err;
intervals = damon_sysfs_intervals_alloc(5000, 100000, 60000000);
if (!intervals)
return -ENOMEM;
err = kobject_init_and_add(&intervals->kobj,
&damon_sysfs_intervals_ktype, &attrs->kobj,
"intervals");
if (err)
goto put_intervals_out;
err = damon_sysfs_intervals_add_dirs(intervals);
if (err)
goto put_intervals_out;
attrs->intervals = intervals;
nr_regions_range = damon_sysfs_ul_range_alloc(10, 1000);
if (!nr_regions_range) {
err = -ENOMEM;
goto put_intervals_out;
}
err = kobject_init_and_add(&nr_regions_range->kobj,
&damon_sysfs_ul_range_ktype, &attrs->kobj,
"nr_regions");
if (err)
goto put_nr_regions_intervals_out;
attrs->nr_regions_range = nr_regions_range;
return 0;
put_nr_regions_intervals_out:
kobject_put(&nr_regions_range->kobj);
attrs->nr_regions_range = NULL;
put_intervals_out:
kobject_put(&intervals->kobj);
attrs->intervals = NULL;
return err;
}
static void damon_sysfs_attrs_rm_dirs(struct damon_sysfs_attrs *attrs)
{
kobject_put(&attrs->nr_regions_range->kobj);
damon_sysfs_intervals_rm_dirs(attrs->intervals);
kobject_put(&attrs->intervals->kobj);
}
static void damon_sysfs_attrs_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_attrs, kobj));
}
static struct attribute *damon_sysfs_attrs_attrs[] = {
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_attrs);
static const struct kobj_type damon_sysfs_attrs_ktype = {
.release = damon_sysfs_attrs_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_attrs_groups,
};
/*
* context directory
*/
struct damon_sysfs_ops_name {
enum damon_ops_id ops_id;
char *name;
};
static const struct damon_sysfs_ops_name damon_sysfs_ops_names[] = {
{
.ops_id = DAMON_OPS_VADDR,
.name = "vaddr",
},
{
.ops_id = DAMON_OPS_FVADDR,
.name = "fvaddr",
},
{
.ops_id = DAMON_OPS_PADDR,
.name = "paddr",
},
};
struct damon_sysfs_context {
struct kobject kobj;
enum damon_ops_id ops_id;
unsigned long addr_unit;
struct damon_sysfs_attrs *attrs;
struct damon_sysfs_targets *targets;
struct damon_sysfs_schemes *schemes;
};
static struct damon_sysfs_context *damon_sysfs_context_alloc(
enum damon_ops_id ops_id)
{
struct damon_sysfs_context *context = kmalloc(sizeof(*context),
GFP_KERNEL);
if (!context)
return NULL;
context->kobj = (struct kobject){};
context->ops_id = ops_id;
context->addr_unit = 1;
return context;
}
static int damon_sysfs_context_set_attrs(struct damon_sysfs_context *context)
{
struct damon_sysfs_attrs *attrs = damon_sysfs_attrs_alloc();
int err;
if (!attrs)
return -ENOMEM;
err = kobject_init_and_add(&attrs->kobj, &damon_sysfs_attrs_ktype,
&context->kobj, "monitoring_attrs");
if (err)
goto out;
err = damon_sysfs_attrs_add_dirs(attrs);
if (err)
goto out;
context->attrs = attrs;
return 0;
out:
kobject_put(&attrs->kobj);
return err;
}
static int damon_sysfs_context_set_targets(struct damon_sysfs_context *context)
{
struct damon_sysfs_targets *targets = damon_sysfs_targets_alloc();
int err;
if (!targets)
return -ENOMEM;
err = kobject_init_and_add(&targets->kobj, &damon_sysfs_targets_ktype,
&context->kobj, "targets");
if (err) {
kobject_put(&targets->kobj);
return err;
}
context->targets = targets;
return 0;
}
static int damon_sysfs_context_set_schemes(struct damon_sysfs_context *context)
{
struct damon_sysfs_schemes *schemes = damon_sysfs_schemes_alloc();
int err;
if (!schemes)
return -ENOMEM;
err = kobject_init_and_add(&schemes->kobj, &damon_sysfs_schemes_ktype,
&context->kobj, "schemes");
if (err) {
kobject_put(&schemes->kobj);
return err;
}
context->schemes = schemes;
return 0;
}
static int damon_sysfs_context_add_dirs(struct damon_sysfs_context *context)
{
int err;
err = damon_sysfs_context_set_attrs(context);
if (err)
return err;
err = damon_sysfs_context_set_targets(context);
if (err)
goto put_attrs_out;
err = damon_sysfs_context_set_schemes(context);
if (err)
goto put_targets_attrs_out;
return 0;
put_targets_attrs_out:
kobject_put(&context->targets->kobj);
context->targets = NULL;
put_attrs_out:
kobject_put(&context->attrs->kobj);
context->attrs = NULL;
return err;
}
static void damon_sysfs_context_rm_dirs(struct damon_sysfs_context *context)
{
damon_sysfs_attrs_rm_dirs(context->attrs);
kobject_put(&context->attrs->kobj);
damon_sysfs_targets_rm_dirs(context->targets);
kobject_put(&context->targets->kobj);
damon_sysfs_schemes_rm_dirs(context->schemes);
kobject_put(&context->schemes->kobj);
}
static ssize_t avail_operations_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int len = 0;
int i;
for (i = 0; i < ARRAY_SIZE(damon_sysfs_ops_names); i++) {
const struct damon_sysfs_ops_name *ops_name;
ops_name = &damon_sysfs_ops_names[i];
if (!damon_is_registered_ops(ops_name->ops_id))
continue;
len += sysfs_emit_at(buf, len, "%s\n", ops_name->name);
}
return len;
}
static ssize_t operations_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_context *context = container_of(kobj,
struct damon_sysfs_context, kobj);
int i;
for (i = 0; i < ARRAY_SIZE(damon_sysfs_ops_names); i++) {
const struct damon_sysfs_ops_name *ops_name;
ops_name = &damon_sysfs_ops_names[i];
if (ops_name->ops_id == context->ops_id)
return sysfs_emit(buf, "%s\n", ops_name->name);
}
return -EINVAL;
}
static ssize_t operations_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_context *context = container_of(kobj,
struct damon_sysfs_context, kobj);
int i;
for (i = 0; i < ARRAY_SIZE(damon_sysfs_ops_names); i++) {
const struct damon_sysfs_ops_name *ops_name;
ops_name = &damon_sysfs_ops_names[i];
if (sysfs_streq(buf, ops_name->name)) {
context->ops_id = ops_name->ops_id;
return count;
}
}
return -EINVAL;
}
static ssize_t addr_unit_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_context *context = container_of(kobj,
struct damon_sysfs_context, kobj);
return sysfs_emit(buf, "%lu\n", context->addr_unit);
}
static ssize_t addr_unit_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_context *context = container_of(kobj,
struct damon_sysfs_context, kobj);
unsigned long input_addr_unit;
int err = kstrtoul(buf, 0, &input_addr_unit);
if (err)
return err;
if (!input_addr_unit)
return -EINVAL;
context->addr_unit = input_addr_unit;
return count;
}
static void damon_sysfs_context_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_context, kobj));
}
static struct kobj_attribute damon_sysfs_context_avail_operations_attr =
__ATTR_RO_MODE(avail_operations, 0400);
static struct kobj_attribute damon_sysfs_context_operations_attr =
__ATTR_RW_MODE(operations, 0600);
static struct kobj_attribute damon_sysfs_context_addr_unit_attr =
__ATTR_RW_MODE(addr_unit, 0600);
static struct attribute *damon_sysfs_context_attrs[] = {
&damon_sysfs_context_avail_operations_attr.attr,
&damon_sysfs_context_operations_attr.attr,
&damon_sysfs_context_addr_unit_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_context);
static const struct kobj_type damon_sysfs_context_ktype = {
.release = damon_sysfs_context_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_context_groups,
};
/*
* contexts directory
*/
struct damon_sysfs_contexts {
struct kobject kobj;
struct damon_sysfs_context **contexts_arr;
int nr;
};
static struct damon_sysfs_contexts *damon_sysfs_contexts_alloc(void)
{
return kzalloc(sizeof(struct damon_sysfs_contexts), GFP_KERNEL);
}
static void damon_sysfs_contexts_rm_dirs(struct damon_sysfs_contexts *contexts)
{
struct damon_sysfs_context **contexts_arr = contexts->contexts_arr;
int i;
for (i = 0; i < contexts->nr; i++) {
damon_sysfs_context_rm_dirs(contexts_arr[i]);
kobject_put(&contexts_arr[i]->kobj);
}
contexts->nr = 0;
kfree(contexts_arr);
contexts->contexts_arr = NULL;
}
static int damon_sysfs_contexts_add_dirs(struct damon_sysfs_contexts *contexts,
int nr_contexts)
{
struct damon_sysfs_context **contexts_arr, *context;
int err, i;
damon_sysfs_contexts_rm_dirs(contexts);
if (!nr_contexts)
return 0;
contexts_arr = kmalloc_array(nr_contexts, sizeof(*contexts_arr),
GFP_KERNEL | __GFP_NOWARN);
if (!contexts_arr)
return -ENOMEM;
contexts->contexts_arr = contexts_arr;
for (i = 0; i < nr_contexts; i++) {
context = damon_sysfs_context_alloc(DAMON_OPS_VADDR);
if (!context) {
damon_sysfs_contexts_rm_dirs(contexts);
return -ENOMEM;
}
err = kobject_init_and_add(&context->kobj,
&damon_sysfs_context_ktype, &contexts->kobj,
"%d", i);
if (err)
goto out;
err = damon_sysfs_context_add_dirs(context);
if (err)
goto out;
contexts_arr[i] = context;
contexts->nr++;
}
return 0;
out:
damon_sysfs_contexts_rm_dirs(contexts);
kobject_put(&context->kobj);
return err;
}
static ssize_t nr_contexts_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_contexts *contexts = container_of(kobj,
struct damon_sysfs_contexts, kobj);
return sysfs_emit(buf, "%d\n", contexts->nr);
}
static ssize_t nr_contexts_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_contexts *contexts;
int nr, err;
err = kstrtoint(buf, 0, &nr);
if (err)
return err;
/* TODO: support multiple contexts per kdamond */
if (nr < 0 || 1 < nr)
return -EINVAL;
contexts = container_of(kobj, struct damon_sysfs_contexts, kobj);
if (!mutex_trylock(&damon_sysfs_lock))
return -EBUSY;
err = damon_sysfs_contexts_add_dirs(contexts, nr);
mutex_unlock(&damon_sysfs_lock);
if (err)
return err;
return count;
}
static void damon_sysfs_contexts_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_contexts, kobj));
}
static struct kobj_attribute damon_sysfs_contexts_nr_attr
= __ATTR_RW_MODE(nr_contexts, 0600);
static struct attribute *damon_sysfs_contexts_attrs[] = {
&damon_sysfs_contexts_nr_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_contexts);
static const struct kobj_type damon_sysfs_contexts_ktype = {
.release = damon_sysfs_contexts_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_contexts_groups,
};
/*
* kdamond directory
*/
struct damon_sysfs_kdamond {
struct kobject kobj;
struct damon_sysfs_contexts *contexts;
struct damon_ctx *damon_ctx;
unsigned int refresh_ms;
};
static struct damon_sysfs_kdamond *damon_sysfs_kdamond_alloc(void)
{
return kzalloc(sizeof(struct damon_sysfs_kdamond), GFP_KERNEL);
}
static int damon_sysfs_kdamond_add_dirs(struct damon_sysfs_kdamond *kdamond)
{
struct damon_sysfs_contexts *contexts;
int err;
contexts = damon_sysfs_contexts_alloc();
if (!contexts)
return -ENOMEM;
err = kobject_init_and_add(&contexts->kobj,
&damon_sysfs_contexts_ktype, &kdamond->kobj,
"contexts");
if (err) {
kobject_put(&contexts->kobj);
return err;
}
kdamond->contexts = contexts;
return err;
}
static void damon_sysfs_kdamond_rm_dirs(struct damon_sysfs_kdamond *kdamond)
{
damon_sysfs_contexts_rm_dirs(kdamond->contexts);
kobject_put(&kdamond->contexts->kobj);
}
/*
* enum damon_sysfs_cmd - Commands for a specific kdamond.
*/
enum damon_sysfs_cmd {
/* @DAMON_SYSFS_CMD_ON: Turn the kdamond on. */
DAMON_SYSFS_CMD_ON,
/* @DAMON_SYSFS_CMD_OFF: Turn the kdamond off. */
DAMON_SYSFS_CMD_OFF,
/* @DAMON_SYSFS_CMD_COMMIT: Update kdamond inputs. */
DAMON_SYSFS_CMD_COMMIT,
/*
* @DAMON_SYSFS_CMD_COMMIT_SCHEMES_QUOTA_GOALS: Commit the quota goals
* to DAMON.
*/
DAMON_SYSFS_CMD_COMMIT_SCHEMES_QUOTA_GOALS,
/*
* @DAMON_SYSFS_CMD_UPDATE_SCHEMES_STATS: Update scheme stats sysfs
* files.
*/
DAMON_SYSFS_CMD_UPDATE_SCHEMES_STATS,
/*
* @DAMON_SYSFS_CMD_UPDATE_SCHEMES_TRIED_BYTES: Update
* tried_regions/total_bytes sysfs files for each scheme.
*/
DAMON_SYSFS_CMD_UPDATE_SCHEMES_TRIED_BYTES,
/*
* @DAMON_SYSFS_CMD_UPDATE_SCHEMES_TRIED_REGIONS: Update schemes tried
* regions
*/
DAMON_SYSFS_CMD_UPDATE_SCHEMES_TRIED_REGIONS,
/*
* @DAMON_SYSFS_CMD_CLEAR_SCHEMES_TRIED_REGIONS: Clear schemes tried
* regions
*/
DAMON_SYSFS_CMD_CLEAR_SCHEMES_TRIED_REGIONS,
/*
* @DAMON_SYSFS_CMD_UPDATE_SCHEMES_EFFECTIVE_QUOTAS: Update the
* effective size quota of the scheme in bytes.
*/
DAMON_SYSFS_CMD_UPDATE_SCHEMES_EFFECTIVE_QUOTAS,
/*
* @DAMON_SYSFS_CMD_UPDATE_TUNED_INTERVALS: Update the tuned monitoring
* intevals.
*/
DAMON_SYSFS_CMD_UPDATE_TUNED_INTERVALS,
/*
* @NR_DAMON_SYSFS_CMDS: Total number of DAMON sysfs commands.
*/
NR_DAMON_SYSFS_CMDS,
};
/* Should match with enum damon_sysfs_cmd */
static const char * const damon_sysfs_cmd_strs[] = {
"on",
"off",
"commit",
"commit_schemes_quota_goals",
"update_schemes_stats",
"update_schemes_tried_bytes",
"update_schemes_tried_regions",
"clear_schemes_tried_regions",
"update_schemes_effective_quotas",
"update_tuned_intervals",
};
static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct damon_sysfs_kdamond *kdamond = container_of(kobj,
struct damon_sysfs_kdamond, kobj);
struct damon_ctx *ctx;
bool running = false;
if (!mutex_trylock(&damon_sysfs_lock))
return -EBUSY;
ctx = kdamond->damon_ctx;
if (ctx)
running = damon_is_running(ctx);
mutex_unlock(&damon_sysfs_lock);
return sysfs_emit(buf, "%s\n", running ?
damon_sysfs_cmd_strs[DAMON_SYSFS_CMD_ON] :
damon_sysfs_cmd_strs[DAMON_SYSFS_CMD_OFF]);
}
static int damon_sysfs_set_attrs(struct damon_ctx *ctx,
struct damon_sysfs_attrs *sys_attrs)
{
struct damon_sysfs_intervals *sys_intervals = sys_attrs->intervals;
struct damon_sysfs_intervals_goal *sys_goal =
sys_intervals->intervals_goal;
struct damon_sysfs_ul_range *sys_nr_regions =
sys_attrs->nr_regions_range;
struct damon_attrs attrs = {
.sample_interval = sys_intervals->sample_us,
.aggr_interval = sys_intervals->aggr_us,
.intervals_goal = {
.access_bp = sys_goal->access_bp,
.aggrs = sys_goal->aggrs,
.min_sample_us = sys_goal->min_sample_us,
.max_sample_us = sys_goal->max_sample_us},
.ops_update_interval = sys_intervals->update_us,
.min_nr_regions = sys_nr_regions->min,
.max_nr_regions = sys_nr_regions->max,
};
return damon_set_attrs(ctx, &attrs);
}
static int damon_sysfs_set_regions(struct damon_target *t,
struct damon_sysfs_regions *sysfs_regions,
unsigned long min_sz_region)
{
struct damon_addr_range *ranges = kmalloc_array(sysfs_regions->nr,
sizeof(*ranges), GFP_KERNEL | __GFP_NOWARN);
int i, err = -EINVAL;
if (!ranges)
return -ENOMEM;
for (i = 0; i < sysfs_regions->nr; i++) {
struct damon_sysfs_region *sys_region =
sysfs_regions->regions_arr[i];
if (sys_region->ar.start > sys_region->ar.end)
goto out;
ranges[i].start = sys_region->ar.start;
ranges[i].end = sys_region->ar.end;
if (i == 0)
continue;
if (ranges[i - 1].end > ranges[i].start)
goto out;
}
err = damon_set_regions(t, ranges, sysfs_regions->nr, min_sz_region);
out:
kfree(ranges);
return err;
}
static int damon_sysfs_add_target(struct damon_sysfs_target *sys_target,
struct damon_ctx *ctx)
{
struct damon_target *t = damon_new_target();
if (!t)
return -ENOMEM;
damon_add_target(ctx, t);
if (damon_target_has_pid(ctx)) {
t->pid = find_get_pid(sys_target->pid);
if (!t->pid)
/* caller will destroy targets */
return -EINVAL;
}
return damon_sysfs_set_regions(t, sys_target->regions, ctx->min_sz_region);
}
static int damon_sysfs_add_targets(struct damon_ctx *ctx,
struct damon_sysfs_targets *sysfs_targets)
{
int i, err;
/* Multiple physical address space monitoring targets makes no sense */
if (ctx->ops.id == DAMON_OPS_PADDR && sysfs_targets->nr > 1)
return -EINVAL;
for (i = 0; i < sysfs_targets->nr; i++) {
struct damon_sysfs_target *st = sysfs_targets->targets_arr[i];
err = damon_sysfs_add_target(st, ctx);
if (err)
return err;
}
return 0;
}
/*
* damon_sysfs_upd_schemes_stats() - Update schemes stats sysfs files.
* @data: The kobject wrapper that associated to the kdamond thread.
*
* This function reads the schemes stats of specific kdamond and update the
* related values for sysfs files. This function should be called from DAMON
* worker thread,to safely access the DAMON contexts-internal data. Caller
* should also ensure holding ``damon_syfs_lock``, and ->damon_ctx of @data is
* not NULL but a valid pointer, to safely access DAMON sysfs variables.
*/
static int damon_sysfs_upd_schemes_stats(void *data)
{
struct damon_sysfs_kdamond *kdamond = data;
struct damon_ctx *ctx = kdamond->damon_ctx;
damon_sysfs_schemes_update_stats(
kdamond->contexts->contexts_arr[0]->schemes, ctx);
return 0;
}
static inline bool damon_sysfs_kdamond_running(
struct damon_sysfs_kdamond *kdamond)
{
return kdamond->damon_ctx &&
damon_is_running(kdamond->damon_ctx);
}
static int damon_sysfs_apply_inputs(struct damon_ctx *ctx,
struct damon_sysfs_context *sys_ctx)
{
int err;
err = damon_select_ops(ctx, sys_ctx->ops_id);
if (err)
return err;
ctx->addr_unit = sys_ctx->addr_unit;
/* addr_unit is respected by only DAMON_OPS_PADDR */
if (sys_ctx->ops_id == DAMON_OPS_PADDR)
ctx->min_sz_region = max(
DAMON_MIN_REGION / sys_ctx->addr_unit, 1);
err = damon_sysfs_set_attrs(ctx, sys_ctx->attrs);
if (err)
return err;
err = damon_sysfs_add_targets(ctx, sys_ctx->targets);
if (err)
return err;
return damon_sysfs_add_schemes(ctx, sys_ctx->schemes);
}
static struct damon_ctx *damon_sysfs_build_ctx(
struct damon_sysfs_context *sys_ctx);
/*
* damon_sysfs_commit_input() - Commit user inputs to a running kdamond.
* @kdamond: The kobject wrapper for the associated kdamond.
*
* Returns error if the sysfs input is wrong.
*/
static int damon_sysfs_commit_input(void *data)
{
struct damon_sysfs_kdamond *kdamond = data;
struct damon_ctx *param_ctx, *test_ctx;
int err;
if (!damon_sysfs_kdamond_running(kdamond))
return -EINVAL;
/* TODO: Support multiple contexts per kdamond */
if (kdamond->contexts->nr != 1)
return -EINVAL;
param_ctx = damon_sysfs_build_ctx(kdamond->contexts->contexts_arr[0]);
if (IS_ERR(param_ctx))
return PTR_ERR(param_ctx);
test_ctx = damon_new_ctx();
err = damon_commit_ctx(test_ctx, param_ctx);
if (err) {
damon_destroy_ctx(test_ctx);
goto out;
}
err = damon_commit_ctx(kdamond->damon_ctx, param_ctx);
out:
damon_destroy_ctx(param_ctx);
return err;
}
static int damon_sysfs_commit_schemes_quota_goals(void *data)
{
struct damon_sysfs_kdamond *sysfs_kdamond = data;
struct damon_ctx *ctx;
struct damon_sysfs_context *sysfs_ctx;
if (!damon_sysfs_kdamond_running(sysfs_kdamond))
return -EINVAL;
/* TODO: Support multiple contexts per kdamond */
if (sysfs_kdamond->contexts->nr != 1)
return -EINVAL;
ctx = sysfs_kdamond->damon_ctx;
sysfs_ctx = sysfs_kdamond->contexts->contexts_arr[0];
return damos_sysfs_set_quota_scores(sysfs_ctx->schemes, ctx);
}
/*
* damon_sysfs_upd_schemes_effective_quotas() - Update schemes effective quotas
* sysfs files.
* @data: The kobject wrapper that associated to the kdamond thread.
*
* This function reads the schemes' effective quotas of specific kdamond and
* update the related values for sysfs files. This function should be called
* from DAMON callbacks while holding ``damon_syfs_lock``, to safely access the
* DAMON contexts-internal data and DAMON sysfs variables.
*/
static int damon_sysfs_upd_schemes_effective_quotas(void *data)
{
struct damon_sysfs_kdamond *kdamond = data;
struct damon_ctx *ctx = kdamond->damon_ctx;
damos_sysfs_update_effective_quotas(
kdamond->contexts->contexts_arr[0]->schemes, ctx);
return 0;
}
static int damon_sysfs_upd_tuned_intervals(void *data)
{
struct damon_sysfs_kdamond *kdamond = data;
struct damon_ctx *ctx = kdamond->damon_ctx;
kdamond->contexts->contexts_arr[0]->attrs->intervals->sample_us =
ctx->attrs.sample_interval;
kdamond->contexts->contexts_arr[0]->attrs->intervals->aggr_us =
ctx->attrs.aggr_interval;
return 0;
}
static struct damon_ctx *damon_sysfs_build_ctx(
struct damon_sysfs_context *sys_ctx)
{
struct damon_ctx *ctx = damon_new_ctx();
int err;
if (!ctx)
return ERR_PTR(-ENOMEM);
err = damon_sysfs_apply_inputs(ctx, sys_ctx);
if (err) {
damon_destroy_ctx(ctx);
return ERR_PTR(err);
}
return ctx;
}
static int damon_sysfs_repeat_call_fn(void *data)
{
struct damon_sysfs_kdamond *sysfs_kdamond = data;
static unsigned long next_update_jiffies;
if (!sysfs_kdamond->refresh_ms)
return 0;
if (time_before(jiffies, next_update_jiffies))
return 0;
next_update_jiffies = jiffies +
msecs_to_jiffies(sysfs_kdamond->refresh_ms);
if (!mutex_trylock(&damon_sysfs_lock))
return 0;
damon_sysfs_upd_tuned_intervals(sysfs_kdamond);
damon_sysfs_upd_schemes_stats(sysfs_kdamond);
damon_sysfs_upd_schemes_effective_quotas(sysfs_kdamond);
mutex_unlock(&damon_sysfs_lock);
return 0;
}
static int damon_sysfs_turn_damon_on(struct damon_sysfs_kdamond *kdamond)
{
struct damon_ctx *ctx;
struct damon_call_control *repeat_call_control;
int err;
if (damon_sysfs_kdamond_running(kdamond))
return -EBUSY;
/* TODO: support multiple contexts per kdamond */
if (kdamond->contexts->nr != 1)
return -EINVAL;
if (kdamond->damon_ctx)
damon_destroy_ctx(kdamond->damon_ctx);
kdamond->damon_ctx = NULL;
repeat_call_control = kmalloc(sizeof(*repeat_call_control),
GFP_KERNEL);
if (!repeat_call_control)
return -ENOMEM;
ctx = damon_sysfs_build_ctx(kdamond->contexts->contexts_arr[0]);
if (IS_ERR(ctx)) {
kfree(repeat_call_control);
return PTR_ERR(ctx);
}
err = damon_start(&ctx, 1, false);
if (err) {
kfree(repeat_call_control);
damon_destroy_ctx(ctx);
return err;
}
kdamond->damon_ctx = ctx;
repeat_call_control->fn = damon_sysfs_repeat_call_fn;
repeat_call_control->data = kdamond;
repeat_call_control->repeat = true;
repeat_call_control->dealloc_on_cancel = true;
damon_call(ctx, repeat_call_control);
return err;
}
static int damon_sysfs_turn_damon_off(struct damon_sysfs_kdamond *kdamond)
{
if (!kdamond->damon_ctx)
return -EINVAL;
return damon_stop(&kdamond->damon_ctx, 1);
/*
* To allow users show final monitoring results of already turned-off
* DAMON, we free kdamond->damon_ctx in next
* damon_sysfs_turn_damon_on(), or kdamonds_nr_store()
*/
}
static int damon_sysfs_damon_call(int (*fn)(void *data),
struct damon_sysfs_kdamond *kdamond)
{
struct damon_call_control call_control = {};
int err;
if (!kdamond->damon_ctx)
return -EINVAL;
call_control.fn = fn;
call_control.data = kdamond;
err = damon_call(kdamond->damon_ctx, &call_control);
return err ? err : call_control.return_code;
}
struct damon_sysfs_schemes_walk_data {
struct damon_sysfs_kdamond *sysfs_kdamond;
bool total_bytes_only;
};
/* populate the region directory */
static void damon_sysfs_schemes_tried_regions_upd_one(void *data, struct damon_ctx *ctx,
struct damon_target *t, struct damon_region *r,
struct damos *s, unsigned long sz_filter_passed)
{
struct damon_sysfs_schemes_walk_data *walk_data = data;
struct damon_sysfs_kdamond *sysfs_kdamond = walk_data->sysfs_kdamond;
damos_sysfs_populate_region_dir(
sysfs_kdamond->contexts->contexts_arr[0]->schemes,
ctx, t, r, s, walk_data->total_bytes_only,
sz_filter_passed);
}
static int damon_sysfs_update_schemes_tried_regions(
struct damon_sysfs_kdamond *sysfs_kdamond, bool total_bytes_only)
{
struct damon_sysfs_schemes_walk_data walk_data = {
.sysfs_kdamond = sysfs_kdamond,
.total_bytes_only = total_bytes_only,
};
struct damos_walk_control control = {
.walk_fn = damon_sysfs_schemes_tried_regions_upd_one,
.data = &walk_data,
};
struct damon_ctx *ctx = sysfs_kdamond->damon_ctx;
if (!ctx)
return -EINVAL;
damon_sysfs_schemes_clear_regions(
sysfs_kdamond->contexts->contexts_arr[0]->schemes);
return damos_walk(ctx, &control);
}
/*
* damon_sysfs_handle_cmd() - Handle a command for a specific kdamond.
* @cmd: The command to handle.
* @kdamond: The kobject wrapper for the associated kdamond.
*
* This function handles a DAMON sysfs command for a kdamond.
*
* Return: 0 on success, negative error code otherwise.
*/
static int damon_sysfs_handle_cmd(enum damon_sysfs_cmd cmd,
struct damon_sysfs_kdamond *kdamond)
{
switch (cmd) {
case DAMON_SYSFS_CMD_ON:
return damon_sysfs_turn_damon_on(kdamond);
case DAMON_SYSFS_CMD_OFF:
return damon_sysfs_turn_damon_off(kdamond);
case DAMON_SYSFS_CMD_COMMIT:
return damon_sysfs_damon_call(
damon_sysfs_commit_input, kdamond);
case DAMON_SYSFS_CMD_COMMIT_SCHEMES_QUOTA_GOALS:
return damon_sysfs_damon_call(
damon_sysfs_commit_schemes_quota_goals,
kdamond);
case DAMON_SYSFS_CMD_UPDATE_SCHEMES_STATS:
return damon_sysfs_damon_call(
damon_sysfs_upd_schemes_stats, kdamond);
case DAMON_SYSFS_CMD_UPDATE_SCHEMES_TRIED_BYTES:
return damon_sysfs_update_schemes_tried_regions(kdamond, true);
case DAMON_SYSFS_CMD_UPDATE_SCHEMES_TRIED_REGIONS:
return damon_sysfs_update_schemes_tried_regions(kdamond, false);
case DAMON_SYSFS_CMD_CLEAR_SCHEMES_TRIED_REGIONS:
return damon_sysfs_schemes_clear_regions(
kdamond->contexts->contexts_arr[0]->schemes);
case DAMON_SYSFS_CMD_UPDATE_SCHEMES_EFFECTIVE_QUOTAS:
return damon_sysfs_damon_call(
damon_sysfs_upd_schemes_effective_quotas,
kdamond);
case DAMON_SYSFS_CMD_UPDATE_TUNED_INTERVALS:
return damon_sysfs_damon_call(
damon_sysfs_upd_tuned_intervals, kdamond);
default:
return -EINVAL;
}
}
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct damon_sysfs_kdamond *kdamond = container_of(kobj,
struct damon_sysfs_kdamond, kobj);
enum damon_sysfs_cmd cmd;
ssize_t ret = -EINVAL;
if (!mutex_trylock(&damon_sysfs_lock))
return -EBUSY;
for (cmd = 0; cmd < NR_DAMON_SYSFS_CMDS; cmd++) {
if (sysfs_streq(buf, damon_sysfs_cmd_strs[cmd])) {
ret = damon_sysfs_handle_cmd(cmd, kdamond);
break;
}
}
mutex_unlock(&damon_sysfs_lock);
if (!ret)
ret = count;
return ret;
}
static ssize_t pid_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_kdamond *kdamond = container_of(kobj,
struct damon_sysfs_kdamond, kobj);
struct damon_ctx *ctx;
int pid = -1;
if (!mutex_trylock(&damon_sysfs_lock))
return -EBUSY;
ctx = kdamond->damon_ctx;
if (!ctx)
goto out;
mutex_lock(&ctx->kdamond_lock);
if (ctx->kdamond)
pid = ctx->kdamond->pid;
mutex_unlock(&ctx->kdamond_lock);
out:
mutex_unlock(&damon_sysfs_lock);
return sysfs_emit(buf, "%d\n", pid);
}
static ssize_t refresh_ms_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_kdamond *kdamond = container_of(kobj,
struct damon_sysfs_kdamond, kobj);
return sysfs_emit(buf, "%u\n", kdamond->refresh_ms);
}
static ssize_t refresh_ms_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_kdamond *kdamond = container_of(kobj,
struct damon_sysfs_kdamond, kobj);
unsigned int nr;
int err = kstrtouint(buf, 0, &nr);
if (err)
return err;
kdamond->refresh_ms = nr;
return count;
}
static void damon_sysfs_kdamond_release(struct kobject *kobj)
{
struct damon_sysfs_kdamond *kdamond = container_of(kobj,
struct damon_sysfs_kdamond, kobj);
if (kdamond->damon_ctx)
damon_destroy_ctx(kdamond->damon_ctx);
kfree(kdamond);
}
static struct kobj_attribute damon_sysfs_kdamond_state_attr =
__ATTR_RW_MODE(state, 0600);
static struct kobj_attribute damon_sysfs_kdamond_pid_attr =
__ATTR_RO_MODE(pid, 0400);
static struct kobj_attribute damon_sysfs_kdamond_refresh_ms_attr =
__ATTR_RW_MODE(refresh_ms, 0600);
static struct attribute *damon_sysfs_kdamond_attrs[] = {
&damon_sysfs_kdamond_state_attr.attr,
&damon_sysfs_kdamond_pid_attr.attr,
&damon_sysfs_kdamond_refresh_ms_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_kdamond);
static const struct kobj_type damon_sysfs_kdamond_ktype = {
.release = damon_sysfs_kdamond_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_kdamond_groups,
};
/*
* kdamonds directory
*/
struct damon_sysfs_kdamonds {
struct kobject kobj;
struct damon_sysfs_kdamond **kdamonds_arr;
int nr;
};
static struct damon_sysfs_kdamonds *damon_sysfs_kdamonds_alloc(void)
{
return kzalloc(sizeof(struct damon_sysfs_kdamonds), GFP_KERNEL);
}
static void damon_sysfs_kdamonds_rm_dirs(struct damon_sysfs_kdamonds *kdamonds)
{
struct damon_sysfs_kdamond **kdamonds_arr = kdamonds->kdamonds_arr;
int i;
for (i = 0; i < kdamonds->nr; i++) {
damon_sysfs_kdamond_rm_dirs(kdamonds_arr[i]);
kobject_put(&kdamonds_arr[i]->kobj);
}
kdamonds->nr = 0;
kfree(kdamonds_arr);
kdamonds->kdamonds_arr = NULL;
}
static bool damon_sysfs_kdamonds_busy(struct damon_sysfs_kdamond **kdamonds,
int nr_kdamonds)
{
int i;
for (i = 0; i < nr_kdamonds; i++) {
if (damon_sysfs_kdamond_running(kdamonds[i]))
return true;
}
return false;
}
static int damon_sysfs_kdamonds_add_dirs(struct damon_sysfs_kdamonds *kdamonds,
int nr_kdamonds)
{
struct damon_sysfs_kdamond **kdamonds_arr, *kdamond;
int err, i;
if (damon_sysfs_kdamonds_busy(kdamonds->kdamonds_arr, kdamonds->nr))
return -EBUSY;
damon_sysfs_kdamonds_rm_dirs(kdamonds);
if (!nr_kdamonds)
return 0;
kdamonds_arr = kmalloc_array(nr_kdamonds, sizeof(*kdamonds_arr),
GFP_KERNEL | __GFP_NOWARN);
if (!kdamonds_arr)
return -ENOMEM;
kdamonds->kdamonds_arr = kdamonds_arr;
for (i = 0; i < nr_kdamonds; i++) {
kdamond = damon_sysfs_kdamond_alloc();
if (!kdamond) {
damon_sysfs_kdamonds_rm_dirs(kdamonds);
return -ENOMEM;
}
err = kobject_init_and_add(&kdamond->kobj,
&damon_sysfs_kdamond_ktype, &kdamonds->kobj,
"%d", i);
if (err)
goto out;
err = damon_sysfs_kdamond_add_dirs(kdamond);
if (err)
goto out;
kdamonds_arr[i] = kdamond;
kdamonds->nr++;
}
return 0;
out:
damon_sysfs_kdamonds_rm_dirs(kdamonds);
kobject_put(&kdamond->kobj);
return err;
}
static ssize_t nr_kdamonds_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct damon_sysfs_kdamonds *kdamonds = container_of(kobj,
struct damon_sysfs_kdamonds, kobj);
return sysfs_emit(buf, "%d\n", kdamonds->nr);
}
static ssize_t nr_kdamonds_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct damon_sysfs_kdamonds *kdamonds;
int nr, err;
err = kstrtoint(buf, 0, &nr);
if (err)
return err;
if (nr < 0)
return -EINVAL;
kdamonds = container_of(kobj, struct damon_sysfs_kdamonds, kobj);
if (!mutex_trylock(&damon_sysfs_lock))
return -EBUSY;
err = damon_sysfs_kdamonds_add_dirs(kdamonds, nr);
mutex_unlock(&damon_sysfs_lock);
if (err)
return err;
return count;
}
static void damon_sysfs_kdamonds_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_kdamonds, kobj));
}
static struct kobj_attribute damon_sysfs_kdamonds_nr_attr =
__ATTR_RW_MODE(nr_kdamonds, 0600);
static struct attribute *damon_sysfs_kdamonds_attrs[] = {
&damon_sysfs_kdamonds_nr_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_kdamonds);
static const struct kobj_type damon_sysfs_kdamonds_ktype = {
.release = damon_sysfs_kdamonds_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_kdamonds_groups,
};
/*
* damon user interface directory
*/
struct damon_sysfs_ui_dir {
struct kobject kobj;
struct damon_sysfs_kdamonds *kdamonds;
};
static struct damon_sysfs_ui_dir *damon_sysfs_ui_dir_alloc(void)
{
return kzalloc(sizeof(struct damon_sysfs_ui_dir), GFP_KERNEL);
}
static int damon_sysfs_ui_dir_add_dirs(struct damon_sysfs_ui_dir *ui_dir)
{
struct damon_sysfs_kdamonds *kdamonds;
int err;
kdamonds = damon_sysfs_kdamonds_alloc();
if (!kdamonds)
return -ENOMEM;
err = kobject_init_and_add(&kdamonds->kobj,
&damon_sysfs_kdamonds_ktype, &ui_dir->kobj,
"kdamonds");
if (err) {
kobject_put(&kdamonds->kobj);
return err;
}
ui_dir->kdamonds = kdamonds;
return err;
}
static void damon_sysfs_ui_dir_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_ui_dir, kobj));
}
static struct attribute *damon_sysfs_ui_dir_attrs[] = {
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_ui_dir);
static const struct kobj_type damon_sysfs_ui_dir_ktype = {
.release = damon_sysfs_ui_dir_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_ui_dir_groups,
};
static int __init damon_sysfs_init(void)
{
struct kobject *damon_sysfs_root;
struct damon_sysfs_ui_dir *admin;
int err;
damon_sysfs_root = kobject_create_and_add("damon", mm_kobj);
if (!damon_sysfs_root)
return -ENOMEM;
admin = damon_sysfs_ui_dir_alloc();
if (!admin) {
kobject_put(damon_sysfs_root);
return -ENOMEM;
}
err = kobject_init_and_add(&admin->kobj, &damon_sysfs_ui_dir_ktype,
damon_sysfs_root, "admin");
if (err)
goto out;
err = damon_sysfs_ui_dir_add_dirs(admin);
if (err)
goto out;
return 0;
out:
kobject_put(&admin->kobj);
kobject_put(damon_sysfs_root);
return err;
}
subsys_initcall(damon_sysfs_init);
#include "tests/sysfs-kunit.h"