[PATCH v3 4/5] linux-user/riscv: Add vector state to signal context

Nicholas Piggin posted 5 patches 1 day ago
[PATCH v3 4/5] linux-user/riscv: Add vector state to signal context
Posted by Nicholas Piggin 1 day ago
This enables vector state to be saved and restored across signals.

Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
 linux-user/riscv/signal.c    | 175 +++++++++++++++++++++++++++++++++--
 target/riscv/cpu.h           |   4 +
 target/riscv/csr.c           |   7 +-
 target/riscv/vector_helper.c |  19 +++-
 4 files changed, 191 insertions(+), 14 deletions(-)

diff --git a/linux-user/riscv/signal.c b/linux-user/riscv/signal.c
index e20b9ac177..2e1a1a5027 100644
--- a/linux-user/riscv/signal.c
+++ b/linux-user/riscv/signal.c
@@ -41,7 +41,17 @@ struct target_fp_state {
     uint32_t fcsr;
 };
 
+struct target_v_ext_state {
+    abi_ulong vstart;
+    abi_ulong vl;
+    abi_ulong vtype;
+    abi_ulong vcsr;
+    abi_ulong vlenb;
+    abi_ptr   datap;
+};
+
 /* The Magic number for signal context frame header. */
+#define RISCV_V_MAGIC   0x53465457
 #define END_MAGIC       0x0
 
 /* The size of END signal context header. */
@@ -106,6 +116,130 @@ static abi_ulong get_sigframe(struct target_sigaction *ka,
     return sp;
 }
 
+static unsigned int get_v_state_hdr_size(CPURISCVState *env)
+{
+    return sizeof(struct target_ctx_hdr) +
+           sizeof(struct target_v_ext_state);
+}
+
+static unsigned int get_v_state_data_size(CPURISCVState *env)
+{
+    RISCVCPU *cpu = env_archcpu(env);
+    return cpu->cfg.vlenb * 32;
+}
+
+static struct target_ctx_hdr *save_v_state(CPURISCVState *env,
+                                           struct target_ctx_hdr *hdr)
+{
+    RISCVCPU *cpu = env_archcpu(env);
+    target_ulong vlenb = cpu->cfg.vlenb;
+    uint32_t riscv_v_sc_size = get_v_state_hdr_size(env) +
+                               get_v_state_data_size(env);
+    struct target_v_ext_state *vs;
+    abi_ulong vcsr;
+    abi_ptr datap;
+    void *host_datap;
+
+#ifdef CONFIG_DEBUG_REMAP
+    /*
+     * The host pointers are derived from lock_user, not g2h, so
+     * h2g can not be used when CONFIG_DEBUG_REMAP=y.
+     */
+    qemu_log_mask(LOG_UNIMP, "signal: sigcontext can not save V state "
+                             "when CONFIG_DEBUG_REMAP=y\n");
+    return hdr;
+#endif
+
+    vs = (struct target_v_ext_state *)(hdr + 1);
+    vcsr = riscv_csr_read(env, CSR_VCSR);
+    host_datap = (vs + 1);
+    datap = h2g(host_datap);
+
+    __put_user(RISCV_V_MAGIC, &hdr->magic);
+    __put_user(riscv_v_sc_size, &hdr->size);
+
+    __put_user(env->vstart, &vs->vstart);
+    __put_user(env->vl, &vs->vl);
+    __put_user(env->vtype, &vs->vtype);
+    __put_user(vcsr, &vs->vcsr);
+    __put_user(vlenb, &vs->vlenb);
+    __put_user(datap, &vs->datap);
+
+    for (int i = 0; i < 32; i++) {
+        for (int j = 0; j < vlenb; j += 8) {
+            size_t idx = (i * vlenb + j);
+            __put_user(env->vreg[idx / 8],
+                       (uint64_t *)(host_datap + idx));
+        }
+    }
+
+    return (void *)hdr + riscv_v_sc_size;
+}
+
+static bool restore_v_state(CPURISCVState *env,
+                            struct target_ctx_hdr *hdr)
+{
+    RISCVCPU *cpu = env_archcpu(env);
+    target_ulong vlenb;
+    target_ulong vcsr, vl, vtype, vstart;
+    struct target_v_ext_state *vs;
+    uint32_t size;
+    abi_ptr datap;
+    void *host_datap;
+
+    if (!riscv_has_ext(env, RVV)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext has V state but "
+                                       "CPU does not support V extension\n");
+        return false;
+    }
+
+    __get_user(size, &hdr->size);
+    if (size < get_v_state_hdr_size(env)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext V state header "
+                                       "size is too small (%u)\n", size);
+        return false;
+    }
+
+    vs = (struct target_v_ext_state *)(hdr + 1);
+
+    __get_user(vstart, &vs->vstart);
+    __get_user(vl, &vs->vl);
+    __get_user(vtype, &vs->vtype);
+    __get_user(vcsr, &vs->vcsr);
+
+    riscv_cpu_set_vstart(env, vstart);
+    riscv_cpu_vsetvl(env, vl, vtype, 0);
+    riscv_csr_write(env, CSR_VCSR, vcsr);
+
+    __get_user(vlenb, &vs->vlenb);
+
+    if (vlenb != cpu->cfg.vlenb) {
+        qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext has invalid "
+                                       "vlenb\n");
+        return false;
+    }
+
+    __get_user(datap, &vs->datap);
+
+    host_datap = lock_user(VERIFY_READ, datap, vlenb * 32, true);
+    if (!host_datap) {
+        qemu_log_mask(LOG_GUEST_ERROR, "signal: sigcontext has V state but "
+                                       "datap pointer is invalid\n");
+        return false;
+    }
+
+    for (int i = 0; i < 32; i++) {
+        for (int j = 0; j < vlenb; j += 8) {
+            size_t idx = (i * vlenb + j);
+            __get_user(env->vreg[idx / 8],
+                       (uint64_t *)(host_datap + idx));
+        }
+    }
+    unlock_user(host_datap, datap, 0);
+
+    return true;
+}
+
 static void setup_sigcontext(struct target_sigcontext *sc, CPURISCVState *env)
 {
     struct target_ctx_hdr *hdr;
@@ -126,6 +260,9 @@ static void setup_sigcontext(struct target_sigcontext *sc, CPURISCVState *env)
     __put_user(0, &sc->sc_extdesc.reserved);
 
     hdr = &sc->sc_extdesc.hdr;
+    if (riscv_has_ext(env, RVV)) {
+        hdr = save_v_state(env, hdr);
+    }
     __put_user(END_MAGIC, &hdr->magic);
     __put_user(END_HDR_SIZE, &hdr->size);
 }
