Subject: [RFC PATCH 06/13] powerpc/dexcr: Add prctl implementation

From: Benjamin Gray <[email-masked]>
Date: Mon, 28 Nov 2022 13:44:51 +1100

Adds an initial prctl interface implementation. Unprivileged processes
can query the current prctl setting, including whether an aspect is
implemented by the hardware or is permitted to be modified by a setter
prctl. Editable aspects can be changed by a CAP_SYS_ADMIN privileged
process.

The prctl setting represents what the process itself has requested, and
does not account for any overrides. Either the kernel or a hypervisor
may enforce a different setting for an aspect.

Userspace can access a readonly view of the current DEXCR via SPR 812,
and a readonly view of the aspects enforced by the hypervisor via
SPR 455. A bitwise OR of these two SPRs will give the effective
DEXCR aspect state of the process.

Signed-off-by: Benjamin Gray <[email-masked]>
---
 arch/powerpc/include/asm/processor.h |  13 +++
 arch/powerpc/kernel/dexcr.c          | 133 ++++++++++++++++++++++++++-
 arch/powerpc/kernel/process.c        |   6 ++
 3 files changed, 151 insertions(+), 1 deletion(-)

diff --git a/arch/powerpc/include/asm/processor.h b/arch/powerpc/include/asm/processor.h
index 2381217c95dc..4c995258f668 100644
--- a/arch/powerpc/include/asm/processor.h
+++ b/arch/powerpc/include/asm/processor.h
@@ -265,6 +265,9 @@ struct thread_struct {
 	unsigned long   sier2;
 	unsigned long   sier3;
 	unsigned long	hashkeyr;
+	unsigned int	dexcr_override;
+	unsigned int	dexcr_mask;
+	unsigned int	dexcr_forced;
 
 #endif
 };
@@ -338,6 +341,16 @@ extern int set_endian(struct task_struct *tsk, unsigned int val);
 extern int get_unalign_ctl(struct task_struct *tsk, unsigned long adr);
 extern int set_unalign_ctl(struct task_struct *tsk, unsigned int val);
 
+#ifdef CONFIG_PPC_BOOK3S_64
+
+#define PPC_GET_DEXCR_ASPECT(tsk, asp) dexcr_prctl_get((tsk), (asp))
+#define PPC_SET_DEXCR_ASPECT(tsk, asp, val) dexcr_prctl_set((tsk), (asp), (val))
+
+int dexcr_prctl_get(struct task_struct *tsk, unsigned long asp);
+int dexcr_prctl_set(struct task_struct *tsk, unsigned long asp, unsigned long val);
+
+#endif
+
 extern void load_fp_state(struct thread_fp_state *fp);
 extern void store_fp_state(struct thread_fp_state *fp);
 extern void load_vr_state(struct thread_vr_state *vr);
diff --git a/arch/powerpc/kernel/dexcr.c b/arch/powerpc/kernel/dexcr.c
index 11515e67afac..9290beed722a 100644
--- a/arch/powerpc/kernel/dexcr.c
+++ b/arch/powerpc/kernel/dexcr.c
@@ -1,5 +1,8 @@
 #include <linux/cache.h>
+#include <linux/capability.h>
 #include <linux/init.h>
+#include <linux/prctl.h>
+#include <linux/sched.h>
 
 #include <asm/cpu_has_feature.h>
 #include <asm/cputable.h>
@@ -11,6 +14,10 @@
 
 #define DEFAULT_DEXCR	0
 
