1 // 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/errno.h>
12 #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 
19 static struct uthread main_thread = {
20 	.list = LIST_HEAD_INIT(main_thread.list),
21 };
22 
23 static 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  */
uthread_trampoline(void)33 static 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  */
uthread_free(struct uthread * uthread)49 static void uthread_free(struct uthread *uthread)
50 {
51 	if (!uthread)
52 		return;
53 	free(uthread->stack);
54 	free(uthread);
55 }
56 
uthread_create(struct uthread * uthr,void (* fn)(void *),void * arg,size_t stack_sz,unsigned int grp_id)57 int 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;
86 err:
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  */
uthread_resume(struct uthread * uthread)97 static void uthread_resume(struct uthread *uthread)
98 {
99 	if (!setjmp(current->ctx)) {
100 		current = uthread;
101 		longjmp(uthread->ctx, 1);
102 	}
103 }
104 
uthread_schedule(void)105 bool 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 
uthread_grp_new_id(void)122 unsigned int uthread_grp_new_id(void)
123 {
124 	static unsigned int id;
125 
126 	return ++id;
127 }
128 
uthread_grp_done(unsigned int grp_id)129 bool 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 }
140 
uthread_mutex_lock(struct uthread_mutex * mutex)141 int 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 
uthread_mutex_trylock(struct uthread_mutex * mutex)150 int 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 
uthread_mutex_unlock(struct uthread_mutex * mutex)160 int uthread_mutex_unlock(struct uthread_mutex *mutex)
161 {
162 	mutex->state = UTHREAD_MUTEX_UNLOCKED;
163 
164 	return 0;
165 }
166