Skip to content

Commit

Permalink
riscv: Add support for userspace pointer masking
Browse files Browse the repository at this point in the history
RISC-V supports pointer masking with a variable number of tag bits
(which is called "PMLEN" in the specification) 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 lower bound on the number of tag bits and
determine the actual number of tag bits. As with arm64's
PR_TAGGED_ADDR_ENABLE, the pointer masking configuration is
thread-scoped, inherited on clone() and fork() and cleared on execve().

Reviewed-by: Charlie Jenkins <charlie@rivosinc.com>
Tested-by: Charlie Jenkins <charlie@rivosinc.com>
Signed-off-by: Samuel Holland <samuel.holland@sifive.com>
Link: https://lore.kernel.org/r/20241016202814.4061541-5-samuel.holland@sifive.com
Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
  • Loading branch information
Samuel Holland authored and Palmer Dabbelt committed Oct 24, 2024
1 parent 29eedc7 commit 09d6775
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 1 deletion.
12 changes: 12 additions & 0 deletions Documentation/arch/riscv/uabi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,15 @@ Misaligned accesses
Misaligned scalar accesses are supported in userspace, but they may perform
poorly. Misaligned vector accesses are only supported if the Zicclsm extension
is supported.

Pointer masking
---------------

Support for pointer masking in userspace (the Supm extension) is provided via
the ``PR_SET_TAGGED_ADDR_CTRL`` and ``PR_GET_TAGGED_ADDR_CTRL`` ``prctl()``
operations. Pointer masking is disabled by default. To enable it, userspace
must call ``PR_SET_TAGGED_ADDR_CTRL`` with the ``PR_PMLEN`` field set to the
number of mask/tag bits needed by the application. ``PR_PMLEN`` is interpreted
as a lower bound; if the kernel is unable to satisfy the request, the
``PR_SET_TAGGED_ADDR_CTRL`` operation will fail. The actual number of tag bits
is returned in ``PR_PMLEN`` by the ``PR_GET_TAGGED_ADDR_CTRL`` operation.
11 changes: 11 additions & 0 deletions arch/riscv/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,17 @@ config RISCV_ISA_C

If you don't know what to do here, say Y.

config RISCV_ISA_SUPM
bool "Supm extension for userspace pointer masking"
depends on 64BIT
default y
help
Add support for pointer masking in userspace (Supm) when the
underlying hardware extension (Smnpm or Ssnpm) is detected at boot.

If this option is disabled, userspace will be unable to use
the prctl(PR_{SET,GET}_TAGGED_ADDR_CTRL) API.

config RISCV_ISA_SVNAPOT
bool "Svnapot extension support for supervisor mode NAPOT pages"
depends on 64BIT && MMU
Expand Down
8 changes: 8 additions & 0 deletions arch/riscv/include/asm/processor.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ extern int set_unalign_ctl(struct task_struct *tsk, unsigned int val);
#define RISCV_SET_ICACHE_FLUSH_CTX(arg1, arg2) riscv_set_icache_flush_ctx(arg1, arg2)
extern int riscv_set_icache_flush_ctx(unsigned long ctx, unsigned long per_thread);

#ifdef CONFIG_RISCV_ISA_SUPM
/* 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 */
11 changes: 11 additions & 0 deletions arch/riscv/include/asm/switch_to.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ static __always_inline bool has_fpu(void) { return false; }
#define __switch_to_fpu(__prev, __next) do { } while (0)
#endif

static inline void envcfg_update_bits(struct task_struct *task,
unsigned long mask, unsigned long val)
{
unsigned long envcfg;

envcfg = (task->thread.envcfg & ~mask) | val;
task->thread.envcfg = envcfg;
if (task == current)
csr_write(CSR_ENVCFG, envcfg);
}

static inline void __switch_to_envcfg(struct task_struct *next)
{
asm volatile (ALTERNATIVE("nop", "csrw " __stringify(CSR_ENVCFG) ", %0",
Expand Down
91 changes: 91 additions & 0 deletions arch/riscv/kernel/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Copyright (C) 2017 SiFive
*/

#include <linux/bitfield.h>
#include <linux/cpu.h>
#include <linux/kernel.h>
#include <linux/sched.h>
Expand Down Expand Up @@ -180,6 +181,10 @@ void flush_thread(void)
memset(&current->thread.vstate, 0, sizeof(struct __riscv_v_ext_state));
clear_tsk_thread_flag(current, TIF_RISCV_V_DEFER_RESTORE);
#endif
#ifdef CONFIG_RISCV_ISA_SUPM
if (riscv_has_extension_unlikely(RISCV_ISA_EXT_SUPM))
envcfg_update_bits(current, ENVCFG_PMM, ENVCFG_PMM_PMLEN_0);
#endif
}

void arch_release_task_struct(struct task_struct *tsk)
Expand Down Expand Up @@ -242,3 +247,89 @@ void __init arch_task_cache_init(void)
{
riscv_v_setup_ctx_cache();
}

#ifdef CONFIG_RISCV_ISA_SUPM
enum {
PMLEN_0 = 0,
PMLEN_7 = 7,
PMLEN_16 = 16,
};

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);
unsigned long pmm;
u8 pmlen;

if (is_compat_thread(ti))
return -EINVAL;

if (arg & ~valid_mask)
return -EINVAL;

/*
* Prefer the smallest PMLEN that satisfies the user's request,
* in case choosing a larger PMLEN has a performance impact.
*/
pmlen = FIELD_GET(PR_PMLEN_MASK, arg);
if (pmlen == PMLEN_0)
pmm = ENVCFG_PMM_PMLEN_0;
else if (pmlen <= PMLEN_7 && have_user_pmlen_7)
pmm = ENVCFG_PMM_PMLEN_7;
else if (pmlen <= PMLEN_16 && have_user_pmlen_16)
pmm = ENVCFG_PMM_PMLEN_16;
else
return -EINVAL;

envcfg_update_bits(task, ENVCFG_PMM, pmm);

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, PMLEN_7);
break;
case ENVCFG_PMM_PMLEN_16:
ret = FIELD_PREP(PR_PMLEN_MASK, PMLEN_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_SUPM))
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_SUPM */
5 changes: 4 additions & 1 deletion include/uapi/linux/prctl.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ struct prctl_mm_map {
# define PR_PAC_APDBKEY (1UL << 3)
# define PR_PAC_APGAKEY (1UL << 4)

/* Tagged user address controls for arm64 */
/* Tagged user address controls for arm64 and RISC-V */
#define PR_SET_TAGGED_ADDR_CTRL 55
#define PR_GET_TAGGED_ADDR_CTRL 56
# define PR_TAGGED_ADDR_ENABLE (1UL << 0)
Expand All @@ -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
Expand Down

0 comments on commit 09d6775

Please sign in to comment.