summaryrefslogtreecommitdiff
path: root/quantum/deferred_exec.c
blob: b3be3747d47d07aa5ef13371aadcc8629881c1aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// 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

//------------------------------------
// 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 < table_count; ++i) {
        if (table[i].token == token) {
            return false;
        }
    }
    return true;
}

static inline deferred_token allocate_token(deferred_executor_t *table, size_t table_count) {
    deferred_token first = ++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.
            return INVALID_DEFERRED_TOKEN;
        }
    }
    return current_token;
}

//------------------------------------
// 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 < 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(table, table_count);
            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_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 < 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;
            return true;
        }
    }

    // Not found
    return false;
}

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 < 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;
            entry->trigger_time = 0;
            entry->callback     = NULL;
            entry->cb_arg       = NULL;
            return true;
        }
    }

    // Not found
    return false;
}

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_execution_time))) > 0) {
        *last_execution_time = now;

        // Run through each of the executors
        for (int i = 0; i < table_count; ++i) {
            deferred_executor_t *entry      = &table[i];
            deferred_token       curr_token = entry->token;

            // Check if we're supposed to execute this entry
            if (curr_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);

                // If the token has changed, then the callback has canceled and re-queued. Skip further processing.
                if (entry->token != curr_token) {
                    continue;
                }

                // 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;
                }
            }
        }
    }
}

//------------------------------------
// 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);
}