Linux Documentation
 help / color / mirror / Atom feed
* [RFC PATCH 04/24] pidfd: create taskless spawn builders
From: Li Chen @ 2026-07-16 14:31 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Kees Cook, Gabriel Krisman Bertazi, Josh Triplett, Mateusz Guzik,
	Andy Lutomirski, John Ericson, Jonathan Corbet, Shuah Khan,
	Arnd Bergmann, Oleg Nesterov, Andrew Morton, Paul Moore,
	Eric Paris, Mickaël Salaün, Günther Noack,
	Alexander Viro, Jan Kara, linux-api, linux-fsdevel, linux-kernel,
	linux-kselftest, linux-doc, audit, linux-security-module,
	linux-arch, linux-mm, Li Chen
In-Reply-To: <cover.1784204592.git.me@linux.beauty>

Add a pidfs-backed constructor for taskless spawn-builder files. The inode
owns the builder state, so procfd reopens and bind mounts retain it without
creating per-file lifetime rules.

The constructor allocates no task, struct pid, numeric PID, or
process-count charge. Until a later run operation publishes a pid, ordinary
pidfd operations resolve the file as -ESRCH. Preserve that error through
setns() and pidfd_send_signal() instead of translating a valid future pidfd
into an unrelated descriptor error. Assert that PIDFD_EMPTY does not
overlap a valid open flag, so future collisions fail the build instead of
silently aliasing builder creation.

Keep the public pidfd_open() entry point disabled until the complete spawn
state machine is wired later in the series.

Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Li Chen <me@linux.beauty>
---
 MAINTAINERS                 |  2 +
 fs/Makefile                 |  2 +-
 fs/pidfd_spawn.c            | 81 +++++++++++++++++++++++++++++++++++++
 fs/pidfs.c                  |  5 ++-
 include/linux/pidfd_spawn.h |  9 +++++
 kernel/nsproxy.c            | 11 +++--
 kernel/signal.c             |  2 +-
 7 files changed, 106 insertions(+), 6 deletions(-)
 create mode 100644 fs/pidfd_spawn.c
 create mode 100644 include/linux/pidfd_spawn.h

diff --git a/MAINTAINERS b/MAINTAINERS
index b63bc1ee0171f..8d7f94f09f414 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21287,6 +21287,8 @@ M:	Christian Brauner <christian@brauner.io>
 L:	linux-kernel@vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux.git
+F:	fs/pidfd_spawn.c
+F:	include/linux/pidfd_spawn.h
 F:	include/uapi/linux/pidfd_spawn.h
 F:	samples/pidfd/
 F:	tools/include/uapi/linux/pidfd_spawn.h
diff --git a/fs/Makefile b/fs/Makefile
index aa847be93bc6f..f24beb7c9b7dc 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -8,7 +8,7 @@
 
 
 obj-y :=	open.o read_write.o file_table.o super.o \
-		char_dev.o stat.o exec.o pipe.o namei.o fcntl.o \
+		char_dev.o stat.o exec.o pidfd_spawn.o pipe.o namei.o fcntl.o \
 		ioctl.o readdir.o select.o dcache.o inode.o \
 		attr.o bad_inode.o file.o filesystems.o namespace.o \
 		seq_file.o xattr.o libfs.o fs-writeback.o \
diff --git a/fs/pidfd_spawn.c b/fs/pidfd_spawn.c
new file mode 100644
index 0000000000000..ff2b0cb6365a5
--- /dev/null
+++ b/fs/pidfd_spawn.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * pidfd-backed process spawn builders
+ */
+
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/pid.h>
+#include <linux/pidfd_spawn.h>
+#include <linux/pidfs.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <uapi/linux/pidfd.h>
+
+struct pidfd_spawn_state {
+	refcount_t count;
+	struct pid *pid;
+};
+
+static void pidfd_spawn_state_put(struct pidfd_spawn_state *state);
+
+static void pidfd_spawn_free_state(struct pidfd_spawn_state *state)
+{
+	put_pid(state->pid);
+	kfree(state);
+}
+
+static void pidfd_spawn_state_put(struct pidfd_spawn_state *state)
+{
+	if (refcount_dec_and_test(&state->count))
+		pidfd_spawn_free_state(state);
+}
+
+static void pidfd_spawn_state_release(void *data)
+{
+	pidfd_spawn_state_put(data);
+}
+
+static struct pid *pidfd_spawn_file_pid(void *data)
+{
+	struct pidfd_spawn_state *state = data;
+	struct pid *pid;
+
+	pid = READ_ONCE(state->pid);
+	return pid ? pid : ERR_PTR(-ESRCH);
+}
+
+static const struct pidfs_future_file_ops pidfd_spawn_future_ops = {
+	.get_pid = pidfd_spawn_file_pid,
+	.release = pidfd_spawn_state_release,
+};
+
+int pidfd_empty_open(unsigned int flags)
+{
+	struct pidfd_spawn_state *state;
+	struct file *pidfile;
+	int pidfd;
+
+	state = kzalloc_obj(*state, GFP_KERNEL_ACCOUNT);
+	if (!state)
+		return -ENOMEM;
+
+	refcount_set(&state->count, 1);
+
+	pidfile = pidfs_alloc_future_file("[pidfd_spawn]", state,
+					  &pidfd_spawn_future_ops,
+					  O_RDWR | flags);
+	if (IS_ERR(pidfile)) {
+		pidfd_spawn_state_put(state);
+		return PTR_ERR(pidfile);
+	}
+
+	pidfd = get_unused_fd_flags(O_CLOEXEC);
+	if (pidfd < 0) {
+		fput(pidfile);
+		return pidfd;
+	}
+
+	fd_install(pidfd, pidfile);
+	return pidfd;
+}
diff --git a/fs/pidfs.c b/fs/pidfs.c
index ebd8cc463811b..28464fe274c9e 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -2,6 +2,7 @@
 #include <linux/anon_inodes.h>
 #include <linux/compat.h>
 #include <linux/exportfs.h>
+#include <linux/fcntl.h>
 #include <linux/file.h>
 #include <linux/fs.h>
 #include <linux/cgroup.h>
@@ -1261,7 +1262,9 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
 	 * other or with uapi pidfd flags.
 	 */
 	BUILD_BUG_ON(hweight32(PIDFD_THREAD | PIDFD_NONBLOCK |
-				PIDFD_STALE | PIDFD_AUTOKILL) != 4);
+				PIDFD_EMPTY | PIDFD_STALE |
+				PIDFD_AUTOKILL) != 5);
+	BUILD_BUG_ON(PIDFD_EMPTY & VALID_OPEN_FLAGS);
 
 	ret = path_from_stashed(&pid->stashed, pidfs_mnt, get_pid(pid), &path);
 	if (ret < 0)
diff --git a/include/linux/pidfd_spawn.h b/include/linux/pidfd_spawn.h
new file mode 100644
index 0000000000000..8df168cf0c2f9
--- /dev/null
+++ b/include/linux/pidfd_spawn.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_PIDFD_SPAWN_H
+#define _LINUX_PIDFD_SPAWN_H
+
+#include <uapi/linux/pidfd_spawn.h>
+
+int pidfd_empty_open(unsigned int flags);
+
+#endif /* _LINUX_PIDFD_SPAWN_H */
diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
index d9d3d5973bf52..20befb6185e2d 100644
--- a/kernel/nsproxy.c
+++ b/kernel/nsproxy.c
@@ -581,10 +581,15 @@ SYSCALL_DEFINE2(setns, int, fd, int, flags)
 		if (flags && (ns->ns_type != flags))
 			err = -EINVAL;
 		flags = ns->ns_type;
-	} else if (!IS_ERR(pidfd_pid(fd_file(f)))) {
-		err = check_setns_flags(flags);
 	} else {
-		err = -EINVAL;
+		struct pid *pid = pidfd_pid(fd_file(f));
+
+		if (!IS_ERR(pid))
+			err = check_setns_flags(flags);
+		else if (PTR_ERR(pid) == -ESRCH)
+			err = -ESRCH;
+		else
+			err = -EINVAL;
 	}
 	if (err)
 		goto out;
diff --git a/kernel/signal.c b/kernel/signal.c
index fdee0b012a117..4c7d95981263e 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -3996,7 +3996,7 @@ static struct pid *pidfd_to_pid(const struct file *file)
 	struct pid *pid;
 
 	pid = pidfd_pid(file);
-	if (!IS_ERR(pid))
+	if (!IS_ERR(pid) || PTR_ERR(pid) != -EBADF)
 		return pid;
 
 	return tgid_pidfd_to_pid(file);
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH 03/24] pidfs: add taskless future pidfd inodes
From: Li Chen @ 2026-07-16 14:31 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Kees Cook, Gabriel Krisman Bertazi, Josh Triplett, Mateusz Guzik,
	Andy Lutomirski, John Ericson, Jonathan Corbet, Shuah Khan,
	Arnd Bergmann, Oleg Nesterov, Andrew Morton, Paul Moore,
	Eric Paris, Mickaël Salaün, Günther Noack,
	Alexander Viro, Jan Kara, linux-api, linux-fsdevel, linux-kernel,
	linux-kselftest, linux-doc, audit, linux-security-module,
	linux-arch, linux-mm, Li Chen
In-Reply-To: <cover.1784204592.git.me@linux.beauty>

Teach pidfs inodes to represent either a struct pid or a typed future
object. Use the ordinary pidfs file operations for both kinds and
resolve a future object through its callback.

Allocate a stable inode identity without allocating a task or struct
pid. Ordinary pidfd operations return -ESRCH until the future producer
publishes a process, while inode-only GETVERSION remains available.

Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Li Chen <me@linux.beauty>
---
 fs/pidfs.c            | 254 ++++++++++++++++++++++++++++++++++++++----
 include/linux/pid.h   |  10 ++
 include/linux/pidfs.h |  21 ++++
 3 files changed, 262 insertions(+), 23 deletions(-)

diff --git a/fs/pidfs.c b/fs/pidfs.c
index c55f46c32801d..ebd8cc463811b 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -25,6 +25,7 @@
 #include <net/net_namespace.h>
 #include <linux/coredump.h>
 #include <linux/rhashtable.h>
+#include <linux/security.h>
 #include <linux/llist.h>
 #include <linux/xattr.h>
 #include <linux/cookie.h>
@@ -35,6 +36,8 @@
 #define PIDFS_PID_DEAD ERR_PTR(-ESRCH)
 
 static struct kmem_cache *pidfs_attr_cachep __ro_after_init;
+static const struct file_operations pidfs_file_operations;
+static struct vfsmount *pidfs_mnt __ro_after_init;
 
 static struct path pidfs_root_path = {};
 
@@ -106,6 +109,64 @@ struct pidfs_attr {
 	};
 };
 
+struct pidfs_future_file {
+	struct pidfs_node node;
+	void *data;
+	const struct pidfs_future_file_ops *ops;
+	struct pidfs_attr *attr;
+	u64 ino;
+};
+
+static struct pidfs_node *pidfs_inode_node(const struct inode *inode)
+{
+	if (!pidfs_mnt || inode->i_sb != pidfs_mnt->mnt_sb ||
+	    inode->i_fop != &pidfs_file_operations)
+		return NULL;
+	return inode->i_private;
+}
+
+static struct pidfs_future_file *
+pidfs_future_file(const struct inode *inode)
+{
+	struct pidfs_node *node = pidfs_inode_node(inode);
+
+	if (!node || node->type != PIDFS_NODE_FUTURE)
+		return NULL;
+	return container_of(node, struct pidfs_future_file, node);
+}
+
+void *pidfs_future_file_data(const struct file *file,
+			     const struct pidfs_future_file_ops *ops)
+{
+	struct pidfs_future_file *future;
+
+	if (file->f_op != &pidfs_file_operations)
+		return NULL;
+	future = pidfs_future_file(file_inode(file));
+	return future && future->ops == ops ? future->data : NULL;
+}
+
+static struct pid *pidfs_inode_pid(const struct inode *inode)
+{
+	struct pidfs_future_file *future;
+	struct pidfs_node *node;
+	struct pid *pid;
+
+	node = pidfs_inode_node(inode);
+	if (!node)
+		return ERR_PTR(-EBADF);
+	if (node->type == PIDFS_NODE_PID)
+		return container_of(node, struct pid, pidfs_node);
+
+	future = pidfs_future_file(inode);
+	if (!future || !future->ops || !future->ops->get_pid)
+		return ERR_PTR(-EBADF);
+	pid = future->ops->get_pid(future->data);
+	if (WARN_ON_ONCE(!pid))
+		return ERR_PTR(-EBADF);
+	return pid;
+}
+
 #if BITS_PER_LONG == 32
 
 DEFINE_SPINLOCK(pidfs_ino_lock);
