MIPS: Setup an instruction emulation in VDSO protected page instead of user stack
authorLeonid Yegoshin <Leonid.Yegoshin@imgtec.com>
Tue, 18 Nov 2014 01:50:17 +0000 (17:50 -0800)
committerRaghu Gandham <raghu.gandham@imgtec.com>
Tue, 2 Dec 2014 00:58:36 +0000 (16:58 -0800)
Historically, during FPU emulation MIPS runs live BD-slot instruction in stack.
This is needed because it was the only way to correctly handle branch
exceptions with unknown COP2 instructions in BD-slot. Now there is
an eXecuteInhibit feature and it is desirable to protect stack from execution
for security reasons.
This patch moves FPU emulation from stack area to VDSO-located page which is set
write-protected for application access. VDSO page itself is now per-thread and
it's addresses and offsets are stored in thread_info.
Small stack of emulation blocks is supported because nested traps are possible
in MIPS32/64 R6 emulation mix with FPU emulation.

Explanation of problem (current state before patch):

If I set eXecute-disabled stack in ELF binary initialisation then GLIBC ignores
it and may change stack protection at library load. If this library has
eXecute-disabled stack then anything is OK, but if this section (PT_GNU_STACK)
is just missed as usual, then GLIBC applies it's own default == eXecute-enabled
stack.
So, ssh_keygen is built explicitly with eXecute-disabled stack. But GLIBC
ignores it and set stack executable. And because of that - anything works,
FPU emulation and hacker tools.
However, if I use all *.SO libraries with eXecute-disabled stack in PT_GNU_STACK
section then GLIBC keeps stack non-executable but things fails at FPU emulation
later.

Here are two issues which are bind together and to solve an incorrect
behaviour of GLIBC (ignoring X ssh-keygen intention) the splitting both issues
is needed. So, I did a kernel emulation protected and out of stack.

Signed-off-by: Leonid Yegoshin <Leonid.Yegoshin@imgtec.com>
15 files changed:
arch/mips/include/asm/fpu_emulator.h
arch/mips/include/asm/mmu.h
arch/mips/include/asm/processor.h
arch/mips/include/asm/switch_to.h
arch/mips/include/asm/thread_info.h
arch/mips/include/asm/tlbmisc.h
arch/mips/include/asm/vdso.h
arch/mips/kernel/mips-r2-emul.c
arch/mips/kernel/process.c
arch/mips/kernel/signal.c
arch/mips/kernel/vdso.c
arch/mips/math-emu/cp1emu.c
arch/mips/math-emu/dsemul.c
arch/mips/mm/fault.c
arch/mips/mm/tlb-r4k.c

