| // 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 |