Change the thread api a bit.
[kugel-rb.git] / firmware / target / hosted / sdl / thread-sdl.c
blobeaffa86aeea64405b654899d56054a083f1b3d29
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2006 Dan Everton
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 #include <stdbool.h>
23 #include <time.h>
24 #include <SDL.h>
25 #include <SDL_thread.h>
26 #include <stdlib.h>
27 #include <string.h> /* memset() */
28 #include <setjmp.h>
29 #include "system-sdl.h"
30 #include "thread-sdl.h"
31 #include "system.h"
32 #include "kernel.h"
33 #include "thread.h"
34 #include "debug.h"
36 /* Define this as 1 to show informational messages that are not errors. */
37 #define THREAD_SDL_DEBUGF_ENABLED 0
39 #if THREAD_SDL_DEBUGF_ENABLED
40 #define THREAD_SDL_DEBUGF(...) DEBUGF(__VA_ARGS__)
41 static char __name[32];
42 #define THREAD_SDL_GET_NAME(thread) \
43 ({ thread_get_name(__name, ARRAYLEN(__name), thread); __name; })
44 #else
45 #define THREAD_SDL_DEBUGF(...)
46 #define THREAD_SDL_GET_NAME(thread)
47 #endif
49 #define THREAD_PANICF(str...) \
50 ({ fprintf(stderr, str); exit(-1); })
52 /* Thread/core entries as in rockbox core */
53 static struct core_entry cores[NUM_CORES];
54 struct thread_entry threads[MAXTHREADS];
55 /* Jump buffers for graceful exit - kernel threads don't stay neatly
56 * in their start routines responding to messages so this is the only
57 * way to get them back in there so they may exit */
58 static jmp_buf thread_jmpbufs[MAXTHREADS];
59 /* this mutex locks out other Rockbox threads while one runs,
60 * that enables us to simulate a cooperative environment even if
61 * the host is preemptive */
62 static SDL_mutex *m;
63 #define THREADS_RUN 0
64 #define THREADS_EXIT 1
65 #define THREADS_EXIT_COMMAND_DONE 2
66 static volatile int threads_status = THREADS_RUN;
68 extern long start_tick;
70 void sim_thread_shutdown(void)
72 int i;
74 /* This *has* to be a push operation from a thread not in the pool
75 so that they may be dislodged from their blocking calls. */
77 /* Tell all threads jump back to their start routines, unlock and exit
78 gracefully - we'll check each one in turn for it's status. Threads
79 _could_ terminate via remove_thread or multiple threads could exit
80 on each unlock but that is safe. */
82 /* Do this before trying to acquire lock */
83 threads_status = THREADS_EXIT;
85 /* Take control */
86 SDL_LockMutex(m);
88 /* Signal all threads on delay or block */
89 for (i = 0; i < MAXTHREADS; i++)
91 struct thread_entry *thread = &threads[i];
92 if (thread->context.s == NULL)
93 continue;
94 SDL_SemPost(thread->context.s);
97 /* Wait for all threads to finish and cleanup old ones. */
98 for (i = 0; i < MAXTHREADS; i++)
100 struct thread_entry *thread = &threads[i];
101 SDL_Thread *t = thread->context.t;
103 if (t != NULL)
105 SDL_UnlockMutex(m);
106 /* Wait for it to finish */
107 SDL_WaitThread(t, NULL);
108 /* Relock for next thread signal */
109 SDL_LockMutex(m);
110 /* Already waited and exiting thread would have waited .told,
111 * replacing it with t. */
112 thread->context.told = NULL;
114 else
116 /* Wait on any previous thread in this location-- could be one not quite
117 * finished exiting but has just unlocked the mutex. If it's NULL, the
118 * call returns immediately.
120 * See remove_thread below for more information. */
121 SDL_WaitThread(thread->context.told, NULL);
125 SDL_UnlockMutex(m);
127 /* Signal completion of operation */
128 threads_status = THREADS_EXIT_COMMAND_DONE;
131 static void new_thread_id(unsigned int slot_num,
132 struct thread_entry *thread)
134 unsigned int version =
135 (thread->id + (1u << THREAD_ID_VERSION_SHIFT))
136 & THREAD_ID_VERSION_MASK;
138 if (version == 0)
139 version = 1u << THREAD_ID_VERSION_SHIFT;
141 thread->id = version | (slot_num & THREAD_ID_SLOT_MASK);
144 static struct thread_entry * find_empty_thread_slot(void)
146 struct thread_entry *thread = NULL;
147 int n;
149 for (n = 0; n < MAXTHREADS; n++)
151 int state = threads[n].state;
153 if (state == STATE_KILLED)
155 thread = &threads[n];
156 break;
160 return thread;
164 /* Initialize SDL threading */
165 void init_threads(void)
167 struct thread_entry *thread;
168 int n;
170 memset(cores, 0, sizeof(cores));
171 memset(threads, 0, sizeof(threads));
173 m = SDL_CreateMutex();
175 if (SDL_LockMutex(m) == -1)
177 fprintf(stderr, "Couldn't lock mutex\n");
178 return;
181 /* Initialize all IDs */
182 for (n = 0; n < MAXTHREADS; n++)
183 threads[n].id = THREAD_ID_INIT(n);
185 /* Slot 0 is reserved for the main thread - initialize it here and
186 then create the SDL thread - it is possible to have a quick, early
187 shutdown try to access the structure. */
188 thread = &threads[0];
189 thread->stack = (uintptr_t *)" ";
190 thread->stack_size = 8;
191 thread->name = "main";
192 thread->state = STATE_RUNNING;
193 thread->context.s = SDL_CreateSemaphore(0);
194 thread->context.t = NULL; /* NULL for the implicit main thread */
195 cores[CURRENT_CORE].running = thread;
197 if (thread->context.s == NULL)
199 fprintf(stderr, "Failed to create main semaphore\n");
200 return;
203 /* Tell all threads jump back to their start routines, unlock and exit
204 gracefully - we'll check each one in turn for it's status. Threads
205 _could_ terminate via remove_thread or multiple threads could exit
206 on each unlock but that is safe. */
208 /* Setup jump for exit */
209 if (setjmp(thread_jmpbufs[0]) == 0)
211 THREAD_SDL_DEBUGF("Main thread: %p\n", thread);
212 return;
215 SDL_UnlockMutex(m);
217 /* Set to 'COMMAND_DONE' when other rockbox threads have exited. */
218 while (threads_status < THREADS_EXIT_COMMAND_DONE)
219 SDL_Delay(10);
221 SDL_DestroyMutex(m);
223 /* We're the main thead - perform exit - doesn't return. */
224 sim_do_exit();
227 void sim_thread_exception_wait(void)
229 while (1)
231 SDL_Delay(HZ/10);
232 if (threads_status != THREADS_RUN)
233 thread_exit();
237 /* A way to yield and leave the threading system for extended periods */
238 void sim_thread_lock(void *me)
240 SDL_LockMutex(m);
241 cores[CURRENT_CORE].running = (struct thread_entry *)me;
243 if (threads_status != THREADS_RUN)
244 thread_exit();
247 void * sim_thread_unlock(void)
249 struct thread_entry *current = cores[CURRENT_CORE].running;
250 SDL_UnlockMutex(m);
251 return current;
254 struct thread_entry * thread_id_entry(unsigned int thread_id)
256 return &threads[thread_id & THREAD_ID_SLOT_MASK];
259 static void add_to_list_l(struct thread_entry **list,
260 struct thread_entry *thread)
262 if (*list == NULL)
264 /* Insert into unoccupied list */
265 thread->l.next = thread;
266 thread->l.prev = thread;
267 *list = thread;
269 else
271 /* Insert last */
272 thread->l.next = *list;
273 thread->l.prev = (*list)->l.prev;
274 thread->l.prev->l.next = thread;
275 (*list)->l.prev = thread;
279 static void remove_from_list_l(struct thread_entry **list,
280 struct thread_entry *thread)
282 if (thread == thread->l.next)
284 /* The only item */
285 *list = NULL;
286 return;
289 if (thread == *list)
291 /* List becomes next item */
292 *list = thread->l.next;
295 /* Fix links to jump over the removed entry. */
296 thread->l.prev->l.next = thread->l.next;
297 thread->l.next->l.prev = thread->l.prev;
300 unsigned int thread_self(void)
302 return cores[CURRENT_CORE].running->id;
305 struct thread_entry* thread_self_entry(void)
307 return cores[CURRENT_CORE].running;
310 void switch_thread(void)
312 struct thread_entry *current = cores[CURRENT_CORE].running;
314 enable_irq();
316 switch (current->state)
318 case STATE_RUNNING:
320 SDL_UnlockMutex(m);
321 /* Any other thread waiting already will get it first */
322 SDL_LockMutex(m);
323 break;
324 } /* STATE_RUNNING: */
326 case STATE_BLOCKED:
328 int oldlevel;
330 SDL_UnlockMutex(m);
331 SDL_SemWait(current->context.s);
332 SDL_LockMutex(m);
334 oldlevel = disable_irq_save();
335 current->state = STATE_RUNNING;
336 restore_irq(oldlevel);
337 break;
338 } /* STATE_BLOCKED: */
340 case STATE_BLOCKED_W_TMO:
342 int result, oldlevel;
344 SDL_UnlockMutex(m);
345 result = SDL_SemWaitTimeout(current->context.s, current->tmo_tick);
346 SDL_LockMutex(m);
348 oldlevel = disable_irq_save();
350 if (current->state == STATE_BLOCKED_W_TMO)
352 /* Timed out */
353 remove_from_list_l(current->bqp, current);
355 #ifdef HAVE_WAKEUP_EXT_CB
356 if (current->wakeup_ext_cb != NULL)
357 current->wakeup_ext_cb(current);
358 #endif
359 current->state = STATE_RUNNING;
362 if (result == SDL_MUTEX_TIMEDOUT)
364 /* Other signals from an explicit wake could have been made before
365 * arriving here if we timed out waiting for the semaphore. Make
366 * sure the count is reset. */
367 while (SDL_SemValue(current->context.s) > 0)
368 SDL_SemTryWait(current->context.s);
371 restore_irq(oldlevel);
372 break;
373 } /* STATE_BLOCKED_W_TMO: */
375 case STATE_SLEEPING:
377 SDL_UnlockMutex(m);
378 SDL_SemWaitTimeout(current->context.s, current->tmo_tick);
379 SDL_LockMutex(m);
380 current->state = STATE_RUNNING;
381 break;
382 } /* STATE_SLEEPING: */
385 cores[CURRENT_CORE].running = current;
387 if (threads_status != THREADS_RUN)
388 thread_exit();
391 void sleep_thread(int ticks)
393 struct thread_entry *current = cores[CURRENT_CORE].running;
394 int rem;
396 current->state = STATE_SLEEPING;
398 rem = (SDL_GetTicks() - start_tick) % (1000/HZ);
399 if (rem < 0)
400 rem = 0;
402 current->tmo_tick = (1000/HZ) * ticks + ((1000/HZ)-1) - rem;
405 void block_thread(struct thread_entry *current)
407 current->state = STATE_BLOCKED;
408 add_to_list_l(current->bqp, current);
411 void block_thread_w_tmo(struct thread_entry *current, int ticks)
413 current->state = STATE_BLOCKED_W_TMO;
414 current->tmo_tick = (1000/HZ)*ticks;
415 add_to_list_l(current->bqp, current);
418 unsigned int wakeup_thread(struct thread_entry **list)
420 struct thread_entry *thread = *list;
422 if (thread != NULL)
424 switch (thread->state)
426 case STATE_BLOCKED:
427 case STATE_BLOCKED_W_TMO:
428 remove_from_list_l(list, thread);
429 thread->state = STATE_RUNNING;
430 SDL_SemPost(thread->context.s);
431 return THREAD_OK;
435 return THREAD_NONE;
438 unsigned int thread_queue_wake(struct thread_entry **list)
440 unsigned int result = THREAD_NONE;
442 for (;;)
444 unsigned int rc = wakeup_thread(list);
446 if (rc == THREAD_NONE)
447 break;
449 result |= rc;
452 return result;
455 void thread_thaw(unsigned int thread_id)
457 struct thread_entry *thread = thread_id_entry(thread_id);
459 if (thread->id == thread_id && thread->state == STATE_FROZEN)
461 thread->state = STATE_RUNNING;
462 SDL_SemPost(thread->context.s);
466 int runthread(void *data)
468 struct thread_entry *current;
469 jmp_buf *current_jmpbuf;
471 /* Cannot access thread variables before locking the mutex as the
472 data structures may not be filled-in yet. */
473 SDL_LockMutex(m);
474 cores[CURRENT_CORE].running = (struct thread_entry *)data;
475 current = cores[CURRENT_CORE].running;
476 current_jmpbuf = &thread_jmpbufs[current - threads];
478 /* Setup jump for exit */
479 if (setjmp(*current_jmpbuf) == 0)
481 /* Run the thread routine */
482 if (current->state == STATE_FROZEN)
484 SDL_UnlockMutex(m);
485 SDL_SemWait(current->context.s);
486 SDL_LockMutex(m);
487 cores[CURRENT_CORE].running = current;
490 if (threads_status == THREADS_RUN)
492 current->context.start();
493 THREAD_SDL_DEBUGF("Thread Done: %d (%s)\n",
494 current - threads, THREAD_SDL_GET_NAME(current));
495 /* Thread routine returned - suicide */
498 thread_exit();
500 else
502 /* Unlock and exit */
503 SDL_UnlockMutex(m);
506 return 0;
509 unsigned int create_thread(void (*function)(void),
510 void* stack, size_t stack_size,
511 unsigned flags, const char *name)
513 struct thread_entry *thread;
514 SDL_Thread* t;
515 SDL_sem *s;
517 THREAD_SDL_DEBUGF("Creating thread: (%s)\n", name ? name : "");
519 thread = find_empty_thread_slot();
520 if (thread == NULL)
522 DEBUGF("Failed to find thread slot\n");
523 return 0;
526 s = SDL_CreateSemaphore(0);
527 if (s == NULL)
529 DEBUGF("Failed to create semaphore\n");
530 return 0;
533 t = SDL_CreateThread(runthread, thread);
534 if (t == NULL)
536 DEBUGF("Failed to create SDL thread\n");
537 SDL_DestroySemaphore(s);
538 return 0;
541 thread->stack = stack;
542 thread->stack_size = stack_size;
543 thread->name = name;
544 thread->state = (flags & CREATE_THREAD_FROZEN) ?
545 STATE_FROZEN : STATE_RUNNING;
546 thread->context.start = function;
547 thread->context.t = t;
548 thread->context.s = s;
550 THREAD_SDL_DEBUGF("New Thread: %d (%s)\n",
551 thread - threads, THREAD_SDL_GET_NAME(thread));
553 return thread->id;
556 #ifndef ALLOW_REMOVE_THREAD
557 static void remove_thread(unsigned int thread_id)
558 #else
559 void remove_thread(unsigned int thread_id)
560 #endif
562 struct thread_entry *current = cores[CURRENT_CORE].running;
563 struct thread_entry *thread = thread_id_entry(thread_id);
565 SDL_Thread *t;
566 SDL_sem *s;
568 if (thread->id != thread_id)
569 return;
571 int oldlevel = disable_irq_save();
573 t = thread->context.t;
574 s = thread->context.s;
576 /* Wait the last thread here and keep this one or SDL will leak it since
577 * it doesn't free its own library allocations unless a wait is performed.
578 * Such behavior guards against the memory being invalid by the time
579 * SDL_WaitThread is reached and also against two different threads having
580 * the same pointer. It also makes SDL_WaitThread a non-concurrent function.
582 * However, see more below about SDL_KillThread.
584 SDL_WaitThread(thread->context.told, NULL);
586 thread->context.t = NULL;
587 thread->context.s = NULL;
588 thread->context.told = t;
590 if (thread != current)
592 switch (thread->state)
594 case STATE_BLOCKED:
595 case STATE_BLOCKED_W_TMO:
596 /* Remove thread from object it's waiting on */
597 remove_from_list_l(thread->bqp, thread);
599 #ifdef HAVE_WAKEUP_EXT_CB
600 if (thread->wakeup_ext_cb != NULL)
601 thread->wakeup_ext_cb(thread);
602 #endif
603 break;
606 SDL_SemPost(s);
609 THREAD_SDL_DEBUGF("Removing thread: %d (%s)\n",
610 thread - threads, THREAD_SDL_GET_NAME(thread));
612 new_thread_id(thread->id, thread);
613 thread->state = STATE_KILLED;
614 thread_queue_wake(&thread->queue);
616 SDL_DestroySemaphore(s);
618 if (thread == current)
620 /* Do a graceful exit - perform the longjmp back into the thread
621 function to return */
622 restore_irq(oldlevel);
623 longjmp(thread_jmpbufs[current - threads], 1);
626 /* SDL_KillThread frees the old pointer too because it uses SDL_WaitThread
627 * to wait for the host to remove it. */
628 thread->context.told = NULL;
629 SDL_KillThread(t);
630 restore_irq(oldlevel);
633 void thread_exit(void)
635 unsigned int id = thread_self();
636 remove_thread(id);
637 /* This should never and must never be reached - if it is, the
638 * state is corrupted */
639 THREAD_PANICF("thread_exit->K:*R (ID: %d)", id);
640 while (1);
643 void thread_wait(unsigned int thread_id)
645 struct thread_entry *current = cores[CURRENT_CORE].running;
646 struct thread_entry *thread = thread_id_entry(thread_id);
648 if (thread->id == thread_id && thread->state != STATE_KILLED)
650 current->bqp = &thread->queue;
651 block_thread(current);
652 switch_thread();
656 int thread_stack_usage(const struct thread_entry *thread)
658 return 50;
659 (void)thread;
662 /* Return name if one or ID if none */
663 void thread_get_name(char *buffer, int size,
664 struct thread_entry *thread)
666 if (size <= 0)
667 return;
669 *buffer = '\0';
671 if (thread)
673 /* Display thread name if one or ID if none */
674 bool named = thread->name && *thread->name;
675 const char *fmt = named ? "%s" : "%04lX";
676 intptr_t name = named ?
677 (intptr_t)thread->name : (intptr_t)thread->id;
678 snprintf(buffer, size, fmt, name);