/* ka10_dpk.c: Systems Concepts DK-10, Datapoint kludge.

   Copyright (c) 2018-2020, Lars Brinkhoff

   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
   RICHARD CORNWELL 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.

   The Systems Concepts DK-10, also known as the Datapoint kludge, is
   a device with 16 terminal ports.  It's specific to the MIT AI lab
   PDP-10.
*/

#include <time.h>
#include "sim_defs.h"
#include "sim_tmxr.h"
#include "kx10_defs.h"

#define DPK_NAME        "DPK"
#define DPK_DEVNUM      0604
#define DPK_LINES       16

#define DPK_IEN          04000000 /* Interrupt enable. */

#define DPK_PIA         000000007 /* PI channel assignment */
#define DPK_IDONE       000000010 /* Input char available. */
#define DPK_NXM         000000020 /* NXM. */
#define DPK_PAR         000000040 /* Parity error. */
#define DPK_BUSY        000000100 /* Ouput line busy. */
#define DPK_IN          000000200 /* State of input line. */
#define DPK_ODONE       000000400 /* Output buffer done. */
#define DPK_OLINE       017000000 /* Line number, output. */

#define DPK_CONI_BITS   (DPK_PIA | DPK_IDONE | DPK_PAR | DPK_NXM | \
                          DPK_BUSY | DPK_IN | DPK_ODONE | DPK_OLINE)

#define DPK_FN           000000700 /* Function. */
#define DPK_SET_ODONE    000000000 /* Set output done. */
#define DPK_OSTART       000000100 /* Start output. */
#define DPK_ISTOP        000000200 /* Stop input. */
#define DPK_ISTART       000000300 /* Start input. */
#define DPK_OSTOP        000000400 /* Stop output, clear output done. */
#define DPK_OSPEED       000000500 /* Set output speed, start output. */
#define DPK_ISPEED_STOP  000000600 /* Set input speed, stop input. */
#define DPK_ISPEED_START 000000700 /* Set input speed, start input. */
#define DPK_SPEED        000007000 /* Speed code. */
#define DPK_ILINE        000170000 /* Line number. */
#define DPK_MANY         000200000 /* Apply to selected line through highest. */
#define DPK_RESET        000400000 /* Master clear. */

#define PORT_OUTPUT   1
#define PORT_INPUT    2

static t_stat       dpk_devio(uint32 dev, uint64 *data);
static t_stat       dpk_input_svc (UNIT *uptr);
static t_stat       dpk_output_svc (UNIT *uptr);
static t_stat       dpk_reset (DEVICE *dptr);
static t_stat       dpk_attach (UNIT *uptr, CONST char *cptr);
static t_stat       dpk_detach (UNIT *uptr);
static const char   *dpk_description (DEVICE *dptr);
static t_stat       dpk_help (FILE *st, DEVICE *dptr, UNIT *uptr,
                               int32 flag, const char *cptr);
extern int32        tmxr_poll;

TMLN dpk_ldsc[DPK_LINES] = { 0 };
TMXR dpk_desc = { DPK_LINES, 0, 0, dpk_ldsc };

static int dpk_ien = 0;
static int dpk_base = 0;
static int dpk_status = 0;
static int dpk_port[16];
static int dpk_ibuf[16];
static int dpk_ird = 0;
static int dpk_iwr = 0;
static int dpk_port_done = 0;

UNIT                dpk_unit[] = {
    {UDATA(dpk_input_svc, TT_MODE_8B|UNIT_IDLE|UNIT_ATTABLE, 0)},
    {UDATA(dpk_output_svc, UNIT_DIS|UNIT_IDLE, 0)},
};
DIB dpk_dib = {DPK_DEVNUM, 1, &dpk_devio, NULL};

