target/arm: Handle overflow in calculation of next timer tick
In commitedac4d8a16
back in 2015 when we added support for the virtual timer offset CNTVOFF_EL2, we didn't correctly update the timer-recalculation code that figures out when the timer interrupt is next going to change state. We got it wrong in two ways: * for the 0->1 transition, we didn't notice that gt->cval + offset can overflow a uint64_t * for the 1->0 transition, we didn't notice that the transition might now happen before the count rolls over, if offset > count In the former case, we end up trying to set the next interrupt for a time in the past, which results in QEMU hanging as the timer fires continuously. In the latter case, we would fail to update the interrupt status when we are supposed to. Fix the calculations in both cases. The test case is Alex Bennée's from the bug report, and tests the 0->1 transition overflow case. Fixes:edac4d8a16
("target-arm: Add CNTVOFF_EL2") Cc: qemu-stable@nongnu.org Resolves: https://gitlab.com/qemu-project/qemu/-/issues/60 Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20231120173506.3729884-1-peter.maydell@linaro.org Reviewed-by: Peter Maydell <peter.maydell@linaro.org> (cherry picked from commit8d37a1425b
) Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
This commit is contained in:
parent
169c593f78
commit
49727560c7
@ -2616,11 +2616,28 @@ static void gt_recalc_timer(ARMCPU *cpu, int timeridx)
|
||||
qemu_set_irq(cpu->gt_timer_outputs[timeridx], irqstate);
|
||||
|
||||
if (istatus) {
|
||||
/* Next transition is when count rolls back over to zero */
|
||||
nexttick = UINT64_MAX;
|
||||
/*
|
||||
* Next transition is when (count - offset) rolls back over to 0.
|
||||
* If offset > count then this is when count == offset;
|
||||
* if offset <= count then this is when count == offset + 2^64
|
||||
* For the latter case we set nexttick to an "as far in future
|
||||
* as possible" value and let the code below handle it.
|
||||
*/
|
||||
if (offset > count) {
|
||||
nexttick = offset;
|
||||
} else {
|
||||
nexttick = UINT64_MAX;
|
||||
}
|
||||
} else {
|
||||
/* Next transition is when we hit cval */
|
||||
nexttick = gt->cval + offset;
|
||||
/*
|
||||
* Next transition is when (count - offset) == cval, i.e.
|
||||
* when count == (cval + offset).
|
||||
* If that would overflow, then again we set up the next interrupt
|
||||
* for "as far in the future as possible" for the code below.
|
||||
*/
|
||||
if (uadd64_overflow(gt->cval, offset, &nexttick)) {
|
||||
nexttick = UINT64_MAX;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Note that the desired next expiry time might be beyond the
|
||||
|
@ -45,7 +45,8 @@ TESTS+=memory-sve
|
||||
|
||||
# Running
|
||||
QEMU_BASE_MACHINE=-M virt -cpu max -display none
|
||||
QEMU_OPTS+=$(QEMU_BASE_MACHINE) -semihosting-config enable=on,target=native,chardev=output -kernel
|
||||
QEMU_BASE_ARGS=-semihosting-config enable=on,target=native,chardev=output
|
||||
QEMU_OPTS+=$(QEMU_BASE_MACHINE) $(QEMU_BASE_ARGS) -kernel
|
||||
|
||||
# console test is manual only
|
||||
QEMU_SEMIHOST=-chardev stdio,mux=on,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -mon chardev=stdio0,mode=readline
|
||||
@ -55,6 +56,10 @@ run-semiconsole: semiconsole
|
||||
run-plugin-semiconsole-with-%: semiconsole
|
||||
$(call skip-test, $<, "MANUAL ONLY")
|
||||
|
||||
# vtimer test needs EL2
|
||||
QEMU_EL2_MACHINE=-machine virt,virtualization=on,gic-version=2 -cpu cortex-a57 -smp 4
|
||||
run-vtimer: QEMU_OPTS=$(QEMU_EL2_MACHINE) $(QEMU_BASE_ARGS) -kernel
|
||||
|
||||
# Simple Record/Replay Test
|
||||
.PHONY: memory-record
|
||||
run-memory-record: memory-record memory
|
||||
|
48
tests/tcg/aarch64/system/vtimer.c
Normal file
48
tests/tcg/aarch64/system/vtimer.c
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Simple Virtual Timer Test
|
||||
*
|
||||
* Copyright (c) 2020 Linaro Ltd
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <minilib.h>
|
||||
|
||||
/* grabbed from Linux */
|
||||
#define __stringify_1(x...) #x
|
||||
#define __stringify(x...) __stringify_1(x)
|
||||
|
||||
#define read_sysreg(r) ({ \
|
||||
uint64_t __val; \
|
||||
asm volatile("mrs %0, " __stringify(r) : "=r" (__val)); \
|
||||
__val; \
|
||||
})
|
||||
|
||||
#define write_sysreg(r, v) do { \
|
||||
uint64_t __val = (uint64_t)(v); \
|
||||
asm volatile("msr " __stringify(r) ", %x0" \
|
||||
: : "rZ" (__val)); \
|
||||
} while (0)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
ml_printf("VTimer Test\n");
|
||||
|
||||
write_sysreg(cntvoff_el2, 1);
|
||||
write_sysreg(cntv_cval_el0, -1);
|
||||
write_sysreg(cntv_ctl_el0, 1);
|
||||
|
||||
ml_printf("cntvoff_el2=%lx\n", read_sysreg(cntvoff_el2));
|
||||
ml_printf("cntv_cval_el0=%lx\n", read_sysreg(cntv_cval_el0));
|
||||
ml_printf("cntv_ctl_el0=%lx\n", read_sysreg(cntv_ctl_el0));
|
||||
|
||||
/* Now read cval a few times */
|
||||
for (i = 0; i < 10; i++) {
|
||||
ml_printf("%d: cntv_cval_el0=%lx\n", i, read_sysreg(cntv_cval_el0));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user