You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@nuttx.apache.org by Pavel Pisa <pp...@pikron.com> on 2021/07/18 10:46:49 UTC

Tickless mode compare value check at counter overflow

Hello everybody,

I have noticed problematic pattern in the imxRT code
which is probably copied from STM32 code

  ./arch/arm/src/stm32f7/stm32_tickless.c:  while (tm <= stm32_get_counter())
  ./arch/arm/src/imxrt/imxrt_tickless.c:  while (tm <= imxrt_get_counter())

Criticality is minor, because overflow or 64-bit counter occurs
for microsecond resolution only once per  ~ 5e5 years and has
to coincide with miss of he event. But problem reported anyway,
because it similar problem appear in other places where cyclic
arithmetics is intended

The code

  https://github.com/apache/incubator-nuttx/blob/master/arch/arm/src/imxrt/imxrt_tickless.c#L539

  size_t offset = 1;
  uint64_t tm ....
  ....
  while (tm <= imxrt_get_counter())
    {
      tm = imxrt_get_counter() + offset++;
      putreg32(tm, g_tickless.base + IMXRT_GPT_OCR1_OFFSET + \
              (4 * (g_tickless.out_compare - 1)));
    }

When the cyclic nature of the tm requires cyclic (modulo arithmetics)
compare. But as is shown in the example below, the comparison
can lead incorrect decision that alarm has been set
successfully even when the time is and counter overflows.
The correct condition for cyclic compare is

  while ((int64_t)(tm - imxrt_get_counter()) <= 0)

Modulo, cyclic arithmetics and overflows are guaranteed only for unsigned
types by C language standard. That was correct. But for substraction
to find which way is the shorter path on the cycle between two points,
it is necessary to cast result to the signed type no larger than each
of the inputs. In this case uint64_t - uint64_t, so same or smaller
type with sign is int64_t .

The code has another problem, that alarm time can be easily far
after hardware counter overflow. But that should not be a problem
in such case, because compare to value truncated to 32-bits is triggered
earlier and if all other logic goes well, the next event would be shedulled
after complete cycle of the 32-bit part of the counter.

The analysis would get much more complex when calibration of the clock
and fine scaling is considered in the future. In such case, maximam
revet realizable time has to be considered and value has to be limited
to what is supported in the hardware. I can help to help read Linux kernel
part of this code... I have contributed to it years ago when I have extended
i.MX1 support to switch from tick based solution to high resolution
timing introduced by Thomas Gleixner at the time.

Ability to use clock/timer which period is not multiple of 1 usec
could be considered for NuttX in the long term perspective.

Best wishes,

Pavel

PS: I personally prefer to document cyclic nature of given comparison
    by macros ul_cyclic_gt(x,y) and #define ul_cyclic_ge(x,y)

      https://gitlab.com/pikron/sw-base/ulut/-/blob/master/ulut/ul_utdefs.h#L93

    but solution is a little fragile for case where some integer type can be
    defined on some target as using only 16 bits from 20 or 24-bit char
    on some DSP with memory organized by words. But even in DSP cases
    I have not found such problem yet. !6-bit type forced to align to
    4 bytes could be possible even on less obscure platform.

---------------------------------------------------------------------------------------------
#include <stdio.h>
#include <stdint.h>

int main(int argc, char *argv[])
{
  uint64_t counter;
  uint64_t tm;
  int cmp11, cmp12, cmp13, cmp21, cmp22, cmp23;

  counter = 1234;
  tm = counter + 10;
  counter = counter + 20;

  cmp11 = (tm <= counter);
  cmp12 = ((tm - counter) <= 0);
  cmp13 = ((int64_t)(tm - counter) <= 0);

  printf("compare 1 default %d, substract %d, cyclic %d\n", cmp11, cmp12, cmp13);

  counter = (uint64_t)-15;
  tm = counter + 10;
  counter = counter + 20;

  cmp21 = (tm <= counter);
  cmp22 = ((tm - counter) <= 0);
  cmp23 = ((int64_t)(tm - counter) <= 0);

  printf("compare 2 default %d, substract %d, cyclic %d\n", cmp21, cmp22, cmp23);

  return 0;
}
---------------------------------------------------------------------------------------------