MTAB dpk_mod[] = {
    { TT_MODE, TT_MODE_7B, "7b", "7B", NULL, NULL, NULL, "7 bit mode" },
    { TT_MODE, TT_MODE_8B, "8b", "8B", NULL, NULL, NULL, "8 bit mode" },
    { TT_MODE, TT_MODE_7P, "7p", "7P", NULL, NULL, NULL, "7 bit mode - non printing suppressed" },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 1, NULL, "DISCONNECT",
        &tmxr_dscln, NULL, &dpk_desc, "Disconnect a specific line" },
    { UNIT_ATT, UNIT_ATT, "SUMMARY", NULL,
        NULL, &tmxr_show_summ, (void *) &dpk_desc, "Display a summary of line states" },
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, 1, "CONNECTIONS", NULL,
        NULL, &tmxr_show_cstat, (void *) &dpk_desc, "Display current connections" },
    { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "STATISTICS", NULL,
        NULL, &tmxr_show_cstat, (void *) &dpk_desc, "Display multiplexer statistics" },
    { 0 }
    };

DEVICE dpk_dev = {
    DPK_NAME, dpk_unit, NULL, dpk_mod,
    2, 8, 0, 1, 8, 36,
    NULL, NULL, dpk_reset, NULL, dpk_attach, dpk_detach,
    &dpk_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG | DEV_MUX, 0, dev_debug,
    NULL, NULL, dpk_help, NULL, NULL, dpk_description
};

static void dpk_set_ospeed (int port, uint64 data)
{
    static const char *map[] =
        { "134", "600", "110", "150", "300", "1200", "2400", "4800" };
    static const char *speed;
    speed = map[(data & DPK_SPEED) >> 9];
    sim_debug(DEBUG_CMD, &dpk_dev, "Set port %d output speed %s\n",
              port, speed);
    tmxr_set_line_speed(&dpk_ldsc[port], speed);
}

static t_stat dpk_devio(uint32 dev, uint64 *data)
{
    static int scan = 0;
    DEVICE *dptr = &dpk_dev;
    int port;

    switch(dev & 07) {
    case CONO|4:
        sim_debug(DEBUG_CONO, &dpk_dev, "%012llo\n", *data);
        port = (*data & DPK_ILINE) >> 12;
        if (*data & DPK_RESET)
            dpk_reset (&dpk_dev);
        if (*data & DPK_IDONE) {
            dpk_status &= ~DPK_IDONE;
            dpk_iwr = dpk_ird = 0;
        }
        if (*data & DPK_PAR)
            dpk_status &= ~DPK_PAR;
        if (*data & DPK_NXM)
            dpk_status &= ~DPK_NXM;
        switch (*data & DPK_FN) {
        case DPK_SET_ODONE:
            dpk_status |= DPK_ODONE;
            break;
        case DPK_OSTART:
            dpk_port[port] |= PORT_OUTPUT;
            dpk_port_done &= ~(1 << port);
            if (dpk_port_done == 0)
              dpk_status &= ~DPK_ODONE;
            sim_activate_abs (&dpk_unit[1], 0);
            break;
        case DPK_ISTOP:
            dpk_port[port] &= ~PORT_INPUT;
            break;
        case DPK_ISTART:
            dpk_port[port] |= PORT_INPUT;
            break;
        case DPK_OSTOP:
            dpk_port[port] &= ~PORT_OUTPUT;
            dpk_port_done &= ~(1 << port);
            if (dpk_port_done == 0)
              dpk_status &= ~DPK_ODONE;
            break;
        case DPK_OSPEED:
            dpk_set_ospeed (port, *data);
            dpk_port[port] |= PORT_OUTPUT;
            sim_activate_abs (&dpk_unit[1], 0);
            break;
        case DPK_ISPEED_STOP:
            dpk_port[port] &= ~PORT_INPUT;
            goto ispeed;
        case DPK_ISPEED_START:
            dpk_port[port] |= PORT_INPUT;
        ispeed:
            sim_debug(DEBUG_CMD, &dpk_dev, "Set port %d input speed %lld\n",
                      port, (*data & DPK_SPEED) >> 9);
            break;
        default:
            fprintf (stderr, "Unknown function: %llo\n", *data);
            exit (1);
            break;
        }
        dpk_status &= ~DPK_PIA;
        dpk_status |= *data & DPK_PIA;
        break;
    case CONI|4:
        dpk_status &= ~DPK_OLINE;
        for (port = 0; port < DPK_LINES; port++) {
          scan = (scan + 1) & 017;
          if (dpk_port_done & (1 << scan)) {
            dpk_status |= scan << 18;
            break;
          }
        }
        *data = dpk_status & DPK_CONI_BITS;
        sim_debug(DEBUG_CONI, &dpk_dev, "%07llo\n", *data);
        break;
    case DATAO|4:
        dpk_base = *data & 03777777;
        dpk_ien = ((*data & DPK_IEN) != 0);
        sim_debug(DEBUG_DATAIO, &dpk_dev, "DATAO %06llo\n", *data);
        break;
    case DATAI|4:
        if (dpk_ird == dpk_iwr) {
            *data = 0;
            break;
        }
        *data = dpk_ibuf[dpk_ird++];
        dpk_ird &= 15;
        sim_debug(DEBUG_DATAIO, &dpk_dev, "DATAI %06llo\n", *data);
        if (dpk_ird == dpk_iwr) {
            dpk_status &= ~DPK_IDONE;
        }
        break;
    }

    if (dpk_ien && (dpk_status & (DPK_IDONE | DPK_ODONE)))
        set_interrupt(DPK_DEVNUM, dpk_status & DPK_PIA);
    else
        clr_interrupt(DPK_DEVNUM);

    return SCPE_OK;
}

