3B2: LPT Device; MMU and SCSI fixes

This change adds support for printing to an attached text file via the
Centronics port of a simulated PORTS feature card. A new device named
"LPT" has been added. See "help lpt" for documentation.

Additionally, there has been a fix to a bug in the SCSI tape boot
implementation and a very minor bug fix to the Rev 3 MMU.
This commit is contained in:
Seth Morabito 2023-04-09 10:36:40 -07:00
parent e4ad37eccc
commit 7be9f2f3e8
10 changed files with 253 additions and 87 deletions

View file

@ -846,9 +846,9 @@ t_stat cpu_show_cio(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
fprintf(st, "---------------------\n");
for (slot = 0; slot < CIO_SLOTS; slot++) {
if (cio[slot].populated) {
fprintf(st, " %2d %s\n", slot, cio[slot].name);
fprintf(st, " %2d %s\n", slot + 1, cio[slot].name);
} else {
fprintf(st, " %2d -\n", slot);
fprintf(st, " %2d -\n", slot + 1);
}
}

View file

@ -158,6 +158,7 @@ extern DEVICE timer_dev;
extern DEVICE tod_dev;
extern DEVICE tti_dev;
extern DEVICE tto_dev;
extern DEVICE lpt_dev;
#if defined(REV3)
extern DEVICE flt_dev;
extern DEVICE ha_dev;

View file

@ -51,8 +51,6 @@
#include "3b2_mem.h"
#include "3b2_stddev.h"
#define IO_SCHED 1000
/* Device and units for PORTS cards
* --------------------------------
*
@ -100,13 +98,8 @@
#define PORTS_DIAG_CRC5 0x4be7bccc /* Used by SVR 2.0.5 */
#define PORTS_DIAG_CRC6 0x3197f6dd /* Used by SVR 2.0.5 */
#define PORTS_DFLT_LINES 4
#define PORTS_DFLT_CARDS 1
#define LN(slot,port) (ports_slot_ln[(slot)] + (port))
#define LSLOT(ln) (ports_ln_slot[ln])
/* #define LN(slot,port) ((PORTS_LINES * ((slot) - ports_base_slot)) + (port)) */
/* #define LSLOT(ln) (((ln) / PORTS_LINES) + ports_base_slot) */
#define LPORT(ln) ((ln) % PORTS_LINES)
int8 ports_base_slot; /* First slot in our contiguous block */
@ -115,21 +108,23 @@ uint8 ports_int_subdev; /* Interrupting subdevice */
t_bool ports_conf = FALSE; /* Have PORTS cards been configured? */
uint32 ports_crc; /* CRC32 of downloaded memory */
/* Mapping of line number to CIO card slot. Up to 32 lines spread over
8 slots are supported. */
/* Mapping of line number to CIO card slot. Up to 32 lines spread over 8
slots are supported. */
uint8 ports_ln_slot[MAX_LINES];
/* Mapping of slot number to base line number belonging to the card in
that slot. I.e., if there are two PORTS cards, one in slot 3 and
one in slot 5, index 3 will have starting line 0, index 5 will have
starting line 4. */
that slot. I.e., if there are two PORTS cards, one in slot 3 and one in
slot 5, index 3 will have starting line 0, index 5 will have starting
line 4. */
uint32 ports_slot_ln[CIO_SLOTS];
/* PORTS-specific state for each slot */
PORTS_LINE_STATE *ports_state = NULL;
/* Baud rates determined by the low nybble
* of the PORT_OPTIONS cflag */
/* Line-printer state (only one is supported) */
PORTS_LINE_STATE lpt_state;
/* 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",
@ -199,6 +194,38 @@ DEVICE ports_dev = {
NULL, /* device description */
};
UNIT lpt_unit = {
UDATA(NULL, UNIT_SEQ|UNIT_ATTABLE|UNIT_TEXT, 0), SERIAL_OUT_WAIT
};
DEVICE lpt_dev = {
"LPT", /* name */
&lpt_unit, /* units */
NULL, /* registers */
NULL, /* modifiers */
1, /* # units */
16, /* address radix */
32, /* address width */
1, /* address incr. */
16, /* data radix */
8, /* data width */
NULL, /* examine routine */
NULL, /* deposit routine */
&lpt_reset, /* reset routine */
NULL, /* boot routine */
&lpt_attach, /* attach routine */
&lpt_detach, /* detach routine */
NULL, /* context */
DEV_DISABLE|DEV_DIS, /* flags */
0, /* debug control flags */
NULL, /* debug flag names */
NULL, /* memory size change */
NULL, /* logical name */
&lpt_help, /* help routine */
NULL, /* attach help routine */
NULL, /* help context */
&lpt_description /* device description */
};
static void cio_irq(uint8 slot, uint8 dev, int32 delay)
{
@ -207,6 +234,23 @@ static void cio_irq(uint8 slot, uint8 dev, int32 delay)
sim_activate(&ports_unit[2], delay);
}
static void lpt_out(uint8 c)
{
if (!lpt_state.conn) {
return;
}
fputc(c, lpt_unit.fileref);
if (ferror(lpt_unit.fileref)) {
sim_perror("LPT I/O error");
clearerr(lpt_unit.fileref);
return;
}
lpt_unit.pos++;
}
/*
* 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.
@ -276,10 +320,22 @@ static void ports_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data)
PORTS_OPTIONS opts;
char line_config[16];
uint8 app_data[4] = {0};
PORTS_LINE_STATE *state;
centry.address = rentry->address;
cio[slot].op = rentry->opcode;
ln = LN(slot, rentry->subdevice & 0xf);
if ((rentry->subdevice & 7) == PORTS_CENTRONICS) {
ln = 0;
state = &lpt_state;
} else {
ln = LN(slot, rentry->subdevice & 7);
state = &ports_state[ln];
}
sim_debug(TRACE_DBG, &ports_dev,
"[ports_cmd] Command: slot %d, subdev %d, opcode 0x%02x\n",
slot, rentry->subdevice, rentry->opcode);
switch(rentry->opcode) {
case CIO_DLM:
@ -378,10 +434,10 @@ static void ports_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data)
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;
state->iflag = opts.iflag;
state->oflag = opts.oflag;
if ((rentry->subdevice & 0xf) < PORTS_LINES) {
if ((rentry->subdevice & 7) < PORTS_LINES) {
/* Adjust baud rate */
sprintf(line_config, "%s-8N1",
ports_baud[opts.cflag&0xf]);
@ -409,8 +465,7 @@ static void ports_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data)
centry.opcode = CIO_ULM;
/* TODO: It's unknown what the value 0x50 means, but this
* is what a real board sends. */
/* 0x50 (80 decimal) is the expected PROM version */
app_data[0] = 0x50;
cio_cqueue(slot, CIO_STAT, PPQESIZE, &centry, app_data);
cio_irq(slot, rentry->subdevice, DELAY_VERS);
@ -421,7 +476,9 @@ static void ports_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data)
"[ports_cmd] PPC CONNECT - subdevice = %02x\n",
rentry->subdevice);
if (rentry->subdevice < PORTS_LINES) {
ports_state[ln].conn = TRUE;
}
centry.opcode = PPC_CONN;
centry.subdevice = rentry->subdevice;
@ -437,11 +494,10 @@ static void ports_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data)
"[ports_cmd] PPC XMIT - subdevice = %02x, address=%08x, byte_count=%d\n",
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;
state->tx_addr = rentry->address;
state->tx_req_addr = state->tx_addr;
state->tx_chars = rentry->byte_count + 1;
state->tx_req_chars = state->tx_chars;
sim_activate_after(&ports_unit[1], ports_unit[1].wait);
@ -490,20 +546,26 @@ static void ports_cmd(uint8 slot, cio_entry *rentry, uint8 *rapp_data)
/*
* Update the connection status of the given port.
*/
static void ports_update_conn(uint32 ln)
static void ports_update_conn(uint8 slot, uint8 subdev)
{
cio_entry centry = {0};
uint8 slot;
uint8 app_data[4] = {0};
uint32 ln = LN(slot, subdev);
slot = LSLOT(ln);
/* If the card hasn't sysgened, there's no way to write a
* completion queue entry */
/* If the card hasn't sysgened, there's no way to write a completion
* queue entry */
if (cio[slot].sysgen_s != CIO_SYSGEN) {
return;
}
sim_debug(TRACE_DBG, &ports_dev, "[ports_update_conn] slot=%d, subdev=%d\n", slot, subdev);
if (subdev == PORTS_CENTRONICS) {
if (!lpt_state.conn) {
return;
}
app_data[0] = AC_CON;
} else {
if (ports_ldsc[ln].conn) {
app_data[0] = AC_CON;
ports_state[ln].conn = TRUE;
@ -515,9 +577,10 @@ static void ports_update_conn(uint32 ln)
app_data[0] = 0;
}
}
}
centry.opcode = PPC_ASYNC;
centry.subdevice = LPORT(ln);
centry.subdevice = subdev;
cio_cqueue(slot, CIO_CMD, PPQESIZE, &centry, app_data);
/* Interrupt */
@ -556,7 +619,7 @@ void ports_full(uint8 slot)
cio_entry rqe = {0};
uint8 app_data[4] = {0};
for (i = 0; i < PORTS_LINES; i++) {
for (i = 0; i < PORTS_RCV_QUEUE; i++) {
if (cio_rqueue(slot, i, PPQESIZE, &rqe, app_data) == SCPE_OK) {
ports_cmd(slot, &rqe, app_data);
}
@ -576,7 +639,7 @@ t_stat ports_reset(DEVICE *dptr)
sim_set_uname(&ports_unit[1], "PORTS-XMT");
sim_set_uname(&ports_unit[2], "PORTS-CIO");
ports_desc.lines = PORTS_DFLT_LINES;
ports_desc.lines = PORTS_LINES;
ports_desc.ldsc = ports_ldsc =
(TMLN *)calloc(ports_desc.lines, sizeof(*ports_ldsc));
}
@ -641,11 +704,13 @@ t_stat ports_cio_svc(UNIT *uptr)
switch (cio[ports_int_slot].op) {
case PPC_CONN:
cio[ports_int_slot].op = PPC_ASYNC;
if (ports_int_subdev < PORTS_LINES) {
ports_ldsc[LN(ports_int_slot, ports_int_subdev)].rcve = 1;
}
sim_activate(&ports_unit[2], DELAY_ASYNC);
break;
case PPC_ASYNC:
ports_update_conn(LN(ports_int_slot, ports_int_subdev));
ports_update_conn(ports_int_slot, ports_int_subdev);
break;
default:
break;
@ -670,14 +735,16 @@ t_stat ports_rcv_svc(UNIT *uptr)
ln = tmxr_poll_conn(&ports_desc);
if (ln >= 0) {
ports_update_conn(ln);
/* Possibly connect a newly opened port */
ports_update_conn(LSLOT(ln), LPORT(ln));
}
for (ln = 0; ln < ports_desc.lines; ln++) {
slot = LSLOT(ln);
if (!ports_ldsc[ln].conn && ports_state[ln].conn) {
ports_update_conn(ln);
/* Disconnect a connected line, it has been dropped */
ports_update_conn(LSLOT(ln), LPORT(ln));
} else if (ports_ldsc[ln].conn && ports_state[ln].conn) {
temp = tmxr_getc_ln(&ports_ldsc[ln]);
@ -728,7 +795,7 @@ t_stat ports_xmt_svc(UNIT *uptr)
uint8 app_data[4] = {0};
uint32 wait = 0x7fffffff;
/* Scan all lines for output */
/* Scan all MUX lines for output */
for (ln = 0; ln < ports_desc.lines; ln++) {
slot = LSLOT(ln);
if (ports_ldsc[ln].conn && ports_state[ln].tx_chars > 0) {
@ -748,30 +815,53 @@ t_stat ports_xmt_svc(UNIT *uptr)
/* Indicate that we're in a CRLF translation */
ports_state[ln].crlf = TRUE;
}
break;
}
} else {
ports_state[ln].crlf = FALSE;
if (tmxr_putc_ln(&ports_ldsc[ln], c) == SCPE_OK) {
wait = MIN(wait, ports_ldsc[ln].txdeltausecs);
ports_state[ln].tx_chars--;
ports_state[ln].tx_addr++;
sim_debug(IO_DBG, &ports_dev,
"[ports_xmt_svc] [LINE %d] XMIT: %02x (%c)\n",
ln, c, c);
}
if (ports_state[ln].tx_chars == 0) {
sim_debug(TRACE_DBG, &ports_dev,
"[ports_xmt_svc] Done with xmit, card=%d port=%d. Interrupting.\n",
slot, 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;
app_data[0] = GC_FLU;
cio_cqueue(slot, CIO_STAT, PPQESIZE, &centry, app_data);
CIO_SET_INT(slot);
}
}
}
}
/* Scan LPT line for output */
if (lpt_state.conn && lpt_state.tx_chars > 0) {
tx = TRUE;
/* This is a hack -- we just want the slot of the first installed
PORTS board */
slot = LSLOT(0);
wait = MIN(wait, SERIAL_OUT_WAIT);
c = pread_b(lpt_state.tx_addr, BUS_PER);
/* The PORTS card optionally handles NL->CRLF */
if (c == 0xa && (lpt_state.oflag & ONLCR) && !(lpt_state.crlf)) {
/* Indicate that we're in a CRLF translation */
lpt_state.crlf = TRUE;
lpt_out(0xd);
} else {
lpt_state.crlf = FALSE;
lpt_state.tx_chars--;
lpt_state.tx_addr++;
lpt_out(c);
if (lpt_state.tx_chars == 0) {
centry.byte_count = lpt_state.tx_req_chars;
centry.subdevice = PORTS_CENTRONICS;
centry.opcode = PPC_XMIT;
centry.address = lpt_state.tx_req_addr;
app_data[0] = GC_FLU;
cio_cqueue(slot, CIO_STAT, PPQESIZE, &centry, app_data);
CIO_SET_INT(slot);
}
@ -836,3 +926,59 @@ t_stat ports_detach(UNIT *uptr)
return SCPE_OK;
}
t_stat lpt_reset(DEVICE *dptr)
{
/* No-op */
return SCPE_OK;
}
t_stat lpt_attach(UNIT *uptr, const char *cptr)
{
t_stat reason;
if (ports_dev.flags & DEV_DIS) {
return SCPE_NOFNC;
}
if ((reason = attach_unit(uptr, cptr)) == SCPE_OK) {
lpt_state.conn = TRUE;
}
return reason;
}
t_stat lpt_detach(UNIT *uptr)
{
lpt_state.conn = FALSE;
return detach_unit(uptr);
}
t_stat lpt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf(st, "Line Printer (LPT)\n\n");
fprintf(st, "The line printer (LPT) simulates an AT&T 470 120cps line printer\n");
fprintf(st, "connected to the Centronics port of the first installed PORTS card. It\n");
fprintf(st, "writes text output to an attached file on disk. To use the LPT device,\n");
fprintf(st, "first ENABLE it, then ATTACH it to a disk file to be used for text \n");
fprintf(st, "output, for example:\n\n");
fprintf(st, " SET LPT ENABLE\n");
fprintf(st, " ATTACH LPT my_output.txt\n\n");
fprintf(st, "To use this device under System V UNIX, set up a printer attached to\n");
fprintf(st, "TTY number 5 of the first installed PORTS card.\n\n");
fprintf(st, "For example, if the first installed PORTS card has created TTY devices\n");
fprintf(st, "/dev/tty21 through /dev/tty25 (where /dev/tty2* indicates that the PORTS\n");
fprintf(st, "card is in backplane slot 2), the printer will be on TTY device /dev/tty25.\n\n");
fprintf(st, "Please note that the LPT requires at least one PORTS card be configured. It\n");
fprintf(st, "will not allow attaching to an output file unless a PORTS card is enabled.\n");
fprintf(st, "If you see the output\n\n");
fprintf(st, " Command not allowed\n\n");
fprintf(st, "when trying to attach the LPT device, it means you have not enabled at\n");
fprintf(st, "least one PORTS card.\n");
return SCPE_OK;
}
const char *lpt_description(DEVICE *dptr)
{
return "AT&T 470 120cps Dot Matrix Printer";
}

View file

@ -51,16 +51,15 @@
#define PORTS_IPL 10
#define PORTS_VERSION 1
#define MAX_CARDS 8 /* Up to 8 PORTS cards with 32 lines total
supported */
#define PORTS_LINES 4
#define PORTS_RCV_QUEUE 5
#define MAX_CARDS 8 /* Up to 8 PORTS cards supported */
#define PORTS_CENTRONICS 4 /* Subdevice 4 is the centronics port */
#define PORTS_LINES 4 /* Subdevices 0-3 are RS232 ports */
#define PORTS_RCV_QUEUE 5 /* Total of 5 receive queues for all ports */
/*
* 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.
* 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 */
@ -222,6 +221,11 @@ 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);
t_stat lpt_reset (DEVICE *dptr);
t_stat lpt_attach (UNIT *uptr, CONST char *ptr);
t_stat lpt_detach (UNIT *uptr);
t_stat lpt_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
const char *lpt_description (DEVICE *dptr);
void ports_sysgen(uint8 slot);
void ports_express(uint8 slot);
void ports_full(uint8 slot);

View file

@ -97,6 +97,8 @@
#define MEMID_1M 2
#define MEMID_2M 1
#define MEMID_4M 3
#define MMUBASE 0x40000
#define MMUSIZE 0x1000
/* DMA Controller */
#define DMACBASE 0x48000

View file

@ -140,9 +140,6 @@
*
***********************************************************************/
#define MMUBASE 0x40000
#define MMUSIZE 0x1000
#define MMU_SRS 0x04 /* Section RAM array size (words) */
#define MMU_SDCS 0x20 /* Segment Descriptor Cache H/L array size
(words) */

View file

@ -60,6 +60,7 @@ DEVICE *sim_devices[] = {
&if_dev,
&id_dev,
&ports_dev,
&lpt_dev,
&ctc_dev,
&ni_dev,
NULL
@ -77,6 +78,7 @@ void full_reset()
id_reset(&id_dev);
csr_reset(&csr_dev);
ports_reset(&ports_dev);
lpt_reset(&lpt_dev);
ctc_reset(&ctc_dev);
ni_reset(&ni_dev);
}

View file

@ -600,6 +600,10 @@ void mmu_write(uint32 pa, uint32 val, size_t size)
/* Flush all PDC cache entries for this section */
for (i = 0; i < MMU_PDCS; i++) {
if (((mmu_state.pdch[i] >> 24) & 0x3) == index) {
sim_debug(MMU_CACHE_DBG, &mmu_dev,
"Flushing MMU PDC entry at index %d "
"(pdc_lo=%08x pdc_hi=%08x)\n",
i, mmu_state.pdcl[i], mmu_state.pdch[i]);
mmu_state.pdch[i] &= ~(PDC_G_MASK);
}
}
@ -1041,8 +1045,9 @@ uint32 mmu_xlate_addr(uint32 va, uint8 r_acc)
succ = mmu_decode_va(va, r_acc, TRUE, &pa);
if (succ == SCPE_OK) {
mmu_state.var = va;
if (succ == SCPE_OK) {
return pa;
} else {
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT);

View file

@ -60,6 +60,7 @@ DEVICE *sim_devices[] = {
&if_dev,
&ha_dev,
&ports_dev,
&lpt_dev,
&ni_dev,
NULL
};
@ -76,5 +77,6 @@ void full_reset()
ha_reset(&ha_dev);
csr_reset(&csr_dev);
ports_reset(&ports_dev);
lpt_reset(&lpt_dev);
ni_reset(&ni_dev);
}

View file

@ -506,7 +506,16 @@ static void ha_boot_tape(UNIT *uptr, uint8 tc)
return;
}
r = sim_tape_rdrecf(uptr, buf, &sectsread, HA_BLKSZ); /* Read block 0 */
r = sim_tape_sprecf(uptr, &sectsread); /* Skip block 0 */
if (r != SCPE_OK) {
sim_debug(HA_TRACE, &ha_dev,
"[ha_boot_tape] Could not skip block 0.\n");
HA_STAT(tc, HA_CKCON, CIO_SUCCESS);
return;
}
r = sim_tape_rdrecf(uptr, buf, &sectsread, HA_BLKSZ); /* Read block 1 */
if (r != SCPE_OK) {
sim_debug(HA_TRACE, &ha_dev,
@ -523,8 +532,6 @@ static void ha_boot_tape(UNIT *uptr, uint8 tc)
"[ha_boot_tape] Transfered 512 bytes to 0x%08x\n",
HA_BOOT_ADDR);
r = sim_tape_sprecf(uptr, &sectsread); /* Skip block 1 */
HA_STAT(tc, HA_GOOD, CIO_SUCCESS);
ha_state.ts[tc].rep.addr = HA_BOOT_ADDR;