3b2: CIO PORTS and CTC devices

This change adds Common-IO implementations of the PORTS 4-port serial
line card and the CTC tape controller card.
This commit is contained in:
Seth Morabito 2018-05-21 14:29:58 -07:00
parent 584147fb64
commit ba9d8626e9
17 changed files with 2647 additions and 347 deletions

View file

@ -127,8 +127,8 @@ static DEBTAB cpu_deb_tab[] = {
{ "EXECUTE", EXECUTE_MSG, "Instruction execute" },
{ "INIT", INIT_MSG, "Initialization" },
{ "IRQ", IRQ_MSG, "Interrupt Handling" },
{ "IO", IO_D_MSG, "I/O Dispatch" },
{ "TRACE", TRACE_MSG, "Call Trace" },
{ "IO", IO_DBG, "I/O Dispatch" },
{ "TRACE", TRACE_DBG, "Call Trace" },
{ "ERROR", ERR_MSG, "Error" },
{ NULL, 0 }
};
@ -138,6 +138,11 @@ UNIT cpu_unit = { UDATA (NULL, UNIT_FIX|UNIT_BINK|UNIT_IDLE, MAXMEMSIZE) };
#define UNIT_V_EXHALT (UNIT_V_UF + 0) /* halt to console */
#define UNIT_EXHALT (1u << UNIT_V_EXHALT)
const char *cio_names[8] = {
"", "*VOID*", "*VOID*", "PORTS",
"*VOID*", "CTC", "*VOID*", "*VOID*"
};
MTAB cpu_mod[] = {
{ UNIT_MSIZE, (1u << 20), NULL, "1M",
&cpu_set_size, NULL, NULL, "Set Memory to 1M bytes" },
@ -149,8 +154,10 @@ MTAB cpu_mod[] = {
&cpu_set_hist, &cpu_show_hist, NULL, "Displays instruction history" },
{ MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "VIRTUAL", NULL,
NULL, &cpu_show_virt, NULL, "Show translation for virtual address" },
{ MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "STACK", "STACK",
{ MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "STACK", NULL,
NULL, &cpu_show_stack, NULL, "Display the current stack with optional depth" },
{ MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "CIO", NULL,
NULL, &cpu_show_cio, NULL, "Display CIO configuration" },
{ MTAB_XTD|MTAB_VDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle },
{ MTAB_XTD|MTAB_VDV, 0, NULL, "NOIDLE", &sim_clr_idle, NULL },
{ UNIT_EXHALT, UNIT_EXHALT, "Halt on Exception", "EXHALT",
@ -532,6 +539,19 @@ t_stat cpu_show_stack(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
return SCPE_OK;
}
t_stat cpu_show_cio(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
uint32 i;
fprintf(st, " SLOT DEVICE\n");
fprintf(st, "---------------------\n");
for (i = 0; i < CIO_SLOTS; i++) {
fprintf(st, " %d %s\n", i, cio_names[cio[i].id & 0x7]);
}
return SCPE_OK;
}
void cpu_load_rom()
{
uint32 i, index, sc, mask, val;
@ -988,13 +1008,18 @@ t_stat cpu_show_virt(FILE *of, UNIT *uptr, int32 val, CONST void *desc)
if (r == SCPE_OK) {
fprintf(of, "Virtual %08x = Physical %08x\n", va, pa);
return SCPE_OK;
} else {
fprintf(of, "Translation not possible for virtual address.\n");
return SCPE_ARG;
}
} else {
fprintf(of, "Illegal address format.\n");
return SCPE_ARG;
}
}
fprintf(of, "Translation not possible.\n");
return SCPE_OK;
fprintf(of, "Address argument required.\n");
return SCPE_ARG;
}
@ -1557,6 +1582,10 @@ void cpu_on_interrupt(uint16 vec)
{
uint32 new_pcbp;
sim_debug(IRQ_MSG, &cpu_dev,
"[%08x] [cpu_on_interrupt] vec=%02x (%d)\n",
R[NUM_PC], vec, vec);
/*
* "If a nonmaskable interrupt request is received, an auto-vector
* interrupt acknowledge cycle is performed (as if an autovector
@ -1733,6 +1762,10 @@ t_stat sim_instr(void)
if (cio[i].intr &&
cio[i].ipl == cpu_int_ipl &&
cio[i].ivec == cpu_int_vec) {
sim_debug(IO_DBG, &cpu_dev,
"[%08x] [IRQ] Handling CIO interrupt for card %d\n",
R[NUM_PC], i);
cio[i].intr = FALSE;
}
}

View file

@ -397,6 +397,7 @@ t_stat cpu_set_hist(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat cpu_show_hist(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat cpu_show_virt(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat cpu_show_stack(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat cpu_show_cio(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat cpu_set_halt(UNIT *uptr, int32 val, char *cptr, void *desc);
t_stat cpu_clear_halt(UNIT *uptr, int32 val, char *cptr, void *desc);
t_stat cpu_boot(int32 unit_num, DEVICE *dptr);

817
3B2/3b2_ctc.c Normal file
View file

@ -0,0 +1,817 @@
/* 3b2_ctc.c: AT&T 3B2 Model 400 "CTC" feature card
Copyright (c) 2018, 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_ctc.h"
extern CIO_STATE cio[CIO_SLOTS];
extern UNIT cio_unit;
#define CTQRESIZE 20
#define CTQCESIZE 16
#define DELAY_SYSGEN 2500
#define DELAY_FMT 1000000
#define DELAY_RW 10000
#define DELAY_OPEN 2500
#define DELAY_CLOSE 2500
#define DELAY_CONFIG 2500
#define DELAY_DLM 1000
#define DELAY_ULM 1000
#define DELAY_FCF 1000
#define DELAY_DOS 1000
#define DELAY_DSD 1000
#define DELAY_UNK 1000
#define DELAY_CATCHUP 10000
#define TAPE_DEV 0 /* CTAPE device */
#define XMF_DEV 1 /* XM Floppy device */
#define VTOC_BLOCK 0
#define ATOW(arr,i) ((uint32)arr[i+3] + ((uint32)arr[i+2] << 8) + \
((uint32)arr[i+1] << 16) + ((uint32)arr[i] << 24))
static uint8 int_cid; /* Interrupting card ID */
static uint8 int_subdev; /* Interrupting subdevice */
static t_bool ctc_conf = FALSE; /* Has a CTC card been configured? */
struct partition vtoc_table[VTOC_PART] = {
{ 2, 0, 5272, 8928 }, /* 00 */
{ 3, 1, 126, 5146 }, /* 01 */
{ 4, 0, 14200, 31341 }, /* 02 */
{ 0, 0, 2, 45539 }, /* 03 */
{ 0, 1, 0, 0 }, /* 04 */
{ 0, 1, 0, 0 }, /* 05 */
{ 5, 1, 0, 45541 }, /* 06 */
{ 1, 1, 0, 126 }, /* 07 */
{ 0, 1, 0, 0 }, /* 08 */
{ 0, 1, 0, 0 }, /* 09 */
{ 0, 1, 0, 0 }, /* 10 */
{ 0, 1, 0, 0 }, /* 11 */
{ 0, 1, 0, 0 }, /* 12 */
{ 0, 1, 0, 0 }, /* 13 */
{ 0, 1, 0, 0 }, /* 14 */
{ 0, 1, 0, 0 } /* 15 */
};
/* State. Although we technically have two devices (tape and floppy),
* only the tape drive is supported at this time. */
CTC_STATE ctc_state[2];
UNIT ctc_unit = {
UDATA (&ctc_svc, UNIT_FIX|UNIT_ATTABLE|UNIT_DISABLE|
UNIT_ROABLE|UNIT_BINK, CTC_CAPACITY)
};
MTAB ctc_mod[] = {
{ UNIT_WLK, 0, "write enabled", "WRITEENABLED",
NULL, NULL, NULL, "Write enabled tape drive" },
{ UNIT_WLK, UNIT_WLK, "write locked", "LOCKED",
NULL, NULL, NULL, "Write lock tape drive" },
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "RQUEUE=n", NULL,
NULL, &ctc_show_rqueue, NULL, "Display Request Queue for card n" },
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "CQUEUE=n", NULL,
NULL, &ctc_show_cqueue, NULL, "Display Completion Queue for card n" },
{ 0 }
};
static DEBTAB ctc_debug[] = {
{ "IO", IO_DBG, "I/O" },
{ "TRACE", TRACE_DBG, "Call Trace" },
{ NULL }
};
DEVICE ctc_dev = {
"CTC", /* name */
&ctc_unit, /* units */
NULL, /* registers */
ctc_mod, /* modifiers */
1, /* #units */
16, /* address radix */
32, /* address width */
1, /* address incr. */
16, /* data radix */
8, /* data width */
NULL, /* examine routine */
NULL, /* deposit routine */
&ctc_reset, /* reset routine */
NULL, /* boot routine */
&ctc_attach, /* attach routine */
&ctc_detach, /* detach routine */
NULL, /* context */
DEV_DISABLE|DEV_DIS|DEV_DEBUG|DEV_SECTORS, /* flags */
0, /* debug control flags */
ctc_debug, /* debug flag names */
NULL, /* memory size change */
NULL, /* logical name */
NULL, /* help routine */
NULL, /* attach help routine */
NULL, /* help context */
NULL, /* device description */
};
static void cio_irq(uint8 cid, uint8 dev, int32 delay)
{
int_cid = cid;
int_subdev = dev & 0x3f;
sim_activate_after(&ctc_unit, delay);
}
/*
* Write a VTOC and pdinfo to the tape file
*/
static t_stat ctc_write_vtoc(struct vtoc *vtoc, struct pdinfo *pdinfo, uint32 maxpass)
{
uint8 buf[PD_BYTES];
uint32 wr, offset;
memcpy(buf, vtoc, sizeof(struct vtoc));
offset = sizeof(struct vtoc);
memcpy(buf + offset, pdinfo, sizeof(struct pdinfo));
offset += sizeof(struct pdinfo);
memcpy(buf + offset, &maxpass, sizeof(uint32));
return sim_disk_wrsect(&ctc_unit, VTOC_BLOCK, buf, &wr, 1);
}
/*
* Load a VTOC and pdinfo from the tape file
*/
static t_stat ctc_read_vtoc(struct vtoc *vtoc, struct pdinfo *pdinfo, uint32 *maxpass)
{
uint8 buf[PD_BYTES];
uint32 wr, offset;
t_stat result;
result = sim_disk_rdsect(&ctc_unit, VTOC_BLOCK, buf, &wr, 1);
if (result != SCPE_OK) {
return result;
}
memcpy(vtoc, buf, sizeof(struct vtoc));
offset = sizeof(struct vtoc);
memcpy(pdinfo, buf + offset, sizeof(struct pdinfo));
offset += sizeof(struct pdinfo);
memcpy(maxpass, buf + offset, sizeof(uint32));
return result;
}
/*
* Update the host's in-memory copy of the VTOC and pdinfo
*/
static void ctc_update_vtoc(uint32 maxpass,
uint32 vtoc_addr, uint32 pdinfo_addr,
struct vtoc *vtoc, struct pdinfo *pdinfo)
{
uint32 i;
pwrite_w(vtoc_addr + 12, VTOC_VALID);
pwrite_w(vtoc_addr + 16, vtoc->version);
for (i = 0; i < 8; i++) {
pwrite_b(vtoc_addr + 20 + i, (uint8)(vtoc->volume[i]));
}
pwrite_h(vtoc_addr + 28, vtoc->sectorsz);
pwrite_h(vtoc_addr + 30, vtoc->nparts);
for (i = 0; i < VTOC_PART; i++) {
pwrite_h(vtoc_addr + 72 + (i * 12) + 0, vtoc_table[i].id);
pwrite_h(vtoc_addr + 72 + (i * 12) + 2, vtoc_table[i].flag);
pwrite_w(vtoc_addr + 72 + (i * 12) + 4, vtoc_table[i].sstart);
pwrite_w(vtoc_addr + 72 + (i * 12) + 8, vtoc_table[i].ssize);
}
/* Write the pdinfo */
pwrite_w(pdinfo_addr, pdinfo->driveid);
pwrite_w(pdinfo_addr + 4, pdinfo->sanity);
pwrite_w(pdinfo_addr + 8, pdinfo->version);
for (i = 0; i < 12; i++) {
pwrite_b(pdinfo_addr + 12 + i, pdinfo->serial[i]);
}
pwrite_w(pdinfo_addr + 24, pdinfo->cyls);
pwrite_w(pdinfo_addr + 28, pdinfo->tracks);
pwrite_w(pdinfo_addr + 32, pdinfo->sectors);
pwrite_w(pdinfo_addr + 36, pdinfo->bytes);
pwrite_w(pdinfo_addr + 40, pdinfo->logicalst);
pwrite_w(pdinfo_addr + 44, pdinfo->errlogst);
pwrite_w(pdinfo_addr + 48, pdinfo->errlogsz);
pwrite_w(pdinfo_addr + 52, pdinfo->mfgst);
pwrite_w(pdinfo_addr + 56, pdinfo->mfgsz);
pwrite_w(pdinfo_addr + 60, pdinfo->defectst);
pwrite_w(pdinfo_addr + 64, pdinfo->defectsz);
pwrite_w(pdinfo_addr + 68, pdinfo->relno);
pwrite_w(pdinfo_addr + 72, pdinfo->relst);
pwrite_w(pdinfo_addr + 76, pdinfo->relsz);
pwrite_w(pdinfo_addr + 80, pdinfo->relnext);
/* Now something horrible happens. We sneak RIGHT off the end of
* the pdinfo struct and reach deep into the pdsector struct that
* it is part of. */
pwrite_w(pdinfo_addr + 128, maxpass);
}
/*
* Handle a single request taken from the Request Queue.
*
* Note that the driver stuffs parameters into various different
* fields of the Request Queue entry seemingly at random, and also
* expects response parameters to be placed in specific fields of the
* Completion Queue entry. It can be confusing to follow.
*/
static void ctc_cmd(uint8 cid,
cio_entry *rqe, uint8 *rapp_data,
cio_entry *cqe, uint8 *capp_data)
{
uint32 vtoc_addr, pdinfo_addr, ctjob_addr;
uint32 maxpass, blkno, delay;
uint8 dev;
uint8 sec_buf[512];
int32 secrw = 0;
int32 b;
struct vtoc vtoc = {0};
struct pdinfo pdinfo = {0};
uint32 lba; /* Logical Block Address */
dev = rqe->subdevice & 1; /* Tape or Floppy device */
capp_data[7] = rqe->opcode;
cqe->subdevice = rqe->subdevice;
switch(rqe->opcode) {
case CIO_DLM:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] CIO Download Memory: bytecnt=%04x "
"addr=%08x return_addr=%08x subdev=%02x\n",
rqe->byte_count, rqe->address,
rqe->address, rqe->subdevice);
delay = DELAY_DLM;
cqe->address = rqe->address + rqe->byte_count;
cqe->opcode = CTC_SUCCESS;
break;
case CIO_ULM:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] CIO Upload Memory: return opcode 0\n");
delay = DELAY_ULM;
cqe->opcode = CTC_SUCCESS;
break;
case CIO_FCF:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] CIO Force Function Call: return opcode 0\n");
delay = DELAY_FCF;
/* This is to pass diagnostics. TODO: Figure out how to parse
* the given test x86 code and determine how to respond
* correctly */
pwrite_h(0x200f000, 0x1); /* Test success */
pwrite_h(0x200f002, 0x0); /* Test Number */
pwrite_h(0x200f004, 0x0); /* Actual */
pwrite_h(0x200f006, 0x0); /* Expected */
pwrite_b(0x200f008, 0x1); /* Success flag again */
pwrite_b(0x200f009, 0x30); /* ??? */
/* An interesting (?) side-effect of FORCE FUNCTION CALL is
* that it resets the card state such that a new SYSGEN is
* required in order for new commands to work. In fact, an
* INT0/INT1 combo _without_ a RESET can sysgen the board. So,
* we reset the command bits here. */
cio[cid].sysgen_s = 0;
cqe->opcode = CTC_SUCCESS;
break;
case CIO_DOS:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] CIO_DOS (%d)\n",
rqe->opcode);
delay = DELAY_DOS;
cqe->opcode = CTC_SUCCESS;
break;
case CIO_DSD:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] CTC_DSD (%d)\n",
rqe->opcode);
delay = DELAY_DSD;
/* The system wants us to write sub-device structures at the
* supplied address, but we have nothing to write. */
pwrite_h(rqe->address, 0x0);
cqe->opcode = CTC_SUCCESS;
break;
case CTC_FORMAT:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] CTC_FORMAT (%d)\n",
rqe->opcode);
delay = DELAY_FMT;
/* FORMAT stores the job pointer in the jio_start field of the
* completion queue entry's application data */
capp_data[0] = rapp_data[4];
capp_data[1] = rapp_data[5];
capp_data[2] = rapp_data[6];
capp_data[3] = rapp_data[7];
if (dev == XMF_DEV) {
cqe->opcode = CTC_NOTREADY;
break;
}
if ((ctc_unit.flags & UNIT_ATT) == 0) {
cqe->opcode = CTC_NOMEDIA;
break;
}
if (ctc_unit.flags & UNIT_WLK) {
cqe->opcode = CTC_RDONLY;
break;
}
/* Write a valid VTOC and pdinfo to the tape */
vtoc.sanity = VTOC_VALID;
vtoc.version = 1;
strcpy(vtoc.volume, "ctctape");
vtoc.sectorsz = PD_BYTES;
vtoc.nparts = VTOC_PART;
pdinfo.driveid = PD_DRIVEID;
pdinfo.sanity = PD_VALID;
pdinfo.version = 0;
memset(pdinfo.serial, 0, 12);
pdinfo.cyls = PD_CYLS;
pdinfo.tracks = PD_TRACKS;
pdinfo.sectors = PD_SECTORS;
pdinfo.bytes = PD_BYTES;
pdinfo.logicalst = PD_LOGICALST;
pdinfo.errlogst = 0xffffffff;
pdinfo.errlogsz = 0xffffffff;
pdinfo.mfgst = 0xffffffff;
pdinfo.mfgsz = 0xffffffff;
pdinfo.defectst = 0xffffffff;
pdinfo.defectsz = 0xffffffff;
pdinfo.relno = 0xffffffff;
pdinfo.relst = 0xffffffff;
pdinfo.relsz = 0xffffffff;
pdinfo.relnext = 0xffffffff;
maxpass = rqe->address;
ctc_write_vtoc(&vtoc, &pdinfo, maxpass);
cqe->opcode = CTC_SUCCESS;
/* The address field holds the total amount of time (in 25ms
* chunks) used during this format session. We'll fudge and
* say 1 minute for formatting. */
cqe->address = 2400;
break;
case CTC_OPEN:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] CTC_OPEN (%d)\n",
rqe->opcode);
delay = DELAY_OPEN;
ctc_state[dev].time = 0; /* Opening always resets session time to 0 */
vtoc_addr = rqe->address;
pdinfo_addr = ATOW(rapp_data, 4);
ctjob_addr = ATOW(rapp_data, 8);
/* For OPEN commands, the Completion Queue Entry's address
* field contains a pointer to the ctjobstat. */
cqe->address = ctjob_addr;
if (dev == XMF_DEV) {
cqe->opcode = CTC_NOTREADY;
break;
}
if ((ctc_unit.flags & UNIT_ATT) == 0) {
cqe->opcode = CTC_NOMEDIA;
break;
}
/* Load the vtoc, pdinfo, and maxpass from the tape */
ctc_read_vtoc(&vtoc, &pdinfo, &maxpass);
ctc_update_vtoc(maxpass, vtoc_addr, pdinfo_addr, &vtoc, &pdinfo);
cqe->opcode = CTC_SUCCESS;
break;
case CTC_CLOSE:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] CTC_CLOSE (%d)\n",
rqe->opcode);
delay = DELAY_CLOSE;
/* The Request Queue Entry's address field contains the
* ctjobstat pointer, which the driver will want to find in
* the first word of our Completion Queue Entry's application
* data. This must be in place whether we have media attached
* or not. */
capp_data[3] = rqe->address & 0xff;
capp_data[2] = (rqe->address & 0xff00) >> 8;
capp_data[1] = (rqe->address & 0xff0000) >> 16;
capp_data[0] = (rqe->address & 0xff000000) >> 24;
/* The Completion Queue Entry's address field holds the total
* tape time used in this session. */
cqe->address = ctc_state[dev].time;
cqe->opcode = CTC_SUCCESS;
break;
case CTC_WRITE:
case CTC_VWRITE:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] CTC_WRITE or CTC_VWRITE (%d)\n",
rqe->opcode);
delay = DELAY_RW;
cqe->byte_count = rqe->byte_count;
cqe->subdevice = rqe->subdevice;
cqe->address = ATOW(rapp_data, 4);
if (dev == XMF_DEV) {
cqe->opcode = CTC_NOTREADY;
break;
}
if ((ctc_unit.flags & UNIT_ATT) == 0) {
cqe->opcode = CTC_NOMEDIA;
break;
}
if (ctc_unit.flags & UNIT_WLK) {
cqe->opcode = CTC_RDONLY;
break;
}
blkno = ATOW(rapp_data, 0);
for (b = 0; b < rqe->byte_count / 512; b++) {
ctc_state[dev].time += 10;
for (int j = 0; j < 512; j++) {
/* Fill the buffer */
sec_buf[j] = pread_b(rqe->address + (b * 512) + j);
}
lba = blkno + b;
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] ... CTC_WRITE: 512 bytes at block %d (0x%x)\n",
lba, lba);
sim_disk_wrsect(&ctc_unit, lba, sec_buf, &secrw, 1);
}
cqe->opcode = CTC_SUCCESS;
break;
case CTC_READ:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] CTC_READ (%d)\n",
rqe->opcode);
delay = DELAY_RW;
cqe->byte_count = rqe->byte_count;
cqe->subdevice = rqe->subdevice;
cqe->address = ATOW(rapp_data, 4);
if (dev == XMF_DEV) {
cqe->opcode = CTC_NOTREADY;
break;
}
if ((ctc_unit.flags & UNIT_ATT) == 0) {
cqe->opcode = CTC_NOMEDIA;
break;
}
blkno = ATOW(rapp_data, 0);
for (b = 0; b < rqe->byte_count / 512; b++) {
ctc_state[dev].time += 10;
lba = blkno + b;
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] ... CTC_READ: 512 bytes from block %d (0x%x)\n",
lba, lba);
sim_disk_rdsect(&ctc_unit, lba, sec_buf, &secrw, 1);
for (int j = 0; j < 512; j++) {
/* Drain the buffer */
pwrite_b(rqe->address + (b * 512) + j, sec_buf[j]);
}
}
cqe->opcode = CTC_SUCCESS;
break;
case CTC_CONFIG:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] CTC_CONFIG (%d)\n",
rqe->opcode);
delay = DELAY_CONFIG;
cqe->opcode = CTC_SUCCESS;
break;
default:
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] UNHANDLED OP: %d (0x%02x)\n",
rqe->opcode, rqe->opcode);
delay = DELAY_UNK;
cqe->opcode = CTC_HWERROR;
break;
}
cio_irq(cid, rqe->subdevice, delay);
}
void ctc_sysgen(uint8 cid)
{
cio_entry cqe = {0};
uint8 rapp_data[12] = {0};
sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] Handling Sysgen.\n");
sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] rqp=%08x\n", cio[cid].rqp);
sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] cqp=%08x\n", cio[cid].cqp);
sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] rqs=%d\n", cio[cid].rqs);
sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] cqs=%d\n", cio[cid].cqs);
sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] ivec=%d\n", cio[cid].ivec);
sim_debug(TRACE_DBG, &ctc_dev, "[ctc_sysgen] no_rque=%d\n", cio[cid].no_rque);
cqe.opcode = 3; /* Sysgen success! */
cio_cexpress(cid, CTQCESIZE, &cqe, rapp_data);
cio_cqueue(cid, CIO_STAT, CTQCESIZE, &cqe, rapp_data);
int_cid = cid;
sim_activate_after(&ctc_unit, DELAY_SYSGEN);
}
void ctc_express(uint8 cid)
{
cio_entry rqe, cqe;
uint8 rapp_data[12] = {0};
uint8 capp_data[8] = {0};
sim_debug(TRACE_DBG, &ctc_dev, "[ctc_express] Handling Express Request\n");
cio_rexpress(cid, CTQRESIZE, &rqe, rapp_data);
ctc_cmd(cid, &rqe, rapp_data, &cqe, capp_data);
dump_entry(TRACE_DBG, &ctc_dev, "COMPLETION",
CTQCESIZE, &cqe, capp_data);
cio_cexpress(cid, CTQCESIZE, &cqe, capp_data);
}
void ctc_full(uint8 cid)
{
cio_entry rqe, cqe;
uint8 rapp_data[12] = {0};
uint8 capp_data[8] = {0};
sim_debug(TRACE_DBG, &ctc_dev, "[ctc_full] Handling Full Request\n");
while (cio_cqueue_avail(cid, CTQCESIZE) &&
cio_rqueue(cid, TAPE_DEV, CTQRESIZE, &rqe, rapp_data) == SCPE_OK) {
ctc_cmd(cid, &rqe, rapp_data, &cqe, capp_data);
}
cio_cqueue(cid, CIO_STAT, CTQCESIZE, &cqe, capp_data);
}
t_stat ctc_reset(DEVICE *dptr)
{
uint32 i;
uint8 cid, end_slot;
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_reset] Resetting CTC device\n");
memset(ctc_state, 0, 2 * sizeof(CTC_STATE));
if (dptr->flags & DEV_DIS) {
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_reset] REMOVING CARD\n");
for (cid = 0; cid < CIO_SLOTS; cid++) {
if (cio[cid].id == CTC_ID) {
break;
}
}
if (cid == CIO_SLOTS) {
/* No card was ever attached */
return SCPE_OK;
}
cio[cid].id = 0;
cio[cid].ipl = 0;
cio[cid].ivec = 0;
cio[cid].exp_handler = NULL;
cio[cid].full_handler = NULL;
cio[cid].sysgen = NULL;
ctc_conf = FALSE;
} else if (!ctc_conf) {
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_reset] ATTACHING CARD\n");
/* Find the first avaialable slot */
for (cid = 0; cid < CIO_SLOTS; cid++) {
if (cio[cid].id == 0) {
break;
}
}
/* Do we have room? */
if (cid == CIO_SLOTS) {
return SCPE_NXM;
}
cio[cid].id = CTC_ID;
cio[cid].ipl = CTC_IPL;
cio[cid].exp_handler = &ctc_express;
cio[cid].full_handler = &ctc_full;
cio[cid].sysgen = &ctc_sysgen;
ctc_conf = TRUE;
}
return SCPE_OK;
}
t_stat ctc_svc(UNIT *uptr)
{
uint16 lp, ulp;
if (cio[int_cid].ivec > 0) {
sim_debug(TRACE_DBG, &ctc_dev,
"[cio_svc] IRQ for board %d (VEC=%d)\n",
int_cid, cio[int_cid].ivec);
cio[int_cid].intr = TRUE;
}
/* Check to see if the completion queue has more work in it. We
* need to schedule an interrupt for each job if we've fallen
* behind (this should be rare) */
lp = cio_c_lp(int_cid, CTQCESIZE);
ulp = cio_c_ulp(int_cid, CTQCESIZE);
if ((ulp + CTQCESIZE) % (CTQCESIZE * cio[int_cid].cqs) != lp) {
sim_debug(TRACE_DBG, &ctc_dev,
"[cio_svc] Completion queue has fallen behind (lp=%04x ulp=%04x)\n",
lp, ulp);
/* Schedule a catch-up interrupt */
sim_activate_abs(&ctc_unit, DELAY_CATCHUP);
}
return SCPE_OK;
}
t_stat ctc_attach(UNIT *uptr, CONST char *cptr)
{
return sim_disk_attach(uptr, cptr, 512, 1, TRUE, 0, "CIPHER23", 0, 0);
}
t_stat ctc_detach(UNIT *uptr)
{
return sim_disk_detach(uptr);
}
t_stat ctc_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
return ctc_show_queue_common(st, uptr, val, desc, TRUE);
}
t_stat ctc_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
return ctc_show_queue_common(st, uptr, val, desc, FALSE);
}
static t_stat ctc_show_queue_common(FILE *st, UNIT *uptr, int32 val,
CONST void *desc, t_bool rq)
{
uint8 cid;
char *cptr = (char *) desc;
t_stat result;
uint32 ptr, size, no_rque, i, j;
uint8 op, dev, seq, cmdstat;
if (cptr) {
cid = (uint8) get_uint(cptr, 10, 12, &result);
if (result != SCPE_OK) {
return SCPE_ARG;
}
} else {
return SCPE_ARG;
}
/* If the card is not sysgen'ed, give up */
if (cio[cid].sysgen_s != CIO_SYSGEN) {
fprintf(st, "No card in slot %d, or card has not completed sysgen\n", cid);
return SCPE_ARG;
}
if (rq) {
ptr = cio[cid].rqp;
size = cio[cid].rqs;
no_rque = cio[cid].no_rque;
fprintf(st, "Dumping %d Request Queues\n", no_rque);
fprintf(st, "---------------------------------------------------------\n");
fprintf(st, "EXPRESS ENTRY:\n");
fprintf(st, " Byte Count: %d\n", pread_h(ptr));
fprintf(st, " Subdevice: %d\n", pread_b(ptr + 2));
fprintf(st, " Opcode: 0x%02x\n", pread_b(ptr + 3));
fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4));
fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8));
ptr += CTQRESIZE;
for (i = 0; i < no_rque; i++) {
fprintf(st, "---------------------------------------------------------\n");
fprintf(st, "REQUEST QUEUE %d\n", i);
fprintf(st, "---------------------------------------------------------\n");
fprintf(st, "Load Pointer: %d\n", pread_h(ptr) / CTQRESIZE);
fprintf(st, "Unload Pointer: %d\n", pread_h(ptr + 2) / CTQRESIZE);
fprintf(st, "---------------------------------------------------------\n");
ptr += 4;
for (j = 0; j < size; j++) {
dev = pread_b(ptr + 2);
op = pread_b(ptr + 3);
seq = (dev & 0x40) >> 6;
cmdstat = (dev & 0x80) >> 7;
fprintf(st, "REQUEST ENTRY %d\n", j);
fprintf(st, " Byte Count: %d\n", pread_h(ptr));
fprintf(st, " Subdevice: %d\n", dev & 0x3f);
fprintf(st, " Cmd/Stat: %d\n", cmdstat);
fprintf(st, " Seqbit: %d\n", seq);
fprintf(st, " Opcode: 0x%02x (%d)\n", op, op);
fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4));
fprintf(st, " App Data: 0x%08x 0x%08x 0x%08x\n",
pread_w(ptr + 8), pread_w(ptr + 12), pread_w(ptr + 16));
ptr += CTQRESIZE;
}
}
} else {
ptr = cio[cid].cqp;
size = cio[cid].cqs;
no_rque = 0; /* Not used */
fprintf(st, "Dumping Completion Queue\n");
fprintf(st, "---------------------------------------------------------\n");
fprintf(st, "EXPRESS ENTRY:\n");
fprintf(st, " Byte Count: %d\n", pread_h(ptr));
fprintf(st, " Subdevice: %d\n", pread_b(ptr + 2));
fprintf(st, " Opcode: 0x%02x\n", pread_b(ptr + 3));
fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4));
fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8));
ptr += CTQCESIZE;
fprintf(st, "---------------------------------------------------------\n");
fprintf(st, "Load Pointer: %d\n", pread_h(ptr) / CTQCESIZE);
fprintf(st, "Unload Pointer: %d\n", pread_h(ptr + 2) / CTQCESIZE);
fprintf(st, "---------------------------------------------------------\n");
ptr += 4;
for (i = 0; i < size; i++) {
dev = pread_b(ptr + 2);
op = pread_b(ptr + 3);
seq = (dev & 0x40) >> 6;
cmdstat = (dev & 0x80) >> 7;
fprintf(st, "COMPLETION ENTRY %d\n", i);
fprintf(st, " Byte Count: %d\n", pread_h(ptr));
fprintf(st, " Subdevice: %d\n", dev & 0x3f);
fprintf(st, " Cmd/Stat: %d\n", cmdstat);
fprintf(st, " Seqbit: %d\n", seq);
fprintf(st, " Opcode: 0x%02x (%d)\n", op, op);
fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4));
fprintf(st, " App Data: 0x%08x 0x%08x\n",
pread_w(ptr + 8), pread_w(ptr + 12));
ptr += CTQCESIZE;
}
}
return SCPE_OK;
}

