Skip to content

arch/rp23xx: add /dev/timer driver and tickless OS on the RP2350 system TIMER#19530

Merged
acassis merged 2 commits into
apache:masterfrom
casaroli:rp2350-timer-tickless-pr
Jul 25, 2026
Merged

arch/rp23xx: add /dev/timer driver and tickless OS on the RP2350 system TIMER#19530
acassis merged 2 commits into
apache:masterfrom
casaroli:rp2350-timer-tickless-pr

Conversation

@casaroli

Copy link
Copy Markdown
Contributor

Summary

The RP2350 has two independent system timer blocks (TIMER0, TIMER1), each a
free-running 64-bit microsecond counter clocked by the TICKS block (1 MHz) and
independent of the ARM SysTick that drives the OS tick. Each block has four
independent alarms (ALARM0–3), each with its own compare register and IRQ.
The port had no driver for these blocks. This PR adds two features built on
them and makes them share the hardware cleanly.

1. /dev/timer driver (rp23xx_timer.c)

  • A standard NuttX timer lower-half bound to a block; uses ALARM0 (matches the
    low 32 bits of the counter) for single-shot or periodic timeouts, 1 µs
    resolution, up to 2³²−1 µs (~71.5 min) per interval.
  • Periodic reloads are scheduled relative to the previous expiry (no drift),
    never behind the counter (a past alarm would not match until the 32-bit
    counter wraps).
  • Enabled with CONFIG_RP23XX_TIMER; each block is toggled independently:
    CONFIG_RP23XX_TIMER0/dev/timer0, CONFIG_RP23XX_TIMER1/dev/timer1.

2. Tickless OS (rp23xx_oneshot.c)

  • An ONESHOT_COUNT alarm/oneshot lower-half (mirrors
    arch/risc-v/src/common/riscv_mtimer.c) that drives the tickless scheduler
    through up_alarm_set_lowerhalf(). The 64-bit counter is the monotonic time
    base (current()); ALARM0 is the next-event compare.
  • The one adaptation vs. a full 64-bit-compare timer: the ALARM registers match
    only the low 32 bits of the counter. max_delay() is capped below 2³² counts
    so the scheduler never asks for a longer interval, and any deadline already
    due — or reached while the alarm is being armed — is forced immediately via
    the INTF register instead of waiting a full 32-bit wrap.
  • Enabled with CONFIG_RP23XX_SYSTIMER_TICKLESS (mutually exclusive with
    CONFIG_RP23XX_SYSTIMER_SYSTICK); the block is selectable with
    CONFIG_RP23XX_SYSTIMER_TICKLESS_TIMER0 (default) / _TIMER1.
    ARCH_CHIP_RP23XX now selects ARCH_HAVE_TICKLESS.

Coexistence. The block chosen for tickless is claimed exclusively by the
scheduler and is removed from the /dev/timer choices in Kconfig
(RP23XX_TIMER0 depends on !RP23XX_SYSTIMER_TICKLESS_TIMER0, and likewise for
TIMER1), so the two features never collide on the same block or its alarm IRQ.
Result:

  • tickless disabled → both /dev/timer0 and /dev/timer1 available;
  • tickless on TIMER0 → scheduler owns TIMER0, /dev/timer1 available;
  • tickless on TIMER1 → scheduler owns TIMER1, /dev/timer0 available.

Impact

  • New feature, opt-in; no change to existing behavior when both options are
    disabled. Default OS tick remains the ARM SysTick.
  • Arch: arm (rp23xx / RP2350). Boards: rp23xx common bringup registers the
    enabled /dev/timerN device(s).
  • No new external dependencies. No ABI/API changes outside the new Kconfig
    symbols and the new /dev/timerN device nodes.

Testing

Hardware: RP2350 board (Pimoroni Pico Plus 2), raspberrypi-pico-2:nsh
configuration, programmed over SWD with a Raspberry Pi Debug Probe (probe-rs)
and driven from the nsh console on UART0 through the same probe's UART
bridge. Both timer blocks were exercised in both roles, and in each build
the two features ran at the same time on different blocks.