+/* Allow process configuration of these by default */
+#define DEXCR_PRCTL_EDITABLE (DEXCR_PRO_SBHE | DEXCR_PRO_IBRTPD | \
+			      DEXCR_PRO_SRAPD | DEXCR_PRO_NPHIE)
+
 static int __init dexcr_init(void)
 {
 	if (!early_cpu_has_feature(CPU_FTR_ARCH_31))
@@ -43,5 +50,129 @@ bool is_hashchk_trap(struct pt_regs const *regs)
 
 unsigned long get_thread_dexcr(struct thread_struct const *t)
 {
-	return DEFAULT_DEXCR;
+	unsigned long dexcr = DEFAULT_DEXCR;
+
+	/* Apply prctl overrides */
+	dexcr = (dexcr & ~t->dexcr_mask) | t->dexcr_override;
+
+	return dexcr;
+}
+
+static void update_dexcr_on_cpu(void *info)
+{
+	mtspr(SPRN_DEXCR, get_thread_dexcr(&current->thread));
+}
+
+static int dexcr_aspect_get(struct task_struct *task, unsigned int aspect)
+{
+	int ret = 0;
+
+	if (aspect & DEXCR_PRCTL_EDITABLE)
+		ret |= PR_PPC_DEXCR_PRCTL;
+
+	if (aspect & task->thread.dexcr_mask) {
+		if (aspect & task->thread.dexcr_override) {
+			if (aspect & task->thread.dexcr_forced)
+				ret |= PR_PPC_DEXCR_FORCE_SET_ASPECT;
+			else
+				ret |= PR_PPC_DEXCR_SET_ASPECT;
+		} else {
+			ret |= PR_PPC_DEXCR_CLEAR_ASPECT;
+		}
+	}
+
+	return ret;
+}
+
+int dexcr_prctl_get(struct task_struct *task, unsigned long which)
+{
+	switch (which) {
+	case PR_PPC_DEXCR_SBHE:
+		if (!cpu_has_feature(CPU_FTR_DEXCR_SBHE))
+			return -ENODEV;
+		return dexcr_aspect_get(task, DEXCR_PRO_SBHE);
+	case PR_PPC_DEXCR_IBRTPD:
+		if (!cpu_has_feature(CPU_FTR_DEXCR_IBRTPD))
+			return -ENODEV;
+		return dexcr_aspect_get(task, DEXCR_PRO_IBRTPD);
+	case PR_PPC_DEXCR_SRAPD:
+		if (!cpu_has_feature(CPU_FTR_DEXCR_SRAPD))
+			return -ENODEV;
+		return dexcr_aspect_get(task, DEXCR_PRO_SRAPD);
+	case PR_PPC_DEXCR_NPHIE:
+		if (!cpu_has_feature(CPU_FTR_DEXCR_NPHIE))
+			return -ENODEV;
+		return dexcr_aspect_get(task, DEXCR_PRO_NPHIE);
+	default:
+		return -ENODEV;
+	}
+}
+
+static int dexcr_aspect_set(struct task_struct *task, unsigned int aspect, unsigned long ctrl)
+{
+	if (!(aspect & DEXCR_PRCTL_EDITABLE))
+		return -ENXIO;  /* Aspect is not allowed to be changed by prctl */
+
+	if (aspect & task->thread.dexcr_forced)
+		return -EPERM;  /* Aspect has been forced to current state */
+
+	switch (ctrl) {
+	case PR_PPC_DEXCR_SET_ASPECT:
+		task->thread.dexcr_mask |= aspect;
+		task->thread.dexcr_override |= aspect;
+		break;
+	case PR_PPC_DEXCR_FORCE_SET_ASPECT:
+		task->thread.dexcr_mask |= aspect;
+		task->thread.dexcr_override |= aspect;
+		task->thread.dexcr_forced |= aspect;
+		break;
+	case PR_PPC_DEXCR_CLEAR_ASPECT:
+		task->thread.dexcr_mask |= aspect;
+		task->thread.dexcr_override &= ~aspect;
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	return 0;
+}
+
+int dexcr_prctl_set(struct task_struct *task, unsigned long which, unsigned long ctrl)
+{
+	int err = 0;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	switch (which) {
+	case PR_PPC_DEXCR_SBHE:
+		if (!cpu_has_feature(CPU_FTR_DEXCR_SBHE))
+			return -ENODEV;
+		err = dexcr_aspect_set(task, DEXCR_PRO_SBHE, ctrl);
+		break;
+	case PR_PPC_DEXCR_IBRTPD:
+		if (!cpu_has_feature(CPU_FTR_DEXCR_IBRTPD))
+			return -ENODEV;
+		err = dexcr_aspect_set(task, DEXCR_PRO_IBRTPD, ctrl);
+		break;
+	case PR_PPC_DEXCR_SRAPD:
+		if (!cpu_has_feature(CPU_FTR_DEXCR_SRAPD))
+			return -ENODEV;
+		err = dexcr_aspect_set(task, DEXCR_PRO_SRAPD, ctrl);
+		break;
+	case PR_PPC_DEXCR_NPHIE:
+		if (!cpu_has_feature(CPU_FTR_DEXCR_NPHIE))
+			return -ENODEV;
+		err = dexcr_aspect_set(task, DEXCR_PRO_NPHIE, ctrl);
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	if (err)
+		return err;
+
+	update_dexcr_on_cpu(NULL);
+
+	return 0;
 }
diff --git a/arch/powerpc/kernel/process.c b/arch/powerpc/kernel/process.c
index 4d7b0c7641d0..a280842750f9 100644
--- a/arch/powerpc/kernel/process.c
+++ b/arch/powerpc/kernel/process.c
@@ -1825,6 +1825,12 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
 #ifdef CONFIG_PPC_BOOK3S_64
 	if (cpu_has_feature(CPU_FTR_DEXCR_NPHIE))
 		p->thread.hashkeyr = current->thread.hashkeyr;
+
+	if (cpu_has_feature(CPU_FTR_ARCH_31)) {
+		p->thread.dexcr_override = current->thread.dexcr_override;
+		p->thread.dexcr_mask = current->thread.dexcr_mask;
+		p->thread.dexcr_forced = current->thread.dexcr_forced;
+	}
 #endif
 	/*
 	 * Run with the current AMR value of the kernel
-- 
2.38.1