158
3B2/3b2_ctc.h Normal file
View file

@ -0,0 +1,158 @@
/* 3b2_ctc.h: AT&T 3B2 Model 400 "CTC" feature card
Copyright (c) 2018, 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.
*/
/*
* CTC is an intelligent feature card for the 3B2 that supports a
* Cipher "FloppyTape(tm)" 525 drive that can read and write 23MB
* DC600A cartridges.
*
* The CTC card is based on the Common I/O (CIO) platform.
*
* Notes:
* ------
*
* The Cipher FloppyTape is an odd beast. Although it's a tape drive,
* it is controlled by a floppy controller. It is divided into virtual
* sectors that can be addressed by Cylinder / Track / Sector.
* Stepping and head select pulses dictate where on the tape to read
* from or write to. Moreover, System V maps a filesystem onto the
* tape, and a properly formatted tape drive will have a VTOC on
* partition 0.
*
*/
#ifndef _3B2_CTC_H_
#define _3B2_CTC_H_
#include "3b2_defs.h"
#include "3b2_io.h"
#define UNIT_V_WLK (DKUF_V_UF + 0) /* Write-locked tape */
#define UNIT_WLK (1 << UNIT_V_WLK)
#define CTC_ID 0x0005
#define CTC_IPL 12
#define CTC_VERSION 1
/* Request Opcodes */
#define CTC_CONFIG 30
#define CTC_CLOSE 31
#define CTC_FORMAT 32
#define CTC_OPEN 33
#define CTC_READ 34
#define CTC_WRITE 35
#define CTC_VWRITE 36
/* Completion Opcodes */
#define CTC_SUCCESS 0
#define CTC_HWERROR 32
#define CTC_RDONLY 33
#define CTC_NOTREADY 36
#define CTC_NOMEDIA 42
/* VTOC values */
#define VTOC_VERSION 1
#define VTOC_SECSZ 512
#define VTOC_PART 16 /* Number of "partitions" on tape */
#define VTOC_VALID 0x600DDEEE /* Magic number for valid VTOC */
/* Physical Device Info (pdinfo) values */
#define PD_VALID 0xCA5E600D /* Magic number for valid PDINFO */
#define PD_DRIVEID 5
#define PD_VERSION 0
#define PD_CYLS 6
#define PD_TRACKS 245
#define PD_SECTORS 31
#define PD_BYTES 512
#define PD_LOGICALST 29
#define CTC_CAPACITY (PD_CYLS * PD_TRACKS * PD_SECTORS) /* In blocks */
struct partition {
uint16 id; /* Partition ID */
uint16 flag; /* Permission Flags */
uint32 sstart; /* Starting Sector */
uint32 ssize; /* Size in Sectors */
};
struct vtoc {
uint32 bootinfo[3]; /* n/a */
uint32 sanity; /* magic number */
uint32 version; /* layout version */
uint8 volume[8]; /* volume name */
uint16 sectorsz; /* sector size in bytes */
uint16 nparts; /* number of partitions */
uint32 reserved[10]; /* free space */
struct partition part[VTOC_PART]; /* partition headers */
uint32 timestamp[VTOC_PART]; /* partition timestamp */
};
struct pdinfo {
uint32 driveid; /* identifies the device type */
uint32 sanity; /* verifies device sanity */
uint32 version; /* version number */
uint8 serial[12]; /* serial number of the device */
uint32 cyls; /* number of cylinders per drive */
uint32 tracks; /* number tracks per cylinder */
uint32 sectors; /* number sectors per track */
uint32 bytes; /* number of bytes per sector */
uint32 logicalst; /* sector address of logical sector 0 */
uint32 errlogst; /* sector address of error log area */
uint32 errlogsz; /* size in bytes of error log area */
uint32 mfgst; /* sector address of mfg. defect info */
uint32 mfgsz; /* size in bytes of mfg. defect info */
uint32 defectst; /* sector address of the defect map */
uint32 defectsz; /* size in bytes of defect map */
uint32 relno; /* number of relocation areas */
uint32 relst; /* sector address of relocation area */
uint32 relsz; /* size in sectors of relocation area */
uint32 relnext; /* address of next avail reloc sector */
};
typedef struct {
uint32 time; /* Time used during a tape session (in 25ms chunks) */
} CTC_STATE;
extern DEVICE ctc_dev;
t_stat ctc_reset(DEVICE *dptr);
t_stat ctc_svc(UNIT *uptr);
t_stat ctc_attach(UNIT *uptr, CONST char *cptr);
t_stat ctc_detach(UNIT *uptr);
void ctc_sysgen(uint8 cid);
void ctc_express(uint8 cid);
void ctc_full(uint8 cid);
/* Largely here for debugging purposes */
static t_stat ctc_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat ctc_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat ctc_show_queue_common(FILE *st, UNIT *uptr, int32 val, CONST void *desc, t_bool rq);
#endif /* _3B2_CTC_H_ */

