linux/arch/mips/loongson64/cop2-ex.c
<<
>>
Prefs
   1/*
   2 * This file is subject to the terms and conditions of the GNU General Public
   3 * License.  See the file "COPYING" in the main directory of this archive
   4 * for more details.
   5 *
   6 * Copyright (C) 2014 Lemote Corporation.
   7 *   written by Huacai Chen <chenhc@lemote.com>
   8 *
   9 * based on arch/mips/cavium-octeon/cpu.c
  10 * Copyright (C) 2009 Wind River Systems,
  11 *   written by Ralf Baechle <ralf@linux-mips.org>
  12 */
  13#include <linux/init.h>
  14#include <linux/sched.h>
  15#include <linux/notifier.h>
  16#include <linux/ptrace.h>
  17#include <linux/uaccess.h>
  18#include <linux/sched/signal.h>
  19
  20#include <asm/fpu.h>
  21#include <asm/cop2.h>
  22#include <asm/inst.h>
  23#include <asm/branch.h>
  24#include <asm/current.h>
  25#include <asm/mipsregs.h>
  26#include <asm/unaligned-emul.h>
  27
  28static int loongson_cu2_call(struct notifier_block *nfb, unsigned long action,
  29        void *data)
  30{
  31        unsigned int res, fpu_owned;
  32        unsigned long ra, value, value_next;
  33        union mips_instruction insn;
  34        int fr = !test_thread_flag(TIF_32BIT_FPREGS);
  35        struct pt_regs *regs = (struct pt_regs *)data;
  36        void __user *addr = (void __user *)regs->cp0_badvaddr;
  37        unsigned int __user *pc = (unsigned int __user *)exception_epc(regs);
  38
  39        ra = regs->regs[31];
  40        __get_user(insn.word, pc);
  41
  42        switch (action) {
  43        case CU2_EXCEPTION:
  44                preempt_disable();
  45                fpu_owned = __is_fpu_owner();
  46                if (!fr)
  47                        set_c0_status(ST0_CU1 | ST0_CU2);
  48                else
  49                        set_c0_status(ST0_CU1 | ST0_CU2 | ST0_FR);
  50                enable_fpu_hazard();
  51                KSTK_STATUS(current) |= (ST0_CU1 | ST0_CU2);
  52                if (fr)
  53                        KSTK_STATUS(current) |= ST0_FR;
  54                else
  55                        KSTK_STATUS(current) &= ~ST0_FR;
  56                /* If FPU is owned, we needn't init or restore fp */
  57                if (!fpu_owned) {
  58                        set_thread_flag(TIF_USEDFPU);
  59                        init_fp_ctx(current);
  60                        _restore_fp(current);
  61                }
  62                preempt_enable();
  63
  64                return NOTIFY_STOP;     /* Don't call default notifier */
  65
  66        case CU2_LWC2_OP:
  67                if (insn.loongson3_lswc2_format.ls == 0)
  68                        goto sigbus;
  69
  70                if (insn.loongson3_lswc2_format.fr == 0) {      /* gslq */
  71                        if (!access_ok(addr, 16))
  72                                goto sigbus;
  73
  74                        LoadDW(addr, value, res);
  75                        if (res)
  76                                goto fault;
  77
  78                        LoadDW(addr + 8, value_next, res);
  79                        if (res)
  80                                goto fault;
  81
  82                        regs->regs[insn.loongson3_lswc2_format.rt] = value;
  83                        regs->regs[insn.loongson3_lswc2_format.rq] = value_next;
  84                        compute_return_epc(regs);
  85                } else {                                        /* gslqc1 */
  86                        if (!access_ok(addr, 16))
  87                                goto sigbus;
  88
  89                        lose_fpu(1);
  90                        LoadDW(addr, value, res);
  91                        if (res)
  92                                goto fault;
  93
  94                        LoadDW(addr + 8, value_next, res);
  95                        if (res)
  96                                goto fault;
  97
  98                        set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rt], 0, value);
  99                        set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rq], 0, value_next);
 100                        compute_return_epc(regs);
 101                        own_fpu(1);
 102                }
 103                return NOTIFY_STOP;     /* Don't call default notifier */
 104
 105        case CU2_SWC2_OP:
 106                if (insn.loongson3_lswc2_format.ls == 0)
 107                        goto sigbus;
 108
 109                if (insn.loongson3_lswc2_format.fr == 0) {      /* gssq */
 110                        if (!access_ok(addr, 16))
 111                                goto sigbus;
 112
 113                        /* write upper 8 bytes first */
 114                        value_next = regs->regs[insn.loongson3_lswc2_format.rq];
 115
 116                        StoreDW(addr + 8, value_next, res);
 117                        if (res)
 118                                goto fault;
 119                        value = regs->regs[insn.loongson3_lswc2_format.rt];
 120
 121                        StoreDW(addr, value, res);
 122                        if (res)
 123                                goto fault;
 124
 125                        compute_return_epc(regs);
 126                } else {                                        /* gssqc1 */
 127                        if (!access_ok(addr, 16))
 128                                goto sigbus;
 129
 130                        lose_fpu(1);
 131                        value_next = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rq], 0);
 132
 133                        StoreDW(addr + 8, value_next, res);
 134                        if (res)
 135                                goto fault;
 136
 137                        value = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lswc2_format.rt], 0);
 138
 139                        StoreDW(addr, value, res);
 140                        if (res)
 141                                goto fault;
 142
 143                        compute_return_epc(regs);
 144                        own_fpu(1);
 145                }
 146                return NOTIFY_STOP;     /* Don't call default notifier */
 147
 148        case CU2_LDC2_OP:
 149                switch (insn.loongson3_lsdc2_format.opcode1) {
 150                /*
 151                 * Loongson-3 overridden ldc2 instructions.
 152                 * opcode1              instruction
 153                 *   0x1          gslhx: load 2 bytes to GPR
 154                 *   0x2          gslwx: load 4 bytes to GPR
 155                 *   0x3          gsldx: load 8 bytes to GPR
 156                 *   0x6          gslwxc1: load 4 bytes to FPR
 157                 *   0x7          gsldxc1: load 8 bytes to FPR
 158                 */
 159                case 0x1:
 160                        if (!access_ok(addr, 2))
 161                                goto sigbus;
 162
 163                        LoadHW(addr, value, res);
 164                        if (res)
 165                                goto fault;
 166
 167                        compute_return_epc(regs);
 168                        regs->regs[insn.loongson3_lsdc2_format.rt] = value;
 169                        break;
 170                case 0x2:
 171                        if (!access_ok(addr, 4))
 172                                goto sigbus;
 173
 174                        LoadW(addr, value, res);
 175                        if (res)
 176                                goto fault;
 177
 178                        compute_return_epc(regs);
 179                        regs->regs[insn.loongson3_lsdc2_format.rt] = value;
 180                        break;
 181                case 0x3:
 182                        if (!access_ok(addr, 8))
 183                                goto sigbus;
 184
 185                        LoadDW(addr, value, res);
 186                        if (res)
 187                                goto fault;
 188
 189                        compute_return_epc(regs);
 190                        regs->regs[insn.loongson3_lsdc2_format.rt] = value;
 191                        break;
 192                case 0x6:
 193                        die_if_kernel("Unaligned FP access in kernel code", regs);
 194                        BUG_ON(!used_math());
 195                        if (!access_ok(addr, 4))
 196                                goto sigbus;
 197
 198                        lose_fpu(1);
 199                        LoadW(addr, value, res);
 200                        if (res)
 201                                goto fault;
 202
 203                        set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0, value);
 204                        compute_return_epc(regs);
 205                        own_fpu(1);
 206
 207                        break;
 208                case 0x7:
 209                        die_if_kernel("Unaligned FP access in kernel code", regs);
 210                        BUG_ON(!used_math());
 211                        if (!access_ok(addr, 8))
 212                                goto sigbus;
 213
 214                        lose_fpu(1);
 215                        LoadDW(addr, value, res);
 216                        if (res)
 217                                goto fault;
 218
 219                        set_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0, value);
 220                        compute_return_epc(regs);
 221                        own_fpu(1);
 222                        break;
 223
 224                }
 225                return NOTIFY_STOP;     /* Don't call default notifier */
 226
 227        case CU2_SDC2_OP:
 228                switch (insn.loongson3_lsdc2_format.opcode1) {
 229                /*
 230                 * Loongson-3 overridden sdc2 instructions.
 231                 * opcode1              instruction
 232                 *   0x1          gsshx: store 2 bytes from GPR
 233                 *   0x2          gsswx: store 4 bytes from GPR
 234                 *   0x3          gssdx: store 8 bytes from GPR
 235                 *   0x6          gsswxc1: store 4 bytes from FPR
 236                 *   0x7          gssdxc1: store 8 bytes from FPR
 237                 */
 238                case 0x1:
 239                        if (!access_ok(addr, 2))
 240                                goto sigbus;
 241
 242                        compute_return_epc(regs);
 243                        value = regs->regs[insn.loongson3_lsdc2_format.rt];
 244
 245                        StoreHW(addr, value, res);
 246                        if (res)
 247                                goto fault;
 248
 249                        break;
 250                case 0x2:
 251                        if (!access_ok(addr, 4))
 252                                goto sigbus;
 253
 254                        compute_return_epc(regs);
 255                        value = regs->regs[insn.loongson3_lsdc2_format.rt];
 256
 257                        StoreW(addr, value, res);
 258                        if (res)
 259                                goto fault;
 260
 261                        break;
 262                case 0x3:
 263                        if (!access_ok(addr, 8))
 264                                goto sigbus;
 265
 266                        compute_return_epc(regs);
 267                        value = regs->regs[insn.loongson3_lsdc2_format.rt];
 268
 269                        StoreDW(addr, value, res);
 270                        if (res)
 271                                goto fault;
 272
 273                        break;
 274
 275                case 0x6:
 276                        die_if_kernel("Unaligned FP access in kernel code", regs);
 277                        BUG_ON(!used_math());
 278
 279                        if (!access_ok(addr, 4))
 280                                goto sigbus;
 281
 282                        lose_fpu(1);
 283                        value = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0);
 284
 285                        StoreW(addr, value, res);
 286                        if (res)
 287                                goto fault;
 288
 289                        compute_return_epc(regs);
 290                        own_fpu(1);
 291
 292                        break;
 293                case 0x7:
 294                        die_if_kernel("Unaligned FP access in kernel code", regs);
 295                        BUG_ON(!used_math());
 296
 297                        if (!access_ok(addr, 8))
 298                                goto sigbus;
 299
 300                        lose_fpu(1);
 301                        value = get_fpr64(&current->thread.fpu.fpr[insn.loongson3_lsdc2_format.rt], 0);
 302
 303                        StoreDW(addr, value, res);
 304                        if (res)
 305                                goto fault;
 306
 307                        compute_return_epc(regs);
 308                        own_fpu(1);
 309
 310                        break;
 311                }
 312                return NOTIFY_STOP;     /* Don't call default notifier */
 313        }
 314
 315        return NOTIFY_OK;               /* Let default notifier send signals */
 316
 317fault:
 318        /* roll back jump/branch */
 319        regs->regs[31] = ra;
 320        regs->cp0_epc = (unsigned long)pc;
 321        /* Did we have an exception handler installed? */
 322        if (fixup_exception(regs))
 323                return NOTIFY_STOP;     /* Don't call default notifier */
 324
 325        die_if_kernel("Unhandled kernel unaligned access", regs);
 326        force_sig(SIGSEGV);
 327
 328        return NOTIFY_STOP;     /* Don't call default notifier */
 329
 330sigbus:
 331        die_if_kernel("Unhandled kernel unaligned access", regs);
 332        force_sig(SIGBUS);
 333
 334        return NOTIFY_STOP;     /* Don't call default notifier */
 335}
 336
 337static int __init loongson_cu2_setup(void)
 338{
 339        return cu2_notifier(loongson_cu2_call, 0);
 340}
 341early_initcall(loongson_cu2_setup);
 342