blob: 062fca7d209137a68661ab8f5f2f32b70c7e6870 [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>
Jerome Forissier16637842025-04-18 16:09:35 +020011#include <linux/errno.h>
Jerome Forissierc14cfee2025-04-18 16:09:34 +020012#include <linux/kernel.h>
13#include <linux/list.h>
14#include <malloc.h>
15#include <setjmp.h>
16#include <stdint.h>
17#include <uthread.h>
18
19static struct uthread main_thread = {
20 .list = LIST_HEAD_INIT(main_thread.list),
21};
22
23static struct uthread *current = &main_thread;
24
25/**
26 * uthread_trampoline() - Call the current thread's entry point then resume the
27 * main thread.
28 *
29 * This is a helper function which is used as the @func argument to the
30 * initjmp() function, and ultimately invoked via setjmp(). It does not return
31 * but instead longjmp()'s back to the main thread.
32 */
33static void __noreturn uthread_trampoline(void)
34{
35 struct uthread *curr = current;
36
37 curr->fn(curr->arg);
38 curr->done = true;
39 current = &main_thread;
40 longjmp(current->ctx, 1);
41 /* Not reached */
42 while (true)
43 ;
44}
45
46/**
47 * uthread_free() - Free memory used by a uthread object.
48 */
49static void uthread_free(struct uthread *uthread)
50{
51 if (!uthread)
52 return;
53 free(uthread->stack);
54 free(uthread);
55}
56
57int uthread_create(struct uthread *uthr, void (*fn)(void *), void *arg,
58 size_t stack_sz, unsigned int grp_id)
59{
60 bool user_allocated = false;
61
62 if (!stack_sz)
63 stack_sz = CONFIG_UTHREAD_STACK_SIZE;
64
65 if (uthr) {
66 user_allocated = true;
67 } else {
68 uthr = calloc(1, sizeof(*uthr));
69 if (!uthr)
70 return -1;
71 }
72
73 uthr->stack = memalign(16, stack_sz);
74 if (!uthr->stack)
75 goto err;
76
77 uthr->fn = fn;
78 uthr->arg = arg;
79 uthr->grp_id = grp_id;
80
81 list_add_tail(&uthr->list, &current->list);
82
83 initjmp(uthr->ctx, uthread_trampoline, uthr->stack, stack_sz);
84
85 return 0;
86err:
87 if (!user_allocated)
88 free(uthr);
89 return -1;
90}
91
92/**
93 * uthread_resume() - switch execution to a given thread
94 *
95 * @uthread: the thread object that should be resumed
96 */
97static void uthread_resume(struct uthread *uthread)
98{
99 if (!setjmp(current->ctx)) {
100 current = uthread;
101 longjmp(uthread->ctx, 1);
102 }
103}
104
105bool uthread_schedule(void)
106{
107 struct uthread *next;
108 struct uthread *tmp;
109
110 list_for_each_entry_safe(next, tmp, &current->list, list) {
111 if (!next->done) {
112 uthread_resume(next);
113 return true;
114 }
115 /* Found a 'done' thread, free its resources */
116 list_del(&next->list);
117 uthread_free(next);
118 }
119 return false;
120}
121
122unsigned int uthread_grp_new_id(void)
123{
124 static unsigned int id;
125
126 return ++id;
127}
128
129bool uthread_grp_done(unsigned int grp_id)
130{
131 struct uthread *next;
132
133 list_for_each_entry(next, &main_thread.list, list) {
134 if (next->grp_id == grp_id && !next->done)
135 return false;
136 }
137
138 return true;
139}
Jerome Forissier16637842025-04-18 16:09:35 +0200140
141int uthread_mutex_lock(struct uthread_mutex *mutex)
142{
143 while (mutex->state == UTHREAD_MUTEX_LOCKED)
144 uthread_schedule();
145
146 mutex->state = UTHREAD_MUTEX_LOCKED;
147 return 0;
148}
149
150int uthread_mutex_trylock(struct uthread_mutex *mutex)
151{
152 if (mutex->state == UTHREAD_MUTEX_UNLOCKED) {
153 mutex->state = UTHREAD_MUTEX_LOCKED;
154 return 0;
155 }
156
157 return -EBUSY;
158}
159
160int uthread_mutex_unlock(struct uthread_mutex *mutex)
161{
162 mutex->state = UTHREAD_MUTEX_UNLOCKED;
163
164 return 0;
165}