/* sigma_lp.c: Sigma 7440/7450 line printer | |
Copyright (c) 2007-2017, 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 | |
ROBERT M SUPNIK 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 Robert M Supnik shall not be | |
used in advertising or otherwise to promote the sale, use or other dealings | |
in this Software without prior written authorization from Robert M Supnik. | |
lp 7440/7445 or 7450 line printer | |
09-Mar-2017 RMS Fixed unclosed file returns in CCT load (COVERITY) | |
*/ | |
#include "sigma_io_defs.h" | |
/* Device definitions */ | |
#define CCT_LNT 256 /* carriage ctl max */ | |
#define BUF_LNT4 132 /* line lengths */ | |
#define BUF_LNT5 128 | |
#define LP_7440 0 /* models */ | |
#define LP_7450 1 | |
/* Device states */ | |
#define LPS_INIT 0x101 | |
#define LPS_END 0x102 | |
#define LPS_PRI 0x1 | |
#define LPS_FMT 0x3 | |
#define LPS_FMTP 0x5 | |
#define LPS_INT 0x40 | |
/* Device status */ | |
#define LPDV_ODD 0x40 /* odd */ | |
#define LPDV_TOF 0x10 /* top of form */ | |
#define LPDV_MOV 0x08 /* paper moving */ | |
#define LPDV_V_RUN 2 /* runaway CCT */ | |
#define LPDV_RUN (1u << LPDV_V_RUN) | |
#define LPDV_WT2 0x02 /* waiting for 2nd */ | |
/* Format characters */ | |
#define FMT_INH 0x60 | |
#define FMT_SPC 0xC0 | |
#define FMT_SKP 0xF0 | |
#define FMT_MSPC4 15 /* max space cmd */ | |
#define FMT_MSPC5 7 | |
#define SPC_MASK ((lp_model == LP_7440)? FMT_MSPC4: FMT_MSPC5) | |
#define FMT_MCH4 7 /* max CCT channel */ | |
#define FMT_MCH5 1 | |
#define CCH_MASK ((lp_model == LP_7440)? FMT_MCH5: FMT_MCH4) | |
#define CH_BOF 0 /* CCT bot of form */ | |
#define CH_TOF 1 /* CCT top of form */ | |
#define CHP(ch,val) ((val) & (1 << (ch))) | |
uint32 lp_cmd = 0; | |
uint32 lp_stopioe = 1; | |
uint32 lp_cctp = 0; /* CCT position */ | |
uint32 lp_cctl = 1; /* CCT length */ | |
uint32 lp_lastcmd = 0; /* last command */ | |
uint32 lp_pass = 0; /* 7450 print pass */ | |
uint32 lp_inh = 0; /* space inhibit */ | |
uint32 lp_run = 0; /* CCT runaway */ | |
uint32 lp_model = LP_7440; | |
uint8 lp_buf[BUF_LNT4]; /* print buffer */ | |
uint8 lp_cct[CCT_LNT] = { 0xFF }; /* carriage ctl tape */ | |
uint8 lp_to_ascii[64] = { | |
' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', | |
'H', 'I', '`', '.', '<', '(', '+', '|', | |
'&', 'J', 'K', 'L', 'M', 'N', 'O', 'P', | |
'Q', 'R', '!', '$', '*', ')', ';', '~', | |
'-', '/', 'S', 'T', 'U', 'V', 'W', 'X', | |
'Y', 'Z', '^', ',', '%', '_', '>', '?', | |
'0', '1', '2', '3', '4', '5', '6', '7', | |
'8', '9', ':', '#', '@', '\'', '=', '"' | |
}; | |
extern uint32 chan_ctl_time; | |
uint32 lp_disp (uint32 op, uint32 dva, uint32 *dvst); | |
uint32 lp_tio_status (void); | |
uint32 lp_tdv_status (void); | |
t_stat lp_chan_err (uint32 st); | |
t_stat lp_svc (UNIT *uptr); | |
t_stat lp_reset (DEVICE *dptr); | |
t_stat lp_attach (UNIT *uptr, CONST char *cptr); | |
t_stat lp_settype (UNIT *uptr, int32 val, CONST char *cptr, void *desc); | |
t_stat lp_showtype (FILE *st, UNIT *uptr, int32 val, CONST void *desc); | |
t_stat lp_load_cct (UNIT *uptr, int32 val, CONST char *cptr, void *desc); | |
t_stat lp_read_cct (FILE *cfile); | |
uint32 lp_fmt (UNIT *uptr); | |
uint32 lp_skip (UNIT *uptr, uint32 ch); | |
uint32 lp_space (UNIT *uptr, uint32 lines, t_bool skp); | |
uint32 lp_print (UNIT *uptr); | |
/* LP data structures | |
lp_dev LP device descriptor | |
lp_unit LP unit descriptors | |
lp_reg LP register list | |
lp_mod LP modifiers list | |
*/ | |
dib_t lp_dib = { DVA_LP, lp_disp, 0, NULL }; | |
UNIT lp_unit = { UDATA (&lp_svc, UNIT_ATTABLE+UNIT_SEQ, 0), SERIAL_OUT_WAIT }; | |
REG lp_reg[] = { | |
{ HRDATA (CMD, lp_cmd, 9) }, | |
{ BRDATA (BUF, lp_buf, 16, 7, BUF_LNT4) }, | |
{ FLDATA (PASS, lp_pass, 0) }, | |
{ FLDATA (INH, lp_inh, 0) }, | |
{ FLDATA (RUNAWAY, lp_run, LPDV_V_RUN) }, | |
{ BRDATA (CCT, lp_cct, 8, 8, CCT_LNT) }, | |
{ DRDATA (CCTP, lp_cctp, 8), PV_LEFT }, | |
{ DRDATA (CCTL, lp_cctl, 8), PV_LEFT + REG_HRO + REG_NZ }, | |
{ DRDATA (POS, lp_unit.pos, T_ADDR_W), PV_LEFT }, | |
{ DRDATA (TIME, lp_unit.wait, 24), PV_LEFT }, | |
{ FLDATA (STOP_IOE, lp_stopioe, 0) }, | |
{ HRDATA (LASTC, lp_lastcmd, 8), REG_HIDDEN }, | |
{ FLDATA (MODEL, lp_model, 0), REG_HRO }, | |
{ HRDATA (DEVNO, lp_dib.dva, 12), REG_HRO }, | |
{ NULL } | |
}; | |
MTAB lp_mod[] = { | |
{ MTAB_XTD | MTAB_VDV, LP_7440, NULL, "7440", | |
&lp_settype, NULL, NULL }, | |
{ MTAB_XTD | MTAB_VDV, LP_7450, NULL, "7450", | |
&lp_settype, NULL, NULL }, | |
{ MTAB_XTD | MTAB_VDV, 0, "TYPE", NULL, | |
NULL, &lp_showtype, NULL }, | |
{ MTAB_XTD | MTAB_VDV | MTAB_NC, 0, NULL, "CCT", | |
&lp_load_cct, NULL, NULL }, | |
{ MTAB_XTD|MTAB_VDV, 0, "CHAN", "CHAN", | |
&io_set_dvc, &io_show_dvc, NULL }, | |
{ MTAB_XTD|MTAB_VDV, 0, "DVA", "DVA", | |
&io_set_dva, &io_show_dva, NULL }, | |
{ MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "CSTATE", NULL, | |
NULL, &io_show_cst, NULL }, | |
{ 0 } | |
}; | |
DEVICE lp_dev = { | |
"LP", &lp_unit, lp_reg, lp_mod, | |
1, 10, 31, 1, 16, 8, | |
NULL, NULL, &lp_reset, | |
NULL, &lp_attach, NULL, | |
&lp_dib, 0 | |
}; | |
/* Line printer: IO dispatch routine */ | |
uint32 lp_disp (uint32 op, uint32 dva, uint32 *dvst) | |
{ | |
switch (op) { /* case on op */ | |
case OP_SIO: /* start I/O */ | |
*dvst = lp_tio_status (); /* get status */ | |
if ((*dvst & DVS_DST) == 0) { /* idle? */ | |
lp_cmd = LPS_INIT; /* start dev thread */ | |
sim_activate (&lp_unit, chan_ctl_time); | |
} | |
break; | |
case OP_TIO: /* test status */ | |
*dvst = lp_tio_status (); /* return status */ | |
break; | |
case OP_TDV: /* test status */ | |
*dvst = lp_tdv_status (); /* return status */ | |
break; | |
case OP_HIO: /* halt I/O */ | |
chan_clr_chi (lp_dib.dva); /* clear int */ | |
*dvst = lp_tio_status (); /* get status */ | |
if ((*dvst & DVS_DST) != 0) { /* busy? */ | |
sim_cancel (&lp_unit); /* stop dev thread */ | |
chan_uen (lp_dib.dva); /* uend */ | |
} | |
break; | |
case OP_AIO: /* acknowledge int */ | |
chan_clr_chi (lp_dib.dva); /* clear int */ | |
*dvst = lp_lastcmd & LPS_INT; /* int requested */ | |
lp_lastcmd = 0; | |
break; | |
default: | |
*dvst = 0; | |
return SCPE_IERR; | |
} | |
return 0; | |
} | |
/* Service routine */ | |
t_stat lp_svc (UNIT *uptr) | |
{ | |
uint32 cmd; | |
uint32 st; | |
switch (lp_cmd) { /* case on state */ | |
case LPS_INIT: /* I/O init */ | |
st = chan_get_cmd (lp_dib.dva, &cmd); /* get command */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return lp_chan_err (st); | |
lp_inh = 0; /* clear inhibit, */ | |
lp_run = 0; /* runaway */ | |
lp_cmd = lp_lastcmd = cmd; /* save command */ | |
sim_activate (uptr, chan_ctl_time); /* continue thread */ | |
break; | |
case LPS_FMT: | |
case LPS_FMT|LPS_INT: /* format only */ | |
sim_activate (uptr, uptr->wait); /* continue thread */ | |
if ((uptr->flags & UNIT_ATT) == 0) /* not attached? */ | |
return lp_stopioe? SCPE_UNATT: SCPE_OK; | |
st = lp_fmt (uptr); /* format */ | |
if (CHS_IFERR (st)) /* error? */ | |
return lp_chan_err (st); | |
if ((lp_model == LP_7440) && /* 7440? lnt chk */ | |
(st != CHS_ZBC) && | |
chan_set_chf (lp_dib.dva, CHF_LNTE)) /* not ignored? */ | |
return lp_chan_err (SCPE_OK); /* force uend */ | |
lp_cmd = LPS_END; /* actual print */ | |
break; | |
case LPS_FMTP: | |
case LPS_FMTP|LPS_INT: /* format and print */ | |
sim_activate (uptr, uptr->wait); /* continue thread */ | |
if ((uptr->flags & UNIT_ATT) == 0) /* not attached? */ | |
return lp_stopioe? SCPE_UNATT: SCPE_OK; | |
st = lp_fmt (uptr); /* format */ | |
if (CHS_IFERR (st)) /* error? */ | |
return lp_chan_err (st); | |
if (st == CHS_ZBC) { /* command done? */ | |
if ((lp_model == LP_7440) && /* 7440? lnt err */ | |
chan_set_chf (lp_dib.dva, CHF_LNTE)) /* not ignored? */ | |
return lp_chan_err (SCPE_OK); | |
} | |
else { /* more to do */ | |
st = lp_print (uptr); /* print */ | |
if (CHS_IFERR (st)) /* error */ | |
return lp_chan_err (st); | |
} | |
break; | |
case LPS_PRI: | |
case LPS_PRI|LPS_INT: /* print only */ | |
sim_activate (uptr, uptr->wait); /* continue thread */ | |
if ((uptr->flags & UNIT_ATT) == 0) /* not attached? */ | |
return lp_stopioe? SCPE_UNATT: SCPE_OK; | |
st = lp_print (uptr); /* print */ | |
if (CHS_IFERR (st)) /* error? */ | |
return lp_chan_err (st); | |
break; | |
case LPS_END: /* command done */ | |
if ((lp_lastcmd & LPS_INT) && !lp_pass) /* int requested? */ | |
chan_set_chi (lp_dib.dva, 0); | |
st = chan_end (lp_dib.dva); /* set channel end */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return lp_chan_err (st); | |
if (st == CHS_CCH) { /* command chain? */ | |
lp_cmd = LPS_INIT; /* restart thread */ | |
sim_activate (uptr, chan_ctl_time); | |
} | |
break; | |
default: /* invalid cmd */ | |
chan_uen (lp_dib.dva); /* uend */ | |
break; | |
} | |
return SCPE_OK; | |
} | |
/* Format routine */ | |
uint32 lp_fmt (UNIT *uptr) | |
{ | |
uint32 c, i; | |
uint32 st; | |
st = chan_RdMemB (lp_dib.dva, &c); /* get char */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return st; /* caller handles */ | |
if (lp_pass) /* only on pass 1 */ | |
return 0; | |
if ((c & 0x7F) == FMT_INH) /* inhibit? */ | |
lp_inh = 1; | |
else if ((c & ~(((lp_model == LP_7450)? 0x20: 0) | SPC_MASK)) == FMT_SPC) { | |
c = c & SPC_MASK; /* space? */ | |
for (i = 1; i <= c; i++) { /* look for BOF */ | |
if (CHP (CH_BOF, lp_cct[(lp_cctp + i) % lp_cctl])) | |
return lp_skip (uptr, CH_TOF); /* found, TOF */ | |
} | |
return lp_space (uptr, c, FALSE); /* space */ | |
} | |
else if ((c & ~CCH_MASK) == FMT_SKP) /* skip? */ | |
return lp_skip (uptr, c & CCH_MASK); /* skip to chan */ | |
return 0; | |
} | |
/* Skip to channel */ | |
uint32 lp_skip (UNIT *uptr, uint32 ch) | |
{ | |
uint32 i; | |
for (i = 1; i < (lp_cctl + 1); i++) { /* sweep thru CCT */ | |
if (CHP (ch, lp_cct[(lp_cctp + i) % lp_cctl])) /* channel punched? */ | |
return lp_space (uptr, i, TRUE); /* space to chan */ | |
} | |
lp_run = LPDV_RUN; /* runaway CCT */ | |
return lp_space (uptr, lp_cctl, TRUE); /* space max */ | |
} | |
/* Space routine */ | |
uint32 lp_space (UNIT *uptr, uint32 cnt, t_bool skp) | |
{ | |
uint32 i; | |
lp_cctp = (lp_cctp + cnt) % lp_cctl; /* adv cct, mod lnt */ | |
if (skp && CHP (CH_TOF, lp_cct[lp_cctp])) /* skip, TOF? */ | |
fputs ("\f", uptr->fileref); /* ff */ | |
else { /* space */ | |
for (i = 0; i < cnt; i++) | |
fputc ('\n', uptr->fileref); | |
} | |
uptr->pos = ftell (uptr->fileref); /* update position */ | |
if (ferror (uptr->fileref)) { /* error? */ | |
sim_perror ("Line printer I/O error"); | |
clearerr (uptr->fileref); | |
chan_set_chf (lp_dib.dva, CHF_XMDE); | |
return SCPE_IOERR; | |
} | |
return 0; | |
} | |
/* Print routine */ | |
uint32 lp_print (UNIT *uptr) | |
{ | |
uint32 i, bp, c; | |
uint32 max = (lp_model == LP_7440)? BUF_LNT4: BUF_LNT5; | |
uint32 st; | |
if (lp_pass == 0) { /* pass 1? clr buf */ | |
for (i = 0; i < BUF_LNT4; i++) lp_buf[i] = ' '; | |
} | |
for (bp = 0, st = 0; (bp < max) && !st; bp++) { /* fill buffer */ | |
st = chan_RdMemB (lp_dib.dva, &c); /* get char */ | |
if (CHS_IFERR (st)) /* channel error? */ | |
return st; /* caller handles */ | |
if ((lp_model == LP_7440) || /* 7440 or */ | |
((bp & 1) == lp_pass)) /* correct pass? */ | |
lp_buf[bp] = lp_to_ascii[c & 0x3F]; | |
} | |
if ((lp_model == LP_7440) || lp_pass) { /* ready to print? */ | |
lp_pass = 0; | |
for (i = BUF_LNT4; (i > 0) && (lp_buf[i - 1] == ' '); i--) ; /* trim */ | |
if (i) /* write line */ | |
sim_fwrite (lp_buf, 1, i, uptr->fileref); | |
fputc (lp_inh? '\r': '\n', uptr->fileref); /* cr or nl */ | |
uptr->pos = ftell (uptr->fileref); /* update position */ | |
if (ferror (uptr->fileref)) { /* error? */ | |
sim_perror ("Line printer I/O error"); | |
clearerr (uptr->fileref); | |
chan_set_chf (lp_dib.dva, CHF_XMDE); | |
return SCPE_IOERR; | |
} | |
if ((lp_model == LP_7440) && /* 7440? */ | |
((bp != BUF_LNT4) || (st != CHS_ZBC)) && /* check lnt err */ | |
chan_set_chf (lp_dib.dva, CHF_LNTE)) | |
return CHS_INACTV; /* stop if asked */ | |
} | |
else lp_pass = 1; /* 7450 pass 2 */ | |
lp_cmd = LPS_END; /* end state */ | |
return 0; | |
} | |
/* LP status routine */ | |
uint32 lp_tio_status (void) | |
{ | |
uint32 st; | |
st = (lp_unit.flags & UNIT_ATT)? DVS_AUTO: 0; /* auto? */ | |
if (sim_is_active (&lp_unit)) /* busy? */ | |
st |= (DVS_CBUSY | DVS_DBUSY | (CC2 << DVT_V_CC)); | |
return st; | |
} | |
uint32 lp_tdv_status (void) | |
{ | |
uint32 st; | |
st = lp_run; /* runaway flag */ | |
if ((lp_unit.flags & UNIT_ATT) == 0) /* fault? */ | |
st |= (CC2 << DVT_V_CC); | |
if (lp_cmd == LPS_END) /* end state? */ | |
st |= LPDV_MOV; /* printing */ | |
if (lp_pass && (lp_model == LP_7450)) { /* 7450 pass 2? */ | |
st |= LPDV_ODD; /* odd state */ | |
if (lp_cmd == LPS_INIT) /* wait for cmd? */ | |
st |= LPDV_WT2; | |
} | |
return st; | |
} | |
/* Channel error */ | |
t_stat lp_chan_err (uint32 st) | |
{ | |
sim_cancel (&lp_unit); /* stop dev thread */ | |
chan_uen (lp_dib.dva); /* uend */ | |
if (st < CHS_ERR) | |
return st; | |
return SCPE_OK; | |
} | |
/* Reset routine */ | |
t_stat lp_reset (DEVICE *dptr) | |
{ | |
sim_cancel (&lp_unit); /* stop dev thread */ | |
lp_cmd = 0; | |
lp_lastcmd = 0; | |
lp_pass = 0; | |
lp_inh = 0; | |
lp_run = 0; | |
chan_reset_dev (lp_dib.dva); /* clr int, active */ | |
return SCPE_OK; | |
} | |
/* Attach routine */ | |
t_stat lp_attach (UNIT *uptr, CONST char *cptr) | |
{ | |
lp_cctp = 0; /* clear cct ptr */ | |
lp_pass = 0; | |
return attach_unit (uptr, cptr); | |
} | |
/* Set handler for carriage control tape */ | |
t_stat lp_load_cct (UNIT *uptr, int32 val, CONST char *cptr, void *desc) | |
{ | |
FILE *cfile; | |
t_stat r; | |
if ((cptr == NULL) || (*cptr == 0)) | |
return SCPE_ARG; | |
if ((cfile = fopen (cptr, "r")) == NULL) | |
return SCPE_OPENERR; | |
r = lp_read_cct (cfile); | |
fclose (cfile); | |
return r; | |
} | |
/* Read carriage control tape - used by SET and LOAD */ | |
t_stat lp_read_cct (FILE *cfile) | |
{ | |
uint32 col, rpt, ptr, mask; | |
uint8 cctbuf[CCT_LNT]; | |
const char *cptr; | |
t_stat r; | |
char cbuf[CBUFSIZE], gbuf[CBUFSIZE]; | |
ptr = 0; | |
for ( ; (cptr = fgets (cbuf, CBUFSIZE, cfile)) != NULL; ) { | |
mask = 0; | |
if (*cptr == '(') { /* repeat count? */ | |
cptr = get_glyph (cptr + 1, gbuf, ')'); /* get 1st field */ | |
rpt = get_uint (gbuf, 10, CCT_LNT, &r); /* repeat count */ | |
if (r != SCPE_OK) | |
return SCPE_FMT; | |
} | |
else rpt = 1; | |
while (*cptr != 0) { /* get col no's */ | |
cptr = get_glyph (cptr, gbuf, ','); /* get next field */ | |
col = get_uint (gbuf, 10, FMT_MCH4, &r); /* column number */ | |
if (r != SCPE_OK) | |
return SCPE_FMT; | |
mask = mask | (1 << col); /* set bit */ | |
} | |
for ( ; rpt > 0; rpt--) { /* store vals */ | |
if (ptr >= CCT_LNT) | |
return SCPE_FMT; | |
cctbuf[ptr++] = mask; | |
} | |
} | |
if (ptr == 0) | |
return SCPE_FMT; | |
lp_cctl = ptr; | |
lp_cctp = 0; | |
for (rpt = 0; rpt < lp_cctl; rpt++) | |
lp_cct[rpt] = cctbuf[rpt]; | |
return SCPE_OK; | |
} | |
/* Set controller type */ | |
t_stat lp_settype (UNIT *uptr, int32 val, CONST char *cptr, void *desc) | |
{ | |
lp_model = val; | |
lp_reset (&lp_dev); | |
return SCPE_OK; | |
} | |
/* Show controller type */ | |
t_stat lp_showtype (FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
{ | |
fprintf (st, lp_model? "7450": "7440"); | |
return SCPE_OK; | |
} |