/* B5500_io.c: Burroughs 5500 I/O System. | |
Copyright (c) 2016, Richard Cornwell | |
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 | |
RICHARD CORNWELL 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. | |
*/ | |
#include "b5500_defs.h" | |
#define EOR 1 | |
#define USEGM 2 | |
t_stat chan_reset(DEVICE * dptr); | |
/* Channel data structures | |
chan_dev Channel device descriptor | |
chan_unit Channel unit descriptor | |
chan_reg Channel register list | |
chan_mod Channel modifiers list | |
*/ | |
t_uint64 D[NUM_CHAN]; /* Current I/O instruction */ | |
uint8 CC[NUM_CHAN]; /* Channel character count */ | |
t_uint64 W[NUM_CHAN]; /* Assembly register */ | |
uint8 status[NUM_CHAN]; /* Channel status */ | |
uint8 cstatus; /* Active status */ | |
#define WC(x) (uint16)(((x) & DEV_WC) >> DEV_WC_V) | |
#define toWC(x) (((t_uint64)(x) << DEV_WC_V) & DEV_WC) | |
UNIT chan_unit[] = { | |
/* Normal channels */ | |
{UDATA(NULL,UNIT_DISABLE,0)},/* A */ | |
{UDATA(NULL,UNIT_DISABLE,0)},/* B */ | |
{UDATA(NULL,UNIT_DISABLE,0)},/* C */ | |
{UDATA(NULL,UNIT_DISABLE,0)},/* D */ | |
}; | |
REG chan_reg[] = { | |
{BRDATA(D, D, 8, 48, NUM_CHAN), REG_RO}, | |
{BRDATA(CC, CC, 7, 6, NUM_CHAN), REG_RO}, | |
{BRDATA(W, W, 8, 48, NUM_CHAN), REG_RO}, | |
{NULL} | |
}; | |
/* Simulator debug controls */ | |
DEBTAB chn_debug[] = { | |
{"CHANNEL", DEBUG_CHAN}, | |
{"DETAIL", DEBUG_DETAIL}, | |
{"DATA", DEBUG_DATA}, | |
{"CH0", 0x0100 << 0}, | |
{"CH1", 0x0100 << 1}, | |
{"CH2", 0x0100 << 2}, | |
{"CH3", 0x0100 << 3}, | |
{0, 0} | |
}; | |
DEVICE chan_dev = { | |
"IO", chan_unit, chan_reg, NULL, | |
NUM_CHAN, 10, 18, 1, 10, 44, | |
NULL, NULL, &chan_reset, NULL, NULL, NULL, | |
NULL, DEV_DEBUG, 0, chn_debug | |
}; | |
t_stat | |
chan_reset(DEVICE * dptr) | |
{ | |
int i; | |
int j = 1; | |
cstatus = 0; | |
/* Clear channel assignment */ | |
for (i = 0; i < NUM_CHAN; i++) { | |
status[i] = 0; | |
D[i] = 0; | |
W[i] = 0; | |
CC[i] = 0; | |
if (chan_unit[i].flags & UNIT_DIS) | |
cstatus |= j; | |
j <<= 1; | |
} | |
return SCPE_OK; | |
} | |
/* Boot from given device */ | |
t_stat | |
chan_boot(t_uint64 desc) | |
{ | |
M[020] = desc; | |
M[010] = 020; | |
loading = 1; | |
start_io(); | |
return SCPE_OK; | |
} | |
int | |
find_chan() { | |
int i; | |
int chan; | |
i = 1; | |
for(chan = 0; chan < NUM_CHAN; chan++) { | |
if ((cstatus & i) == 0) | |
break; | |
i <<= 1; | |
} | |
if (chan == NUM_CHAN) { | |
return 0; | |
} | |
return chan + 1; | |
} | |
void | |
chan_release(int chan) { | |
cstatus &= ~(1 << chan); | |
} | |
int | |
chan_advance(int chan) { | |
uint16 addr = (uint16)(D[chan] & CORE); | |
if (D[chan] & DEV_WCFLG) { | |
uint16 wc = WC(D[chan]); | |
if (wc == 0) { | |
status[chan] |= EOR; | |
return 1; | |
} | |
D[chan] &= ~DEV_WC; | |
D[chan] |= toWC(wc-1); | |
} | |
if (addr > MEMSIZE) { | |
D[chan] |= DEV_MEMERR; | |
status[chan] |= EOR; | |
return 1; | |
} | |
W[chan] = M[addr]; | |
D[chan] &= ~CORE; | |
if (D[chan] & DEV_BACK) | |
D[chan] |= (t_uint64)((addr - 1) & CORE); | |
else | |
D[chan] |= (t_uint64)((addr + 1) & CORE); | |
CC[chan] = 0; | |
return 0; | |
} | |
void | |
start_io() { | |
int i; | |
int chan; | |
t_stat r; | |
uint16 dev; | |
uint16 cmd; | |
uint16 wc; | |
int addr; | |
chan = find_chan(); | |
addr = M[010] & CORE; | |
if (chan == 0) { | |
IAR |= IRQ_1; | |
return; | |
} | |
chan--; | |
i = 1 << chan; | |
sim_debug(DEBUG_DETAIL, &chan_dev, "strtio(%016llo %d)\n", M[addr], chan); | |
D[chan] = M[addr] & D_MASK; | |
CC[chan] = 0; | |
W[chan] = 0; | |
dev = (uint16)((D[chan] & DEVMASK) >> DEV_V); | |
cmd = (uint16)((D[chan] & DEV_CMD) >> DEV_CMD_V); | |
wc = WC(D[chan]); | |
D[chan] &= ~DEV_RESULT; | |
status[chan] = 0; | |
if (dev & 1) { | |
#if (NUM_DEVS_MT > 0) | |
status[chan] = USEGM; | |
r = mt_cmd(cmd, dev, chan, &wc); | |
#else | |
r = SCPE_UNATT; | |
#endif | |
} else { | |
switch(dev) { | |
#if (NUM_DEVS_DR > 0) | |
case DRUM1_DEV: | |
case DRUM2_DEV: | |
r = drm_cmd(cmd, dev, chan, &wc, (uint8)((M[addr] & PRESENT)!=0)); | |
break; | |
#endif | |
#if (NUM_DEVS_CDR > 0) | (NUM_DEVS_CDP > 0) | |
case CARD1_DEV: | |
case CARD2_DEV: | |
r = card_cmd(cmd, dev, chan, &wc); | |
break; | |
#endif | |
#if (NUM_DEVS_DSK > 0) | |
case DSK1_DEV: | |
case DSK2_DEV: | |
/* Need to pass word count to identify interrogates */ | |
r = dsk_cmd(cmd, dev, chan, &wc); | |
break; | |
#endif | |
#if (NUM_DEVS_DTC > 0) | |
case DTC_DEV: | |
status[chan] = USEGM; | |
/* Word count is TTU and BUF number */ | |
r = dtc_cmd(cmd, dev, chan, &wc); | |
if (r == SCPE_OK) | |
D[chan] &= ~DEV_WC; | |
D[chan] &= ~(DEV_BIN|DEV_WCFLG); | |
wc = 0; | |
break; | |
#endif | |
#if (NUM_DEVS_LPR > 0) | |
case PRT1_DEV: | |
case PRT2_DEV: | |
r = lpr_cmd(cmd, dev, chan, &wc); | |
if (r == SCPE_OK) | |
D[chan] &= ~DEV_BACK; /* Clear this bit, since printer | |
uses this to determine 120/132 | |
char line */ | |
break; | |
#endif | |
#if (NUM_DEVS_CON > 0) | |
case SPO_DEV: | |
status[chan] = USEGM; | |
r = con_cmd(cmd, dev, chan, &wc); | |
break; | |
#endif | |
default: | |
r = SCPE_UNATT; | |
break; | |
} | |
} | |
if (wc != 0) { | |
D[chan] &= ~DEV_WC; | |
D[chan] |= toWC(wc) | DEV_WCFLG; | |
} | |
switch(r) { | |
case SCPE_OK: | |
cstatus |= i; | |
return; | |
case SCPE_NXDEV: | |
case SCPE_UNATT: | |
D[chan] |= DEV_NOTRDY; | |
break; | |
case SCPE_BUSY: | |
D[chan] |= DEV_BUSY; | |
break; | |
case SCPE_EOF: | |
D[chan] |= DEV_EOF; | |
break; | |
} | |
chan_set_end(chan); | |
} | |
void | |
chan_set_end(int chan) { | |
uint16 dev; | |
dev = (uint16)((D[chan] & DEVMASK) >> DEV_V); | |
/* Set character count if reading and tape */ | |
if ((dev & 1) && (D[chan] & DEV_IORD)) { | |
D[chan] &= ~((7LL)<<DEV_WC_V); | |
/* If not data transfered, return zero code */ | |
if ((D[chan] & DEV_BACK) != 0 && | |
(status[chan] & EOR) != 0) | |
D[chan] |= ((t_uint64)((7-CC[chan]) & 07)) << DEV_WC_V; | |
else | |
D[chan] |= ((t_uint64)(CC[chan] & 07)) << DEV_WC_V; | |
} | |
M[014+chan] = D[chan]; | |
if (loading == 0) | |
IAR |= IRQ_5 << chan; | |
else | |
loading = 0; | |
sim_debug(DEBUG_DETAIL, &chan_dev, "endio (%016llo %o)\n", D[chan], chan); | |
} | |
void | |
chan_set_eof(int chan) { | |
D[chan] |= DEV_EOF; | |
} | |
void | |
chan_set_parity(int chan) { | |
D[chan] |= DEV_PARITY; | |
} | |
void | |
chan_set_error(int chan) { | |
D[chan] |= DEV_ERROR; | |
} | |
void | |
chan_set_wcflg(int chan) { | |
D[chan] |= DEV_WCFLG; | |
} | |
void | |
chan_set_read(int chan) { | |
D[chan] |= DEV_IORD; | |
} | |
void | |
chan_set_gm(int chan) { | |
D[chan] |= DEV_BACK; | |
} | |
void | |
chan_set_notrdy(int chan) { | |
D[chan] |= DEV_NOTRDY; | |
chan_set_end(chan); | |
} | |
void | |
chan_set_eot(int chan) { | |
D[chan] &= ~DEV_WC; | |
D[chan] |= DEV_EOT; | |
} | |
void | |
chan_set_bot(int chan) { | |
D[chan] &= ~DEV_WC; | |
D[chan] |= DEV_BOT; | |
} | |
void | |
chan_set_blank(int chan) { | |
D[chan] &= ~DEV_WC; | |
D[chan] |= DEV_BLANK; | |
} | |
void | |
chan_set_wrp(int chan) { | |
D[chan] |= DEV_ERROR|DEV_MEMERR; | |
} | |
void | |
chan_set_wc(int chan, uint16 wc) { | |
D[chan] &= ~DEV_WC; | |
D[chan] |= toWC(wc); | |
} | |
/* | |
Internal BCD | |
00 0000 00 1010 0000 -> 1010 | |
00 0001 00 0001 | |
00 0010 00 0010 | |
00 0011 00 0011 | |
00 0100 00 0100 | |
00 0101 00 0101 | |
00 0110 00 0110 | |
00 0111 00 0111 | |
00 1000 00 1000 | |
00 1001 00 1001 | |
00 1010 00 1011 1010 -> 1011 | |
00 1011 00 1100 1011 -> 1100 | |
00 1100 00 0000 1100 -> 0000 | |
00 1101 00 1101 | |
00 1110 00 1110 | |
00 1111 00 1111 | |
01 0000 11 1010 0000 -> 1010 10 | |
01 0001 11 0001 1 | |
01 0010 11 0010 2 | |
01 0011 11 0011 3 | |
01 0100 11 0100 | |
01 0101 11 0101 | |
01 0110 11 0110 | |
01 0111 11 0111 | |
01 1000 11 1000 8 | |
01 1001 11 1001 9 | |
01 1010 11 1011 1010 -> 1011 10 | |
01 1011 11 1100 1011 -> 1100 11 | |
01 1100 11 0000 1100 -> 0000 12 | |
01 1101 11 1101 13 | |
01 1110 11 1110 14 | |
01 1111 11 1111 15 | |
10 0000 10 1010 0000 -> 1010 | |
10 0001 10 0001 | |
10 0010 10 0010 | |
10 0011 10 0011 | |
10 0100 10 0100 | |
10 0101 10 0101 | |
10 0110 10 0110 | |
10 0111 10 0111 | |
10 1000 10 1000 | |
10 1001 10 1001 | |
10 1010 10 1011 1010 -> 1011 | |
10 1011 10 1100 1011 -> 1100 | |
10 1100 10 0000 1100 -> 0000 | |
10 1101 10 1101 | |
10 1110 10 1110 | |
10 1111 10 1111 | |
11 0000 01 0000 | |
11 0001 01 0001 | |
11 0010 01 0010 | |
11 0011 01 0011 | |
11 0100 01 0100 | |
11 0101 01 0101 | |
11 0110 01 0110 | |
11 0111 01 0111 | |
11 1000 01 1000 | |
11 1001 01 1001 | |
11 1010 01 1011 1010 -> 1011 | |
11 1011 01 1100 1011 -> 1100 | |
11 1100 01 1010 1100 -> 1010 | |
11 1101 01 1101 | |
11 1110 01 1110 | |
11 1111 01 1111 */ | |
/* returns 1 when channel can take no more characters. | |
A return of 1 indicates that the character was not | |
processed. | |
*/ | |
int chan_write_char(int chan, uint8 *ch, int flags) { | |
uint8 c; | |
if (status[chan] & EOR) | |
return 1; | |
if (D[chan] & DEV_INHTRF) { | |
status[chan] |= EOR; | |
return 1; | |
} | |
/* Check if first word */ | |
if (CC[chan] == 0) { | |
uint16 wc = WC(D[chan]); | |
if (D[chan] & DEV_WCFLG && wc == 0) { | |
sim_debug(DEBUG_DATA, &chan_dev, "zerowc(%d)\n", chan); | |
status[chan] |= EOR; | |
return 1; | |
} | |
W[chan] = 0; | |
} | |
c = *ch & 077; | |
if ((D[chan] & DEV_BIN) == 0) { | |
/* Translate BCL to BCD */ | |
uint8 cx = c & 060; | |
c &= 017; | |
switch(c) { | |
case 0: | |
/* 11-0 -> 01 C */ | |
/* 10-0 -> 10 C */ | |
/* 01-0 -> 11 0 */ | |
/* 00-0 -> 00 C */ | |
if (cx != 020) | |
c = 0xc; | |
break; | |
case 1: | |
case 2: | |
case 3: | |
case 4: | |
case 5: | |
case 6: | |
case 7: | |
case 8: | |
case 9: | |
case 0xd: | |
case 0xe: | |
case 0xf: | |
break; | |
case 0xa: | |
/* 11-A -> 01 0 */ | |
/* 10-A -> 10 0 */ | |
/* 01-A -> 11 C */ | |
/* 00-A -> 00 0 */ | |
if (cx == 020) | |
c = 0xc; | |
else | |
c = 0; | |
break; | |
case 0xb: | |
c = 0xa; | |
break; | |
case 0xc: | |
c = 0xb; | |
break; | |
} | |
c |= cx ^ ((cx & 020)<<1); | |
} | |
if (D[chan] & DEV_BACK) | |
W[chan] |= ((t_uint64)c) << ((CC[chan]) * 6); | |
else | |
W[chan] |= ((t_uint64)c) << ((7 - CC[chan]) * 6); | |
CC[chan]++; | |
if (CC[chan] == 8) { | |
uint16 addr = (uint16)(D[chan] & CORE); | |
M[addr] = W[chan]; | |
sim_debug(DEBUG_DATA, &chan_dev, "write(%d, %05o, %016llo)\n", | |
chan, addr, W[chan]); | |
if (chan_advance(chan)) | |
return 1; | |
W[chan] = 0; | |
} | |
if (flags) { | |
if ((D[chan] & (DEV_BIN|DEV_WCFLG)) == 0) { | |
/* Insert group mark */ | |
if (D[chan] & DEV_BACK) { | |
int i = CC[chan]; | |
W[chan] |= (t_uint64)037LL << ((CC[chan]) * 6); | |
while(i < 8) { | |
W[chan] |= (t_uint64)014LL << (i * 6); | |
i++; | |
} | |
} else { | |
W[chan] |= 037LL << ((7 - CC[chan]) * 6); | |
} | |
CC[chan]++; | |
} | |
/* Flush last word */ | |
if (CC[chan] != 0) { | |
uint16 addr = (uint16)(D[chan] & CORE); | |
M[addr] = W[chan]; | |
sim_debug(DEBUG_DATA, &chan_dev, "writef(%d, %05o, %016llo)\n", | |
chan, addr, W[chan]); | |
(void)chan_advance(chan); | |
W[chan] = 0; | |
} | |
status[chan] |= EOR; | |
return 1; | |
} | |
return 0; | |
} | |
/* Returns 1 on last character. If it returns 1, the | |
character in ch is not valid. If flag is set to 1, then | |
this is the last character the device will request. | |
*/ | |
int chan_read_char(int chan, uint8 *ch, int flags) { | |
uint8 c; | |
int gm; | |
if (status[chan] & EOR) | |
return 1; | |
if (D[chan] & DEV_INHTRF) { | |
status[chan] |= EOR; | |
return 1; | |
} | |
if (CC[chan] == 0) { | |
uint16 addr = (uint16)(D[chan] & CORE); | |
if (chan_advance(chan)) | |
return 1; | |
sim_debug(DEBUG_DATA, &chan_dev, "read(%d, %05o, %016llo)\n", chan, | |
addr, W[chan]); | |
} | |
if (D[chan] & DEV_BACK) | |
c = 077 & (W[chan] >> ((CC[chan]) * 6)); | |
else | |
c = 077 & (W[chan] >> ((7 - CC[chan]) * 6)); | |
gm = (c == 037); | |
CC[chan]++; | |
if (CC[chan] == 8) { | |
CC[chan] = 0; | |
} | |
if ((D[chan] & DEV_BIN) == 0) { | |
/* Translate BCD to BCL */ | |
uint8 cx = c & 060; | |
c &= 017; | |
switch(c) { | |
case 0: | |
if (cx != 060) | |
c = 0xa; | |
break; | |
case 1: | |
case 2: | |
case 3: | |
case 4: | |
case 5: | |
case 6: | |
case 7: | |
case 8: | |
case 9: | |
case 0xd: | |
case 0xe: | |
case 0xf: | |
break; | |
case 0xa: | |
c = 0xb; | |
break; | |
case 0xb: | |
c = 0xc; | |
break; | |
case 0xc: | |
if (cx == 060) | |
c = 0xa; | |
else | |
c = 0; | |
break; | |
} | |
c |= cx ^ ((cx & 020)<<1); | |
} | |
*ch = c; | |
if ((status[chan] & USEGM) != 0 && (D[chan] & DEV_WCFLG) == 0 && gm) { | |
status[chan] |= EOR; | |
return 1; | |
} | |
if (flags) { | |
status[chan] |= EOR; | |
} | |
return 0; | |
} | |
/* Same as chan_read_char, however we do not check word count | |
nor do we advance it. | |
*/ | |
int chan_read_disk(int chan, uint8 *ch, int flags) { | |
uint8 c; | |
if (CC[chan] == 0) { | |
uint16 addr = (uint16)(D[chan] & CORE); | |
if (addr > MEMSIZE) { | |
D[chan] |= DEV_MEMERR; | |
return 1; | |
} | |
W[chan] = M[addr]; | |
D[chan] &= ~CORE; | |
D[chan] |= (addr + 1) & CORE; | |
} | |
c = 077 & (W[chan] >> ((7 - CC[chan]) * 6)); | |
CC[chan]++; | |
*ch = c; | |
if (CC[chan] == 8) { | |
CC[chan] = 0; | |
return 1; | |
} | |
return 0; | |
} | |
int | |
chan_advance_drum(int chan) { | |
uint16 addr = (uint16)(D[chan] & CORE); | |
uint16 wc = WC(D[chan]); | |
if (wc == 0) { | |
status[chan] |= EOR; | |
return 1; | |
} | |
D[chan] &= ~DEV_WC; | |
D[chan] |= toWC(wc-1); | |
if (addr > MEMSIZE) { | |
D[chan] |= DEV_MEMERR; | |
status[chan] |= EOR; | |
return 1; | |
} | |
W[chan] = M[addr]; | |
D[chan] &= ~CORE; | |
D[chan] |= (addr + 1) & CORE; | |
CC[chan] = 0; | |
return 0; | |
} | |
/* returns 1 when channel can take no more characters. | |
A return of 1 indicates that the character was not | |
processed. | |
*/ | |
int chan_write_drum(int chan, uint8 *ch, int flags) { | |
uint8 c; | |
if (status[chan] & EOR) | |
return 1; | |
/* Check if first word */ | |
if (CC[chan] == 0) { | |
uint16 wc = WC(D[chan]); | |
if (wc == 0) { | |
status[chan] |= EOR; | |
return 1; | |
} | |
W[chan] = 0; | |
} | |
c = *ch; | |
c &= 077; | |
W[chan] |= (t_uint64)c << ((7 - CC[chan]) * 6); | |
CC[chan]++; | |
if (CC[chan] == 8) { | |
uint16 addr = (uint16)(D[chan] & CORE); | |
M[addr] = W[chan]; | |
if (chan_advance_drum(chan)) | |
return 1; | |
} | |
if (flags) { | |
/* Flush last word */ | |
if (CC[chan] != 0) { | |
uint16 addr = (uint16)(D[chan] & CORE); | |
M[addr] = W[chan]; | |
(void)chan_advance_drum(chan); | |
} | |
status[chan] |= EOR; | |
return 1; | |
} | |
return 0; | |
} | |
/* Returns 1 on last character. If it returns 1, the | |
character in ch is not valid. If flag is set to 1, then | |
this is the last character the device will request. | |
*/ | |
int chan_read_drum(int chan, uint8 *ch, int flags) { | |
uint8 c; | |
if (status[chan] & EOR) | |
return 1; | |
if (CC[chan] == 0) { | |
if (chan_advance_drum(chan)) | |
return 1; | |
} | |
c = 077 & (W[chan] >> ((7 - CC[chan]) * 6)); | |
CC[chan]++; | |
if (CC[chan] == 8) { | |
CC[chan] = 0; | |
} | |
*ch = c; | |
if (flags) { | |
status[chan] |= EOR; | |
} | |
return 0; | |
} | |