View file

@ -83,10 +83,10 @@ noret __libc_longjmp (jmp_buf buf, int val);
#define PHYS_MEM_BASE 0x2000000
#define ROM_BASE 0
#define IO_BASE 0x40000
#define IO_SIZE 0x10000
#define IOB_BASE 0x200000
#define IOB_SIZE 0x1E00000
/* IO area */
#define IO_BOTTOM 0x40000
#define IO_TOP 0x50000
/* Register numbers */
#define NUM_FP 9
@ -140,8 +140,8 @@ noret __libc_longjmp (jmp_buf buf, int val);
#define EXECUTE_MSG 0x008
#define INIT_MSG 0x010
#define IRQ_MSG 0x020
#define IO_D_MSG 0x040
#define TRACE_MSG 0x080
#define IO_DBG 0x040
#define TRACE_DBG 0x080
#define ERR_MSG 0x100
/* Data types operated on by instructions. NB: These integer values
@ -307,33 +307,18 @@ noret __libc_longjmp (jmp_buf buf, int val);
#define MEMSIZE_REG 0x4C003
#define CIO_BOTTOM 0x200000
#define CIO_TOP 0x2000000
#define CIO_SLOTS 12
#define CIO_CSBIT 0x80
#define CIO_CMDSTAT 0x80
#define CIO_SEQBIT 0x40
#define CIO_INT_DELAY 8000
/* Timer definitions */
#define TMR_CLK 0 /* The clock responsible for IPL 15 interrupts */
#define TPS_CLK 100 /* 100 ticks per second */
#define CLK_MIN_TICKS 500 /* No fewer than 500 sim steps between ticks */
/* TIMING SECTION */
/* ----------------------------------------------- */
/* Calculate delays (in simulator steps) for times */
/* System clock runs at 10MHz; 100ns period. */
#define US_PER_INST 1.6
#define INST_PER_MS (1000.0 / US_PER_INST)
#define DELAY_US(us) ((uint32)((us) / US_PER_INST))
#define DELAY_MS(ms) ((uint32)(((ms) * 1000) / US_PER_INST))
/* global symbols from the CPU */
extern jmp_buf save_env;
@ -415,6 +400,7 @@ extern void cpu_clear_irq(uint8 ipl, uint16 csr_flags);
/* global symbols from the IO system */
extern uint32 io_read(uint32 pa, size_t size);
extern void io_write(uint32 pa, uint32 val, size_t size);
extern void cio_clear(uint8 cid);
extern void cio_xfer();
extern uint8 cio_int;
extern uint16 cio_ipl;

View file

@ -44,27 +44,14 @@
#include "3b2_id.h"
/* Wait times, in CPU steps, for various actions */
/* Each step is 50 us in buffered mode */
#define ID_SEEK_WAIT 100 /* us */
#define ID_SEEK_BASE 700 /* us */
#define ID_RECAL_WAIT 6000 /* us */
/* Reading data takes about 8ms per sector */
#define ID_RW_WAIT 8000 /* us */
/* Sense Unit Status completes in about 200 us */
#define ID_SUS_WAIT 200 /* us */
/* Specify takes a bit longer, 1.25 ms */
#define ID_SPEC_WAIT 1250 /* us */
/* Sense Interrupt Status is about 142 us */
#define ID_SIS_WAIT 142 /* us */
/* The catch-all command wait time is about 140 us */
#define ID_CMD_WAIT 140 /* us */
#define ID_SEEK_WAIT 50
#define ID_SEEK_BASE 700
#define ID_RECAL_WAIT 6000
#define ID_RW_WAIT 1000
#define ID_SUS_WAIT 200
#define ID_SPEC_WAIT 1250
#define ID_SIS_WAIT 142
#define ID_CMD_WAIT 140
/* Data FIFO pointer - Read */
uint8 id_dpr = 0;
@ -198,10 +185,9 @@ static SIM_INLINE void id_clear_fifo()
id_dpw = 0;
}
/* TODO: Remove after debugging */
static SIM_INLINE void id_activate(UNIT *uptr, int32 delay)
{
sim_activate(uptr, delay);
sim_activate_abs(uptr, delay);
}
/*
@ -282,7 +268,7 @@ t_stat id_unit_svc(UNIT *uptr)
"[%08x]\tINTR\t\tCOMPLETING Recal/Seek SEEK_0 UNIT %d\n",
R[NUM_PC], unit);
id_seek_state[unit] = ID_SEEK_1;
id_activate(uptr, DELAY_US(8000)); /* TODO: Correct Delay based on steps */
id_activate(uptr, 8000); /* TODO: Correct Delay based on steps */
break;
case ID_SEEK_1:
sim_debug(EXECUTE_MSG, &id_dev,
@ -738,7 +724,7 @@ void id_handle_command(uint8 val)
"[%08x]\tCOMMAND\t%02x\tSense Int. Status\n",
R[NUM_PC], val);
id_status &= ~ID_STAT_SRQ; /* SIS immediately de-asserts SRQ */
id_activate(id_ctlr_unit, DELAY_US(ID_SIS_WAIT));
id_activate(id_ctlr_unit, ID_SIS_WAIT);
break;
case ID_CMD_SPEC:
sim_debug(WRITE_MSG, &id_dev,
@ -748,19 +734,19 @@ void id_handle_command(uint8 val)
id_etn = id_data[3];
id_esn = id_data[4];
id_polling = (id_dtlh & ID_DTLH_POLL) == 0;
id_activate(id_ctlr_unit, DELAY_US(ID_SPEC_WAIT));
id_activate(id_ctlr_unit, ID_SPEC_WAIT);
break;
case ID_CMD_SUS:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tSense Unit Status - %d\n",
R[NUM_PC], val, id_ua);
id_activate(id_sel_unit, DELAY_US(ID_SUS_WAIT));
id_activate(id_sel_unit, ID_SUS_WAIT);
break;
case ID_CMD_DERR:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tDetect Error\n",
R[NUM_PC], val);
id_activate(id_ctlr_unit, DELAY_US(ID_CMD_WAIT));
id_activate(id_ctlr_unit, ID_CMD_WAIT);
break;
case ID_CMD_RECAL:
time = id_cyl[id_unit_num];
@ -770,12 +756,12 @@ void id_handle_command(uint8 val)
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tRecalibrate - %d - POLLING\n",
R[NUM_PC], val, id_ua);
id_activate(id_sel_unit, DELAY_US(1000));
id_activate(id_sel_unit, 1000);
} else {
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tRecalibrate - %d - NORMAL\n",
R[NUM_PC], val, id_ua);
id_activate(id_sel_unit, DELAY_US(ID_RECAL_WAIT + (time * ID_SEEK_WAIT)));
id_activate(id_sel_unit, (ID_RECAL_WAIT + (time * ID_SEEK_WAIT)));
}
break;
case ID_CMD_SEEK:
@ -790,12 +776,12 @@ void id_handle_command(uint8 val)
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tSeek - %d - POLLING\n",
R[NUM_PC], val, id_ua);
id_activate(id_sel_unit, DELAY_US(1000));
id_activate(id_sel_unit, 4000);
} else {
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tSeek - %d - NORMAL\n",
R[NUM_PC], val, id_ua);
id_activate(id_sel_unit, DELAY_US(ID_SEEK_BASE + (time * ID_SEEK_WAIT)));
id_activate(id_sel_unit, ID_SEEK_BASE + (time * ID_SEEK_WAIT));
}
break;
case ID_CMD_FMT:
@ -838,7 +824,7 @@ void id_handle_command(uint8 val)
id_data[1] = id_scnt;
id_activate(id_sel_unit, DELAY_US(ID_CMD_WAIT));
id_activate(id_sel_unit, ID_CMD_WAIT);
break;
case ID_CMD_VID:
sim_debug(WRITE_MSG, &id_dev,
@ -846,7 +832,7 @@ void id_handle_command(uint8 val)
R[NUM_PC], val, id_ua);
id_data[0] = 0;
id_data[1] = 0x05; /* What do we put here? */
id_activate(id_sel_unit, DELAY_US(ID_CMD_WAIT));
id_activate(id_sel_unit, ID_CMD_WAIT);
break;
case ID_CMD_RID:
sim_debug(WRITE_MSG, &id_dev,
@ -867,13 +853,13 @@ void id_handle_command(uint8 val)
"[%08x]\tUNIT %d NOT ATTACHED, CANNOT READ ID.\n",
R[NUM_PC], id_ua);
}
id_activate(id_sel_unit, DELAY_US(ID_CMD_WAIT));
id_activate(id_sel_unit, ID_CMD_WAIT);
break;
case ID_CMD_RDIAG:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tRead Diag - %d\n",
R[NUM_PC], val, id_ua);
id_activate(id_sel_unit, DELAY_US(ID_CMD_WAIT));
id_activate(id_sel_unit, ID_CMD_WAIT);
break;
case ID_CMD_RDATA:
sim_debug(WRITE_MSG, &id_dev,
@ -895,25 +881,25 @@ void id_handle_command(uint8 val)
"[%08x]\tUNIT %d NOT ATTACHED, CANNOT READ DATA.\n",
R[NUM_PC], id_ua);
}
id_activate(id_sel_unit, DELAY_US(ID_RW_WAIT));
id_activate(id_sel_unit, ID_RW_WAIT);
break;
case ID_CMD_CHECK:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tCheck - %d\n",
R[NUM_PC], val, id_ua);
id_activate(id_sel_unit, DELAY_US(ID_CMD_WAIT));
id_activate(id_sel_unit, ID_CMD_WAIT);
break;
case ID_CMD_SCAN:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tScan - %d\n",
R[NUM_PC], val, id_ua);
id_activate(id_sel_unit, DELAY_US(ID_CMD_WAIT));
id_activate(id_sel_unit, ID_CMD_WAIT);
break;
case ID_CMD_VDATA:
sim_debug(WRITE_MSG, &id_dev,
"[%08x]\tCOMMAND\t%02x\tVerify Data - %d\n",
R[NUM_PC], val, id_ua);
id_activate(id_sel_unit, DELAY_US(ID_CMD_WAIT));
id_activate(id_sel_unit, ID_CMD_WAIT);
break;
case ID_CMD_WDATA:
sim_debug(WRITE_MSG, &id_dev,
@ -935,7 +921,7 @@ void id_handle_command(uint8 val)
"[%08x]\tUNIT %d NOT ATTACHED, CANNOT WRITE.\n",
R[NUM_PC], id_ua);
}
id_activate(id_sel_unit, DELAY_US(ID_RW_WAIT));
id_activate(id_sel_unit, ID_RW_WAIT);
break;
}
}