/* Note, the byte pointer used by the hardware has halfwords swapped. */
static int ildb (uint64 *pointer)
{
    uint64 bp = *pointer;
    int pos, ch, addr;

 again:
    pos = (bp >> 12) & 077;
    pos -= 7;
    addr = (bp >> 18) & 0777777;
    if (pos < 0) {
        pos = 36 - 7;
        addr++;
        addr &= 0777777;
    }

    if (M[addr] & 1) {
        bp = M[addr];
        goto again;
    } else
        *pointer = ((uint64)addr << 18) | (pos << 12) | 7 << 6;

    ch = (M[addr] >> pos) & 0177;
    return ch;
}

static void dpk_output (int port, TMLN *lp)
{
    uint64 count;
    int ch;

    if ((dpk_port[port] & PORT_OUTPUT) == 0)
        return;

    if (tmxr_txdone_ln (lp) == 0)
        return;

    if (M[dpk_base + 2*port] == 0777777777777LL) {
        dpk_port[port] &= ~PORT_OUTPUT;
        dpk_status |= DPK_ODONE;
        dpk_port_done |= 1 << port;
        if (dpk_ien)
            set_interrupt(DPK_DEVNUM, dpk_status & DPK_PIA);
        return;
    }

    ch = ildb (&M[dpk_base + 2*port + 1]);
    ch = sim_tt_outcvt(ch & 0377, TT_GET_MODE (dpk_unit[0].flags));
    tmxr_putc_ln (lp, ch);
            
    count = M[dpk_base + 2*port] - 1;
    M[dpk_base + 2*port] = count & 0777777777777LL;
}

static t_stat dpk_input_svc (UNIT *uptr)
{
    static int scan = 0;
    int32 ch;
    int i;

    sim_clock_coschedule (uptr, 1000);

    i = tmxr_poll_conn (&dpk_desc);
    if (i >= 0) {
        dpk_ldsc[i].rcve = 1;
        dpk_ldsc[i].xmte = 1;
        sim_debug(DEBUG_CMD, &dpk_dev, "Connect %d\n", i);
    }

    tmxr_poll_rx (&dpk_desc);

    for (i = 0; i < DPK_LINES; i++) {
        /* Round robin scan 16 lines. */
        scan = (scan + 1) & 017;

        ch = tmxr_getc_ln(&dpk_ldsc[scan]);
        if (ch & TMXR_VALID) {
            if ((dpk_port[scan] & PORT_INPUT) == 0)
                continue;
            dpk_ibuf[dpk_iwr++] = (scan << 18) | (ch & 0177);
            dpk_iwr &= 15;
            dpk_status |= DPK_IDONE;
            if (dpk_ien)
               set_interrupt(DPK_DEVNUM, dpk_status & DPK_PIA);
            break;
        }
    }

    return SCPE_OK;
}

