| // SPDX-License-Identifier: GPL-2.0 |
| // Copyright (C) 2005-2017 Andes Technology Corporation |
| |
| #include <linux/proc_fs.h> |
| #include <linux/uaccess.h> |
| #include <linux/sysctl.h> |
| #include <asm/unaligned.h> |
| |
| #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 */ |