]> err.no Git - linux-2.6/blobdiff - arch/arm/kernel/traps.c
Merge /spare/repo/linux-2.6/
[linux-2.6] / arch / arm / kernel / traps.c
index 0078aeb85737197a84af1eeb0353dbef74427901..4554c961251c5871e0a05a2c6a74d1f660d0af73 100644 (file)
@@ -30,6 +30,7 @@
 #include <asm/traps.h>
 
 #include "ptrace.h"
+#include "signal.h"
 
 const char *processor_modes[]=
 { "USER_26", "FIQ_26" , "IRQ_26" , "SVC_26" , "UK4_26" , "UK5_26" , "UK6_26" , "UK7_26" ,
@@ -218,7 +219,8 @@ NORET_TYPE void die(const char *str, struct pt_regs *regs, int err)
                tsk->comm, tsk->pid, tsk->thread_info + 1);
 
        if (!user_mode(regs) || in_interrupt()) {
-               dump_mem("Stack: ", regs->ARM_sp, 8192+(unsigned long)tsk->thread_info);
+               dump_mem("Stack: ", regs->ARM_sp,
+                        THREAD_SIZE + (unsigned long)tsk->thread_info);
                dump_backtrace(regs, tsk);
                dump_instr(regs);
        }
@@ -228,16 +230,8 @@ NORET_TYPE void die(const char *str, struct pt_regs *regs, int err)
        do_exit(SIGSEGV);
 }
 
