diff --git a/3B2/3b2_ctc.c b/3B2/3b2_ctc.c index 819764b7..2a6026da 100644 --- a/3B2/3b2_ctc.c +++ b/3B2/3b2_ctc.c @@ -1,816 +1,816 @@ -/* 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, j; - 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 (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 (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) -{ - uint8 cid; - - 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; -} +/* 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 b, j; + t_seccnt secrw = 0; + 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((char *)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 (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 (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) +{ + uint8 cid; + + 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; +} diff --git a/3B2/3b2_ctc.h b/3B2/3b2_ctc.h index ea5e1527..7b0d6556 100644 --- a/3B2/3b2_ctc.h +++ b/3B2/3b2_ctc.h @@ -1,158 +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_ */ +/* 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_ */ diff --git a/3B2/3b2_id.c b/3B2/3b2_id.c index 790b9c1f..af43bb2a 100644 --- a/3B2/3b2_id.c +++ b/3B2/3b2_id.c @@ -105,7 +105,7 @@ size_t id_buf_ptr = 0; uint8 id_idfield[ID_IDFIELD_LEN]; uint8 id_idfield_ptr = 0; -uint8 id_seek_state[ID_NUM_UNITS] = {ID_SEEK_NONE}; +int8 id_seek_state[ID_NUM_UNITS] = {ID_SEEK_NONE}; struct id_dtype { uint8 hd; /* Number of heads */ diff --git a/3B2/3b2_iu.c b/3B2/3b2_iu.c index b754f017..5b17d773 100644 --- a/3B2/3b2_iu.c +++ b/3B2/3b2_iu.c @@ -177,7 +177,7 @@ REG contty_reg[] = { { NULL } }; -char *brg_rates[IU_SPEED_REGS][IU_SPEEDS] = { +CONST char *brg_rates[IU_SPEED_REGS][IU_SPEEDS] = { {NULL, "110", NULL, NULL, "300", NULL, NULL, "1200", "2400", "4800", NULL, "9600", @@ -188,7 +188,7 @@ char *brg_rates[IU_SPEED_REGS][IU_SPEEDS] = { "19200", NULL, NULL, NULL} }; -char *parity[3] = {"O", "E", "N"}; +CONST char *parity[3] = {"O", "E", "N"}; UNIT contty_unit[2] = { { UDATA(&iu_svc_contty_rcv, UNIT_ATTABLE, 0) }, diff --git a/3B2/3b2_ports.c b/3B2/3b2_ports.c index 4d37d2a7..ff571ec3 100644 --- a/3B2/3b2_ports.c +++ b/3B2/3b2_ports.c @@ -1,963 +1,963 @@ -/* 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, 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; - 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; - 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) -{ - int32 i; - uint8 cid, line, 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; -} +/* 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, 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; + 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; + 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) +{ + int32 i; + uint8 cid, line, 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; +} diff --git a/3B2/3b2_ports.h b/3B2/3b2_ports.h index d6580195..558dc0c3 100644 --- a/3B2/3b2_ports.h +++ b/3B2/3b2_ports.h @@ -1,237 +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_ */ +/* 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_ */