blob: 8a12e60f246fc05b01a68a650f0e8c88a7c65426 [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"
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_sysgen(uint8 cid)
{
uint32 sysgen_p;
uint32 cq_exp;
cio_entry cqe;
sysgen_p = pread_w(SYSGEN_PTR);
sim_debug(IO_D_MSG, &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_D_MSG, &cpu_dev,
"[SYSGEN] sysgen rqp = %08x\n",
cio[cid].rqp);
sim_debug(IO_D_MSG, &cpu_dev,
"[SYSGEN] sysgen cqp = %08x\n",
cio[cid].cqp);
sim_debug(IO_D_MSG, &cpu_dev,
"[SYSGEN] sysgen rqs = %02x\n",
cio[cid].rqs);
sim_debug(IO_D_MSG, &cpu_dev,
"[SYSGEN] sysgen cqs = %02x\n",
cio[cid].cqs);
sim_debug(IO_D_MSG, &cpu_dev,
"[SYSGEN] sysgen ivec = %02x\n",
cio[cid].ivec);
sim_debug(IO_D_MSG, &cpu_dev,
"[SYSGEN] sysgen no_rque = %02x\n",
cio[cid].no_rque);
cq_exp = cio[cid].cqp;
cqe.byte_count = 0;
cqe.subdevice = 0;
cqe.opcode = 3;
cqe.address = 0;
cqe.app_data = 0;
cio_cexpress(cid, &cqe);
sim_debug(IO_D_MSG, &cpu_dev,
"[SYSGEN] Sysgen complete. Completion Queue written.\n");
/* If the card has a custom sysgen handler, run it */
if (cio[cid].sysgen != NULL) {
cio[cid].sysgen(cid);
} else {
sim_debug(IO_D_MSG, &cpu_dev,
"[%08x] [cio_sysgen] Not running custom sysgen.\n",
R[NUM_PC]);
}
}
void cio_cexpress(uint8 cid, cio_entry *cqe)
{
uint32 cqp;
cqp = cio[cid].cqp;
sim_debug(IO_D_MSG, &cpu_dev,
"[%08x] [cio_cexpress] cqp = %08x seqbit = %d\n",
R[NUM_PC], cqp, cio[cid].seqbit);
cio[cid].seqbit ^= 1;
if (cio[cid].seqbit) {
cqe->subdevice |= CIO_SEQBIT;
}
pwrite_h(cqp, cqe->byte_count);
pwrite_b(cqp + 2, cqe->subdevice);
pwrite_b(cqp + 3, cqe->opcode);
pwrite_w(cqp + 4, cqe->address);
pwrite_w(cqp + 8, cqe->app_data);
}
/* Write an entry into the Completion Queue */
void cio_cqueue(uint8 cid, cio_entry *cqe)
{
uint32 cqp, top;
uint16 lp;
/* 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 + QUE_OFFSET;
/* 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 + LOAD_OFFSET);
/* 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);
pwrite_w(top + lp + 8, cqe->app_data);
/* 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 + QUE_E_SIZE) % (QUE_E_SIZE * cio[cid].cqs);
/* Store it back to the correct location */
pwrite_h(cqp + LOAD_OFFSET, lp);
} else {
sim_debug(IO_D_MSG, &cpu_dev,
"[%08x] [cio_cqueue] ERROR! Completion Queue Size is 0!",
R[NUM_PC]);
}
}
/* Retrieve an entry from the Request Queue */
void cio_rqueue(uint8 cid, cio_entry *cqe)
{
uint32 rqp, top, i;
uint16 ulp;
/* Get the physical address of the request queue in main memory */
rqp = cio[cid].rqp + 12; /* Skip past the Express Queue Entry */
/* Scan each queue until we find one with a command in it. */
for (i = 0; i < cio[cid].no_rque; i++) {
/* Get the physical address of the first entry in the request
* queue */
top = rqp + 4;
/* Check to see what we've got in the queue. */
ulp = pread_h(rqp + 2);
cqe->opcode = pread_b(top + ulp + 3);
if (cqe->opcode > 0) {
break;
}
rqp += 4 + (12 * cio[cid].rqs);
}
if (i >= cio[cid].no_rque) {
sim_debug(IO_D_MSG, &cpu_dev,
"[%08x] [cio_rque] FAILURE! NO MORE QUEUES TO EXAMINE.\n",
R[NUM_PC]);
return;
}
/* Retrieve the entry at the supplied address */
cqe->byte_count = pread_h(top + ulp);
cqe->subdevice = pread_b(top + ulp + 2);
cqe->address = pread_w(top + ulp + 4);
cqe->app_data = pread_w(top + ulp + 8);
dump_entry("REQUEST", cqe);
/* 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 + QUE_E_SIZE) % (QUE_E_SIZE * cio[cid].rqs);
/* Store it back to the correct location */
pwrite_h(rqp + 2, ulp);
} else {
sim_debug(IO_D_MSG, &cpu_dev,
"[%08x] [cio_rqueue] ERROR! Request Queue Size is 0!",
R[NUM_PC]);
}
}
uint32 io_read(uint32 pa, size_t size)
{
struct iolink *p;
uint8 cid, reg, data;
/* Special devices */
if (pa == MEMSIZE_REG) {
/* It appears that 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;
}
}
/* IO Board Area - Unimplemented */
if (pa >= CIO_BOTTOM && pa < CIO_TOP) {
cid = CID(pa);
reg = pa - CADDR(cid);
if (cio[cid].id == 0) {
/* Nothing lives here */
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].cmdbits) {
case 0x00: /* We've never seen an INT0 or INT1 */
case 0x01: /* We've seen an INT0 but not an INT1. */
sim_debug(IO_D_MSG, &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 0x02: /* We've seen an INT1 but not an INT0. Time to sysgen */
sim_debug(IO_D_MSG, &cpu_dev,
"[READ] [%08x] (%d INT0) SYSGEN\n",
R[NUM_PC], cid);
cio_sysgen(cid);
data = cio[cid].ivec;
break;
case 0x03: /* We've already sysgen'ed */
sim_debug(IO_D_MSG, &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_D_MSG, &cpu_dev,
"[READ] [%08x] (%d INT0) ERROR IN STATE MACHINE cmdbits=%02x\n",
R[NUM_PC], cid, cio[cid].cmdbits);
data = 0;
break;
}
/* Record that we've seen an INT0 */
cio[cid].cmdbits |= CIO_INT0;
return data;
case IOF_CTRL:
switch(cio[cid].cmdbits) {
case 0x00: /* We've never seen an INT0 or INT1 */
case 0x02: /* We've seen an INT1 but not an INT0 */
/* There's nothing to do in this instance */
break;
case 0x01: /* We've seen an INT0 but not an INT1. Time to sysgen */
sim_debug(IO_D_MSG, &cpu_dev,
"[READ] [%08x] (%d INT1) SYSGEN\n",
R[NUM_PC], cid);
cio_sysgen(cid);
break;
case 0x03: /* We've already sysgen'ed */
sim_debug(IO_D_MSG, &cpu_dev,
"[READ] [%08x] (%d INT1) FULL\n",
R[NUM_PC], cid);
cio[cid].full_handler(cid);
break;
default:
/* This should never happen */
stop_reason = STOP_ERR;
sim_debug(IO_D_MSG, &cpu_dev,
"[READ] [%08x] (%d INT1) ERROR IN STATE MACHINE cmdbits=%02x\n",
R[NUM_PC], cid, cio[cid].cmdbits);
break;
}
/* Record that we've seen an INT1 */
cio[cid].cmdbits |= CIO_INT1;
return 0; /* Data returned is arbitrary */
case IOF_STAT:
sim_debug(IO_D_MSG, &cpu_dev,
"[READ] [%08x] (%d RESET)\n",
R[NUM_PC], cid);
cio[cid].cmdbits = 0;
return 0; /* Data returned is arbitrary */
default:
/* We should never reach here, but if we do, there's
* nothing listening. */
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_D_MSG, &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 */
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].cmdbits) {
case 0x00: /* We've never seen an INT0 or INT1 */
case 0x01: /* We've seen an INT0 but not an INT1. */
sim_debug(IO_D_MSG, &cpu_dev,
"[WRITE] [%08x] (%d INT0) ID\n",
R[NUM_PC], cid);
break;
case 0x02: /* We've seen an INT1 but not an INT0. Time to sysgen */
sim_debug(IO_D_MSG, &cpu_dev,
"[READ] [%08x] (%d INT0) SYSGEN\n",
R[NUM_PC], cid);
cio_sysgen(cid);
break;
case 0x03: /* We've already sysgen'ed */
sim_debug(IO_D_MSG, &cpu_dev,
"[READ] [%08x] (%d INT0) EXPRESS JOB\n",
R[NUM_PC], cid);
cio[cid].exp_handler(cid);
break;
default:
/* This should never happen */
stop_reason = STOP_ERR;
sim_debug(IO_D_MSG, &cpu_dev,
"[READ] [%08x] (%d INT0) ERROR IN STATE MACHINE cmdbits=%02x\n",
R[NUM_PC], cid, cio[cid].cmdbits);
break;
}
/* Record that we've seen an INT0 */
cio[cid].cmdbits |= CIO_INT0;
return;
case IOF_CTRL:
switch(cio[cid].cmdbits) {
case 0x00: /* We've never seen an INT0 or INT1 */
case 0x02: /* We've seen an INT1 but not an INT0 */
/* There's nothing to do in this instance */
sim_debug(IO_D_MSG, &cpu_dev,
"[WRITE] [%08x] (%d INT1)\n",
R[NUM_PC], cid);
break;
case 0x01: /* We've seen an INT0 but not an INT1. Time to sysgen */
sim_debug(IO_D_MSG, &cpu_dev,
"[WRITE] [%08x] (%d INT1) SYSGEN\n",
R[NUM_PC], cid);
cio_sysgen(cid);
break;
case 0x03: /* We've already sysgen'ed */
sim_debug(IO_D_MSG, &cpu_dev,
"[WRITE] [%08x] (%d INT1) FULL\n",
R[NUM_PC], cid);
cio[cid].full_handler(cid);
break;
default:
/* This should never happen */
stop_reason = STOP_ERR;
sim_debug(IO_D_MSG, &cpu_dev,
"[WRITE] [%08x] (%d INT1) ERROR IN STATE MACHINE cmdbits=%02x\n",
R[NUM_PC], cid, cio[cid].cmdbits);
break;
}
/* Record that we've seen an INT1 */
cio[cid].cmdbits |= CIO_INT1;
return;
case IOF_STAT:
sim_debug(IO_D_MSG, &cpu_dev,
"[WRITE] [%08x] (%d RESET)\n",
R[NUM_PC], cid);
cio[cid].cmdbits = 0;
return;
default:
/* We should never reach here, but if we do, there's
* nothing listening. */
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_D_MSG, &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(CONST char *type, cio_entry *entry)
{
sim_debug(IO_D_MSG, &cpu_dev,
"*** %s ENTRY: byte_count=%04x, subdevice=%02x,\n"
" opcode=%d, address=%08x, app_data=%08x\n",
type, entry->byte_count, entry->subdevice,
entry->opcode, entry->address, entry->app_data);
}