@@ -166,6 +227,7 @@ static u64 pidfs_alloc_ino(void)
 
 void pidfs_prepare_pid(struct pid *pid)
 {
+	pid->pidfs_node.type = PIDFS_NODE_PID;
 	pid->stashed = NULL;
 	pid->attr = NULL;
 	pid->ino = 0;
@@ -275,7 +337,7 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
 	struct pid_namespace *ns;
 	pid_t nr = -1;
 
-	if (likely(pid_has_task(pid, PIDTYPE_PID))) {
+	if (!IS_ERR(pid) && likely(pid_has_task(pid, PIDTYPE_PID))) {
 		ns = proc_pid_ns(file_inode(m->file)->i_sb);
 		nr = pid_nr_ns(pid, ns);
 	}
@@ -305,11 +367,15 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
  */
 static __poll_t pidfd_poll(struct file *file, struct poll_table_struct *pts)
 {
-	struct pid *pid = pidfd_pid(file);
 	struct task_struct *task;
 	__poll_t poll_flags = 0;
+	struct pid *pid = pidfd_pid(file);
+
+	if (IS_ERR(pid))
+		return PTR_ERR(pid) == -ESRCH ? 0 : EPOLLHUP;
 
 	poll_wait(file, &pid->wait_pidfd, pts);
+
 	/*
 	 * Don't wake waiters if the thread-group leader exited
 	 * prematurely. They either get notified when the last subthread
@@ -376,6 +442,8 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
 
 	BUILD_BUG_ON(sizeof(struct pidfd_info) != PIDFD_INFO_SIZE_VER3);
 
+	if (IS_ERR(pid))
+		return PTR_ERR(pid);
 	if (!uinfo)
 		return -EINVAL;
 	if (usize < PIDFD_INFO_SIZE_VER0)
@@ -532,6 +600,7 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 	struct task_struct *task __free(put_task) = NULL;
 	struct nsproxy *nsp __free(put_nsproxy) = NULL;
 	struct ns_common *ns_common = NULL;
+	struct pid *pid;
 
 	if (!pidfs_ioctl_valid(cmd))
 		return -ENOIOCTLCMD;
@@ -544,11 +613,15 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 		return put_user(file_inode(file)->i_generation, argp);
 	}
 
+	pid = pidfd_pid(file);
+	if (IS_ERR(pid))
+		return PTR_ERR(pid);
+
 	/* Extensible IOCTL that does not open namespace FDs, take a shortcut */
 	if (_IOC_NR(cmd) == _IOC_NR(PIDFD_GET_INFO))
 		return pidfd_info(file, cmd, arg);
 
-	task = get_pid_task(pidfd_pid(file), PIDTYPE_PID);
+	task = get_pid_task(pid, PIDTYPE_PID);
 	if (!task)
 		return -ESRCH;
 
@@ -673,11 +746,14 @@ static long pidfd_compat_ioctl(struct file *file, unsigned int cmd,
 
 static int pidfs_file_release(struct inode *inode, struct file *file)
 {
-	struct pid *pid = inode->i_private;
+	struct pid *pid;
 	struct task_struct *task;
 
 	if (!(file->f_flags & PIDFD_AUTOKILL))
 		return 0;
+	pid = pidfd_pid(file);
+	if (IS_ERR(pid))
+		return 0;
 
 	guard(rcu)();
 	task = pid_task(pid, PIDTYPE_TGID);
@@ -705,9 +781,9 @@ static const struct file_operations pidfs_file_operations = {
 
 struct pid *pidfd_pid(const struct file *file)
 {
-	if (file->f_op != &pidfs_file_operations)
+	if (unlikely(file->f_op != &pidfs_file_operations))
 		return ERR_PTR(-EBADF);
-	return file_inode(file)->i_private;
+	return pidfs_inode_pid(file_inode(file));
 }
 
 /*
@@ -797,8 +873,6 @@ void pidfs_coredump(const struct coredump_params *cprm)
 }
 #endif
 
-static struct vfsmount *pidfs_mnt __ro_after_init;
-
 /*
  * The vfs falls back to simple_setattr() if i_op->setattr() isn't
  * implemented. Let's reject it completely until we have a clean
@@ -820,7 +894,10 @@ static int pidfs_getattr(struct mnt_idmap *idmap, const struct path *path,
 static ssize_t pidfs_listxattr(struct dentry *dentry, char *buf, size_t size)
 {
 	struct inode *inode = d_inode(dentry);
-	struct pid *pid = inode->i_private;
+	struct pid *pid = pidfs_inode_pid(inode);
+
+	if (IS_ERR(pid))
+		return PTR_ERR(pid);
 
 	return simple_xattr_list(inode, &pid->attr->xattrs, buf, size);
 }
@@ -833,10 +910,25 @@ static const struct inode_operations pidfs_inode_operations = {
 
 static void pidfs_evict_inode(struct inode *inode)
 {
-	struct pid *pid = inode->i_private;
+	struct pidfs_future_file *future;
+	struct pidfs_node *node = pidfs_inode_node(inode);
 
 	clear_inode(inode);
-	put_pid(pid);
+	if (!node)
+		return;
+	if (node->type == PIDFS_NODE_PID) {
+		put_pid(container_of(node, struct pid, pidfs_node));
+		return;
+	}
+	if (WARN_ON_ONCE(node->type != PIDFS_NODE_FUTURE))
+		return;
+
+	future = container_of(node, struct pidfs_future_file, node);
+	if (future->ops->release)
+		future->ops->release(future->data);
+	if (future->attr)
+		kmem_cache_free(pidfs_attr_cachep, future->attr);
+	kfree(future);
 }
 
 static const struct super_operations pidfs_sops = {
@@ -854,15 +946,24 @@ static char *pidfs_dname(struct dentry *dentry, char *buffer, int buflen)
 	return dynamic_dname(buffer, buflen, "anon_inode:[pidfd]");
 }
 
+static void pidfs_dentry_prune(struct dentry *dentry)
+{
+	if (dentry->d_fsdata)
+		stashed_dentry_prune(dentry);
+}
+
 const struct dentry_operations pidfs_dentry_operations = {
 	.d_dname	= pidfs_dname,
-	.d_prune	= stashed_dentry_prune,
+	.d_prune	= pidfs_dentry_prune,
 };
 
 static int pidfs_encode_fh(struct inode *inode, u32 *fh, int *max_len,
 			   struct inode *parent)
 {
-	const struct pid *pid = inode->i_private;
+	const struct pid *pid = pidfs_inode_pid(inode);
+
+	if (IS_ERR(pid))
+		return FILEID_INVALID;
 
 	if (*max_len < 2) {
 		*max_len = 2;
@@ -974,22 +1075,36 @@ static const struct export_operations pidfs_export_operations = {
 	.permission	= pidfs_export_permission,
 };
 
-static int pidfs_init_inode(struct inode *inode, void *data)
+static void pidfs_init_common_inode(struct inode *inode,
+				    struct pidfs_node *node, u64 ino)
 {
-	const struct pid *pid = data;
-
-	inode->i_private = data;
+	inode->i_private = node;
 	inode->i_flags |= S_PRIVATE | S_ANON_INODE;
 	/* We allow to set xattrs. */
 	inode->i_flags &= ~S_IMMUTABLE;
-	inode->i_mode |= S_IRWXU;
+	inode->i_mode = S_IFREG | 0700;
+	inode->i_uid = GLOBAL_ROOT_UID;
+	inode->i_gid = GLOBAL_ROOT_GID;
 	inode->i_op = &pidfs_inode_operations;
 	inode->i_fop = &pidfs_file_operations;
-	inode->i_ino = pidfs_ino(pid->ino);
-	inode->i_generation = pidfs_gen(pid->ino);
+	inode->i_ino = pidfs_ino(ino);
+	inode->i_generation = pidfs_gen(ino);
+}
+
+static int pidfs_init_inode(struct inode *inode, void *data)
+{
+	struct pid *pid = data;
+
+	pidfs_init_common_inode(inode, &pid->pidfs_node, pid->ino);
 	return 0;
 }
 
+static bool pidfs_inode_data_matches(const struct inode *inode,
+				     const void *data)
+{
+	return pidfs_inode_pid(inode) == data;
+}
+
 static void pidfs_put_data(void *data)
 {
 	struct pid *pid = data;
@@ -1044,9 +1159,11 @@ int pidfs_register_pid_gfp(struct pid *pid, gfp_t gfp)
 static struct dentry *pidfs_stash_dentry(struct dentry **stashed,
 					 struct dentry *dentry)
 {
+	struct pid *pid = pidfs_inode_pid(d_inode(dentry));
 	int ret;
-	struct pid *pid = d_inode(dentry)->i_private;
 
+	if (WARN_ON_ONCE(IS_ERR(pid)))
+		return ERR_CAST(pid);
 	VFS_WARN_ON_ONCE(stashed != &pid->stashed);
 
 	ret = pidfs_register_pid(pid);
@@ -1060,15 +1177,19 @@ static const struct stashed_operations pidfs_stashed_ops = {
 	.stash_dentry	= pidfs_stash_dentry,
 	.init_inode	= pidfs_init_inode,
 	.put_data	= pidfs_put_data,
+	.data_matches	= pidfs_inode_data_matches,
 };
 
 static int pidfs_xattr_get(const struct xattr_handler *handler,
 			   struct dentry *unused, struct inode *inode,
 			   const char *suffix, void *value, size_t size)
 {
-	struct pid *pid = inode->i_private;
+	struct pid *pid = pidfs_inode_pid(inode);
 	const char *name = xattr_full_name(handler, suffix);
 
+	if (IS_ERR(pid))
+		return PTR_ERR(pid);
+
 	return simple_xattr_get(&pidfs_xa_cache, &pid->attr->xattrs, name, value, size);
 }
 
@@ -1077,10 +1198,13 @@ static int pidfs_xattr_set(const struct xattr_handler *handler,
 			   struct inode *inode, const char *suffix,
 			   const void *value, size_t size, int flags)
 {
-	struct pid *pid = inode->i_private;
+	struct pid *pid = pidfs_inode_pid(inode);
 	const char *name = xattr_full_name(handler, suffix);
 	struct simple_xattr *old_xattr;
 
+	if (IS_ERR(pid))
+		return PTR_ERR(pid);
+
 	/* Ensure we're the only one to set @attr->xattrs. */
 	WARN_ON_ONCE(!inode_is_locked(inode));
 
@@ -1158,6 +1282,90 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
 	return pidfd_file;
 }
 
+/**
+ * pidfs_alloc_future_file - allocate a taskless pidfs file
+ * @name: anonymous file name used by LSM initialization
+ * @data: producer data retained for the inode lifetime
+ * @ops: callbacks that resolve and release @data
+ * @flags: file status flags
+ *
+ * Ownership of @data transfers to the returned file on success. One serialized
+ * producer owns all later association and publication transitions. It may
+ * associate one preallocated pid with
+ * pidfs_future_file_set_pid(), publish its pidfs metadata with
+ * pidfs_future_file_publish_pid(), make @ops->get_pid() resolve that pid, and
+ * finally wake waiters with pidfs_future_file_notify().
+ *
+ * A task associated with the future pid must not become runnable before
+ * publication metadata and exit-wakeup forwarding are installed.
+ *
+ * Return: A new pidfs file on success or an error pointer on failure.
+ */
+struct file *pidfs_alloc_future_file(const char *name, void *data,
+				     const struct pidfs_future_file_ops *ops,
+				     unsigned int flags)
+{
+	struct pidfs_future_file *future;
+	struct inode *inode;
+	struct file *file;
+	u64 ino;
+	int ret;
+
+	if (!ops || !ops->get_pid)
+		return ERR_PTR(-EINVAL);
+
+	future = kzalloc_obj(*future, GFP_KERNEL_ACCOUNT);
+	if (!future)
+		return ERR_PTR(-ENOMEM);
+	future->attr = kmem_cache_zalloc(pidfs_attr_cachep, GFP_KERNEL_ACCOUNT);
+	if (!future->attr) {
+		kfree(future);
+		return ERR_PTR(-ENOMEM);
+	}
+	INIT_LIST_HEAD_RCU(&future->attr->xattrs);
+
+	inode = new_inode_pseudo(pidfs_mnt->mnt_sb);
+	if (!inode) {
+		kmem_cache_free(pidfs_attr_cachep, future->attr);
+		kfree(future);
+		return ERR_PTR(-ENOMEM);
+	}
+	/* Preserve the anonymous-inode creation check for future pidfds. */
+	ret = security_inode_init_security_anon(inode, &QSTR(name), NULL);
+	if (ret) {
+		iput(inode);
+		kmem_cache_free(pidfs_attr_cachep, future->attr);
+		kfree(future);
+		return ERR_PTR(ret);
+	}
+	ino = pidfs_alloc_ino();
+	simple_inode_init_ts(inode);
+	pidfs_init_common_inode(inode, NULL, ino);
+
+	file = alloc_file_pseudo(inode, pidfs_mnt, name, flags,
+				 &pidfs_file_operations);
+	if (IS_ERR(file)) {
+		iput(inode);
+		kmem_cache_free(pidfs_attr_cachep, future->attr);
+		kfree(future);
+		return file;
+	}
+
+	future->node.type = PIDFS_NODE_FUTURE;
+	future->data = data;
+	future->ops = ops;
+	future->ino = ino;
+	inode->i_private = &future->node;
+	return file;
+}
+
+u64 pidfs_future_file_ino(const struct file *file)
+{
+	struct pidfs_future_file *future = pidfs_future_file(file_inode(file));
+
+	return future ? future->ino : 0;
+}
+
 void __init pidfs_init(void)
 {
 	if (rhashtable_init(&pidfs_ino_ht, &pidfs_ino_ht_params))
diff --git a/include/linux/pid.h b/include/linux/pid.h
index ddaef0bbc8ba3..a29ffe2a5fa8e 100644
--- a/include/linux/pid.h
+++ b/include/linux/pid.h
@@ -50,6 +50,15 @@
 
 struct pidfs_attr;
 
+enum pidfs_node_type {
+	PIDFS_NODE_PID,
+	PIDFS_NODE_FUTURE,
+};
+
+struct pidfs_node {
+	enum pidfs_node_type type;
+};
+
 struct upid {
 	int nr;
 	struct pid_namespace *ns;
@@ -59,6 +68,7 @@ struct pid {
 	refcount_t count;
 	unsigned int level;
 	spinlock_t lock;
+	struct pidfs_node pidfs_node;
 	struct {
 		u64 ino;
 		struct rhash_head pidfs_hash;
diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h
index 0abf7da9ab236..6b7fbc54ab388 100644
--- a/include/linux/pidfs.h
+++ b/include/linux/pidfs.h
@@ -5,8 +5,29 @@
 #include <linux/gfp_types.h>
 
 struct coredump_params;
+struct file;
+struct pid;
+
+/**
+ * struct pidfs_future_file_ops - callbacks for a taskless pidfs file
+ * @get_pid: Return the published, borrowed, non-NULL process identity, or an
+ *	error pointer while the producer is still taskless. The producer must
+ *	keep the returned pid alive for the future inode lifetime.
+ * @release: Optionally release producer-owned data when the pidfs inode is
+ *	evicted. If provided, this is called at most once.
+ */
+struct pidfs_future_file_ops {
+	struct pid *(*get_pid)(void *data);
+	void (*release)(void *data);
+};
 
 struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags);
+struct file *pidfs_alloc_future_file(const char *name, void *data,
+				     const struct pidfs_future_file_ops *ops,
+				     unsigned int flags);
+void *pidfs_future_file_data(const struct file *file,
+			     const struct pidfs_future_file_ops *ops);
+u64 pidfs_future_file_ino(const struct file *file);
 void __init pidfs_init(void);
 void pidfs_prepare_pid(struct pid *pid);
 int pidfs_add_pid(struct pid *pid);
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH 02/24] libfs: allow custom validation of stashed inode data
From: Li Chen @ 2026-07-16 14:31 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Kees Cook, Gabriel Krisman Bertazi, Josh Triplett, Mateusz Guzik,
	Andy Lutomirski, John Ericson, Jonathan Corbet, Shuah Khan,
	Arnd Bergmann, Oleg Nesterov, Andrew Morton, Paul Moore,
	Eric Paris, Mickaël Salaün, Günther Noack,
	Alexander Viro, Jan Kara, linux-api, linux-fsdevel, linux-kernel,
	linux-kselftest, linux-doc, audit, linux-security-module,
	linux-arch, linux-mm, Li Chen
In-Reply-To: <cover.1784204592.git.me@linux.beauty>

path_from_stashed() assumes that inode->i_private is identical to the
caller data used to find or create a dentry. That is too strict for an
inode whose stable identity can later resolve through another object.

Add an optional data_matches() callback to stashed_operations. Existing
users retain the pointer-identity check when no callback is provided.

Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Li Chen <me@linux.beauty>
---
 fs/internal.h | 1 +
 fs/libfs.c    | 5 ++++-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/fs/internal.h b/fs/internal.h
index 174f063575558..71cc43e72b33e 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -334,6 +334,7 @@ struct stashed_operations {
 				       struct dentry *dentry);
 	void (*put_data)(void *data);
 	int (*init_inode)(struct inode *inode, void *data);
+	bool (*data_matches)(const struct inode *inode, const void *data);
 };
 int path_from_stashed(struct dentry **stashed, struct vfsmount *mnt, void *data,
 		      struct path *path);
diff --git a/fs/libfs.c b/fs/libfs.c
index 5a0d276379d13..68cc1671b4699 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -2260,7 +2260,10 @@ int path_from_stashed(struct dentry **stashed, struct vfsmount *mnt, void *data,
 	path->dentry = res;
 	path->mnt = mntget(mnt);
 	VFS_WARN_ON_ONCE(path->dentry->d_fsdata != stashed);
-	VFS_WARN_ON_ONCE(d_inode(path->dentry)->i_private != data);
+	if (sops->data_matches)
+		VFS_WARN_ON_ONCE(!sops->data_matches(d_inode(path->dentry), data));
+	else
+		VFS_WARN_ON_ONCE(d_inode(path->dentry)->i_private != data);
 	return 0;
 }
 
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH 01/24] pidfd: add spawn builder uapi
From: Li Chen @ 2026-07-16 14:31 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Kees Cook, Gabriel Krisman Bertazi, Josh Triplett, Mateusz Guzik,
	Andy Lutomirski, John Ericson, Jonathan Corbet, Shuah Khan,
	Arnd Bergmann, Oleg Nesterov, Andrew Morton, Paul Moore,
	Eric Paris, Mickaël Salaün, Günther Noack,
	Alexander Viro, Jan Kara, linux-api, linux-fsdevel, linux-kernel,
	linux-kselftest, linux-doc, audit, linux-security-module,
	linux-arch, linux-mm, Li Chen
In-Reply-To: <cover.1784204592.git.me@linux.beauty>

Define PIDFD_EMPTY, the fsconfig-style configuration command and path
key, and versioned run arguments for pidfd spawn builders. Add the
initial ordered file-action records.

Store userspace pointers in full-width aligned containers on every ABI.
Keep flags and reserved fields zero so later kernels can extend the
structures without changing their initial layout.

Suggested-by: Christian Brauner <brauner@kernel.org>
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Li Chen <me@linux.beauty>
---
 MAINTAINERS                            |  2 ++
 include/uapi/linux/pidfd.h             |  1 +
 include/uapi/linux/pidfd_spawn.h       | 49 ++++++++++++++++++++++++++
 tools/include/uapi/linux/pidfd_spawn.h | 49 ++++++++++++++++++++++++++
 4 files changed, 101 insertions(+)
 create mode 100644 include/uapi/linux/pidfd_spawn.h
 create mode 100644 tools/include/uapi/linux/pidfd_spawn.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 8729cea57c3dd..b63bc1ee0171f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21287,7 +21287,9 @@ M:	Christian Brauner <christian@brauner.io>
 L:	linux-kernel@vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux.git
+F:	include/uapi/linux/pidfd_spawn.h
 F:	samples/pidfd/
+F:	tools/include/uapi/linux/pidfd_spawn.h
 F:	tools/testing/selftests/clone3/
 F:	tools/testing/selftests/pidfd/
 K:	(?i)pidfd
diff --git a/include/uapi/linux/pidfd.h b/include/uapi/linux/pidfd.h
index 0919246a1611c..cad5b722e9f0e 100644
--- a/include/uapi/linux/pidfd.h
+++ b/include/uapi/linux/pidfd.h
@@ -10,6 +10,7 @@
 /* Flags for pidfd_open().  */
 #define PIDFD_NONBLOCK	O_NONBLOCK
 #define PIDFD_THREAD	O_EXCL
+#define PIDFD_EMPTY	(1U << 27)
 #ifdef __KERNEL__
 #include <linux/sched.h>
 #define PIDFD_STALE CLONE_PIDFD
diff --git a/include/uapi/linux/pidfd_spawn.h b/include/uapi/linux/pidfd_spawn.h
new file mode 100644
index 0000000000000..46d61e72435cc
--- /dev/null
+++ b/include/uapi/linux/pidfd_spawn.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _UAPI_LINUX_PIDFD_SPAWN_H
+#define _UAPI_LINUX_PIDFD_SPAWN_H
+
+#include <linux/types.h>
+
+#define PIDFD_SPAWN_RUN_SIZE_VER0	64
+#define PIDFD_SPAWN_ACTION_SIZE_VER0	32
+
+/* Commands for pidfd_config(). */
+#define PIDFD_CONFIG_SET_STRING	0
+
+/* String keys for pidfd_config(). */
+#define PIDFD_CONFIG_KEY_PATH	"path"
+
+struct pidfd_spawn_run_args {
+	__u32 flags;
+	__u32 nr_actions;
+	/* The next four fields are full-width userspace virtual addresses. */
+	__aligned_u64 path;
+	__aligned_u64 argv;
+	__aligned_u64 envp;
+	__aligned_u64 actions;
+	__u32 action_size;
+	__u32 reserved0;
+	__u64 reserved[2];
+};
+
+enum pidfd_spawn_action_type {
+	PIDFD_SPAWN_ACTION_DUP2 = 0,
+	PIDFD_SPAWN_ACTION_CLOSE_RANGE = 1,
+	PIDFD_SPAWN_ACTION_FCHDIR = 2,
+};
+
+struct pidfd_spawn_action {
+	__u32 type;
+	__u32 flags;
+	/*
+	 * DUP2 uses fd as the source and newfd as the destination.
+	 * CLOSE_RANGE uses fd as the first and newfd as the last descriptor.
+	 * FCHDIR uses fd as the directory descriptor and requires newfd to be 0.
+	 */
+	__u32 fd;
+	__u32 newfd;
+	__u64 reserved[2];
+};
+
+#endif /* _UAPI_LINUX_PIDFD_SPAWN_H */
diff --git a/tools/include/uapi/linux/pidfd_spawn.h b/tools/include/uapi/linux/pidfd_spawn.h
new file mode 100644
index 0000000000000..46d61e72435cc
--- /dev/null
+++ b/tools/include/uapi/linux/pidfd_spawn.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _UAPI_LINUX_PIDFD_SPAWN_H
+#define _UAPI_LINUX_PIDFD_SPAWN_H
+
+#include <linux/types.h>
+
+#define PIDFD_SPAWN_RUN_SIZE_VER0	64
+#define PIDFD_SPAWN_ACTION_SIZE_VER0	32
+
+/* Commands for pidfd_config(). */
+#define PIDFD_CONFIG_SET_STRING	0
+
+/* String keys for pidfd_config(). */
+#define PIDFD_CONFIG_KEY_PATH	"path"
+
+struct pidfd_spawn_run_args {
+	__u32 flags;
+	__u32 nr_actions;
+	/* The next four fields are full-width userspace virtual addresses. */
+	__aligned_u64 path;
+	__aligned_u64 argv;
+	__aligned_u64 envp;
+	__aligned_u64 actions;
+	__u32 action_size;
+	__u32 reserved0;
+	__u64 reserved[2];
+};
+
+enum pidfd_spawn_action_type {
+	PIDFD_SPAWN_ACTION_DUP2 = 0,
+	PIDFD_SPAWN_ACTION_CLOSE_RANGE = 1,
+	PIDFD_SPAWN_ACTION_FCHDIR = 2,
+};
+
+struct pidfd_spawn_action {
+	__u32 type;
+	__u32 flags;
+	/*
+	 * DUP2 uses fd as the source and newfd as the destination.
+	 * CLOSE_RANGE uses fd as the first and newfd as the last descriptor.
+	 * FCHDIR uses fd as the directory descriptor and requires newfd to be 0.
+	 */
+	__u32 fd;
+	__u32 newfd;
+	__u64 reserved[2];
+};
+
+#endif /* _UAPI_LINUX_PIDFD_SPAWN_H */
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH 00/24] pidfd: add a minimal process spawn builder
From: Li Chen @ 2026-07-16 14:31 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Kees Cook, Gabriel Krisman Bertazi, Josh Triplett, Mateusz Guzik,
	Andy Lutomirski, John Ericson, Jonathan Corbet, Shuah Khan,
	Arnd Bergmann, Oleg Nesterov, Andrew Morton, Paul Moore,
	Eric Paris, Mickaël Salaün, Günther Noack,
	Alexander Viro, Jan Kara, linux-api, linux-fsdevel, linux-kernel,
	linux-kselftest, linux-doc, audit, linux-security-module,
	linux-arch, linux-mm

Hi,

This RFC follows feedback on my earlier spawn_template RFC [1]. That
proposal made caching the primary interface; this one starts with general
process construction. Christian suggested a pidfd/pidfs exec builder
modeled after fsconfig(), with enough semantics for userspace to implement
posix_spawn() [2], and Kees agreed [3].

This RFC is based on linux-next next-20260710 and depends on two pidfs
fixes that I sent separately:

  * pidfs: preserve thread pidfds reopened by file handle
    https://lore.kernel.org/all/20260716052726.1032092-1-me@linux.beauty/
  * pidfs: handle FS_IOC32_GETVERSION in compat ioctl
    https://lore.kernel.org/all/20260716052822.1034228-1-me@linux.beauty/

The initial implementation is source-based. The executable path can be
provided with the final run request:

    struct pidfd_spawn_run_args run = {
        .path = (unsigned long)"/usr/bin/rg",
        .argv = (unsigned long)argv,
        .envp = (unsigned long)envp,
    };

    fd = pidfd_open(0, PIDFD_EMPTY);
    pidfd_spawn_run(fd, &run, sizeof(run));

Alternatively, the path can be staged before the final run step:

    struct pidfd_spawn_run_args run = {
        .argv = (unsigned long)argv,
        .envp = (unsigned long)envp,
    };

    fd = pidfd_open(0, PIDFD_EMPTY);
    pidfd_config(fd, PIDFD_CONFIG_SET_STRING,
                 PIDFD_CONFIG_KEY_PATH, "/usr/bin/rg", 0);
    pidfd_spawn_run(fd, &run, sizeof(run));

pidfd_open(0, PIDFD_EMPTY) creates a taskless future pidfd with a stable
pidfs inode, but no task, PID, or process-count charge. pidfd_spawn_run()
creates the task and PID; after publication the same fd is the child pidfd,
and a numeric pidfd resolves to the same inode. Live-task operations return
-ESRCH before publication. A terminal pre-task failure wakes poll/epoll
with POLLERR | POLLHUP.

Source-based mode follows posix_spawn-style defaults. At run time it
samples the caller's cwd, root, umask, fd table, signal dispositions and
blocked mask, and namespaces.
Non-FD_CLOEXEC descriptors remain unless an action changes them; file and
filesystem state are private child copies before actions. Configuration and
run stay bound to the creating mm_struct, exact credential object, and
child PID namespace, so SCM_RIGHTS does not delegate launch authority.

The first authorized run claims the builder before copying its payload, so
later failures are terminal. Pre-task failure leaves the fd taskless;
setup or exec failure leaves it as the child pidfd and exits the child with
status 127. PIDFD_GET_INFO with PIDFD_INFO_EXIT distinguishes them.
Success returns the positive child PID in the caller's PID namespace.

The RFC supports ordered DUP2, CLOSE_RANGE, and FCHDIR actions in
extensible UAPI records. This exercises child-private fd and cwd setup, but
is not the complete posix_spawn() action or attribute surface.

Between task publication and successful exec, the child is explicitly
embryonic and may not have a valid userspace register frame. Ptrace and
pidfd_getfd() are denied, procfs treats the PID as absent to other tasks,
and coredump information reports PIDFD_COREDUMP_SKIP. The child can use its
own proc entries during executable lookup. Setup runs as initial child task
work, and successful exec releases this state before exec events are
published.

Seccomp sees pidfd_spawn_run(), not separate file-action or exec syscalls,
and cannot inspect the path or action records behind the run pointer. An
exec-only denylist that allows unknown syscalls therefore does not block
this initial exec; policy must filter the builder syscall as a unit. Should
later expansion provide an immutable restriction profile or action mask
that seccomp can reason about, or is the coarse syscall boundary
preferable?

LSM exec checks and inherited seccomp state remain active. Child setup uses
a dedicated AUDIT_PIDFD_SPAWN transaction, not a synthetic AUDIT_SYSCALL.
Should it be selected through the source pidfd_spawn_run() exit rule? An
existing rule naming only execve() or execveat() does not select it.
For source/child correlation, could an auxiliary record carry
the source PID plus the stable pidfs inode? An already traced source is
rejected before the claim; ptrace auto-attach is not implemented.

The implementation still uses CLONE_VM | CLONE_VFORK plus exec internally.
Mateusz suggested that an initial implementation might start with vfork to
get the API off the ground [4]. That is what this RFC does. It does not yet
construct a pristine target process without first inheriting source state.

Missing posix_spawn() pieces include open and close file actions, resetids,
signal masks/defaults, process groups, sessions, scheduler attributes,
affinity, cgroup placement, PATH lookup/posix_spawnp(), and exec by fd. The
RFC also does not include pristine/no-source creation or the executable
metadata/template cache from my earlier work.

John Ericson described a real, partially initialized process that remains
unscheduled while callers install its state, and linked an exploratory
FreeBSD proc_new()/proc_setfd()/proc_start() refactoring [5]. This RFC
implements the source-based mode first; a lower-authority pristine/no-source
mode would be explicit follow-up work.

Direct process construction is not unprecedented. XNU's posix_spawn path
does not inherit the parent's address space [6], and Windows
CreateProcess() accepts explicit startup state [7]. Josh's io_uring_spawn
LPC slides list "set up process from scratch" as future work and provide
useful performance context [8]; I am not using those numbers as a claim for
this RFC.

If the direction is acceptable, I plan to continue toward:

  * the complete file-action and attribute set needed by posix_spawn();
  * pristine target-process construction and an explicit no-source mode;
  * PATH/posix_spawnp support, if it belongs on the kernel side; and
  * an optional executable/template layer for workloads such as agent tool
    calling and compiler drivers.

The exposed UAPI surface is intentionally limited so the state model and
kernel/userspace boundary can be reviewed first. If maintainers would
prefer more posix_spawn semantics or backend work in this RFC, please say
so and I will adjust the split.

Codex GPT-5.5 and GPT-5.6-sol provided substantial assistance across design
conceptualization, implementation, patch splitting, code review, development
of self-test cases, and test planning and execution.

Thanks to Christian, Kees, Mateusz, Gabriel, Josh, Andy, John, and others
for the review and direction.

[1]: https://patchew.org/linux/20260528095235.2491226-1-me%40linux.beauty/
[2]: https://lore.kernel.org/all/20260528-madig-fachrichtung-fehlinformation-61117ba640da@brauner/
[3]: https://lore.kernel.org/all/202606011254.5FCBD65@keescook/
[4]: https://lore.kernel.org/all/vealb52tv5suireenkke4lul2l3wbnaul2rp3ea545ly5wa5ty@yk3aksvp7skt/
[5]: https://lore.kernel.org/all/ce71d6df-6851-4e4b-9603-1d55d8d522b8@app.fastmail.com/
[6]: https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/bsd/kern/kern_exec.c#L4039
[7]: https://learn.microsoft.com/en-us/windows/win32/procthread/creating-processes
[8]: https://lpc.events/event/16/contributions/1213/attachments/1012/1945/io-uring-spawn.pdf


Li Chen (24):
  pidfd: add spawn builder uapi
  libfs: allow custom validation of stashed inode data
  pidfs: add taskless future pidfd inodes
  pidfd: create taskless spawn builders
  pidfd: add spawn builder path configuration
  exec: expose execveat internals to process builders
  fork: expose vfork completion helper
  pidfs: attach pids to future pidfd files
  fork: let process builders supply preallocated pids
  pidfs: publish future pidfd files
  pidfd: add spawn builder state tracking
  fork: let kernel callers create embryonic tasks
  fork: let new tasks start with task work
  pidfd: create and execute spawn builder tasks
  fork: keep embryonic tasks hidden until exec completes
  audit: add pidfd spawn child contexts
  pidfd: audit child spawn execution
  pidfd: make spawn builder execution signal-safe
  file: expose spawn file-action helpers
  pidfd: add initial spawn file actions
  pidfd: consume spawn builders on the first run attempt
  pidfd: expose spawn builder system calls
  selftests/pidfd: cover pidfd spawn builders
  Documentation: describe pidfd spawn builders

 Documentation/userspace-api/index.rst         |    1 +
 Documentation/userspace-api/pidfd_spawn.rst   |  247 ++++
 MAINTAINERS                                   |    6 +
 arch/alpha/kernel/syscalls/syscall.tbl        |    2 +
 arch/arm/tools/syscall.tbl                    |    2 +
 arch/arm64/tools/syscall_32.tbl               |    2 +
 arch/m68k/kernel/syscalls/syscall.tbl         |    2 +
 arch/microblaze/kernel/syscalls/syscall.tbl   |    2 +
 arch/mips/kernel/syscalls/syscall_n32.tbl     |    2 +
 arch/mips/kernel/syscalls/syscall_n64.tbl     |    2 +
 arch/mips/kernel/syscalls/syscall_o32.tbl     |    2 +
 arch/parisc/kernel/syscalls/syscall.tbl       |    2 +
 arch/powerpc/kernel/syscalls/syscall.tbl      |    2 +
 arch/s390/kernel/syscalls/syscall.tbl         |    2 +
 arch/sh/kernel/syscalls/syscall.tbl           |    2 +
 arch/sparc/kernel/syscalls/syscall.tbl        |    2 +
 arch/x86/entry/syscalls/syscall_32.tbl        |    2 +
 arch/x86/entry/syscalls/syscall_64.tbl        |    2 +
 arch/xtensa/kernel/syscalls/syscall.tbl       |    2 +
 fs/Makefile                                   |    2 +-
 fs/coredump.c                                 |    4 +-
 fs/exec.c                                     |   30 +-
 fs/exec_internal.h                            |   40 +
 fs/file.c                                     |   11 +-
 fs/internal.h                                 |    3 +
 fs/libfs.c                                    |    5 +-
 fs/open.c                                     |    7 +-
 fs/pidfd_spawn.c                              | 1095 +++++++++++++++
 fs/pidfs.c                                    |  478 ++++++-
 fs/proc/base.c                                |   11 +-
 fs/proc/internal.h                            |   18 +-
 include/linux/audit.h                         |   31 +
 include/linux/pid.h                           |   13 +
 include/linux/pidfd_spawn.h                   |    9 +
 include/linux/pidfs.h                         |   28 +
 include/linux/sched.h                         |   21 +
 include/linux/sched/task.h                    |    6 +
 include/linux/syscalls.h                      |    7 +
 include/uapi/asm-generic/unistd.h             |    8 +-
 include/uapi/linux/audit.h                    |    1 +
 include/uapi/linux/pidfd.h                    |    1 +
 include/uapi/linux/pidfd_spawn.h              |   49 +
 kernel/audit.h                                |    1 +
 kernel/auditsc.c                              |  105 +-
 kernel/fork.c                                 |   26 +-
 kernel/nsproxy.c                              |   11 +-
 kernel/pid.c                                  |   41 +-
 kernel/ptrace.c                               |    4 +
 kernel/signal.c                               |    2 +-
 scripts/syscall.tbl                           |    2 +
 tools/include/uapi/asm-generic/unistd.h       |    8 +-
 tools/include/uapi/linux/pidfd_spawn.h        |   49 +
 .../arch/alpha/entry/syscalls/syscall.tbl     |    2 +
 .../perf/arch/arm/entry/syscalls/syscall.tbl  |    2 +
 .../arch/arm64/entry/syscalls/syscall_32.tbl  |   11 +
 .../arch/mips/entry/syscalls/syscall_n64.tbl  |    2 +
 .../arch/parisc/entry/syscalls/syscall.tbl    |    2 +
 .../arch/powerpc/entry/syscalls/syscall.tbl   |    2 +
 .../perf/arch/s390/entry/syscalls/syscall.tbl |    2 +
 tools/perf/arch/sh/entry/syscalls/syscall.tbl |    2 +
 .../arch/sparc/entry/syscalls/syscall.tbl     |    2 +
 .../arch/x86/entry/syscalls/syscall_32.tbl    |    2 +
 .../arch/x86/entry/syscalls/syscall_64.tbl    |    2 +
 .../arch/xtensa/entry/syscalls/syscall.tbl    |    2 +
 tools/scripts/syscall.tbl                     |    2 +
 tools/testing/selftests/landlock/audit.h      |    6 +-
 tools/testing/selftests/pidfd/.gitignore      |    9 +
 tools/testing/selftests/pidfd/Makefile        |   25 +-
 tools/testing/selftests/pidfd/config          |    6 +
 .../pidfd/pidfd_spawn_accounting_test.c       |  428 ++++++
 .../pidfd/pidfd_spawn_actions_test.c          |  474 +++++++
 .../selftests/pidfd/pidfd_spawn_audit_test.c  |  521 +++++++
 .../selftests/pidfd/pidfd_spawn_common.c      |  512 +++++++
 .../selftests/pidfd/pidfd_spawn_common.h      |   59 +
 .../selftests/pidfd/pidfd_spawn_compat.c      |  221 +++
 .../selftests/pidfd/pidfd_spawn_exec_test.c   |  301 ++++
 .../selftests/pidfd/pidfd_spawn_policy_test.c |  294 ++++
 .../selftests/pidfd/pidfd_spawn_race_test.c   |  923 ++++++++++++
 .../pidfd/pidfd_spawn_security_test.c         | 1242 +++++++++++++++++
 .../selftests/pidfd/pidfd_spawn_test.c        |  550 ++++++++
 80 files changed, 7938 insertions(+), 81 deletions(-)
 create mode 100644 Documentation/userspace-api/pidfd_spawn.rst
 create mode 100644 fs/exec_internal.h
 create mode 100644 fs/pidfd_spawn.c
 create mode 100644 include/linux/pidfd_spawn.h
 create mode 100644 include/uapi/linux/pidfd_spawn.h
 create mode 100644 tools/include/uapi/linux/pidfd_spawn.h
 create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_accounting_test.c
 create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_actions_test.c
 create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_audit_test.c
 create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_common.c
 create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_common.h
 create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_compat.c
 create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_exec_test.c
 create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_policy_test.c
 create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_race_test.c
 create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_security_test.c
 create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_test.c

-- 
2.52.0


^ permalink raw reply

* Re: [PATCH v3 4/6] printk: deprecate boot_delay in favour of printk_delay
From: Petr Mladek @ 2026-07-16 13:36 UTC (permalink / raw)
  To: Andrew Murray
  Cc: Steven Rostedt, John Ogness, Sergey Senozhatsky, Jonathan Corbet,
	Shuah Khan, Russell King, Florian Fainelli,
	Broadcom internal kernel review list, Ray Jui, Scott Branden,
	Andrew Morton, Greg Kroah-Hartman, Sebastian Andrzej Siewior,
	Clark Williams, linux-kernel, linux-doc, linux-arm-kernel,
	linux-rpi-kernel, linux-rt-devel
In-Reply-To: <20260712-printkcleanup-v3-4-574547b8f71b@thegoodpenguin.co.uk>

On Sun 2026-07-12 11:20:35, Andrew Murray wrote:
> The boot_delay (BOOT_PRINTK_DELAY) kernel parameter and printk_delay sysctl
> are two distinct mechanisms for providing similar functionality which add a
> delay prior to each printed printk message.
> 
> boot_delay provides a kernel parameter for delaying printk output from
> kernel start through to boot (SYSTEM_RUNNING), whereas printk_delay is
> configurable only via sysctl and thus is only used post boot.
> 
> Let's deprecate the boot_delay feature in favour of printk_delay. In order
> to preserve functionality, we'll also extend printk_delay such that it can
> additionally configured via an early kernel parameter.
> 
> Behavior change:
> 
> The delay enabled by both "boot_delay" and "printk_delay" continues
> working even in SYSTEM_RUNNING state. It must be explicitly stopped
> by setting printk_delay=0 via sysctl.
> 
> The delay is skipped when the message is suppressed in all system
> states. It used to skipped only for the boot_delay.

s/used to skipped/used to be skipped/

> Signed-off-by: Andrew Murray <amurray@thegoodpenguin.co.uk>

The code changes look good to me. With the above typo fix:

Reviewed-by: Petr Mladek <pmladek@suse.com>

Best Regards,
Petr

^ permalink raw reply

* Re: [PATCH v3 2/6] printk: add bounds checking to boot_delay
From: Petr Mladek @ 2026-07-16 13:13 UTC (permalink / raw)
  To: Andrew Murray
  Cc: Steven Rostedt, John Ogness, Sergey Senozhatsky, Jonathan Corbet,
	Shuah Khan, Russell King, Florian Fainelli,
	Broadcom internal kernel review list, Ray Jui, Scott Branden,
	Andrew Morton, Greg Kroah-Hartman, Sebastian Andrzej Siewior,
	Clark Williams, linux-kernel, linux-doc, linux-arm-kernel,
	linux-rpi-kernel, linux-rt-devel
In-Reply-To: <20260712-printkcleanup-v3-2-574547b8f71b@thegoodpenguin.co.uk>

On Sun 2026-07-12 11:20:33, Andrew Murray wrote:
> As the boot_delay kernel parameter represents a duration in
> milliseconds, let's set its type to be unsigned int and add
> bounds checking.
> 
> Please note that the existing pr_debug will only be displayed
> when boot_delay is non-zero:
> 
>  pr_debug("printk_delay: %u, preset_lpj: %ld, lpj: %lu, "
>           "HZ: %d, loops_per_msec: %llu\n",
>           printk_delay_msec, preset_lpj, lpj, HZ, loops_per_msec);
> 
> Signed-off-by: Andrew Murray <amurray@thegoodpenguin.co.uk>
> ---
>  kernel/printk/printk.c | 11 +++++++----
>  1 file changed, 7 insertions(+), 4 deletions(-)
> 
> diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
> index 31aabdf8248cc39c54ee11685d4a37deac1c174c..8be562c9be277670ba3209ed1f810fc87175848a 100644
> --- a/kernel/printk/printk.c
> +++ b/kernel/printk/printk.c
> @@ -1291,19 +1291,22 @@ static bool suppress_message_printing(int level)
>  
>  #ifdef CONFIG_BOOT_PRINTK_DELAY
>  
> -static int boot_delay; /* msecs delay after each printk during bootup */
> +static unsigned int boot_delay; /* msecs delay after each printk during bootup */
>  static unsigned long long loops_per_msec;	/* based on boot_delay */
>  
>  static int __init boot_delay_setup(char *str)
>  {
>  	unsigned long lpj;
> +	int boot_delay_val;

As Sashiko AI pointed out [1], the variable should get initialized:

	int boot_delay_val = 0;

get_option() keeps the original (random) value, for example, when the given
string is empty.

[1] https://sashiko.dev/#/patchset/20260712-printkcleanup-v3-0-574547b8f71b%40thegoodpenguin.co.uk

>  	lpj = preset_lpj ? preset_lpj : 1000000;	/* some guess */
>  	loops_per_msec = (unsigned long long)lpj / 1000 * HZ;
>  
> -	get_option(&str, &boot_delay);
> -	if (boot_delay > 10 * 1000)
> -		boot_delay = 0;
> +	get_option(&str, &boot_delay_val);
> +	if (boot_delay_val < 0 || boot_delay_val > 10 * 1000)
> +		return 0;
> +
> +	boot_delay = (unsigned int)boot_delay_val;
>  
>  	pr_debug("boot_delay: %u, preset_lpj: %ld, lpj: %lu, "
>  		"HZ: %d, loops_per_msec: %llu\n",

The problem gets fixed in 4th patch. But should fix it here as well
to do not break bisection. Also later changes might get reverted from
other reasons, ...

With the initialized variable:

Reviewed-by: Petr Mladek <pmladek@suse.com>

Best Regards,
Petr

^ permalink raw reply

* Re: [PATCH v3 1/6] printk: sysctl: use unsigned int for printk_delay
From: Petr Mladek @ 2026-07-16 13:01 UTC (permalink / raw)
  To: Andrew Murray
  Cc: Steven Rostedt, John Ogness, Sergey Senozhatsky, Jonathan Corbet,
	Shuah Khan, Russell King, Florian Fainelli,
	Broadcom internal kernel review list, Ray Jui, Scott Branden,
	Andrew Morton, Greg Kroah-Hartman, Sebastian Andrzej Siewior,
	Clark Williams, linux-kernel, linux-doc, linux-arm-kernel,
	linux-rpi-kernel, linux-rt-devel
In-Reply-To: <20260712-printkcleanup-v3-1-574547b8f71b@thegoodpenguin.co.uk>

On Sun 2026-07-12 11:20:32, Andrew Murray wrote:
> As the printk_delay sysctl represents a duration in milliseconds,
> let's set its type to be unsigned int.
> 
> Signed-off-by: Andrew Murray <amurray@thegoodpenguin.co.uk>

Makes sense:

Reviewed-by: Petr Mladek <pmladek@suse.com>

Best Regards,
Petr

^ permalink raw reply

* Re: [PATCH v4 net-next 3/7] selftests/ptp: Add testptp support for attributes ioctls
From: saeed bishara @ 2026-07-16 13:00 UTC (permalink / raw)
  To: Arthur Kiyanovski
  Cc: David Miller, Jakub Kicinski, netdev, Richard Cochran,
	Eric Dumazet, Paolo Abeni, David Woodhouse, Thomas Gleixner,
	Miroslav Lichvar, Andrew Lunn, Wen Gu, Xuan Zhuo, David Woodhouse,
	Yonatan Sarna, Zorik Machulsky, Alexander Matushevsky,
	Saeed Bshara, Matt Wilson, Anthony Liguori, Nafea Bshara,
	Evgeny Schmeilin, Netanel Belgazal, Ali Saidi,
	Benjamin Herrenschmidt, Noam Dagan, David Arinzon,
	Evgeny Ostrovsky, Ofir Tabachnik, Amit Bernstein, linux-kselftest,
	linux-doc, shuah, Jonathan Corbet, Shuah Khan, Simon Horman,
	vadim.fedorenko
In-Reply-To: <20260714020340.25014-4-akiyano@amazon.com>

>                 " -t val     shift the ptp clock time by 'val' seconds\n"
>                 " -T val     set the ptp clock time to 'val' seconds\n"
>                 " -x val     get an extended ptp clock time with the desired number of samples (up to %d)\n"
> +               " -a val     get extended timestamps with attributes (error_bound,\n"
timestamps -> ptp clock to be consistent
also, have you considered making the "-a" additional flag to -x and -A
instead of an exclusive option?


> +               printf("sample #%2d: unknown clock %d %s: %lld.%09u\n",
> +                      sample_num, clockid, when, sec, nsec);
In the case of an unknown clock, will the additional parameters
(when/sec/..) be useful?

>                 break;
>         }
>  }
> @@ -188,6 +193,7 @@ int main(int argc, char *argv[])
>         struct ptp_sys_offset *sysoff;
>         struct ptp_sys_offset_extended *soe;
>         struct ptp_sys_offset_precise *xts;
> +       struct ptp_sys_offset_attrs *attrs_data;
>
>         char *progname;
>         unsigned int i;
> @@ -208,7 +214,9 @@ int main(int argc, char *argv[])
>         int list_pins = 0;
>         int pct_offset = 0;
>         int getextended = 0;
> +       int getextendedattrs = 0;
>         int getcross = 0;
> +       int getcrossattrs = 0;
>         int n_samples = 0;
>         int pin_index = -1, pin_func;
>         int pps = -1;
> @@ -226,7 +234,8 @@ int main(int argc, char *argv[])
>
>         progname = strrchr(argv[0], '/');
>         progname = progname ? 1+progname : argv[0];
> -       while (EOF != (c = getopt(argc, argv, "cd:e:E:f:F:ghH:i:k:lL:n:o:p:P:rsSt:T:w:x:Xy:z"))) {
> +       while (EOF != (c = getopt(argc, argv,
> +                                 "a:Acd:e:E:f:F:ghH:i:k:lL:n:o:p:P:rsSt:T:w:x:Xy:z"))) {
>                 switch (c) {
>                 case 'c':
>                         capabilities = 1;
> @@ -311,9 +320,22 @@ int main(int argc, char *argv[])
>                                 return -1;
>                         }
>                         break;
> +               case 'a':
> +                       getextendedattrs = atoi(optarg);
> +                       if (getextendedattrs < 1 ||
> +                           getextendedattrs > PTP_MAX_SAMPLES) {
> +                               fprintf(stderr,
> +                                       "number of extended attrs timestamp samples must be between 1 and %d; was asked for %d\n",
> +                                       PTP_MAX_SAMPLES, getextendedattrs);
> +                               return -1;
> +                       }
> +                       break;
>                 case 'X':
>                         getcross = 1;
>                         break;
> +               case 'A':
> +                       getcrossattrs = 1;
> +                       break;
>                 case 'y':
>                         if (!strcasecmp(optarg, "realtime"))
>                                 ext_clockid = CLOCK_REALTIME;
> @@ -367,6 +389,8 @@ int main(int argc, char *argv[])
>                                "  %d programmable pins\n"
>                                "  %d cross timestamping\n"
>                                "  %d adjust_phase\n"
> +                              "  %d extended_attrs\n"
> +                              "  %d precise_attrs\n"
>                                "  %d maximum phase adjustment (ns)\n",
>                                caps.max_adj,
>                                caps.n_alarm,
> @@ -376,6 +400,8 @@ int main(int argc, char *argv[])
>                                caps.n_pins,
>                                caps.cross_timestamping,
>                                caps.adjust_phase,
> +                              caps.extended_attrs,
> +                              caps.precise_attrs,
>                                caps.max_phase_adj);
>                 }
>         }
> @@ -648,6 +674,49 @@ int main(int argc, char *argv[])
>                 free(soe);
>         }
>
> +       if (getextendedattrs) {
> +               attrs_data = calloc(1, sizeof(*attrs_data) +
> +                                   getextendedattrs * sizeof(struct ptp_timestamp));
> +               if (!attrs_data) {
> +                       perror("calloc");
> +                       return -1;
> +               }
> +
> +               attrs_data->request.num_samples = getextendedattrs;
> +               attrs_data->request.clock_id = ext_clockid;
> +
> +               if (ioctl(fd, PTP_SYS_OFFSET_EXTENDED_ATTRS, attrs_data)) {
> +                       perror("PTP_SYS_OFFSET_EXTENDED_ATTRS");
> +               } else {
> +                       printf("extended attrs timestamp request returned %d samples\n",
> +                              getextendedattrs);
> +
> +                       for (i = 0; i < getextendedattrs; i++) {
> +                               struct ptp_timestamp *ts = &attrs_data->timestamps[i];
> +
> +                               printf("  sample #%u:\n", i);
> +                               printf("    sys before: %lld ns\n",
> +                                      (long long)ts->pre_systime.sys_time);
> +                               printf("    phc time:   %lld.%09u\n",
> +                                      ts->devtime.device_time.sec,
> +                                      ts->devtime.device_time.nsec);
> +                               if (ts->devtime.attrs.valid & PTP_ATTRS_VALID_ERROR_BOUND)
> +                                       printf("    error_bound: %u ns\n",
> +                                              ts->devtime.attrs.error_bound);
in case device doesn't report error bound, I think it's better to
print explicit message

^ permalink raw reply

* [PATCH v5 5/5] selftests/mm: rewrite gup_test as a standalone harness-based selftest
From: Sarthak Sharma @ 2026-07-16 12:32 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand
  Cc: Lorenzo Stoakes, Liam R . Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Shuah Khan, Shuah Khan,
	Jason Gunthorpe, John Hubbard, Peter Xu, Leon Romanovsky, Zi Yan,
	Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
	Lance Yang, Jonathan Corbet, Mark Brown, Anshuman Khandual,
	linux-mm, linux-kselftest, linux-doc, linux-kernel,
	Sarthak Sharma
In-Reply-To: <20260716123226.197736-1-sarthak.sharma@arm.com>

Rewrite gup_test.c using kselftest_harness.h. The new test covers 12
mapping configurations: THP on, THP off and hugetlb, each across
private/shared and read/write variants. It runs seven test cases per
variant: get_user_pages, get_user_pages_fast, pin_user_pages,
pin_user_pages_fast, pin_user_pages_longterm, and DUMP_USER_PAGES_TEST
via both get and pin.

Each test case sweeps four nr_pages_per_call values: 1, 512, 123, and
all pages. This preserves the old run_gup_matrix() sweep: 12 mapping
combinations x 5 GUP/PUP operations x 4 batch sizes = 240 ioctl sweeps.
It also expands DUMP_USER_PAGES_TEST coverage from one standalone
invocation to 12 variants x 2 dump modes x 4 batch sizes = 96
additional sweeps.

Preserve the old sparse dump coverage from run_vmtests.sh with one
standalone test that exercises DUMP_USER_PAGES_TEST with page indices
0, 19 and 0x1000, fetching the full mapping in one ioctl call. This
brings the total to 85 TAP-reported cases (12 x 7 + 1) and 337 ioctl
sweeps (240 + 96 + 1).

On a Radxa Orion O6 board, ./gup_test completes in 5.39s on average
over 10 runs (range: 5.33s - 5.48s).

Update run_vmtests.sh: remove run_gup_matrix() and the multiple flagged
invocations of gup_test, replacing them with a single unconditional
invocation. Benchmark functionality is handled by tools/mm/gup_bench
introduced in the previous patch.

Update Documentation/core-api/pin_user_pages.rst to reflect the new
harness-based gup_test interface rather than command-line flag
invocations.

Suggested-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Sarthak Sharma <sarthak.sharma@arm.com>
---
 Documentation/core-api/pin_user_pages.rst |  12 +-
 tools/testing/selftests/mm/gup_test.c     | 605 ++++++++++++++--------
 tools/testing/selftests/mm/run_vmtests.sh |  37 +-
 3 files changed, 397 insertions(+), 257 deletions(-)

diff --git a/Documentation/core-api/pin_user_pages.rst b/Documentation/core-api/pin_user_pages.rst
index c16ca163b55e..ea722adf22cc 100644
--- a/Documentation/core-api/pin_user_pages.rst
+++ b/Documentation/core-api/pin_user_pages.rst
@@ -230,10 +230,16 @@ This file::
 
  tools/testing/selftests/mm/gup_test.c
 
-has the following new calls to exercise the new pin*() wrapper functions:
+contains the following test cases to exercise pin_user_pages*():
 
-* PIN_FAST_BENCHMARK (./gup_test -a)
-* PIN_BASIC_TEST (./gup_test -b)
+* pin_user_pages via PIN_BASIC_TEST
+* pin_user_pages_fast via PIN_FAST_BENCHMARK
+* pin_user_pages_longterm via PIN_LONGTERM_BENCHMARK
+
+Run with::
+
+  make -C tools/testing/selftests/mm
+  ./tools/testing/selftests/mm/gup_test
 
 You can monitor how many total dma-pinned pages have been acquired and released
 since the system was booted, via two new /proc/vmstat entries: ::
diff --git a/tools/testing/selftests/mm/gup_test.c b/tools/testing/selftests/mm/gup_test.c
index 5f44761dbec0..d9caa9fb8b78 100644
--- a/tools/testing/selftests/mm/gup_test.c
+++ b/tools/testing/selftests/mm/gup_test.c
@@ -1,274 +1,443 @@
 #define __SANE_USERSPACE_TYPES__ // Use ll64
 #include <fcntl.h>
 #include <errno.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 #include <dirent.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <pthread.h>
-#include <assert.h>
 #include <mm/gup_test.h>
 #include "kselftest.h"
 #include "vm_util.h"
+#include "kselftest_harness.h"
 
 #define MB (1UL << 20)
 
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#endif
+
 /* Just the flags we need, copied from the kernel internals. */
 #define FOLL_WRITE	0x01	/* check pte is writable */
 
+/* Page counts exercising single, THP-batch, partial, and full-mapping GUP. */
+static const int nr_pages_list[] = { 1, 512, 123, -1 };
+
 #define GUP_TEST_FILE "/sys/kernel/debug/gup_test"
 
-static unsigned long cmd = GUP_FAST_BENCHMARK;
-static int gup_fd, repeats = 1;
-static unsigned long size = 128 * MB;
-/* Serialize prints */
-static pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;
+FIXTURE(gup_test)
+{
+	int gup_fd;
+	char *addr;
+	unsigned long size;
+};
+
+FIXTURE_VARIANT(gup_test)
+{
+	bool thp;
+	bool hugetlb;
+	bool write;
+	bool shared;
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_write)
+{
+	.thp = false,
+	.hugetlb = false,
+	.write = true,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_readonly)
+{
+	.thp = false,
+	.hugetlb = false,
+	.write = false,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_write_thp)
+{
+	.thp = true,
+	.hugetlb = false,
+	.write = true,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_readonly_thp)
+{
+	.thp = true,
+	.hugetlb = false,
+	.write = false,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_write_hugetlb)
+{
+	.thp = false,
+	.hugetlb = true,
+	.write = true,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, private_readonly_hugetlb)
+{
+	.thp = false,
+	.hugetlb = true,
+	.write = false,
+	.shared = false,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, shared_write)
+{
+	.thp = false,
+	.hugetlb = false,
+	.write = true,
+	.shared = true,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, shared_readonly)
+{
+	.thp = false,
+	.hugetlb = false,
+	.write = false,
+	.shared = true,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, shared_write_thp)
+{
+	.thp = true,
+	.hugetlb = false,
+	.write = true,
+	.shared = true,
+};
 
-static char *cmd_to_str(unsigned long cmd)
+FIXTURE_VARIANT_ADD(gup_test, shared_readonly_thp)
 {
-	switch (cmd) {
-	case GUP_FAST_BENCHMARK:
-		return "GUP_FAST_BENCHMARK";
-	case PIN_FAST_BENCHMARK:
-		return "PIN_FAST_BENCHMARK";
-	case PIN_LONGTERM_BENCHMARK:
-		return "PIN_LONGTERM_BENCHMARK";
-	case GUP_BASIC_TEST:
-		return "GUP_BASIC_TEST";
-	case PIN_BASIC_TEST:
-		return "PIN_BASIC_TEST";
-	case DUMP_USER_PAGES_TEST:
-		return "DUMP_USER_PAGES_TEST";
+	.thp = true,
+	.hugetlb = false,
+	.write = false,
+	.shared = true,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, shared_write_hugetlb)
+{
+	.thp = false,
+	.hugetlb = true,
+	.write = true,
+	.shared = true,
+};
+
+FIXTURE_VARIANT_ADD(gup_test, shared_readonly_hugetlb)
+{
+	.thp = false,
+	.hugetlb = true,
+	.write = false,
+	.shared = true,
+};
+
+FIXTURE_SETUP(gup_test)
+{
+	int mmap_flags = MAP_PRIVATE;
+	int zero_fd;
+	char *p;
+
+	/* zero_fd has to be >= 0. Already checked in main() */
+	zero_fd = open("/dev/zero", O_RDWR);
+	ASSERT_GE(zero_fd, 0);
+
+	/* gup_fd has to be >= 0. Already checked in main() */
+	self->gup_fd = open(GUP_TEST_FILE, O_RDWR);
+	ASSERT_GE(self->gup_fd, 0);
+
+	self->size = variant->hugetlb ? 256 * MB : 128 * MB;
+
+	/* Check for hugetlb */
+	if (variant->hugetlb) {
+		unsigned long hp_size = default_huge_page_size();
+
+		if (!hp_size) {
+			close(zero_fd);
+			close(self->gup_fd);
+			SKIP(return, "HugeTLB not available\n");
+		}
+
+		self->size = (self->size + hp_size - 1) & ~(hp_size - 1);
+		if (!hugetlb_setup_default(self->size / hp_size)) {
+			hugetlb_restore_settings();
+			close(zero_fd);
+			close(self->gup_fd);
+			SKIP(return, "Not enough huge pages\n");
+		}
+
+		mmap_flags |= (MAP_HUGETLB | MAP_ANONYMOUS);
+	}
+
+	if (variant->shared)
+		mmap_flags = (mmap_flags & ~MAP_PRIVATE) | MAP_SHARED;
+
+	self->addr = mmap(NULL, self->size, PROT_READ | PROT_WRITE,
+			  mmap_flags, zero_fd, 0);
+
+	close(zero_fd);
+
+	ASSERT_NE(self->addr, MAP_FAILED) {
+		int err = errno;
+
+		close(self->gup_fd);
+		if (variant->hugetlb)
+			hugetlb_restore_settings();
+		TH_LOG("mmap failed: %s", strerror(err));
 	}
-	return "Unknown command";
+
+	if (variant->thp)
+		madvise(self->addr, self->size, MADV_HUGEPAGE);
+	else
+		madvise(self->addr, self->size, MADV_NOHUGEPAGE);
+
+	for (p = self->addr; (unsigned long)p < (unsigned long)self->addr
+			+ self->size; p += psize())
+		p[0] = 0;
 }
 
-void *gup_thread(void *data)
+FIXTURE_TEARDOWN(gup_test)
 {
-	struct gup_test gup = *(struct gup_test *)data;
-	int i, status;
-
-	/* Only report timing information on the *_BENCHMARK commands: */
-	if ((cmd == PIN_FAST_BENCHMARK) || (cmd == GUP_FAST_BENCHMARK) ||
-	     (cmd == PIN_LONGTERM_BENCHMARK)) {
-		for (i = 0; i < repeats; i++) {
-			gup.size = size;
-			status = ioctl(gup_fd, cmd, &gup);
-			if (status)
-				break;
-
-			pthread_mutex_lock(&print_mutex);
-			ksft_print_msg("%s: Time: get:%lld put:%lld us",
-				       cmd_to_str(cmd), gup.get_delta_usec,
-				       gup.put_delta_usec);
-			if (gup.size != size)
-				ksft_print_msg(", truncated (size: %lld)", gup.size);
-			ksft_print_msg("\n");
-			pthread_mutex_unlock(&print_mutex);
-		}
-	} else {
-		gup.size = size;
-		status = ioctl(gup_fd, cmd, &gup);
-		if (status)
-			goto return_;
-
-		pthread_mutex_lock(&print_mutex);
-		ksft_print_msg("%s: done\n", cmd_to_str(cmd));
-		if (gup.size != size)
-			ksft_print_msg("Truncated (size: %lld)\n", gup.size);
-		pthread_mutex_unlock(&print_mutex);
+	munmap(self->addr, self->size);
+	close(self->gup_fd);
+
+	if (variant->hugetlb)
+		hugetlb_restore_settings();
+}
+
+TEST_F(gup_test, get_user_pages)
+{
+	/* Tests the get_user_pages path */
+	int i;
+
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
+
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
+
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, GUP_BASIC_TEST, &gup), 0);
+	}
+}
+
+TEST_F(gup_test, pin_user_pages)
+{
+	/* Tests the pin_user_pages path */
+	int i;
+
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
+
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
+
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, PIN_BASIC_TEST, &gup), 0);
 	}
+}
 
-return_:
-	ksft_test_result(!status, "ioctl status %d\n", status);
-	return NULL;
+TEST_F(gup_test, dump_user_pages_with_get)
+{
+	/* Tests DUMP_USER_PAGES_TEST using get_user_pages */
+	int i;
+
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
+
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
+
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		gup.which_pages[0] = 1;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, DUMP_USER_PAGES_TEST, &gup), 0);
+	}
 }
 
