[RFC PATCH v2 37/41] arm64/sve: Enable default vector length control via procfs

Dave Martin <[email protected]>
Newsgroups gmane.comp.gdb.devel,gmane.linux.ports.arm.kernel
Message-ID <[email protected]>
This patch makes the default SVE vector length at exec() for user tasks
controllable via procfs, in /proc/cpu/sve_default_vector_length.

Limited effort is made to return sensible errors when writing the
procfs file, and anyway the value gets silently clamped to the
maximum VL supported by the platform: users should close and reopen
the file and read back to see the result.

Signed-off-by: Dave Martin <[email protected]>
---
 arch/arm64/kernel/fpsimd.c | 162 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 160 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kernel/fpsimd.c b/arch/arm64/kernel/fpsimd.c
index 982b1d7..ddb651a 100644
--- a/arch/arm64/kernel/fpsimd.c
+++ b/arch/arm64/kernel/fpsimd.c
@@ -22,8 +22,12 @@
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/prctl.h>
+#include <linux/proc_fs.h>
 #include <linux/sched/signal.h>
+#include <linux/seq_file.h>
 #include <linux/signal.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
 #include <linux/hardirq.h>
 
 #include <asm/fpsimd.h>
@@ -100,6 +104,8 @@ static DEFINE_PER_CPU(struct fpsimd_state *, fpsimd_last_state);
 
 /* Maximum supported vector length across all CPUs (initially poisoned) */
 int sve_max_vl = -1;
+/* Default VL for tasks that don't set it explicitly: */
+int sve_default_vl = -1;
 
 void *__sve_state(struct task_struct *task)
 {
@@ -334,13 +340,154 @@ int sve_get_task_vl(struct task_struct *task)
 	return sve_prctl_status(task);
 }
 
+#ifdef CONFIG_PROC_FS
+
+struct default_vl_write_state {
+	bool invalid;
+	size_t len;
+	char buf[40];	/* enough for "0x" + 64-bit hex integer + NUL */
+};
+
+static int sve_default_vl_show(struct seq_file *s, void *data)
+{
+	seq_printf(s, "%d\n", sve_default_vl);
+	return 0;
+}
+
+static ssize_t sve_default_vl_write(struct file *f, const char __user *buf,
+				    size_t size, loff_t *pos)
+{
+	struct default_vl_write_state *state =
+		((struct seq_file *)f->private_data)->private;
+	long ret;
+
+	if (!size)
+		return 0;
+
+	if (*pos > sizeof(state->buf) ||
+	    size >= sizeof(state->buf) - *pos) {
+		ret = -ENOSPC;
+		goto error;
+	}
+
+	ret = copy_from_user(state->buf + *pos, buf, size);
+	if (ret > 0)
+		ret = -EINVAL;
+	if (ret)
+		goto error;
+
+	*pos += size;
+	if (*pos > state->len)
+		state->len = *pos;
+
+	return size;
+
+error:
+	state->invalid = true;
+	return ret;
+}
+
+static int sve_default_vl_release(struct inode *i, struct file *f)
+{
+	int ret = 0;
+	int t;
+	unsigned long value;
+	struct default_vl_write_state *state =
+		((struct seq_file *)f->private_data)->private;
+
+	if (!(f->f_mode & FMODE_WRITE))
+		goto out;
+
+	if (state->invalid)
+		goto out;
+
+	if (state->len >= sizeof(state->buf)) {
+		WARN_ON(1);
+		state->len = sizeof(state->buf) - 1;
+	}
+
+	state->buf[state->len] = '\0';
+	t = kstrtoul(state->buf, 0, &value);
+	if (t)
+		ret = t;
+
+	if (!sve_vl_valid(value))
+		ret = -EINVAL;
+
+	if (!sve_vl_valid(sve_max_vl)) {
+		WARN_ON(1);
+		ret = -EINVAL;
+	}
+
+	if (value > sve_max_vl)
+		value = sve_max_vl;
+
+	if (!ret)
+		sve_default_vl = value;
+
+out:
+	t = seq_release_private(i, f);
+	return ret ? ret : t;
+}
+
+static int sve_default_vl_open(struct inode *i, struct file *f)
+{
+	struct default_vl_write_state *data = NULL;
+	int ret;
+
+	if (f->f_mode & FMODE_WRITE) {
+		data = kzalloc(sizeof(*data), GFP_KERNEL);
+		if (!data)
+			return -ENOMEM;
+
+		data->invalid = false;
+		data->len = 0;
+	}
+
+	ret = single_open(f, sve_default_vl_show, data);
+	if (ret)
+		kfree(data);
+
+	return ret;
+}
+
+static const struct file_operations sve_default_vl_fops = {
+	.open		= sve_default_vl_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= sve_default_vl_release,
+	.write		= sve_default_vl_write,
+};
+
+static int __init sve_procfs_init(void)
+{
+	struct proc_dir_entry *dir;
+
+	/* This should be moved elsewhere is anything else ever uses it: */
+	dir = proc_mkdir("cpu", NULL);
+	if (!dir)
+		return -ENOMEM;
+
+	if (!proc_create("sve_default_vector_length",
+			 S_IRUGO | S_IWUSR, dir, &sve_default_vl_fops))
+		return -ENOMEM;
+
+	return 0;
+}
+
+#else /* ! CONFIG_PROC_FS && CONFIG_ARM64_SVE */
+static int __init sve_procfs_init(void) { return 0; }
+#endif /* ! CONFIG_PROC_FS && CONFIG_ARM64_SVE */
+
 #else /* ! CONFIG_ARM64_SVE */
 
 /* Dummy declarations for usage protected with IS_ENABLED(CONFIG_ARM64_SVE): */
 extern int sve_max_vl;
