Files
2024-06-17 20:18:01 +08:00

459 lines
9.5 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020 Loongson Technology Co., Ltd.
*
* Handle unaligned accesses by emulation.
*
* This file contains exception handler for address error exception with the
* special capability to execute faulting instructions in software. The
* handler does not try to handle the case when the program counter points
* to an address not aligned to a word boundary.
*
*/
#include <linux/context_tracking.h>
#include <linux/entry-common.h>
#include <linux/mm.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/debugfs.h>
#include <linux/perf_event.h>
#include <linux/uaccess.h>
#include <asm/asm.h>
#include <asm/branch.h>
#include <asm/byteorder.h>
#include <asm/debug.h>
#include <asm/fpu.h>
#include <asm/inst.h>
#include "access-helper.h"
extern unsigned long unaligned_read(void *addr, void *value, unsigned long n, bool symbol);
extern unsigned long unaligned_write(void *addr, unsigned long value, unsigned long n);
enum {
UNALIGNED_ACTION_QUIET,
UNALIGNED_ACTION_SIGNAL,
UNALIGNED_ACTION_SHOW,
};
#ifdef CONFIG_DEBUG_FS
static u32 unaligned_instructions;
static u32 unaligned_action;
#else
#define unaligned_action UNALIGNED_ACTION_QUIET
#endif
extern void show_registers(struct pt_regs *regs);
static inline void write_fpr(unsigned int fd, unsigned long value)
{
#define WRITE_FPR(fd, value) \
{ \
__asm__ __volatile__( \
"movgr2fr.d $f%1, %0\n\t" \
:: "r"(value), "i"(fd)); \
}
switch (fd) {
case 0:
WRITE_FPR(0, value);
break;
case 1:
WRITE_FPR(1, value);
break;
case 2:
WRITE_FPR(2, value);
break;
case 3:
WRITE_FPR(3, value);
break;
case 4:
WRITE_FPR(4, value);
break;
case 5:
WRITE_FPR(5, value);
break;
case 6:
WRITE_FPR(6, value);
break;
case 7:
WRITE_FPR(7, value);
break;
case 8:
WRITE_FPR(8, value);
break;
case 9:
WRITE_FPR(9, value);
break;
case 10:
WRITE_FPR(10, value);
break;
case 11:
WRITE_FPR(11, value);
break;
case 12:
WRITE_FPR(12, value);
break;
case 13:
WRITE_FPR(13, value);
break;
case 14:
WRITE_FPR(14, value);
break;
case 15:
WRITE_FPR(15, value);
break;
case 16:
WRITE_FPR(16, value);
break;
case 17:
WRITE_FPR(17, value);
break;
case 18:
WRITE_FPR(18, value);
break;
case 19:
WRITE_FPR(19, value);
break;
case 20:
WRITE_FPR(20, value);
break;
case 21:
WRITE_FPR(21, value);
break;
case 22:
WRITE_FPR(22, value);
break;
case 23:
WRITE_FPR(23, value);
break;
case 24:
WRITE_FPR(24, value);
break;
case 25:
WRITE_FPR(25, value);
break;
case 26:
WRITE_FPR(26, value);
break;
case 27:
WRITE_FPR(27, value);
break;
case 28:
WRITE_FPR(28, value);
break;
case 29:
WRITE_FPR(29, value);
break;
case 30:
WRITE_FPR(30, value);
break;
case 31:
WRITE_FPR(31, value);
break;
default:
panic("unexpected fd '%d'", fd);
}
#undef WRITE_FPR
}
static inline unsigned long read_fpr(unsigned int fd)
{
#define READ_FPR(fd, __value) \
{ \
__asm__ __volatile__( \
"movfr2gr.d\t%0, $f%1\n\t" \
: "=r"(__value) : "i"(fd)); \
}
unsigned long __value;
switch (fd) {
case 0:
READ_FPR(0, __value);
break;
case 1:
READ_FPR(1, __value);
break;
case 2:
READ_FPR(2, __value);
break;
case 3:
READ_FPR(3, __value);
break;
case 4:
READ_FPR(4, __value);
break;
case 5:
READ_FPR(5, __value);
break;
case 6:
READ_FPR(6, __value);
break;
case 7:
READ_FPR(7, __value);
break;
case 8:
READ_FPR(8, __value);
break;
case 9:
READ_FPR(9, __value);
break;
case 10:
READ_FPR(10, __value);
break;
case 11:
READ_FPR(11, __value);
break;
case 12:
READ_FPR(12, __value);
break;
case 13:
READ_FPR(13, __value);
break;
case 14:
READ_FPR(14, __value);
break;
case 15:
READ_FPR(15, __value);
break;
case 16:
READ_FPR(16, __value);
break;
case 17:
READ_FPR(17, __value);
break;
case 18:
READ_FPR(18, __value);
break;
case 19:
READ_FPR(19, __value);
break;
case 20:
READ_FPR(20, __value);
break;
case 21:
READ_FPR(21, __value);
break;
case 22:
READ_FPR(22, __value);
break;
case 23:
READ_FPR(23, __value);
break;
case 24:
READ_FPR(24, __value);
break;
case 25:
READ_FPR(25, __value);
break;
case 26:
READ_FPR(26, __value);
break;
case 27:
READ_FPR(27, __value);
break;
case 28:
READ_FPR(28, __value);
break;
case 29:
READ_FPR(29, __value);
break;
case 30:
READ_FPR(30, __value);
break;
case 31:
READ_FPR(31, __value);
break;
default:
panic("unexpected fd '%d'", fd);
}
#undef READ_FPR
return __value;
}
static void emulate_load_store_insn(struct pt_regs *regs, void __user *addr, unsigned int *pc)
{
bool user = user_mode(regs);
unsigned int res;
unsigned long value = 0;
unsigned long origpc;
unsigned long origra;
union loongarch_instruction insn;
origpc = (unsigned long)pc;
origra = regs->regs[1];
perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, 0);
/*
* This load never faults.
*/
__get_inst(&insn.word, pc, user);
if (user && !access_ok(addr, 8))
goto sigbus;
if (insn.reg2i12_format.opcode == ldd_op ||
insn.reg2i14_format.opcode == ldptrd_op ||
insn.reg3_format.opcode == ldxd_op) {
res = unaligned_read(addr, &value, 8, 1);
if (res)
goto fault;
regs->regs[insn.reg2i12_format.rd] = value;
} else if (insn.reg2i12_format.opcode == ldw_op ||
insn.reg2i14_format.opcode == ldptrw_op ||
insn.reg3_format.opcode == ldxw_op) {
res = unaligned_read(addr, &value, 4, 1);
if (res)
goto fault;
regs->regs[insn.reg2i12_format.rd] = value;
} else if (insn.reg2i12_format.opcode == ldwu_op ||
insn.reg3_format.opcode == ldxwu_op) {
res = unaligned_read(addr, &value, 4, 0);
if (res)
goto fault;
regs->regs[insn.reg2i12_format.rd] = value;
} else if (insn.reg2i12_format.opcode == ldh_op ||
insn.reg3_format.opcode == ldxh_op) {
res = unaligned_read(addr, &value, 2, 1);
if (res)
goto fault;
regs->regs[insn.reg2i12_format.rd] = value;
} else if (insn.reg2i12_format.opcode == ldhu_op ||
insn.reg3_format.opcode == ldxhu_op) {
res = unaligned_read(addr, &value, 2, 0);
if (res)
goto fault;
regs->regs[insn.reg2i12_format.rd] = value;
} else if (insn.reg2i12_format.opcode == std_op ||
insn.reg2i14_format.opcode == stptrd_op ||
insn.reg3_format.opcode == stxd_op) {
value = regs->regs[insn.reg2i12_format.rd];
res = unaligned_write(addr, value, 8);
if (res)
goto fault;
} else if (insn.reg2i12_format.opcode == stw_op ||
insn.reg2i14_format.opcode == stptrw_op ||
insn.reg3_format.opcode == stxw_op) {
value = regs->regs[insn.reg2i12_format.rd];
res = unaligned_write(addr, value, 4);
if (res)
goto fault;
} else if (insn.reg2i12_format.opcode == sth_op ||
insn.reg3_format.opcode == stxh_op) {
value = regs->regs[insn.reg2i12_format.rd];
res = unaligned_write(addr, value, 2);
if (res)
goto fault;
} else if (insn.reg2i12_format.opcode == fldd_op ||
insn.reg3_format.opcode == fldxd_op) {
res = unaligned_read(addr, &value, 8, 1);
if (res)
goto fault;
write_fpr(insn.reg2i12_format.rd, value);
} else if (insn.reg2i12_format.opcode == flds_op ||
insn.reg3_format.opcode == fldxs_op) {
res = unaligned_read(addr, &value, 4, 1);
if (res)
goto fault;
write_fpr(insn.reg2i12_format.rd, value);
} else if (insn.reg2i12_format.opcode == fstd_op ||
insn.reg3_format.opcode == fstxd_op) {
value = read_fpr(insn.reg2i12_format.rd);
res = unaligned_write(addr, value, 8);
if (res)
goto fault;
} else if (insn.reg2i12_format.opcode == fsts_op ||
insn.reg3_format.opcode == fstxs_op) {
value = read_fpr(insn.reg2i12_format.rd);
res = unaligned_write(addr, value, 4);
if (res)
goto fault;
} else
goto sigbus;
#ifdef CONFIG_DEBUG_FS
unaligned_instructions++;
#endif
compute_return_era(regs);
return;
fault:
/* roll back jump/branch */
regs->csr_era = origpc;
regs->regs[1] = origra;
/* Did we have an exception handler installed? */
if (fixup_exception(regs))
return;
die_if_kernel("Unhandled kernel unaligned access", regs);
force_sig(SIGSEGV);
return;
sigbus:
die_if_kernel("Unhandled kernel unaligned access", regs);
force_sig(SIGBUS);
return;
}
asmlinkage void noinstr do_ade(struct pt_regs *regs)
{
irqentry_state_t state = irqentry_enter(regs);
if (unaligned_action == UNALIGNED_ACTION_SHOW)
show_registers(regs);
die_if_kernel("Kernel ade access", regs);
force_sig_fault(SIGBUS, BUS_ADRERR, (void __user *)regs->csr_badvaddr);
irqentry_exit(regs, state);
}
asmlinkage void noinstr do_ale(struct pt_regs *regs)
{
unsigned int *pc;
irqentry_state_t state = irqentry_enter(regs);
perf_sw_event(PERF_COUNT_SW_ALIGNMENT_FAULTS,
1, regs, regs->csr_badvaddr);
/*
* Did we catch a fault trying to load an instruction?
*/
if (regs->csr_badvaddr == regs->csr_era)
goto sigbus;
if (user_mode(regs) && !test_thread_flag(TIF_FIXADE))
goto sigbus;
if (unaligned_action == UNALIGNED_ACTION_SIGNAL)
goto sigbus;
if (unaligned_action == UNALIGNED_ACTION_SHOW)
show_registers(regs);
pc = (unsigned int *)exception_era(regs);
emulate_load_store_insn(regs, (void __user *)regs->csr_badvaddr, pc);
goto out;
sigbus:
die_if_kernel("Kernel unaligned instruction access", regs);
force_sig_fault(SIGBUS, BUS_ADRALN, (void __user *)regs->csr_badvaddr);
out:
irqentry_exit(regs, state);
}
#ifdef CONFIG_DEBUG_FS
static int __init debugfs_unaligned(void)
{
debugfs_create_u32("unaligned_instructions", S_IRUGO,
loongarch_debugfs_dir, &unaligned_instructions);
debugfs_create_u32("unaligned_action", S_IRUGO | S_IWUSR,
loongarch_debugfs_dir, &unaligned_action);
return 0;
}
arch_initcall(debugfs_unaligned);
#endif