/* pdp10_tim.c: PDP-10 tim subsystem simulator

   Copyright (c) 1993-2012, 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.

   tim          timer subsystem

   10-Nov-16    R.V     Fix wallclock issue for 50 Hz systems (R. Voorhorst)
   18-Apr-12    RMS     Removed absolute scheduling on reset
   18-Jun-07    RMS     Added UNIT_IDLE flag
   03-Nov-06    RMS     Rewritten to support idling
   29-Oct-06    RMS     Added clock coscheduling function
   02-Feb-04    RMS     Exported variables needed by Ethernet simulator
   29-Jan-02    RMS     New data structures
   06-Jan-02    RMS     Added enable/disable support
   02-Dec-01    RMS     Fixed bug in ITS PC sampling (found by Dave Conroy)
   31-Aug-01    RMS     Changed int64 to t_int64 for Windoze
   17-Jul-01    RMS     Moved function prototype
   04-Jul-01    RMS     Added DZ11 support
*/

#include "pdp10_defs.h"
#include <math.h>

/* The KS timer works off a 4.100 MHz (243.9024 nsec) oscillator that
 * is independent of all other system timing.
 *
 * Two pieces of timekeeping hardware are exposed to the OS.
 *  o The interval timer, which can interrupt at a programmed interval.
 *  o The timebase, which records time (71 bits).
 *
 * The clock is architecturally readable in units of 243.9024 nsec via
 * the timebase.  The implementation is somewhat different.
 *
 * The instructions that update the clocks specify time in these units.
 *
 * However, both timekeepers are incremented by the microcode when
 * a 12 bit counter overflows; e.g. at a period of 999.0244 usec.
 * Thus, the granularity of timer interrupts is approximately 1 msec.
 *
 * The OS programs the interval timer to interrupt as though the
 * the 12 least significant bits mattered.  Thus, for a (roughly)
 * 1 msec interval, it would program 1 * 4096 into the interval timer.
 * The sign bit is not used, so 35-12 = 23 bits for the maximum interval,
 * which is 139.674 minutes.  If any of the least significant bits
 * are non-zero, the interval is extended by 1 * 4096 counts.
 *
 * The timer merely sets the INTERVAL DONE flag in the APR flags.
 * Whether that actually causes an interrupt is controlled by the
 * APR interrupt enable for the flag and by the  PI system.
 *
 * The flag is readable as an APR condition by RDAPR, and CONSO/Z APR,.
 * The flag is cleared by WRAPR 1b22!1b30 (clear, count done).
 *
 * The timebase is maintained with the 12 LSB zero in a workspace
 * register.  When read by the OS, the actual value of the 10 MSB of
 * the hardware counter is inserted into those bits, providing increased
 * resolution.  Although the system reference manual says otherwise, the
 * two LSB of the counter are read as zero by the microcode (DPM2), so
 * bits <70:71> of the timebase are also read as zero by software.
 *
 * When the OS sets the timebase, the 12 LSB that it supplies are ignored.
 *
 * The timebase is typically used for accurate time of day and CPU runtime
 * accounting.  The simulator adjusts the equivalent of the 12-bit counter,
 * so CPU time will reflect simulator wall clock, not simulated machine cycles.
 * Since time of day must be accurate, this may result in the OS reporting
 * CPU times that are unrealistically faster - or slower - than on the
 * real hardware.
 *
 * This module also implements the TCU, a battery backed-up TOY clock
 * that was supported by TOPS-10, but not sold by DEC.
 */

/* Invariants */

#define TIM_HW_FREQ     4100000                         /* 4.1Mhz */
#define TIM_HWRE_MASK   07777                           /* Timer field of timebase */
#define TIM_BASE_RAZ    03                              /* Timer bits read as zero by ucode */
#define UNIT_V_Y2K      (UNIT_V_UF + 0)                 /* Y2K compliant OS */
#define UNIT_Y2K        (1u << UNIT_V_Y2K)

#define TIM_TMXR_FREQ  60                               /* Target frequency (HZ) for tmxr polls */

 /* Estimate of simulator instructions/sec for initialization and fixed timing.
  * This came from prior magic constant of 8000 at 60 tics/sec.
  * The machine was marketed as ~ 300KIPs, which would imply 3 usec/instr.
  * So 8,000 instructions should take ~24 msec.  This would indicate that
  * the simulator from which this came was ~1.4 x the speed of the real
  * hardware.  Current milage will vary.
  */
#define TIM_WAIT_IPS   480000

/* Clock mode TOPS-10/ITS */

