All of lore.kernel.org
 help / color / mirror / Atom feed
From: ~axelheider <axelheider@git.sr.ht>
To: qemu-devel@nongnu.org
Cc: peter.maydell@linaro.org, qemu-arm@nongnu.org
Subject: [PATCH qemu.git v2 9/9] hw/timer/imx_epit: fix compare timer handling
Date: Thu, 03 Nov 2022 12:09:28 +0100	[thread overview]
Message-ID: <166783932395.3279.1096141058484230644-9@git.sr.ht> (raw)
In-Reply-To: <166783932395.3279.1096141058484230644-0@git.sr.ht>

From: Axel Heider <axel.heider@hensoldt.net>

- fix #1263
- rework compare time handling
  - The compare timer has to run even if CR.OCIEN is not set,
    as SR.OCIF must be updated.
  - The compare timer fires exactly once when the
    compare value is less than the current value, but the
    reload values is less than the compare value.
  - The compare timer will never fire if the reload value is
    less than the compare value. Disable it in this case.

Signed-off-by: Axel Heider <axel.heider@hensoldt.net>
---
 hw/timer/imx_epit.c | 188 +++++++++++++++++++++++++++++---------------
 1 file changed, 123 insertions(+), 65 deletions(-)

diff --git a/hw/timer/imx_epit.c b/hw/timer/imx_epit.c
index 77bd2b0a2b..cb2880cabc 100644
--- a/hw/timer/imx_epit.c
+++ b/hw/timer/imx_epit.c
@@ -6,6 +6,7 @@
  * Originally written by Hans Jiang
  * Updated by Peter Chubb
  * Updated by Jean-Christophe Dubois <jcd@tribudubois.net>
+ * Updated by Axel Heider
  *
  * This code is licensed under GPL version 2 or later.  See
  * the COPYING file in the top-level directory.
@@ -110,33 +111,84 @@ static uint64_t imx_epit_read(void *opaque, hwaddr offset, unsigned size)
     return reg_value;
 }
 
