// SPDX-License-Identifier: GPL-2.0
/*
* ' traps . c ' handles hardware traps and faults after we have saved some
* state in ' entry . S ' .
*
* SuperH version : Copyright ( C ) 1999 Niibe Yutaka
* Copyright ( C ) 2000 Philipp Rumpf
* Copyright ( C ) 2000 David Howells
* Copyright ( C ) 2002 - 2010 Paul Mundt
*/
#include <linux/kernel.h>
#include <linux/ptrace.h>
#include <linux/hardirq.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/kallsyms.h>
#include <linux/io.h>
#include <linux/bug.h>
#include <linux/debug_locks.h>
#include <linux/kdebug.h>
#include <linux/limits.h>
#include <linux/sysfs.h>
#include <linux/uaccess.h>
#include <linux/perf_event.h>
#include <linux/sched/task_stack.h>
#include <asm /alignment.h>
#include <asm /fpu.h>
#include <asm /kprobes.h>
#include <asm /setup.h>
#include <asm /traps.h>
#include <asm /bl_bit.h>
#ifdef CONFIG_CPU_SH2
# define TRAP_RESERVED_INST 4
# define TRAP_ILLEGAL_SLOT_INST 6
# define TRAP_ADDRESS_ERROR 9
# ifdef CONFIG_CPU_SH2A
# define TRAP_UBC 12
# define TRAP_FPU_ERROR 13
# define TRAP_DIVZERO_ERROR 17
# define TRAP_DIVOVF_ERROR 18
# endif
#else
#define TRAP_RESERVED_INST 12
#define TRAP_ILLEGAL_SLOT_INST 13
#endif
static inline void sign_extend(unsigned int count, unsigned char *dst)
{
#ifdef __LITTLE_ENDIAN__
if ((count == 1 ) && dst[0 ] & 0 x80) {
dst[1 ] = 0 xff;
dst[2 ] = 0 xff;
dst[3 ] = 0 xff;
}
if ((count == 2 ) && dst[1 ] & 0 x80) {
dst[2 ] = 0 xff;
dst[3 ] = 0 xff;
}
#else
if ((count == 1 ) && dst[3 ] & 0 x80) {
dst[2 ] = 0 xff;
dst[1 ] = 0 xff;
dst[0 ] = 0 xff;
}
if ((count == 2 ) && dst[2 ] & 0 x80) {
dst[1 ] = 0 xff;
dst[0 ] = 0 xff;
}
#endif
}
static struct mem_access user_mem_access = {
copy_from_user,
copy_to_user,
};
static unsigned long copy_from_kernel_wrapper(void *dst, const void __user *src,
unsigned long cnt)
{
return copy_from_kernel_nofault(dst, (const void __force *)src, cnt);
}
static unsigned long copy_to_kernel_wrapper(void __user *dst, const void *src,
unsigned long cnt)
{
return copy_to_kernel_nofault((void __force *)dst, src, cnt);
}
static struct mem_access kernel_mem_access = {
copy_from_kernel_wrapper,
copy_to_kernel_wrapper,
};
/*
* handle an instruction that does an unaligned memory access by emulating the
* desired behaviour
* - note that PC _ may not_ point to the faulting instruction
* ( if that instruction is in a branch delay slot )
* - return 0 if emulation okay , - EFAULT on existential error
*/
static int handle_unaligned_ins(insn_size_t instruction, struct pt_regs *regs,
struct mem_access *ma)
{
int ret, index, count;
unsigned long *rm, *rn;
unsigned char *src, *dst;
unsigned char __user *srcu, *dstu;
index = (instruction>>8 )&15 ; /* 0x0F00 */
rn = ®s->regs[index];
index = (instruction>>4 )&15 ; /* 0x00F0 */
rm = ®s->regs[index];
count = 1 <<(instruction&3 );
switch (count) {
case 1 : inc_unaligned_byte_access(); break ;
case 2 : inc_unaligned_word_access(); break ;
case 4 : inc_unaligned_dword_access(); break ;
case 8 : inc_unaligned_multi_access(); break ;
}
ret = -EFAULT;
switch (instruction>>12 ) {
case 0 : /* mov.[bwl] to/from memory via r0+rn */
if (instruction & 8 ) {
/* from memory */
srcu = (unsigned char __user *)*rm;
srcu += regs->regs[0 ];
dst = (unsigned char *)rn;
*(unsigned long *)dst = 0 ;
#if !defined (__LITTLE_ENDIAN__)
dst += 4 -count;
#endif
if (ma->from(dst, srcu, count))
goto fetch_fault;
sign_extend(count, dst);
} else {
/* to memory */
src = (unsigned char *)rm;
#if !defined (__LITTLE_ENDIAN__)
src += 4 -count;
#endif
dstu = (unsigned char __user *)*rn;
dstu += regs->regs[0 ];
if (ma->to(dstu, src, count))
goto fetch_fault;
}
ret = 0 ;
break ;
case 1 : /* mov.l Rm,@(disp,Rn) */
src = (unsigned char *) rm;
dstu = (unsigned char __user *)*rn;
dstu += (instruction&0 x000F)<<2 ;
if (ma->to(dstu, src, 4 ))
goto fetch_fault;
ret = 0 ;
break ;
case 2 : /* mov.[bwl] to memory, possibly with pre-decrement */
if (instruction & 4 )
*rn -= count;
src = (unsigned char *) rm;
dstu = (unsigned char __user *)*rn;
#if !defined (__LITTLE_ENDIAN__)
src += 4 -count;
#endif
if (ma->to(dstu, src, count))
goto fetch_fault;
ret = 0 ;
break ;
case 5 : /* mov.l @(disp,Rm),Rn */
srcu = (unsigned char __user *)*rm;
srcu += (instruction & 0 x000F) << 2 ;
dst = (unsigned char *)rn;
*(unsigned long *)dst = 0 ;
if (ma->from(dst, srcu, 4 ))
goto fetch_fault;
ret = 0 ;
break ;
case 6 : /* mov.[bwl] from memory, possibly with post-increment */
srcu = (unsigned char __user *)*rm;
if (instruction & 4 )
*rm += count;
dst = (unsigned char *) rn;
*(unsigned long *)dst = 0 ;
#if !defined (__LITTLE_ENDIAN__)
dst += 4 -count;
#endif
if (ma->from(dst, srcu, count))
goto fetch_fault;
sign_extend(count, dst);
ret = 0 ;
break ;
case 8 :
switch ((instruction&0 xFF00)>>8 ) {
case 0 x81: /* mov.w R0,@(disp,Rn) */
src = (unsigned char *) ®s->regs[0 ];
#if !defined (__LITTLE_ENDIAN__)
src += 2 ;
#endif
dstu = (unsigned char __user *)*rm; /* called Rn in the spec */
dstu += (instruction & 0 x000F) << 1 ;
if (ma->to(dstu, src, 2 ))
goto fetch_fault;
ret = 0 ;
break ;
case 0 x85: /* mov.w @(disp,Rm),R0 */
srcu = (unsigned char __user *)*rm;
srcu += (instruction & 0 x000F) << 1 ;
dst = (unsigned char *) ®s->regs[0 ];
*(unsigned long *)dst = 0 ;
#if !defined (__LITTLE_ENDIAN__)
dst += 2 ;
#endif
if (ma->from(dst, srcu, 2 ))
goto fetch_fault;
sign_extend(2 , dst);
ret = 0 ;
break ;
}
break ;
case 9 : /* mov.w @(disp,PC),Rn */
srcu = (unsigned char __user *)regs->pc;
srcu += 4 ;
srcu += (instruction & 0 x00FF) << 1 ;
dst = (unsigned char *)rn;
*(unsigned long *)dst = 0 ;
#if !defined (__LITTLE_ENDIAN__)
dst += 2 ;
#endif
if (ma->from(dst, srcu, 2 ))
goto fetch_fault;
sign_extend(2 , dst);
ret = 0 ;
break ;
case 0 xd: /* mov.l @(disp,PC),Rn */
srcu = (unsigned char __user *)(regs->pc & ~0 x3);
srcu += 4 ;
srcu += (instruction & 0 x00FF) << 2 ;
dst = (unsigned char *)rn;
*(unsigned long *)dst = 0 ;
if (ma->from(dst, srcu, 4 ))
goto fetch_fault;
ret = 0 ;
break ;
}
return ret;
fetch_fault:
/* Argh. Address not only misaligned but also non-existent.
* Raise an EFAULT and see if it ' s trapped
*/
die_if_no_fixup("Fault in unaligned fixup" , regs, 0 );
return -EFAULT;
}
/*
* emulate the instruction in the delay slot
* - fetches the instruction from PC + 2
*/
static inline int handle_delayslot(struct pt_regs *regs,
insn_size_t old_instruction,
struct mem_access *ma)
{
insn_size_t instruction;
void __user *addr = (void __user *)(regs->pc +
instruction_size(old_instruction));
if (copy_from_user(&instruction, addr, sizeof (instruction))) {
/* the instruction-fetch faulted */
if (user_mode(regs))
return -EFAULT;
/* kernel */
die("delay-slot-insn faulting in handle_unaligned_delayslot" ,
regs, 0 );
}
return handle_unaligned_ins(instruction, regs, ma);
}
/*
* handle an instruction that does an unaligned memory access
* - have to be careful of branch delay - slot instructions that fault
* SH3 :
* - if the branch would be taken PC points to the branch
* - if the branch would not be taken , PC points to delay - slot
* SH4 :
* - PC always points to delayed branch
* - return 0 if handled , - EFAULT if failed ( may not return if in kernel )
*/
/* Macros to determine offset from current PC for branch instructions */
/* Explicit type coercion is used to force sign extension where needed */
#define SH_PC_8BIT_OFFSET(instr) ((((signed char )(instr))*2 ) + 4 )
#define SH_PC_12BIT_OFFSET(instr) ((((signed short )(instr<<4 ))>>3 ) + 4 )
int handle_unaligned_access(insn_size_t instruction, struct pt_regs *regs,
struct mem_access *ma, int expected,
unsigned long address)
{
u_int rm;
int ret, index;
/*
* XXX : We can ' t handle mixed 16 / 32 - bit instructions yet
*/
if (instruction_size(instruction) != 2 )
return -EINVAL;
index = (instruction>>8 )&15 ; /* 0x0F00 */
rm = regs->regs[index];
/*
* Log the unexpected fixups , and then pass them on to perf .
*
* We intentionally don ' t report the expected cases to perf as
* otherwise the trapped I / O case will skew the results too much
* to be useful .
*/
if (!expected) {
unaligned_fixups_notify(current, instruction, regs);
perf_sw_event(PERF_COUNT_SW_ALIGNMENT_FAULTS, 1 ,
regs, address);
}
ret = -EFAULT;
switch (instruction&0 xF000) {
case 0 x0000:
if (instruction==0 x000B) {
/* rts */
ret = handle_delayslot(regs, instruction, ma);
if (ret==0 )
regs->pc = regs->pr;
}
else if ((instruction&0 x00FF)==0 x0023) {
/* braf @Rm */
ret = handle_delayslot(regs, instruction, ma);
if (ret==0 )
regs->pc += rm + 4 ;
}
else if ((instruction&0 x00FF)==0 x0003) {
/* bsrf @Rm */
ret = handle_delayslot(regs, instruction, ma);
if (ret==0 ) {
regs->pr = regs->pc + 4 ;
regs->pc += rm + 4 ;
}
}
else {
/* mov.[bwl] to/from memory via r0+rn */
goto simple;
}
break ;
case 0 x1000: /* mov.l Rm,@(disp,Rn) */
goto simple;
case 0 x2000: /* mov.[bwl] to memory, possibly with pre-decrement */
goto simple;
case 0 x4000:
if ((instruction&0 x00FF)==0 x002B) {
/* jmp @Rm */
ret = handle_delayslot(regs, instruction, ma);
if (ret==0 )
regs->pc = rm;
}
else if ((instruction&0 x00FF)==0 x000B) {
/* jsr @Rm */
ret = handle_delayslot(regs, instruction, ma);
if (ret==0 ) {
regs->pr = regs->pc + 4 ;
regs->pc = rm;
}
}
else {
/* mov.[bwl] to/from memory via r0+rn */
goto simple;
}
break ;
case 0 x5000: /* mov.l @(disp,Rm),Rn */
goto simple;
case 0 x6000: /* mov.[bwl] from memory, possibly with post-increment */
goto simple;
case 0 x8000: /* bf lab, bf/s lab, bt lab, bt/s lab */
switch (instruction&0 x0F00) {
case 0 x0100: /* mov.w R0,@(disp,Rm) */
goto simple;
case 0 x0500: /* mov.w @(disp,Rm),R0 */
goto simple;
case 0 x0B00: /* bf lab - no delayslot*/
ret = 0 ;
break ;
case 0 x0F00: /* bf/s lab */
ret = handle_delayslot(regs, instruction, ma);
if (ret==0 ) {
#if defined (CONFIG_CPU_SH4) || defined (CONFIG_SH7705_CACHE_32KB)
if ((regs->sr & 0 x00000001) != 0 )
regs->pc += 4 ; /* next after slot */
else
#endif
regs->pc += SH_PC_8BIT_OFFSET(instruction);
}
break ;
case 0 x0900: /* bt lab - no delayslot */
ret = 0 ;
break ;
case 0 x0D00: /* bt/s lab */
ret = handle_delayslot(regs, instruction, ma);
if (ret==0 ) {
#if defined (CONFIG_CPU_SH4) || defined (CONFIG_SH7705_CACHE_32KB)
if ((regs->sr & 0 x00000001) == 0 )
regs->pc += 4 ; /* next after slot */
else
#endif
regs->pc += SH_PC_8BIT_OFFSET(instruction);
}
break ;
}
break ;
case 0 x9000: /* mov.w @(disp,Rm),Rn */
goto simple;
case 0 xA000: /* bra label */
ret = handle_delayslot(regs, instruction, ma);
if (ret==0 )
regs->pc += SH_PC_12BIT_OFFSET(instruction);
break ;
case 0 xB000: /* bsr label */
ret = handle_delayslot(regs, instruction, ma);
if (ret==0 ) {
regs->pr = regs->pc + 4 ;
regs->pc += SH_PC_12BIT_OFFSET(instruction);
}
break ;
case 0 xD000: /* mov.l @(disp,Rm),Rn */
goto simple;
}
return ret;
/* handle non-delay-slot instruction */
simple:
ret = handle_unaligned_ins(instruction, regs, ma);
if (ret==0 )
regs->pc += instruction_size(instruction);
return ret;
}
/*
* Handle various address error exceptions :
* - instruction address error :
* misaligned PC
* PC > = 0 x80000000 in user mode
* - data address error ( read and write )
* misaligned data access
* access to > = 0 x80000000 is user mode
* Unfortuntaly we can ' t distinguish between instruction address error
* and data address errors caused by read accesses .
*/
asmlinkage void do_address_error(struct pt_regs *regs,
unsigned long writeaccess,
unsigned long address)
{
unsigned long error_code = 0 ;
insn_size_t instruction;
int tmp;
/* Intentional ifdef */
#ifdef CONFIG_CPU_HAS_SR_RB
error_code = lookup_exception_vector();
#endif
if (user_mode(regs)) {
int si_code = BUS_ADRERR;
unsigned int user_action;
local_irq_enable();
inc_unaligned_user_access();
if (copy_from_user(&instruction, (insn_size_t __user *)(regs->pc & ~1 ),
sizeof (instruction))) {
goto uspace_segv;
}
/* shout about userspace fixups */
unaligned_fixups_notify(current, instruction, regs);
user_action = unaligned_user_action();
if (user_action & UM_FIXUP)
goto fixup;
if (user_action & UM_SIGNAL)
goto uspace_segv;
else {
/* ignore */
regs->pc += instruction_size(instruction);
return ;
}
fixup:
/* bad PC is not something we can fix */
if (regs->pc & 1 ) {
si_code = BUS_ADRALN;
goto uspace_segv;
}
tmp = handle_unaligned_access(instruction, regs,
&user_mem_access, 0 ,
address);
if (tmp == 0 )
return ; /* sorted */
uspace_segv:
printk(KERN_NOTICE "Sending SIGBUS to \" %s\" due to unaligned "
"access (PC %lx PR %lx)\n" , current->comm, regs->pc,
regs->pr);
force_sig_fault(SIGBUS, si_code, (void __user *)address);
} else {
inc_unaligned_kernel_access();
if (regs->pc & 1 )
die("unaligned program counter" , regs, error_code);
if (copy_from_kernel_nofault(&instruction, (void *)(regs->pc),
sizeof (instruction))) {
/* Argh. Fault on the instruction itself.
This should never happen non - SMP
*/
die("insn faulting in do_address_error" , regs, 0 );
}
unaligned_fixups_notify(current, instruction, regs);
handle_unaligned_access(instruction, regs, &kernel_mem_access,
0 , address);
}
}
#ifdef CONFIG_SH_DSP
/*
* SH - DSP support gerg @ snapgear . com .
*/
static int is_dsp_inst(struct pt_regs *regs)
{
unsigned short inst = 0 ;
/*
* Safe guard if DSP mode is already enabled or we ' re lacking
* the DSP altogether .
*/
if (!(current_cpu_data.flags & CPU_HAS_DSP) || (regs->sr & SR_DSP))
return 0 ;
get_user(inst, ((unsigned short *) regs->pc));
inst &= 0 xf000;
/* Check for any type of DSP or support instruction */
if ((inst == 0 xf000) || (inst == 0 x4000))
return 1 ;
return 0 ;
}
#else
static inline int is_dsp_inst(struct pt_regs *regs) { return 0 ; }
#endif /* CONFIG_SH_DSP */
#ifdef CONFIG_CPU_SH2A
asmlinkage void do_divide_error(unsigned long r4)
{
int code;
switch (r4) {
case TRAP_DIVZERO_ERROR:
code = FPE_INTDIV;
break ;
case TRAP_DIVOVF_ERROR:
code = FPE_INTOVF;
break ;
default :
/* Let gcc know unhandled cases don't make it past here */
return ;
}
force_sig_fault(SIGFPE, code, NULL);
}
#endif
asmlinkage void do_reserved_inst(void )
{
struct pt_regs *regs = current_pt_regs();
unsigned long error_code;
#ifdef CONFIG_SH_FPU_EMU
unsigned short inst = 0 ;
int err;
get_user(inst, (unsigned short __user *)regs->pc);
err = do_fpu_inst(inst, regs);
if (!err) {
regs->pc += instruction_size(inst);
return ;
}
/* not a FPU inst. */
#endif
#ifdef CONFIG_SH_DSP
/* Check if it's a DSP instruction */
if (is_dsp_inst(regs)) {
/* Enable DSP mode, and restart instruction. */
regs->sr |= SR_DSP;
/* Save DSP mode */
current->thread.dsp_status.status |= SR_DSP;
return ;
}
#endif
error_code = lookup_exception_vector();
local_irq_enable();
force_sig(SIGILL);
die_if_no_fixup("reserved instruction" , regs, error_code);
}
#ifdef CONFIG_SH_FPU_EMU
static int emulate_branch(unsigned short inst, struct pt_regs *regs)
{
/*
* bfs : 8 fxx : PC + = d * 2 + 4 ;
* bts : 8 dxx : PC + = d * 2 + 4 ;
* bra : axxx : PC + = D * 2 + 4 ;
* bsr : bxxx : PC + = D * 2 + 4 after PR = PC + 4 ;
* braf : 0 x23 : PC + = Rn * 2 + 4 ;
* bsrf : 0 x03 : PC + = Rn * 2 + 4 after PR = PC + 4 ;
* jmp : 4 x2b : PC = Rn ;
* jsr : 4 x0b : PC = Rn after PR = PC + 4 ;
* rts : 000 b : PC = PR ;
*/
if (((inst & 0 xf000) == 0 xb000) || /* bsr */
((inst & 0 xf0ff) == 0 x0003) || /* bsrf */
((inst & 0 xf0ff) == 0 x400b)) /* jsr */
regs->pr = regs->pc + 4 ;
if ((inst & 0 xfd00) == 0 x8d00) { /* bfs, bts */
regs->pc += SH_PC_8BIT_OFFSET(inst);
return 0 ;
}
if ((inst & 0 xe000) == 0 xa000) { /* bra, bsr */
regs->pc += SH_PC_12BIT_OFFSET(inst);
return 0 ;
}
if ((inst & 0 xf0df) == 0 x0003) { /* braf, bsrf */
regs->pc += regs->regs[(inst & 0 x0f00) >> 8 ] + 4 ;
return 0 ;
}
if ((inst & 0 xf0df) == 0 x400b) { /* jmp, jsr */
regs->pc = regs->regs[(inst & 0 x0f00) >> 8 ];
return 0 ;
}
if ((inst & 0 xffff) == 0 x000b) { /* rts */
regs->pc = regs->pr;
return 0 ;
}
return 1 ;
}
#endif
asmlinkage void do_illegal_slot_inst(void )
{
struct pt_regs *regs = current_pt_regs();
unsigned long inst;
if (kprobe_handle_illslot(regs->pc) == 0 )
return ;
#ifdef CONFIG_SH_FPU_EMU
get_user(inst, (unsigned short __user *)regs->pc + 1 );
if (!do_fpu_inst(inst, regs)) {
get_user(inst, (unsigned short __user *)regs->pc);
if (!emulate_branch(inst, regs))
return ;
/* fault in branch.*/
}
/* not a FPU inst. */
#endif
inst = lookup_exception_vector();
local_irq_enable();
force_sig(SIGILL);
die_if_no_fixup("illegal slot instruction" , regs, inst);
}
asmlinkage void do_exception_error(void )
{
long ex;
ex = lookup_exception_vector();
die_if_kernel("exception" , current_pt_regs(), ex);
}
void per_cpu_trap_init(void )
{
extern void *vbr_base;
/* NOTE: The VBR value should be at P1
( or P2 , virtural " fixed " address space ) .
It's definitely should not in physical address. */
asm volatile ("ldc %0, vbr"
: /* no output */
: "r" (&vbr_base)
: "memory" );
/* disable exception blocking now when the vbr has been setup */
clear_bl_bit();
}
void *set_exception_table_vec(unsigned int vec, void *handler)
{
extern void *exception_handling_table[];
void *old_handler;
old_handler = exception_handling_table[vec];
exception_handling_table[vec] = handler;
return old_handler;
}
void __init trap_init(void )
{
set_exception_table_vec(TRAP_RESERVED_INST, do_reserved_inst);
set_exception_table_vec(TRAP_ILLEGAL_SLOT_INST, do_illegal_slot_inst);
#if defined (CONFIG_CPU_SH4) && !defined (CONFIG_SH_FPU) || \
defined (CONFIG_SH_FPU_EMU)
/*
* For SH - 4 lacking an FPU , treat floating point instructions as
* reserved . They ' ll be handled in the math - emu case , or faulted on
* otherwise .
*/
set_exception_table_evt(0 x800, do_reserved_inst);
set_exception_table_evt(0 x820, do_illegal_slot_inst);
#elif defined (CONFIG_SH_FPU)
set_exception_table_evt(0 x800, fpu_state_restore_trap_handler);
set_exception_table_evt(0 x820, fpu_state_restore_trap_handler);
#endif
#ifdef CONFIG_CPU_SH2
set_exception_table_vec(TRAP_ADDRESS_ERROR, address_error_trap_handler);
#endif
#ifdef CONFIG_CPU_SH2A
set_exception_table_vec(TRAP_DIVZERO_ERROR, do_divide_error);
set_exception_table_vec(TRAP_DIVOVF_ERROR, do_divide_error);
#ifdef CONFIG_SH_FPU
set_exception_table_vec(TRAP_FPU_ERROR, fpu_error_trap_handler);
#endif
#endif
#ifdef TRAP_UBC
set_exception_table_vec(TRAP_UBC, breakpoint_trap_handler);
#endif
}
Messung V0.5 in Prozent C=95 H=93 G=93
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-10-11)
¤
*© Formatika GbR, Deutschland