| /* 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, ¢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\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, ¢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; |
| 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; |
| 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, ¢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; |
| 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, ¢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; |
| } |