xtensa: add support for call0 ABI in userspace
authorMax Filippov <jcmvbkbc@gmail.com>
Mon, 12 Jan 2015 06:44:44 +0000 (09:44 +0300)
committerMax Filippov <jcmvbkbc@gmail.com>
Sun, 1 Sep 2019 20:11:57 +0000 (13:11 -0700)
Provide a Kconfig choice to select whether only the default ABI, only
call0 ABI or both are supported. The default for XEA2 is windowed, but
it may change for XEA3. Call0 only runs userspace with PS.WOE disabled.
Supporting both windowed and call0 ABIs is tricky, as there's no
indication in the ELF binaries which ABI they use. So it is done by
probing: each process is started with PS.WOE disabled, but the handler
of an illegal instruction exception taken with PS.WOE retries faulting
instruction after enabling PS.WOE. It must happen before any signal is
delivered to the process, otherwise it may be delivered incorrectly.

Signed-off-by: Max Filippov <jcmvbkbc@gmail.com>
arch/xtensa/Kconfig
arch/xtensa/include/asm/processor.h
arch/xtensa/kernel/entry.S
arch/xtensa/kernel/signal.c
arch/xtensa/kernel/stacktrace.c
arch/xtensa/kernel/traps.c

index ebc135bda92119f38abfc5cb2eb8c2d91f760d41..fb64469ca8f0ee2e14012496245fd6cb3871a837 100644 (file)
@@ -385,6 +385,54 @@ config FAST_SYSCALL_SPILL_REGISTERS
 
          If unsure, say N.
 
+config USER_ABI_CALL0
+       bool
+
+choice
+       prompt "Userspace ABI"
+       default USER_ABI_DEFAULT
+       help
+         Select supported userspace ABI.
+
+         If unsure, choose the default ABI.
+
+config USER_ABI_DEFAULT
+       bool "Default ABI only"
+       help
+         Assume default userspace ABI. For XEA2 cores it is windowed ABI.
+         call0 ABI binaries may be run on such kernel, but signal delivery
+         will not work correctly for them.
+
+config USER_ABI_CALL0_ONLY
+       bool "Call0 ABI only"
+       select USER_ABI_CALL0
+       help
+         Select this option to support only call0 ABI in userspace.
+         Windowed ABI binaries will crash with a segfault caused by
+         an illegal instruction exception on the first 'entry' opcode.
+
+         Choose this option if you're planning to run only user code
+         built with call0 ABI.
+
+config USER_ABI_CALL0_PROBE
+       bool "Support both windowed and call0 ABI by probing"
+       select USER_ABI_CALL0
+       help
+         Select this option to support both windowed and call0 userspace
+         ABIs. When enabled all processes are started with PS.WOE disabled
+         and a fast user exception handler for an illegal instruction is
+         used to turn on PS.WOE bit on the first 'entry' opcode executed by
+         the userspace.
+
+         This option should be enabled for the kernel that must support
+         both call0 and windowed ABIs in userspace at the same time.
+
+         Note that Xtensa ISA does not guarantee that entry opcode will
+         raise an illegal instruction exception on cores with XEA2 when
+         PS.WOE is disabled, check whether the target core supports it.
+
+endchoice
+
 endmenu
 
 config XTENSA_CALIBRATE_CCOUNT
index 0b4efec9e19e271c094d305450ff9b281b0f0ba9..7495520d7a3e92b65c57207220f48142cb874214 100644 (file)
@@ -176,14 +176,21 @@ struct thread_struct {
 
 /*
  * Do necessary setup to start up a newly executed thread.
- * Note: We set-up ps as if we did a call4 to the new pc.
+ * Note: When windowed ABI is used for userspace we set-up ps
+ *       as if we did a call4 to the new pc.
  *       set_thread_state in signal.c depends on it.
  */
+#if IS_ENABLED(CONFIG_USER_ABI_CALL0)
+#define USER_PS_VALUE ((USER_RING << PS_RING_SHIFT) |                  \
+                      (1 << PS_UM_BIT) |                               \
+                      (1 << PS_EXCM_BIT))
+#else
 #define USER_PS_VALUE (PS_WOE_MASK |                                   \
                       (1 << PS_CALLINC_SHIFT) |                        \
                       (USER_RING << PS_RING_SHIFT) |                   \
                       (1 << PS_UM_BIT) |                               \
                       (1 << PS_EXCM_BIT))
+#endif
 
 /* Clearing a0 terminates the backtrace. */
 #define start_thread(regs, new_pc, new_sp) \
index 9afe8f612f2392c8e00e01f9c5b04ff589769e7a..9e3676879168ab920696c4e0809696edcda44ffe 100644 (file)
@@ -1003,7 +1003,41 @@ ENTRY(fast_alloca)
 4:     j       _WindowUnderflow4
 ENDPROC(fast_alloca)
 