#define TIM_TPS_T10     60                              /* Initial frequency guess for TOPS-10 (close, not exact) */
#define TIM_ITS_QUANT   (TIM_HW_FREQ / TIM_TPS_T10)     /* ITS PC sampling and user runtime interval */

/* Clock mode TOPS-20/KLAD */

#define TIM_TPS_T20     1000                            /* Initial estimate for TOPS-20 - 1msec seems fast? */

/* Probability function for TOPS-20 idlelock */

#define PROB(x)         (((rand() * 100) / RAND_MAX) >= (x))

static d10 tim_base[2] = { 0, 0 };                      /* 71b timebase */
static d10 tim_interval = 0;                            /* value programmed into the clock */
static d10 tim_period = 0;                              /* period in HW ticks adjusted for non-zero LSBs */
static d10 tim_new_period = 0;                          /* period for the next interval */
static int32 tim_mult;                                  /* Multiple of interval timer period at which tmxr is polled */

d10 quant = 0;                                          /* ITS quantum */

/* Exported variables - initialized by set CPU model and reset */

int32 clk_tps;                                          /* Interval clock ticks/sec */
int32 tick_in_usecs;                                    /* Interval tick size in usecs */
int32 tmr_poll;                                         /* SimH instructions/clock service */
int32 tmxr_poll;                                        /* SimH instructions/term mux poll */

extern int32 apr_flg, pi_act;
extern d10 pcst;

static t_stat tcu_rd (int32 *data, int32 PA, int32 access);
static t_stat tim_svc (UNIT *uptr);
static t_stat tim_reset (DEVICE *dptr);
static t_bool update_interval (d10 new_interval);
static void tim_incr_base (d10 *base, d10 incr);

extern t_stat wr_nop (int32 data, int32 PA, int32 access);

/* TIM data structures

   tim_dev      TIM device descriptor
   tim_unit     TIM unit descriptor
   tim_reg      TIM register list
*/

DIB tcu_dib = { IOBA_TCU, IOLN_TCU, &tcu_rd, &wr_nop, 0 };

static UNIT tim_unit = { UDATA (&tim_svc, UNIT_IDLE, 0), 0 };

static REG tim_reg[] = {
    { BRDATAD (TIMEBASE, tim_base, 8, 36, 2, "time base (double precision)") },
    { ORDATAD (PERIOD, tim_period, 36, "reset value for interval") },
    { ORDATAD (QUANT, quant, 36, "quantum timer (ITS only)") },
    { DRDATAD (TIME, tim_unit.wait, 24, "tick delay"), REG_NZ + PV_LEFT },
    { DRDATA (POLL, tmr_poll, 32), REG_HRO + PV_LEFT },
    { DRDATA (MUXPOLL, tmxr_poll, 32), REG_HRO + PV_LEFT },
    { DRDATA (MULT, tim_mult, 6), REG_HRO + PV_LEFT },
    { DRDATA (TPS, clk_tps, 12), REG_HRO + PV_LEFT },
    { NULL }
    };

static MTAB tim_mod[] = {
    { UNIT_Y2K, 0, "non Y2K OS", "NOY2K", NULL },
    { UNIT_Y2K, UNIT_Y2K, "Y2K OS", "Y2K", NULL },
    { MTAB_XTD|MTAB_VDV, 000, "ADDRESS", NULL,
      NULL, &show_addr, NULL },
    { 0 }
    };

/* Debug detail levels */

#define DEB_RRD       0001                          /* reg reads */
#define DEB_RWR       0002                          /* reg writes */
#define DEB_TPS       0004                          /* Ticks Per Second changes */
#define DEB_INT       0010                          /* interrupts */
#define DEB_TRC       0020                          /* trace */

static DEBTAB tim_deb[] = {
    { "RRD",      DEB_RRD, "register reads" },
    { "RWR",      DEB_RWR, "register writes" },
    { "TPS",      DEB_TPS, "Ticks Per Second changes " },
    { "INT",      DEB_INT, "interrupts" },
    { "TRACE",    DEB_TRC, "trace" },
    { NULL, 0 }
    };


DEVICE tim_dev = {
    "TIM", &tim_unit, tim_reg, tim_mod,
    1, 0, 0, 0, 0, 0,
    NULL, NULL, &tim_reset,
    NULL, NULL, NULL,
    &tcu_dib, DEV_UBUS | DEV_DEBUG,
    0, tim_deb
    };

/* Timer instructions */

