Skip to content
Closed
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
119 changes: 119 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 @@ -444,8 +462,15 @@ rp_stats_t rp_get_stats( const radio_planner_t* rp )

void rp_callback( radio_planner_t* rp )
{
// UNLOCK_RADIO_ACCESS must be exempt like LOCK_RADIO_ACCESS: a lock task
// held open longer than the failsafe window (e.g. continuous RX under the
// raw RAC) keeps its original start_time_ms, and unlock_radio_access
// retypes the still-RUNNING task to UNLOCK before the engine processes
// it — the very next rp_callback would evaluate the failsafe against the
// stale start time and panic at the moment the client releases the lock.
if( ( rp->tasks[rp->radio_task_id].state == RP_TASK_STATE_RUNNING ) &&
( rp->tasks[rp->radio_task_id].type != RP_TASK_TYPE_LOCK_RADIO_ACCESS ) &&
( rp->tasks[rp->radio_task_id].type != RP_TASK_TYPE_UNLOCK_RADIO_ACCESS ) &&
( rp->disable_failsafe != RP_DISABLE_FAILSAFE_KEY ) &&
( ( int32_t ) ( rp->tasks[rp->radio_task_id].start_time_ms + 128000 - smtc_modem_hal_get_time_in_ms( ) ) < 0 ) )
{
Expand Down Expand Up @@ -521,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 );
}
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 @@ -534,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 @@ -776,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
SMTC_MODEM_HAL_RP_TRACE_PRINTF( " RP: No more active tasks\n" );
}
}
Expand Down Expand Up @@ -969,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 @@ -1169,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 );
Comment on lines +1279 to +1285

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

Defer TCXO shutdown until the lazy-sleep flush.

After Lines 567-569 arm the STDBY_XOSC hold, rp_callback still unconditionally stops the TCXO at Lines 597-599. The eventual flush here only sleeps the radio, so the TCXO is stopped too early and the next task cannot benefit from the hysteresis window. Skip TCXO shutdown while a hold or replacement task is active, and stop it here after ral_set_sleep.

🤖 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 1279 - 1285,
Defer TCXO shutdown from the unconditional stop in rp_callback until the
lazy-sleep flush: skip stopping it while the STDBY_XOSC hold or replacement task
is active, and in the lazy_sleep_pending branch after ral_set_sleep succeeds,
stop the TCXO. Update the relevant rp_callback logic and this lazy-sleep
handling without changing the existing sleep and fallback-state behavior.

SMTC_MODEM_HAL_RP_TRACE_PRINTF( " RP: lazy-sleep hysteresis expired, radio to sleep\n" );
}
}
#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;
} 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