On Wed, 23 Sept 2026 at 22:56, Manuele Conti
<[email protected]> wrote:
>
> RM0090 counters count from 0 to ARR inclusive, with period ARR + 1
> for all ARR values including ARR == 0. Fix set_alarm to wrap CNT,
> arm an absolute expiry and delete the QEMU timer when CEN is cleared.
>
> Freeze CNT while CEN == 0 in stopped_cnt so a TIM_CNT write
> programmed while stopped is preserved and resumed on enable, and
> reset deletes any pending timer event.
>
> VMState goes to version 2 to serialize stopped_cnt. Loading a v1
> stream leaves stopped_cnt at 0, so a timer stopped with CNT != 0
> loads CNT as 0; no post_load workaround. The pre-existing QEMUTimer
> expiry limit is unchanged.
>
> Add qtests covering PSC/ARR/CEN/UIF, CNT <= ARR, CNT preset before
> CEN, ARR == 0, stop freeze on CEN=0, restart, and no spurious IRQ
> after reset.
>
> Signed-off-by: Manuele Conti <[email protected]>
> ---
>  MAINTAINERS                        |   1 +
>  hw/timer/stm32f2xx_timer.c         | 101 ++++++++++++++++----
>  include/hw/timer/stm32f2xx_timer.h |   7 ++
>  tests/qtest/meson.build            |   4 +
>  tests/qtest/stm32f405-timer-test.c | 142 +++++++++++++++++++++++++++++
>  5 files changed, 236 insertions(+), 19 deletions(-)
>  create mode 100644 tests/qtest/stm32f405-timer-test.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5bf895732c..f1b3d75cc8 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1213,6 +1213,7 @@ F: hw/misc/stm32f4xx_syscfg.c
>  F: hw/misc/stm32f4xx_exti.c
>  F: hw/misc/stm32_rcc.c
>  F: include/hw/misc/stm32_rcc.h
> +F: tests/qtest/stm32f405*
>
>  Netduino 2
>  M: Alistair Francis <[email protected]>
> diff --git a/hw/timer/stm32f2xx_timer.c b/hw/timer/stm32f2xx_timer.c
> index 0442ac64ad..1639037130 100644
> --- a/hw/timer/stm32f2xx_timer.c
> +++ b/hw/timer/stm32f2xx_timer.c
> @@ -53,7 +53,8 @@ static void stm32f2xx_timer_interrupt(void *opaque)
>      if (s->tim_dier & TIM_DIER_UIE && s->tim_cr1 & TIM_CR1_CEN) {
>          s->tim_sr |= 1;
>          qemu_irq_pulse(s->irq);
> -        stm32f2xx_timer_set_alarm(s, s->hit_time);
> +        stm32f2xx_timer_set_alarm(s,
> +                                  qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));

This change means that the only uses of s->hit_time are in the
sequence in stm32f2xx_timer_set_alarm():
    s->hit_time = (uint64_t)now + delta_ns;
    timer_mod(s->timer, s->hit_time);
    DB_PRINT("Wait Time: %" PRId64 " ticks\n", s->hit_time);

That means we can change hit_time to be a simple local variable
in that function; it doesn't need to be a state field any more.

>      }
>
>      if (s->tim_ccmr1 & (TIM_CCMR1_OC2M2 | TIM_CCMR1_OC2M1) &&

>  static uint64_t stm32f2xx_timer_read(void *opaque, hwaddr offset,
> @@ -148,8 +172,11 @@ static uint64_t stm32f2xx_timer_read(void *opaque, 
> hwaddr offset,
>      case TIM_CCER:
>          return s->tim_ccer;
>      case TIM_CNT:
> -        return stm32f2xx_ns_to_ticks(s, 
> qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)) -
> -               s->tick_offset;
> +        if (!(s->tim_cr1 & TIM_CR1_CEN)) {
> +            return s->stopped_cnt % ((uint64_t)s->tim_arr + 1);
> +        }

This looks odd -- I wouldn't expect the auto-reload register to have
any effect if the counter is stopped. That is, if you do:
 * set the counter to disabled
 * set ARR to 100
 * set CNT to 200
 * read CNT

I would expect to read back 200 (and then if you enable the counter
it probably rolls back to 0 on the next clock, regardless of
what its value above the auto-reload value is?)

Do you have real hardware to confirm the behaviour against?

> +        return stm32f2xx_timer_get_cnt(s,
> +                   qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
>      case TIM_PSC:
>          return s->tim_psc;
>      case TIM_ARR:

> @@ -272,7 +328,7 @@ static const MemoryRegionOps stm32f2xx_timer_ops = {
>
>  static const VMStateDescription vmstate_stm32f2xx_timer = {
>      .name = TYPE_STM32F2XX_TIMER,
> -    .version_id = 1,
> +    .version_id = 2,
>      .minimum_version_id = 1,
>      .fields = (const VMStateField[]) {
>          VMSTATE_INT64(tick_offset, STM32F2XXTimerState),
> @@ -294,6 +350,13 @@ static const VMStateDescription vmstate_stm32f2xx_timer 
> = {
>          VMSTATE_UINT32(tim_dcr, STM32F2XXTimerState),
>          VMSTATE_UINT32(tim_dmar, STM32F2XXTimerState),
>          VMSTATE_UINT32(tim_or, STM32F2XXTimerState),
> +        /*
> +         * New in version 2: frozen CNT while CEN == 0. Pre-existing
> +         * limitation left unchanged: QEMUTimer expiry (hit_time) is
> +         * not migrated and is re-armed by the next register write;
> +         * see stm32f2xx_timer_set_alarm.
> +         */
> +        VMSTATE_UINT32_V(stopped_cnt, STM32F2XXTimerState, 2),

We don't care about migration compatibility on these machine types,
so we could just bump both version_id and minimum_version_id to 2
and define the field as a simple VMSTATE_UINT32().

>          VMSTATE_END_OF_LIST()
>      }
>  };

Otherwise this looks OK, and certainly better than what we have now.

thanks
-- PMM

Reply via email to