-/* Must be called from ptimer_transaction_begin/commit block for s->timer_cmp */
-static void imx_epit_reload_compare_timer(IMXEPITState *s)
+/*
+ * Must be called from a ptimer_transaction_begin/commit block for
+ * s->timer_cmp, but outside of a transaction block of s->timer_reload,
+ * so the proper counter value is read.
+ */
+static void imx_epit_update_compare_timer(IMXEPITState *s)
 {
-    if ((s->cr & (CR_EN | CR_OCIEN)) == (CR_EN | CR_OCIEN))  {
-        /* if the compare feature is on and timers are running */
-        uint32_t tmp = ptimer_get_count(s->timer_reload);
-        uint64_t next;
-        if (tmp > s->cmp) {
-            /* It'll fire in this round of the timer */
-            next = tmp - s->cmp;
-        } else { /* catch it next time around */
-            next = tmp - s->cmp + ((s->cr & CR_RLD) ? EPIT_TIMER_MAX : s->lr);
+    uint64_t counter = 0;
+    bool is_oneshot = false;
+    /* The compare timer only has to run if the timer peripheral is active
+     * and there is an input clock, Otherwise it can be switched off.
+     */
+    bool is_active = (s->cr & CR_EN) && imx_epit_get_freq(s);
+    if (is_active)
+    {
+        /*
+         * Calculate next timeout for compare timer. Reading the reload
+         * counter returns proper results only if pending transactions
+         * on it are committed here. Otherwise stale values are be read.
+         */
+        counter = ptimer_get_count(s->timer_reload);
+        uint64_t limit = ptimer_get_limit(s->timer_cmp);
+        /* The compare timer is a periodic timer if the limit is at least
+         * the compare value. Otherwise it may fire at most once in the
+         * current round.
+         */
+        bool is_oneshot = (limit >= s->cmp);
+        if (counter >= s->cmp) {
+            /* The compare timer fires in the current round. */
+            counter -= s->cmp;
+        } else if (!is_oneshot) {
+            /*
+             * The compare timer fires after a reload, as it below the
+             * compare value already in this round. Note that the counter
+             * value calculated below can be above the 32-bit limit, which
+             * is legal here because the compare timer is an internal
+             * helper ptimer only.
+             */
+            counter += limit - s->cmp;
+        } else {
+            /*
+             * The compare timer wont fire in this round, and the limit is
+             * set to a value below the compare value. This practically means
+             * it will never fire, so it can be switched off.
+             */
+            is_active = false;
         }
-        ptimer_set_count(s->timer_cmp, next);
     }
+
+    /*
+     * Set the compare timer and let it run, or stop it. This is agnostic
+     * of CR.OCIEN bit, as this only matters for interrupt generation. The
+     * compare timer needs to run in any case, as the SR.OCIF bit must be
+     * updated even if no interrupt in generated.
+     * Note that the timer might already be stopped or be running with
+     * counter values. However, finding out when an update is needed and
+     * when not is not trivial. It's much easier applying the setting again,
+     * as this does not harm either and the overhead is negligible.
+     */
+    if (is_active) {
+        ptimer_set_count(s->timer_cmp, counter);
+        ptimer_run(s->timer_cmp, is_oneshot ? 1 : 0);
+    } else {
+        ptimer_stop(s->timer_cmp);
+    }
+
 }
 
 static void imx_epit_write_cr(IMXEPITState *s, uint32_t value)
 {
     uint32_t freq = 0;
-    uint32_t oldcr = s->cr;
+    bool set_limit = false;
+    bool set_counter = false;
 
     /* SWR bit is never persisted, it clears itself once reset is done */
+    uint32_t old_cr = s->cr;
     s->cr = (value & ~CR_SWR) & 0x03ffffff;
-
-    ptimer_transaction_begin(s->timer_cmp);
-    ptimer_transaction_begin(s->timer_reload);
+    uint32_t toggled_cr = old_cr ^ s->cr;
 
     if (value & CR_SWR) {
         /* Soft reset doesn't touch some bits; only a hard reset clears them */
@@ -149,49 +201,52 @@ static void imx_epit_write_cr(IMXEPITState *s, uint32_t value)
         /* turn interrupt off since SR and the OCIEN bit got cleared */
         qemu_irq_lower(s->irq);
         /* reset timer limits, set timer values to these limits */
-        ptimer_set_limit(s->timer_cmp, EPIT_TIMER_MAX, 1);
-        ptimer_set_limit(s->timer_reload, EPIT_TIMER_MAX, 1);
+        set_limit = true;
+        set_counter = true;
     } else {
-        freq = imx_epit_get_freq(s);
-        if (freq) {
-            ptimer_set_freq(s->timer_reload, freq);
-            ptimer_set_freq(s->timer_cmp, freq);
-        }
+        /* re-initialize the limits if CR.RLD has changed */
+        set_limit = toggled_cr & CR_RLD;
+        /* set the counter if the timer got just enabled and CR.ENMOD is set */
+        set_counter = ((toggled_cr & s->cr) & CR_EN) && (s->cr & CR_ENMOD);
+    }
+
+    ptimer_transaction_begin(s->timer_cmp);
+    ptimer_transaction_begin(s->timer_reload);
+
+    freq = imx_epit_get_freq(s);
+    if (freq) {
+        ptimer_set_freq(s->timer_reload, freq);
+        ptimer_set_freq(s->timer_cmp, freq);
     }
 
-    if (freq && (s->cr & CR_EN) && !(oldcr & CR_EN)) {
-        if (s->cr & CR_ENMOD) {
-            if (s->cr & CR_RLD) {
-                ptimer_set_limit(s->timer_reload, s->lr, 1);
-                ptimer_set_limit(s->timer_cmp, s->lr, 1);
-            } else {
-                ptimer_set_limit(s->timer_reload, EPIT_TIMER_MAX, 1);
-                ptimer_set_limit(s->timer_cmp, EPIT_TIMER_MAX, 1);
-            }
+    if (set_limit || set_counter) {
+        uint64_t limit = (s->cr & CR_RLD) ? s->lr : EPIT_TIMER_MAX;
+        if (set_limit) {
+            ptimer_set_limit(s->timer_cmp, limit, 0);
         }
+        ptimer_set_limit(s->timer_reload, limit, set_counter ? 1 : 0);
+    }
 
-        imx_epit_reload_compare_timer(s);
+    /*
+     * If there is an input clock and the peripheral is enabled, then ensure
+     * the wall clock timer is ticking. Otherwise stop the timers. The compare
+     * timer will be updated later.
+     */
+    if (freq && (s->cr & CR_EN)) {
         ptimer_run(s->timer_reload, 0);
-        if (s->cr & CR_OCIEN) {
-            ptimer_run(s->timer_cmp, 0);
-        } else {
-            ptimer_stop(s->timer_cmp);
-        }
-    } else if (!(s->cr & CR_EN)) {
-        /* stop both timers */
-        ptimer_stop(s->timer_reload);
-        ptimer_stop(s->timer_cmp);
-    } else  if (s->cr & CR_OCIEN) {
-        if (!(oldcr & CR_OCIEN)) {
-            imx_epit_reload_compare_timer(s);
-            ptimer_run(s->timer_cmp, 0);
-        }
     } else {
+        ptimer_stop(s->timer_reload);
+        /* Stop the compare timer also. This just plays safe, the call to
+         * imx_epit_update_compare_timer() below should also so this. */
         ptimer_stop(s->timer_cmp);
     }
 
-    ptimer_transaction_commit(s->timer_cmp);
+    /* Commit the changes to s->timer_reload, so they can propagate. */
     ptimer_transaction_commit(s->timer_reload);
+
+    /* Update the compare timer based on the committed reload timer value. */
+    imx_epit_update_compare_timer(s);
+    ptimer_transaction_commit(s->timer_cmp);
 }
 
 static void imx_epit_write_sr(IMXEPITState *s, uint32_t value)
@@ -218,14 +273,10 @@ static void imx_epit_write_lr(IMXEPITState *s, uint32_t value)
         /* If IOVW bit is set then set the timer value */
         ptimer_set_count(s->timer_reload, s->lr);
     }
-    /*
-     * Commit the change to s->timer_reload, so it can propagate. Otherwise
-     * the timer interrupt may not fire properly. The commit must happen
-     * before calling imx_epit_reload_compare_timer(), which reads
-     * s->timer_reload internally again.
-     */
+    /* Commit the changes to s->timer_reload, so they can propagate. */
     ptimer_transaction_commit(s->timer_reload);
-    imx_epit_reload_compare_timer(s);
+    /* Update the compare timer based on the committed reload timer value. */
+    imx_epit_update_compare_timer(s);
     ptimer_transaction_commit(s->timer_cmp);
 }
 
@@ -233,8 +284,9 @@ static void imx_epit_write_cmp(IMXEPITState *s, uint32_t value)
 {
     s->cmp = value;
 
+    /* Update the compare timer based on the committed reload timer value. */
     ptimer_transaction_begin(s->timer_cmp);
-    imx_epit_reload_compare_timer(s);
+    imx_epit_update_compare_timer(s);
     ptimer_transaction_commit(s->timer_cmp);
 }
 
@@ -274,16 +326,22 @@ static void imx_epit_cmp(void *opaque)
 {
     IMXEPITState *s = IMX_EPIT(opaque);
 
-    /* Set the interrupt status flag to signaled. */
-    DPRINTF("sr was %d\n", s->sr);
-    s->sr = 1;
+    if (s->cr & CR_EN) {
+        /* Set the interrupt status flag to signaled. */
+        DPRINTF("sr was %d\n", s->sr);
+        s->sr = 1;
 
-    /*
-     * An actual interrupt is generated only if the peripheral is enabled
-     * and the interrupt generation is enabled.
-     */
-    if ((s->cr & (CR_EN | CR_OCIEN)) == (CR_EN | CR_OCIEN)) {
-        qemu_irq_raise(s->irq);
+        /* If CR,OCIEN is set, an actual interrupt is generated */
+        if (s->cr & CR_OCIEN) {
+            qemu_irq_raise(s->irq);
+        }
+    } else {
+        /*
+         * The cmp ptimer is not supposed to be running when the
+         * peripheral is not enabled. Ignore this. However, it's
+         * worth investigating why this happened.
+         */
+        DPRINTF("compare trigger when timer not enabled\n");
     }
 }
 
-- 
2.34.5


  parent reply	other threads:[~2022-11-07 16:44 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-11-07 16:42 [PATCH qemu.git v2 0/9] hw/timer/imx_epit: imprive and fix compare timer handling ~axelheider
2022-10-25 10:33 ` [PATCH qemu.git v2 6/9] hw/timer/imx_epit: remove explicit fields cnt and freq ~axelheider
2022-11-18 15:54   ` Peter Maydell
2022-10-25 11:23 ` [PATCH qemu.git v2 3/9] hw/timer/imx_epit: simplify interrupt logic ~axelheider
2022-11-18 15:40   ` Peter Maydell
2022-11-21 17:35     ` Axel Heider
2022-10-25 15:33 ` [PATCH qemu.git v2 1/9] hw/timer/imx_epit: improve comments ~axelheider
2022-11-18 15:35   ` Peter Maydell
2022-10-25 18:32 ` [PATCH qemu.git v2 4/9] hw/timer/imx_epit: software reset clears the interrupt ~axelheider
2022-11-18 15:42   ` Peter Maydell
2022-10-27 13:09 ` [PATCH qemu.git v2 7/9] hw/timer/imx_epit: factor out register write handlers ~axelheider
2022-11-18 16:05   ` Peter Maydell
2022-10-30 23:59 ` [PATCH qemu.git v2 2/9] hw/timer/imx_epit: cleanup CR defines ~axelheider
2022-11-18 15:35   ` Peter Maydell
2022-11-02 15:36 ` [PATCH qemu.git v2 5/9] hw/timer/imx_epit: do not persist CR.SWR bit ~axelheider
2022-11-18 15:47   ` Peter Maydell
2022-11-19 17:41     ` Axel Heider
2022-11-03 11:09 ` ~axelheider [this message]
2022-11-18 19:00   ` [PATCH qemu.git v2 9/9] hw/timer/imx_epit: fix compare timer handling Peter Maydell
2022-11-29 22:27     ` Axel Heider
2022-11-04 15:01 ` [PATCH qemu.git v2 8/9] hw/timer/imx_epit: change reset handling ~axelheider
2022-11-18 15:58   ` Peter Maydell

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=166783932395.3279.1096141058484230644-9@git.sr.ht \
    --to=axelheider@git.sr.ht \
    --cc=axelheider@gmx.de \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-arm@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    /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.