| /* 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. | |
| */ | |
| #include "pdp11_defs.h" | |
| #include "sim_fio.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; | |
| 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, CONST 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, CONST 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); | |
| } |