BII: High-precision timer backported from SS

This commit is contained in:
kanjitalk755 2023-05-02 18:05:09 +09:00
parent ef3f61c30d
commit 1199f8115e
4 changed files with 470 additions and 772 deletions

View File

@ -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

View File

@ -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();

View File

@ -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 <stdio.h>
#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 <pthread.h>
#include <semaphore.h>
#endif
#ifdef PRECISE_TIMING_MACH
#include <mach/mach.h>
#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; i<NUM_DESCS; i++)
if (!desc[i].in_use) {
desc[i].task = tm;
desc[i].in_use = true;
return i;
if (desc == tmDescList) {
tmDescList = desc->next;
} 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; i<NUM_DESCS; i++)
if (desc[i].in_use && desc[i].task == tm)
return i;
return -1;
TMDesc *desc = tmDescList;
while (desc) {
if (desc->task == 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<NUM_DESCS; i++)
free_desc(i);
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
}
@ -151,6 +291,26 @@ void TimerInit(void)
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
}
@ -160,9 +320,13 @@ void TimerExit(void)
void TimerReset(void)
{
// Mark all descriptors as inactive
for (int i=0; i<NUM_DESCS; i++)
free_desc(i);
TMDesc *desc = tmDescList;
while (desc) {
TMDesc *next = desc->next;
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; i<NUM_DESCS; i++)
if (desc[i].in_use) {
uint32 tm = desc[i].task;
if ((ReadMacInt16(tm + qType) & 0x8000) && timer_cmp_time(desc[i].wakeup, now) < 0) {
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);
// 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
}

View File

@ -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 <pthread.h>
#include <semaphore.h>
#endif
#ifdef PRECISE_TIMING_MACH
#include <mach/mach.h>
#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
}