/* Timebase - the timer is always running at less than hardware frequency,
 * need to interpolate the value by calculating how much of the current
 * clock tick has elapsed, and what that equates to in sysfreq units.
 *
 * Read the contents of the time base registers, add the current contents of the
 * millisecond counter to the doubleword read, and place the result in location
 * E,E+1.
 */

t_bool rdtim (a10 ea, int32 prv)
{
d10 tempbase[2];                  /* Local copy of tempbase to interpolate */
d10 incr;                         /* Interpolated increment for timebase   */

tempbase[0] = tim_base[0];                              /* copy time base */
tempbase[1] = tim_base[1];

/*
 * incr is approximate number of HW ticks to add to the timebase
 * value returned.  This does NOT update the timebase.
 */
incr = (d10)((((double)TIM_HW_FREQ) / 1000000.0) * (tick_in_usecs - sim_activate_time_usecs (&tim_unit)));
tim_incr_base (tempbase, incr);

/* Although the two LSB of the counter contribute carry to the
 * value, they are read as zero by microcode, and thus cleared here.
 *
 * The reason that these bits are forced to zero in the hardware is
 * that the counter is in a different clock domain from the microcode.
 * To make the domain crossing, the microcode reads the counter
 * until two consecutive values match.
 *
 * Since the microcode cycle time is 300 nsec, the LSBs of the
 * counter run too fast (244 nsec) for the strategy to work.
 * Ignoring the two LSB ensures that the value can't change any
 * faster than ~976 nsec, which guarantees a stable value can be
 * obtained in at most three attempts.
 */
tempbase[1] &= ~((d10) TIM_BASE_RAZ);

/* If the destination is arranged so that the first word is OK, but
 * the second pagefaults, the value will be half-written.  As we
 * expect the PFH to restart the instruction, the both halves will
 * be written the second time.  We could read and write back both
 * halves to avoid this, but the hardware doesn't seem to either.
 */
Write (ea, tempbase[0], prv);
Write (INCA(ea), tempbase[1], prv);
sim_debug (DEB_RRD, &tim_dev, "rdtim() = %012" LL_FMT "o %012" LL_FMT "o\n", tempbase[0], tempbase[1]);
return FALSE;
}

/*
 * Read the contents of location E,E+l, clear the right twelve bits of the low
 * order word read (the part corresponding to the hardware millisecond
 * counter), and place the result in the time base registers in the workspace.
 */
t_bool wrtim (a10 ea, int32 prv)
{
tim_base[0] = Read (ea, prv);
tim_base[1] = CLRS (Read (INCA (ea), prv) & ~((d10) TIM_HWRE_MASK));
sim_debug (DEB_RWR, &tim_dev, "wrtim(%012" LL_FMT "o, %012" LL_FMT "o)\n", tim_base[0], tim_base[1]);
return FALSE;
}

/*
 * Read the contents of the interval register into location E. The period read is
 * the same as that supplied by WRINT.
 */
t_bool rdint (a10 ea, int32 prv)
{
Write (ea, tim_interval, prv);
sim_debug (DEB_RRD, &tim_dev, "rdint() = %012" LL_FMT "o\n", tim_interval);
return FALSE;
}

/* write a new interval timer period (in timer ticks).
 * This does not clear the harware counter, so the first
 * completion can come up to ~1 msc later than the new
 * period.
 *
 * Load the contents of location E into the interval register in
 * the workspace.
 */

t_bool wrint (a10 ea, int32 prv)
{
tim_interval = CLRS (Read (ea, prv));
sim_debug (DEB_RWR, &tim_dev, "wrint(%012" LL_FMT "o)\n", tim_interval);
return update_interval (tim_interval);
}

