2013-07-06 04:37:13 +00:00
|
|
|
/*
|
2015-10-22 05:13:26 +00:00
|
|
|
* Apple // emulator for *ix
|
2013-06-28 06:36:25 +00:00
|
|
|
*
|
2013-10-06 06:22:08 +00:00
|
|
|
* This software package is subject to the GNU General Public License
|
2015-10-22 05:13:26 +00:00
|
|
|
* version 3 or later (your choice) as published by the Free Software
|
2013-10-06 06:22:08 +00:00
|
|
|
* Foundation.
|
2013-06-28 06:36:25 +00:00
|
|
|
*
|
2015-10-22 05:13:26 +00:00
|
|
|
* Copyright 2013-2015 Aaron Culliney
|
2013-07-06 04:37:13 +00:00
|
|
|
*
|
2013-06-28 06:36:25 +00:00
|
|
|
*/
|
|
|
|
|
2014-01-23 04:42:34 +00:00
|
|
|
#include "common.h"
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2018-11-07 14:47:10 +00:00
|
|
|
#define DEBUG_TIMING 0 // enable to print timing stats
|
2015-01-11 20:12:51 +00:00
|
|
|
#if DEBUG_TIMING
|
|
|
|
# define TIMING_LOG(...) LOG(__VA_ARGS__)
|
|
|
|
#else
|
|
|
|
# define TIMING_LOG(...)
|
|
|
|
#endif
|
|
|
|
|
2018-11-20 19:32:28 +00:00
|
|
|
#define DISK_MOTOR_QUIET_NSECS (NANOSECONDS_PER_SECOND>2)
|
2015-01-11 20:12:51 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
// cycle counting
|
|
|
|
double cycles_persec_target = CLK_6502;
|
2016-07-24 00:34:59 +00:00
|
|
|
unsigned long cycles_count_total = 0; // Running at spec ~1MHz, this will approach overflow in ~4000secs (for 32bit architectures)
|
2018-07-30 00:12:23 +00:00
|
|
|
unsigned int cycles_video_frame = 0;
|
2015-01-31 21:57:10 +00:00
|
|
|
int cycles_speaker_feedback = 0;
|
2015-02-04 06:10:47 +00:00
|
|
|
static int32_t cycles_checkpoint_count = 0;
|
2013-10-06 08:31:58 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
// scaling and speed adjustments
|
|
|
|
static bool auto_adjust_speed = true;
|
2015-10-02 07:11:49 +00:00
|
|
|
static bool is_paused = false;
|
|
|
|
static unsigned long _pause_spinLock = 0;
|
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
double cpu_scale_factor = 1.0;
|
2013-10-06 08:31:58 +00:00
|
|
|
double cpu_altscale_factor = 1.0;
|
2015-01-31 21:57:10 +00:00
|
|
|
bool is_fullspeed = false;
|
2015-03-23 01:53:13 +00:00
|
|
|
bool alt_speed_enabled = false;
|
2013-12-21 21:55:40 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
// misc
|
2016-01-24 20:54:58 +00:00
|
|
|
static bool emul_reinitialize_audio = false;
|
2015-10-02 07:11:49 +00:00
|
|
|
static bool emul_pause_audio = false;
|
|
|
|
static bool emul_resume_audio = false;
|
2018-11-11 20:10:33 +00:00
|
|
|
static bool emul_video_dirty = false;
|
2015-09-11 07:00:04 +00:00
|
|
|
static bool cpu_shutting_down = false;
|
2014-03-30 17:57:56 +00:00
|
|
|
pthread_t cpu_thread_id = 0;
|
2016-01-10 19:54:49 +00:00
|
|
|
pthread_mutex_t interface_mutex = PTHREAD_MUTEX_INITIALIZER;
|
2015-02-16 16:46:29 +00:00
|
|
|
pthread_cond_t dbg_thread_cond = PTHREAD_COND_INITIALIZER;
|
2015-02-07 22:23:16 +00:00
|
|
|
pthread_cond_t cpu_thread_cond = PTHREAD_COND_INITIALIZER;
|
2013-11-06 06:11:27 +00:00
|
|
|
|
2013-06-28 06:36:25 +00:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
struct timespec timespec_diff(struct timespec start, struct timespec end, bool *negative) {
|
2013-06-28 06:36:25 +00:00
|
|
|
struct timespec t;
|
|
|
|
|
2013-12-29 19:37:05 +00:00
|
|
|
if (negative)
|
2013-10-06 06:22:08 +00:00
|
|
|
{
|
|
|
|
*negative = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if start > end, swizzle...
|
|
|
|
if ( (start.tv_sec > end.tv_sec) || ((start.tv_sec == end.tv_sec) && (start.tv_nsec > end.tv_nsec)) )
|
|
|
|
{
|
|
|
|
t=start;
|
|
|
|
start=end;
|
|
|
|
end=t;
|
|
|
|
if (negative)
|
|
|
|
{
|
|
|
|
*negative = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-28 06:36:25 +00:00
|
|
|
// assuming time_t is signed ...
|
2013-07-06 04:37:13 +00:00
|
|
|
if (end.tv_nsec < start.tv_nsec)
|
|
|
|
{
|
2013-06-28 06:36:25 +00:00
|
|
|
t.tv_sec = end.tv_sec - start.tv_sec - 1;
|
2013-10-06 06:22:08 +00:00
|
|
|
t.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec;
|
2013-07-06 04:37:13 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-06-28 06:36:25 +00:00
|
|
|
t.tv_sec = end.tv_sec - start.tv_sec;
|
|
|
|
t.tv_nsec = end.tv_nsec - start.tv_nsec;
|
|
|
|
}
|
|
|
|
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
2016-01-08 06:41:10 +00:00
|
|
|
struct timespec timespec_add(struct timespec start, unsigned long nsecs) {
|
2013-10-06 06:22:08 +00:00
|
|
|
|
|
|
|
start.tv_nsec += nsecs;
|
2015-01-31 21:57:10 +00:00
|
|
|
if (start.tv_nsec > NANOSECONDS_PER_SECOND)
|
2013-07-22 00:20:03 +00:00
|
|
|
{
|
2015-01-31 21:57:10 +00:00
|
|
|
start.tv_sec += (start.tv_nsec / NANOSECONDS_PER_SECOND);
|
|
|
|
start.tv_nsec %= NANOSECONDS_PER_SECOND;
|
2013-07-22 00:20:03 +00:00
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
|
|
|
return start;
|
2013-07-22 00:20:03 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
static void _timing_initialize(double scale) {
|
2016-04-16 21:08:00 +00:00
|
|
|
is_fullspeed = (scale > CPU_SCALE_FASTEST_PIVOT);
|
2015-01-31 21:57:10 +00:00
|
|
|
if (!is_fullspeed) {
|
|
|
|
cycles_persec_target = CLK_6502 * scale;
|
2013-10-06 08:31:58 +00:00
|
|
|
}
|
2015-01-31 21:57:10 +00:00
|
|
|
speaker_reset();
|
2015-07-12 20:00:39 +00:00
|
|
|
//TIMING_LOG("ClockRate:%0.2lf ClockCyclesPerSpeakerSample:%0.2lf", cycles_persec_target, speaker_cyclesPerSample());
|
2013-07-22 00:20:03 +00:00
|
|
|
}
|
|
|
|
|
2015-09-06 21:03:02 +00:00
|
|
|
#if !TESTING
|
|
|
|
static
|
|
|
|
#endif
|
|
|
|
void reinitialize(void) {
|
|
|
|
#if !TESTING
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_ON_CPU_THREAD();
|
2015-09-06 21:03:02 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
cycles_count_total = 0;
|
2018-07-30 00:12:23 +00:00
|
|
|
cycles_video_frame = 0;
|
|
|
|
#if !TEST_CPU
|
|
|
|
video_scannerReset();
|
|
|
|
#endif
|
|
|
|
|
2016-07-24 00:34:59 +00:00
|
|
|
#if TESTING
|
|
|
|
extern unsigned long (*testing_getCyclesCount)(void);
|
|
|
|
if (testing_getCyclesCount) {
|
|
|
|
cycles_count_total = testing_getCyclesCount();
|
|
|
|
}
|
|
|
|
#endif
|
2015-09-06 21:03:02 +00:00
|
|
|
|
2015-09-11 07:00:04 +00:00
|
|
|
vm_initialize();
|
2015-09-06 21:03:02 +00:00
|
|
|
|
|
|
|
cpu65_init();
|
|
|
|
|
|
|
|
timing_initialize();
|
|
|
|
|
|
|
|
MB_Reset();
|
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
void timing_initialize(void) {
|
2015-09-07 04:03:59 +00:00
|
|
|
#if !TESTING
|
2016-01-05 08:11:58 +00:00
|
|
|
assert(cpu_isPaused() || (pthread_self() == cpu_thread_id));
|
2015-09-07 04:03:59 +00:00
|
|
|
#endif
|
2015-01-31 21:57:10 +00:00
|
|
|
_timing_initialize(alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor);
|
2013-06-28 06:36:25 +00:00
|
|
|
}
|
|
|
|
|
2015-07-26 20:38:43 +00:00
|
|
|
void timing_toggleCPUSpeed(void) {
|
|
|
|
assert(cpu_isPaused() || (pthread_self() == cpu_thread_id));
|
2015-01-31 21:57:10 +00:00
|
|
|
alt_speed_enabled = !alt_speed_enabled;
|
|
|
|
timing_initialize();
|
2013-06-28 06:36:25 +00:00
|
|
|
}
|
|
|
|
|
2016-07-23 18:44:49 +00:00
|
|
|
static void timing_reinitializeAudio(void) {
|
2015-10-02 07:11:49 +00:00
|
|
|
SPINLOCK_ACQUIRE(&_pause_spinLock);
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_NOT_ON_CPU_THREAD();
|
2016-04-06 05:04:57 +00:00
|
|
|
#if !TESTING
|
2015-10-02 07:11:49 +00:00
|
|
|
assert(cpu_isPaused());
|
2016-04-06 05:04:57 +00:00
|
|
|
#endif
|
2015-07-28 05:36:39 +00:00
|
|
|
emul_reinitialize_audio = true;
|
2015-10-02 07:11:49 +00:00
|
|
|
emul_pause_audio = false;
|
|
|
|
emul_resume_audio = false;
|
2018-11-11 20:10:33 +00:00
|
|
|
emul_video_dirty = false;
|
2015-10-02 07:11:49 +00:00
|
|
|
SPINLOCK_RELINQUISH(&_pause_spinLock);
|
2013-10-06 08:31:58 +00:00
|
|
|
}
|
2013-07-26 05:58:31 +00:00
|
|
|
|
2015-07-05 01:17:04 +00:00
|
|
|
void cpu_pause(void) {
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_NOT_ON_CPU_THREAD();
|
2015-07-28 05:36:39 +00:00
|
|
|
|
2015-10-02 07:11:49 +00:00
|
|
|
SPINLOCK_ACQUIRE(&_pause_spinLock);
|
|
|
|
do {
|
|
|
|
if (is_paused) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// CPU thread will be paused when it next tries to acquire interface_mutex
|
2015-10-21 03:51:21 +00:00
|
|
|
LOG("PAUSING CPU...");
|
2015-10-02 07:11:49 +00:00
|
|
|
if (!emul_reinitialize_audio) {
|
|
|
|
emul_pause_audio = true;
|
|
|
|
}
|
|
|
|
pthread_mutex_lock(&interface_mutex);
|
|
|
|
is_paused = true;
|
|
|
|
} while (0);
|
|
|
|
SPINLOCK_RELINQUISH(&_pause_spinLock);
|
2015-07-26 20:38:43 +00:00
|
|
|
}
|
|
|
|
|
2015-07-05 01:17:04 +00:00
|
|
|
void cpu_resume(void) {
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_NOT_ON_CPU_THREAD();
|
2015-07-26 20:38:43 +00:00
|
|
|
|
2015-10-02 07:11:49 +00:00
|
|
|
SPINLOCK_ACQUIRE(&_pause_spinLock);
|
|
|
|
do {
|
|
|
|
if (!is_paused) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// CPU thread will be unblocked to acquire interface_mutex
|
|
|
|
if (!emul_reinitialize_audio) {
|
|
|
|
emul_resume_audio = true;
|
2018-11-11 20:10:33 +00:00
|
|
|
emul_video_dirty = true;
|
2015-10-02 07:11:49 +00:00
|
|
|
}
|
2015-10-21 03:51:21 +00:00
|
|
|
LOG("RESUMING CPU...");
|
2015-10-02 07:11:49 +00:00
|
|
|
is_paused = false;
|
2018-11-11 20:10:33 +00:00
|
|
|
pthread_mutex_unlock(&interface_mutex);
|
2015-10-02 07:11:49 +00:00
|
|
|
} while (0);
|
|
|
|
SPINLOCK_RELINQUISH(&_pause_spinLock);
|
2015-07-05 01:17:04 +00:00
|
|
|
}
|
|
|
|
|
2015-07-26 20:38:43 +00:00
|
|
|
bool cpu_isPaused(void) {
|
|
|
|
return is_paused;
|
|
|
|
}
|
|
|
|
|
2018-11-12 03:43:49 +00:00
|
|
|
#if TESTING
|
|
|
|
void timing_setVideoDirty(void) {
|
|
|
|
emul_video_dirty = true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2015-07-26 20:38:43 +00:00
|
|
|
bool timing_shouldAutoAdjustSpeed(void) {
|
2015-01-31 21:57:10 +00:00
|
|
|
double speed = alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor;
|
2016-04-16 21:08:00 +00:00
|
|
|
return auto_adjust_speed && (speed <= CPU_SCALE_FASTEST_PIVOT);
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-09-06 21:03:02 +00:00
|
|
|
static void *cpu_thread(void *dummyptr) {
|
2014-03-30 17:57:56 +00:00
|
|
|
|
2017-07-09 00:50:39 +00:00
|
|
|
#ifndef NDEBUG // Spamsung Galaxy Y running Gingerbread triggers this, wTf?!
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_ON_CPU_THREAD();
|
2017-07-09 00:50:39 +00:00
|
|
|
#endif
|
2014-03-30 17:57:56 +00:00
|
|
|
|
2015-07-26 20:38:43 +00:00
|
|
|
LOG("cpu_thread : initialized...");
|
2015-01-31 21:57:10 +00:00
|
|
|
|
2016-01-10 19:54:49 +00:00
|
|
|
struct timespec deltat = { 0 };
|
|
|
|
struct timespec disk_motor_time = { 0 };
|
|
|
|
struct timespec t0 = { 0 }; // the target timer
|
|
|
|
struct timespec ti = { 0 }; // actual before time sample
|
|
|
|
struct timespec tj = { 0 }; // actual after time sample
|
2013-10-06 06:22:08 +00:00
|
|
|
bool negative = false;
|
2014-03-22 18:24:57 +00:00
|
|
|
long drift_adj_nsecs = 0; // generic drift adjustment between target and actual
|
|
|
|
|
|
|
|
int debugging_cycles = 0;
|
2013-07-23 07:33:29 +00:00
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
unsigned long dbg_ticks = 0;
|
2016-01-17 19:59:34 +00:00
|
|
|
#if DEBUG_TIMING
|
2013-11-17 23:01:23 +00:00
|
|
|
int speaker_neg_feedback = 0;
|
|
|
|
int speaker_pos_feedback = 0;
|
2016-01-17 19:59:34 +00:00
|
|
|
unsigned long dbg_cycles_executed = 0;
|
2013-11-17 23:01:23 +00:00
|
|
|
#endif
|
2013-07-23 07:33:29 +00:00
|
|
|
|
2016-01-24 20:54:58 +00:00
|
|
|
audio_init();
|
|
|
|
speaker_init();
|
|
|
|
MB_Initialize();
|
|
|
|
|
2018-01-15 21:03:31 +00:00
|
|
|
run_args.emul_reinitialize = 1;
|
|
|
|
|
2016-10-02 02:49:59 +00:00
|
|
|
cpu_runloop:
|
2018-01-15 21:03:31 +00:00
|
|
|
do {
|
2017-08-20 06:05:58 +00:00
|
|
|
LOG("CPUTHREAD %lu LOCKING FOR MAYBE INITIALIZING AUDIO ...", (unsigned long)cpu_thread_id);
|
2015-09-13 21:24:17 +00:00
|
|
|
pthread_mutex_lock(&interface_mutex);
|
2015-07-28 05:36:39 +00:00
|
|
|
if (emul_reinitialize_audio) {
|
|
|
|
emul_reinitialize_audio = false;
|
|
|
|
|
2015-07-26 20:38:43 +00:00
|
|
|
speaker_destroy();
|
2016-01-24 20:54:58 +00:00
|
|
|
extern void MB_SoftDestroy(void);
|
|
|
|
MB_SoftDestroy();
|
2015-07-26 20:38:43 +00:00
|
|
|
audio_shutdown();
|
|
|
|
|
|
|
|
audio_init();
|
|
|
|
speaker_init();
|
2016-01-24 20:54:58 +00:00
|
|
|
extern void MB_SoftInitialize(void);
|
|
|
|
MB_SoftInitialize();
|
2015-07-26 20:38:43 +00:00
|
|
|
}
|
2015-09-13 21:24:17 +00:00
|
|
|
pthread_mutex_unlock(&interface_mutex);
|
2015-10-21 03:51:21 +00:00
|
|
|
LOG("UNLOCKING FOR MAYBE INITIALIZING AUDIO ...");
|
2015-07-28 05:36:39 +00:00
|
|
|
|
2018-01-15 21:03:31 +00:00
|
|
|
if (run_args.emul_reinitialize) {
|
2015-07-26 20:38:43 +00:00
|
|
|
reinitialize();
|
|
|
|
}
|
|
|
|
|
2013-10-07 04:01:00 +00:00
|
|
|
LOG("cpu_thread : begin main loop ...");
|
2013-10-06 06:22:08 +00:00
|
|
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &t0);
|
2013-11-06 06:11:27 +00:00
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
do {
|
2018-11-23 18:35:38 +00:00
|
|
|
////SCOPE_TRACE_CPU("CPU mainloop");
|
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
// -LOCK----------------------------------------------------------------------------------------- SAMPLE ti
|
2015-10-02 07:11:49 +00:00
|
|
|
if (UNLIKELY(emul_pause_audio)) {
|
|
|
|
emul_pause_audio = false;
|
|
|
|
audio_pause();
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
pthread_mutex_lock(&interface_mutex);
|
2015-10-02 07:11:49 +00:00
|
|
|
if (UNLIKELY(emul_resume_audio)) {
|
|
|
|
emul_resume_audio = false;
|
|
|
|
audio_resume();
|
|
|
|
}
|
2018-11-11 20:10:33 +00:00
|
|
|
if (UNLIKELY(emul_video_dirty)) {
|
|
|
|
emul_video_dirty = false;
|
|
|
|
video_setDirty(A2_DIRTY_FLAG);
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ti);
|
|
|
|
|
|
|
|
deltat = timespec_diff(t0, ti, &negative);
|
2018-01-15 21:03:31 +00:00
|
|
|
if (deltat.tv_sec) {
|
2015-01-31 21:57:10 +00:00
|
|
|
if (!is_fullspeed) {
|
|
|
|
TIMING_LOG("NOTE : serious divergence from target time ...");
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
t0 = ti;
|
|
|
|
deltat = timespec_diff(t0, ti, &negative);
|
|
|
|
}
|
|
|
|
t0 = timespec_add(t0, EXECUTION_PERIOD_NSECS); // expected interval
|
|
|
|
drift_adj_nsecs = negative ? ~deltat.tv_nsec : deltat.tv_nsec;
|
|
|
|
|
|
|
|
// set up increment & decrement counters
|
2018-01-15 21:03:31 +00:00
|
|
|
run_args.cpu65_cycles_to_execute = (cycles_persec_target / 1000); // cycles_persec_target * EXECUTION_PERIOD_NSECS / NANOSECONDS_PER_SECOND
|
2015-01-31 21:57:10 +00:00
|
|
|
if (!is_fullspeed) {
|
2018-01-15 21:03:31 +00:00
|
|
|
run_args.cpu65_cycles_to_execute += cycles_speaker_feedback;
|
2015-01-31 21:57:10 +00:00
|
|
|
}
|
2018-01-15 21:03:31 +00:00
|
|
|
if (run_args.cpu65_cycles_to_execute < 0) {
|
|
|
|
run_args.cpu65_cycles_to_execute = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2013-11-06 06:11:27 +00:00
|
|
|
MB_StartOfCpuExecute();
|
2014-03-22 18:24:57 +00:00
|
|
|
if (is_debugging) {
|
2018-01-15 21:03:31 +00:00
|
|
|
debugging_cycles = run_args.cpu65_cycles_to_execute;
|
2014-03-22 18:24:57 +00:00
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2014-03-22 18:24:57 +00:00
|
|
|
do {
|
|
|
|
if (is_debugging) {
|
2018-01-15 21:03:31 +00:00
|
|
|
run_args.cpu65_cycles_to_execute = 1;
|
2014-03-22 18:24:57 +00:00
|
|
|
}
|
|
|
|
|
2018-01-15 21:03:31 +00:00
|
|
|
run_args.cpu65_cycle_count = 0;
|
2015-01-31 21:57:10 +00:00
|
|
|
cycles_checkpoint_count = 0;
|
2018-01-15 21:03:31 +00:00
|
|
|
|
|
|
|
cpu65_run(&run_args); // run emulation for cpu65_cycles_to_execute cycles ...
|
|
|
|
|
2016-09-24 18:41:30 +00:00
|
|
|
#if DEBUG_TIMING
|
2018-01-15 21:03:31 +00:00
|
|
|
dbg_cycles_executed += run_args.cpu65_cycle_count;
|
2016-09-24 18:41:30 +00:00
|
|
|
#endif
|
2014-03-22 18:24:57 +00:00
|
|
|
|
|
|
|
if (is_debugging) {
|
2018-01-15 21:03:31 +00:00
|
|
|
debugging_cycles -= run_args.cpu65_cycle_count;
|
2018-04-07 18:32:52 +00:00
|
|
|
timing_checkpointCycles();
|
2016-09-24 18:41:30 +00:00
|
|
|
|
2019-03-24 22:50:16 +00:00
|
|
|
if (debugger_shouldBreak() || (debugging_cycles <= 0)) {
|
2014-03-22 18:24:57 +00:00
|
|
|
int err = 0;
|
2015-02-16 16:46:29 +00:00
|
|
|
if ((err = pthread_cond_signal(&dbg_thread_cond))) {
|
2017-07-16 00:34:43 +00:00
|
|
|
LOG("pthread_cond_signal : %d", err);
|
2014-03-22 18:24:57 +00:00
|
|
|
}
|
2014-03-30 18:17:12 +00:00
|
|
|
if ((err = pthread_cond_wait(&cpu_thread_cond, &interface_mutex))) {
|
2017-07-16 00:34:43 +00:00
|
|
|
LOG("pthread_cond_wait : %d", err);
|
2014-03-22 18:24:57 +00:00
|
|
|
}
|
|
|
|
if (debugging_cycles <= 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-09-24 18:41:30 +00:00
|
|
|
|
2018-01-15 21:03:31 +00:00
|
|
|
if (run_args.emul_reinitialize) {
|
2016-10-02 02:49:59 +00:00
|
|
|
pthread_mutex_unlock(&interface_mutex);
|
|
|
|
goto cpu_runloop;
|
2016-07-24 00:34:59 +00:00
|
|
|
}
|
2014-04-28 18:40:51 +00:00
|
|
|
}
|
2014-03-22 18:24:57 +00:00
|
|
|
} while (is_debugging);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2016-09-24 18:41:30 +00:00
|
|
|
MB_UpdateCycles();
|
2018-07-30 00:12:23 +00:00
|
|
|
// TODO : modularize MB and other peripheral card cycles/interrupts ...
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
speaker_flush(); // play audio
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2018-11-23 18:35:38 +00:00
|
|
|
TRACE_CPU_BEGIN("advance scanner");
|
2018-11-10 21:06:40 +00:00
|
|
|
video_scannerUpdate();
|
2018-11-23 18:35:38 +00:00
|
|
|
TRACE_CPU_END();
|
2013-11-06 06:11:27 +00:00
|
|
|
|
2013-10-06 06:22:08 +00:00
|
|
|
clock_gettime(CLOCK_MONOTONIC, &tj);
|
|
|
|
pthread_mutex_unlock(&interface_mutex);
|
|
|
|
// -UNLOCK--------------------------------------------------------------------------------------- SAMPLE tj
|
|
|
|
|
2018-11-20 19:32:28 +00:00
|
|
|
if (timing_shouldAutoAdjustSpeed() && !is_fullspeed) {
|
2015-01-31 21:57:10 +00:00
|
|
|
disk_motor_time = timespec_diff(disk6.motor_time, tj, &negative);
|
2018-11-07 14:47:10 +00:00
|
|
|
if (UNLIKELY(negative)) {
|
|
|
|
LOG("WHOA... time went backwards #1! Did you just cross a timezone?");
|
|
|
|
disk_motor_time.tv_sec = 1;
|
|
|
|
}
|
2018-11-20 19:32:28 +00:00
|
|
|
if (!speaker_isActive() && !video_isDirty(A2_DIRTY_FLAG) && (disk6.disk[disk6.drive].file_name != NULL) &&
|
|
|
|
!disk6.motor_off && (disk_motor_time.tv_sec || disk_motor_time.tv_nsec > DISK_MOTOR_QUIET_NSECS) )
|
2015-01-11 20:27:39 +00:00
|
|
|
{
|
|
|
|
TIMING_LOG("auto switching to full speed");
|
2015-01-31 21:57:10 +00:00
|
|
|
_timing_initialize(CPU_SCALE_FASTEST);
|
2015-01-11 20:27:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (!is_fullspeed) {
|
2015-01-11 20:12:51 +00:00
|
|
|
deltat = timespec_diff(ti, tj, &negative);
|
2018-11-07 14:47:10 +00:00
|
|
|
if (UNLIKELY(negative)) {
|
|
|
|
LOG("WHOA... time went backwards #2! Did you just cross a timezone?");
|
2016-05-06 04:06:52 +00:00
|
|
|
deltat.tv_sec = 1;
|
|
|
|
}
|
2015-01-11 20:12:51 +00:00
|
|
|
long sleepfor = 0;
|
2016-05-06 04:06:52 +00:00
|
|
|
if (LIKELY(!deltat.tv_sec))
|
2013-10-06 06:22:08 +00:00
|
|
|
{
|
2015-01-11 20:12:51 +00:00
|
|
|
sleepfor = EXECUTION_PERIOD_NSECS - drift_adj_nsecs - deltat.tv_nsec;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-11 20:12:51 +00:00
|
|
|
if (sleepfor <= 0)
|
|
|
|
{
|
|
|
|
// lagging ...
|
|
|
|
static time_t throttle_warning = 0;
|
|
|
|
if (t0.tv_sec - throttle_warning > 0)
|
|
|
|
{
|
2015-07-05 20:17:41 +00:00
|
|
|
TIMING_LOG("not sleeping to catch up ... %ld . %ld", deltat.tv_sec, deltat.tv_nsec);
|
2015-01-11 20:12:51 +00:00
|
|
|
throttle_warning = t0.tv_sec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
deltat.tv_sec = 0;
|
|
|
|
deltat.tv_nsec = sleepfor;
|
2018-11-23 18:35:38 +00:00
|
|
|
////TRACE_CPU_BEGIN("sleep");
|
2015-01-11 20:12:51 +00:00
|
|
|
nanosleep(&deltat, NULL);
|
2018-11-23 18:35:38 +00:00
|
|
|
////TRACE_CPU_END();
|
2015-01-11 20:12:51 +00:00
|
|
|
}
|
2013-11-17 23:01:23 +00:00
|
|
|
|
2015-01-11 20:12:51 +00:00
|
|
|
#if DEBUG_TIMING
|
|
|
|
// collect timing statistics
|
2015-01-31 21:57:10 +00:00
|
|
|
if (speaker_neg_feedback > cycles_speaker_feedback)
|
2015-01-11 20:12:51 +00:00
|
|
|
{
|
2015-01-31 21:57:10 +00:00
|
|
|
speaker_neg_feedback = cycles_speaker_feedback;
|
2015-01-11 20:12:51 +00:00
|
|
|
}
|
2015-01-31 21:57:10 +00:00
|
|
|
if (speaker_pos_feedback < cycles_speaker_feedback)
|
2015-01-11 20:12:51 +00:00
|
|
|
{
|
2015-01-31 21:57:10 +00:00
|
|
|
speaker_pos_feedback = cycles_speaker_feedback;
|
2015-01-11 20:12:51 +00:00
|
|
|
}
|
2013-11-17 23:01:23 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if ((dbg_ticks % NANOSECONDS_PER_SECOND) == 0)
|
2015-01-11 20:12:51 +00:00
|
|
|
{
|
2015-01-31 21:57:10 +00:00
|
|
|
TIMING_LOG("tick:(%ld.%ld) real:(%ld.%ld) cycles exe: %d ... speaker feedback: %d/%d", t0.tv_sec, t0.tv_nsec, ti.tv_sec, ti.tv_nsec, dbg_cycles_executed, speaker_neg_feedback, speaker_pos_feedback);
|
2015-01-11 20:12:51 +00:00
|
|
|
dbg_cycles_executed = 0;
|
|
|
|
speaker_neg_feedback = 0;
|
|
|
|
speaker_pos_feedback = 0;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
#endif
|
2016-01-24 19:03:50 +00:00
|
|
|
if ((dbg_ticks % NANOSECONDS_PER_SECOND) == 0) {
|
|
|
|
dbg_ticks = 0;
|
|
|
|
}
|
2015-01-11 20:12:51 +00:00
|
|
|
}
|
2015-01-31 21:57:10 +00:00
|
|
|
|
2018-11-20 19:32:28 +00:00
|
|
|
if (timing_shouldAutoAdjustSpeed() && is_fullspeed) {
|
|
|
|
disk_motor_time = timespec_diff(disk6.motor_time, tj, &negative);
|
|
|
|
if (UNLIKELY(negative)) {
|
|
|
|
LOG("WHOA... time went backwards #3! Did you just cross a timezone?");
|
|
|
|
disk_motor_time.tv_sec = 1;
|
|
|
|
}
|
|
|
|
if (speaker_isActive() || video_isDirty(A2_DIRTY_FLAG) ||
|
|
|
|
(disk6.motor_off && (disk_motor_time.tv_sec || disk_motor_time.tv_nsec > DISK_MOTOR_QUIET_NSECS)) )
|
2015-01-31 21:57:10 +00:00
|
|
|
{
|
|
|
|
double speed = alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor;
|
2016-04-16 21:08:00 +00:00
|
|
|
if (speed <= CPU_SCALE_FASTEST_PIVOT) {
|
2015-01-31 21:57:10 +00:00
|
|
|
TIMING_LOG("auto switching to configured speed");
|
|
|
|
_timing_initialize(speed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-05-31 19:59:26 +00:00
|
|
|
|
2018-01-15 21:03:31 +00:00
|
|
|
if (UNLIKELY(run_args.emul_reinitialize)) {
|
2015-05-31 19:59:26 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-07-26 20:38:43 +00:00
|
|
|
if (UNLIKELY(emul_reinitialize_audio)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-09-11 07:00:04 +00:00
|
|
|
if (UNLIKELY(cpu_shutting_down)) {
|
2015-05-31 19:59:26 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while (1);
|
|
|
|
|
2015-09-11 07:00:04 +00:00
|
|
|
if (UNLIKELY(cpu_shutting_down)) {
|
2015-05-31 19:59:26 +00:00
|
|
|
break;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
} while (1);
|
2014-09-17 03:39:08 +00:00
|
|
|
|
2015-05-31 19:59:26 +00:00
|
|
|
speaker_destroy();
|
|
|
|
MB_Destroy();
|
2015-06-14 22:05:44 +00:00
|
|
|
audio_shutdown();
|
2015-05-31 19:59:26 +00:00
|
|
|
|
2016-09-17 19:11:10 +00:00
|
|
|
cpu_thread_id = 0;
|
|
|
|
cpu_pause();
|
|
|
|
|
2016-09-17 19:24:47 +00:00
|
|
|
disk6_eject(0);
|
|
|
|
disk6_eject(1);
|
|
|
|
|
2014-09-17 03:39:08 +00:00
|
|
|
return NULL;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
2013-07-26 05:58:31 +00:00
|
|
|
|
2018-03-25 22:54:23 +00:00
|
|
|
bool timing_isCPUThread(void) {
|
|
|
|
return pthread_self() == cpu_thread_id;
|
|
|
|
}
|
|
|
|
|
2015-09-06 21:03:02 +00:00
|
|
|
void timing_startCPU(void) {
|
2015-09-11 07:00:04 +00:00
|
|
|
cpu_shutting_down = false;
|
2018-11-17 20:39:43 +00:00
|
|
|
assert(cpu_thread_id == 0);
|
2015-09-13 21:24:17 +00:00
|
|
|
int err = TEMP_FAILURE_RETRY(pthread_create(&cpu_thread_id, NULL, (void *)&cpu_thread, (void *)NULL));
|
2019-02-24 17:24:12 +00:00
|
|
|
assert(!err);
|
2015-09-06 21:03:02 +00:00
|
|
|
}
|
|
|
|
|
2015-09-11 07:00:04 +00:00
|
|
|
void timing_stopCPU(void) {
|
|
|
|
cpu_shutting_down = true;
|
|
|
|
|
|
|
|
LOG("Emulator waiting for CPU thread clean up...");
|
|
|
|
if (pthread_join(cpu_thread_id, NULL)) {
|
2017-07-16 00:34:43 +00:00
|
|
|
LOG("OOPS: pthread_join of CPU thread ...");
|
2015-09-11 07:00:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-07 18:32:52 +00:00
|
|
|
// Called when accurate global cycle count info is needed
|
|
|
|
void timing_checkpointCycles(void) {
|
2018-03-25 22:54:23 +00:00
|
|
|
ASSERT_ON_CPU_THREAD();
|
2015-02-07 22:23:16 +00:00
|
|
|
|
2018-01-15 21:03:31 +00:00
|
|
|
const int32_t d = run_args.cpu65_cycle_count - cycles_checkpoint_count;
|
2015-01-31 21:57:10 +00:00
|
|
|
assert(d >= 0);
|
2018-07-30 00:12:23 +00:00
|
|
|
cycles_video_frame += d;
|
2016-09-24 18:41:30 +00:00
|
|
|
#if !TESTING
|
|
|
|
cycles_count_total += d;
|
|
|
|
#else
|
2016-07-24 00:34:59 +00:00
|
|
|
unsigned long previous_cycles_count_total = cycles_count_total;
|
2015-01-31 21:57:10 +00:00
|
|
|
cycles_count_total += d;
|
2016-07-24 00:34:59 +00:00
|
|
|
if (UNLIKELY(cycles_count_total < previous_cycles_count_total)) {
|
|
|
|
extern void (*testing_cyclesOverflow)(void);
|
|
|
|
if (testing_cyclesOverflow) {
|
|
|
|
testing_cyclesOverflow();
|
|
|
|
}
|
2016-01-17 19:54:22 +00:00
|
|
|
}
|
|
|
|
#endif
|
2018-01-15 21:03:31 +00:00
|
|
|
cycles_checkpoint_count = run_args.cpu65_cycle_count;
|
2013-06-28 06:36:25 +00:00
|
|
|
}
|
2013-07-22 00:20:03 +00:00
|
|
|
|
2016-03-26 05:34:33 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2016-10-13 03:36:16 +00:00
|
|
|
bool timing_saveState(StateHelper_s *helper) {
|
|
|
|
bool saved = false;
|
|
|
|
int fd = helper->fd;
|
|
|
|
|
|
|
|
do {
|
2016-10-16 19:52:27 +00:00
|
|
|
uint32_t lVal = 0;
|
|
|
|
uint8_t serialized[4] = { 0 };
|
|
|
|
|
|
|
|
assert(cpu_scale_factor >= 0);
|
|
|
|
assert(cpu_altscale_factor >= 0);
|
|
|
|
|
2017-09-22 00:30:09 +00:00
|
|
|
lVal = (cpu_scale_factor * 100.);
|
2016-10-16 19:52:27 +00:00
|
|
|
serialized[0] = (uint8_t)((lVal & 0xFF000000) >> 24);
|
|
|
|
serialized[1] = (uint8_t)((lVal & 0xFF0000 ) >> 16);
|
|
|
|
serialized[2] = (uint8_t)((lVal & 0xFF00 ) >> 8);
|
|
|
|
serialized[3] = (uint8_t)((lVal & 0xFF ) >> 0);
|
|
|
|
if (!helper->save(fd, serialized, sizeof(serialized))) {
|
2016-10-13 03:36:16 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-09-22 00:30:09 +00:00
|
|
|
lVal = (cpu_altscale_factor * 100.);
|
2016-10-16 19:52:27 +00:00
|
|
|
serialized[0] = (uint8_t)((lVal & 0xFF000000) >> 24);
|
|
|
|
serialized[1] = (uint8_t)((lVal & 0xFF0000 ) >> 16);
|
|
|
|
serialized[2] = (uint8_t)((lVal & 0xFF00 ) >> 8);
|
|
|
|
serialized[3] = (uint8_t)((lVal & 0xFF ) >> 0);
|
|
|
|
if (!helper->save(fd, serialized, sizeof(serialized))) {
|
2016-10-13 03:36:16 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t bVal = alt_speed_enabled ? 1 : 0;
|
|
|
|
if (!helper->save(fd, &bVal, sizeof(bVal))) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
saved = true;
|
|
|
|
} while (0);
|
|
|
|
|
|
|
|
return saved;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool timing_loadState(StateHelper_s *helper) {
|
|
|
|
bool loaded = false;
|
|
|
|
int fd = helper->fd;
|
|
|
|
|
|
|
|
do {
|
2016-10-16 19:52:27 +00:00
|
|
|
uint32_t lVal = 0;
|
|
|
|
uint8_t serialized[4] = { 0 };
|
2016-10-13 03:36:16 +00:00
|
|
|
|
2016-10-16 19:52:27 +00:00
|
|
|
if (!helper->load(fd, serialized, sizeof(uint32_t))) {
|
2016-10-13 03:36:16 +00:00
|
|
|
break;
|
|
|
|
}
|
2016-10-16 19:52:27 +00:00
|
|
|
lVal = (uint32_t)(serialized[0] << 24);
|
|
|
|
lVal |= (uint32_t)(serialized[1] << 16);
|
|
|
|
lVal |= (uint32_t)(serialized[2] << 8);
|
|
|
|
lVal |= (uint32_t)(serialized[3] << 0);
|
2016-10-13 03:36:16 +00:00
|
|
|
cpu_scale_factor = lVal / 100.;
|
|
|
|
|
2016-10-16 19:52:27 +00:00
|
|
|
if (!helper->load(fd, serialized, sizeof(uint32_t))) {
|
2016-10-13 03:36:16 +00:00
|
|
|
break;
|
|
|
|
}
|
2016-10-16 19:52:27 +00:00
|
|
|
lVal = (uint32_t)(serialized[0] << 24);
|
|
|
|
lVal |= (uint32_t)(serialized[1] << 16);
|
|
|
|
lVal |= (uint32_t)(serialized[2] << 8);
|
|
|
|
lVal |= (uint32_t)(serialized[3] << 0);
|
2016-10-13 03:36:16 +00:00
|
|
|
cpu_altscale_factor = lVal / 100.;
|
|
|
|
|
|
|
|
uint8_t bVal = 0;
|
|
|
|
if (!helper->load(fd, &bVal, sizeof(bVal))) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
alt_speed_enabled = !!bVal;
|
|
|
|
|
|
|
|
timing_initialize();
|
|
|
|
|
|
|
|
loaded = true;
|
|
|
|
} while (0);
|
|
|
|
|
|
|
|
return loaded;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2018-11-07 14:47:10 +00:00
|
|
|
static void timing_prefsChanged(const char *domain) {
|
2016-04-14 02:45:55 +00:00
|
|
|
(void)domain;
|
|
|
|
|
2016-03-26 05:34:33 +00:00
|
|
|
float fVal = 1.0;
|
2016-04-06 05:04:57 +00:00
|
|
|
|
2016-04-14 02:45:55 +00:00
|
|
|
cpu_scale_factor = prefs_parseFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE, &fVal) ? fVal / 100.f : 1.f;
|
|
|
|
if (cpu_scale_factor < CPU_SCALE_SLOWEST) {
|
|
|
|
cpu_scale_factor = CPU_SCALE_SLOWEST;
|
|
|
|
}
|
2016-04-16 21:08:00 +00:00
|
|
|
if (cpu_scale_factor > CPU_SCALE_FASTEST_PIVOT) {
|
2016-04-14 02:45:55 +00:00
|
|
|
cpu_scale_factor = CPU_SCALE_FASTEST;
|
|
|
|
}
|
|
|
|
cpu_altscale_factor = prefs_parseFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, &fVal) ? fVal / 100.f : 1.f;
|
|
|
|
if (cpu_altscale_factor < CPU_SCALE_SLOWEST) {
|
|
|
|
cpu_altscale_factor = CPU_SCALE_SLOWEST;
|
|
|
|
}
|
2016-04-16 21:08:00 +00:00
|
|
|
if (cpu_altscale_factor > CPU_SCALE_FASTEST_PIVOT) {
|
2016-04-14 02:45:55 +00:00
|
|
|
cpu_altscale_factor = CPU_SCALE_FASTEST;
|
|
|
|
}
|
2016-04-06 05:04:57 +00:00
|
|
|
|
2016-04-14 02:45:55 +00:00
|
|
|
static float audioLatency = 0.f;
|
|
|
|
float latency = prefs_parseFloatValue(PREF_DOMAIN_AUDIO, PREF_AUDIO_LATENCY, &fVal) ? fVal : 0.25f;
|
2016-04-06 05:04:57 +00:00
|
|
|
#define SMALL_EPSILON (1.f/1024.f)
|
2016-04-14 02:45:55 +00:00
|
|
|
if (fabsf(audioLatency - latency) > SMALL_EPSILON) {
|
|
|
|
audioLatency = latency;
|
|
|
|
audio_setLatency(latency);
|
|
|
|
timing_reinitializeAudio();
|
|
|
|
}
|
2016-04-06 05:04:57 +00:00
|
|
|
|
2016-04-14 02:45:55 +00:00
|
|
|
static bool mbEnabled = false;
|
|
|
|
bool bVal = false;
|
|
|
|
bool enabled = prefs_parseBoolValue(PREF_DOMAIN_AUDIO, PREF_MOCKINGBOARD_ENABLED, &bVal) ? bVal : true;
|
|
|
|
if (enabled != mbEnabled) {
|
|
|
|
mbEnabled = enabled;
|
|
|
|
MB_SetEnabled(enabled);
|
|
|
|
timing_reinitializeAudio();
|
2016-04-06 05:04:57 +00:00
|
|
|
}
|
2018-11-07 14:47:10 +00:00
|
|
|
|
|
|
|
auto_adjust_speed = prefs_parseBoolValue(PREF_DOMAIN_INTERFACE, PREF_DISK_FAST_LOADING, &bVal) ? bVal : true;
|
2016-03-26 05:34:33 +00:00
|
|
|
}
|
|
|
|
|
2018-11-07 14:47:10 +00:00
|
|
|
static __attribute__((constructor)) void _init_timing(void) {
|
|
|
|
prefs_registerListener(PREF_DOMAIN_VM, &timing_prefsChanged);
|
|
|
|
prefs_registerListener(PREF_DOMAIN_AUDIO, &timing_prefsChanged);
|
|
|
|
prefs_registerListener(PREF_DOMAIN_INTERFACE, &timing_prefsChanged);
|
2016-03-26 05:34:33 +00:00
|
|
|
}
|
|
|
|
|