/* * Copyright (c) 2026, Alessandro Mauri . * All rights reserved. * * This file is a heavily modified, single-header derivative of the * protothreads library. * * ============================================================================= * Original Protothreads License: * ============================================================================= * Copyright (c) 2004-2005, Swedish Institute of Computer Science. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Author: Adam Dunkels */ #ifndef _COROUTINE_H #define _COROUTINE_H /* This is an implementation of stackless coroutines that uses the labels as * values [1] feature of GNU C, similar to protothreads [2] with the lc-addrlabels.h * include, the only difference being better ergonomics and minimal type checking. * * Since coroutines implemented this way need to return to wait for a condition * stack-local variables are NOT preserved between wait()s, as such these functions * have to be considered stackless. Any state that has to be preserved between * waits have to be declared static or have to be preserved outside the routine. * * This library's intended use is in microcontrollers and memory-constrained * systems where having a full rtos and/or having stackful coroutines is either * too heavy or overkill. Stackless coroutines implemented this way are an * alternative way of expressing state machines. Since coroutines have to be * repeatedly called to be "scheduled", the code maintains a main loop, but state * changes can be expressed with waits on conditions, timers, semaphores and other * more expressive constructs. * * A simple example program is: * * #include "coroutine.h" * * // Updates the timer when called, returns true when it expires * static int timer_expired(struct timer *t); * // Sets a timer in milliseconds * static void timer_set(struct timer *t, int interval); * * // First coroutine, waits for 1s and prints something * coroutine coro_1(coro_state_t *s) * { * static struct timer t; * coro_begin(s); * for (;;) { * timer_set(&t, 1000); * coro_wait_until(s, timer_expired(&t)); * printf("coroutine 1\n"); * } * coro_end(); * } * * // Second coroutine, waits for 500ms and prints something * coroutine coro_2(coro_state_t *s) * { * static struct timer t; * coro_begin(s); * for (;;) { * timer_set(&t, 500); * coro_wait_until(s, timer_expired(&t)); * printf("coroutine 2\n"); * } * coro_end(); * } * * int main(void) * { * coro_state_t s1, s2; * coro_init(&s1); * coro_init(&s2); * * // Main loop, calls each coroutine periodically * for (;;) { * coro_1(&s1); * coro_2(&s2); * usleep(100); * } * } * * [1]: https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Labels-as-Values.html * [2]: https://dunkels.com/adam/pt/index.html */ // Useful macros #define CORO_CONCAT2(s1, s2) s1##s2 #define CORO_CONCAT(s1, s2) CORO_CONCAT2(s1, s2) #define CORO_CHECK_TYPE(var,type) { typedef void (*type_t)(type); type_t tmp = (type_t)0; if(0) tmp(var); } // Return value of a coroutine, a coroutine should be scheduled (called) until // it's return value becomes DONE typedef enum { CORO_INIT = 0, CORO_CONT = CORO_INIT, CORO_DONE = 1, CORO_YIELDED = 2, CORO_WAITING = 3, } coroutine; // Local continuation of the coroutine, where it left off typedef void* coro_state_t; /* -------------------------- COROUTINE MANAGEMENT -------------------------- */ // Initialize the coroutine state #define coro_init(s) CORO_CHECK_TYPE(s, coro_state_t*) \ do { *s = NULL; } while (0) // Jump to the previous state where the coroutine left off, __yield flag indicates // wether the coroutine should yield this cycle #define coro_begin(s) CORO_CHECK_TYPE(s, coro_state_t*) \ unsigned char __yield = 0; \ do { if(*s != NULL) goto *(*s); } while (0) #define coro_end() // Set a jump with a gcc label as value feature, this is a GNU C extension #define CORO_SET_JUMP(s) CORO_CHECK_TYPE(s, coro_state_t*) \ do { \ CORO_CONCAT(LC_LABEL, __LINE__): \ *s = &&CORO_CONCAT(LC_LABEL, __LINE__); \ } while (0) // Yield from a coroutine #define coro_yield(s) CORO_CHECK_TYPE(s, coro_state_t*) \ do { \ __yield = 1; \ CORO_SET_JUMP(s); \ if (__yield == 1) return CORO_YIELDED; \ } while (0) // Wait until a condition is met (becomes true) #define coro_wait_until(s, cond) CORO_CHECK_TYPE(s, coro_state_t*) \ do { \ CORO_SET_JUMP(s); \ if (!(cond)) return CORO_WAITING; \ } while (0) // Shorthand for wait_until #define coro_wait(s, cond) coro_wait_until(s, cond) // Wait while a condition remains true #define coro_wait_while(s, cond) coro_wait_until(s, !(cond)) // Exit from the coroutine, resets the state #define coro_exit(s) CORO_CHECK_TYPE(s, coro_state_t*) \ do { \ coro_init(s); \ return CORO_DONE; \ } while (0) // Restart a coroutine, when called it will not resume from where it left off, // instead it will start from the beginning, the only difference with exit is // the return value. To determine wether a coroutine has exited or restarted // is to check it's return value #define coro_restart(s) CORO_CHECK_TYPE(s, coro_state_t*) \ do { \ coro_init(s); \ return CORO_INIT; \ } while(0) /* ------------------------- SEMAPHORE IMPLEMENTATION ----------------------- */ // Implements a basic semaphore type with the classic wait and signal operations // semaphores are not bound checked typedef int coro_semaphore_t; // Initializes a semaphore with a starting value of "count" #define coro_sem_init(sem, count) CORO_CHECK_TYPE(sem, coro_semaphore_t*) \ do { \ *sem = (int)count; \ } while (0) // Wait until a semaphore has a value other than zero #define coro_sem_wait(state, sem) \ CORO_CHECK_TYPE(state, coro_state_t*) \ CORO_CHECK_TYPE(sem, coro_semaphore_t*) \ do { \ coro_wait_until(state, *(sem) > 0); \ --(*sem); \ } while (0) // Signal a semaphore #define coro_sem_signal(state, sem) \ CORO_CHECK_TYPE(state, coro_state_t*) \ CORO_CHECK_TYPE(sem, coro_semaphore_t*) \ ++(*sem) #endif