Adrian Bunk | 88278ca | 2008-05-19 16:53:02 -0700 | [diff] [blame] | 1 | /* |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 2 | * muldiv.c: Hardware multiply/division illegal instruction trap |
| 3 | * for sun4c/sun4 (which do not have those instructions) |
| 4 | * |
| 5 | * Copyright (C) 1996 Jakub Jelinek (jj@sunsite.mff.cuni.cz) |
| 6 | * Copyright (C) 1996 David S. Miller (davem@caip.rutgers.edu) |
| 7 | * |
| 8 | * 2004-12-25 Krzysztof Helt (krzysztof.h1@wp.pl) |
| 9 | * - fixed registers constrains in inline assembly declarations |
| 10 | */ |
| 11 | |
| 12 | #include <linux/kernel.h> |
| 13 | #include <linux/sched.h> |
| 14 | #include <linux/mm.h> |
| 15 | #include <asm/ptrace.h> |
| 16 | #include <asm/processor.h> |
| 17 | #include <asm/system.h> |
| 18 | #include <asm/uaccess.h> |
| 19 | |
Sam Ravnborg | 8d74e32 | 2008-12-08 01:04:59 -0800 | [diff] [blame] | 20 | #include "kernel.h" |
| 21 | |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 22 | /* #define DEBUG_MULDIV */ |
| 23 | |
| 24 | static inline int has_imm13(int insn) |
| 25 | { |
| 26 | return (insn & 0x2000); |
| 27 | } |
| 28 | |
| 29 | static inline int is_foocc(int insn) |
| 30 | { |
| 31 | return (insn & 0x800000); |
| 32 | } |
| 33 | |
| 34 | static inline int sign_extend_imm13(int imm) |
| 35 | { |
| 36 | return imm << 19 >> 19; |
| 37 | } |
| 38 | |
| 39 | static inline void advance(struct pt_regs *regs) |
| 40 | { |
| 41 | regs->pc = regs->npc; |
| 42 | regs->npc += 4; |
| 43 | } |
| 44 | |
| 45 | static inline void maybe_flush_windows(unsigned int rs1, unsigned int rs2, |
| 46 | unsigned int rd) |
| 47 | { |
| 48 | if(rs2 >= 16 || rs1 >= 16 || rd >= 16) { |
| 49 | /* Wheee... */ |
| 50 | __asm__ __volatile__("save %sp, -0x40, %sp\n\t" |
| 51 | "save %sp, -0x40, %sp\n\t" |
| 52 | "save %sp, -0x40, %sp\n\t" |
| 53 | "save %sp, -0x40, %sp\n\t" |
| 54 | "save %sp, -0x40, %sp\n\t" |
| 55 | "save %sp, -0x40, %sp\n\t" |
| 56 | "save %sp, -0x40, %sp\n\t" |
| 57 | "restore; restore; restore; restore;\n\t" |
| 58 | "restore; restore; restore;\n\t"); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | #define fetch_reg(reg, regs) ({ \ |
Sam Ravnborg | 4d7b92a | 2009-01-02 19:32:59 -0800 | [diff] [blame] | 63 | struct reg_window32 __user *win; \ |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 64 | register unsigned long ret; \ |
| 65 | \ |
| 66 | if (!(reg)) ret = 0; \ |
| 67 | else if ((reg) < 16) { \ |
| 68 | ret = regs->u_regs[(reg)]; \ |
| 69 | } else { \ |
| 70 | /* Ho hum, the slightly complicated case. */ \ |
Sam Ravnborg | 4d7b92a | 2009-01-02 19:32:59 -0800 | [diff] [blame] | 71 | win = (struct reg_window32 __user *)regs->u_regs[UREG_FP];\ |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 72 | if (get_user (ret, &win->locals[(reg) - 16])) return -1;\ |
| 73 | } \ |
| 74 | ret; \ |
| 75 | }) |
| 76 | |
| 77 | static inline int |
| 78 | store_reg(unsigned int result, unsigned int reg, struct pt_regs *regs) |
| 79 | { |
Sam Ravnborg | 4d7b92a | 2009-01-02 19:32:59 -0800 | [diff] [blame] | 80 | struct reg_window32 __user *win; |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 81 | |
| 82 | if (!reg) |
| 83 | return 0; |
| 84 | if (reg < 16) { |
| 85 | regs->u_regs[reg] = result; |
| 86 | return 0; |
| 87 | } else { |
| 88 | /* need to use put_user() in this case: */ |
Sam Ravnborg | 4d7b92a | 2009-01-02 19:32:59 -0800 | [diff] [blame] | 89 | win = (struct reg_window32 __user *) regs->u_regs[UREG_FP]; |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 90 | return (put_user(result, &win->locals[reg - 16])); |
| 91 | } |
| 92 | } |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 93 | |
| 94 | /* Should return 0 if mul/div emulation succeeded and SIGILL should |
| 95 | * not be issued. |
| 96 | */ |
| 97 | int do_user_muldiv(struct pt_regs *regs, unsigned long pc) |
| 98 | { |
| 99 | unsigned int insn; |
| 100 | int inst; |
| 101 | unsigned int rs1, rs2, rdv; |
| 102 | |
| 103 | if (!pc) |
| 104 | return -1; /* This happens to often, I think */ |
| 105 | if (get_user (insn, (unsigned int __user *)pc)) |
| 106 | return -1; |
| 107 | if ((insn & 0xc1400000) != 0x80400000) |
| 108 | return -1; |
| 109 | inst = ((insn >> 19) & 0xf); |
| 110 | if ((inst & 0xe) != 10 && (inst & 0xe) != 14) |
| 111 | return -1; |
| 112 | |
| 113 | /* Now we know we have to do something with umul, smul, udiv or sdiv */ |
| 114 | rs1 = (insn >> 14) & 0x1f; |
| 115 | rs2 = insn & 0x1f; |
| 116 | rdv = (insn >> 25) & 0x1f; |
| 117 | if (has_imm13(insn)) { |
| 118 | maybe_flush_windows(rs1, 0, rdv); |
| 119 | rs2 = sign_extend_imm13(insn); |
| 120 | } else { |
| 121 | maybe_flush_windows(rs1, rs2, rdv); |
| 122 | rs2 = fetch_reg(rs2, regs); |
| 123 | } |
| 124 | rs1 = fetch_reg(rs1, regs); |
| 125 | switch (inst) { |
| 126 | case 10: /* umul */ |
| 127 | #ifdef DEBUG_MULDIV |
| 128 | printk ("unsigned muldiv: 0x%x * 0x%x = ", rs1, rs2); |
| 129 | #endif |
| 130 | __asm__ __volatile__ ("\n\t" |
| 131 | "mov %0, %%o0\n\t" |
| 132 | "call .umul\n\t" |
| 133 | " mov %1, %%o1\n\t" |
| 134 | "mov %%o0, %0\n\t" |
| 135 | "mov %%o1, %1\n\t" |
| 136 | : "=r" (rs1), "=r" (rs2) |
| 137 | : "0" (rs1), "1" (rs2) |
| 138 | : "o0", "o1", "o2", "o3", "o4", "o5", "o7", "cc"); |
| 139 | #ifdef DEBUG_MULDIV |
| 140 | printk ("0x%x%08x\n", rs2, rs1); |
| 141 | #endif |
| 142 | if (store_reg(rs1, rdv, regs)) |
| 143 | return -1; |
| 144 | regs->y = rs2; |
| 145 | break; |
| 146 | case 11: /* smul */ |
| 147 | #ifdef DEBUG_MULDIV |
| 148 | printk ("signed muldiv: 0x%x * 0x%x = ", rs1, rs2); |
| 149 | #endif |
| 150 | __asm__ __volatile__ ("\n\t" |
| 151 | "mov %0, %%o0\n\t" |
| 152 | "call .mul\n\t" |
| 153 | " mov %1, %%o1\n\t" |
| 154 | "mov %%o0, %0\n\t" |
| 155 | "mov %%o1, %1\n\t" |
| 156 | : "=r" (rs1), "=r" (rs2) |
| 157 | : "0" (rs1), "1" (rs2) |
| 158 | : "o0", "o1", "o2", "o3", "o4", "o5", "o7", "cc"); |
| 159 | #ifdef DEBUG_MULDIV |
| 160 | printk ("0x%x%08x\n", rs2, rs1); |
| 161 | #endif |
| 162 | if (store_reg(rs1, rdv, regs)) |
| 163 | return -1; |
| 164 | regs->y = rs2; |
| 165 | break; |
| 166 | case 14: /* udiv */ |
| 167 | #ifdef DEBUG_MULDIV |
| 168 | printk ("unsigned muldiv: 0x%x%08x / 0x%x = ", regs->y, rs1, rs2); |
| 169 | #endif |
| 170 | if (!rs2) { |
| 171 | #ifdef DEBUG_MULDIV |
| 172 | printk ("DIVISION BY ZERO\n"); |
| 173 | #endif |
| 174 | handle_hw_divzero (regs, pc, regs->npc, regs->psr); |
| 175 | return 0; |
| 176 | } |
| 177 | __asm__ __volatile__ ("\n\t" |
| 178 | "mov %2, %%o0\n\t" |
| 179 | "mov %0, %%o1\n\t" |
| 180 | "mov %%g0, %%o2\n\t" |
| 181 | "call __udivdi3\n\t" |
| 182 | " mov %1, %%o3\n\t" |
| 183 | "mov %%o1, %0\n\t" |
| 184 | "mov %%o0, %1\n\t" |
| 185 | : "=r" (rs1), "=r" (rs2) |
| 186 | : "r" (regs->y), "0" (rs1), "1" (rs2) |
| 187 | : "o0", "o1", "o2", "o3", "o4", "o5", "o7", |
| 188 | "g1", "g2", "g3", "cc"); |
| 189 | #ifdef DEBUG_MULDIV |
| 190 | printk ("0x%x\n", rs1); |
| 191 | #endif |
| 192 | if (store_reg(rs1, rdv, regs)) |
| 193 | return -1; |
| 194 | break; |
| 195 | case 15: /* sdiv */ |
| 196 | #ifdef DEBUG_MULDIV |
| 197 | printk ("signed muldiv: 0x%x%08x / 0x%x = ", regs->y, rs1, rs2); |
| 198 | #endif |
| 199 | if (!rs2) { |
| 200 | #ifdef DEBUG_MULDIV |
| 201 | printk ("DIVISION BY ZERO\n"); |
| 202 | #endif |
| 203 | handle_hw_divzero (regs, pc, regs->npc, regs->psr); |
| 204 | return 0; |
| 205 | } |
| 206 | __asm__ __volatile__ ("\n\t" |
| 207 | "mov %2, %%o0\n\t" |
| 208 | "mov %0, %%o1\n\t" |
| 209 | "mov %%g0, %%o2\n\t" |
| 210 | "call __divdi3\n\t" |
| 211 | " mov %1, %%o3\n\t" |
| 212 | "mov %%o1, %0\n\t" |
| 213 | "mov %%o0, %1\n\t" |
| 214 | : "=r" (rs1), "=r" (rs2) |
| 215 | : "r" (regs->y), "0" (rs1), "1" (rs2) |
| 216 | : "o0", "o1", "o2", "o3", "o4", "o5", "o7", |
| 217 | "g1", "g2", "g3", "cc"); |
| 218 | #ifdef DEBUG_MULDIV |
| 219 | printk ("0x%x\n", rs1); |
| 220 | #endif |
| 221 | if (store_reg(rs1, rdv, regs)) |
| 222 | return -1; |
| 223 | break; |
| 224 | } |
| 225 | if (is_foocc (insn)) { |
| 226 | regs->psr &= ~PSR_ICC; |
| 227 | if ((inst & 0xe) == 14) { |
| 228 | /* ?div */ |
| 229 | if (rs2) regs->psr |= PSR_V; |
| 230 | } |
| 231 | if (!rs1) regs->psr |= PSR_Z; |
| 232 | if (((int)rs1) < 0) regs->psr |= PSR_N; |
| 233 | #ifdef DEBUG_MULDIV |
| 234 | printk ("psr muldiv: %08x\n", regs->psr); |
| 235 | #endif |
| 236 | } |
| 237 | advance(regs); |
| 238 | return 0; |
| 239 | } |