| /* sigma_lp.c: Sigma 7440/7450 line printer | |
| Copyright (c) 2007-2008, 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 | |
| */ | |
| #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, char *cptr); | |
| t_stat lp_settype (UNIT *uptr, int32 val, char *cptr, void *desc); | |
| t_stat lp_showtype (FILE *st, UNIT *uptr, int32 val, void *desc); | |
| t_stat lp_load_cct (UNIT *uptr, int32 val, char *cptr, void *desc); | |
| 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? */ | |
| 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? */ | |
| 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, char *cptr) | |
| { | |
| lp_cctp = 0; /* clear cct ptr */ | |
| lp_pass = 0; | |
| return attach_unit (uptr, cptr); | |
| } | |
| /* Set carriage control tape */ | |
| t_stat lp_load_cct (UNIT *uptr, int32 val, char *cptr, void *desc) | |
| { | |
| uint32 col, rpt, ptr, mask; | |
| uint8 cctbuf[CCT_LNT]; | |
| t_stat r; | |
| char cbuf[CBUFSIZE], gbuf[CBUFSIZE]; | |
| FILE *cfile; | |
| if ((cptr == NULL) || (*cptr == 0)) return SCPE_ARG; | |
| if ((cfile = fopen (cptr, "r")) == NULL) | |
| return SCPE_OPENERR; | |
| 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, 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, void *desc) | |
| { | |
| fprintf (st, lp_model? "7450": "7440"); | |
| return SCPE_OK; | |
| } |