-int main(int argc, char **argv)
+TEST_F(gup_test, dump_user_pages_with_pin)
 {
-	struct gup_test gup = { 0 };
-	int filed, i, opt, nr_pages = 1, thp = -1, write = 1, nthreads = 1, ret;
-	int flags = MAP_PRIVATE;
-	char *file = "/dev/zero";
-	bool hugetlb = false;
-	pthread_t *tid;
-	char *p;
+	/* Tests DUMP_USER_PAGES_TEST using pin_user_pages */
+	int i;
 
-	while ((opt = getopt(argc, argv, "m:r:n:F:f:abcj:tTLUuwWSHpz")) != -1) {
-		switch (opt) {
-		case 'a':
-			cmd = PIN_FAST_BENCHMARK;
-			break;
-		case 'b':
-			cmd = PIN_BASIC_TEST;
-			break;
-		case 'L':
-			cmd = PIN_LONGTERM_BENCHMARK;
-			break;
-		case 'c':
-			cmd = DUMP_USER_PAGES_TEST;
-			/*
-			 * Dump page 0 (index 1). May be overridden later, by
-			 * user's non-option arguments.
-			 *
-			 * .which_pages is zero-based, so that zero can mean "do
-			 * nothing".
-			 */
-			gup.which_pages[0] = 1;
-			break;
-		case 'p':
-			/* works only with DUMP_USER_PAGES_TEST */
-			gup.test_flags |= GUP_TEST_FLAG_DUMP_PAGES_USE_PIN;
-			break;
-		case 'F':
-			/* strtol, so you can pass flags in hex form */
-			gup.gup_flags = strtol(optarg, 0, 0);
-			break;
-		case 'j':
-			nthreads = atoi(optarg);
-			break;
-		case 'm':
-			size = atoi(optarg) * MB;
-			break;
-		case 'r':
-			repeats = atoi(optarg);
-			break;
-		case 'n':
-			nr_pages = atoi(optarg);
-			if (nr_pages < 0)
-				nr_pages = size / psize();
-			break;
-		case 't':
-			thp = 1;
-			break;
-		case 'T':
-			thp = 0;
-			break;
-		case 'U':
-			cmd = GUP_BASIC_TEST;
-			break;
-		case 'u':
-			cmd = GUP_FAST_BENCHMARK;
-			break;
-		case 'w':
-			write = 1;
-			break;
-		case 'W':
-			write = 0;
-			break;
-		case 'f':
-			file = optarg;
-			break;
-		case 'S':
-			flags &= ~MAP_PRIVATE;
-			flags |= MAP_SHARED;
-			break;
-		case 'H':
-			flags |= (MAP_HUGETLB | MAP_ANONYMOUS);
-			hugetlb = true;
-			break;
-		default:
-			ksft_exit_fail_msg("Wrong argument\n");
-		}
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
+
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
+
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		gup.which_pages[0] = 1;
+		gup.test_flags |= GUP_TEST_FLAG_DUMP_PAGES_USE_PIN;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, DUMP_USER_PAGES_TEST, &gup), 0);
 	}
