Add per-device attributes under /sys/kernel/mm/ghost_swap/ghost_/: path (RO) — dentry basename max (RW) — current max pages; write to trigger swap_ghost_extend_max() The ghost_kobj field ties each ghost device to its sysfs directory, created at swapon and removed at swapoff. Signed-off-by: Baoquan He --- include/linux/swap.h | 1 + mm/swapfile.c | 111 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/include/linux/swap.h b/include/linux/swap.h index b87688b7d4ba..d9ae2a9e33cc 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -283,6 +283,7 @@ struct swap_info_struct { struct xarray redirect_xa; /* ghost offset → phys swp_entry_t */ struct vm_struct *cluster_vm; /* ghost: sparse vm_area for cluster_info */ char *ghost_name; /* ghost: dentry name for sysfs */ + struct kobject *ghost_kobj; /* ghost: sysfs per-device directory */ #endif }; diff --git a/mm/swapfile.c b/mm/swapfile.c index 8bf336678887..ede10539156a 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include #include #include @@ -60,6 +62,8 @@ static void move_cluster(struct swap_info_struct *si, #define GHOST_MAX_CLUSTER_BYTES (64UL * 1024 * 1024) static int swap_ghost_extend_max(struct swap_info_struct *si, unsigned int new_maxpages); +static int ghost_sysfs_register(struct swap_info_struct *si); +static void ghost_sysfs_unregister(struct swap_info_struct *si); #endif /* @@ -3250,6 +3254,7 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile) p->swap_file = NULL; maxpages = p->max; if (p->flags & SWP_GHOST) { + ghost_sysfs_unregister(p); p->max = 0; cluster_info = NULL; } else { @@ -4010,6 +4015,13 @@ SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags) (si->flags & SWP_AREA_DISCARD) ? "s" : "", (si->flags & SWP_PAGE_DISCARD) ? "c" : ""); + if (si->flags & SWP_GHOST) { + error = ghost_sysfs_register(si); + if (error) + pr_warn("swapon: ghost sysfs register failed: %d\n", + error); + } + mutex_unlock(&swapon_mutex); atomic_inc(&proc_poll_event); wake_up_interruptible(&proc_poll_wait); @@ -4357,6 +4369,105 @@ static int swap_ghost_extend_max(struct swap_info_struct *si, spin_unlock(&swap_lock); return 0; } + +/* ─── Ghost swap sysfs interface ─── */ + +static struct kobject *ghost_swap_kobj; + +static struct swap_info_struct *ghost_kobj_to_si(struct kobject *kobj) +{ + int type; + + for (type = 0; type < MAX_SWAPFILES; type++) { + struct swap_info_struct *si = READ_ONCE(swap_info[type]); + + if (si && si->ghost_kobj == kobj && (si->flags & SWP_USED)) + return si; + } + return NULL; +} + +#define GHOST_ATTR_RO(_n) \ + static ssize_t _n##_show(struct kobject *k, struct kobj_attribute *a, char *b) +#define GHOST_ATTR_WO(_n) \ + static ssize_t _n##_store(struct kobject *k, struct kobj_attribute *a, \ + const char *b, size_t n) + +GHOST_ATTR_RO(path) +{ + struct swap_info_struct *si = ghost_kobj_to_si(k); + + if (!si || !si->ghost_name) + return -ENODEV; + return sysfs_emit(b, "%s\n", si->ghost_name); +} + +GHOST_ATTR_RO(max) +{ + struct swap_info_struct *si = ghost_kobj_to_si(k); + + if (!si) + return -ENODEV; + return sysfs_emit(b, "%u\n", si->max); +} + +GHOST_ATTR_WO(max) +{ + struct swap_info_struct *si = ghost_kobj_to_si(k); + unsigned long val; + int ret; + + if (!si) + return -ENODEV; + ret = kstrtoul(b, 10, &val); + if (ret) + return ret; + if (mutex_lock_interruptible(&swapon_mutex)) + return -ERESTARTSYS; + ret = swap_ghost_extend_max(si, (unsigned int)val); + mutex_unlock(&swapon_mutex); + return ret ? ret : n; +} + +static struct kobj_attribute ghost_attr_path = __ATTR_RO(path); +static struct kobj_attribute ghost_attr_max = __ATTR_RW(max); +static struct attribute *ghost_attrs[] = { + &ghost_attr_path.attr, + &ghost_attr_max.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ghost); + +static int ghost_sysfs_register(struct swap_info_struct *si) +{ + char name[32]; + int ret; + + if (!ghost_swap_kobj) { + ghost_swap_kobj = kobject_create_and_add("ghost_swap", mm_kobj); + if (!ghost_swap_kobj) + return -ENOMEM; + } + snprintf(name, sizeof(name), "ghost_%d", si->type); + si->ghost_kobj = kobject_create_and_add(name, ghost_swap_kobj); + if (!si->ghost_kobj) + return -ENOMEM; + ret = sysfs_create_groups(si->ghost_kobj, ghost_groups); + if (ret) { + kobject_put(si->ghost_kobj); + si->ghost_kobj = NULL; + } + return ret; +} + +static void ghost_sysfs_unregister(struct swap_info_struct *si) +{ + if (!si->ghost_kobj) + return; + sysfs_remove_groups(si->ghost_kobj, ghost_groups); + kobject_put(si->ghost_kobj); + si->ghost_kobj = NULL; +} #endif /* CONFIG_ZSWAP */ static int __init swapfile_init(void) -- 2.54.0