mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
With the intended removal of PREEMPT_NONE this_cpu operations based on
atomic instructions, guarded with preempt_disable()/preempt_enable() pairs
become more expensive: the preempt_disable() / preempt_enable() pairs are
not optimized away anymore during compile time.
In particular the conditional call to preempt_schedule_notrace() after
preempt_enable() adds additional code and register pressure.
E.g. this simple C code sequence
DEFINE_PER_CPU(long, foo);
long bar(long a) { return this_cpu_add_return(foo, a); }
generates this code:
11a976: eb af f0 68 00 24 stmg %r10,%r15,104(%r15)
11a97c: b9 04 00 ef lgr %r14,%r15
11a980: b9 04 00 b2 lgr %r11,%r2
11a984: e3 f0 ff c8 ff 71 lay %r15,-56(%r15)
11a98a: e3 e0 f0 98 00 24 stg %r14,152(%r15)
11a990: eb 01 03 a8 00 6a asi 936,1 <- __preempt_count_add(1)
11a996: c0 10 00 d2 ac b5 larl %r1,1b70300 <- address of percpu var
11a9a0: e3 10 23 b8 00 08 ag %r1,952 <- add percpu offset
11a9a6: eb ab 10 00 00 e8 laag %r10,%r11,0(%r1) <- atomic op
11a9ac: eb ff 03 a8 00 6e alsi 936,-1 <- __preempt_count_dec_and_test()
11a9b2: a7 54 00 05 jnhe 11a9bc <bar+0x4c>
11a9b6: c0 e5 00 76 d1 bd brasl %r14,ff4d30 <preempt_schedule_notrace>
11a9bc: b9 e8 b0 2a agrk %r2,%r10,%r11
11a9c0: eb af f0 a0 00 04 lmg %r10,%r15,160(%r15)
11a9c6 07 fe br %r14
Even though the above example is more or less the worst case, since the
branch to preempt_schedule_notrace() requires a stackframe, which
otherwise wouldn't be necessary, there is also the conditional jnhe branch
instruction.
Get rid of the conditional branch with the following code sequence:
11a8e6: c0 30 00 d0 c5 0d larl %r3,1b33300
11a8ec: b9 04 00 43 lgr %r4,%r3
11a8f0: eb 00 43 c0 00 52 mviy 960,4
11a8f6: e3 40 03 b8 00 08 ag %r4,952
11a8fc: eb 52 40 00 00 e8 laag %r5,%r2,0(%r4)
11a902: eb 00 03 c0 00 52 mviy 960,0
11a908: b9 08 00 25 agr %r2,%r5
11a90c 07 fe br %r14
The general idea is that this_cpu operations based on atomic instructions
are guarded with mviy instructions:
- The first mviy instruction writes the register number, which contains
the percpu address variable to lowcore. This also indicates that a
percpu code section is executed.
- The first instruction following the mviy instruction must be the ag
instruction which adds the percpu offset to the percpu address register.
- Afterwards the atomic percpu operation follows.
- Then a second mviy instruction writes a zero to lowcore, which indicates
the end of the percpu code section.
- In case of an interrupt/exception/nmi the register number which was
written to lowcore is copied to the exception frame (pt_regs), and a zero
is written to lowcore.
- On return to the previous context it is checked if a percpu code section
was executed (saved register number not zero), and if the process was
migrated to a different cpu. If the percpu offset was already added to
the percpu address register (instruction address does _not_ point to the
ag instruction) the content of the percpu address register is adjusted so
it points to percpu variable of the new cpu.
Reviewed-by: Alexander Gordeev <agordeev@linux.ibm.com>
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Alexander Gordeev <agordeev@linux.ibm.com>
451 lines
12 KiB
C
451 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* S390 version
|
|
* Copyright IBM Corp. 1999, 2000
|
|
* Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
|
|
* Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com),
|
|
*
|
|
* Derived from "arch/i386/kernel/traps.c"
|
|
* Copyright (C) 1991, 1992 Linus Torvalds
|
|
*/
|
|
|
|
#include <linux/cpufeature.h>
|
|
#include <linux/kprobes.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/randomize_kstack.h>
|
|
#include <linux/extable.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/entry-common.h>
|
|
#include <linux/kmsan.h>
|
|
#include <linux/bug.h>
|
|
#include <asm/entry-percpu.h>
|
|
#include <asm/asm-extable.h>
|
|
#include <asm/irqflags.h>
|
|
#include <asm/ptrace.h>
|
|
#include <asm/vtime.h>
|
|
#include <asm/fpu.h>
|
|
#include <asm/fault.h>
|
|
#include "entry.h"
|
|
|
|
static inline void __user *get_trap_ip(struct pt_regs *regs)
|
|
{
|
|
unsigned long address;
|
|
|
|
if (regs->int_code & 0x200)
|
|
address = current->thread.trap_tdb.data[3];
|
|
else
|
|
address = regs->psw.addr;
|
|
return (void __user *)(address - (regs->int_code >> 16));
|
|
}
|
|
|
|
#ifdef CONFIG_GENERIC_BUG
|
|
int is_valid_bugaddr(unsigned long addr)
|
|
{
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
void do_report_trap(struct pt_regs *regs, int si_signo, int si_code, char *str)
|
|
{
|
|
if (user_mode(regs)) {
|
|
force_sig_fault(si_signo, si_code, get_trap_ip(regs));
|
|
report_user_fault(regs, si_signo, 0);
|
|
} else {
|
|
if (!fixup_exception(regs))
|
|
die(regs, str);
|
|
}
|
|
}
|
|
|
|
static void do_trap(struct pt_regs *regs, int si_signo, int si_code, char *str)
|
|
{
|
|
if (notify_die(DIE_TRAP, str, regs, 0, regs->int_code, si_signo) == NOTIFY_STOP)
|
|
return;
|
|
do_report_trap(regs, si_signo, si_code, str);
|
|
}
|
|
NOKPROBE_SYMBOL(do_trap);
|
|
|
|
void do_per_trap(struct pt_regs *regs)
|
|
{
|
|
if (notify_die(DIE_SSTEP, "sstep", regs, 0, 0, SIGTRAP) == NOTIFY_STOP)
|
|
return;
|
|
if (!current->ptrace)
|
|
return;
|
|
force_sig_fault(SIGTRAP, TRAP_HWBKPT, (void __force __user *)current->thread.per_event.address);
|
|
}
|
|
NOKPROBE_SYMBOL(do_per_trap);
|
|
|
|
static void default_trap_handler(struct pt_regs *regs)
|
|
{
|
|
if (user_mode(regs)) {
|
|
report_user_fault(regs, SIGSEGV, 0);
|
|
force_exit_sig(SIGSEGV);
|
|
} else
|
|
die(regs, "Unknown program exception");
|
|
}
|
|
|
|
#define DO_ERROR_INFO(name, signr, sicode, str) \
|
|
static void name(struct pt_regs *regs) \
|
|
{ \
|
|
do_trap(regs, signr, sicode, str); \
|
|
}
|
|
|
|
DO_ERROR_INFO(addressing_exception, SIGILL, ILL_ILLADR, "addressing exception")
|
|
DO_ERROR_INFO(divide_exception, SIGFPE, FPE_INTDIV, "fixpoint divide exception")
|
|
DO_ERROR_INFO(execute_exception, SIGILL, ILL_ILLOPN, "execute exception")
|
|
DO_ERROR_INFO(hfp_divide_exception, SIGFPE, FPE_FLTDIV, "HFP divide exception")
|
|
DO_ERROR_INFO(hfp_overflow_exception, SIGFPE, FPE_FLTOVF, "HFP overflow exception")
|
|
DO_ERROR_INFO(hfp_significance_exception, SIGFPE, FPE_FLTRES, "HFP significance exception")
|
|
DO_ERROR_INFO(hfp_sqrt_exception, SIGFPE, FPE_FLTINV, "HFP square root exception")
|
|
DO_ERROR_INFO(hfp_underflow_exception, SIGFPE, FPE_FLTUND, "HFP underflow exception")
|
|
DO_ERROR_INFO(operand_exception, SIGILL, ILL_ILLOPN, "operand exception")
|
|
DO_ERROR_INFO(overflow_exception, SIGFPE, FPE_INTOVF, "fixpoint overflow exception")
|
|
DO_ERROR_INFO(privileged_op, SIGILL, ILL_PRVOPC, "privileged operation")
|
|
DO_ERROR_INFO(special_op_exception, SIGILL, ILL_ILLOPN, "special operation exception")
|
|
DO_ERROR_INFO(specification_exception, SIGILL, ILL_ILLOPN, "specification exception");
|
|
DO_ERROR_INFO(transaction_exception, SIGILL, ILL_ILLOPN, "transaction constraint exception")
|
|
|
|
static inline void do_fp_trap(struct pt_regs *regs, __u32 fpc)
|
|
{
|
|
int si_code = 0;
|
|
|
|
/* FPC[2] is Data Exception Code */
|
|
if ((fpc & 0x00000300) == 0) {
|
|
/* bits 6 and 7 of DXC are 0 iff IEEE exception */
|
|
if (fpc & 0x8000) /* invalid fp operation */
|
|
si_code = FPE_FLTINV;
|
|
else if (fpc & 0x4000) /* div by 0 */
|
|
si_code = FPE_FLTDIV;
|
|
else if (fpc & 0x2000) /* overflow */
|
|
si_code = FPE_FLTOVF;
|
|
else if (fpc & 0x1000) /* underflow */
|
|
si_code = FPE_FLTUND;
|
|
else if (fpc & 0x0800) /* inexact */
|
|
si_code = FPE_FLTRES;
|
|
}
|
|
do_trap(regs, SIGFPE, si_code, "floating point exception");
|
|
}
|
|
|
|
static void translation_specification_exception(struct pt_regs *regs)
|
|
{
|
|
/* May never happen. */
|
|
panic("Translation-Specification Exception");
|
|
}
|
|
|
|
static void illegal_op(struct pt_regs *regs)
|
|
{
|
|
int is_uprobe_insn = 0;
|
|
u16 __user *location;
|
|
int signal = 0;
|
|
u16 opcode;
|
|
|
|
location = get_trap_ip(regs);
|
|
if (user_mode(regs)) {
|
|
if (get_user(opcode, location))
|
|
return;
|
|
if (opcode == S390_BREAKPOINT_U16) {
|
|
if (current->ptrace)
|
|
force_sig_fault(SIGTRAP, TRAP_BRKPT, location);
|
|
else
|
|
signal = SIGILL;
|
|
#ifdef CONFIG_UPROBES
|
|
} else if (opcode == UPROBE_SWBP_INSN) {
|
|
is_uprobe_insn = 1;
|
|
#endif
|
|
} else {
|
|
signal = SIGILL;
|
|
}
|
|
}
|
|
/*
|
|
* This is either an illegal op in kernel mode, or user space trapped
|
|
* on a uprobes illegal instruction. See if kprobes or uprobes picks
|
|
* it up. If not, SIGILL.
|
|
*/
|
|
if (is_uprobe_insn || !user_mode(regs)) {
|
|
if (notify_die(DIE_BPT, "bpt", regs, 0, 3, SIGTRAP) != NOTIFY_STOP)
|
|
signal = SIGILL;
|
|
}
|
|
if (signal)
|
|
do_trap(regs, signal, ILL_ILLOPC, "illegal operation");
|
|
}
|
|
NOKPROBE_SYMBOL(illegal_op);
|
|
|
|
static void vector_exception(struct pt_regs *regs)
|
|
{
|
|
int si_code, vic;
|
|
|
|
/* get vector interrupt code from fpc */
|
|
save_user_fpu_regs();
|
|
vic = (current->thread.ufpu.fpc & 0xf00) >> 8;
|
|
switch (vic) {
|
|
case 1: /* invalid vector operation */
|
|
si_code = FPE_FLTINV;
|
|
break;
|
|
case 2: /* division by zero */
|
|
si_code = FPE_FLTDIV;
|
|
break;
|
|
case 3: /* overflow */
|
|
si_code = FPE_FLTOVF;
|
|
break;
|
|
case 4: /* underflow */
|
|
si_code = FPE_FLTUND;
|
|
break;
|
|
case 5: /* inexact */
|
|
si_code = FPE_FLTRES;
|
|
break;
|
|
default: /* unknown cause */
|
|
si_code = 0;
|
|
}
|
|
do_trap(regs, SIGFPE, si_code, "vector exception");
|
|
}
|
|
|
|
static void data_exception(struct pt_regs *regs)
|
|
{
|
|
save_user_fpu_regs();
|
|
if (current->thread.ufpu.fpc & FPC_DXC_MASK)
|
|
do_fp_trap(regs, current->thread.ufpu.fpc);
|
|
else
|
|
do_trap(regs, SIGILL, ILL_ILLOPN, "data exception");
|
|
}
|
|
|
|
static void space_switch_exception(struct pt_regs *regs)
|
|
{
|
|
/* Set user psw back to home space mode. */
|
|
if (user_mode(regs))
|
|
regs->psw.mask |= PSW_ASC_HOME;
|
|
/* Send SIGILL. */
|
|
do_trap(regs, SIGILL, ILL_PRVOPC, "space switch event");
|
|
}
|
|
|
|
#if defined(CONFIG_BUG) && defined(CONFIG_CC_HAS_ASM_IMMEDIATE_STRINGS)
|
|
|
|
void *__warn_args(struct arch_va_list *args, struct pt_regs *regs)
|
|
{
|
|
struct stack_frame *stack_frame;
|
|
|
|
/*
|
|
* Generate va_list from pt_regs. See ELF Application Binary Interface
|
|
* s390x Supplement documentation for details.
|
|
*
|
|
* - __overflow_arg_area needs to point to the parameter area, which
|
|
* is right above the standard stack frame (160 bytes)
|
|
*
|
|
* - __reg_save_area needs to point to a register save area where
|
|
* general registers (%r2 - %r6) can be found at offset 16. Which
|
|
* means that the gprs save area of pt_regs can be used
|
|
*
|
|
* - __gpr must be set to one, since the first parameter has been
|
|
* processed (pointer to bug_entry)
|
|
*/
|
|
stack_frame = (struct stack_frame *)regs->gprs[15];
|
|
args->__overflow_arg_area = stack_frame + 1;
|
|
args->__reg_save_area = regs->gprs;
|
|
args->__gpr = 1;
|
|
return args;
|
|
}
|
|
|
|
#endif /* CONFIG_BUG && CONFIG_CC_HAS_ASM_IMMEDIATE_STRINGS */
|
|
|
|
static void monitor_event_exception(struct pt_regs *regs)
|
|
{
|
|
enum bug_trap_type btt;
|
|
|
|
if (user_mode(regs))
|
|
return;
|
|
if (regs->monitor_code == MONCODE_BUG_ARG) {
|
|
regs->psw.addr = regs->gprs[14];
|
|
btt = report_bug_entry((struct bug_entry *)regs->gprs[2], regs);
|
|
} else {
|
|
btt = report_bug(regs->psw.addr - (regs->int_code >> 16), regs);
|
|
}
|
|
switch (btt) {
|
|
case BUG_TRAP_TYPE_NONE:
|
|
fixup_exception(regs);
|
|
break;
|
|
case BUG_TRAP_TYPE_WARN:
|
|
break;
|
|
case BUG_TRAP_TYPE_BUG:
|
|
die(regs, "monitor event");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void kernel_stack_invalid(struct pt_regs *regs)
|
|
{
|
|
/*
|
|
* Normally regs are unpoisoned by the generic entry code, but
|
|
* kernel_stack_overflow() is a rare case that is called bypassing it.
|
|
*/
|
|
kmsan_unpoison_entry_regs(regs);
|
|
bust_spinlocks(1);
|
|
pr_emerg("Kernel stack pointer invalid\n");
|
|
show_regs(regs);
|
|
bust_spinlocks(0);
|
|
panic("Invalid kernel stack pointer, cannot continue");
|
|
}
|
|
NOKPROBE_SYMBOL(kernel_stack_invalid);
|
|
|
|
static void __init test_monitor_call(void)
|
|
{
|
|
int val = 1;
|
|
|
|
if (!IS_ENABLED(CONFIG_BUG))
|
|
return;
|
|
asm_inline volatile(
|
|
" mc %[monc](%%r0),0\n"
|
|
"0: lhi %[val],0\n"
|
|
"1:\n"
|
|
EX_TABLE(0b, 1b)
|
|
: [val] "+d" (val)
|
|
: [monc] "i" (MONCODE_BUG));
|
|
if (!val)
|
|
panic("Monitor call doesn't work!\n");
|
|
}
|
|
|
|
void __init trap_init(void)
|
|
{
|
|
struct lowcore *lc = get_lowcore();
|
|
unsigned long flags;
|
|
struct ctlreg cr0;
|
|
|
|
local_irq_save(flags);
|
|
cr0 = local_ctl_clear_bit(0, CR0_LOW_ADDRESS_PROTECTION_BIT);
|
|
psw_bits(lc->external_new_psw).mcheck = 1;
|
|
psw_bits(lc->program_new_psw).mcheck = 1;
|
|
psw_bits(lc->svc_new_psw).mcheck = 1;
|
|
psw_bits(lc->io_new_psw).mcheck = 1;
|
|
local_ctl_load(0, &cr0);
|
|
local_irq_restore(flags);
|
|
local_mcck_enable();
|
|
test_monitor_call();
|
|
}
|
|
|
|
static void (*pgm_check_table[128])(struct pt_regs *regs);
|
|
|
|
void noinstr __do_pgm_check(struct pt_regs *regs)
|
|
{
|
|
struct lowcore *lc = get_lowcore();
|
|
bool percpu_needs_fixup;
|
|
irqentry_state_t state;
|
|
unsigned int trapnr;
|
|
union teid teid;
|
|
|
|
teid.val = lc->trans_exc_code;
|
|
regs->int_code = lc->pgm_int_code;
|
|
regs->int_parm_long = teid.val;
|
|
regs->monitor_code = lc->monitor_code;
|
|
/*
|
|
* In case of a guest fault, short-circuit the fault handler and return.
|
|
* This way the sie64a() function will return 0; fault address and
|
|
* other relevant bits are saved in current->thread.gmap_teid, and
|
|
* the fault number in current->thread.gmap_int_code. KVM will be
|
|
* able to use this information to handle the fault.
|
|
*/
|
|
if (test_pt_regs_flag(regs, PIF_GUEST_FAULT)) {
|
|
current->thread.gmap_teid.val = regs->int_parm_long;
|
|
current->thread.gmap_int_code = regs->int_code & 0xffff;
|
|
return;
|
|
}
|
|
percpu_entry(regs);
|
|
state = irqentry_enter(regs);
|
|
if (user_mode(regs)) {
|
|
update_timer_sys();
|
|
if (!cpu_has_bear()) {
|
|
if (regs->last_break < 4096)
|
|
regs->last_break = 1;
|
|
}
|
|
current->thread.last_break = regs->last_break;
|
|
}
|
|
if (lc->pgm_code & 0x0200) {
|
|
/* transaction abort */
|
|
current->thread.trap_tdb = lc->pgm_tdb;
|
|
}
|
|
if (lc->pgm_code & PGM_INT_CODE_PER) {
|
|
if (user_mode(regs)) {
|
|
struct per_event *ev = ¤t->thread.per_event;
|
|
|
|
set_thread_flag(TIF_PER_TRAP);
|
|
ev->address = lc->per_address;
|
|
ev->cause = lc->per_code_combined;
|
|
ev->paid = lc->per_access_id;
|
|
} else {
|
|
/* PER event in kernel is kprobes */
|
|
__arch_local_irq_ssm(regs->psw.mask & ~PSW_MASK_PER);
|
|
do_per_trap(regs);
|
|
goto out;
|
|
}
|
|
}
|
|
if (!irqs_disabled_flags(regs->psw.mask))
|
|
trace_hardirqs_on();
|
|
__arch_local_irq_ssm(regs->psw.mask & ~PSW_MASK_PER);
|
|
trapnr = regs->int_code & PGM_INT_CODE_MASK;
|
|
if (trapnr)
|
|
pgm_check_table[trapnr](regs);
|
|
out:
|
|
local_irq_disable();
|
|
percpu_needs_fixup = percpu_code_check(regs);
|
|
irqentry_exit(regs, state);
|
|
percpu_exit(regs, percpu_needs_fixup);
|
|
}
|
|
|
|
/*
|
|
* The program check table contains exactly 128 (0x00-0x7f) entries. Each
|
|
* line defines the function to be called corresponding to the program check
|
|
* interruption code.
|
|
*/
|
|
static void (*pgm_check_table[128])(struct pt_regs *regs) = {
|
|
[0x00] = default_trap_handler,
|
|
[0x01] = illegal_op,
|
|
[0x02] = privileged_op,
|
|
[0x03] = execute_exception,
|
|
[0x04] = do_protection_exception,
|
|
[0x05] = addressing_exception,
|
|
[0x06] = specification_exception,
|
|
[0x07] = data_exception,
|
|
[0x08] = overflow_exception,
|
|
[0x09] = divide_exception,
|
|
[0x0a] = overflow_exception,
|
|
[0x0b] = divide_exception,
|
|
[0x0c] = hfp_overflow_exception,
|
|
[0x0d] = hfp_underflow_exception,
|
|
[0x0e] = hfp_significance_exception,
|
|
[0x0f] = hfp_divide_exception,
|
|
[0x10] = do_dat_exception,
|
|
[0x11] = do_dat_exception,
|
|
[0x12] = translation_specification_exception,
|
|
[0x13] = special_op_exception,
|
|
[0x14] = default_trap_handler,
|
|
[0x15] = operand_exception,
|
|
[0x16] = default_trap_handler,
|
|
[0x17] = default_trap_handler,
|
|
[0x18] = transaction_exception,
|
|
[0x19] = default_trap_handler,
|
|
[0x1a] = default_trap_handler,
|
|
[0x1b] = vector_exception,
|
|
[0x1c] = space_switch_exception,
|
|
[0x1d] = hfp_sqrt_exception,
|
|
[0x1e ... 0x37] = default_trap_handler,
|
|
[0x38] = do_dat_exception,
|
|
[0x39] = do_dat_exception,
|
|
[0x3a] = do_dat_exception,
|
|
[0x3b] = do_dat_exception,
|
|
[0x3c] = default_trap_handler,
|
|
[0x3d] = do_secure_storage_access,
|
|
[0x3e] = default_trap_handler,
|
|
[0x3f] = default_trap_handler,
|
|
[0x40] = monitor_event_exception,
|
|
[0x41 ... 0x7f] = default_trap_handler,
|
|
};
|
|
|
|
#define COND_TRAP(x) asm( \
|
|
".weak " __stringify(x) "\n\t" \
|
|
".set " __stringify(x) "," \
|
|
__stringify(default_trap_handler))
|
|
|
|
COND_TRAP(do_secure_storage_access);
|