Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advanced deferred_exec for core-side code. #15579

Merged
merged 1 commit into from
Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 48 additions & 37 deletions quantum/deferred_exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,27 @@
# 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) {
//------------------------------------
// Helpers
//

static deferred_token current_token = 0;

static inline bool token_can_be_used(deferred_executor_t *table, size_t table_count, deferred_token token) {
if (token == INVALID_DEFERRED_TOKEN) {
return false;
}
for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) {
if (executors[i].token == token) {
for (int i = 0; i < table_count; ++i) {
if (table[i].token == token) {
return false;
}
}
return true;
}

static inline deferred_token allocate_token(void) {
static inline deferred_token allocate_token(deferred_executor_t *table, size_t table_count) {
deferred_token first = ++current_token;
while (!token_can_be_used(current_token)) {
while (!token_can_be_used(table, table_count, 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.
Expand All @@ -44,18 +39,22 @@ static inline deferred_token allocate_token(void) {
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) {
//------------------------------------
// Advanced API: used when a custom-allocated table is used, primarily for core code.
//

deferred_token defer_exec_advanced(deferred_executor_t *table, size_t table_count, uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) {
// Ignore queueing if the table isn't valid, it's a zero-time delay, or the token is not valid
if (!table || table_count == 0 || 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];
for (int i = 0; i < table_count; ++i) {
deferred_executor_t *entry = &table[i];
if (entry->token == INVALID_DEFERRED_TOKEN) {
// Work out the new token value, dropping out if none were available
deferred_token token = allocate_token();
deferred_token token = allocate_token(table, table_count);
if (token == INVALID_DEFERRED_TOKEN) {
return false;
}
Expand All @@ -73,15 +72,15 @@ deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, vo
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) {
bool extend_deferred_exec_advanced(deferred_executor_t *table, size_t table_count, deferred_token token, uint32_t delay_ms) {
// Ignore queueing if the table isn't valid, it's a zero-time delay, or the token is not valid
if (!table || table_count == 0 || 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];
for (int i = 0; i < table_count; ++i) {
deferred_executor_t *entry = &table[i];
if (entry->token == token) {
// Found it, extend the delay
entry->trigger_time = timer_read32() + delay_ms;
Expand All @@ -93,15 +92,15 @@ bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) {
return false;
}

bool cancel_deferred_exec(deferred_token token) {
// Ignore request if the token is not valid
if (token == INVALID_DEFERRED_TOKEN) {
bool cancel_deferred_exec_advanced(deferred_executor_t *table, size_t table_count, deferred_token token) {
// Ignore request if the table/token are not valid
if (!table || table_count == 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];
for (int i = 0; i < table_count; ++i) {
deferred_executor_t *entry = &table[i];
if (entry->token == token) {
// Found it, cancel and clear the table entry
entry->token = INVALID_DEFERRED_TOKEN;
Expand All @@ -116,16 +115,16 @@ bool cancel_deferred_exec(deferred_token token) {
return false;
}

void deferred_exec_task(void) {
void deferred_exec_advanced_task(deferred_executor_t *table, size_t table_count, uint32_t *last_execution_time) {
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;
if (((int32_t)TIMER_DIFF_32(now, (*last_execution_time))) > 0) {
*last_execution_time = now;

// Run through each of the executors
for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) {
deferred_executor_t *entry = &executors[i];
for (int i = 0; i < table_count; ++i) {
deferred_executor_t *entry = &table[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) {
Expand All @@ -150,3 +149,15 @@ void deferred_exec_task(void) {
}
}
}

//------------------------------------
// Basic API: used by user-mode code, guaranteed to not collide with core deferred execution
//

static uint32_t last_deferred_exec_check = 0;
static deferred_executor_t basic_executors[MAX_DEFERRED_EXECUTORS] = {0};

deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) { return defer_exec_advanced(basic_executors, MAX_DEFERRED_EXECUTORS, delay_ms, callback, cb_arg); }
bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) { return extend_deferred_exec_advanced(basic_executors, MAX_DEFERRED_EXECUTORS, token, delay_ms); }
bool cancel_deferred_exec(deferred_token token) { return cancel_deferred_exec_advanced(basic_executors, MAX_DEFERRED_EXECUTORS, token); }
void deferred_exec_task(void) { deferred_exec_advanced_task(basic_executors, MAX_DEFERRED_EXECUTORS, &last_deferred_exec_check); }
119 changes: 101 additions & 18 deletions quantum/deferred_exec.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,117 @@

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

// A token that can be used to cancel an existing deferred execution.
//------------------------------------
// Common
//------------------------------------

/**
* @typedef A token that can be used to cancel or extend an existing deferred execution.
*/
typedef uint8_t deferred_token;

/**
* @def The constant used to denote an invalid deferred execution 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 Callback to execute.
* @param trigger_time[in] the intended trigger time to execute the callback -- equivalent time-space as timer_read32()
* @param cb_arg[in] the callback argument specified when enqueueing the deferred executor
* @return 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
//------------------------------------
// Basic API: used by user-mode code, guaranteed to not collide with core deferred execution
//------------------------------------

/**
* Configures the supplied deferred executor to be executed after the required number of milliseconds.
*
* @param delay_ms[in] the number of milliseconds before executing the callback
* @param callback[in] the executor to invoke
* @param cb_arg[in] the argument to pass to the executor, may be NULL if unused by the executor
* @return a token usable for extension/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
/**
* Allows for extending the timeframe before an existing deferred execution is invoked.
*
* @param token[in] the returned value from defer_exec for the deferred execution you wish to extend
* @param delay_ms[in] the number of milliseconds before executing the callback
* @return true if the token was extended successfully, otherwise false
*/
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
/**
* Allows for cancellation of an existing deferred execution.
*
* @param token[in] the returned value from defer_exec for the deferred execution you wish to cancel
* @return true if the token was cancelled successfully, otherwise false
*/
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.
/**
* 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);

//------------------------------------
// Advanced API: used when a custom-allocated table is used, primarily for core code.
//------------------------------------

/**
* @struct Structure for containing self-hosted deferred executor tables.
* @brief Core-side code can use this to create their own tables without impacting on the use of users' ability to add deferred execution.
* Code outside deferred_exec.c should not worry about internals of this struct, and should just allocate the required number in an array.
*/
typedef struct deferred_executor_t {
deferred_token token;
uint32_t trigger_time;
deferred_exec_callback callback;
void * cb_arg;
} deferred_executor_t;

/**
* Configures the supplied deferred executor to be executed after the required number of milliseconds.
*
* @param table[in] the custom table used for storage
* @param table_count[in] the number of available items in the table
* @param delay_ms[in] the number of milliseconds before executing the callback
* @param callback[in] the executor to invoke
* @param cb_arg[in] the argument to pass to the executor, may be NULL if unused by the executor
* @return a token usable for extension/cancellation, or INVALID_DEFERRED_TOKEN if an error occurred
*/
deferred_token defer_exec_advanced(deferred_executor_t *table, size_t table_count, uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg);

/**
* Allows for extending the timeframe before an existing deferred execution is invoked.
*
* @param token[in] the returned value from defer_exec for the deferred execution you wish to extend
* @param delay_ms[in] the number of milliseconds before executing the callback
* @return true if the token was extended successfully, otherwise false
*/
bool extend_deferred_exec_advanced(deferred_executor_t *table, size_t table_count, deferred_token token, uint32_t delay_ms);

/**
* Allows for cancellation of an existing deferred execution.
*
* @param token[in] the returned value from defer_exec for the deferred execution you wish to cancel
* @return true if the token was cancelled successfully, otherwise false
*/
bool cancel_deferred_exec_advanced(deferred_executor_t *table, size_t table_count, deferred_token token);

/**
* Forward declaration for the main loop in order to execute any custom table deferred executors. Should not be invoked by keyboard/user code.
* Needed for any custom-allocated deferred execution tables. Any core tasks should add appropriate invocation to quantum/main.c.
*
* @param table[in] the custom table used for storage
* @param table_count[in] the number of available items in the table
* @param last_execution_time[in,out] the last execution time -- this will be checked first to determine if execution is needed, and updated if execution occurred
*/
void deferred_exec_advanced_task(deferred_executor_t *table, size_t table_count, uint32_t *last_execution_time);