maxikrie opened a new pull request, #21005:
URL: https://github.com/apache/nuttx/pull/21005

   ## Summary
   
   With `CONFIG_NRF52_SYSTIMER_RTC` (tickless on an RTC), the nRF52 tickless
   driver has three bugs: most alarms fire early, an alarm that has already
   passed can stall the scheduler for up to one RTC period (512 s), and the time
   read right after an RTC overflow can be 512 s off.
   
   The first two are in `rtc_prepare_alarm()`, which set CC0 to the alarm's
   absolute position within the current RTC period.
   
   - **Early alarms.** The position was converted with `USEC_TO_COUNTER()`,
     which divides by 31 instead of 30.52 once it exceeds 0x7fffff us
     (~8.4 s). The target came out 1.6 % of the position too early, 2 s early at
     128 s into the period. A target already behind the COUNTER was moved to
     COUNTER + 2, so from about 64 s into each period on a 1 s alarm fired after
     61 us. The scheduler found nothing expired and set the same alarm again,
     every 61 us until the deadline.
   - **Stall.** An alarm that had already passed (`clock_timespec_subtract()`
     gives zero) still took its position within its own period. Programmed just
     after an overflow for a deadline just before it, CC0 ended up near 0xffffff
     with the COUNTER near 0. The check `CC < COUNTER + 2` compares without
     regard to the wrap, accepted it, and the alarm fired only when the COUNTER
     came round again. Nothing ran on a timer until some other interrupt set a
     new alarm. We saw this once as a 383 s freeze of `CLOCK_MONOTONIC` and all
     timed waits on an nRF52840.
   
   The second commit programs CC0 from one COUNTER read instead:
   
   - the time left until the alarm (zero if it has passed), converted to counts
     exactly and rounded up so the alarm never fires early, is added to that
     reading;
   - the COUNTER is read again: if it is less than two counts short of CC0,
     where the Product Specification no longer guarantees a match, CC0 moves to
     the earliest guaranteed match and is checked again. Both distances are
     taken modulo the 24-bit COUNTER, so this holds across an overflow.
   
   `USEC_TO_COUNTER()`, `NRF52_RTC_MAX` and `NRF52_RTC_MAX_TIME` are no longer
   used and are removed.
   
   The third commit fixes the time read around an overflow. The time is
   `periods * 512 s` plus the COUNTER, and `periods` is counted by the RTC
   interrupt. Read with interrupts disabled between the COUNTER wrapping and
   that interrupt (`up_timer_gettime()` from a critical section,
   `rtc_prepare_alarm()` from `up_alarm_start()`), the time came out 512 s 
early.
   `up_alarm_cancel()` converted its COUNTER after leaving its critical section,
   so it could come out 512 s late. All reads now go through `rtc_now()` with
   interrupts disabled: while the overflow is not yet counted its OVRFLW event
   is still set, and a COUNTER in its lower half was read after the overflow.
   
   The first commit fixes the nxstyle error already in the file (a missing blank
   line in `up_alarm_start()`), whitespace only.
   
   ## Impact
   
   - nRF52 with `CONFIG_NRF52_SYSTIMER_RTC`: alarms fire 0-33 us after their
     deadline (one count is 30.5 us) wherever they fall in the RTC period; no
     more interrupt bursts before a deadline, and no stall after an overflow.
   - `up_timer_gettime()` (CLOCK_MONOTONIC, CLOCK_BOOTTIME) no longer goes back
     512 s when read right after an overflow with interrupts disabled.
   - An alarm more than one RTC period away is still left to the overflow
     handler, as before.
   - No Kconfig, API or build changes. nRF53 and nRF91 have their own tickless
     drivers and are not changed here.
   
   ## Testing
   
   Host: Debian Linux (kernel 6.1), x86_64.
   Toolchain: `arm-none-eabi-gcc (Arm GNU Toolchain 13.3.Rel1) 13.3.1 20240614`
   
   Build (CMake/Ninja), without warnings: `nrf52840-dk:ostest_tickless`,
   `nrf52832-dk:ostest_tickless`. All three commits pass `tools/checkpatch.sh`.
   
   Host model (second commit): the old and the new `rtc_prepare_alarm()`, 
copied unchanged from
   the driver, compiled against a modelled 24-bit COUNTER and CC0 (the COUNTER
   advancing once every 8-15 register reads, or frozen). Fixed cases:
   
   | Case | Old: fires in | New: fires in |
   |---|---|---|
   | deadline passed 30 us before an overflow, programmed 60 us after it | 504 
s | 61 us |
   | 1 s ahead, 8 s into the period | 0.86 s | 1.000 s |
   | 1 s ahead, 64 / 256 / 500 s into the period | 61 us | 1.000 s |
   | 2 ms ahead, across an overflow | 61 us | 2.01 ms |
   
   And 2,000,000 random deadlines from 10 ms in the past to 600 s ahead at
   random points in the period: the old code fired early or up to a period late
   in 64 % of them. The new code fired every alarm within the period 0-33 us
   after its deadline and every passed one two counts after the call, with CC0
   always at least two counts ahead when written.
   
   Time read (third commit), same model with an OVRFLW event: read just after an
   overflow whose interrupt has not run, `up_timer_gettime()` returned 
1536.0005 s
   instead of 2048.0005 s before, and the right time after; reads before the
   overflow and after its interrupt are unchanged.
   
   Hardware: nRF52840-DK (PCA10056). `nrf52840-dk:nsh` with tickless on RTC1
   (`NRF52_SYSTIMER_RTC_INSTANCE=1`, `SCHED_TICKLESS_ALARM=y`,
   `USEC_PER_TICK=100`) plus a small test command, built from `master` (before)
   and from this branch (after), otherwise identical. The command:
   
   1. counts RTC1 COMPARE0 events in hardware (PPI to TIMER2 in counter mode)
      during 100 `usleep(20000)` calls, 16 s into the RTC period;
   2. forces an overflow (`TASKS_TRIGOVRFLW`) with interrupts disabled and
      reads `CLOCK_BOOTTIME` just before and just after it, before the overflow
      interrupt runs;
   3. in the same window, starts a watchdog (`wd_start_abstick()`) for the tick
      read just before the overflow, then enables interrupts and waits up to 5 s
      for it.
   
   Before:
   
   ```
   1. RTC1 alarms, 100 sleeps of 20 ms at 16 s into the period
      32971 alarms fired: FAIL (alarms fire early and re-arm)
   2. CLOCK_BOOTTIME across the overflow: -511998535 us: FAIL (time jumped)
   3. passed deadline across the overflow: not fired after 5 s: FAIL (stalled; 
reset the board)
   FAIL: 3 of 3 failed
   ```
   
   After:
   
   ```
   1. RTC1 alarms, 100 sleeps of 20 ms at 16 s into the period
      100 alarms fired: PASS (about one per sleep)
   2. CLOCK_BOOTTIME across the overflow: +1465 us: PASS
   3. passed deadline across the overflow fired after 61 us: PASS
   PASS: 0 of 3 failed
   ```
   
   `nrf52840-dk:ostest_tickless` (RTC0, 10 ms ticks): `ostest` passes with
   "Exiting with status 0" both before and after.
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to