Configuration A — tickless on TIMER0, /dev/timer1 on TIMER1

RP23XX_SYSTIMER_TICKLESS + SYSTIMER_TICKLESS_TIMER0 + SCHED_TICKLESS +
SCHED_TICKLESS_ALARM + RP23XX_TIMER + RP23XX_TIMER1 + EXAMPLES_TIMER
with CONFIG_EXAMPLES_TIMER_DEVNAME="/dev/timer1".

  • Boots to nsh.
  • Kconfig exclusion confirmed at runtime: ls /dev lists timer1 and
    not timer0 — the block the scheduler claimed is not offered as a
    /dev/timer device.
  • examples/timer on /dev/timer1 runs while tickless drives the scheduler
    from TIMER0
    — this is the coexistence proof. timeleft counts down
    smoothly within each 1 s interval and nsignals increments 0 → 1 → 2 as the
    ALARM0 matches are delivered to the user signal handler; the app stops the
    timer and exits cleanly.
  • Timed wakeups (sleep N, measured by when the prompt returns):
    1 s → +0.014 s, 2 s → +0.061 s, 4 s → +0.053 s, 8 s → +0.063 s.
  • Clock rate: board uptime advanced 123 s while the host wall clock
    advanced 123.04 s
    — −0.034 % over two minutes, within uptime's 1 s
    display quantization.

Configuration B — tickless on TIMER1, /dev/timer0 on TIMER0

The mirror image of A (SYSTIMER_TICKLESS_TIMER1 + RP23XX_TIMER0,
CONFIG_EXAMPLES_TIMER_DEVNAME="/dev/timer0"), which exercises the block-select
path for both features.

  • Boots to nsh; ls /dev lists timer0 and not timer1.
  • examples/timer on /dev/timer0 runs to completion with nsignals
    incrementing, while tickless runs on TIMER1.
  • Timed wakeups: 1 s → +0.055 s, 2 s → +0.043 s, 4 s → +0.059 s,
    8 s → +0.061 s.
  • Clock rate: uptime +123 s vs 123.02 s wall — −0.016 % over two minutes.

The residual tens of milliseconds on sleep are shell/scheduler round-trip
overhead, not clock drift: the two-minute rate measurements bound the drift at
well under 0.05 %.

Build / config matrix (all clean)

Configuration Result
RP23XX_TIMER + RP23XX_TIMER0 + RP23XX_TIMER1 + EXAMPLES_TIMER builds & links
tickless on TIMER0 + /dev/timer1 (config A above) builds, links, HW-tested
tickless on TIMER1 + /dev/timer0 (config B above) builds, links, HW-tested
  • tools/checkpatch.sh -f (nxstyle) and -g (patches + commit messages):
    clean on both commits.
  • No Kconfig warnings; the forward references between the two features degrade
    to n when only one is present.

Note on the console used