+#ifdef CONFIG_USER_ABI_CALL0_PROBE
 /*
+ * fast illegal instruction handler.
+ *
+ * This is used to fix up user PS.WOE on the exception caused
+ * by the first opcode related to register window. If PS.WOE is
+ * already set it goes directly to the common user exception handler.
+ *
+ * Entry condition:
+ *
+ *   a0:       trashed, original value saved on stack (PT_AREG0)
+ *   a1:       a1
+ *   a2:       new stack pointer, original in DEPC
+ *   a3:       a3
+ *   depc:     a2, original value saved on stack (PT_DEPC)
+ *   excsave_1:        dispatch table
+ */
+
+ENTRY(fast_illegal_instruction_user)
+
+       rsr     a0, ps
+       bbsi.l  a0, PS_WOE_BIT, user_exception
+       s32i    a3, a2, PT_AREG3
+       movi    a3, PS_WOE_MASK
+       or      a0, a0, a3
+       wsr     a0, ps
+       l32i    a3, a2, PT_AREG3
+       l32i    a0, a2, PT_AREG0
+       rsr     a2, depc
+       rfe
+
+ENDPROC(fast_illegal_instruction_user)
+#endif
+
+       /*
  * fast system calls.
  *
  * WARNING:  The kernel doesn't save the entire user context before
index fbedf2aba09dcb69d4e60df107cce1d7dea8daab..dae83cddd6ca2d09b16d77656da1a0adf87c4556 100644 (file)
@@ -335,7 +335,8 @@ static int setup_frame(struct ksignal *ksig, sigset_t *set,
 {
        struct rt_sigframe *frame;
        int err = 0, sig = ksig->sig;
-       unsigned long sp, ra, tp;
+       unsigned long sp, ra, tp, ps;
+       unsigned int base;
 
        sp = regs->areg[1];
 
@@ -385,17 +386,26 @@ static int setup_frame(struct ksignal *ksig, sigset_t *set,
 
        /* Set up registers for signal handler; preserve the threadptr */
        tp = regs->threadptr;
+       ps = regs->ps;
        start_thread(regs, (unsigned long) ksig->ka.sa.sa_handler,
                     (unsigned long) frame);
 
-       /* Set up a stack frame for a call4
-        * Note: PS.CALLINC is set to one by start_thread
-        */
-       regs->areg[4] = (((unsigned long) ra) & 0x3fffffff) | 0x40000000;
-       regs->areg[6] = (unsigned long) sig;
-       regs->areg[7] = (unsigned long) &frame->info;
-       regs->areg[8] = (unsigned long) &frame->uc;
+       /* Set up a stack frame for a call4 if userspace uses windowed ABI */
+       if (ps & PS_WOE_MASK) {
+               base = 4;
+               regs->areg[base] =
+                       (((unsigned long) ra) & 0x3fffffff) | 0x40000000;
+               ps = (ps & ~(PS_CALLINC_MASK | PS_OWB_MASK)) |
+                       (1 << PS_CALLINC_SHIFT);
+       } else {
+               base = 0;
+               regs->areg[base] = (unsigned long) ra;
+       }
+       regs->areg[base + 2] = (unsigned long) sig;
+       regs->areg[base + 3] = (unsigned long) &frame->info;
+       regs->areg[base + 4] = (unsigned long) &frame->uc;
        regs->threadptr = tp;
+       regs->ps = ps;
 
        pr_debug("SIG rt deliver (%s:%d): signal=%d sp=%p pc=%08lx\n",
                 current->comm, current->pid, sig, frame, regs->pc);
index b9f82510c65019506ffb98f3f23ac494f7285efa..c822abb93d2084921a6a85873c499c944205c499 100644 (file)
@@ -44,6 +44,11 @@ void xtensa_backtrace_user(struct pt_regs *regs, unsigned int depth,
        if (pc == 0 || pc >= TASK_SIZE || ufn(&frame, data))
                return;
 
+       if (IS_ENABLED(CONFIG_USER_ABI_CALL0_ONLY) ||
+           (IS_ENABLED(CONFIG_USER_ABI_CALL0_PROBE) &&
+            !(regs->ps & PS_WOE_MASK)))
+               return;
+
        /* Two steps:
         *
         * 1. Look through the register window for the
index f060348c1b233a04373294dad0d834e0a129f60d..4a6c495ce9b6da69302b9a16075ab99933964724 100644 (file)
@@ -51,6 +51,7 @@
 extern void kernel_exception(void);
 extern void user_exception(void);
 
+extern void fast_illegal_instruction_user(void);
 extern void fast_syscall_user(void);
 extern void fast_alloca(void);
 extern void fast_unaligned(void);
@@ -87,6 +88,9 @@ typedef struct {
 
 static dispatch_init_table_t __initdata dispatch_init_table[] = {
 
+#ifdef CONFIG_USER_ABI_CALL0_PROBE
+{ EXCCAUSE_ILLEGAL_INSTRUCTION,        USER,      fast_illegal_instruction_user },
+#endif
 { EXCCAUSE_ILLEGAL_INSTRUCTION,        0,         do_illegal_instruction},
 { EXCCAUSE_SYSTEM_CALL,                USER,      fast_syscall_user },
 { EXCCAUSE_SYSTEM_CALL,                0,         system_call },