diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-09-18 00:04:10 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-09-18 00:04:10 -0600 |
commit | 690a9d955e65608b96fad34a2b31e639223798aa (patch) | |
tree | 60bf87a326b6433297ee86e8d039199e4d4becdb | |
parent | 4412b908755c7b9899cf8cce4e0985aa6816121a (diff) |
wip
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | 9psrv.c | 11 | ||||
-rw-r--r-- | cfg_limits.h | 13 | ||||
-rw-r--r-- | coroutine.c | 211 | ||||
-rw-r--r-- | coroutine.h | 77 | ||||
-rw-r--r-- | net9p.c | 53 | ||||
-rw-r--r-- | net9p.h | 8 |
7 files changed, 237 insertions, 137 deletions
@@ -4,3 +4,4 @@ /tusb_helpers.h *.o *.log +/9psrv @@ -1,13 +1,14 @@ +#include <error.h> +#include <stdio.h> #include "coroutine.h" #include "net9p.h" int main() { coroutine_init(); - net9p_listen_stack_t stack = {0}; - coroutine_add(net9p_listen_cr, &stack); + if (!coroutine_add(net9p_listen_cr, NULL)) + error(1, 0, "coroutine_add(net9p_listen_cr, NULL)"); - for (;;) { - coroutine_task(); - } + coroutine_main(); + return 1; } diff --git a/cfg_limits.h b/cfg_limits.h new file mode 100644 index 0000000..f6ebaf0 --- /dev/null +++ b/cfg_limits.h @@ -0,0 +1,13 @@ +/* cfg_limits.h - Compile-time hard-coded limits + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#ifndef _CFG_LIMITS_H_ +#define _CFG_LIMITS_H_ + +#define COROUTINE_NUM 5 +#define COROUTINE_STACK_SIZE 0x100000 /* 1MiB */ + +#endif /* _CFG_LIMITS_H_ */ diff --git a/coroutine.c b/coroutine.c index 3062ad9..65971d4 100644 --- a/coroutine.c +++ b/coroutine.c @@ -4,79 +4,164 @@ * SPDX-Licence-Identifier: AGPL-3.0-or-later */ -#include <stdio.h> -#include <stdlib.h> /* for malloc(pico_malloc) and realloc(pico_malloc) */ +#include <stdint.h> /* for uint8_t */ +#include <string.h> /* for memset() */ +#include <stdio.h> /* for fprintf(), stderr */ +#include <assert.h> +#include <setjmp.h> +#include "cfg_limits.h" #include "coroutine.h" -static cid_t coroutine_table_len = 0; -_cr_entry_t *_coroutine_table = NULL; -cid_t _cur_cid = 0; +enum coroutine_state { + CR_ZERO = 0, + CR_GARBAGE, + CR_INITIALIZING, + CR_RUNNING, + CR_RUNNABLE, + CR_PAUSED, +}; -void coroutine_init(void) { - printf("coroutine_table_len = %u\n", (int)coroutine_table_len); - if (coroutine_table_len) { - printf("return\n"); - return; +struct coroutine { + enum coroutine_state state; + jmp_buf env; + uint8_t stack[COROUTINE_STACK_SIZE]; +}; + +static struct coroutine coroutine_table[COROUTINE_NUM] = {0}; +static cid_t coroutine_running = 0; +static jmp_buf scheduler_env; + +void coroutine_init(void) {} + +void *__sp; +#define SP ({ asm("movq %%rsp,%0" : "=r"(__sp)); __sp; }) + +static void print_addr(char *name, void *addr) { + if ((void*)&coroutine_table <= addr && addr < ((void*)&coroutine_table) + sizeof(coroutine_table)) { + for (size_t i = 0; i < COROUTINE_NUM; i++) { + if ((void*)&coroutine_table[i] <= addr && addr < ((void*)&coroutine_table[i]) + sizeof(coroutine_table[i])) { + printf("%s=%p (in coroutine_table[%zu] [%p, %p])\n", name, addr, i, + (void*)&coroutine_table[i], ((void*)&coroutine_table[i]) + sizeof(coroutine_table[i])); + break; + } + } + } else { + printf("%s=%p (outside coroutine_table [%p, %p])\n", name, addr, + (void*)&coroutine_table, ((void*)&coroutine_table) + sizeof(coroutine_table)); } - coroutine_table_len = 1; - printf("coroutine_table_len = %u\n", (int)coroutine_table_len); - _coroutine_table = malloc(sizeof _coroutine_table[0]); - _coroutine_table[0] = (_cr_entry_t){ - .fn = (cr_fn_t)0xDEAD, - .stack = NULL, - .state = 0, - .blocked = true, - }; } -cid_t coroutine_add(cr_fn_t fn, void *stack) { - cid_t cid = 0; - for (cid_t i = 1; cid == 0 && i < coroutine_table_len; i++) - if (_coroutine_table[i].fn == NULL) - cid = i; - if (cid == 0) { - cid = coroutine_table_len++; - _coroutine_table = realloc(_coroutine_table, (sizeof _coroutine_table[0]) * coroutine_table_len); +static void call_with_stack(void *stack, cr_fn_t fn, void *args) { + static void *scheduler_sp = NULL; + + /* I don't intend to run nThis only really exists for running on ARM-32, but being + * able to run it on x86-64 is useful for debugging. */ +#if __x86_64__ +#define STACK_GROWS_DOWNWARD 1 + print_addr("sp", SP); + asm volatile ("movq %%rsp , %0\n\t" /* scheduler_sp = sp */ + "movq %1 , %%rsp\n\t" /* sp = stack */ + "movq %3 , %%rdi\n\t" /* arg0 = args */ + "call *%2\n\t" /* fn() */ + "movq %0 , %%rsp" /* sp = scheduler_sp */ + : + : /* %0 */"m"(scheduler_sp), + /* %1 */"r"(stack), + /* %2 */"r"(fn), + /* %3 */"r"(args) + : + ); +#elif __arm__ +#define STACK_GROWS_DOWNWARD 1 + asm volatile ("mov r0, coroutine_table[_cur_cid].arg" + "mov _saved_stack sp" + "mov sp, coroutine_table[_cur_cid].stack" + "bl coroutine_table[_cur_cid].fn" + "mov _saved_stack sp"); +#else +#error unsupported architecture +#endif +} + +cid_t coroutine_add(cr_fn_t fn, void *args) { + static cid_t last_created = 0; + + cid_t child; + { + size_t idx_base = last_created; + for (size_t idx_shift = 0; idx_shift < COROUTINE_NUM; idx_shift++) { + child = ((idx_base + idx_shift) % COROUTINE_NUM) + 1; + if (coroutine_table[child-1].state == CR_ZERO || coroutine_table[child-1].state == CR_GARBAGE) + goto found; + } + return 0; + found: + } + + if (coroutine_table[child-1].state == CR_GARBAGE) + memset(&coroutine_table[child-1], 0, sizeof(struct coroutine)); + last_created = child; + + cid_t parent = coroutine_running; + assert(parent == 0 || coroutine_table[parent-1].state == CR_RUNNING); + coroutine_running = child; + coroutine_table[child-1].state = CR_INITIALIZING; + if (child == 2) { + print_addr("&child", &child); } - _coroutine_table[cid] = (_cr_entry_t){ - .fn = fn, - .stack = stack, - .state = 0, - .blocked = false, - }; - printf("added coroutine %u\n", (int)cid); - return cid; + if (!setjmp(scheduler_env)) { + call_with_stack(coroutine_table[child-1].stack + (STACK_GROWS_DOWNWARD ? COROUTINE_STACK_SIZE : 0), fn, args); + assert(coroutine_table[child-1].state == CR_INITIALIZING); + coroutine_table[child-1].state = CR_RUNNABLE; + } + assert(coroutine_table[child-1].state == CR_RUNNABLE); + coroutine_running = parent; + + return child; } -void coroutine_task(void) { - cid_t start = (_cur_cid + 1) % coroutine_table_len; - cid_t shift; - for (shift = 0; - shift < coroutine_table_len && - (_coroutine_table[(start+shift)%coroutine_table_len].fn == NULL || - _coroutine_table[(start+shift)%coroutine_table_len].blocked); - shift++) {} - if (shift == coroutine_table_len) { - printf("idle\n"); - return; +void coroutine_main(void) { + bool ran; + for (coroutine_running = 1;; coroutine_running = (coroutine_running%COROUTINE_NUM)+1) { + if (coroutine_running == 1) + ran = false; + if (coroutine_table[coroutine_running-1].state == CR_RUNNABLE) { + ran = true; + coroutine_table[coroutine_running-1].state = CR_RUNNING; + if (!setjmp(scheduler_env)) { + longjmp(coroutine_table[coroutine_running-1].env, 1); + coroutine_table[coroutine_running-1].state = CR_GARBAGE; + } + } + if (coroutine_running == COROUTINE_NUM+1 && !ran) { + fprintf(stderr, "error: no runnable coroutines\n"); + return; + } } - _cur_cid = (start + shift) % coroutine_table_len; - printf("running %u\n", (int)_cur_cid); - if (_cur_cid == 0) - exit(1); - _coroutine_table[_cur_cid].fn(_coroutine_table[_cur_cid].stack); } -// arm32 -mov r0, coroutine_table[_cur_cid].arg -mov _saved_stack sp -mov sp, coroutine_table[_cur_cid].stack -bl coroutine_table[_cur_cid].fn -mov _saved_stack sp -// x86-64 -movl coroutine_table[_cur_cid].arg %edi -movq %rsp _saved_stack -movq coroutine_table[_cur_cid].stack %rsp -call coroutine_table[_cur_cid].fn -movq _saved_stack %rsp +bool _cr_begin(void) { + assert(coroutine_table[coroutine_running-1].state == CR_INITIALIZING); + return setjmp(coroutine_table[coroutine_running-1].env); +} + +static inline void _cr_transition(enum coroutine_state state) { + assert(coroutine_table[coroutine_running-1].state == CR_RUNNING); + coroutine_table[coroutine_running-1].state = state; + if (state == CR_GARBAGE || !setjmp(coroutine_table[coroutine_running-1].env)) + longjmp(scheduler_env, 1); +} + +void cr_exit(void) { _cr_transition(CR_GARBAGE); } +void cr_yield(void) { _cr_transition(CR_RUNNABLE); } +void cr_pause_and_yield(void) { _cr_transition(CR_PAUSED); } + +void cr_unpause(cid_t cid) { + assert(coroutine_table[cid-1].state == CR_PAUSED); + coroutine_table[cid-1].state = CR_RUNNABLE; +} + +cid_t cr_getcid(void) { + return coroutine_running; +} diff --git a/coroutine.h b/coroutine.h index d22eb2e..ae52f22 100644 --- a/coroutine.h +++ b/coroutine.h @@ -8,65 +8,60 @@ #define _COROUTINE_H_ #include <stddef.h> /* for size_t */ -#include <stdbool.h> /* for bool, true, false */ +#include <stdbool.h> /* for bool */ /* typedefs *******************************************************************/ + typedef size_t cid_t; /* 0=none; otherwise 1-indexed */ -typedef void (*cr_fn_t)(void *stack); -typedef struct { - cr_fn_t fn; - void *stack; - unsigned int state; - bool blocked; -} _cr_entry_t; +typedef void (*cr_fn_t)(void *args); -/* globals ********************************************************************/ -extern _cr_entry_t *_coroutine_table; -extern cid_t _cur_cid; +/* managing coroutines ********************************************************/ -/* bona-fide functions ********************************************************/ void coroutine_init(void); -cid_t coroutine_add(cr_fn_t fn, void *stack); -void coroutine_task(void); +cid_t coroutine_add(cr_fn_t fn, void *args); +void coroutine_main(void); + +/* inside of coroutines *******************************************************/ -/* core macros for use in a cr_fn_t() *****************************************/ -#define cr_begin() switch(_coroutine_table[_cur_cid].state) { case 0: -#define cr_exit() do { _coroutine_table[_cur_cid] = (cr_entry_t){0}; return; } while (0) -#define cr_yield() _cr_yield(__COUNTER__+1) -#define _cr_yield(_state) do { _coroutine_table[_cur_cid].state = _state; return; case _state:; } while (0) -#define cr_end() } +bool _cr_begin(void); +#define cr_begin() do { if (!_cr_begin()) return; } while (0) +void cr_exit(void); +void cr_yield(void); +void cr_pause_and_yield(void); +void cr_unpause(cid_t); +#define cr_end() + +cid_t cr_getcid(void); /* request/response channels **************************************************/ + #define cr_chan_t(req_t, resp_t) struct { \ cid_t requester; \ cid_t responder; \ req_t req; \ resp_t resp; \ } -#define cr_chan_req(ch, _resp_p, _req) do { \ - (ch)->requester = _cur_cid; \ - (ch)->req = (_req); \ - if ((ch)->responder != 0) \ - _coroutine_table[(ch)->responder].blocked = false; \ - _coroutine_table[_cur_cid].blocked = true; \ - cr_yield(); \ - if ((typeof(&(ch)->resp))(_resp_p)) \ - *((typeof(&(ch)->resp))(_resp_p)) = (ch)->resp; \ +#define cr_chan_req(ch, _resp_p, _req) do { \ + (ch)->requester = cr_getcid(); \ + (ch)->req = (_req); \ + if ((ch)->responder != 0) \ + cr_unpause((ch)->responder); \ + cr_pause_and_yield(); \ + if ((typeof(&(ch)->resp))(_resp_p)) \ + *((typeof(&(ch)->resp))(_resp_p)) = (ch)->resp; \ } while (0) #define cr_chan_have_req(ch) ((ch)->requester != 0) -#define cr_chan_recv_req(ch, _req_p) do { \ - (ch)->responder = _cur_cid; \ - if ((ch)->requester == 0) { \ - _coroutine_table[_cur_cid].blocked = true; \ - cr_yield(); \ - } \ - *(_req_p) = (ch)->req; \ +#define cr_chan_recv_req(ch, _req_p) do { \ + (ch)->responder = cr_getcid(); \ + if ((ch)->requester == 0) { \ + cr_pause_and_yield(); \ + *(_req_p) = (ch)->req; \ } while (0) -#define cr_chan_send_resp(ch, _resp) do { \ - (ch)->responder = 0; \ - (ch)->requester = 0; \ - (ch)->resp = (_resp); \ - _coroutine_table[(ch)->requester].blocked = false; \ +#define cr_chan_send_resp(ch, _resp) do { \ + cr_unpause((ch)->requester); \ + (ch)->responder = 0; \ + (ch)->requester = 0; \ + (ch)->resp = (_resp); \ } while (0) #endif /* _COROUTINE_H_ */ @@ -1,3 +1,7 @@ +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <error.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> @@ -7,36 +11,45 @@ #include "net9p.h" #include "coroutine.h" -void net9p_listen_cr(void *_stack) { - static union { +void net9p_listen_cr(void *_arg) { + (void)_arg; + printf("listen initializng...\n"); + cr_begin(); + printf("listen running...\n"); + + union { struct sockaddr_in in; struct sockaddr gen; } addr = {0}; - if (!addr.in.sin_family) { - addr.in.sin_family = AF_INET; - addr.in.sin_port = 0x2823; - } - net9p_listen_stack_t *stack = _stack; - - cr_begin(); + addr.in.sin_family = AF_INET; + addr.in.sin_port = htons(9001); - stack->fd = socket(AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0); - bind(stack->fd, &addr.gen, sizeof addr); - listen(stack->fd, 5); + int fd = socket(AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0); + if (fd < 0) + error(1, errno, "socket"); + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0) + error(1, errno, "setsockopt"); + if (bind(fd, &addr.gen, sizeof addr) < 0) + error(1, errno, "bind"); + if (listen(fd, 5) < 0) + error(1, errno, "listen"); + int conn = 9; + if (!coroutine_add(net9p_worker_cr, &conn)) + error(1, 0, "coroutine_add(net9p_worker_cr, &%d)", conn); for (;;) { - int conn = accept(stack->fd, NULL, NULL); - if (conn >= 0) { - net9p_worker_stack_t *connstack = malloc(sizeof(net9p_worker_stack_t)); - connstack->fd = conn; - coroutine_add(net9p_worker_cr, connstack); - } cr_yield(); } cr_end(); } -void net9p_worker_cr(void *_stack) { - net9p_worker_stack_t *stack = _stack; +void net9p_worker_cr(void *_arg) { + int fd = *((int *)_arg); + cr_begin(); + + printf("worker %zu\n", cr_getcid()); + close(fd); + + cr_end(); } @@ -1,14 +1,6 @@ #ifndef _NET9P_H_ #define _NET9P_H_ -typedef struct { - int fd; -} net9p_listen_stack_t; - -typedef struct { - int fd; -} net9p_worker_stack_t; - void net9p_listen_cr(void *); void net9p_worker_cr(void *); |