+}
 
-	if (optind < argc) {
-		int extra_arg_count = 0;
-		/*
-		 * For example:
-		 *
-		 *   ./gup_test -c 0 1 0x1001
-		 *
-		 * ...to dump pages 0, 1, and 4097
-		 */
-
-		while ((optind < argc) &&
-		       (extra_arg_count < GUP_TEST_MAX_PAGES_TO_DUMP)) {
-			/*
-			 * Do the 1-based indexing here, so that the user can
-			 * use normal 0-based indexing on the command line.
-			 */
-			long page_index = strtol(argv[optind], 0, 0) + 1;
-
-			gup.which_pages[extra_arg_count] = page_index;
-			extra_arg_count++;
-			optind++;
-		}
+TEST_F(gup_test, get_user_pages_fast)
+{
+	/* Tests the lockless get_user_pages_fast() path */
+	int i;
+
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
+
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
+
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, GUP_FAST_BENCHMARK, &gup), 0);
 	}
+}
+
+TEST_F(gup_test, pin_user_pages_fast)
+{
+	/* Tests the lockless pin_user_pages_fast() path */
+	int i;
 
-	ksft_print_header();
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
 
-	if (hugetlb) {
-		unsigned long hp_size = default_huge_page_size();
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
 
-		if (!hp_size)
-			ksft_exit_skip("HugeTLB is unavailable\n");
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
 
-		size = (size + hp_size - 1) & ~(hp_size - 1);
-		if (!hugetlb_setup_default(size / hp_size))
-			ksft_exit_skip("Not enough huge pages\n");
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, PIN_FAST_BENCHMARK, &gup), 0);
 	}
+}
 
-	ksft_set_plan(nthreads);
+TEST_F(gup_test, pin_user_pages_longterm)
+{
+	/* Tests pin_user_pages() with FOLL_LONGTERM */
+	int i;
 
-	filed = open(file, O_RDWR|O_CREAT, 0664);
-	if (filed < 0)
-		ksft_exit_fail_msg("Unable to open %s: %s\n", file, strerror(errno));
+	for (i = 0; i < (int)ARRAY_SIZE(nr_pages_list); i++) {
+		struct gup_test gup = { 0 };
 
-	gup.nr_pages_per_call = nr_pages;
-	if (write)
-		gup.gup_flags |= FOLL_WRITE;
+		gup.addr = (unsigned long)self->addr;
+		gup.size = self->size;
+		gup.nr_pages_per_call = nr_pages_list[i] < 0 ?
+			self->size / psize() : nr_pages_list[i];
 
-	gup_fd = open(GUP_TEST_FILE, O_RDWR);
-	if (gup_fd == -1) {
-		switch (errno) {
-		case EACCES:
-			if (getuid())
-				ksft_print_msg("Please run this test as root\n");
-			break;
-		case ENOENT:
-			if (opendir("/sys/kernel/debug") == NULL)
-				ksft_print_msg("mount debugfs at /sys/kernel/debug\n");
-			ksft_print_msg("check if CONFIG_GUP_TEST is enabled in kernel config\n");
-			break;
-		default:
-			ksft_print_msg("failed to open %s: %s\n", GUP_TEST_FILE, strerror(errno));
-			break;
-		}
-		ksft_test_result_skip("Please run this test as root\n");
-		ksft_exit_pass();
+		if (variant->write)
+			gup.gup_flags |= FOLL_WRITE;
+
+		TH_LOG("nr_pages_per_call=%u", gup.nr_pages_per_call);
+		ASSERT_EQ(ioctl(self->gup_fd, PIN_LONGTERM_BENCHMARK, &gup), 0);
 	}
+}
+
+TEST(dump_user_pages_sparse_indices)
+{
+	/* Tests sparse multi-index which_pages[] inputs. */
+	struct gup_test gup = { 0 };
+	unsigned long size = 128 * MB;
+	int zero_fd, gup_fd;
+	char *addr, *p;
+
+	zero_fd = open("/dev/zero", O_RDWR);
+	ASSERT_GE(zero_fd, 0);
 
-	p = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, filed, 0);
-	if (p == MAP_FAILED)
-		ksft_exit_fail_msg("mmap: %s\n", strerror(errno));
-	gup.addr = (unsigned long)p;
+	gup_fd = open(GUP_TEST_FILE, O_RDWR);
+	ASSERT_GE(gup_fd, 0);
+
+	addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, zero_fd, 0);
+	close(zero_fd);
+	ASSERT_NE(addr, MAP_FAILED);
 
-	if (thp == 1)
-		madvise(p, size, MADV_HUGEPAGE);
-	else if (thp == 0)
-		madvise(p, size, MADV_NOHUGEPAGE);
+	madvise(addr, size, MADV_HUGEPAGE);
 
-	/* Fault them in here, from user space. */
-	for (; (unsigned long)p < gup.addr + size; p += psize())
+	for (p = addr; (unsigned long)p < (unsigned long)addr + size;
+	     p += psize())
 		p[0] = 0;
 
-	tid = malloc(sizeof(pthread_t) * nthreads);
-	assert(tid);
-	for (i = 0; i < nthreads; i++) {
-		ret = pthread_create(&tid[i], NULL, gup_thread, &gup);
-		assert(ret == 0);
-	}
-	for (i = 0; i < nthreads; i++) {
-		ret = pthread_join(tid[i], NULL);
-		assert(ret == 0);
+	gup.addr = (unsigned long)addr;
+	gup.size = size;
+	gup.nr_pages_per_call = size / psize();
+	gup.gup_flags = FOLL_WRITE;
+	gup.which_pages[0] = 1;
+	gup.which_pages[1] = 20;
+	gup.which_pages[2] = 0x1001;
+
+	/*
+	 * Preserve the old "./gup_test -ct -F 0x1 0 19 0x1000" sparse dump
+	 * coverage after removing command-line parsing from this binary.
+	 */
+	ASSERT_EQ(ioctl(gup_fd, DUMP_USER_PAGES_TEST, &gup), 0);
+
+	munmap(addr, size);
+	close(gup_fd);
+}
+
+int main(int argc, char **argv)
+{
+	int fd;
+	char *file = "/dev/zero";
+
+	fd = open(file, O_RDWR);
+	if (fd < 0) {
+		ksft_print_header();
+		ksft_exit_fail_msg("Unable to open %s: %s\n", file, strerror(errno));
 	}
+	close(fd);
 
-	free(tid);
+	fd = open(GUP_TEST_FILE, O_RDWR);
+	if (fd == -1) {
+		ksft_print_header();
+		if (errno == EACCES)
+			ksft_exit_skip("Please run this test as root\n");
+		if (errno == ENOENT) {
+			DIR *debugfs = opendir("/sys/kernel/debug");
+
+			if (!debugfs) {
+				ksft_exit_skip("Mount debugfs at /sys/kernel/debug\n");
+			} else {
+				closedir(debugfs);
+				ksft_exit_skip("Check CONFIG_GUP_TEST in kernel config\n");
+			}
+		}
+		ksft_exit_skip("failed to open %s: %s\n", GUP_TEST_FILE, strerror(errno));
+	}
+	close(fd);
 
-	ksft_exit_pass();
+	return test_harness_run(argc, argv);
 }
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index 8c296dedf047..56df0dcc9640 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -130,30 +130,6 @@ test_selected() {
 	fi
 }
 
-run_gup_matrix() {
-    # -t: thp=on, -T: thp=off, -H: hugetlb=on
-    local hugetlb_mb=256
-
-    for huge in -t -T "-H -m $hugetlb_mb"; do
-        # -u: gup-fast, -U: gup-basic, -a: pin-fast, -b: pin-basic, -L: pin-longterm
-        for test_cmd in -u -U -a -b -L; do
-            # -w: write=1, -W: write=0
-            for write in -w -W; do
-                # -S: shared
-                for share in -S " "; do
-                    # -n: How many pages to fetch together?  512 is special
-                    # because it's default thp size (or 2M on x86), 123 to
-                    # just test partial gup when hit a huge in whatever form
-                    for num in "-n 1" "-n 512" "-n 123" "-n -1"; do
-                        CATEGORY="gup_test" run_test ./gup_test \
-                                $huge $test_cmd $write $share $num
-                    done
-                done
-            done
-        done
-    done
-}
-
 # filter 64bit architectures
 ARCH64STR="arm64 mips64 parisc64 ppc64 ppc64le riscv64 s390x sparc64 x86_64"
 if [ -z "$ARCH" ]; then
@@ -276,18 +252,7 @@ fi
 
 CATEGORY="mmap" run_test ./map_fixed_noreplace
 
-if $RUN_ALL; then
-    run_gup_matrix
-else
-    # get_user_pages_fast() benchmark
-    CATEGORY="gup_test" run_test ./gup_test -u -n 1
-    CATEGORY="gup_test" run_test ./gup_test -u -n -1
-    # pin_user_pages_fast() benchmark
-    CATEGORY="gup_test" run_test ./gup_test -a -n 1
-    CATEGORY="gup_test" run_test ./gup_test -a -n -1
-fi
-# Dump pages 0, 19, and 4096, using pin_user_pages:
-CATEGORY="gup_test" run_test ./gup_test -ct -F 0x1 0 19 0x1000
+CATEGORY="gup_test" run_test ./gup_test
 CATEGORY="gup_test" run_test ./gup_longterm
 
 CATEGORY="userfaultfd" run_test ./uffd-unit-tests
-- 
2.39.5


^ permalink raw reply related

* [PATCH v5 4/5] tools/mm: add a standalone GUP microbenchmark
From: Sarthak Sharma @ 2026-07-16 12:32 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand
  Cc: Lorenzo Stoakes, Liam R . Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Shuah Khan, Shuah Khan,
	Jason Gunthorpe, John Hubbard, Peter Xu, Leon Romanovsky, Zi Yan,
	Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
	Lance Yang, Jonathan Corbet, Mark Brown, Anshuman Khandual,
	linux-mm, linux-kselftest, linux-doc, linux-kernel,
	Sarthak Sharma
In-Reply-To: <20260716123226.197736-1-sarthak.sharma@arm.com>

Add a command-line tool for benchmarking get_user_pages fast-path
(GUP_FAST), pin_user_pages fast-path (PIN_FAST), and pin_user_pages
longterm (PIN_LONGTERM) via the CONFIG_GUP_TEST debugfs interface.

When invoked without arguments, gup_bench runs the benchmark subset
of configurations covered by run_gup_matrix() in run_vmtests.sh: all
three GUP commands across read/write, private/shared mappings, and a
range of page counts, with THP on/off for regular mappings and hugetlb
for huge page mappings. Restore HugeTLB settings after each hugetlb
benchmark run. Validate numeric command-line arguments instead of
relying on atoi().

This tool is a mix of reused and new logic. The mapping/setup path comes
from selftests/mm/gup_test.c, while the default benchmark matrix matches
the benchmark subset of run_gup_matrix() in run_vmtests.sh. The standalone
CLI and tools/mm integration are added here and the HugeTLB setup code is
shared via tools/lib/mm.

Add gup_bench to BUILD_TARGETS and INSTALL_TARGETS in tools/mm/Makefile,
link it against the shared tools/lib/mm hugepage helpers, and ignore the
resulting binary in tools/mm/.gitignore. While here, also add the
missing thp_swap_allocator_test entry to .gitignore.

Add tools/mm/gup_bench.c to the GUP entry in MAINTAINERS.

Suggested-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Sarthak Sharma <sarthak.sharma@arm.com>
---
 MAINTAINERS          |   1 +
 tools/mm/.gitignore  |   2 +
 tools/mm/Makefile    |  10 +-
 tools/mm/gup_bench.c | 482 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 492 insertions(+), 3 deletions(-)
 create mode 100644 tools/mm/gup_bench.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 26b06d80ab45..7d121dee9f0f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17024,6 +17024,7 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
 F:	mm/gup.c
 F:	mm/gup_test.c
 F:	mm/gup_test.h
+F:	tools/mm/gup_bench.c
 F:	tools/testing/selftests/mm/gup_longterm.c
 F:	tools/testing/selftests/mm/gup_test.c
 
diff --git a/tools/mm/.gitignore b/tools/mm/.gitignore
index 922879f93fc8..154d740be02e 100644
--- a/tools/mm/.gitignore
+++ b/tools/mm/.gitignore
@@ -2,3 +2,5 @@
 slabinfo
 page-types
 page_owner_sort
+thp_swap_allocator_test
+gup_bench
diff --git a/tools/mm/Makefile b/tools/mm/Makefile
index f5725b5c23aa..d82cc8c43ee0 100644
--- a/tools/mm/Makefile
+++ b/tools/mm/Makefile
@@ -3,13 +3,14 @@
 #
 include ../scripts/Makefile.include
 
-BUILD_TARGETS=page-types slabinfo page_owner_sort thp_swap_allocator_test
+BUILD_TARGETS=page-types slabinfo page_owner_sort thp_swap_allocator_test gup_bench
 INSTALL_TARGETS = $(BUILD_TARGETS) thpmaps
 
 LIB_DIR = ../lib/api
 LIBS = $(LIB_DIR)/libapi.a
+GUP_BENCH_OBJS = gup_bench.c ../lib/mm/hugepage_settings.c ../lib/mm/file_utils.c
 
-CFLAGS += -Wall -Wextra -I../lib/ -pthread
+CFLAGS += -Wall -Wextra -I../lib/ -I../.. -pthread
 LDFLAGS += $(LIBS) -pthread
 
 all: $(BUILD_TARGETS)
@@ -22,8 +23,11 @@ $(LIBS):
 %: %.c
 	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
 
+gup_bench: $(GUP_BENCH_OBJS) $(LIBS)
+	$(CC) $(CFLAGS) -o $@ $(GUP_BENCH_OBJS) $(LDFLAGS)
+
 clean:
