Skip to content

Commit 8b3c94f

Browse files
committed
bug wac-o-mole with pico_time_test - we broke the invariant of adding a wakeup in the future if an
alarm was created and destroyed before the ta_set_timeout was ever called. we must always set an alarm if there is one
1 parent 057582a commit 8b3c94f

2 files changed

Lines changed: 26 additions & 22 deletions

File tree

src/common/pico_time/time.c

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ static void alarm_pool_irq_handler(void);
139139

140140
// marker which we can use in place of handler function to indicate we are a repeating timer
141141

142-
#define repeating_timer_marker ((alarm_callback_t)alarm_pool_irq_handler)
142+
#define repeating_timer_marker ((alarm_callback_t)(uintptr_t)2)
143+
#define deleted_timer_marker ((alarm_callback_t)(uintptr_t)4)
144+
143145
#include "hardware/gpio.h"
144146
static void alarm_pool_irq_handler(void) {
145147
// This IRQ handler does the main work, as it always (assuming the IRQ hasn't been enabled on both cores
@@ -168,23 +170,23 @@ static void alarm_pool_irq_handler(void) {
168170
if (earliest_index >= 0) {
169171
alarm_pool_entry_t *earliest_entry = &pool->entries[earliest_index];
170172
earliest_target = earliest_entry->target;
171-
if (((int64_t)ta_time_us_64(timer) - earliest_target) >= 0) {
172-
// time to call the callback now (or in the past)
173-
// note that an entry->target of < 0 means the entry has been canceled (not this is set
174-
// by this function, in response to the entry having been queued by the cancel_alarm API
175-
// meaning that we don't need to worry about tearing of the 64 bit value)
173+
// delete_timer flag is set by this function, and then the item is moved to the front
174+
// of the queue for deletion now
175+
bool delete_timer = earliest_entry->callback == deleted_timer_marker;
176+
if (delete_timer || ((int64_t)ta_time_us_64(timer) - earliest_target) >= 0) {
176177
int64_t delta;
177-
if (earliest_target >= 0) {
178-
// special case repeating timer without making another function call which adds overhead
178+
if (!delete_timer) {
179+
// time to call the callback now (or in the past)
179180
if (earliest_entry->callback == repeating_timer_marker) {
181+
// special case repeating timer without making another function call which adds overhead
180182
repeating_timer_t *rpt = (repeating_timer_t *)earliest_entry->user_data;
181183
delta = rpt->callback(rpt) ? rpt->delay_us : 0;
182184
} else {
183185
alarm_id_t id = make_alarm_id(pool->ordered_head, earliest_entry->sequence);
184186
delta = earliest_entry->callback(id, earliest_entry->user_data);
185187
}
186188
} else {
187-
// negative target means cancel alarm
189+
// cancel alarm
188190
delta = 0;
189191
}
190192
if (delta) {
@@ -243,6 +245,19 @@ static void alarm_pool_irq_handler(void) {
243245
new_entry->next = next;
244246
}
245247
}
248+
earliest_index = pool->ordered_head;
249+
if (earliest_index < 0) break;
250+
alarm_pool_entry_t *earliest_entry = &pool->entries[earliest_index];
251+
//printf("NOW EARLIEST INDEX %d timeout %lld\n", earliest_index, earliest_entry->target);
252+
earliest_target = earliest_entry->target;
253+
if (earliest_entry->callback != deleted_timer_marker) {
254+
// we are leaving a timeout every 2^32 microseconds anyway if there is no valid target, so we can choose any value.
255+
// best_effort_wfe_or_timeout now relies on it being the last value set, and arguably this is the
256+
// best value anyway, as it is the furthest away from the last fire.
257+
//printf("SET TIMEOUT %lld\n", earliest_target);
258+
ta_set_timeout(timer, timer_alarm_num, earliest_target);
259+
}
260+
246261
// if we have any canceled alarms, then mark them for removal by setting their due time to -1 (which will
247262
// cause them to be handled the next time round and removed)
248263
if (pool->has_pending_cancellations) {
@@ -255,7 +270,7 @@ static void alarm_pool_irq_handler(void) {
255270
int16_t next = entry->next;
256271
if ((int16_t)entry->sequence < 0) {
257272
// mark for deletion
258-
entry->target = -1;
273+
entry->callback = deleted_timer_marker;
259274
if (index != pool->ordered_head) {
260275
// move to start of queue
261276
*prev = entry->next;
@@ -268,17 +283,6 @@ static void alarm_pool_irq_handler(void) {
268283
index = next;
269284
}
270285
}
271-
earliest_index = pool->ordered_head;
272-
if (earliest_index < 0) break;
273-
// need to wait
274-
alarm_pool_entry_t *earliest_entry = &pool->entries[earliest_index];
275-
earliest_target = earliest_entry->target;
276-
// we are leaving a timeout every 2^32 microseconds anyway if there is no valid target, so we can choose any value.
277-
// best_effort_wfe_or_timeout now relies on it being the last value set, and arguably this is the
278-
// best value anyway, as it is the furthest away from the last fire.
279-
if (earliest_target != -1) { // cancelled alarm has target of -1
280-
ta_set_timeout(timer, timer_alarm_num, earliest_target);
281-
}
282286
// check we haven't now passed the target time; if not we don't want to loop again
283287
} while ((earliest_target - (int64_t)ta_time_us_64(timer)) <= 0);
284288
// We always want the timer IRQ to wake a WFE so that best_effort_wfe_or_timeout() will wake up. It will wake

test/pico_time_test/pico_time_test.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ int64_t timer_callback1(alarm_id_t id, void *user_data) {
5252
assert(timeout >= timeouts && timeout < (timeouts + NUM_TIMEOUTS));
5353
timeout->fired_at = get_absolute_time();
5454
timeout->fired_count++;
55-
// printf("%d %d %ld\n", timeout->pool, id, to_us_since_boot(timeout->target));
55+
// printf("%d %d %lld\n", timeout->pool, id, to_us_since_boot(timeout->target));
5656
return 0;
5757
}
5858

0 commit comments

Comments
 (0)