diff options
| -rw-r--r-- | arch/x86/include/asm/processor.h | 6 | ||||
| -rw-r--r-- | arch/x86/include/asm/uintr.h | 13 | ||||
| -rw-r--r-- | arch/x86/kernel/Makefile | 1 | ||||
| -rw-r--r-- | arch/x86/kernel/uintr_core.c | 240 | ||||
| -rw-r--r-- | arch/x86/kernel/uintr_fd.c | 58 |
5 files changed, 318 insertions, 0 deletions
diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h index 9ad2acaaae9b..d229bfac8b4f 100644 --- a/arch/x86/include/asm/processor.h +++ b/arch/x86/include/asm/processor.h @@ -9,6 +9,7 @@ struct task_struct; struct mm_struct; struct io_bitmap; struct vm86; +struct uintr_receiver; #include <asm/math_emu.h> #include <asm/segment.h> @@ -529,6 +530,11 @@ struct thread_struct { */ u32 pkru; +#ifdef CONFIG_X86_USER_INTERRUPTS + /* User Interrupt state*/ + struct uintr_receiver *ui_recv; +#endif + /* Floating point and extended processor state */ struct fpu fpu; /* diff --git a/arch/x86/include/asm/uintr.h b/arch/x86/include/asm/uintr.h new file mode 100644 index 000000000000..4f35bd8bd4e0 --- /dev/null +++ b/arch/x86/include/asm/uintr.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_UINTR_H +#define _ASM_X86_UINTR_H + +#ifdef CONFIG_X86_USER_INTERRUPTS + +bool uintr_arch_enabled(void); +int do_uintr_register_handler(u64 handler); +int do_uintr_unregister_handler(void); + +#endif /* CONFIG_X86_USER_INTERRUPTS */ + +#endif /* _ASM_X86_UINTR_H */ diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 8f4e8fa6ed75..060ca9f23e23 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -140,6 +140,7 @@ obj-$(CONFIG_UPROBES) += uprobes.o obj-$(CONFIG_PERF_EVENTS) += perf_regs.o obj-$(CONFIG_TRACING) += tracepoint.o obj-$(CONFIG_SCHED_MC_PRIO) += itmt.o +obj-$(CONFIG_X86_USER_INTERRUPTS) += uintr_fd.o uintr_core.o obj-$(CONFIG_X86_UMIP) += umip.o obj-$(CONFIG_UNWINDER_ORC) += unwind_orc.o diff --git a/arch/x86/kernel/uintr_core.c b/arch/x86/kernel/uintr_core.c new file mode 100644 index 000000000000..2c6042a6840a --- /dev/null +++ b/arch/x86/kernel/uintr_core.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021, Intel Corporation. + * + * Sohil Mehta <[email protected]> + * Jacob Pan <[email protected]> + */ +#define pr_fmt(fmt) "uintr: " fmt + +#include <linux/refcount.h> +#include <linux/sched.h> +#include <linux/sched/task.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <asm/apic.h> +#include <asm/fpu/internal.h> +#include <asm/irq_vectors.h> +#include <asm/msr.h> +#include <asm/msr-index.h> +#include <asm/uintr.h> + +/* User Posted Interrupt Descriptor (UPID) */ +struct uintr_upid { + struct { + u8 status; /* bit 0: ON, bit 1: SN, bit 2-7: reserved */ + u8 reserved1; /* Reserved */ + u8 nv; /* Notification vector */ + u8 reserved2; /* Reserved */ + u32 ndst; /* Notification destination */ + } nc __packed; /* Notification control */ + u64 puir; /* Posted user interrupt requests */ +} __aligned(64); + +/* UPID Notification control status */ +#define UPID_ON 0x0 /* Outstanding notification */ +#define UPID_SN 0x1 /* Suppressed notification */ + +struct uintr_upid_ctx { + struct uintr_upid *upid; + refcount_t refs; +}; + +struct uintr_receiver { + struct uintr_upid_ctx *upid_ctx; +}; + +inline bool uintr_arch_enabled(void) +{ + return static_cpu_has(X86_FEATURE_UINTR); +} + +static inline bool is_uintr_receiver(struct task_struct *t) +{ + return !!t->thread.ui_recv; +} + +static inline u32 cpu_to_ndst(int cpu) +{ + u32 apicid = (u32)apic->cpu_present_to_apicid(cpu); + + WARN_ON_ONCE(apicid == BAD_APICID); + + if (!x2apic_enabled()) + return (apicid << 8) & 0xFF00; + + return apicid; +} + +static void free_upid(struct uintr_upid_ctx *upid_ctx) +{ + kfree(upid_ctx->upid); + upid_ctx->upid = NULL; + kfree(upid_ctx); +} + +/* TODO: UPID needs to be allocated by a KPTI compatible allocator */ +static struct uintr_upid_ctx *alloc_upid(void) +{ + struct uintr_upid_ctx *upid_ctx; + struct uintr_upid *upid; + + upid_ctx = kzalloc(sizeof(*upid_ctx), GFP_KERNEL); + if (!upid_ctx) + return NULL; + + upid = kzalloc(sizeof(*upid), GFP_KERNEL); + + if (!upid) { + kfree(upid_ctx); + return NULL; + } + + upid_ctx->upid = upid; + refcount_set(&upid_ctx->refs, 1); + + return upid_ctx; +} + +static void put_upid_ref(struct uintr_upid_ctx *upid_ctx) +{ + if (refcount_dec_and_test(&upid_ctx->refs)) + free_upid(upid_ctx); +} + +int do_uintr_unregister_handler(void) +{ + struct task_struct *t = current; + struct fpu *fpu = &t->thread.fpu; + struct uintr_receiver *ui_recv; + u64 msr64; + + if (!is_uintr_receiver(t)) + return -EINVAL; + + pr_debug("recv: Unregister handler and clear MSRs for task=%d\n", + t->pid); + + /* + * TODO: Evaluate usage of fpregs_lock() and get_xsave_addr(). Bugs + * have been reported recently for PASID and WRPKRU. + * + * UPID and ui_recv will be referenced during context switch. Need to + * disable preemption while modifying the MSRs, UPID and ui_recv thread + * struct. + */ + fpregs_lock(); + + /* Clear only the receiver specific state. Sender related state is not modified */ + if (fpregs_state_valid(fpu, smp_processor_id())) { + /* Modify only the relevant bits of the MISC MSR */ + rdmsrl(MSR_IA32_UINTR_MISC, msr64); + msr64 &= ~GENMASK_ULL(39, 32); + wrmsrl(MSR_IA32_UINTR_MISC, msr64); + wrmsrl(MSR_IA32_UINTR_PD, 0ULL); + wrmsrl(MSR_IA32_UINTR_RR, 0ULL); + wrmsrl(MSR_IA32_UINTR_STACKADJUST, 0ULL); + wrmsrl(MSR_IA32_UINTR_HANDLER, 0ULL); + } else { + struct uintr_state *p; + + p = get_xsave_addr(&fpu->state.xsave, XFEATURE_UINTR); + if (p) { + p->handler = 0; + p->stack_adjust = 0; + p->upid_addr = 0; + p->uinv = 0; + p->uirr = 0; + } + } + + ui_recv = t->thread.ui_recv; + /* + * Suppress notifications so that no further interrupts are generated + * based on this UPID. + */ + set_bit(UPID_SN, (unsigned long *)&ui_recv->upid_ctx->upid->nc.status); + + put_upid_ref(ui_recv->upid_ctx); + kfree(ui_recv); + t->thread.ui_recv = NULL; + + fpregs_unlock(); + + return 0; +} + +int do_uintr_register_handler(u64 handler) +{ + struct uintr_receiver *ui_recv; + struct uintr_upid *upid; + struct task_struct *t = current; + struct fpu *fpu = &t->thread.fpu; + u64 misc_msr; + int cpu; + + if (is_uintr_receiver(t)) + return -EBUSY; + + ui_recv = kzalloc(sizeof(*ui_recv), GFP_KERNEL); + if (!ui_recv) + return -ENOMEM; + + ui_recv->upid_ctx = alloc_upid(); + if (!ui_recv->upid_ctx) { + kfree(ui_recv); + pr_debug("recv: alloc upid failed for task=%d\n", t->pid); + return -ENOMEM; + } + + /* + * TODO: Evaluate usage of fpregs_lock() and get_xsave_addr(). Bugs + * have been reported recently for PASID and WRPKRU. + * + * UPID and ui_recv will be referenced during context switch. Need to + * disable preemption while modifying the MSRs, UPID and ui_recv thread + * struct. + */ + fpregs_lock(); + + cpu = smp_processor_id(); + upid = ui_recv->upid_ctx->upid; + upid->nc.nv = UINTR_NOTIFICATION_VECTOR; + upid->nc.ndst = cpu_to_ndst(cpu); + + t->thread.ui_recv = ui_recv; + + if (fpregs_state_valid(fpu, cpu)) { + wrmsrl(MSR_IA32_UINTR_HANDLER, handler); + wrmsrl(MSR_IA32_UINTR_PD, (u64)ui_recv->upid_ctx->upid); + + /* Set value as size of ABI redzone */ + wrmsrl(MSR_IA32_UINTR_STACKADJUST, 128); + + /* Modify only the relevant bits of the MISC MSR */ + rdmsrl(MSR_IA32_UINTR_MISC, misc_msr); + misc_msr |= (u64)UINTR_NOTIFICATION_VECTOR << 32; + wrmsrl(MSR_IA32_UINTR_MISC, misc_msr); + } else { + struct xregs_state *xsave; + struct uintr_state *p; + + xsave = &fpu->state.xsave; + xsave->header.xfeatures |= XFEATURE_MASK_UINTR; + p = get_xsave_addr(&fpu->state.xsave, XFEATURE_UINTR); + if (p) { + p->handler = handler; + p->upid_addr = (u64)ui_recv->upid_ctx->upid; + p->stack_adjust = 128; + p->uinv = UINTR_NOTIFICATION_VECTOR; + } + } + + fpregs_unlock(); + + pr_debug("recv: task=%d register handler=%llx upid %px\n", + t->pid, handler, upid); + + return 0; +} diff --git a/arch/x86/kernel/uintr_fd.c b/arch/x86/kernel/uintr_fd.c new file mode 100644 index 000000000000..a1a9c105fdab --- /dev/null +++ b/arch/x86/kernel/uintr_fd.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021, Intel Corporation. + * + * Sohil Mehta <[email protected]> + */ +#define pr_fmt(fmt) "uintr: " fmt + +#include <linux/sched.h> +#include <linux/syscalls.h> + +#include <asm/uintr.h> + +/* + * sys_uintr_register_handler - setup user interrupt handler for receiver. + */ +SYSCALL_DEFINE2(uintr_register_handler, u64 __user *, handler, unsigned int, flags) +{ + int ret; + + if (!uintr_arch_enabled()) + return -EOPNOTSUPP; + + if (flags) + return -EINVAL; + + /* TODO: Validate the handler address */ + if (!handler) + return -EFAULT; + + ret = do_uintr_register_handler((u64)handler); + + pr_debug("recv: register handler task=%d flags %d handler %lx ret %d\n", + current->pid, flags, (unsigned long)handler, ret); + + return ret; +} + +/* + * sys_uintr_unregister_handler - Teardown user interrupt handler for receiver. + */ +SYSCALL_DEFINE1(uintr_unregister_handler, unsigned int, flags) +{ + int ret; + + if (!uintr_arch_enabled()) + return -EOPNOTSUPP; + + if (flags) + return -EINVAL; + + ret = do_uintr_unregister_handler(); + + pr_debug("recv: unregister handler task=%d flags %d ret %d\n", + current->pid, flags, ret); + + return ret; +} |