-	$(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test
+	$(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test gup_bench
 	make -C $(LIB_DIR) clean
 
 sbindir ?= /usr/sbin
diff --git a/tools/mm/gup_bench.c b/tools/mm/gup_bench.c
new file mode 100644
index 000000000000..497b8f3f6ac4
--- /dev/null
+++ b/tools/mm/gup_bench.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Microbenchmark for get_user_pages (GUP) kernel interfaces.
+ *
+ * Exercises GUP_FAST_BENCHMARK, PIN_FAST_BENCHMARK, and
+ * PIN_LONGTERM_BENCHMARK via the CONFIG_GUP_TEST debugfs interface.
+ *
+ * Example use:
+ *   # Run the full matrix (all commands, access modes, page counts):
+ *   ./gup_bench
+ *
+ *   # Single run: pin_user_pages_fast, 512 pages, write access, hugetlb:
+ *   ./gup_bench -a -n 512 -w -H
+ *
+ * Requires CONFIG_GUP_TEST=y and debugfs mounted at /sys/kernel/debug.
+ * Must be run as root.
+ */
+
+#define __SANE_USERSPACE_TYPES__ // Use ll64
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+#include <stdint.h>
+#include <limits.h>
+#include <string.h>
+#include <mm/gup_test.h>
+#include <mm/hugepage_settings.h>
+
+#define MB (1UL << 20)
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#endif
+
+/* Just the flags we need, copied from the kernel internals. */
+#define FOLL_WRITE	0x01	/* check pte is writable */
+
+#define GUP_TEST_FILE "/sys/kernel/debug/gup_test"
+
+static unsigned int psize(void)
+{
+	static unsigned int __page_size;
+
+	if (!__page_size)
+		__page_size = sysconf(_SC_PAGESIZE);
+	return __page_size;
+}
+
+static unsigned long cmd;
+static const char *bench_label;
+static int gup_fd, repeats = 1;
+static unsigned long size = 128 * MB;
+static atomic_int bench_error;
+/* Serialize prints */
+static pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static const unsigned long bench_cmds[] = {
+	GUP_FAST_BENCHMARK,
+	PIN_FAST_BENCHMARK,
+	PIN_LONGTERM_BENCHMARK,
+};
+
+static const int bench_thp_modes[] = { 1, 0 };	/* on, off */
+static const int bench_nr_pages_list[] = { 1, 512, 123, -1 };
+
+static const char *cmd_to_str(unsigned long cmd)
+{
+	switch (cmd) {
+	case GUP_FAST_BENCHMARK:
+		return "GUP_FAST_BENCHMARK";
+	case PIN_FAST_BENCHMARK:
+		return "PIN_FAST_BENCHMARK";
+	case PIN_LONGTERM_BENCHMARK:
+		return "PIN_LONGTERM_BENCHMARK";
+	}
+	return "Unknown command";
+}
+
+static long parse_long_arg_base(const char *arg, const char *name, int base)
+{
+	char *end;
+	long val;
+
+	errno = 0;
+	val = strtol(arg, &end, base);
+	if (errno || end == arg || *end != '\0') {
+		fprintf(stderr, "Invalid %s '%s'\n", name, arg);
+		exit(1);
+	}
+	return val;
+}
+
+static long parse_long_arg(const char *arg, const char *name)
+{
+	return parse_long_arg_base(arg, name, 10);
+}
+
+static long parse_positive_long_arg(const char *arg, const char *name)
+{
+	long val = parse_long_arg(arg, name);
+
+	if (val < 1) {
+		fprintf(stderr, "Invalid %s '%s'\n", name, arg);
+		exit(1);
+	}
+
+	return val;
+}
+
+struct bench_run {
+	unsigned long cmd;
+	int thp;		/* -1: default, 0: off, 1: on */
+	bool hugetlb;
+	bool write;		/* whether to set FOLL_WRITE */
+	bool shared;
+	int nr_pages;		/* -1 means all pages (size / psize()) */
+	unsigned long size;
+	char *file;
+	int nthreads;
+	unsigned int gup_flags;
+};
+
+void *gup_thread(void *data)
+{
+	struct gup_test gup = *(struct gup_test *)data;
+	int i, status;
+
+	for (i = 0; i < repeats; i++) {
+		gup.size = size;
+		status = ioctl(gup_fd, cmd, &gup);
+		if (status) {
+			bench_error = 1;
+			break;
+		}
+
+		pthread_mutex_lock(&print_mutex);
+		printf("%s time: get:%lld put:%lld us",
+		       bench_label, gup.get_delta_usec,
+		       gup.put_delta_usec);
+		if (gup.size != size)
+			printf(", truncated (size: %lld)", gup.size);
+		printf("\n");
+		pthread_mutex_unlock(&print_mutex);
+	}
+
+	return NULL;
+}
+
+static int run_bench(struct bench_run *run)
+{
+	struct gup_test gup = { 0 };
+	int zero_fd, i, ret, started_threads = 0;
+	unsigned long nr_pages_per_call;
+	int flags = MAP_PRIVATE;
+	pthread_t *tid;
+	char label[128];
+	char *p;
+	bool restore_hugetlb = false;
+
+	/* Set globals consumed by gup_thread */
+	cmd = run->cmd;
+	size = run->size;
+	bench_error = 0;
+
+	if (run->hugetlb) {
+		unsigned long hp_size = default_huge_page_size();
+
+		if (!hp_size) {
+			fprintf(stderr, "Could not determine huge page size\n");
+			return 1;
+		}
+
+		if (size > ULONG_MAX - (hp_size - 1)) {
+			fprintf(stderr, "HugeTLB mapping size is too large\n");
+			return 1;
+		}
+
+		size = (size + hp_size - 1) & ~(hp_size - 1);
+		if (!hugetlb_setup_default(size / hp_size)) {
+			fprintf(stderr, "Not enough huge pages\n");
+			hugetlb_restore_settings();
+			return 1;
+		}
+		restore_hugetlb = true;
+		flags |= (MAP_HUGETLB | MAP_ANONYMOUS);
+	}
+
+	if (run->shared) {
+		flags &= ~MAP_PRIVATE;
+		flags |= MAP_SHARED;
+	}
+
+	nr_pages_per_call = run->nr_pages < 0 ? size / psize() :
+		(unsigned long)run->nr_pages;
+
+	if (nr_pages_per_call > UINT_MAX) {
+		fprintf(stderr, "Page count is too large\n");
+		if (restore_hugetlb)
+			hugetlb_restore_settings();
+		return 1;
+	}
+	gup.nr_pages_per_call = nr_pages_per_call;
+
+	gup.gup_flags = run->gup_flags;
+	if (run->write)
+		gup.gup_flags |= FOLL_WRITE;
+
+	snprintf(label, sizeof(label), "%s (nr_pages=%-4u %s %s %s %s)",
+		 cmd_to_str(run->cmd),
+		 gup.nr_pages_per_call,
+		 run->write  ? "write"   : "read",
+		 run->shared ? "shared"  : "private",
+		 run->hugetlb ? "hugetlb=on" : "hugetlb=off",
+		 run->hugetlb ? "thp=off" :
+		 (run->thp == 1 ? "thp=on" :
+		 (run->thp == 0 ? "thp=off" : "thp=default")));
+	bench_label = label;
+
+	zero_fd = open(run->file, O_RDWR);
+	if (zero_fd < 0) {
+		fprintf(stderr, "Unable to open %s: %s\n", run->file, strerror(errno));
+		if (restore_hugetlb)
+			hugetlb_restore_settings();
+		return 1;
+	}
+
+	p = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, zero_fd, 0);
+	close(zero_fd);
+	if (p == MAP_FAILED) {
+		fprintf(stderr, "mmap: %s\n", strerror(errno));
+		if (restore_hugetlb)
+			hugetlb_restore_settings();
+		return 1;
+	}
+	gup.addr = (unsigned long)p;
+
+	if (run->thp == 1)
+		madvise(p, size, MADV_HUGEPAGE);
+	else if (run->thp == 0)
+		madvise(p, size, MADV_NOHUGEPAGE);
+
+	/* Fault them in here, from user space. */
+	for (; (unsigned long)p < gup.addr + size; p += psize())
+		p[0] = 0;
+
+	tid = malloc(sizeof(pthread_t) * run->nthreads);
+	if (!tid) {
+		fprintf(stderr, "Failed to allocate %d threads: %s\n",
+			run->nthreads, strerror(errno));
+		munmap((void *)gup.addr, size);
+		if (restore_hugetlb)
+			hugetlb_restore_settings();
+		return 1;
+	}
+
+	for (i = 0; i < run->nthreads; i++) {
+		ret = pthread_create(&tid[i], NULL, gup_thread, &gup);
+		if (ret) {
+			fprintf(stderr, "pthread_create failed: %s\n", strerror(ret));
+			bench_error = 1;
+			break;
+		}
+		started_threads++;
+	}
+	for (i = 0; i < started_threads; i++) {
+		ret = pthread_join(tid[i], NULL);
+		if (ret) {
+			fprintf(stderr, "pthread_join failed: %s\n", strerror(ret));
+			bench_error = 1;
+		}
+	}
+
+	free(tid);
+	munmap((void *)gup.addr, size);
+	if (restore_hugetlb)
+		hugetlb_restore_settings();
+
+	return bench_error ? 1 : 0;
+}
+
+static int run_matrix(void)
+{
+	unsigned int c, t, w, s, n;
+	int ret = 0;
+
+	for (c = 0; c < ARRAY_SIZE(bench_cmds); c++) {
+		for (w = 0; w <= 1; w++) {
+			for (s = 0; s <= 1; s++) {
+				for (t = 0; t < ARRAY_SIZE(bench_thp_modes); t++) {
+					for (n = 0; n < ARRAY_SIZE(bench_nr_pages_list); n++) {
+						struct bench_run run = {
+							.cmd	  = bench_cmds[c],
+							.thp	  = bench_thp_modes[t],
+							.hugetlb  = false,
+							.write	  = w,
+							.shared	  = s,
+							.nr_pages = bench_nr_pages_list[n],
+							.size	  = 128 * MB,
+							.file	  = "/dev/zero",
+							.nthreads = 1,
+						};
+						ret |= run_bench(&run);
+					}
+				}
+				/* hugetlb: 256M to match run_gup_matrix() in run_vmtests.sh */
+				for (n = 0; n < ARRAY_SIZE(bench_nr_pages_list); n++) {
+					struct bench_run run = {
+						.cmd	  = bench_cmds[c],
+						.thp	  = -1,
+						.hugetlb  = true,
+						.write	  = w,
+						.shared	  = s,
+						.nr_pages = bench_nr_pages_list[n],
+						.size	  = 256 * MB,
+						.file	  = "/dev/zero",
+						.nthreads = 1,
+					};
+					ret |= run_bench(&run);
+				}
+			}
+		}
+	}
+	return ret;
+}
+
+int main(int argc, char **argv)
+{
+	struct bench_run run = {
+		.cmd	  = GUP_FAST_BENCHMARK,
+		.thp	  = -1,
+		.hugetlb  = false,
+		.write	  = true,
+		.shared	  = false,
+		.nr_pages = 1,
+		.size	  = 128 * MB,
+		.file	  = "/dev/zero",
+		.nthreads = 1,
+	};
+	int opt, result;
+
+	while ((opt = getopt(argc, argv, "m:r:n:F:f:aj:tTLuwWSH")) != -1) {
+		switch (opt) {
+		/* Command selection */
+		case 'u':
+			run.cmd = GUP_FAST_BENCHMARK;
+			break;
+		case 'a':
+			run.cmd = PIN_FAST_BENCHMARK;
+			break;
+		case 'L':
+			run.cmd = PIN_LONGTERM_BENCHMARK;
+			break;
+
+		/* Memory type */
+		case 'H':
+			run.hugetlb = true;
+			break;
+		case 't':
+			run.thp = 1;
+			break;
+		case 'T':
+			run.thp = 0;
+			break;
+
+		/* Access mode */
+		case 'w':
+			run.write = true;
+			break;
+		case 'W':
+			run.write = false;
+			break;
+		case 'S':
+			run.shared = true;
+			break;
+
+		/* Mapping */
+		case 'f':
+			run.file = optarg;
+			break;
+
+		/* Sizing and iteration */
+		case 'm':
+			run.size = parse_positive_long_arg(optarg, "size");
+			if (run.size > ULONG_MAX / MB) {
+				fprintf(stderr, "Invalid size '%s'\n", optarg);
+				exit(1);
+			}
+			run.size *= MB;
+			break;
+		case 'n': {
+			long val;
+
+			val = parse_long_arg(optarg, "page count");
+			if (val != -1 && (val < 1 || val > INT_MAX)) {
+				fprintf(stderr, "Invalid page count '%s'\n", optarg);
+				exit(1);
+			}
+			run.nr_pages = val;
+			break;
+		}
+		case 'r': {
+			long val;
+
+			val = parse_positive_long_arg(optarg, "repeat count");
+			if (val > INT_MAX) {
+				fprintf(stderr, "Invalid repeat count '%s'\n", optarg);
+				exit(1);
+			}
+			repeats = val;
+			break;
+		}
+		case 'j': {
+			long val;
+
+			val = parse_positive_long_arg(optarg, "thread count");
+			if (val > INT_MAX ||
+			    (size_t)val > SIZE_MAX / sizeof(pthread_t)) {
+				fprintf(stderr, "Invalid thread count '%s'\n", optarg);
+				exit(1);
+			}
+			run.nthreads = val;
+			break;
+		}
+
+		/* Advanced */
+		case 'F': {
+			long val;
+
+			val = parse_long_arg_base(optarg, "GUP flags", 0);
+			if (val < 0 || val > UINT_MAX) {
+				fprintf(stderr, "Invalid GUP flags '%s'\n", optarg);
+				exit(1);
+			}
+
+			run.gup_flags = val;
+			break;
+		}
+
+		default:
+			fprintf(stderr, "Wrong argument\n");
+			exit(1);
+		}
+	}
+
+	if (optind != argc) {
+		fprintf(stderr, "Unexpected argument '%s'\n", argv[optind]);
+		exit(1);
+	}
+
+	gup_fd = open(GUP_TEST_FILE, O_RDWR);
+	if (gup_fd == -1) {
+		if (errno == EACCES) {
+			fprintf(stderr, "Please run as root\n");
+		} else if (errno == ENOENT) {
+			DIR *debugfs = opendir("/sys/kernel/debug");
+
+			if (!debugfs) {
+				fprintf(stderr, "Mount debugfs at /sys/kernel/debug\n");
+			} else {
+				closedir(debugfs);
+				fprintf(stderr, "Check CONFIG_GUP_TEST in kernel config\n");
+			}
+		} else {
+			fprintf(stderr, "Failed to open %s: %s\n", GUP_TEST_FILE,
+				strerror(errno));
+		}
+		exit(1);
+	}
+
+	result = (argc == 1) ? run_matrix() : run_bench(&run);
+	close(gup_fd);
+	return result;
+}
-- 
2.39.5


^ permalink raw reply related

* [PATCH v5 3/5] tools/lib/mm: move hugepage_settings out of selftests
From: Sarthak Sharma @ 2026-07-16 12:32 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand
  Cc: Lorenzo Stoakes, Liam R . Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Shuah Khan, Shuah Khan,
	Jason Gunthorpe, John Hubbard, Peter Xu, Leon Romanovsky, Zi Yan,
	Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
	Lance Yang, Jonathan Corbet, Mark Brown, Anshuman Khandual,
	linux-mm, linux-kselftest, linux-doc, linux-kernel,
	Sarthak Sharma
In-Reply-To: <20260716123226.197736-1-sarthak.sharma@arm.com>

Move hugepage_settings.[ch] from tools/testing/selftests/mm/ to
tools/lib/mm/ so the THP and HugeTLB helpers can be shared more easily
between selftests and other tools.

Keep the helpers exposed to mm selftests through vm_util.h where possible,
and use direct <mm/hugepage_settings.h> includes for users that do not
include vm_util.h. Adjust the selftests/mm build to compile the moved
implementation from its new location.

Remove the remaining kselftest dependency from hugepage_settings.c by
replacing ksft_print_msg() calls with TAP comment style printf()
diagnostics.

Signed-off-by: Sarthak Sharma <sarthak.sharma@arm.com>
---
 .../selftests => lib}/mm/hugepage_settings.c    | 17 ++++++++++++-----
 .../selftests => lib}/mm/hugepage_settings.h    |  0
 tools/testing/selftests/mm/Makefile             |  6 ++++--
 tools/testing/selftests/mm/compaction_test.c    |  2 +-
 tools/testing/selftests/mm/cow.c                |  1 -
 .../selftests/mm/folio_split_race_test.c        |  1 -
 tools/testing/selftests/mm/guard-regions.c      |  1 -
 tools/testing/selftests/mm/gup_longterm.c       |  1 -
 tools/testing/selftests/mm/gup_test.c           |  1 -
 tools/testing/selftests/mm/hmm-tests.c          |  6 +++---
 tools/testing/selftests/mm/hugetlb-madvise.c    |  1 -
 tools/testing/selftests/mm/hugetlb-mmap.c       |  1 -
 tools/testing/selftests/mm/hugetlb-mremap.c     |  1 -
 tools/testing/selftests/mm/hugetlb-shm.c        |  1 -
 .../testing/selftests/mm/hugetlb-soft-offline.c |  2 +-
 tools/testing/selftests/mm/hugetlb-vmemmap.c    |  1 -
 tools/testing/selftests/mm/hugetlb_dio.c        |  1 -
 .../selftests/mm/hugetlb_fault_after_madv.c     |  1 -
 .../testing/selftests/mm/hugetlb_madv_vs_map.c  |  1 -
 tools/testing/selftests/mm/khugepaged.c         |  1 -
 tools/testing/selftests/mm/ksm_tests.c          |  1 -
 tools/testing/selftests/mm/migration.c          |  5 ++---
 tools/testing/selftests/mm/pagemap_ioctl.c      |  1 -
 tools/testing/selftests/mm/prctl_thp_disable.c  |  1 -
 tools/testing/selftests/mm/protection_keys.c    |  2 +-
 tools/testing/selftests/mm/soft-dirty.c         |  1 -
 .../testing/selftests/mm/split_huge_page_test.c |  1 -
 tools/testing/selftests/mm/thuge-gen.c          |  1 -
 tools/testing/selftests/mm/transhuge-stress.c   |  1 -
 tools/testing/selftests/mm/uffd-common.h        |  1 -
 tools/testing/selftests/mm/uffd-wp-mremap.c     |  2 +-
 .../testing/selftests/mm/va_high_addr_switch.c  |  1 -
 tools/testing/selftests/mm/vm_util.h            |  1 +
 33 files changed, 26 insertions(+), 40 deletions(-)
 rename tools/{testing/selftests => lib}/mm/hugepage_settings.c (97%)
 rename tools/{testing/selftests => lib}/mm/hugepage_settings.h (100%)

diff --git a/tools/testing/selftests/mm/hugepage_settings.c b/tools/lib/mm/hugepage_settings.c
similarity index 97%
rename from tools/testing/selftests/mm/hugepage_settings.c
rename to tools/lib/mm/hugepage_settings.c
index 202c8e6b0246..8d9e44727edb 100644
--- a/tools/testing/selftests/mm/hugepage_settings.c
+++ b/tools/lib/mm/hugepage_settings.c
@@ -9,11 +9,16 @@
 #include <string.h>
 #include <unistd.h>
 
-#include "vm_util.h"
+#include "file_utils.h"
 #include "hugepage_settings.h"
 
 #define THP_SYSFS "/sys/kernel/mm/transparent_hugepage/"
 #define MAX_SETTINGS_DEPTH 4
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#endif
+
 static struct thp_settings settings_stack[MAX_SETTINGS_DEPTH];
 static int settings_index;
 static struct thp_settings saved_settings;
@@ -403,8 +408,7 @@ int detect_hugetlb_page_sizes(unsigned long sizes[], int max)
 		if (sscanf(entry->d_name, "hugepages-%zukB", &kb) != 1)
 			continue;
 		sizes[count++] = kb * 1024;
-		ksft_print_msg("[INFO] detected hugetlb page size: %zu KiB\n",
-			       kb);
+		printf("# [INFO] detected hugetlb page size: %zu KiB\n", kb);
 	}
 	closedir(dir);
 	return count;
