I’ve always wanted to have the option to have simulated devices behave more naturally with respect to I/O operations. By more naturally I mean that the current simulator model I/O is either polled (for asynchronous things link Muxes and Network), or it is performed in the middle of some instruction execution taking possibly many milliseconds (disk and/or tapes). The existing model creates quite deterministic behavior which helps to debug and understand issues, but it trades off potential instruction execution while performing these I/O operations in between instruction execution. To address this concept (while still retaining the potential advantages of the original model), I’ve designed an Asynch I/O model extension for simh. In order to flesh-out and debug this design, I’ve also refactored several devices to utilize this capability. Please read the attached 0readmeAsynchIO.txt file for concept details about the approach. In order to make disk devices easy to implement (within or without the AsynchIO framework), I’ve created a sim_disk.c library which is modeled on the sim_tape.c library to generalize disk I/O like tape I/O is generalized in sim_tape.c. This sim_disk.c library now provides that natural place to implement support for various disk implementation formats (just like sim_tape support several formats, and one day will be the place to add direct physical tape access). The current sim_disk library provides the framework for direct support of 3 different disk formats: 1) standard simh disk format 2) platform specific physical disk access and 3) platform independent Virtual Disk format. The Virtual Disk format is an implementation of the format described in the ”Microsoft Virtual Hard Disk (VHD) Image Format Specification”. The VHD specification is available for anyone to implement under the "Microsoft Open Specification Promise" described at http://www.microsoft.com/interop/osp/default.mspx. The VHD implementation includes support for: 1) Fixed sized disks 2) Dynamically expanding disks and 3) Differencing Disks. Dynamically expanding disks don’t change their “Virtual Size”, but they don’t consume disk space on the containing storage until the virtual sectors in the disk are actually written to (i.e. an RA81 or RA92 VHD with a VMS installed on it may initially only contain 30+ MB of files, and the resulting VHD will be 30+ MB). The VHD format contains meta data which describes the virtual device. Amongst this meta data is the simh device type which the VHD was originally created as. This metadata is therefore available whenever that VHD is attached to an emulated disk device in the future so the device type & size can be automatically be configured. Sim_disk_attach is used by device emulations to attach a simh/vhd/raw device to a simulated device. The following simh command switches are used by the sim_disk_attach API: -R Attach Read Only. -E Must Exist (if not specified an attempt to create the indicated virtual disk will be attempted). -F Open the indicated disk container in a specific format (default is to autodetect VHD defaulting to simh if the indicated container is not a VHD). -X When creating a VHD, create a fixed sized VHD (vs a Dynamically expanding one). -C Create a VHD and copy its contents from another disk (simh, VHD, or RAW format). -D Create a Differencing VHD (relative to an already existing VHD disk) Examples: sim> show rq RQ, address=20001468-2000146B*, no vector, 4 units RQ0, 159MB, not attached, write enabled, RD54, autosize, SIMH format RQ1, 159MB, not attached, write enabled, RD54, autosize, SIMH format RQ2, 159MB, not attached, write enabled, RD54, autosize, SIMH format RQ3, 409KB, not attached, write enabled, RX50, autosize, SIMH format sim> atta rq0 RA81.vhd sim> show rq0 RQ0, 456MB, attached to RA81.vhd, write enabled, RA81, autosize, VHD format sim> set rq2 ra92 sim> att rq2 -f vhd RA92.vhd RQ2: creating new file sim> sho rq2 RQ2, 1505MB, attached to RA92.vhd, write enabled, RA92, autosize, VHD format sim> ! dir RA92.vhd Volume in drive H is New Volume Volume Serial Number is F8DE-510C Directory of H:\Data 04/14/2011 12:57 PM 5,120 RA92.vhd 1 File(s) 5,120 bytes 0 Dir(s) 3,074,412,544 bytes free sim> atta rq3 -c RA92-1.vhd RA92.vhd sim> atta rq3 -c RA92-1.vhd RA92.vhd RQ3: creating new virtual disk 'RA92-1.vhd' RQ3: Copied 1505MB. 99% complete. RQ3: Copied 1505MB. Done. sim> sh rq3 RQ3, 1505MB, attached to RA92-1.vhd, write enabled, RA92, autosize, VHD format sim> ! dir RA92* Volume in drive H is New Volume Volume Serial Number is F8DE-510C Directory of H:\Data 04/14/2011 01:12 PM 5,120 RA92-1.vhd 04/14/2011 12:58 PM 5,120 RA92.vhd 2 File(s) 10,240 bytes 0 Dir(s) 3,074,404,352 bytes free sim> sho rq2 RQ2, 1505MB, not attached, write enabled, RA92, autosize, VHD format sim> set rq2 ra81 sim> set rq2 noauto sim> sho rq2 RQ2, 456MB, not attached, write enabled, RA81, noautosize, VHD format sim> set rq2 format=simh sim> sho rq2 RQ2, 456MB, not attached, write enabled, RA81, noautosize, SIMH format sim> atta rq2 -c RA81-Copy.vhd VMS055.dsk RQ2: creating new virtual disk 'RA81-Copy.vhd' RQ2: Copied 456MB. 99% complete. RQ2: Copied 456MB. Done. sim> sho rq2 RQ2, 456MB, attached to RA81-Copy.vhd, write enabled, RA81, noautosize, VHD format sim> det rq2 sim> ! dir RA81-Copy.vhd Volume in drive H is New Volume Volume Serial Number is F8DE-510C Directory of H:\Data 04/14/2011 01:22 PM 178,304,512 RA81-Copy.vhd 1 File(s) 178,304,512 bytes 0 Dir(s) 2,896,097,280 bytes free sim> ! dir VMS055.dsk Volume in drive H is New Volume Volume Serial Number is F8DE-510C Directory of H:\Data 03/08/2011 01:42 PM 403,663,872 VMS055.dsk 1 File(s) 403,663,872 bytes 0 Dir(s) 2,896,097,280 bytes free sim>
780 lines
22 KiB
C
780 lines
22 KiB
C
/* sim_timer.c: simulator timer library
|
|
|
|
Copyright (c) 1993-2008, Robert M Supnik
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
to deal in the Software without restriction, including without limitation
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
Except as contained in this notice, the name of Robert M Supnik shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from Robert M Supnik.
|
|
|
|
05-Jan-11 MP Added Asynch I/O support
|
|
22-Sep-08 RMS Added "stability threshold" for idle routine
|
|
27-May-08 RMS Fixed bug in Linux idle routines (from Walter Mueller)
|
|
18-Jun-07 RMS Modified idle to exclude counted delays
|
|
22-Mar-07 RMS Added sim_rtcn_init_all
|
|
17-Oct-06 RMS Added idle support (based on work by Mark Pizzolato)
|
|
Added throttle support
|
|
16-Aug-05 RMS Fixed C++ declaration and cast problems
|
|
02-Jan-04 RMS Split out from SCP
|
|
|
|
This library includes the following routines:
|
|
|
|
sim_timer_init - initialize timing system
|
|
sim_rtc_init - initialize calibration
|
|
sim_rtc_calb - calibrate clock
|
|
sim_timer_init - initialize timing system
|
|
sim_idle - virtual machine idle
|
|
sim_os_msec - return elapsed time in msec
|
|
sim_os_sleep - sleep specified number of seconds
|
|
sim_os_ms_sleep - sleep specified number of milliseconds
|
|
|
|
The calibration, idle, and throttle routines are OS-independent; the _os_
|
|
routines are not.
|
|
*/
|
|
|
|
#include "sim_defs.h"
|
|
#include <ctype.h>
|
|
|
|
t_bool sim_idle_enab = FALSE; /* global flag */
|
|
t_bool sim_idle_wait = FALSE; /* global flag */
|
|
|
|
static uint32 sim_idle_rate_ms = 0;
|
|
static uint32 sim_idle_stable = SIM_IDLE_STDFLT;
|
|
static uint32 sim_throt_ms_start = 0;
|
|
static uint32 sim_throt_ms_stop = 0;
|
|
static uint32 sim_throt_type = 0;
|
|
static uint32 sim_throt_val = 0;
|
|
static uint32 sim_throt_state = 0;
|
|
static int32 sim_throt_wait = 0;
|
|
extern int32 sim_interval, sim_switches;
|
|
extern FILE *sim_log;
|
|
extern UNIT *sim_clock_queue;
|
|
|
|
t_stat sim_throt_svc (UNIT *uptr);
|
|
|
|
UNIT sim_throt_unit = { UDATA (&sim_throt_svc, 0, 0) };
|
|
|
|
/* OS-dependent timer and clock routines */
|
|
|
|
/* VMS */
|
|
|
|
#if defined (VMS)
|
|
|
|
#if defined (__VAX)
|
|
#define sys$gettim SYS$GETTIM
|
|
#define sys$setimr SYS$SETIMR
|
|
#define lib$emul LIB$EMUL
|
|
#define sys$waitfr SYS$WAITFR
|
|
#define lib$subx LIB$SUBX
|
|
#define lib$ediv LIB$EDIV
|
|
#endif
|
|
|
|
#include <starlet.h>
|
|
#include <lib$routines.h>
|
|
#include <unistd.h>
|
|
|
|
const t_bool rtc_avail = TRUE;
|
|
|
|
uint32 sim_os_msec ()
|
|
{
|
|
uint32 quo, htod, tod[2];
|
|
int32 i;
|
|
|
|
sys$gettim (tod); /* time 0.1usec */
|
|
|
|
/* To convert to msec, must divide a 64b quantity by 10000. This is actually done
|
|
by dividing the 96b quantity 0'time by 10000, producing 64b of quotient, the
|
|
high 32b of which are discarded. This can probably be done by a clever multiply...
|
|
*/
|
|
|
|
quo = htod = 0;
|
|
for (i = 0; i < 64; i++) { /* 64b quo */
|
|
htod = (htod << 1) | ((tod[1] >> 31) & 1); /* shift divd */
|
|
tod[1] = (tod[1] << 1) | ((tod[0] >> 31) & 1);
|
|
tod[0] = tod[0] << 1;
|
|
quo = quo << 1; /* shift quo */
|
|
if (htod >= 10000) { /* divd work? */
|
|
htod = htod - 10000; /* subtract */
|
|
quo = quo | 1; /* set quo bit */
|
|
}
|
|
}
|
|
return quo;
|
|
}
|
|
|
|
void sim_os_sleep (unsigned int sec)
|
|
{
|
|
sleep (sec);
|
|
return;
|
|
}
|
|
|
|
uint32 sim_os_ms_sleep_init (void)
|
|
{
|
|
#if defined (__VAX)
|
|
return 10; /* VAX/VMS is 10ms */
|
|
#else
|
|
return 1; /* Alpha/VMS is 1ms */
|
|
#endif
|
|
}
|
|
|
|
uint32 sim_os_ms_sleep (unsigned int msec)
|
|
{
|
|
uint32 stime = sim_os_msec ();
|
|
uint32 qtime[2];
|
|
int32 nsfactor = -10000;
|
|
static int32 zero = 0;
|
|
|
|
lib$emul (&msec, &nsfactor, &zero, qtime);
|
|
sys$setimr (2, qtime, 0, 0);
|
|
sys$waitfr (2);
|
|
return sim_os_msec () - stime;
|
|
}
|
|
|
|
#if defined(SIM_ASYNCH_IO)
|
|
#ifndef CLOCK_REALTIME
|
|
#define CLOCK_REALTIME 1
|
|
int clock_gettime(int clk_id, struct timespec *tp)
|
|
{
|
|
uint32 secs, ns, tod[2], unixbase[2] = {0xd53e8000, 0x019db1de};
|
|
|
|
if (clk_id != CLOCK_REALTIME)
|
|
return -1;
|
|
|
|
sys$gettim (tod); /* time 0.1usec */
|
|
lib$subx(tod, unixbase, tod); /* convert to unix base */
|
|
lib$ediv(&10000000, tod, &secs, &ns); /* isolate seconds & 100ns parts */
|
|
tp->tv_sec = secs;
|
|
tp->tv_nsec = ns*100;
|
|
return 0;
|
|
}
|
|
#endif /* CLOCK_REALTIME */
|
|
#endif /* SIM_ASYNCH_IO */
|
|
|
|
#elif defined (_WIN32)
|
|
|
|
/* Win32 routines */
|
|
|
|
#include <windows.h>
|
|
|
|
const t_bool rtc_avail = TRUE;
|
|
|
|
uint32 sim_os_msec ()
|
|
{
|
|
if (sim_idle_rate_ms)
|
|
return timeGetTime ();
|
|
else return GetTickCount ();
|
|
}
|
|
|
|
void sim_os_sleep (unsigned int sec)
|
|
{
|
|
Sleep (sec * 1000);
|
|
return;
|
|
}
|
|
|
|
void sim_timer_exit (void)
|
|
{
|
|
timeEndPeriod (sim_idle_rate_ms);
|
|
return;
|
|
}
|
|
|
|
uint32 sim_os_ms_sleep_init (void)
|
|
{
|
|
TIMECAPS timers;
|
|
|
|
if (timeGetDevCaps (&timers, sizeof (timers)) != TIMERR_NOERROR)
|
|
return 0;
|
|
if ((timers.wPeriodMin == 0) || (timers.wPeriodMin > SIM_IDLE_MAX))
|
|
return 0;
|
|
if (timeBeginPeriod (timers.wPeriodMin) != TIMERR_NOERROR)
|
|
return 0;
|
|
atexit (sim_timer_exit);
|
|
Sleep (1);
|
|
Sleep (1);
|
|
Sleep (1);
|
|
Sleep (1);
|
|
Sleep (1);
|
|
return timers.wPeriodMin; /* sim_idle_rate_ms */
|
|
}
|
|
|
|
uint32 sim_os_ms_sleep (unsigned int msec)
|
|
{
|
|
uint32 stime = sim_os_msec();
|
|
|
|
Sleep (msec);
|
|
return sim_os_msec () - stime;
|
|
}
|
|
|
|
#if !defined(CLOCK_REALTIME) && defined (SIM_ASYNCH_IO)
|
|
#define CLOCK_REALTIME 1
|
|
int clock_gettime(int clk_id, struct timespec *tp)
|
|
{
|
|
t_uint64 now, unixbase;
|
|
|
|
unixbase = 116444736;
|
|
unixbase *= 1000000000;
|
|
GetSystemTimeAsFileTime((FILETIME*)&now);
|
|
now -= unixbase;
|
|
tp->tv_sec = (long)(now/10000000);
|
|
tp->tv_nsec = (now%10000000)*100;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#elif defined (__OS2__)
|
|
|
|
/* OS/2 routines, from Bruce Ray */
|
|
|
|
const t_bool rtc_avail = FALSE;
|
|
|
|
uint32 sim_os_msec ()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void sim_os_sleep (unsigned int sec)
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint32 sim_os_ms_sleep_init (void)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
uint32 sim_os_ms_sleep (unsigned int msec)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Metrowerks CodeWarrior Macintosh routines, from Ben Supnik */
|
|
|
|
#elif defined (__MWERKS__) && defined (macintosh)
|
|
|
|
#include <Timer.h>
|
|
#include <Mactypes.h>
|
|
#include <sioux.h>
|
|
#include <unistd.h>
|
|
#include <siouxglobals.h>
|
|
#define NANOS_PER_MILLI 1000000
|
|
#define MILLIS_PER_SEC 1000
|
|
|
|
const t_bool rtc_avail = TRUE;
|
|
|
|
uint32 sim_os_msec (void)
|
|
{
|
|
unsigned long long micros;
|
|
UnsignedWide macMicros;
|
|
unsigned long millis;
|
|
|
|
Microseconds (&macMicros);
|
|
micros = *((unsigned long long *) &macMicros);
|
|
millis = micros / 1000LL;
|
|
return (uint32) millis;
|
|
}
|
|
|
|
void sim_os_sleep (unsigned int sec)
|
|
{
|
|
sleep (sec);
|
|
return;
|
|
}
|
|
|
|
uint32 sim_os_ms_sleep_init (void)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
uint32 sim_os_ms_sleep (unsigned int milliseconds)
|
|
{
|
|
uint32 stime = sim_os_msec ();
|
|
struct timespec treq;
|
|
|
|
treq.tv_sec = milliseconds / MILLIS_PER_SEC;
|
|
treq.tv_nsec = (milliseconds % MILLIS_PER_SEC) * NANOS_PER_MILLI;
|
|
(void) nanosleep (&treq, NULL);
|
|
return sim_os_msec () - stime;
|
|
}
|
|
|
|
#if !defined(CLOCK_REALTIME) && defined (SIM_ASYNCH_IO)
|
|
#define CLOCK_REALTIME 1
|
|
int clock_gettime(int clk_id, struct timespec *tp)
|
|
{
|
|
struct timeval cur;
|
|
struct timezone foo;
|
|
|
|
if (clk_id != CLOCK_REALTIME)
|
|
return -1;
|
|
gettimeofday (&cur, &foo);
|
|
tp->tv_sec = cur.tv_sec;
|
|
tp->tv_nsec = cur.tv_usec*1000;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#else
|
|
|
|
/* UNIX routines */
|
|
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
#define NANOS_PER_MILLI 1000000
|
|
#define MILLIS_PER_SEC 1000
|
|
#define sleep1Samples 100
|
|
|
|
const t_bool rtc_avail = TRUE;
|
|
|
|
uint32 sim_os_msec ()
|
|
{
|
|
struct timeval cur;
|
|
struct timezone foo;
|
|
uint32 msec;
|
|
|
|
gettimeofday (&cur, &foo);
|
|
msec = (((uint32) cur.tv_sec) * 1000) + (((uint32) cur.tv_usec) / 1000);
|
|
return msec;
|
|
}
|
|
|
|
void sim_os_sleep (unsigned int sec)
|
|
{
|
|
sleep (sec);
|
|
return;
|
|
}
|
|
|
|
uint32 sim_os_ms_sleep_init (void)
|
|
{
|
|
uint32 i, t1, t2, tot, tim;
|
|
|
|
for (i = 0, tot = 0; i < sleep1Samples; i++) {
|
|
t1 = sim_os_msec ();
|
|
sim_os_ms_sleep (1);
|
|
t2 = sim_os_msec ();
|
|
tot += (t2 - t1);
|
|
}
|
|
tim = (tot + (sleep1Samples - 1)) / sleep1Samples;
|
|
if (tim > SIM_IDLE_MAX)
|
|
tim = 0;
|
|
return tim;
|
|
}
|
|
|
|
#if !defined(_POSIX_SOURCE) && defined(SIM_ASYNCH_IO)
|
|
#ifndef CLOCK_REALTIME
|
|
#define CLOCK_REALTIME 1
|
|
typedef int clockid_t;
|
|
#endif
|
|
int clock_gettime(clockid_t clk_id, struct timespec *tp)
|
|
{
|
|
struct timeval cur;
|
|
struct timezone foo;
|
|
|
|
if (clk_id != CLOCK_REALTIME)
|
|
return -1;
|
|
gettimeofday (&cur, &foo);
|
|
tp->tv_sec = cur.tv_sec;
|
|
tp->tv_nsec = cur.tv_usec*1000;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
uint32 sim_os_ms_sleep (unsigned int milliseconds)
|
|
{
|
|
uint32 stime = sim_os_msec ();
|
|
struct timespec treq;
|
|
|
|
treq.tv_sec = milliseconds / MILLIS_PER_SEC;
|
|
treq.tv_nsec = (milliseconds % MILLIS_PER_SEC) * NANOS_PER_MILLI;
|
|
(void) nanosleep (&treq, NULL);
|
|
return sim_os_msec () - stime;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(SIM_ASYNCH_IO)
|
|
uint32 sim_idle_ms_sleep (unsigned int msec)
|
|
{
|
|
uint32 start_time = sim_os_msec();
|
|
struct timespec done_time;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &done_time);
|
|
done_time.tv_sec += (msec/1000);
|
|
done_time.tv_nsec += 1000000*(msec%1000);
|
|
if (done_time.tv_nsec > 1000000000) {
|
|
done_time.tv_sec += 1;
|
|
done_time.tv_nsec = done_time.tv_nsec%1000000000;
|
|
}
|
|
pthread_mutex_lock (&sim_asynch_lock);
|
|
sim_idle_wait = TRUE;
|
|
pthread_cond_timedwait (&sim_asynch_wake, &sim_asynch_lock, &done_time);
|
|
sim_idle_wait = FALSE;
|
|
pthread_mutex_unlock (&sim_asynch_lock);
|
|
return sim_os_msec() - start_time;
|
|
}
|
|
#define SIM_IDLE_MS_SLEEP sim_idle_ms_sleep
|
|
#else
|
|
#define SIM_IDLE_MS_SLEEP sim_os_ms_sleep
|
|
#endif
|
|
|
|
/* OS independent clock calibration package */
|
|
|
|
static int32 rtc_ticks[SIM_NTIMERS] = { 0 }; /* ticks */
|
|
static int32 rtc_hz[SIM_NTIMERS] = { 0 }; /* tick rate */
|
|
static uint32 rtc_rtime[SIM_NTIMERS] = { 0 }; /* real time */
|
|
static uint32 rtc_vtime[SIM_NTIMERS] = { 0 }; /* virtual time */
|
|
static uint32 rtc_nxintv[SIM_NTIMERS] = { 0 }; /* next interval */
|
|
static int32 rtc_based[SIM_NTIMERS] = { 0 }; /* base delay */
|
|
static int32 rtc_currd[SIM_NTIMERS] = { 0 }; /* current delay */
|
|
static int32 rtc_initd[SIM_NTIMERS] = { 0 }; /* initial delay */
|
|
static uint32 rtc_elapsed[SIM_NTIMERS] = { 0 }; /* sec since init */
|
|
|
|
void sim_rtcn_init_all (void)
|
|
{
|
|
uint32 i;
|
|
|
|
for (i = 0; i < SIM_NTIMERS; i++) {
|
|
if (rtc_initd[i] != 0) sim_rtcn_init (rtc_initd[i], i);
|
|
}
|
|
return;
|
|
}
|
|
|
|
int32 sim_rtcn_init (int32 time, int32 tmr)
|
|
{
|
|
if (time == 0)
|
|
time = 1;
|
|
if ((tmr < 0) || (tmr >= SIM_NTIMERS))
|
|
return time;
|
|
rtc_rtime[tmr] = sim_os_msec ();
|
|
rtc_vtime[tmr] = rtc_rtime[tmr];
|
|
rtc_nxintv[tmr] = 1000;
|
|
rtc_ticks[tmr] = 0;
|
|
rtc_hz[tmr] = 0;
|
|
rtc_based[tmr] = time;
|
|
rtc_currd[tmr] = time;
|
|
rtc_initd[tmr] = time;
|
|
rtc_elapsed[tmr] = 0;
|
|
return time;
|
|
}
|
|
|
|
int32 sim_rtcn_calb (int32 ticksper, int32 tmr)
|
|
{
|
|
uint32 new_rtime, delta_rtime;
|
|
int32 delta_vtime;
|
|
|
|
if ((tmr < 0) || (tmr >= SIM_NTIMERS))
|
|
return 10000;
|
|
rtc_hz[tmr] = ticksper;
|
|
rtc_ticks[tmr] = rtc_ticks[tmr] + 1; /* count ticks */
|
|
if (rtc_ticks[tmr] < ticksper) /* 1 sec yet? */
|
|
return rtc_currd[tmr];
|
|
rtc_ticks[tmr] = 0; /* reset ticks */
|
|
rtc_elapsed[tmr] = rtc_elapsed[tmr] + 1; /* count sec */
|
|
if (!rtc_avail) /* no timer? */
|
|
return rtc_currd[tmr];
|
|
new_rtime = sim_os_msec (); /* wall time */
|
|
if (new_rtime < rtc_rtime[tmr]) { /* time running backwards? */
|
|
rtc_rtime[tmr] = new_rtime; /* reset wall time */
|
|
return rtc_currd[tmr]; /* can't calibrate */
|
|
}
|
|
delta_rtime = new_rtime - rtc_rtime[tmr]; /* elapsed wtime */
|
|
rtc_rtime[tmr] = new_rtime; /* adv wall time */
|
|
rtc_vtime[tmr] = rtc_vtime[tmr] + 1000; /* adv sim time */
|
|
if (delta_rtime > 30000) /* gap too big? */
|
|
return rtc_initd[tmr]; /* can't calibr */
|
|
if (delta_rtime == 0) /* gap too small? */
|
|
rtc_based[tmr] = rtc_based[tmr] * ticksper; /* slew wide */
|
|
else rtc_based[tmr] = (int32) (((double) rtc_based[tmr] * (double) rtc_nxintv[tmr]) /
|
|
((double) delta_rtime)); /* new base rate */
|
|
delta_vtime = rtc_vtime[tmr] - rtc_rtime[tmr]; /* gap */
|
|
if (delta_vtime > SIM_TMAX) /* limit gap */
|
|
delta_vtime = SIM_TMAX;
|
|
else if (delta_vtime < -SIM_TMAX)
|
|
delta_vtime = -SIM_TMAX;
|
|
rtc_nxintv[tmr] = 1000 + delta_vtime; /* next wtime */
|
|
rtc_currd[tmr] = (int32) (((double) rtc_based[tmr] * (double) rtc_nxintv[tmr]) /
|
|
1000.0); /* next delay */
|
|
if (rtc_based[tmr] <= 0) /* never negative or zero! */
|
|
rtc_based[tmr] = 1;
|
|
if (rtc_currd[tmr] <= 0) /* never negative or zero! */
|
|
rtc_currd[tmr] = 1;
|
|
AIO_SET_INTERRUPT_LATENCY(rtc_currd[tmr]*ticksper); /* set interrrupt latency */
|
|
return rtc_currd[tmr];
|
|
}
|
|
|
|
/* Prior interfaces - default to timer 0 */
|
|
|
|
int32 sim_rtc_init (int32 time)
|
|
{
|
|
return sim_rtcn_init (time, 0);
|
|
}
|
|
|
|
int32 sim_rtc_calb (int32 ticksper)
|
|
{
|
|
return sim_rtcn_calb (ticksper, 0);
|
|
}
|
|
|
|
/* sim_timer_init - get minimum sleep time available on this host */
|
|
|
|
t_bool sim_timer_init (void)
|
|
{
|
|
sim_idle_enab = FALSE; /* init idle off */
|
|
sim_idle_rate_ms = sim_os_ms_sleep_init (); /* get OS timer rate */
|
|
return (sim_idle_rate_ms != 0);
|
|
}
|
|
|
|
/* sim_idle - idle simulator until next event or for specified interval
|
|
|
|
Inputs:
|
|
tmr = calibrated timer to use
|
|
|
|
Must solve the linear equation
|
|
|
|
ms_to_wait = w * ms_per_wait
|
|
|
|
Or
|
|
w = ms_to_wait / ms_per_wait
|
|
*/
|
|
|
|
t_bool sim_idle (uint32 tmr, t_bool sin_cyc)
|
|
{
|
|
static uint32 cyc_ms;
|
|
uint32 w_ms, w_idle, act_ms;
|
|
int32 act_cyc;
|
|
|
|
if ((sim_clock_queue == NULL) || /* clock queue empty? */
|
|
((sim_clock_queue->flags & UNIT_IDLE) == 0) || /* event not idle-able? */
|
|
(rtc_elapsed[tmr] < sim_idle_stable)) { /* timer not stable? */
|
|
if (sin_cyc)
|
|
sim_interval = sim_interval - 1;
|
|
return FALSE;
|
|
}
|
|
if (!cyc_ms)
|
|
cyc_ms = (rtc_currd[tmr] * rtc_hz[tmr]) / 1000; /* cycles per msec */
|
|
if ((sim_idle_rate_ms == 0) || (cyc_ms == 0)) { /* not possible? */
|
|
if (sin_cyc)
|
|
sim_interval = sim_interval - 1;
|
|
return FALSE;
|
|
}
|
|
w_ms = (uint32) sim_interval / cyc_ms; /* ms to wait */
|
|
w_idle = w_ms / sim_idle_rate_ms; /* intervals to wait */
|
|
if (w_idle == 0) { /* none? */
|
|
if (sin_cyc)
|
|
sim_interval = sim_interval - 1;
|
|
return FALSE;
|
|
}
|
|
act_ms = SIM_IDLE_MS_SLEEP (w_ms); /* wait */
|
|
act_cyc = act_ms * cyc_ms;
|
|
if (sim_interval > act_cyc)
|
|
sim_interval = sim_interval - act_cyc;
|
|
else sim_interval = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
/* Set idling - implicitly disables throttling */
|
|
|
|
t_stat sim_set_idle (UNIT *uptr, int32 val, char *cptr, void *desc)
|
|
{
|
|
t_stat r;
|
|
uint32 v;
|
|
|
|
if (sim_idle_rate_ms == 0)
|
|
return SCPE_NOFNC;
|
|
if ((val != 0) && (sim_idle_rate_ms > (uint32) val))
|
|
return SCPE_NOFNC;
|
|
if (cptr) {
|
|
v = (uint32) get_uint (cptr, 10, SIM_IDLE_STMAX, &r);
|
|
if ((r != SCPE_OK) || (v < SIM_IDLE_STMIN))
|
|
return SCPE_ARG;
|
|
sim_idle_stable = v;
|
|
}
|
|
sim_idle_enab = TRUE;
|
|
if (sim_throt_type != SIM_THROT_NONE) {
|
|
sim_set_throt (0, NULL);
|
|
printf ("Throttling disabled\n");
|
|
if (sim_log)
|
|
fprintf (sim_log, "Throttling disabled\n");
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Clear idling */
|
|
|
|
t_stat sim_clr_idle (UNIT *uptr, int32 val, char *cptr, void *desc)
|
|
{
|
|
sim_idle_enab = FALSE;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Show idling */
|
|
|
|
t_stat sim_show_idle (FILE *st, UNIT *uptr, int32 val, void *desc)
|
|
{
|
|
if (sim_idle_enab)
|
|
fprintf (st, "idle enabled, stability wait = %ds", sim_idle_stable);
|
|
else fputs ("idle disabled", st);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Throttling package */
|
|
|
|
t_stat sim_set_throt (int32 arg, char *cptr)
|
|
{
|
|
char *tptr, c;
|
|
t_value val;
|
|
|
|
if (arg == 0) {
|
|
if ((cptr != 0) && (*cptr != 0))
|
|
return SCPE_ARG;
|
|
sim_throt_type = SIM_THROT_NONE;
|
|
sim_throt_cancel ();
|
|
}
|
|
else if (sim_idle_rate_ms == 0)
|
|
return SCPE_NOFNC;
|
|
else {
|
|
val = strtotv (cptr, &tptr, 10);
|
|
if (cptr == tptr)
|
|
return SCPE_ARG;
|
|
c = toupper (*tptr++);
|
|
if (*tptr != 0)
|
|
return SCPE_ARG;
|
|
if (c == 'M')
|
|
sim_throt_type = SIM_THROT_MCYC;
|
|
else if (c == 'K')
|
|
sim_throt_type = SIM_THROT_KCYC;
|
|
else if ((c == '%') && (val > 0) && (val < 100))
|
|
sim_throt_type = SIM_THROT_PCT;
|
|
else return SCPE_ARG;
|
|
if (sim_idle_enab) {
|
|
printf ("Idling disabled\n");
|
|
if (sim_log)
|
|
fprintf (sim_log, "Idling disabled\n");
|
|
sim_clr_idle (NULL, 0, NULL, NULL);
|
|
}
|
|
sim_throt_val = (uint32) val;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat sim_show_throt (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, char *cptr)
|
|
{
|
|
if (sim_idle_rate_ms == 0)
|
|
fprintf (st, "Throttling not available\n");
|
|
else {
|
|
switch (sim_throt_type) {
|
|
|
|
case SIM_THROT_MCYC:
|
|
fprintf (st, "Throttle = %d megacycles\n", sim_throt_val);
|
|
break;
|
|
|
|
case SIM_THROT_KCYC:
|
|
fprintf (st, "Throttle = %d kilocycles\n", sim_throt_val);
|
|
break;
|
|
|
|
case SIM_THROT_PCT:
|
|
fprintf (st, "Throttle = %d%%\n", sim_throt_val);
|
|
break;
|
|
|
|
default:
|
|
fprintf (st, "Throttling disabled\n");
|
|
break;
|
|
}
|
|
|
|
if (sim_switches & SWMASK ('D')) {
|
|
fprintf (st, "Wait rate = %d ms\n", sim_idle_rate_ms);
|
|
if (sim_throt_type != 0)
|
|
fprintf (st, "Throttle interval = %d cycles\n", sim_throt_wait);
|
|
}
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
void sim_throt_sched (void)
|
|
{
|
|
sim_throt_state = 0;
|
|
if (sim_throt_type)
|
|
sim_activate (&sim_throt_unit, SIM_THROT_WINIT);
|
|
return;
|
|
}
|
|
|
|
void sim_throt_cancel (void)
|
|
{
|
|
sim_cancel (&sim_throt_unit);
|
|
}
|
|
|
|
/* Throttle service
|
|
|
|
Throttle service has three distinct states
|
|
|
|
0 take initial measurement
|
|
1 take final measurement, calculate wait values
|
|
2 periodic waits to slow down the CPU
|
|
*/
|
|
|
|
t_stat sim_throt_svc (UNIT *uptr)
|
|
{
|
|
uint32 delta_ms;
|
|
double a_cps, d_cps;
|
|
|
|
switch (sim_throt_state) {
|
|
|
|
case 0: /* take initial reading */
|
|
sim_throt_ms_start = sim_os_msec ();
|
|
sim_throt_wait = SIM_THROT_WST;
|
|
sim_throt_state++; /* next state */
|
|
break; /* reschedule */
|
|
|
|
case 1: /* take final reading */
|
|
sim_throt_ms_stop = sim_os_msec ();
|
|
delta_ms = sim_throt_ms_stop - sim_throt_ms_start;
|
|
if (delta_ms < SIM_THROT_MSMIN) { /* not enough time? */
|
|
if (sim_throt_wait >= 100000000) { /* too many inst? */
|
|
sim_throt_state = 0; /* fails in 32b! */
|
|
return SCPE_OK;
|
|
}
|
|
sim_throt_wait = sim_throt_wait * SIM_THROT_WMUL;
|
|
sim_throt_ms_start = sim_throt_ms_stop;
|
|
}
|
|
else { /* long enough */
|
|
a_cps = ((double) sim_throt_wait) * 1000.0 / (double) delta_ms;
|
|
if (sim_throt_type == SIM_THROT_MCYC) /* calc desired cps */
|
|
d_cps = (double) sim_throt_val * 1000000.0;
|
|
else if (sim_throt_type == SIM_THROT_KCYC)
|
|
d_cps = (double) sim_throt_val * 1000.0;
|
|
else d_cps = (a_cps * ((double) sim_throt_val)) / 100.0;
|
|
if (d_cps >= a_cps) {
|
|
sim_throt_state = 0;
|
|
return SCPE_OK;
|
|
}
|
|
sim_throt_wait = (int32) /* time between waits */
|
|
((a_cps * d_cps * ((double) sim_idle_rate_ms)) /
|
|
(1000.0 * (a_cps - d_cps)));
|
|
if (sim_throt_wait < SIM_THROT_WMIN) { /* not long enough? */
|
|
sim_throt_state = 0;
|
|
return SCPE_OK;
|
|
}
|
|
sim_throt_state++;
|
|
// fprintf (stderr, "Throttle values a_cps = %f, d_cps = %f, wait = %d\n",
|
|
// a_cps, d_cps, sim_throt_wait);
|
|
}
|
|
break;
|
|
|
|
case 2: /* throttling */
|
|
sim_os_ms_sleep (1);
|
|
break;
|
|
}
|
|
|
|
sim_activate (uptr, sim_throt_wait); /* reschedule */
|
|
return SCPE_OK;
|
|
}
|