From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from out-170.mta0.migadu.com (out-170.mta0.migadu.com [91.218.175.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 66875310777 for ; Tue, 7 Jul 2026 08:27:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.170 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783412874; cv=none; b=aaq380AsORYqzBZ/0myYjJH9VaNgFSZLo127rUNfcszcNnniAoli1Rrnke+x5Y15QXud6NGU4OYKNZDYE9+qYLMBB+KkU0QpwlhiGcre2gd1BehnGNSQlnt6CVR8/dNeidMLg2dYve0lf4o4MYHzmgwAPmzXZ1VBfL9gjEBdEGk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783412874; c=relaxed/simple; bh=9IDbl5ivpCL8QH2y1YHryIbG0wJxVw0uakVV6vQo/OQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type:Content-type; b=OOb0WM27Pb04VG0h6Mal12j5P5zYzxf741n4HG6O9xC3tetjoBb4ZRlmtGf9x0wXAVFxj/5e4NY6JUvak98KcBbYlBNCDIYgYkyBI2JVSqtza2lPsvjTlT1ke6HscIB0SwoEbuCtRIsFtENV8a2h71POzHJijD0eAs+myYsIEYM= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=sMajsTlQ; arc=none smtp.client-ip=91.218.175.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="sMajsTlQ" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1783412870; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-type:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3Ru8WYn++oZvGMNoRs83TkPNucu4BwEx3PldacFtQlM=; b=sMajsTlQ6ayjpEYZPr+c5Aj8FZXE7p4tuArgzE3ffPxvDivEqnRksJ+z9zZJrtQ+NBr/7l ETGTcNULDqxG7qKa3pl0K9iyMDqoarCSJnGxtKkNQdcO/wpXaBBJRnaZaOmPxDAYLHI8ym 7QZhmJvYdnmFlRhN5vxwZVN24ZWoF14= From: Baoquan He To: linux-mm@kvack.org Cc: akpm@linux-foundation.org, chrisl@kernel.org, kasong@tencent.com, shikemeng@huaweicloud.com, nphamcs@gmail.com, baohua@kernel.org, youngjun.park@lge.com, hannes@cmpxchg.org, yosry@kernel.org, chengming.zhou@linux.dev, linux-kernel@vger.kernel.org, Baoquan He Subject: [RFC PATCH 10/10] mm/swapfile: add sysfs interface for ghost swap extension Date: Tue, 7 Jul 2026 16:26:12 +0800 Message-ID: <20260707082614.95030-11-baoquan.he@linux.dev> In-Reply-To: <20260707082614.95030-1-baoquan.he@linux.dev> References: <20260707082614.95030-1-baoquan.he@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-type: text/plain Content-Transfer-Encoding: 8bit X-Migadu-Flow: FLOW_OUT 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