mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 11:18:45 +07:00
bc0ee47603
This patch adds the core support for switching and managing the SVE architectural state of user tasks. Calls to the existing FPSIMD low-level save/restore functions are factored out as new functions task_fpsimd_{save,load}(), since SVE now dynamically may or may not need to be handled at these points depending on the kernel configuration, hardware features discovered at boot, and the runtime state of the task. To make these decisions as fast as possible, const cpucaps are used where feasible, via the system_supports_sve() helper. The SVE registers are only tracked for threads that have explicitly used SVE, indicated by the new thread flag TIF_SVE. Otherwise, the FPSIMD view of the architectural state is stored in thread.fpsimd_state as usual. When in use, the SVE registers are not stored directly in thread_struct due to their potentially large and variable size. Because the task_struct slab allocator must be configured very early during kernel boot, it is also tricky to configure it correctly to match the maximum vector length provided by the hardware, since this depends on examining secondary CPUs as well as the primary. Instead, a pointer sve_state in thread_struct points to a dynamically allocated buffer containing the SVE register data, and code is added to allocate and free this buffer at appropriate times. TIF_SVE is set when taking an SVE access trap from userspace, if suitable hardware support has been detected. This enables SVE for the thread: a subsequent return to userspace will disable the trap accordingly. If such a trap is taken without sufficient system- wide hardware support, SIGILL is sent to the thread instead as if an undefined instruction had been executed: this may happen if userspace tries to use SVE in a system where not all CPUs support it for example. The kernel will clear TIF_SVE and disable SVE for the thread whenever an explicit syscall is made by userspace. For backwards compatibility reasons and conformance with the spirit of the base AArch64 procedure call standard, the subset of the SVE register state that aliases the FPSIMD registers is still preserved across a syscall even if this happens. The remainder of the SVE register state logically becomes zero at syscall entry, though the actual zeroing work is currently deferred until the thread next tries to use SVE, causing another trap to the kernel. This implementation is suboptimal: in the future, the fastpath case may be optimised to zero the registers in-place and leave SVE enabled for the task, where beneficial. TIF_SVE is also cleared in the following slowpath cases, which are taken as reasonable hints that the task may no longer use SVE: * exec * fork and clone Code is added to sync data between thread.fpsimd_state and thread.sve_state whenever enabling/disabling SVE, in a manner consistent with the SVE architectural programmer's model. Signed-off-by: Dave Martin <Dave.Martin@arm.com> Reviewed-by: Catalin Marinas <catalin.marinas@arm.com> Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org> Cc: Alex Bennée <alex.bennee@linaro.org> [will: added #include to fix allnoconfig build] [will: use enable_daif in do_sve_acc] Signed-off-by: Will Deacon <will.deacon@arm.com>
483 lines
12 KiB
C
483 lines
12 KiB
C
/*
|
|
* Based on arch/arm/kernel/process.c
|
|
*
|
|
* Original Copyright (C) 1995 Linus Torvalds
|
|
* Copyright (C) 1996-2000 Russell King - Converted to ARM.
|
|
* Copyright (C) 2012 ARM Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <linux/compat.h>
|
|
#include <linux/efi.h>
|
|
#include <linux/export.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/sched/task.h>
|
|
#include <linux/sched/task_stack.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/stddef.h>
|
|
#include <linux/unistd.h>
|
|
#include <linux/user.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/init.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/elfcore.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/tick.h>
|
|
#include <linux/utsname.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/random.h>
|
|
#include <linux/hw_breakpoint.h>
|
|
#include <linux/personality.h>
|
|
#include <linux/notifier.h>
|
|
#include <trace/events/power.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/thread_info.h>
|
|
|
|
#include <asm/alternative.h>
|
|
#include <asm/compat.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/exec.h>
|
|
#include <asm/fpsimd.h>
|
|
#include <asm/mmu_context.h>
|
|
#include <asm/processor.h>
|
|
#include <asm/stacktrace.h>
|
|
|
|
#ifdef CONFIG_CC_STACKPROTECTOR
|
|
#include <linux/stackprotector.h>
|
|
unsigned long __stack_chk_guard __read_mostly;
|
|
EXPORT_SYMBOL(__stack_chk_guard);
|
|
#endif
|
|
|
|
/*
|
|
* Function pointers to optional machine specific functions
|
|
*/
|
|
void (*pm_power_off)(void);
|
|
EXPORT_SYMBOL_GPL(pm_power_off);
|
|
|
|
void (*arm_pm_restart)(enum reboot_mode reboot_mode, const char *cmd);
|
|
|
|
/*
|
|
* This is our default idle handler.
|
|
*/
|
|
void arch_cpu_idle(void)
|
|
{
|
|
/*
|
|
* This should do all the clock switching and wait for interrupt
|
|
* tricks
|
|
*/
|
|
trace_cpu_idle_rcuidle(1, smp_processor_id());
|
|
cpu_do_idle();
|
|
local_irq_enable();
|
|
trace_cpu_idle_rcuidle(PWR_EVENT_EXIT, smp_processor_id());
|
|
}
|
|
|
|
#ifdef CONFIG_HOTPLUG_CPU
|
|
void arch_cpu_idle_dead(void)
|
|
{
|
|
cpu_die();
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Called by kexec, immediately prior to machine_kexec().
|
|
*
|
|
* This must completely disable all secondary CPUs; simply causing those CPUs
|
|
* to execute e.g. a RAM-based pin loop is not sufficient. This allows the
|
|
* kexec'd kernel to use any and all RAM as it sees fit, without having to
|
|
* avoid any code or data used by any SW CPU pin loop. The CPU hotplug
|
|
* functionality embodied in disable_nonboot_cpus() to achieve this.
|
|
*/
|
|
void machine_shutdown(void)
|
|
{
|
|
disable_nonboot_cpus();
|
|
}
|
|
|
|
/*
|
|
* Halting simply requires that the secondary CPUs stop performing any
|
|
* activity (executing tasks, handling interrupts). smp_send_stop()
|
|
* achieves this.
|
|
*/
|
|
void machine_halt(void)
|
|
{
|
|
local_irq_disable();
|
|
smp_send_stop();
|
|
while (1);
|
|
}
|
|
|
|
/*
|
|
* Power-off simply requires that the secondary CPUs stop performing any
|
|
* activity (executing tasks, handling interrupts). smp_send_stop()
|
|
* achieves this. When the system power is turned off, it will take all CPUs
|
|
* with it.
|
|
*/
|
|
void machine_power_off(void)
|
|
{
|
|
local_irq_disable();
|
|
smp_send_stop();
|
|
if (pm_power_off)
|
|
pm_power_off();
|
|
}
|
|
|
|
/*
|
|
* Restart requires that the secondary CPUs stop performing any activity
|
|
* while the primary CPU resets the system. Systems with multiple CPUs must
|
|
* provide a HW restart implementation, to ensure that all CPUs reset at once.
|
|
* This is required so that any code running after reset on the primary CPU
|
|
* doesn't have to co-ordinate with other CPUs to ensure they aren't still
|
|
* executing pre-reset code, and using RAM that the primary CPU's code wishes
|
|
* to use. Implementing such co-ordination would be essentially impossible.
|
|
*/
|
|
void machine_restart(char *cmd)
|
|
{
|
|
/* Disable interrupts first */
|
|
local_irq_disable();
|
|
smp_send_stop();
|
|
|
|
/*
|
|
* UpdateCapsule() depends on the system being reset via
|
|
* ResetSystem().
|
|
*/
|
|
if (efi_enabled(EFI_RUNTIME_SERVICES))
|
|
efi_reboot(reboot_mode, NULL);
|
|
|
|
/* Now call the architecture specific reboot code. */
|
|
if (arm_pm_restart)
|
|
arm_pm_restart(reboot_mode, cmd);
|
|
else
|
|
do_kernel_restart(cmd);
|
|
|
|
/*
|
|
* Whoops - the architecture was unable to reboot.
|
|
*/
|
|
printk("Reboot failed -- System halted\n");
|
|
while (1);
|
|
}
|
|
|
|
static void print_pstate(struct pt_regs *regs)
|
|
{
|
|
u64 pstate = regs->pstate;
|
|
|
|
if (compat_user_mode(regs)) {
|
|
printk("pstate: %08llx (%c%c%c%c %c %s %s %c%c%c)\n",
|
|
pstate,
|
|
pstate & COMPAT_PSR_N_BIT ? 'N' : 'n',
|
|
pstate & COMPAT_PSR_Z_BIT ? 'Z' : 'z',
|
|
pstate & COMPAT_PSR_C_BIT ? 'C' : 'c',
|
|
pstate & COMPAT_PSR_V_BIT ? 'V' : 'v',
|
|
pstate & COMPAT_PSR_Q_BIT ? 'Q' : 'q',
|
|
pstate & COMPAT_PSR_T_BIT ? "T32" : "A32",
|
|
pstate & COMPAT_PSR_E_BIT ? "BE" : "LE",
|
|
pstate & COMPAT_PSR_A_BIT ? 'A' : 'a',
|
|
pstate & COMPAT_PSR_I_BIT ? 'I' : 'i',
|
|
pstate & COMPAT_PSR_F_BIT ? 'F' : 'f');
|
|
} else {
|
|
printk("pstate: %08llx (%c%c%c%c %c%c%c%c %cPAN %cUAO)\n",
|
|
pstate,
|
|
pstate & PSR_N_BIT ? 'N' : 'n',
|
|
pstate & PSR_Z_BIT ? 'Z' : 'z',
|
|
pstate & PSR_C_BIT ? 'C' : 'c',
|
|
pstate & PSR_V_BIT ? 'V' : 'v',
|
|
pstate & PSR_D_BIT ? 'D' : 'd',
|
|
pstate & PSR_A_BIT ? 'A' : 'a',
|
|
pstate & PSR_I_BIT ? 'I' : 'i',
|
|
pstate & PSR_F_BIT ? 'F' : 'f',
|
|
pstate & PSR_PAN_BIT ? '+' : '-',
|
|
pstate & PSR_UAO_BIT ? '+' : '-');
|
|
}
|
|
}
|
|
|
|
void __show_regs(struct pt_regs *regs)
|
|
{
|
|
int i, top_reg;
|
|
u64 lr, sp;
|
|
|
|
if (compat_user_mode(regs)) {
|
|
lr = regs->compat_lr;
|
|
sp = regs->compat_sp;
|
|
top_reg = 12;
|
|
} else {
|
|
lr = regs->regs[30];
|
|
sp = regs->sp;
|
|
top_reg = 29;
|
|
}
|
|
|
|
show_regs_print_info(KERN_DEFAULT);
|
|
print_pstate(regs);
|
|
print_symbol("pc : %s\n", regs->pc);
|
|
print_symbol("lr : %s\n", lr);
|
|
printk("sp : %016llx\n", sp);
|
|
|
|
i = top_reg;
|
|
|
|
while (i >= 0) {
|
|
printk("x%-2d: %016llx ", i, regs->regs[i]);
|
|
i--;
|
|
|
|
if (i % 2 == 0) {
|
|
pr_cont("x%-2d: %016llx ", i, regs->regs[i]);
|
|
i--;
|
|
}
|
|
|
|
pr_cont("\n");
|
|
}
|
|
}
|
|
|
|
void show_regs(struct pt_regs * regs)
|
|
{
|
|
__show_regs(regs);
|
|
dump_backtrace(regs, NULL);
|
|
}
|
|
|
|
static void tls_thread_flush(void)
|
|
{
|
|
write_sysreg(0, tpidr_el0);
|
|
|
|
if (is_compat_task()) {
|
|
current->thread.tp_value = 0;
|
|
|
|
/*
|
|
* We need to ensure ordering between the shadow state and the
|
|
* hardware state, so that we don't corrupt the hardware state
|
|
* with a stale shadow state during context switch.
|
|
*/
|
|
barrier();
|
|
write_sysreg(0, tpidrro_el0);
|
|
}
|
|
}
|
|
|
|
void flush_thread(void)
|
|
{
|
|
fpsimd_flush_thread();
|
|
tls_thread_flush();
|
|
flush_ptrace_hw_breakpoint(current);
|
|
}
|
|
|
|
void release_thread(struct task_struct *dead_task)
|
|
{
|
|
}
|
|
|
|
void arch_release_task_struct(struct task_struct *tsk)
|
|
{
|
|
fpsimd_release_task(tsk);
|
|
}
|
|
|
|
/*
|
|
* src and dst may temporarily have aliased sve_state after task_struct
|
|
* is copied. We cannot fix this properly here, because src may have
|
|
* live SVE state and dst's thread_info may not exist yet, so tweaking
|
|
* either src's or dst's TIF_SVE is not safe.
|
|
*
|
|
* The unaliasing is done in copy_thread() instead. This works because
|
|
* dst is not schedulable or traceable until both of these functions
|
|
* have been called.
|
|
*/
|
|
int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
|
|
{
|
|
if (current->mm)
|
|
fpsimd_preserve_current_state();
|
|
*dst = *src;
|
|
|
|
return 0;
|
|
}
|
|
|
|
asmlinkage void ret_from_fork(void) asm("ret_from_fork");
|
|
|
|
int copy_thread(unsigned long clone_flags, unsigned long stack_start,
|
|
unsigned long stk_sz, struct task_struct *p)
|
|
{
|
|
struct pt_regs *childregs = task_pt_regs(p);
|
|
|
|
memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context));
|
|
|
|
/*
|
|
* Unalias p->thread.sve_state (if any) from the parent task
|
|
* and disable discard SVE state for p:
|
|
*/
|
|
clear_tsk_thread_flag(p, TIF_SVE);
|
|
p->thread.sve_state = NULL;
|
|
|
|
if (likely(!(p->flags & PF_KTHREAD))) {
|
|
*childregs = *current_pt_regs();
|
|
childregs->regs[0] = 0;
|
|
|
|
/*
|
|
* Read the current TLS pointer from tpidr_el0 as it may be
|
|
* out-of-sync with the saved value.
|
|
*/
|
|
*task_user_tls(p) = read_sysreg(tpidr_el0);
|
|
|
|
if (stack_start) {
|
|
if (is_compat_thread(task_thread_info(p)))
|
|
childregs->compat_sp = stack_start;
|
|
else
|
|
childregs->sp = stack_start;
|
|
}
|
|
|
|
/*
|
|
* If a TLS pointer was passed to clone (4th argument), use it
|
|
* for the new thread.
|
|
*/
|
|
if (clone_flags & CLONE_SETTLS)
|
|
p->thread.tp_value = childregs->regs[3];
|
|
} else {
|
|
memset(childregs, 0, sizeof(struct pt_regs));
|
|
childregs->pstate = PSR_MODE_EL1h;
|
|
if (IS_ENABLED(CONFIG_ARM64_UAO) &&
|
|
cpus_have_const_cap(ARM64_HAS_UAO))
|
|
childregs->pstate |= PSR_UAO_BIT;
|
|
p->thread.cpu_context.x19 = stack_start;
|
|
p->thread.cpu_context.x20 = stk_sz;
|
|
}
|
|
p->thread.cpu_context.pc = (unsigned long)ret_from_fork;
|
|
p->thread.cpu_context.sp = (unsigned long)childregs;
|
|
|
|
ptrace_hw_copy_thread(p);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void tls_preserve_current_state(void)
|
|
{
|
|
*task_user_tls(current) = read_sysreg(tpidr_el0);
|
|
}
|
|
|
|
static void tls_thread_switch(struct task_struct *next)
|
|
{
|
|
unsigned long tpidr, tpidrro;
|
|
|
|
tls_preserve_current_state();
|
|
|
|
tpidr = *task_user_tls(next);
|
|
tpidrro = is_compat_thread(task_thread_info(next)) ?
|
|
next->thread.tp_value : 0;
|
|
|
|
write_sysreg(tpidr, tpidr_el0);
|
|
write_sysreg(tpidrro, tpidrro_el0);
|
|
}
|
|
|
|
/* Restore the UAO state depending on next's addr_limit */
|
|
void uao_thread_switch(struct task_struct *next)
|
|
{
|
|
if (IS_ENABLED(CONFIG_ARM64_UAO)) {
|
|
if (task_thread_info(next)->addr_limit == KERNEL_DS)
|
|
asm(ALTERNATIVE("nop", SET_PSTATE_UAO(1), ARM64_HAS_UAO));
|
|
else
|
|
asm(ALTERNATIVE("nop", SET_PSTATE_UAO(0), ARM64_HAS_UAO));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We store our current task in sp_el0, which is clobbered by userspace. Keep a
|
|
* shadow copy so that we can restore this upon entry from userspace.
|
|
*
|
|
* This is *only* for exception entry from EL0, and is not valid until we
|
|
* __switch_to() a user task.
|
|
*/
|
|
DEFINE_PER_CPU(struct task_struct *, __entry_task);
|
|
|
|
static void entry_task_switch(struct task_struct *next)
|
|
{
|
|
__this_cpu_write(__entry_task, next);
|
|
}
|
|
|
|
/*
|
|
* Thread switching.
|
|
*/
|
|
__notrace_funcgraph struct task_struct *__switch_to(struct task_struct *prev,
|
|
struct task_struct *next)
|
|
{
|
|
struct task_struct *last;
|
|
|
|
fpsimd_thread_switch(next);
|
|
tls_thread_switch(next);
|
|
hw_breakpoint_thread_switch(next);
|
|
contextidr_thread_switch(next);
|
|
entry_task_switch(next);
|
|
uao_thread_switch(next);
|
|
|
|
/*
|
|
* Complete any pending TLB or cache maintenance on this CPU in case
|
|
* the thread migrates to a different CPU.
|
|
* This full barrier is also required by the membarrier system
|
|
* call.
|
|
*/
|
|
dsb(ish);
|
|
|
|
/* the actual thread switch */
|
|
last = cpu_switch_to(prev, next);
|
|
|
|
return last;
|
|
}
|
|
|
|
unsigned long get_wchan(struct task_struct *p)
|
|
{
|
|
struct stackframe frame;
|
|
unsigned long stack_page, ret = 0;
|
|
int count = 0;
|
|
if (!p || p == current || p->state == TASK_RUNNING)
|
|
return 0;
|
|
|
|
stack_page = (unsigned long)try_get_task_stack(p);
|
|
if (!stack_page)
|
|
return 0;
|
|
|
|
frame.fp = thread_saved_fp(p);
|
|
frame.pc = thread_saved_pc(p);
|
|
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
|
frame.graph = p->curr_ret_stack;
|
|
#endif
|
|
do {
|
|
if (unwind_frame(p, &frame))
|
|
goto out;
|
|
if (!in_sched_functions(frame.pc)) {
|
|
ret = frame.pc;
|
|
goto out;
|
|
}
|
|
} while (count ++ < 16);
|
|
|
|
out:
|
|
put_task_stack(p);
|
|
return ret;
|
|
}
|
|
|
|
unsigned long arch_align_stack(unsigned long sp)
|
|
{
|
|
if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
|
|
sp -= get_random_int() & ~PAGE_MASK;
|
|
return sp & ~0xf;
|
|
}
|
|
|
|
unsigned long arch_randomize_brk(struct mm_struct *mm)
|
|
{
|
|
if (is_compat_task())
|
|
return randomize_page(mm->brk, SZ_32M);
|
|
else
|
|
return randomize_page(mm->brk, SZ_1G);
|
|
}
|
|
|
|
/*
|
|
* Called from setup_new_exec() after (COMPAT_)SET_PERSONALITY.
|
|
*/
|
|
void arch_setup_new_exec(void)
|
|
{
|
|
current->mm->context.flags = is_compat_task() ? MMCF_AARCH32 : 0;
|
|
}
|