View file

@ -35,13 +35,11 @@
*/
double if_start_time;
#define IF_START_TIME() { if_start_time = sim_gtime(); }
#define IF_DIFF_MS() ((sim_gtime() - if_start_time) / INST_PER_MS)
#ifndef max
#define max(x,y) ((x) > (y) ? (x) : (y))
#ifndef MAX
#define MAX(x,y) ((x) > (y) ? (x) : (y))
#endif
#ifndef min
#define min(x,y) ((x) < (y) ? (x) : (y))
#ifndef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#endif
/*
@ -59,12 +57,12 @@ double if_start_time;
* step is 6ms and head settling time is 30ms.
*/
#define IF_STEP_DELAY 6 /* ms */
#define IF_R_DELAY 85 /* ms */
#define IF_W_DELAY 90 /* ms */
#define IF_VERIFY_DELAY 30 /* ms */
#define IF_HLD_DELAY 80 /* ms */
#define IF_HSW_DELAY 60 /* ms */
#define IF_STEP_DELAY 6000 /* us */
#define IF_R_DELAY 85000 /* us */
#define IF_W_DELAY 90000 /* us */
#define IF_VERIFY_DELAY 30000 /* us */
#define IF_HLD_DELAY 80000 /* us */
#define IF_HSW_DELAY 60000 /* us */
UNIT if_unit = {
UDATA (&if_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+
@ -103,8 +101,7 @@ static SIM_INLINE void if_clear_irq()
static SIM_INLINE void if_activate(uint32 delay)
{
IF_START_TIME();
sim_activate_abs(&if_unit, (int32) DELAY_MS(delay));
sim_activate_abs(&if_unit, delay);
}
static SIM_INLINE void if_cancel_pending_irq()
@ -131,7 +128,7 @@ t_stat if_svc(UNIT *uptr)
if_state.cmd = 0;
/* Request an interrupt */
sim_debug(IRQ_MSG, &if_dev, "\tINTR\t\tDELTA=%f ms\n", IF_DIFF_MS());
sim_debug(IRQ_MSG, &if_dev, "\tINTR\n");
if_set_irq();
return SCPE_OK;
@ -322,20 +319,20 @@ void if_handle_command()
case IF_STEP_T:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep\n", if_state.cmd);
if_activate(IF_STEP_DELAY);
if_state.track = (uint8) min(max((int) if_state.track + if_state.step_dir, 0), 0x4f);
if_state.track = (uint8) MIN(MAX((int) if_state.track + if_state.step_dir, 0), 0x4f);
break;
case IF_STEP_IN:
case IF_STEP_IN_T:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep In\n", if_state.cmd);
if_state.step_dir = IF_STEP_IN_DIR;
if_state.track = (uint8) max((int) if_state.track + if_state.step_dir, 0);
if_state.track = (uint8) MAX((int) if_state.track + if_state.step_dir, 0);
if_activate(IF_STEP_DELAY);
break;
case IF_STEP_OUT:
case IF_STEP_OUT_T:
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep Out\n", if_state.cmd);
if_state.step_dir = IF_STEP_OUT_DIR;
if_state.track = (uint8) min((int) if_state.track + if_state.step_dir, 0x4f);
if_state.track = (uint8) MIN((int) if_state.track + if_state.step_dir, 0x4f);
if_activate(IF_STEP_DELAY);
break;
case IF_SEEK:

View file

@ -49,15 +49,32 @@ struct iolink iotable[] = {
{ 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;
}
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,
sim_debug(IO_DBG, &cpu_dev,
"[%08x] [SYSGEN] Starting sysgen for card %d. sysgen_p=%08x\n",
R[NUM_PC], cid, sysgen_p);
@ -71,75 +88,68 @@ void cio_sysgen(uint8 cid)
cio[cid].ivec = pread_b(sysgen_p + 10);
cio[cid].no_rque = pread_b(sysgen_p + 11);
sim_debug(IO_D_MSG, &cpu_dev,
sim_debug(IO_DBG, &cpu_dev,
"[SYSGEN] sysgen rqp = %08x\n",
cio[cid].rqp);
sim_debug(IO_D_MSG, &cpu_dev,
sim_debug(IO_DBG, &cpu_dev,
"[SYSGEN] sysgen cqp = %08x\n",
cio[cid].cqp);
sim_debug(IO_D_MSG, &cpu_dev,
sim_debug(IO_DBG, &cpu_dev,
"[SYSGEN] sysgen rqs = %02x\n",
cio[cid].rqs);
sim_debug(IO_D_MSG, &cpu_dev,
sim_debug(IO_DBG, &cpu_dev,
"[SYSGEN] sysgen cqs = %02x\n",
cio[cid].cqs);
sim_debug(IO_D_MSG, &cpu_dev,
sim_debug(IO_DBG, &cpu_dev,
"[SYSGEN] sysgen ivec = %02x\n",
cio[cid].ivec);
sim_debug(IO_D_MSG, &cpu_dev,
sim_debug(IO_DBG, &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,
sim_debug(IO_DBG, &cpu_dev,
"[%08x] [cio_sysgen] Not running custom sysgen.\n",
R[NUM_PC]);
}
}
void cio_cexpress(uint8 cid, cio_entry *cqe)
void cio_cexpress(uint8 cid, uint16 esize, cio_entry *cqe, uint8 *app_data)
{
uint32 cqp;
uint32 cqp, i;
cqp = cio[cid].cqp;
sim_debug(IO_D_MSG, &cpu_dev,
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;
if (cio[cid].seqbit) {
cqe->subdevice |= CIO_SEQBIT;
}
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);
pwrite_w(cqp + 8, cqe->app_data);
/* Write application-specific data. */
for (i = 0; i < (esize - QESIZE); i++) {
pwrite_b(cqp + 8 + i, app_data[i]);
}
}
/* Write an entry into the Completion Queue */
void cio_cqueue(uint8 cid, cio_entry *cqe)
void cio_cqueue(uint8 cid, uint8 cmd_stat, uint16 esize,
cio_entry *cqe, uint8 *app_data)
{
uint32 cqp, top;
uint16 lp;
uint32 cqp, top, i;
uint16 lp, ulp;
/* Apply the CMD/STAT bit */
cqe->subdevice |= (cmd_stat << 7);
/* Get the physical address of the completion queue
* in main memory */
@ -147,90 +157,163 @@ void cio_cqueue(uint8 cid, cio_entry *cqe)
/* Get the physical address of the first entry in
* the completion queue */
top = cqp + QUE_OFFSET;
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 + LOAD_OFFSET);
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);
pwrite_w(top + lp + 8, cqe->app_data);
/* 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 + QUE_E_SIZE) % (QUE_E_SIZE * cio[cid].cqs);
lp = (lp + esize) % (esize * cio[cid].cqs);
/* Store it back to the correct location */
pwrite_h(cqp + LOAD_OFFSET, lp);
pwrite_h(cqp + esize, lp);
} else {
sim_debug(IO_D_MSG, &cpu_dev,
sim_debug(IO_DBG, &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)
/*
* Retrieve the Express Entry from the Request Queue
*/
void cio_rexpress(uint8 cid, uint16 esize, cio_entry *rqe, uint8 *app_data)
{
uint32 rqp, i;
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)
{
uint32 rqp, top, i;
uint16 ulp;
uint16 lp, ulp;
/* Get the physical address of the request queue in main memory */
rqp = cio[cid].rqp + 12; /* Skip past the Express Queue Entry */
rqp = cio[cid].rqp +
esize +
(qnum * (LUSIZE + (esize * cio[cid].rqs)));
/* 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;
lp = pread_h(rqp);
ulp = pread_h(rqp + 2);
/* 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 (lp == ulp) {
return SCPE_NXM;
}
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;
}
top = rqp + LUSIZE;
/* 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);
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);
dump_entry("REQUEST", cqe);
/* 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 + QUE_E_SIZE) % (QUE_E_SIZE * cio[cid].rqs);
/* Store it back to the correct location */
ulp = (ulp + esize) % (esize * cio[cid].rqs);
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]);
}
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(uint 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)
@ -241,7 +324,7 @@ uint32 io_read(uint32 pa, size_t size)
/* Special devices */
if (pa == MEMSIZE_REG) {
/* It appears that the following values map to memory sizes:
/* The following values map to memory sizes:
0x00: 512KB ( 524,288 B)
0x01: 2MB (2,097,152 B)
0x02: 1MB (1,048,576 B)
@ -261,13 +344,16 @@ uint32 io_read(uint32 pa, size_t size)
}
}
/* IO Board Area - Unimplemented */
/* 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;
@ -282,10 +368,11 @@ uint32 io_read(uint32 pa, size_t size)
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,
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 */
@ -295,15 +382,17 @@ uint32 io_read(uint32 pa, size_t size)
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,
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 0x03: /* We've already sysgen'ed */
sim_debug(IO_D_MSG, &cpu_dev,
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);
@ -312,55 +401,60 @@ uint32 io_read(uint32 pa, size_t size)
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);
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;
}
/* 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 */
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 0x01: /* We've seen an INT0 but not an INT1. Time to sysgen */
sim_debug(IO_D_MSG, &cpu_dev,
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 0x03: /* We've already sysgen'ed */
sim_debug(IO_D_MSG, &cpu_dev,
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_D_MSG, &cpu_dev,
"[READ] [%08x] (%d INT1) ERROR IN STATE MACHINE cmdbits=%02x\n",
R[NUM_PC], cid, cio[cid].cmdbits);
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;
}
/* 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,
sim_debug(IO_DBG, &cpu_dev,
"[READ] [%08x] (%d RESET)\n",
R[NUM_PC], cid);
cio[cid].cmdbits = 0;
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;
@ -375,7 +469,7 @@ uint32 io_read(uint32 pa, size_t size)
}
/* Not found. */
sim_debug(IO_D_MSG, &cpu_dev,
sim_debug(IO_DBG, &cpu_dev,
"[%08x] [io_read] ADDR=%08x: No device found.\n",
R[NUM_PC], pa);
csr_data |= CSRTIMO;
@ -395,6 +489,9 @@ void io_write(uint32 pa, uint32 val, size_t size)
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;
@ -409,79 +506,84 @@ void io_write(uint32 pa, uint32 val, size_t size)
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,
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 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",
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 0x03: /* We've already sysgen'ed */
sim_debug(IO_D_MSG, &cpu_dev,
"[READ] [%08x] (%d INT0) EXPRESS JOB\n",
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_D_MSG, &cpu_dev,
"[READ] [%08x] (%d INT0) ERROR IN STATE MACHINE cmdbits=%02x\n",
R[NUM_PC], cid, cio[cid].cmdbits);
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;
}
/* 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 */
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_D_MSG, &cpu_dev,
"[WRITE] [%08x] (%d INT1)\n",
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] (%d INT1) IGNORED\n",
R[NUM_PC], cid);
cio[cid].sysgen_s |= CIO_INT1;
break;
case 0x01: /* We've seen an INT0 but not an INT1. Time to sysgen */
sim_debug(IO_D_MSG, &cpu_dev,
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 0x03: /* We've already sysgen'ed */
sim_debug(IO_D_MSG, &cpu_dev,
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_D_MSG, &cpu_dev,
"[WRITE] [%08x] (%d INT1) ERROR IN STATE MACHINE cmdbits=%02x\n",
R[NUM_PC], cid, cio[cid].cmdbits);
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;
}
/* Record that we've seen an INT1 */
cio[cid].cmdbits |= CIO_INT1;
return;
case IOF_STAT:
sim_debug(IO_D_MSG, &cpu_dev,
sim_debug(IO_DBG, &cpu_dev,
"[WRITE] [%08x] (%d RESET)\n",
R[NUM_PC], cid);
cio[cid].cmdbits = 0;
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;
@ -497,7 +599,7 @@ void io_write(uint32 pa, uint32 val, size_t size)
}
/* Not found. */
sim_debug(IO_D_MSG, &cpu_dev,
sim_debug(IO_DBG, &cpu_dev,
"[%08x] [io_write] ADDR=%08x: No device found.\n",
R[NUM_PC], pa);
csr_data |= CSRTIMO;
@ -506,11 +608,13 @@ void io_write(uint32 pa, uint32 val, size_t size)
/* For debugging only */
void dump_entry(CONST char *type, cio_entry *entry)
void dump_entry(uint32 dbits, DEVICE *dev, CONST char *type,
uint16 esize, cio_entry *entry, uint8 *app_data)
{
sim_debug(IO_D_MSG, &cpu_dev,
"*** %s ENTRY: byte_count=%04x, subdevice=%02x,\n"
" opcode=%d, address=%08x, app_data=%08x\n",
uint32 i;
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, entry->app_data);
entry->opcode, entry->address);
}

View file

@ -79,19 +79,33 @@
*
* The Queue structures (one for request, one for completion) hold:
* - An express entry
*
* And then one or more queues, each queue consiting of
* - A set of pointers for load and unload from the queue
* - Zero or more Queue Entries
* - One or more Queue Entries
*
* | Address | Size | Contents |
* +---------------+------+-----------------------------------------+
* | QUEUE_P | 12 | Express Queue Entry [1] |
* | QUEUE_P + 12 | 2 | Load Pointer |
* | QUEUE_P + 14 | 2 | Unload Pointer |
* | QUEUE_P + 16 | 12 | Entry 0 [1] |
* | QUEUE_P + 28 | 12 | Entry 1 [1] |
* +---------------+------+-----------------------------------------+
* | QUEUE_P + 12 | 2 | Load Pointer for Queue 0 |
* | QUEUE_P + 14 | 2 | Unload Pointer for Queue 0 |
* | QUEUE_P + 16 | 12 | Queue 0 Entry 0 [1] |
* | QUEUE_P + 28 | 12 | Queue 0 Entry 1 [1] |
* | ... | ... | ... |
* +---------------+------+-----------------------------------------+
* | QUEUE_P + n | 2 | Load Pointer for Queue 1 |
* | QUEUE_P + n | 2 | Unload Pointer for Queue 1 |
* | QUEUE_P + n | 12 | Queue 1 Entry 0 [1] |
* | QUEUE_P + n | 12 | Queue 1 Entry 1 [1] |
* | ... | ... | ... |
*
* [1] See Queue Entry above
*
* NB: There are multiple Request queues, usually one per subdevice,
* and EACH Request queue starts with a Load Pointer, an Unload
* Pointer, and then 'n' Queue Entries.
*
*/
#ifndef _3B2_IO_H_
@ -111,34 +125,33 @@
#define IOF_CTRL 3
#define IOF_STAT 5
#define SYSGEN_PTR PHYS_MEM_BASE
#define CIO_LOAD_SIZE 0x4
#define CIO_ENTRY_SIZE 0x0c
#define CIO_QUE_OFFSET 0x10
#define CIO_SLOTS 12
#define SYSGEN_PTR PHYS_MEM_BASE
/* CIO opcodes */
#define CIO_DLM 1
#define CIO_ULM 2
#define CIO_FCF 3
#define CIO_DOS 4
#define CIO_DSD 5
#define CIO_DLM 1
#define CIO_ULM 2
#define CIO_FCF 3
#define CIO_DOS 4
#define CIO_DSD 5
/* Map a physical address to a card ID */
#define CID(pa) (((((pa) >> 0x14) & 0x1f) / 2) - 1)
/* Map a card ID to a base address */
#define CADDR(bid) (((((bid) + 1) * 2) << 0x14))
#define CIO_INT0 0x1
#define CIO_INT1 0x2
/* Offsets into the request/completion queues of various values */
#define LOAD_OFFSET 12
#define ULOAD_OFFSET 14
#define QUE_OFFSET 16
#define QUE_E_SIZE 12
#define LUSIZE 4 /* Load/Unload pointers size */
#define QESIZE 8 /* Queue entry is 8 bytes + application data */
#define CIO_STAT 0
#define CIO_CMD 1
/* Sysgen State */
#define CIO_INT_NONE 0
#define CIO_INT0 1
#define CIO_INT1 2
#define CIO_SYSGEN 3
#define CIO_SYGEN_MASK 0x3
typedef struct {
uint16 id; /* Card ID */
@ -153,10 +166,9 @@ typedef struct {
uint8 no_rque; /* Number of request queues */
uint8 ipl; /* IPL that this card uses */
t_bool intr; /* Card needs to interrupt */
uint8 cmdbits; /* Commands received since RESET */
uint8 sysgen_s; /* Sysgen state */
uint8 seqbit; /* Squence Bit */
uint8 op; /* Last received opcode */
TMLN *lines[4]; /* Terminal Multiplexer lines */
} CIO_STATE;
typedef struct {
@ -164,7 +176,6 @@ typedef struct {
uint8 subdevice;
uint8 opcode;
uint32 address;
uint32 app_data;
} cio_entry;
struct iolink {
@ -208,15 +219,21 @@ extern CIO_STATE cio[CIO_SLOTS];
t_stat cio_reset(DEVICE *dptr);
t_stat cio_svc(UNIT *uptr);
/* Put an entry into the Completion Queue's Express entry */
void cio_cexpress(uint8 cid, cio_entry *cqe);
/* Put an entry into the Completion Queue */
void cio_cqueue(uint8 cid, cio_entry *cqe);
/* Get an entry from the Request Queue */
void cio_rqueue(uint8 cid, cio_entry *cqe);
/* Perform a Sysgen */
void cio_clear(uint8 cid);
void cio_cexpress(uint8 cid, uint16 esize, cio_entry *cqe, uint8 *app_data);
void cio_cqueue(uint8 cid, uint8 cmd_stat, uint16 esize,
cio_entry *cqe, uint8 *app_data);
void cio_rexpress(uint8 cid, uint16 esize, cio_entry *rqe, uint8 *app_data);
t_stat cio_rqueue(uint8 cid, uint8 qnum, uint16 esize,
cio_entry *rqe, uint8 *app_data);
t_bool cio_cqueue_avail(uint cid, uint16 esize);
uint16 cio_r_lp(uint8 cid, uint8 qnum, uint16 esize);
uint16 cio_r_ulp(uint8 cid, uint8 qnum, uint16 esize);
uint16 cio_c_lp(uint8 cid, uint16 esize);
uint16 cio_c_ulp(uint8 cid, uint16 esize);
void cio_sysgen(uint8 cid);
/* Debugging only */
void dump_entry(CONST char *type, cio_entry *entry);
void dump_entry(uint32 dbits, DEVICE *dev, CONST char *type,
uint16 esize, cio_entry *entry, uint8 *app_data);
#endif

View file

@ -324,7 +324,7 @@ t_stat tti_reset(DEVICE *dptr)
/* Start the Console TTI polling loop */
if (!sim_is_active(&tti_unit)) {
sim_activate(&tti_unit, tti_unit.wait);
sim_activate_after(&tti_unit, tti_unit.wait);
}
return SCPE_OK;
@ -347,6 +347,10 @@ t_stat contty_reset(DEVICE *dtpr)
memset(&iu_state, 0, sizeof(IU_STATE));
memset(&iu_contty, 0, sizeof(IU_PORT));
/* DCD is off (inverted logic, 1 means off) */
iu_state.inprt |= IU_DCDB;
brg_reg = 0;
brg_clk = BRG_DEFAULT;
parity_sel = IU_PARITY_EVEN;
@ -361,7 +365,7 @@ t_stat contty_reset(DEVICE *dtpr)
/* Start the CONTTY polling loop */
if (!sim_is_active(contty_rcv_unit)) {
sim_activate(contty_rcv_unit, contty_rcv_unit->wait);
sim_activate_after(contty_rcv_unit, contty_rcv_unit->wait);
}
return SCPE_OK;
@ -393,7 +397,7 @@ t_stat iu_svc_tti(UNIT *uptr)
*/
if ((temp = sim_poll_kbd()) < SCPE_KFLAG) {
return temp;
return temp;
}
if (iu_console.conf & RX_EN) {
@ -438,22 +442,26 @@ t_stat iu_svc_contty_rcv(UNIT *uptr)
return SCPE_OK;
}
ln = tmxr_poll_conn(&contty_desc);
if (ln >= 0) {
/* Check for connect */
if ((ln = tmxr_poll_conn(&contty_desc)) >= 0) {
contty_ldsc[ln].rcve = 1;
/* Set DCD */
iu_state.inprt &= ~(IU_DCDB);
iu_state.ipcr |= IU_DCDB;
/* Cause an interrupt */
csr_data |= CSRUART;
}
tmxr_poll_rx(&contty_desc);
/* Check for disconnect */
if (!contty_ldsc[0].conn && (iu_state.inprt & IU_DCDB) == 0) {
contty_ldsc[0].rcve = 0;
iu_state.inprt |= IU_DCDB;
iu_state.ipcr |= IU_DCDB;
csr_data |= CSRUART;
} else if (iu_contty.conf & RX_EN) {
tmxr_poll_rx(&contty_desc);
if (contty_ldsc[0].conn) {
temp = tmxr_getc_ln(&contty_ldsc[0]);
if (temp && !(temp & SCPE_BREAK)) {
if (iu_contty.conf & RX_EN) {
if (contty_ldsc[0].conn) {
temp = tmxr_getc_ln(&contty_ldsc[0]);
if (temp && !(temp & SCPE_BREAK)) {
if ((iu_contty.stat & STS_FFL) == 0) {
iu_contty.rxbuf[iu_contty.w_p] = (temp & 0xff);
iu_contty.w_p = (iu_contty.w_p + 1) % IU_BUF_SIZE;
@ -530,7 +538,7 @@ t_stat iu_svc_timer(UNIT *uptr)
uint32 iu_read(uint32 pa, size_t size)
{
uint8 reg, modep;
uint32 data, delay;
uint32 data;
reg = (uint8) (pa - IUBASE);
@ -544,15 +552,17 @@ uint32 iu_read(uint32 pa, size_t size)
data = iu_console.stat;
break;
case RHRA:
data = iu_console.rxbuf[iu_console.r_p];
iu_console.r_p = (iu_console.r_p + 1) % IU_BUF_SIZE;
/* If the FIFO is not empty, we must cause another interrupt
* to continue reading */
if (iu_console.r_p == iu_console.w_p) {
iu_console.stat &= ~(STS_RXR|STS_FFL);
iu_state.istat &= ~ISTS_RAI;
} else {
csr_data |= CSRUART;
if (iu_console.conf & RX_EN) {
data = iu_console.rxbuf[iu_console.r_p];
iu_console.r_p = (iu_console.r_p + 1) % IU_BUF_SIZE;
/* If the FIFO is not empty, we must cause another interrupt
* to continue reading */
if (iu_console.r_p == iu_console.w_p) {
iu_console.stat &= ~(STS_RXR|STS_FFL);
iu_state.istat &= ~ISTS_RAI;
} else if (iu_state.imr & IMR_RXRA) {
csr_data |= CSRUART;
}
}
break;
case IPCR:
@ -579,15 +589,17 @@ uint32 iu_read(uint32 pa, size_t size)
data = iu_contty.stat;
break;
case RHRB:
data = iu_contty.rxbuf[iu_contty.r_p];
iu_contty.r_p = (iu_contty.r_p + 1) % IU_BUF_SIZE;
/* If the FIFO is not empty, we must cause another interrupt
* to continue reading */
if (iu_contty.r_p == iu_contty.w_p) {
iu_contty.stat &= ~(STS_RXR|STS_FFL);
iu_state.istat &= ~ISTS_RBI;
} else {
csr_data |= CSRUART;
if (iu_contty.conf & RX_EN) {
data = iu_contty.rxbuf[iu_contty.r_p];
iu_contty.r_p = (iu_contty.r_p + 1) % IU_BUF_SIZE;
/* If the FIFO is not empty, we must cause another interrupt
* to continue reading */
if (iu_contty.r_p == iu_contty.w_p) {
iu_contty.stat &= ~(STS_RXR|STS_FFL);
iu_state.istat &= ~ISTS_RBI;
} else if (iu_state.imr & IMR_RXRB) {
csr_data |= CSRUART;
}
}
break;
case INPRT:
@ -596,8 +608,7 @@ uint32 iu_read(uint32 pa, size_t size)
case START_CTR:
data = 0;
iu_state.istat &= ~ISTS_CRI;
delay = (uint32) (IU_TIMER_STP * iu_timer_state.c_set);
sim_activate_abs(&iu_timer_unit, (int32) DELAY_US(delay));
sim_activate_abs(&iu_timer_unit, iu_timer_state.c_set * IU_TIMER_RATE);
break;
case STOP_CTR:
data = 0;
@ -607,12 +618,6 @@ uint32 iu_read(uint32 pa, size_t size)
break;
case 17: /* Clear DMAC interrupt */
data = 0;
/*
iu_console.drq = FALSE;
iu_console.dma = FALSE;
iu_contty.drq = FALSE;
iu_contty.dma = FALSE;
*/
csr_data &= ~CSRDMA;
break;
default:
@ -639,7 +644,18 @@ void iu_write(uint32 pa, uint32 val, size_t size)
iu_increment_a = TRUE;
break;
case CSRA:
/* Set baud rate - not yet implemented on console */
if (brg_rates[brg_reg][brg_clk] != NULL) {
sprintf(line_config, "%s-%d%s1",
brg_rates[brg_reg][brg_clk],
bits_per_char,
parity[parity_sel]);
sim_debug(EXECUTE_MSG, &contty_dev,
"Setting CONTTY line to %s\n",
line_config);
tmxr_set_config_line(&contty_ldsc[0], line_config);
}
break;
case CRA: /* Command A */
iu_w_cmd(PORT_A, bval);
@ -756,8 +772,8 @@ t_stat iu_tx(uint8 portno, uint8 val)
}
p->stat |= STS_RXR;
iu_state.istat |= ists;
if (iu_state.imr & imr_mask) {
iu_state.istat |= ists;
csr_data |= CSRUART;
}
@ -775,7 +791,7 @@ t_stat iu_tx(uint8 portno, uint8 val)
sim_debug(EXECUTE_MSG, &tto_dev,
"[iu_tx] CONSOLE transmit %02x (%c)\n",
(uint8) c, (char) c);
status = sim_putchar(c);
status = sim_putchar_s(c);
} else {
sim_debug(EXECUTE_MSG, &contty_dev,
"[iu_tx] CONTTY transmit %02x (%c)\n",

View file

@ -140,28 +140,9 @@ extern DEVICE tto_dev;
extern DEVICE contty_dev;
extern DEVICE iu_timer_dev;
extern int32 tmxr_poll;
#define IUBASE 0x49000
#define IUSIZE 0x100
/* The UART is driven by a 3.6864 MHz crystal. This is divided by 16
to clock the timer. (One peculiarity: 3.6864 MHz /16 is 230400 Hz,
but the SVR3 source code claims the /16 clock is actually 230525
Hz. So, we'll go with 230525 Hz until proven otherwise.)
UART clock period = 4338ns
System clock period = 100ns
That means the system ticks 43.3792 times for every one tick of the
UART clock.
But this is a simulated system, where each simulator step is
CYCLES_PER_INST long. So we take that into account.
*/
#define IU_TIMER_STP 4.33792
#define IU_BUF_SIZE 3
#define IU_DCDA 0x01
@ -177,6 +158,8 @@ extern int32 tmxr_poll;
/* Default baud rate generator (9600 baud) */
#define BRG_DEFAULT 11
#define IU_TIMER_RATE 2.114 /* microseconds per step */
typedef struct iu_port {
uint8 stat; /* Port Status */

View file

@ -236,8 +236,8 @@ t_bool addr_is_mem(uint32 pa)
t_bool addr_is_io(uint32 pa)
{
return ((pa >= IO_BASE && pa < IO_BASE + IO_SIZE) ||
(pa >= IOB_BASE && pa < IOB_BASE + IOB_SIZE));
return ((pa >= IO_BOTTOM && pa < IO_TOP) ||
(pa >= CIO_BOTTOM && pa < CIO_TOP));
}
/*
@ -631,7 +631,7 @@ t_stat mmu_decode_va(uint32 va, uint8 r_acc, t_bool fc, uint32 *pa)
uint8 pd_acc;
t_stat sd_cached, pd_cached;
if (!mmu_state.enabled || addr_is_io(va)) {
if (!mmu_state.enabled) {
*pa = va;
return SCPE_OK;
}

964
3B2/3b2_ports.c Normal file
View file

@ -0,0 +1,964 @@
/* 3b2_ports.c: AT&T 3B2 Model 400 "PORTS" feature card
Copyright (c) 2018, 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.
*/
/*
* PORTS is an intelligent feature card for the 3B2 that supports four
* serial lines and one Centronics parallel port.
*
* The PORTS card is based on the Common I/O (CIO) platform. It uses
* two SCN2681A DUARTs to supply the four serial lines, and uses the
* SCN2681A parallel I/O pins for the Centronics parallel port.
*
* We make no attempt to emulate a PORTS card's internal workings
* precisely. Instead, we treat it as a black box as seen from the 3B2
* system board's point of view.
*
*/
#include "3b2_ports.h"
extern CIO_STATE cio[CIO_SLOTS];
extern UNIT cio_unit;
/* Device and units for PORTS cards
* --------------------------------
*
* A 3B2/400 system can have up to 12 PORTS cards installed. Each
* card, in turn, has 5 TTY devices - four serial TTYs and one
* parallel printer port (the printer port is not supported at this
* time, and is a no-op).
*
* The PORTS emulator is backed by a terminal multiplexer with up to
* 48 (12 * 4) serial lines. Lines can be specified with:
*
* SET PORTS LINES=n
*
* Lines must be specified in multiples of 4.
*
* Implementation8
* --------------
*
* Each set of 4 lines is mapped to a CIO_STATE struct in the "cio"
* CIO_STATE structure.
*
*/
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#define PPQESIZE 12
#define DELAY_ASYNC 25
#define DELAY_DLM 100
#define DELAY_ULM 100
#define DELAY_FCF 100
#define DELAY_DOS 100
#define DELAY_DSD 100
#define DELAY_OPTIONS 100
#define DELAY_VERS 100
#define DELAY_CONN 100
#define DELAY_XMIT 50
#define DELAY_RECV 25
#define DELAY_DEVICE 25
#define DELAY_STD 100
#define LN(cid,port) ((PORTS_LINES * ((cid) - base_cid)) + (port))
#define LCID(ln) (((ln) / PORTS_LINES) + base_cid)
#define LPORT(ln) ((ln) % PORTS_LINES)
t_bool ports_conf = FALSE; /* Have PORTS cards been configured? */
int8 base_cid; /* First cid in our contiguous block */
uint8 int_cid; /* Interrupting card ID */
uint8 int_subdev; /* Interrupting subdevice */
/* PORTS-specific state for each slot */
PORTS_LINE_STATE *ports_state = NULL;
/* Baud rates determined by the low nybble
* of the PORT_OPTIONS cflag */
CONST char *ports_baud[16] = {
"75", "110", "134", "150",
"300", "600", "1200", "2000",
"2400", "4800", "1800", "9600",
"19200", "9600", "9600", "9600"
};
TMLN *ports_ldsc = NULL;
TMXR ports_desc = { 0, 0, 0, NULL };
/* Three units service the Receive, Transmit, and CIO */
UNIT ports_unit[3] = {
{ UDATA(&ports_rcv_svc, UNIT_IDLE|UNIT_ATTABLE|TT_MODE_8B, 0) },
{ UDATA(&ports_xmt_svc, UNIT_DIS, 0), SERIAL_OUT_WAIT },
{ UDATA(&ports_cio_svc, UNIT_DIS, 0) }
};
MTAB ports_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, 0, "RQUEUE=n", NULL,
NULL, &ports_show_rqueue, NULL, "Display Request Queue for card n" },
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "CQUEUE=n", NULL,
NULL, &ports_show_cqueue, NULL, "Display Completion Queue for card n" },
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "LINES", "LINES=n",
&ports_setnl, &tmxr_show_lines, (void *) &ports_desc, "Display number of lines" },
{ 0 }
};
static DEBTAB ports_debug[] = {
{ "IO", IO_DBG, "I/O Character Trace" },
{ "TRACE", TRACE_DBG, "Call Trace" },
{ "XMT", TMXR_DBG_XMT, "TMXR Transmit Data" },
{ "RCV", TMXR_DBG_RCV, "TMXR Received Data" },
{ "RET", TMXR_DBG_RET, "TMXR Returned Received Data" },
{ "MDM", TMXR_DBG_XMT, "TMXR Modem Signals" },
{ "CON", TMXR_DBG_XMT, "TMXR Connection Activity" },
{ "ASY", TMXR_DBG_ASY, "TMXR Async Activity" },
{ "PXMT", TMXR_DBG_PXMT, "TMXR Transmit Packets" },
{ "PRCV", TMXR_DBG_PRCV, "TMXR Received Packets" },
{ NULL }
};
DEVICE ports_dev = {
"PORTS", /* name */
ports_unit, /* units */
NULL, /* registers */
ports_mod, /* modifiers */
3, /* #units */
16, /* address radix */
32, /* address width */
1, /* address incr. */
16, /* data radix */
8, /* data width */
NULL, /* examine routine */
NULL, /* deposit routine */
&ports_reset, /* reset routine */
NULL, /* boot routine */
&ports_attach, /* attach routine */
&ports_detach, /* detach routine */
NULL, /* context */
DEV_DISABLE|DEV_DIS|DEV_DEBUG|DEV_MUX, /* flags */
0, /* debug control flags */
ports_debug, /* debug flag names */
NULL, /* memory size change */
NULL, /* logical name */
NULL, /* help routine */
NULL, /* attach help routine */
(void *)&ports_desc, /* help context */
NULL, /* device description */
};
static void cio_irq(uint8 cid, uint8 dev, int32 delay)
{
int_cid = cid;
int_subdev = dev & 0xf;
sim_activate(&ports_unit[2], delay);
}
/*
* Set the number of lines for the PORTS mux. This will add or remove
* cards as necessary. The number of lines must be a multiple of 4.
*/
t_stat ports_setnl(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
int32 newln, newcards, i, t;
t_stat r = SCPE_OK;
if (cptr == NULL) {
return SCPE_ARG;
}
newln = (int32) get_uint(cptr, 10, (MAX_PORTS_CARDS * PORTS_LINES), &r);
if ((r != SCPE_OK) || (newln == ports_desc.lines)) {
return r;
}
if ((newln == 0) || LPORT(newln) != 0) {
return SCPE_ARG;
}
if (newln < ports_desc.lines) {
for (i = newln, t = 0; i < ports_desc.lines; i++) {
t = t | ports_ldsc[i].conn;
}
if (t && !get_yn("This will disconnect users; proceed [N]?", FALSE)) {
return SCPE_OK;
}
for (i = newln; i < ports_desc.lines; i++) {
if (ports_ldsc[i].conn) {
tmxr_linemsg(&ports_ldsc[i], "\r\nOperator disconnected line\r\n");
tmxr_send_buffered_data(&ports_ldsc[i]);
}
/* completely reset line */
tmxr_detach_ln(&ports_ldsc[i]);
if (LPORT(i) == (PORTS_LINES - 1)) {
/* Also drop the corresponding card from the CIO array */
cio_clear(LCID(i));
}
}
}
ports_desc.ldsc = ports_ldsc = (TMLN *)realloc(ports_ldsc, newln*sizeof(*ports_ldsc));
ports_state = (PORTS_LINE_STATE *)realloc(ports_state, newln*sizeof(*ports_state));
if (ports_desc.lines < newln) {
memset(ports_ldsc + ports_desc.lines, 0, sizeof(*ports_ldsc)*(newln-ports_desc.lines));
memset(ports_state + ports_desc.lines, 0, sizeof(*ports_state)*(newln-ports_desc.lines));
}
ports_desc.lines = newln;
/* setup lines and auto config */
ports_conf = FALSE;
return ports_reset(&ports_dev);
}
static void ports_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data)
{
cio_entry centry = {0};
uint32 i, ln;
char c;
PORTS_OPTIONS opts;
char line_config[16];
uint8 app_data[4] = {0};
centry.address = rentry->address;
cio[cid].op = rentry->opcode;
ln = LN(cid, rentry->subdevice & 0xf);
switch(rentry->opcode) {
case CIO_DLM:
centry.address = rentry->address + rentry->byte_count;
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_cmd] CIO Download Memory: bytecnt=%04x "
"addr=%08x return_addr=%08x subdev=%02x\n",
R[NUM_PC],
rentry->byte_count, rentry->address,
centry.address, centry.subdevice);
cio_cexpress(cid, PPQESIZE, &centry, app_data);
cio_irq(cid, rentry->subdevice, DELAY_DLM);
break;
case CIO_ULM:
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_cmd] CIO Upload Memory\n",
R[NUM_PC]);
cio_cexpress(cid, PPQESIZE, &centry, app_data);
cio_irq(cid, rentry->subdevice, DELAY_ULM);
break;
case CIO_FCF:
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_cmd] CIO Force Function Call\n",
R[NUM_PC]);
/* This is to pass diagnostics. TODO: Figure out how to parse the
* given test x86 code and determine how to respond correctly */
pwrite_h(0x200f000, 0x1); /* Test success */
pwrite_h(0x200f002, 0x0); /* Test Number */
pwrite_h(0x200f004, 0x0); /* Actual */
pwrite_h(0x200f006, 0x0); /* Expected */
pwrite_b(0x200f008, 0x1); /* Success flag again */
pwrite_b(0x200f009, 0x30); /* ??? */
/* An interesting (?) side-effect of FORCE FUNCTION CALL is
* that it resets the card state such that a new SYSGEN is
* required in order for new commands to work. In fact, an
* INT0/INT1 combo _without_ a RESET can sysgen the board. So,
* we reset the command bits here. */
cio[cid].sysgen_s = 0;
cio_cexpress(cid, PPQESIZE, &centry, app_data);
cio_irq(cid, rentry->subdevice, DELAY_FCF);
break;
case CIO_DOS:
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_cmd] CIO Determine Op Status\n",
R[NUM_PC]);
cio_cexpress(cid, PPQESIZE, &centry, app_data);
cio_irq(cid, rentry->subdevice, DELAY_DOS);
break;
case CIO_DSD:
/* Determine Sub-Devices. We have none. */
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_cmd] Determine Sub-Devices.\n",
R[NUM_PC]);
/* The system wants us to write sub-device structures
* at the supplied address */
pwrite_h(rentry->address, 0x0);
cio_cexpress(cid, PPQESIZE, &centry, app_data);
cio_irq(cid, rentry->subdevice, DELAY_DSD);
break;
case PPC_OPTIONS:
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_cmd] PPC Options Operation\n",
R[NUM_PC]);
opts.line = pread_h(rentry->address);
opts.iflag = pread_h(rentry->address + 4);
opts.oflag = pread_h(rentry->address + 6);
opts.cflag = pread_h(rentry->address + 8);
opts.lflag = pread_h(rentry->address + 10);
opts.cerase = pread_b(rentry->address + 11);
opts.ckill = pread_b(rentry->address + 12);
opts.cinter = pread_b(rentry->address + 13);
opts.cquit = pread_b(rentry->address + 14);
opts.ceof = pread_b(rentry->address + 15);
opts.ceol = pread_b(rentry->address + 16);
opts.itime = pread_b(rentry->address + 17);
opts.vtime = pread_b(rentry->address + 18);
opts.vcount = pread_b(rentry->address + 19);
sim_debug(TRACE_DBG, &ports_dev, " PPC Options: iflag=%04x\n", opts.iflag);
sim_debug(TRACE_DBG, &ports_dev, " PPC Options: oflag=%04x\n", opts.oflag);
sim_debug(TRACE_DBG, &ports_dev, " PPC Options: cflag=%04x\n", opts.cflag);
sim_debug(TRACE_DBG, &ports_dev, " PPC Options: lflag=%04x\n", opts.lflag);
sim_debug(TRACE_DBG, &ports_dev, " PPC Options: itime=%02x\n", opts.itime);
sim_debug(TRACE_DBG, &ports_dev, " PPC Options: vtime=%02x\n", opts.vtime);
sim_debug(TRACE_DBG, &ports_dev, " PPC Options: vcount=%02x\n", opts.vcount);
ports_state[ln].iflag = opts.iflag;
ports_state[ln].oflag = opts.oflag;
if ((rentry->subdevice & 0xf) < PORTS_LINES) {
/* Adjust baud rate */
sprintf(line_config, "%s-8N1",
ports_baud[opts.cflag&0xf]);
sim_debug(TRACE_DBG, &ports_dev,
"Setting PORTS line %d to %s\n",
ln, line_config);
tmxr_set_config_line(&ports_ldsc[ln], line_config);
}
centry.byte_count = sizeof(PPC_OPTIONS);
centry.opcode = PPC_OPTIONS;
centry.subdevice = rentry->subdevice;
centry.address = rentry->address;
cio_cqueue(cid, CIO_STAT, PPQESIZE, &centry, app_data);
cio_irq(cid, rentry->subdevice, DELAY_OPTIONS);
break;
case PPC_VERS:
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_cmd] PPC Version\n",
R[NUM_PC]);
/* Write the version number at the supplied address */
pwrite_b(rentry->address, PORTS_VERSION);
centry.opcode = CIO_ULM;
/* TODO: It's unknown what the value 0x50 means, but this
* is what a real board sends. */
app_data[0] = 0x50;
cio_cqueue(cid, CIO_STAT, PPQESIZE, &centry, app_data);
cio_irq(cid, rentry->subdevice, DELAY_VERS);
break;
case PPC_CONN:
/* CONNECT - Full request and completion queues */
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_cmd] PPC CONNECT - subdevice = %02x\n",
R[NUM_PC], rentry->subdevice);
ports_state[ln].conn = TRUE;
centry.opcode = PPC_CONN;
centry.subdevice = rentry->subdevice;
centry.address = rentry->address;
cio_cqueue(cid, CIO_STAT, PPQESIZE, &centry, app_data);
cio_irq(cid, rentry->subdevice, DELAY_CONN);
break;
case PPC_XMIT:
/* XMIT - Full request and completion queues */
/* The port being referred to is in the subdevice. */
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_cmd] PPC XMIT - subdevice = %02x, address=%08x, byte_count=%d\n",
R[NUM_PC], rentry->subdevice, rentry->address, rentry->byte_count);
/* Set state for xmit */
ports_state[ln].tx_addr = rentry->address;
ports_state[ln].tx_req_addr = rentry->address;
ports_state[ln].tx_chars = rentry->byte_count + 1;
ports_state[ln].tx_req_chars = rentry->byte_count + 1;
sim_activate_after(&ports_unit[1], ports_unit[1].wait);
break;
case PPC_DEVICE:
/* DEVICE Control - Express request and completion queues */
/* The port being referred to is in the subdevice. */
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_cmd] PPC DEVICE - subdevice = %02x\n",
R[NUM_PC], rentry->subdevice);
centry.subdevice = rentry->subdevice;
centry.opcode = PPC_DEVICE;
cio_cexpress(cid, PPQESIZE, &centry, app_data);
cio_irq(cid, rentry->subdevice, DELAY_DEVICE);
break;
case PPC_RECV:
/* RECV - Full request and completion queues */
/* The port being referred to is in the subdevice. */
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_cmd] PPC RECV - subdevice = %02x addr=%08x\n",
R[NUM_PC], rentry->subdevice, rentry->address);
break;
case PPC_DISC:
/* Disconnect */
centry.subdevice = rentry->subdevice;
centry.opcode = PPC_DISC;
ports_ldsc[ln].rcve = 0;
cio_cqueue(cid, CIO_STAT, PPQESIZE, &centry, app_data);
cio_irq(cid, rentry->subdevice, DELAY_STD);
break;
case PPC_BRK:
case PPC_CLR:
default:
sim_debug(TRACE_DBG, &ports_dev,
">>> Op %d Not Handled Yet\n",
rentry->opcode);
cio_cexpress(cid, PPQESIZE, &centry, app_data);
cio_irq(cid, rentry->subdevice, DELAY_STD);
break;
}
}
/*
* Update the connection status of the given port.
*/
static void ports_update_conn(uint32 ln)
{
cio_entry centry;
uint8 cid;
uint8 app_data[4] = {0};
cid = LCID(ln);
/* If the card hasn't sysgened, there's no way to write a
* completion queue entry */
if (cio[cid].sysgen_s != CIO_SYSGEN) {
return;
}
if (ports_ldsc[ln].conn) {
app_data[0] = AC_CON;
ports_state[ln].conn = TRUE;
} else {
if (ports_state[ln].conn) {
app_data[0] = AC_DIS;
ports_state[ln].conn = FALSE;
} else {
app_data[0] = 0;
}
}
centry.opcode = PPC_ASYNC;
centry.subdevice = LPORT(ln);
cio_cqueue(cid, CIO_CMD, PPQESIZE, &centry, app_data);
/* Interrupt */
if (cio[cid].ivec > 0) {
cio[cid].intr = TRUE;
}
}
void ports_sysgen(uint8 cid)
{
cio_entry cqe;
uint8 app_data[4] = {0};
cqe.opcode = 3; /* Sysgen success! */
/* It's not clear why we put a response in both the express
* and the full queue. */
cio_cexpress(cid, PPQESIZE, &cqe, app_data);
cio_cqueue(cid, CIO_STAT, PPQESIZE, &cqe, app_data);
int_cid = cid;
sim_activate(&ports_unit[2], DELAY_STD);
}
void ports_express(uint8 cid)
{
cio_entry rqe;
uint8 app_data[4] = {0};
cio_rexpress(cid, PPQESIZE, &rqe, app_data);
ports_cmd(cid, &rqe, app_data);
}
void ports_full(uint8 cid)
{
uint32 i, ln;
cio_entry rqe;
uint8 app_data[4] = {0};
for (i = 0; i < PORTS_LINES; i++) {
if (cio_rqueue(cid, i, PPQESIZE, &rqe, app_data) == SCPE_OK) {
ports_cmd(cid, &rqe, app_data);
}
}
}
t_stat ports_reset(DEVICE *dptr)
{
uint32 i;
uint8 cid, line, cards, ln, end_slot;
TMLN *lp;
sim_debug(TRACE_DBG, &ports_dev,
"[ports_reset] Resetting PORTS device\n");
if ((dptr->flags & DEV_DIS)) {
for (cid = 0; cid < CIO_SLOTS; cid++) {
if (cio[cid].id == PORTS_ID) {
cio[cid].id = 0;
cio[cid].ipl = 0;
cio[cid].ivec = 0;
cio[cid].exp_handler = NULL;
cio[cid].full_handler = NULL;
cio[cid].sysgen = NULL;
}
}
ports_conf = FALSE;
} else if (!ports_conf) {
/* Clear out any old cards, we're starting fresh */
for (cid = 0; cid < CIO_SLOTS; cid++) {
if (cio[cid].id == PORTS_ID) {
cio[cid].id = 0;
cio[cid].ipl = 0;
cio[cid].ivec = 0;
cio[cid].exp_handler = NULL;
cio[cid].full_handler = NULL;
cio[cid].sysgen = NULL;
}
}
/* Find the first avaialable slot */
for (cid = 0; cid < CIO_SLOTS; cid++) {
if (cio[cid].id == 0) {
break;
}
}
/* Do we have room? */
if (cid >= CIO_SLOTS || cid > (CIO_SLOTS - (ports_desc.lines/PORTS_LINES))) {
return SCPE_NXM;
}
/* Remember the base card slot */
base_cid = cid;
end_slot = (cid + (ports_desc.lines/PORTS_LINES));
for (; cid < end_slot; cid++) {
/* Set up the ports structure */
cio[cid].id = PORTS_ID;
cio[cid].ipl = PORTS_IPL;
cio[cid].exp_handler = &ports_express;
cio[cid].full_handler = &ports_full;
cio[cid].sysgen = &ports_sysgen;
for (line = 0; line < PORTS_LINES; line++) {
ln = LN(cid, line);
sim_debug(TRACE_DBG, &ports_dev,
">>> Setting up lp %d (card %d, line %d)\n",
ln, cid, line);
lp = &ports_ldsc[ln];
tmxr_set_get_modem_bits(lp, TMXR_MDM_DTR|TMXR_MDM_RTS, 0, NULL);
}
}
ports_conf = TRUE;
if (ports_ldsc == NULL) {
ports_desc.lines = DEF_PORTS_CARDS * PORTS_LINES;
ports_desc.ldsc = ports_ldsc =
(TMLN *)calloc(ports_desc.lines, sizeof(*ports_ldsc));
}
if (ports_state == NULL) {
sim_debug(TRACE_DBG, &ports_dev,
"[ports_reset] calloc for ports_state...\n");
ports_state = (PORTS_LINE_STATE *)calloc(ports_desc.lines, sizeof(*ports_state));
}
memset(ports_state, 0, ports_desc.lines*sizeof(*ports_state));
tmxr_set_port_speed_control(&ports_desc);
for (i = 0; i < ports_desc.lines; i++) {
sim_debug(TRACE_DBG, &ports_dev,
"[ports_reset] Setting up line %d...\n", i);
tmxr_set_line_unit(&ports_desc, i, &ports_unit[0]);
tmxr_set_line_output_unit(&ports_desc, i, &ports_unit[1]);
if (!ports_ldsc[i].conn) {
ports_ldsc[i].xmte = 1;
}
ports_ldsc[i].rcve = 0;
tmxr_set_config_line(&ports_ldsc[i], "9600-8N1");
}
}
if (!sim_is_active(&ports_unit[0])) {
sim_debug(TRACE_DBG, &ports_dev,
"[ports_reset] starting receive polling...\n");
sim_activate(&ports_unit[0], ports_unit[0].wait);
}
sim_debug(TRACE_DBG, &ports_dev,
"[ports_reset] returning scpe_ok\n");
return SCPE_OK;
}
t_stat ports_cio_svc(UNIT *uptr)
{
sim_debug(TRACE_DBG, &ports_dev,
"[ports_cio_svc] IRQ for board %d device %d\n",
int_cid, int_subdev);
if (cio[int_cid].ivec > 0) {
cio[int_cid].intr = TRUE;
}
switch (cio[int_cid].op) {
case PPC_CONN:
cio[int_cid].op = PPC_ASYNC;
ports_ldsc[LN(int_cid, int_subdev)].rcve = 1;
sim_activate(&ports_unit[2], DELAY_ASYNC);
break;
case PPC_ASYNC:
ports_update_conn(LN(int_cid, int_subdev));
break;
default:
break;
}
return SCPE_OK;
}
t_stat ports_rcv_svc(UNIT *uptr)
{
uint8 cid, subdev;
int32 temp, ln;
char c;
cio_entry rentry = {0};
cio_entry centry = {0};
uint8 rapp_data[4] = {0};
uint8 capp_data[4] = {0};
if ((uptr->flags & UNIT_ATT) == 0) {
return SCPE_OK;
}
ln = tmxr_poll_conn(&ports_desc);
if (ln >= 0) {
ports_update_conn(ln);
}
tmxr_poll_rx(&ports_desc);
for (ln = 0; ln < ports_desc.lines; ln++) {
cid = LCID(ln);
subdev = LPORT(ln);
if (!ports_ldsc[ln].conn && ports_state[ln].conn) {
ports_update_conn(ln);
} else if (ports_ldsc[ln].conn && ports_state[ln].conn) {
temp = tmxr_getc_ln(&ports_ldsc[ln]);
if (temp && !(temp & SCPE_BREAK)) {
c = (char) (temp & 0xff);
sim_debug(IO_DBG, &ports_dev,
"[LINE %d RECEIVE] char = %02x (%c)\n",
ln, c, c);
if (c == 0xd && (ports_state[ln].iflag & ICRNL)) {
c = 0xa;
}
if (cio[cid].ivec > 0 &&
cio_rqueue(cid, PORTS_RCV_QUEUE,
PPQESIZE, &rentry, rapp_data) == SCPE_OK) {
cio[cid].intr = TRUE;
/* Write the character to the memory address */
pwrite_b(rentry.address, c);
centry.subdevice = LPORT(ln);
centry.opcode = PPC_RECV;
centry.address = rentry.address;
capp_data[3] = RC_TMR;
cio_cqueue(cid, CIO_STAT, PPQESIZE, &centry, capp_data);
}
}
}
}
tmxr_clock_coschedule(uptr, tmxr_poll);
return SCPE_OK;
}
t_stat ports_xmt_svc(UNIT *uptr)
{
uint8 cid, ln;
char c;
t_bool tx = FALSE; /* Did a tx ever occur? */
cio_entry centry;
uint8 app_data[4] = {0};
uint32 wait = 0x7fffffff;
/* Scan all lines for output */
for (ln = 0; ln < ports_desc.lines; ln++) {
cid = LCID(ln);
if (ports_ldsc[ln].conn && ports_state[ln].tx_chars > 0) {
tx = TRUE; /* Even an attempt at TX counts for rescheduling */
c = sim_tt_outcvt(pread_b(ports_state[ln].tx_addr),
TT_GET_MODE(ports_unit[0].flags));
/* The PORTS card optionally handles NL->CRLF */
if (c == 0xa &&
(ports_state[ln].oflag & ONLCR) &&
!(ports_state[ln].crlf)) {
if (tmxr_putc_ln(&ports_ldsc[ln], 0xd) == SCPE_OK) {
wait = MIN(wait, ports_ldsc[ln].txdelta);
sim_debug(IO_DBG, &ports_dev,
"[%08x] [ports_xmt_svc] [LINE %d] XMIT (crlf): %02x (%c)\n",
R[NUM_PC], ln, 0xd, 0xd);
/* Indicate that we're in a CRLF translation */
ports_state[ln].crlf = TRUE;
}
break;
}
ports_state[ln].crlf = FALSE;
if (tmxr_putc_ln(&ports_ldsc[ln], c) == SCPE_OK) {
wait = MIN(wait, ports_ldsc[ln].txdelta);
ports_state[ln].tx_chars--;
ports_state[ln].tx_addr++;
sim_debug(IO_DBG, &ports_dev,
"[%08x] [ports_xmt_svc] [LINE %d] XMIT: %02x (%c)\n",
R[NUM_PC], ln, c, c);
}
if (ports_state[ln].tx_chars == 0) {
sim_debug(TRACE_DBG, &ports_dev,
"[%08x] [ports_xmt_svc] Done with xmit, card=%d port=%d. Interrupting.\n",
R[NUM_PC], cid, LPORT(ln));
centry.byte_count = ports_state[ln].tx_req_chars;
centry.subdevice = LPORT(ln);
centry.opcode = PPC_XMIT;
centry.address = ports_state[ln].tx_req_addr;
app_data[0] = RC_FLU;
cio_cqueue(cid, CIO_STAT, PPQESIZE, &centry, app_data);
cio[cid].intr = TRUE;
}
}
}
tmxr_poll_tx(&ports_desc);
if (tx) {
tmxr_activate_after(uptr, wait);
}
return SCPE_OK;
}
t_stat ports_attach(UNIT *uptr, CONST char *cptr)
{
t_stat r;
sim_debug(TRACE_DBG, &ports_dev, "ports_attach()\n");
tmxr_set_modem_control_passthru(&ports_desc);
r = tmxr_attach(&ports_desc, uptr, cptr);
if (r != SCPE_OK) {
tmxr_clear_modem_control_passthru(&ports_desc);
return r;
}
return SCPE_OK;
}
t_stat ports_detach(UNIT *uptr)
{
t_stat r;
r = tmxr_detach(&ports_desc, uptr);
if (r != SCPE_OK) {
return r;
}
if (sim_is_active(&ports_unit[0])) {
sim_debug(TRACE_DBG, &ports_dev,
"[ports_detach] Stopping receive polling...\n");
sim_cancel(&ports_unit[0]);
}
tmxr_clear_modem_control_passthru(&ports_desc);
return SCPE_OK;
}
/*
* Useful routines for debugging request and completion queues
*/
t_stat ports_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
return ports_show_queue_common(st, uptr, val, desc, TRUE);
}
t_stat ports_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
return ports_show_queue_common(st, uptr, val, desc, FALSE);
}
static t_stat ports_show_queue_common(FILE *st, UNIT *uptr, int32 val,
CONST void *desc, t_bool rq)
{
uint8 cid;
char *cptr = (char *) desc;
t_stat result;
uint32 ptr, size, no_rque, i, j;
uint8 op, dev, seq, cmdstat;
if (cptr) {
cid = (uint8) get_uint(cptr, 10, 12, &result);
if (result != SCPE_OK) {
return SCPE_ARG;
}
} else {
return SCPE_ARG;
}
/* If the card is not sysgen'ed, give up */
if (cio[cid].sysgen_s != CIO_SYSGEN) {
fprintf(st, "No card in slot %d, or card has not completed sysgen\n", cid);
return SCPE_ARG;
}
/* Get the top of the queue */
if (rq) {
ptr = cio[cid].rqp;
size = cio[cid].rqs;
no_rque = cio[cid].no_rque;
} else {
ptr = cio[cid].cqp;
size = cio[cid].cqs;
no_rque = 0; /* Not used */
}
if (rq) {
fprintf(st, "Dumping %d Request Queues\n", no_rque);
} else {
fprintf(st, "Dumping Completion Queue\n");
}
fprintf(st, "---------------------------------------------------------\n");
fprintf(st, "EXPRESS ENTRY:\n");
fprintf(st, " Byte Count: %d\n", pread_h(ptr));
fprintf(st, " Subdevice: %d\n", pread_b(ptr + 2));
fprintf(st, " Opcode: 0x%02x\n", pread_b(ptr + 3));
fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4));
fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8));
ptr += 12;
if (rq) {
for (i = 0; i < no_rque; i++) {
fprintf(st, "---------------------------------------------------------\n");
fprintf(st, "REQUEST QUEUE %d\n", i);
fprintf(st, "---------------------------------------------------------\n");
fprintf(st, "Load Pointer: %d\n", pread_h(ptr) / 12);
fprintf(st, "Unload Pointer: %d\n", pread_h(ptr + 2) / 12);
fprintf(st, "---------------------------------------------------------\n");
ptr += 4;
for (j = 0; j < size; j++) {
dev = pread_b(ptr + 2);
op = pread_b(ptr + 3);
seq = (dev & 0x40) >> 6;
cmdstat = (dev & 0x80) >> 7;
fprintf(st, "REQUEST ENTRY %d\n", j);
fprintf(st, " Byte Count: %d\n", pread_h(ptr));
fprintf(st, " Subdevice: %d\n", dev & 0x3f);
fprintf(st, " Cmd/Stat: %d\n", cmdstat);
fprintf(st, " Seqbit: %d\n", seq);
fprintf(st, " Opcode: 0x%02x (%d)\n", op, op);
fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4));
fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8));
ptr += 12;
}
}
} else {
fprintf(st, "---------------------------------------------------------\n");
fprintf(st, "Load Pointer: %d\n", pread_h(ptr) / 12);
fprintf(st, "Unload Pointer: %d\n", pread_h(ptr + 2) / 12);
fprintf(st, "---------------------------------------------------------\n");
ptr += 4;
for (i = 0; i < size; i++) {
dev = pread_b(ptr + 2);
op = pread_b(ptr + 3);
seq = (dev & 0x40) >> 6;
cmdstat = (dev & 0x80) >> 7;
fprintf(st, "COMPLETION ENTRY %d\n", i);
fprintf(st, " Byte Count: %d\n", pread_h(ptr));
fprintf(st, " Subdevice: %d\n", dev & 0x3f);
fprintf(st, " Cmd/Stat: %d\n", cmdstat);
fprintf(st, " Seqbit: %d\n", seq);
fprintf(st, " Opcode: 0x%02x (%d)\n", op, op);
fprintf(st, " Addr/Data: 0x%08x\n", pread_w(ptr + 4));
fprintf(st, " App Data: 0x%08x\n", pread_w(ptr + 8));
ptr += 12;
}
}
return SCPE_OK;
}

