// SPDX-License-Identifier: GPL-2.0 /* * Handle unaligned accesses by emulation. * * Copyright (C) 2020-2022 Loongson Technology Corporation Limited * * Derived from MIPS: * Copyright (C) 1996, 1998, 1999, 2002 by Ralf Baechle * Copyright (C) 1999 Silicon Graphics, Inc. * Copyright (C) 2014 Imagination Technologies Ltd. */ #include <linux/mm.h> #include <linux/sched.h> #include <linux/signal.h> #include <linux/debugfs.h> #include <linux/perf_event.h> #include <asm/asm.h> #include <asm/branch.h> #include <asm/fpu.h> #include <asm/inst.h> #include "access-helper.h" #ifdef CONFIG_DEBUG_FS static u32 unaligned_instructions_user; static u32 unaligned_instructions_kernel; #endif static inline unsigned long read_fpr(unsigned int idx) { #define READ_FPR(idx, __value) \ __asm__ __volatile__("movfr2gr.d %0, $f"#idx"\n\t" : "=r"(__value)); unsigned long __value; switch (idx) { 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 idx '%d'", idx); } #undef READ_FPR return __value; } static inline void write_fpr(unsigned int idx, unsigned long value) { #define WRITE_FPR(idx, value) \ __asm__ __volatile__("movgr2fr.d $f"#idx", %0\n\t" :: "r"(value)); switch (idx) { 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 idx '%d'", idx); } #undef WRITE_FPR } void emulate_load_store_insn(struct pt_regs *regs, void __user *addr, unsigned int *pc) { bool fp = false; bool sign, write; bool user = user_mode(regs); unsigned int res, size = 0; unsigned long value = 0; union loongarch_instruction insn; perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, 0); __get_inst(&insn.word, pc, user); switch (insn.reg2i12_format.opcode) { case ldh_op: size = 2; sign = true; write = false; break; case ldhu_op: size = 2; sign = false; write = false; break; case sth_op: size = 2; sign = true; write = true; break; case ldw_op: size = 4; sign = true; write = false; break; case ldwu_op: size = 4; sign = false; write = false; break; case stw_op: size = 4; sign = true; write = true; break; case ldd_op: size = 8; sign = true; write = false; break; case std_op: size = 8; sign = true; write = true; break; case flds_op: size = 4; fp = true; sign = true; write = false; break; case fsts_op: size = 4; fp = true; sign = true; write = true; break; case fldd_op: size = 8; fp = true; sign = true; write = false; break; case fstd_op: size = 8; fp = true; sign = true; write = true; break; } switch (insn.reg2i14_format.opcode) { case ldptrw_op: size = 4; sign = true; write = false; break; case stptrw_op: size = 4; sign = true; write = true; break; case ldptrd_op: size = 8; sign = true; write = false; break; case stptrd_op: size = 8; sign = true; write = true; break; } switch (insn.reg3_format.opcode) { case ldxh_op: size = 2; sign = true; write = false; break; case ldxhu_op: size = 2; sign = false; write = false; break; case stxh_op: size = 2; sign = true; write = true; break; case ldxw_op: size = 4; sign = true; write = false; break; case ldxwu_op: size = 4; sign = false; write = false; break; case stxw_op: size = 4; sign = true; write = true; break; case ldxd_op: size = 8; sign = true; write = false; break; case stxd_op: size = 8; sign = true; write = true; break; case fldxs_op: size = 4; fp = true; sign = true; write = false; break; case fstxs_op: size = 4; fp = true; sign = true; write = true; break; case fldxd_op: size = 8; fp = true; sign = true; write = false; break; case fstxd_op: size = 8; fp = true; sign = true; write = true; break; } if (!size) goto sigbus; if (user && !access_ok(addr, size)) goto sigbus; if (!write) { res = unaligned_read(addr, &value, size, sign); if (res) goto fault; /* Rd is the same field in any formats */ if (!fp) regs->regs[insn.reg3_format.rd] = value; else { if (is_fpu_owner()) write_fpr(insn.reg3_format.rd, value); else set_fpr64(¤t->thread.fpu.fpr[insn.reg3_format.rd], 0, value); } } else { /* Rd is the same field in any formats */ if (!fp) value = regs->regs[insn.reg3_format.rd]; else { if (is_fpu_owner()) value = read_fpr(insn.reg3_format.rd); else value = get_fpr64(¤t->thread.fpu.fpr[insn.reg3_format.rd], 0); } res = unaligned_write(addr, value, size); if (res) goto fault; } #ifdef CONFIG_DEBUG_FS if (user) unaligned_instructions_user++; else unaligned_instructions_kernel++; #endif compute_return_era(regs); return; fault: /* 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; } #ifdef CONFIG_DEBUG_FS static int __init debugfs_unaligned(void) { struct dentry *d; d = debugfs_create_dir("loongarch", NULL); debugfs_create_u32("unaligned_instructions_user", S_IRUGO, d, &unaligned_instructions_user); debugfs_create_u32("unaligned_instructions_kernel", S_IRUGO, d, &unaligned_instructions_kernel); return 0; } arch_initcall(debugfs_unaligned); #endif