[RFC PATCH 09/10] arm64/sve: Enable default vector length control via procfs
Dave Martin
Dave.Martin at arm.com
Thu Jan 12 03:26:08 PST 2017
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 <Dave.Martin at arm.com>
---
Caveats:
* /proc may not be the correct place for this (but it's not clear
where in sysfs it should go either, and it's a global control with
no corresponding kobject)
* There seems to be no suitable framework for writable controls of
this sort in /proc (unlike debugfs or sysfs). So, I had to write a
suspiciously large amount of code -- I may have missed an obvious
trick :P
arch/arm64/kernel/fpsimd.c | 160 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 158 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kernel/fpsimd.c b/arch/arm64/kernel/fpsimd.c
index f010a1c..3c0e08c 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.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>
@@ -102,9 +106,132 @@ void do_fpsimd_acc(unsigned int esr, struct pt_regs *regs)
/* 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;
#ifdef CONFIG_ARM64_SVE
+#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,
+};
+
+#endif /* !CONFIG_PROC_FS */
+
static void task_fpsimd_to_sve(struct task_struct *task);
static void task_fpsimd_load(struct task_struct *task);
static void task_fpsimd_save(struct task_struct *task);
@@ -299,7 +426,7 @@ void fpsimd_flush_thread(void)
!(current->thread.sve_flags & THREAD_VL_INHERIT)) {
BUG_ON(!sve_vl_valid(sve_max_vl));
- current->thread.sve_vl = sve_max_vl;
+ current->thread.sve_vl = sve_default_vl;
current->thread.sve_flags = 0;
}
}
@@ -723,6 +850,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;
arch_task_struct_size = sizeof(struct task_struct) +
35 * sve_max_vl;
@@ -734,6 +869,27 @@ void __init fpsimd_init_task_struct_size(void)
/*
* FP/SIMD support code initialisation.
*/
+static int __init sve_procfs_init(void)
+{
+#if defined(CONFIG_ARM64_SVE) && defined(CONFIG_PROC_FS)
+ struct proc_dir_entry *dir;
+
+ if (!(elf_hwcap & HWCAP_SVE))
+ return 0;
+
+ /* 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;
+#endif
+
+ return 0;
+}
+
static int __init fpsimd_init(void)
{
if (elf_hwcap & HWCAP_FP) {
@@ -749,6 +905,6 @@ static int __init fpsimd_init(void)
if (!(elf_hwcap & HWCAP_SVE))
pr_info("Scalable Vector Extension available\n");
- return 0;
+ return sve_procfs_init();
}
late_initcall(fpsimd_init);
--
2.1.4
More information about the linux-arm-kernel
mailing list