--- SheepShaver/src/timer.cpp 2005/01/30 21:48:19 1.3 +++ SheepShaver/src/timer.cpp 2010/08/22 19:31:36 1.14 @@ -1,7 +1,7 @@ /* * timer.cpp - Time Manager emulation * - * SheepShaver (C) 1997-2005 Christian Bauer and Marc Hellwig + * SheepShaver (C) 1997-2008 Christian Bauer and Marc Hellwig * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,26 +18,25 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/* - * TODO: Prime(0) - */ - #include "sysdeps.h" #include "timer.h" #include "macos_util.h" #include "main.h" #include "cpu_emulation.h" +#ifdef PRECISE_TIMING_POSIX +#include +#include +#endif + +#ifdef PRECISE_TIMING_MACH +#include +#endif + #define DEBUG 0 #include "debug.h" -#if __BEOS__ -#define PRECISE_TIMING 1 -#else -#define PRECISE_TIMING 0 -#endif - #define TM_QUEUE 0 // Enable TMQueue management (doesn't work) @@ -54,58 +53,70 @@ enum { // TMTask struct struct TMDesc { uint32 task; // Mac address of associated TMTask tm_time_t wakeup; // Time this task is scheduled for execution - bool in_use; // Flag: descriptor in use + TMDesc *next; }; -const int NUM_DESCS = 64; // Maximum number of descriptors -static TMDesc desc[NUM_DESCS]; +static TMDesc *tmDescList; #if PRECISE_TIMING +#ifdef PRECISE_TIMING_BEOS static thread_id timer_thread = -1; static bool thread_active = true; -static volatile tm_time_t wakeup_time = 0x7fffffffffffffff; +static const tm_time_t wakeup_time_max = 0x7fffffffffffffff; +static volatile tm_time_t wakeup_time = wakeup_time_max; static sem_id wakeup_time_sem = -1; static int32 timer_func(void *arg); #endif +#ifdef PRECISE_TIMING_POSIX +static pthread_t timer_thread; +static bool timer_thread_active = false; +static volatile bool timer_thread_cancel = false; +static tm_time_t wakeup_time_max = { 0x7fffffff, 999999999 }; +static tm_time_t wakeup_time = wakeup_time_max; +static pthread_mutex_t wakeup_time_lock = PTHREAD_MUTEX_INITIALIZER; +static void *timer_func(void *arg); +#endif +#ifdef PRECISE_TIMING_MACH +static clock_serv_t system_clock; +static thread_act_t timer_thread; +static bool timer_thread_active = false; +static tm_time_t wakeup_time_max = { 0x7fffffff, 999999999 }; +static tm_time_t wakeup_time = wakeup_time_max; +static semaphore_t wakeup_time_sem; +static void *timer_func(void *arg); +#endif +#endif -/* - * Allocate descriptor for given TMTask in list - */ - -static int alloc_desc(uint32 tm) +inline static void free_desc(TMDesc *desc) { - // Search for first free descriptor - for (int i=0; inext; + } else { + for (TMDesc *d = tmDescList; d; d = d->next) { + if (d->next == desc) { + d->next = desc->next; + break; + } } - return -1; -} - - -/* - * Free descriptor in list - */ - -inline static void free_desc(int i) -{ - desc[i].in_use = false; + } + delete desc; } - /* * Find descriptor associated with given TMTask */ -inline static int find_desc(uint32 tm) +inline static TMDesc *find_desc(uint32 tm) { - for (int i=0; itask == tm) { + return desc; + } + desc = desc->next; + } + return NULL; } @@ -143,20 +154,133 @@ static void dequeue_tm(uint32 tm) /* + * Timer thread operations + */ + +#ifdef PRECISE_TIMING_POSIX +const int SIGSUSPEND = SIGRTMIN + 6; +const int SIGRESUME = SIGRTMIN + 7; +static struct sigaction sigsuspend_action; +static struct sigaction sigresume_action; + +static int suspend_count = 0; +static pthread_mutex_t suspend_count_lock = PTHREAD_MUTEX_INITIALIZER; +static sem_t suspend_ack_sem; +static sigset_t suspend_handler_mask; + +// Signal handler for suspended thread +static void sigsuspend_handler(int sig) +{ + sem_post(&suspend_ack_sem); + sigsuspend(&suspend_handler_mask); +} + +// Signal handler for resumed thread +static void sigresume_handler(int sig) +{ + /* simply trigger a signal to stop clock_nanosleep() */ +} + +// Initialize timer thread +static bool timer_thread_init(void) +{ + // Install suspend signal handler + sigemptyset(&sigsuspend_action.sa_mask); + sigaddset(&sigsuspend_action.sa_mask, SIGRESUME); + sigsuspend_action.sa_handler = sigsuspend_handler; + sigsuspend_action.sa_flags = SA_RESTART; +#ifdef HAVE_SIGNAL_SA_RESTORER + sigsuspend_action.sa_restorer = NULL; +#endif + if (sigaction(SIGSUSPEND, &sigsuspend_action, NULL) < 0) + return false; + + // Install resume signal handler + sigemptyset(&sigresume_action.sa_mask); + sigresume_action.sa_handler = sigresume_handler; + sigresume_action.sa_flags = SA_RESTART; +#ifdef HAVE_SIGNAL_SA_RESTORER + sigresume_action.sa_restorer = NULL; +#endif + if (sigaction(SIGRESUME, &sigresume_action, NULL) < 0) + return false; + + // Initialize semaphore + if (sem_init(&suspend_ack_sem, 0, 0) < 0) + return false; + + // Initialize suspend_handler_mask, it excludes SIGRESUME + if (sigfillset(&suspend_handler_mask) != 0) + return false; + if (sigdelset(&suspend_handler_mask, SIGRESUME) != 0) + return false; + + // Create thread in running state + suspend_count = 0; + return (pthread_create(&timer_thread, NULL, timer_func, NULL) == 0); +} + +// Kill timer thread +static void timer_thread_kill(void) +{ + timer_thread_cancel = true; +#ifdef HAVE_PTHREAD_CANCEL + pthread_cancel(timer_thread); +#endif + pthread_join(timer_thread, NULL); +} + +// Suspend timer thread +static void timer_thread_suspend(void) +{ + pthread_mutex_lock(&suspend_count_lock); + if (suspend_count == 0) { + suspend_count ++; + if (pthread_kill(timer_thread, SIGSUSPEND) == 0) + sem_wait(&suspend_ack_sem); + } + pthread_mutex_unlock(&suspend_count_lock); +} + +// Resume timer thread +static void timer_thread_resume(void) +{ + pthread_mutex_lock(&suspend_count_lock); + assert(suspend_count > 0); + if (suspend_count == 1) { + suspend_count = 0; + pthread_kill(timer_thread, SIGRESUME); + } + pthread_mutex_unlock(&suspend_count_lock); +} +#endif + + +/* * Initialize Time Manager */ void TimerInit(void) { - // Mark all descriptors as inactive - for (int i=0; i 0) { +#ifdef PRECISE_TIMING_BEOS status_t l; thread_active = false; suspend_thread(timer_thread); resume_thread(timer_thread); wait_for_thread(timer_thread, &l); delete_sem(wakeup_time_sem); +#endif +#ifdef PRECISE_TIMING_MACH + timer_thread_active = false; + semaphore_destroy(mach_task_self(), wakeup_time_sem); +#endif +#ifdef PRECISE_TIMING_POSIX + timer_thread_kill(); +#endif } #endif } @@ -187,9 +320,13 @@ void TimerExit(void) void TimerReset(void) { - // Mark all descriptors as inactive - for (int i=0; inext; + delete desc; + desc = desc->next; + } + tmDescList = NULL; } @@ -201,12 +338,13 @@ int16 InsTime(uint32 tm, uint16 trap) { D(bug("InsTime %08lx, trap %04x\n", tm, trap)); WriteMacInt16((uint32)tm + qType, ReadMacInt16((uint32)tm + qType) & 0x1fff | (trap << 4) & 0x6000); - if (find_desc(tm) >= 0) - printf("WARNING: InsTime(): Task re-inserted\n"); + if (find_desc(tm)) + printf("WARNING: InsTime(%08lx): Task re-inserted\n", tm); else { - int i = alloc_desc(tm); - if (i < 0) - printf("FATAL: InsTime(): No free Time Manager descriptor\n"); + TMDesc *desc = new TMDesc; + desc->task = tm; + desc->next = tmDescList; + tmDescList = desc; } return 0; } @@ -221,17 +359,25 @@ int16 RmvTime(uint32 tm) D(bug("RmvTime %08lx\n", tm)); // Find descriptor - int i = find_desc(tm); - if (i < 0) { + TMDesc *desc = find_desc(tm); + if (!desc) { printf("WARNING: RmvTime(%08lx): Descriptor not found\n", tm); return 0; } // Task active? -#if PRECISE_TIMING +#if PRECISE_TIMING_BEOS while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ; suspend_thread(timer_thread); #endif +#ifdef PRECISE_TIMING_MACH + semaphore_wait(wakeup_time_sem); + thread_suspend(timer_thread); +#endif +#if PRECISE_TIMING_POSIX + timer_thread_suspend(); + pthread_mutex_lock(&wakeup_time_lock); +#endif if (ReadMacInt16(tm + qType) & 0x8000) { // Yes, make task inactive and remove it from the Time Manager queue @@ -239,23 +385,22 @@ int16 RmvTime(uint32 tm) dequeue_tm(tm); #if PRECISE_TIMING // Look for next task to be called and set wakeup_time - wakeup_time = 0x7fffffffffffffff; - for (int j=0; jnext) + if ((ReadMacInt16(d->task + qType) & 0x8000)) + if (timer_cmp_time(d->wakeup, wakeup_time) < 0) + wakeup_time = d->wakeup; #endif // Compute remaining time tm_time_t remaining, current; timer_current_time(current); - timer_sub_time(remaining, desc[i].wakeup, current); + timer_sub_time(remaining, desc->wakeup, current); WriteMacInt32(tm + tmCount, timer_host2mac_time(remaining)); } else WriteMacInt32(tm + tmCount, 0); D(bug(" tmCount %ld\n", ReadMacInt32(tm + tmCount))); -#if PRECISE_TIMING +#if PRECISE_TIMING_BEOS release_sem(wakeup_time_sem); thread_info info; do { @@ -263,9 +408,19 @@ int16 RmvTime(uint32 tm) get_thread_info(timer_thread, &info); } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?) #endif +#ifdef PRECISE_TIMING_MACH + semaphore_signal(wakeup_time_sem); + thread_abort(timer_thread); + thread_resume(timer_thread); +#endif +#if PRECISE_TIMING_POSIX + pthread_mutex_unlock(&wakeup_time_lock); + timer_thread_resume(); + assert(suspend_count == 0); +#endif // Free descriptor - free_desc(i); + free_desc(desc); return 0; } @@ -279,9 +434,9 @@ int16 PrimeTime(uint32 tm, int32 time) D(bug("PrimeTime %08lx, time %ld\n", tm, time)); // Find descriptor - int i = find_desc(tm); - if (i < 0) { - printf("FATAL: PrimeTime(): Descriptor not found\n"); + TMDesc *desc = find_desc(tm); + if (!desc) { + printf("FATAL: PrimeTime(%08lx): Descriptor not found\n", tm); return 0; } @@ -295,24 +450,25 @@ int16 PrimeTime(uint32 tm, int32 time) // Yes, tmWakeUp set? if (ReadMacInt32(tm + tmWakeUp)) { - //!! PrimeTime(0) means continue previous delay - // (save wakeup time in RmvTime?) + // PrimeTime(0) can either mean (a) "the task runs as soon as interrupts are enabled" + // or (b) "continue previous delay" if an expired task was stopped via RmvTime() and + // then re-installed using InsXTime(). Since tmWakeUp was set, this is case (b). + // The remaining time was saved in tmCount by RmvTime(). if (time == 0) { - printf("FATAL: Unsupported PrimeTime(0)\n"); - return 0; + timer_mac2host_time(delay, ReadMacInt16(tm + tmCount)); } // Yes, calculate wakeup time relative to last scheduled time tm_time_t wakeup; - timer_add_time(wakeup, desc[i].wakeup, delay); - desc[i].wakeup = wakeup; + timer_add_time(wakeup, desc->wakeup, delay); + desc->wakeup = wakeup; } else { // No, calculate wakeup time relative to current time tm_time_t now; timer_current_time(now); - timer_add_time(desc[i].wakeup, now, delay); + timer_add_time(desc->wakeup, now, delay); } // Set tmWakeUp to indicate that task was scheduled @@ -323,24 +479,32 @@ int16 PrimeTime(uint32 tm, int32 time) // Not extended task, calculate wakeup time relative to current time tm_time_t now; timer_current_time(now); - timer_add_time(desc[i].wakeup, now, delay); + timer_add_time(desc->wakeup, now, delay); } // Make task active and enqueue it in the Time Manager queue -#if PRECISE_TIMING +#if PRECISE_TIMING_BEOS while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ; suspend_thread(timer_thread); #endif +#ifdef PRECISE_TIMING_MACH + semaphore_wait(wakeup_time_sem); + thread_suspend(timer_thread); +#endif +#if PRECISE_TIMING_POSIX + timer_thread_suspend(); + pthread_mutex_lock(&wakeup_time_lock); +#endif WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) | 0x8000); enqueue_tm(tm); #if PRECISE_TIMING // Look for next task to be called and set wakeup_time - wakeup_time = 0x7fffffffffffffff; - for (int j=0; jnext) + if ((ReadMacInt16(d->task + qType) & 0x8000)) + if (timer_cmp_time(d->wakeup, wakeup_time) < 0) + wakeup_time = d->wakeup; +#ifdef PRECISE_TIMING_BEOS release_sem(wakeup_time_sem); thread_info info; do { @@ -348,15 +512,26 @@ int16 PrimeTime(uint32 tm, int32 time) get_thread_info(timer_thread, &info); } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?) #endif +#ifdef PRECISE_TIMING_MACH + semaphore_signal(wakeup_time_sem); + thread_abort(timer_thread); + thread_resume(timer_thread); +#endif +#ifdef PRECISE_TIMING_POSIX + pthread_mutex_unlock(&wakeup_time_lock); + timer_thread_resume(); + assert(suspend_count == 0); +#endif +#endif return 0; } -#if PRECISE_TIMING /* * Time Manager thread */ +#ifdef PRECISE_TIMING_BEOS static int32 timer_func(void *arg) { while (thread_active) { @@ -378,6 +553,53 @@ static int32 timer_func(void *arg) } #endif +#ifdef PRECISE_TIMING_MACH +static void *timer_func(void *arg) +{ + timer_thread = mach_thread_self(); + timer_thread_active = true; + + while (timer_thread_active) { + clock_sleep(system_clock, TIME_ABSOLUTE, wakeup_time, NULL); + semaphore_wait(wakeup_time_sem); + + tm_time_t system_time; + + timer_current_time(system_time); + if (timer_cmp_time(wakeup_time, system_time) < 0) { + wakeup_time = wakeup_time_max; + SetInterruptFlag(INTFLAG_TIMER); + TriggerInterrupt(); + } + semaphore_signal(wakeup_time_sem); + } + return NULL; +} +#endif + +#ifdef PRECISE_TIMING_POSIX +static void *timer_func(void *arg) +{ + while (!timer_thread_cancel) { + // Wait until time specified by wakeup_time + clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &wakeup_time, NULL); + + tm_time_t system_time; + timer_current_time(system_time); + if (timer_cmp_time(wakeup_time, system_time) < 0) { + + // Timer expired, trigger interrupt + pthread_mutex_lock(&wakeup_time_lock); + wakeup_time = wakeup_time_max; + pthread_mutex_unlock(&wakeup_time_lock); + SetInterruptFlag(INTFLAG_TIMER); + TriggerInterrupt(); + } + } + return NULL; +} +#endif + /* * Timer interrupt function (executed as part of 60Hz interrupt) @@ -390,38 +612,50 @@ void TimerInterrupt(void) // Look for active TMTasks that have expired tm_time_t now; timer_current_time(now); - for (int i=0; inext; + uint32 tm = desc->task; + if ((ReadMacInt16(tm + qType) & 0x8000) && timer_cmp_time(desc->wakeup, now) <= 0) { + + // Found one, mark as inactive and remove it from the Time Manager queue + WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) & 0x7fff); + dequeue_tm(tm); + + // Call timer function + uint32 addr = ReadMacInt32(tm + tmAddr); + if (addr) { + D(bug("Calling TimeTask %08lx, addr %08lx\n", tm, addr)); + M68kRegisters r; + r.a[0] = addr; + r.a[1] = tm; + Execute68k(r.a[0], &r); + D(bug(" returned from TimeTask\n")); } } + desc = next; + } #if PRECISE_TIMING // Look for next task to be called and set wakeup_time +#if PRECISE_TIMING_BEOS while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ; suspend_thread(timer_thread); - wakeup_time = 0x7fffffffffffffff; - for (int j=0; jnext) + if ((ReadMacInt16(d->task + qType) & 0x8000)) + if (timer_cmp_time(d->wakeup, wakeup_time) < 0) + wakeup_time = d->wakeup; +#if PRECISE_TIMING_BEOS release_sem(wakeup_time_sem); thread_info info; do { @@ -429,4 +663,15 @@ void TimerInterrupt(void) get_thread_info(timer_thread, &info); } while (info.state == B_THREAD_SUSPENDED); // Sometimes, resume_thread() doesn't work (BeOS bug?) #endif +#if PRECISE_TIMING_MACH + semaphore_signal(wakeup_time_sem); + thread_abort(timer_thread); + thread_resume(timer_thread); +#endif +#if PRECISE_TIMING_POSIX + pthread_mutex_unlock(&wakeup_time_lock); + timer_thread_resume(); + assert(suspend_count == 0); +#endif +#endif }