blob: e7d0f58c65815721bac2e5f649acec20e80613be [file] [log] [blame] [raw]
/* 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};
t_stat result;
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;
result = sim_disk_wrsect(&ctc_unit, lba, sec_buf, &secrw, 1);
if (result == SCPE_OK) {
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] ... CTC_WRITE: 512 bytes at block %d (0x%x)\n",
lba, lba);
cqe->opcode = CTC_SUCCESS;
} else {
cqe->opcode = CTC_RWERROR;
break;
}
}
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;
result = sim_disk_rdsect(&ctc_unit, lba, sec_buf, &secrw, 1);
if (result == SCPE_OK) {
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] ... CTC_READ: 512 bytes from block %d (0x%x)\n",
lba, lba);
for (j = 0; j < 512; j++) {
/* Drain the buffer */
pwrite_b(rqe->address + (b * 512) + j, sec_buf[j]);
}
} else {
sim_debug(TRACE_DBG, &ctc_dev,
"[ctc_cmd] Error reading sector at address %d. Giving up\n", lba);
break;
}
}
if (result == SCPE_OK) {
cqe->opcode = CTC_SUCCESS;
} else {
cqe->opcode = CTC_RWERROR;
}
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;
}