static t_bool update_interval (d10 new_interval)
{
int32 old_clk_tps = clk_tps;
int32 old_tick_in_usecs = tick_in_usecs;

/*
 * The value provided is in hardware clicks. For a frequency of 4.1
 * MHz, that means that dividing by 4096 (shifting 12 to the right) we get
 * the aproximate value in milliseconds. If any of the rightmost bits is
 * one, we add one unit (4096 ticks ). Reference:
 * AA-H391A-TK_DECsystem-10_DECSYSTEM-20_Processor_Reference_Jun1982.pdf
 * (page 4-37):
 *
 * The timer includes a 12-bit hardware millisecond counter, a doubleword
 * time base kept from it, and an interval register for timed interrupts. The
 * millisecond counter runs continuously at 4.1 MHz and represents an
 * elapsed time of just under 1 ms at each overflow. Whenever the counter is
 * read, its two least significant bits are ignored, so its contents effectively
 * represent a count in microseconds (1/1025th ms).
 * The time base is a double length number kept in a pair of registers in
 * the workspace. It is a 71-bit unsigned quantity in which the entire first
 * word comprises the high order thirty-six bits, and the low order thirty-five
 * are in bits 1-35 of the second word.  In this doubleword, the hardware
 * counter corresponds to the right twelve bits of the low order word. The
 * program can initialize the time base as a number of milliseconds (the low
 * order twelve bits are ignored), and every time the counter overflows the
 * microcode adds 4096 (2**12) to the base.
 *
 * The interval register (in the workspace) holds a period that is specified
 * by the program and corresponds in magnitude to the low order word of the
 * time base. This allows a maximum interval of 223 ms, which is almost 140
 * minutes. At the end of each interval, the :microcode sets Interval Done
 * (RDAPR bit 30), requesting an interrupt on the level assigned to the system
 * flags (§4.8). In a separate workspace register, the microcode starts with
 * the given period, decrements it by 4096 (2**12) every time the millisecond
 * counter overflows, and sets the flag when the contents of this "time to go"
 * register reach zero or less. Hence the countdown is by milliseconds, and
 * any nonzero quantity in the low order twelve bits of the given period adds
 * a whole millisecond to the count. (However, following specification of an
 * interval by the program, the first downcount occurs at the first counter
 * overflow regardless of when the register was loaded.)
 */

tim_new_period = new_interval & ~TIM_HWRE_MASK;
if (new_interval & TIM_HWRE_MASK)
    tim_new_period += 010000;

if (tim_new_period == 0) {
    sim_debug (DEB_TPS, &tim_dev, "update_interval() - ignoring 0 value interval\n");
    return FALSE;
    }
tick_in_usecs = (int32)(((double)new_interval)/(((double)TIM_HW_FREQ)/1000000.0));
if (tick_in_usecs != old_tick_in_usecs)
    sim_debug (DEB_TPS, &tim_dev, "update_interval() - tick_in_usecs changed from %d to %d\n", old_tick_in_usecs, tick_in_usecs);
/* clk_tps is the new number of clocks ticks per second */
clk_tps = (int32) ceil(((double)TIM_HW_FREQ /(double)tim_new_period) - 0.5);
if (clk_tps != old_clk_tps)
    sim_debug (DEB_TPS, &tim_dev, "update_interval() - clk_tps changed from %d to %d\n", old_clk_tps, clk_tps);

/* tmxr is polled every tim_mult clks.  Compute the divisor matching the target. */
tim_mult = (clk_tps <= TIM_TMXR_FREQ) ? 1 : (clk_tps / TIM_TMXR_FREQ) ;

/* Estimate instructions/tick for fixed timing - just for KLAD */
tim_unit.wait = TIM_WAIT_IPS / clk_tps;
tmxr_poll = tim_unit.wait * tim_mult;

/* The next tim_svc will update the activation time.
 *
 */
return FALSE;
}

/* Timer service - the timer is only serviced when the interval
 * programmed in tim_period by wrint expires.  If the interval
 * changes, the timebase update is based on the previous interval.
 * The interval calibration is based on what the new interval will be.
 */

static t_stat tim_svc (UNIT *uptr)
{
if (cpu_unit.flags & UNIT_KLAD) {                       /* diags? */
    tmr_poll = uptr->wait;                              /* fixed clock */
    sim_activate (uptr, tmr_poll);                      /* reactivate unit */
    }
else {
    sim_activate_after (uptr, tick_in_usecs);           /* reactivate unit */
    tmr_poll = sim_activate_time (uptr) - 1;
    }

/* tmxr is polled every tim_mult clks.  Compute the divisor matching the target. */
tim_mult = (clk_tps <= TIM_TMXR_FREQ) ? 1 : (clk_tps / TIM_TMXR_FREQ) ;

tmxr_poll = tim_mult * (int32)(sim_timer_inst_per_sec () / clk_tps);/* set mux poll */
tim_incr_base (tim_base, tim_period);                   /* incr time base based on period of expired interval */
tim_period = tim_new_period;                            /* If interval has changed, update period */
apr_flg = apr_flg | APRF_TIM;                           /* request interrupt */
sim_debug (DEB_INT, &tim_dev, "tim_svc(INT) tmr_poll=%d, tmxr_poll=%d, tim_period=%" LL_FMT "d\n", tmr_poll, tmxr_poll, tim_period);
if (Q_ITS) {                                            /* ITS? */
    if (pi_act == 0)
        quant = (quant + TIM_ITS_QUANT) & DMASK;
    if (TSTS (pcst)) {                                  /* PC sampling? */
        WriteP ((a10) pcst & AMASK, pager_PC);          /* store sample */
        pcst = AOB (pcst);                              /* add 1,,1 */
        }
    }                                                   /* end ITS */
return SCPE_OK;
}

