aboutsummaryrefslogtreecommitdiff
path: root/quantum
diff options
context:
space:
mode:
authorNick Brassel <nick@tzarc.org>2021-11-16 05:21:09 +1100
committerGitHub <noreply@github.com>2021-11-15 18:21:09 +0000
commit36d123e9c5a9ce0e29b9bc22ef87661bf479e299 (patch)
tree1033718afb33e1eb7dc3cd7b99db59a90668fa8a /quantum
parentb3ee124da666287ef13787523ea88bf40713ff4c (diff)
downloadqmk_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.c152
-rw-r--r--quantum/deferred_exec.h38
-rw-r--r--quantum/main.c10
-rw-r--r--quantum/quantum.h4
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
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}
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.
10typedef 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.
17typedef 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
24deferred_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
30bool 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
35bool 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.
38void 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
47void 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
57extern layer_state_t default_layer_state; 61extern layer_state_t default_layer_state;
58 62
59#ifndef NO_ACTION_LAYER 63#ifndef NO_ACTION_LAYER