237
3B2/3b2_ports.h Normal file
View file

@ -0,0 +1,237 @@
/* 3b2_ports.h: AT&T 3B2 Model 400 "PORTS" feature card
Copyright (c) 2018, 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.
*/
/*
* PORTS is an intelligent feature card for the 3B2 that supports four
* serial lines and one Centronics parallel port.
*
* The PORTS card is based on the Common I/O (CIO) platform. It uses
* two SCN2681A DUARTs to supply the four serial lines, and uses the
* SCN2681A parallel I/O pins for the Centronics parallel port.
*
* This file implements the required logic for the PORTS CIO
* interface. The SCN2681A functionality is implemented in the file
* 3b2_duart.c, and is used by both this feature card and the System
* Board console/contty functionality.
*/
#ifndef _3B2_PORTS_H_
#define _3B2_PORTS_H_
#include "3b2_defs.h"
#include "3b2_io.h"
#define PORTS_ID 0x0003
#define PORTS_IPL 10
#define PORTS_VERSION 1
#define DEF_PORTS_CARDS 1
#define MAX_PORTS_CARDS 12
#define PORTS_LINES 4
#define PORTS_RCV_QUEUE 5
/*
* Sub-field values for the PPC_DEVICE request entry; these are placed
* in app_data.bt[0] in the PPC_DEVICE application field. The prefix
* DR indicates that this is a code for use in "device" request
* entries only.
*/
#define DR_ENA 1 /* enable a device */
#define DR_DIS 2 /* disable a device */
#define DR_ABR 3 /* abort reception on a device */
#define DR_ABX 4 /* abort transmission on a device */
#define DR_BRK 5 /* transmit "break" on a device */
#define DR_SUS 6 /* suspend xmit on a device */
#define DR_RES 7 /* resume xmit on a device */
#define DR_BLK 8 /* transmit STOP character */
#define DR_UNB 9 /* transmit START character */
/*
* Sub-field values for the PPC_DEVICE completion entry; these appear
* in app_data.bt[0] in the PPC_DEVICE application field. These are
* mutually exclusive and cannot be combined. The prefix DC indicates
* that this is a code for use in "device" completion entries only.
*/
#define DC_NORM 0x00 /* command executed as requested */
#define DC_DEV 0x01 /* bad device number */
#define DC_NON 0x02 /* bad sub-code on request */
#define DC_FAIL 0x03 /* failed to read express entry */
/*
* Sub-field values for the PPC_RECV completion entry; these appear in
* app_data.bt[0] in the PPC_RECV application field. These are NOT
* mutually exclusive and may appear in combination. The prefix RC
* indicates that this is a code for use in "read" completion entries
* only.
*/
#define RC_DSR 0x01 /* disruption of service */
#define RC_FLU 0x02 /* buffer flushed */
#define RC_TMR 0x04 /* inter-character timer expired */
#define RC_BQO 0x08 /* PPC buffer queue overflow */
#define RC_UAO 0x10 /* uart overrun */
#define RC_PAR 0x20 /* parity error */
#define RC_FRA 0x40 /* framing error */
#define RC_BRK 0x80 /* break received */
/*
* The following codes are included on the DISC (disconnect) command.
* They are "or"ed into the app_data.bt[1] application field in a
* request. These codes are NOT mutually exclusive and can be used in
* any combination.
*/
#define GR_DTR 0x01
#define GR_CREAD 0x02
/*
* Sub-field values for the PPC_XMIT and PPC_OPTIONS completion
* entries; these appear in app_data.bt[0] in the application fields.
* These are NOT mutually exclusive and may appear in combination.
* The prefix GC indicates that this is a code for use in "general"
* completion entries only.
*/
#define GC_DSR 0x01 /* disruption of service */
#define GC_FLU 0x02 /* buffer flushed */
/*
* Sub-field values for the PPC_ASYNC completion entry; these appear
* in app_data.bt[0] in the PPC_ASYNC application field. These are
* mutually exclusive and cannot be combined. The prefix AC indicates
* that this is a code for use in "asynchronous" completion entries
* only.
*/
#define AC_CON 0x01 /* connection detected */
#define AC_DIS 0x02 /* disconnection detected */
#define AC_BRK 0x03 /* asynchronous "break" */
#define AC_FLU 0x04 /* xmit flush complete */
/* Line Discipline flags (input and output) */
#define IGNBRK 0x0001
#define BRKINT 0x0002
#define IGNPAR 0x0004
#define PARMRK 0x0008
#define INPCK 0x0010
#define ISTRIP 0x0020
#define INLCR 0x0040
#define IGNCR 0x0080
#define ICRNL 0x0100
#define IUCLC 0x0200
#define IXON 0x0400
#define IXANY 0x0800
#define OPOST 0x0001
#define OLCUC 0x0002
#define ONLCR 0x0004
#define OCRNL 0x0008
#define ONOCR 0x0010
#define ONLRET 0x0020
#define OFILL 0x0040
#define OFDEL 0x0080
#define ONLDLY 0x0100
#define OCRDLY 0x0600
#define OTABDLY 0x1800
#define OBSDLY 0x2000
#define OVTDLY 0x4000
#define OFFDLY 0x8000
/* Opcodes for PORTS card */
#define PPC_OPTIONS 32 /* GEN, COMP queues: set PPC options */
#define PPC_XMIT 33 /* GEN, COMP queues: transmit a buffer */
#define PPC_CONN 34 /* GEN, COMP queues: connect a device */
#define PPC_DISC 35 /* GEN, COMP queues: disconnect a device */
#define PPC_BRK 36 /* GEN, COMP queues: ioctl break */
#define PPC_DEVICE 40 /* EXP, ECOMP entries: device control command */
#define PPC_CLR 41 /* EXP, ECOMP entries: board clear */
#define PPC_RECV 50 /* RECV, COMP queues: receive request */
#define PPC_ASYNC 60 /* Asynchronous request */
#define CFW_CONFIG 70 /* GEN, COMP queues: set PPC port 0 hardware options */
#define CFW_IREAD 71 /* GEN, COMP queues: read immediate one to four bytes */
#define CFW_IWRITE 72 /* GEN, COMP queues: write immediate one to four bytes */
#define CFW_WRITE 73 /* GEN, COMP queues: write */
#define PPC_VERS 80 /* EXP, COMP queues: Version */
typedef struct {
uint32 tx_addr; /* Address to next read from */
uint32 tx_req_addr; /* Original request address */
uint32 tx_chars; /* Number of chars left to transfer */
uint32 tx_req_chars; /* Original number of chars */
uint8 rlp; /* Last known load pointer */
uint16 iflag; /* Line Discipline: Input flags */
uint16 oflag; /* Line Discipline: Output flags */
t_bool crlf; /* Indicates we are in a CRLF output transform */
t_bool conn; /* TRUE if connected, FALSE otherwise */
} PORTS_LINE_STATE;
typedef struct {
uint16 line; /* line discipline */
uint16 pad1;
uint16 iflag; /* input options word */
uint16 oflag; /* output options word */
uint16 cflag; /* hardware options */
uint16 lflag; /* line discipline options */
uint8 cerase; /* "erase" character */
uint8 ckill; /* "kill" character */
uint8 cinter; /* "interrupt" character */
uint8 cquit; /* "quit" character */
uint8 ceof; /* "end of file" character */
uint8 ceol; /* "end of line" character */
uint8 itime; /* inter character timer multiplier */
uint8 vtime; /* user-specified inter char timer */
uint8 vcount; /* user-specified maximum buffer char count */
uint8 pad2;
uint16 pad3;
} PORTS_OPTIONS;
extern DEVICE ports_dev;
t_stat ports_reset(DEVICE *dptr);
t_stat ports_setnl(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat ports_show_cqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat ports_show_rqueue(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat ports_rcv_svc(UNIT *uptr);
t_stat ports_xmt_svc(UNIT *uptr);
t_stat ports_cio_svc(UNIT *uptr);
t_stat ports_attach(UNIT *uptr, CONST char *cptr);
t_stat ports_detach(UNIT *uptr);
void ports_sysgen(uint8 cid);
void ports_express(uint8 cid);
void ports_full(uint8 cid);
static t_stat ports_show_queue_common(FILE *st, UNIT *uptr, int32 val,
CONST void *desc, t_bool rq);
#endif /* _3B2_PORTS_H_ */

View file

@ -35,6 +35,8 @@
#include "3b2_if.h"
#include "3b2_id.h"
#include "3b2_mmu.h"
#include "3b2_ctc.h"
#include "3b2_ports.h"
#include "3b2_sysdev.h"
char sim_name[] = "AT&T 3B2 Model 400";
@ -61,6 +63,8 @@ DEVICE *sim_devices[] = {
&dmac_dev,
&if_dev,
&id_dev,
&ports_dev,
&ctc_dev,
NULL
};
@ -87,6 +91,8 @@ void full_reset()
if_reset(&if_dev);
id_reset(&id_dev);
csr_reset(&csr_dev);
ports_reset(&ports_dev);
ctc_reset(&ctc_dev);
}
t_stat sim_load(FILE *fileref, CONST char *cptr, CONST char *fnam, int flag)

View file

@ -49,7 +49,7 @@ DEBTAB sys_deb_tab[] = {
{ "WRITE", WRITE_MSG, "Write activity" },
{ "EXECUTE", EXECUTE_MSG, "Execute activity" },
{ "IRQ", IRQ_MSG, "Interrupt activity"},
{ "TRACE", TRACE_MSG, "Detailed activity" },
{ "TRACE", TRACE_DBG, "Detailed activity" },
{ NULL, 0 }
};
@ -481,7 +481,7 @@ t_stat timer0_svc(UNIT *uptr)
time = TIMER_STP_US;
}
sim_activate_abs(uptr, (int32) DELAY_US(time));
sim_activate_after_abs(uptr, time);
return SCPE_OK;
}
@ -489,7 +489,7 @@ t_stat timer0_svc(UNIT *uptr)
t_stat timer1_svc(UNIT *uptr)
{
struct timer_ctr *ctr;
int32 ticks, t;
int32 t;
ctr = (struct timer_ctr *)uptr->tmr;
@ -498,14 +498,8 @@ t_stat timer1_svc(UNIT *uptr)
csr_data |= CSRCLK;
}
ticks = ctr->divider / TIMER_STP_US;
if (ticks < CLK_MIN_TICKS) {
ticks = TPS_CLK;
}
t = sim_rtcn_calb(ticks, TMR_CLK);
sim_activate_after(uptr, (uint32) (1000000 / ticks));
t = sim_rtcn_calb(TPS_CLK, TMR_CLK);
sim_activate_after_abs(uptr, 1000000/TPS_CLK);
tmxr_poll = t;
return SCPE_OK;
@ -524,7 +518,7 @@ t_stat timer2_svc(UNIT *uptr)
time = TIMER_STP_US;
}
sim_activate_abs(uptr, (int32) DELAY_US(time));
sim_activate_after_abs(uptr, time);
return SCPE_OK;
}
@ -598,7 +592,7 @@ void handle_timer_write(uint8 ctrnum, uint32 val)
ctr->enabled = TRUE;
ctr->stime = sim_gtime();
sim_cancel(timer_clk_unit);
sim_activate_abs(timer_clk_unit, ctr->divider * TIMER_STP_US);
sim_activate_after_abs(timer_clk_unit, ctr->divider * TIMER_STP_US);
break;
case 0x20:
ctr->divider &= 0x00ff;
@ -608,7 +602,7 @@ void handle_timer_write(uint8 ctrnum, uint32 val)
ctr->stime = sim_gtime();
/* Kick the timer to get the new divider value */
sim_cancel(timer_clk_unit);
sim_activate_abs(timer_clk_unit, ctr->divider * TIMER_STP_US);
sim_activate_after_abs(timer_clk_unit, ctr->divider * TIMER_STP_US);
break;
case 0x30:
if (ctr->lmb) {
@ -622,7 +616,7 @@ void handle_timer_write(uint8 ctrnum, uint32 val)
R[NUM_PC], ctrnum, val & 0xff);
/* Kick the timer to get the new divider value */
sim_cancel(timer_clk_unit);
sim_activate_abs(timer_clk_unit, ctr->divider * TIMER_STP_US);
sim_activate_after_abs(timer_clk_unit, ctr->divider * TIMER_STP_US);
} else {
ctr->lmb = TRUE;
ctr->divider = (ctr->divider & 0xff00) | (val & 0xff);

View file

@ -1730,7 +1730,8 @@ ATT3B2 = ${ATT3B2D}/3b2_cpu.c ${ATT3B2D}/3b2_mmu.c \
${ATT3B2D}/3b2_iu.c ${ATT3B2D}/3b2_if.c \
${ATT3B2D}/3b2_id.c ${ATT3B2D}/3b2_dmac.c \
${ATT3B2D}/3b2_sys.c ${ATT3B2D}/3b2_io.c \
${ATT3B2D}/3b2_sysdev.c
${ATT3B2D}/3b2_ports.c ${ATT3B2D}/3b2_ctc.c \
${ATT3B2D}/3b2_sysdev.c
ATT3B2_OPT = -I ${ATT3B2D} -DUSE_INT64 -DUSE_ADDR64
#
# Build everything (not the unsupported/incomplete or experimental simulators)