├── .gitignore ├── package.json ├── install_deps.sh ├── README.md ├── Makefile ├── test └── sched_ut.c └── src └── sched ├── sched.h └── sched.c /.gitignore: -------------------------------------------------------------------------------- 1 | deps 2 | build 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sched", 3 | "description": "Embedded (bare metal) C scheduler library", 4 | "repo": "bradschl/embedded-c-scheduler", 5 | "license": "MIT", 6 | "version": "0.0.1", 7 | "src": [ 8 | "src/sched/sched.c", 9 | "src/sched/sched.h" 10 | ], 11 | "dependencies": { 12 | "bradschl/timermath.h": "*", 13 | "clibs/strdup": "*" 14 | }, 15 | "development": { 16 | "stephenmathieson/describe.h": "*" 17 | }, 18 | "keywords": [ 19 | "scheduler", 20 | "c", 21 | "embedded" 22 | ] 23 | } -------------------------------------------------------------------------------- /install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | THIS_SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 4 | 5 | 6 | # Check that clib is installed 7 | command -v clib >/dev/null 2>&1 || { 8 | printf 'clib is not installed. Aborting.\n' >&2 9 | printf 'See https://github.com/clibs/clib\n' >&2 10 | exit 1 11 | } 12 | 13 | 14 | # Install 15 | DEPS_PACKAGES="clibs/strdup bradschl/timermath.h bradschl/metamake stephenmathieson/describe.h" 16 | 17 | clib install ${DEPS_PACKAGES} -o ${THIS_SCRIPT_DIR}/deps 18 | if (($? > 0)); then 19 | printf 'Failed to install packages\n' >&2 20 | exit 1 21 | fi 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Embedded C Scheduler 2 | Task scheduler library for embedded (bare metal) projects. It has support for both tick based scheduling, as well as hand coded slot scheduling. It has no direct hardware dependencies, so it is portable to many platforms. 3 | 4 | ## Installation 5 | This library can be installed into your project using [clib](https://github.com/clibs/clib): 6 | 7 | ``` 8 | $ clib install bradschl/embedded-c-scheduler 9 | ``` 10 | 11 | ## Example 12 | ```C 13 | #include 14 | #include "sched/sched.h" 15 | 16 | static uint32_t 17 | get_current_time(void * hint) 18 | { 19 | (void) hint; 20 | 21 | // Timer is 1us resolution. The hardware counts down from the maximum 22 | // value, so the raw value needs to be flipped to be compatible with the 23 | // scheduler 24 | return Timer_1_INIT_PERIOD - Timer_1_ReadCounter(); 25 | } 26 | 27 | 28 | struct foo_task_ctx { 29 | /* ... */ 30 | } 31 | 32 | static void 33 | foo_task(void * hint) 34 | { 35 | struct foo_task_ctx * ctx = (struct foo_task_ctx *) hint; 36 | /* Do tasks stuff */ 37 | } 38 | 39 | 40 | int 41 | main() 42 | { 43 | // Create the scheduler, 1000us (1ms) per tick 44 | Timer_1_Start(); 45 | 46 | struct sched_ctx * scheduler = sched_alloc_context( 47 | NULL, get_current_time, Timer_1_INIT_PERIOD, 1000); 48 | 49 | ASSERT_NOT_NULL(scheduler); 50 | 51 | 52 | // Create the task 53 | struct foo_task_ctx foo_ctx; 54 | 55 | struct sched_task * foo_task_handle = sched_alloc_task( 56 | scheduler, foo_ctx, foo_task, "my foo task", TASK_TICK_4); 57 | 58 | ASSERT_NOT_NULL(foo_task_handle); 59 | 60 | 61 | for(;;) { 62 | sched_run(scheduler); 63 | 64 | /* ... */ 65 | } 66 | } 67 | ``` 68 | 69 | ## API 70 | See [sched.h](src/sched/sched.h) for the C API. 71 | 72 | ## Dependencies and Resources 73 | This library uses heap when allocating structures. After initialization, additional allocations will not be made. This should be fine for an embedded target, since memory fragmentation only happens if memory is freed. 74 | 75 | Compiled, this library is only a few kilobytes. Runtime memory footprint is very small, and is dependent on the number of tasks allocated. 76 | 77 | ## License 78 | MIT license for all files. 79 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------- DEFAULT GOAL 2 | .DEFAULT_GOAL := all 3 | 4 | 5 | # -------------------------------------------------------------- VERBOSE OUTPUT 6 | 7 | ifeq ("$(origin V)", "command line") 8 | ENABLE_VERBOSE = $(V) 9 | endif 10 | ifndef ENABLE_VERBOSE 11 | ENABLE_VERBOSE = 0 12 | endif 13 | 14 | ifeq ($(ENABLE_VERBOSE),1) 15 | Q = 16 | else 17 | Q = @ 18 | endif 19 | 20 | 21 | # ------------------------------------------------------------------ META RULES 22 | # This must be included before any meta rules are used 23 | include deps/metamake/Meta.mk 24 | 25 | 26 | # --------------------------------------------------------- BUILD ARCHITECTURES 27 | $(call BEGIN_DEFINE_ARCH, host_test, build/host_test) 28 | PREFIX := 29 | CF := -O0 -g3 -Wall -Wextra -std=gnu11 -D_GNU_SOURCE=1 30 | $(call END_DEFINE_ARCH) 31 | 32 | $(call BEGIN_DEFINE_ARCH, host_c99, build/host_c99) 33 | PREFIX := 34 | CF := -O2 -Wall -Wextra -std=c99 35 | $(call END_DEFINE_ARCH) 36 | 37 | $(call BEGIN_DEFINE_ARCH, host_c11, build/host_c11) 38 | PREFIX := 39 | CF := -O2 -Wall -Wextra -std=c11 40 | $(call END_DEFINE_ARCH) 41 | 42 | 43 | # ------------------------------------------------------------- BUILD LIBRARIES 44 | sched_SRC := $(call FIND_SOURCE_IN_DIR, src) 45 | $(call BEGIN_UNIVERSAL_BUILD) 46 | $(call IMPORT_DEPS, deps) 47 | 48 | $(call ADD_C_INCLUDE, src) 49 | $(call BUILD_SOURCE, $(sched_SRC)) 50 | $(call MAKE_LIBRARY, sched) 51 | 52 | $(call EXPORT_SHALLOW_DEPS, sched) 53 | 54 | # Build for all defined architectures, even if nothing depends on it 55 | $(call APPEND_ALL_TARGET_VAR) 56 | $(call END_UNIVERSAL_BUILD) 57 | 58 | 59 | deps_SRC := $(call FIND_SOURCE_IN_DIR, deps) 60 | $(call BEGIN_UNIVERSAL_BUILD) 61 | $(call ADD_C_INCLUDE, deps) 62 | 63 | CF := -Wno-unused-variable 64 | $(call BUILD_SOURCE, $(deps_SRC)) 65 | $(call MAKE_LIBRARY, deps) 66 | 67 | $(call EXPORT_SHALLOW_DEPS, deps) 68 | $(call END_UNIVERSAL_BUILD) 69 | 70 | 71 | # ----------------------------------------------------------- BUILD EXECUTABLES 72 | 73 | sched_ut_SRC := test/sched_ut.c 74 | 75 | $(call BEGIN_ARCH_BUILD, host_test) 76 | $(call IMPORT_DEPS, sched deps) 77 | $(call BUILD_SOURCE, $(sched_ut_SRC)) 78 | 79 | $(call CC_LINK, sched_ut) 80 | 81 | # Always build 82 | $(call APPEND_ALL_TARGET_VAR) 83 | $(call END_ARCH_BUILD) 84 | 85 | 86 | # ---------------------------------------------------------------- GLOBAL RULES 87 | 88 | .PHONY: all 89 | all: METAMAKE_ALL 90 | @echo "===== All build finished =====" 91 | 92 | .PHONY: clean 93 | clean: METAMAKE_CLEAN 94 | @echo "===== Clean finished =====" 95 | 96 | .PHONY: help 97 | help: 98 | @echo "Available targets:" 99 | @echo " all - Build all top level targets" 100 | @echo " clean - Clean intermediate build files" 101 | -------------------------------------------------------------------------------- /test/sched_ut.c: -------------------------------------------------------------------------------- 1 | 2 | #include "describe/describe.h" 3 | #include "sched/sched.h" 4 | 5 | 6 | uint32_t 7 | mock_get_time(void * hint) 8 | { 9 | uint32_t t = 0; 10 | if(NULL != hint) { 11 | t = *((uint32_t *) hint); 12 | } 13 | return t; 14 | } 15 | 16 | void 17 | mock_task(void * hint) 18 | { 19 | if (NULL != hint) { 20 | uint32_t * call_count = (uint32_t *) hint; 21 | ++(*call_count); 22 | } 23 | } 24 | 25 | 26 | int main(int argc, char const *argv[]) 27 | { 28 | (void) argv; 29 | (void) argc; 30 | 31 | uint32_t max_time = 255; 32 | uint32_t tick_period = 1; 33 | 34 | describe("The scheduler context") { 35 | 36 | struct sched_ctx * ctx = NULL; 37 | it("can be allocated") { 38 | ctx = sched_alloc_context(NULL, mock_get_time, max_time, tick_period); 39 | assert_not_null(ctx); 40 | } 41 | 42 | struct sched_task * task = NULL; 43 | it("can allocate a task") { 44 | task = sched_alloc_task(ctx, NULL, mock_task, "mock_task", TASK_TICK_IDLE); 45 | assert_not_null(task); 46 | } 47 | 48 | it("can free a task") { 49 | sched_free_task(task); 50 | } 51 | 52 | it("can be freed") { 53 | sched_free_context(ctx); 54 | } 55 | 56 | } 57 | 58 | describe("The scheduler") { 59 | uint32_t now = 0; 60 | 61 | struct sched_ctx * ctx = NULL; 62 | it("can allocate a context") { 63 | ctx = sched_alloc_context(&now, mock_get_time, max_time, tick_period); 64 | assert_not_null(ctx); 65 | } 66 | 67 | uint32_t tick_idle_count = 0; 68 | struct sched_task * task_idle = NULL; 69 | it("can allocate the idle task") { 70 | task_idle = sched_alloc_task(ctx, &tick_idle_count, mock_task, NULL, TASK_TICK_IDLE); 71 | } 72 | 73 | 74 | uint32_t tick_1_count = 0; 75 | struct sched_task * task_1 = NULL; 76 | it("can allocate the 1 tick task") { 77 | task_1 = sched_alloc_task(ctx, &tick_1_count, mock_task, NULL, TASK_TICK_1); 78 | } 79 | 80 | 81 | uint32_t tick_2_count = 0; 82 | struct sched_task * task_2 = NULL; 83 | it("can allocate the 2 tick task") { 84 | task_2 = sched_alloc_task(ctx, &tick_2_count, mock_task, NULL, TASK_TICK_2); 85 | } 86 | 87 | uint32_t tick_4_count = 0; 88 | struct sched_task * task_4 = NULL; 89 | it("can allocate the 4 tick task") { 90 | task_4 = sched_alloc_task(ctx, &tick_4_count, mock_task, NULL, TASK_TICK_4); 91 | } 92 | 93 | uint32_t tick_8_count = 0; 94 | struct sched_task * task_8 = NULL; 95 | it("can allocate the 8 tick task") { 96 | task_8 = sched_alloc_task(ctx, &tick_8_count, mock_task, NULL, TASK_TICK_8); 97 | } 98 | 99 | uint32_t tick_16_count = 0; 100 | struct sched_task * task_16 = NULL; 101 | it("can allocate the 16 tick task") { 102 | task_16 = sched_alloc_task(ctx, &tick_16_count, mock_task, NULL, TASK_TICK_16); 103 | } 104 | 105 | uint32_t tick_32_count = 0; 106 | struct sched_task * task_32 = NULL; 107 | it("can allocate the 32 tick task") { 108 | task_32 = sched_alloc_task(ctx, &tick_32_count, mock_task, NULL, TASK_TICK_32); 109 | } 110 | 111 | it("can execute the tasks") { 112 | assert_equal(0, tick_idle_count); 113 | assert_equal(0, tick_1_count); 114 | assert_equal(0, tick_2_count); 115 | assert_equal(0, tick_4_count); 116 | assert_equal(0, tick_8_count); 117 | assert_equal(0, tick_16_count); 118 | assert_equal(0, tick_32_count); 119 | 120 | uint32_t i; 121 | for (i = 0; i < 32; ++i) { 122 | ++now; 123 | sched_run(ctx); 124 | } 125 | 126 | assert_equal(0, tick_idle_count); 127 | assert_equal(32, tick_1_count); 128 | assert_equal(16, tick_2_count); 129 | assert_equal(8, tick_4_count); 130 | assert_equal(4, tick_8_count); 131 | assert_equal(2, tick_16_count); 132 | assert_equal(1, tick_32_count); 133 | } 134 | 135 | it("can execute the idle task") { 136 | assert_equal(0, tick_idle_count); 137 | sched_run(ctx); 138 | assert_equal(1, tick_idle_count); 139 | } 140 | 141 | it("context can be freed before the tasks") { 142 | sched_free_context(ctx); 143 | } 144 | 145 | it("can deallocate the tasks") { 146 | sched_free_task(task_idle); 147 | sched_free_task(task_1); 148 | sched_free_task(task_2); 149 | sched_free_task(task_4); 150 | sched_free_task(task_8); 151 | sched_free_task(task_16); 152 | sched_free_task(task_32); 153 | } 154 | } 155 | 156 | return assert_failures(); 157 | } 158 | 159 | -------------------------------------------------------------------------------- /src/sched/sched.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Bradley Kim Schleusner < bradschl@gmail.com > 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #ifndef SCHED_H_ 24 | #define SCHED_H_ 25 | 26 | #include 27 | #include 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif /* __cplusplus */ 32 | 33 | 34 | // ---------------------------------------------------------- Scheduler context 35 | 36 | 37 | // ------- Internal scheduler structure 38 | struct sched_ctx; 39 | 40 | 41 | /** 42 | * @brief Function pointer prototype for getting the current time 43 | * @details The returned time must be an incrementing type, starting at 0, 44 | * incrementing to its maximum value, and then rolling back to 0. 45 | * The exact units of time do not matter to the scheduler. It is 46 | * recommended that the time function is at least faster than the task 47 | * tick rate such that task execution time can be measured with 48 | * meaningful resolution. 49 | * If the implementation references a count down time source, then it 50 | * should invert the time source value, i.e. 51 | * return (TIMER_MAX_VALUE - timer_value); 52 | * 53 | * @param hint Optional hint parameter. This pointer may be used for whatever 54 | * the implementation wants, such as a this pointer. It can be set to 55 | * NULL if it isn't used. 56 | * @return Current time 57 | */ 58 | typedef uint32_t (*sched_get_time_fn)(void * hint); 59 | 60 | 61 | /** 62 | * @brief Allocates the scheduler context on the heap 63 | * 64 | * @param hint Optional hint parameter for the get_time_fn function. The 65 | * get_time_fn will always be called with this parameter. If unused, 66 | * set to NULL 67 | * @param get_time_fn Get time function pointer. This will be used by the 68 | * scheduler to time task execution, as well as to determine when to 69 | * run the next task tick 70 | * @param max_time Maximum value that the get_time_fn will return 71 | * @param tick_period Number of counts returned by the get_time_fn per task 72 | * tick 73 | * @return Scheduler context, or NULL on failure 74 | */ 75 | struct sched_ctx * 76 | sched_alloc_context(void * hint, 77 | sched_get_time_fn get_time_fn, 78 | uint32_t max_time, 79 | uint32_t tick_period); 80 | 81 | 82 | /** 83 | * @brief Deallocates a scheduler 84 | * @details This will free all resources used by the scheduler an unregister 85 | * all tasks. 86 | * 87 | * @param sched_ctx Scheduler context to free 88 | */ 89 | void 90 | sched_free_context(struct sched_ctx * ctx); 91 | 92 | 93 | /** 94 | * @brief Periodic call to drive the scheduler 95 | * @details This needs to be called continuously to drive task execution. This 96 | * will return to allow for top level code outside of the scheduler to 97 | * run. To run the scheduler forever, use the sched_run_noret wrapper. 98 | * Example: 99 | * for (;;) { 100 | * sched_run(sched_ctx); 101 | * if (can_sleep) { 102 | * enter_lpm(); 103 | * sched_reset(sched_ctx); 104 | * } 105 | * } 106 | * 107 | * @param sched_ctx Scheduler context 108 | */ 109 | void 110 | sched_run(struct sched_ctx * ctx); 111 | 112 | 113 | /** 114 | * @brief Executes the scheduler and never returns 115 | * 116 | * @param sched_ctx Scheduler context 117 | */ 118 | static inline void 119 | sched_run_noret(struct sched_ctx * ctx) 120 | { 121 | for(;;) { 122 | sched_run(ctx); 123 | } 124 | } 125 | 126 | 127 | /** 128 | * @brief Resets the current task tick and next tick time 129 | * @details This is useful for reseting the scheduler after coming out of a 130 | * sleep mode. This will allow tasks to execute right. 131 | * 132 | * @param sched_ctx Scheduler context 133 | */ 134 | void 135 | sched_reset(struct sched_ctx * ctx); 136 | 137 | 138 | // ---------------------------------------------------------------------- Tasks 139 | 140 | // ------------ Internal task structure 141 | struct sched_task; 142 | 143 | 144 | /** 145 | * @brief Tasks function pointer prototype 146 | * 147 | * @param hint Optional hint parameter. Implementation may use this for 148 | * whatever the implementation wants, such as a this pointer 149 | */ 150 | typedef void (*sched_task_fn)(void * hint); 151 | 152 | 153 | /** 154 | * @brief Allocates a new task on the heap and registers it with the scheduler 155 | * 156 | * @param sched_ctx Scheduler context to register with 157 | * @param hint Optional hint parameter for the task_fn function. This will be 158 | * passed to the task_fn function every time it is called. If not 159 | * used, set to NULL 160 | * @param task_fn Task function callback 161 | * @param name Human readable name for the task. If unused, set to NULL. If not 162 | * NULL, then the string will be copied into the returned task handle 163 | * @param tick_mask Tick mask to execute the task on. The scheduler implements 164 | * ticks as a 32 bit number with only one bit set. The scheduler 165 | * increments the tick by rotating the tick number right by one. If 166 | * (tick_mask & tick) is true, then the task is executed. If the tick 167 | * mask is 0, then the task is executed as an idle task. 168 | * Users may use this tick strategy to hard code exact tick slots 169 | * that tasks execute in. To use the scheduler as a "standard" power 170 | * of 2 type scheduler, use the "Standard task tick masks" included in 171 | * this header 172 | * @return Scheduler tasks handle or NULL on failure 173 | */ 174 | struct sched_task * 175 | sched_alloc_task(struct sched_ctx * ctx, 176 | void * hint, 177 | sched_task_fn task_fn, 178 | const char * name, 179 | uint32_t tick_mask); 180 | 181 | 182 | /** 183 | * @brief Deallocates a task handle 184 | * @details This will unregister the task from the scheduler and free its 185 | * resources 186 | * 187 | * @param sched_task Scheduler task handle 188 | */ 189 | void 190 | sched_free_task(struct sched_task * task); 191 | 192 | 193 | // --------------------------------------------------- Standard task tick masks 194 | 195 | #define TASK_TICK_IDLE 0b00000000000000000000000000000000 196 | #define TASK_TICK_1 0b11111111111111111111111111111111 197 | #define TASK_TICK_2 0b10101010101010101010101010101010 198 | #define TASK_TICK_4 0b01000100010001000100010001000100 199 | #define TASK_TICK_8 0b00010000000100000001000000010000 200 | #define TASK_TICK_16 0b00000001000000000000000100000000 201 | #define TASK_TICK_32 0b00000000000000010000000000000000 202 | 203 | 204 | // ------------------------------------------------------------- Task debugging 205 | 206 | /** 207 | * The task debug functions may be used to pull run time stats about 208 | * each task registered with the scheduler. This can be used to figure out if 209 | * tasks are overflowing their tick. 210 | * 211 | * bool new_info; 212 | * struct sched_task_info info; 213 | * for (new_info = sched_get_first_task_info(sched_ctx, &info); 214 | * false != new_info; 215 | * new_info = sched_get_next_task_info(&info)) 216 | * { 217 | * // Print out tasks stats to debug console 218 | * } 219 | * 220 | * sched_reset_stats(sched_ctx); 221 | */ 222 | struct sched_task_info { 223 | // Pointer to the next task. For internal use only 224 | void * _iter; 225 | 226 | // Task name string 227 | const char * name; 228 | 229 | // Average execution time of the task. Units are implementation specific 230 | // and will be the same resolution as the get_time_fn function 231 | uint32_t average_time; 232 | 233 | // Maximum execution time of the task, same units as average_time 234 | uint32_t max_time; 235 | }; 236 | 237 | 238 | /** 239 | * @brief Initializes a task iterator for the first task in the scheduler 240 | * 241 | * @param sched_ctx Scheduler context 242 | * @param sched_task_info Task info structure to initialize 243 | * @return true on success, else false 244 | */ 245 | bool 246 | sched_get_first_task_info(struct sched_ctx * ctx, 247 | struct sched_task_info * info); 248 | 249 | 250 | /** 251 | * @brief Updates the task info structure to the next task 252 | * @details This follows the iterator embedded in the task info structure to 253 | * get at the next tasks information. See the section example for 254 | * detailed usage 255 | * 256 | * @param sched_task_info Task info structure to update with the next tasks 257 | * info 258 | * @return true on success, else false 259 | */ 260 | bool 261 | sched_get_next_task_info(struct sched_task_info * info); 262 | 263 | 264 | /** 265 | * @brief Resets all task timing statistics 266 | * 267 | * @param sched_ctx Scheduler context 268 | * @return true on success, else false 269 | */ 270 | void 271 | sched_reset_stats(struct sched_ctx * ctx); 272 | 273 | 274 | 275 | #ifdef __cplusplus 276 | } 277 | #endif /* __cplusplus */ 278 | 279 | #endif /* SCHED_H_ */ 280 | -------------------------------------------------------------------------------- /src/sched/sched.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Bradley Kim Schleusner < bradschl@gmail.com > 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "sched.h" 29 | #include "timermath/timermath.h" 30 | #include "strdup/strdup.h" 31 | 32 | // ------------------------------------------------------------ Private settings 33 | 34 | #define SHORT_NAME_LENGTH 16 35 | 36 | 37 | // -------------------------------------------------------------- Private types 38 | 39 | struct sched_ctx { 40 | // The current tick to run when the (now >= last_tick_time + tick_period) 41 | uint32_t current_tick; 42 | 43 | // Time of execution of the last tick 44 | uint32_t last_tick_time; 45 | uint32_t tick_period; 46 | struct tm_math tm; 47 | 48 | // Task linked list root pointer 49 | struct sched_task * root; 50 | 51 | // Time function 52 | sched_get_time_fn get_time; 53 | void * hint; 54 | }; 55 | 56 | struct sched_task { 57 | // Linked list storage 58 | struct sched_ctx * ctx; 59 | struct sched_task * next; 60 | 61 | 62 | // Execution will happen when (tick_mask & current_tick != 0), or it will 63 | // be executed in the idle loop if (tick_mask == 0) 64 | uint32_t tick_mask; 65 | 66 | 67 | // Task execution callback 68 | sched_task_fn execute; 69 | void * hint; 70 | 71 | 72 | // Task name 73 | char short_name[SHORT_NAME_LENGTH]; 74 | char * long_name; 75 | 76 | 77 | // Task stats 78 | uint32_t average_time; 79 | uint32_t max_time; 80 | }; 81 | 82 | 83 | // ---------------------------------------------------------- Private functions 84 | 85 | // --------------------- Task functions 86 | 87 | static struct sched_task * 88 | get_last_linked_task(struct sched_ctx * ctx) 89 | { 90 | struct sched_task * last; 91 | for (last = ctx->root; NULL != last; last = last->next) { 92 | if (NULL == last->next) { 93 | break; 94 | } 95 | } 96 | return last; 97 | } 98 | 99 | 100 | static void 101 | unlink_task(struct sched_task * task) 102 | { 103 | if (NULL != task) { 104 | struct sched_ctx * ctx = task->ctx; 105 | struct sched_task * next = task->next; 106 | task->ctx = NULL; 107 | task->next = NULL; 108 | 109 | struct sched_task * prev = NULL; 110 | if (NULL != ctx) { 111 | if (ctx->root == task) { 112 | ctx->root = next; 113 | } else { 114 | for(prev = ctx->root; prev != NULL; prev = prev->next) { 115 | if (task == prev->next) { 116 | break; 117 | } 118 | } 119 | } 120 | } 121 | 122 | if (NULL != prev) { 123 | prev->next = next; 124 | } 125 | } 126 | } 127 | 128 | static void 129 | link_task(struct sched_ctx *ctx, struct sched_task * task) 130 | { 131 | task->ctx = ctx; 132 | task->next = NULL; 133 | 134 | if (NULL == ctx->root) { 135 | ctx->root = task; 136 | } else { 137 | struct sched_task * last = get_last_linked_task(ctx); 138 | last->next = task; 139 | } 140 | } 141 | 142 | 143 | static void 144 | update_task_stats(struct sched_task * task, int32_t exec_time) 145 | { 146 | if (exec_time >= 0) { 147 | uint32_t t = (uint32_t) exec_time; 148 | task->average_time = (task->average_time + t) >> 1; 149 | if (t > task->max_time) { 150 | task->max_time = t; 151 | } 152 | } 153 | } 154 | 155 | 156 | static bool 157 | get_task_info(struct sched_task * task, struct sched_task_info * info) 158 | { 159 | bool success = false; 160 | 161 | if ((NULL != task) && (NULL != info)) { 162 | info->_iter = task->next; 163 | 164 | if (NULL != task->long_name) { 165 | info->name = task->long_name; 166 | } else { 167 | info->name = task->short_name; 168 | } 169 | 170 | info->average_time = task->average_time; 171 | info->max_time = task->max_time; 172 | success = true; 173 | } 174 | 175 | return success; 176 | } 177 | 178 | 179 | // ---------------- Scheduler functions 180 | 181 | static inline uint32_t 182 | rot_left_1(uint32_t a) 183 | { 184 | return (a << 1) | (a >> ((sizeof(a) * 8) - 1)); 185 | } 186 | 187 | 188 | static void 189 | execute_idle_tasks(struct sched_ctx *ctx) 190 | { 191 | struct sched_task * task; 192 | for (task = ctx->root; NULL != task; task = task->next) { 193 | if (0 == task->tick_mask) { 194 | uint32_t start = ctx->get_time(ctx->hint); 195 | 196 | task->execute(task->hint); 197 | 198 | uint32_t stop = ctx->get_time(ctx->hint); 199 | update_task_stats(task, tm_get_diff(&ctx->tm, stop, start)); 200 | } 201 | } 202 | } 203 | 204 | 205 | static void 206 | execute_current_tick(struct sched_ctx *ctx) 207 | { 208 | struct sched_task * task; 209 | for (task = ctx->root; NULL != task; task = task->next) { 210 | if(0 != (ctx->current_tick & task->tick_mask)) { 211 | uint32_t start = ctx->get_time(ctx->hint); 212 | 213 | task->execute(task->hint); 214 | 215 | uint32_t stop = ctx->get_time(ctx->hint); 216 | update_task_stats(task, tm_get_diff(&ctx->tm, stop, start)); 217 | } 218 | } 219 | } 220 | 221 | 222 | 223 | // ----------------------------------------------------------- Public functions 224 | 225 | // ---------------- Scheduler functions 226 | 227 | struct sched_ctx * 228 | sched_alloc_context(void * hint, 229 | sched_get_time_fn get_time_fn, 230 | uint32_t max_time, 231 | uint32_t tick_period) 232 | { 233 | if ((max_time < 4) || (tick_period < 1) || (tick_period >= (max_time >> 1))) { 234 | return NULL; 235 | } 236 | 237 | struct sched_ctx * ctx = (struct sched_ctx *) malloc(sizeof(struct sched_ctx)); 238 | 239 | if (NULL != ctx) { 240 | ctx->current_tick = 0; 241 | ctx->last_tick_time = 0; 242 | ctx->tick_period = tick_period; 243 | tm_initialize(&ctx->tm, max_time); 244 | ctx->root = NULL; 245 | ctx->get_time = get_time_fn; 246 | ctx->hint = hint; 247 | } 248 | 249 | return ctx; 250 | } 251 | 252 | 253 | void 254 | sched_free_context(struct sched_ctx * ctx) 255 | { 256 | if (NULL != ctx) { 257 | while(NULL != ctx->root) { 258 | unlink_task(ctx->root); 259 | } 260 | 261 | free(ctx); 262 | } 263 | } 264 | 265 | 266 | void 267 | sched_run(struct sched_ctx * ctx) 268 | { 269 | bool execute_tick = false; 270 | 271 | uint32_t now = ctx->get_time(ctx->hint); 272 | 273 | if (0 == ctx->current_tick) { 274 | ctx->current_tick = 0x00000001; 275 | ctx->last_tick_time = now; 276 | execute_tick = true; 277 | } else { 278 | int32_t delta = tm_get_diff(&ctx->tm, now, ctx->last_tick_time); 279 | if (delta < 0) { 280 | ctx->last_tick_time = now; 281 | execute_tick = true; 282 | } else if (((uint32_t) delta) >= ctx->tick_period) { 283 | ctx->last_tick_time = 284 | tm_offset(&ctx->tm, ctx->last_tick_time, ctx->tick_period); 285 | execute_tick = true; 286 | } 287 | } 288 | 289 | if (execute_tick) { 290 | execute_current_tick(ctx); 291 | ctx->current_tick = rot_left_1(ctx->current_tick); 292 | } else { 293 | execute_idle_tasks(ctx); 294 | } 295 | } 296 | 297 | 298 | void 299 | sched_reset(struct sched_ctx * ctx) 300 | { 301 | if (NULL != ctx) { 302 | ctx->current_tick = 0; 303 | } 304 | } 305 | 306 | 307 | // --------------------- Task functions 308 | 309 | struct sched_task * 310 | sched_alloc_task(struct sched_ctx * ctx, 311 | void * hint, 312 | sched_task_fn task_fn, 313 | const char * name, 314 | uint32_t tick_mask) 315 | { 316 | struct sched_task * task = NULL; 317 | if (NULL == ctx) { 318 | goto out; 319 | } 320 | 321 | task = (struct sched_task *) malloc(sizeof(struct sched_task)); 322 | if (NULL == task) { 323 | goto out; 324 | } 325 | 326 | task->ctx = NULL; 327 | task->next = NULL; 328 | 329 | task->tick_mask = tick_mask; 330 | task->execute = task_fn; 331 | task->hint = hint; 332 | 333 | task->average_time = 0; 334 | task->max_time = 0; 335 | 336 | task->long_name = NULL; 337 | task->short_name[0] = '\0'; 338 | 339 | if (NULL != name) { 340 | if (strlen(name) < SHORT_NAME_LENGTH) { 341 | strcpy(task->short_name, name); 342 | } else { 343 | task->long_name = strdup(name); 344 | if(NULL == task->long_name) { 345 | goto out_name_fail; 346 | } 347 | } 348 | } 349 | 350 | link_task(ctx, task); 351 | goto out; 352 | 353 | out_name_fail: 354 | free(task); 355 | task = NULL; 356 | 357 | out: 358 | return task; 359 | } 360 | 361 | 362 | void 363 | sched_free_task(struct sched_task * task) 364 | { 365 | if (NULL != task) { 366 | unlink_task(task); 367 | 368 | if (NULL != task->long_name) { 369 | free(task->long_name); 370 | } 371 | 372 | free(task); 373 | } 374 | } 375 | 376 | // ------------------------------------------------------------- Task debugging 377 | 378 | bool 379 | sched_get_first_task_info(struct sched_ctx * ctx, 380 | struct sched_task_info * info) 381 | { 382 | bool success = false; 383 | 384 | if (NULL != ctx) { 385 | success = get_task_info(ctx->root, info); 386 | } 387 | 388 | return success; 389 | } 390 | 391 | 392 | bool 393 | sched_get_next_task_info(struct sched_task_info * info) 394 | { 395 | bool success = false; 396 | 397 | if (NULL != info) { 398 | struct sched_task * next = (struct sched_task *) info->_iter; 399 | success = get_task_info(next, info); 400 | } 401 | 402 | return success; 403 | } 404 | 405 | 406 | void 407 | sched_reset_stats(struct sched_ctx * ctx) 408 | { 409 | if (NULL != ctx) { 410 | struct sched_task * task; 411 | for (task = ctx->root; NULL != task; task = task->next) { 412 | task->average_time = 0; 413 | task->max_time = 0; 414 | } 415 | } 416 | } 417 | --------------------------------------------------------------------------------