summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--target/riscv/op_helper.c17
1 files changed, 15 insertions, 2 deletions
diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c
index e34715df4e..7c6068bac9 100644
--- a/target/riscv/op_helper.c
+++ b/target/riscv/op_helper.c
@@ -144,8 +144,21 @@ void csr_write_helper(CPURISCVState *env, target_ulong val_to_write,
         }
 
         mstatus = (mstatus & ~mask) | (val_to_write & mask);
-        int dirty = (mstatus & MSTATUS_FS) == MSTATUS_FS;
-        dirty |= (mstatus & MSTATUS_XS) == MSTATUS_XS;
+
+        /* Note: this is a workaround for an issue where mstatus.FS
+           does not report dirty after floating point operations
+           that modify floating point state. This workaround is
+           technically compliant with the RISC-V Privileged
+           specification as it is legal to return only off, or dirty.
+           at the expense of extra floating point save/restore. */
+
+        /* FP is always dirty or off */
+        if (mstatus & MSTATUS_FS) {
+            mstatus |= MSTATUS_FS;
+        }
+
+        int dirty = ((mstatus & MSTATUS_FS) == MSTATUS_FS) |
+                    ((mstatus & MSTATUS_XS) == MSTATUS_XS);
         mstatus = set_field(mstatus, MSTATUS_SD, dirty);
         env->mstatus = mstatus;
         break;