From bb306c2db67857fb120c6b2f61c38ab6dc574cf5 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 18 Oct 2021 13:50:50 +1100 Subject: [PATCH 01/13] Add support for deferred executors. --- common_features.mk | 5 + docs/custom_quantum_functions.md | 62 +++++++++++ quantum/deferred_exec.c | 106 +++++++++++++++++++ quantum/deferred_exec.h | 37 +++++++ quantum/main.c | 10 ++ tmk_core/protocol/arm_atsam/main_arm_atsam.c | 9 ++ 6 files changed, 229 insertions(+) create mode 100644 quantum/deferred_exec.c create mode 100644 quantum/deferred_exec.h diff --git a/common_features.mk b/common_features.mk index aff91641e42a..08f402af4537 100644 --- a/common_features.mk +++ b/common_features.mk @@ -113,6 +113,11 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes) SRC += $(QUANTUM_DIR)/pointing_device.c endif +ifeq ($(strip $(DEFERRED_EXEC_ENABLE)), yes) + OPT_DEFS += -DDEFERRED_EXEC_ENABLE + SRC += $(QUANTUM_DIR)/deferred_exec.c +endif + VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi EEPROM_DRIVER ?= vendor ifeq ($(filter $(EEPROM_DRIVER),$(VALID_EEPROM_DRIVER_TYPES)),) diff --git a/docs/custom_quantum_functions.md b/docs/custom_quantum_functions.md index 463366ff7632..c93d270764ae 100644 --- a/docs/custom_quantum_functions.md +++ b/docs/custom_quantum_functions.md @@ -405,3 +405,65 @@ And you're done. The RGB layer indication will only work if you want it to. And * Keymap: `void eeconfig_init_user(void)`, `uint32_t eeconfig_read_user(void)` and `void eeconfig_update_user(uint32_t val)` The `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. + +### Deferred Execution + +QMK has the ability to execute a callback after a specified period of time, rather than having to manually manage timers. + +#### Deferred executor callbacks + +All _deferred executor callbacks_ have a common function signature and look like: + +```c +uint32_t my_callback(void *cb_arg) { + /* do something */ + bool repeat = my_deferred_functionality(); + return repeat ? 500 : 0; +} +``` + +The argument `cb_arg` is the same argument passed into the registration function. + +The 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`. + +#### Deferred executor registration + +Once a callback has been defined, it can be scheduled using the following API: + +```c +deferred_token my_token = enqueue_deferred_exec(1500, my_callback, NULL); +``` + +The first argument is the number of milliseconds to wait until executing `my_callback` -- in the case above, `1500` milliseconds, or 1.5 seconds. + +The 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. + +The 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. + +#### Extending a deferred execution + +The `deferred_token` returned by `enqueue_deferred_exec()` can be used to extend a the duration a pending execution waits before it gets invoked: +```c +// This will re-delay my_token's future execution such that it is invoked 800ms after the current time +extend_deferred_exec(my_token, 800); +``` + +#### Cancelling a deferred execution + +The `deferred_token` returned by `enqueue_deferred_exec()` can be used to cancel a pending execution before it gets invoked: +```c +// This will cancel my_token's future execution +cancel_deferred_exec(my_token); +``` + +Once a token has been canceled, it should be considered invalid. Reusing the same token is not supported. + +#### Deferred callback limits + +There are a maximum number of deferred callbacks that can be scheduled, controlled by the value of the define `MAX_DEFERRED_EXECUTORS`. + +If 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: + +```c +#define MAX_DEFERRED_EXECUTORS 16 +``` diff --git a/quantum/deferred_exec.c b/quantum/deferred_exec.c new file mode 100644 index 000000000000..9f6f37c3d903 --- /dev/null +++ b/quantum/deferred_exec.c @@ -0,0 +1,106 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#ifndef MAX_DEFERRED_EXECUTORS +# define MAX_DEFERRED_EXECUTORS 8 +#endif + +typedef struct deferred_executor_t { + uint16_t token; + uint32_t trigger_time; + deferred_exec_callback callback; + void * cb_arg; +} deferred_executor_t; + +static uint16_t current_token = 0; +static uint32_t last_deferred_exec_check = 0; +static deferred_executor_t executors[MAX_DEFERRED_EXECUTORS] = {0}; + +deferred_token enqueue_deferred_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) { + // Ignore queueing if it's a zero-time delay + if (delay_ms == 0 || !callback) { + return INVALID_DEFERRED_TOKEN; + } + + // Find an unused slot and claim it + for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { + if (executors[i].trigger_time == 0) { + // Work out the new token value + do { + ++current_token; + } while (current_token == INVALID_DEFERRED_TOKEN); // Skip INVALID_DEFERRED_TOKEN + + // Set up the executor table entry + executors[i].token = current_token; + executors[i].trigger_time = timer_read32() + delay_ms; + executors[i].callback = callback; + executors[i].cb_arg = cb_arg; + return (deferred_token)(current_token); + } + } + + // None available + return INVALID_DEFERRED_TOKEN; +} + +bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) { + for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { + if (executors[i].token == (uint16_t)token) { + executors[i].trigger_time = timer_read32() + delay_ms; + return true; + } + } + return false; +} + +bool cancel_deferred_exec(deferred_token token) { + for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { + if (executors[i].token == (uint16_t)token) { + executors[i].token = INVALID_DEFERRED_TOKEN; + executors[i].trigger_time = 0; + executors[i].callback = NULL; + executors[i].cb_arg = NULL; + return true; + } + } + return false; +} + +void deferred_exec_task(void) { + uint32_t now = timer_read32(); + + // Throttle only once per millisecond + if (((int32_t)TIMER_DIFF_32(now, last_deferred_exec_check)) > 0) { + last_deferred_exec_check = now; + + // Run through each of the executors + for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { + deferred_executor_t *entry = &executors[i]; + + // Check if we're supposed to execute this entry + if (entry->token != INVALID_DEFERRED_TOKEN && entry->trigger_time > 0 && ((int32_t)TIMER_DIFF_32(entry->trigger_time, now)) <= 0) { + // Invoke the callback and work work out if we should be requeued + uint32_t delay_ms = entry->callback(entry->cb_arg); + + // Update the trigger time if we have to repeat, otherwise clear it out + if (delay_ms > 0) { + // Intentionally add just the delay to the existing trigger time -- this ensures the next + // invocation is with respect to the previous trigger, rather than when it got to execution. Under + // normal circumstances this won't cause issue, but if another executor is invoked that takes a + // considerable length of time, then this + entry->trigger_time += delay_ms; + } else { + // If it was zero, then the callback is cancelling repeated execution. Free up the slot. + entry->token = INVALID_DEFERRED_TOKEN; + entry->trigger_time = 0; + entry->callback = NULL; + entry->cb_arg = NULL; + } + } + } + } +} diff --git a/quantum/deferred_exec.h b/quantum/deferred_exec.h new file mode 100644 index 000000000000..7d1934cb59cc --- /dev/null +++ b/quantum/deferred_exec.h @@ -0,0 +1,37 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +// A token that can be used to cancel an existing deferred execution. +typedef uint8_t deferred_token; +#define INVALID_DEFERRED_TOKEN 0 + +// Callback to execute. +// -- Parameter cb_arg: the callback argument specified when enqueueing the deferred executor +// -- Return value: Non-zero re-queues the callback to execute after the returned number of milliseconds. Zero cancels repeated execution. +typedef uint32_t (*deferred_exec_callback)(void *cb_arg); + +// Configures the supplied deferred executor to be executed after the required number of milliseconds. +// -- Parameter delay_ms: the number of milliseconds before executing the callback +// -- callback: the executor to invoke +// -- cb_arg: the argument to pass to the executor, may be NULL if unused by the executor +// -- Return value: a token usable for cancellation, or INVALID_DEFERRED_TOKEN if an error occurred +deferred_token enqueue_deferred_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg); + +// Allows for extending the timeframe before an existing deferred execution is invoked. +// -- Parameter token: the returned value from enqueue_deferred_exec for the deferred execution you wish to extend. +// -- delay_ms: the new delay (with respect to the current time) +// -- Return value: if the token was found, and the delay was extended +bool extend_deferred_exec(deferred_token token, uint32_t delay_ms); + +// Allows for cancellation of an existing deferred execution. +// -- Parameter token: the returned value from enqueue_deferred_exec for the deferred execution you wish to cancel. +// -- Return value: if the token was found, and the executor was cancelled +bool cancel_deferred_exec(deferred_token token); + +// Forward declaration for the main loop in order to execute any deferred executors. Should not be invoked by keyboard/user code. +void deferred_exec_task(void); diff --git a/quantum/main.c b/quantum/main.c index 2cbcd73d8ffa..ab9a6fa687e3 100644 --- a/quantum/main.c +++ b/quantum/main.c @@ -22,6 +22,10 @@ void protocol_setup(void); void protocol_init(void); void protocol_task(void); +#ifdef DEFERRED_EXEC_ENABLE +void deferred_exec_task(void); +#endif // DEFERRED_EXEC_ENABLE + /** \brief Main * * FIXME: Needs doc @@ -36,6 +40,12 @@ int main(void) { /* Main loop */ while (true) { protocol_task(); + +#ifdef DEFERRED_EXEC_ENABLE + // Run deferred executions + deferred_exec_task(); +#endif // DEFERRED_EXEC_ENABLE + housekeeping_task(); } } diff --git a/tmk_core/protocol/arm_atsam/main_arm_atsam.c b/tmk_core/protocol/arm_atsam/main_arm_atsam.c index 858b4cd9fc02..b9dca6b84d35 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); void send_system(uint16_t data); void send_consumer(uint16_t data); +#ifdef DEFERRED_EXEC_ENABLE +void deferred_exec_task(void); +#endif // DEFERRED_EXEC_ENABLE + host_driver_t arm_atsam_driver = {keyboard_leds, send_keyboard, send_mouse, send_system, send_consumer}; uint8_t led_states; @@ -360,6 +364,11 @@ int main(void) { } #endif // CONSOLE_ENABLE +#ifdef DEFERRED_EXEC_ENABLE + // Run deferred executions + deferred_exec_task(); +#endif // DEFERRED_EXEC_ENABLE + // Run housekeeping housekeeping_task(); } From e8de952228638519a84a6eeb8da9db82998b4036 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 18 Oct 2021 13:58:22 +1100 Subject: [PATCH 02/13] More docs. --- docs/config_options.md | 2 ++ docs/custom_quantum_functions.md | 2 +- docs/getting_started_make_guide.md | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/config_options.md b/docs/config_options.md index dbf3d3da2eb0..da7cca992861 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -444,6 +444,8 @@ Use these to enable or disable building certain features. The more you have enab * Forces the keyboard to wait for a USB connection to be established before it starts up * `NO_USB_STARTUP_CHECK` * 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. +* `DEFERRED_EXEC_ENABLE` + * Enables deferred executor support -- timed delays before callbacks are invoked. See [deferred execution](custom_quantum_functions.md#deferred-execution) for more information. ## USB Endpoint Limitations diff --git a/docs/custom_quantum_functions.md b/docs/custom_quantum_functions.md index c93d270764ae..6222b8b30a1a 100644 --- a/docs/custom_quantum_functions.md +++ b/docs/custom_quantum_functions.md @@ -406,7 +406,7 @@ And you're done. The RGB layer indication will only work if you want it to. And The `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. -### Deferred Execution +### Deferred Execution :id=deferred-execution QMK has the ability to execute a callback after a specified period of time, rather than having to manually manage timers. diff --git a/docs/getting_started_make_guide.md b/docs/getting_started_make_guide.md index deef7520e5a2..a0af43026587 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 Lets you replace the default key debouncing routine with an alternative one. If `custom` you will need to provide your own implementation. +`DEFERRED_EXEC_ENABLE` + +Enables deferred executor support -- timed delays before callbacks are invoked. See [deferred execution](custom_quantum_functions.md#deferred-execution) for more information. + ## Customizing Makefile Options on a Per-Keymap Basis If 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. From a63e7316b58d226276c7266e8ba6b849d8a58285 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 18 Oct 2021 14:00:42 +1100 Subject: [PATCH 03/13] Include from quantum.h --- quantum/quantum.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/quantum/quantum.h b/quantum/quantum.h index 9250f5acce7a..02b1bb8c3b8e 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -54,6 +54,10 @@ #include #include +#ifdef DEFERRED_EXEC_ENABLE +# include "deferred_exec.h" +#endif + extern layer_state_t default_layer_state; #ifndef NO_ACTION_LAYER From 4d7bdee13a6bf5beb5d100d3d579a520cea17b13 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 18 Oct 2021 14:11:35 +1100 Subject: [PATCH 04/13] Cleanup. --- quantum/deferred_exec.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/quantum/deferred_exec.c b/quantum/deferred_exec.c index 9f6f37c3d903..da2e2afa464c 100644 --- a/quantum/deferred_exec.c +++ b/quantum/deferred_exec.c @@ -28,18 +28,19 @@ deferred_token enqueue_deferred_exec(uint32_t delay_ms, deferred_exec_callback c // Find an unused slot and claim it for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { - if (executors[i].trigger_time == 0) { + deferred_executor_t *entry = &executors[i]; + if (entry->trigger_time == 0) { // Work out the new token value do { ++current_token; } while (current_token == INVALID_DEFERRED_TOKEN); // Skip INVALID_DEFERRED_TOKEN // Set up the executor table entry - executors[i].token = current_token; - executors[i].trigger_time = timer_read32() + delay_ms; - executors[i].callback = callback; - executors[i].cb_arg = cb_arg; - return (deferred_token)(current_token); + entry->token = current_token; + entry->trigger_time = timer_read32() + delay_ms; + entry->callback = callback; + entry->cb_arg = cb_arg; + return (deferred_token)current_token; } } @@ -49,8 +50,9 @@ deferred_token enqueue_deferred_exec(uint32_t delay_ms, deferred_exec_callback c bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) { for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { - if (executors[i].token == (uint16_t)token) { - executors[i].trigger_time = timer_read32() + delay_ms; + deferred_executor_t *entry = &executors[i]; + if (entry->token == (uint16_t)token) { + entry->trigger_time = timer_read32() + delay_ms; return true; } } @@ -59,11 +61,12 @@ bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) { bool cancel_deferred_exec(deferred_token token) { for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { - if (executors[i].token == (uint16_t)token) { - executors[i].token = INVALID_DEFERRED_TOKEN; - executors[i].trigger_time = 0; - executors[i].callback = NULL; - executors[i].cb_arg = NULL; + deferred_executor_t *entry = &executors[i]; + if (entry->token == (uint16_t)token) { + entry->token = INVALID_DEFERRED_TOKEN; + entry->trigger_time = 0; + entry->callback = NULL; + entry->cb_arg = NULL; return true; } } From 0c7601bfd68d8ba6fa27e4dcca10559a659ced63 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 18 Oct 2021 14:13:18 +1100 Subject: [PATCH 05/13] Parameter checks --- quantum/deferred_exec.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/quantum/deferred_exec.c b/quantum/deferred_exec.c index da2e2afa464c..081a3fa69600 100644 --- a/quantum/deferred_exec.c +++ b/quantum/deferred_exec.c @@ -21,7 +21,7 @@ static uint32_t last_deferred_exec_check = 0; static deferred_executor_t executors[MAX_DEFERRED_EXECUTORS] = {0}; deferred_token enqueue_deferred_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) { - // Ignore queueing if it's a zero-time delay + // Ignore queueing if it's a zero-time delay, or invalid callback if (delay_ms == 0 || !callback) { return INVALID_DEFERRED_TOKEN; } @@ -49,6 +49,11 @@ deferred_token enqueue_deferred_exec(uint32_t delay_ms, deferred_exec_callback c } bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) { + // Ignore queueing if it's a zero-time delay + if (delay_ms == 0) { + return false; + } + for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { deferred_executor_t *entry = &executors[i]; if (entry->token == (uint16_t)token) { From f995faf0bbb758ff6968faf14e67b9bbd39c0128 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 18 Oct 2021 14:17:36 +1100 Subject: [PATCH 06/13] Comments. --- quantum/deferred_exec.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/quantum/deferred_exec.c b/quantum/deferred_exec.c index 081a3fa69600..0305490f9040 100644 --- a/quantum/deferred_exec.c +++ b/quantum/deferred_exec.c @@ -54,20 +54,26 @@ bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) { return false; } + // Find the entry corresponding to the token for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { deferred_executor_t *entry = &executors[i]; if (entry->token == (uint16_t)token) { + // Found it, extend the delay entry->trigger_time = timer_read32() + delay_ms; return true; } } + + // Not found return false; } bool cancel_deferred_exec(deferred_token token) { + // Find the entry corresponding to the token for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { deferred_executor_t *entry = &executors[i]; if (entry->token == (uint16_t)token) { + // Found it, cancel and clear the table entry entry->token = INVALID_DEFERRED_TOKEN; entry->trigger_time = 0; entry->callback = NULL; @@ -75,6 +81,8 @@ bool cancel_deferred_exec(deferred_token token) { return true; } } + + // Not found return false; } From 404c276f3aadeccf90f3c8f676de0e544ac77d0d Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 18 Oct 2021 14:25:20 +1100 Subject: [PATCH 07/13] qmk format-c --- quantum/main.c | 4 ++-- tmk_core/protocol/arm_atsam/main_arm_atsam.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/quantum/main.c b/quantum/main.c index ab9a6fa687e3..fd54a85ebb51 100644 --- a/quantum/main.c +++ b/quantum/main.c @@ -24,7 +24,7 @@ void protocol_task(void); #ifdef DEFERRED_EXEC_ENABLE void deferred_exec_task(void); -#endif // DEFERRED_EXEC_ENABLE +#endif // DEFERRED_EXEC_ENABLE /** \brief Main * @@ -44,7 +44,7 @@ int main(void) { #ifdef DEFERRED_EXEC_ENABLE // Run deferred executions deferred_exec_task(); -#endif // DEFERRED_EXEC_ENABLE +#endif // DEFERRED_EXEC_ENABLE housekeeping_task(); } diff --git a/tmk_core/protocol/arm_atsam/main_arm_atsam.c b/tmk_core/protocol/arm_atsam/main_arm_atsam.c index b9dca6b84d35..74f9372f5075 100644 --- a/tmk_core/protocol/arm_atsam/main_arm_atsam.c +++ b/tmk_core/protocol/arm_atsam/main_arm_atsam.c @@ -42,7 +42,7 @@ void send_consumer(uint16_t data); #ifdef DEFERRED_EXEC_ENABLE void deferred_exec_task(void); -#endif // DEFERRED_EXEC_ENABLE +#endif // DEFERRED_EXEC_ENABLE host_driver_t arm_atsam_driver = {keyboard_leds, send_keyboard, send_mouse, send_system, send_consumer}; @@ -367,7 +367,7 @@ int main(void) { #ifdef DEFERRED_EXEC_ENABLE // Run deferred executions deferred_exec_task(); -#endif // DEFERRED_EXEC_ENABLE +#endif // DEFERRED_EXEC_ENABLE // Run housekeeping housekeeping_task(); From ad52e6b89d2be0caf59e3aaec6cfac881304caf7 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 18 Oct 2021 14:47:09 +1100 Subject: [PATCH 08/13] I accidentally a few words. --- quantum/deferred_exec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/deferred_exec.c b/quantum/deferred_exec.c index 0305490f9040..9b10275e8fde 100644 --- a/quantum/deferred_exec.c +++ b/quantum/deferred_exec.c @@ -107,7 +107,7 @@ void deferred_exec_task(void) { // Intentionally add just the delay to the existing trigger time -- this ensures the next // invocation is with respect to the previous trigger, rather than when it got to execution. Under // normal circumstances this won't cause issue, but if another executor is invoked that takes a - // considerable length of time, then this + // considerable length of time, then this ensures best-effort timing between invocations. entry->trigger_time += delay_ms; } else { // If it was zero, then the callback is cancelling repeated execution. Free up the slot. From 964b5e1009831230c5c80169f1064fc42ead1141 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 18 Oct 2021 15:15:59 +1100 Subject: [PATCH 09/13] API name change. --- docs/custom_quantum_functions.md | 6 +++--- quantum/deferred_exec.c | 2 +- quantum/deferred_exec.h | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/custom_quantum_functions.md b/docs/custom_quantum_functions.md index 6222b8b30a1a..4ab58d988649 100644 --- a/docs/custom_quantum_functions.md +++ b/docs/custom_quantum_functions.md @@ -431,7 +431,7 @@ The return value is the number of milliseconds to use if the function should be Once a callback has been defined, it can be scheduled using the following API: ```c -deferred_token my_token = enqueue_deferred_exec(1500, my_callback, NULL); +deferred_token my_token = defer_exec(1500, my_callback, NULL); ``` The first argument is the number of milliseconds to wait until executing `my_callback` -- in the case above, `1500` milliseconds, or 1.5 seconds. @@ -442,7 +442,7 @@ The return value is a `deferred_token` that can consequently be used to cancel t #### Extending a deferred execution -The `deferred_token` returned by `enqueue_deferred_exec()` can be used to extend a the duration a pending execution waits before it gets invoked: +The `deferred_token` returned by `defer_exec()` can be used to extend a the duration a pending execution waits before it gets invoked: ```c // This will re-delay my_token's future execution such that it is invoked 800ms after the current time extend_deferred_exec(my_token, 800); @@ -450,7 +450,7 @@ extend_deferred_exec(my_token, 800); #### Cancelling a deferred execution -The `deferred_token` returned by `enqueue_deferred_exec()` can be used to cancel a pending execution before it gets invoked: +The `deferred_token` returned by `defer_exec()` can be used to cancel a pending execution before it gets invoked: ```c // This will cancel my_token's future execution cancel_deferred_exec(my_token); diff --git a/quantum/deferred_exec.c b/quantum/deferred_exec.c index 9b10275e8fde..feccba7b789f 100644 --- a/quantum/deferred_exec.c +++ b/quantum/deferred_exec.c @@ -20,7 +20,7 @@ static uint16_t current_token = 0; static uint32_t last_deferred_exec_check = 0; static deferred_executor_t executors[MAX_DEFERRED_EXECUTORS] = {0}; -deferred_token enqueue_deferred_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) { +deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) { // Ignore queueing if it's a zero-time delay, or invalid callback if (delay_ms == 0 || !callback) { return INVALID_DEFERRED_TOKEN; diff --git a/quantum/deferred_exec.h b/quantum/deferred_exec.h index 7d1934cb59cc..65f281c4ce6e 100644 --- a/quantum/deferred_exec.h +++ b/quantum/deferred_exec.h @@ -20,16 +20,16 @@ typedef uint32_t (*deferred_exec_callback)(void *cb_arg); // -- callback: the executor to invoke // -- cb_arg: the argument to pass to the executor, may be NULL if unused by the executor // -- Return value: a token usable for cancellation, or INVALID_DEFERRED_TOKEN if an error occurred -deferred_token enqueue_deferred_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg); +deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg); // Allows for extending the timeframe before an existing deferred execution is invoked. -// -- Parameter token: the returned value from enqueue_deferred_exec for the deferred execution you wish to extend. +// -- Parameter token: the returned value from defer_exec for the deferred execution you wish to extend. // -- delay_ms: the new delay (with respect to the current time) // -- Return value: if the token was found, and the delay was extended bool extend_deferred_exec(deferred_token token, uint32_t delay_ms); // Allows for cancellation of an existing deferred execution. -// -- Parameter token: the returned value from enqueue_deferred_exec for the deferred execution you wish to cancel. +// -- Parameter token: the returned value from defer_exec for the deferred execution you wish to cancel. // -- Return value: if the token was found, and the executor was cancelled bool cancel_deferred_exec(deferred_token token); From cd458264d8a74ddd4458660503f18dff8c0adf60 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 18 Oct 2021 17:18:38 +1100 Subject: [PATCH 10/13] Apply suggestions from code review Co-authored-by: Sergey Vlasov --- quantum/deferred_exec.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/quantum/deferred_exec.c b/quantum/deferred_exec.c index feccba7b789f..4aaaee38bc14 100644 --- a/quantum/deferred_exec.c +++ b/quantum/deferred_exec.c @@ -10,16 +10,28 @@ #endif typedef struct deferred_executor_t { - uint16_t token; + deferred_token token; uint32_t trigger_time; deferred_exec_callback callback; void * cb_arg; } deferred_executor_t; -static uint16_t current_token = 0; +static deferred_token current_token = 0; static uint32_t last_deferred_exec_check = 0; static deferred_executor_t executors[MAX_DEFERRED_EXECUTORS] = {0}; +static inline bool token_can_be_used(deferred_token token) { + if (token == INVALID_DEFERRED_TOKEN) { + return false; + } + for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { + if (executors[i].token == token) { + return false; + } + } + return true; +} + deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) { // Ignore queueing if it's a zero-time delay, or invalid callback if (delay_ms == 0 || !callback) { @@ -29,18 +41,18 @@ deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, vo // Find an unused slot and claim it for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { deferred_executor_t *entry = &executors[i]; - if (entry->trigger_time == 0) { + if (entry->token == INVALID_DEFERRED_TOKEN) { // Work out the new token value do { ++current_token; - } while (current_token == INVALID_DEFERRED_TOKEN); // Skip INVALID_DEFERRED_TOKEN + } while (!token_can_be_used(current_token)); // Skip invalid or busy values // Set up the executor table entry entry->token = current_token; entry->trigger_time = timer_read32() + delay_ms; entry->callback = callback; entry->cb_arg = cb_arg; - return (deferred_token)current_token; + return current_token; } } @@ -49,15 +61,15 @@ deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, vo } bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) { - // Ignore queueing if it's a zero-time delay - if (delay_ms == 0) { + // Ignore queueing if it's a zero-time delay, or the token is not valid + if (delay_ms == 0 || token == INVALID_DEFERRED_TOKEN) { return false; } // Find the entry corresponding to the token for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { deferred_executor_t *entry = &executors[i]; - if (entry->token == (uint16_t)token) { + if (entry->token == token) { // Found it, extend the delay entry->trigger_time = timer_read32() + delay_ms; return true; @@ -69,10 +81,15 @@ bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) { } bool cancel_deferred_exec(deferred_token token) { + // Ignore request if the token is not valid + if (token == INVALID_DEFERRED_TOKEN) { + return false; + } + // Find the entry corresponding to the token for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { deferred_executor_t *entry = &executors[i]; - if (entry->token == (uint16_t)token) { + if (entry->token == token) { // Found it, cancel and clear the table entry entry->token = INVALID_DEFERRED_TOKEN; entry->trigger_time = 0; @@ -98,7 +115,7 @@ void deferred_exec_task(void) { deferred_executor_t *entry = &executors[i]; // Check if we're supposed to execute this entry - if (entry->token != INVALID_DEFERRED_TOKEN && entry->trigger_time > 0 && ((int32_t)TIMER_DIFF_32(entry->trigger_time, now)) <= 0) { + if (entry->token != INVALID_DEFERRED_TOKEN && ((int32_t)TIMER_DIFF_32(entry->trigger_time, now)) <= 0) { // Invoke the callback and work work out if we should be requeued uint32_t delay_ms = entry->callback(entry->cb_arg); From 881bd981d5a7651f39964cc35c23f86095f43b0a Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Tue, 19 Oct 2021 06:43:17 +1100 Subject: [PATCH 11/13] Review comments. --- docs/custom_quantum_functions.md | 8 ++++++-- quantum/deferred_exec.c | 23 ++++++++++++++++++----- quantum/deferred_exec.h | 5 +++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/docs/custom_quantum_functions.md b/docs/custom_quantum_functions.md index 4ab58d988649..d6dba8b840cc 100644 --- a/docs/custom_quantum_functions.md +++ b/docs/custom_quantum_functions.md @@ -415,17 +415,21 @@ QMK has the ability to execute a callback after a specified period of time, rath All _deferred executor callbacks_ have a common function signature and look like: ```c -uint32_t my_callback(void *cb_arg) { +uint32_t my_callback(uint32_t trigger_time, void *cb_arg) { /* do something */ bool repeat = my_deferred_functionality(); return repeat ? 500 : 0; } ``` -The argument `cb_arg` is the same argument passed into the registration function. +The 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. + +The 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. The 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`. +?> 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. + #### Deferred executor registration Once a callback has been defined, it can be scheduled using the following API: diff --git a/quantum/deferred_exec.c b/quantum/deferred_exec.c index 4aaaee38bc14..0d0b7f07d3bb 100644 --- a/quantum/deferred_exec.c +++ b/quantum/deferred_exec.c @@ -32,6 +32,18 @@ static inline bool token_can_be_used(deferred_token token) { return true; } +static inline deferred_token allocate_token(void) { + deferred_token first = ++current_token; + while (!token_can_be_used(current_token)) { + ++current_token; + if (current_token == first) { + // If we've looped back around to the first, everything is already allocated (yikes!). Need to exit with a failure. + return INVALID_DEFERRED_TOKEN; + } + } + return current_token; +} + deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) { // Ignore queueing if it's a zero-time delay, or invalid callback if (delay_ms == 0 || !callback) { @@ -42,10 +54,11 @@ deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, vo for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) { deferred_executor_t *entry = &executors[i]; if (entry->token == INVALID_DEFERRED_TOKEN) { - // Work out the new token value - do { - ++current_token; - } while (!token_can_be_used(current_token)); // Skip invalid or busy values + // Work out the new token value, dropping out if none were available + deferred_token token = allocate_token(); + if(token == INVALID_DEFERRED_TOKEN) { + return false; + } // Set up the executor table entry entry->token = current_token; @@ -117,7 +130,7 @@ void deferred_exec_task(void) { // Check if we're supposed to execute this entry if (entry->token != INVALID_DEFERRED_TOKEN && ((int32_t)TIMER_DIFF_32(entry->trigger_time, now)) <= 0) { // Invoke the callback and work work out if we should be requeued - uint32_t delay_ms = entry->callback(entry->cb_arg); + uint32_t delay_ms = entry->callback(entry->trigger_time, entry->cb_arg); // Update the trigger time if we have to repeat, otherwise clear it out if (delay_ms > 0) { diff --git a/quantum/deferred_exec.h b/quantum/deferred_exec.h index 65f281c4ce6e..f80d353169bf 100644 --- a/quantum/deferred_exec.h +++ b/quantum/deferred_exec.h @@ -11,9 +11,10 @@ typedef uint8_t deferred_token; #define INVALID_DEFERRED_TOKEN 0 // Callback to execute. -// -- Parameter cb_arg: the callback argument specified when enqueueing the deferred executor +// -- Parameter trigger_time: the intended trigger time to execute the callback -- equivalent time-space as timer_read32() +// cb_arg: the callback argument specified when enqueueing the deferred executor // -- Return value: Non-zero re-queues the callback to execute after the returned number of milliseconds. Zero cancels repeated execution. -typedef uint32_t (*deferred_exec_callback)(void *cb_arg); +typedef uint32_t (*deferred_exec_callback)(uint32_t trigger_time, void *cb_arg); // Configures the supplied deferred executor to be executed after the required number of milliseconds. // -- Parameter delay_ms: the number of milliseconds before executing the callback From f098bdc164828e17a96b5f4fd1f39cebe9c3a32b Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Tue, 19 Oct 2021 06:44:42 +1100 Subject: [PATCH 12/13] qmk format-c --- quantum/deferred_exec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/deferred_exec.c b/quantum/deferred_exec.c index 0d0b7f07d3bb..5b0a5b14258b 100644 --- a/quantum/deferred_exec.c +++ b/quantum/deferred_exec.c @@ -56,7 +56,7 @@ deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, vo if (entry->token == INVALID_DEFERRED_TOKEN) { // Work out the new token value, dropping out if none were available deferred_token token = allocate_token(); - if(token == INVALID_DEFERRED_TOKEN) { + if (token == INVALID_DEFERRED_TOKEN) { return false; } From 04eee7c7167ce3a96e84b3e502c90bbc636b2cbd Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Tue, 16 Nov 2021 04:48:08 +1100 Subject: [PATCH 13/13] Review comments. --- builddefs/generic_features.mk | 1 + common_features.mk | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk index 2281a82917e6..e41ce6ac91d4 100644 --- a/builddefs/generic_features.mk +++ b/builddefs/generic_features.mk @@ -19,6 +19,7 @@ GRAVE_ESC_ENABLE ?= yes GENERIC_FEATURES = \ COMBO \ COMMAND \ + DEFERRED_EXEC \ DIGITIZER \ DIP_SWITCH \ DYNAMIC_KEYMAP \ diff --git a/common_features.mk b/common_features.mk index 331cb2b304f5..896fbb0129d0 100644 --- a/common_features.mk +++ b/common_features.mk @@ -146,11 +146,6 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes) endif endif -ifeq ($(strip $(DEFERRED_EXEC_ENABLE)), yes) - OPT_DEFS += -DDEFERRED_EXEC_ENABLE - SRC += $(QUANTUM_DIR)/deferred_exec.c -endif - VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi EEPROM_DRIVER ?= vendor ifeq ($(filter $(EEPROM_DRIVER),$(VALID_EEPROM_DRIVER_TYPES)),)