#pragma module LRDRIVER "X-4" /* ***************************************************************************** * * Copyright © Digital Equipment Corporation, 1993, 1995 All Rights Reserved. * Unpublished rights reserved under the copyright laws of the United States. * * The software contained on this media is proprietary to and embodies the * confidential technology of Digital Equipment Corporation. Possession, use, * duplication or dissemination of the software and media is authorized only * pursuant to a valid written license from Digital Equipment Corporation. * * RESTRICTED RIGHTS LEGEND Use, duplication, or disclosure by the U.S. * Government is subject to restrictions as set forth in Subparagraph * (c)(1)(ii) of DFARS 252.227-7013, or in FAR 52.227-19, as applicable. * ***************************************************************************** * * * FACILITY: * * Example Device Driver for OpenVMS AXP * * ABSTRACT: * * This is an example device driver for OpenVMS AXP Version V7.0 for the * parallel printer port of the VL82C106 Combo chip. This driver supports * the VL82C106 either on the system bus or on an ISA option card. * * The parallel printer port is a simple programmed I/O device. There * is a single control register (LPC), a status register (LPS), and a * write data register (LWD). * * This driver supports transfers from buffers in 64-bit virtual address * spaces. * * AUTHOR: * * OpenVMS Alpha Development Group * * REVISION HISTORY: * * X-4 VMS002 VMS Engineering 31-Mar-2003 * Fold of a couple of changes, making this example driver * an exact copy of the source that is used to build * sys$loadable_images:sys$lrdriver.exe. * * X-10 VMS OpenVMS Alpha Drivers 25-March-1996 * Yet another fix for the timer polling code. We cannout use * UCB$V_INT to signal that the output is done. If the printer * stalls for some reason the device timeout code clears UCB$V_INT * even though there is still data to output. The flag to use is * UCB$V_BSY. The busy bit should only be cleared when there is * nothing more to output. * * X-9 VMS OpenVMS Alpha Drivers 12-February-1996 * Fix IPL misuse in lt$timer_int routine. It saved starting IPL * in the wrong place. Then when it restored IPL when releasing * the fork lock it got a random IPL value. * * X-3 VMS001 OpenVMS Alpha Driver2 6-Jan-1996 * If call to exe_std$writechk fails abort the I/O. The code * assumed that the call aborted the I/O. The routine claimed * that is what it did. The problem is that the code did not do * what the routine description said it did. * * X-2 VMS000 OpenVMS Alpha Drivers 29-Jun-1995 * This example driver is now the same source that is used to * produce the SYS$LRDRIVER.EXE image that ships on the VMS kit. * This driver supports transfers from buffers in a 64-bit * virtual address space. * * X-1 VMS000 OpenVMS Alpha Drivers 5-Nov-1993 * Initial version. * */ /* Define system data structure types and constants */ #include /* Define the packet header for a system */ /* buffer for buffered I/O data */ #include /* Channel control block */ #include /* Controller request block */ #include /* Controller register access method */ #include /* Device codes */ #include /* Device data block */ #include /* Driver dispatch table */ #include /* Device characteristics */ #include /* Driver prologue table */ #include /* Dynamic data structure types */ #include /* Function decision table */ #include /* Fork block */ #include /* Hardware restart parameter block */ #include /* Interrupt data block */ #include /* IOC constants */ #include /* I/O function codes */ #include /* I/O request packet */ #include /* DEC 2000 Model 300 AXP specific defs */ #include /* Line printer definitions */ #include /* Object rights block */ #include /* Process control block */ #include /* System-wide mailbox message codes */ #include /* System service status codes */ #include /* Status value fields */ #include /* Timer queue element defintion */ #include /* Unit control block */ #include /* IDB interrupt transfer vector */ /* Define function prototypes for system routines */ #include /* Prototypes for exe$ and exe_std$ routines */ #include /* Prototypes for ioc$ and ioc_std$ routines */ #include /* Prototypes for sch$ and sch_std$ routines */ /* Define various device driver macros */ #include /* Device driver support macros, including */ /* table initialization macros and prototypes*/ /* Define the DEC C functions used by this driver */ #include /* OpenVMS AXP specific C builtin functions */ #include /* String routines provided by "kernel CRTL" */ #include "src$:lrdriver.h" /* Fallback translation table */ /* Define constants specific to this driver */ enum { /* Miscellaneous constants */ FALSE = 0, /* True and False Flags */ TRUE = 1, DEVICE_IPL = 21, /* Interrupt priority level of device */ NUMBER_CRAMS = 3, /* Number of CRAMs needed */ LINES_PER_PAGE = 66, /* Default paper size */ DATA_EXPND_CUSHION = 32 /* Extra room in system buffer for expansion */ }; enum { /* Define various timeout constants */ LR_WFI_TMO = 15, /* Interrupt timeout value in seconds */ LR_OFFLINE_TMO = 60, /* Initial interval between offline messages */ ONE_HOUR = (60*60) /* One hour in seconds */ }; enum { /* Define names for some ASCII characters */ CR = '\x0d', /* Carriage return character */ DEL = '\x7f', /* Delete */ FF = '\x0c', /* Form Feed */ HT = '\x09', /* Horizontal Tab */ LF = '\x0a', /* Line feed character */ SP = '\x20', /* Space */ VT = '\x0b' /* Vertical Tab character */ }; /* Define the line printer port CSR offsets. * * Note: We have to do some special setup work because the Jensen built in * parallel port is on the system bus and is not byte-laned. At the present * time other systems with built in parallel ports treat these as though they * live on the ISA bus. The Unit Init routine figures how to correctly deal * with the built in controller and add on controllers. * * Note also that due to the byte-laned I/O space, data read from the ISA * LPS register (byte offset 1) must be shifted right 1 byte, and data read * from the LPC register (byte offset 2) must be shifted right 2 bytes. */ /* Offsets for Jensen parallel port on system bus */ #define LR_JENSEN_LWD 0x3bc /* line printer port data write */ #define LR_JENSEN_LPS 0x3bd /* line printer port status */ #define LR_JENSEN_LCW 0x3be /* line printer port control write */ /* Offsets for ISA space parallel port */ #define LR_LPT1_PORT 0x3bc /* ISA I/O address for LPT1 */ #define LR_LPT2_PORT 0x378 /* ISA I/O address for LPT2 */ #define LR_LPT3_PORT 0x278 /* ISA I/O address for LPT3 */ /* Actual register offset is LR_LPT2_PORT or */ /* LR_LPT2_PORT plus one of the following: */ #define LR_ISA_LWD 0x0 /* line printer port data write */ #define LR_ISA_LPS 0x1 /* line printer port status */ #define LR_ISA_LCW 0x2 /* line printer port control write */ #define LR_LPT2_IRQ 7 /* Expected ISA IRQ for LPT2 */ #define LR_LPT3_IRQ 5 /* Expected ISA IRQ for LPT3 */ /* Line Printer Control Register * Mask values are defined for each of the control bits in the LPC. This * driver always writes a new value to the LPC when a bit needs to be set. * A convenient way of doing this is to logically or together a subset of the * following masks to form the new LPC value. */ enum lpc_masks { LPC_M_STROBE = 0x01, /* Strobe data to printer */ LPC_M_AUTO_FEED = 0x02, /* Auto line feed enabled */ LPC_M_INIT_OFF = 0x04, /* Disable INIT signal */ LPC_M_SELECT = 0x08, /* Select printer "on line" */ LPC_M_IRQ_EN = 0x10, /* Interrupt enable */ LPC_M_DIR_READ = 0x20 /* Direction is read if set, else write */ }; /* Line Printer Status Register * Define a structure type with bit fields that corresponds to the status * bits. This structure type facilitates the testing of these conditions. */ typedef struct _lps { unsigned int : 2; /* Reserved */ unsigned int lps_irqp : 1; /* Interrupt pending */ unsigned int lps_ok : 1; /* Ok status, i.e. no error */ unsigned int lps_online : 1; /* Select on line */ unsigned int lps_paperout : 1; /* Paper empty */ unsigned int lps_nak : 1; /* Not acknowledge */ unsigned int lps_ready : 1; /* Ready, i.e. not busy */ } LPS; /* Define a structure type for the carraige control information that * is returned from exe_std$carriage. This information is returned in * the IRP at the longword that begins with irp->irp$b_carcon. */ typedef struct { uint8 prefix_count; /* Number of prefix chars */ char prefix_char; /* The prefix char, 0 if newline */ uint8 suffix_count; /* Number of suffix chars */ char suffix_char; /* The suffix char, 0 if newline */ } CARCON; /* Define a structure with the character formatting data in it. This is passed * to character formatting code. */ typedef struct { int buffer_space; /* Size of system buffer in bytes */ int column_pos; /* Column on page where character will go */ int cr_pend; /* If printer is /CR flag indicating we held a CR */ int line_on_page; /* Line numer we are currently on */ int page_length; /* Length of page */ int page_width; /* Width of papge in columns */ int total_bytes; /* Numbers of bytes to output */ int total_lines; /* Total lines printed for this I/O request */ char *sys_datap; /* Pointer to current slot in system buffer */ } FMT_DATA; /* Define Device-Dependent Unit Control Block with extensions for LR device */ typedef struct { UCB ucb$r_ucb; /* Generic UCB */ int ucb$l_lr_msg_tmo; /* Time out value for device offline msg */ int ucb$l_lr_oflcnt; /* Offline time, print msg when reaches lr_msg_tmo */ int ucb$l_lr_cursor; /* Current horizontal position */ int ucb$l_lr_lincnt; /* Current line count on page */ int ucb$l_lr_cr_pend; /* Pending CR flag */ int ucb$l_lr_jensen; /* Unit is on system bus, not ISA option */ int ucb$l_lr_isa_io_address[2]; /* ISA I/O address range */ int ucb$l_lr_isa_irq[2]; /* IRQ returned from ioc$node_data */ CRAM *ucb$ps_cram_lwd; /* Line printer write data register */ CRAM *ucb$ps_cram_lps; /* Line printer status register */ CRAM *ucb$ps_cram_lcw; /* Line printer control register write */ TQE ucb$l_tqe; /* Build in a TQE for the timer tick */ } LR_UCB; /* Prototypes for driver routines defined in this module */ /* Driver table initialization routine */ int driver$init_tables (); /* Device I/O database structure initialization routine */ void lr$struc_init (CRB *crb, DDB *ddb, IDB *idb, ORB *orb, LR_UCB *ucb); /* Device I/O database structure re-initialization routine */ void lr$struc_reinit (CRB *crb, DDB *ddb, IDB *idb, ORB *orb, LR_UCB *ucb); /* Unit initialization routine */ int lr$unit_init (IDB *idb, LR_UCB *ucb); /* FDT routine for write functions */ int lr$write (IRP *irp, PCB *pcb, LR_UCB *ucb, CCB *ccb); /* Output formatting routine */ int lr$format_char(LR_UCB *ucb, unsigned char out_char, FMT_DATA *fmt_data); /* FDT routine for set mode and set characteristics functions */ int lr$setmode (IRP *irp, PCB *pcb, LR_UCB *ucb, CCB *ccb); /* Start I/O routine */ void lr$startio (IRP *irp, LR_UCB *ucb); /* Local routine that sends the next character to the device */ static int lr$send_char_dev (LR_UCB *ucb); /* Interrupt service routine */ void lr$interrupt (IDB *idb); /* Driver fork routine entered when all I/O completed by interrupt service */ void lr$iodone_fork (IRP *irp, void *not_used, LR_UCB *ucb); /* Wait-for-interrupt timeout routine */ void lr$wfi_timeout (IRP *irp, void *not_used, LR_UCB *ucb); /* Periodic Check for Device Ready via Fork-wait mechanism */ void lr$check_ready_fork (IRP *irp, void *not_used, LR_UCB *ucb); /* Timer tick that send character to device or completes request */ void lr$timer_int (void *fr3, LR_UCB *ucb, TQE *tqe); /* * DRIVER$INIT_TABLES - Initialize Driver Tables * * Functional description: * * This routine completes the initialization of the DPT, DDT, and FDT * structures. If a driver image contains a routine named DRIVER$INIT_TABLES * then this routine is called once by the $LOAD_DRIVER service immediately * after the driver image is loaded or reloaded and before any validity checks * are performed on the DPT, DDT, and FDT. A prototype version of these * structures is built into this image at link time from the * VMS$VOLATILE_PRIVATE_INTERFACES.OLB library. Note that the device related * data structures (e.g. DDB, UCB, etc.) have not yet been created when this * routine is called. Thus the actions of this routine must be confined to * the initialization of the DPT, DDT, and FDT structures which are contained * in the driver image. * * Calling convention: * * status = driver$init_tables (); * * Input parameters: * * None. * * Output parameters: * * None. * * Return value: * * status If the status is not successful, then the driver image will * be unloaded. Note that the ini_* macros used below will * result in a return from this routine with an error status if * an initialization error is detected. * * Implicit inputs: * * driver$dpt, driver$ddt, driver$fdt * These are the externally defined names for the prototype * DPT, DDT, and FDT structures that are linked into this driver. * * Environment: * * Kernel mode, system context. */ int driver$init_tables () { /* Prototype driver DPT, DDT, and FDT will be pulled in from the * VMS$VOLATILE_PRIVATE_INTERFACES.OLB library at link time. */ extern DPT driver$dpt; extern DDT driver$ddt; extern FDT driver$fdt; /* Finish initialization of the Driver Prologue Table (DPT) */ ini_dpt_name (&driver$dpt, "LRDRIVER"); ini_dpt_adapt (&driver$dpt, AT$_KA0602); ini_dpt_defunits (&driver$dpt, 1); ini_dpt_ucbsize (&driver$dpt, sizeof(LR_UCB)); ini_dpt_struc_init (&driver$dpt, lr$struc_init ); ini_dpt_struc_reinit(&driver$dpt, lr$struc_reinit ); ini_dpt_ucb_crams (&driver$dpt, NUMBER_CRAMS); ini_dpt_end (&driver$dpt); /* Finish initialization of the Driver Dispatch Table (DDT) */ ini_ddt_unitinit (&driver$ddt, lr$unit_init); ini_ddt_start (&driver$ddt, lr$startio); ini_ddt_cancel (&driver$ddt, ioc_std$cancelio); ini_ddt_end (&driver$ddt); /* Finish initialization of the Function Decision Table (FDT) */ /* */ /* The BUFFERED_64 indicates that this driver supports a 64-bit */ /* virtual address in the QIO P1 parameter for that function. */ /* This driver, therefore, supports 64-bit user buffers in all */ /* of its I/O functions. */ ini_fdt_act (&driver$fdt, IO$_WRITELBLK, lr$write, BUFFERED_64); ini_fdt_act (&driver$fdt, IO$_WRITEPBLK, lr$write, BUFFERED_64); ini_fdt_act (&driver$fdt, IO$_WRITEVBLK, lr$write, BUFFERED_64); ini_fdt_act (&driver$fdt, IO$_SETMODE, lr$setmode, BUFFERED_64); ini_fdt_act (&driver$fdt, IO$_SETCHAR, lr$setmode, BUFFERED_64); ini_fdt_act (&driver$fdt, IO$_SENSEMODE, exe_std$sensemode, BUFFERED_64); ini_fdt_act (&driver$fdt, IO$_SENSECHAR, exe_std$sensemode, BUFFERED_64); ini_fdt_end (&driver$fdt); /* If we got this far then everything worked, so return success. */ return SS$_NORMAL; } /* * LR$STRUC_INIT - Device Data Structure Initialization Routine * * Functional description: * * This routine is called once for each unit by the $LOAD_DRIVER service * after that UCB is created. At the point of this call the UCB has not * yet been fully linked into the I/O database. This routine is responsible * for filling in driver specific fields that in the I/O database structures * that are passed as parameters to this routine. * * This routine is responsible for filling in the fields that are not * affected by a RELOAD of the driver image. In contrast, the structure * reinitialization routine is responsible for filling in the fields that * need to be corrected when (and if) this driver image is reloaded. * * After this routine is called for a new unit, then the reinitialization * routine is called as well. Then the $LOAD_DRIVER service completes the * integration of these device specific structures into the I/O database. * * Note that this routine must confine its actions to filling in these I/O * database structures and may not attempt to initialize the hardware device. * Initialization of the hardware device is the responsibility of the * controller and unit initialization routines which are called some time * later. * * Calling convention: * * lr$struc_init (crb, ddb, idb, orb, ucb) * * Input parameters: * * crb Pointer to associated controller request block. * ddb Pointer to associated device data block. * idb Pointer to associated interrupt dispatch block. * orb Pointer to associated object rights block. * ucb Pointer to the unit control block that is to be initialized. * * Output parameters: * * None. * * Return value: * * None. * * Environment: * * Kernel mode, system context, IPL may be as high as 31 and may not be * altered. * */ void lr$struc_init (CRB *crb, DDB *ddb, IDB *idb, ORB *orb, LR_UCB *ucb) { /* Initialize the fork lock and device IPL fields */ ucb->ucb$r_ucb.ucb$b_flck = SPL$C_IOLOCK8; ucb->ucb$r_ucb.ucb$b_dipl = DEVICE_IPL; /* Device Characteristics are : Record oriented (REC), Available (AVL), * Carriage control device (CCL), Output device (ODV) */ ucb->ucb$r_ucb.ucb$l_devchar = DEV$M_REC | DEV$M_AVL | DEV$M_CCL | DEV$M_ODV; /* Set to prefix device name with "node$", set device class, device type, * and default buffer size. */ ucb->ucb$r_ucb.ucb$l_devchar2 = DEV$M_NNM; ucb->ucb$r_ucb.ucb$b_devclass = DC$_LP; ucb->ucb$r_ucb.ucb$b_devtype = LP$_LP11; ucb->ucb$r_ucb.ucb$w_devbufsiz = 132; /* Lines per page in highest byte of ucb$l_devdepend and LP attributes * in lower three bytes. */ ucb->ucb$r_ucb.ucb$l_devdepend = (LINES_PER_PAGE << 24) | LP$M_MECHFORM | LP$M_TRUNCATE; ucb->ucb$l_tqe.tqe$w_size = TQE$S_TQEDEF; ucb->ucb$l_tqe.tqe$b_type = DYN$C_TQE; ucb->ucb$l_tqe.tqe$b_rqtype = TQE$C_SSREPT; ucb->ucb$l_tqe.tqe$q_fr3 = 0; ucb->ucb$l_tqe.tqe$q_fr4 = (__int64) ucb; ucb->ucb$l_tqe.tqe$l_fpc = (int) lr$timer_int; ucb->ucb$l_tqe.tqe$q_delta = 100000; return; } /* * LR$STRUC_REINIT - Device Data Structure Re-Initialization Routine * * Functional description: * * This routine is called once for each unit by the $LOAD_DRIVER service * immediately after the structure initialization routine is called. * * Additionally, this routine is called once for each unit by the $LOAD_DRIVER * service when a driver image is RELOADED. Thus, this routine is * responsible for filling in the fields in the I/O database structures * that point into this driver image. * * Note that this routine must confine its actions to filling in these I/O * database structures. * * Calling convention: * * lr$struc_reinit (crb, ddb, idb, orb, ucb) * * Input parameters: * * crb Pointer to associated controller request block. * ddb Pointer to associated device data block. * idb Pointer to associated interrupt dispatch block. * orb Pointer to associated object rights block. * ucb Pointer to the unit control block that is to be initialized. * * Output parameters: * * None. * * Return value: * * None. * * Environment: * * Kernel mode, system context, IPL may be as high as 31 and may not be * altered. * */ void lr$struc_reinit (CRB *crb, DDB *ddb, IDB *idb, ORB *orb, LR_UCB *ucb) { extern DDT driver$ddt; /* Setup the pointer from our DDB in the I/O database to the driver * dispatch table that's within this driver image. */ ddb->ddb$ps_ddt = &driver$ddt; /* Setup the procedure descriptor and code entry addresses in the VEC * portion of the CRB in the I/O database to point to the interrupt * service routine that's within this driver image. */ dpt_store_isr (crb, lr$interrupt); return; } /* * LR$UNIT_INIT - Unit Initialization Routine * * Functional description: * * This routine is called once for each unit by the $LOAD_DRIVER service * after a new unit control block has been created, initialized, and * fully integrated into the I/O database. * * This routine is also called for each unit during power fail recovery. * * It is the responsibility of this routine to bring unit "on line" and * to make it ready to accept I/O requests. * * Calling convention: * * status = lr$unit_init (idb, ucb) * * Input parameters: * * idb Pointer to associated interrupt dispatch block. * ucb Pointer to the unit control block that is to be initialized. * * Output parameters: * * None. * * Return value: * * status SS$_NORMAL indicates that the unit was initialized successfully. * SS$_IVADDR indicates that an unexpected ISA I/O address or IRQ * level was detected. * * Environment: * * Kernel mode, system context, IPL 31. */ int lr$unit_init (IDB *idb, LR_UCB *ucb) { extern uint64 EXE$GQ_SYSTYPE; static int jensen_combo_initialized = 0; /* First unit is on system bus */ CRAM *cram; ADP *adp; int isa_io_addr; /* Slot I/O address if ISA option */ int device_data; /* Data from or for CRAM */ int status; #if defined DEBUG /* If a debug version of this driver is being built then invoke the loaded * system debugger. This could either be the High Level Language System * Debugger, XDELTA, or nothing. */ { extern void ini$brk (void); ini$brk (); } #endif /* Set device initially offline (for error exits) and initialize other * UCB cells. */ ucb->ucb$r_ucb.ucb$v_online = 0; ucb->ucb$l_lr_msg_tmo = LR_OFFLINE_TMO; /* This driver can service only a single unit per DDB and IDB. Thus, * make the single unit the permanent owner of the IDB. This facilitates * getting the UCB address in our interrupt service routine. */ idb->idb$ps_owner = &(ucb->ucb$r_ucb); /* Initialize the three CRAMs that were requested in our DPT and allocated * before this unit initialization routine was called. */ adp = ucb->ucb$r_ucb.ucb$ps_adp; /* Pointer to our ADP */ cram = ucb->ucb$r_ucb.ucb$ps_cram; /* Pointer to first CRAM */ /* If the system is a Jensen then we assume that the first port is * the VL82C106 on the system bus. All subsequent units are in ISA * space. */ if ( ! jensen_combo_initialized && EXE$GQ_SYSTYPE == HWRPB_SYSTYPE$K_JENSEN) { jensen_combo_initialized = 1; /* Unit on system bus initialized */ ucb->ucb$l_lr_jensen = 1; /* This unit is for VL82C106 on system bus */ /* Initialize CRAM used to write the data register */ cram->cram$v_der = 1; ucb->ucb$ps_cram_lwd = cram; ioc$cram_cmd (CRAMCMD$K_WTLONG32, LR_JENSEN_LWD, adp, cram, 0); /* Initialize CRAM used to read the status register */ cram = cram->cram$l_flink; cram->cram$v_der = 1; ucb->ucb$ps_cram_lps = cram; ioc$cram_cmd (CRAMCMD$K_RDLONG32, LR_JENSEN_LPS, adp, cram, 0); /* Initialize CRAM used to write the control register */ cram = cram->cram$l_flink; cram->cram$v_der = 1; ucb->ucb$ps_cram_lcw = cram; ioc$cram_cmd (CRAMCMD$K_WTLONG32, LR_JENSEN_LCW, adp, cram, 0); } else { /* This unit is ISA bus card */ /* Get and validate the ISA IRQ */ status = ioc$node_data (ucb->ucb$r_ucb.ucb$l_crb, IOC$K_EISA_IRQ, &ucb->ucb$l_lr_isa_irq[0] ); if ( ! $VMS_STATUS_SUCCESS(status) ) return status; /* Get and validate the ISA I/O address */ status = ioc$node_data (ucb->ucb$r_ucb.ucb$l_crb, IOC$K_EISA_IO_PORT, &ucb->ucb$l_lr_isa_io_address[0] ); if ( ! $VMS_STATUS_SUCCESS(status) ) return status; isa_io_addr = ucb->ucb$l_lr_isa_io_address[0] & 0xfff; /* Keep Address only */ /* Initialize CRAM used to write the data register */ cram->cram$v_der = 1; ucb->ucb$ps_cram_lwd = cram; ioc$cram_cmd (CRAMCMD$K_WTBYTE32, isa_io_addr+LR_ISA_LWD, adp, cram, 0); /* Initialize CRAM used to read the status register */ cram = cram->cram$l_flink; cram->cram$v_der = 1; ucb->ucb$ps_cram_lps = cram; ioc$cram_cmd (CRAMCMD$K_RDBYTE32, isa_io_addr+LR_ISA_LPS, adp, cram, 0); /* Initialize CRAM used to write the control register */ cram = cram->cram$l_flink; cram->cram$v_der = 1; ucb->ucb$ps_cram_lcw = cram; ioc$cram_cmd (CRAMCMD$K_WTBYTE32, isa_io_addr+LR_ISA_LCW, adp, cram, 0); } /* Enable interrupts */ status = ioc$node_function (ucb->ucb$r_ucb.ucb$l_crb, IOC$K_ENABLE_INTR); if ( ! $VMS_STATUS_SUCCESS(status) ) return status; /* Set the INIT_OFF bit in the port control register. The INIT signal is * asserted as long as INIT_OFF is clear. Note byte-lane shift if ISA * option. */ if (ucb->ucb$l_lr_jensen) device_data = LPC_M_INIT_OFF; else device_data = LPC_M_INIT_OFF << 16; ucb->ucb$ps_cram_lcw->cram$q_wdata = device_data; ioc$cram_io (ucb->ucb$ps_cram_lcw); /* Mark the device as "on line" and ready to accept I/O requests */ ucb->ucb$r_ucb.ucb$v_online = 1; return SS$_NORMAL; } /* * LR$SETMODE - FDT Routine for Set Mode and Set Characteristics * * Functional description: * * This routine is called by the FDT dispatcher in the $QIO system service * to process set mode and set characteristics functions. This FDT routine * completes the I/O request without sending it to the driver start I/O * routine. The user buffer address is contained in irp$q_qio_p1 ($QIO * P1 parameter) on input and will be treated as a 64-bit address. * * Since this is an upper-level FDT routine, this routine always returns * the SS$_FDT_COMPL status. The $QIO status that is to be returned to * the caller of the $QIO system service is returned indirectly by the * FDT completion routines (e. g. exe_std$abortio, exe_std$finishio) via * the FDT context structure. * * Calling convention: * * status = lr$setmode (irp, pcb, ucb, ccb) * * Input parameters: * * irp Pointer to I/O request packet * pcb Pointer process control block * ucb Pointer to unit control block * ccb Pointer to channel control block * * Output parameters: * * None. * * Return value: * * status SS$_FDT_COMPL * * Environment: * * Kernel mode, user process context, IPL 2. */ int lr$setmode (IRP *irp, PCB *pcb, LR_UCB *ucb, CCB *ccb) { /* Define a structure that corresponds to the layout of the caller's * set mode or set characteristics buffer and declare a local pointer * to a structure of this type. */ typedef struct { unsigned char devclass; unsigned char devtype; unsigned short devbufsiz; unsigned int devdepend; } SETMODE_BUF; #pragma __required_pointer_size __save #pragma __required_pointer_size __long /* Define a type for a 64-bit pointer to a SETMODE_BUF structure. */ typedef SETMODE_BUF *SETMODE_BUF_PQ; #pragma __required_pointer_size __restore /* This must be a pointer to a 64-bit address since it will be containing * the address of a user buffer which may be a 64-bit or a 32-bit value. */ SETMODE_BUF_PQ setmode_bufp; /* The caller passes the address of their setmode buffer in the $QIO P1 * parameter. */ setmode_bufp = (SETMODE_BUF_PQ) irp->irp$q_qio_p1; /* Assure that the caller's setmode buffer is readable by the caller. * If not, abort the I/O request now with an ACCVIO status and return * back to the FDT dispatcher in the $QIO system service. */ if (! ( __PAL_PROBER (setmode_bufp, sizeof(SETMODE_BUF)-1, irp->irp$b_rmod) )) return ( call_abortio (irp, pcb, (UCB *)ucb, SS$_ACCVIO) ); /* If function is SETCHAR then set dev class and type */ if (irp->irp$v_fcode == IO$_SETCHAR) { ucb->ucb$r_ucb.ucb$b_devclass = setmode_bufp->devclass; ucb->ucb$r_ucb.ucb$b_devtype = setmode_bufp->devtype; } /* Set the default buffer and device dependent characteristics */ ucb->ucb$r_ucb.ucb$w_devbufsiz = setmode_bufp->devbufsiz; ucb->ucb$r_ucb.ucb$l_devdepend = setmode_bufp->devdepend; /* Finish the IO; return SS$_FDT_COMPL to the FDT dispatcher in the $QIO * system service. */ return ( call_finishio (irp, (UCB *)ucb, SS$_NORMAL, 0) ); } /* * LR$WRITE - FDT Routine for Write Function Codes * * Functional description: * * This routine is called by the FDT dispatcher in the $QIO system service * to process write functions. This FDT routine validates the request, * allocates a buffered I/O packet, formats and copies the contents of the * user buffer into the buffered I/O packet, and queues the IRP to this * driver's start I/O routine. The user buffer address is contained in * irp$q_qio_p1 ($QIO P1 parameter) on input and will be treated as a * 64-bit address. * * When the IRP is successfully queued to the driver's start I/O routine, * irp$ps_bufio_pkt points to the buffered I/O packet, irp$l_boff is the * number of bytes that have been charged against the process, and irp$l_bcnt * is the actual count of data bytes in the buffered I/O packet that are * to be sent to the printer. Note that the contents of the irp$ps_bufio_pkt * and irp$l_boff cells must not be changed since I/O post processing will * use these to deallocate the buffer packet and to credit the process. * * Since this is an upper-level FDT routine, this routine always returns * the SS$_FDT_COMPL status. The $QIO status that is to be returned to * the caller of the $QIO system service is returned indirectly by the * FDT completion routines (e. g. exe_std$abortio, exe_std$qiodrvpkt) via * the FDT context structure. * * Calling convention: * * status = lr$write (irp, pcb, ucb, ccb) * * Input parameters: * * irp Pointer to I/O request packet * pcb Pointer process control block * ucb Pointer to unit control block * ccb Pointer to channel control block * * Output parameters: * * None. * * Return value: * * status SS$_FDT_COMPL * * Environment: * * Kernel mode, user process context, IPL 2. */ int lr$write (IRP *irp, PCB *pcb, LR_UCB *ucb, CCB *ccb) { CHAR_PQ qio_bufp; /* 64-bit pointer to caller's buffer */ int qio_buflen; /* Number of bytes in caller's buffer */ BUFIO *sys_bufp; /* Pointer to a system buffer packet */ int32 sys_buflen; /* Computed required system packet size */ int sys_bufspace; /* Actual space in system buffer for data */ char *sys_datap; /* Working pointer to next byte in sysbuf */ int pass_all; /* True if this is a "pass all" write */ int carcon_count; char carcon_char; int status; int tmp_status; FMT_DATA fmt_data; /* Formatting status information */ /* Get the pointer to the caller's buffer and the size of the caller's * buffer from the $QIO P1 and P2 parameters respectively. The caller's * buffer is treated as a 64-bit address although it may be a 32-bit * address. */ qio_bufp = (CHAR_PQ)irp->irp$q_qio_p1; qio_buflen = irp->irp$l_qio_p2; /* Assure that the caller has read access to this buffer to do a write * operation. If not, exe_std$writechk will abort the I/O request and * return the SS$_FDT_COMPL warning status. If this is the case, we must * return back to the FDT dispatcher in the $QIO system service. Note we * continue on even if the user buffer is zero length since there may be * carriage control to output. */ if (qio_buflen != 0) { status = exe_std$writechk (irp, pcb, &(ucb->ucb$r_ucb), qio_bufp, qio_buflen); if ( ! $VMS_STATUS_SUCCESS(status) ) return status; } /* Start out assuming that the required system buffer packet size is * the size of the $QIO buffer plus the size of the 64-bit buffer * packet header. */ sys_buflen = qio_buflen + BUFIO$K_HDRLEN64; /* This is a "pass all" request either if the write physical function * was specified or if the device is set to "write pass all" mode. */ pass_all = irp->irp$v_func == IO$_WRITEPBLK || (ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_PASSALL); /* If this is not a "pass all" request, then interpret the $QIO P4 * carriage control parameter. Adjust the required system buffer packet * size by the prefix and suffix counts plus room of data expansion. * Currently, the only expansion possible is an extra CR in the prefix * and suffix characters if "new line" was specified. */ if (pass_all) { /* Allocate a system buffer for the data in the user buffer. If this * fails then abort the I/O request and return back to the FDT dispatcher * in the $QIO system service. Otherwise, exe_std$alloc_bufio_64 will * point irp$ps_bufio_pkt (overlays irp$l_svapte) to the bufio packet * and irp$l_boff to the number of bytes charged. */ status = exe_std$alloc_bufio_64(irp, pcb, (VOID_PQ) qio_bufp, /* user buffer */ sys_buflen); /* buff size plus header */ if ( ! $VMS_STATUS_SUCCESS(status) ) return ( call_abortio (irp, pcb, (UCB *)ucb, status) ); /* sys_bufp points to the bufio header packet. */ sys_bufp = irp->irp$ps_bufio_pkt; /* sys_datap points to the first free data byte in the buffer packet. */ sys_datap = sys_bufp->bufio$ps_pktdata; /* Copy the contents of the user buffer to the bufio data area. */ memcpy (sys_datap, qio_bufp, qio_buflen); irp->irp$l_bcnt = qio_buflen; } else { /* These next steps only need to be done once before formatting the * buffer. */ irp->irp$l_iost2 = irp->irp$l_qio_p4; exe_std$carriage (irp); sys_buflen += ((CARCON *) &irp->irp$b_carcon)->prefix_count + ((CARCON *) &irp->irp$b_carcon)->suffix_count; /* When we format the buffer it is possible that the buffer we allocate * will not be large enough. So we allocate a system buffer and try to * format users buffer into it. If it does not fit we will deallocate * the buffer and return the quota and try a larger buffer. If it fits * we will update the row and column data and drop out of the format * loop. */ fmt_data.page_length = (int) ucb->ucb$r_ucb.ucb$b_vertsz; fmt_data.page_width = (int) ucb->ucb$r_ucb.ucb$w_devbufsiz; do { sys_buflen += DATA_EXPND_CUSHION; status = exe_std$alloc_bufio_64(irp, pcb, (VOID_PQ) qio_bufp, /* user buffer */ sys_buflen);/* buf siz plus header */ if ( ! $VMS_STATUS_SUCCESS(status) ) return ( call_abortio (irp, pcb, (UCB *)ucb, status) ); /* sys_bufp points to the bufio header packet. */ sys_bufp = irp->irp$ps_bufio_pkt; /* sys_buflen is the number of bytes charged by alloc_bufio. */ sys_buflen = sys_bufp->bufio$w_size; fmt_data.cr_pend = ucb->ucb$l_lr_cr_pend; fmt_data.sys_datap = (char *) sys_bufp->bufio$ps_pktdata; fmt_data.buffer_space = sys_buflen - BUFIO$K_HDRLEN64; fmt_data.column_pos = ucb->ucb$l_lr_cursor; fmt_data.line_on_page = ucb->ucb$l_lr_lincnt; fmt_data.total_lines = 0; fmt_data.total_bytes = 0; /* Expand the prefix carriage control into the allocated system * buffer. If the carriage control count is non-zero and the * carriage control character is 0, this means "new line." Output * an initial CR, then the counted number of LFs. */ carcon_count = ((CARCON *) &irp->irp$b_carcon)->prefix_count; if (carcon_count != 0) { carcon_char = ((CARCON *) &irp->irp$b_carcon)->prefix_char; if (carcon_char == 0) { status = lr$format_char (ucb, CR, &fmt_data); carcon_char = LF; } while ((status & SS$_NORMAL) && (carcon_count > 0)) { status = lr$format_char(ucb, carcon_char, &fmt_data); carcon_count -= 1; } } /* If no error so far then format the users buffer */ carcon_count = 0; while ((status & SS$_NORMAL) && (carcon_count < qio_buflen)) { status = lr$format_char(ucb, qio_bufp[carcon_count], &fmt_data); carcon_count += 1; } /* Expand the suffix carriage control into the allocated system * buffer. If the carriage control count is non-zero and the * carriage control character is 0, this means "new line." Output * an initial CR, then the counted number of LFs. */ carcon_count = ((CARCON *) &irp->irp$b_carcon)->suffix_count; if ((carcon_count != 0) && (status & SS$_NORMAL)) { carcon_char = ((CARCON *) &irp->irp$b_carcon)->suffix_char; if (carcon_char == 0) { status = lr$format_char(ucb, CR, &fmt_data); carcon_char = LF; } while ((status & SS$_NORMAL) && (carcon_count > 0)) { status = lr$format_char(ucb, carcon_char, &fmt_data); carcon_count -= 1; } } /* If an error has occured then we need to delete the buffer so * we can try to get a larger buffer and try to format it once * again. */ if (!($VMS_STATUS_SUCCESS(status))) { exe_std$credit_bytcnt(irp->irp$l_boff, pcb); irp->irp$ps_bufio_pkt = (void *) 0; irp->irp$l_boff = 0; tmp_status = exe_std$deanonpaged((void *)sys_bufp); } } while (! $VMS_STATUS_SUCCESS(status)); ucb->ucb$l_lr_cr_pend = fmt_data.cr_pend; ucb->ucb$l_lr_cursor = fmt_data.column_pos; ucb->ucb$l_lr_lincnt = fmt_data.line_on_page; irp->irp$l_iost2 = fmt_data.total_lines; irp->irp$l_bcnt = fmt_data.total_bytes; } /* If characters to be output Queue this I/O request to the start I/O * routine and return SS$_FDT_COMPL back to the FDT dispatcher in the * $QIO system service. If not then just finish the request, it is * possible that there will be no output if the printer is set to truncate * and we are already at the right margin when a new output is started. */ if (irp->irp$l_bcnt) { return ( call_qiodrvpkt (irp, (UCB *)ucb) ); } else { return ( call_finishio (irp, (UCB *)ucb, SS$_NORMAL, 0) ); } } /* * LR$FORMAT_CHAR - This routine is used to format users data * * Functional description: * * * This routine determines if any special action needs to be taken based * on what the character is and how the printer port is configured. * Additionally, it handles truncating output or wrapping output, as well * as tabs, line feeds, form feeds, and carriage return. * * Calling convention: * * status = lr$format_char (ucb, out_char, fmt_data) * * Input parameters: * * ucb Pointer to UCB for this device * out_char Character to be output * fmt_data Data structure with a variety of data * * Output parameters: * * none * * Return value: * * status SS$NORMAL - Buffer filled with no problem * SS$_TOOMUCHDATA - Buffer to small could not format all the data * * Environment: * * Kernel mode, user process context, IPL 2. */ int lr$format_char (LR_UCB *ucb, unsigned char out_char, FMT_DATA *fmt_data) { unsigned char tmp_char; int char_mask; /* Bit in array segment for this character */ int fill_chars; /* Number filler chracters needed */ int i; /* temporary counter */ int index; /* Index into array of character characteristics */ int status = SS$_NORMAL; int tab_stop; /* Next tab stop position */ if (fmt_data->cr_pend) { fmt_data->cr_pend = FALSE; if ((out_char != FF) && (out_char != VT) && (out_char < DEL)) { tmp_char = out_char; fmt_data->column_pos = 0; if (fmt_data->total_bytes++ < fmt_data->buffer_space) { *fmt_data->sys_datap++ = CR; status = lr$format_char(ucb, tmp_char, fmt_data); return(status); } else return(SS$_TOOMUCHDATA); } } /* Compute character array index and bit position. This is done to make it easy to see if character is considerd a control character or something that can be upcased */ index = (int) out_char/32; char_mask = 1 << (int) out_char%32; if (CTRL_TABLE[index] & char_mask) { if ((out_char >= DEL) && (!(ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_PRINTALL))) { return (SS$_NORMAL); /* Drop character */ } else if (out_char == CR) /* CR */ { if (!(ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_CR)) { fmt_data->cr_pend = TRUE; return (SS$_NORMAL); } else { if (fmt_data->total_bytes++ < fmt_data->buffer_space) { fmt_data->column_pos = 0; *fmt_data->sys_datap++ = CR; return(SS$_NORMAL); } else return(SS$_TOOMUCHDATA); } } else if (out_char == HT) /* TAB */ { if (!(ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_TAB)) { tab_stop = (fmt_data->column_pos + 8) & ~7; fill_chars = tab_stop - fmt_data->column_pos; i = 0; while ((status & SS$_NORMAL) && (i < fill_chars)) { status = lr$format_char(ucb, SP, fmt_data); i += 1; } return (status); } } else if (out_char == VT) /* VT */ { if (!(ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_PRINTALL)) { return (SS$_NORMAL); /* Drop character */ } } else if (out_char == FF) /* FF */ { fill_chars = fmt_data->page_length - fmt_data->line_on_page; if (ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_MECHFORM) { fmt_data->total_lines = fmt_data->total_lines + fill_chars; fmt_data->line_on_page = 0; } else { i = 0; while ((status & SS$_NORMAL) && (i < fill_chars)) { status = lr$format_char(ucb, LF, fmt_data); i += 1; } return (status); } } else if (out_char == LF) /* LF */ { if (fmt_data->total_bytes++ < fmt_data->buffer_space) { *fmt_data->sys_datap++ = LF; fmt_data->line_on_page += 1; fmt_data->total_lines += 1; fmt_data->column_pos = 0; if (fmt_data->line_on_page >= fmt_data->page_length) { fmt_data->line_on_page = 0; } return(SS$_NORMAL); } else return(SS$_TOOMUCHDATA); } else /* Other control chars */ { if (!(ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_PRINTALL)) { return (SS$_NORMAL); /* Drop character */ } } } else if (!(ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_LOWER)) { if (CASE_TABLE[index] & char_mask) /* Character is lower case */ { out_char = out_char - SP; } } /* If here we have a character to output see if room to do so. If space and if FALLBACK is set then translate it */ if (fmt_data->column_pos > fmt_data->page_width) { if ((ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_TRUNCATE) && (!(ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_WRAP))) { return (SS$_NORMAL); } else { status= lr$format_char(ucb, CR, fmt_data); if (status & SS$_NORMAL) { status= lr$format_char(ucb, LF, fmt_data); if (!(status & SS$_NORMAL)) return (status); } } } fmt_data->column_pos +=1; if (fmt_data->total_bytes++ < fmt_data->buffer_space) { if (!(ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_FALLBACK)) { *fmt_data->sys_datap++ = out_char; } else { *fmt_data->sys_datap++ = TRANS_TABLE[out_char]; } return (SS$_NORMAL); } else { return (SS$_TOOMUCHDATA); } } /* * LR$STARTIO - Start I/O Routine * * Functional description: * * This routine is the driver start I/O routine. This routine is called * by ioc_std$initiate to process the next I/O request that has been * queued to this device. For this driver, the only function that is * passed to the start I/O routine is a write operation. * * Before this routine is called, ucb$v_cancel, ucb$v_int, ucb$v_tim, and * ucb$v_timout are cleared. The ucb$l_svapte, ucb$l_boff, and ucb$l_bcnt * cells are set in ioc_std$initiate from their corresponding IRP cells. * Unlike their IRP counterparts, these UCB cells are working storage and * can be changed by a driver. This driver uses ucb$l_svapte to point to * the next byte to output in the system buffer packet, and irp$l_bcnt to * keep the count of the remaining bytes to output. * * This routine acquires the device lock and raises IPL to device IPL. * The device lock is restored and the original IPL is restored via wfikpch * before this routine returns to its caller. * * Calling convention: * * lr$startio (irp, ucb) * * Input parameters: * * irp Pointer to I/O request packet * ucb Pointer to unit control block * * Output parameters: * * None. * * Return value: * * None. * * Environment: * * Kernel mode, system context, fork IPL, fork lock held. */ void lr$startio (IRP *irp, LR_UCB *ucb) { extern unsigned __int64 EXE$GQ_SYSTIME; unsigned __int64 bin_time; int orig_ipl; /* Adjust ucb$l_svapte such that it points to the start of the data in * the system buffer packet. */ ucb->ucb$r_ucb.ucb$l_svapte = (char *) ucb->ucb$r_ucb.ucb$l_svapte + BUFIO$K_HDRLEN64; if (!(ucb->ucb$r_ucb.ucb$l_devdepend & LP$M_POLLED)) { /* Acquire the device lock, raise IPL, saving original IPL */ device_lock (ucb->ucb$r_ucb.ucb$l_dlck, RAISE_IPL, &orig_ipl); /* Send the first character to the device. We can ignore the status, * since we will timeout if the device is not ready. */ lr$send_char_dev (ucb); /* Set up a wait for the completion of the I/O by using the wfikpch macro. * Wfikpch will restore the device lock and restore IPL. When output of * the entire buffer has been completed, the lr$interrupt routine will * queue the lr$iodone_fork routine. If the I/O does not complete within * LR_WFI_TMO seconds, then exe$timeout will call lr$wfi_timeout. */ wfikpch (lr$iodone_fork, lr$wfi_timeout, irp, 0, ucb, LR_WFI_TMO, orig_ipl); } else { /* Start the timer ticking */ if (ucb->ucb$l_tqe.tqe$q_fr3 == 0) { ucb->ucb$l_tqe.tqe$b_rqtype = TQE$M_REPEAT | TQE$C_SSREPT; ucb->ucb$l_tqe.tqe$q_fr3 = 1; ucb->ucb$l_tqe.tqe$q_fr4 = (__int64) ucb; ucb->ucb$l_tqe.tqe$l_fpc = (int) lr$timer_int; ucb->ucb$l_tqe.tqe$q_delta = 100000; bin_time = EXE$GQ_SYSTIME + ucb->ucb$l_tqe.tqe$q_delta; exe_std$instimq((int) (bin_time & 0xffffffff), (int) ((bin_time >> 32) & 0xffffffff), (TQE *) &ucb->ucb$l_tqe); } device_lock (ucb->ucb$r_ucb.ucb$l_dlck, RAISE_IPL, &orig_ipl); /* Send the first character to the device. We can ignore the status, * since we will timeout if the device is not ready. */ lr$send_char_dev (ucb); /* Set up a wait for the completion of the I/O by using the wfikpch macro. * Wfikpch will restore the device lock and restore IPL. When output of * the entire buffer has been completed, the lr$interrupt routine will * queue the lr$iodone_fork routine. If the I/O does not complete within * LR_WFI_TMO seconds, then exe$timeout will call lr$wfi_timeout. */ wfikpch (lr$iodone_fork, lr$wfi_timeout, irp, 0, ucb, LR_WFI_TMO, orig_ipl); } return; } /* * LR$SEND_CHAR_DEV - Send Character to the Device * * Functional description: * * This routine sends the next character from the system buffer to the * device via the printer write data register. This routine decrements the * count of remaining bytes (ucb$l_bcnt) and advances the pointer to the * next character (ucb$l_svapte). (ucb$l_svapte was made to point to the * bufio data packet area in LR$STARTIO by adding the header length to the * original bufio header pointer.) * * This is an internal routine that is used by the start I/O, interrupt * service, and periodic check device ready routines. * * Calling convention: * * status = lr$send_char_dev (ucb) * * Input parameters: * * ucb Pointer to unit control block * * Output parameters: * * None. * * Return value: * * status SS$_NORMAL if the next data byte was sent to the printer * device. * SS$_DEVOFFLINE if the next data byte was not sent to the * printer device since it is not ready to accept * data. * * Environment: * * Kernel mode, system context, device IPL, device lock held. */ static int lr$send_char_dev (LR_UCB *ucb) { int device_data; /* Data from or for CRAM */ char *sys_datap; /* Pointer to next byte in buffer packet */ /* Set the Port Control Register. * Set the INIT_OFF bit to disable the "INIT" signal. Set the IRQ_EN bit * to enable interrupts. Assure that the STROBE bit is clear so that we * can cause a 0-to-1 transition after loading the data register. Assure * that the DIR_READ bit is clear since we are doing writes to the data * register. Note byte-lane shift if ISA option. */ if (ucb->ucb$l_lr_jensen) device_data = LPC_M_INIT_OFF | LPC_M_IRQ_EN; else device_data = (LPC_M_INIT_OFF | LPC_M_IRQ_EN) << 16; ucb->ucb$ps_cram_lcw->cram$q_wdata = device_data; ioc$cram_io (ucb->ucb$ps_cram_lcw); /* Read the port status register. Note byte-lane shift if ISA option. */ ioc$cram_io (ucb->ucb$ps_cram_lps); device_data = ucb->ucb$ps_cram_lps->cram$q_rdata; if ( ! ucb->ucb$l_lr_jensen) device_data >>= 8; /* If the device is not ready to accept a character, then do not attempt * to send it. Return an error status. */ if ( ((LPS *) &device_data)->lps_paperout || /* paper out */ ! ((LPS *) &device_data)->lps_ok || /* not ok, i.e. error */ ! ((LPS *) &device_data)->lps_online || /* not online */ ! ((LPS *) &device_data)->lps_ready ) /* not ready */ return SS$_DEVOFFLINE; /* The device is ready. Load the data byte. Update ucb$l_svapte to * point to the next byte and decrement the count of bytes lef in * ucb$l_bcnt. Note that no byte-lane shift is necessary for this register. */ sys_datap = (char *) ucb->ucb$r_ucb.ucb$l_svapte; device_data = *sys_datap++; ucb->ucb$r_ucb.ucb$l_svapte = (void *) sys_datap; ucb->ucb$r_ucb.ucb$l_bcnt--; ucb->ucb$ps_cram_lwd->cram$q_wdata = device_data; ioc$cram_io (ucb->ucb$ps_cram_lwd); /* Latch the data byte to the printer. * Because some printers trigger on the 0 to 1 transistion of STROBE and * other trigger on the 1 to 0 transitions we have to write to the line * control register twice. INIT_OFF and IRQ_EN were set earlier and are * kept set. DIR_READ is kept clear. Note byte-lane shift if ISA option. */ if (ucb->ucb$l_lr_jensen) device_data = LPC_M_INIT_OFF | LPC_M_IRQ_EN | LPC_M_STROBE; else device_data = (LPC_M_INIT_OFF | LPC_M_IRQ_EN | LPC_M_STROBE) << 16; ucb->ucb$ps_cram_lcw->cram$q_wdata = device_data; ioc$cram_io (ucb->ucb$ps_cram_lcw); if (ucb->ucb$l_lr_jensen) device_data = LPC_M_INIT_OFF | LPC_M_IRQ_EN; else device_data = (LPC_M_INIT_OFF | LPC_M_IRQ_EN) << 16; ucb->ucb$ps_cram_lcw->cram$q_wdata = device_data; ioc$cram_io (ucb->ucb$ps_cram_lcw); /* Data byte sent. Return success. */ return SS$_NORMAL; } /* * LR$INTERRUPT - Interrupt Service Routine * * Functional description: * * This is the interrupt service routine for the parallel line printer * port. This routine is called by the system interrupt dispatcher. * * This routine will attempt to send the next character to the device * until either there are no more characters left or the I/O is canceled. * At which point, this routine will queue the lr$iodone_fork routine * which was set up either in lr$startio or lr$check_ready_fork. * * If the interrupt is not expected by an active I/O on this device then * it is simply dismissed. * * * Calling convention: * * lr$interrupt (idb) * * Input parameters: * * idb Pointer to interrupt dispatch block * * Output parameters: * * None. * * Return value: * * None. * * Environment: * * Kernel mode, system context, device IPL. * */ void lr$interrupt (IDB *idb) { LR_UCB *ucb; int device_data; /* Data from or for CRAM */ int status; /* Get the UCB from the IDB owner field which was set up by the lr$unit_init * routine. */ ucb = (LR_UCB *) idb->idb$ps_owner; /* Acquire the device lock. We are already at device IPL */ device_lock (ucb->ucb$r_ucb.ucb$l_dlck, NORAISE_IPL, NOSAVE_IPL); /* If interrupt is expected, then process it, otherwise ignore it */ if (ucb->ucb$r_ucb.ucb$v_int) { /* If there are characters left and the I/O has not been cancelled * then attempt to send the next character. There is no need to check * the status since the interrupt timeout will expire if the device is * not ready. Otherwise, queue the I/O done fork routine that was * setup via wfikpch. */ if (ucb->ucb$r_ucb.ucb$l_bcnt > 0 && ! ucb->ucb$r_ucb.ucb$v_cancel) { lr$send_char_dev (ucb); } else { ucb->ucb$r_ucb.ucb$v_int = 0; ucb->ucb$r_ucb.ucb$v_tim = 0; exe_std$queue_fork( (FKB *)ucb ); } } /* Restore the device lock, stay at device IPL */ device_unlock (ucb->ucb$r_ucb.ucb$l_dlck, NOLOWER_IPL, SMP_RESTORE); /* return back to interrupt dispatcher */ return; } /* * LR$IODONE_FORK - I/O Completion Fork Routine * * Functional description: * * This is the fork routine which passes the current I/O request on to * I/O postprocessing. This routine is queued by the interrupt service * routine when the I/O request has been completed. This routine can also * be called directly from lr$check_ready_fork if the I/O request is * cancelled while it is stalled due to an offline condition. * * Calling convention: * * lr$iodone_fork (irp, not_used, ucb) * * Input parameters: * * irp Pointer to I/O request packet * not_used Unused fork routine parameter fr4 * ucb Pointer to unit control block * * Output parameters: * * None. * * Return value: * * None. * * Environment: * * Kernel mode, system context, fork IPL, fork lock held. */ void lr$iodone_fork (IRP *irp, void *not_used, LR_UCB *ucb) { int status = SS$_NORMAL; /* Assume everything went ok */ /* If the request was cancelled or timed out of its own accord then * set the status accordingly. */ if (ucb->ucb$r_ucb.ucb$v_cancel) { status = SS$_ABORT; } else if (ucb->ucb$r_ucb.ucb$v_timout) { status = SS$_TIMEOUT; } /* Send this I/O request to I/O post processing */ ioc_std$reqcom (status, 0, &(ucb->ucb$r_ucb)); return; } /* * LR$WFI_TIMEOUT - Wait-for-interrupt timeout routine * * Functional description: * * This routine is the wait-for-interrupt timeout routine. It is called * by exe$timeout when an operation set up by wfikpch takes more that the * specified number of seconds. * * This routine queues a fork routine, lr$check_ready_fork, to handle * periodic checking of the readiness of the device to resume output and * to issue periodic "device offline" messages via OPCOM. * * Calling convention: * * lr$wfi_timeout (irp, not_used, ucb) * * Input parameters: * * irp Pointer to I/O request packet * not_used Unused fork routine parameter fr4 * ucb Pointer to unit control block * * Output parameters: * * None. * * Return value: * * None. * * Environment: * * Kernel mode, system context, device IPL, fork lock held, device lock held. * */ void lr$wfi_timeout (IRP *irp, void *not_used, LR_UCB *ucb) { /* A wait-for-interrupt has timed out. Count the device as having been * offline for the duration of the wait-for-interrupt interval. */ ucb->ucb$l_lr_oflcnt = LR_WFI_TMO; /* Queue a fork-wait thread that checks once a second for the device being * ready to accept data. One reason for deferring this work to fork level * is that exe_std$sndevmsg cannot be called at device IPL. */ fork_wait (lr$check_ready_fork, irp, 0, ucb); return; } /* * LR$CHECK_READY_FORK - Periodic Check for Device Ready * * Functional description: * * This routine performs a once-a-second check of the readiness of the * device to resume output. While the device remains offline this fork * routine reschedules itself via the fork wait queue. When the device * is ready to resume, the next character is sent and the remainder of * the output is done by the interrupt service routine. * * If the device remains offline for ucb$l_lr_msg_tmo seconds (initially * set to LR_OFFLINE_TMO) then a "device offline" message is sent to * OPCOM. The device offline message interval is doubled each time while * it is less than an hour. When the device becomes ready again, the offline * message interval is reset to its initial LR_OFFLINE_TMO value. * * Calling convention: * * lr$check_ready_fork (irp, not_used, ucb) * * Input parameters: * * irp Pointer to I/O request packet * not_used Unused fork routine parameter fr4 * ucb Pointer to unit control block * * Output parameters: * * None. * * Return value: * * None. * * Environment: * * Kernel mode, system context, fork IPL, fork lock held. */ void lr$check_ready_fork (IRP *irp, void *not_used, LR_UCB *ucb) { int orig_ipl; int status; /* If the I/O request has been canceled while we've been waiting or there * are no more characters to send to the device then call our I/O done fork * routine directly to complete the I/O request and then return from this * routine. */ if (ucb->ucb$r_ucb.ucb$v_cancel || ucb->ucb$r_ucb.ucb$l_bcnt == 0) { lr$iodone_fork (irp, 0, ucb); return; } /* Acquire the device lock, raise IPL, saving original IPL */ device_lock (ucb->ucb$r_ucb.ucb$l_dlck, RAISE_IPL, &orig_ipl); /* Attempt to send the next character to the device. If the device is * still not ready, then the character will not be sent and an error status * will be returned. */ status = lr$send_char_dev (ucb); /* If we successfully sent a character to the device then we're back in * business. Set up a wait for the completion of the I/O via wfikpch * just like our start I/O routine. Wfikpch will restore the device lock * and restore IPL. But first, clear the offline count and set the offline * message interval to its initial value. And, return from this routine. */ if ( $VMS_STATUS_SUCCESS(status) ) { ucb->ucb$l_lr_msg_tmo = LR_OFFLINE_TMO; ucb->ucb$l_lr_oflcnt = 0; wfikpch (lr$iodone_fork, lr$wfi_timeout, irp, 0, ucb, LR_WFI_TMO, orig_ipl); return; } /* Otherwise, the device is still offline. Increment the offline time. */ ucb->ucb$l_lr_oflcnt++; /* Restore the device lock, return to the original entry IPL */ device_unlock (ucb->ucb$r_ucb.ucb$l_dlck, orig_ipl, SMP_RESTORE); /* If the offline count has reached the "device offline" message interval * then it's time to send it to OPCOM and start a new offline interval. * If this message interval was less than an hour, double the next one. */ if (ucb->ucb$l_lr_oflcnt >= ucb->ucb$l_lr_msg_tmo) { extern MB_UCB *sys$ar_oprmbx; /* Pointer to OPCOM mbx ucb */ exe_std$sndevmsg (sys$ar_oprmbx, MSG$_DEVOFFLIN, &(ucb->ucb$r_ucb)); ucb->ucb$l_lr_oflcnt = 0; if (ucb->ucb$l_lr_msg_tmo < ONE_HOUR) ucb->ucb$l_lr_msg_tmo *= 2; } /* Setup to check the device again in one second via the fork-wait queue */ fork_wait (lr$check_ready_fork, irp, 0, ucb); return; } /* * LR$TIMER_INT Periodic Check for Device Ready * * Functional description: * * This routine is called once ever timer tick to see if we can send out * any data. It basically simulates an interrupt. * * Calling convention: * * lr$timer_int (irp, ucb, tqe) * * Input parameters: * * irp Pointer to I/O request packet * ucb Pointer to unit control block * tqe Poitner to TQE used to time request * * Output parameters: * * None. * * Return value: * * None. * * Environment: * * Kernel mode, system context, at timer IPL. */ void lr$timer_int (void *fr3, LR_UCB *ucb, TQE *tqe) { int orig_fipl; int orig_ipl; int status; CRAM *cram; fork_lock (ucb->ucb$r_ucb.ucb$b_flck, &orig_fipl); device_lock (ucb->ucb$r_ucb.ucb$l_dlck, RAISE_IPL, &orig_ipl); cram = ucb->ucb$ps_cram_lwd; lr$interrupt(cram->cram$l_idb); /* If all done cancel the timer */ if (! (ucb->ucb$r_ucb.ucb$v_bsy)) { tqe->tqe$b_rqtype = 0; tqe->tqe$q_fr3 = 0; } device_unlock (ucb->ucb$r_ucb.ucb$l_dlck, orig_ipl, SMP_RESTORE); fork_unlock (ucb->ucb$r_ucb.ucb$b_flck, orig_fipl, SMP_RESTORE); return; }