Update the API version too.
[kugel-rb.git] / uisimulator / sdl / thread-sdl.c
blobab1086dd7d8733b00e84e28efa8090a518b1bd03
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 <memory.h>
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 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 static SDL_mutex *m;
60 static bool threads_exit = false;
62 extern long start_tick;
64 void thread_sdl_shutdown(void)
66 int i;
67 /* Take control */
68 SDL_LockMutex(m);
70 /* Tell all threads jump back to their start routines, unlock and exit
71 gracefully - we'll check each one in turn for it's status. Threads
72 _could_ terminate via remove_thread or multiple threads could exit
73 on each unlock but that is safe. */
74 threads_exit = true;
76 for (i = 0; i < MAXTHREADS; i++)
78 struct thread_entry *thread = &threads[i];
79 if (thread->context.t != NULL)
81 /* Signal thread on delay or block */
82 SDL_Thread *t = thread->context.t;
83 SDL_SemPost(thread->context.s);
84 SDL_UnlockMutex(m);
85 /* Wait for it to finish */
86 SDL_WaitThread(t, NULL);
87 /* Relock for next thread signal */
88 SDL_LockMutex(m);
92 SDL_UnlockMutex(m);
93 SDL_DestroyMutex(m);
96 static void new_thread_id(unsigned int slot_num,
97 struct thread_entry *thread)
99 unsigned int version =
100 (thread->id + (1u << THREAD_ID_VERSION_SHIFT))
101 & THREAD_ID_VERSION_MASK;
103 if (version == 0)
104 version = 1u << THREAD_ID_VERSION_SHIFT;
106 thread->id = version | (slot_num & THREAD_ID_SLOT_MASK);
109 static struct thread_entry * find_empty_thread_slot(void)
111 struct thread_entry *thread = NULL;
112 int n;
114 for (n = 0; n < MAXTHREADS; n++)
116 int state = threads[n].state;
118 if (state == STATE_KILLED)
120 thread = &threads[n];
121 break;
125 return thread;
128 /* Do main thread creation in this file scope to avoid the need to double-
129 return to a prior call-level which would be unaware of the fact setjmp
130 was used */
131 extern void app_main(void *param);
132 static int thread_sdl_app_main(void *param)
134 SDL_LockMutex(m);
135 cores[CURRENT_CORE].running = &threads[0];
137 /* Set the jump address for return */
138 if (setjmp(thread_jmpbufs[0]) == 0)
140 app_main(param);
141 /* should not ever be reached but... */
142 THREAD_PANICF("app_main returned!\n");
145 /* Unlock and exit */
146 SDL_UnlockMutex(m);
147 return 0;
150 /* Initialize SDL threading */
151 bool thread_sdl_init(void *param)
153 struct thread_entry *thread;
154 int n;
156 memset(cores, 0, sizeof(cores));
157 memset(threads, 0, sizeof(threads));
159 m = SDL_CreateMutex();
161 if (SDL_LockMutex(m) == -1)
163 fprintf(stderr, "Couldn't lock mutex\n");
164 return false;
167 /* Initialize all IDs */
168 for (n = 0; n < MAXTHREADS; n++)
169 threads[n].id = THREAD_ID_INIT(n);
171 /* Slot 0 is reserved for the main thread - initialize it here and
172 then create the SDL thread - it is possible to have a quick, early
173 shutdown try to access the structure. */
174 thread = &threads[0];
175 thread->stack = (uintptr_t *)" ";
176 thread->stack_size = 8;
177 thread->name = "main";
178 thread->state = STATE_RUNNING;
179 thread->context.s = SDL_CreateSemaphore(0);
180 cores[CURRENT_CORE].running = thread;
182 if (thread->context.s == NULL)
184 fprintf(stderr, "Failed to create main semaphore\n");
185 return false;
188 thread->context.t = SDL_CreateThread(thread_sdl_app_main, param);
190 if (thread->context.t == NULL)
192 SDL_DestroySemaphore(thread->context.s);
193 fprintf(stderr, "Failed to create main thread\n");
194 return false;
197 THREAD_SDL_DEBUGF("Main thread: %p\n", thread);
199 SDL_UnlockMutex(m);
200 return true;
203 /* A way to yield and leave the threading system for extended periods */
204 void thread_sdl_thread_lock(void *me)
206 SDL_LockMutex(m);
207 cores[CURRENT_CORE].running = (struct thread_entry *)me;
209 if (threads_exit)
210 thread_exit();
213 void * thread_sdl_thread_unlock(void)
215 struct thread_entry *current = cores[CURRENT_CORE].running;
216 SDL_UnlockMutex(m);
217 return current;
220 struct thread_entry * thread_id_entry(unsigned int thread_id)
222 return (thread_id == THREAD_ID_CURRENT) ?
223 cores[CURRENT_CORE].running :
224 &threads[thread_id & THREAD_ID_SLOT_MASK];
227 static void add_to_list_l(struct thread_entry **list,
228 struct thread_entry *thread)
230 if (*list == NULL)
232 /* Insert into unoccupied list */
233 thread->l.next = thread;
234 thread->l.prev = thread;
235 *list = thread;
237 else
239 /* Insert last */
240 thread->l.next = *list;
241 thread->l.prev = (*list)->l.prev;
242 thread->l.prev->l.next = thread;
243 (*list)->l.prev = thread;
247 static void remove_from_list_l(struct thread_entry **list,
248 struct thread_entry *thread)
250 if (thread == thread->l.next)
252 /* The only item */
253 *list = NULL;
254 return;
257 if (thread == *list)
259 /* List becomes next item */
260 *list = thread->l.next;
263 /* Fix links to jump over the removed entry. */
264 thread->l.prev->l.next = thread->l.next;
265 thread->l.next->l.prev = thread->l.prev;
268 unsigned int thread_get_current(void)
270 return cores[CURRENT_CORE].running->id;
273 void switch_thread(void)
275 struct thread_entry *current = cores[CURRENT_CORE].running;
277 enable_irq();
279 switch (current->state)
281 case STATE_RUNNING:
283 SDL_UnlockMutex(m);
284 /* Any other thread waiting already will get it first */
285 SDL_LockMutex(m);
286 break;
287 } /* STATE_RUNNING: */
289 case STATE_BLOCKED:
291 int oldlevel;
293 SDL_UnlockMutex(m);
294 SDL_SemWait(current->context.s);
295 SDL_LockMutex(m);
297 oldlevel = disable_irq_save();
298 current->state = STATE_RUNNING;
299 restore_irq(oldlevel);
300 break;
301 } /* STATE_BLOCKED: */
303 case STATE_BLOCKED_W_TMO:
305 int result, oldlevel;
307 SDL_UnlockMutex(m);
308 result = SDL_SemWaitTimeout(current->context.s, current->tmo_tick);
309 SDL_LockMutex(m);
311 oldlevel = disable_irq_save();
313 if (current->state == STATE_BLOCKED_W_TMO)
315 /* Timed out */
316 remove_from_list_l(current->bqp, current);
318 #ifdef HAVE_WAKEUP_EXT_CB
319 if (current->wakeup_ext_cb != NULL)
320 current->wakeup_ext_cb(current);
321 #endif
322 current->state = STATE_RUNNING;
325 if (result == SDL_MUTEX_TIMEDOUT)
327 /* Other signals from an explicit wake could have been made before
328 * arriving here if we timed out waiting for the semaphore. Make
329 * sure the count is reset. */
330 while (SDL_SemValue(current->context.s) > 0)
331 SDL_SemTryWait(current->context.s);
334 restore_irq(oldlevel);
335 break;
336 } /* STATE_BLOCKED_W_TMO: */
338 case STATE_SLEEPING:
340 SDL_UnlockMutex(m);
341 SDL_SemWaitTimeout(current->context.s, current->tmo_tick);
342 SDL_LockMutex(m);
343 current->state = STATE_RUNNING;
344 break;
345 } /* STATE_SLEEPING: */
348 cores[CURRENT_CORE].running = current;
350 if (threads_exit)
351 thread_exit();
354 void sleep_thread(int ticks)
356 struct thread_entry *current = cores[CURRENT_CORE].running;
357 int rem;
359 current->state = STATE_SLEEPING;
361 rem = (SDL_GetTicks() - start_tick) % (1000/HZ);
362 if (rem < 0)
363 rem = 0;
365 current->tmo_tick = (1000/HZ) * ticks + ((1000/HZ)-1) - rem;
368 void block_thread(struct thread_entry *current)
370 current->state = STATE_BLOCKED;
371 add_to_list_l(current->bqp, current);
374 void block_thread_w_tmo(struct thread_entry *current, int ticks)
376 current->state = STATE_BLOCKED_W_TMO;
377 current->tmo_tick = (1000/HZ)*ticks;
378 add_to_list_l(current->bqp, current);
381 unsigned int wakeup_thread(struct thread_entry **list)
383 struct thread_entry *thread = *list;
385 if (thread != NULL)
387 switch (thread->state)
389 case STATE_BLOCKED:
390 case STATE_BLOCKED_W_TMO:
391 remove_from_list_l(list, thread);
392 thread->state = STATE_RUNNING;
393 SDL_SemPost(thread->context.s);
394 return THREAD_OK;
398 return THREAD_NONE;
401 unsigned int thread_queue_wake(struct thread_entry **list)
403 unsigned int result = THREAD_NONE;
405 for (;;)
407 unsigned int rc = wakeup_thread(list);
409 if (rc == THREAD_NONE)
410 break;
412 result |= rc;
415 return result;
418 void thread_thaw(unsigned int thread_id)
420 struct thread_entry *thread = thread_id_entry(thread_id);
422 if (thread->id == thread_id && thread->state == STATE_FROZEN)
424 thread->state = STATE_RUNNING;
425 SDL_SemPost(thread->context.s);
429 int runthread(void *data)
431 struct thread_entry *current;
432 jmp_buf *current_jmpbuf;
434 /* Cannot access thread variables before locking the mutex as the
435 data structures may not be filled-in yet. */
436 SDL_LockMutex(m);
437 cores[CURRENT_CORE].running = (struct thread_entry *)data;
438 current = cores[CURRENT_CORE].running;
439 current_jmpbuf = &thread_jmpbufs[current - threads];
441 /* Setup jump for exit */
442 if (setjmp(*current_jmpbuf) == 0)
444 /* Run the thread routine */
445 if (current->state == STATE_FROZEN)
447 SDL_UnlockMutex(m);
448 SDL_SemWait(current->context.s);
449 SDL_LockMutex(m);
450 cores[CURRENT_CORE].running = current;
453 if (!threads_exit)
455 current->context.start();
456 THREAD_SDL_DEBUGF("Thread Done: %d (%s)\n",
457 current - threads, THREAD_SDL_GET_NAME(current));
458 /* Thread routine returned - suicide */
461 thread_exit();
463 else
465 /* Unlock and exit */
466 SDL_UnlockMutex(m);
469 return 0;
472 unsigned int create_thread(void (*function)(void),
473 void* stack, size_t stack_size,
474 unsigned flags, const char *name)
476 struct thread_entry *thread;
477 SDL_Thread* t;
478 SDL_sem *s;
480 THREAD_SDL_DEBUGF("Creating thread: (%s)\n", name ? name : "");
482 thread = find_empty_thread_slot();
483 if (thread == NULL)
485 DEBUGF("Failed to find thread slot\n");
486 return 0;
489 s = SDL_CreateSemaphore(0);
490 if (s == NULL)
492 DEBUGF("Failed to create semaphore\n");
493 return 0;
496 t = SDL_CreateThread(runthread, thread);
497 if (t == NULL)
499 DEBUGF("Failed to create SDL thread\n");
500 SDL_DestroySemaphore(s);
501 return 0;
504 thread->stack = stack;
505 thread->stack_size = stack_size;
506 thread->name = name;
507 thread->state = (flags & CREATE_THREAD_FROZEN) ?
508 STATE_FROZEN : STATE_RUNNING;
509 thread->context.start = function;
510 thread->context.t = t;
511 thread->context.s = s;
513 THREAD_SDL_DEBUGF("New Thread: %d (%s)\n",
514 thread - threads, THREAD_SDL_GET_NAME(thread));
516 return thread->id;
519 void init_threads(void)
521 /* Main thread is already initialized */
522 if (cores[CURRENT_CORE].running != &threads[0])
524 THREAD_PANICF("Wrong main thread in init_threads: %p\n",
525 cores[CURRENT_CORE].running);
528 THREAD_SDL_DEBUGF("First Thread: %d (%s)\n",
529 0, THREAD_SDL_GET_NAME(&threads[0]));
532 #ifndef ALLOW_REMOVE_THREAD
533 static void remove_thread(unsigned int thread_id)
534 #else
535 void remove_thread(unsigned int thread_id)
536 #endif
538 struct thread_entry *current = cores[CURRENT_CORE].running;
539 struct thread_entry *thread = thread_id_entry(thread_id);
541 SDL_Thread *t;
542 SDL_sem *s;
544 if (thread_id != THREAD_ID_CURRENT && thread->id != thread_id)
545 return;
547 int oldlevel = disable_irq_save();
549 t = thread->context.t;
550 s = thread->context.s;
551 thread->context.t = NULL;
553 if (thread != current)
555 switch (thread->state)
557 case STATE_BLOCKED:
558 case STATE_BLOCKED_W_TMO:
559 /* Remove thread from object it's waiting on */
560 remove_from_list_l(thread->bqp, thread);
562 #ifdef HAVE_WAKEUP_EXT_CB
563 if (thread->wakeup_ext_cb != NULL)
564 thread->wakeup_ext_cb(thread);
565 #endif
566 break;
569 SDL_SemPost(s);
572 THREAD_SDL_DEBUGF("Removing thread: %d (%s)\n",
573 thread - threads, THREAD_SDL_GET_NAME(thread));
575 new_thread_id(thread->id, thread);
576 thread->state = STATE_KILLED;
577 thread_queue_wake(&thread->queue);
579 SDL_DestroySemaphore(s);
581 if (thread == current)
583 /* Do a graceful exit - perform the longjmp back into the thread
584 function to return */
585 restore_irq(oldlevel);
586 longjmp(thread_jmpbufs[current - threads], 1);
589 SDL_KillThread(t);
590 restore_irq(oldlevel);
593 void thread_exit(void)
595 remove_thread(THREAD_ID_CURRENT);
598 void thread_wait(unsigned int thread_id)
600 struct thread_entry *current = cores[CURRENT_CORE].running;
601 struct thread_entry *thread = thread_id_entry(thread_id);
603 if (thread_id == THREAD_ID_CURRENT ||
604 (thread->id == thread_id && thread->state != STATE_KILLED))
606 current->bqp = &thread->queue;
607 block_thread(current);
608 switch_thread();
612 int thread_stack_usage(const struct thread_entry *thread)
614 return 50;
615 (void)thread;
618 /* Return name if one or ID if none */
619 void thread_get_name(char *buffer, int size,
620 struct thread_entry *thread)
622 if (size <= 0)
623 return;
625 *buffer = '\0';
627 if (thread)
629 /* Display thread name if one or ID if none */
630 bool named = thread->name && *thread->name;
631 const char *fmt = named ? "%s" : "%08lX";
632 intptr_t name = named ?
633 (intptr_t)thread->name : (intptr_t)thread;
634 snprintf(buffer, size, fmt, name);