2013-07-06 04:37:13 +00:00
|
|
|
/*
|
2013-06-28 06:36:25 +00:00
|
|
|
* Apple // emulator for Linux
|
|
|
|
*
|
|
|
|
* CPU Timing Support.
|
|
|
|
*
|
|
|
|
* Mostly this adds support for specifically throttling the emulator speed to
|
|
|
|
* match a 1.02MHz Apple //e.
|
|
|
|
*
|
|
|
|
* Added 2013 by Aaron Culliney
|
2013-07-06 04:37:13 +00:00
|
|
|
*
|
2013-06-28 06:36:25 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "timing.h"
|
2013-07-08 03:52:30 +00:00
|
|
|
#include "misc.h"
|
2013-06-28 06:36:25 +00:00
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <time.h>
|
2013-07-02 08:10:57 +00:00
|
|
|
#include <pthread.h>
|
2013-06-28 06:36:25 +00:00
|
|
|
|
2013-07-02 08:10:57 +00:00
|
|
|
#define DEFAULT_SLEEP 120
|
2013-06-28 06:36:25 +00:00
|
|
|
|
|
|
|
static unsigned int sleep_hz = DEFAULT_SLEEP; // sleep intervals per sec
|
|
|
|
static unsigned long cpu_target_hz = APPLE2_HZ; // target clock speed
|
|
|
|
static unsigned long cycles_interval = APPLE2_HZ / DEFAULT_SLEEP; // Number of 65c02 instructions to be executed at sleep_hz
|
|
|
|
static unsigned long processing_interval = NANOSECONDS / DEFAULT_SLEEP; // Number of nanoseconds in sleep_hz intervals
|
|
|
|
|
|
|
|
static struct timespec deltat, t0, ti, tj;
|
|
|
|
static unsigned long cycle=0;
|
|
|
|
static long sleep_adjust=0;
|
|
|
|
static long sleep_adjust_inc=0;
|
|
|
|
|
2013-07-02 08:10:57 +00:00
|
|
|
extern pthread_mutex_t mutex;
|
|
|
|
extern pthread_cond_t cond;
|
|
|
|
|
2013-06-28 06:36:25 +00:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// assuming end > start, returns end - start
|
2013-07-06 04:37:13 +00:00
|
|
|
static inline struct timespec timespec_diff(struct timespec start, struct timespec end) {
|
2013-06-28 06:36:25 +00:00
|
|
|
struct timespec t;
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
t.tv_nsec = NANOSECONDS + 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;
|
|
|
|
}
|
|
|
|
|
2013-07-06 04:37:13 +00:00
|
|
|
static inline long timespec_nsecs(struct timespec t) {
|
2013-06-28 06:36:25 +00:00
|
|
|
return t.tv_sec*NANOSECONDS + t.tv_nsec;
|
|
|
|
}
|
|
|
|
|
2013-07-06 04:37:13 +00:00
|
|
|
void timing_initialize() {
|
2013-06-28 06:36:25 +00:00
|
|
|
clock_gettime(CLOCK_MONOTONIC, &t0);
|
|
|
|
ti=t0;
|
|
|
|
}
|
|
|
|
|
2013-07-06 04:37:13 +00:00
|
|
|
void timing_set_cpu_target_hz(unsigned long hz) {
|
2013-06-28 06:36:25 +00:00
|
|
|
cpu_target_hz = hz;
|
|
|
|
}
|
|
|
|
|
2013-07-06 04:37:13 +00:00
|
|
|
void timing_set_sleep_hz(unsigned int hz) {
|
2013-06-28 06:36:25 +00:00
|
|
|
sleep_hz = hz;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Throttles the 65c02 CPU down to a target frequency of X.
|
|
|
|
* Currently set to target the Apple //e @ 1.02MHz
|
2013-07-02 08:10:57 +00:00
|
|
|
*
|
|
|
|
* This is called from cpu65_run() on the cpu-thread
|
2013-06-28 06:36:25 +00:00
|
|
|
*/
|
2013-07-06 04:37:13 +00:00
|
|
|
void timing_throttle() {
|
2013-06-28 06:36:25 +00:00
|
|
|
++cycle;
|
|
|
|
|
2013-07-02 08:10:57 +00:00
|
|
|
static time_t severe_lag=0;
|
|
|
|
|
2013-07-06 04:37:13 +00:00
|
|
|
if ((cycle%cycles_interval) == 0)
|
|
|
|
{
|
2013-07-02 08:10:57 +00:00
|
|
|
|
|
|
|
// wake render thread as we go to sleep
|
|
|
|
pthread_mutex_lock(&mutex);
|
|
|
|
pthread_cond_signal(&cond);
|
|
|
|
pthread_mutex_unlock(&mutex);
|
|
|
|
|
2013-06-28 06:36:25 +00:00
|
|
|
clock_gettime(CLOCK_MONOTONIC, &tj);
|
|
|
|
deltat = timespec_diff(ti, tj);
|
|
|
|
ti=tj;
|
2013-07-06 04:37:13 +00:00
|
|
|
if (deltat.tv_sec != 0)
|
|
|
|
{
|
2013-07-02 08:10:57 +00:00
|
|
|
// severely lagging, don't bother sleeping ...
|
2013-07-06 04:37:13 +00:00
|
|
|
if (severe_lag < time(NULL))
|
|
|
|
{
|
2013-07-02 08:10:57 +00:00
|
|
|
severe_lag = time(NULL)+2;
|
|
|
|
fprintf(stderr, "Severe lag detected...\n");
|
|
|
|
}
|
2013-07-06 04:37:13 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-06-28 06:36:25 +00:00
|
|
|
deltat.tv_nsec = processing_interval - deltat.tv_nsec + sleep_adjust_inc;
|
|
|
|
nanosleep(&deltat, NULL); // NOTE: spec says will return right away if deltat.tv_nsec value < 0 ...
|
|
|
|
ti.tv_nsec += deltat.tv_nsec;
|
|
|
|
}
|
|
|
|
|
2013-07-06 04:37:13 +00:00
|
|
|
if ((cycle%cpu_target_hz) == 0)
|
|
|
|
{
|
2013-06-28 06:36:25 +00:00
|
|
|
clock_gettime(CLOCK_MONOTONIC, &tj);
|
|
|
|
|
|
|
|
deltat = timespec_diff(t0, tj);
|
2013-07-06 04:37:13 +00:00
|
|
|
struct timespec t = (struct timespec) {.tv_sec=1, .tv_nsec=0 };
|
2013-06-28 06:36:25 +00:00
|
|
|
|
|
|
|
long adj = (deltat.tv_sec == 0)
|
2013-07-06 04:37:13 +00:00
|
|
|
? timespec_nsecs(timespec_diff(deltat, t))
|
|
|
|
: -1 * timespec_nsecs(timespec_diff(t, deltat));
|
2013-06-28 06:36:25 +00:00
|
|
|
|
|
|
|
sleep_adjust += adj;
|
|
|
|
sleep_adjust_inc = sleep_adjust/sleep_hz;
|
|
|
|
|
|
|
|
t0=tj;
|
|
|
|
ti=t0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|