diff options
Diffstat (limited to 'quantum')
-rw-r--r-- | quantum/deferred_exec.c | 152 | ||||
-rw-r--r-- | quantum/deferred_exec.h | 38 | ||||
-rw-r--r-- | quantum/main.c | 10 | ||||
-rw-r--r-- | quantum/quantum.h | 4 |
4 files changed, 204 insertions, 0 deletions
diff --git a/quantum/deferred_exec.c b/quantum/deferred_exec.c new file mode 100644 index 0000000000..5b0a5b1425 --- /dev/null +++ b/quantum/deferred_exec.c @@ -0,0 +1,152 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <stddef.h> +#include <timer.h> +#include <deferred_exec.h> + +#ifndef MAX_DEFERRED_EXECUTORS +# define MAX_DEFERRED_EXECUTORS 8 +#endif + +typedef struct deferred_executor_t { + deferred_token token; + uint32_t trigger_time; + deferred_exec_callback callback; + void * cb_arg; +} deferred_executor_t; + +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; +} + +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) { + return INVALID_DEFERRED_TOKEN; + } + + // Find an unused slot and claim it + 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, 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; + entry->trigger_time = timer_read32() + delay_ms; + entry->callback = callback; + entry->cb_arg = cb_arg; + return current_token; + } + } + + // None available + return INVALID_DEFERRED_TOKEN; +} + +bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) { + // 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 == 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) { + // 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 == token) { + // Found it, cancel and clear the table entry + entry->token = INVALID_DEFERRED_TOKEN; + entry->trigger_time = 0; + entry->callback = NULL; + entry->cb_arg = NULL; + return true; + } + } + + // Not found + 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 && ((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->trigger_time, 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 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. + 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 0000000000..f80d353169 --- /dev/null +++ b/quantum/deferred_exec.h @@ -0,0 +1,38 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +// 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 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)(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 +// -- 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 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 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 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); + +// 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 3814d371c1..6ed6b95741 100644 --- a/quantum/main.c +++ b/quantum/main.c @@ -43,6 +43,10 @@ void protocol_task(void) { protocol_post_task(); } +#ifdef DEFERRED_EXEC_ENABLE +void deferred_exec_task(void); +#endif // DEFERRED_EXEC_ENABLE + /** \brief Main * * FIXME: Needs doc @@ -58,6 +62,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/quantum/quantum.h b/quantum/quantum.h index e6015adbe9..b34ff6ec51 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -54,6 +54,10 @@ #include <stddef.h> #include <stdlib.h> +#ifdef DEFERRED_EXEC_ENABLE +# include "deferred_exec.h" +#endif + extern layer_state_t default_layer_state; #ifndef NO_ACTION_LAYER |