qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

deferred_exec.c (6900B)


      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 
     12 //------------------------------------
     13 // Helpers
     14 //
     15 
     16 static deferred_token current_token = 0;
     17 
     18 static inline bool token_can_be_used(deferred_executor_t *table, size_t table_count, deferred_token token) {
     19     if (token == INVALID_DEFERRED_TOKEN) {
     20         return false;
     21     }
     22     for (int i = 0; i < table_count; ++i) {
     23         if (table[i].token == token) {
     24             return false;
     25         }
     26     }
     27     return true;
     28 }
     29 
     30 static inline deferred_token allocate_token(deferred_executor_t *table, size_t table_count) {
     31     deferred_token first = ++current_token;
     32     while (!token_can_be_used(table, table_count, current_token)) {
     33         ++current_token;
     34         if (current_token == first) {
     35             // If we've looped back around to the first, everything is already allocated (yikes!). Need to exit with a failure.
     36             return INVALID_DEFERRED_TOKEN;
     37         }
     38     }
     39     return current_token;
     40 }
     41 
     42 //------------------------------------
     43 // Advanced API: used when a custom-allocated table is used, primarily for core code.
     44 //
     45 
     46 deferred_token defer_exec_advanced(deferred_executor_t *table, size_t table_count, uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) {
     47     // Ignore queueing if the table isn't valid, it's a zero-time delay, or the token is not valid
     48     if (!table || table_count == 0 || delay_ms == 0 || !callback) {
     49         return INVALID_DEFERRED_TOKEN;
     50     }
     51 
     52     // Find an unused slot and claim it
     53     for (int i = 0; i < table_count; ++i) {
     54         deferred_executor_t *entry = &table[i];
     55         if (entry->token == INVALID_DEFERRED_TOKEN) {
     56             // Work out the new token value, dropping out if none were available
     57             deferred_token token = allocate_token(table, table_count);
     58             if (token == INVALID_DEFERRED_TOKEN) {
     59                 return false;
     60             }
     61 
     62             // Set up the executor table entry
     63             entry->token        = current_token;
     64             entry->trigger_time = timer_read32() + delay_ms;
     65             entry->callback     = callback;
     66             entry->cb_arg       = cb_arg;
     67             return current_token;
     68         }
     69     }
     70 
     71     // None available
     72     return INVALID_DEFERRED_TOKEN;
     73 }
     74 
     75 bool extend_deferred_exec_advanced(deferred_executor_t *table, size_t table_count, deferred_token token, uint32_t delay_ms) {
     76     // Ignore queueing if the table isn't valid, it's a zero-time delay, or the token is not valid
     77     if (!table || table_count == 0 || delay_ms == 0 || token == INVALID_DEFERRED_TOKEN) {
     78         return false;
     79     }
     80 
     81     // Find the entry corresponding to the token
     82     for (int i = 0; i < table_count; ++i) {
     83         deferred_executor_t *entry = &table[i];
     84         if (entry->token == token) {
     85             // Found it, extend the delay
     86             entry->trigger_time = timer_read32() + delay_ms;
     87             return true;
     88         }
     89     }
     90 
     91     // Not found
     92     return false;
     93 }
     94 
     95 bool cancel_deferred_exec_advanced(deferred_executor_t *table, size_t table_count, deferred_token token) {
     96     // Ignore request if the table/token are not valid
     97     if (!table || table_count == 0 || token == INVALID_DEFERRED_TOKEN) {
     98         return false;
     99     }
    100 
    101     // Find the entry corresponding to the token
    102     for (int i = 0; i < table_count; ++i) {
    103         deferred_executor_t *entry = &table[i];
    104         if (entry->token == token) {
    105             // Found it, cancel and clear the table entry
    106             entry->token        = INVALID_DEFERRED_TOKEN;
    107             entry->trigger_time = 0;
    108             entry->callback     = NULL;
    109             entry->cb_arg       = NULL;
    110             return true;
    111         }
    112     }
    113 
    114     // Not found
    115     return false;
    116 }
    117 
    118 void deferred_exec_advanced_task(deferred_executor_t *table, size_t table_count, uint32_t *last_execution_time) {
    119     uint32_t now = timer_read32();
    120 
    121     // Throttle only once per millisecond
    122     if (((int32_t)TIMER_DIFF_32(now, (*last_execution_time))) > 0) {
    123         *last_execution_time = now;
    124 
    125         // Run through each of the executors
    126         for (int i = 0; i < table_count; ++i) {
    127             deferred_executor_t *entry      = &table[i];
    128             deferred_token       curr_token = entry->token;
    129 
    130             // Check if we're supposed to execute this entry
    131             if (curr_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                 // If the token has changed, then the callback has canceled and re-queued. Skip further processing.
    136                 if (entry->token != curr_token) {
    137                     continue;
    138                 }
    139 
    140                 // Update the trigger time if we have to repeat, otherwise clear it out
    141                 if (delay_ms > 0) {
    142                     // Intentionally add just the delay to the existing trigger time -- this ensures the next
    143                     // invocation is with respect to the previous trigger, rather than when it got to execution. Under
    144                     // normal circumstances this won't cause issue, but if another executor is invoked that takes a
    145                     // considerable length of time, then this ensures best-effort timing between invocations.
    146                     entry->trigger_time += delay_ms;
    147                 } else {
    148                     // If it was zero, then the callback is cancelling repeated execution. Free up the slot.
    149                     entry->token        = INVALID_DEFERRED_TOKEN;
    150                     entry->trigger_time = 0;
    151                     entry->callback     = NULL;
    152                     entry->cb_arg       = NULL;
    153                 }
    154             }
    155         }
    156     }
    157 }
    158 
    159 //------------------------------------
    160 // Basic API: used by user-mode code, guaranteed to not collide with core deferred execution
    161 //
    162 
    163 static uint32_t            last_deferred_exec_check                = 0;
    164 static deferred_executor_t basic_executors[MAX_DEFERRED_EXECUTORS] = {0};
    165 
    166 deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) {
    167     return defer_exec_advanced(basic_executors, MAX_DEFERRED_EXECUTORS, delay_ms, callback, cb_arg);
    168 }
    169 bool extend_deferred_exec(deferred_token token, uint32_t delay_ms) {
    170     return extend_deferred_exec_advanced(basic_executors, MAX_DEFERRED_EXECUTORS, token, delay_ms);
    171 }
    172 bool cancel_deferred_exec(deferred_token token) {
    173     return cancel_deferred_exec_advanced(basic_executors, MAX_DEFERRED_EXECUTORS, token);
    174 }
    175 void deferred_exec_task(void) {
    176     deferred_exec_advanced_task(basic_executors, MAX_DEFERRED_EXECUTORS, &last_deferred_exec_check);
    177 }