[kvm-unit-tests PATCH v8 31/35] powerpc: add usermode support

Nicholas Piggin
2024-04-05T08:37:57Z

The biggest difficulty for user mode is MMU support. Otherwise it is
a simple matter of setting and clearing MSR[PR] with rfid and sc
respectively.

Some common harness operations will fail in usermode, so some workarounds
are reqiured (e.g., puts() can't be used directly).

A usermode privileged instruction interrupt test is added.

Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
lib/powerpc/asm/processor.h | 9 +++++++++
lib/powerpc/asm/reg.h | 1 +
lib/powerpc/asm/smp.h | 1 +
lib/powerpc/io.c | 7 +++++++
lib/powerpc/processor.c | 38 +++++++++++++++++++++++++++++++++++++
lib/powerpc/rtas.c | 3 +++
lib/powerpc/setup.c | 8 ++++++--
lib/powerpc/spinlock.c | 4 ++++
lib/ppc64/mmu.c | 2 ++
powerpc/interrupts.c | 28 +++++++++++++++++++++++++++
10 files changed, 99 insertions(+), 2 deletions(-)

diff --git a/lib/powerpc/asm/processor.h b/lib/powerpc/asm/processor.h
index d348239c5..749155696 100644
--- a/lib/powerpc/asm/processor.h
+++ b/lib/powerpc/asm/processor.h
@@ -19,6 +19,8 @@ extern bool cpu_has_prefix;
extern bool cpu_has_sc_lev;
extern bool cpu_has_pause_short;

+bool in_usermode(void);
+
static inline uint64_t mfspr(int nr)
{
uint64_t ret;
@@ -51,6 +53,8 @@ static inline void local_irq_enable(void)
{
unsigned long msr;

+ assert(!in_usermode());
+
asm volatile(
" mfmsr %0 \n \
ori %0,%0,%1 \n \
@@ -62,6 +66,8 @@ static inline void local_irq_disable(void)
{
unsigned long msr;

+ assert(!in_usermode());
+
asm volatile(
" mfmsr %0 \n \
andc %0,%0,%1 \n \
@@ -90,4 +96,7 @@ static inline bool machine_is_pseries(void)
void enable_mcheck(void);
void disable_mcheck(void);

+void enter_usermode(void);
+void exit_usermode(void);
+
#endif /* _ASMPOWERPC_PROCESSOR_H_ */
diff --git a/lib/powerpc/asm/reg.h b/lib/powerpc/asm/reg.h
index b2fab4313..69ef21adb 100644
--- a/lib/powerpc/asm/reg.h
+++ b/lib/powerpc/asm/reg.h
@@ -58,5 +58,6 @@
#define MSR_SE UL(0x0400) /* Single Step Enable */
#define MSR_EE UL(0x8000)
#define MSR_ME UL(0x1000)
+#define MSR_PR UL(0x4000)

#endif
diff --git a/lib/powerpc/asm/smp.h b/lib/powerpc/asm/smp.h
index 820c05e9e..b96a55903 100644
--- a/lib/powerpc/asm/smp.h
+++ b/lib/powerpc/asm/smp.h
@@ -11,6 +11,7 @@ struct cpu {
unsigned long server_no;
unsigned long stack;
unsigned long exception_stack;
+ bool in_user;
secondary_entry_fn entry;
pgd_t *pgtable;
} __attribute__((packed)); /* used by asm */
diff --git a/lib/powerpc/io.c b/lib/powerpc/io.c
index cb7f2f050..5c2810884 100644
--- a/lib/powerpc/io.c
+++ b/lib/powerpc/io.c
@@ -11,6 +11,7 @@
#include <asm/setup.h>
#include <asm/processor.h>
#include <asm/atomic.h>
+#include <asm/smp.h>
#include "io.h"

static struct spinlock print_lock;
@@ -41,10 +42,16 @@ void io_init(void)

void puts(const char *s)
{
+ bool user = in_usermode();
+
+ if (user)
+ exit_usermode();
spin_lock(&print_lock);
while (*s)
putchar(*s++);
spin_unlock(&print_lock);
+ if (user)
+ enter_usermode();
}

/*
diff --git a/lib/powerpc/processor.c b/lib/powerpc/processor.c
index 09f6bb9d8..6c3000d5c 100644
--- a/lib/powerpc/processor.c
+++ b/lib/powerpc/processor.c
@@ -47,6 +47,8 @@ void do_handle_exception(struct pt_regs *regs)
unsigned char v;

__current_cpu = (struct cpu *)mfspr(SPR_SPRG0);
+ if (in_usermode())
+ current_cpu()->in_user = false;

/*
* We run with AIL=0, so interrupts taken with MMU disabled.
@@ -60,6 +62,8 @@ void do_handle_exception(struct pt_regs *regs)

if (v < 128 && handlers[v].func) {
handlers[v].func(regs, handlers[v].data);
+ if (regs->msr & MSR_PR)
+ current_cpu()->in_user = true;
return;
}

@@ -169,3 +173,37 @@ void disable_mcheck(void)
{
rfid_msr(mfmsr() & ~MSR_ME);
}
+
+bool in_usermode(void)
+{
+ return current_cpu()->in_user;
+}
+
+static void usermode_sc_handler(struct pt_regs *regs, void *data)
+{
+ regs->msr &= ~(MSR_PR|MSR_EE);
+ /* Interrupt return handler will keep in_user clear */
+}
+
+void enter_usermode(void)
+{
+ assert_msg(!in_usermode(), "enter_usermode called with in_usermode");
+ /* mfmsr would fault in usermode anyway */
+ assert_msg(!(mfmsr() & MSR_PR), "enter_usermode called from user mode");
+ assert_msg(!(mfmsr() & MSR_EE), "enter_usermode called with interrupts enabled");
+ assert_msg((mfmsr() & (MSR_IR|MSR_DR)) == (MSR_IR|MSR_DR),
+ "enter_usermode called with virtual memory disabled");
+
+ handle_exception(0xc00, usermode_sc_handler, NULL);
+ rfid_msr(mfmsr() | (MSR_PR|MSR_IR|MSR_DR|MSR_EE));
+ current_cpu()->in_user = true;
+}
+
+void exit_usermode(void)
+{
+ assert_msg(in_usermode(), "enter_usermode called with !in_usermode");
+ asm volatile("sc 0" ::: "memory");
+ handle_exception(0xc00, NULL, NULL);
+ assert(!in_usermode());
+ assert(!(mfmsr() & MSR_PR));
+}
diff --git a/lib/powerpc/rtas.c b/lib/powerpc/rtas.c
index b477a38e0..9c1e0affc 100644
--- a/lib/powerpc/rtas.c
+++ b/lib/powerpc/rtas.c
@@ -9,6 +9,7 @@
#include <libfdt/libfdt.h>
#include <devicetree.h>
#include <asm/spinlock.h>
+#include <asm/smp.h>
#include <asm/hcall.h>
#include <asm/io.h>
#include <asm/rtas.h>
@@ -137,6 +138,8 @@ int rtas_call(int token, int nargs, int nret, int *outputs, ...)
va_list list;
int ret;

+ assert_msg(!in_usermode(), "May not make RTAS call from user mode\n");
+
spin_lock(&rtas_lock);

va_start(list, outputs);
diff --git a/lib/powerpc/setup.c b/lib/powerpc/setup.c
index 977608288..da56cb369 100644
--- a/lib/powerpc/setup.c
+++ b/lib/powerpc/setup.c
@@ -202,8 +202,11 @@ void cpu_init(struct cpu *cpu, int cpu_id)
cpu->exception_stack = (unsigned long)memalign_pages(SZ_4K, SZ_64K);
cpu->exception_stack += SZ_64K - 64;
cpu->pgtable = NULL;
+ cpu->in_user = false;
}

+bool is_hvmode;
+
void setup(const void *fdt)
{
void *freemem = &stacktop;
@@ -212,8 +215,6 @@ void setup(const void *fdt)
u32 fdt_size;
int ret;

- cpu_has_hv = !!(mfmsr() & (1ULL << MSR_HV_BIT));
-
memset(cpus, 0xff, sizeof(cpus));

cpu = &cpus[0];
@@ -221,10 +222,13 @@ void setup(const void *fdt)
cpu->exception_stack = (unsigned long)boot_exception_stack;
cpu->exception_stack += SZ_64K - 64;
cpu->pgtable = NULL;
+ cpu->in_user = false;

mtspr(SPR_SPRG0, (unsigned long)cpu);
__current_cpu = cpu;

+ cpu_has_hv = !!(mfmsr() & (1ULL << MSR_HV_BIT));
+
enable_mcheck();

/*
diff --git a/lib/powerpc/spinlock.c b/lib/powerpc/spinlock.c
index 623a1f2c1..2c4904a33 100644
--- a/lib/powerpc/spinlock.c
+++ b/lib/powerpc/spinlock.c
@@ -9,6 +9,8 @@
*/
void spin_lock(struct spinlock *lock)
{
+ assert(!in_usermode());
+
if (!multithreaded) {
assert(lock->v == 0);
lock->v = 1;
@@ -20,7 +22,9 @@ void spin_lock(struct spinlock *lock)

void spin_unlock(struct spinlock *lock)
{
+ assert(!in_usermode());
assert(lock->v == 1);
+
if (!multithreaded) {
lock->v = 0;
} else {
diff --git a/lib/ppc64/mmu.c b/lib/ppc64/mmu.c
index 84be31752..bdc5e4637 100644
--- a/lib/ppc64/mmu.c
+++ b/lib/ppc64/mmu.c
@@ -42,6 +42,7 @@ void mmu_enable(pgd_t *pgtable)

cpu->pgtable = pgtable;

+ assert(!in_usermode());
mtmsr(mfmsr() | (MSR_IR|MSR_DR));
}

@@ -51,6 +52,7 @@ void mmu_disable(void)

cpu->pgtable = NULL;

+ assert(!in_usermode());
mtmsr(mfmsr() & ~(MSR_IR|MSR_DR));
}

diff --git a/powerpc/interrupts.c b/powerpc/interrupts.c
index 6bed26e41..ba965ff76 100644
--- a/powerpc/interrupts.c
+++ b/powerpc/interrupts.c
@@ -326,6 +326,33 @@ static void test_illegal(void)
report_prefix_pop();
}

+static void dec_ignore_handler(struct pt_regs *regs, void *data)
+{
+ mtspr(SPR_DEC, 0x7fffffff);
+}
+
+static void test_privileged(void)
+{
+ unsigned long msr;
+
+ if (!mmu_enabled())
+ return;
+
+ report_prefix_push("privileged instruction");
+
+ handle_exception(0x700, &program_handler, NULL);
+ handle_exception(0x900, &dec_ignore_handler, NULL);
+ enter_usermode();
+ asm volatile("mfmsr %0" : "=r"(msr) :: "memory");
+ exit_usermode();
+ report(got_interrupt, "interrupt on privileged instruction");
+ got_interrupt = false;
+ handle_exception(0x900, NULL, NULL);
+ handle_exception(0x700, NULL, NULL);
+
+ report_prefix_pop();
+}
+
static void sc_handler(struct pt_regs *regs, void *data)
{
got_interrupt = true;
@@ -478,6 +505,7 @@ int main(int argc, char **argv)
test_mce();
test_mmu();
test_illegal();
+ test_privileged();
test_dec();
test_sc();
test_trace();
--
2.43.0