Commit 2762cd51ee for qemu.org
commit 2762cd51ee033dccb3167110376dd125244cc819
Author: Matt Turner <mattst88@gmail.com>
Date: Tue May 26 11:05:33 2026 -0400
linux-user/s390x: restore fpu_status rounding mode from FPC on sigreturn
QEMU keeps the s390x floating-point control register (FPC) in env->fpc.
The rounding mode bits [2:0] of FPC are reflected into the derived
env->fpu_status via set_float_rounding_mode(); every architectural
write to FPC goes through HELPER(sfpc) which keeps the two in sync.
restore_sigregs() restored FPC with a direct assignment:
__get_user(env->fpc, &sc->fpregs.fpc);
This wrote env->fpc correctly but never updated env->fpu_status, so on
sigreturn the interrupted code resumed with whatever rounding mode the
signal handler last installed in fpu_status.
Factor the two-step "write fpc + sync fpu_status" logic out of
HELPER(sfpc) into cpu_s390x_load_fpc(), declare it in cpu.h, and call
it from restore_sigregs() in place of the direct assignment.
cpu_s390x_load_fpc() partially reuses the sanity check from
HELPER(sfpc): if the FPC value has an invalid rounding mode or reserved
bits set, it falls back to 0, matching the kernel's fpu_lfpc_safe()
behavior where a corrupt signal frame value causes a specification
exception and 0 is used instead.
HELPER(sfpc) now calls cpu_s390x_load_fpc() after its full
specification-exception check, including the FEAT_FLOATING_POINT_EXT
test that is not needed for the signal restore path.
Fixes: 2941e0fa05 ("linux-user/s390x: Save/restore fpc when handling a signal")
Cc: qemu-stable@nongnu.org
Signed-off-by: Matt Turner <mattst88@gmail.com>
Signed-off-by: Helge Deller <deller@gmx.de>
diff --git a/linux-user/s390x/signal.c b/linux-user/s390x/signal.c
index 96d1c8d11c..28ad80bde4 100644
--- a/linux-user/s390x/signal.c
+++ b/linux-user/s390x/signal.c
@@ -332,7 +332,11 @@ static void restore_sigregs(CPUS390XState *env, target_sigregs *sc)
for (i = 0; i < 16; i++) {
__get_user(env->aregs[i], &sc->regs.acrs[i]);
}
- __get_user(env->fpc, &sc->fpregs.fpc);
+ {
+ uint32_t fpc;
+ __get_user(fpc, &sc->fpregs.fpc);
+ cpu_s390x_load_fpc(env, fpc);
+ }
for (i = 0; i < 16; i++) {
__get_user(*get_freg(env, i), &sc->fpregs.fprs[i]);
}
diff --git a/target/s390x/cpu.h b/target/s390x/cpu.h
index 3acbe83f0f..f55b79ef8a 100644
--- a/target/s390x/cpu.h
+++ b/target/s390x/cpu.h
@@ -895,6 +895,7 @@ void s390_init_sigp(void);
/* helper.c */
void s390_cpu_set_psw(CPUS390XState *env, uint64_t mask, uint64_t addr);
uint64_t s390_cpu_get_psw_mask(CPUS390XState *env);
+void cpu_s390x_load_fpc(CPUS390XState *env, uint32_t fpc);
/* outside of target/s390x/ */
S390CPU *s390_cpu_addr2state(uint16_t cpu_addr);
diff --git a/target/s390x/tcg/fpu_helper.c b/target/s390x/tcg/fpu_helper.c
index 6ca0b7162b..107025e675 100644
--- a/target/s390x/tcg/fpu_helper.c
+++ b/target/s390x/tcg/fpu_helper.c
@@ -1087,6 +1087,19 @@ static const int fpc_to_rnd[8] = {
float_round_to_odd,
};
+void cpu_s390x_load_fpc(CPUS390XState *env, uint32_t fpc)
+{
+ /*
+ * Mimic kernel fpu_lfpc_safe(): a corrupt signal frame value that would
+ * trigger a specification exception instead results in FPC being set to 0.
+ */
+ if (fpc_to_rnd[fpc & 0x7] == -1 || fpc & 0x03030088u) {
+ fpc = 0;
+ }
+ env->fpc = fpc;
+ set_float_rounding_mode(fpc_to_rnd[fpc & 0x7], &env->fpu_status);
+}
+
/* set fpc */
void HELPER(sfpc)(CPUS390XState *env, uint64_t fpc)
{
@@ -1094,12 +1107,7 @@ void HELPER(sfpc)(CPUS390XState *env, uint64_t fpc)
(!s390_has_feat(S390_FEAT_FLOATING_POINT_EXT) && fpc & 0x4)) {
tcg_s390_program_interrupt(env, PGM_SPECIFICATION, GETPC());
}
-
- /* Install everything in the main FPC. */
- env->fpc = fpc;
-
- /* Install the rounding mode in the shadow fpu_status. */
- set_float_rounding_mode(fpc_to_rnd[fpc & 0x7], &env->fpu_status);
+ cpu_s390x_load_fpc(env, fpc);
}
/* set fpc and signal */