diff options
Diffstat (limited to 'firmware/target/hosted/sdl/thread-sdl.c')
-rw-r--r-- | firmware/target/hosted/sdl/thread-sdl.c | 610 |
1 files changed, 610 insertions, 0 deletions
diff --git a/firmware/target/hosted/sdl/thread-sdl.c b/firmware/target/hosted/sdl/thread-sdl.c new file mode 100644 index 0000000000..fbe2621d40 --- /dev/null +++ b/firmware/target/hosted/sdl/thread-sdl.c | |||
@@ -0,0 +1,610 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2006 Dan Everton | ||
11 | * | ||
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. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
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" | ||
35 | |||
36 | /* Define this as 1 to show informational messages that are not errors. */ | ||
37 | #define THREAD_SDL_DEBUGF_ENABLED 0 | ||
38 | |||
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 | ||
48 | |||
49 | #define THREAD_PANICF(str...) \ | ||
50 | ({ fprintf(stderr, str); exit(-1); }) | ||
51 | |||
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 | static volatile bool threads_exit = false; | ||
64 | |||
65 | extern long start_tick; | ||
66 | |||
67 | void sim_thread_shutdown(void) | ||
68 | { | ||
69 | int i; | ||
70 | |||
71 | /* Tell all threads jump back to their start routines, unlock and exit | ||
72 | gracefully - we'll check each one in turn for it's status. Threads | ||
73 | _could_ terminate via remove_thread or multiple threads could exit | ||
74 | on each unlock but that is safe. */ | ||
75 | |||
76 | /* Do this before trying to acquire lock */ | ||
77 | threads_exit = true; | ||
78 | |||
79 | /* Take control */ | ||
80 | SDL_LockMutex(m); | ||
81 | |||
82 | for (i = 0; i < MAXTHREADS; i++) | ||
83 | { | ||
84 | struct thread_entry *thread = &threads[i]; | ||
85 | /* exit all current threads, except the main one */ | ||
86 | if (thread->context.t != NULL) | ||
87 | { | ||
88 | /* Signal thread on delay or block */ | ||
89 | SDL_Thread *t = thread->context.t; | ||
90 | SDL_SemPost(thread->context.s); | ||
91 | SDL_UnlockMutex(m); | ||
92 | /* Wait for it to finish */ | ||
93 | SDL_WaitThread(t, NULL); | ||
94 | /* Relock for next thread signal */ | ||
95 | SDL_LockMutex(m); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | SDL_UnlockMutex(m); | ||
100 | SDL_DestroyMutex(m); | ||
101 | } | ||
102 | |||
103 | static void new_thread_id(unsigned int slot_num, | ||
104 | struct thread_entry *thread) | ||
105 | { | ||
106 | unsigned int version = | ||
107 | (thread->id + (1u << THREAD_ID_VERSION_SHIFT)) | ||
108 | & THREAD_ID_VERSION_MASK; | ||
109 | |||
110 | if (version == 0) | ||
111 | version = 1u << THREAD_ID_VERSION_SHIFT; | ||
112 | |||
113 | thread->id = version | (slot_num & THREAD_ID_SLOT_MASK); | ||
114 | } | ||
115 | |||
116 | static struct thread_entry * find_empty_thread_slot(void) | ||
117 | { | ||
118 | struct thread_entry *thread = NULL; | ||
119 | int n; | ||
120 | |||
121 | for (n = 0; n < MAXTHREADS; n++) | ||
122 | { | ||
123 | int state = threads[n].state; | ||
124 | |||
125 | if (state == STATE_KILLED) | ||
126 | { | ||
127 | thread = &threads[n]; | ||
128 | break; | ||
129 | } | ||
130 | } | ||
131 | |||
132 | return thread; | ||
133 | } | ||
134 | |||
135 | |||
136 | /* Initialize SDL threading */ | ||
137 | void init_threads(void) | ||
138 | { | ||
139 | struct thread_entry *thread; | ||
140 | int n; | ||
141 | |||
142 | memset(cores, 0, sizeof(cores)); | ||
143 | memset(threads, 0, sizeof(threads)); | ||
144 | |||
145 | m = SDL_CreateMutex(); | ||
146 | |||
147 | if (SDL_LockMutex(m) == -1) | ||
148 | { | ||
149 | fprintf(stderr, "Couldn't lock mutex\n"); | ||
150 | return; | ||
151 | } | ||
152 | |||
153 | /* Initialize all IDs */ | ||
154 | for (n = 0; n < MAXTHREADS; n++) | ||
155 | threads[n].id = THREAD_ID_INIT(n); | ||
156 | |||
157 | /* Slot 0 is reserved for the main thread - initialize it here and | ||
158 | then create the SDL thread - it is possible to have a quick, early | ||
159 | shutdown try to access the structure. */ | ||
160 | thread = &threads[0]; | ||
161 | thread->stack = (uintptr_t *)" "; | ||
162 | thread->stack_size = 8; | ||
163 | thread->name = "main"; | ||
164 | thread->state = STATE_RUNNING; | ||
165 | thread->context.s = SDL_CreateSemaphore(0); | ||
166 | thread->context.t = NULL; /* NULL for the implicit main thread */ | ||
167 | cores[CURRENT_CORE].running = thread; | ||
168 | |||
169 | if (thread->context.s == NULL) | ||
170 | { | ||
171 | fprintf(stderr, "Failed to create main semaphore\n"); | ||
172 | return; | ||
173 | } | ||
174 | |||
175 | THREAD_SDL_DEBUGF("Main thread: %p\n", thread); | ||
176 | |||
177 | return; | ||
178 | } | ||
179 | |||
180 | void sim_thread_exception_wait(void) | ||
181 | { | ||
182 | while (1) | ||
183 | { | ||
184 | SDL_Delay(HZ/10); | ||
185 | if (threads_exit) | ||
186 | thread_exit(); | ||
187 | } | ||
188 | } | ||
189 | |||
190 | /* A way to yield and leave the threading system for extended periods */ | ||
191 | void sim_thread_lock(void *me) | ||
192 | { | ||
193 | SDL_LockMutex(m); | ||
194 | cores[CURRENT_CORE].running = (struct thread_entry *)me; | ||
195 | |||
196 | if (threads_exit) | ||
197 | thread_exit(); | ||
198 | } | ||
199 | |||
200 | void * sim_thread_unlock(void) | ||
201 | { | ||
202 | struct thread_entry *current = cores[CURRENT_CORE].running; | ||
203 | SDL_UnlockMutex(m); | ||
204 | return current; | ||
205 | } | ||
206 | |||
207 | struct thread_entry * thread_id_entry(unsigned int thread_id) | ||
208 | { | ||
209 | return (thread_id == THREAD_ID_CURRENT) ? | ||
210 | cores[CURRENT_CORE].running : | ||
211 | &threads[thread_id & THREAD_ID_SLOT_MASK]; | ||
212 | } | ||
213 | |||
214 | static void add_to_list_l(struct thread_entry **list, | ||
215 | struct thread_entry *thread) | ||
216 | { | ||
217 | if (*list == NULL) | ||
218 | { | ||
219 | /* Insert into unoccupied list */ | ||
220 | thread->l.next = thread; | ||
221 | thread->l.prev = thread; | ||
222 | *list = thread; | ||
223 | } | ||
224 | else | ||
225 | { | ||
226 | /* Insert last */ | ||
227 | thread->l.next = *list; | ||
228 | thread->l.prev = (*list)->l.prev; | ||
229 | thread->l.prev->l.next = thread; | ||
230 | (*list)->l.prev = thread; | ||
231 | } | ||
232 | } | ||
233 | |||
234 | static void remove_from_list_l(struct thread_entry **list, | ||
235 | struct thread_entry *thread) | ||
236 | { | ||
237 | if (thread == thread->l.next) | ||
238 | { | ||
239 | /* The only item */ | ||
240 | *list = NULL; | ||
241 | return; | ||
242 | } | ||
243 | |||
244 | if (thread == *list) | ||
245 | { | ||
246 | /* List becomes next item */ | ||
247 | *list = thread->l.next; | ||
248 | } | ||
249 | |||
250 | /* Fix links to jump over the removed entry. */ | ||
251 | thread->l.prev->l.next = thread->l.next; | ||
252 | thread->l.next->l.prev = thread->l.prev; | ||
253 | } | ||
254 | |||
255 | unsigned int thread_get_current(void) | ||
256 | { | ||
257 | return cores[CURRENT_CORE].running->id; | ||
258 | } | ||
259 | |||
260 | void switch_thread(void) | ||
261 | { | ||
262 | struct thread_entry *current = cores[CURRENT_CORE].running; | ||
263 | |||
264 | enable_irq(); | ||
265 | |||
266 | switch (current->state) | ||
267 | { | ||
268 | case STATE_RUNNING: | ||
269 | { | ||
270 | SDL_UnlockMutex(m); | ||
271 | /* Any other thread waiting already will get it first */ | ||
272 | SDL_LockMutex(m); | ||
273 | break; | ||
274 | } /* STATE_RUNNING: */ | ||
275 | |||
276 | case STATE_BLOCKED: | ||
277 | { | ||
278 | int oldlevel; | ||
279 | |||
280 | SDL_UnlockMutex(m); | ||
281 | SDL_SemWait(current->context.s); | ||
282 | SDL_LockMutex(m); | ||
283 | |||
284 | oldlevel = disable_irq_save(); | ||
285 | current->state = STATE_RUNNING; | ||
286 | restore_irq(oldlevel); | ||
287 | break; | ||
288 | } /* STATE_BLOCKED: */ | ||
289 | |||
290 | case STATE_BLOCKED_W_TMO: | ||
291 | { | ||
292 | int result, oldlevel; | ||
293 | |||
294 | SDL_UnlockMutex(m); | ||
295 | result = SDL_SemWaitTimeout(current->context.s, current->tmo_tick); | ||
296 | SDL_LockMutex(m); | ||
297 | |||
298 | oldlevel = disable_irq_save(); | ||
299 | |||
300 | if (current->state == STATE_BLOCKED_W_TMO) | ||
301 | { | ||
302 | /* Timed out */ | ||
303 | remove_from_list_l(current->bqp, current); | ||
304 | |||
305 | #ifdef HAVE_WAKEUP_EXT_CB | ||
306 | if (current->wakeup_ext_cb != NULL) | ||
307 | current->wakeup_ext_cb(current); | ||
308 | #endif | ||
309 | current->state = STATE_RUNNING; | ||
310 | } | ||
311 | |||
312 | if (result == SDL_MUTEX_TIMEDOUT) | ||
313 | { | ||
314 | /* Other signals from an explicit wake could have been made before | ||
315 | * arriving here if we timed out waiting for the semaphore. Make | ||
316 | * sure the count is reset. */ | ||
317 | while (SDL_SemValue(current->context.s) > 0) | ||
318 | SDL_SemTryWait(current->context.s); | ||
319 | } | ||
320 | |||
321 | restore_irq(oldlevel); | ||
322 | break; | ||
323 | } /* STATE_BLOCKED_W_TMO: */ | ||
324 | |||
325 | case STATE_SLEEPING: | ||
326 | { | ||
327 | SDL_UnlockMutex(m); | ||
328 | SDL_SemWaitTimeout(current->context.s, current->tmo_tick); | ||
329 | SDL_LockMutex(m); | ||
330 | current->state = STATE_RUNNING; | ||
331 | break; | ||
332 | } /* STATE_SLEEPING: */ | ||
333 | } | ||
334 | |||
335 | cores[CURRENT_CORE].running = current; | ||
336 | |||
337 | if (threads_exit) | ||
338 | thread_exit(); | ||
339 | } | ||
340 | |||
341 | void sleep_thread(int ticks) | ||
342 | { | ||
343 | struct thread_entry *current = cores[CURRENT_CORE].running; | ||
344 | int rem; | ||
345 | |||
346 | current->state = STATE_SLEEPING; | ||
347 | |||
348 | rem = (SDL_GetTicks() - start_tick) % (1000/HZ); | ||
349 | if (rem < 0) | ||
350 | rem = 0; | ||
351 | |||
352 | current->tmo_tick = (1000/HZ) * ticks + ((1000/HZ)-1) - rem; | ||
353 | } | ||
354 | |||
355 | void block_thread(struct thread_entry *current) | ||
356 | { | ||
357 | current->state = STATE_BLOCKED; | ||
358 | add_to_list_l(current->bqp, current); | ||
359 | } | ||
360 | |||
361 | void block_thread_w_tmo(struct thread_entry *current, int ticks) | ||
362 | { | ||
363 | current->state = STATE_BLOCKED_W_TMO; | ||
364 | current->tmo_tick = (1000/HZ)*ticks; | ||
365 | add_to_list_l(current->bqp, current); | ||
366 | } | ||
367 | |||
368 | unsigned int wakeup_thread(struct thread_entry **list) | ||
369 | { | ||
370 | struct thread_entry *thread = *list; | ||
371 | |||
372 | if (thread != NULL) | ||
373 | { | ||
374 | switch (thread->state) | ||
375 | { | ||
376 | case STATE_BLOCKED: | ||
377 | case STATE_BLOCKED_W_TMO: | ||
378 | remove_from_list_l(list, thread); | ||
379 | thread->state = STATE_RUNNING; | ||
380 | SDL_SemPost(thread->context.s); | ||
381 | return THREAD_OK; | ||
382 | } | ||
383 | } | ||
384 | |||
385 | return THREAD_NONE; | ||
386 | } | ||
387 | |||
388 | unsigned int thread_queue_wake(struct thread_entry **list) | ||
389 | { | ||
390 | unsigned int result = THREAD_NONE; | ||
391 | |||
392 | for (;;) | ||
393 | { | ||
394 | unsigned int rc = wakeup_thread(list); | ||
395 | |||
396 | if (rc == THREAD_NONE) | ||
397 | break; | ||
398 | |||
399 | result |= rc; | ||
400 | } | ||
401 | |||
402 | return result; | ||
403 | } | ||
404 | |||
405 | void thread_thaw(unsigned int thread_id) | ||
406 | { | ||
407 | struct thread_entry *thread = thread_id_entry(thread_id); | ||
408 | |||
409 | if (thread->id == thread_id && thread->state == STATE_FROZEN) | ||
410 | { | ||
411 | thread->state = STATE_RUNNING; | ||
412 | SDL_SemPost(thread->context.s); | ||
413 | } | ||
414 | } | ||
415 | |||
416 | int runthread(void *data) | ||
417 | { | ||
418 | struct thread_entry *current; | ||
419 | jmp_buf *current_jmpbuf; | ||
420 | |||
421 | /* Cannot access thread variables before locking the mutex as the | ||
422 | data structures may not be filled-in yet. */ | ||
423 | SDL_LockMutex(m); | ||
424 | cores[CURRENT_CORE].running = (struct thread_entry *)data; | ||
425 | current = cores[CURRENT_CORE].running; | ||
426 | current_jmpbuf = &thread_jmpbufs[current - threads]; | ||
427 | |||
428 | /* Setup jump for exit */ | ||
429 | if (setjmp(*current_jmpbuf) == 0) | ||
430 | { | ||
431 | /* Run the thread routine */ | ||
432 | if (current->state == STATE_FROZEN) | ||
433 | { | ||
434 | SDL_UnlockMutex(m); | ||
435 | SDL_SemWait(current->context.s); | ||
436 | SDL_LockMutex(m); | ||
437 | cores[CURRENT_CORE].running = current; | ||
438 | } | ||
439 | |||
440 | if (!threads_exit) | ||
441 | { | ||
442 | current->context.start(); | ||
443 | THREAD_SDL_DEBUGF("Thread Done: %d (%s)\n", | ||
444 | current - threads, THREAD_SDL_GET_NAME(current)); | ||
445 | /* Thread routine returned - suicide */ | ||
446 | } | ||
447 | |||
448 | thread_exit(); | ||
449 | } | ||
450 | else | ||
451 | { | ||
452 | /* Unlock and exit */ | ||
453 | SDL_UnlockMutex(m); | ||
454 | } | ||
455 | |||
456 | return 0; | ||
457 | } | ||
458 | |||
459 | unsigned int create_thread(void (*function)(void), | ||
460 | void* stack, size_t stack_size, | ||
461 | unsigned flags, const char *name) | ||
462 | { | ||
463 | struct thread_entry *thread; | ||
464 | SDL_Thread* t; | ||
465 | SDL_sem *s; | ||
466 | |||
467 | THREAD_SDL_DEBUGF("Creating thread: (%s)\n", name ? name : ""); | ||
468 | |||
469 | thread = find_empty_thread_slot(); | ||
470 | if (thread == NULL) | ||
471 | { | ||
472 | DEBUGF("Failed to find thread slot\n"); | ||
473 | return 0; | ||
474 | } | ||
475 | |||
476 | s = SDL_CreateSemaphore(0); | ||
477 | if (s == NULL) | ||
478 | { | ||
479 | DEBUGF("Failed to create semaphore\n"); | ||
480 | return 0; | ||
481 | } | ||
482 | |||
483 | t = SDL_CreateThread(runthread, thread); | ||
484 | if (t == NULL) | ||
485 | { | ||
486 | DEBUGF("Failed to create SDL thread\n"); | ||
487 | SDL_DestroySemaphore(s); | ||
488 | return 0; | ||
489 | } | ||
490 | |||
491 | thread->stack = stack; | ||
492 | thread->stack_size = stack_size; | ||
493 | thread->name = name; | ||
494 | thread->state = (flags & CREATE_THREAD_FROZEN) ? | ||
495 | STATE_FROZEN : STATE_RUNNING; | ||
496 | thread->context.start = function; | ||
497 | thread->context.t = t; | ||
498 | thread->context.s = s; | ||
499 | |||
500 | THREAD_SDL_DEBUGF("New Thread: %d (%s)\n", | ||
501 | thread - threads, THREAD_SDL_GET_NAME(thread)); | ||
502 | |||
503 | return thread->id; | ||
504 | } | ||
505 | |||
506 | #ifndef ALLOW_REMOVE_THREAD | ||
507 | static void remove_thread(unsigned int thread_id) | ||
508 | #else | ||
509 | void remove_thread(unsigned int thread_id) | ||
510 | #endif | ||
511 | { | ||
512 | struct thread_entry *current = cores[CURRENT_CORE].running; | ||
513 | struct thread_entry *thread = thread_id_entry(thread_id); | ||
514 | |||
515 | SDL_Thread *t; | ||
516 | SDL_sem *s; | ||
517 | |||
518 | if (thread_id != THREAD_ID_CURRENT && thread->id != thread_id) | ||
519 | return; | ||
520 | |||
521 | int oldlevel = disable_irq_save(); | ||
522 | |||
523 | t = thread->context.t; | ||
524 | s = thread->context.s; | ||
525 | thread->context.t = NULL; | ||
526 | |||
527 | if (thread != current) | ||
528 | { | ||
529 | switch (thread->state) | ||
530 | { | ||
531 | case STATE_BLOCKED: | ||
532 | case STATE_BLOCKED_W_TMO: | ||
533 | /* Remove thread from object it's waiting on */ | ||
534 | remove_from_list_l(thread->bqp, thread); | ||
535 | |||
536 | #ifdef HAVE_WAKEUP_EXT_CB | ||
537 | if (thread->wakeup_ext_cb != NULL) | ||
538 | thread->wakeup_ext_cb(thread); | ||
539 | #endif | ||
540 | break; | ||
541 | } | ||
542 | |||
543 | SDL_SemPost(s); | ||
544 | } | ||
545 | |||
546 | THREAD_SDL_DEBUGF("Removing thread: %d (%s)\n", | ||
547 | thread - threads, THREAD_SDL_GET_NAME(thread)); | ||
548 | |||
549 | new_thread_id(thread->id, thread); | ||
550 | thread->state = STATE_KILLED; | ||
551 | thread_queue_wake(&thread->queue); | ||
552 | |||
553 | SDL_DestroySemaphore(s); | ||
554 | |||
555 | if (thread == current) | ||
556 | { | ||
557 | /* Do a graceful exit - perform the longjmp back into the thread | ||
558 | function to return */ | ||
559 | restore_irq(oldlevel); | ||
560 | longjmp(thread_jmpbufs[current - threads], 1); | ||
561 | } | ||
562 | |||
563 | SDL_KillThread(t); | ||
564 | restore_irq(oldlevel); | ||
565 | } | ||
566 | |||
567 | void thread_exit(void) | ||
568 | { | ||
569 | remove_thread(THREAD_ID_CURRENT); | ||
570 | } | ||
571 | |||
572 | void thread_wait(unsigned int thread_id) | ||
573 | { | ||
574 | struct thread_entry *current = cores[CURRENT_CORE].running; | ||
575 | struct thread_entry *thread = thread_id_entry(thread_id); | ||
576 | |||
577 | if (thread_id == THREAD_ID_CURRENT || | ||
578 | (thread->id == thread_id && thread->state != STATE_KILLED)) | ||
579 | { | ||
580 | current->bqp = &thread->queue; | ||
581 | block_thread(current); | ||
582 | switch_thread(); | ||
583 | } | ||
584 | } | ||
585 | |||
586 | int thread_stack_usage(const struct thread_entry *thread) | ||
587 | { | ||
588 | return 50; | ||
589 | (void)thread; | ||
590 | } | ||
591 | |||
592 | /* Return name if one or ID if none */ | ||
593 | void thread_get_name(char *buffer, int size, | ||
594 | struct thread_entry *thread) | ||
595 | { | ||
596 | if (size <= 0) | ||
597 | return; | ||
598 | |||
599 | *buffer = '\0'; | ||
600 | |||
601 | if (thread) | ||
602 | { | ||
603 | /* Display thread name if one or ID if none */ | ||
604 | bool named = thread->name && *thread->name; | ||
605 | const char *fmt = named ? "%s" : "%08lX"; | ||
606 | intptr_t name = named ? | ||
607 | (intptr_t)thread->name : (intptr_t)thread; | ||
608 | snprintf(buffer, size, fmt, name); | ||
609 | } | ||
610 | } | ||