diff --git a/BasiliskII/src/Unix/sysdeps.h b/BasiliskII/src/Unix/sysdeps.h index a4a7ed97..51f436c0 100644 --- a/BasiliskII/src/Unix/sysdeps.h +++ b/BasiliskII/src/Unix/sysdeps.h @@ -498,4 +498,13 @@ static inline uae_u32 do_byteswap_16(uae_u32 v) #define ALWAYS_INLINE inline __attribute__((always_inline)) #define memptr uint32 +// High-precision timing +#if defined(HAVE_PTHREADS) && defined(HAVE_CLOCK_NANOSLEEP) +#define PRECISE_TIMING 1 +#define PRECISE_TIMING_POSIX 1 +#elif defined(HAVE_PTHREADS) && defined(__MACH__) +#define PRECISE_TIMING 1 +#define PRECISE_TIMING_MACH 1 +#endif + #endif diff --git a/BasiliskII/src/emul_op.cpp b/BasiliskII/src/emul_op.cpp index 81d67323..c51aed21 100644 --- a/BasiliskII/src/emul_op.cpp +++ b/BasiliskII/src/emul_op.cpp @@ -462,7 +462,9 @@ void EmulOp(uint16 opcode, M68kRegisters *r) if (HasMacStarted()) { // Mac has started, execute all 60Hz interrupt functions +#if !PRECISE_TIMING TimerInterrupt(); +#endif VideoInterrupt(); // Call DoVBLTask(0) @@ -494,7 +496,12 @@ void EmulOp(uint16 opcode, M68kRegisters *r) ClearInterruptFlag(INTFLAG_ETHER); EtherInterrupt(); } - +#if PRECISE_TIMING + if (InterruptFlags & INTFLAG_TIMER) { + ClearInterruptFlag(INTFLAG_TIMER); + TimerInterrupt(); + } +#endif if (InterruptFlags & INTFLAG_AUDIO) { ClearInterruptFlag(INTFLAG_AUDIO); AudioInterrupt(); diff --git a/BasiliskII/src/timer.cpp b/BasiliskII/src/timer.cpp index 316d8d38..573abc77 100644 --- a/BasiliskII/src/timer.cpp +++ b/BasiliskII/src/timer.cpp @@ -1,7 +1,7 @@ /* * timer.cpp - Time Manager emulation * - * Basilisk II (C) 1997-2008 Christian Bauer + * Basilisk II (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,26 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/* - * SEE ALSO - * Inside Macintosh: Processes, chapter 3 "Time Manager" - * Technote 1063: "Inside Macintosh: Processes: Time Manager Addenda" - */ - -#include - #include "sysdeps.h" -#include "cpu_emulation.h" -#include "main.h" -#include "macos_util.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" -// Set this to 1 to enable TMQueue management (doesn't work) -#define TM_QUEUE 0 +#define TM_QUEUE 0 // Enable TMQueue management (doesn't work) // Definitions for Time Manager @@ -53,50 +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 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; + } + delete desc; } - -/* - * Free descriptor in list - */ - -inline static void free_desc(int i) -{ - desc[i].in_use = false; -} - - /* * 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; } @@ -133,15 +153,135 @@ 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 } @@ -160,9 +320,13 @@ void TimerExit(void) void TimerReset(void) { - // Mark all descriptors as inactive - for (int i=0; inext; + delete desc; + desc = next; + } + tmDescList = NULL; } @@ -174,12 +338,13 @@ int16 InsTime(uint32 tm, uint16 trap) { D(bug("InsTime %08lx, trap %04x\n", tm, trap)); WriteMacInt16(tm + qType, (ReadMacInt16(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(%08x): 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; } @@ -194,30 +359,68 @@ 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(%08x): Descriptor not found\n", tm); return 0; } // Task active? +#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 + pthread_mutex_lock(&wakeup_time_lock); + timer_thread_suspend(); +#endif if (ReadMacInt16(tm + qType) & 0x8000) { // Yes, make task inactive and remove it from the Time Manager queue WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) & 0x7fff); dequeue_tm(tm); +#if PRECISE_TIMING + // Look for next task to be called and set wakeup_time + wakeup_time = wakeup_time_max; + for (TMDesc *d = tmDescList; d; d = d->next) + 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 %d\n", ReadMacInt32(tm + tmCount))); +#if PRECISE_TIMING_BEOS + release_sem(wakeup_time_sem); + thread_info info; + do { + resume_thread(timer_thread); // This will unblock the thread + 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; } @@ -231,38 +434,41 @@ int16 PrimeTime(uint32 tm, int32 time) D(bug("PrimeTime %08x, time %d\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(%08x): Descriptor not found\n", tm); return 0; } + // Convert delay time + tm_time_t delay; + timer_mac2host_time(delay, time); + // Extended task? if (ReadMacInt16(tm + qType) & 0x4000) { - // Convert delay time - tm_time_t delay; - timer_mac2host_time(delay, time); - // Yes, tmWakeUp set? if (ReadMacInt32(tm + tmWakeUp)) { - //!! PrimeTime(0) means continue previous delay - // (save wakeup time in RmvTime?) - if (time == 0) - printf("WARNING: Unsupported PrimeTime(0)\n"); + // 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) { + 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 @@ -271,19 +477,130 @@ int16 PrimeTime(uint32 tm, int32 time) } else { // Not extended task, calculate wakeup time relative to current time - tm_time_t delay; - timer_mac2host_time(delay, time); - timer_current_time(desc[i].wakeup); - timer_add_time(desc[i].wakeup, desc[i].wakeup, delay); + tm_time_t now; + timer_current_time(now); + timer_add_time(desc->wakeup, now, delay); } // Make task active and enqueue it in the Time Manager queue +#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 + pthread_mutex_lock(&wakeup_time_lock); + timer_thread_suspend(); +#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 = wakeup_time_max; + for (TMDesc *d = tmDescList; d; d = d->next) + 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 { + resume_thread(timer_thread); // This will unblock the thread + 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; } +/* + * Time Manager thread + */ + +#ifdef PRECISE_TIMING_BEOS +static int32 timer_func(void *arg) +{ + while (thread_active) { + + // Wait until time specified by wakeup_time + snooze_until(wakeup_time, B_SYSTEM_TIMEBASE); + + while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ; + if (wakeup_time < system_time()) { + + // Timer expired, trigger interrupt + wakeup_time = 0x7fffffffffffffff; + SetInterruptFlag(INTFLAG_TIMER); + TriggerInterrupt(); + } + release_sem(wakeup_time_sem); + } + return 0; +} +#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) */ @@ -293,24 +610,66 @@ 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); + // 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(addr, &r); - } + // 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); +#endif +#if PRECISE_TIMING_MACH + semaphore_wait(wakeup_time_sem); + thread_suspend(timer_thread); +#endif +#if PRECISE_TIMING_POSIX + pthread_mutex_lock(&wakeup_time_lock); + timer_thread_suspend(); +#endif + wakeup_time = wakeup_time_max; + for (TMDesc *d = tmDescList; d; d = d->next) + 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 { + resume_thread(timer_thread); // This will unblock the thread + 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 } diff --git a/SheepShaver/src/timer.cpp b/SheepShaver/src/timer.cpp deleted file mode 100644 index ca733340..00000000 --- a/SheepShaver/src/timer.cpp +++ /dev/null @@ -1,677 +0,0 @@ -/* - * timer.cpp - Time Manager emulation - * - * 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 - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#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" - - -#define TM_QUEUE 0 // Enable TMQueue management (doesn't work) - - -// Definitions for Time Manager -enum { // TMTask struct - tmAddr = 6, - tmCount = 10, - tmWakeUp = 14, - tmReserved = 18 -}; - - -// Array of additional info for each installed TMTask -struct TMDesc { - uint32 task; // Mac address of associated TMTask - tm_time_t wakeup; // Time this task is scheduled for execution - TMDesc *next; -}; - -static TMDesc *tmDescList; - -#if PRECISE_TIMING -#ifdef PRECISE_TIMING_BEOS -static thread_id timer_thread = -1; -static bool thread_active = true; -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 - - -inline static void free_desc(TMDesc *desc) -{ - if (desc == tmDescList) { - tmDescList = desc->next; - } else { - for (TMDesc *d = tmDescList; d; d = d->next) { - if (d->next == desc) { - d->next = desc->next; - break; - } - } - } - delete desc; -} - -/* - * Find descriptor associated with given TMTask - */ - -inline static TMDesc *find_desc(uint32 tm) -{ - TMDesc *desc = tmDescList; - while (desc) { - if (desc->task == tm) { - return desc; - } - desc = desc->next; - } - return NULL; -} - - -/* - * Enqueue task in Time Manager queue - */ - -static void enqueue_tm(uint32 tm) -{ -#if TM_QUEUE - uint32 tm_var = ReadMacInt32(0xb30); - WriteMacInt32(tm + qLink, ReadMacInt32(tm_var)); - WriteMacInt32(tm_var, tm); -#endif -} - - -/* - * Remove task from Time Manager queue - */ - -static void dequeue_tm(uint32 tm) -{ -#if TM_QUEUE - uint32 p = ReadMacInt32(0xb30); - while (p) { - uint32 next = ReadMacInt32(p + qLink); - if (next == tm) { - WriteMacInt32(p + qLink, ReadMacInt32(next + qLink)); - return; - } - } -#endif -} - - -/* - * 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) -{ - TimerReset(); - -#if PRECISE_TIMING - // Start timer thread -#ifdef PRECISE_TIMING_BEOS - wakeup_time_sem = create_sem(1, "Wakeup Time"); - timer_thread = spawn_thread(timer_func, "Time Manager", B_REAL_TIME_PRIORITY, NULL); - resume_thread(timer_thread); -#elif PRECISE_TIMING_MACH - pthread_t pthread; - - host_get_clock_service(mach_host_self(), REALTIME_CLOCK, &system_clock); - semaphore_create(mach_task_self(), &wakeup_time_sem, SYNC_POLICY_FIFO, 1); - - pthread_create(&pthread, NULL, &timer_func, NULL); -#endif -#ifdef PRECISE_TIMING_POSIX - timer_thread_active = timer_thread_init(); -#endif -#endif -} - - -/* - * Exit Time Manager - */ - -void TimerExit(void) -{ -#if PRECISE_TIMING - // Quit timer thread - if (timer_thread > 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 -} - - -/* - * Emulator reset, remove all timer tasks - */ - -void TimerReset(void) -{ - TMDesc *desc = tmDescList; - while (desc) { - TMDesc *next = desc->next; - delete desc; - desc = next; - } - tmDescList = NULL; -} - - -/* - * Insert timer task - */ - -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)) - printf("WARNING: InsTime(%08x): Task re-inserted\n", tm); - else { - TMDesc *desc = new TMDesc; - desc->task = tm; - desc->next = tmDescList; - tmDescList = desc; - } - return 0; -} - - -/* - * Remove timer task - */ - -int16 RmvTime(uint32 tm) -{ - D(bug("RmvTime %08lx\n", tm)); - - // Find descriptor - TMDesc *desc = find_desc(tm); - if (!desc) { - printf("WARNING: RmvTime(%08x): Descriptor not found\n", tm); - return 0; - } - - // Task active? -#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 - pthread_mutex_lock(&wakeup_time_lock); - timer_thread_suspend(); -#endif - if (ReadMacInt16(tm + qType) & 0x8000) { - - // Yes, make task inactive and remove it from the Time Manager queue - WriteMacInt16(tm + qType, ReadMacInt16(tm + qType) & 0x7fff); - dequeue_tm(tm); -#if PRECISE_TIMING - // Look for next task to be called and set wakeup_time - wakeup_time = wakeup_time_max; - for (TMDesc *d = tmDescList; d; d = d->next) - 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->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_BEOS - release_sem(wakeup_time_sem); - thread_info info; - do { - resume_thread(timer_thread); // This will unblock the thread - 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(desc); - return 0; -} - - -/* - * Start timer task - */ - -int16 PrimeTime(uint32 tm, int32 time) -{ - D(bug("PrimeTime %08lx, time %ld\n", tm, time)); - - // Find descriptor - TMDesc *desc = find_desc(tm); - if (!desc) { - printf("FATAL: PrimeTime(%08x): Descriptor not found\n", tm); - return 0; - } - - // Convert delay time - tm_time_t delay; - timer_mac2host_time(delay, time); - - // Extended task? - if (ReadMacInt16(tm + qType) & 0x4000) { - - // Yes, tmWakeUp set? - if (ReadMacInt32(tm + tmWakeUp)) { - - // 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) { - 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->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->wakeup, now, delay); - } - - // Set tmWakeUp to indicate that task was scheduled - WriteMacInt32(tm + tmWakeUp, 0x12345678); - - } else { - - // Not extended task, calculate wakeup time relative to current time - tm_time_t now; - timer_current_time(now); - timer_add_time(desc->wakeup, now, delay); - } - - // Make task active and enqueue it in the Time Manager queue -#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 - pthread_mutex_lock(&wakeup_time_lock); - timer_thread_suspend(); -#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 = wakeup_time_max; - for (TMDesc *d = tmDescList; d; d = d->next) - 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 { - resume_thread(timer_thread); // This will unblock the thread - 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; -} - - -/* - * Time Manager thread - */ - -#ifdef PRECISE_TIMING_BEOS -static int32 timer_func(void *arg) -{ - while (thread_active) { - - // Wait until time specified by wakeup_time - snooze_until(wakeup_time, B_SYSTEM_TIMEBASE); - - while (acquire_sem(wakeup_time_sem) == B_INTERRUPTED) ; - if (wakeup_time < system_time()) { - - // Timer expired, trigger interrupt - wakeup_time = 0x7fffffffffffffff; - SetInterruptFlag(INTFLAG_TIMER); - TriggerInterrupt(); - } - release_sem(wakeup_time_sem); - } - return 0; -} -#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) - */ - -void TimerInterrupt(void) -{ -// D(bug("TimerIRQ\n")); - - // Look for active TMTasks that have expired - tm_time_t now; - timer_current_time(now); - TMDesc *desc = tmDescList; - while (desc) { - TMDesc *next = desc->next; - 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); -#endif -#if PRECISE_TIMING_MACH - semaphore_wait(wakeup_time_sem); - thread_suspend(timer_thread); -#endif -#if PRECISE_TIMING_POSIX - pthread_mutex_lock(&wakeup_time_lock); - timer_thread_suspend(); -#endif - wakeup_time = wakeup_time_max; - for (TMDesc *d = tmDescList; d; d = d->next) - 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 { - resume_thread(timer_thread); // This will unblock the thread - 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 -}