#include "ibm1130_defs.h" | |
/* ibm1130_cr.c: IBM 1130 1442 Card Reader simulator | |
Copyright (c) 2002, Brian Knittel | |
Based on PDP-11 simulator written by Robert M Supnik | |
NOTE - there is a problem with this code. The Device Status Word (DSW) is | |
computed from current conditions when requested by an XIO load status | |
command; the value of DSW available to the simulator's examine & save | |
commands may NOT be accurate. This should probably be fixed. | |
* Update 2002-02-29: Added deck-list option. If you issue an attach | |
command and specify the filename as "@filename", the named file is interpreted | |
as a list of filenames to be read in sequence; the effect is that the reader | |
sees the concatenation of all of the files named. "reset" rewinds the deck | |
list. Filenames can be followed by whitespace and the letter "a" or "b", | |
which indicates "ascii to 029" or "binary", respectively. Example: | |
attach cr @decklist | |
where file "decklist" contains: | |
file01 a | |
file02 b | |
file03 b | |
file04 b | |
If "a" or "b" is not specified, the device mode setting is used. | |
('a' means 029, so, if you need 026 coding, specify the | |
device default as the correct 026 code and omit the 'a' on the text files lines). | |
* note: I'm not sure but I think we'll need a way to simulate the 'start' | |
button. What may end up being necessary is to fake this in the 'attach' | |
command. In a GUI build we may want to wait until they press a button. | |
Have to research: does DMS issue a read request which is only | |
satisfied when START is pressed, or does START cause an interrupt that | |
then asks DMS to issue a read request. I think it's the former but need | |
to check. After all the status register says "empty" and "not ready" | |
when the hopper is empty. So what gives? On the 360 I think the start | |
button causes issues some sort of attention request. | |
* Card image format. | |
Card files can be ascii text or binary. There are several ASCII modes: | |
CODE_029, CODE_26F, etc, corresponding to different code sets. | |
Punch and reader modes can be set independently. | |
The 1442 card read/punch has several cycles: | |
feed cycle: moves card from hopper to read station | |
card from read station to punch station | |
card from punch station to stacker | |
read or punch: operates on card at read or punch station (but not both). | |
The simulator requires input cards to be read from the file attached | |
to the card reader unit. A feed cycle reads one line (text mode) or | |
160 bytes (binary mode) from the input file to the read station buffer, | |
copies the read station buffer to the punch station buffer, and if | |
the punch unit is attached to a file, writes the punch station buffer to | |
the output file. | |
The read and punch cycles operate on the appropriate card buffer. | |
Detaching the card punch flushes the punch station buffer if necessary. | |
As does the 1442, a read or punch cycle w/o a feed cycle causes a | |
feed cycle first. | |
A feed cycle on an empty deck (reader unattaced or at EOF) clears | |
the appropriate buffer, so you can punch w/o attaching a deck to | |
the card reader. | |
// -- this may need changing depending on how things work in hardware. TBD. | |
|| A read cycle on an empty deck causes an error. | |
|| Hmmm -- what takes the place of the Start button on | |
\\ the card reader? | |
Binary format is stored using fwrite of short ints, in this format: | |
1 1 | |
2 2 0 1 2 3 4 5 6 7 8 9 | |
* * * * * * * * * * * * 0 0 0 0 | |
MSB LSB | |
byte 0 [ 6] [ 7] [ 8] [ 9] 0 0 0 0 | |
byte 1 [12] [11] [ 0] [ 1] [ 2] [ 3] [ 4] [ 5] | |
This means we can read words (little endian) and get this in memory: | |
12 11 0 1 2 3 4 5 6 7 8 9 - - - - | |
which is what the 1130 sees. | |
ASCII can be read in blocks of 80 characters but can be terminated by newline prematurely. | |
Booting: card reader IPL loads 80 columns (1 card) into memory starting | |
at location 0 in a split fashion: | |
________________ _ _ _ | |
/ | |
12 | | |
11 | | |
0 | | |
1 | | |
2 | | |
3 | Punched card | |
4 | | |
5 | | |
6 | | |
7 | | |
8 | | |
9 | | |
+------------------ - - - | |
12 11 0 1 2 3 4 5 6 7 8 9 | |
| | | | | 0 0 0 / \ | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | |
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15| | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | |
| OPCODE | F| Tag | DISPLACEMENT | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | |
The zeros mean that all IPL instructions are short form, | |
nonindexed. The 3 column is repeated in bits 8 and 9 so | |
it's a sign bit. | |
Boot command on a binary deck does this. Boot on an unattached | |
reader loads the standard boot2 card image. Boot with an ASCII | |
deck will not be very helpful. | |
*/ | |
#define READ_DELAY 100 | |
#define PUNCH_DELAY 300 | |
#define FEED_DELAY 500 | |
// #define IS_ONLINE(u) (((u)->flags & (UNIT_ATT|UNIT_DIS)) == UNIT_ATT) | |
static t_stat cr_svc (UNIT *uptr); | |
static t_stat cr_reset (DEVICE *dptr); | |
static t_stat cr_set_code (UNIT *uptr, int32 match); | |
static t_stat cr_attach (UNIT *uptr, char *cptr); | |
static t_stat cr_detach (UNIT *uptr); | |
static t_stat cp_reset (DEVICE *dptr); | |
static t_stat cp_set_code (UNIT *uptr, int32 match); | |
static t_stat cp_detach (UNIT *uptr); | |
static int16 cr_dsw = 0; /* device status word */ | |
static int32 cr_wait = READ_DELAY; /* read per-column wait */ | |
static int32 cf_wait = PUNCH_DELAY; /* punch per-column wait */ | |
static int32 cp_wait = FEED_DELAY; /* feed op wait */ | |
#define UNIT_V_OPERATION (UNIT_V_UF + 0) /* operation in progress */ | |
#define UNIT_V_CODE (UNIT_V_UF + 2) | |
#define UNIT_V_EMPTY (UNIT_V_UF + 4) | |
#define UNIT_V_LASTPUNCH (UNIT_V_UF + 0) /* bit in unit_cp flags */ | |
#define UNIT_OP (3u << UNIT_V_OPERATION) /* two bits */ | |
#define UNIT_CODE (3u << UNIT_V_CODE) /* two bits */ | |
#define UNIT_EMPTY (1u << UNIT_V_EMPTY) | |
#define UNIT_LASTPUNCH (1u << UNIT_V_LASTPUNCH) | |
#define OP_IDLE (0u << UNIT_V_OPERATION) | |
#define OP_READING (1u << UNIT_V_OPERATION) | |
#define OP_PUNCHING (2u << UNIT_V_OPERATION) | |
#define OP_FEEDING (3u << UNIT_V_OPERATION) | |
#define SET_OP(op) {cr_unit.flags &= ~UNIT_OP; cr_unit.flags |= op;} | |
#define CODE_029 (0u << UNIT_V_CODE) | |
#define CODE_026F (1u << UNIT_V_CODE) | |
#define CODE_026C (2u << UNIT_V_CODE) | |
#define CODE_BINARY (3u << UNIT_V_CODE) | |
#define SET_CODE(un,cd) {un.flags &= ~UNIT_CODE; un.flags |= cd;} | |
#define COLUMN u4 /* column field in unit record */ | |
UNIT cr_unit = { UDATA (&cr_svc, UNIT_ATTABLE, 0) }; | |
UNIT cp_unit = { UDATA (NULL, UNIT_ATTABLE, 0) }; | |
MTAB cr_mod[] = { | |
{ UNIT_CODE, CODE_029, "029", "029", &cr_set_code}, | |
{ UNIT_CODE, CODE_026F, "026F", "026F", &cr_set_code}, | |
{ UNIT_CODE, CODE_026C, "026C", "026C", &cr_set_code}, | |
{ UNIT_CODE, CODE_BINARY, "BINARY", "BINARY", &cr_set_code}, | |
{ 0 } }; | |
MTAB cp_mod[] = { | |
{ UNIT_CODE, CODE_029, "029", "029", &cp_set_code}, | |
{ UNIT_CODE, CODE_026F, "026F", "026F", &cp_set_code}, | |
{ UNIT_CODE, CODE_026C, "026C", "026C", &cp_set_code}, | |
{ UNIT_CODE, CODE_BINARY, "BINARY", "BINARY", &cp_set_code}, | |
{ 0 } }; | |
REG cr_reg[] = { | |
{ HRDATA (CRDSW, cr_dsw, 16) }, /* device status word */ | |
{ DRDATA (CRTIME, cr_wait, 24), PV_LEFT }, /* operation wait */ | |
{ DRDATA (CFTIME, cf_wait, 24), PV_LEFT }, /* operation wait */ | |
{ NULL } }; | |
REG cp_reg[] = { | |
{ DRDATA (CPTIME, cp_wait, 24), PV_LEFT }, /* operation wait */ | |
{ NULL } }; | |
DEVICE cr_dev = { | |
"CR", &cr_unit, cr_reg, cr_mod, | |
1, 16, 16, 1, 16, 16, | |
NULL, NULL, cr_reset, | |
cr_boot, cr_attach, cr_detach}; | |
DEVICE cp_dev = { | |
"CP", &cp_unit, cp_reg, cp_mod, | |
1, 16, 16, 1, 16, 16, | |
NULL, NULL, cp_reset, | |
NULL, NULL, cp_detach}; | |
#define CR_DSW_READ_RESPONSE 0x8000 /* device status word bits */ | |
#define CR_DSW_PUNCH_RESPONSE 0x4000 | |
#define CR_DSW_ERROR_CHECK 0x2000 | |
#define CR_DSW_LAST_CARD 0x1000 | |
#define CR_DSW_OP_COMPLETE 0x0800 | |
#define CR_DSW_FEED_CHECK 0x0100 | |
#define CR_DSW_BUSY 0x0002 | |
#define CR_DSW_NOT_READY 0x0001 | |
typedef struct { | |
int hollerith; | |
char ascii; | |
} CPCODE; | |
static CPCODE cardcode_029[] = | |
{ | |
0x0000, ' ', | |
0x8000, '&', // + in 026 Fortran | |
0x4000, '-', | |
0x2000, '0', | |
0x1000, '1', | |
0x0800, '2', | |
0x0400, '3', | |
0x0200, '4', | |
0x0100, '5', | |
0x0080, '6', | |
0x0040, '7', | |
0x0020, '8', | |
0x0010, '9', | |
0x9000, 'A', | |
0x8800, 'B', | |
0x8400, 'C', | |
0x8200, 'D', | |
0x8100, 'E', | |
0x8080, 'F', | |
0x8040, 'G', | |
0x8020, 'H', | |
0x8010, 'I', | |
0x5000, 'J', | |
0x4800, 'K', | |
0x4400, 'L', | |
0x4200, 'M', | |
0x4100, 'N', | |
0x4080, 'O', | |
0x4040, 'P', | |
0x4020, 'Q', | |
0x4010, 'R', | |
0x3000, '/', | |
0x2800, 'S', | |
0x2400, 'T', | |
0x2200, 'U', | |
0x2100, 'V', | |
0x2080, 'W', | |
0x2040, 'X', | |
0x2020, 'Y', | |
0x2010, 'Z', | |
0x0820, ':', | |
0x0420, '#', // = in 026 Fortran | |
0x0220, '@', // ' in 026 Fortran | |
0x0120, '\'', | |
0x00A0, '=', | |
0x0060, '"', | |
0x8820, 'c', // cent | |
0x8420, '.', | |
0x8220, '<', // ) in 026 Fortran | |
0x8120, '(', | |
0x80A0, '+', | |
0x8060, '|', | |
0x4820, '!', | |
0x4420, '$', | |
0x4220, '*', | |
0x4120, ')', | |
0x40A0, ';', | |
0x4060, 'n', // not | |
0x2820, 'x', // what? | |
0x2420, ',', | |
0x2220, '%', // ( in 026 Fortran | |
0x2120, '_', | |
0x20A0, '>', | |
0x2060, '>', | |
}; | |
static CPCODE cardcode_026F[] = // 026 fortran | |
{ | |
0x0000, ' ', | |
0x8000, '+', | |
0x4000, '-', | |
0x2000, '0', | |
0x1000, '1', | |
0x0800, '2', | |
0x0400, '3', | |
0x0200, '4', | |
0x0100, '5', | |
0x0080, '6', | |
0x0040, '7', | |
0x0020, '8', | |
0x0010, '9', | |
0x9000, 'A', | |
0x8800, 'B', | |
0x8400, 'C', | |
0x8200, 'D', | |
0x8100, 'E', | |
0x8080, 'F', | |
0x8040, 'G', | |
0x8020, 'H', | |
0x8010, 'I', | |
0x5000, 'J', | |
0x4800, 'K', | |
0x4400, 'L', | |
0x4200, 'M', | |
0x4100, 'N', | |
0x4080, 'O', | |
0x4040, 'P', | |
0x4020, 'Q', | |
0x4010, 'R', | |
0x3000, '/', | |
0x2800, 'S', | |
0x2400, 'T', | |
0x2200, 'U', | |
0x2100, 'V', | |
0x2080, 'W', | |
0x2040, 'X', | |
0x2020, 'Y', | |
0x2010, 'Z', | |
0x0420, '=', | |
0x0220, '\'', // ' in 026 Fortran | |
0x8420, '.', | |
0x8220, ')', | |
0x4420, '$', | |
0x4220, '*', | |
0x2420, ',', | |
0x2220, '(', | |
}; | |
static CPCODE cardcode_026C[] = // 026 commercial | |
{ | |
0x0000, ' ', | |
0x8000, '+', | |
0x4000, '-', | |
0x2000, '0', | |
0x1000, '1', | |
0x0800, '2', | |
0x0400, '3', | |
0x0200, '4', | |
0x0100, '5', | |
0x0080, '6', | |
0x0040, '7', | |
0x0020, '8', | |
0x0010, '9', | |
0x9000, 'A', | |
0x8800, 'B', | |
0x8400, 'C', | |
0x8200, 'D', | |
0x8100, 'E', | |
0x8080, 'F', | |
0x8040, 'G', | |
0x8020, 'H', | |
0x8010, 'I', | |
0x5000, 'J', | |
0x4800, 'K', | |
0x4400, 'L', | |
0x4200, 'M', | |
0x4100, 'N', | |
0x4080, 'O', | |
0x4040, 'P', | |
0x4020, 'Q', | |
0x4010, 'R', | |
0x3000, '/', | |
0x2800, 'S', | |
0x2400, 'T', | |
0x2200, 'U', | |
0x2100, 'V', | |
0x2080, 'W', | |
0x2040, 'X', | |
0x2020, 'Y', | |
0x2010, 'Z', | |
0x0420, '=', | |
0x0220, '\'', // ' in 026 Fortran | |
0x8420, '.', | |
0x8220, ')', | |
0x4420, '$', | |
0x4220, '*', | |
0x2420, ',', | |
0x2220, '(', | |
}; | |
static int16 ascii_to_card[256]; | |
CPCODE *cardcode; | |
int ncardcode; | |
int32 active_cr_code; /* the code most recently specified */ | |
FILE *deckfile = NULL; | |
static int16 punchstation[80]; | |
static int16 readstation[80]; | |
static enum {STATION_EMPTY, STATION_LOADED, STATION_READ, STATION_PUNCHED} punchstate = STATION_EMPTY, readstate = STATION_EMPTY; | |
static t_bool nextdeck (void); | |
static void checkdeck (void); | |
/* lookup_codetable - use code flag setting to get code table pointer and length */ | |
static t_bool lookup_codetable (int32 match, CPCODE **pcode, int *pncode) | |
{ | |
switch (match) { | |
case CODE_029: | |
*pcode = cardcode_029; | |
*pncode = sizeof(cardcode_029) / sizeof(CPCODE); | |
break; | |
case CODE_026F: | |
*pcode = cardcode_026F; | |
*pncode = sizeof(cardcode_026F) / sizeof(CPCODE); | |
break; | |
case CODE_026C: | |
*pcode = cardcode_026C; | |
*pncode = sizeof(cardcode_026C) / sizeof(CPCODE); | |
break; | |
case CODE_BINARY: | |
*pcode = NULL; | |
*pncode = 0; | |
break; | |
default: | |
printf("Eek! Undefined code table index"); | |
return FALSE; | |
} | |
return TRUE; | |
} | |
t_stat set_active_cr_code (int match) | |
{ | |
CPCODE *code; | |
int i, ncode; | |
active_cr_code = match; | |
if (! lookup_codetable(match, &code, &ncode)) | |
return SCPE_ARG; | |
memset(ascii_to_card, 0, sizeof(ascii_to_card)); | |
for (i = 0; i < ncode; i++) // set ascii to card code table | |
ascii_to_card[code[i].ascii] = (int16) code[i].hollerith; | |
return SCPE_OK; | |
} | |
t_stat cr_set_code (UNIT *uptr, int32 match) | |
{ | |
return set_active_cr_code(match); | |
} | |
t_stat cp_set_code (UNIT *uptr, int32 match) | |
{ | |
CPCODE *code; | |
int ncode; | |
if (! lookup_codetable(match, &code, &ncode)) | |
return SCPE_ARG; | |
cardcode = code; // save code table for punch output | |
ncardcode = ncode; | |
return SCPE_OK; | |
} | |
t_stat load_cr_boot (int drvno) | |
{ | |
/* this is from the "boot2" cold start card. Columns have been */ | |
/* expanded already from 12 to 16 bits. */ | |
static unsigned short boot2_data[] = { | |
0xc80a, 0x18c2, 0xd008, 0xc019, 0x8007, 0xd017, 0xc033, 0x100a, | |
0xd031, 0x7015, 0x000c, 0xe800, 0x0020, 0x08f8, 0x4828, 0x7035, | |
0x70fa, 0x4814, 0xf026, 0x2000, 0x8800, 0x9000, 0x9800, 0xa000, | |
0xb000, 0xb800, 0xb810, 0xb820, 0xb830, 0xb820, 0x3000, 0x08ea, | |
0xc0eb, 0x4828, 0x70fb, 0x9027, 0x4830, 0x70f8, 0x8001, 0xd000, | |
0xc0f4, 0xd0d9, 0xc01d, 0x1804, 0xe8d6, 0xd0d9, 0xc8e3, 0x18d3, | |
0xd017, 0x18c4, 0xd0d8, 0x9016, 0xd815, 0x90db, 0xe8cc, 0xd0ef, | |
0xc016, 0x1807, 0x0035, 0x00d0, 0xc008, 0x1803, 0xe8c4, 0xd00f, | |
0x080d, 0x08c4, 0x1003, 0x4810, 0x70d9, 0x3000, 0x08df, 0x3000, | |
0x7010, 0x00d1, 0x0028, 0x000a, 0x70f3, 0x0000, 0x00d0, 0xa0c0 | |
}; | |
int i; | |
if (drvno >= 0) /* if specified, set toggle switches to disk drive no */ | |
CES = drvno; /* so BOOT DSK1 will work correctly */ | |
IAR = 0; /* clear IAR */ | |
for (i = 0; i < 80; i++) /* copy memory */ | |
WriteW(i, boot2_data[i]); | |
#ifdef GUI_SUPPORT | |
remark_cmd("Loaded BOOT2 cold start card\n"); | |
#endif | |
return SCPE_OK; | |
} | |
t_stat cr_boot (int unitno) | |
{ | |
t_stat rval; | |
short buf[80]; | |
int i; | |
if ((rval = reset_all(0)) != SCPE_OK) | |
return rval; | |
if (! (cr_unit.flags & UNIT_ATT)) // no deck; load standard boot anyway | |
return load_cr_boot(-1); | |
if ((active_cr_code & UNIT_CODE) != CODE_BINARY) { | |
printf("Can only boot from card reader when set to BINARY mode"); | |
return SCPE_IOERR; | |
} | |
if (fread(buf, sizeof(short), 80, cr_unit.fileref) != 80) | |
return SCPE_IOERR; | |
IAR = 0; /* Program Load sets IAR = 0 */ | |
for (i = 0; i < 80; i++) /* shift 12 bits into 16 */ | |
WriteW(i, (buf[i] & 0xF800) | ((buf[i] & 0x0400) ? 0x00C0 : 0x0000) | ((buf[i] & 0x03F0) >> 4)); | |
return SCPE_OK; | |
} | |
char card_to_ascii (int16 hol) | |
{ | |
int i; | |
for (i = 0; i < ncardcode; i++) | |
if (cardcode[i].hollerith == hol) | |
return cardcode[i].ascii; | |
return ' '; | |
} | |
/* feedcycle - move cards to next station */ | |
static void feedcycle (t_bool load, t_bool punching) | |
{ | |
char buf[84], *x; | |
int i, nread, nwrite, ch; | |
/* write punched card if punch is attached to a file */ | |
if (cp_unit.flags & UNIT_ATT) { | |
if (punchstate != STATION_EMPTY) { | |
if ((cp_unit.flags & UNIT_CODE) == CODE_BINARY) { | |
fwrite(punchstation, sizeof(short), 80, cp_unit.fileref); | |
} | |
else { | |
for (i = 80; --i >= 0; ) { /* find last nonblank column */ | |
if (buf[i] != 0) | |
break; | |
} | |
/* i is now index of last character to output or -1 if all blank */ | |
for (nwrite = 0; nwrite <= i; nwrite++) { /* convert characters */ | |
buf[nwrite] = card_to_ascii(punchstation[nwrite]); | |
} | |
/* nwrite is now number of characters to output */ | |
buf[nwrite++] = '\n'; /* append newline */ | |
fwrite(buf, sizeof(char), nwrite, cp_unit.fileref); | |
} | |
} | |
} | |
if (! load) // all we wanted to do was flush the punch | |
return; | |
/* slide cards from reader to punch. If we know we're punching, | |
* generate a blank card in any case. Otherwise, it should take two feed | |
* cycles to get a read card from the hopper to punch station */ | |
if (readstate == STATION_EMPTY) { | |
if (punching) { | |
memset(punchstation, 0, sizeof(punchstation)); | |
punchstate = STATION_LOADED; | |
} | |
else | |
punchstate = STATION_EMPTY; | |
} | |
else { | |
memcpy(punchstation, readstation, sizeof(punchstation)); | |
punchstate = STATION_LOADED; | |
} | |
/* load card into read station */ | |
again: /* jump here if we've loaded a new deck after emptying the previous one */ | |
if (cr_unit.flags & UNIT_ATT) { | |
memset(readstation, 0, sizeof(readstation)); /* blank out the card image */ | |
if (cr_unit.fileref == NULL) { | |
nread = 0; | |
} | |
else if ((active_cr_code & UNIT_CODE) == CODE_BINARY) { /* binary read is straightforward */ | |
nread = fread(readstation, sizeof(short), 80, cr_unit.fileref); | |
} | |
else { /* text read is harder: */ | |
if (fgets(buf, 81, cr_unit.fileref) == NULL) /* read up to 80 chars */ | |
nread = 0; /* hmm, end of file */ | |
else { /* check for newline */ | |
if ((x = strchr(buf, '\r')) == NULL) | |
x = strchr(buf, '\n'); | |
if (x == NULL) { /* there were no delimiters, check for newline after the 80 chars, eat if present */ | |
ch = getc(cr_unit.fileref); | |
if (ch != '\r' && ch != '\n' && ch != EOF) | |
ungetc(ch, cr_unit.fileref); | |
nread = 80; | |
} | |
else { | |
*x = ' '; /* replace with blank */ | |
nread = x-buf+1; | |
} | |
} | |
upcase(buf); /* force uppercase */ | |
for (i = 0; i < nread; i++) /* convert ascii to punch code */ | |
readstation[i] = ascii_to_card[buf[i]]; | |
} | |
if (nread <= 0) { /* set hopper flag accordingly */ | |
if (deckfile != NULL && nextdeck()) | |
goto again; | |
SETBIT(cr_unit.flags, UNIT_EMPTY); | |
readstate = STATION_EMPTY; | |
} | |
else { | |
CLRBIT(cr_unit.flags, UNIT_EMPTY); | |
readstate = STATION_LOADED; | |
} | |
} | |
else | |
readstate = STATION_EMPTY; | |
cr_unit.COLUMN = -1; /* neither device is currently cycling */ | |
cp_unit.COLUMN = -1; | |
} | |
#ifdef NO_USE_FOR_THIS_CURRENTLY | |
/* this routine should probably be hooked up to the GUI somehow */ | |
/* NPRO - nonprocess runout, flushes out the reader/punch */ | |
static void npro (void) | |
{ | |
if (cr_unit.flags & UNIT_ATT) | |
fseek(cr_unit.fileref, 0, SEEK_END); /* push reader to EOF */ | |
if (deckfile != NULL) | |
fseek(deckfile, 0, SEEK_END); /* skip to end of deck list */ | |
if (punchstate == STATION_PUNCHED) | |
feedcycle(FALSE, FALSE); /* flush out card just punched */ | |
readstate = punchstate = STATION_EMPTY; | |
cr_unit.COLUMN = -1; /* neither device is currently cycling */ | |
cp_unit.COLUMN = -1; | |
SETBIT(cr_unit.flags, UNIT_EMPTY); /* set hopper empty */ | |
} | |
#endif | |
/* skipbl - skip leading whitespace in a string */ | |
static char * skipbl (char *str) | |
{ | |
while (*str && *str <= ' ') | |
str++; | |
return str; | |
} | |
/* alltrim - remove all leading and trailing whitespace from a string */ | |
static char * alltrim (char *str) | |
{ | |
char *s, *lastnb; | |
if ((s = skipbl(str)) != str) /* slide down over leading whitespace */ | |
strcpy(str, s); | |
for (lastnb = str-1, s = str; *s; s++) /* point to last nonblank characteter in string */ | |
if (*s > ' ') | |
lastnb = s; | |
lastnb[1] = '\0'; /* clip just after it */ | |
return str; | |
} | |
/* checkdeck - set hopper empty status based on condition of current reader file */ | |
static void checkdeck (void) | |
{ | |
t_bool empty; | |
if (cr_unit.fileref == NULL) { /* there is no open file */ | |
empty = TRUE; | |
} | |
else { | |
fseek(cr_unit.fileref, 0, SEEK_END); | |
empty = ftell(cr_unit.fileref) <= 0; /* see if file has anything) */ | |
fseek(cr_unit.fileref, 0, SEEK_SET); /* rewind deck */ | |
} | |
if (empty) { | |
SETBIT(cr_unit.flags, UNIT_EMPTY); | |
if (cr_unit.fileref != NULL) /* real file but it's empty, hmmm, try another */ | |
nextdeck(); | |
} | |
else | |
CLRBIT(cr_unit.flags, UNIT_EMPTY); | |
} | |
/* nextdeck - attempt to load a new file from the deck list into the hopper */ | |
static t_bool nextdeck (void) | |
{ | |
char buf[200], *e; | |
int code; | |
if (deckfile == NULL) /* we can't help */ | |
return FALSE; | |
code = cr_unit.flags & UNIT_CODE; /* default code */ | |
if (cr_unit.fileref != NULL) { | |
fclose(cr_unit.fileref); | |
cr_unit.fileref = NULL; | |
} | |
for (;;) { /* get a filename */ | |
if (fgets(buf, sizeof(buf), deckfile) == NULL) | |
break; /* oops, no more names */ | |
alltrim(buf); | |
if (! *buf) | |
continue; /* empty line */ | |
e = buf + strlen(buf) - 1; /* last character in name */ | |
if (e > (buf+1) && e[-1] <= ' ') { /* if there is at least a name + blank + character, and 2nd to last is blank */ | |
if (*e == 'b' || *e == 'B') { | |
code = CODE_BINARY; | |
e[-1] = '\0'; /* clip at the space and re-trim */ | |
alltrim(buf); | |
} | |
else if (*e == 'a' || *e == 'A') { | |
code = CODE_029; | |
e[-1] = '\0'; | |
alltrim(buf); | |
} | |
} | |
if ((cr_unit.fileref = fopen(buf, "rb")) == NULL) | |
printf("File '%s' specified in deck file '%s' cannot be opened\n", buf, cr_unit.filename+1); | |
else | |
break; | |
} | |
checkdeck(); | |
set_active_cr_code(code); /* set specified code */ | |
return (cr_unit.flags & UNIT_EMPTY) == 0; /* return TRUE if a deck has been loaded */ | |
} | |
static t_stat cr_reset (DEVICE *dptr) | |
{ | |
cr_set_code(&cr_unit, active_cr_code & UNIT_CODE); /* reset to specified code table */ | |
readstate = STATION_EMPTY; | |
cr_dsw = 0; | |
sim_cancel(&cr_unit); /* cancel any pending ops */ | |
calc_ints(); | |
SET_OP(OP_IDLE); | |
SETBIT(cr_unit.flags, UNIT_EMPTY); /* assume hopper empty */ | |
if (cr_unit.flags & UNIT_ATT) { | |
// if (deckfile != NULL) { | |
// fseek(deckfile, 0, SEEK_SET); | |
// nextdeck(); | |
// } | |
// else | |
// checkdeck(); | |
if (cr_unit.fileref != NULL) | |
feedcycle(FALSE, FALSE); | |
} | |
cr_unit.COLUMN = -1; /* neither device is currently cycling */ | |
return SCPE_OK; | |
} | |
static t_stat cp_reset (DEVICE *dptr) | |
{ | |
cp_set_code(&cp_unit, cp_unit.flags & UNIT_CODE); | |
punchstate = STATION_EMPTY; | |
cp_unit.COLUMN = -1; | |
return SCPE_OK; | |
} | |
static t_stat cr_attach (UNIT *uptr, char *cptr) | |
{ | |
t_stat rval; | |
t_bool use_decklist; | |
// no - don't cancel pending read? | |
// sim_cancel(uptr); /* cancel pending operations */ | |
cr_detach(uptr); /* detach file and possibly deckfile */ | |
cptr = skipbl(cptr); /* skip any leading whitespace */ | |
use_decklist = (*cptr == '@'); /* filename starts with @: it's a deck list */ | |
if (use_decklist) | |
cptr++; | |
if ((rval = attach_unit(uptr, cptr)) != SCPE_OK) | |
return rval; | |
if (use_decklist) { /* if we skipped the '@', store the actually-specified name */ | |
strncpy(uptr->filename, cptr-1, CBUFSIZE); | |
deckfile = cr_unit.fileref; /* save the deck file stream in our local variable */ | |
cr_unit.fileref = NULL; | |
nextdeck(); | |
} | |
else | |
checkdeck(); | |
// no - don't reset the reader | |
// cr_reset(&cr_dev); /* reset the whole thing */ | |
// cp_reset(&cp_dev); | |
return SCPE_OK; | |
} | |
static t_stat cr_detach (UNIT *uptr) | |
{ | |
if (deckfile != NULL) { | |
fclose(deckfile); | |
deckfile = NULL; | |
} | |
return detach_unit(uptr); | |
} | |
static t_stat cp_detach (UNIT *uptr) | |
{ | |
if (cp_unit.flags & UNIT_ATT) | |
if (punchstate == STATION_PUNCHED) | |
feedcycle(FALSE, FALSE); /* flush out card just punched */ | |
return detach_unit(uptr); | |
} | |
static void op_done (void) | |
{ | |
SET_OP(OP_IDLE); | |
SETBIT(cr_dsw, CR_DSW_OP_COMPLETE); | |
SETBIT(ILSW[4], ILSW_4_1442_CARD); | |
calc_ints(); | |
} | |
static t_stat cr_svc (UNIT *uptr) | |
{ | |
switch (cr_unit.flags & UNIT_OP) { | |
case OP_IDLE: | |
break; | |
case OP_FEEDING: | |
op_done(); | |
break; | |
case OP_READING: | |
if (readstate == STATION_EMPTY) { /* read active but no cards? hang */ | |
sim_activate(&cr_unit, cf_wait); | |
break; | |
} | |
if (++cr_unit.COLUMN < 80) { | |
SETBIT(cr_dsw, CR_DSW_READ_RESPONSE); | |
SETBIT(ILSW[0], ILSW_0_1442_CARD); | |
calc_ints(); | |
sim_activate(&cr_unit, cr_wait); | |
} | |
else { | |
readstate = STATION_READ; | |
op_done(); | |
} | |
break; | |
case OP_PUNCHING: | |
if (punchstate == STATION_EMPTY) { /* punch active but no cards? hang */ | |
sim_activate(&cr_unit, cf_wait); | |
break; | |
} | |
if (cp_unit.flags & UNIT_LASTPUNCH) { | |
punchstate = STATION_PUNCHED; | |
op_done(); | |
} | |
else { | |
SETBIT(cr_dsw, CR_DSW_PUNCH_RESPONSE); | |
SETBIT(ILSW[0], ILSW_0_1442_CARD); | |
calc_ints(); | |
sim_activate(&cr_unit, cp_wait); | |
} | |
break; | |
} | |
return SCPE_OK; | |
} | |
void xio_1142_card (int32 addr, int32 func, int32 modify) | |
{ | |
char msg[80]; | |
int ch; | |
int16 wd; | |
t_bool lastcard; | |
switch (func) { | |
case XIO_SENSE_DEV: | |
if (cp_unit.flags & UNIT_ATT) | |
lastcard = FALSE; /* if punch file is open, assume infinite blank cards in reader */ | |
else if (readstate == STATION_EMPTY || (cr_unit.flags & UNIT_ATT) == 0) | |
lastcard = TRUE; /* if nothing to read, hopper's empty */ | |
else if ((ch = getc(cr_unit.fileref)) == EOF) | |
lastcard = TRUE; /* there is nothing left to read for a next card */ | |
else { | |
ungetc(ch, cr_unit.fileref); /* put character back; hopper's not empty */ | |
lastcard = FALSE; | |
} | |
CLRBIT(cr_dsw, CR_DSW_LAST_CARD|CR_DSW_BUSY|CR_DSW_NOT_READY); | |
if (lastcard) | |
SETBIT(cr_dsw, CR_DSW_LAST_CARD); | |
if ((cr_unit.flags & UNIT_OP) != OP_IDLE) | |
SETBIT(cr_dsw, CR_DSW_BUSY|CR_DSW_NOT_READY); | |
else if (readstate == STATION_EMPTY && punchstate == STATION_EMPTY && ! lastcard) | |
SETBIT(cr_dsw, CR_DSW_NOT_READY); | |
if (modify & 0x01) { /* reset interrupts */ | |
CLRBIT(cr_dsw, CR_DSW_READ_RESPONSE|CR_DSW_PUNCH_RESPONSE); | |
CLRBIT(ILSW[0], ILSW_0_1442_CARD); | |
} | |
if (modify & 0x02) { | |
CLRBIT(cr_dsw, CR_DSW_OP_COMPLETE); | |
CLRBIT(ILSW[4], ILSW_4_1442_CARD); | |
} | |
ACC = cr_dsw; /* return the DSW */ | |
break; | |
case XIO_READ: /* get card data into word pointed to in IOCC packet */ | |
if (cr_unit.flags & OP_READING) { | |
if (cr_unit.COLUMN < 0) { | |
xio_error("1442: Premature read!"); | |
} | |
else if (cr_unit.COLUMN < 80) { | |
WriteW(addr, readstation[cr_unit.COLUMN]); | |
} | |
else if (cr_unit.COLUMN == 80) { | |
xio_error("1442: Read past column 80!"); | |
cr_unit.COLUMN++; // don't report it again | |
} | |
} | |
else { | |
xio_error("1442: Read when not in a read cycle!"); | |
} | |
break; | |
case XIO_WRITE: | |
if (cr_unit.flags & OP_PUNCHING) { | |
if (cp_unit.COLUMN < 0) { | |
xio_error("1442: Premature write!"); | |
} | |
else if (cp_unit.flags & UNIT_LASTPUNCH) { | |
xio_error("1442: Punch past last-punch column!"); | |
cp_unit.COLUMN = 81; | |
} | |
else if (cp_unit.COLUMN < 80) { | |
wd = ReadW(addr); /* store one word to punch buffer */ | |
punchstation[cp_unit.COLUMN] = wd & 0xFFF0; | |
if (wd & 0x0008) /* mark this as last column to be punched */ | |
SETBIT(cp_unit.flags, UNIT_LASTPUNCH); | |
} | |
else if (cp_unit.COLUMN == 80) { | |
xio_error("1442: Punch past column 80!"); | |
cp_unit.COLUMN++; // don't report it again | |
} | |
} | |
else { | |
xio_error("1442: Write when not in a punch cycle!"); | |
} | |
break; | |
case XIO_CONTROL: | |
switch (modify & 7) { | |
case 1: /* start punch */ | |
if (punchstate != STATION_LOADED) | |
feedcycle(TRUE, TRUE); | |
SET_OP(OP_PUNCHING); | |
cp_unit.COLUMN = -1; | |
CLRBIT(cp_unit.flags, UNIT_LASTPUNCH); | |
sim_cancel(&cr_unit); | |
sim_activate(&cr_unit, cp_wait); | |
break; | |
case 2: /* feed cycle */ | |
feedcycle(TRUE, FALSE); | |
SET_OP(OP_FEEDING); | |
sim_cancel(&cr_unit); | |
sim_activate(&cr_unit, cf_wait); | |
break; | |
case 4: /* start read */ | |
if (readstate != STATION_LOADED) | |
feedcycle(TRUE, FALSE); | |
SET_OP(OP_READING); | |
cr_unit.COLUMN = -1; | |
sim_cancel(&cr_unit); | |
sim_activate(&cr_unit, cr_wait); | |
break; | |
case 0: | |
break; | |
default: | |
sprintf(msg, "1442: Multiple operations in XIO_CONTROL: %x", modify); | |
xio_error(msg); | |
return; | |
} | |
break; | |
default: | |
sprintf(msg, "Invalid 1442 XIO function %x", func); | |
xio_error(msg); | |
break; | |
} | |
} |