blob: 48a27b73dc851a391fff88d564657799dd433d23 [file] [log] [blame] [raw]
/* 3b2_cpu.h: AT&T 3B2 Model 400 IO and CIO feature cards
Copyright (c) 2017, Seth J. Morabito
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 THE AUTHORS OR COPYRIGHT HOLDERS
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 the author shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization
from the author.
*/
#include "3b2_io.h"
#define CRC_POLYNOMIAL 0xEDB88320
CIO_STATE cio[CIO_SLOTS] = { 0 };
struct iolink iotable[] = {
{ MMUBASE, MMUBASE+MMUSIZE, &mmu_read, &mmu_write },
{ IFBASE, IFBASE+IFSIZE, &if_read, &if_write },
{ IDBASE, IDBASE+IDSIZE, &id_read, &id_write },
{ TIMERBASE, TIMERBASE+TIMERSIZE, &timer_read, &timer_write },
{ NVRAMBASE, NVRAMBASE+NVRAMSIZE, &nvram_read, &nvram_write },
{ CSRBASE, CSRBASE+CSRSIZE, &csr_read, &csr_write },
{ IUBASE, IUBASE+IUSIZE, &iu_read, &iu_write },
{ DMAIDBASE, DMAIDBASE+DMAIDSIZE, &dmac_read, &dmac_write },
{ DMAIUABASE, DMAIUABASE+DMAIUASIZE, &dmac_read, &dmac_write },
{ DMAIUBBASE, DMAIUBBASE+DMAIUBSIZE, &dmac_read, &dmac_write },
{ DMACBASE, DMACBASE+DMACSIZE, &dmac_read, &dmac_write },
{ DMAIFBASE, DMAIFBASE+DMAIFSIZE, &dmac_read, &dmac_write },
{ TODBASE, TODBASE+TODSIZE, &tod_read, &tod_write },
{ 0, 0, NULL, NULL}
};
void cio_clear(uint8 cid)
{
cio[cid].id = 0;
cio[cid].exp_handler = NULL;
cio[cid].full_handler = NULL;
cio[cid].sysgen = NULL;
cio[cid].rqp = 0;
cio[cid].cqp = 0;
cio[cid].rqs = 0;
cio[cid].cqs = 0;
cio[cid].ivec = 0;
cio[cid].no_rque = 0;
cio[cid].ipl = 0;
cio[cid].intr = FALSE;
cio[cid].sysgen_s = 0;
cio[cid].seqbit = 0;
cio[cid].op = 0;
}
/*
* A braindead CRC32 calculator.
*
* This is overkill for what we need: A simple way to tag the contents
* of a block of memory uploaded to a CIO card (so we can
* differentiate between desired functions without actually having to
* disassemble and understand 80186 code!)
*/
uint32 cio_crc32_shift(uint32 crc, uint8 data)
{
uint8 i;
crc = ~crc;
crc ^= data;
for (i = 0; i < 8; i++) {
if (crc & 1) {
crc = (crc >> 1) ^ CRC_POLYNOMIAL;
} else {
crc = crc >> 1;
}
}
return ~crc;
}
void cio_sysgen(uint8 cid)
{
uint32 sysgen_p;
sysgen_p = pread_w(SYSGEN_PTR);
sim_debug(IO_DBG, &cpu_dev,
"[%08x] [SYSGEN] Starting sysgen for card %d. sysgen_p=%08x\n",
R[NUM_PC], cid, sysgen_p);
/* seqbit is always reset to 0 on completion */
cio[cid].seqbit = 0;
cio[cid].rqp = pread_w(sysgen_p);
cio[cid].cqp = pread_w(sysgen_p + 4);
cio[cid].rqs = pread_b(sysgen_p + 8);
cio[cid].cqs = pread_b(sysgen_p + 9);
cio[cid].ivec = pread_b(sysgen_p + 10);
cio[cid].no_rque = pread_b(sysgen_p + 11);
sim_debug(IO_DBG, &cpu_dev,
"[SYSGEN] sysgen rqp = %08x\n",
cio[cid].rqp);
sim_debug(IO_DBG, &cpu_dev,
"[SYSGEN] sysgen cqp = %08x\n",
cio[cid].cqp);
sim_debug(IO_DBG, &cpu_dev,
"[SYSGEN] sysgen rqs = %02x\n",
cio[cid].rqs);
sim_debug(IO_DBG, &cpu_dev,
"[SYSGEN] sysgen cqs = %02x\n",
cio[cid].cqs);
sim_debug(IO_DBG, &cpu_dev,
"[SYSGEN] sysgen ivec = %02x\n",
cio[cid].ivec);
sim_debug(IO_DBG, &cpu_dev,
"[SYSGEN] sysgen no_rque = %02x\n",
cio[cid].no_rque);
/* If the card has a custom sysgen handler, run it */
if (cio[cid].sysgen != NULL) {
cio[cid].sysgen(cid);
} else {
sim_debug(IO_DBG, &cpu_dev,
"[%08x] [cio_sysgen] Not running custom sysgen.\n",
R[NUM_PC]);
}
}
void cio_cexpress(uint8 cid, uint16 esize, cio_entry *cqe, uint8 *app_data)
{
int32 i;
uint32 cqp;
cqp = cio[cid].cqp;
sim_debug(IO_DBG, &cpu_dev,
"[%08x] [cio_cexpress] cqp = %08x seqbit = %d\n",
R[NUM_PC], cqp, cio[cid].seqbit);
cio[cid].seqbit ^= 1;
cqe->subdevice |= (cio[cid].seqbit << 6);
pwrite_h(cqp, cqe->byte_count);
pwrite_b(cqp + 2, cqe->subdevice);
pwrite_b(cqp + 3, cqe->opcode);
pwrite_w(cqp + 4, cqe->address);
/* Write application-specific data. */
for (i = 0; i < (esize - QESIZE); i++) {
pwrite_b(cqp + 8 + i, app_data[i]);
}
}
void cio_cqueue(uint8 cid, uint8 cmd_stat, uint16 esize,
cio_entry *cqe, uint8 *app_data)
{
int32 i;
uint32 cqp, top;
uint16 lp, ulp;
/* Apply the CMD/STAT bit */
cqe->subdevice |= (cmd_stat << 7);
/* Get the physical address of the completion queue
* in main memory */
cqp = cio[cid].cqp;
/* Get the physical address of the first entry in
* the completion queue */
top = cqp + esize + LUSIZE;
/* Get the load pointer. This is a 16-bit absolute offset
* from the top of the queue to the start of the entry. */
lp = pread_h(cqp + esize);
ulp = pread_h(cqp + esize + 2);
/* Load the entry at the supplied address */
pwrite_h(top + lp, cqe->byte_count);
pwrite_b(top + lp + 2, cqe->subdevice);
pwrite_b(top + lp + 3, cqe->opcode);
pwrite_w(top + lp + 4, cqe->address);
/* Write application-specific data. */
for (i = 0; i < (esize - QESIZE); i++) {
pwrite_b(top + lp + 8 + i, app_data[i]);
}
/* Increment the load pointer to the next queue location.
* If we go past the end of the queue, wrap around to the
* start of the queue */
if (cio[cid].cqs > 0) {
lp = (lp + esize) % (esize * cio[cid].cqs);
/* Store it back to the correct location */
pwrite_h(cqp + esize, lp);
} else {
sim_debug(IO_DBG, &cpu_dev,
"[%08x] [cio_cqueue] ERROR! Completion Queue Size is 0!",
R[NUM_PC]);
}
}
/*
* Retrieve the Express Entry from the Request Queue
*/
void cio_rexpress(uint8 cid, uint16 esize, cio_entry *rqe, uint8 *app_data)
{
int32 i;
uint32 rqp;
rqp = cio[cid].rqp;
/* Unload the express entry from the request queue */
rqe->byte_count = pread_h(rqp);
rqe->subdevice = pread_b(rqp + 2);
rqe->opcode = pread_b(rqp + 3);
rqe->address = pread_w(rqp + 4);
for (i = 0; i < (esize - QESIZE); i++) {
app_data[i] = pread_b(rqp + 8 + i);
}
}
/*
* Retrieve an entry from the Request Queue. This function
* returns the load pointer that points to the NEXT available slot.
* This may be used by callers to determine which queue(s) need to
* be serviced.
*
* Returns SCPE_OK on success, or SCPE_NXM if no entry was found.
*/
t_stat cio_rqueue(uint8 cid, uint8 qnum, uint16 esize,
cio_entry *rqe, uint8 *app_data)
{
int32 i;
uint32 rqp, top;
uint16 lp, ulp;
/* Get the physical address of the request queue in main memory */
rqp = cio[cid].rqp +
esize +
(qnum * (LUSIZE + (esize * cio[cid].rqs)));
lp = pread_h(rqp);
ulp = pread_h(rqp + 2);
if (lp == ulp) {
return SCPE_NXM;
}
top = rqp + LUSIZE;
/* Retrieve the entry at the supplied address */
rqe->byte_count = pread_h(top + ulp);
rqe->subdevice = pread_b(top + ulp + 2);
rqe->opcode = pread_b(top + ulp + 3);
rqe->address = pread_w(top + ulp + 4);
/* Read application-specific data. */
for (i = 0; i < (esize - QESIZE); i++) {
app_data[i] = pread_b(top + ulp + 8 + i);
}
/* Increment the unload pointer to the next queue location. If we
* go past the end of the queue, wrap around to the start of the
* queue */
if (cio[cid].rqs > 0) {
ulp = (ulp + esize) % (esize * cio[cid].rqs);
pwrite_h(rqp + 2, ulp);
}
return SCPE_OK;
}
/*
* Return the Load Pointer for the given request queue
*/
uint16 cio_r_lp(uint8 cid, uint8 qnum, uint16 esize)
{
uint32 rqp;
rqp = cio[cid].rqp +
esize +
(qnum * (LUSIZE + (esize * cio[cid].rqs)));
return pread_h(rqp);
}
/*
* Return the Unload Pointer for the given request queue
*/
uint16 cio_r_ulp(uint8 cid, uint8 qnum, uint16 esize)
{
uint32 rqp;
rqp = cio[cid].rqp +
esize +
(qnum * (LUSIZE + (esize * cio[cid].rqs)));
return pread_h(rqp + 2);
}
uint16 cio_c_lp(uint8 cid, uint16 esize)
{
uint32 cqp;
cqp = cio[cid].cqp + esize;
return pread_h(cqp);
}
uint16 cio_c_ulp(uint8 cid, uint16 esize)
{
uint32 cqp;
cqp = cio[cid].cqp + esize;
return pread_h(cqp + 2);
}
/*
* Returns true if there is room in the completion queue
* for a new entry.
*/
t_bool cio_cqueue_avail(uint8 cid, uint16 esize)
{
uint32 lp, ulp;
lp = pread_h(cio[cid].cqp + esize);
ulp = pread_h(cio[cid].cqp + esize + 2);
return(((lp + esize) % (cio[cid].cqs * esize)) != ulp);
}
uint32 io_read(uint32 pa, size_t size)
{
struct iolink *p;
uint8 cid, reg, data;
/* Special devices */
if (pa == MEMSIZE_REG) {
/* The following values map to memory sizes:
0x00: 512KB ( 524,288 B)
0x01: 2MB (2,097,152 B)
0x02: 1MB (1,048,576 B)
0x03: 4MB (4,194,304 B)
*/
switch(MEM_SIZE) {
case 0x80000: /* 512KB */
return 0;
case 0x100000: /* 1MB */
return 2;
case 0x200000: /* 2MB */
return 1;
case 0x400000: /* 4MB */
return 3;
default:
return 0;
}
}
/* CIO board area */
if (pa >= CIO_BOTTOM && pa < CIO_TOP) {
cid = CID(pa);
reg = pa - CADDR(cid);
if (cio[cid].id == 0) {
/* Nothing lives here */
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] No card at cid=%d reg=%d\n",
R[NUM_PC], cid, reg);
csr_data |= CSRTIMO;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
return 0;
}
/* A normal SYSGEN sequence is: RESET -> INT0 -> INT1.
* However, there's a bug in the 3B2/400 DGMON test suite that
* runs on every startup. This diagnostic code performs a
* SYSGEN by calling RESET -> INT1 -> INT0. So, we must handle
* both orders. */
switch (reg) {
case IOF_ID:
case IOF_VEC:
switch(cio[cid].sysgen_s) {
case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */
case CIO_INT0: /* We've seen an INT0 but not an INT1. */
cio[cid].sysgen_s |= CIO_INT0;
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] (%d INT0) ID\n",
R[NUM_PC], cid);
/* Return the correct byte of our board ID */
if (reg == IOF_ID) {
data = (cio[cid].id >> 8) & 0xff;
} else {
data = (cio[cid].id & 0xff);
}
break;
case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */
cio[cid].sysgen_s |= CIO_INT0;
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] (%d INT0) SYSGEN\n",
R[NUM_PC], cid);
cio_sysgen(cid);
data = cio[cid].ivec;
break;
case CIO_SYSGEN: /* We've already sysgen'ed */
cio[cid].sysgen_s |= CIO_INT0; /* This must come BEFORE the exp_handler */
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] (%d INT0) EXPRESS JOB\n",
R[NUM_PC], cid);
cio[cid].exp_handler(cid);
data = cio[cid].ivec;
break;
default:
/* This should never happen */
stop_reason = STOP_ERR;
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n",
R[NUM_PC], cid, cio[cid].sysgen_s);
data = 0;
break;
}
return data;
case IOF_CTRL:
switch(cio[cid].sysgen_s) {
case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */
case CIO_INT1: /* We've seen an INT1 but not an INT0 */
/* There's nothing to do in this instance */
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] (%d INT1) IGNORED\n",
R[NUM_PC], cid);
cio[cid].sysgen_s |= CIO_INT1;
break;
case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] (%d INT1) SYSGEN\n",
R[NUM_PC], cid);
cio[cid].sysgen_s |= CIO_INT1;
cio_sysgen(cid);
break;
case CIO_SYSGEN: /* We've already sysgen'ed */
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] (%d INT1) FULL\n",
R[NUM_PC], cid);
cio[cid].sysgen_s |= CIO_INT1; /* This must come BEFORE the full handler */
cio[cid].full_handler(cid);
break;
default:
/* This should never happen */
stop_reason = STOP_ERR;
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n",
R[NUM_PC], cid, cio[cid].sysgen_s);
break;
}
return 0; /* Data returned is arbitrary */
case IOF_STAT:
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] (%d RESET)\n",
R[NUM_PC], cid);
cio[cid].sysgen_s = 0;
return 0; /* Data returned is arbitrary */
default:
/* We should never reach here, but if we do, there's
* nothing listening. */
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] No card at cid=%d reg=%d\n",
R[NUM_PC], cid, reg);
csr_data |= CSRTIMO;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
return 0;
}
}
/* Memory-mapped IO devices */
for (p = &iotable[0]; p->low != 0; p++) {
if ((pa >= p->low) && (pa < p->high) && p->read) {
return p->read(pa, size);
}
}
/* Not found. */
sim_debug(IO_DBG, &cpu_dev,
"[%08x] [io_read] ADDR=%08x: No device found.\n",
R[NUM_PC], pa);
csr_data |= CSRTIMO;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
return 0;
}
void io_write(uint32 pa, uint32 val, size_t size)
{
struct iolink *p;
uint8 cid, reg;
/* Feature Card Area */
if (pa >= CIO_BOTTOM && pa < CIO_TOP) {
cid = CID(pa);
reg = pa - CADDR(cid);
if (cio[cid].id == 0) {
/* Nothing lives here */
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] No card at cid=%d reg=%d\n",
R[NUM_PC], cid, reg);
csr_data |= CSRTIMO;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
return;
}
/* A normal SYSGEN sequence is: RESET -> INT0 -> INT1.
* However, there's a bug in the 3B2/400 DGMON test suite that
* runs on every startup. This diagnostic code performs a
* SYSGEN by calling RESET -> INT1 -> INT0. So, we must handle
* both orders. */
switch (reg) {
case IOF_ID:
case IOF_VEC:
switch(cio[cid].sysgen_s) {
case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */
case CIO_INT0: /* We've seen an INT0 but not an INT1. */
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] (%d INT0) ID\n",
R[NUM_PC], cid);
cio[cid].sysgen_s |= CIO_INT0;
break;
case CIO_INT1: /* We've seen an INT1 but not an INT0. Time to sysgen */
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] (%d INT0) SYSGEN\n",
R[NUM_PC], cid);
cio[cid].sysgen_s |= CIO_INT0;
cio_sysgen(cid);
break;
case CIO_SYSGEN: /* We've already sysgen'ed */
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] (%d INT0) EXPRESS JOB\n",
R[NUM_PC], cid);
cio[cid].sysgen_s |= CIO_INT0;
cio[cid].exp_handler(cid);
break;
default:
/* This should never happen */
stop_reason = STOP_ERR;
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] (%d INT0) ERROR IN STATE MACHINE sysgen_s=%02x\n",
R[NUM_PC], cid, cio[cid].sysgen_s);
break;
}
return;
case IOF_CTRL:
switch(cio[cid].sysgen_s) {
case CIO_INT_NONE: /* We've never seen an INT0 or INT1 */
case CIO_INT1: /* We've seen an INT1 but not an INT0 */
/* There's nothing to do in this instance */
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] (%d INT1) IGNORED\n",
R[NUM_PC], cid);
cio[cid].sysgen_s |= CIO_INT1;
break;
case CIO_INT0: /* We've seen an INT0 but not an INT1. Time to sysgen */
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] (%d INT1) SYSGEN\n",
R[NUM_PC], cid);
cio[cid].sysgen_s |= CIO_INT1;
cio_sysgen(cid);
break;
case CIO_SYSGEN: /* We've already sysgen'ed */
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] (%d INT1) FULL\n",
R[NUM_PC], cid);
cio[cid].sysgen_s |= CIO_INT1;
cio[cid].full_handler(cid);
break;
default:
/* This should never happen */
stop_reason = STOP_ERR;
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] (%d INT1) ERROR IN STATE MACHINE sysgen_s=%02x\n",
R[NUM_PC], cid, cio[cid].sysgen_s);
break;
}
return;
case IOF_STAT:
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] (%d RESET)\n",
R[NUM_PC], cid);
cio[cid].sysgen_s = 0;
return;
default:
/* We should never reach here, but if we do, there's
* nothing listening. */
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] No card at cid=%d reg=%d\n",
R[NUM_PC], cid, reg);
csr_data |= CSRTIMO;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
return;
}
}
/* Memory-mapped IO devices */
for (p = &iotable[0]; p->low != 0; p++) {
if ((pa >= p->low) && (pa < p->high) && p->write) {
p->write(pa, val, size);
return;
}
}
/* Not found. */
sim_debug(IO_DBG, &cpu_dev,
"[%08x] [io_write] ADDR=%08x: No device found.\n",
R[NUM_PC], pa);
csr_data |= CSRTIMO;
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);
}
/* For debugging only */
void dump_entry(uint32 dbits, DEVICE *dev, CONST char *type,
uint16 esize, cio_entry *entry, uint8 *app_data)
{
sim_debug(dbits, dev,
"*** %s ENTRY: byte_count=%04x, subdevice=%02x, opcode=%d, address=%08x\n",
type, entry->byte_count, entry->subdevice,
entry->opcode, entry->address);
}