| #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; | |
| } | |
| } |