index 56f4d0c51831935fb026f8ec1466b1bfc0afe941..04e4fd9da9029a340f855e9a350954814388c5cd 100644 (file)
@@ -52,7 +52,7 @@ do {                                                                  \
 #endif /* CONFIG_DEBUG_FS */
 
 extern int mips_dsemul(struct pt_regs *regs, mips_instruction ir,
-       unsigned long cpc);
+       unsigned long cpc, unsigned long bpc, unsigned long r31);
 extern int do_dsemulret(struct pt_regs *xcp);
 extern int fpu_emulator_cop1Handler(struct pt_regs *xcp,
                                    struct mips_fpu_struct *ctx, int has_fpu,
index c436138945a84dca9f1f311e54a801090c396853..621a5131d8f277fcc35aa3d8341e36ab679ad178 100644 (file)
@@ -3,7 +3,10 @@
 
 typedef struct {
        unsigned long asid[NR_CPUS];
+       unsigned long vdso_asid[NR_CPUS];
+       struct page   *vdso_page[NR_CPUS];
        void *vdso;
+       struct vm_area_struct   *vdso_vma;
 } mm_context_t;
 
 #endif /* __ASM_MMU_H */
index 1470b7b68b0e98996d271b25b86584fb6f3261a5..7fcbeec963c42bce15e9a87b6166ab76d6fcc989 100644 (file)
@@ -40,7 +40,7 @@ extern unsigned int vced_count, vcei_count;
  * A special page (the vdso) is mapped into all processes at the very
  * top of the virtual memory space.
  */
-#define SPECIAL_PAGES_SIZE PAGE_SIZE
+#define SPECIAL_PAGES_SIZE (PAGE_SIZE * 2)
 
 #ifdef CONFIG_32BIT
 #ifdef CONFIG_KVM_GUEST
index 271126f529cb757b1caa95150492293cca76f38a..26e8768c2444e7a84e1c9be3fc91a545c159a90b 100644 (file)
@@ -15,6 +15,7 @@
 #include <asm/cpu-features.h>
 #include <asm/watch.h>
 #include <asm/dsp.h>
+#include <asm/mmu_context.h>
 
 struct task_struct;
 
@@ -82,12 +83,25 @@ do {                                                                        \
        (last) = resume(prev, next, task_thread_info(next), __usedfpu); \
 } while (0)
 
+static inline void flush_vdso_page(void)
+{
+       int cpu = raw_smp_processor_id();
+
+       if (current->mm && cpu_context(cpu, current->mm) &&
+           (current->mm->context.vdso_page[cpu] != current_thread_info()->vdso_page) &&
+           (current->mm->context.vdso_asid[cpu] == cpu_asid(cpu, current->mm))) {
+               local_flush_tlb_page(current->mm->mmap, (unsigned long)current->mm->context.vdso);
+               current->mm->context.vdso_asid[cpu] = 0;
+       }
+}
+
 #define finish_arch_switch(prev)                                       \
 do {                                                                   \
        if (cpu_has_dsp)                                                \
                __restore_dsp(current);                                 \
        if (cpu_has_userlocal)                                          \
                write_c0_userlocal(current_thread_info()->tp_value);    \
+       flush_vdso_page();                                              \
        __restore_watch();                                              \
 } while (0)
 
index 895320e25662cd98a673980c449ec916841920d7..2a9d070016b57ffb566b7467d4285281a73b639b 100644 (file)
@@ -34,6 +34,8 @@ struct thread_info {
                                                 * 0x7fffffff for user-thead
                                                 * 0xffffffff for kernel-thread
                                                 */
+       unsigned long           vdso_offset;
+       struct page             *vdso_page;
        struct restart_block    restart_block;
        struct pt_regs          *regs;
 };
@@ -49,6 +51,7 @@ struct thread_info {
        .cpu            = 0,                    \
        .preempt_count  = INIT_PREEMPT_COUNT,   \
        .addr_limit     = KERNEL_DS,            \
+       .vdso_page      = NULL,                 \
        .restart_block  = {                     \
                .fn = do_no_restart_syscall,    \
        },                                      \
index 6494a51c6adbcb12f19286c0c2721a4e8a05446e..2101421ba1feb1f0f0d17ecb29e139d7da8dadcd 100644 (file)
@@ -10,5 +10,6 @@ void remove_wired_entry(void);
 int wired_push(unsigned long entryhi, unsigned long entrylo0,
               unsigned long entrylo1, unsigned long pagemask);
 int wired_pop(void);
+int install_vdso_tlb(void);
 
 #endif /* __ASM_TLBMISC_H */
index cca56aa40ff4a1cd475457c16783bc71855f43c5..77056fc38df65037ddacd2e25179a204ec65cd4c 100644 (file)
@@ -11,6 +11,9 @@
 
 #include <linux/types.h>
 
+void mips_thread_vdso(struct thread_info *ti);
+void arch_release_thread_info(struct thread_info *info);
+void vdso_epc_adjust(struct pt_regs *xcp);
 
 #ifdef CONFIG_32BIT
 struct mips_vdso {
index 4f21d51b06a45bbb1e81551e2ba0b9079bc1ab69..bd8cdc037af6a3dfaedf4927e3c2474fee179981 100644 (file)
@@ -282,6 +282,7 @@ static int jr_func(struct pt_regs *regs, u32 ir)
        unsigned long epc;
        unsigned long cpc;
        unsigned long nepc;
+       unsigned long r31;
        u32 nir;
 
        if (delay_slot(regs))
@@ -289,6 +290,7 @@ static int jr_func(struct pt_regs *regs, u32 ir)
        nepc = regs->cp0_epc;
        regs->cp0_epc -= 4;
        epc = regs->cp0_epc;
+       r31 = regs->regs[31];
        err = __compute_return_epc(regs);
        if (err < 0)
                return(SIGEMT);
@@ -301,7 +303,7 @@ static int jr_func(struct pt_regs *regs, u32 ir)
                /* Negative err means FPU instruction in BD-slot,
                   Zero err means 'BD-slot emulation done' */
                if ((err = mipsr6_emul(regs,nir)) > 0) {
-                       err = mips_dsemul(regs, nir, cpc);
+                       err = mips_dsemul(regs, nir, cpc, epc, r31);
                        if (err == SIGILL)
                                err = SIGEMT;
                        MIPS_R2_STATS(dsemul);
@@ -805,7 +807,7 @@ repeat:
                        }
                        if (nir) {  /* NOP is easy */
                                if ((err = mipsr6_emul(regs,nir)) > 0) {
-                                       err = mips_dsemul(regs, nir, cpc);
+                                       err = mips_dsemul(regs, nir, cpc, epc, r31);
                                        if (err == SIGILL)
                                                err = SIGEMT;
                                        MIPS_R2_STATS(dsemul);
@@ -850,7 +852,7 @@ repeat:
                        }
                        if (nir) {  /* NOP is easy */
                                if ((err = mipsr6_emul(regs,nir)) > 0) {
-                                       err = mips_dsemul(regs, nir, cpc);
+                                       err = mips_dsemul(regs, nir, cpc, epc, r31);
                                        if (err == SIGILL)
                                                err = SIGEMT;
                                        MIPS_R2_STATS(dsemul);
@@ -913,7 +915,7 @@ repeat:
                }
                if (nir) {  /* NOP is easy */
                        if ((err = mipsr6_emul(regs,nir)) > 0) {
-                               err = mips_dsemul(regs, nir, cpc);
+                               err = mips_dsemul(regs, nir, cpc, epc, r31);
                                if (err == SIGILL)
                                        err = SIGEMT;
                                MIPS_R2_STATS(dsemul);
index 2847687abc3eaf3a3b150918fe15c87e94e83a6b..7d15d2679bd236a93349c686e038bbfecf6d7127 100644 (file)
@@ -41,6 +41,7 @@
 #include <asm/isadep.h>
 #include <asm/inst.h>
 #include <asm/stacktrace.h>
+#include <asm/vdso.h>
 
 #ifdef CONFIG_HOTPLUG_CPU
 void arch_cpu_idle_dead(void)
@@ -58,6 +59,8 @@ void start_thread(struct pt_regs * regs, unsigned long pc, unsigned long sp)
 {
        unsigned long status;
 
+       mips_thread_vdso(current_thread_info());
+
        /* New thread loses kernel privileges. */
        status = regs->cp0_status & ~(ST0_CU0|ST0_CU1|ST0_FR|KU_MASK);
        status |= KU_USER;
@@ -72,6 +75,7 @@ void start_thread(struct pt_regs * regs, unsigned long pc, unsigned long sp)
 
 void exit_thread(void)
 {
+       arch_release_thread_info(current_thread_info());
 }
 
 void flush_thread(void)
@@ -98,6 +102,9 @@ int copy_thread(unsigned long clone_flags, unsigned long usp,
 
        preempt_enable();
 
+       ti->vdso_page = NULL;
+       mips_thread_vdso(ti);
+
        /* set up new TSS. */
        childregs = (struct pt_regs *) childksp - 1;
        /*  Put the stack after the struct pt_regs.  */
index d94739dd66d20a7eb5c3abe9ee565d7aa8d574e1..42be76beddef87310a23045d303eb1539f454c5d 100644 (file)
@@ -538,6 +538,10 @@ static void handle_signal(unsigned long sig, siginfo_t *info,
                regs->regs[0] = 0;              /* Don't deal with this again.  */
        }
 
+       /* adjust emulation stack if signal happens during emulation */
+       if (current_thread_info()->vdso_page)
+               vdso_epc_adjust(regs);
+
        if (sig_uses_siginfo(ka))
                ret = abi->setup_rt_frame(vdso + abi->rt_signal_return_offset,
                                          ka, regs, sig, oldset, info);
index 0f1af58b036a155dab46b898c2d7df04fc2ef245..568b7ce765e83e39c2248379486a0389bf2545a8 100644 (file)
@@ -19,6 +19,8 @@
 
 #include <asm/vdso.h>
 #include <asm/uasm.h>
+#include <asm/cacheflush.h>
+#include <asm/tlbflush.h>
 
 /*
  * Including <asm/unistd.h> would give use the 64-bit syscall numbers ...
@@ -88,14 +90,18 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp)
 
        ret = install_special_mapping(mm, addr, PAGE_SIZE,
                                      VM_READ|VM_EXEC|
-                                     VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC,
+                                     VM_MAYREAD|VM_MAYEXEC,
                                      &vdso_page);
 
        if (ret)
                goto up_fail;
 
        mm->context.vdso = (void *)addr;
+       /* if cache aliasing - use a different cache flush later */
+       if (cpu_has_rixi && (cpu_has_dc_aliases || cpu_has_ic_aliases))
+               mm->context.vdso_vma = find_vma(mm,addr);
 
+       mips_thread_vdso(current_thread_info());
 up_fail:
        up_write(&mm->mmap_sem);
        return ret;
@@ -107,3 +113,36 @@ const char *arch_vma_name(struct vm_area_struct *vma)
                return "[vdso]";
        return NULL;
 }
+
+void mips_thread_vdso(struct thread_info *ti)
+{
+       struct page *vdso;
+       unsigned long addr;
+
+       if (cpu_has_rixi && ti->task->mm && !ti->vdso_page) {
+               vdso = alloc_page(GFP_USER);
+               if (!vdso)
+                       return;
+               ti->vdso_page = vdso;
+               ti->vdso_offset = PAGE_SIZE;
+               addr = (unsigned long)page_address(vdso);
+               copy_page((void *)addr, (void *)page_address(vdso_page));
+               if (!cpu_has_ic_fills_f_dc)
+                       flush_data_cache_page(addr);
+               /* any vma in mmap is used, just to get ASIDs back from mm */
+               local_flush_tlb_page(ti->task->mm->mmap,addr);
+       }
+}
+
+void arch_release_thread_info(struct thread_info *info)
+{
+       if (info->vdso_page) {
+               if (info->task->mm) {
+                       /* any vma in mmap is used, just to get ASIDs */
+                       local_flush_tlb_page(info->task->mm->mmap,(unsigned long)page_address(info->vdso_page));
+                       info->task->mm->context.vdso_asid[smp_processor_id()] = 0;
+               }
+               __free_page(info->vdso_page);
+               info->vdso_page = NULL;
+       }
+}
index 6d41b79c66bb3700d6a5b33cc15948dedd434c92..de614414bd680dfb9be3ee596fc166070836d246 100644 (file)
@@ -1063,10 +1063,14 @@ static int cop1Emulate(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
 {
        mips_instruction ir;
        unsigned long contpc = xcp->cp0_epc + dec_insn.pc_inc;
+       unsigned long r31;
+       unsigned long s_epc;
        unsigned int cond;
        int pc_inc;
        int likely;
 
+       s_epc = xcp->cp0_epc;
+       r31 = xcp->regs[31];
        /* XXX NEC Vr54xx bug workaround */
        if (xcp->cp0_cause & CAUSEF_BD) {
                if (dec_insn.micro_mips_mode) {
@@ -1399,7 +1403,7 @@ branch_cont:    {
                                                 * Single step the non-CP1
                                                 * instruction in the dslot.
                                                 */
-                                               return mips_dsemul(xcp, ir, contpc);
+                                               return mips_dsemul(xcp, ir, contpc, s_epc, r31);
                                        }
                                } else
                                        contpc = (xcp->cp0_epc + (contpc << 2));
@@ -1432,7 +1436,7 @@ branch_cont:    {
                                 * Single step the non-cp1
                                 * instruction in the dslot
                                 */
-                               return mips_dsemul(xcp, ir, contpc);
+                               return mips_dsemul(xcp, ir, contpc, s_epc, r31);
                        } else {
                                /* branch not taken */
                                if (likely) {
index 364df098892bdbd4cc1ae4a41e80bee06d87dc42..d33f5e2609b07f756d6a5ffa0bf4016b122b9530 100644 (file)
@@ -10,6 +10,7 @@
 #include <asm/inst.h>
 #include <asm/processor.h>
 #include <asm/uaccess.h>
+#include <asm/vdso.h>
 #include <asm/branch.h>
 #include <asm/mipsregs.h>
 #include <asm/cacheflush.h>
@@ -47,13 +48,19 @@ struct emuframe {
        mips_instruction        badinst;
        mips_instruction        cookie;
        unsigned long           epc;
+       unsigned long           bpc;
+       unsigned long           r31;
 };
+/* round structure size to N*8 to force a fit two instructions in a single cache line */
+#define EMULFRAME_ROUNDED_SIZE  ((sizeof(struct emuframe) + 0x7) & ~0x7)
 
-int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc)
+int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc,
+               unsigned long bpc, unsigned long r31)
 {
        extern asmlinkage void handle_dsemulret(void);
        struct emuframe __user *fr;
        int err;
+       unsigned char *pg_addr;
 
        if ((get_isa16_mode(regs->cp0_epc) && ((ir >> 16) == MM_NOP16)) ||
                (ir == 0)) {
@@ -68,7 +75,7 @@ int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc)
 #endif
 
        /*
-        * The strategy is to push the instruction onto the user stack
+        * The strategy is to push the instruction onto the user stack/VDSO page
         * and put a trap after it which we can catch and jump to
         * the required address any alternative apart from full
         * instruction emulation!!.
@@ -85,36 +92,81 @@ int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc)
         * handler (single entry point).
         */
 
-       /* Ensure that the two instructions are in the same cache line */
-       fr = (struct emuframe __user *)
-               ((regs->regs[29] - sizeof(struct emuframe)) & ~0x7);
+       if (current_thread_info()->vdso_page) {
+               /*
+                * Use VDSO page and fill structure via kernel VA,
+                * user write is disabled
+                */
+               pg_addr = (unsigned char *)page_address(current_thread_info()->vdso_page);
+               fr = (struct emuframe __user *)
+                           (pg_addr + current_thread_info()->vdso_offset -
+                            EMULFRAME_ROUNDED_SIZE);
+
+               /* verify that we don't overflow into trampoline areas */
+               if ((unsigned char *)fr < (unsigned char *)(((struct mips_vdso *)pg_addr) + 1)) {
+                       MIPS_FPU_EMU_INC_STATS(errors);
+                       return SIGBUS;
+               }
+
+               current_thread_info()->vdso_offset -= EMULFRAME_ROUNDED_SIZE;
 
-       /* Verify that the stack pointer is not competely insane */
-       if (unlikely(!access_ok(VERIFY_WRITE, fr, sizeof(struct emuframe))))
-               return SIGBUS;
+               if (get_isa16_mode(regs->cp0_epc)) {
+                       *(u16 *)&fr->emul = (u16)(ir >> 16);
+                       *((u16 *)(&fr->emul) + 1) = (u16)(ir & 0xffff);
+                       *((u16 *)(&fr->emul) + 2) = (u16)(BREAK_MATH >> 16);
+                       *((u16 *)(&fr->emul) + 3) = (u16)(BREAK_MATH &0xffff);
+               } else {
+                       fr->emul = ir;
+                       fr->badinst = (mips_instruction)BREAK_MATH;
+               }
+               fr->cookie = (mips_instruction)BD_COOKIE;
+               fr->epc = cpc;
+               fr->bpc = bpc;
+               fr->r31 = r31;
 
-       if (get_isa16_mode(regs->cp0_epc)) {
-               err = __put_user(ir >> 16, (u16 __user *)(&fr->emul));
-               err |= __put_user(ir & 0xffff, (u16 __user *)((long)(&fr->emul) + 2));
-               err |= __put_user(BREAK_MATH >> 16, (u16 __user *)(&fr->badinst));
-               err |= __put_user(BREAK_MATH & 0xffff, (u16 __user *)((long)(&fr->badinst) + 2));
+               /* fill CP0_EPC with user VA */
+               regs->cp0_epc = ((unsigned long)(current->mm->context.vdso +
+                               current_thread_info()->vdso_offset)) |
+                               get_isa16_mode(regs->cp0_epc);
+               if (cpu_has_dc_aliases || cpu_has_ic_aliases)
+                       mips_flush_data_cache_range(current->mm->context.vdso_vma,
+                               regs->cp0_epc, current_thread_info()->vdso_page,
+                               (unsigned long)fr, sizeof(struct emuframe));
+               else
+                       /* it is a less expensive on CPU with correct SYNCI */
+                       flush_cache_sigtramp((unsigned long)fr);
        } else {
-               err = __put_user(ir, &fr->emul);
-               err |= __put_user((mips_instruction)BREAK_MATH, &fr->badinst);
-       }
+               /* Ensure that the two instructions are in the same cache line */
+               fr = (struct emuframe __user *)
+                       ((regs->regs[29] - sizeof(struct emuframe)) & ~0x7);
 
-       err |= __put_user((mips_instruction)BD_COOKIE, &fr->cookie);
-       err |= __put_user(cpc, &fr->epc);
+               /* Verify that the stack pointer is not competely insane */
+               if (unlikely(!access_ok(VERIFY_WRITE, fr, sizeof(struct emuframe))))
+                       return SIGBUS;
 
-       if (unlikely(err)) {
-               MIPS_FPU_EMU_INC_STATS(errors);
-               return SIGBUS;
-       }
+               if (get_isa16_mode(regs->cp0_epc)) {
+                       err = __put_user(ir >> 16, (u16 __user *)(&fr->emul));
+                       err |= __put_user(ir & 0xffff, (u16 __user *)((long)(&fr->emul) + 2));
+                       err |= __put_user(BREAK_MATH >> 16, (u16 __user *)(&fr->badinst));
+                       err |= __put_user(BREAK_MATH & 0xffff, (u16 __user *)((long)(&fr->badinst) + 2));
+               } else {
+                       err = __put_user(ir, &fr->emul);
+                       err |= __put_user((mips_instruction)BREAK_MATH, &fr->badinst);
+               }
+
+               err |= __put_user((mips_instruction)BD_COOKIE, &fr->cookie);
+               err |= __put_user(cpc, &fr->epc);
 
-       regs->cp0_epc = ((unsigned long) &fr->emul) |
-               get_isa16_mode(regs->cp0_epc);
+               if (unlikely(err)) {
+                       MIPS_FPU_EMU_INC_STATS(errors);
+                       return SIGBUS;
+               }
 
-       flush_cache_sigtramp((unsigned long)&fr->badinst);
+               regs->cp0_epc = ((unsigned long) &fr->emul) |
+                       get_isa16_mode(regs->cp0_epc);
+
+               flush_cache_sigtramp((unsigned long)&fr->badinst);
+       }
 
        return SIGILL;          /* force out of emulation loop */
 }
@@ -152,7 +204,10 @@ int do_dsemulret(struct pt_regs *xcp)
        }
        err |= __get_user(cookie, &fr->cookie);
 
-       if (unlikely(err || (insn != BREAK_MATH) || (cookie != BD_COOKIE))) {
+       if (unlikely(err || (insn != BREAK_MATH) || (cookie != BD_COOKIE) ||
+           (current_thread_info()->vdso_page &&
+            ((xcp->cp0_epc & PAGE_MASK) !=
+                       (unsigned long)current->mm->context.vdso)))) {
                MIPS_FPU_EMU_INC_STATS(errors);
                return 0;
        }
@@ -177,7 +232,53 @@ int do_dsemulret(struct pt_regs *xcp)
                return 0;
        }
 
+       if (current_thread_info()->vdso_page) {
+               /* restore VDSO stack level */
+               current_thread_info()->vdso_offset += EMULFRAME_ROUNDED_SIZE;
+               if (current_thread_info()->vdso_offset > PAGE_SIZE) {
+                       /* This is not a good situation to be in */
+                       current_thread_info()->vdso_offset -= EMULFRAME_ROUNDED_SIZE;
+                       force_sig(SIGBUS, current);
+
+                       return 0;
+               }
+       }
        /* Set EPC to return to post-branch instruction */
        xcp->cp0_epc = epc;
        return 1;
 }
+
+/* check and adjust an emulation stack before start a signal handler */
+void vdso_epc_adjust(struct pt_regs *xcp)
+{
+       struct emuframe __user *fr;
+       unsigned long epc;
+       unsigned long r31;
+
+       while (current_thread_info()->vdso_offset < PAGE_SIZE) {
+               epc = msk_isa16_mode(xcp->cp0_epc);
+               if ((epc >= ((unsigned long)current->mm->context.vdso + PAGE_SIZE)) ||
+                   (epc < (unsigned long)((struct mips_vdso *)current->mm->context.vdso + 1)))
+                       return;
+
+               fr = (struct emuframe __user *)
+                       ((unsigned long)current->mm->context.vdso +
+                        current_thread_info()->vdso_offset);
+
+               /*
+                * epc must point to emul instruction or badinst
+                * in case of emul - it is not executed, so return to start
+                *                   and restore GPR31
+                * in case of badinst - instruction is executed, return to destination
+                */
+               if (epc == (unsigned long)&fr->emul) {
+                       __get_user(r31, &fr->r31);
+                       xcp->regs[31] = r31;
+                       __get_user(epc, &fr->bpc);
+               } else {
+                       __get_user(epc, &fr->epc);
+               }
+               xcp->cp0_epc = epc;
+               current_thread_info()->vdso_offset += EMULFRAME_ROUNDED_SIZE;
+       }
+}
index 0fead53d1c26b261affa89d00c1fa06844f9e602..d0f0ee949967a9d4cb3dbe26a79f288589499716 100644 (file)
@@ -25,6 +25,7 @@
 #include <asm/uaccess.h>
 #include <asm/ptrace.h>
 #include <asm/highmem.h>               /* For VMALLOC_END */
+#include <asm/tlbmisc.h>
 #include <linux/kdebug.h>
 
 /*
@@ -135,6 +136,9 @@ good_area:
 #endif
                                goto bad_area;
                        }
+                       if (((address & PAGE_MASK) == (unsigned long)(mm->context.vdso)) &&
+                           install_vdso_tlb())
+                               goto up_return;
                } else {
                        if (!(vma->vm_flags & (VM_READ | VM_WRITE | VM_EXEC)))
                                goto bad_area;
@@ -183,6 +187,7 @@ good_area:
                }
        }
 
+up_return:
        up_read(&mm->mmap_sem);
        return;
 
index 1df7eb0664f79d0ce3c9f30b5edbf9af0d15e1f0..31f72eceb995e5a63fff729acc540cf668a0158a 100644 (file)
@@ -414,6 +414,48 @@ int wired_pop(void)
        return(--current_wired);
 }
 
+int install_vdso_tlb(void)
+{
+       int tlbidx;
+       int cpu;
+       unsigned long flags;
+
+       if (!current_thread_info()->vdso_page)
+               return(0);
+
+       local_irq_save(flags);
+       cpu = smp_processor_id();
+       write_c0_entryhi(((unsigned long)current->mm->context.vdso & (PAGE_MASK << 1)) |
+                        cpu_asid(cpu, current->mm));
+
+       mtc0_tlbw_hazard();
+       tlb_probe();
+       tlb_probe_hazard();
+       tlbidx = read_c0_index();
+#if defined(CONFIG_64BIT_PHYS_ADDR) && defined(CONFIG_CPU_MIPS32)
+               write_c0_entrylo0(pte_val(pfn_pte(
+                       page_to_pfn(current_thread_info()->vdso_page),
+                       __pgprot(_page_cachable_default|_PAGE_VALID)))>>32);
+#else
+               write_c0_entrylo0(pte_to_entrylo(pte_val(pfn_pte(
+                       page_to_pfn(current_thread_info()->vdso_page),
+                       __pgprot(_page_cachable_default|_PAGE_VALID)))));
+#endif
+       write_c0_entrylo1(0);
+       mtc0_tlbw_hazard();
+       if (tlbidx < 0)
+               tlb_write_random();
+       else
+               tlb_write_indexed();
+       tlbw_use_hazard();
+
+       current->mm->context.vdso_asid[cpu] = cpu_asid(cpu, current->mm);
+       current->mm->context.vdso_page[cpu] = current_thread_info()->vdso_page;
+       local_irq_restore(flags);
+
+       return(1);
+}
+
 void add_wired_entry(unsigned long entrylo0, unsigned long entrylo1,
                     unsigned long entryhi, unsigned long pagemask)
 {