@@ -538,7 +542,8 @@ unsigned long hugetlb_setup(unsigned long nr, unsigned long sizes[],
 		return 0;
 
 	if (nr_enabled > max) {
-		ksft_print_msg("detected %d huge page sizes, will only test %d\n", nr_enabled, max);
+		printf("# detected %d huge page sizes, will only test %d\n",
+		       nr_enabled, max);
 		nr_enabled = max;
 	}
 
@@ -610,8 +615,10 @@ static void hugepage_restore_settings_atexit(void)
 
 static void hugepage_restore_settings_sighandler(int sig)
 {
+	(void)sig;
+
 	/* exit() will invoke the hugepage_restore_settings_atexit handler. */
-	exit(KSFT_FAIL);
+	exit(EXIT_FAILURE);
 }
 
 void hugepage_save_settings(bool thp, bool hugetlb)
diff --git a/tools/testing/selftests/mm/hugepage_settings.h b/tools/lib/mm/hugepage_settings.h
similarity index 100%
rename from tools/testing/selftests/mm/hugepage_settings.h
rename to tools/lib/mm/hugepage_settings.h
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index 70d6a9ccf9c9..e77c4cc41b47 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -188,8 +188,10 @@ TEST_FILES += write_hugetlb_memory.sh
 
 include ../lib.mk
 
-$(TEST_GEN_PROGS): vm_util.c hugepage_settings.c $(top_srcdir)/tools/lib/mm/file_utils.c
-$(TEST_GEN_FILES): vm_util.c hugepage_settings.c $(top_srcdir)/tools/lib/mm/file_utils.c
+$(TEST_GEN_PROGS): vm_util.c $(top_srcdir)/tools/lib/mm/hugepage_settings.c \
+		   $(top_srcdir)/tools/lib/mm/file_utils.c
+$(TEST_GEN_FILES): vm_util.c $(top_srcdir)/tools/lib/mm/hugepage_settings.c \
+		   $(top_srcdir)/tools/lib/mm/file_utils.c
 
 $(OUTPUT)/uffd-stress: uffd-common.c
 $(OUTPUT)/uffd-unit-tests: uffd-common.c
diff --git a/tools/testing/selftests/mm/compaction_test.c b/tools/testing/selftests/mm/compaction_test.c
index 5b582588e015..b2eaa490e7c2 100644
--- a/tools/testing/selftests/mm/compaction_test.c
+++ b/tools/testing/selftests/mm/compaction_test.c
@@ -15,9 +15,9 @@
 #include <errno.h>
 #include <unistd.h>
 #include <string.h>
+#include <mm/hugepage_settings.h>
 
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #define MAP_SIZE_MB	100
 #define MAP_SIZE	(MAP_SIZE_MB * 1024 * 1024)
diff --git a/tools/testing/selftests/mm/cow.c b/tools/testing/selftests/mm/cow.c
index 0c627ea89ff7..3c3d129c91b2 100644
--- a/tools/testing/selftests/mm/cow.c
+++ b/tools/testing/selftests/mm/cow.c
@@ -29,7 +29,6 @@
 #include "../../../../mm/gup_test.h"
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 static size_t pagesize;
 static int pagemap_fd;
diff --git a/tools/testing/selftests/mm/folio_split_race_test.c b/tools/testing/selftests/mm/folio_split_race_test.c
index 6329e37fff4c..496b74b37476 100644
--- a/tools/testing/selftests/mm/folio_split_race_test.c
+++ b/tools/testing/selftests/mm/folio_split_race_test.c
@@ -25,7 +25,6 @@
 #include <unistd.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 uint64_t page_size;
 uint64_t pmd_pagesize;
diff --git a/tools/testing/selftests/mm/guard-regions.c b/tools/testing/selftests/mm/guard-regions.c
index b21df3040b1c..ed967b3c7f37 100644
--- a/tools/testing/selftests/mm/guard-regions.c
+++ b/tools/testing/selftests/mm/guard-regions.c
@@ -21,7 +21,6 @@
 #include <sys/uio.h>
 #include <unistd.h>
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #include "../pidfd/pidfd.h"
 
diff --git a/tools/testing/selftests/mm/gup_longterm.c b/tools/testing/selftests/mm/gup_longterm.c
index eb8963e9d98f..68c840bae082 100644
--- a/tools/testing/selftests/mm/gup_longterm.c
+++ b/tools/testing/selftests/mm/gup_longterm.c
@@ -29,7 +29,6 @@
 #include "../../../../mm/gup_test.h"
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 static size_t pagesize;
 static int nr_hugetlbsizes;
diff --git a/tools/testing/selftests/mm/gup_test.c b/tools/testing/selftests/mm/gup_test.c
index 3f841a96f870..5f44761dbec0 100644
--- a/tools/testing/selftests/mm/gup_test.c
+++ b/tools/testing/selftests/mm/gup_test.c
@@ -14,7 +14,6 @@
 #include <mm/gup_test.h>
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define MB (1UL << 20)
 
diff --git a/tools/testing/selftests/mm/hmm-tests.c b/tools/testing/selftests/mm/hmm-tests.c
index 2f2b9879d100..e1bb031019f2 100644
--- a/tools/testing/selftests/mm/hmm-tests.c
+++ b/tools/testing/selftests/mm/hmm-tests.c
@@ -10,9 +10,6 @@
  * bugs.
  */
 
-#include "kselftest_harness.h"
-#include "hugepage_settings.h"
-
 #include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
@@ -29,6 +26,9 @@
 #include <sys/mman.h>
 #include <sys/ioctl.h>
 #include <sys/time.h>
+#include <mm/hugepage_settings.h>
+
+#include "kselftest_harness.h"
 
 /*
  * This is a private UAPI to the kernel test module so it isn't exported
diff --git a/tools/testing/selftests/mm/hugetlb-madvise.c b/tools/testing/selftests/mm/hugetlb-madvise.c
index 555b4b3d1430..57cf790ca478 100644
--- a/tools/testing/selftests/mm/hugetlb-madvise.c
+++ b/tools/testing/selftests/mm/hugetlb-madvise.c
@@ -14,7 +14,6 @@
 #include <fcntl.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #define MIN_FREE_PAGES	20
 #define NR_HUGE_PAGES	10	/* common number of pages to map/allocate */
diff --git a/tools/testing/selftests/mm/hugetlb-mmap.c b/tools/testing/selftests/mm/hugetlb-mmap.c
index 0f2aad1b7dbd..a458becf5832 100644
--- a/tools/testing/selftests/mm/hugetlb-mmap.c
+++ b/tools/testing/selftests/mm/hugetlb-mmap.c
@@ -18,7 +18,6 @@
 #include <linux/memfd.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #define LENGTH (256UL*1024*1024)
 #define PROTECTION (PROT_READ | PROT_WRITE)
diff --git a/tools/testing/selftests/mm/hugetlb-mremap.c b/tools/testing/selftests/mm/hugetlb-mremap.c
index ed3d92e862d8..9b724af66e93 100644
--- a/tools/testing/selftests/mm/hugetlb-mremap.c
+++ b/tools/testing/selftests/mm/hugetlb-mremap.c
@@ -26,7 +26,6 @@
 #include <stdbool.h>
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define DEFAULT_LENGTH_MB 10UL
 #define MB_TO_BYTES(x) (x * 1024 * 1024)
diff --git a/tools/testing/selftests/mm/hugetlb-shm.c b/tools/testing/selftests/mm/hugetlb-shm.c
index 3ff7f062b7eb..f4514da49e1d 100644
--- a/tools/testing/selftests/mm/hugetlb-shm.c
+++ b/tools/testing/selftests/mm/hugetlb-shm.c
@@ -29,7 +29,6 @@
 #include <sys/mman.h>
 
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define LENGTH (256UL*1024*1024)
 
diff --git a/tools/testing/selftests/mm/hugetlb-soft-offline.c b/tools/testing/selftests/mm/hugetlb-soft-offline.c
index bc202e4ed2bd..20864e7d4e0c 100644
--- a/tools/testing/selftests/mm/hugetlb-soft-offline.c
+++ b/tools/testing/selftests/mm/hugetlb-soft-offline.c
@@ -21,9 +21,9 @@
 #include <sys/mman.h>
 #include <sys/statfs.h>
 #include <sys/types.h>
+#include <mm/hugepage_settings.h>
 
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #ifndef MADV_SOFT_OFFLINE
 #define MADV_SOFT_OFFLINE 101
diff --git a/tools/testing/selftests/mm/hugetlb-vmemmap.c b/tools/testing/selftests/mm/hugetlb-vmemmap.c
index 507df78a158d..c46a656c25a0 100644
--- a/tools/testing/selftests/mm/hugetlb-vmemmap.c
+++ b/tools/testing/selftests/mm/hugetlb-vmemmap.c
@@ -11,7 +11,6 @@
 #include <sys/mman.h>
 #include <fcntl.h>
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define PAGE_COMPOUND_HEAD	(1UL << 15)
 #define PAGE_COMPOUND_TAIL	(1UL << 16)
diff --git a/tools/testing/selftests/mm/hugetlb_dio.c b/tools/testing/selftests/mm/hugetlb_dio.c
index fb4600570e13..9495974eccbe 100644
--- a/tools/testing/selftests/mm/hugetlb_dio.c
+++ b/tools/testing/selftests/mm/hugetlb_dio.c
@@ -20,7 +20,6 @@
 #include <sys/syscall.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #ifndef STATX_DIOALIGN
 #define STATX_DIOALIGN		0x00002000U
diff --git a/tools/testing/selftests/mm/hugetlb_fault_after_madv.c b/tools/testing/selftests/mm/hugetlb_fault_after_madv.c
index 2dc158054f66..56c5a8533e9d 100644
--- a/tools/testing/selftests/mm/hugetlb_fault_after_madv.c
+++ b/tools/testing/selftests/mm/hugetlb_fault_after_madv.c
@@ -10,7 +10,6 @@
 
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #define INLOOP_ITER 100
 
diff --git a/tools/testing/selftests/mm/hugetlb_madv_vs_map.c b/tools/testing/selftests/mm/hugetlb_madv_vs_map.c
index f94549efcc6f..2532a42b98df 100644
--- a/tools/testing/selftests/mm/hugetlb_madv_vs_map.c
+++ b/tools/testing/selftests/mm/hugetlb_madv_vs_map.c
@@ -25,7 +25,6 @@
 #include <unistd.h>
 
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define INLOOP_ITER 100
 
diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c
index 5eac27dd9683..2382973b2f55 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -22,7 +22,6 @@
 #include "linux/magic.h"
 
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define BASE_ADDR ((void *)(1UL << 30))
 static unsigned long hpage_pmd_size;
diff --git a/tools/testing/selftests/mm/ksm_tests.c b/tools/testing/selftests/mm/ksm_tests.c
index a050f4840cfa..cc37492cf32c 100644
--- a/tools/testing/selftests/mm/ksm_tests.c
+++ b/tools/testing/selftests/mm/ksm_tests.c
@@ -15,7 +15,6 @@
 #include "kselftest.h"
 #include <include/vdso/time64.h>
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define KSM_SYSFS_PATH "/sys/kernel/mm/ksm/"
 #define KSM_FP(s) (KSM_SYSFS_PATH s)
diff --git a/tools/testing/selftests/mm/migration.c b/tools/testing/selftests/mm/migration.c
index 29f7492453d4..4fe97c033261 100644
--- a/tools/testing/selftests/mm/migration.c
+++ b/tools/testing/selftests/mm/migration.c
@@ -4,9 +4,6 @@
  * paths in the kernel.
  */
 
-#include "kselftest_harness.h"
-#include "hugepage_settings.h"
-
 #include <strings.h>
 #include <pthread.h>
 #include <numa.h>
@@ -16,6 +13,8 @@
 #include <sys/types.h>
 #include <signal.h>
 #include <time.h>
+
+#include "kselftest_harness.h"
 #include "vm_util.h"
 
 #define TWOMEG		(2<<20)
diff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/selftests/mm/pagemap_ioctl.c
index 6f8971d5b3ce..804372803080 100644
--- a/tools/testing/selftests/mm/pagemap_ioctl.c
+++ b/tools/testing/selftests/mm/pagemap_ioctl.c
@@ -23,7 +23,6 @@
 
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #define PAGEMAP_BITS_ALL		(PAGE_IS_WPALLOWED | PAGE_IS_WRITTEN |	\
 					 PAGE_IS_FILE | PAGE_IS_PRESENT |	\
diff --git a/tools/testing/selftests/mm/prctl_thp_disable.c b/tools/testing/selftests/mm/prctl_thp_disable.c
index d8d9d1de57b8..a4f8451791cb 100644
--- a/tools/testing/selftests/mm/prctl_thp_disable.c
+++ b/tools/testing/selftests/mm/prctl_thp_disable.c
@@ -14,7 +14,6 @@
 #include <sys/wait.h>
 
 #include "kselftest_harness.h"
-#include "hugepage_settings.h"
 #include "vm_util.h"
 
 #ifndef PR_THP_DISABLE_EXCEPT_ADVISED
diff --git a/tools/testing/selftests/mm/protection_keys.c b/tools/testing/selftests/mm/protection_keys.c
index 9a6d954ee371..5ba2033e8a09 100644
--- a/tools/testing/selftests/mm/protection_keys.c
+++ b/tools/testing/selftests/mm/protection_keys.c
@@ -45,8 +45,8 @@
 #include <unistd.h>
 #include <sys/ptrace.h>
 #include <setjmp.h>
+#include <mm/hugepage_settings.h>
 
-#include "hugepage_settings.h"
 #include "pkey-helpers.h"
 
 int iteration_nr = 1;
diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c
index fb1864a68e1c..6c22ac9e93db 100644
--- a/tools/testing/selftests/mm/soft-dirty.c
+++ b/tools/testing/selftests/mm/soft-dirty.c
@@ -9,7 +9,6 @@
 
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define PAGEMAP_FILE_PATH "/proc/self/pagemap"
 #define TEST_ITERATIONS 10000
diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
index 83a75001518b..e744c9434e7d 100644
--- a/tools/testing/selftests/mm/split_huge_page_test.c
+++ b/tools/testing/selftests/mm/split_huge_page_test.c
@@ -21,7 +21,6 @@
 #include <time.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 uint64_t pagesize;
 unsigned int pageshift;
diff --git a/tools/testing/selftests/mm/thuge-gen.c b/tools/testing/selftests/mm/thuge-gen.c
index 22b9c2f1c35d..0da15d530a1f 100644
--- a/tools/testing/selftests/mm/thuge-gen.c
+++ b/tools/testing/selftests/mm/thuge-gen.c
@@ -14,7 +14,6 @@
 #include <string.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 #if !defined(MAP_HUGETLB)
 #define MAP_HUGETLB	0x40000
diff --git a/tools/testing/selftests/mm/transhuge-stress.c b/tools/testing/selftests/mm/transhuge-stress.c
index 8eb0c5630e7e..96f72898ebe0 100644
--- a/tools/testing/selftests/mm/transhuge-stress.c
+++ b/tools/testing/selftests/mm/transhuge-stress.c
@@ -17,7 +17,6 @@
 #include <sys/mman.h>
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 int backing_fd = -1;
 int mmap_flags = MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE;
diff --git a/tools/testing/selftests/mm/uffd-common.h b/tools/testing/selftests/mm/uffd-common.h
index 92a21b97f745..0723843a7626 100644
--- a/tools/testing/selftests/mm/uffd-common.h
+++ b/tools/testing/selftests/mm/uffd-common.h
@@ -37,7 +37,6 @@
 
 #include "kselftest.h"
 #include "vm_util.h"
-#include "hugepage_settings.h"
 
 #define UFFD_FLAGS	(O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY)
 
diff --git a/tools/testing/selftests/mm/uffd-wp-mremap.c b/tools/testing/selftests/mm/uffd-wp-mremap.c
index c973d6722720..eb4b2433b00e 100644
--- a/tools/testing/selftests/mm/uffd-wp-mremap.c
+++ b/tools/testing/selftests/mm/uffd-wp-mremap.c
@@ -7,8 +7,8 @@
 #include <assert.h>
 #include <linux/mman.h>
 #include <sys/mman.h>
+#include <mm/hugepage_settings.h>
 #include "kselftest.h"
-#include "hugepage_settings.h"
 #include "uffd-common.h"
 
 static int pagemap_fd;
diff --git a/tools/testing/selftests/mm/va_high_addr_switch.c b/tools/testing/selftests/mm/va_high_addr_switch.c
index e24d7ba00b44..5a354a664d1f 100644
--- a/tools/testing/selftests/mm/va_high_addr_switch.c
+++ b/tools/testing/selftests/mm/va_high_addr_switch.c
@@ -11,7 +11,6 @@
 
 #include "vm_util.h"
 #include "kselftest.h"
-#include "hugepage_settings.h"
 
 /*
  * The hint addr value is used to allocate addresses
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index d45135283732..aa856f0d32d6 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -9,6 +9,7 @@
 #include "kselftest.h"
 #include <linux/fs.h>
 #include <mm/file_utils.h>
+#include <mm/hugepage_settings.h>
 
 #define BIT_ULL(nr)                   (1ULL << (nr))
 #define PM_SOFT_DIRTY                 BIT_ULL(55)
-- 
2.39.5


^ permalink raw reply related

* [PATCH v5 2/5] tools/lib/mm: add shared file helpers
From: Sarthak Sharma @ 2026-07-16 12:32 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand
  Cc: Lorenzo Stoakes, Liam R . Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Shuah Khan, Shuah Khan,
	Jason Gunthorpe, John Hubbard, Peter Xu, Leon Romanovsky, Zi Yan,
	Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
	Lance Yang, Jonathan Corbet, Mark Brown, Anshuman Khandual,
	linux-mm, linux-kselftest, linux-doc, linux-kernel,
	Sarthak Sharma
In-Reply-To: <20260716123226.197736-1-sarthak.sharma@arm.com>

Move read_file(), write_file(), read_num(), and write_num() out of
tools/testing/selftests/mm/vm_util.c into a new shared helper under
tools/lib/mm/.

These helpers are used by mm selftests today and will also be needed by
shared hugepage helpers in subsequent patches. Move them to a generic
location so they can be reused outside selftests as well.

Keep the helpers exposed to mm selftests through vm_util.h by including
the new shared header there, and link the new helper into the
selftests/mm build.

Update the explicit x86 protection_keys 32-bit and 64-bit build rules
to preserve prerequisite paths, now that file_utils.c is built from
tools/lib/mm.

Add tools/lib/mm/ to the MEMORY MANAGEMENT - MISC entry in MAINTAINERS.

Signed-off-by: Sarthak Sharma <sarthak.sharma@arm.com>
---
 MAINTAINERS                          |   1 +
 tools/lib/mm/file_utils.c            | 119 +++++++++++++++++++++++++++
 tools/lib/mm/file_utils.h            |  12 +++
 tools/testing/selftests/mm/Makefile  |  11 +--
 tools/testing/selftests/mm/vm_util.c | 109 ------------------------
 tools/testing/selftests/mm/vm_util.h |   6 +-
 6 files changed, 139 insertions(+), 119 deletions(-)
 create mode 100644 tools/lib/mm/file_utils.c
 create mode 100644 tools/lib/mm/file_utils.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 806bd2d80d15..26b06d80ab45 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17128,6 +17128,7 @@ F:	mm/memory-tiers.c
 F:	mm/page_idle.c
 F:	mm/pgalloc-track.h
 F:	mm/process_vm_access.c
+F:	tools/lib/mm/
 F:	tools/testing/selftests/mm/
 
 MEMORY MANAGEMENT - NUMA MEMBLOCKS AND NUMA EMULATION
diff --git a/tools/lib/mm/file_utils.c b/tools/lib/mm/file_utils.c
new file mode 100644
index 000000000000..792f4b4db625
--- /dev/null
+++ b/tools/lib/mm/file_utils.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "file_utils.h"
+
+int read_file(const char *path, char *buf, size_t buflen)
+{
+	int fd;
+	ssize_t numread;
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1) {
+		int err = errno;
+
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
+	}
+
+	numread = read(fd, buf, buflen - 1);
+	if (numread < 1) {
+		int err = numread ? errno : ENODATA;
+
+		close(fd);
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
+	}
+
+	buf[numread] = '\0';
+	close(fd);
+
+	return (int)numread;
+}
+
+int write_file(const char *path, const char *buf, size_t buflen)
+{
+	int fd, saved_errno;
+	ssize_t numwritten;
+
+	if (buflen < 2) {
+		printf("# %s: %s (%d)\n", path, strerror(EINVAL), EINVAL);
+		return -EINVAL;
+	}
+
+	fd = open(path, O_WRONLY);
+	if (fd == -1) {
+		int err = errno;
+
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
+	}
+
+	numwritten = write(fd, buf, buflen - 1);
+	saved_errno = errno;
+	close(fd);
+	if (numwritten < 0) {
+		printf("# %s: %s (%d)\n", path, strerror(saved_errno),
+		       saved_errno);
+		return -saved_errno;
+	}
+	if (numwritten != (ssize_t)(buflen - 1)) {
+		printf("# %s write(%.*s) is truncated, expected %zu bytes, got %zd bytes\n",
+		       path, (int)(buflen - 1), buf, buflen - 1, numwritten);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int read_num(const char *path, unsigned long *num)
+{
+	unsigned long val;
+	int err, ret;
+	char buf[21];
+	char *end;
+
+	if (!num)
+		return -EINVAL;
+
+	ret = read_file(path, buf, sizeof(buf));
+	if (ret < 0)
+		return ret;
+
+	errno = 0;
+	val = strtoul(buf, &end, 10);
+	if (errno) {
+		err = errno;
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
+	}
+
+	if (end == buf || buf[0] == '-') {
+		printf("# %s: invalid numeric value\n", path);
+		return -EINVAL;
+	}
+
+	if (*end == '\n')
+		end++;
+
+	if (*end != '\0') {
+		printf("# %s: invalid numeric value\n", path);
+		return -EINVAL;
+	}
+
+	*num = val;
+	return 0;
+}
+
+int write_num(const char *path, unsigned long num)
+{
+	char buf[21];
+
+	snprintf(buf, sizeof(buf), "%lu", num);
+	return write_file(path, buf, strlen(buf) + 1);
+}
diff --git a/tools/lib/mm/file_utils.h b/tools/lib/mm/file_utils.h
new file mode 100644
index 000000000000..38576790a342
--- /dev/null
+++ b/tools/lib/mm/file_utils.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __MM_FILE_UTILS_H__
+#define __MM_FILE_UTILS_H__
+
+#include <stddef.h>
+
+int read_file(const char *path, char *buf, size_t buflen);
+int write_file(const char *path, const char *buf, size_t buflen);
+int read_num(const char *path, unsigned long *num);
+int write_num(const char *path, unsigned long num);
+
+#endif
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index e6df968f0971..70d6a9ccf9c9 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -37,7 +37,8 @@ endif
 # LDLIBS.
 MAKEFLAGS += --no-builtin-rules
 
-CFLAGS = -Wall -O2 -I $(top_srcdir) $(EXTRA_CFLAGS) $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
+CFLAGS = -Wall -O2 -I $(top_srcdir) -I $(top_srcdir)/tools/lib
+CFLAGS += $(EXTRA_CFLAGS) $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
 CFLAGS += -Wunreachable-code
 LDLIBS = -lrt -lpthread -lm
 
@@ -187,8 +188,8 @@ TEST_FILES += write_hugetlb_memory.sh
 
 include ../lib.mk
 
-$(TEST_GEN_PROGS): vm_util.c hugepage_settings.c
-$(TEST_GEN_FILES): vm_util.c hugepage_settings.c
+$(TEST_GEN_PROGS): vm_util.c hugepage_settings.c $(top_srcdir)/tools/lib/mm/file_utils.c
+$(TEST_GEN_FILES): vm_util.c hugepage_settings.c $(top_srcdir)/tools/lib/mm/file_utils.c
 
 $(OUTPUT)/uffd-stress: uffd-common.c
 $(OUTPUT)/uffd-unit-tests: uffd-common.c
@@ -217,7 +218,7 @@ $(BINARIES_32): CFLAGS += -m32 -mxsave
 $(BINARIES_32): LDLIBS += -lrt -ldl -lm
 $(BINARIES_32): $(OUTPUT)/%_32: %.c
 	$(call msg,CC,,$@)
-	$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(notdir $^) $(LDLIBS) -o $@
+	$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $^ $(LDLIBS) -o $@
 $(foreach t,$(VMTARGETS),$(eval $(call gen-target-rule-32,$(t))))
 endif
 
@@ -226,7 +227,7 @@ $(BINARIES_64): CFLAGS += -m64 -mxsave
 $(BINARIES_64): LDLIBS += -lrt -ldl
 $(BINARIES_64): $(OUTPUT)/%_64: %.c
 	$(call msg,CC,,$@)
-	$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(notdir $^) $(LDLIBS) -o $@
+	$(Q)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $^ $(LDLIBS) -o $@
 $(foreach t,$(VMTARGETS),$(eval $(call gen-target-rule-64,$(t))))
 endif
 
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index bfe0efbe517e..f276eee57680 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -698,115 +698,6 @@ int unpoison_memory(unsigned long pfn)
 	return ret > 0 ? 0 : -errno;
 }
 
-int read_file(const char *path, char *buf, size_t buflen)
-{
-	int fd;
-	ssize_t numread;
-
-	fd = open(path, O_RDONLY);
-	if (fd == -1) {
-		int err = errno;
-
-		printf("# %s: %s (%d)\n", path, strerror(err), err);
-		return -err;
-	}
-
-	numread = read(fd, buf, buflen - 1);
-	if (numread < 1) {
-		int err = numread ? errno : ENODATA;
-		close(fd);
-		printf("# %s: %s (%d)\n", path, strerror(err), err);
-		return -err;
-	}
-
-	buf[numread] = '\0';
-	close(fd);
-
-	return (int)numread;
-}
-
-int write_file(const char *path, const char *buf, size_t buflen)
-{
-	int fd, saved_errno;
-	ssize_t numwritten;
-
-	if (buflen < 2) {
-		printf("# %s: %s (%d)\n", path, strerror(EINVAL), EINVAL);
-		return -EINVAL;
-	}
-
-	fd = open(path, O_WRONLY);
-	if (fd == -1) {
-		int err = errno;
-
-		printf("# %s: %s (%d)\n", path, strerror(err), err);
-		return -err;
-	}
-
-	numwritten = write(fd, buf, buflen - 1);
-	saved_errno = errno;
-	close(fd);
-	if (numwritten < 0) {
-		printf("# %s: %s (%d)\n", path, strerror(saved_errno), saved_errno);
-		return -saved_errno;
-	}
-
-	if (numwritten != (ssize_t)(buflen - 1)) {
-		printf("# %s write(%.*s) is truncated, expected %zu bytes, got %zd bytes\n",
-		       path, (int)(buflen - 1), buf, buflen - 1, numwritten);
-		return -EIO;
-	}
-
-	return 0;
-}
-
-int read_num(const char *path, unsigned long *num)
-{
-	unsigned long val;
-	int err, ret;
-	char buf[21];
-	char *end;
-
-	if (!num)
-		return -EINVAL;
-
-	ret = read_file(path, buf, sizeof(buf));
-	if (ret < 0)
-		return ret;
-
-	errno = 0;
-	val = strtoul(buf, &end, 10);
-	if (errno) {
-		err = errno;
-		printf("# %s: %s (%d)\n", path, strerror(err), err);
-		return -err;
-	}
-
-	if (end == buf || buf[0] == '-') {
-		printf("# %s: invalid numeric value\n", path);
-		return -EINVAL;
-	}
-
-	if (*end == '\n')
-		end++;
-
-	if (*end != '\0') {
-		printf("# %s: invalid numeric value\n", path);
-		return -EINVAL;
-	}
-
-	*num = val;
-	return 0;
-}
-
-int write_num(const char *path, unsigned long num)
-{
-	char buf[21];
-
-	sprintf(buf, "%lu", num);
-	return write_file(path, buf, strlen(buf) + 1);
-}
-
 static unsigned long shmall, shmmax;
 
 void __shm_limits_restore(void)
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index 28c3d7c1faed..d45135283732 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -8,6 +8,7 @@
 #include <unistd.h> /* _SC_PAGESIZE */
 #include "kselftest.h"
 #include <linux/fs.h>
+#include <mm/file_utils.h>
 
 #define BIT_ULL(nr)                   (1ULL << (nr))
 #define PM_SOFT_DIRTY                 BIT_ULL(55)
@@ -164,11 +165,6 @@ int unpoison_memory(unsigned long pfn);
 #define PAGEMAP_PRESENT(ent)	(((ent) & (1ull << 63)) != 0)
 #define PAGEMAP_PFN(ent)	((ent) & ((1ull << 55) - 1))
 
-int read_file(const char *path, char *buf, size_t buflen);
-int write_file(const char *path, const char *buf, size_t buflen);
-int read_num(const char *path, unsigned long *num);
-int write_num(const char *path, unsigned long num);
-
 void shm_limits_prepare(unsigned long length);
 void __shm_limits_restore(void);
 
-- 
2.39.5


^ permalink raw reply related

* [PATCH v5 1/5] selftests/mm: make file helpers return errors
From: Sarthak Sharma @ 2026-07-16 12:32 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand
  Cc: Lorenzo Stoakes, Liam R . Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Shuah Khan, Shuah Khan,
	Jason Gunthorpe, John Hubbard, Peter Xu, Leon Romanovsky, Zi Yan,
	Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
	Lance Yang, Jonathan Corbet, Mark Brown, Anshuman Khandual,
	linux-mm, linux-kselftest, linux-doc, linux-kernel,
	Sarthak Sharma
In-Reply-To: <20260716123226.197736-1-sarthak.sharma@arm.com>

Change read_file(), write_file(), read_num() and write_num() in vm_util.c
to report failures to callers instead of exiting from the helper.

Update the helpers to print TAP compatible diagnostics before returning
errors and update their callers to handle those errors. This prepares
the helpers to be moved to tools/lib/mm without carrying selftest-specific
process-exit behaviour into the shared implementation.

Also, make read_file() return a negative errno on failure instead of 0, so
callers can distinguish a successful read from an I/O error. Make
read_num() reject negative and malformed values.

Signed-off-by: Sarthak Sharma <sarthak.sharma@arm.com>
---
 .../testing/selftests/mm/hugepage_settings.c  |  64 +++++++---
 tools/testing/selftests/mm/khugepaged.c       |  13 +-
 .../selftests/mm/split_huge_page_test.c       |   4 +-
 tools/testing/selftests/mm/vm_util.c          | 115 +++++++++++++-----
 tools/testing/selftests/mm/vm_util.h          |   6 +-
 5 files changed, 150 insertions(+), 52 deletions(-)

diff --git a/tools/testing/selftests/mm/hugepage_settings.c b/tools/testing/selftests/mm/hugepage_settings.c
index 2eab2110ac6a..202c8e6b0246 100644
--- a/tools/testing/selftests/mm/hugepage_settings.c
+++ b/tools/testing/selftests/mm/hugepage_settings.c
@@ -61,10 +61,9 @@ int thp_read_string(const char *name, const char * const strings[])
 		exit(EXIT_FAILURE);
 	}
 
-	if (!read_file(path, buf, sizeof(buf))) {
-		perror(path);
+	ret = read_file(path, buf, sizeof(buf));
+	if (ret < 0)
 		exit(EXIT_FAILURE);
-	}
 
 	c = strchr(buf, '[');
 	if (!c) {
@@ -103,12 +102,15 @@ void thp_write_string(const char *name, const char *val)
 		printf("%s: Pathname is too long\n", __func__);
 		exit(EXIT_FAILURE);
 	}
-	write_file(path, val, strlen(val) + 1);
+	ret = write_file(path, val, strlen(val) + 1);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
 }
 
 unsigned long thp_read_num(const char *name)
 {
 	char path[PATH_MAX];
+	unsigned long num;
 	int ret;
 
 	ret = snprintf(path, PATH_MAX, THP_SYSFS "%s", name);
@@ -116,7 +118,11 @@ unsigned long thp_read_num(const char *name)
 		printf("%s: Pathname is too long\n", __func__);
 		exit(EXIT_FAILURE);
 	}
-	return read_num(path);
+	ret = read_num(path, &num);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
+
+	return num;
 }
 
 void thp_write_num(const char *name, unsigned long num)
@@ -129,7 +135,9 @@ void thp_write_num(const char *name, unsigned long num)
 		printf("%s: Pathname is too long\n", __func__);
 		exit(EXIT_FAILURE);
 	}
-	write_num(path, num);
+	ret = write_num(path, num);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
 }
 
 void thp_read_settings(struct thp_settings *settings)
@@ -157,8 +165,13 @@ void thp_read_settings(struct thp_settings *settings)
 		.max_ptes_shared = thp_read_num("khugepaged/max_ptes_shared"),
 		.pages_to_scan = thp_read_num("khugepaged/pages_to_scan"),
 	};
-	if (dev_queue_read_ahead_path[0])
-		settings->read_ahead_kb = read_num(dev_queue_read_ahead_path);
+	if (dev_queue_read_ahead_path[0]) {
+		int ret = read_num(dev_queue_read_ahead_path,
+				   &settings->read_ahead_kb);
+
+		if (ret < 0)
+			exit(EXIT_FAILURE);
+	}
 
 	for (i = 0; i < NR_ORDERS; i++) {
 		if (!((1 << i) & orders)) {
@@ -208,8 +221,13 @@ void thp_write_settings(struct thp_settings *settings)
 	thp_write_num("khugepaged/max_ptes_shared", khugepaged->max_ptes_shared);
 	thp_write_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan);
 
-	if (dev_queue_read_ahead_path[0])
-		write_num(dev_queue_read_ahead_path, settings->read_ahead_kb);
+	if (dev_queue_read_ahead_path[0]) {
+		int ret = write_num(dev_queue_read_ahead_path,
+				    settings->read_ahead_kb);
+
+		if (ret < 0)
+			exit(EXIT_FAILURE);
+	}
 
 	for (i = 0; i < NR_ORDERS; i++) {
 		if (!((1 << i) & orders))
@@ -306,8 +324,11 @@ static unsigned long __thp_supported_orders(bool is_shmem)
 			exit(EXIT_FAILURE);
 		}
 
+		if (access(path, F_OK))
+			continue;
+
 		ret = read_file(path, buf, sizeof(buf));
-		if (ret)
+		if (ret > 0)
 			orders |= 1UL << i;
 	}
 
