Skip to content

Writing a reserved frm value through fcsr silently drops the fflags update #274

Description

@carlosqwqqwq

Writing a reserved frm value through fcsr silently drops the fflags update

Summary

In the current RVVM revision, a guest write to fcsr that carries a reserved rounding-mode value in the frm field (101/110/111) is rejected as a whole: riscv_update_fcsr() returns early and the fflags bits written by the same instruction are lost. On real RV64 hardware and in QEMU, the fflags field update takes effect and only the reserved frm value is handled per WARL semantics (kept or mapped to a supported value). The bug silently corrupts floating-point exception state restoration for any software that writes fcsr with a non-canonical frm value.

Affected revision

current master as of 2026-08-04 (master at reproduction time).

Reproduction

Build the attached probe source (a single C file) with the RISC-V GNU toolchain and run it: riscv64-linux-gnu-gcc -static -O2 -march=rv64gc. The probe performs, among others, this sequence:

csr_write(FCSR, 0);
csr_write(FCSR, (5 << 5) | 0x1f);   /* reserved frm=5, all fflags set */
printf("fcsr=%08" PRIx64 "...", csr_read(FCSR));

Observed behavior

Real RV64 hardware (native RISC-V hardware, Linux 6.6.63) and QEMU print:

case2_frm5 fcsr=000000bf frm=5 fflags=1f

The current RVVM build prints:

case2_frm5 fcsr=00000000 frm=0 fflags=0

RVVM drops the complete write, including the fflags update. The same behavior occurs for frm=6 and frm=7, and for a direct frm CSR write of a reserved value (where QEMU/hardware keep the written value; WARL permits either keeping or mapping it).

Root cause

src/cpu/riscv_csr.c, riscv_update_fcsr():

if (unlikely(new_frm != old_frm)) {
    if (new_frm > RM_RMM) {
        // Invalid rounding mode written
        return;
    } else {
        // Set host rounding mode
        fpu_set_rounding_mode(new_frm);
    }
}

The early return aborts the whole CSR write. fcsr is a single CSR whose fflags field (bits 4:0) is a plain read/write field; only the frm field (bits 7:5) is WARL. The fflags update must be applied regardless of the reserved frm value.

Minimal fix

Keep the old frm value (WARL) and continue applying the remaining fields:

if (new_frm > RM_RMM) {
    // Invalid rounding mode written: keep the old FRM value per
    // WARL semantics, but still apply the other fields of fcsr
    new_fcsr = bit_replace(new_fcsr, 5, 3, old_frm);
    new_frm  = old_frm;
} else {
    // Set host rounding mode
    fpu_set_rounding_mode(new_frm);
}

The proposed change contains this change. In an isolated source copy of the affected revision, the patched build prints:

case2_frm5 fcsr=0000001f frm=0 fflags=1f

(fflags applied; reserved frm mapped to the previous value, which is the WARL-preserving choice).

Specification references

  • RISC-V unpriv spec, F extension: fcsr/frm/fflags field layout and reserved rounding-mode encodings 101-110 (static) and 101-111 (dynamic).
  • RISC-V priv spec, CSR field semantics: WARL fields accept any written value and return a legal value on read; only the WARL field itself is affected by an unsupported write.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions