From: Chao Liu <chao.liu@processmission.com>
To: Zephyr Li <fritchleybohrer@gmail.com>
Cc: qemu-devel@nongnu.org, qemu-riscv@nongnu.org,
qemu-stable@nongnu.org, pbonzini@redhat.com,
marcandre.lureau@redhat.com, berrange@redhat.com,
pierrick.bouvier@oss.qualcomm.com, alex.bennee@linaro.org,
palmer@dabbelt.com, alistair.francis@wdc.com,
liwei1518@gmail.com, daniel.barboza@oss.qualcomm.com,
zhiwei_liu@linux.alibaba.com
Subject: Re: [PATCH v2] target/riscv: fix RV32 fixed counter accesses
Date: Fri, 11 Sep 2026 12:44:37 +0800 [thread overview]
Message-ID: <aqOHF2QVtK0UWM-e@MacBook-Pro-4.local> (raw)
In-Reply-To: <20260909072356.42784-1-fritchleybohrer@gmail.com>
On Wed, Sep 09, 2026 at 03:23:55PM +0800, Zephyr Li wrote:
> 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: Chao Liu <chao.liu@processmission.com>
Thanks,
Chao
>
> ---
> Changes in v2:
> - Move the test to a new tests/tcg/riscv32 directory.
> - Convert the test registration to the Meson TCG test framework.
> - Add RV32 TCG cross-compiler options and cover the test directory in
> MAINTAINERS.
> - Add a riscv32-local semihosting linker script.
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 7183babd6a..5f75b2684f 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 292625af08..2ceebe7a44 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -452,6 +452,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/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
> index bd4b6dc114..65985efb22 100644
> --- a/target/riscv/tcg/csr.c
> +++ b/target/riscv/tcg/csr.c
> @@ -1337,23 +1337,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;
> @@ -1363,20 +1363,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);
> }
> @@ -1407,12 +1409,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))) {
> @@ -1431,7 +1432,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 d41a228fb3..60b5ce2529 100644
> --- a/tests/tcg/meson.build
> +++ b/tests/tcg/meson.build
> @@ -169,6 +169,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..c08dfd772d
> --- /dev/null
> +++ b/tests/tcg/riscv32/meson.build
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +
> +cc = 'riscv64-linux-gnu-gcc'
> +cc_dockerfile = 'debian-all-test-cross'
> +cc_docker_host_arch = ['aarch64', 'x86_64']
> +
> +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..16f9a06c94
> --- /dev/null
> +++ b/tests/tcg/riscv32/system/meson.build
> @@ -0,0 +1,37 @@
> +# 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,
> + 'cc_docker_host_arch': cc_docker_host_arch,
> + '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 .
> --
> 2.43.0
prev parent reply other threads:[~2026-09-11 4:45 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-09 7:23 [PATCH v2] target/riscv: fix RV32 fixed counter accesses Zephyr Li
2026-09-09 19:58 ` Daniel Henrique Barboza
2026-09-11 4:44 ` Chao Liu [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aqOHF2QVtK0UWM-e@MacBook-Pro-4.local \
--to=chao.liu@processmission.com \
--cc=alex.bennee@linaro.org \
--cc=alistair.francis@wdc.com \
--cc=berrange@redhat.com \
--cc=daniel.barboza@oss.qualcomm.com \
--cc=fritchleybohrer@gmail.com \
--cc=liwei1518@gmail.com \
--cc=marcandre.lureau@redhat.com \
--cc=palmer@dabbelt.com \
--cc=pbonzini@redhat.com \
--cc=pierrick.bouvier@oss.qualcomm.com \
--cc=qemu-devel@nongnu.org \
--cc=qemu-riscv@nongnu.org \
--cc=qemu-stable@nongnu.org \
--cc=zhiwei_liu@linux.alibaba.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.