FEAT_SME adds the TPIDR2 userspace-accessible system register, which
is used as part of the procedure calling standard's lazy saving
scheme for the ZA registers:
https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#66the-za-lazy-saving-scheme
The Linux kernel has a signal frame record for saving
and restoring this value when calling signal handlers, but
we forgot to implement this. The result is that code which
tries to unwind an exception out of a signal handler will
not work correctly.
Add support for the missing record.
Cc: qemu-stable@nongnu.org
Fixes: 78011586b90d1 ("target/arm: Enable SME for user-only")
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
---
linux-user/aarch64/signal.c | 46 +++++++++++++++++++++++++++++++++++--
1 file changed, 44 insertions(+), 2 deletions(-)
diff --git a/linux-user/aarch64/signal.c b/linux-user/aarch64/signal.c
index b4bab7c040d..9a903576add 100644
--- a/linux-user/aarch64/signal.c
+++ b/linux-user/aarch64/signal.c
@@ -121,6 +121,13 @@ struct target_za_context {
#define TARGET_ZA_SIG_CONTEXT_SIZE(VQ) \
TARGET_ZA_SIG_ZAV_OFFSET(VQ, VQ * TARGET_SVE_VQ_BYTES)
+#define TARGET_TPIDR2_MAGIC 0x54504902
+
+struct target_tpidr2_context {
+ struct target_aarch64_ctx head;
+ uint64_t tpidr2;
+};
+
struct target_rt_sigframe {
struct target_siginfo info;
struct target_ucontext uc;
@@ -253,6 +260,14 @@ static void target_setup_za_record(struct target_za_context *za,
}
}
+static void target_setup_tpidr2_record(struct target_tpidr2_context *tpidr2,
+ CPUARMState *env, int size)
+{
+ __put_user(TARGET_TPIDR2_MAGIC, &tpidr2->head.magic);
+ __put_user(sizeof(struct target_tpidr2_context), &tpidr2->head.size);
+ __put_user(env->cp15.tpidr2_el0, &tpidr2->tpidr2);
+}
+
static void target_restore_general_frame(CPUARMState *env,
struct target_rt_sigframe *sf)
{
@@ -403,6 +418,16 @@ static bool target_restore_za_record(CPUARMState *env,
return true;
}
+static bool target_restore_tpidr2_record(CPUARMState *env,
+ struct target_tpidr2_context *tpidr2)
+{
+ if (!cpu_isar_feature(aa64_sme, env_archcpu(env))) {
+ return false;
+ }
+ __get_user(env->cp15.tpidr2_el0, &tpidr2->tpidr2);
+ return true;
+}
+
static int target_restore_sigframe(CPUARMState *env,
struct target_rt_sigframe *sf)
{
@@ -410,6 +435,7 @@ static int target_restore_sigframe(CPUARMState *env,
struct target_fpsimd_context *fpsimd = NULL;
struct target_sve_context *sve = NULL;
struct target_za_context *za = NULL;
+ struct target_tpidr2_context *tpidr2 = NULL;
uint64_t extra_datap = 0;
bool used_extra = false;
int sve_size = 0;
@@ -460,6 +486,13 @@ static int target_restore_sigframe(CPUARMState *env,
za_size = size;
break;
+ case TARGET_TPIDR2_MAGIC:
+ if (tpidr2 || size != sizeof(struct target_tpidr2_context)) {
+ goto err;
+ }
+ tpidr2 = (struct target_tpidr2_context *)ctx;
+ break;
+
case TARGET_EXTRA_MAGIC:
if (extra || size != sizeof(struct target_extra_context)) {
goto err;
@@ -497,6 +530,9 @@ static int target_restore_sigframe(CPUARMState *env,
if (za && !target_restore_za_record(env, za, za_size, &svcr)) {
goto err;
}
+ if (tpidr2 && !target_restore_tpidr2_record(env, tpidr2)) {
+ goto err;
+ }
if (env->svcr != svcr) {
env->svcr = svcr;
arm_rebuild_hflags(env);
@@ -568,8 +604,8 @@ static void target_setup_frame(int usig, struct target_sigaction *ka,
.total_size = offsetof(struct target_rt_sigframe,
uc.tuc_mcontext.__reserved),
};
- int fpsimd_ofs, fr_ofs, sve_ofs = 0, za_ofs = 0;
- int sve_size = 0, za_size = 0;
+ int fpsimd_ofs, fr_ofs, sve_ofs = 0, za_ofs = 0, tpidr2_ofs = 0;
+ int sve_size = 0, za_size = 0, tpidr2_size = 0;
struct target_rt_sigframe *frame;
struct target_rt_frame_record *fr;
abi_ulong frame_addr, return_addr;
@@ -585,6 +621,8 @@ static void target_setup_frame(int usig, struct target_sigaction *ka,
sve_ofs = alloc_sigframe_space(sve_size, &layout);
}
if (cpu_isar_feature(aa64_sme, env_archcpu(env))) {
+ tpidr2_size = sizeof(struct target_tpidr2_context);
+ tpidr2_ofs = alloc_sigframe_space(tpidr2_size, &layout);
/* ZA state needs saving only if it is enabled. */
if (FIELD_EX64(env->svcr, SVCR, ZA)) {
za_size = TARGET_ZA_SIG_CONTEXT_SIZE(sme_vq(env));
@@ -644,6 +682,10 @@ static void target_setup_frame(int usig, struct target_sigaction *ka,
if (za_ofs) {
target_setup_za_record((void *)frame + za_ofs, env, za_size);
}
+ if (tpidr2_ofs) {
+ target_setup_tpidr2_record((void *)frame + tpidr2_ofs,
+ env, tpidr2_size);
+ }
/* Set up the stack frame for unwinding. */
fr = (void *)frame + fr_ofs;
--
2.43.0
On 7/25/25 04:22, Peter Maydell wrote:
> FEAT_SME adds the TPIDR2 userspace-accessible system register, which
> is used as part of the procedure calling standard's lazy saving
> scheme for the ZA registers:
> https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#66the-za-lazy-saving-scheme
>
> The Linux kernel has a signal frame record for saving
> and restoring this value when calling signal handlers, but
> we forgot to implement this. The result is that code which
> tries to unwind an exception out of a signal handler will
> not work correctly.
>
> Add support for the missing record.
>
> Cc: qemu-stable@nongnu.org
> Fixes: 78011586b90d1 ("target/arm: Enable SME for user-only")
> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
> ---
> linux-user/aarch64/signal.c | 46 +++++++++++++++++++++++++++++++++++--
> 1 file changed, 44 insertions(+), 2 deletions(-)
Oh my. I spent yesterday working on this and other signal handling fixes. Though from a
FEAT_GCS starting point, so I still hadn't seen the clear tpidr2 on signal delivery change.
> +static void target_setup_tpidr2_record(struct target_tpidr2_context *tpidr2,
> + CPUARMState *env, int size)
> +{
> + __put_user(TARGET_TPIDR2_MAGIC, &tpidr2->head.magic);
> + __put_user(sizeof(struct target_tpidr2_context), &tpidr2->head.size);
> + __put_user(env->cp15.tpidr2_el0, &tpidr2->tpidr2);
> +}
Drop the unused size argument.
> +static bool target_restore_tpidr2_record(CPUARMState *env,
> + struct target_tpidr2_context *tpidr2)
> +{
> + if (!cpu_isar_feature(aa64_sme, env_archcpu(env))) {
> + return false;
> + }
> + __get_user(env->cp15.tpidr2_el0, &tpidr2->tpidr2);
> + return true;
> +}
The sme check is better placed in target_restore_sigframe ...
> + case TARGET_TPIDR2_MAGIC:
> + if (tpidr2 || size != sizeof(struct target_tpidr2_context)) {
> + goto err;
> + }
... here. Then target_restore_tpidr2_record has no failure modes and can return void ...
> @@ -497,6 +530,9 @@ static int target_restore_sigframe(CPUARMState *env,
> if (za && !target_restore_za_record(env, za, za_size, &svcr)) {
> goto err;
> }
> + if (tpidr2 && !target_restore_tpidr2_record(env, tpidr2)) {
> + goto err;
> + }
... which simplifies this.
r~
On Fri, 25 Jul 2025 at 17:39, Richard Henderson
<richard.henderson@linaro.org> wrote:
>
> On 7/25/25 04:22, Peter Maydell wrote:
> > FEAT_SME adds the TPIDR2 userspace-accessible system register, which
> > is used as part of the procedure calling standard's lazy saving
> > scheme for the ZA registers:
> > https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#66the-za-lazy-saving-scheme
> >
> > The Linux kernel has a signal frame record for saving
> > and restoring this value when calling signal handlers, but
> > we forgot to implement this. The result is that code which
> > tries to unwind an exception out of a signal handler will
> > not work correctly.
> >
> > Add support for the missing record.
> >
> > Cc: qemu-stable@nongnu.org
> > Fixes: 78011586b90d1 ("target/arm: Enable SME for user-only")
> > Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
> > ---
> > linux-user/aarch64/signal.c | 46 +++++++++++++++++++++++++++++++++++--
> > 1 file changed, 44 insertions(+), 2 deletions(-)
>
> Oh my. I spent yesterday working on this and other signal handling fixes. Though from a
> FEAT_GCS starting point, so I still hadn't seen the clear tpidr2 on signal delivery change.
>
>
> > +static void target_setup_tpidr2_record(struct target_tpidr2_context *tpidr2,
> > + CPUARMState *env, int size)
> > +{
> > + __put_user(TARGET_TPIDR2_MAGIC, &tpidr2->head.magic);
> > + __put_user(sizeof(struct target_tpidr2_context), &tpidr2->head.size);
> > + __put_user(env->cp15.tpidr2_el0, &tpidr2->tpidr2);
> > +}
>
> Drop the unused size argument.
>
> > +static bool target_restore_tpidr2_record(CPUARMState *env,
> > + struct target_tpidr2_context *tpidr2)
> > +{
> > + if (!cpu_isar_feature(aa64_sme, env_archcpu(env))) {
> > + return false;
> > + }
> > + __get_user(env->cp15.tpidr2_el0, &tpidr2->tpidr2);
> > + return true;
> > +}
>
> The sme check is better placed in target_restore_sigframe ...
>
> > + case TARGET_TPIDR2_MAGIC:
> > + if (tpidr2 || size != sizeof(struct target_tpidr2_context)) {
> > + goto err;
> > + }
>
> ... here. Then target_restore_tpidr2_record has no failure modes and can return void ...
>
> > @@ -497,6 +530,9 @@ static int target_restore_sigframe(CPUARMState *env,
> > if (za && !target_restore_za_record(env, za, za_size, &svcr)) {
> > goto err;
> > }
> > + if (tpidr2 && !target_restore_tpidr2_record(env, tpidr2)) {
> > + goto err;
> > + }
>
> ... which simplifies this.
Mmm. I wasn't sure to what extent we were trying to follow the
kernel signal.c (which defers essentially all error checks including
sizes to its restore_*_context functions, so that the "switch on
type of record" function is pretty much the same code for each
block, whether it's a complicated variable size one or a simple
fixed record.
-- PMM
© 2016 - 2025 Red Hat, Inc.