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 |