@@ -425,28 +446,43 @@ static void hugetlb_sysfs_path(char *buf, size_t buflen,
 unsigned long hugetlb_nr_pages(unsigned long size)
 {
 	char path[PATH_MAX];
+	unsigned long nr;
+	int ret;
 
 	hugetlb_sysfs_path(path, sizeof(path), size, "nr_hugepages");
 
-	return read_num(path);
+	ret = read_num(path, &nr);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
+
+	return nr;
 }
 
 void hugetlb_set_nr_pages(unsigned long size, unsigned long nr)
 {
 	char path[PATH_MAX];
+	int ret;
 
 	hugetlb_sysfs_path(path, sizeof(path), size, "nr_hugepages");
 
-	write_num(path, nr);
+	ret = write_num(path, nr);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
 }
 
 unsigned long hugetlb_free_pages(unsigned long size)
 {
 	char path[PATH_MAX];
+	unsigned long nr;
+	int ret;
 
 	hugetlb_sysfs_path(path, sizeof(path), size, "free_hugepages");
 
-	return read_num(path);
+	ret = read_num(path, &nr);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
+
+	return nr;
 }
 
 static bool __hugetlb_setup(unsigned long size, unsigned long nr)
diff --git a/tools/testing/selftests/mm/khugepaged.c b/tools/testing/selftests/mm/khugepaged.c
index 10e8dedcb087..5eac27dd9683 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -119,6 +119,7 @@ static void get_finfo(const char *dir)
 	char buf[1 << 10];
 	char path[PATH_MAX];
 	char *str, *end;
+	int ret;
 
 	finfo.dir = dir;
 	stat(finfo.dir, &path_stat);
@@ -138,8 +139,9 @@ static void get_finfo(const char *dir)
 		     major(path_stat.st_dev), minor(path_stat.st_dev))
 	    >= sizeof(path))
 		ksft_exit_fail_msg("%s: Pathname is too long\n", __func__);
-	if (read_file(path, buf, sizeof(buf)) < 0)
-		ksft_exit_fail_perror("read_file(read_num)");
+	ret = read_file(path, buf, sizeof(buf));
+	if (ret < 0)
+		ksft_exit_fail();
 	if (strstr(buf, "DEVTYPE=disk")) {
 		/* Found it */
 		if (snprintf(finfo.dev_queue_read_ahead_path,
@@ -319,7 +321,7 @@ static void *file_setup_area_common(int nr_hpages, enum file_setup_ops setup)
 {
 	const int open_opt = setup == FILE_SETUP_READ_ONLY_FS ? O_RDONLY : O_RDWR;
 	const int mmap_prot = setup == FILE_SETUP_READ_ONLY_FS ? PROT_READ : (PROT_READ | PROT_WRITE);
-	int fd;
+	int fd, ret;
 	void *p;
 	unsigned long size;
 
@@ -363,7 +365,10 @@ static void *file_setup_area_common(int nr_hpages, enum file_setup_ops setup)
 		ksft_exit_fail_perror("mmap()");
 
 	/* Drop page cache */
-	write_file("/proc/sys/vm/drop_caches", "3", 2);
+	ret = write_file("/proc/sys/vm/drop_caches", "3", 2);
+	if (ret)
+		ksft_exit_fail();
+
 	success("OK");
 	return p;
 }
diff --git a/tools/testing/selftests/mm/split_huge_page_test.c b/tools/testing/selftests/mm/split_huge_page_test.c
index 32b991472f74..83a75001518b 100644
--- a/tools/testing/selftests/mm/split_huge_page_test.c
+++ b/tools/testing/selftests/mm/split_huge_page_test.c
@@ -269,7 +269,9 @@ static void write_debugfs(const char *fmt, ...)
 	if (ret >= INPUT_MAX)
 		ksft_exit_fail_msg("%s: Debugfs input is too long\n", __func__);
 
-	write_file(SPLIT_DEBUGFS, input, ret + 1);
+	ret = write_file(SPLIT_DEBUGFS, input, ret + 1);
+	if (ret)
+		ksft_exit_fail();
 }
 
 static char *allocate_zero_filled_hugepage(size_t len)
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index 311fc5b4513e..bfe0efbe517e 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -704,87 +704,142 @@ int read_file(const char *path, char *buf, size_t buflen)
 	ssize_t numread;
 
 	fd = open(path, O_RDONLY);
-	if (fd == -1)
-		return 0;
+	if (fd == -1) {
+		int err = errno;
+
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
+	}
 
 	numread = read(fd, buf, buflen - 1);
 	if (numread < 1) {
+		int err = numread ? errno : ENODATA;
 		close(fd);
-		return 0;
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
 	}
 
 	buf[numread] = '\0';
 	close(fd);
 
-	return (unsigned int) numread;
+	return (int)numread;
 }
 
-void write_file(const char *path, const char *buf, size_t buflen)
+int write_file(const char *path, const char *buf, size_t buflen)
 {
 	int fd, saved_errno;
 	ssize_t numwritten;
 
-	if (buflen < 2)
-		ksft_exit_fail_msg("Incorrect buffer len: %zu\n", buflen);
+	if (buflen < 2) {
+		printf("# %s: %s (%d)\n", path, strerror(EINVAL), EINVAL);
+		return -EINVAL;
+	}
 
 	fd = open(path, O_WRONLY);
-	if (fd == -1)
-		ksft_exit_fail_msg("%s open failed: %s\n", path, strerror(errno));
+	if (fd == -1) {
+		int err = errno;
+
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
+	}
 
 	numwritten = write(fd, buf, buflen - 1);
 	saved_errno = errno;
 	close(fd);
-	errno = saved_errno;
-	if (numwritten < 0)
-		ksft_exit_fail_msg("%s write(%.*s) failed: %s\n", path, (int)(buflen - 1),
-				buf, strerror(errno));
-	if (numwritten != buflen - 1)
-		ksft_exit_fail_msg("%s write(%.*s) is truncated, expected %zu bytes, got %zd bytes\n",
-				path, (int)(buflen - 1), buf, buflen - 1, numwritten);
+	if (numwritten < 0) {
+		printf("# %s: %s (%d)\n", path, strerror(saved_errno), saved_errno);
+		return -saved_errno;
+	}
+
+	if (numwritten != (ssize_t)(buflen - 1)) {
+		printf("# %s write(%.*s) is truncated, expected %zu bytes, got %zd bytes\n",
+		       path, (int)(buflen - 1), buf, buflen - 1, numwritten);
+		return -EIO;
+	}
+
+	return 0;
 }
 
-unsigned long read_num(const char *path)
+int read_num(const char *path, unsigned long *num)
 {
+	unsigned long val;
+	int err, ret;
 	char buf[21];
+	char *end;
 
-	if (read_file(path, buf, sizeof(buf)) < 0)
-		ksft_exit_fail_perror("read_file()");
+	if (!num)
+		return -EINVAL;
 
-	return strtoul(buf, NULL, 10);
+	ret = read_file(path, buf, sizeof(buf));
+	if (ret < 0)
+		return ret;
+
+	errno = 0;
+	val = strtoul(buf, &end, 10);
+	if (errno) {
+		err = errno;
+		printf("# %s: %s (%d)\n", path, strerror(err), err);
+		return -err;
+	}
+
+	if (end == buf || buf[0] == '-') {
+		printf("# %s: invalid numeric value\n", path);
+		return -EINVAL;
+	}
+
+	if (*end == '\n')
+		end++;
+
+	if (*end != '\0') {
+		printf("# %s: invalid numeric value\n", path);
+		return -EINVAL;
+	}
+
+	*num = val;
+	return 0;
 }
 
-void write_num(const char *path, unsigned long num)
+int write_num(const char *path, unsigned long num)
 {
 	char buf[21];
 
 	sprintf(buf, "%lu", num);
-	write_file(path, buf, strlen(buf) + 1);
+	return write_file(path, buf, strlen(buf) + 1);
 }
 
 static unsigned long shmall, shmmax;
 
 void __shm_limits_restore(void)
 {
-	if (shmmax)
-		write_num("/proc/sys/kernel/shmmax", shmmax);
-	if (shmall)
-		write_num("/proc/sys/kernel/shmall", shmall);
+	if (shmmax && write_num("/proc/sys/kernel/shmmax", shmmax))
+		ksft_exit_fail();
+	if (shmall && write_num("/proc/sys/kernel/shmall", shmall))
+		ksft_exit_fail();
 }
 
 void shm_limits_prepare(unsigned long length)
 {
 	unsigned long nr = length / psize();
 	unsigned long val;
+	int ret;
 
-	val = read_num("/proc/sys/kernel/shmmax");
+	ret = read_num("/proc/sys/kernel/shmmax", &val);
+	if (ret)
+		ksft_exit_fail();
 	if (val < length) {
-		write_num("/proc/sys/kernel/shmmax", length);
+		ret = write_num("/proc/sys/kernel/shmmax", length);
+		if (ret)
+			ksft_exit_fail();
 		shmmax = val;
 	}
 
-	val = read_num("/proc/sys/kernel/shmall");
+	ret = read_num("/proc/sys/kernel/shmall", &val);
+	if (ret)
+		ksft_exit_fail();
 	if (val < nr) {
-		write_num("/proc/sys/kernel/shmall", nr);
+		ret = write_num("/proc/sys/kernel/shmall", nr);
+		if (ret)
+			ksft_exit_fail();
 		shmall = val;
 	}
 }
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index ea8fc8fdf0eb..28c3d7c1faed 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -164,10 +164,10 @@ int unpoison_memory(unsigned long pfn);
 #define PAGEMAP_PRESENT(ent)	(((ent) & (1ull << 63)) != 0)
 #define PAGEMAP_PFN(ent)	((ent) & ((1ull << 55) - 1))
 
-void write_file(const char *path, const char *buf, size_t buflen);
 int read_file(const char *path, char *buf, size_t buflen);
-unsigned long read_num(const char *path);
-void write_num(const char *path, unsigned long num);
+int write_file(const char *path, const char *buf, size_t buflen);
+int read_num(const char *path, unsigned long *num);
+int write_num(const char *path, unsigned long num);
 
 void shm_limits_prepare(unsigned long length);
 void __shm_limits_restore(void);
-- 
2.39.5


^ permalink raw reply related

* [PATCH v5 0/5] selftests/mm: separate GUP microbenchmarking from functional testing
From: Sarthak Sharma @ 2026-07-16 12:32 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand
  Cc: Lorenzo Stoakes, Liam R . Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Shuah Khan, Shuah Khan,
	Jason Gunthorpe, John Hubbard, Peter Xu, Leon Romanovsky, Zi Yan,
	Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
	Lance Yang, Jonathan Corbet, Mark Brown, Anshuman Khandual,
	linux-mm, linux-kselftest, linux-doc, linux-kernel,
	Sarthak Sharma

gup_test.c currently serves two distinct purposes: microbenchmarking
(GUP_FAST_BENCHMARK, PIN_FAST_BENCHMARK, PIN_LONGTERM_BENCHMARK) and
functional correctness testing (GUP_BASIC_TEST, PIN_BASIC_TEST,
DUMP_USER_PAGES_TEST). Mixing these in a single binary means that
functional tests cannot be run or reported individually, and
run_vmtests.sh must invoke the binary multiple times with different flag
combinations to cover all configurations.

This series separates the two concerns: tools/mm/gup_bench handles
benchmarking, while tools/testing/selftests/mm/gup_test handles functional
testing. To avoid duplicating the HugeTLB and related file helpers, the
series first prepares the existing helpers for sharing, then moves the
common code to tools/lib/mm/ for use by both selftests and tools/mm.

Patch 1 makes read_file(), write_file(), read_num() and write_num() in
vm_util.c return errors to callers rather than exiting. It also makes
read_num() reject negative, malformed and out-of-range values. Existing
mm selftest callers are updated to report failures through kselftest
helpers. This avoids carrying behavior specific to selftests into the
shared helper implementation introduced in the next patch.

Patch 2 adds tools/lib/mm/file_utils.[ch], moving read_file(),
write_file(), read_num() and write_num() out of vm_util.c into a shared
helper without a kselftest dependency. It keeps the helpers available to
mm selftests through vm_util.h, updates the explicit x86 protection_keys
build rules to preserve full prerequisite paths, and adds tools/lib/mm/
to the MEMORY MANAGEMENT - MISC entry in MAINTAINERS.

Patch 3 moves hugepage_settings.[ch] from selftests/mm to tools/lib/mm/.
It keeps the helpers available to selftests through vm_util.h where
possible, uses direct <mm/hugepage_settings.h> includes for users that do
not include vm_util.h, and removes the remaining kselftest dependency
from the implementation. The existing HugeTLB diagnostic messages are
preserved as TAP-compatible printf() diagnostics.

Patch 4 adds tools/mm/gup_bench.c, a standalone microbenchmark for
GUP_FAST, PIN_FAST and PIN_LONGTERM through the CONFIG_GUP_TEST debugfs
interface. It runs the benchmark subset of the configuration matrix
previously covered by run_gup_matrix(), covering all three benchmark
commands, read and write GUP access, private and shared mappings, four
page counts, and THP, non-THP and hugetlb mappings. It also restores
HugeTLB settings after each hugetlb benchmark run and validates
command-line arguments, including their numeric ranges.

Patch 5 rewrites gup_test.c to use the kselftest harness. It covers all
five GUP/PUP kernel functions (get_user_pages, get_user_pages_fast,
pin_user_pages, pin_user_pages_fast, and pin_user_pages with
FOLL_LONGTERM), plus DUMP_USER_PAGES_TEST, across 12 mapping
configurations. These configurations cover THP, non-THP and hugetlb
mappings, each with private and shared mappings and read and write
access. Each configuration is tested with four batch sizes (1, 512, 123,
and all pages). The patch also preserves the old sparse dump coverage
for pages 0, 19 and 0x1000. Results are reported in standard TAP format,
with no command-line arguments required.

---
Changes in v5:
- Rebase onto v7.2-rc3
- Address feedback from Mike, John, Dev and Sashiko
- Make file helper diagnostics TAP-compatible
- Validate read_num() input and propagate errors while restoring
  shared memory limits
- Preserve build bisectability for the x86 protection_keys test
- Preserve HugeTLB diagnostics using TAP-compatible printf() output
- Validate the remaining numeric arguments accepted by gup_bench

Changes in v4:
- Address review feedback from Mike and Sashiko
- Add a preparatory patch so shared file helpers return errors instead of exiting
- Reduce include churn by keeping shared helpers exposed through vm_util.h
- Preserve HugeTLB diagnostics and restore HugeTLB state more carefully
- Fix selftests/mm build details after moving helpers to tools/lib/mm
- Tighten gup_bench argument handling and gup_test setup/sparse-dump coverage

Changes in v3:
- Address v2 feedback from Sashiko
- Add shared file_utils helpers under tools/lib/mm
- Move hugepage_settings out of selftests and into tools/lib/mm
- Convert gup_bench to use the shared tools/lib/mm helpers
- Guard against invalid thread counts in gup_bench
- Handle thread-array allocation failure cleanly in gup_bench
- Restore hugetlb settings on setup failure in gup_test
- Add sparse DUMP_USER_PAGES_TEST coverage for pages 0, 19 and 0x1000 in gup_test

Changes in v2:
- Address v1 feedback from Sashiko
- Add fast and longterm GUP/PUP coverage
- Sweep nr_pages_per_call over 1, 512, 123 and all pages
- Call madvise(MADV_NOHUGEPAGE) in non-THP variants
- Use 256 MB for hugetlb fixtures
- Use hugetlb_restore_settings() in FIXTURE_TEARDOWN instead of atexit()
- Add TH_LOG to report nr_pages_per_call for each iteration
- Update Documentation/core-api/pin_user_pages.rst unit testing section

Previous versions:
v4: lore.kernel.org/all/20260527142432.230127-1-sarthak.sharma@arm.com/
v3: lore.kernel.org/all/20260521111801.173019-1-sarthak.sharma@arm.com/
v2: lore.kernel.org/all/20260519120506.184512-1-sarthak.sharma@arm.com/
v1: lore.kernel.org/all/20260515084840.174652-1-sarthak.sharma@arm.com/

---
Sarthak Sharma (5):
  selftests/mm: make file helpers return errors
  tools/lib/mm: add shared file helpers
  tools/lib/mm: move hugepage_settings out of selftests
  tools/mm: add a standalone GUP microbenchmark
  selftests/mm: rewrite gup_test as a standalone harness-based selftest

 Documentation/core-api/pin_user_pages.rst     |  12 +-
 MAINTAINERS                                   |   2 +
 tools/lib/mm/file_utils.c                     | 119 ++++
 tools/lib/mm/file_utils.h                     |  12 +
 .../selftests => lib}/mm/hugepage_settings.c  |  81 ++-
 .../selftests => lib}/mm/hugepage_settings.h  |   0
 tools/mm/.gitignore                           |   2 +
 tools/mm/Makefile                             |  10 +-
 tools/mm/gup_bench.c                          | 482 ++++++++++++++
 tools/testing/selftests/mm/Makefile           |  13 +-
 tools/testing/selftests/mm/compaction_test.c  |   2 +-
 tools/testing/selftests/mm/cow.c              |   1 -
 .../selftests/mm/folio_split_race_test.c      |   1 -
 tools/testing/selftests/mm/guard-regions.c    |   1 -
 tools/testing/selftests/mm/gup_longterm.c     |   1 -
 tools/testing/selftests/mm/gup_test.c         | 606 +++++++++++-------
 tools/testing/selftests/mm/hmm-tests.c        |   6 +-
 tools/testing/selftests/mm/hugetlb-madvise.c  |   1 -
 tools/testing/selftests/mm/hugetlb-mmap.c     |   1 -
 tools/testing/selftests/mm/hugetlb-mremap.c   |   1 -
 tools/testing/selftests/mm/hugetlb-shm.c      |   1 -
 .../selftests/mm/hugetlb-soft-offline.c       |   2 +-
 tools/testing/selftests/mm/hugetlb-vmemmap.c  |   1 -
 tools/testing/selftests/mm/hugetlb_dio.c      |   1 -
 .../selftests/mm/hugetlb_fault_after_madv.c   |   1 -
 .../selftests/mm/hugetlb_madv_vs_map.c        |   1 -
 tools/testing/selftests/mm/khugepaged.c       |  14 +-
 tools/testing/selftests/mm/ksm_tests.c        |   1 -
 tools/testing/selftests/mm/migration.c        |   5 +-
 tools/testing/selftests/mm/pagemap_ioctl.c    |   1 -
 .../testing/selftests/mm/prctl_thp_disable.c  |   1 -
 tools/testing/selftests/mm/protection_keys.c  |   2 +-
 tools/testing/selftests/mm/run_vmtests.sh     |  37 +-
 tools/testing/selftests/mm/soft-dirty.c       |   1 -
 .../selftests/mm/split_huge_page_test.c       |   5 +-
 tools/testing/selftests/mm/thuge-gen.c        |   1 -
 tools/testing/selftests/mm/transhuge-stress.c |   1 -
 tools/testing/selftests/mm/uffd-common.h      |   1 -
 tools/testing/selftests/mm/uffd-wp-mremap.c   |   2 +-
 .../selftests/mm/va_high_addr_switch.c        |   1 -
 tools/testing/selftests/mm/vm_util.c          |  88 +--
 tools/testing/selftests/mm/vm_util.h          |   7 +-
 42 files changed, 1131 insertions(+), 398 deletions(-)
 create mode 100644 tools/lib/mm/file_utils.c
 create mode 100644 tools/lib/mm/file_utils.h
 rename tools/{testing/selftests => lib}/mm/hugepage_settings.c (91%)
 rename tools/{testing/selftests => lib}/mm/hugepage_settings.h (100%)
 create mode 100644 tools/mm/gup_bench.c


base-commit: a13c140cc289c0b7b3770bce5b3ad42ab35074aa
--
2.39.5


^ permalink raw reply

* Re: [PATCH bpf-next v5 8/8] selftests: net: add test for XDP_PASS skb checksum invalidation
From: Stanislav Fomichev @ 2026-07-16 12:30 UTC (permalink / raw)
  To: Lorenzo Bianconi
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Alexei Starovoitov, Daniel Borkmann,
	Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
	Andrew Lunn, Tony Nguyen, Przemek Kitszel, Alexander Lobakin,
	Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu,
	Yonghong Song, KP Singh, Hao Luo, Jiri Olsa, Shuah Khan,
	Maciej Fijalkowski, Jonathan Corbet, Shuah Khan,
	Kumar Kartikeya Dwivedi, Emil Tsalapatis, Vladimir Vdovin,
	Jakub Sitnicki, netdev, bpf, intel-wired-lan, linux-kselftest,
	linux-doc
In-Reply-To: <20260715-bpf-xdp-meta-rxcksum-v5-8-623d5c0d0ab7@kernel.org>

On 07/15, Lorenzo Bianconi wrote:
> Add a test that verifies skb->ip_summed is set to CHECKSUM_NONE
> when a device running in XDP mode creates an skb from a xdp_buff
> if the attached ebpf program returns an XDP_PASS.
> The test attaches an XDP program returning XDP_PASS, and a TC
> ingress program that runs the bpf_skb_rx_checksum() kfunc to
> inspect the resulting skb. After XDP_PASS the driver must invalidate
> any previously computed hardware RX checksum since XDP may have
> modified the packet data.
> The BPF program counts packets per checksum type in a map, and the
> test runner verifies that after sending traffic the CHECKSUM_NONE
> counter is non-zero while CHECKSUM_UNNECESSARY and CHECKSUM_COMPLETE
> counters are zero.
> 
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> ---
>  Documentation/networking/xdp-rx-metadata.rst       |  5 ++
>  .../selftests/drivers/net/hw/xdp_metadata.py       | 55 +++++++++++++++-
>  .../selftests/net/lib/skb_metadata_csum.bpf.c      | 73 ++++++++++++++++++++++
>  3 files changed, 132 insertions(+), 1 deletion(-)
> 
> diff --git a/Documentation/networking/xdp-rx-metadata.rst b/Documentation/networking/xdp-rx-metadata.rst
> index 93918b3769a3..7434ac98242a 100644
> --- a/Documentation/networking/xdp-rx-metadata.rst
> +++ b/Documentation/networking/xdp-rx-metadata.rst
> @@ -90,6 +90,11 @@ conversion, and the XDP metadata is not used by the kernel when building
>  ``skbs``. However, TC-BPF programs can access the XDP metadata area using
>  the ``data_meta`` pointer.

[..]

> +If a driver is running in XDP mode, any existing hardware RX checksum
> +(``CHECKSUM_UNNECESSARY`` or ``CHECKSUM_COMPLETE``) must be invalidated
> +by setting ``skb->ip_summed`` to ``CHECKSUM_NONE`` before passing the
> +skb to the kernel, since XDP may have modified the packet data.
> +
>  In the future, we'd like to support a case where an XDP program
>  can override some of the metadata used for building ``skbs``.

Sorry for keeping nitpicking on this, but I'm still not convinced that
it is what we currently do. From my previous reply:

> > Looking at a few drivers:
> > - bnxt (bnxt_rx_pkt) does UNNECESSARY - ok
> > - mlx5 (mlx5e_handle_csum) does UNNECESSARY and skips COMPLETE if there is
> >   bpf prog attached
> > - fbnic (fbnic_rx_csum) - can do COMPLETE even with xdp attached?
> > - gve (gve_rx) - can do COMPLETE even with xdp attached?

(although for gve I might be wrong, there is also gve_rx_skb_csum that only
does UNNECESSARY).

I'd wait for Jakub to chime in, but it feels like we should just document
what we currently do as a recommended approach: for the drivers
that support COMPLETE, do not report it when the bpf program is attached.
Both NONE and UNNECESSARY are ok.

Also, did you run this test on real HW? NIPA now has HW tests, maybe it
makes sense to route this series via net-next to get the real coverage?

^ permalink raw reply

* Re: [PATCH v6 1/5] spi: dt-bindings: Add spi-device-addr peripheral property
From: Mark Brown @ 2026-07-16 12:28 UTC (permalink / raw)
  To: Nuno Sá
  Cc: Janani Sunil, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
	Jonathan Corbet, Shuah Khan, Marius Cristea, Marcus Folkesson,
	Kent Gustavsson, linux-iio, devicetree, linux-kernel, linux-doc,
	Janani Sunil, linux-spi, Kent Gustavsson
In-Reply-To: <jsximyohkijphqrtgat6cjtcvtirjsdtbgn3lb6npw6yhgmcth@tq64dotfowss>

