Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions smtc_rac_lib/radio_planner/src/radio_planner.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ static void rp_consumption_statistics_updated( radio_planner_t* rp, const uint8_
*/
static void rp_timer_irq_callback( void* obj );

#if( RP_LAZY_SLEEP_DELAY_MS > 0 )
/**
* @brief rp_lazy_sleep_flush put a standby-held radio to sleep if a lazy-sleep
* hold is pending (no-op otherwise)
*
* @param rp pointer to the radioplanner object itself
*/
static void rp_lazy_sleep_flush( radio_planner_t* rp );
#endif

/**
* @brief rp_hook_callback call the callback associated to the id
*
Expand Down Expand Up @@ -236,6 +246,14 @@ void rp_init( radio_planner_t* rp, const ralf_t* radio )
rp->next_state_status = RP_STATUS_NO_MORE_TASK_SCHEDULE;
rp->margin_delay = RP_MARGIN_DELAY;
rp->disable_failsafe = 0;
#if( RP_LAZY_SLEEP_DELAY_MS > 0 )
rp->lazy_sleep_pending = false;
rp->lazy_fallback_configured = false;
rp->lazy_sleep_deadline_ms = 0;
rp->lazy_sleep_target = rp->radio;
SMTC_MODEM_HAL_TRACE_PRINTF( "RP: lazy-sleep hysteresis enabled: %d ms\n",
RP_LAZY_SLEEP_DELAY_MS );
#endif
print_hook_ids( );
}
rp_hook_status_t rp_attach_new_radio( radio_planner_t* rp, const ralf_t* radio, const uint8_t hook_id )
Expand Down Expand Up @@ -528,7 +546,30 @@ void rp_callback( radio_planner_t* rp )
// the arbiter
rp->radio_is_free = false; // this is a kind of critical section
rp_task_free( rp, &rp->tasks[rp->radio_task_id] );
#if( RP_LAZY_SLEEP_DELAY_MS > 0 )
// Lazy sleep: leave the radio in standby instead of sleeping it.
// If the hook callback (or a later enqueue inside the hysteresis
// window) launches a new task, it starts from standby with no
// sleep-wakeup cost. The arbiter below arms the hysteresis
// deadline; on expiry with no task the radio sleeps as before.
// Configure the transceiver (once) to fall back to STDBY_XOSC by
// itself after TX/RX so the oscillator/TCXO stays running between
// back-to-back tasks; a synchronous SetStandby(XOSC) here would
// just re-pay the TCXO startup on the critical path instead
// (the chip drops to STDBY_RC on TX done and restarting the
// oscillator blocks the next SPI command for the TCXO delay).
// Best-effort: not all radios support a fallback mode.
if( rp->lazy_fallback_configured == false )
{
rp->lazy_fallback_configured = true;
( void ) ral_set_rx_tx_fallback_mode( TARGET_RAL, RAL_FALLBACK_STDBY_XOSC );
}
Comment on lines +562 to +566

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

ast-grep outline smtc_rac_lib/radio_planner/src/radio_planner.c --items all --type function
rg -n -C 5 --glob '*.[ch]' \
  '\bral_set_rx_tx_fallback_mode\s*\(|RAL_FALLBACK_STDBY_XOSC' .
rg -n -C 5 --glob '*.[ch]' \
  '\brp_task_launch_current\s*\(|launch_task_callbacks' smtc_rac_lib/radio_planner/src

Repository: Open-Source-Space-Foundation/usp

Length of output: 17466


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Inspect the immediate control flow around the fallback setup and task launching.
sed -n '463,590p' smtc_rac_lib/radio_planner/src/radio_planner.c
printf '\n--- rp_task_launch_current ---\n'
sed -n '1051,1073p' smtc_rac_lib/radio_planner/src/radio_planner.c
printf '\n--- rp_radio_irq_callback/irq path ---\n'
sed -n '1510,1525p' smtc_rac_lib/radio_planner/src/radio_planner.c
printf '\n--- call sites of rp_callback ---\n'
rg -n -C 4 '\brp_callback\s*\(' .

Repository: Open-Source-Space-Foundation/usp

Length of output: 15328


Configure the fallback before launching the next radio task.

The fallback is configured only during rp_radio_irq_callback, after the previous radio operation has transitioned the transceiver to standby. The next task’s rp_task_launch_current callback can run before this idle-task path, so move the fallback setup before launching RX/TX and only mark it configured after ral_set_rx_tx_fallback_mode() succeeds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@smtc_rac_lib/radio_planner/src/radio_planner.c` around lines 555 - 559, Move
the lazy fallback setup from the idle path in rp_radio_irq_callback to
immediately before launching the next RX/TX operation in rp_task_launch_current.
Call ral_set_rx_tx_fallback_mode() before the launch, and set
rp->lazy_fallback_configured to true only when that call succeeds; retain the
guard to avoid repeated configuration.

rp->lazy_sleep_pending = true;
rp->lazy_sleep_target = TARGET_RADIO;
rp->lazy_sleep_deadline_ms = smtc_modem_hal_get_time_in_ms( ) + RP_LAZY_SLEEP_DELAY_MS;
#else
SMTC_MODEM_HAL_PANIC_ON_FAILURE( ral_set_sleep( TARGET_RAL, true ) == RAL_STATUS_OK );
#endif
rp->radio = TARGET_RADIO;

rp_hook_callback( rp, rp->radio_task_id );
Expand All @@ -541,6 +582,10 @@ void rp_callback( radio_planner_t* rp )
{
// A radio irq happened and no rp task is on going, put radio to sleep
// even in case multiple radio put only main radio in sleep
#if( RP_LAZY_SLEEP_DELAY_MS > 0 )
rp->lazy_sleep_pending = false; // all radios sleep below
rp->lazy_fallback_configured = false;
#endif
for( int i = 0; i < RP_NB_HOOKS; i++ )
{
SMTC_MODEM_HAL_PANIC_ON_FAILURE(
Expand Down Expand Up @@ -783,17 +828,62 @@ static void rp_task_arbiter( radio_planner_t* rp, const char* caller_func_name )
{
if( rp->timer_value > rp->margin_delay )
{
#if( RP_LAZY_SLEEP_DELAY_MS > 0 )
// The alarm now belongs to a scheduled future task: give up a
// pending lazy-sleep hold and sleep the radio immediately, as
// the legacy code would (the future launch pays the wakeup).
if( rp->tasks[rp->radio_task_id].state != RP_TASK_STATE_RUNNING )
{
rp_lazy_sleep_flush( rp );
}
#endif
rp_set_alarm( rp, rp->timer_value - rp->margin_delay );
}
else
{
rp->timer_irq_flag = true;
}
}
#if( RP_LAZY_SLEEP_DELAY_MS > 0 )
else if( ( rp->lazy_sleep_pending == true ) &&
( rp->tasks[rp->radio_task_id].state != RP_TASK_STATE_RUNNING ) )
{
// Priority task exists but no timer to set and nothing launched:
// arm/expire the lazy-sleep hysteresis deadline.
int32_t lazy_remaining_ms =
( int32_t ) ( rp->lazy_sleep_deadline_ms - smtc_modem_hal_get_time_in_ms( ) );
if( lazy_remaining_ms > 0 )
{
rp_set_alarm( rp, ( uint32_t ) lazy_remaining_ms );
}
else
{
rp_lazy_sleep_flush( rp );
}
}
#endif
}
else
{ // No more tasks in the radio planner
rp_task_call_aborted( rp );
#if( RP_LAZY_SLEEP_DELAY_MS > 0 )
if( ( rp->lazy_sleep_pending == true ) &&
( rp->tasks[rp->radio_task_id].state != RP_TASK_STATE_RUNNING ) )
{
int32_t lazy_remaining_ms =
( int32_t ) ( rp->lazy_sleep_deadline_ms - smtc_modem_hal_get_time_in_ms( ) );
if( lazy_remaining_ms > 0 )
{
// Re-arm the planner timer for the remainder of the hysteresis
// window; expiry re-enters this arbiter and sleeps the radio.
rp_set_alarm( rp, ( uint32_t ) lazy_remaining_ms );
}
else
{
rp_lazy_sleep_flush( rp );
}
}
#endif
Comment thread
coderabbitai[bot] marked this conversation as resolved.
SMTC_MODEM_HAL_RP_TRACE_PRINTF( " RP: No more active tasks\n" );
}
}
Expand Down Expand Up @@ -976,6 +1066,13 @@ static void rp_task_launch_current( radio_planner_t* rp )
}
else
{
#if( RP_LAZY_SLEEP_DELAY_MS > 0 )
// The task takes over the radio: cancel any pending lazy-sleep hold so
// the hysteresis expiry cannot sleep a radio a task is now using. The
// launch proceeds from STDBY_XOSC with no wakeup cost (the HAL wake
// path only runs when the radio is actually asleep).
rp->lazy_sleep_pending = false;
#endif
rp_task_print( rp, &rp->tasks[id] );
rp->radio = TARGET_RADIO;
rp->tasks[id].launch_task_callbacks( ( void* ) rp );
Expand Down Expand Up @@ -1176,6 +1273,21 @@ static void rp_timer_irq( radio_planner_t* rp )
rp_task_arbiter( rp, __func__ );
}

#if( RP_LAZY_SLEEP_DELAY_MS > 0 )
static void rp_lazy_sleep_flush( radio_planner_t* rp )
{
if( rp->lazy_sleep_pending == true )
{
rp->lazy_sleep_pending = false;
// Re-apply the fallback mode on the first hold after the next wakeup:
// it is not guaranteed to survive the sleep period on all radios.
rp->lazy_fallback_configured = false;
SMTC_MODEM_HAL_PANIC_ON_FAILURE( ral_set_sleep( &( rp->lazy_sleep_target->ral ), true ) == RAL_STATUS_OK );
SMTC_MODEM_HAL_RP_TRACE_PRINTF( " RP: lazy-sleep hysteresis expired, radio to sleep\n" );
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
#endif

static void rp_task_call_aborted( radio_planner_t* rp )
{
for( int32_t i = 0; i < RP_NB_HOOKS; i++ )
Expand Down
7 changes: 7 additions & 0 deletions smtc_rac_lib/radio_planner/src/radio_planner.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ typedef struct radio_planner_s
const ralf_t* radio;
const ralf_t* radio_target_attached_to_this_hook[RP_NB_HOOKS];
uint32_t margin_delay;
// Lazy-sleep hysteresis state (see RP_LAZY_SLEEP_DELAY_MS): radio is held
// in STDBY_XOSC after a task completes, and only put to sleep once the
// hysteresis deadline expires with no new task launched.
bool lazy_sleep_pending;
bool lazy_fallback_configured;
uint32_t lazy_sleep_deadline_ms;
const ralf_t* lazy_sleep_target;
Comment on lines +104 to +107

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Track lazy holds per radio, or flush before switching targets.

A completed task on radio A sets this single target, but launching a task on radio B clears lazy_sleep_pending without flushing A. A can then remain in standby indefinitely. Store hold/fallback state per target, or flush the previous target before changing radios.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@smtc_rac_lib/radio_planner/src/radio_planner.h` around lines 104 - 107,
Update the lazy-sleep state associated with lazy_sleep_pending,
lazy_fallback_configured, lazy_sleep_deadline_ms, and lazy_sleep_target so holds
are tracked independently for each radio target, or flush the currently pending
target before switching to another radio. Ensure launching work on a different
radio cannot clear radio A’s pending hold without completing or preserving it.

} radio_planner_t;

/*
Expand Down
17 changes: 17 additions & 0 deletions smtc_rac_lib/radio_planner/src/radio_planner_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,23 @@ extern "C" {
*/
#define RP_DISABLE_FAILSAFE_KEY 0xF00D4BEE

/*!
* @brief Lazy-sleep hysteresis window in ms.
*
* When a task completes and no other task is ready to launch, the radio is
* held in STDBY_XOSC (oscillator still running) for this long before being
* put to sleep. A task launched inside the window starts from standby and
* skips the full sleep-wakeup sequence (NSS wake glitch, busy-settle wait,
* TCXO restart), which otherwise re-pays the TCXO startup delay on every
* back-to-back transmission. On expiry with the queue still empty the radio
* is put to sleep exactly as before.
*
* Set to 0 to disable and compile the legacy immediate-sleep behavior.
*/
#ifndef RP_LAZY_SLEEP_DELAY_MS
#define RP_LAZY_SLEEP_DELAY_MS 200
#endif

/* clang-format on */

/*
Expand Down