MIPS: kernel: unaligned: Handle unaligned accesses for EVA
authorLeonid Yegoshin <Leonid.Yegoshin@imgtec.com>
Thu, 12 Dec 2013 16:15:15 +0000 (16:15 +0000)
committerRalf Baechle <ralf@linux-mips.org>
Wed, 26 Mar 2014 22:09:16 +0000 (23:09 +0100)
Handle unaligned accesses when we access userspace memory
EVA mode.

Signed-off-by: Leonid Yegoshin <Leonid.Yegoshin@imgtec.com>
Signed-off-by: Markos Chandras <markos.chandras@imgtec.com>
arch/mips/kernel/unaligned.c

index 5ec8f00b51b59580e93cf3cec51e83cea557053b..2b3517214d6d8cbdbe6a7bbc31187e1abcc893a5 100644 (file)
@@ -431,7 +431,9 @@ static void emulate_load_store_insn(struct pt_regs *regs,
        unsigned long origpc;
        unsigned long orig31;
        void __user *fault_addr = NULL;
-
+#ifdef CONFIG_EVA
+       mm_segment_t seg;
+#endif
        origpc = (unsigned long)pc;
        orig31 = regs->regs[31];
 
@@ -476,6 +478,88 @@ static void emulate_load_store_insn(struct pt_regs *regs,
                 * The remaining opcodes are the ones that are really of
                 * interest.
                 */
+#ifdef CONFIG_EVA
+       case spec3_op:
+               /*
+                * we can land here only from kernel accessing user memory,
+                * so we need to "switch" the address limit to user space, so
+                * address check can work properly.
+                */
+               seg = get_fs();
+               set_fs(USER_DS);
+               switch (insn.spec3_format.func) {
+               case lhe_op:
+                       if (!access_ok(VERIFY_READ, addr, 2)) {
+                               set_fs(seg);
+                               goto sigbus;
+                       }
+                       LoadHW(addr, value, res);
+                       if (res) {
+                               set_fs(seg);
+                               goto fault;
+                       }
+                       compute_return_epc(regs);
+                       regs->regs[insn.spec3_format.rt] = value;
+                       break;
+               case lwe_op:
+                       if (!access_ok(VERIFY_READ, addr, 4)) {
+                               set_fs(seg);
+                               goto sigbus;
+                       }
+                               LoadW(addr, value, res);
+                       if (res) {
+                               set_fs(seg);
+                               goto fault;
+                       }
+                       compute_return_epc(regs);
+                       regs->regs[insn.spec3_format.rt] = value;
+                       break;
+               case lhue_op:
+                       if (!access_ok(VERIFY_READ, addr, 2)) {
+                               set_fs(seg);
+                               goto sigbus;
+                       }
+                       LoadHWU(addr, value, res);
+                       if (res) {
+                               set_fs(seg);
+                               goto fault;
+                       }
+                       compute_return_epc(regs);
+                       regs->regs[insn.spec3_format.rt] = value;
+                       break;
+               case she_op:
+                       if (!access_ok(VERIFY_WRITE, addr, 2)) {
+                               set_fs(seg);
+                               goto sigbus;
+                       }
+                       compute_return_epc(regs);
+                       value = regs->regs[insn.spec3_format.rt];
+                       StoreHW(addr, value, res);
+                       if (res) {
+                               set_fs(seg);
+                               goto fault;
+                       }
+                       break;
+               case swe_op:
+                       if (!access_ok(VERIFY_WRITE, addr, 4)) {
+                               set_fs(seg);
+                               goto sigbus;
+                       }
+                       compute_return_epc(regs);
+                       value = regs->regs[insn.spec3_format.rt];
+                       StoreW(addr, value, res);
+                       if (res) {
+                               set_fs(seg);
+                               goto fault;
+                       }
+                       break;
+               default:
+                       set_fs(seg);
+                       goto sigill;
+               }
+               set_fs(seg);
+               break;
+#endif
        case lh_op:
                if (!access_ok(VERIFY_READ, addr, 2))
                        goto sigbus;