diff options
author | Nick Brassel <nick@tzarc.org> | 2021-11-16 05:21:09 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-15 18:21:09 +0000 |
commit | 36d123e9c5a9ce0e29b9bc22ef87661bf479e299 (patch) | |
tree | 1033718afb33e1eb7dc3cd7b99db59a90668fa8a /quantum | |
parent | b3ee124da666287ef13787523ea88bf40713ff4c (diff) | |
download | qmk_firmware-36d123e9c5a9ce0e29b9bc22ef87661bf479e299.tar.gz qmk_firmware-36d123e9c5a9ce0e29b9bc22ef87661bf479e299.zip |
Add support for deferred executors. (#14859)
* Add support for deferred executors.
* More docs.
* Include from quantum.h
* Cleanup.
* Parameter checks
* Comments.
* qmk format-c
* I accidentally a few words.
* API name change.
* Apply suggestions from code review
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
* Review comments.
* qmk format-c
* Review comments.
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
Diffstat (limited to 'quantum')
-rw-r--r-- | quantum/deferred_exec.c | 152 | ||||
-rw-r--r-- | quantum/deferred_exec.h | 38 | ||||
-rw-r--r-- | quantum/main.c | 10 | ||||
-rw-r--r-- | quantum/quantum.h | 4 |
4 files changed, 204 insertions, 0 deletions
diff --git a/quantum/deferred_exec.c b/quantum/deferred_exec.c new file mode 100644 index 000000000..5b0a5b142 --- /dev/null +++ b/quantum/deferred_exec.c | |||
@@ -0,0 +1,152 @@ | |||
1 | // Copyright 2021 Nick Brassel (@tzarc) | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | #include <stddef.h> | ||
5 | #include <timer.h> | ||
6 | #include <deferred_exec.h> | ||
7 | |||
8 | #ifndef MAX_DEFERRED_EXECUTORS | ||
9 | # define MAX_DEFERRED_EXECUTORS 8 | ||
10 | #endif | ||
11 | |||
12 | typedef struct deferred_executor_t { | ||
13 | deferred_token token; | ||
14 | uint32_t trigger_time; | ||
15 | deferred_exec_callback callback; | ||
16 | void * cb_arg; | ||
17 | } deferred_executor_t; | ||
18 | |||
19 | static deferred_token current_token = 0; | ||
20 | static uint32_t last_deferred_exec_check = 0; | ||
21 | static deferred_executor_t executors[MAX_DEFERRED_EXECUTORS] = {0}; | ||
22 | |||
23 | static inline bool token_can_be_used(deferred_token token) { | ||
24 | if (token == INVALID_DEFERRED_TOKEN) { | ||
25 | return false; | ||
26 | } | ||
27 | for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { | ||
28 | if (executors[i].token == token) { | ||
29 | return false; | ||
30 | } | ||
31 | } | ||
32 | return true; | ||
33 | } | ||
34 | |||
35 | static inline deferred_token allocate_token(void) { | ||
36 | deferred_token first = ++current_token; | ||
37 | while (!token_can_be_used(current_token)) { | ||
38 | ++current_token; | ||
39 | if (current_token == first) { | ||
40 | // If we've looped back around to the first, everything is already allocated (yikes!). Need to exit with a failure. | ||
41 | return INVALID_DEFERRED_TOKEN; | ||
42 | } | ||
43 | } | ||
44 | return current_token; | ||
45 | } | ||
46 | |||
47 | deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) { | ||
48 | // Ignore queueing if it's a zero-time delay, or invalid callback | ||
49 | if (delay_ms == 0 || !callback) { | ||
50 | return INVALID_DEFERRED_TOKEN; | ||
51 | } | ||
52 | |||
53 | // Find an unused slot and claim it | ||
54 | for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { | ||
55 | deferred_executor_t *entry = &executors[i]; | ||
56 | if (entry->token == INVALID_DEFERRED_TOKEN) { | ||
57 | // Work out the new token value, dropping out if none were available | ||
58 | deferred_token token = allocate_token(); | ||
59 | if (token == INVALID_DEFERRED_TOKEN) { | ||
60 | return false; | ||
61 | } | ||
62 | |||
63 | // Set up the executor table entry | ||
64 | entry->token = current_token; | ||
65 | entry->trigger_time = timer_read32() + delay_ms; | ||
66 | entry->callback = callback; | ||
67 | entry->cb_arg = cb_arg; | ||
68 | return current_token; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | // None available | ||
73 | return INVALID_DEFERRED_TOKEN; | ||
74 | } | ||
75 | |||
76 | bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) { | ||
77 | // Ignore queueing if it's a zero-time delay, or the token is not valid | ||
78 | if (delay_ms == 0 || token == INVALID_DEFERRED_TOKEN) { | ||
79 | return false; | ||
80 | } | ||
81 | |||
82 | // Find the entry corresponding to the token | ||
83 | for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { | ||
84 | deferred_executor_t *entry = &executors[i]; | ||
85 | if (entry->token == token) { | ||
86 | // Found it, extend the delay | ||
87 | entry->trigger_time = timer_read32() + delay_ms; | ||
88 | return true; | ||
89 | } | ||
90 | } | ||
91 | |||
92 | // Not found | ||
93 | return false; | ||
94 | } | ||
95 | |||
96 | bool cancel_deferred_exec(deferred_token token) { | ||
97 | // Ignore request if the token is not valid | ||
98 | if (token == INVALID_DEFERRED_TOKEN) { | ||
99 | return false; | ||
100 | } | ||
101 | |||
102 | // Find the entry corresponding to the token | ||
103 | for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { | ||
104 | deferred_executor_t *entry = &executors[i]; | ||
105 | if (entry->token == token) { | ||
106 | // Found it, cancel and clear the table entry | ||
107 | entry->token = INVALID_DEFERRED_TOKEN; | ||
108 | entry->trigger_time = 0; | ||
109 | entry->callback = NULL; | ||
110 | entry->cb_arg = NULL; | ||
111 | return true; | ||
112 | } | ||
113 | } | ||
114 | |||
115 | // Not found | ||
116 | return false; | ||
117 | } | ||
118 | |||
119 | void deferred_exec_task(void) { | ||
120 | uint32_t now = timer_read32(); | ||
121 | |||
122 | // Throttle only once per millisecond | ||
123 | if (((int32_t)TIMER_DIFF_32(now, last_deferred_exec_check)) > 0) { | ||
124 | last_deferred_exec_check = now; | ||
125 | |||
126 | // Run through each of the executors | ||
127 | for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { | ||
128 | deferred_executor_t *entry = &executors[i]; | ||
129 | |||
130 | // Check if we're supposed to execute this entry | ||
131 | if (entry->token != INVALID_DEFERRED_TOKEN && ((int32_t)TIMER_DIFF_32(entry->trigger_time, now)) <= 0) { | ||
132 | // Invoke the callback and work work out if we should be requeued | ||
133 | uint32_t delay_ms = entry->callback(entry->trigger_time, entry->cb_arg); | ||
134 | |||
135 | // Update the trigger time if we have to repeat, otherwise clear it out | ||
136 | if (delay_ms > 0) { | ||
137 | // Intentionally add just the delay to the existing trigger time -- this ensures the next | ||
138 | // invocation is with respect to the previous trigger, rather than when it got to execution. Under | ||
139 | // normal circumstances this won't cause issue, but if another executor is invoked that takes a | ||
140 | // considerable length of time, then this ensures best-effort timing between invocations. | ||
141 | entry->trigger_time += delay_ms; | ||
142 | } else { | ||
143 | // If it was zero, then the callback is cancelling repeated execution. Free up the slot. | ||
144 | entry->token = INVALID_DEFERRED_TOKEN; | ||
145 | entry->trigger_time = 0; | ||
146 | entry->callback = NULL; | ||
147 | entry->cb_arg = NULL; | ||
148 | } | ||
149 | } | ||
150 | } | ||
151 | } | ||
152 | } | ||
diff --git a/quantum/deferred_exec.h b/quantum/deferred_exec.h new file mode 100644 index 000000000..f80d35316 --- /dev/null +++ b/quantum/deferred_exec.h | |||
@@ -0,0 +1,38 @@ | |||
1 | // Copyright 2021 Nick Brassel (@tzarc) | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | #pragma once | ||
5 | |||
6 | #include <stdbool.h> | ||
7 | #include <stdint.h> | ||
8 | |||
9 | // A token that can be used to cancel an existing deferred execution. | ||
10 | typedef uint8_t deferred_token; | ||
11 | #define INVALID_DEFERRED_TOKEN 0 | ||
12 | |||
13 | // Callback to execute. | ||
14 | // -- Parameter trigger_time: the intended trigger time to execute the callback -- equivalent time-space as timer_read32() | ||
15 | // cb_arg: the callback argument specified when enqueueing the deferred executor | ||
16 | // -- Return value: Non-zero re-queues the callback to execute after the returned number of milliseconds. Zero cancels repeated execution. | ||
17 | typedef uint32_t (*deferred_exec_callback)(uint32_t trigger_time, void *cb_arg); | ||
18 | |||
19 | // Configures the supplied deferred executor to be executed after the required number of milliseconds. | ||
20 | // -- Parameter delay_ms: the number of milliseconds before executing the callback | ||
21 | // -- callback: the executor to invoke | ||
22 | // -- cb_arg: the argument to pass to the executor, may be NULL if unused by the executor | ||
23 | // -- Return value: a token usable for cancellation, or INVALID_DEFERRED_TOKEN if an error occurred | ||
24 | deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg); | ||
25 | |||
26 | // Allows for extending the timeframe before an existing deferred execution is invoked. | ||
27 | // -- Parameter token: the returned value from defer_exec for the deferred execution you wish to extend. | ||
28 | // -- delay_ms: the new delay (with respect to the current time) | ||
29 | // -- Return value: if the token was found, and the delay was extended | ||
30 | bool extend_deferred_exec(deferred_token token, uint32_t delay_ms); | ||
31 | |||
32 | // Allows for cancellation of an existing deferred execution. | ||
33 | // -- Parameter token: the returned value from defer_exec for the deferred execution you wish to cancel. | ||
34 | // -- Return value: if the token was found, and the executor was cancelled | ||
35 | bool cancel_deferred_exec(deferred_token token); | ||
36 | |||
37 | // Forward declaration for the main loop in order to execute any deferred executors. Should not be invoked by keyboard/user code. | ||
38 | void deferred_exec_task(void); | ||
diff --git a/quantum/main.c b/quantum/main.c index 3814d371c..6ed6b9574 100644 --- a/quantum/main.c +++ b/quantum/main.c | |||
@@ -43,6 +43,10 @@ void protocol_task(void) { | |||
43 | protocol_post_task(); | 43 | protocol_post_task(); |
44 | } | 44 | } |
45 | 45 | ||
46 | #ifdef DEFERRED_EXEC_ENABLE | ||
47 | void deferred_exec_task(void); | ||
48 | #endif // DEFERRED_EXEC_ENABLE | ||
49 | |||
46 | /** \brief Main | 50 | /** \brief Main |
47 | * | 51 | * |
48 | * FIXME: Needs doc | 52 | * FIXME: Needs doc |
@@ -58,6 +62,12 @@ int main(void) { | |||
58 | /* Main loop */ | 62 | /* Main loop */ |
59 | while (true) { | 63 | while (true) { |
60 | protocol_task(); | 64 | protocol_task(); |
65 | |||
66 | #ifdef DEFERRED_EXEC_ENABLE | ||
67 | // Run deferred executions | ||
68 | deferred_exec_task(); | ||
69 | #endif // DEFERRED_EXEC_ENABLE | ||
70 | |||
61 | housekeeping_task(); | 71 | housekeeping_task(); |
62 | } | 72 | } |
63 | } | 73 | } |
diff --git a/quantum/quantum.h b/quantum/quantum.h index e6015adbe..b34ff6ec5 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h | |||
@@ -54,6 +54,10 @@ | |||
54 | #include <stddef.h> | 54 | #include <stddef.h> |
55 | #include <stdlib.h> | 55 | #include <stdlib.h> |
56 | 56 | ||
57 | #ifdef DEFERRED_EXEC_ENABLE | ||
58 | # include "deferred_exec.h" | ||
59 | #endif | ||
60 | |||
57 | extern layer_state_t default_layer_state; | 61 | extern layer_state_t default_layer_state; |
58 | 62 | ||
59 | #ifndef NO_ACTION_LAYER | 63 | #ifndef NO_ACTION_LAYER |