Testing used the UART0 console rather than usbnsh. On this board the USB-CDC
console drops after a couple of commands; that is independent of this PR
a pristine raspberrypi-pico-2:usbnsh build with none of these options enabled
was flashed as a control and behaved identically, which matches the existing
entry in the rp23xx documentation ("USB — Experimental — usbnsh configuration
is somewhat working with some data corruption"). Nothing in this PR touches the
USB device driver.

@casaroli
casaroli requested a review from jerpelea as a code owner July 25, 2026 08:29
@github-actions github-actions Bot added Area: Documentation Improvements or additions to documentation Arch: arm Issues related to ARM (32-bit) architecture Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces. Board: arm labels Jul 25, 2026
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

@xiaoxiang781216

Copy link
Copy Markdown
Contributor

please fix the conflict @casaroli

casaroli added 2 commits July 25, 2026 14:34
The RP2350 has two system timer blocks (TIMER0, TIMER1), each a free-running
64-bit counter incremented once per microsecond by the TICKS block (set up in
rp23xx_clock.c).  They are independent of the ARM SysTick that drives the OS
tick, so they are free for application use, but the port had no driver for
them.

Add rp23xx_timer.c, a NuttX timer lower-half that binds a block to a
/dev/timerN device.  It uses ALARM0 of the block, which matches the low 32
bits of the microsecond counter, to implement single-shot and periodic
timeouts with 1 us resolution and a maximum interval of 2^32 - 1 us (~71.5
minutes).  Periodic reloads are scheduled relative to the previous expiry to
avoid drift, but never behind the counter (an alarm set in the past would not
match until the 32-bit counter wraps).

Enable with CONFIG_RP23XX_TIMER (which selects CONFIG_TIMER), then turn on each
block independently: CONFIG_RP23XX_TIMER0 registers /dev/timer0 and
CONFIG_RP23XX_TIMER1 registers /dev/timer1.  A block claimed by the tickless
oneshot (CONFIG_RP23XX_SYSTIMER_TICKLESS) is excluded from these choices in
Kconfig, so the two features can be enabled together without colliding on the
same block or its alarm IRQ.

Tested on Pimoroni Pico Plus 2 (RP2350B) hardware with examples/timer and a
CMSIS-DAP probe.  The device registers as /dev/timer0; the ALARM0 interrupt
fires at the programmed period (verified over SWD at the configured 1 s
interval, not a busy loop), and the full path -- alarm match, the driver ISR,
the timer notification and delivery of the SIGNO to user space -- reaches the
example's signal handler and increments its counter.

Assisted-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
The port could only run with a periodic ARM SysTick tick.  Add an alarm/
oneshot lower-half backed by an RP2350 system timer block so the scheduler
can run tickless, waking the CPU only when a timer actually expires.

rp23xx_oneshot.c implements the ONESHOT_COUNT lower-half (mirroring the
RISC-V mtimer driver).  A timer block is a free-running 64-bit microsecond
counter (clocked by the TICKS block, independent of the SysTick), which serves
directly as the monotonic time base returned by current(), so timekeeping is
exact to 1 us.  The one adaptation versus a full 64-bit compare timer is that
the RP2350 ALARM registers match only the low 32 bits of the counter:
max_delay() is therefore capped below 2^32 counts (~71.5 minutes) so the
scheduler never asks for a longer interval, and any deadline that is already
due -- or that the counter reaches while the alarm is being armed -- is
raised immediately through the INTF force register instead of waiting a full
32-bit wrap for the compare to match again.

Enabled with CONFIG_RP23XX_SYSTIMER_TICKLESS (mutually exclusive with
RP23XX_SYSTIMER_SYSTICK), which selects ONESHOT, ONESHOT_COUNT and
ALARM_ARCH; up_timer_initialize() then hands the oneshot to
up_alarm_set_lowerhalf().  ARCH_CHIP_RP23XX now selects ARCH_HAVE_TICKLESS.
The block is selectable with CONFIG_RP23XX_SYSTIMER_TICKLESS_TIMER0 (default)
or _TIMER1; the chosen block is claimed exclusively by the scheduler and is
excluded from the /dev/timer driver (CONFIG_RP23XX_TIMER) in Kconfig, so the
tickless clock and a /dev/timer device can coexist on different blocks.

Tested on Pimoroni Pico Plus 2 (RP2350B) hardware with CONFIG_SCHED_TICKLESS
and CONFIG_SCHED_TICKLESS_ALARM: the image boots to nsh and keeps accurate
time -- "uptime" advances at real-time rate (17 s over a measured 17.4 s) and
"sleep 4" blocks for 4.25 s of wall time -- confirming the oneshot both drives
the scheduler and provides a correct 1 MHz monotonic clock.

Assisted-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
@casaroli
casaroli force-pushed the rp2350-timer-tickless-pr branch from 9995efe to e851647 Compare July 25, 2026 13:40
@acassis
acassis merged commit 5fd518a into apache:master Jul 25, 2026
39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: arm Issues related to ARM (32-bit) architecture Area: Documentation Improvements or additions to documentation Board: arm Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants