/* pdp11_uc15.c: UC15 interface simulator

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

   uca           DR11 #1
   ucb           DR11 #2

   The DR11Cs provide control communications with the DR15C in the PDP15.

   The PDP15 and UC15 use a master/slave communications protocol.
   - The PDP15 initiates a request to the PDP11 by writing TCBP and
     clearing TCBP acknowledge. This alerts/interrupts the PDP11.
   - The PDP11 reads TCBP. This sets TCBP acknowledge, which is
     not wired to interrupt on the PDP15. Note that TCBP has been
     converted from a word address to a byte address by the way
     the two systems are wired together.
   - The PDP11 processes the request.
   - The PDP11 signals completion by writing a vector into one of
     four API request levels.
   - The PDP15 is interrupted, and the request is considered complete.

   The UC15 must "call out" to the PDP15 to signal two conditions:
   - the TCB pointer has been read
   - an API interrupt is requested

   The DR15 must "call in" to the UC15 for two reasons:
   - the TCBP has been written
   - API interrupt status has changed

   The DR15 and UC15 use a shared memory section and ATOMIC operations
   to communicate. Shared state is maintained in shared memory, with one
   side having read/write access, the other read-only. Actions are
   implemented by setting signals with an atomic compare-and-swap.
   The signals may be polled with non-atomic operations but must be
   verified with an atomic compare-and-swap.

   21-Jul-18    RMS         Fixed missing size multiplier in reset (Mark Pizzolato)
*/

#include "pdp11_defs.h"

#include "sim_shmem.h"
#include "uc15_defs.h"

/* Constants */

/* DR11 #1 */

#define UCAC_APID           (CSR_DONE)
#define UCAB_V_TCBHI        0
#define UCAB_M_TCBHI        03
#define UCAB_API2           0000100
#define UCAB_API0           0000200
#define UCAB_V_LOCAL        8
#define UCAB_M_LOCAL        07
#define UCAB_API3           0040000
#define UCAB_API1           0100000

/* DR11 #2 */

#define UCBC_NTCB           (CSR_DONE)

/* Declarations */

extern int32 int_req[IPL_HLVL];
extern UNIT cpu_unit;
extern uint16 *M;

int32 uca_csr = 0;                                      /* DR11C #1 CSR */
int32 uca_buf = 0;                                      /* DR11C #1 input buffer */
int32 ucb_csr = 0;
int32 ucb_buf = 0;
int32 uc15_poll = 3;                                    /* polling interval */
SHMEM *uc15_shmem = NULL;                               /* shared state identifier */
int32 *uc15_shstate = NULL;                             /* shared state base */
SHMEM *pdp15_shmem = NULL;                              /* PDP15 mem identifier */
int32 *pdp15_mem = NULL;
uint32 uc15_memsize = 0;

DEVICE uca_dev, ucb_dev;
t_stat uca_rd (int32 *data, int32 PA, int32 access);
t_stat uca_wr (int32 data, int32 PA, int32 access);
t_stat ucb_rd (int32 *data, int32 PA, int32 access);
t_stat ucb_wr (int32 data, int32 PA, int32 access);
t_stat uc15_reset (DEVICE *dptr);
t_stat uc15_svc (UNIT *uptr);
t_stat uc15_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw);
t_stat uc15_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw);
t_stat uc15_attach (UNIT *uptr, char *cptr);
t_stat uc15_detach (UNIT *uptr);

void uc15_set_memsize (void);
int32 uc15_get_uca_buf (void);
t_stat uc15_api_req (int32 lvl, int32 vec);

/* UC15 data structures

   uca_dev, ucb_dev       UC15 device descriptor
   uca_unit, ucb_unit     UC15 unit descriptor
   uca_reg, ucb reg       UC15 register list

   The two DR11Cs must be separate devices because they interrupt at
   different IPLs and must have different DIBs!
*/

DIB uca_dib = {
    IOBA_UCA, IOLN_UCA, &uca_rd, &uca_wr,
    1, IVCL (UCA), VEC_UCA, { NULL }
    };

UNIT uca_unit = { UDATA (&uc15_svc, 0, UNIT_ATTABLE) };

REG uca_reg[] = {
    { ORDATA (CSR, uca_csr, 16) },
    { ORDATA (BUF, uca_buf, 16) },
    { FLDATA (APID, uca_csr, CSR_V_DONE) },
    { FLDATA (IE, uca_csr, CSR_V_IE) },
    { DRDATA (POLL, uc15_poll, 10), REG_NZ },
    { DRDATA (UCMEMSIZE, uc15_memsize, 18), REG_HRO },
    { NULL }
    };

MTAB uc15_mod[] = {
    { MTAB_XTD|MTAB_VDV, 006, "ADDRESS", "ADDRESS",
      NULL, &show_addr, NULL },
    { MTAB_XTD|MTAB_VDV, 0, "VECTOR", "VECTOR",
      NULL, &show_vec, NULL },
    { 0 }
    };

DEVICE uca_dev = {
    "UCA", &uca_unit, uca_reg, uc15_mod,
    1, 8, 10, 1, 8, 32,
    &uc15_ex, &uc15_dep, &uc15_reset,
    NULL, &uc15_attach, &uc15_detach,
    &uca_dib, DEV_DISABLE | DEV_DEBUG
    };

DIB ucb_dib = {
    IOBA_UCB, IOLN_UCB, &ucb_rd, &ucb_wr,
    1, IVCL (UCB), VEC_UCB, { NULL }
    };

UNIT ucb_unit = { UDATA (NULL, 0, 0) };

REG ucb_reg[] = {
    { ORDATA (CSR, ucb_csr, 16) },
    { ORDATA (BUF, ucb_buf, 16) },
    { FLDATA (NTCB, ucb_csr, CSR_V_DONE) },
    { FLDATA (IE, ucb_csr, CSR_V_IE) },
    { NULL }
    };

DEVICE ucb_dev = {
    "UCB", &ucb_unit, ucb_reg, uc15_mod,
    1, 8, 18, 1, 8, 18,
    NULL, NULL, NULL,
    NULL, NULL, NULL,
    &ucb_dib, DEV_DISABLE
    };

/* IO routines */

/* DR11 #1 */

t_stat uca_rd (int32 *data, int32 PA, int32 access)
{
switch ((PA >> 1) & 03) {                               /* case on PA<2:1> */

case 0:                                                 /* CSR */
    *data = uca_csr;
    return SCPE_OK;

case 1:                                                 /* output buffers */
    return SCPE_OK;

case 2:                                                 /* input buffer */
    *data = uc15_get_uca_buf ();                        /* assemble buffer */
    return SCPE_OK;
    }

return SCPE_NXM;
}

t_stat uca_wr (int32 data, int32 PA, int32 access)
{
switch ((PA >> 1) & 03) {                               /* case on PA<2:1> */

case 0:                                                 /* CSR */
    if (PA & 1)
        return SCPE_OK;
    if ((data & CSR_IE) == 0)
        CLR_INT (UCA);
    else if ((uca_csr & (UCAC_APID + CSR_IE)) == UCAC_APID)
        SET_INT (UCA);
    uca_csr = (uca_csr & ~CSR_IE) | (data & CSR_IE);
    return SCPE_OK;

case 1:                                                 /* output buffer */
    if (PA & 1)                                         /* odd byte? API 1 */
        uc15_api_req (1, data & 0377);
    else {
        if (access == WRITE)                            /* full word? API 1 */
            uc15_api_req (1, (data >> 8) & 0377);
        uc15_api_req (0, data & 0377);                  /* API 0 */
        }
    return SCPE_OK;

case 2:
    return SCPE_OK;
    }

return SCPE_NXM;
}

t_stat ucb_rd (int32 *data, int32 PA, int32 access)
{
switch ((PA >> 1) & 03) {                               /* case on PA<2:1> */

case 0:                                                 /* CSR */
    *data = ucb_csr;
    return SCPE_OK;

case 1:                                                 /* output buffers */
    return SCPE_OK;

case 2:                                                 /* input buffer */
     *data = ucb_buf = (UC15_SHARED_RD (UC15_TCBP) << 1) & 0177777;
     ucb_csr &= ~UCBC_NTCB;                             /* clear TCBP rdy */
     CLR_INT (UCB);                                     /* clear int */
     UC15_ATOMIC_CAS (UC15_TCBP_RD, 0, 1);              /* send ACK */
     if (DEBUG_PRS (uca_dev)) {
        uint32 apiv, apil, fnc, tsk, pa;
        t_bool spl;

        pa = ucb_buf + MEMSIZE;
        apiv = RdMemB (pa);
        apil = RdMemB (pa + 1);
        fnc = RdMemB (pa + 2);
        spl = (RdMemB (pa + 3) & 0200) != 0;
        tsk = RdMemB (pa + 3) & 0177;
        fprintf (sim_deb, ">> UC15: TCB rcvd, API = %o/%d, fnc = %o, %s task = %o, eventvar = %o\n",
            apiv, apil, fnc, spl? "Spooled": "Unspooled", tsk, RdMemW (pa + 4));
        fprintf (sim_deb, "Additional parameters = %o %o %o %o %o\n",
            RdMemW (pa + 6), RdMemW (pa + 8), RdMemW (pa + 10), RdMemW (pa + 12), RdMemW (pa + 14));
        }
     return SCPE_OK;
     }

return SCPE_NXM;
}     

t_stat ucb_wr (int32 data, int32 PA, int32 access)
{
switch ((PA >> 1) & 03) {                               /* case on PA<2:1> */

case 0:                                                 /* CSR */
    if (PA & 1)
        return SCPE_OK;
    if ((data & CSR_IE) == 0)                           /* IE = 0? */
        CLR_INT (UCB);
    else if ((ucb_csr & (UCBC_NTCB + CSR_IE)) == UCBC_NTCB)
        SET_INT (UCB);
    ucb_csr = (ucb_csr & ~CSR_IE) | (data & CSR_IE);
    return SCPE_OK;

case 1:                                                 /* output buffer */
    if (PA & 1)                                         /* odd byte? API 3*/
        uc15_api_req (3, data & 0377);
    else {
        if (access == WRITE)                            /* full word? API 3 */
            uc15_api_req (3, (data >> 8) & 0377);
        uc15_api_req (2, data & 0377);                  /* API 2 */
        }
    return SCPE_OK;

case 2:
    return SCPE_OK;
    }

return SCPE_NXM;
}

/* Request PDP15 to take an API interrupt */

t_stat uc15_api_req (int32 lvl, int32 vec)
{
UC15_SHARED_WR (UC15_API_VEC + (lvl * UC15_API_VEC_MUL), vec);
UC15_ATOMIC_CAS (UC15_API_REQ + (lvl * UC15_API_VEC_MUL), 0, 1);
if (DEBUG_PRS (uca_dev))
    fprintf (sim_deb, ">>UC15: API request sent, API = %o/%d\n",
                vec, lvl);
return SCPE_OK;
}

/* Routine to poll for state changes from PDP15 */

t_stat uc15_svc (UNIT *uptr)
{
uint32 t;

t = UC15_SHARED_RD (UC15_TCBP_WR);                      /* TCBP written? */
if ((t != 0) && UC15_ATOMIC_CAS (UC15_TCBP_WR, 1, 0)) { /* for real? */
    ucb_csr |= UCBC_NTCB;                               /* set new TCB flag */
    if (ucb_csr & CSR_IE)
        SET_INT (UCB);
    uc15_set_memsize ();                                /* update mem size */
    }
t = UC15_SHARED_RD (UC15_API_UPD);                      /* API update? */
if ((t != 0) && UC15_ATOMIC_CAS (UC15_API_UPD, 1, 0)) { /* for real? */
    uc15_get_uca_buf ();                                /* update UCA buf */
    }
sim_activate (uptr, uc15_poll);                         /* next poll */
return SCPE_OK;
}

/* Routine to assemble/update uca_buf

  Note that the PDP-15 and PDP-11 have opposite interpretations of
  API requests. On the PDP-15, a "1" indicates an active request.
  On the PDP-11, a "1" indicates request done (API inactive).
*/

int32 uc15_get_uca_buf (void)
{
int32 i, t;
static int32 ucab_api[4] =
    { UCAB_API0, UCAB_API1, UCAB_API2, UCAB_API3 };

t = UC15_SHARED_RD (UC15_TCBP);                         /* get TCB ptr */
uca_buf = (t >> 15) & UCAB_M_TCBHI;                     /* PDP15 bits<1:2> */
t = cpu_unit.capac >> 13;                               /* local mem in 4KW */
uca_buf |= ((t & UCAB_M_LOCAL) << UCAB_V_LOCAL);
t = UC15_SHARED_RD (UC15_API_SUMM);                     /* get API summary */
for (i = 0; i < 4; i++) {                               /* check 0..3 */
    if (((t >> i) & 1) == 0)                            /* level inactive? */
        uca_buf |= ucab_api[i];                          /* set status bit */
    }
if ((t == 0) && ((uca_csr & UCAC_APID) == 0)) {         /* API req now 0? */
    uca_csr |= UCAC_APID;                               /* set flag */
    if ((uca_csr & CSR_IE) != 0)                        /* if ie, req int */
        SET_INT (UCA);
    }
return uca_buf;
}

/* Routine to set overall memory limit for UC15 checking */

void uc15_set_memsize (void)
{
uint32 t = UC15_SHARED_RD (UC15_PDP15MEM);              /* get PDP15 memory size */
if (t == 0)                                             /* PDP15 not running? */
    t = PDP15_MAXMEM * 2;                               /* max mem in bytes */
uc15_memsize = t + MEMSIZE;                             /* shared + local mem */
if (uc15_memsize > (UNIMEMSIZE - IOPAGESIZE))           /* more than 18b? */
    uc15_memsize = UNIMEMSIZE - IOPAGESIZE;             /* limit */
return;
}

/* Reset routine

   Aside from performing a device reset, this routine sets up shared
   UC15 state and shared PDP15 main memory. It also reads the size
   of PDP15 main memory (in PDP11 bytes) from the shared state region.
*/

t_stat uc15_reset (DEVICE *dptr)
{
t_stat r;
void *basead;

uca_csr = 0;
uca_buf = 0;
ucb_csr = 0;
ucb_buf = 0;
CLR_INT (UCA);
CLR_INT (UCB);
if (uc15_shmem == NULL) {                               /* allocate shared state */
    r = sim_shmem_open ("UC15SharedState", UC15_STATE_SIZE * sizeof (int32), &uc15_shmem, &basead);
    if (r != SCPE_OK)
        return r;
    uc15_shstate = (int32 *) basead;
    }
if (pdp15_shmem == NULL) {                              /* allocate shared memory */
    r = sim_shmem_open ("PDP15MainMemory", PDP15_MAXMEM * sizeof (int32), &pdp15_shmem, &basead);
    if (r != SCPE_OK)
        return r;
    pdp15_mem = (int32 *) basead;
    }
uc15_set_memsize ();
sim_activate (dptr->units, uc15_poll);                  /* start polling */
return SCPE_OK;
}

/* Shared state ex/mod routines for debug */

t_stat uc15_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw)
{
if (addr >= UC15_STATE_SIZE)
    return SCPE_NXM;
if (vptr != NULL)
    *vptr = UC15_SHARED_RD ((int32) addr);
return SCPE_OK;
}

t_stat uc15_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw)
{
if (addr >= UC15_STATE_SIZE)
    return SCPE_NXM;
UC15_SHARED_WR ((int32) addr, (int32) val);
return SCPE_OK;
}

/* Fake attach routine to kill attach attempts */

t_stat uc15_attach (UNIT *uptr, char *cptr)
{
return SCPE_NOFNC;
}

/* Shutdown detach routine to release shared memories */

t_stat uc15_detach (UNIT *uptr)
{
if ((sim_switches & SIM_SW_SHUT) == 0)                  /* only if shutdown */
    return SCPE_NOFNC;
sim_shmem_close (uc15_shmem);                           /* release shared state */
sim_shmem_close (pdp15_shmem);                          /* release shared mem */
return SCPE_OK;
}

/* Physical read/write memory routines
   Used by CPU and IO devices
   Physical address is known to be legal
   We can use MEMSIZE rather than cpu_memsize because configurations
   were limited to 16KW of local memory
   8b and 16b writes clear the upper 2b of PDP-15 memory
*/

int32 uc15_RdMemW (int32 pa)
{
if (((uint32) pa) < MEMSIZE)
    return M[pa >> 1];
else {
    pa = pa - MEMSIZE;
    return (pdp15_mem[pa >> 1] & DMASK);
    }
}

int32 uc15_RdMemB (int32 pa)
{
if (((uint32) pa) < MEMSIZE)
    return ((pa & 1)? (M[pa >> 1] >> 8): (M[pa >> 1] & 0377));
else {
    pa = pa - MEMSIZE;
    return ((pa & 1)? (pdp15_mem[pa >> 1] >> 8): (pdp15_mem[pa >> 1] & 0377));
    }
}

void uc15_WrMemW (int32 pa, int32 d)
{
if (((uint32) pa) < MEMSIZE)
    M[pa >> 1] = d;
else {
    pa = pa - MEMSIZE;
    pdp15_mem[pa >> 1] = d & DMASK;
    }
return;
}

void uc15_WrMemB (int32 pa, int32 d)
{
if (((uint32) pa) < MEMSIZE)
    M[pa >> 1] = (pa & 1)?
         ((M[pa >> 1] & 0377) | ((d & 0377) << 8)): \
         ((M[pa >> 1] & ~0377) | (d & 0377));
else {
    pa = pa - MEMSIZE;
    pdp15_mem[pa >> 1] = (pa & 1)?
         ((pdp15_mem[pa >> 1] & 0377) | ((d & 0377) << 8)): \
         ((pdp15_mem[pa >> 1] & ~0377) | (d & 0377));
    }
return;
}

/* 18b DMA routines - physical only */

int32 Map_Read18 (uint32 ba, int32 bc, uint32 *buf)
{
uint32 alim, lim;

ba = (ba & UNIMASK) & ~01;                              /* trim, align addr */
lim = ba + (bc & ~01);
if (lim < uc15_memsize)                                 /* end ok? */
    alim = lim;
else if (ba < uc15_memsize)                             /* no, strt ok? */
    alim = uc15_memsize;
else return bc;                                         /* no, err */
for ( ; ba < alim; ba = ba + 2) {                       /* by 18b words */
    if (ba < MEMSIZE)
        *buf++ = M[ba >> 1];
    else *buf++ = pdp15_mem[(ba - MEMSIZE) >> 1] & 0777777;
    }
return (lim - alim);
}

int32 Map_Write18 (uint32 ba, int32 bc, uint32 *buf)
{
uint32 alim, lim;

ba = (ba & UNIMASK) & ~01;                              /* trim, align addr */
lim = ba + (bc & ~01);
if (lim < uc15_memsize)                                 /* end ok? */
    alim = lim;
else if (ba < uc15_memsize)                              /* no, strt ok? */
    alim = uc15_memsize;
else return bc;                                         /* no, err */
for ( ; ba < alim; ba = ba + 2) {                       /* by 18 bit words */
    if (ba < MEMSIZE)
        M[ba >> 1] = *buf++ & DMASK;
    else pdp15_mem[(ba - MEMSIZE) >> 1] = *buf++ & 0777777;
    }
return (lim - alim);
}
