aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--builddefs/generic_features.mk1
-rw-r--r--docs/config_options.md2
-rw-r--r--docs/custom_quantum_functions.md66
-rw-r--r--docs/getting_started_make_guide.md4
-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
-rw-r--r--tmk_core/protocol/arm_atsam/main_arm_atsam.c9
9 files changed, 286 insertions, 0 deletions
diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk
index 2281a8291..e41ce6ac9 100644
--- a/builddefs/generic_features.mk
+++ b/builddefs/generic_features.mk
@@ -19,6 +19,7 @@ GRAVE_ESC_ENABLE ?= yes
19GENERIC_FEATURES = \ 19GENERIC_FEATURES = \
20 COMBO \ 20 COMBO \
21 COMMAND \ 21 COMMAND \
22 DEFERRED_EXEC \
22 DIGITIZER \ 23 DIGITIZER \
23 DIP_SWITCH \ 24 DIP_SWITCH \
24 DYNAMIC_KEYMAP \ 25 DYNAMIC_KEYMAP \
diff --git a/docs/config_options.md b/docs/config_options.md
index ece7ab8ee..9f2453b69 100644
--- a/docs/config_options.md
+++ b/docs/config_options.md
@@ -446,6 +446,8 @@ Use these to enable or disable building certain features. The more you have enab
446 * Forces the keyboard to wait for a USB connection to be established before it starts up 446 * Forces the keyboard to wait for a USB connection to be established before it starts up
447* `NO_USB_STARTUP_CHECK` 447* `NO_USB_STARTUP_CHECK`
448 * Disables usb suspend check after keyboard startup. Usually the keyboard waits for the host to wake it up before any tasks are performed. This is useful for split keyboards as one half will not get a wakeup call but must send commands to the master. 448 * Disables usb suspend check after keyboard startup. Usually the keyboard waits for the host to wake it up before any tasks are performed. This is useful for split keyboards as one half will not get a wakeup call but must send commands to the master.
449* `DEFERRED_EXEC_ENABLE`
450 * Enables deferred executor support -- timed delays before callbacks are invoked. See [deferred execution](custom_quantum_functions.md#deferred-execution) for more information.
449 451
450## USB Endpoint Limitations 452## USB Endpoint Limitations
451 453
diff --git a/docs/custom_quantum_functions.md b/docs/custom_quantum_functions.md
index 798c346e6..dd1654bd2 100644
--- a/docs/custom_quantum_functions.md
+++ b/docs/custom_quantum_functions.md
@@ -405,3 +405,69 @@ And you're done. The RGB layer indication will only work if you want it to. And
405* Keymap: `void eeconfig_init_user(void)`, `uint32_t eeconfig_read_user(void)` and `void eeconfig_update_user(uint32_t val)` 405* Keymap: `void eeconfig_init_user(void)`, `uint32_t eeconfig_read_user(void)` and `void eeconfig_update_user(uint32_t val)`
406 406
407The `val` is the value of the data that you want to write to EEPROM. And the `eeconfig_read_*` function return a 32 bit (DWORD) value from the EEPROM. 407The `val` is the value of the data that you want to write to EEPROM. And the `eeconfig_read_*` function return a 32 bit (DWORD) value from the EEPROM.
408
409### Deferred Execution :id=deferred-execution
410
411QMK has the ability to execute a callback after a specified period of time, rather than having to manually manage timers.
412
413#### Deferred executor callbacks
414
415All _deferred executor callbacks_ have a common function signature and look like:
416
417```c
418uint32_t my_callback(uint32_t trigger_time, void *cb_arg) {
419 /* do something */
420 bool repeat = my_deferred_functionality();
421 return repeat ? 500 : 0;
422}
423```
424
425The first argument `trigger_time` is the intended time of execution. If other delays prevent executing at the exact trigger time, this allows for "catch-up" or even skipping intervals, depending on the required behaviour.
426
427The second argument `cb_arg` is the same argument passed into `defer_exec()` below, and can be used to access state information from the original call context.
428
429The return value is the number of milliseconds to use if the function should be repeated -- if the callback returns `0` then it's automatically unregistered. In the example above, a hypothetical `my_deferred_functionality()` is invoked to determine if the callback needs to be repeated -- if it does, it reschedules for a `500` millisecond delay, otherwise it informs the deferred execution background task that it's done, by returning `0`.
430
431?> Note that the returned delay will be applied to the intended trigger time, not the time of callback invocation. This allows for generally consistent timing even in the face of occasional late execution.
432
433#### Deferred executor registration
434
435Once a callback has been defined, it can be scheduled using the following API:
436
437```c
438deferred_token my_token = defer_exec(1500, my_callback, NULL);
439```
440
441The first argument is the number of milliseconds to wait until executing `my_callback` -- in the case above, `1500` milliseconds, or 1.5 seconds.
442
443The third parameter is the `cb_arg` that gets passed to the callback at the point of execution. This value needs to be valid at the time the callback is invoked -- a local function value will be destroyed before the callback is executed and should not be used. If this is not required, `NULL` should be used.
444
445The return value is a `deferred_token` that can consequently be used to cancel the deferred executor callback before it's invoked. If a failure occurs, the returned value will be `INVALID_DEFERRED_TOKEN`. Usually this will be as a result of supplying `0` to the delay, or a `NULL` for the callback. The other failure case is if there are too many deferred executions "in flight" -- this can be increased by changing the limit, described below.
446
447#### Extending a deferred execution
448
449The `deferred_token` returned by `defer_exec()` can be used to extend a the duration a pending execution waits before it gets invoked:
450```c
451// This will re-delay my_token's future execution such that it is invoked 800ms after the current time
452extend_deferred_exec(my_token, 800);
453```
454
455#### Cancelling a deferred execution
456
457The `deferred_token` returned by `defer_exec()` can be used to cancel a pending execution before it gets invoked:
458```c
459// This will cancel my_token's future execution
460cancel_deferred_exec(my_token);
461```
462
463Once a token has been canceled, it should be considered invalid. Reusing the same token is not supported.
464
465#### Deferred callback limits
466
467There are a maximum number of deferred callbacks that can be scheduled, controlled by the value of the define `MAX_DEFERRED_EXECUTORS`.
468
469If registrations fail, then you can increase this value in your keyboard or keymap `config.h` file, for example to 16 instead of the default 8:
470
471```c
472#define MAX_DEFERRED_EXECUTORS 16
473```
diff --git a/docs/getting_started_make_guide.md b/docs/getting_started_make_guide.md
index a51486435..1a7e27609 100644
--- a/docs/getting_started_make_guide.md
+++ b/docs/getting_started_make_guide.md
@@ -145,6 +145,10 @@ Lets you replace the default matrix scanning routine with your own code. For fur
145 145
146Lets you replace the default key debouncing routine with an alternative one. If `custom` you will need to provide your own implementation. 146Lets you replace the default key debouncing routine with an alternative one. If `custom` you will need to provide your own implementation.
147 147
148`DEFERRED_EXEC_ENABLE`
149
150Enables deferred executor support -- timed delays before callbacks are invoked. See [deferred execution](custom_quantum_functions.md#deferred-execution) for more information.
151
148## Customizing Makefile Options on a Per-Keymap Basis 152## Customizing Makefile Options on a Per-Keymap Basis
149 153
150If your keymap directory has a file called `rules.mk` any options you set in that file will take precedence over other `rules.mk` options for your particular keyboard. 154If your keymap directory has a file called `rules.mk` any options you set in that file will take precedence over other `rules.mk` options for your particular keyboard.
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
diff --git a/tmk_core/protocol/arm_atsam/main_arm_atsam.c b/tmk_core/protocol/arm_atsam/main_arm_atsam.c
index 1df5112ed..de12821a7 100644
--- a/tmk_core/protocol/arm_atsam/main_arm_atsam.c
+++ b/tmk_core/protocol/arm_atsam/main_arm_atsam.c
@@ -40,6 +40,10 @@ void send_mouse(report_mouse_t *report);
40void send_system(uint16_t data); 40void send_system(uint16_t data);
41void send_consumer(uint16_t data); 41void send_consumer(uint16_t data);
42 42
43#ifdef DEFERRED_EXEC_ENABLE
44void deferred_exec_task(void);
45#endif // DEFERRED_EXEC_ENABLE
46
43host_driver_t arm_atsam_driver = {keyboard_leds, send_keyboard, send_mouse, send_system, send_consumer}; 47host_driver_t arm_atsam_driver = {keyboard_leds, send_keyboard, send_mouse, send_system, send_consumer};
44 48
45uint8_t led_states; 49uint8_t led_states;
@@ -360,6 +364,11 @@ int main(void) {
360 } 364 }
361#endif // CONSOLE_ENABLE 365#endif // CONSOLE_ENABLE
362 366
367#ifdef DEFERRED_EXEC_ENABLE
368 // Run deferred executions
369 deferred_exec_task();
370#endif // DEFERRED_EXEC_ENABLE
371
363 // Run housekeeping 372 // Run housekeeping
364 housekeeping_task(); 373 housekeeping_task();
365 } 374 }