diff options
Diffstat (limited to 'linux-user/signal.c')
-rw-r--r-- | linux-user/signal.c | 212 |
1 files changed, 193 insertions, 19 deletions
diff --git a/linux-user/signal.c b/linux-user/signal.c index f8bc0aa397..2ce5d7a3c7 100644 --- a/linux-user/signal.c +++ b/linux-user/signal.c @@ -1455,6 +1455,34 @@ struct target_extra_context { uint32_t reserved[3]; }; +#define TARGET_SVE_MAGIC 0x53564501 + +struct target_sve_context { + struct target_aarch64_ctx head; + uint16_t vl; + uint16_t reserved[3]; + /* The actual SVE data immediately follows. It is layed out + * according to TARGET_SVE_SIG_{Z,P}REG_OFFSET, based off of + * the original struct pointer. + */ +}; + +#define TARGET_SVE_VQ_BYTES 16 + +#define TARGET_SVE_SIG_ZREG_SIZE(VQ) ((VQ) * TARGET_SVE_VQ_BYTES) +#define TARGET_SVE_SIG_PREG_SIZE(VQ) ((VQ) * (TARGET_SVE_VQ_BYTES / 8)) + +#define TARGET_SVE_SIG_REGS_OFFSET \ + QEMU_ALIGN_UP(sizeof(struct target_sve_context), TARGET_SVE_VQ_BYTES) +#define TARGET_SVE_SIG_ZREG_OFFSET(VQ, N) \ + (TARGET_SVE_SIG_REGS_OFFSET + TARGET_SVE_SIG_ZREG_SIZE(VQ) * (N)) +#define TARGET_SVE_SIG_PREG_OFFSET(VQ, N) \ + (TARGET_SVE_SIG_ZREG_OFFSET(VQ, 32) + TARGET_SVE_SIG_PREG_SIZE(VQ) * (N)) +#define TARGET_SVE_SIG_FFR_OFFSET(VQ) \ + (TARGET_SVE_SIG_PREG_OFFSET(VQ, 16)) +#define TARGET_SVE_SIG_CONTEXT_SIZE(VQ) \ + (TARGET_SVE_SIG_PREG_OFFSET(VQ, 17)) + struct target_rt_sigframe { struct target_siginfo info; struct target_ucontext uc; @@ -1529,6 +1557,34 @@ static void target_setup_end_record(struct target_aarch64_ctx *end) __put_user(0, &end->size); } +static void target_setup_sve_record(struct target_sve_context *sve, + CPUARMState *env, int vq, int size) +{ + int i, j; + + __put_user(TARGET_SVE_MAGIC, &sve->head.magic); + __put_user(size, &sve->head.size); + __put_user(vq * TARGET_SVE_VQ_BYTES, &sve->vl); + + /* Note that SVE regs are stored as a byte stream, with each byte element + * at a subsequent address. This corresponds to a little-endian store + * of our 64-bit hunks. + */ + for (i = 0; i < 32; ++i) { + uint64_t *z = (void *)sve + TARGET_SVE_SIG_ZREG_OFFSET(vq, i); + for (j = 0; j < vq * 2; ++j) { + __put_user_e(env->vfp.zregs[i].d[j], z + j, le); + } + } + for (i = 0; i <= 16; ++i) { + uint16_t *p = (void *)sve + TARGET_SVE_SIG_PREG_OFFSET(vq, i); + for (j = 0; j < vq; ++j) { + uint64_t r = env->vfp.pregs[i].p[j >> 2]; + __put_user_e(r >> ((j & 3) * 16), p + j, le); + } + } +} + static void target_restore_general_frame(CPUARMState *env, struct target_rt_sigframe *sf) { @@ -1572,14 +1628,45 @@ static void target_restore_fpsimd_record(CPUARMState *env, } } +static void target_restore_sve_record(CPUARMState *env, + struct target_sve_context *sve, int vq) +{ + int i, j; + + /* Note that SVE regs are stored as a byte stream, with each byte element + * at a subsequent address. This corresponds to a little-endian load + * of our 64-bit hunks. + */ + for (i = 0; i < 32; ++i) { + uint64_t *z = (void *)sve + TARGET_SVE_SIG_ZREG_OFFSET(vq, i); + for (j = 0; j < vq * 2; ++j) { + __get_user_e(env->vfp.zregs[i].d[j], z + j, le); + } + } + for (i = 0; i <= 16; ++i) { + uint16_t *p = (void *)sve + TARGET_SVE_SIG_PREG_OFFSET(vq, i); + for (j = 0; j < vq; ++j) { + uint16_t r; + __get_user_e(r, p + j, le); + if (j & 3) { + env->vfp.pregs[i].p[j >> 2] |= (uint64_t)r << ((j & 3) * 16); + } else { + env->vfp.pregs[i].p[j >> 2] = r; + } + } + } +} + static int target_restore_sigframe(CPUARMState *env, struct target_rt_sigframe *sf) { struct target_aarch64_ctx *ctx, *extra = NULL; struct target_fpsimd_context *fpsimd = NULL; + struct target_sve_context *sve = NULL; uint64_t extra_datap = 0; bool used_extra = false; bool err = false; + int vq = 0, sve_size = 0; target_restore_general_frame(env, sf); @@ -1611,6 +1698,18 @@ static int target_restore_sigframe(CPUARMState *env, fpsimd = (struct target_fpsimd_context *)ctx; break; + case TARGET_SVE_MAGIC: + if (arm_feature(env, ARM_FEATURE_SVE)) { + vq = (env->vfp.zcr_el[1] & 0xf) + 1; + sve_size = QEMU_ALIGN_UP(TARGET_SVE_SIG_CONTEXT_SIZE(vq), 16); + if (!sve && size == sve_size) { + sve = (struct target_sve_context *)ctx; + break; + } + } + err = true; + goto exit; + case TARGET_EXTRA_MAGIC: if (extra || size != sizeof(struct target_extra_context)) { err = true; @@ -1640,12 +1739,18 @@ static int target_restore_sigframe(CPUARMState *env, err = true; } + /* SVE data, if present, overwrites FPSIMD data. */ + if (sve) { + target_restore_sve_record(env, sve, vq); + } + exit: unlock_user(extra, extra_datap, 0); return err; } -static abi_ulong get_sigframe(struct target_sigaction *ka, CPUARMState *env) +static abi_ulong get_sigframe(struct target_sigaction *ka, + CPUARMState *env, int size) { abi_ulong sp; @@ -1658,30 +1763,97 @@ static abi_ulong get_sigframe(struct target_sigaction *ka, CPUARMState *env) sp = target_sigaltstack_used.ss_sp + target_sigaltstack_used.ss_size; } - sp = (sp - sizeof(struct target_rt_sigframe)) & ~15; + sp = (sp - size) & ~15; return sp; } +typedef struct { + int total_size; + int extra_base; + int extra_size; + int std_end_ofs; + int extra_ofs; + int extra_end_ofs; +} target_sigframe_layout; + +static int alloc_sigframe_space(int this_size, target_sigframe_layout *l) +{ + /* Make sure there will always be space for the end marker. */ + const int std_size = sizeof(struct target_rt_sigframe) + - sizeof(struct target_aarch64_ctx); + int this_loc = l->total_size; + + if (l->extra_base) { + /* Once we have begun an extra space, all allocations go there. */ + l->extra_size += this_size; + } else if (this_size + this_loc > std_size) { + /* This allocation does not fit in the standard space. */ + /* Allocate the extra record. */ + l->extra_ofs = this_loc; + l->total_size += sizeof(struct target_extra_context); + + /* Allocate the standard end record. */ + l->std_end_ofs = l->total_size; + l->total_size += sizeof(struct target_aarch64_ctx); + + /* Allocate the requested record. */ + l->extra_base = this_loc = l->total_size; + l->extra_size = this_size; + } + l->total_size += this_size; + + return this_loc; +} + static void target_setup_frame(int usig, struct target_sigaction *ka, target_siginfo_t *info, target_sigset_t *set, CPUARMState *env) { - int size = offsetof(struct target_rt_sigframe, uc.tuc_mcontext.__reserved); - int fpsimd_ofs, end1_ofs, fr_ofs, end2_ofs = 0; - int extra_ofs = 0, extra_base = 0, extra_size = 0; + target_sigframe_layout layout = { + /* Begin with the size pointing to the reserved space. */ + .total_size = offsetof(struct target_rt_sigframe, + uc.tuc_mcontext.__reserved), + }; + int fpsimd_ofs, fr_ofs, sve_ofs = 0, vq = 0, sve_size = 0; struct target_rt_sigframe *frame; struct target_rt_frame_record *fr; abi_ulong frame_addr, return_addr; - fpsimd_ofs = size; - size += sizeof(struct target_fpsimd_context); - end1_ofs = size; - size += sizeof(struct target_aarch64_ctx); - fr_ofs = size; - size += sizeof(struct target_rt_frame_record); + /* FPSIMD record is always in the standard space. */ + fpsimd_ofs = alloc_sigframe_space(sizeof(struct target_fpsimd_context), + &layout); + + /* SVE state needs saving only if it exists. */ + if (arm_feature(env, ARM_FEATURE_SVE)) { + vq = (env->vfp.zcr_el[1] & 0xf) + 1; + sve_size = QEMU_ALIGN_UP(TARGET_SVE_SIG_CONTEXT_SIZE(vq), 16); + sve_ofs = alloc_sigframe_space(sve_size, &layout); + } - frame_addr = get_sigframe(ka, env); + if (layout.extra_ofs) { + /* Reserve space for the extra end marker. The standard end marker + * will have been allocated when we allocated the extra record. + */ + layout.extra_end_ofs + = alloc_sigframe_space(sizeof(struct target_aarch64_ctx), &layout); + } else { + /* Reserve space for the standard end marker. + * Do not use alloc_sigframe_space because we cheat + * std_size therein to reserve space for this. + */ + layout.std_end_ofs = layout.total_size; + layout.total_size += sizeof(struct target_aarch64_ctx); + } + + /* Reserve space for the return code. On a real system this would + * be within the VDSO. So, despite the name this is not a "real" + * record within the frame. + */ + fr_ofs = layout.total_size; + layout.total_size += sizeof(struct target_rt_frame_record); + + frame_addr = get_sigframe(ka, env, layout.total_size); trace_user_setup_frame(env, frame_addr); if (!lock_user_struct(VERIFY_WRITE, frame, frame_addr, 0)) { goto give_sigsegv; @@ -1689,13 +1861,15 @@ static void target_setup_frame(int usig, struct target_sigaction *ka, target_setup_general_frame(frame, env, set); target_setup_fpsimd_record((void *)frame + fpsimd_ofs, env); - if (extra_ofs) { - target_setup_extra_record((void *)frame + extra_ofs, - frame_addr + extra_base, extra_size); - } - target_setup_end_record((void *)frame + end1_ofs); - if (end2_ofs) { - target_setup_end_record((void *)frame + end2_ofs); + target_setup_end_record((void *)frame + layout.std_end_ofs); + if (layout.extra_ofs) { + target_setup_extra_record((void *)frame + layout.extra_ofs, + frame_addr + layout.extra_base, + layout.extra_size); + target_setup_end_record((void *)frame + layout.extra_end_ofs); + } + if (sve_ofs) { + target_setup_sve_record((void *)frame + sve_ofs, env, vq, sve_size); } /* Set up the stack frame for unwinding. */ |