@@ -136,7 +136,9 @@ static void alarm_pool_irq_handler(void);
136
136
static void alarm_pool_irq_handler (void ) {
137
137
// This IRQ handler does the main work, as it always (assuming the IRQ hasn't been enabled on both cores
138
138
// which is unsupported) run on the alarm pool's core, and can't be preempted by itself, meaning
139
- // that it doesn't need locks except to protect against linked list access
139
+ // that it doesn't need locks except to protect against linked list, or other state access.
140
+ // This simplifies the code considerably, and makes it much faster in general, even though we are forced to take
141
+ // two IRQs per alarm.
140
142
uint timer_alarm_num ;
141
143
alarm_pool_timer_t * timer = ta_from_current_irq (& timer_alarm_num );
142
144
uint timer_num = ta_timer_num (timer );
@@ -263,9 +265,17 @@ static void alarm_pool_irq_handler(void) {
263
265
// need to wait
264
266
alarm_pool_entry_t * earliest_entry = & pool -> entries [earliest_index ];
265
267
earliest_target = earliest_entry -> target ;
266
- ta_set_timeout (timer , timer_alarm_num , earliest_target );
267
- // check we haven't now past the target time; if not we don't want to loop again
268
+ // we are leaving a timeout every 2^32 microseconds anyway if there is no valid target, so we can choose any value.
269
+ // best_effort_wfe_or_timeout now relies on it being the last value set, and arguably this is the
270
+ // best value anyway, as it is the furthest away from the last fire.
271
+ if (earliest_target != -1 ) { // cancelled alarm has target of -1
272
+ ta_set_timeout (timer , timer_alarm_num , earliest_target );
273
+ }
274
+ // check we haven't now passed the target time; if not we don't want to loop again
268
275
} while ((earliest_target - (int64_t )ta_time_us_64 (timer )) <= 0 );
276
+ // We always want the timer IRQ to wake a WFE so that best_effort_wfe_or_timeout() will wake up. It will wake
277
+ // a WFE on its own core by nature of having taken an IRQ, but we do an explicit SEV so it wakes the other core
278
+ __sev ();
269
279
}
270
280
271
281
void alarm_pool_post_alloc_init (alarm_pool_t * pool , alarm_pool_timer_t * timer , uint hardware_alarm_num , uint max_timers ) {
@@ -437,26 +447,48 @@ bool best_effort_wfe_or_timeout(absolute_time_t timeout_timestamp) {
437
447
return time_reached (timeout_timestamp );
438
448
} else {
439
449
alarm_id_t id ;
440
- id = add_alarm_at (timeout_timestamp , sleep_until_callback , NULL , false);
441
- if (id <= 0 ) {
442
- tight_loop_contents ();
450
+ // note that as of SDK 2.0.0 calling add_alarm_at always causes a SEV. What we really
451
+ // want to do is cause an IRQ at the specified time in the future if there is not
452
+ // an IRQ already happening before then. The problem is that the IRQ may be happening on the
453
+ // other core, so taking an IRQ is the only way to get the state protection.
454
+ //
455
+ // Therefore, we make a compromise; we will set the alarm, if we won't wake up before the right time
456
+ // already. This means that repeated calls to this function with the same timeout will work correctly
457
+ // after the first one! This is fine, because we ask callers to use a polling loop on another
458
+ // event variable when using this function.
459
+ //
460
+ // For this to work, we require that once we have set an alarm, an SEV happens no later than that, even
461
+ // if we cancel the alarm as we do below. Therefore, the IRQ handler (which is always enabled) will
462
+ // never set its wakeup time to a later value, but instead wake up once and then wake up again.
463
+ //
464
+ // This overhead when canceling alarms is a small price to pay for the much simpler/faster/cleaner
465
+ // implementation that relies on the IRQ handler (on a single core) being the only state accessor.
466
+ //
467
+ // Note also, that the use of software spin locks on RP2350 to access state would always cause a SEV
468
+ // due to use of LDREX etc., so actually using spin locks to protect the state would be worse.
469
+ if (ta_wakes_up_on_or_before (alarm_pool_get_default ()-> timer , alarm_pool_get_default ()-> timer_alarm_num ,
470
+ (int64_t )to_us_since_boot (timeout_timestamp ))) {
471
+ // we already are waking up at or before when we want to (possibly due to us having been called
472
+ // before in a loop), so we can do an actual WFE. Note we rely on the fact that the alarm pool IRQ
473
+ // handler always does an explicit SEV, since it may be on the other core.
474
+ __wfe ();
443
475
return time_reached (timeout_timestamp );
444
476
} else {
445
- // the above alarm add now may force an IRQ which will wake us up,
446
- // so we want to consume one __wfe.. we do an explicit __sev
447
- // just to make sure there is one
448
- __sev (); // make sure there is an event sow ee don't block
449
- __wfe ();
450
- if (!time_reached (timeout_timestamp ))
451
- {
452
- // ^ at the point above the timer hadn't fired, so it is safe
453
- // to wait; the event will happen due to IRQ at some point between
454
- // then and the correct wakeup time
455
- __wfe ();
477
+ id = add_alarm_at (timeout_timestamp , sleep_until_callback , NULL , false);
478
+ if (id <= 0 ) {
479
+ tight_loop_contents ();
480
+ return time_reached (timeout_timestamp );
481
+ } else {
482
+ if (!time_reached (timeout_timestamp )) {
483
+ // ^ at the point above the timer hadn't fired, so it is safe
484
+ // to wait; the event will happen due to IRQ at some point between
485
+ // then and the correct wakeup time
486
+ __wfe ();
487
+ }
488
+ // we need to clean up if it wasn't us that caused the wfe; if it was this will be a noop.
489
+ cancel_alarm (id );
490
+ return time_reached (timeout_timestamp );
456
491
}
457
- // we need to clean up if it wasn't us that caused the wfe; if it was this will be a noop.
458
- cancel_alarm (id );
459
- return time_reached (timeout_timestamp );
460
492
}
461
493
}
462
494
#else
0 commit comments