[-- Attachment #1: Type: text/plain, Size: 928 bytes --]

On Thu, Jul 16, 2026 at 11:22:19AM +0200, Nuno Sá wrote:
> On Wed, Jul 15, 2026 at 02:37:12PM +0100, Mark Brown wrote:

> > You still need to work out how the ID appears in the byte stream that
> > gets sent to/from the device, that's way more information than just a
> > number and not something the byte stream SPI offers is going to cope
> > well with.

> Not sure if I'm following you. Those ID pins just become something you
> need to set on the spi_xfer. Or in Janani case, she's using regmap
> reg_base in order to set the right thing depending on the address. I
> would image this is always something that peripherals need to address in
> terms of how the message/stream needs to be set. So my understanding is
> that this should be pretty much transparent for the spi core.

Oh, isn't that just multi-pin chip selects then:

   https://patch.msgid.link/cover.1783729282.git.Jonathan.Santos@analog.com

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply

* Re: [PATCH v4 net-next 2/7] selftests/ptp: Extract print_system_timestamp helper in testptp
From: saeed bishara @ 2026-07-16 11:34 UTC (permalink / raw)
  To: Arthur Kiyanovski
  Cc: David Miller, Jakub Kicinski, netdev, Richard Cochran,
	Eric Dumazet, Paolo Abeni, David Woodhouse, Thomas Gleixner,
	Miroslav Lichvar, Andrew Lunn, Wen Gu, Xuan Zhuo, David Woodhouse,
	Yonatan Sarna, Zorik Machulsky, Alexander Matushevsky,
	Saeed Bshara, Matt Wilson, Anthony Liguori, Nafea Bshara,
	Evgeny Schmeilin, Netanel Belgazal, Ali Saidi,
	Benjamin Herrenschmidt, Noam Dagan, David Arinzon,
	Evgeny Ostrovsky, Ofir Tabachnik, Amit Bernstein, linux-kselftest,
	linux-doc, shuah, Jonathan Corbet, Shuah Khan, Simon Horman,
	vadim.fedorenko
In-Reply-To: <20260714020340.25014-3-akiyano@amazon.com>

>
> +static void print_system_timestamp(int sample_num, __kernel_clockid_t clockid,
> +                                  long long sec, unsigned int nsec,
> +                                  const char *when)
> +{
> +       switch (clockid) {
> +       case CLOCK_REALTIME:
> +               printf("sample #%2d: real time %s: %lld.%09u\n",
> +                      sample_num, when, sec, nsec);
> +               break;
> +       case CLOCK_MONOTONIC:
> +               printf("sample #%2d: monotonic time %s: %lld.%09u\n",
> +                      sample_num, when, sec, nsec);
> +               break;
> +       case CLOCK_MONOTONIC_RAW:
> +               printf("sample #%2d: monotonic-raw time %s: %lld.%09u\n",
> +                      sample_num, when, sec, nsec);
> +               break;
These three printfs are also kind of duplicated, I think mapping the
clockid to name first would save a few lines.

^ permalink raw reply

* Re: [PATCH v7 0/4] kallsyms: embed source file:line info in kernel stack traces
From: Sasha Levin @ 2026-07-16 11:32 UTC (permalink / raw)
  To: Geert Uytterhoeven
  Cc: Andrew Morton, Masahiro Yamada, Luis Chamberlain, Linus Torvalds,
	Richard Weinberger, Juergen Gross, James Bottomley,
	Jonathan Corbet, Nathan Chancellor, Nicolas Schier, Petr Pavlu,
	Daniel Gomez, Greg KH, Petr Mladek, Steven Rostedt, Kees Cook,
	Peter Zijlstra, Thorsten Leemhuis, Vlastimil Babka, Helge Deller,
	Randy Dunlap, Laurent Pinchart, Vivian Wang, Zhen Lei,
	Sami Tolvanen, linux-kernel, linux-kbuild, linux-modules,
	linux-doc
In-Reply-To: <CAMuHMdUCAX642tSJhvG_Ku6Y=D8GK3owbyed9qkhzPpgrU1kdg@mail.gmail.com>

On Fri, Jul 10, 2026 at 12:04:56PM +0200, Geert Uytterhoeven wrote:
>Hi Sasha,
>
>On Thu, 9 Jul 2026 at 18:38, Sasha Levin <sashal@kernel.org> wrote:
>> Changes since v6
>> ================
>>
>> - Address Sashiko AI review comments.
>
>What does that mean?
>Please list the changes, so reviewers know what to look at.
>Thanks!

Sorry! I've missed your mail.

There were a few smaller changes, mostly on the kunit tests side, here it is:

   - Bound lineinfo lookups to the resolved symbol's start so it stops stale annotations on __init code and __pfx_* padding
   - gen_lineinfo: handle SHT_REL .rel.debug_line (i386/arm32 modules)
   - gen_lineinfo: bias all exec sections, drop foreign DWARF sequences; cover .noinstr.text (kvm.ko)
   - __aligned(8) + static_asserts on the blob structs — fixes 32-bit layout mismatch (i386/m68k)
   - SLEB128 decoder: do the shift unsigned
   - KUnit: skip instead of fail when =m without KALLSYMS_LINEINFO_MODULES
   - KUnit: dereference_function_descriptor() for target addresses (ppc64 ELFv1/parisc)
   - KUnit: volatile accumulator in lineinfo_target_many_lines() (compilers fold it otherwise)
   - KUnit: OPTIMIZER_HIDE_VAR for the mid-function address — fixes objtool !ENDBR warning
   - KUnit: new regression tests (init-text stale annotation, 5-byte SLEB128 decode)

-- 
Thanks,
Sasha

^ permalink raw reply

* Re: [PATCH v4 net-next 1/7] ptp: Add ioctls for PHC timestamps with quality attributes
From: saeed bishara @ 2026-07-16 11:22 UTC (permalink / raw)
  To: Arthur Kiyanovski
  Cc: David Miller, Jakub Kicinski, netdev, Richard Cochran,
	Eric Dumazet, Paolo Abeni, David Woodhouse, Thomas Gleixner,
	Miroslav Lichvar, Andrew Lunn, Wen Gu, Xuan Zhuo, David Woodhouse,
	Yonatan Sarna, Zorik Machulsky, Alexander Matushevsky,
	Saeed Bshara, Matt Wilson, Anthony Liguori, Nafea Bshara,
	Evgeny Schmeilin, Netanel Belgazal, Ali Saidi,
	Benjamin Herrenschmidt, Noam Dagan, David Arinzon,
	Evgeny Ostrovsky, Ofir Tabachnik, Amit Bernstein, linux-kselftest,
	linux-doc, shuah, Jonathan Corbet, Shuah Khan, Simon Horman,
	vadim.fedorenko
In-Reply-To: <20260714020340.25014-2-akiyano@amazon.com>

On Tue, Jul 14, 2026 at 5:06 AM Arthur Kiyanovski <akiyano@amazon.com> wrote:

> +       n_samples = data->request.num_samples;
> +       sts.clockid = data->request.clock_id;
> +       kfree(data);
> +       data = kzalloc(struct_size(data, timestamps, n_samples), GFP_KERNEL);
> +       if (!data)
> +               return -ENOMEM;
any idea why you free then allocate back?

> --- a/include/linux/ptp_clock_kernel.h
> +++ b/include/linux/ptp_clock_kernel.h
> @@ -123,11 +123,34 @@ struct ptp_system_timestamp {
>   *               reading the lowest bits of the PHC timestamp and the second
>   *               reading immediately follows that.
>   *
> + * @gettimexattrs64:  Reads the current time from the hardware clock and
> + *                    optionally also the system clock with additional clock
> + *                    attributes.
> + *                    parameter ts: Holds the PHC timestamp.
> + *                    parameter sts: If not NULL, it holds a pair of
> + *                    timestamps from the system clock. The first reading is
> + *                    made right before reading the lowest bits of the PHC
> + *                    timestamp and the second reading immediately follows that.
The descriptions for ts and sts here are identical to gettimex64.
Instead of duplicating this text, could we just refer back to the
gettimex64 documentation?

^ permalink raw reply

* Re: [PATCH v2] arch: arm64: add early_param idle=<wfi|yield|nop>
From: Sudeep Holla @ 2026-07-16 10:58 UTC (permalink / raw)
  To: Yureka Lilian
  Cc: Jonathan Corbet, Shuah Khan, Catalin Marinas, Will Deacon,
	Anshuman Khandual, linux-doc, linux-kernel, linux-arm-kernel
In-Reply-To: <5f1af8d0-ed73-4dac-9bd3-30f8e82d4b26@cyberchaos.dev>

On Wed, Jul 15, 2026 at 03:24:03PM +0200, Yureka Lilian wrote:
> On 7/13/26 11:57, Sudeep Holla wrote:

[...]

> > 
> > If WFI is replaced by NOP or YIELD, do we really need to save/restore
> > IRQ context used for pseudo-NMIs which may add some overhead ?
> 
> There are optimizations, even in the ARM64_IDLE_WFI case, which could be
> done here, such as checking that an interrupt actually occurred before
> continuing (and repeating the wfi/yield/nop until this is the case). I would
> prefer not to do these optimizations in this patch series, and leave it as
> future work, because I don't understand all the implications at this point.
> Is this acceptable for you?
> 

Sure, I am fine with that. Anyways it is left to the maintainers.

-- 
Regards,
Sudeep

^ permalink raw reply

* Re: [PATCH v12 02/29] arm64/fpsimd: Update FA64 and ZT0 enables when loading SME state
From: Mark Rutland @ 2026-07-16 10:52 UTC (permalink / raw)
  To: Mark Brown
  Cc: Marc Zyngier, Joey Gouly, Catalin Marinas, Suzuki K Poulose,
	Will Deacon, Paolo Bonzini, Jonathan Corbet, Shuah Khan,
	Oliver Upton, Dave Martin, Fuad Tabba, Ben Horgan,
	Jean-Philippe Brucker, linux-arm-kernel, kvmarm, linux-kernel,
	kvm, linux-doc, linux-kselftest, Peter Maydell, Eric Auger
In-Reply-To: <20260709-kvm-arm64-sme-v12-2-d0301d79ef58@kernel.org>

Hi Mark,

This generally looks good, but I have a few thoughts below. The key
things are:

(1) I think the commit message could be clearer. I've provided some
    suggested wording below.

(2) There are a couple of trivial bits to change in the code, commented
    below.

(3) AFAICT there's a latent bug where ZCR_ELx[63:4] aren't always reset
    before returning to userspace when we have a return from idle. We
    get away with that because those bits haven't been allocated yet,
    but given that they're RES0, we should reset them to avoid
    unexpected behaviour once they are allocated a meaning in future.
    
    This patch happens to fix that, but for the sake of stable I think
    we should have a preparatory patch to reset ZCR_ELx when returning
    from idle. For example, rename sme_suspend_exit() to
    fpsimd_suspend_exit(), and add the appropriate logic there.

On Thu, Jul 09, 2026 at 07:27:23PM +0100, Mark Brown wrote:
> Currently we enable EL0 and EL1 access to FA64 and ZT0 at boot and leave
> them enabled throughout the runtime of the system. When we add KVM support
> we will need to make this configuration dynamic, these features may be
> disabled for some KVM guests. Since the host kernel saves the floating
> point state for non-protected guests and we wish to avoid KVM having to
> reload the floating point state needlessly on guest reentry let's move the
> configuration of these enables to the floating point state reload.

This description is hard to follow.

How about:

| arm64/fpsimd: Configure all ZCR/SMCR bits when loading task state
|
| Currently, when loading a task's SVE and/or SME state, we configure
| ZCR_ELx.LEN and/or SMCR_ELx.LEN with read-modify-write sequences,
| preserving all other bits of the registers (e.g. SMCR_ELx.{FA64,ZT0}).
| This relies on all the other bits already holding the expected value
| for the task.
|
| It would be simpler and more robust to configure all the bits without
| a read-modify-write sequence:
|
| * Doing so will remove the need to configure these registers during
|   feature detection and when returning from idle, simplifying the code
|   and removing some redundant writes to the registers.
|
| * Doing so will permit KVM to clobber other bits of the registers when
|   no task state is bound. Along with other changes (e.g. when saving
|   state), this will make it possible for KVM to expose a different
|   configuration to guests (e.g. disabling ZT0 for vCPUs, even if ZT0
|   is exposed to userspace).
|
| When SVE and/or SME is not exposed to userspace (and userspace access
| it prevented by CPACR_ELx.{ZEN,SMEN}), the values of ZCR_ELx and
| SMCR_ELx are immaterial, as all functionality affected by those
| registers will be trapped. Thus there is no need to reset those
| registers to ensure correct behaviour when SVE and/or SME is not used
| by a task.

I've deliberately avoided detail on how KVM saves or relaods state,
because there are a large number of interacting pieces there, and I
don't think it's material beyond "this change is a step towards allowing
KVM to expose a different configuration to vCPUs".

> Provide a helper task_smcr() which generates the value of SMCR_EL1 to
> use based on the task struct and use it when we set the vector length
> SMCR_EL1, currently while handling SME access traps or FP state load.
> 
> For consistency handle ZCR_EL1 the same way, currently the only field it
> has is the LEN so the change is less meaningful there.

I think we can drop this part of the description.

> Signed-off-by: Mark Brown <broonie@kernel.org>
> ---
>  arch/arm64/include/asm/fpsimd.h |  2 --
>  arch/arm64/kernel/cpufeature.c  |  2 --
>  arch/arm64/kernel/fpsimd.c      | 72 ++++++++++++++++++-----------------------
>  3 files changed, 31 insertions(+), 45 deletions(-)

[...]

> @@ -402,12 +421,13 @@ static void task_fpsimd_load(void)
>  
>  	/* Restore SME, override SVE register configuration if needed */
>  	if (system_supports_sme()) {
> -		unsigned long sme_vl = task_get_sme_vl(current);
> -
> -		/* Ensure VL is set up for restoring data */
> +		/*
> +		 * Ensure VL is set up for restoring data.  KVM might
> +		 * disable subfeatures so we reset them each time.
> +		 */

Let's delete the comment.

The existing principle that we must restore the length before the data
also applies to ZCR_ELx (which has no similar comment), and the general
principle that KVM might clobber the register is common to all the
FPSIMD/SVE/SME registers.

>  		if (test_thread_flag(TIF_SME)) {
> -			unsigned long vq = sve_vq_from_vl(sme_vl);
> -			sysreg_clear_set_s(SYS_SMCR_EL1, SMCR_ELx_LEN, vq - 1);
> +			sysreg_cond_update_s(SYS_SMCR_EL1, task_smcr(current));
> +			isb();
>  		}

What's the ISB for? That mysteriously appeared in v11 without
explanation. It wasn't in the original code, prior versions of the
series, or my suggested rework with the task_smcr() helper.

I don't believe it's necessary to add an ISB here.

Mark.

^ permalink raw reply

* Re: [PATCH v21 06/14] dmaengine: qcom: bam_dma: add support for BAM locking
From: Stephan Gerhold @ 2026-07-16 10:44 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Jonathan Corbet, Thara Gopinath, Herbert Xu, David S. Miller,
	Udit Tiwari, Md Sadre Alam, Dmitry Baryshkov,
	Manivannan Sadhasivam, 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, Bartosz Golaszewski, Bartosz Golaszewski,
	Vinod Koul
In-Reply-To: <CAMRc=McZK1=S2SFgpCjtHSRecY0SiQWbBqsUxGX1w4229s2yKg@mail.gmail.com>

On Thu, Jul 16, 2026 at 12:43:04AM -0700, Bartosz Golaszewski wrote:
> On Wed, 15 Jul 2026 18:06:27 +0200, Stephan Gerhold
> <stephan.gerhold@linaro.org> said:
> > On Wed, Jul 15, 2026 at 09:06:46PM +0530, Vinod Koul wrote:
> >> On 15-07-26, 14:43, Bartosz Golaszewski wrote:
> >> > On Tue, 14 Jul 2026 11:49:14 +0200, Stephan Gerhold
> >
> 
> ...
> 
> > I would avoid using struct dma_slave_config->dst_addr (like in v11) [1]
> > though, since it's more a "address to dummy register" now than the
> > actual dst_addr of the peripheral. That's why I suggested
> > struct dma_slave_config->peripheral_config.
> 
> As in:
> 
> struct bam_dma_peripheral_config {
> 	phys_addr_t scratchpad_addr;
> };
> 
> struct dma_slave_config cfg = { .peripheral_config = &scratchpad_addr };
> 
> ?
> 

I think you have the same thought in mind, but there are a little bit
too many odd typos in your example to give you a simple "yes". :-)
(Why assign &scratchpad_addr to .peripheral_config and not the struct?)

While at it, perhaps we could rename scratchpad_addr to have something
with "lock" in the name, to make it clearer what it is used for?
(lock_scratchpad_addr? lock_dummy_register? lock_scratch_register?)

Thanks,
Stephan

^ permalink raw reply

* Re: [PATCH v2] overlayfs.rst: remove mention of workdir needing to be empty
From: Amir Goldstein @ 2026-07-16  9:53 UTC (permalink / raw)
  To: jesse.vangavere, Jonathan Corbet
  Cc: Miklos Szeredi, Shuah Khan, linux-unionfs, linux-doc,
	linux-kernel
In-Reply-To: <20260716-overlayfs-rst-update-v2-1-ba25393ea7a3@teledyne.com>

On Thu, Jul 16, 2026 at 8:23 AM Jesse Van Gavere via B4 Relay
<devnull+jesse.vangavere.teledyne.com@kernel.org> wrote:
>
> From: Jesse Van Gavere <jesse.vangavere@teledyne.com>
>
> This requirement has not been true since v4.8 when automatic cleanup was
> added in commit eea2fb4851e9 ("ovl: proper cleanup of workdir")
>
> Signed-off-by: Jesse Van Gavere <jesse.vangavere@teledyne.com>

Reviewed-by: Amir Goldstein <amir73il@gmail.com>

Jon,

Can you please pick this one?

Thanks,
Amir.

> ---
> Changes in v2:
> - Handle remark from Amir Goldstein to drop workdir cleanup reference
> - Link to v1: https://patch.msgid.link/20260715-overlayfs-rst-update-v1-1-70625a67880e@teledyne.com
>
> To: Miklos Szeredi <miklos@szeredi.hu>
> To: Amir Goldstein <amir73il@gmail.com>
> To: Jonathan Corbet <corbet@lwn.net>
> To: Shuah Khan <skhan@linuxfoundation.org>
> Cc: linux-unionfs@vger.kernel.org
> Cc: linux-doc@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> ---
>  Documentation/filesystems/overlayfs.rst | 3 +--
>  1 file changed, 1 insertion(+), 2 deletions(-)
>
> diff --git a/Documentation/filesystems/overlayfs.rst b/Documentation/filesystems/overlayfs.rst
> index eb846518e6ac..ba41c0694e62 100644
> --- a/Documentation/filesystems/overlayfs.rst
> +++ b/Documentation/filesystems/overlayfs.rst
> @@ -123,8 +123,7 @@ At mount time, the two directories given as mount options "lowerdir" and
>    mount -t overlay overlay -olowerdir=/lower,upperdir=/upper,\
>    workdir=/work /merged
>
> -The "workdir" needs to be an empty directory on the same filesystem
> -as upperdir.
> +The "workdir" needs to be a directory on the same filesystem as upperdir.
>
>  Then whenever a lookup is requested in such a merged directory, the
>  lookup is performed in each actual directory and the combined result
>
> ---
> base-commit: 58717b2a1365d06c8c64b72aa948541b53fe31eb
> change-id: 20260715-overlayfs-rst-update-750d58792f79
>
> Best regards,
> --
> Jesse Van Gavere <jesse.vangavere@teledyne.com>
>
>

^ permalink raw reply

* Re: [PATCH v4 6/7] net: wwan: t9xx: Add AT & MBIM WWAN ports
From: Simon Horman @ 2026-07-16  9:34 UTC (permalink / raw)
  To: jackbb_wu
  Cc: Simon Horman, loic.poulain, ryazanov.s.a, johannes, andrew+netdev,
	davem, edumazet, kuba, pabeni, wen-zhi.huang, shi-wei.yeh,
	Minano.tseng, matthias.bgg, angelogioacchino.delregno, corbet,
	skhan, linux-kernel, netdev, linux-arm-kernel, linux-mediatek,
	linux-doc
In-Reply-To: <20260709-t9xx_driver_v1-v4-6-a8c009d509c5@compal.com>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev
---
--- Patch [6]: [PATCH v4 6/7] net: wwan: t9xx: Add AT & MBIM WWAN ports ---
commit 1739a55610bf7a44ce24874819e01c07236d9442
Author: Jack Wu <jackbb_wu@compal.com>

net: wwan: t9xx: Add AT & MBIM WWAN ports

Add AT & MBIM ports to the port infrastructure.
The WWAN initialization method is responsible for creating the
corresponding ports using the WWAN framework infrastructure. The
implemented WWAN port operations are start, stop, tx, tx_blocking
and tx_poll.

> diff --git a/drivers/net/wwan/t9xx/mtk_port_io.c b/drivers/net/wwan/t9xx/mtk_port_io.c
> index e3a2de6d2f297..882254b74026f 100644
> --- a/drivers/net/wwan/t9xx/mtk_port_io.c
> +++ b/drivers/net/wwan/t9xx/mtk_port_io.c

[ ... ]

> @@ -234,6 +376,198 @@ static const struct port_ops port_internal_ops = {
>  	.recv = mtk_port_internal_recv,
>  };
>  
> +static int mtk_port_wwan_open(struct wwan_port *w_port)

[ ... ]

> +static int mtk_port_wwan_write(struct wwan_port *w_port, struct sk_buff *skb)
> +{
> +	struct mtk_port *port = wwan_port_get_drvdata(w_port);
> +	union user_buf user_buf;
> +	int ret;
> +
> +	if (unlikely(!skb->len)) {
> +		consume_skb(skb);
> +		return 0;
> +	}
> +
> +	port->info.flags &= ~PORT_F_BLOCKING;
> +	user_buf.kbuf = (void *)skb->data;
> +	ret = mtk_port_common_write(port, user_buf, skb->len, false);
> +	if (ret < 0)
> +		return ret;

[Severity: High]
If mtk_port_common_write() successfully sends the first few fragments but
fails on subsequent ones, returning a positive partial write count, does
this code silently drop the remaining data?

The condition ret < 0 is false for partial writes, so the SKB is consumed
and 0 is returned to the caller, falsely reporting complete success to the
WWAN core.

> +
> +	consume_skb(skb);
> +	return 0;
> +}
> +
> +static int mtk_port_wwan_write_blocking(struct wwan_port *w_port, struct sk_buff *skb)
> +{
> +	struct mtk_port *port = wwan_port_get_drvdata(w_port);
> +	union user_buf user_buf;
> +	int ret;
> +
> +	if (unlikely(!skb->len)) {
> +		consume_skb(skb);
> +		return 0;
> +	}
> +
> +	port->info.flags |= PORT_F_BLOCKING;
> +	user_buf.kbuf = (void *)skb->data;
> +	ret = mtk_port_common_write(port, user_buf, skb->len, false);
> +	if (ret < 0)
> +		return ret;

[Severity: High]
Can the same silent drop of partially written SKBs happen here?

> +
> +	consume_skb(skb);
> +	return 0;
> +}

[ ... ]

> +static void mtk_port_wwan_enable(struct mtk_port *port)
> +{
> +	struct mtk_port_mngr *port_mngr;
> +	int ret;
> +
> +	port_mngr = port->port_mngr;
> +
> +	if (test_bit(PORT_S_ENABLE, &port->status))
> +		return;
> +
> +	ret = mtk_port_ch_enable(port);
> +	if (ret && ret != -EBUSY)
> +		return;
> +
> +	port->w_priv.w_port = wwan_create_port(port_mngr->ctrl_blk->mdev->dev,
> +					       port->w_priv.w_type,
> +					       &wwan_ops, NULL, port);
> +	if (IS_ERR(port->w_priv.w_port)) {

[Severity: High]
Can a race condition occur here if wwan_create_port() fails and returns an
error pointer?

Once mtk_port_ch_enable() completes, hardware RX can start. If
wwan_create_port() fails, an ERR_PTR is stored in port->w_priv.w_port
without holding w_lock. Concurrently, the RX interrupt handler could call
mtk_port_wwan_recv():

	mutex_lock(&port->w_priv.w_lock);
	if (!port->w_priv.w_port) {
		mutex_unlock(&port->w_priv.w_lock);
		return -ENXIO;
	}
	wwan_port_rx(port->w_priv.w_port, skb);

Because an ERR_PTR is not NULL, mtk_port_wwan_recv() will pass it to
wwan_port_rx(), which will then dereference the error pointer.

> +		dev_warn(port_mngr->ctrl_blk->mdev->dev,
> +			 "Failed to create wwan port for (%s)\n", port->info.name);
> +		port->w_priv.w_port = NULL;
> +		mtk_port_ch_disable(port);
> +		return;
> +	}
> +
> +	set_bit(PORT_S_WR, &port->status);
> +	set_bit(PORT_S_ENABLE, &port->status);
> +}
> +

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox