1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
25 #include <SDL_thread.h>
27 #include <string.h> /* memset() */
29 #include "system-sdl.h"
30 #include "thread-sdl.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; })
45 #define THREAD_SDL_DEBUGF(...)
46 #define THREAD_SDL_GET_NAME(thread)
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 */
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)
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
;
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
)
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
;
106 /* Wait for it to finish */
107 SDL_WaitThread(t
, NULL
);
108 /* Relock for next thread signal */
110 /* Already waited and exiting thread would have waited .told,
111 * replacing it with t. */
112 thread
->context
.told
= NULL
;
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
);
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
;
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
;
149 for (n
= 0; n
< MAXTHREADS
; n
++)
151 int state
= threads
[n
].state
;
153 if (state
== STATE_KILLED
)
155 thread
= &threads
[n
];
164 /* Initialize SDL threading */
165 void init_threads(void)
167 struct thread_entry
*thread
;
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");
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");
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
);
217 /* Set to 'COMMAND_DONE' when other rockbox threads have exited. */
218 while (threads_status
< THREADS_EXIT_COMMAND_DONE
)
223 /* We're the main thead - perform exit - doesn't return. */
227 void sim_thread_exception_wait(void)
232 if (threads_status
!= THREADS_RUN
)
237 /* A way to yield and leave the threading system for extended periods */
238 void sim_thread_lock(void *me
)
241 cores
[CURRENT_CORE
].running
= (struct thread_entry
*)me
;
243 if (threads_status
!= THREADS_RUN
)
247 void * sim_thread_unlock(void)
249 struct thread_entry
*current
= cores
[CURRENT_CORE
].running
;
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
)
264 /* Insert into unoccupied list */
265 thread
->l
.next
= thread
;
266 thread
->l
.prev
= thread
;
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
)
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
;
316 switch (current
->state
)
321 /* Any other thread waiting already will get it first */
324 } /* STATE_RUNNING: */
331 SDL_SemWait(current
->context
.s
);
334 oldlevel
= disable_irq_save();
335 current
->state
= STATE_RUNNING
;
336 restore_irq(oldlevel
);
338 } /* STATE_BLOCKED: */
340 case STATE_BLOCKED_W_TMO
:
342 int result
, oldlevel
;
345 result
= SDL_SemWaitTimeout(current
->context
.s
, current
->tmo_tick
);
348 oldlevel
= disable_irq_save();
350 if (current
->state
== STATE_BLOCKED_W_TMO
)
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
);
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
);
373 } /* STATE_BLOCKED_W_TMO: */
378 SDL_SemWaitTimeout(current
->context
.s
, current
->tmo_tick
);
380 current
->state
= STATE_RUNNING
;
382 } /* STATE_SLEEPING: */
385 cores
[CURRENT_CORE
].running
= current
;
387 if (threads_status
!= THREADS_RUN
)
391 void sleep_thread(int ticks
)
393 struct thread_entry
*current
= cores
[CURRENT_CORE
].running
;
396 current
->state
= STATE_SLEEPING
;
398 rem
= (SDL_GetTicks() - start_tick
) % (1000/HZ
);
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
;
424 switch (thread
->state
)
427 case STATE_BLOCKED_W_TMO
:
428 remove_from_list_l(list
, thread
);
429 thread
->state
= STATE_RUNNING
;
430 SDL_SemPost(thread
->context
.s
);
438 unsigned int thread_queue_wake(struct thread_entry
**list
)
440 unsigned int result
= THREAD_NONE
;
444 unsigned int rc
= wakeup_thread(list
);
446 if (rc
== THREAD_NONE
)
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. */
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
)
485 SDL_SemWait(current
->context
.s
);
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 */
502 /* Unlock and exit */
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
;
517 THREAD_SDL_DEBUGF("Creating thread: (%s)\n", name
? name
: "");
519 thread
= find_empty_thread_slot();
522 DEBUGF("Failed to find thread slot\n");
526 s
= SDL_CreateSemaphore(0);
529 DEBUGF("Failed to create semaphore\n");
533 t
= SDL_CreateThread(runthread
, thread
);
536 DEBUGF("Failed to create SDL thread\n");
537 SDL_DestroySemaphore(s
);
541 thread
->stack
= stack
;
542 thread
->stack_size
= stack_size
;
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
));
556 #ifndef ALLOW_REMOVE_THREAD
557 static void remove_thread(unsigned int thread_id
)
559 void remove_thread(unsigned int thread_id
)
562 struct thread_entry
*current
= cores
[CURRENT_CORE
].running
;
563 struct thread_entry
*thread
= thread_id_entry(thread_id
);
568 if (thread
->id
!= thread_id
)
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
)
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
);
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
;
630 restore_irq(oldlevel
);
633 void thread_exit(void)
635 unsigned int id
= thread_self();
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
);
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
);
656 int thread_stack_usage(const struct thread_entry
*thread
)
662 /* Return name if one or ID if none */
663 void thread_get_name(char *buffer
, int size
,
664 struct thread_entry
*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
);