@@ -152,17 +289,24 @@ void setup_rt_frame(int sig, struct target_sigaction *ka,
 {
     abi_ulong frame_addr;
     struct target_rt_sigframe *frame;
+    size_t frame_size = sizeof(*frame);
 
-    frame_addr = get_sigframe(ka, env, sizeof(*frame));
+    if (riscv_has_ext(env, RVV)) {
+        frame_size += get_v_state_hdr_size(env) +
+                      get_v_state_data_size(env);
+    }
+
+    frame_addr = get_sigframe(ka, env, frame_size);
     trace_user_setup_rt_frame(env, frame_addr);
 
-    if (!lock_user_struct(VERIFY_WRITE, frame, frame_addr, 0)) {
+    frame = lock_user(VERIFY_WRITE, frame_addr, frame_size, 0);
+    if (!frame) {
         goto badframe;
     }
 
     setup_ucontext(&frame->uc, env, set);
     frame->info = *info;
-    unlock_user_struct(frame, frame_addr, 1);
+    unlock_user(frame, frame_addr, frame_size);
 
     env->pc = ka->_sa_handler;
     env->gpr[xSP] = frame_addr;
@@ -174,7 +318,7 @@ void setup_rt_frame(int sig, struct target_sigaction *ka,
     return;
 
 badframe:
-    unlock_user_struct(frame, frame_addr, 1);
+    unlock_user(frame, frame_addr, frame_size);
     if (sig == TARGET_SIGSEGV) {
         ka->_sa_handler = TARGET_SIG_DFL;
     }
@@ -211,6 +355,11 @@ static bool restore_sigcontext(CPURISCVState *env, struct target_sigcontext *sc)
     __get_user(magic, &hdr->magic);
     while (magic != END_MAGIC) {
         switch (magic) {
+        case RISCV_V_MAGIC:
+            if (!restore_v_state(env, hdr)) {
+                return false;
+            }
+            break;
         default:
             qemu_log_mask(LOG_GUEST_ERROR, "signal: unknown extended state in "
                                            "sigcontext, magic=0x%08x\n", magic);
@@ -258,11 +407,23 @@ static bool restore_ucontext(CPURISCVState *env, struct target_ucontext *uc)
 long do_rt_sigreturn(CPURISCVState *env)
 {
     struct target_rt_sigframe *frame;
+    size_t frame_size = sizeof(*frame);
     abi_ulong frame_addr;
 
+    if (riscv_has_ext(env, RVV)) {
+        /*
+         * userspace may have set up a discontiguous V state data area,
+         * so need to map that region separately once the address is
+         * known, from datap.
+         */
+        frame_size += get_v_state_hdr_size(env);
+    }
+
     frame_addr = env->gpr[xSP];
     trace_user_do_sigreturn(env, frame_addr);
-    if (!lock_user_struct(VERIFY_READ, frame, frame_addr, 1)) {
+
+    frame = lock_user(VERIFY_READ, frame_addr, frame_size, 1);
+    if (!frame) {
         goto badframe;
     }
 
@@ -272,11 +433,11 @@ long do_rt_sigreturn(CPURISCVState *env)
 
     target_restore_altstack(&frame->uc.uc_stack, env);
 
-    unlock_user_struct(frame, frame_addr, 0);
+    unlock_user(frame, frame_addr, 0);
     return -QEMU_ESIGRETURN;
 
 badframe:
-    unlock_user_struct(frame, frame_addr, 0);
+    unlock_user(frame, frame_addr, 0);
     force_sig(TARGET_SIGSEGV);
     return 0;
 }
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 35d1f6362c..e1eca79197 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -668,6 +668,10 @@ G_NORETURN void riscv_raise_exception(CPURISCVState *env,
 target_ulong riscv_cpu_get_fflags(CPURISCVState *env);
 void riscv_cpu_set_fflags(CPURISCVState *env, target_ulong);
 
+void riscv_cpu_set_vstart(CPURISCVState *env, target_ulong val);
+target_ulong riscv_cpu_vsetvl(CPURISCVState *env, target_ulong s1,
+                              target_ulong s2, target_ulong x0);
+
 #ifndef CONFIG_USER_ONLY
 void cpu_set_exception_base(int vp_index, target_ulong address);
 #endif
diff --git a/target/riscv/csr.c b/target/riscv/csr.c
index 5064483917..8a6fd11fb5 100644
--- a/target/riscv/csr.c
+++ b/target/riscv/csr.c
@@ -991,11 +991,8 @@ static RISCVException write_vstart(CPURISCVState *env, int csrno,
 #if !defined(CONFIG_USER_ONLY)
     env->mstatus |= MSTATUS_VS;
 #endif
-    /*
-     * The vstart CSR is defined to have only enough writable bits
-     * to hold the largest element index, i.e. lg2(VLEN) bits.
-     */
-    env->vstart = val & ~(~0ULL << ctzl(riscv_cpu_cfg(env)->vlenb << 3));
+    riscv_cpu_set_vstart(env, val);
+
     return RISCV_EXCP_NONE;
 }
 
diff --git a/target/riscv/vector_helper.c b/target/riscv/vector_helper.c
index caa8dd9c12..bceefe019b 100644
--- a/target/riscv/vector_helper.c
+++ b/target/riscv/vector_helper.c
@@ -33,8 +33,17 @@
 #include "vector_internals.h"
 #include <math.h>
 
-target_ulong HELPER(vsetvl)(CPURISCVState *env, target_ulong s1,
-                            target_ulong s2, target_ulong x0)
+void riscv_cpu_set_vstart(CPURISCVState *env, target_ulong val)
+{
+    /*
+     * The vstart CSR is defined to have only enough writable bits
+     * to hold the largest element index, i.e. lg2(VLEN) bits.
+     */
+    env->vstart = val & ~(~0ULL << ctzl(riscv_cpu_cfg(env)->vlenb << 3));
+}
+
+target_ulong riscv_cpu_vsetvl(CPURISCVState *env, target_ulong s1,
+                              target_ulong s2, target_ulong x0)
 {
     int vlmax, vl;
     RISCVCPU *cpu = env_archcpu(env);
@@ -99,6 +108,12 @@ target_ulong HELPER(vsetvl)(CPURISCVState *env, target_ulong s1,
     return vl;
 }
 
+target_ulong HELPER(vsetvl)(CPURISCVState *env, target_ulong s1,
+                            target_ulong s2, target_ulong x0)
+{
+    return riscv_cpu_vsetvl(env, s1, s2, x0);
+}
+
 /*
  * Get the maximum number of elements can be operated.
  *
-- 
2.51.0