Commit 57b484e2d2 for qemu.org
commit 57b484e2d232423c95ce559919b9624041254b0c
Author: Zephyr Li <fritchleybohrer@gmail.com>
Date: Thu Oct 8 11:51:50 2026 +0800
target/riscv: fix RV32 fixed counter accesses
Since commit cfc96df65e01, riscv_pmu_ctr_get_fixed_counters_val()
returns the complete 64-bit fixed-counter value. The RV32 counter
access paths, however, still perform parts of the offset calculation
on separately extracted 32-bit halves.
In particular, riscv_pmu_write_ctrh() deposits the low 32 bits of the
complete fixed-counter value into the high half of mhpmcounter_prev.
riscv_pmu_read_ctr() also subtracts a 32-bit half of the previous value
from the complete 64-bit fixed-counter value. Consequently, writes to
mcycleh can be lost and carries between the low and high halves are not
handled correctly.
Keep the fixed-counter offset calculation entirely in 64 bits. Before
a running counter is partially written, materialize its current
architectural value and reset the fixed-counter baseline. On reads,
calculate the complete 64-bit counter value before extracting the half
requested by RV32.
Register RV32 system TCG tests with the Meson build. Add a test for
high-half writes, low-to-high carry, and preserving the carried high
half across a subsequent low-half write.
Fixes: cfc96df65e01 ("target/riscv: Remove upper_half from riscv_pmu_ctr_get_fixed_counters_val")
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4219
Signed-off-by: Zephyr Li <fritchleybohrer@gmail.com>
Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>
Message-ID: <20261008035150.2223-1-fritchleybohrer@gmail.com>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
diff --git a/MAINTAINERS b/MAINTAINERS
index 0455e8e6b9..b3ed343e17 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -377,6 +377,7 @@ F: include/hw/riscv/
F: common-user/host/riscv*
F: tests/functional/riscv32
F: tests/functional/riscv64
+F: tests/tcg/riscv32/
F: tests/tcg/riscv64/
F: tests/qtest/iommu-riscv-test.c
diff --git a/meson_options.txt b/meson_options.txt
index c9247e9de1..895c6e63c2 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -473,6 +473,10 @@ option('tcg_tests_cross_cc_ppc64le', type: 'string',
description: 'cc for ppc64le tcg tests')
option('tcg_tests_cross_cflags_ppc64le', type: 'string',
description: 'cflags for ppc64le tcg tests')
+option('tcg_tests_cross_cc_riscv32', type: 'string',
+ description: 'cc for riscv32 tcg tests')
+option('tcg_tests_cross_cflags_riscv32', type: 'string',
+ description: 'cflags for riscv32 tcg tests')
option('tcg_tests_cross_cc_riscv64', type: 'string',
description: 'cc for riscv64 tcg tests')
option('tcg_tests_cross_cflags_riscv64', type: 'string',
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index 064e1429a4..14990966d3 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -547,6 +547,7 @@ _meson_option_parse() {
--tcg-tests-cross-cc-or1k=*) quote_sh "-Dtcg_tests_cross_cc_or1k=$2" ;;
--tcg-tests-cross-cc-ppc64=*) quote_sh "-Dtcg_tests_cross_cc_ppc64=$2" ;;
--tcg-tests-cross-cc-ppc64le=*) quote_sh "-Dtcg_tests_cross_cc_ppc64le=$2" ;;
+ --tcg-tests-cross-cc-riscv32=*) quote_sh "-Dtcg_tests_cross_cc_riscv32=$2" ;;
--tcg-tests-cross-cc-riscv64=*) quote_sh "-Dtcg_tests_cross_cc_riscv64=$2" ;;
--tcg-tests-cross-cc-s390x=*) quote_sh "-Dtcg_tests_cross_cc_s390x=$2" ;;
--tcg-tests-cross-cc-sh4=*) quote_sh "-Dtcg_tests_cross_cc_sh4=$2" ;;
@@ -573,6 +574,7 @@ _meson_option_parse() {
--tcg-tests-cross-cflags-or1k=*) quote_sh "-Dtcg_tests_cross_cflags_or1k=$2" ;;
--tcg-tests-cross-cflags-ppc64=*) quote_sh "-Dtcg_tests_cross_cflags_ppc64=$2" ;;
--tcg-tests-cross-cflags-ppc64le=*) quote_sh "-Dtcg_tests_cross_cflags_ppc64le=$2" ;;
+ --tcg-tests-cross-cflags-riscv32=*) quote_sh "-Dtcg_tests_cross_cflags_riscv32=$2" ;;
--tcg-tests-cross-cflags-riscv64=*) quote_sh "-Dtcg_tests_cross_cflags_riscv64=$2" ;;
--tcg-tests-cross-cflags-s390x=*) quote_sh "-Dtcg_tests_cross_cflags_s390x=$2" ;;
--tcg-tests-cross-cflags-sh4=*) quote_sh "-Dtcg_tests_cross_cflags_sh4=$2" ;;
diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
index 2ad66239ae..64be8ca309 100644
--- a/target/riscv/tcg/csr.c
+++ b/target/riscv/tcg/csr.c
@@ -1340,23 +1340,23 @@ static RISCVException riscv_pmu_write_ctr(CPURISCVState *env, target_ulong val,
int deposit_size = rv32 ? 32 : 64;
uint64_t ctr;
- counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
- 0, deposit_size, val);
-
if (!get_field(env->mcountinhibit, BIT(ctr_idx)) &&
(riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
riscv_pmu_ctr_monitor_instructions(env, ctr_idx))) {
ctr = riscv_pmu_ctr_get_fixed_counters_val(env, ctr_idx);
- counter->mhpmcounter_prev = deposit64(counter->mhpmcounter_prev,
- 0, deposit_size, ctr);
+ counter->mhpmcounter_val += ctr - counter->mhpmcounter_prev;
+ counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
+ 0, deposit_size, val);
+ counter->mhpmcounter_prev = ctr;
if (ctr_idx > 2) {
riscv_pmu_setup_timer(env, counter->mhpmcounter_val, ctr_idx);
}
} else {
+ counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
+ 0, deposit_size, val);
/* Other counters can keep incrementing from the given value */
counter->mhpmcounter_prev = deposit64(counter->mhpmcounter_prev,
0, deposit_size, val);
-
}
return RISCV_EXCP_NONE;
@@ -1366,20 +1366,22 @@ static RISCVException riscv_pmu_write_ctrh(CPURISCVState *env, target_ulong val,
uint32_t ctr_idx)
{
PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
- uint64_t ctrh;
+ uint64_t ctr;
- counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
- 32, 32, val);
if (!get_field(env->mcountinhibit, BIT(ctr_idx)) &&
(riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
riscv_pmu_ctr_monitor_instructions(env, ctr_idx))) {
- ctrh = riscv_pmu_ctr_get_fixed_counters_val(env, ctr_idx);
- counter->mhpmcounter_prev = deposit64(counter->mhpmcounter_prev,
- 32, 32, ctrh);
+ ctr = riscv_pmu_ctr_get_fixed_counters_val(env, ctr_idx);
+ counter->mhpmcounter_val += ctr - counter->mhpmcounter_prev;
+ counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
+ 32, 32, val);
+ counter->mhpmcounter_prev = ctr;
if (ctr_idx > 2) {
riscv_pmu_setup_timer(env, counter->mhpmcounter_val, ctr_idx);
}
} else {
+ counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
+ 32, 32, val);
counter->mhpmcounter_prev = deposit64(counter->mhpmcounter_prev,
32, 32, val);
}
@@ -1410,12 +1412,11 @@ RISCVException riscv_pmu_read_ctr(CPURISCVState *env, target_ulong *val,
bool rv32 = riscv_cpu_mxl(env) == MXL_RV32;
int start = upper_half ? 32 : 0;
int length = rv32 ? 32 : 64;
- uint64_t ctr_prev, ctr_val;
+ uint64_t ctr_val;
/* Ensure upper_half is only set for XLEN == 32 */
g_assert(rv32 || !upper_half);
- ctr_prev = extract64(counter->mhpmcounter_prev, start, length);
ctr_val = extract64(counter->mhpmcounter_val, start, length);
if (get_field(env->mcountinhibit, BIT(ctr_idx))) {
@@ -1434,7 +1435,8 @@ RISCVException riscv_pmu_read_ctr(CPURISCVState *env, target_ulong *val,
if (riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
riscv_pmu_ctr_monitor_instructions(env, ctr_idx)) {
uint64_t cntr = riscv_pmu_ctr_get_fixed_counters_val(env, ctr_idx) -
- ctr_prev + ctr_val;
+ counter->mhpmcounter_prev +
+ counter->mhpmcounter_val;
*val = extract64(cntr, start, length);
} else {
*val = ctr_val;
diff --git a/tests/tcg/meson.build b/tests/tcg/meson.build
index 4c0e30756d..184edcdbbc 100644
--- a/tests/tcg/meson.build
+++ b/tests/tcg/meson.build
@@ -186,6 +186,7 @@ subdir('mips64el')
subdir('or1k')
subdir('ppc64')
subdir('ppc64le')
+subdir('riscv32')
subdir('riscv64')
subdir('s390x')
subdir('sh4')
diff --git a/tests/tcg/riscv32/meson.build b/tests/tcg/riscv32/meson.build
new file mode 100644
index 0000000000..862f1349c2
--- /dev/null
+++ b/tests/tcg/riscv32/meson.build
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+cc = 'riscv64-linux-gnu-gcc'
+cc_dockerfile = 'debian-all-test-cross'
+
+subdir('system')
diff --git a/tests/tcg/riscv32/semihost.ld b/tests/tcg/riscv32/semihost.ld
new file mode 100644
index 0000000000..874838a865
--- /dev/null
+++ b/tests/tcg/riscv32/semihost.ld
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+ENTRY(_start)
+
+SECTIONS
+{
+ /* virt machine, RAM starts at 2gb */
+ . = 0x80000000;
+ .text : {
+ *(.text)
+ }
+ .rodata : {
+ *(.rodata)
+ }
+ /* align r/w section to next 2mb */
+ . = ALIGN(1 << 21);
+ .data : {
+ *(.data)
+ }
+ .bss : {
+ *(.bss)
+ }
+}
diff --git a/tests/tcg/riscv32/system/meson.build b/tests/tcg/riscv32/system/meson.build
new file mode 100644
index 0000000000..f8e55ad3d4
--- /dev/null
+++ b/tests/tcg/riscv32/system/meson.build
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+tests = []
+
+link_script = files('../semihost.ld')[0]
+cflags = ['-march=rv32im_zicsr',
+ '-mabi=ilp32',
+ '-nostdlib',
+ '-ffreestanding',
+ '-Wa,--noexecstack',
+ '-Wl,-T', link_script]
+qemu_args = ['-M', 'virt',
+ '-display', 'none',
+ '-serial', 'stdio',
+ '-semihosting',
+ '-bios']
+
+tests += {
+ 'test-mcycle.S': {
+ 'cflags': cflags,
+ 'qemu_args': ['-icount', 'shift=1', qemu_args],
+ },
+}
+
+if 'qemu-system-riscv32' in emulators
+ tcg_tests += {
+ 'riscv32-softmmu': {
+ 'cc': cc,
+ 'cc_dockerfile': cc_dockerfile,
+ 'folder': 'riscv32',
+ 'gdb_arch': 'riscv32',
+ 'qemu': emulators['qemu-system-riscv32'],
+ 'tests': tests,
+ }
+ }
+endif
diff --git a/tests/tcg/riscv32/test-mcycle.S b/tests/tcg/riscv32/test-mcycle.S
new file mode 100644
index 0000000000..189d1e13a2
--- /dev/null
+++ b/tests/tcg/riscv32/test-mcycle.S
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+ .option norvc
+
+ .text
+ .global _start
+_start:
+ /* Exercise writes while mcycle is running. */
+ csrw mcountinhibit, zero
+ csrw mcycle, zero
+
+ /* A write to the high half must be immediately observable. */
+ li s0, 0x1234ffff
+ csrw mcycleh, s0
+ csrr t0, mcycleh
+ bne t0, s0, fail
+
+ /* Check carry from the low half into the high half. */
+ li s0, 0x12345678
+ csrw mcycleh, s0
+ li t0, 0xfffffff0
+ csrw mcycle, t0
+ .rept 32
+ nop
+ .endr
+ csrr t0, mcycleh
+ addi s0, s0, 1
+ bne t0, s0, fail
+
+ /* A low-half write must preserve the carried high half. */
+ li t0, 0x22222222
+ csrw mcycle, t0
+ csrr t0, mcycleh
+ bne t0, s0, fail
+
+ li t0, 0x100000
+ li t1, 0x5555 /* FINISHER_PASS */
+ sw t1, 0(t0)
+ j .
+
+fail:
+ li t0, 0x100000
+ li t1, 0x13333 /* status = FINISHER_FAIL, code = 1 */
+ sw t1, 0(t0)
+ j .