From 07fc3a3bd11efa0a3aceed5b73269c733ee15def Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Sun, 2 Aug 2026 11:04:14 -0400 Subject: [PATCH] [tick, timeout] Add support for a 64 bit tick source --- .github/workflows/core-tests.yml | 2 +- docs/writing_a_driver.md | 24 ++++++++++++++++++++++++ tests/core/test_timeout.c | 15 ++++++++++++--- wolfHAL/timeout.h | 19 +++++++++++++++++++ 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/.github/workflows/core-tests.yml b/.github/workflows/core-tests.yml index f122277..2d119f7 100644 --- a/.github/workflows/core-tests.yml +++ b/.github/workflows/core-tests.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - extra_cflags: ["", "-DWHAL_CFG_NO_TIMEOUT"] + extra_cflags: ["", "-DWHAL_CFG_NO_TIMEOUT", "-DWHAL_CFG_64BIT_TICK"] steps: - uses: actions/checkout@v4 diff --git a/docs/writing_a_driver.md b/docs/writing_a_driver.md index cccce8d..095f317 100644 --- a/docs/writing_a_driver.md +++ b/docs/writing_a_driver.md @@ -267,6 +267,9 @@ The tick units are determined by the board's `GetTick` implementation. A 1 kHz SysTick gives millisecond ticks; a 1 MHz timer gives microsecond ticks. Drivers do not need to know the tick rate. +The tick fields are 32-bit by default; define `WHAL_CFG_64BIT_TICK` to widen +them to 64-bit (see "64-bit Ticks" below). + #### whal_Reg_ReadPoll For the common case of polling a register bit, use `whal_Reg_ReadPoll` from @@ -325,6 +328,27 @@ When defined, `WHAL_TIMEOUT_START` becomes a no-op and `WHAL_TIMEOUT_EXPIRED` always evaluates to `0`, so polling loops run until the hardware condition is met with no overhead. +#### 64-bit Ticks + +By default the tick fields (`timeoutTicks`, `startTick`, and the `GetTick` +return value) are 32-bit, which wraps in about 49 days at millisecond +resolution. Define `WHAL_CFG_64BIT_TICK` to widen them to `uint64_t` for +systems that need a longer monotonic range. The elapsed-time comparison in +`WHAL_TIMEOUT_EXPIRED` uses unsigned wraparound arithmetic that adjusts to the +configured width, so both settings handle a tick-counter wrap correctly. + +When the macro is defined, the board's tick source must match the width: its +`g_tick` counter and `GetTick` callback (typically `Board_GetTick`) must be +`uint64_t`, since `whal_Timeout.GetTick` becomes `uint64_t (*)(void)`. The +`WHAL_TICK_MAX` macro in `wolfHAL/timeout.h` evaluates to the maximum tick +value at the configured width (`UINT32_MAX` or `UINT64_MAX`) for wrap-handling +logic. + +A 64-bit `GetTick` must return a coherent snapshot. On a 32-bit MCU a 64-bit +read is two loads, so a tick interrupt landing between them returns a torn value +and `WHAL_TIMEOUT_EXPIRED` reports a spurious timeout. Read with interrupts +masked, or retry until the halves agree. + #### Adding Timeout to a Config Struct Driver config structs that use polling should include an optional timeout diff --git a/tests/core/test_timeout.c b/tests/core/test_timeout.c index 93776d0..1171b13 100644 --- a/tests/core/test_timeout.c +++ b/tests/core/test_timeout.c @@ -24,12 +24,21 @@ #ifndef WHAL_CFG_NO_TIMEOUT +#ifdef WHAL_CFG_64BIT_TICK +static uint64_t g_fakeTick; + +static uint64_t FakeTick(void) +{ + return g_fakeTick; +} +#else static uint32_t g_fakeTick; static uint32_t FakeTick(void) { return g_fakeTick; } +#endif static whal_Timeout g_timeout = { .timeoutTicks = 10, @@ -134,12 +143,12 @@ static void Test_Timeout_TickWrap(void) { int expired; - g_fakeTick = UINT32_MAX - 3; + g_fakeTick = WHAL_TICK_MAX - 3; WHAL_TIMEOUT_START(timeout()); - g_fakeTick = UINT32_MAX; + g_fakeTick = WHAL_TICK_MAX; expired = WHAL_TIMEOUT_EXPIRED(timeout()); WHAL_ASSERT_EQ(expired, 0); - g_fakeTick = UINT32_MAX - 3 + 10; + g_fakeTick = WHAL_TICK_MAX - 3 + 10; expired = WHAL_TIMEOUT_EXPIRED(timeout()); WHAL_ASSERT_NEQ(expired, 0); } diff --git a/wolfHAL/timeout.h b/wolfHAL/timeout.h index 6f9e478..58c6627 100644 --- a/wolfHAL/timeout.h +++ b/wolfHAL/timeout.h @@ -36,10 +36,24 @@ * defined, all timeout operations compile away completely. */ +/* Tick width. Define WHAL_CFG_64BIT_TICK to widen ticks to 64-bit; the default + * is 32-bit. A board's g_tick / GetTick source must match this width. */ +#ifdef WHAL_CFG_64BIT_TICK +#define WHAL_TICK_MAX UINT64_MAX +#else +#define WHAL_TICK_MAX UINT32_MAX +#endif + typedef struct { +#ifdef WHAL_CFG_64BIT_TICK + uint64_t timeoutTicks; + uint64_t startTick; + uint64_t (*GetTick)(void); +#else uint32_t timeoutTicks; uint32_t startTick; uint32_t (*GetTick)(void); +#endif } whal_Timeout; #ifdef WHAL_CFG_NO_TIMEOUT @@ -66,8 +80,13 @@ typedef struct { * * Safe to call with a NULL pointer — returns 0 (not expired). */ +#ifdef WHAL_CFG_64BIT_TICK +#define WHAL_TIMEOUT_EXPIRED(t) \ + ((t) && ((uint64_t)((t)->GetTick() - (t)->startTick) >= (t)->timeoutTicks)) +#else #define WHAL_TIMEOUT_EXPIRED(t) \ ((t) && ((uint32_t)((t)->GetTick() - (t)->startTick) >= (t)->timeoutTicks)) +#endif #endif /* WHAL_CFG_NO_TIMEOUT */