blob: aa264b1d95f76a8bbce1696739d26b9338d6cf19 [file] [log] [blame]
Jerome Forissierc14cfee2025-04-18 16:09:34 +02001// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (C) 2021 Ahmad Fatoum, Pengutronix
4 * Copyright (C) 2025 Linaro Limited
5 *
6 * An implementation of cooperative multi-tasking inspired from barebox threads
7 * https://github.com/barebox/barebox/blob/master/common/bthread.c
8 */
9
10#include <compiler.h>
11#include <linux/kernel.h>
12#include <linux/list.h>
13#include <malloc.h>
14#include <setjmp.h>
15#include <stdint.h>
16#include <uthread.h>
17
18static struct uthread main_thread = {
19 .list = LIST_HEAD_INIT(main_thread.list),
20};
21
22static struct uthread *current = &main_thread;
23
24/**
25 * uthread_trampoline() - Call the current thread's entry point then resume the
26 * main thread.
27 *
28 * This is a helper function which is used as the @func argument to the
29 * initjmp() function, and ultimately invoked via setjmp(). It does not return
30 * but instead longjmp()'s back to the main thread.
31 */
32static void __noreturn uthread_trampoline(void)
33{
34 struct uthread *curr = current;
35
36 curr->fn(curr->arg);
37 curr->done = true;
38 current = &main_thread;
39 longjmp(current->ctx, 1);
40 /* Not reached */
41 while (true)
42 ;
43}
44
45/**
46 * uthread_free() - Free memory used by a uthread object.
47 */
48static void uthread_free(struct uthread *uthread)
49{
50 if (!uthread)
51 return;
52 free(uthread->stack);
53 free(uthread);
54}
55
56int uthread_create(struct uthread *uthr, void (*fn)(void *), void *arg,
57 size_t stack_sz, unsigned int grp_id)
58{
59 bool user_allocated = false;
60
61 if (!stack_sz)
62 stack_sz = CONFIG_UTHREAD_STACK_SIZE;
63
64 if (uthr) {
65 user_allocated = true;
66 } else {
67 uthr = calloc(1, sizeof(*uthr));
68 if (!uthr)
69 return -1;
70 }
71
72 uthr->stack = memalign(16, stack_sz);
73 if (!uthr->stack)
74 goto err;
75
76 uthr->fn = fn;
77 uthr->arg = arg;
78 uthr->grp_id = grp_id;
79
80 list_add_tail(&uthr->list, &current->list);
81
82 initjmp(uthr->ctx, uthread_trampoline, uthr->stack, stack_sz);
83
84 return 0;
85err:
86 if (!user_allocated)
87 free(uthr);
88 return -1;
89}
90
91/**
92 * uthread_resume() - switch execution to a given thread
93 *
94 * @uthread: the thread object that should be resumed
95 */
96static void uthread_resume(struct uthread *uthread)
97{
98 if (!setjmp(current->ctx)) {
99 current = uthread;
100 longjmp(uthread->ctx, 1);
101 }
102}
103
104bool uthread_schedule(void)
105{
106 struct uthread *next;
107 struct uthread *tmp;
108
109 list_for_each_entry_safe(next, tmp, &current->list, list) {
110 if (!next->done) {
111 uthread_resume(next);
112 return true;
113 }
114 /* Found a 'done' thread, free its resources */
115 list_del(&next->list);
116 uthread_free(next);
117 }
118 return false;
119}
120
121unsigned int uthread_grp_new_id(void)
122{
123 static unsigned int id;
124
125 return ++id;
126}
127
128bool uthread_grp_done(unsigned int grp_id)
129{
130 struct uthread *next;
131
132 list_for_each_entry(next, &main_thread.list, list) {
133 if (next->grp_id == grp_id && !next->done)
134 return false;
135 }
136
137 return true;
138}