blob: 82504f7b99906fccef6dd937ed1194882f05453e [file] [log] [blame] [raw]
/* pdp11_cr.c: CR/CM/CD-11 card reader simulator
Copyright (c) 2005, John A. Dundas III
Portions derived from work by Douglas W. Jones, jones@cs.uiowa.edu
Portions derived from work by Robert M Supnik
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of the Author shall
not be used in advertising or otherwise to promote the sale, use
or other dealings in this Software without prior written
authorization from the Author.
------------------------------------------------------------------------------
cr CR11/CD11 punched and mark sense card reader for SIMH
The CR11 controller is also compatible with the CM11-F, CME11, and CMS11.
Information necessary to create this simulation was gathered from
a number of sources including:
CR11 Card Reader System Manual, DEC-11-HCRB-D
http://www.bitsavers.org/pdf/dec/unibus/DEC-11-HCRB-D_CR11_Mar72.pdf
Various editions of the Peripherals Handbook
OpenVMS VAX Card Reader, Line Printer, and LPA11-K I/O User's
Reference Manual, AA-PVXGA-TE
http://h71000.www7.hp.com/DOC/73final/documentation/pdf/OVMS_VAX_CARD_LP_REF.pdf
OpenVMS System Manager's Manual, Volume 1: Essentials
http://h71000.www7.hp.com/DOC/732FINAL/aa-pv5mh-tk/aa-pv5mh-tk.PDF
CRDRIVER.LIS - CR11 Card Reader Driver, X-9, graciously made available
by HP
Various RSTS manuals
RT-11 Software Support Manual
RT-11 System Reference Manual, DEC-11-ORUGA-C-D
Professor Douglas W. Jones's web site:
http://www.cs.uiowa.edu/~jones/cards/
Paul Mattes' x026 keypunch simulator
http://x3270.bgp.nu/x026.html
CDRSER.MAC - TOPS card reader driver source
http://pdp-10.trailing-edge.com/custsupcuspmar86_bb-x130b-sb/02/cdrser.mac
The Card Image format code and documentation is adapted from Prof.
Jones's site, with his permission. Please see his site for additional
documentation as well as the card image utilities referenced in
his documentation (cardmake, cardlist, etc.).
http://www.cs.uiowa.edu/~jones/cards/format.html
Known limitations:
1. Need a copy of the CR bootstrap (and some way to test it)
2. Need a copy of the XXDP+ test deck
3. No testing under RSX; volunteers needed
4. No testing under Ultrix or Unix for PDP-11; volunteers needed
5. No testing under Ultrix or Unix for VAX; volunteers needed
6. No PDP-10 support; volunteers needed
7. The simulator implements a single controller/reader combination
Operating System Notes
RT-11 (and CTS-300) support one CR11 or CM11, but no CD11.
VMS supports multiple CR11 controllers.
RSTS/E supports either the CR11/CM11 or CD11 but not both in
the same SIL. It appears to support only one unit.
For RSX there exists a CR/CM task handler. Is there a CD
handler?
Don't have any information about Unix or Ultrix-11 yet. Same
for VAX Unices.
TOPS: it appears that both the CD11 and CR11 were supported.
I don't have any knowledge on how to make this work with the
PDP-10 simulation, though.
Revision History:
12-May-06 JAD Modify the DEBUG code to use the SIMH DEBUG_x
macros. Modify the UNIT structure to include
the DEBUG bit.
Mark the trans[] array contents constant.
Make device data structures static and constant
as appropriate.
18-Mar-05 JAD Slight optimization for blank punches recognizing
that blank is 0 in all character encodings.
17-Mar-05 JAD Completely initialize ascii_code correctly.
Define the end of deck punch code separately from
the cardcode.i file.
Make initTranslation() set a pointer to the correct
punch code table to use. Modify card read functions
to use this table pointer.
16-Mar-05 JAD Make certain switches passed to the ATTACH command
are valid; return error on any others.
Make default unit wait time compatible with default
device specification.
Implement SET TRANSLATION=value. Still need to
modify the H2ASCII table used for text files;
currently hard-coded to 029.
24-Feb-05 JAD Allow the maintenance bits in CRM to clear as
well as set status bits. Not sure this is the
correct behavior, though, without more documentation.
Catch three more places to spin down the blower
correctly.
Zero the CDDB and CRM at INIT.
17-Feb-05 JAD When the hopper empties, a pick check should
be generated 300ms later. They are simultaneous
for now.
Make sure readColumnBinary() generates a complete
EOF card.
08-Feb-05 JAD Replace blowerWait with different times for blower
spin up and down.
06-Feb-05 JAD After DETACH: mark CD offline, set appropriate
blower state.
Make sure unit wait time is recalculated every
time cpm is set.
04-Feb-05 JAD Better tracking of blower state throughout driver.
Make sure IE gets cleared for CR at INIT.
Normalize error response in read routines.
Finish condition handling for column binary.
02-Feb-05 JAD Remove Qbus support; Unibus only.
Support ATTACH switches:
A - ASCII, B - column binary, I - Card Image
If none given, check for .TXT or .CBN; if none,
examine file for magic header.
Finer granularity to blower state. Expose this
variable to examine/deposit from SIMH.
Preliminary implementation of support for
column binary format.
24-Jan-05 JAD Make AUTOEOF work as a surrogate for the EOF
button of a CD11 reader. May need to separate
this later, though.
Partial implementation of DATAERR for CD11.
Implement the Rev. J mods (as best I understand
them) to the CD11 affecting the CDDB used as a
second status register.
23-Jan-05 JAD Preliminary clean-up of CD state transitions.
Tested with RSTS/E (V9.1-05).
22-Jan-05 JAD Finish CR state transitions; should be close now.
Tested with RSTS/E (V9.1-05), RT-11 (V5.3), and
VAX/VMS (V7.2).
19-Jan-05 JAD Add bounds to the RATE command; also default and
help a la the XQ driver.
Improved handling of empty files.
17-Jan-05 JAD Add the CR maintenance register.
16-Jan-05 JAD Add preliminary CD11 support.
Simulate the STOP and RESET switches.
14-Jan-05 JAD Add the ability to automatically generate an 'EOF'
card recognized by DEC operating systems when
reading ASCII files.
08-Jan-05 JAD Original creation and testing
*/
#if defined (VM_PDP10) /* PDP10 version */
#include "pdp10_defs.h"
extern int32 int_req;
#elif defined (VM_VAX) /* VAX version */
#include "vax_defs.h"
extern int32 int_req[IPL_HLVL];
#else /* PDP-11 version */
#include "pdp11_defs.h"
extern int32 int_req[IPL_HLVL];
#endif
extern FILE *sim_deb; /* sim_console.c */
/* create a int32 constant from four characters */
#define I4C(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
#define I4C_CBN I4C ('C','B','N',' ')
#define I4C_H80 I4C ('H','8','0',' ')
#define I4C_H82 I4C ('H','8','2',' ')
#define I4C_H40 I4C ('H','4','0',' ')
#define UNIT_V_CR11 (UNIT_V_UF + 0)
#define UNIT_CR11 (1u << UNIT_V_CR11)
#define UNIT_V_AUTOEOF (UNIT_V_UF + 1)
#define UNIT_AUTOEOF (1u << UNIT_V_AUTOEOF)
#include <assert.h>
#define ERROR (00404)
#include "pdp11_cr_dat.h"
#define PUNCH_EOD (07417)
#define PUNCH_SPACE (0) /* same for all encodings */
/* CR */
/* also use CSR_ERR, CSR_IE, and CSR_GO */
#define CRCSR_V_CRDDONE 14 /* card done */
#define CRCSR_V_SUPPLY 13 /* supply error */
#define CRCSR_V_RDCHK 12 /* reader check */
#define CRCSR_V_TIMERR 11 /* timing error */
#define CRCSR_V_ONLINE 10 /* on line */
#define CRCSR_V_BUSY 9 /* busy reading */
#define CRCSR_V_OFFLINE 8 /* off line AKA READY? */
#define CRCSR_V_COLRDY 7 /* column ready */
#define CRCSR_V_EJECT 1 /* ignore card */
#define CRCSR_CRDDONE (1u << CRCSR_V_CRDDONE)
#define CRCSR_SUPPLY (1u << CRCSR_V_SUPPLY)
#define CRCSR_RDCHK (1u << CRCSR_V_RDCHK)
#define CRCSR_TIMERR (1u << CRCSR_V_TIMERR)
#define CRCSR_ONLINE (1u << CRCSR_V_ONLINE)
#define CRCSR_BUSY (1u << CRCSR_V_BUSY)
#define CRCSR_OFFLINE (1u << CRCSR_V_OFFLINE)
#define CRCSR_COLRDY (1u << CRCSR_V_COLRDY)
#define CRCSR_EJECT (1u << CRCSR_V_EJECT)
#define CRCSR_IMP (CSR_ERR | CRCSR_CRDDONE | CRCSR_SUPPLY | \
CRCSR_RDCHK | CRCSR_TIMERR | CRCSR_ONLINE | \
CRCSR_BUSY | CRCSR_OFFLINE | CRCSR_COLRDY | \
CSR_IE | CRCSR_EJECT)
#define CRCSR_RW (CSR_IE | CRCSR_EJECT | CSR_GO) /* read/write */
#define CRM_V_MAINT 15 /* enable maint funct */
#define CRM_V_BUSY 14
#define CRM_V_READY 13
#define CRM_V_HOPPER 12
#define CRM_MAINT (1u << CRM_V_MAINT)
#define CRM_BUSY (1u << CRM_V_BUSY)
#define CRM_READY (1u << CRM_V_READY)
#define CRM_HOPPER (1u << CRM_V_HOPPER)
/* CD */
/* also use CSR_ERR, CSR_IE, and CSR_GO */
#define CDCSR_V_RDRCHK 14 /* reader check */
#define CDCSR_V_EOF 13 /* CD11-E EOF button */
#define CDCSR_V_OFFLINE 12 /* off line */
#define CDCSR_V_DATAERR 11 /* data error */
#define CDCSR_V_LATE 10 /* data late */
#define CDCSR_V_NXM 9 /* non-existent memory */
#define CDCSR_V_PWRCLR 8 /* power clear */
#define CDCSR_V_RDY 7 /* ready */
#define CDCSR_V_XBA17 5
#define CDCSR_V_XBA16 4
#define CDCSR_V_ONLINE 3 /* on line transition */
#define CDCSR_V_HOPPER 2 /* hopper check */
#define CDCSR_V_PACK 1 /* data packing */
#define CDCSR_RDRCHK (1u << CDCSR_V_RDRCHK)
#define CDCSR_EOF (1u << CDCSR_V_EOF)
#define CDCSR_OFFLINE (1u << CDCSR_V_OFFLINE)
#define CDCSR_DATAERR (1u << CDCSR_V_DATAERR)
#define CDCSR_LATE (1u << CDCSR_V_LATE)
#define CDCSR_NXM (1u << CDCSR_V_NXM)
#define CDCSR_PWRCLR (1u << CDCSR_V_PWRCLR)
#define CDCSR_RDY (1u << CDCSR_V_RDY)
#define CDCSR_XBA17 (1u << CDCSR_V_XBA17)
#define CDCSR_XBA16 (1u << CDCSR_V_XBA16)
#define CDCSR_ONLINE (1u << CDCSR_V_ONLINE)
#define CDCSR_HOPPER (1u << CDCSR_V_HOPPER)
#define CDCSR_PACK (1u << CDCSR_V_PACK)
#define CDCSR_IMP (CSR_ERR | CDCSR_RDRCHK | CDCSR_EOF | CDCSR_OFFLINE | \
CDCSR_DATAERR | CDCSR_LATE | CDCSR_NXM | \
CDCSR_PWRCLR | CDCSR_RDY | CSR_IE | \
CDCSR_XBA17 | CDCSR_XBA16 | CDCSR_ONLINE | \
CDCSR_HOPPER | CDCSR_PACK | CSR_GO)
#define CDCSR_RW (CDCSR_PWRCLR | CSR_IE | CDCSR_XBA17 | CDCSR_XBA16 | \
CDCSR_PACK | CSR_GO)
/* Blower state values */
#define BLOW_OFF (0) /* steady state off */
#define BLOW_START (1) /* starting up */
#define BLOW_ON (2) /* steady state on */
#define BLOW_STOP (3) /* shutting down */
/* Card Reader state */
static char *cardFormat = "unknown";
static t_bool (*readRtn)(FILE *, int16 *, char *, char *);
static char ascii_code[4096]; /* 2^12 possible values */
static int currCol; /* current column when reading */
static int colStart; /* starting column */
static int colEnd; /* ending column */
static int table = 3; /* character translation table */
static const int *codeTbl = o29_code; /* punch translation table */
static int32 blowerState = BLOW_OFF; /* reader vacuum/blower motor */
static int32 spinUp = 3000; /* blower spin-up time: 3 seconds */
static int32 spinDown = 2000; /* blower spin-down time: 2 seconds */
static t_bool EOFcard = FALSE; /* played special card yet? */
static int32 cpm = 285; /* reader rate: cards per minute */
/* card image in various formats */
static int16 hcard[82]; /* Hollerith format */
static char ccard[82]; /* DEC compressed format */
static char acard[82]; /* ASCII format */
/* CR/CM registers */
static int32 crs = 0; /* control/status */
static int32 crb1 = 0; /* 12-bit Hollerith characters */
static int32 crb2 = 0; /* 8-bit compressed characters */
static int32 crm = 0; /* CMS maintenance register */
/* CD registers */
static int32 cdst = 0; /* control/status */
static int32 cdcc = 0; /* column count */
static int32 cdba = 0; /* current address, low 16 bits */
static int32 cddb = 0; /* data, 2nd status */
/* forward references */
DEVICE cr_dev;
static void setupCardFile (UNIT *, int32);
t_stat cr_rd (int32 *, int32, int32);
t_stat cr_wr (int32, int32, int32);
t_stat cr_svc (UNIT *);
t_stat cr_reset (DEVICE *);
t_stat cr_attach (UNIT *, char *);
t_stat cr_detach (UNIT *);
t_stat cr_set_type (UNIT *, int32, char *, void *);
t_stat cr_show_format (FILE *, UNIT *, int32, void *);
t_stat cr_set_rate (UNIT *, int32, char *, void *);
t_stat cr_show_rate (FILE *, UNIT *, int32, void *);
t_stat cr_set_reset (UNIT *, int32, char *, void *);
t_stat cr_set_stop (UNIT *, int32, char *, void *);
t_stat cr_set_trans (UNIT *, int32, char*, void *);
t_stat cr_show_trans (FILE *, UNIT *, int32, void *);
/* CR data structures
cr_dib CR device information block
cr_unit CR unit descriptor
cr_reg CR register list
cr_mod CR modifier table
cr_dev CR device descriptor
*/
static DIB cr_dib = { IOBA_CR, IOLN_CR, &cr_rd, &cr_wr,
1, IVCL (CR), VEC_CR, { NULL } };
static UNIT cr_unit = {
UDATA (&cr_svc,
UNIT_ATTABLE+UNIT_SEQ+UNIT_ROABLE+UNIT_DISABLE+
UNIT_CR11+UNIT_AUTOEOF, 0),
(60 * 1000) / 285 };
static const REG cr_reg[] = {
{ GRDATA (BUF, cr_unit.buf, DEV_RDX, 8, 0) },
{ GRDATA (CRS, crs, DEV_RDX, 16, 0) },
{ GRDATA (CRB1, crb1, DEV_RDX, 16, 0) },
{ GRDATA (CRB2, crb2, DEV_RDX, 16, 0) },
{ GRDATA (CRM, crm, DEV_RDX, 16, 0) },
{ GRDATA (CDST, cdst, DEV_RDX, 16, 0) },
{ GRDATA (CDCC, cdcc, DEV_RDX, 16, 0) },
{ GRDATA (CDBA, cdba, DEV_RDX, 16, 0) },
{ GRDATA (CDDB, cddb, DEV_RDX, 16, 0) },
{ GRDATA (BLOWER, blowerState, DEV_RDX, 2, 0) },
{ FLDATA (INT, IREQ (CR), INT_V_CR) },
{ FLDATA (ERR, crs, CSR_V_ERR) },
{ FLDATA (IE, crs, CSR_V_IE) },
{ DRDATA (POS, cr_unit.pos, T_ADDR_W), PV_LEFT },
{ DRDATA (TIME, cr_unit.wait, 24), PV_LEFT },
{ GRDATA (DEVADDR, cr_dib.ba, DEV_RDX, 32, 0), REG_HRO },
{ GRDATA (DEVVEC, cr_dib.vec, DEV_RDX, 16, 0), REG_HRO },
{ NULL } };
static const MTAB cr_mod[] = {
{ UNIT_CR11, UNIT_CR11, "CR11", "CR11", &cr_set_type },
{ UNIT_CR11, 0, "CD11", "CD11", &cr_set_type },
{ UNIT_AUTOEOF, UNIT_AUTOEOF, "auto EOF", "AUTOEOF", NULL },
{ UNIT_AUTOEOF, 0, "no auto EOF", "NOAUTOEOF", NULL },
/* card reader RESET switch */
{ MTAB_XTD|MTAB_VDV, 0, NULL, "RESET",
&cr_set_reset, NULL, NULL },
/* card reader STOP switch */
{ MTAB_XTD|MTAB_VDV, 0, NULL, "STOP",
&cr_set_stop, NULL, NULL },
{ MTAB_XTD|MTAB_VUN, 0, "FORMAT", NULL,
NULL, &cr_show_format, NULL },
{ MTAB_XTD|MTAB_VDV, 006, "ADDRESS", "ADDRESS",
&set_addr, &show_addr, NULL },
{ MTAB_XTD|MTAB_VDV, 0, "VECTOR", "VECTOR",
&set_vec, &show_vec, NULL },
{ MTAB_XTD|MTAB_VDV, 0, "RATE", "RATE={DEFAULT|200..1200}",
&cr_set_rate, &cr_show_rate, NULL },
{ MTAB_XTD|MTAB_VDV, 0, "TRANSLATION",
"TRANSLATION={DEFAULT|026|026FTN|029|EBCDIC}",
&cr_set_trans, &cr_show_trans, NULL },
{ 0 } };
DEVICE cr_dev = {
"CR", &cr_unit, (REG *) &cr_reg, (MTAB *) &cr_mod,
1, 10, 31, 1, DEV_RDX, 8,
NULL, NULL, &cr_reset,
NULL, &cr_attach, &cr_detach,
&cr_dib, DEV_DISABLE | DEV_UBUS | DEV_DEBUG };
/* Utility routines */
/*
These functions read a "card" from a virtual deck file (passed in
fp) and fill in three arrays. The first array 'hcard' contains the
12-bit binary image of the punch in each column; the second array
'ccard' contains the 8-bit DEC encoded representation of the
corresponding column; the third array 'acard' contains the ASCII
representation (if possible) of the character. The routines return
TRUE if a card was read (possibly with errors) and FALSE if the
"hopper is empty" (EOF) or fatal file errors prevented any portion
of a card from being read.
Errors other than EOF are signaled out of band in the controller
state variables. Possible errors are data in columns 0 or 81
(signalled as read check; currently these columns are ignored), or
any file errors (signalled as motion check).
Might rethink this. Should probably treat file errors as "pick
check". Retry 3 times. After that, give up with error.
*/
static t_bool readCardImage ( FILE *fp,
int16 *hcard,
char *ccard,
char *acard )
{
int c1, c2, c3, col;
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "readCardImage pos %d\n", ftell (fp));
/* get card header bytes */
c1 = fgetc (fp);
c2 = fgetc (fp);
c3 = fgetc (fp);
cr_unit.pos = ftell (fp);
/* check for EOF */
if (c1 == EOF) {
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "hopper empty\n");
if (!EOFcard && (cr_unit.flags & UNIT_AUTOEOF)) {
EOFcard = TRUE;
for (col = 1; col <= 8; col++) {
hcard[col] = PUNCH_EOD;
ccard[col] = h2c_code[PUNCH_EOD];
acard[col] = ' ';
}
while (col <= colEnd) {
hcard[col] = PUNCH_SPACE;
ccard[col] = PUNCH_SPACE;
acard[col] = ' ';
col++;
}
return (TRUE);
}
crs |= CSR_ERR | CRCSR_RDCHK | CRCSR_SUPPLY | CRCSR_OFFLINE;
crs &= ~(CRCSR_COLRDY | CRCSR_ONLINE);
cdst |= CSR_ERR | CDCSR_RDRCHK | CDCSR_HOPPER;
if (cr_unit.flags & UNIT_AUTOEOF)
cdst |= CDCSR_EOF;
blowerState = BLOW_STOP;
return (FALSE);
}
/* check for valid header */
if ((c2 == EOF) || (c3 == EOF) || ((c1 & 0x80) == 0) ||
((c2 & 0x80) == 0) || ((c3 & 0x80) == 0)) {
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "header error\n");
/* unexpected EOF or format problems */
crs |= CSR_ERR | CRCSR_RDCHK | CRCSR_OFFLINE;
crs &= ~CRCSR_ONLINE;
cdst |= CSR_ERR | CDCSR_RDRCHK;
blowerState = BLOW_STOP;
return (FALSE);
}
assert (colStart < colEnd);
assert (colStart >= 0);
assert (colEnd <= 81);
for (col = colStart; col < colEnd; ) {
int16 i;
/* get 3 bytes */
c1 = fgetc (fp);
c2 = fgetc (fp);
c3 = fgetc (fp);
cr_unit.pos = ftell (fp);
if (ferror (fp) || feof (fp)) {
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "file error\n");
/* signal error; unexpected EOF, format problems, or file error(s) */
crs |= CSR_ERR | CRCSR_RDCHK | CRCSR_OFFLINE;
crs &= ~CRCSR_ONLINE;
cdst |= CSR_ERR | CDCSR_RDRCHK;
blowerState = BLOW_STOP;
return (FALSE);
}
/* convert to 2 columns */
i = ((c1 << 4) | ( c2 >> 4)) & 0xFFF;
hcard[col] = i;
ccard[col] = h2c_code[i];
acard[col] = ascii_code[i];
col++;
i = (((c2 & 017) << 8) | c3) & 0xFFF;
hcard[col] = i;
ccard[col] = h2c_code[i];
acard[col] = ascii_code[i];
col++;
}
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "successfully loaded card\n");
return (TRUE);
}
static t_bool readColumnBinary ( FILE *fp,
int16 *hcard,
char *ccard,
char *acard )
{
int col;
for (col = colStart; col <= colEnd; col++) {
int16 i;
i = fgetc (fp) & 077;
i |= ((fgetc (fp) & 077) << 6);
cr_unit.pos = ftell (fp);
if (feof (fp)) {
if (!EOFcard && (cr_unit.flags & UNIT_AUTOEOF)) {
EOFcard = TRUE;
for (col = 1; col <= 8; col++) {
hcard[col] = PUNCH_EOD;
ccard[col] = h2c_code[PUNCH_EOD];
acard[col] = ' ';
}
while (col <= colEnd) {
hcard[col] = PUNCH_SPACE;
ccard[col] = PUNCH_SPACE;
acard[col] = ' ';
col++;
}
return (TRUE);
}
crs |= CSR_ERR | CRCSR_RDCHK | CRCSR_SUPPLY |
CRCSR_OFFLINE;
crs &= ~(CRCSR_COLRDY | CRCSR_ONLINE);
cdst |= CSR_ERR | CDCSR_RDRCHK | CDCSR_HOPPER;
if (cr_unit.flags & UNIT_AUTOEOF)
cdst |= CDCSR_EOF;
blowerState = BLOW_STOP;
return (FALSE);
}
if (ferror (fp)) {
/* signal error */
crs |= CSR_ERR | CRCSR_RDCHK | CRCSR_OFFLINE;
crs &= ~CRCSR_ONLINE;
cdst |= CSR_ERR | CDCSR_RDRCHK;
blowerState = BLOW_STOP;
return (FALSE);
}
hcard[col] = i;
ccard[col] = h2c_code[i];
acard[col] = ascii_code[i];
}
return (TRUE);
}
/*
Should this routine perform special handling of non-printable,
(e.g., control) characters or characters that have no encoded
representation?
*/
static t_bool readCardASCII ( FILE *fp,
int16 *hcard,
char *ccard,
char *acard )
{
int c, col;
assert (colStart < colEnd);
assert (colStart >= 1);
assert (colEnd <= 80);
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "readCardASCII\n");
for (col = colStart; col <= colEnd; ) {
switch (c = fgetc (fp)) {
case EOF:
if (ferror (fp)) {
/* signal error */
crs |= CSR_ERR | CRCSR_RDCHK | CRCSR_OFFLINE;
crs &= ~CRCSR_ONLINE;
cdst |= CSR_ERR | CDCSR_RDRCHK;
blowerState = BLOW_STOP;
cr_unit.pos = ftell (fp);
return (FALSE);
}
if (col == colStart) {
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "hopper empty\n");
if (!EOFcard && (cr_unit.flags & UNIT_AUTOEOF)) {
EOFcard = TRUE;
for (col = 1; col <= 8; col++) {
hcard[col] = PUNCH_EOD;
ccard[col] = h2c_code[PUNCH_EOD];
acard[col] = ' ';
}
c = '\n';
goto fill_card;
}
crs |= CSR_ERR | CRCSR_RDCHK | CRCSR_SUPPLY | CRCSR_OFFLINE;
crs &= ~(CRCSR_COLRDY | CRCSR_ONLINE);
cdst |= CSR_ERR | CDCSR_RDRCHK | CDCSR_HOPPER;
if (cr_unit.flags & UNIT_AUTOEOF)
cdst |= CDCSR_EOF;
blowerState = BLOW_STOP;
cr_unit.pos = ftell (fp);
return (FALSE);
}
/* fall through */
case '\r':
case '\n':
fill_card:
while (col <= colEnd) {
hcard[col] = PUNCH_SPACE;
ccard[col] = PUNCH_SPACE;
acard[col] = ' ';
col++;
}
break;
case '\t':
do {
hcard[col] = PUNCH_SPACE;
ccard[col] = PUNCH_SPACE;
acard[col] = ' ';
col++;
} while (((col & 07) != 1) && (col <= colEnd));
break;
default:
hcard[col] = codeTbl[c & 0177];
/* check for unrepresentable ASCII characters */
if (hcard[col] == ERROR) {
cdst |= CDCSR_DATAERR;
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb,
"error character at column %d\n",
col);
}
ccard[col] = h2c_code[hcard[col]];
acard[col] = c;
col++;
break;
}
}
/* silently truncate/flush long lines, or flag over-length card? */
if (c != '\n') {
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "truncating card\n");
do c = fgetc (fp);
while ((c != EOF) && (c != '\n') && (c != '\r'));
}
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "successfully loaded card\n");
cr_unit.pos = ftell (fp);
return (TRUE);
}
/*
Initialize the binary translation table. Generally called when a
new deck is attached but could be set manually as well.
*/
static void initTranslation (void)
{
int32 i;
memset (ascii_code, '~', sizeof (ascii_code));
switch (table) {
case 1:
codeTbl = o26_comm_code;
for (i = ' '; i < '`'; i++)
ascii_code[o26_comm_code[i]] = i;
break;
case 2:
codeTbl = o26_ftn_code;
for (i = ' '; i < '`'; i++)
ascii_code[o26_ftn_code[i]] = i;
break;
case 3:
codeTbl = o29_code;
for (i = ' '; i < '`'; i++)
ascii_code[o29_code[i]] = i;
break;
case 4:
codeTbl = EBCDIC_code;
for (i = 0; i < 0177; i++)
ascii_code[EBCDIC_code[i]] = i;
break;
default:
/* can't happen */
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb,
"bad CR translation initialization value\n");
break;
}
}
/*
Examine the command switches, file extension, and virtual card deck
file to determine the format. Set up the global variables
appropriately. Rewind ASCII files to the beginning
*/
static void setupCardFile ( UNIT *uptr,
int32 switches )
{
int32 i;
if (switches & SWMASK ('A'))
i = 0;
else if (switches & SWMASK ('B'))
i = I4C_CBN;
else if (switches & SWMASK ('I'))
goto read_header;
else if (match_ext (uptr->filename, "TXT"))
i = 0;
else if (match_ext (uptr->filename, "CBN"))
i = I4C_CBN;
else {
read_header:
/* look for card image magic file number */
i = fgetc (uptr->fileref);
i = (i << 8) | fgetc (uptr->fileref);
i = (i << 8) | fgetc (uptr->fileref);
i = (i << 8) | ' ';
}
switch (i) {
case I4C_H80:
colStart = 1;
colEnd = 80;
cardFormat = "card image";
readRtn = readCardImage;
break;
case I4C_H82:
colStart = 0;
colEnd = 81;
cardFormat = "card image";
readRtn = readCardImage;
break;
case I4C_H40:
colStart = 1;
colEnd = 40;
cardFormat = "card image";
readRtn = readCardImage;
break;
case I4C_CBN:
colStart = 1;
colEnd = 80;
cardFormat = "column binary";
readRtn = readColumnBinary;
break;
default:
colStart = 1;
colEnd = 80;
cardFormat = "ASCII";
readRtn = readCardASCII;
fseek (uptr->fileref, 0L, SEEK_SET);
break;
}
initTranslation ();
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "colStart = %d, colEnd = %d\n",
colStart, colEnd);
cr_unit.pos = ftell (uptr->fileref);
}
/* Card reader routines
cr_rd I/O page read
cr_wr I/O page write
cr_svc process event (reader ready)
cr_reset process reset
cr_attach process attach
cr_detach process detach
*/
t_stat cr_rd ( int32 *data,
int32 PA,
int32 access )
{
switch ((PA >> 1) & 03) {
case 0: /* CSR */
if (cdst & (077000))
cdst |= CSR_ERR;
else
cdst &= ~CSR_ERR;
*data = (cr_unit.flags & UNIT_CR11) ?
crs & CRCSR_IMP : cdst & CDCSR_IMP;
/* CR: if error removed, clear 15, 14, 11, 10 */
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "cr_rd crs %06o cdst %06o\n",
crs, cdst);
break;
case 1:
*data = (cr_unit.flags & UNIT_CR11) ? crb1 : cdcc;
/* Does crb1 clear after read? Implied by VMS driver. */
crb1 = 0;
crs &= ~CRCSR_COLRDY;
if (DEBUG_PRS (cr_dev)) {
if (cr_unit.flags & UNIT_CR11)
fprintf (sim_deb, "cr_rd crb1 %06o '%c' %d\n",
crb1, cr_unit.buf, cr_unit.buf);
else
fprintf (sim_deb, "cr_rd cdcc %06o\n", cdcc);
}
break;
case 2:
*data = (cr_unit.flags & UNIT_CR11) ? crb2 : cdba;
crb2 = 0; /* see note for crb1 */
crs &= ~CRCSR_COLRDY;
if (DEBUG_PRS (cr_dev)) {
if (cr_unit.flags & UNIT_CR11)
fprintf (sim_deb, "cr_rd crb2 %06o\n", crb2);
else
fprintf (sim_deb, "\r\ncr_rd cdba %06o\n", cdba);
}
break;
case 3:
default:
if (cr_unit.flags & UNIT_CR11)
*data = crm;
else
*data = 0100000 | (cdst & CDCSR_RDRCHK) |
(cdst & CDCSR_OFFLINE) ?
cddb & 0777 : 0777;
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "cr_rd crm %06o cddb %06o data %06o\n",
crm, cddb, *data);
break;
}
return (SCPE_OK);
}
t_stat cr_wr ( int32 data,
int32 PA,
int32 access )
{
switch ((PA >> 1) & 03) {
case 0:
if (cr_unit.flags & UNIT_CR11) {
/* ignore high-byte writes */
if (PA & 1)
break;
/* fixup data for low byte write */
if (access == WRITEB)
data = (crs & ~0377) | (data & 0377);
if (!(data & CSR_IE))
CLR_INT (CR);
crs = (crs & ~CRCSR_RW) | (data & CRCSR_RW);
crs &= ~(CSR_ERR | CRCSR_CRDDONE | CRCSR_TIMERR);
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "cr_wr data %06o crs %06o\n",
data, crs);
if (data & CSR_GO) {
if (blowerState != BLOW_ON) {
sim_activate (&cr_unit, spinUp);
blowerState = BLOW_START;
} else
sim_activate (&cr_unit, cr_unit.wait);
}
} else {
if (data & CDCSR_PWRCLR) {
CLR_INT (CR);
sim_cancel (&cr_unit);
cdst &= ~(CDCSR_RDRCHK |CDCSR_OFFLINE |
CDCSR_RDY | CDCSR_HOPPER);
cdst |= CDCSR_RDY;
cdcc = 0;
cdba = 0;
break;
}
if (!(data & CSR_IE))
CLR_INT (CR);
cdst = (cdst & ~CDCSR_RW) | (data & CDCSR_RW);
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "cr_wr data %06o cdst %06o\n",
data, cdst);
if (data & CSR_GO) {
if (blowerState != BLOW_ON) {
sim_activate (&cr_unit, spinUp);
blowerState = BLOW_START;
} else
sim_activate (&cr_unit, cr_unit.wait);
}
}
break;
case 1:
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "cr_wr cdcc %06o\n", data);
if (cr_unit.flags & UNIT_CR11)
break;
cdcc = data & 0177777;
break;
case 2:
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "cr_wr crba %06o\n", data);
if (cr_unit.flags & UNIT_CR11)
break;
cdba = data & 0177777;
break;
case 3:
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "cr_wr cddb/crm %06o\n", data);
/* ignore writes to cddb */
if (!(cr_unit.flags & UNIT_CR11))
break;
/* fixup data for byte writes and read-modify-write */
if (access == WRITEB)
data = (PA & 1) ?
(crm & 0377) | (data << 8) :
(crm & ~0377) | (data & 0377);
crm = data & 0177777;
/* not 100% certain how these work */
if (!(crm & CRM_MAINT))
break;
crs = (crm & CRM_BUSY) ?
(crs | CRCSR_BUSY) : (crs & ~CRCSR_BUSY);
crs = (crm & CRM_READY) ?
(crs | CRCSR_OFFLINE) : (crs & ~CRCSR_OFFLINE);
crs = (crm & CRM_HOPPER) ?
(crs | CRCSR_SUPPLY | CRCSR_RDCHK) :
(crs & ~(CRCSR_SUPPLY | CRCSR_RDCHK));
crb1 = crm & 07777; /* load low 12 bits */
break;
default:
/* can't happen */
break;
}
return (SCPE_OK);
}
/*
Enter the service routine once for each column read from the card.
CR state bits drive this primarily (see _BUSY and _CRDDONE). However,
when in CD mode, also execute one column of DMA input.
*/
t_stat cr_svc ( UNIT *uptr )
{
uint32 pa;
uint8 c;
uint16 w;
if (blowerState == BLOW_STOP) {
blowerState = BLOW_OFF;
return (SCPE_OK);
}
if (blowerState == BLOW_START)
blowerState = BLOW_ON;
/* (almost) anything we do now will cause a CR interrupt */
if (crs & CSR_IE)
SET_INT (CR);
if (!(uptr->flags & UNIT_ATT) || (crs & CSR_ERR) || (cdst & CSR_ERR))
return (SCPE_OK);
if ((crs & CRCSR_BUSY) && (currCol > colEnd)) {
crs &= ~(CRCSR_BUSY | CSR_GO | CRCSR_COLRDY);
crs |= CRCSR_CRDDONE;
if (cdst & (CDCSR_DATAERR | CDCSR_LATE | CDCSR_NXM))
cdst |= CSR_ERR;
if (cdst & CSR_IE)
SET_INT (CR);
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "cr_svc card done\n");
return (SCPE_OK);
}
if (!(crs & CRCSR_BUSY)) {
/* try to read a card */
/* crs &= ~CRCSR_CRDDONE; */
if (!readRtn (uptr->fileref, hcard, ccard, acard)) {
sim_activate (uptr, spinDown);
return (SCPE_OK);
}
currCol = colStart;
crs |= CRCSR_BUSY; /* indicate reader busy */
}
/* check for overrun (timing error) */
if ((uptr->flags & UNIT_CR11) && (crs & CRCSR_COLRDY))
crs |= CSR_ERR | CRCSR_TIMERR;
crb1 = hcard[currCol] & 07777;
crb2 = ccard[currCol] & 0377;
uptr->buf = acard[currCol] & 0377; /* helpful for debugging */
if (!(uptr->flags & UNIT_CR11)) {
pa = cdba | ((cdst & 060) << 12);
/*
The implementation of _NXM here is not quite the same as I interpret
the (limited) documentaiton I have to indicate. However the effect
should be similar. Documentation indicates that once _NXM is set,
further NPR requests are inhibited though the card is allowed to
read until completion. This implies that CDBA and the XBA bits are
incremented accordingly, even though no data transfer occurs. This
code detects and flags the NXM condition but allows attempts at
subsequent memory writes, thus insuring the address registers are
incremented properly. If this causes problems, I'll fix it.
*/
if (cdst & CDCSR_PACK) {
c = cddb = ccard[currCol] & 0377;
if (Map_WriteB (pa, 1, &c))
cdst |= CDCSR_NXM;
pa = (pa + 1) & 0777777;
} else {
w = cddb = hcard[currCol] & 07777;
if (Map_WriteW (pa, 2, &w))
cdst |= CDCSR_NXM;
pa = (pa + 2) & 0777777;
}
cdba = pa & 0177777;
cdst = (cdst & ~(CDCSR_XBA17|CDCSR_XBA16)) |
((pa & 0600000) >> 12);
cdcc = (cdcc + 1) & 0177777;
#if 0
if (!(cdst & CSR_IE) && !(crs & CRCSR_CRDDONE))
CLR_INT (CR);
#endif
}
currCol++; /* advance the column counter */
if (!(crs & CRCSR_EJECT))
crs |= CRCSR_COLRDY;
else
CLR_INT (CR);
sim_activate (uptr, uptr->wait);
return (SCPE_OK);
}
t_stat cr_reset ( DEVICE *dptr )
{
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "cr_reset\n");
cr_unit.buf = 0;
currCol = 1;
crs &= ~(CSR_ERR|CRCSR_CRDDONE|CRCSR_TIMERR|CRCSR_ONLINE|CRCSR_BUSY|
CRCSR_COLRDY|CSR_IE|CRCSR_EJECT|CSR_GO);
crb1 = 0;
crb2 = 0;
crm = 0;
cdst &= ~(CSR_ERR|CDCSR_RDRCHK|CDCSR_EOF|CDCSR_DATAERR|CDCSR_LATE|
CDCSR_NXM|CSR_IE|CDCSR_XBA17|CDCSR_XBA16|CDCSR_ONLINE|
CDCSR_PACK|CSR_GO);
cdst |= CDCSR_RDY;
cdcc = 0;
cdba = 0;
cddb = 0;
if ((cr_unit.flags & UNIT_ATT) && !feof (cr_unit.fileref)) {
crs |= CRCSR_ONLINE; /* non-standard */
crs &= ~(CRCSR_RDCHK | CRCSR_SUPPLY | CRCSR_OFFLINE);
cdst &= ~(CDCSR_RDRCHK | CDCSR_HOPPER);
} else {
cdst |= CSR_ERR | CDCSR_RDRCHK | CDCSR_HOPPER;
crs = CSR_ERR | CRCSR_RDCHK | CRCSR_SUPPLY | CRCSR_OFFLINE;
}
sim_cancel (&cr_unit); /* deactivate unit */
if (blowerState != BLOW_OFF) {
blowerState = BLOW_STOP;
sim_activate (&cr_unit, spinDown);
}
EOFcard = FALSE;
CLR_INT (CR);
/* TBD: flush current card */
/* init uptr->wait ? */
return (SCPE_OK);
}
/*
Handle the interface status and SIMH portion of the ATTACH. Another
routine is used to evaluate the file and initialize other state
globals correctly.
*/
#define MASK (SWMASK('A')|SWMASK('B')|SWMASK('I')|SWMASK('R'))
t_stat cr_attach ( UNIT *uptr,
char *cptr )
{
t_stat reason;
extern int32 sim_switches;
if (sim_switches & ~MASK)
return (SCPE_INVSW);
/* file must previously exist; kludge */
sim_switches |= SWMASK ('R');
reason = attach_unit (uptr, cptr);
if (!(uptr->flags & UNIT_ATT)) {
crs &= ~CRCSR_ONLINE;
crs |= CSR_ERR | CRCSR_OFFLINE | CRCSR_RDCHK | CRCSR_SUPPLY;
cdst |= CSR_ERR | CDCSR_RDRCHK | CDCSR_HOPPER;
} else {
setupCardFile (uptr, sim_switches);
crs |= CRCSR_ONLINE;
crs &= ~(CSR_ERR | CRCSR_OFFLINE | CRCSR_RDCHK | CRCSR_SUPPLY);
cdst &= ~(CDCSR_RDRCHK | CDCSR_HOPPER);
EOFcard = FALSE;
}
return (reason);
}
t_stat cr_detach ( UNIT *uptr )
{
crs |= CSR_ERR | CRCSR_RDCHK | CRCSR_SUPPLY | CRCSR_OFFLINE;
/* interrupt? */
crs &= ~CRCSR_ONLINE;
cdst |= CSR_ERR | CDCSR_RDRCHK | CDCSR_HOPPER | CDCSR_OFFLINE;
cardFormat = "unknown";
if (blowerState != BLOW_OFF) {
blowerState = BLOW_STOP;
sim_activate (uptr, spinDown);
}
return (detach_unit (uptr));
}
t_stat cr_set_type ( UNIT *uptr,
int32 val,
char *cptr,
void *desc )
{
/* disallow type change if currently attached */
if (uptr->flags & UNIT_ATT)
return (SCPE_NOFNC);
cpm = (val & UNIT_CR11) ? 285 : 1000;
uptr->wait = (60 * 1000) / cpm;
return (SCPE_OK);
}
t_stat cr_show_format ( FILE *st,
UNIT *uptr,
int32 val,
void *desc )
{
fprintf (st, "%s format", cardFormat);
return (SCPE_OK);
}
t_stat cr_set_rate ( UNIT *uptr,
int32 val,
char *cptr,
void *desc )
{
t_stat status = SCPE_OK;
int32 i;
if (!cptr)
return (SCPE_MISVAL);
if (strcmp (cptr, "DEFAULT") == 0)
i = (uptr->flags & UNIT_CR11) ? 285 : 1000;
else
i = (int32) get_uint (cptr, 10, 0xFFFFFFFF, &status);
if (status == SCPE_OK) {
if (i < 200 || i > 1200)
status = SCPE_ARG;
else {
cpm = i;
uptr->wait = (60 * 1000) / cpm;
}
}
return (status);
}
t_stat cr_show_rate ( FILE *st,
UNIT *uptr,
int32 val,
void *desc )
{
fprintf (st, "%d cards per minute", cpm);
return (SCPE_OK);
}
/* simulate pressing the card reader RESET button */
t_stat cr_set_reset ( UNIT *uptr,
int32 val,
char *cptr,
void *desc )
{
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "cr_set_reset\n");
/*
Ignore the RESET switch while a read cycle is in progress or the
unit simply is not attached.
*/
if ((crs & CRCSR_BUSY) || !(uptr->flags & UNIT_ATT))
return (SCPE_OK);
/* if no errors, signal transition to on line */
crs |= CRCSR_ONLINE;
crs &= ~(CSR_ERR|CRCSR_CRDDONE|CRCSR_SUPPLY|CRCSR_RDCHK|CRCSR_TIMERR|
CRCSR_BUSY|CRCSR_COLRDY|CRCSR_EJECT|CSR_GO);
cdst |= CDCSR_ONLINE;
cdst &= ~(CSR_ERR | CDCSR_OFFLINE | CDCSR_RDRCHK | CDCSR_HOPPER |
CDCSR_EOF);
if ((crs & CSR_IE) || (cdst & CSR_IE)) {
SET_INT (CR);
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "cr_set_reset setting interrupt\n");
}
/* start up the blower if the hopper is not empty */
if (blowerState != BLOW_ON)
blowerState = BLOW_START;
return (SCPE_OK);
}
/* simulate pressing the card reader STOP button */
t_stat cr_set_stop ( UNIT *uptr,
int32 val,
char *cptr,
void *desc )
{
if (DEBUG_PRS (cr_dev))
fprintf (sim_deb, "set_stop\n");
crs &= ~CRCSR_ONLINE;
crs |= CSR_ERR | CRCSR_OFFLINE;
cdst |= CDCSR_OFFLINE;
/* CD11 does not appear to interrupt on STOP. */
if (crs & CSR_IE)
SET_INT (CR);
if (blowerState != BLOW_OFF) {
blowerState = BLOW_STOP;
/* set timer to turn it off completely */
sim_activate (uptr, spinDown);
}
return (SCPE_OK);
}
static const char * const trans[] = {
"unknown", "026", "026FTN", "029", "EBCDIC"
};
t_stat cr_set_trans ( UNIT *uptr,
int32 val,
char *cptr,
void *desc )
{
int i;
if (!cptr)
return (SCPE_MISVAL);
if (strcmp (cptr, "DEFAULT") == 0)
i = 3;
else {
for (i = 1; i < 5; i++) {
if (strcmp (cptr, trans[i]) == 0)
break;
}
}
if (i < 1 || i > 4)
return (SCPE_ARG);
table = i;
initTranslation (); /* reinitialize tables */
return (SCPE_OK);
}
t_stat cr_show_trans ( FILE *st,
UNIT *uptr,
int32 val,
void *desc )
{
fprintf (st, "translation %s", trans[table]);
return (SCPE_OK);
}