aboutsummaryrefslogtreecommitdiff
path: root/quantum/deferred_exec.c
diff options
context:
space:
mode:
Diffstat (limited to 'quantum/deferred_exec.c')
-rw-r--r--quantum/deferred_exec.c152
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
12typedef 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
19static deferred_token current_token = 0;
20static uint32_t last_deferred_exec_check = 0;
21static deferred_executor_t executors[MAX_DEFERRED_EXECUTORS] = {0};
22
23static 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
35static 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
47deferred_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
76bool 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
96bool 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
119void 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}