On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koe...@gmail.com> wrote:
>
> From: YannickV <y.vos...@beckhoff.com>
>
> The a9 global timer and arm mp timers rely on the PERIPHCLK as
> their clock source. The current implementation does not take
> that into account. That causes problems for applications assuming
> other frequencies than 1 GHz.
>
> We can now configure frequencies for the a9 global timer and
> arm mp timer. By allowing these values to be set according to
> the application's needs, we ensure that the timers behave
> consistently with the expected system configuration.
>
> The frequency can also be set via the command line, for example
> for the a9 global timer:
> -global driver=arm.cortex-a9-global-timer,
>         property=cpu-freq,value=1000000000

-global is a rare thing for special cases, you don't
need to mention it here. The standard case should be
"the SoC configures the device correctly".

> Information can be found in the Zynq 7000 SoC Technical
> Reference Manual under Timers.
> https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM
>
> Signed-off-by: Yannick Voßen <y.vos...@beckhoff.com>
> ---
>  hw/timer/a9gtimer.c            |  8 +++++---
>  hw/timer/arm_mptimer.c         | 15 +++++++++++----
>  include/hw/timer/a9gtimer.h    |  1 +
>  include/hw/timer/arm_mptimer.h |  2 ++
>  4 files changed, 19 insertions(+), 7 deletions(-)
>
> diff --git a/hw/timer/a9gtimer.c b/hw/timer/a9gtimer.c
> index 9835c35483..a1f5540e75 100644
> --- a/hw/timer/a9gtimer.c
> +++ b/hw/timer/a9gtimer.c
> @@ -63,9 +63,9 @@ static inline int a9_gtimer_get_current_cpu(A9GTimerState 
> *s)
>  static inline uint64_t a9_gtimer_get_conv(A9GTimerState *s)
>  {
>      uint64_t prescale = extract32(s->control, R_CONTROL_PRESCALER_SHIFT,
> -                                  R_CONTROL_PRESCALER_LEN);
> -
> -    return (prescale + 1) * 10;
> +                                  R_CONTROL_PRESCALER_LEN) + 1;
> +    uint64_t ret = NANOSECONDS_PER_SECOND * prescale * 10;
> +    return (uint32_t) (ret / s->cpu_clk_freq_hz);

Why the cast to uint32_t here ?

If you are doing an "x * NANOSECONDS_PER_SECOND / frequency"
calculation, use muldiv64(). (This does the calculation with a
96 bit intermediate result.)

>  }
>
>  static A9GTimerUpdate a9_gtimer_get_update(A9GTimerState *s)
> @@ -374,6 +374,8 @@ static const VMStateDescription vmstate_a9_gtimer = {
>  };
>
>  static const Property a9_gtimer_properties[] = {
> +    DEFINE_PROP_UINT64("cpu-freq", A9GTimerState, cpu_clk_freq_hz,
> +                       NANOSECONDS_PER_SECOND),

You could have a comment mentioning that this default is 1GHz.

The modern way to do this is to have a Clock property, but
we have enough existing timer devices that take an integer
frequency that I don't object to another one.

>      DEFINE_PROP_UINT32("num-cpu", A9GTimerState, num_cpu, 0),
>  };
>
> diff --git a/hw/timer/arm_mptimer.c b/hw/timer/arm_mptimer.c
> index 803dad1e8a..a748b6ab1a 100644
> --- a/hw/timer/arm_mptimer.c
> +++ b/hw/timer/arm_mptimer.c
> @@ -59,9 +59,11 @@ static inline void timerblock_update_irq(TimerBlock *tb)
>  }
>
>  /* Return conversion factor from mpcore timer ticks to qemu timer ticks.  */
> -static inline uint32_t timerblock_scale(uint32_t control)
> +static inline uint32_t timerblock_scale(TimerBlock *tb, uint32_t control)
>  {
> -    return (((control >> 8) & 0xff) + 1) * 10;
> +    uint64_t prescale = (((control >> 8) & 0xff) + 1);
> +    uint64_t ret = NANOSECONDS_PER_SECOND * prescale * 10;
> +    return (uint32_t) (ret / tb->freq_hz);
>  }
>
>  /* Must be called within a ptimer transaction block */
> @@ -155,7 +157,7 @@ static void timerblock_write(void *opaque, hwaddr addr,
>              ptimer_stop(tb->timer);
>          }
>          if ((control & 0xff00) != (value & 0xff00)) {
> -            ptimer_set_period(tb->timer, timerblock_scale(value));
> +            ptimer_set_period(tb->timer, timerblock_scale(tb, value));
>          }
>          if (value & 1) {
>              uint64_t count = ptimer_get_count(tb->timer);
> @@ -222,7 +224,8 @@ static void timerblock_reset(TimerBlock *tb)
>          ptimer_transaction_begin(tb->timer);
>          ptimer_stop(tb->timer);
>          ptimer_set_limit(tb->timer, 0, 1);
> -        ptimer_set_period(tb->timer, timerblock_scale(0));
> +        ptimer_set_period(tb->timer,
> +            timerblock_scale(tb, tb->control));
>          ptimer_transaction_commit(tb->timer);
>      }
>  }
> @@ -269,6 +272,7 @@ static void arm_mptimer_realize(DeviceState *dev, Error 
> **errp)
>       */
>      for (i = 0; i < s->num_cpu; i++) {
>          TimerBlock *tb = &s->timerblock[i];
> +        tb->freq_hz = s->clk_freq_hz;
>          tb->timer = ptimer_init(timerblock_tick, tb, PTIMER_POLICY);
>          sysbus_init_irq(sbd, &tb->irq);
>          memory_region_init_io(&tb->iomem, OBJECT(s), &timerblock_ops, tb,
> @@ -283,6 +287,7 @@ static const VMStateDescription vmstate_timerblock = {
>      .minimum_version_id = 3,
>      .fields = (const VMStateField[]) {
>          VMSTATE_UINT32(control, TimerBlock),
> +        VMSTATE_UINT64(freq_hz, TimerBlock),
>          VMSTATE_UINT32(status, TimerBlock),
>          VMSTATE_PTIMER(timer, TimerBlock),
>          VMSTATE_END_OF_LIST()

You cannot add fields to a VMStateDescription without doing
something to handle migration compatibility. Fortunately,
in this case you don't need to add the field at all -- freq_hz
is a property value that cannot be updated by the guest at
runtime, so it doesn't need to be saved for migration.

> @@ -301,6 +306,8 @@ static const VMStateDescription vmstate_arm_mptimer = {
>  };
>
>  static const Property arm_mptimer_properties[] = {
> +    DEFINE_PROP_UINT64("clk-freq", ARMMPTimerState, clk_freq_hz,
> +                       NANOSECONDS_PER_SECOND),

Can we be consistent about the property name and state struct
field that we use, please?

The one that seems to be most used in hw/timer/ is
a property name "clock-frequency" and a field name freq_hz.

thanks
-- PMM

Reply via email to