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 */