diff options
Diffstat (limited to 'quantum/deferred_exec.c')
| -rw-r--r-- | quantum/deferred_exec.c | 152 |
1 files changed, 152 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 | } | ||
