diff mbox series

[8/8] linux-user/sparc64: Implement signals

Message ID 20210425155749.896330-9-richard.henderson@linaro.org
State New
Headers show
Series linux-user/sparc64: Implement signals | expand

Commit Message

Richard Henderson April 25, 2021, 3:57 p.m. UTC
We've been using the 32-bit sparc abi for 64-bit signals.
There's a surprising amount of difference, beginning with
the fact that 64-bit always uses rt signal frames.

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>

---
 linux-user/sparc/target_signal.h    |   2 +
 linux-user/sparc64/target_syscall.h |  14 +-
 linux-user/sparc64/signal.c         | 245 +++++++++++++++++++++++++++-
 3 files changed, 254 insertions(+), 7 deletions(-)

-- 
2.25.1

Comments

Richard Henderson April 25, 2021, 4:08 p.m. UTC | #1
On 4/25/21 8:57 AM, Richard Henderson wrote:
> We've been using the 32-bit sparc abi for 64-bit signals.

> There's a surprising amount of difference, beginning with

> the fact that 64-bit always uses rt signal frames.

> 

> Signed-off-by: Richard Henderson<richard.henderson@linaro.org>

> ---

>   linux-user/sparc/target_signal.h    |   2 +

>   linux-user/sparc64/target_syscall.h |  14 +-

>   linux-user/sparc64/signal.c         | 245 +++++++++++++++++++++++++++-

>   3 files changed, 254 insertions(+), 7 deletions(-)


Ho hum.  I just realized I've broken sparc32plus.
I'll have to reorg the ifdefs.


r~
diff mbox series

Patch

diff --git a/linux-user/sparc/target_signal.h b/linux-user/sparc/target_signal.h
index 911a3f5af5..651320ab8d 100644
--- a/linux-user/sparc/target_signal.h
+++ b/linux-user/sparc/target_signal.h
@@ -67,7 +67,9 @@  typedef struct target_sigaltstack {
 #define TARGET_MINSIGSTKSZ	4096
 #define TARGET_SIGSTKSZ		16384
 
+#if !defined(TARGET_SPARC64) || defined(TARGET_ABI32)
 #define TARGET_ARCH_HAS_SETUP_FRAME
+#endif
 
 /* bit-flags */
 #define TARGET_SS_AUTODISARM (1U << 31) /* disable sas during sighandling */
diff --git a/linux-user/sparc64/target_syscall.h b/linux-user/sparc64/target_syscall.h
index 696a68b1ed..fcc71db16e 100644
--- a/linux-user/sparc64/target_syscall.h
+++ b/linux-user/sparc64/target_syscall.h
@@ -4,14 +4,16 @@ 
 #include "../sparc/target_errno.h"
 
 struct target_pt_regs {
-	abi_ulong u_regs[16];
-	abi_ulong tstate;
-	abi_ulong pc;
-	abi_ulong npc;
-	abi_ulong y;
-	abi_ulong fprs;
+    abi_ulong u_regs[16];
+    abi_ulong tstate;
+    abi_ulong pc;
+    abi_ulong npc;
+    uint32_t y;
+    uint32_t magic;
 };
 
+#define TARGET_PT_REGS_MAGIC 0x57ac6c00
+
 #define UNAME_MACHINE "sparc64"
 #define UNAME_MINIMUM_RELEASE "2.6.32"
 
diff --git a/linux-user/sparc64/signal.c b/linux-user/sparc64/signal.c
index d27e049c2a..7ba811d342 100644
--- a/linux-user/sparc64/signal.c
+++ b/linux-user/sparc64/signal.c
@@ -17,7 +17,10 @@ 
  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "../sparc/signal.c"
+#include "qemu/osdep.h"
+#include "qemu.h"
+#include "signal-common.h"
+#include "linux-user/trace.h"
 
 #define SPARC_MC_TSTATE 0
 #define SPARC_MC_PC 1
@@ -295,3 +298,243 @@  void sparc64_get_context(CPUSPARCState *env)
     unlock_user_struct(ucp, ucp_addr, 1);
     force_sig(TARGET_SIGSEGV);
 }
