2 * Copyright (C) 2024 Mikulas Patocka
4 * This file is part of Ajla.
6 * Ajla is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
11 * Ajla is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along with
16 * Ajla. If not, see <https://www.gnu.org/licenses/>.
29 struct list wait_entry
;
33 uintptr_t tls
[OS2_THREAD_KEY_MAX
];
35 void (*function
)(void *);
37 thread_priority_t priority
;
40 static struct os2_thread thread_1
;
43 static os2_tls_key_t os2_tls_used
;
46 mutex_t thread_spawn_mutex
;
48 #ifdef HAVE__THREADSTORE
50 static void os2_tcb_set(struct os2_thread
*tcb
)
52 *_threadstore() = tcb
;
55 static inline struct os2_thread
*os2_tcb(void)
57 return cast_cpp(struct os2_thread
*, *_threadstore());
62 #define OS2_MAX_THREADS 256
64 static struct os2_thread
*os2_thread_array
[OS2_MAX_THREADS
];
66 static void os2_tcb_set(struct os2_thread
*tcb
)
68 unsigned id
= (unsigned)*_threadid
;
69 if (unlikely(id
>= OS2_MAX_THREADS
))
70 fatal("too high thread id: %u", id
);
71 os2_thread_array
[id
] = tcb
;
74 static inline struct os2_thread
*os2_tcb(void)
76 unsigned id
= (unsigned)*_threadid
;
77 ajla_assert(id
< OS2_MAX_THREADS
, (file_line
, "too high thread id: %u", id
));
78 return os2_thread_array
[id
];
83 unsigned thread_concurrency(void)
87 r
= DosQuerySysInfo(26, 26, &n_cpus
, sizeof(n_cpus
));
88 if (unlikely(r
!= 0)) {
89 /*warning("DosQuerySysInfo(26) failed: %lu", r);*/
92 if (unlikely(!n_cpus
)) {
93 warning("DosQuerySysInfo(26) returned zero");
96 #ifdef OS2_MAX_THREADS
97 if (unlikely(n_cpus
> OS2_MAX_THREADS
/ 2))
98 return OS2_MAX_THREADS
/ 2;
100 return (unsigned)n_cpus
;
103 #ifdef OS2_USE_FMUTEX
105 #define do_mutex_init(m) \
108 r = _fmutex_create(m, 0); \
109 if (unlikely(r != 0)) \
110 fatal("_fmutex_cerate failed at %s: %u", position_string(position_arg), r);\
113 #define do_mutex_done(m) \
117 av = _fmutex_available(m); \
119 internal(caller_file_line, "mutex_done: _fmutex is still locked");\
120 r = _fmutex_close(m); \
121 if (unlikely(r != 0)) \
122 internal(caller_file_line, "mutex_done: _fmutex_close failed: %u", r);\
125 #define do_mutex_lock(m) \
128 r = _fmutex_request(m, _FMR_IGNINT); \
129 if (unlikely(r != 0)) \
130 internal(caller_file_line, "mutex_lock: _fmutex_request failed: %u", r);\
133 #define do_mutex_trylock(m) \
136 r = _fmutex_request(m, _FMR_IGNINT | _FMR_NOWAIT); \
137 if (unlikely(r != 0)) { \
138 if (likely(r == ERROR_MUTEX_OWNED)) \
140 internal(caller_file_line, "mutex_trylock: _fmutex_request failed: %u", r);\
145 #define do_mutex_unlock(m) \
148 r = _fmutex_release(m); \
149 if (unlikely(r != 0)) \
150 internal(caller_file_line, "mutex_unlock: _fmutex_release failed: %u", r);\
155 #define do_mutex_init(m) \
158 r = DosCreateMutexSem(NULL, m, 0, FALSE); \
159 if (unlikely(r != 0)) \
160 fatal("DosCreateMutexSem failed at %s: %lu", position_string(position_arg), r);\
163 #define do_mutex_done(m) \
166 r = DosCloseMutexSem(*m); \
167 if (unlikely(r != 0)) \
168 internal(caller_file_line, "mutex_done: DosCloseMutexSem failed: %lu", r);\
171 #define do_mutex_lock(m) \
175 r = DosRequestMutexSem(*m, SEM_INDEFINITE_WAIT); \
176 if (unlikely(r != 0)) { \
177 if (r == ERROR_INTERRUPT || r == ERROR_TIMEOUT) \
179 internal(caller_file_line, "mutex_lock: DosRequestMutexSem failed: %lu", r);\
183 #define do_mutex_trylock(m) \
187 r = DosRequestMutexSem(*m, SEM_IMMEDIATE_RETURN); \
188 if (unlikely(r != 0)) { \
189 if (likely(r == ERROR_TIMEOUT)) \
191 if (r == ERROR_INTERRUPT) \
193 internal(caller_file_line, "mutex_trylock: DosRequestMutexSem failed: %lu", r);\
198 #define do_mutex_unlock(m) \
201 r = DosReleaseMutexSem(*m); \
202 if (unlikely(r != 0)) \
203 internal(caller_file_line, "mutex_unlock: DosReleaseMutexSem failed: %lu", r);\
208 #define do_cond_init(c) \
210 mutex_init_position(&(c)->mutex pass_position); \
211 list_init(&(c)->wait_list); \
214 #define do_cond_done(c) \
216 mutex_done_position(&(c)->mutex pass_position); \
217 ajla_assert_lo(list_is_empty(&(c)->wait_list), (caller_file_line, "cond_done: wait list is not empty"));\
220 #define do_cond_lock(c) \
222 mutex_lock_position(&(c)->mutex pass_position); \
225 #define do_cond_unlock(c) \
227 mutex_unlock_position(&(c)->mutex pass_position); \
230 #define do_cond_unlock_signal(c) \
233 struct os2_thread *tcb; \
234 if (unlikely(!list_is_empty(&(c)->wait_list))) { \
235 tcb = get_struct((c)->wait_list.next, struct os2_thread, wait_entry);\
236 list_del(&tcb->wait_entry); \
237 tcb->wait_entry.prev = NULL; \
241 mutex_unlock_position(&(c)->mutex pass_position); \
242 if (unlikely(tcb != NULL)) { \
243 r = DosPostEventSem(tcb->wakeup); \
244 if (unlikely(r != 0)) \
245 internal(caller_file_line, "cond_unlock_signal: DosPostEventSem failed: %lu", r);\
249 #define do_cond_unlock_broadcast(c) \
254 list_take(&list, &(c)->wait_list); \
255 for (l = list.next; l != &list; l = l->next) \
257 mutex_unlock_position(&(c)->mutex pass_position); \
258 while (list.next != &list) { \
259 struct os2_thread *tcb = get_struct(list.next, struct os2_thread, wait_entry);\
260 list.next = tcb->wait_entry.next; \
261 r = DosPostEventSem(tcb->wakeup); \
262 if (unlikely(r != 0)) \
263 internal(caller_file_line, "cond_broadcast: DosPostEventSem failed: %lu", r);\
267 #define do_cond_wait(c) \
271 struct os2_thread *tcb; \
273 list_add(&(c)->wait_list, &tcb->wait_entry); \
274 mutex_unlock_position(&(c)->mutex pass_position); \
276 r = DosWaitEventSem(tcb->wakeup, SEM_INDEFINITE_WAIT); \
277 if (unlikely(r != 0)) { \
278 if (r == ERROR_INTERRUPT || r == ERROR_TIMEOUT) \
280 internal(caller_file_line, "cond_wait: DosWaitEventSem failed: %lu", r);\
282 r = DosResetEventSem(tcb->wakeup, &sink); \
283 if (unlikely(r != 0)) \
284 internal(caller_file_line, "cond_wait: DosResetEventSem failed: %lu", r);\
285 mutex_lock_position(&(c)->mutex pass_position); \
288 /* warning: this function can end prematurely on ERROR_INTERRUPT */
289 #define do_cond_wait_us(c, us) \
293 struct os2_thread *tcb; \
295 list_add(&(c)->wait_list, &tcb->wait_entry); \
296 mutex_unlock_position(&(c)->mutex pass_position); \
297 r = DosWaitEventSem(tcb->wakeup, (us + 999) / 1000); \
298 if (likely(r != 0)) { \
299 if (unlikely(!(likely(r == ERROR_TIMEOUT) || r == ERROR_INTERRUPT)))\
300 internal(caller_file_line, "cond_wait_us: DosWaitEventSem 1 failed: %lu", r);\
301 mutex_lock_position(&(c)->mutex pass_position); \
302 if (likely(tcb->wait_entry.prev != NULL)) { \
303 list_del(&tcb->wait_entry); \
307 r = DosWaitEventSem(tcb->wakeup, SEM_INDEFINITE_WAIT);\
308 if (unlikely(r != 0)) { \
309 if (r == ERROR_INTERRUPT || r == ERROR_TIMEOUT)\
311 internal(caller_file_line, "cond_wait: DosWaitEventSem 2 failed: %lu", r);\
313 r = DosResetEventSem(tcb->wakeup, &sink); \
314 if (unlikely(r != 0)) \
315 internal(caller_file_line, "cond_wait_us: DosResetEventSem 2 failed: %lu", r);\
319 r = DosResetEventSem(tcb->wakeup, &sink); \
320 if (unlikely(r != 0)) \
321 internal(caller_file_line, "cond_wait_us: DosResetEventSem 1 failed: %lu", r);\
322 mutex_lock_position(&(c)->mutex pass_position); \
327 static void os2_thread_init(struct os2_thread
*tcb
, bool t1 argument_position
)
330 r
= DosCreateEventSem(NULL
, &tcb
->wakeup
, 0, FALSE
);
331 if (unlikely(r
!= 0))
332 fatal("DosCreateEventSem 1 failed at %s: %lu", caller_file_line
, r
);
334 r
= DosCreateEventSem(NULL
, &tcb
->terminate
, 0, FALSE
);
335 if (unlikely(r
!= 0))
336 fatal("DosCreateEventSem 2 failed at %s: %lu", caller_file_line
, r
);
338 #ifndef HAVE___THREAD
339 (void)memset(&tcb
->tls
, 0, sizeof tcb
->tls
);
343 static void os2_thread_done(struct os2_thread
*tcb
, bool t1 argument_position
)
347 r
= DosQueryEventSem(tcb
->wakeup
, &cnt
);
348 if (unlikely(r
!= 0))
349 internal(caller_file_line
, "os2_thread_done: DosQueryEventSem 1 failed: %lu", r
);
350 if (unlikely(cnt
!= 0))
351 internal(caller_file_line
, "os2_thread_done: wakeup semaphore set: %lu", cnt
);
352 r
= DosCloseEventSem(tcb
->wakeup
);
353 if (unlikely(r
!= 0))
354 internal(caller_file_line
, "os2_thread_done: DosCloseEventSem 1 failed: %lu", r
);
356 r
= DosQueryEventSem(tcb
->terminate
, &cnt
);
357 if (unlikely(r
!= 0))
358 internal(caller_file_line
, "os2_thread_done: DosQueryEventSem 2 failed: %lu", r
);
359 if (unlikely(cnt
!= 1))
360 internal(caller_file_line
, "os2_thread_done: terminate semaphore not set: %lu", cnt
);
361 r
= DosCloseEventSem(tcb
->terminate
);
362 if (unlikely(r
!= 0))
363 internal(caller_file_line
, "os2_thread_done: DosCloseEventSem 2 failed: %lu", r
);
367 static void os2_thread_function(void *tcb_
)
370 struct os2_thread
*tcb
= cast_cpp(struct os2_thread
*, tcb_
);
374 cls
= PRTYC_NOCHANGE
;
376 if (tcb
->priority
== PRIORITY_TIMER
) {
378 cls
= PRTYC_TIMECRITICAL
;
383 r
= DosSetPriority(PRTYS_THREAD
, cls
, del
, 0);
384 if (unlikely(r
!= 0))
385 warning("DosSetPriority(%ld,%lu) failed: %lu", cls
, del
, r
);
387 tcb
->function(tcb
->arg
);
388 tls_destructor_call();
389 r
= DosPostEventSem(tcb
->terminate
);
390 if (unlikely(r
!= 0))
391 internal(file_line
, "os2_thread_function: DosPostEventSem failed: %lu", r
);
394 #define do_thread_spawn(t, function, arg, priority, err) \
396 struct os2_thread *tcb; \
398 tcb = mem_alloc_mayfail(struct os2_thread *, sizeof(struct os2_thread), err);\
399 if (unlikely(!tcb)) \
401 os2_thread_init(tcb, false pass_position); \
402 tcb->function = function; \
404 tcb->priority = priority; \
405 mutex_lock(&thread_spawn_mutex); \
406 btr = _beginthread(os2_thread_function, NULL, MINIMUM_STACK_SIZE, tcb);\
407 mutex_unlock(&thread_spawn_mutex); \
408 if (unlikely(btr == -1)) { \
410 ajla_error_t e = error_from_errno(EC_SYSCALL, er); \
411 fatal("_beginthread failed at %s: %d, %s", position_string(position_arg), er, error_decode(e));\
418 #define do_thread_join(t) \
421 struct os2_thread *tcb = *(t); \
423 r = DosWaitEventSem(tcb->terminate, SEM_INDEFINITE_WAIT); \
424 if (unlikely(r != 0)) { \
425 if (r == ERROR_INTERRUPT || r == ERROR_TIMEOUT) \
427 internal(caller_file_line, "thread_join: DosWaitEventSem failed: %lu", r);\
429 os2_thread_done(tcb, false pass_position); \
433 #ifndef HAVE___THREAD
435 #define do_tls_init(tl) \
437 ajla_assert_lo(os2_tls_used < OS2_THREAD_KEY_MAX, (caller_file_line, "tls_init: too many tls keys: %d", os2_tls_used));\
438 *(tl) = os2_tls_used++; \
441 #define do_tls_done(tl) \
443 ajla_assert_lo(*(tl) < os2_tls_used, (caller_file_line, "tls_done: invalid tls key: %d >= %d", *(tl), os2_tls_used));\
446 #define do_tls_get(tl, ret) \
448 ajla_assert(*(tl) < os2_tls_used, (caller_file_line, "tls_get: invalid tls key: %d >= %d", *(tl), os2_tls_used));\
449 *(ret) = os2_tcb()->tls[*(tl)]; \
452 #define do_tls_set(tl, val) \
454 ajla_assert(*(tl) < os2_tls_used, (caller_file_line, "tls_set: invalid tls key: %d >= %d", *(tl), os2_tls_used));\
455 os2_tcb()->tls[*(tl)] = (val); \
460 #include "th_com.inc"
462 void thread_init(void)
464 #ifndef HAVE___THREAD
467 os2_thread_init(&thread_1
, true pass_file_line
);
468 os2_tcb_set(&thread_1
);
469 thread_common_init();
470 mutex_init(&thread_spawn_mutex
);
473 void thread_done(void)
475 ajla_assert_lo(os2_tcb() == &thread_1
, (file_line
, "thread_done: mismatching thread 1: %p != %p", os2_tcb(), &thread_1
));
476 mutex_done(&thread_spawn_mutex
);
477 thread_common_done();
478 #if defined(DEBUG_LOW_OVERHEAD)
481 os2_thread_done(&thread_1
, true pass_file_line
);