static void tim_incr_base (d10 *base, d10 incr)
{
base[1] = base[1] + incr;                               /* add on incr */
base[0] = base[0] + (base[1] >> 35);                    /* carry to high */
base[0] = base[0] & DMASK;                              /* mask high */
base[1] = base[1] & MMASK;                              /* mask low */
return;
}

/* Timer reset */

static t_stat tim_reset (DEVICE *dptr)
{
sim_debug (DEB_TRC, &tim_dev, "tim_reset()\n");

tim_base[0] = tim_base[1] = 0;                          /* clear timebase (HW does) */
/* HW does not initialize the interval timer, so the rate at which the timer flag
 * sets is random.  No sensible user would enable interrupts or check the flag without
 * setting an interval.  The timebase is intialized to zero by microcode intialization.
 * It increments based on the overflow, so it would be reasonable for a user to just
 * read it twice and subtract the values to determine elapsed time.
 *
 * Simply to keep the simulator overhead down until the interval timer is initialized
 * by the OS or diagnostic, we will set the internal interval to ~17 msec here.
 * This allows the service routine to increment the timebase, and gives RDTIME an
 * baseline for its interpolation.
 */
tim_interval = 0;
clk_tps = 60;
sim_debug (DEB_TPS, &tim_dev, "tim_reset() - clk_tps set to %d\n", clk_tps);
update_interval(17*4096);
tmr_poll = (int32)(20000 * ((double)sim_rand () / (double)RAND_MAX));

apr_flg = apr_flg & ~APRF_TIM;                          /* clear interrupt */

sim_activate (&tim_unit, tmr_poll);                     /* activate unit */
tmxr_poll = tmr_poll * tim_mult;                        /* set mux poll */
return SCPE_OK;
}

/* Set timer parameters from CPU model */

t_stat tim_set_mod (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
if (val & (UNIT_T20|UNIT_KLAD)) {
    clk_tps = TIM_TPS_T20;
    update_interval(((d10)(1000*4096))/clk_tps);
    tmr_poll = tim_unit.wait;
    uptr->flags = uptr->flags | UNIT_Y2K;
    }
else {
    clk_tps = TIM_TPS_T10;
    update_interval (((d10)(1000*4096))/clk_tps);
    tmr_poll = tim_unit.wait;
    if (Q_ITS)
        uptr->flags = uptr->flags | UNIT_Y2K;
    else uptr->flags = uptr->flags & ~UNIT_Y2K;
    }
return SCPE_OK;
}

/* Time of year clock
 *
 * The hardware clock was never sold by DEC, but support for it exists
 * in TOPS-10.  Code was also available for RSX20F to read and report the
 * to the OS via its20F's SETSPD task.  This implements only the read functions.
 *
 * The manufacturer's manual can be found at
 * http://bitsavers.trailing-edge.com/pdf/digitalPathways/tcu-150.pdf
 */

static t_stat tcu_rd (int32 *data, int32 PA, int32 access)
{
time_t curtim;
struct tm *tptr;
t_stat st = SCPE_OK;

curtim = sim_get_time (NULL);                           /* get time */
tptr = localtime (&curtim);                             /* decompose */
if (tptr == NULL)
    return SCPE_NXM;
if ((tptr->tm_year > 99) && !(tim_unit.flags & UNIT_Y2K))
    tptr->tm_year = 99;                                 /* Y2K prob? */

switch ((PA >> 1) & 03) {                               /* decode PA<3:1> */

    case 0:                                             /* year/month/day */
        *data = (((tptr->tm_year) & 0177) << 9) |
                (((tptr->tm_mon + 1) & 017) << 5) |
                ((tptr->tm_mday) & 037);
        break;

    case 1:                                             /* hour/minute */
        *data = (((tptr->tm_hour) & 037) << 8) |
                ((tptr->tm_min) & 077);
        break;

    case 2:                                             /* second */
        *data = (tptr->tm_sec) & 077;
        break;

    case 3:                                             /* status */
        *data = CSR_DONE;
        break;
        }

sim_debug (DEB_RRD, &tim_dev, "tcu_rd() = %o\n", *data);

return st;
}