static t_stat dpk_output_svc (UNIT *uptr)
{
    int i;

    for (i = 0; i < DPK_LINES; i++) {
        dpk_output (i, &dpk_ldsc[i]);
    }

    tmxr_poll_tx (&dpk_desc);
    sim_activate_after (uptr, 1000000);
    return SCPE_OK;
}

static t_stat dpk_reset (DEVICE *dptr)
{
    int i;

    sim_debug(DEBUG_CMD, &dpk_dev, "Reset\n");
    if (dpk_unit->flags & UNIT_ATT)
        sim_activate (&dpk_unit[0], tmxr_poll);
    else {
        sim_cancel (&dpk_unit[0]);
        sim_cancel (&dpk_unit[1]);
    }

    dpk_ien = 0;
    dpk_base = 0;
    dpk_status = 0;
    dpk_ird = dpk_iwr = 0;
    memset (dpk_port, 0, sizeof dpk_port);
    dpk_port_done = 0;
    clr_interrupt(DPK_DEVNUM);

    for (i = 0; i < DPK_LINES; i++) {
        tmxr_set_line_unit (&dpk_desc, i, &dpk_unit[0]);
        tmxr_set_line_output_unit (&dpk_desc, i, &dpk_unit[1]);
    }

    return SCPE_OK;
}

static t_stat dpk_attach (UNIT *uptr, CONST char *cptr)
{
    t_stat stat;
    int i;

    stat = tmxr_attach (&dpk_desc, uptr, cptr);
    for (i = 0; i < DPK_LINES; i++) {
        dpk_ldsc[i].rcve = 0;
        dpk_ldsc[i].xmte = 0;
    }
    if (stat == SCPE_OK)
        sim_activate (uptr, tmxr_poll);
    return stat;
}

static t_stat dpk_detach (UNIT *uptr)
{
    t_stat stat = tmxr_detach (&dpk_desc, uptr);
    int i;

    for (i = 0; i < DPK_LINES; i++) {
        dpk_ldsc[i].rcve = 0;
        dpk_ldsc[i].xmte = 0;
    }
    dpk_status = 0;
    sim_cancel (&dpk_unit[0]);
    sim_cancel (&dpk_unit[1]);

    return stat;
}

static t_stat dpk_help (FILE *st, DEVICE *dptr, UNIT *uptr,
                         int32 flag, const char *cptr)
{
    fprintf (st, "DPK Datapoint kludge terminal multiplexer\n\n");
    fprintf (st, "The ATTACH command specifies the port to be used:\n\n");
    tmxr_attach_help (st, dptr, uptr, flag, cptr);
    fprintf (st, "Terminals can be set to one of three modes: 7P, 7B, or 8B.\n\n");
    fprintf (st, "  mode  input characters        output characters\n\n");
    fprintf (st, "  7P    high-order bit cleared  high-order bit cleared,\n");
    fprintf (st, "                                non-printing characters suppressed\n");
    fprintf (st, "  7B    high-order bit cleared  high-order bit cleared\n");
    fprintf (st, "  8B    no changes              no changes\n\n");
    fprintf (st, "The default mode is 7B.\n\n");
    fprintf (st, "Once DPK is attached and the simulator is running, the terminals listen for\n");
    fprintf (st, "connections on the specified port.  They assume that the incoming connections\n");
    fprintf (st, "are Telnet connections.  The connections remain open until disconnected either\n");
    fprintf (st, "by the Telnet client, a SET DPK DISCONNECT command, or a DETACH DPK command.\n\n");
    fprintf (st, "Other special commands:\n\n");
    fprintf (st, "   sim> SHOW DPK CONNECTIONS    show current connections\n");
    fprintf (st, "   sim> SHOW DPK STATISTICS     show statistics for active connections\n");
    fprintf (st, "   sim> SET DPKn DISCONNECT     disconnects the specified line.\n");
    fprint_reg_help (st, &dc_dev);
    fprintf (st, "\nThe terminals do not support save and restore.  All open connections\n");
    fprintf (st, "are lost when the simulator shuts down or DPK is detached.\n");
    return SCPE_OK;
}


static const char *dpk_description (DEVICE *dptr)
{
    return "Systems Concepts DK-10, Datapoint kludge";
}
