// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2005-2017 Andes Technology Corporation #include #include #include #include #define DEBUG(enable, tagged, ...) \ do{ \ if (enable) { \ if (tagged) \ pr_warn("[ %30s() ] ", __func__); \ pr_warn(__VA_ARGS__); \ } \ } while (0) #define RT(inst) (((inst) >> 20) & 0x1FUL) #define RA(inst) (((inst) >> 15) & 0x1FUL) #define RB(inst) (((inst) >> 10) & 0x1FUL) #define SV(inst) (((inst) >> 8) & 0x3UL) #define IMM(inst) (((inst) >> 0) & 0x7FFFUL) #define RA3(inst) (((inst) >> 3) & 0x7UL) #define RT3(inst) (((inst) >> 6) & 0x7UL) #define IMM3U(inst) (((inst) >> 0) & 0x7UL) #define RA5(inst) (((inst) >> 0) & 0x1FUL) #define RT4(inst) (((inst) >> 5) & 0xFUL) #define GET_IMMSVAL(imm_value) \ (((imm_value >> 14) & 0x1) ? (imm_value - 0x8000) : imm_value) #define __get8_data(val,addr,err) \ __asm__( \ "1: lbi.bi %1, [%2], #1\n" \ "2:\n" \ " .pushsection .text.fixup,\"ax\"\n" \ " .align 2\n" \ "3: movi %0, #1\n" \ " j 2b\n" \ " .popsection\n" \ " .pushsection __ex_table,\"a\"\n" \ " .align 3\n" \ " .long 1b, 3b\n" \ " .popsection\n" \ : "=r" (err), "=&r" (val), "=r" (addr) \ : "0" (err), "2" (addr)) #define get16_data(addr, val_ptr) \ do { \ unsigned int err = 0, v, a = addr; \ __get8_data(v,a,err); \ *val_ptr = v << 0; \ __get8_data(v,a,err); \ *val_ptr |= v << 8; \ if (err) \ goto fault; \ *val_ptr = le16_to_cpu(*val_ptr); \ } while(0) #define get32_data(addr, val_ptr) \ do { \ unsigned int err = 0, v, a = addr; \ __get8_data(v,a,err); \ *val_ptr = v << 0; \ __get8_data(v,a,err); \ *val_ptr |= v << 8; \ __get8_data(v,a,err); \ *val_ptr |= v << 16; \ __get8_data(v,a,err); \ *val_ptr |= v << 24; \ if (err) \ goto fault; \ *val_ptr = le32_to_cpu(*val_ptr); \ } while(0) #define get_data(addr, val_ptr, len) \ if (len == 2) \ get16_data(addr, val_ptr); \ else \ get32_data(addr, val_ptr); #define set16_data(addr, val) \ do { \ unsigned int err = 0, *ptr = addr ; \ val = le32_to_cpu(val); \ __asm__( \ "1: sbi.bi %2, [%1], #1\n" \ " srli %2, %2, #8\n" \ "2: sbi %2, [%1]\n" \ "3:\n" \ " .pushsection .text.fixup,\"ax\"\n" \ " .align 2\n" \ "4: movi %0, #1\n" \ " j 3b\n" \ " .popsection\n" \ " .pushsection __ex_table,\"a\"\n" \ " .align 3\n" \ " .long 1b, 4b\n" \ " .long 2b, 4b\n" \ " .popsection\n" \ : "=r" (err), "+r" (ptr), "+r" (val) \ : "0" (err) \ ); \ if (err) \ goto fault; \ } while(0) #define set32_data(addr, val) \ do { \ unsigned int err = 0, *ptr = addr ; \ val = le32_to_cpu(val); \ __asm__( \ "1: sbi.bi %2, [%1], #1\n" \ " srli %2, %2, #8\n" \ "2: sbi.bi %2, [%1], #1\n" \ " srli %2, %2, #8\n" \ "3: sbi.bi %2, [%1], #1\n" \ " srli %2, %2, #8\n" \ "4: sbi %2, [%1]\n" \ "5:\n" \ " .pushsection .text.fixup,\"ax\"\n" \ " .align 2\n" \ "6: movi %0, #1\n" \ " j 5b\n" \ " .popsection\n" \ " .pushsection __ex_table,\"a\"\n" \ " .align 3\n" \ " .long 1b, 6b\n" \ " .long 2b, 6b\n" \ " .long 3b, 6b\n" \ " .long 4b, 6b\n" \ " .popsection\n" \ : "=r" (err), "+r" (ptr), "+r" (val) \ : "0" (err) \ ); \ if (err) \ goto fault; \ } while(0) #define set_data(addr, val, len) \ if (len == 2) \ set16_data(addr, val); \ else \ set32_data(addr, val); #define NDS32_16BIT_INSTRUCTION 0x80000000 extern pte_t va_present(struct mm_struct *mm, unsigned long addr); extern pte_t va_kernel_present(unsigned long addr); extern int va_readable(struct pt_regs *regs, unsigned long addr); extern int va_writable(struct pt_regs *regs, unsigned long addr); int unalign_access_mode = 0, unalign_access_debug = 0; static inline unsigned long *idx_to_addr(struct pt_regs *regs, int idx) { /* this should be consistent with ptrace.h */ if (idx >= 0 && idx <= 25) /* R0-R25 */ return ®s->uregs[0] + idx; else if (idx >= 28 && idx <= 30) /* FP, GP, LP */ return ®s->fp + (idx - 28); else if (idx == 31) /* SP */ return ®s->sp; else return NULL; /* cause a segfault */ } static inline unsigned long get_inst(unsigned long addr) { return be32_to_cpu(get_unaligned((u32 *) addr)); } static inline unsigned long sign_extend(unsigned long val, int len) { unsigned long ret = 0; unsigned char *s, *t; int i = 0; val = cpu_to_le32(val); s = (void *)&val; t = (void *)&ret; while (i++ < len) *t++ = *s++; if (((*(t - 1)) & 0x80) && (i < 4)) { while (i++ <= 4) *t++ = 0xff; } return le32_to_cpu(ret); } static inline int do_16(unsigned long inst, struct pt_regs *regs) { int imm, regular, load, len, addr_mode, idx_mode; unsigned long unaligned_addr, target_val, source_idx, target_idx, shift = 0; switch ((inst >> 9) & 0x3F) { case 0x12: /* LHI333 */ imm = 1; regular = 1; load = 1; len = 2; addr_mode = 3; idx_mode = 3; break; case 0x10: /* LWI333 */ imm = 1; regular = 1; load = 1; len = 4; addr_mode = 3; idx_mode = 3; break; case 0x11: /* LWI333.bi */ imm = 1; regular = 0; load = 1; len = 4; addr_mode = 3; idx_mode = 3; break; case 0x1A: /* LWI450 */ imm = 0; regular = 1; load = 1; len = 4; addr_mode = 5; idx_mode = 4; break; case 0x16: /* SHI333 */ imm = 1; regular = 1; load = 0; len = 2; addr_mode = 3; idx_mode = 3; break; case 0x14: /* SWI333 */ imm = 1; regular = 1; load = 0; len = 4; addr_mode = 3; idx_mode = 3; break; case 0x15: /* SWI333.bi */ imm = 1; regular = 0; load = 0; len = 4; addr_mode = 3; idx_mode = 3; break; case 0x1B: /* SWI450 */ imm = 0; regular = 1; load = 0; len = 4; addr_mode = 5; idx_mode = 4; break; default: return -EFAULT; } if (addr_mode == 3) { unaligned_addr = *idx_to_addr(regs, RA3(inst)); source_idx = RA3(inst); } else { unaligned_addr = *idx_to_addr(regs, RA5(inst)); source_idx = RA5(inst); } if (idx_mode == 3) target_idx = RT3(inst); else target_idx = RT4(inst); if (imm) shift = IMM3U(inst) * len; if (regular) unaligned_addr += shift; if (load) { if (!access_ok(VERIFY_READ, (void *)unaligned_addr, len)) return -EACCES; get_data(unaligned_addr, &target_val, len); *idx_to_addr(regs, target_idx) = target_val; } else { if (!access_ok(VERIFY_WRITE, (void *)unaligned_addr, len)) return -EACCES; target_val = *idx_to_addr(regs, target_idx); set_data((void *)unaligned_addr, target_val, len); } if (!regular) *idx_to_addr(regs, source_idx) = unaligned_addr + shift; regs->ipc += 2; return 0; fault: return -EACCES; } static inline int do_32(unsigned long inst, struct pt_regs *regs) { int imm, regular, load, len, sign_ext; unsigned long unaligned_addr, target_val, shift; unaligned_addr = *idx_to_addr(regs, RA(inst)); switch ((inst >> 25) << 1) { case 0x02: /* LHI */ imm = 1; regular = 1; load = 1; len = 2; sign_ext = 0; break; case 0x0A: /* LHI.bi */ imm = 1; regular = 0; load = 1; len = 2; sign_ext = 0; break; case 0x22: /* LHSI */ imm = 1; regular = 1; load = 1; len = 2; sign_ext = 1; break; case 0x2A: /* LHSI.bi */ imm = 1; regular = 0; load = 1; len = 2; sign_ext = 1; break; case 0x04: /* LWI */ imm = 1; regular = 1; load = 1; len = 4; sign_ext = 0; break; case 0x0C: /* LWI.bi */ imm = 1; regular = 0; load = 1; len = 4; sign_ext = 0; break; case 0x12: /* SHI */ imm = 1; regular = 1; load = 0; len = 2; sign_ext = 0; break; case 0x1A: /* SHI.bi */ imm = 1; regular = 0; load = 0; len = 2; sign_ext = 0; break; case 0x14: /* SWI */ imm = 1; regular = 1; load = 0; len = 4; sign_ext = 0; break; case 0x1C: /* SWI.bi */ imm = 1; regular = 0; load = 0; len = 4; sign_ext = 0; break; default: switch (inst & 0xff) { case 0x01: /* LH */ imm = 0; regular = 1; load = 1; len = 2; sign_ext = 0; break; case 0x05: /* LH.bi */ imm = 0; regular = 0; load = 1; len = 2; sign_ext = 0; break; case 0x11: /* LHS */ imm = 0; regular = 1; load = 1; len = 2; sign_ext = 1; break; case 0x15: /* LHS.bi */ imm = 0; regular = 0; load = 1; len = 2; sign_ext = 1; break; case 0x02: /* LW */ imm = 0; regular = 1; load = 1; len = 4; sign_ext = 0; break; case 0x06: /* LW.bi */ imm = 0; regular = 0; load = 1; len = 4; sign_ext = 0; break; case 0x09: /* SH */ imm = 0; regular = 1; load = 0; len = 2; sign_ext = 0; break; case 0x0D: /* SH.bi */ imm = 0; regular = 0; load = 0; len = 2; sign_ext = 0; break; case 0x0A: /* SW */ imm = 0; regular = 1; load = 0; len = 4; sign_ext = 0; break; case 0x0E: /* SW.bi */ imm = 0; regular = 0; load = 0; len = 4; sign_ext = 0; break; default: return -EFAULT; } } if (imm) shift = GET_IMMSVAL(IMM(inst)) * len; else shift = *idx_to_addr(regs, RB(inst)) << SV(inst); if (regular) unaligned_addr += shift; if (load) { if (!access_ok(VERIFY_READ, (void *)unaligned_addr, len)) return -EACCES; get_data(unaligned_addr, &target_val, len); if (sign_ext) *idx_to_addr(regs, RT(inst)) = sign_extend(target_val, len); else *idx_to_addr(regs, RT(inst)) = target_val; } else { if (!access_ok(VERIFY_WRITE, (void *)unaligned_addr, len)) return -EACCES; target_val = *idx_to_addr(regs, RT(inst)); set_data((void *)unaligned_addr, target_val, len); } if (!regular) *idx_to_addr(regs, RA(inst)) = unaligned_addr + shift; regs->ipc += 4; return 0; fault: return -EACCES; } int do_unaligned_access(unsigned long addr, struct pt_regs *regs) { unsigned long inst; int ret = -EFAULT; mm_segment_t seg = get_fs(); inst = get_inst(regs->ipc); DEBUG((unalign_access_debug > 0), 1, "Faulting addr: 0x%08lx, pc: 0x%08lx [inst: 0x%08lx ]\n", addr, regs->ipc, inst); set_fs(USER_DS); if (inst & NDS32_16BIT_INSTRUCTION) ret = do_16((inst >> 16) & 0xffff, regs); else ret = do_32(inst, regs); set_fs(seg); return ret; } #ifdef CONFIG_PROC_FS static struct ctl_table alignment_tbl[3] = { { .procname = "enable", .data = &unalign_access_mode, .maxlen = sizeof(unalign_access_mode), .mode = 0666, .proc_handler = &proc_dointvec } , { .procname = "debug_info", .data = &unalign_access_debug, .maxlen = sizeof(unalign_access_debug), .mode = 0644, .proc_handler = &proc_dointvec } , {} }; static struct ctl_table nds32_sysctl_table[2] = { { .procname = "unaligned_access", .mode = 0555, .child = alignment_tbl}, {} }; static struct ctl_path nds32_path[2] = { {.procname = "nds32"}, {} }; /* * Initialize nds32 alignment-correction interface */ static int __init nds32_sysctl_init(void) { register_sysctl_paths(nds32_path, nds32_sysctl_table); return 0; } __initcall(nds32_sysctl_init); #endif /* CONFIG_PROC_FS */