/* 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" | |
/* Static function declarations */ | |
static t_stat ports_show_queue_common(FILE *st, UNIT *uptr, int32 val, | |
CONST void *desc, t_bool rq); | |
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 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 PORTS_DIAG_CRC1 0x7ceec900 | |
#define PORTS_DIAG_CRC2 0x77a1ea56 | |
#define PORTS_DIAG_CRC3 0x84cf938b | |
#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 */ | |
uint32 ports_crc; /* CRC32 of downloaded memory */ | |
/* 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, 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 ln, i; | |
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: | |
for (i = 0; i < rentry->byte_count; i++) { | |
ports_crc = cio_crc32_shift(ports_crc, pread_b(rentry->address + i)); | |
} | |
sim_debug(TRACE_DBG, &ports_dev, | |
"[%08x] [ports_cmd] CIO Download Memory: bytecnt=%04x " | |
"addr=%08x return_addr=%08x subdev=%02x (CRC=%08x)\n", | |
R[NUM_PC], | |
rentry->byte_count, rentry->address, | |
centry.address, centry.subdevice, ports_crc); | |
centry.address = rentry->address + rentry->byte_count; | |
cio_cexpress(cid, PPQESIZE, ¢ry, 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, ¢ry, 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 (CRC=%08x)\n", | |
R[NUM_PC], ports_crc); | |
/* If the currently running program is a diagnostics program, | |
* we are expected to write results into memory at address | |
* 0x200f000 */ | |
if (ports_crc == PORTS_DIAG_CRC1 || | |
ports_crc == PORTS_DIAG_CRC2 || | |
ports_crc == PORTS_DIAG_CRC3) { | |
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 */ | |
} | |
/* 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, ¢ry, 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, ¢ry, 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, ¢ry, 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, ¢ry, 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, ¢ry, 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, ¢ry, 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, ¢ry, 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, ¢ry, 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, ¢ry, 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 = {0}; | |
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, ¢ry, app_data); | |
/* Interrupt */ | |
if (cio[cid].ivec > 0) { | |
cio[cid].intr = TRUE; | |
} | |
} | |
void ports_sysgen(uint8 cid) | |
{ | |
cio_entry cqe = {0}; | |
uint8 app_data[4] = {0}; | |
ports_crc = 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 = {0}; | |
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; | |
cio_entry rqe = {0}; | |
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) | |
{ | |
int32 i; | |
uint8 cid, line, ln, end_slot; | |
TMLN *lp; | |
ports_crc = 0; | |
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, ¢ry, 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 = {0}; | |
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].txdeltausecs); | |
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].txdeltausecs); | |
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, ¢ry, 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; | |
} |