RISC-V supports pointer masking with a variable number of tag bits ("PMLEN") and which is configured at the next higher privilege level. Wire up the PR_SET_TAGGED_ADDR_CTRL and PR_GET_TAGGED_ADDR_CTRL prctls so userspace can request a minimum number of tag bits and determine the actual number of tag bits. As with PR_TAGGED_ADDR_ENABLE, the pointer masking configuration is thread-scoped, inherited on clone() and fork() and cleared on exec(). Signed-off-by: Samuel Holland <samuel.holland@xxxxxxxxxx> --- arch/riscv/Kconfig | 8 +++ arch/riscv/include/asm/processor.h | 8 +++ arch/riscv/kernel/process.c | 107 +++++++++++++++++++++++++++++ include/uapi/linux/prctl.h | 3 + 4 files changed, 126 insertions(+) diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index e3142ce531a0..a1a1585120f0 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -479,6 +479,14 @@ config RISCV_ISA_C If you don't know what to do here, say Y. +config RISCV_ISA_POINTER_MASKING + bool "Smmpm, Smnpm, and Ssnpm extensions for pointer masking" + depends on 64BIT + default y + help + Add support to dynamically detect the presence of the Smmpm, Smnpm, + and Ssnpm extensions (pointer masking) and enable their usage. + config RISCV_ISA_SVNAPOT bool "Svnapot extension support for supervisor mode NAPOT pages" depends on 64BIT && MMU diff --git a/arch/riscv/include/asm/processor.h b/arch/riscv/include/asm/processor.h index 06b87402a4d8..64b34e839802 100644 --- a/arch/riscv/include/asm/processor.h +++ b/arch/riscv/include/asm/processor.h @@ -185,6 +185,14 @@ extern int set_unalign_ctl(struct task_struct *tsk, unsigned int val); #define GET_UNALIGN_CTL(tsk, addr) get_unalign_ctl((tsk), (addr)) #define SET_UNALIGN_CTL(tsk, val) set_unalign_ctl((tsk), (val)) +#ifdef CONFIG_RISCV_ISA_POINTER_MASKING +/* PR_{SET,GET}_TAGGED_ADDR_CTRL prctl */ +long set_tagged_addr_ctrl(struct task_struct *task, unsigned long arg); +long get_tagged_addr_ctrl(struct task_struct *task); +#define SET_TAGGED_ADDR_CTRL(arg) set_tagged_addr_ctrl(current, arg) +#define GET_TAGGED_ADDR_CTRL() get_tagged_addr_ctrl(current) +#endif + #endif /* __ASSEMBLY__ */ #endif /* _ASM_RISCV_PROCESSOR_H */ diff --git a/arch/riscv/kernel/process.c b/arch/riscv/kernel/process.c index 92922dbd5b5c..3578e75f4aa4 100644 --- a/arch/riscv/kernel/process.c +++ b/arch/riscv/kernel/process.c @@ -7,6 +7,7 @@ * Copyright (C) 2017 SiFive */ +#include <linux/bitfield.h> #include <linux/cpu.h> #include <linux/kernel.h> #include <linux/sched.h> @@ -154,6 +155,18 @@ void start_thread(struct pt_regs *regs, unsigned long pc, #endif } +static void flush_tagged_addr_state(void) +{ +#ifdef CONFIG_RISCV_ISA_POINTER_MASKING + if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_SxNPM)) + return; + + current->thread.envcfg &= ~ENVCFG_PMM; + + sync_envcfg(current); +#endif +} + void flush_thread(void) { #ifdef CONFIG_FPU @@ -173,6 +186,7 @@ void flush_thread(void) memset(¤t->thread.vstate, 0, sizeof(struct __riscv_v_ext_state)); clear_tsk_thread_flag(current, TIF_RISCV_V_DEFER_RESTORE); #endif + flush_tagged_addr_state(); } void arch_release_task_struct(struct task_struct *tsk) @@ -236,3 +250,96 @@ void __init arch_task_cache_init(void) { riscv_v_setup_ctx_cache(); } + +#ifdef CONFIG_RISCV_ISA_POINTER_MASKING +static bool have_user_pmlen_7; +static bool have_user_pmlen_16; + +long set_tagged_addr_ctrl(struct task_struct *task, unsigned long arg) +{ + unsigned long valid_mask = PR_PMLEN_MASK; + struct thread_info *ti = task_thread_info(task); + u8 pmlen; + + if (is_compat_thread(ti)) + return -EINVAL; + + if (arg & ~valid_mask) + return -EINVAL; + + pmlen = FIELD_GET(PR_PMLEN_MASK, arg); + if (pmlen > 16) { + return -EINVAL; + } else if (pmlen > 7) { + if (have_user_pmlen_16) + pmlen = 16; + else + return -EINVAL; + } else if (pmlen > 0) { + /* + * Prefer the smallest PMLEN that satisfies the user's request, + * in case choosing a larger PMLEN has a performance impact. + */ + if (have_user_pmlen_7) + pmlen = 7; + else if (have_user_pmlen_16) + pmlen = 16; + else + return -EINVAL; + } + + task->thread.envcfg &= ~ENVCFG_PMM; + if (pmlen == 7) + task->thread.envcfg |= ENVCFG_PMM_PMLEN_7; + else if (pmlen == 16) + task->thread.envcfg |= ENVCFG_PMM_PMLEN_16; + + if (task == current) + sync_envcfg(current); + + return 0; +} + +long get_tagged_addr_ctrl(struct task_struct *task) +{ + struct thread_info *ti = task_thread_info(task); + long ret = 0; + + if (is_compat_thread(ti)) + return -EINVAL; + + switch (task->thread.envcfg & ENVCFG_PMM) { + case ENVCFG_PMM_PMLEN_7: + ret |= FIELD_PREP(PR_PMLEN_MASK, 7); + break; + case ENVCFG_PMM_PMLEN_16: + ret |= FIELD_PREP(PR_PMLEN_MASK, 16); + break; + } + + return ret; +} + +static bool try_to_set_pmm(unsigned long value) +{ + csr_set(CSR_ENVCFG, value); + return (csr_read_clear(CSR_ENVCFG, ENVCFG_PMM) & ENVCFG_PMM) == value; +} + +static int __init tagged_addr_init(void) +{ + if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_SxNPM)) + return 0; + + /* + * envcfg.PMM is a WARL field. Detect which values are supported. + * Assume the supported PMLEN values are the same on all harts. + */ + csr_clear(CSR_ENVCFG, ENVCFG_PMM); + have_user_pmlen_7 = try_to_set_pmm(ENVCFG_PMM_PMLEN_7); + have_user_pmlen_16 = try_to_set_pmm(ENVCFG_PMM_PMLEN_16); + + return 0; +} +core_initcall(tagged_addr_init); +#endif /* CONFIG_RISCV_ISA_POINTER_MASKING */ diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h index 370ed14b1ae0..488b0d8e8495 100644 --- a/include/uapi/linux/prctl.h +++ b/include/uapi/linux/prctl.h @@ -244,6 +244,9 @@ struct prctl_mm_map { # define PR_MTE_TAG_MASK (0xffffUL << PR_MTE_TAG_SHIFT) /* Unused; kept only for source compatibility */ # define PR_MTE_TCF_SHIFT 1 +/* RISC-V pointer masking tag length */ +# define PR_PMLEN_SHIFT 24 +# define PR_PMLEN_MASK (0x7fUL << PR_PMLEN_SHIFT) /* Control reclaim behavior when allocating memory */ #define PR_SET_IO_FLUSHER 57 -- 2.43.1