Skip to content

Commit 928ba0b

Browse files
authored
Merge pull request #466 from tock/multi_alarm
Fix multi-alarm overlapping bug
2 parents 22f60bb + 702b4c1 commit 928ba0b

File tree

8 files changed

+174
-9
lines changed

8 files changed

+174
-9
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Makefile for user application
2+
3+
# Specify this directory relative to the current application.
4+
TOCK_USERLAND_BASE_DIR = ../../..
5+
6+
# Which files to compile.
7+
C_SRCS := $(wildcard *.c)
8+
9+
# Include userland master makefile. Contains rules and flags for actually
10+
# building the application.
11+
include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Test Multiple Alarms (With Overflow)
2+
3+
This tests the virtual alarms available to userspace. It sets two
4+
alarms, first one that overflows the alarm, such that its expiration
5+
is small in absolute value (but shouldn't fire until after the clock
6+
overflows) and one after 1s. When successful, the second (1s) alarm
7+
should fire after 1 second, while the first alarm should wait to fire
8+
until after the clock overflows (approximately 7 minutes if the clock
9+
is at 32kHz).
10+
11+
If the virtual alarm library is buggy, this test might fail by
12+
scheduling the 1 second alarm _after_ the longer wrapping alarm, and
13+
it won't fire immediately.
14+
15+
# Example Output
16+
17+
Correct:
18+
19+
```
20+
2 10512380 10511329
21+
1 3 1
22+
```
23+
24+
Incorrect:
25+
26+
(after no output for an entire clock overflow, e.g. ~7 minutes)
27+
28+
```
29+
1 3 1
30+
2 10512341 10511329
31+
```
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
4+
#include <libtock-sync/services/alarm.h>
5+
6+
static void event_cb(uint32_t now, uint32_t expiration, void* ud) {
7+
int i = (int)ud;
8+
printf("%d %lu %lu\n", i, now, expiration);
9+
}
10+
11+
int main(void) {
12+
libtock_alarm_t t1;
13+
libtock_alarm_t t2;
14+
15+
uint32_t now;
16+
libtock_alarm_command_read(&now);
17+
18+
uint32_t overflow_ms = libtock_alarm_ticks_to_ms(UINT32_MAX);
19+
20+
libtock_alarm_in_ms(overflow_ms, event_cb, (void*)1, &t1);
21+
libtock_alarm_in_ms(1000, event_cb, (void*)2, &t2);
22+
23+
while (1) {
24+
yield();
25+
}
26+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Makefile for user application
2+
3+
# Specify this directory relative to the current application.
4+
TOCK_USERLAND_BASE_DIR = ../../..
5+
6+
# Which files to compile.
7+
C_SRCS := $(wildcard *.c)
8+
9+
# Include userland master makefile. Contains rules and flags for actually
10+
# building the application.
11+
include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Test Multiple Alarms (Simple)
2+
3+
This tests the virtual alarms available to userspace. It sets two
4+
repeating alarms, at 500ms and 1s respectively, each prints the alarm
5+
index (1 or 2), current tick and alarm expiration to the console. When
6+
successful, both alarms should fire and alarm 1 should fire twice as
7+
often as alarm 2.
8+
9+
## Example Output
10+
11+
Correct:
12+
13+
```sh
14+
1 6316032 6314240
15+
2 10512384 10510592
16+
1 10512384 10511104
17+
1 14722560 14720768
18+
2 18903552 18901760
19+
1 18919680 18917632
20+
1 23116544 23114752
21+
2 27294720 27292928
22+
...
23+
```
24+
25+
(Note that timestamps, the second and third column, and exact ordering of `1` and `2` will differ by execution)
26+
27+
Incorrect:
28+
29+
```sh
30+
1 7254784 7252992
31+
2 11451136 11449344
32+
2 19842304 19840512
33+
2 28233472 28231680
34+
2 36624640 36622848
35+
2 45015808 45014016
36+
2 53406976 53405184
37+
...
38+
```
39+
40+
Note that only alarm `2` appears after the first `1`.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
4+
#include <libtock-sync/services/alarm.h>
5+
6+
static void event_cb(uint32_t now, uint32_t expiration, void* ud) {
7+
int i = (int)ud;
8+
printf("%d %lu %lu\n", i, now, expiration);
9+
}
10+
11+
int main(void) {
12+
libtock_alarm_t t1;
13+
libtock_alarm_t t2;
14+
15+
libtock_alarm_repeating_every_ms(500, event_cb, (void*)1, &t1);
16+
libtock_alarm_repeating_every_ms(1000, event_cb, (void*)2, &t2);
17+
18+
while (1) {
19+
yield();
20+
}
21+
}

libtock/services/alarm.c

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@
55

66
#define MAX_TICKS UINT32_MAX
77

8-
// Returns 0 if a <= b < c, 1 otherwise
9-
static int within_range(uint32_t a, uint32_t b, uint32_t c) {
10-
return (b - a) < (b - c);
11-
}
12-
138
/** \brief Convert milliseconds to clock ticks
149
*
1510
* WARNING: This function will assert if the output
@@ -40,7 +35,7 @@ static uint32_t ms_to_ticks(uint32_t ms) {
4035
return ticks;
4136
}
4237

43-
static uint32_t ticks_to_ms(uint32_t ticks) {
38+
uint32_t libtock_alarm_ticks_to_ms(uint32_t ticks) {
4439
// `ticks_to_ms`'s conversion will be accurate to within the range
4540
// 0 to 1 milliseconds less than the exact conversion
4641
// (true millisecond conversion - [0,1) milliseconds).
@@ -75,19 +70,45 @@ static uint32_t ticks_to_ms(uint32_t ticks) {
7570
static libtock_alarm_ticks_t* root = NULL;
7671

7772
static void root_insert(libtock_alarm_ticks_t* alarm) {
73+
// We want to insert the new alarm just before an existing alarm
74+
// that should expire next after it, as `alarm_upcall` breaks as
75+
// soon as it finds an alarm that should not fire yet. To do so, we
76+
// need to account for an empty list, a non-empty list where the new
77+
// alarm should be last, as well as computing relative expirations
78+
// when alarm expirations may overflow the clock 0 or 1 times.
79+
7880
if (root == NULL) {
7981
root = alarm;
8082
root->next = NULL;
8183
root->prev = NULL;
8284
return;
8385
}
8486

87+
// Compute the tick value at which the new alarm will expire.
88+
uint32_t new_expiration = alarm->reference + alarm->dt;
89+
// Determine whether the clock overflows before the new alarm
90+
// expires. Because ticks are 32-bit, a clock can overflow at most
91+
// once before a 32-bit alarm fires.
92+
bool new_overflows = alarm->reference > (UINT32_MAX - alarm->dt);
93+
8594
libtock_alarm_ticks_t** cur = &root;
8695
libtock_alarm_ticks_t* prev = NULL;
8796
while (*cur != NULL) {
97+
// Compute the tick value at which this alarm will expire.
8898
uint32_t cur_expiration = (*cur)->reference + (*cur)->dt;
89-
uint32_t new_expiration = alarm->reference + alarm->dt;
90-
if (!within_range(alarm->reference, cur_expiration, new_expiration)) {
99+
// Determine whether the clock overflows before this alarm
100+
// expires.
101+
bool cur_overflows = (*cur)->reference > (UINT32_MAX - (*cur)->dt);
102+
103+
// This alarm (`cur`) happens after the new alarm (`alarm`) if:
104+
// - both overflow or neither overflow, and cur expiration is
105+
// larger than the new expiration
106+
// - or, only cur overflows
107+
//
108+
// If the new alarm overflows and this alarm doesn't, this alarm
109+
// happens _before_ the new alarm.
110+
if (((cur_overflows == new_overflows) && (cur_expiration > new_expiration)) ||
111+
cur_overflows) {
91112
// insert before
92113
libtock_alarm_ticks_t* tmp = *cur;
93114
*cur = alarm;
@@ -231,7 +252,7 @@ int libtock_alarm_in_ms(uint32_t ms, libtock_alarm_callback cb, void* opaque, li
231252
// schedule multiple alarms to reach the full length. We calculate the number of full overflows
232253
// and the remainder ticks to reach the target length of time. The overflows use the
233254
// `overflow_callback` for each intermediate overflow.
234-
const uint32_t max_ticks_in_ms = ticks_to_ms(MAX_TICKS);
255+
const uint32_t max_ticks_in_ms = libtock_alarm_ticks_to_ms(MAX_TICKS);
235256
if (ms > max_ticks_in_ms) {
236257
// overflows_left is the number of intermediate alarms that need to be scheduled to reach the target
237258
// dt_ms. After the alarm in this block is scheduled, we have this many overflows left (hence the reason

libtock/services/alarm.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ extern "C" {
3131
// - `arg3` (`opaque`): An arbitrary user pointer passed back to the callback.
3232
typedef void (*libtock_alarm_callback)(uint32_t, uint32_t, void*);
3333

34+
/** \brief Utility function for converting hardware-specific ticks to milliseconds
35+
*/
36+
uint32_t libtock_alarm_ticks_to_ms(uint32_t);
37+
3438
/** \brief Opaque handle to a single-shot alarm.
3539
*
3640
* An opaque handle to an alarm created by `alarm_at` or `alarm_in`. Memory

0 commit comments

Comments
 (0)