+
+struct target_sparc_stackf {
+    struct target_reg_window win;
+    uint64_t xargs[8];
+};
+
+struct target_siginfo_fpu_t {
+    uint64_t dregs[32];
+    uint64_t fsr;
+    uint64_t gsr;
+    uint64_t fprs;
+};
+
+struct target_sigcontext {
+    target_siginfo_t info;
+    struct target_pt_regs regs;
+    uint64_t fpu_save;
+    target_stack_t stack;
+    target_sigset_t mask;
+    uint64_t rwin_save;
+};
+
+struct target_rt_sigframe {
+    struct target_sparc_stackf ss;
+    struct target_sigcontext sc;
+    struct target_siginfo_fpu_t fpu;
+};
+
+static abi_ulong get_sigframe(struct target_sigaction *sa,
+                              CPUSPARCState *env, int framesize)
+{
+    abi_ulong sp = target_sigsp(get_sp_from_cpustate(env), sa);
+    return (sp - framesize) & -16;
+}
+
+static void save_pt_regs(struct target_pt_regs *regs, CPUSPARCState *env)
+{
+    int i;
+
+    for (i = 0; i < 8; i++) {
+        __put_user(env->gregs[i], &regs->u_regs[i]);
+    }
+    for (i = 0; i < 8; i++) {
+        __put_user(env->regwptr[WREG_O0 + i], &regs->u_regs[i + 8]);
+    }
+    __put_user(sparc64_tstate(env), &regs->tstate);
+    __put_user(env->pc, &regs->pc);
+    __put_user(env->npc, &regs->npc);
+    __put_user(env->y, &regs->y);
+    __put_user(TARGET_PT_REGS_MAGIC, &regs->magic);
+}
+
+static void restore_pt_regs(struct target_pt_regs *regs, CPUSPARCState *env)
+{
+    uint64_t tstate;
+    int i;
+
+    for (i = 0; i < 8; i++) {
+        __get_user(env->gregs[i], &regs->u_regs[i]);
+    }
+    for (i = 0; i < 8; i++) {
+        __get_user(env->regwptr[WREG_O0 + i], &regs->u_regs[i + 8]);
+    }
+
+    __get_user(env->y, &regs->y);
+    __get_user(tstate, &regs->tstate);
+
+    /* User can only change condition codes and %asi in tstate. */
+    cpu_put_ccr(env, tstate >> 32);
+    env->asi = extract64(tstate, 24, 8);
+}
+
+static void save_fpu_state(struct target_siginfo_fpu_t *regs,
+                           CPUSPARCState *env)
+{
+    int i;
+
+    /* QEMU does not lazy fpu saving.  Save the entire fp register bank. */
+    for (i = 0; i < 32; ++i) {
+        __put_user(env->fpr[i].ll, &regs->dregs[i]);
+    }
+    __put_user(env->fsr, &regs->fsr);
+    __put_user(env->gsr, &regs->gsr);
+    __put_user(env->fprs, &regs->fprs);
+}
+
+static void restore_fpu_state(struct target_siginfo_fpu_t *regs,
+                              CPUSPARCState *env)
+{
+    uint64_t fprs;
+    int i;
+
+    /* In case the user mucks about with FPRS, restore as directed. */
+    __get_user(fprs, &regs->fprs);
+    if (fprs & FPRS_DL) {
+        for (i = 0; i < 16; ++i) {
+            __get_user(env->fpr[i].ll, &regs->dregs[i]);
+        }
+    }
+    if (fprs & FPRS_DU) {
+        for (i = 16; i < 32; ++i) {
+            __get_user(env->fpr[i].ll, &regs->dregs[i]);
+        }
+    }
+    __get_user(env->fsr, &regs->fsr);
+    __get_user(env->gsr, &regs->gsr);
+    env->fprs |= fprs;
+}
+
+void setup_rt_frame(int sig, struct target_sigaction *ka,
+                    target_siginfo_t *info,
+                    target_sigset_t *set, CPUSPARCState *env)
+{
+    abi_ulong sf_addr, sp;
+    struct target_rt_sigframe *sf = NULL;
+    void *window;
+
+    sf_addr = get_sigframe(ka, env, sizeof(*sf));
+    trace_user_setup_rt_frame(env, sf_addr);
+    if (!lock_user_struct(VERIFY_WRITE, sf, sf_addr, 0)) {
+        goto do_sigsegv;
+    }
+
+    /* 2. Save the current process state */
+    save_pt_regs(&sf->sc.regs, env);
+    save_fpu_state(&sf->fpu, env);
+    __put_user(sf_addr + offsetof(struct target_rt_sigframe, fpu),
+               &sf->sc.fpu_save);
+    __put_user(0, &sf->sc.rwin_save);  /* TODO: save_rwin_state */
+
+    /*
+     * Copy one register window from the top-of-stack into the signal frame.
+     * The balance of the sparc_stackf struct is for the callee --- the call
+     * abi requires the space for spilling argument registers.
+     */
+    sp = get_sp_from_cpustate(env);
+    window = lock_user(VERIFY_READ, sp, sizeof(struct target_reg_window), 1);
+    if (!window) {
+        goto do_sigsegv;
+    }
+    memcpy(sf, window, sizeof(struct target_reg_window));
+    unlock_user(window, sp, 0);
+
+    target_save_altstack(&sf->sc.stack, env);
+    for (int i = 0; i < TARGET_NSIG_WORDS; ++i) {
+        __put_user(set->sig[i], &sf->sc.mask.sig[i]);
+    }
+
+    unlock_user(sf, sf_addr, sizeof(*sf));
+
+    /* 3. signal handler back-trampoline and parameters */
+    env->regwptr[WREG_SP] = sf_addr - TARGET_STACK_BIAS;
+    env->regwptr[WREG_O0] = sig;
+    env->regwptr[WREG_O1] = sf_addr + offsetof(struct target_rt_sigframe, sc);
+    env->regwptr[WREG_O2] = sf_addr + offsetof(struct target_rt_sigframe, sc);
+
+    /* 4. return to kernel instructions */
+    env->regwptr[WREG_O7] = ka->ka_restorer;
+
+    /* 5. signal handler */
+    env->pc = ka->_sa_handler;
+    env->npc = env->pc + 4;
+    return;
+
+ do_sigsegv:
+    unlock_user(sf, sf_addr, 0);
+    force_sigsegv(sig);
+}
+
+/*
+ * __NR_sigreturn still exists for backward compatiblity,
+ * but it is set to sys_nis_syscall for sparc64.
+ */
+long do_sigreturn(CPUSPARCState *env)
+{
+    return -TARGET_ENOSYS;
+}
+
+long do_rt_sigreturn(CPUSPARCState *env)
+{
+    abi_ulong sf_addr, sc_addr, tpc, tnpc, ptr;
+    struct target_sigcontext *sc = NULL;
+    sigset_t set;
+
+    sf_addr = get_sp_from_cpustate(env);
+    trace_user_do_rt_sigreturn(env, sf_addr);
+
+    if (sf_addr & 15) {
+        goto do_sigsegv;
+    }
+    sc_addr = sf_addr + offsetof(struct target_rt_sigframe, sc);
+    if (!lock_user_struct(VERIFY_READ, sc, sc_addr, 1)) {
+        goto do_sigsegv;
+    }
+
+    /* Validate SP alignment.  */
+    __get_user(ptr, &sc->regs.u_regs[8 + WREG_SP]);
+    if ((ptr + TARGET_STACK_BIAS) & 7) {
+        goto do_sigsegv;
+    }
+
+    /* Validate PC and NPC alignment.  */
+    __get_user(tpc, &sc->regs.pc);
+    __get_user(tnpc, &sc->regs.npc);
+    if ((tpc | tnpc) & 3) {
+        goto do_sigsegv;
+    }
+
+    restore_pt_regs(&sc->regs, env);
+
+    __get_user(ptr, &sc->fpu_save);
+    if (ptr) {
+        struct target_siginfo_fpu_t *fpu;
+        if ((ptr & 7) || !lock_user_struct(VERIFY_READ, fpu, ptr, 1)) {
+            goto do_sigsegv;
+        }
+        restore_fpu_state(fpu, env);
+        unlock_user_struct(fpu, ptr, 0);
+    }
+
+    __get_user(ptr, &sc->rwin_save);
+    if (ptr) {
+        goto do_sigsegv;  /* TODO: restore_rwin_state */
+    }
+
+    target_to_host_sigset(&set, &sc->mask);
+    set_sigmask(&set);
+    target_restore_altstack(&sc->stack, env);
+
+    env->pc = tpc;
+    env->npc = tnpc;
+
+    unlock_user_struct(sc, sc_addr, 0);
+    return -TARGET_QEMU_ESIGRETURN;
+
+ do_sigsegv:
+    unlock_user_struct(sc, sc_addr, 0);
+    force_sig(TARGET_SIGSEGV);
+    return -TARGET_QEMU_ESIGRETURN;
+}