+extern int sve_default_vl;
 extern void clear_sve_regs(struct task_struct *task);
 extern void *sve_pffr(struct task_struct *task);
 extern void fpsimd_to_sve(struct task_struct *task);
+extern int __init sve_procfs_init(void);
 
 /* Functions that map to no-ops without SVE: */
 static void sve_to_fpsimd(struct task_struct *task __always_unused) { }
@@ -489,13 +636,13 @@ void fpsimd_flush_thread(void)
 		clear_sve_regs(current);
 
 		current->thread.sve_vl = current->thread.sve_vl_onexec ?
-			current->thread.sve_vl_onexec : sve_max_vl;
+			current->thread.sve_vl_onexec : sve_default_vl;
 
 		/*
 		 * User tasks must have a valid vector length set, but tasks
 		 * forked early (e.g., init) may not initially have one.
 		 * By now, we will know what the hardware supports, so
-		 * sve_max_vl should be valid, and thus the above
+		 * sve_default_vl should be valid, and thus the above
 		 * assignment should ensure a valid VL for the task.
 		 * If not, something went badly wrong.
 		 */
@@ -717,6 +864,14 @@ void __init fpsimd_init_task_struct_size(void)
 	     & 0xf) == 1) {
 		/* FIXME: This should be the minimum across all CPUs */
 		sve_max_vl = sve_get_vl();
+		sve_default_vl = sve_max_vl;
+
+		/*
+		 * To avoid enlarging the signal frame by default, clamp to
+		 * 512 bits until/unless overridden by userspace:
+		 */
+		if (sve_default_vl > 512 / 8)
+			sve_default_vl = 512 / 8;
 
 		BUG_ON(!sve_vl_valid(sve_max_vl));
 		vq = sve_vq_from_vl(sve_max_vl);
@@ -746,6 +901,9 @@ static int __init fpsimd_init(void)
 	if (!(elf_hwcap & HWCAP_SVE))
 		pr_info("Scalable Vector Extension available\n");
 
+	if (IS_ENABLED(CONFIG_ARM64_SVE) && (elf_hwcap & HWCAP_SVE))
+		return sve_procfs_init();
+
 	return 0;
 }
 late_initcall(fpsimd_init);
-- 
2.1.4
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.