-void die_if_kernel(const char *str, struct pt_regs *regs, int err)
-{
-       if (user_mode(regs))
-               return;
-
-       die(str, regs, err);
-}
-
-static void notify_die(const char *str, struct pt_regs *regs, siginfo_t *info,
-                      unsigned long err, unsigned long trap)
+void notify_die(const char *str, struct pt_regs *regs, struct siginfo *info,
+               unsigned long err, unsigned long trap)
 {
        if (user_mode(regs)) {
                current->thread.error_code = err;
@@ -254,16 +248,20 @@ static DEFINE_SPINLOCK(undef_lock);
 
 void register_undef_hook(struct undef_hook *hook)
 {
-       spin_lock_irq(&undef_lock);
+       unsigned long flags;
+
+       spin_lock_irqsave(&undef_lock, flags);
        list_add(&hook->node, &undef_hook);
-       spin_unlock_irq(&undef_lock);
+       spin_unlock_irqrestore(&undef_lock, flags);
 }
 
 void unregister_undef_hook(struct undef_hook *hook)
 {
-       spin_lock_irq(&undef_lock);
+       unsigned long flags;
+
+       spin_lock_irqsave(&undef_lock, flags);
        list_del(&hook->node);
-       spin_unlock_irq(&undef_lock);
+       spin_unlock_irqrestore(&undef_lock, flags);
 }
 
 asmlinkage void do_undefinstr(struct pt_regs *regs)
@@ -450,15 +448,68 @@ asmlinkage int arm_syscall(int no, struct pt_regs *regs)
 
        case NR(set_tls):
                thread->tp_value = regs->ARM_r0;
+#if defined(CONFIG_HAS_TLS_REG)
+               asm ("mcr p15, 0, %0, c13, c0, 3" : : "r" (regs->ARM_r0) );
+#elif !defined(CONFIG_TLS_REG_EMUL)
                /*
-                * Our user accessible TLS ptr is located at 0xffff0ffc.
-                * On SMP read access to this address must raise a fault
-                * and be emulated from the data abort handler.
-                * m
+                * User space must never try to access this directly.
+                * Expect your app to break eventually if you do so.
+                * The user helper at 0xffff0fe0 must be used instead.
+                * (see entry-armv.S for details)
                 */
-               *((unsigned long *)0xffff0ffc) = thread->tp_value;
+               *((unsigned int *)0xffff0ff0) = regs->ARM_r0;
+#endif
                return 0;
 
+#ifdef CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG
+       /*
+        * Atomically store r1 in *r2 if *r2 is equal to r0 for user space.
+        * Return zero in r0 if *MEM was changed or non-zero if no exchange
+        * happened.  Also set the user C flag accordingly.
+        * If access permissions have to be fixed up then non-zero is
+        * returned and the operation has to be re-attempted.
+        *
+        * *NOTE*: This is a ghost syscall private to the kernel.  Only the
+        * __kuser_cmpxchg code in entry-armv.S should be aware of its
+        * existence.  Don't ever use this from user code.
+        */
+       case 0xfff0:
+       {
+               extern void do_DataAbort(unsigned long addr, unsigned int fsr,
+                                        struct pt_regs *regs);
+               unsigned long val;
+               unsigned long addr = regs->ARM_r2;
+               struct mm_struct *mm = current->mm;
+               pgd_t *pgd; pmd_t *pmd; pte_t *pte;
+
+               regs->ARM_cpsr &= ~PSR_C_BIT;
+               spin_lock(&mm->page_table_lock);
+               pgd = pgd_offset(mm, addr);
+               if (!pgd_present(*pgd))
+                       goto bad_access;
+               pmd = pmd_offset(pgd, addr);
+               if (!pmd_present(*pmd))
+                       goto bad_access;
+               pte = pte_offset_map(pmd, addr);
+               if (!pte_present(*pte) || !pte_write(*pte))
+                       goto bad_access;
+               val = *(unsigned long *)addr;
+               val -= regs->ARM_r0;
+               if (val == 0) {
+                       *(unsigned long *)addr = regs->ARM_r1;
+                       regs->ARM_cpsr |= PSR_C_BIT;
+               }
+               spin_unlock(&mm->page_table_lock);
+               return val;
+
+               bad_access:
+               spin_unlock(&mm->page_table_lock);
+               /* simulate a read access fault */
+               do_DataAbort(addr, 15 + (1 << 11), regs);
+               return -1;
+       }
+#endif
+
        default:
                /* Calls 9f00xx..9f07ff are defined to return -ENOSYS
                   if not implemented, rather than raising SIGILL.  This
@@ -493,6 +544,44 @@ asmlinkage int arm_syscall(int no, struct pt_regs *regs)
        return 0;
 }
 
+#ifdef CONFIG_TLS_REG_EMUL
+
+/*
+ * We might be running on an ARMv6+ processor which should have the TLS
+ * register but for some reason we can't use it, or maybe an SMP system
+ * using a pre-ARMv6 processor (there are apparently a few prototypes like
+ * that in existence) and therefore access to that register must be
+ * emulated.
+ */
+
+static int get_tp_trap(struct pt_regs *regs, unsigned int instr)
+{
+       int reg = (instr >> 12) & 15;
+       if (reg == 15)
+               return 1;
+       regs->uregs[reg] = current_thread_info()->tp_value;
+       regs->ARM_pc += 4;
+       return 0;
+}
+
+static struct undef_hook arm_mrc_hook = {
+       .instr_mask     = 0x0fff0fff,
+       .instr_val      = 0x0e1d0f70,
+       .cpsr_mask      = PSR_T_BIT,
+       .cpsr_val       = 0,
+       .fn             = get_tp_trap,
+};
+
+static int __init arm_mrc_hook_init(void)
+{
+       register_undef_hook(&arm_mrc_hook);
+       return 0;
+}
+
+late_initcall(arm_mrc_hook_init);
+
+#endif
+
 void __bad_xchg(volatile void *ptr, int size)
 {
        printk("xchg: bad data size: pc 0x%p, ptr 0x%p, size %d\n",
@@ -528,7 +617,7 @@ baddataabort(int code, unsigned long instr, struct pt_regs *regs)
        notify_die("unknown data abort code", regs, &info, instr, 0);
 }
 
-volatile void __bug(const char *file, int line, void *data)
+void __attribute__((noreturn)) __bug(const char *file, int line, void *data)
 {
        printk(KERN_CRIT"kernel BUG at %s:%d!", file, line);
        if (data)
@@ -580,14 +669,25 @@ void __init trap_init(void)
 {
        extern char __stubs_start[], __stubs_end[];
        extern char __vectors_start[], __vectors_end[];
+       extern char __kuser_helper_start[], __kuser_helper_end[];
+       int kuser_sz = __kuser_helper_end - __kuser_helper_start;
 
        /*
-        * Copy the vectors and stubs (in entry-armv.S) into the
-        * vector page, mapped at 0xffff0000, and ensure these are
-        * visible to the instruction stream.
+        * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
+        * into the vector page, mapped at 0xffff0000, and ensure these
+        * are visible to the instruction stream.
         */
        memcpy((void *)0xffff0000, __vectors_start, __vectors_end - __vectors_start);
        memcpy((void *)0xffff0200, __stubs_start, __stubs_end - __stubs_start);
+       memcpy((void *)0xffff1000 - kuser_sz, __kuser_helper_start, kuser_sz);
+
+       /*
+        * Copy signal return handlers into the vector page, and
+        * set sigreturn to be a pointer to these.
+        */
+       memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
+              sizeof(sigreturn_codes));
+
        flush_icache_range(0xffff0000, 0xffff0000 + PAGE_SIZE);
        modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
 }