#include "insignia.h" #include "host_def.h" #ifdef PRINTER /* * VPC-XT Revision 1.0 * * Title: Parallel Printer Port Emulation * * Description: Emulates the IBM || Printer card as used in the original * IBM XT, which is itself a H/W emulation of an Intel 8255. * * Author: Henry Nash * * Notes: None * * Mods: * * Allow transition to NOTBUSY in the OUTA state as well as the READY * state. i.e. at the leading edge of the ACK pulse after just one * INB (STATUS) rather than two. Our printer port emulation relies on * these INB's to toggle the ACK line and set NOTBUSY true again. So * the port could be left in the BUSY condition at the end of an app's * print job (which can confuse the next print request). NOTE we could * still have a problem if the PC app bypasses the BIOS and is too lazy * to do even one INB(STATUS) after the last print byte. */ /* for NTVDM port -- williamh * There are such things called Dongles which many software companies have * used for copy protection. Each software comes with its dedicated Dongle * that records necessary indentification inforamtion. It is required * to plug the device onto the parallel port in order to run the software * correctly. The device has an outlet which can be connectted to parallel * port printer so the the user doesn't sacrifice his parallel port when * the device in plugged on the original connector. * There are several Dongle vendors and each one of them provides their * propietary library or driver for the applications to link to. These * drivers know how to read/WRITE the Dongle to verify a legitmate copy. * Since it has to maintain compatiblility with standard PC parallel port, * the devices are designed in a way that it can be programmed without * disturbing ordinary parallel port operation. To do this, it usually does * this: * (1) Turn off strobe. * (2) output data pattern to data port * (3) delay a little bit(looping in instructions) and then go to (2) * until the chunk of data has been sent. NOTE THAT THE STROBE LINE * IS NEVER "strobe" * (4). Read status port and by interpreting the line differently, * the driver decodes any id information it is looking for. * * In order to support these devices, we have to do these: * (1). We can not fake printer status. We have to get the real * status line states. * (2). we have to output data to the printer without waiting the data * to be qualified(strobing). * (3). We must be smart enough to detect the application is done with * its Dongle things and wants everything goes back to normal. * We must adjust ourselves under this circumestances. * (4). Down level printer driver must provide function that we can call * to control the port directly. * (5). Printer h/w interrupt is not allowed to be enabled under this * circumstance --and how can we make sure of that????? * */ #ifdef SCCSID static char SccsID[] = "@(#)printer.c 1.19 11/14/94 Copyright Insignia Solutions Ltd."; #endif #ifdef SEGMENTATION /* * The following #include specifies the code segment into which this * module will by placed by the MPW C compiler on the Mac II running * MultiFinder. */ #include "SOFTPC_PRINTER.seg" #endif /* * O/S include files. */ #include #include TypesH #include TimeH #ifdef SYSTEMV #ifdef STINGER #include #endif #endif /* * SoftPC include files */ #include "xt.h" #include CpuH #include "sas.h" #include "ios.h" #include "bios.h" #include "printer.h" #include "error.h" #include "config.h" #include "host_lpt.h" #include "ica.h" #include "quick_ev.h" #include "debug.h" #ifndef PROD #include "trace.h" #endif /* * ============================================================================ * Global data * ============================================================================ */ /* * ============================================================================ * Static data and defines * ============================================================================ */ #define PRINTER_BIT_MASK 0x3 /* bits decoded from address bus */ #define CONTROL_REG_MASK 0xE0; /* unused bits drift to HIGH */ #define STATUS_REG_MASK 0x07; /* unused bits drift to HIGH */ #define DATA_OFFSET 0 /* ouput register */ #define STATUS_OFFSET 1 /* status register */ #define CONTROL_OFFSET 2 /* control register */ #ifdef ERROR #undef ERROR #endif static half_word output_reg[NUM_PARALLEL_PORTS]; static half_word control_reg[NUM_PARALLEL_PORTS]; #define NOTBUSY 0x80 #define ACK 0x40 #define PEND 0x20 #define SELECT 0x10 #define ERROR 0x08 static half_word status_reg[NUM_PARALLEL_PORTS]; #define IRQ 0x10 #define SELECT_IN 0x08 #define INIT_P 0x04 #define AUTO_FEED 0x02 #define STROBE 0x01 LOCAL IU8 retryErrorCount = 0; /* num status inb before clearing ERROR */ static int state[NUM_PARALLEL_PORTS]; /* state control variable */ /* * set up arrays of all port addresses */ static io_addr port_start[] = {LPT1_PORT_START,LPT2_PORT_START,LPT3_PORT_START}; static io_addr port_end[] = {LPT1_PORT_END, LPT2_PORT_END, LPT3_PORT_END}; static int port_no[] = {LPT1_PORT_START & LPT_MASK, LPT2_PORT_START & LPT_MASK, LPT3_PORT_START & LPT_MASK }; static half_word lpt_adapter[] = {LPT1_ADAPTER, LPT2_ADAPTER, LPT3_ADAPTER}; static sys_addr port_address[] = {LPT1_PORT_ADDRESS, LPT2_PORT_ADDRESS, LPT3_PORT_ADDRESS}; static sys_addr timeout_address[] = {LPT1_TIMEOUT_ADDRESS, LPT2_TIMEOUT_ADDRESS, LPT3_TIMEOUT_ADDRESS}; static q_ev_handle handle_for_out_event[NUM_PARALLEL_PORTS]; static q_ev_handle handle_for_outa_event[NUM_PARALLEL_PORTS]; #if defined(NTVDM) && defined(MONITOR) /* sudeepb 24-Jan-1993 for printing performance for x86 */ sys_addr lp16BitPrtBuf; sys_addr lp16BitPrtId; sys_addr lp16BitPrtCount; sys_addr lp16BitPrtBusy; #endif #define STATE_READY 0 #define STATE_OUT 1 #define STATE_OUTA 2 #if defined(NTVDM) #define STATE_DATA 3 #define STATE_DONGLE 4 #endif /* * State transitions: * * +-> STATE_READY * | | * | | ........ write char to output_reg, print on low-high strobe * | V set NOTBUSY to false * | STATE_OUT * | | * | | ........ (read status) set ACK low * | V * | STATE_OUTA * | | * | | ........ (read status) set ACK high * +-----+ * * Caveat: if the control register interrupt request bit is set, * we assume that the application isn't interested in getting the * ACKs and just wants to know when the printer state changes back * to NOTBUSY. I'm not sure to want extent you can get away with * this: however, applications using the BIOS printer services * should be OK. */ #ifdef PS_FLUSHING LOCAL IBOOL psFlushEnabled[NUM_PARALLEL_PORTS]; /* TRUE if PostScript flushing is enabled */ #endif /* PS_FLUSHING */ /* * ============================================================================ * Internal functions & macros * ============================================================================ */ #define set_low(val, bit) val &= ~bit #define set_high(val, bit) val |= bit #define low_high(val1, val2, bit) (!(val1 & bit) && (val2 & bit)) #define high_low(val1, val2, bit) ((val1 & bit) && !(val2 & bit)) #define toggled(val1, val2, bit) ((val1 & bit) != (val2 & bit)) #define negate(val, bit) val ^= bit /* * Defines and variables to handle tables stored in 16-bit code for NT * monitors. */ #if defined(NTVDM) && defined(MONITOR) static BOOL intel_setup = FALSE; static sys_addr status_addr; static sys_addr control_addr; static sys_addr state_addr; #define get_status(adap) (sas_hw_at_no_check(status_addr+(adap))) #define set_status(adap,val) (sas_store_no_check(status_addr+(adap),(val))) #define get_control(adap) (sas_hw_at_no_check(control_addr+(adap))) #define set_control(adap,val) (sas_store_no_check(control_addr+(adap),(val))) #define get_state(adap) (sas_hw_at_no_check(state_addr+(adap))) #define set_state(adap,val) (sas_store_no_check(state_addr+(adap),(val))) #else /* NTVDM && MONITOR */ #define get_status(adap) (status_reg[adapter]) #define set_status(adap,val) (status_reg[adapter] = (val)) #define get_control(adap) (control_reg[adapter]) #define set_control(adap,val) (control_reg[adapter] = (val)) #define get_state(adap) (state[adapter]) #define set_state(adap,val) (state[adapter] = (val)) #endif /* NTVDM && MONITOR */ static void printer_inb IPT2(io_addr, port, half_word *, value); static void printer_outb IPT2(io_addr, port, half_word, value); static void notbusy_check IPT1(int,adapter); /* * ============================================================================ * External functions * ============================================================================ */ void printer_post IFN1(int,adapter) { /* * Set up BIOS data area. */ sas_storew(port_address[adapter],(word)port_start[adapter]); sas_store(timeout_address[adapter], (half_word)0x14 ); /* timeout */ } #if defined(NTVDM) && defined(MONITOR) static void lpr_state_outa_event IFN1(long,adapter) { set_status(adapter, get_status(adapter) | ACK); set_state(adapter, STATE_READY); } static void lpr_state_out_event IFN1(long,adapter) { set_status(adapter, get_status(adapter) & ~ACK); set_state(adapter, STATE_OUTA); handle_for_outa_event[adapter]=add_q_event_t(lpr_state_outa_event,HOST_PRINTER_DELAY,adapter); } #else /* NTVDM && MONITOR */ static void lpr_state_outa_event IFN1(long,adapter) { set_high(status_reg[adapter],ACK); state[adapter]=STATE_READY; } static void lpr_state_out_event IFN1(long,adapter) { set_low(status_reg[adapter], ACK); state[adapter]=STATE_OUTA; handle_for_outa_event[adapter]=add_q_event_t(lpr_state_outa_event,HOST_PRINTER_DELAY,adapter); } #endif /* NTVDM && MONITOR */ static void printer_inb IFN2(io_addr,port, half_word *,value) { int adapter, i; note_trace1(PRINTER_VERBOSE,"inb from printer port %#x ",port); /* ** Scan the ports to find out which one is used. NB the ** port must be valid one because we only used io_define_inb() ** for the valid ports */ for(i=0; i < NUM_PARALLEL_PORTS; i++) if((port & LPT_MASK) == port_no[i]) break; adapter = i % NUM_PARALLEL_PORTS; port = port & PRINTER_BIT_MASK; /* clear unused bits */ switch(port) { case DATA_OFFSET: *value = output_reg[adapter]; break; case STATUS_OFFSET: switch(get_state(adapter)) { #if defined(NTVDM) case STATE_DONGLE: /* read directly from the port for Dongle */ *value = host_read_printer_status_port(adapter); set_status(adapter, *value); break; case STATE_DATA: #endif case STATE_READY: notbusy_check(adapter); *value = get_status(adapter) | STATUS_REG_MASK; /* Clear ERROR as it will be set if we fail on the print. */ /* Clear after two inbs as DOS seems to require this. */ if (retryErrorCount > 0) retryErrorCount--; else set_status(adapter, get_status(adapter) | ERROR); break; case STATE_OUT: *value = get_status(adapter) | STATUS_REG_MASK; #ifndef DELAYED_INTS delete_q_event(handle_for_out_event[adapter]); lpr_state_out_event(adapter); #else set_low(status_reg[adapter], ACK); state[adapter] = STATE_OUTA; #endif /* DELAYED INTS */ break; case STATE_OUTA: notbusy_check(adapter); /* */ *value = get_status(adapter) | STATUS_REG_MASK; #ifndef DELAYED_INTS delete_q_event(handle_for_outa_event[adapter]); lpr_state_outa_event(adapter); #else set_high(status_reg[adapter], ACK); state[adapter] = STATE_READY; #endif break; default: note_trace1(PRINTER_VERBOSE, "", get_state(adapter)); break; } break; case CONTROL_OFFSET: *value = get_control(adapter) | CONTROL_REG_MASK; negate(*value, STROBE); negate(*value, AUTO_FEED); negate(*value, SELECT_IN); break; } note_trace3(PRINTER_VERBOSE, "", port, *value, get_state(adapter)); } static void printer_outb IFN2(io_addr,port, half_word,value) { int adapter, i; half_word old_control; #ifdef PC_CONFIG char variable_text[MAXPATHLEN]; int softpcerr; int severity; softpcerr = 0; severity = 0; #endif note_trace2(PRINTER_VERBOSE,"outb to printer port %#x with value %#x", port, value); /* ** Scan the ports to find out which one is used. NB the ** port must be valid one because we only used io_define_inb() ** for the valid ports */ for(i=0; i < NUM_PARALLEL_PORTS; i++) if((port & LPT_MASK) == port_no[i]) break; adapter = i % NUM_PARALLEL_PORTS; note_trace3(PRINTER_VERBOSE, "", port, value, get_state(adapter)); port = port & PRINTER_BIT_MASK; /* clear unused bits */ switch(get_state(adapter)) { #if defined(NTVDM) case STATE_DONGLE: if (port == DATA_OFFSET) { output_reg[adapter] = value; host_print_byte(adapter, value); break; } // fall through case STATE_DATA: if (port == DATA_OFFSET) { if (host_set_lpt_direct_access(adapter, TRUE)) { host_print_byte(adapter, output_reg[adapter]); host_print_byte(adapter, value); set_state(adapter, STATE_DONGLE); /* Write char to internal buffer */ output_reg[adapter] = value; } else { /* unable to open the lpt for direct access, mark the device busy */ #if !defined(MONITOR) set_low(status_reg[adapter], NOTBUSY); #else /* NTVDM && !MONITOR */ set_status(adapter, 0x7F); #endif } break; } // fall through #endif case STATE_OUT: case STATE_OUTA: case STATE_READY: switch(port) { case DATA_OFFSET: #if defined(NTVDM) set_state(adapter, STATE_DATA); #endif /* Write char to internal buffer */ output_reg[adapter] = value; break; case STATUS_OFFSET: /* Not possible */ break; case CONTROL_OFFSET: /* Write control bits */ old_control = get_control(adapter); /* Save old value to see what's changed */ set_control(adapter, value); if (low_high(old_control, value, INIT_P)) #ifdef PC_CONFIG /* this was a call to host_print_doc - */ host_reset_print(&softpcerr, &severity); if (softpcerr != 0) host_error(softpcerr, severity, variable_text); #else /* this was a call to host_print_doc - */ host_reset_print(adapter); #endif if (toggled(old_control, value, AUTO_FEED)) host_print_auto_feed(adapter, ((value & AUTO_FEED) != 0)); if (low_high(old_control, value, STROBE)) { #if defined(NTVDM) if (get_state(adapter) == STATE_DONGLE) { host_set_lpt_direct_access(adapter, FALSE); /* pass through to print out the last byte * which we have sent it out the data port * while we are in DONGLE state. */ set_state(adapter, STATE_READY); } #endif #ifdef PS_FLUSHING /* * If PostScript flushing is enabled for this * port then we flush on a Ctrl-D */ if ( psFlushEnabled[adapter] && output_reg[adapter] == 0x04 /* ^D */ ) { host_print_doc(adapter); } else { #endif /* PS_FLUSHING */ /* * Send the stored internal buffer to * the printer */ if(host_print_byte(adapter,output_reg[adapter]) == FALSE) { set_status(adapter, get_status(adapter) & ~ERROR); /* active Low */ /* NTVDM had here(?): set_status(adapter, ACK|PEND|SELECT|ERROR); */ /* two status inbs before we clear ERROR */ retryErrorCount = 2; } else { /* clear ERROR condition */ set_status(adapter, get_status(adapter) | ERROR); #if defined(NTVDM) && !defined(MONITOR) set_low(status_reg[adapter], NOTBUSY); #else /* NTVDM && !MONITOR */ set_status(adapter, get_status(adapter) & ~NOTBUSY); #endif /* NTVDM && !MONITOR */ set_state(adapter, STATE_OUT); #ifndef DELAYED_INTS handle_for_out_event[adapter]=add_q_event_t(lpr_state_out_event,HOST_PRINTER_DELAY,adapter); #endif /* DELAYED_INTS */ } #ifdef PS_FLUSHING } #endif /* PS_FLUSHING */ } else if (high_low(old_control, value, STROBE) && get_state(adapter) == STATE_OUT) { if (value & IRQ) { /* * Application is using * interrupts, so we can't * rely on INBs being * used to check the * printer status. */ set_state(adapter, STATE_READY); notbusy_check(adapter); } } #if defined(NTVDM) else if (low_high(old_control, value, IRQ) && get_state(adapter) == STATE_DONGLE) { host_set_lpt_direct_access(adapter, FALSE); set_state(adapter, STATE_READY); } #endif #ifndef PROD if (old_control & IRQ) note_trace1(PRINTER_VERBOSE, "Warning: LPT%d is being interrupt driven\n", number_for_adapter(adapter)); #endif break; } break; default: note_trace1(PRINTER_VERBOSE, "", get_state(adapter)); break; } } void printer_status_changed IFN1(int,adapter) { note_trace1(PRINTER_VERBOSE, "", adapter); /* check whether the printer has just changed state to NOTBUSY */ notbusy_check(adapter); } /* * ============================================================================ * Internal functions * ============================================================================ */ static void notbusy_check IFN1(int,adapter) { /* * This function is used to detect when the printer * state transition to NOTBUSY occurs. * * If the parallel port is being polled, the port * emulation will stop this transition occurring * until the application has detected the ACK * pulse. notbusy_check() is then called each time the * port status is read using the INB; when the host * says the printer is HOST_LPT_BUSY, the port status * returns to the NOTBUSY state. * * If the parallel port is interrupt driven, we cannot * rely on the application using the INB: so we first * check the host printer status immediately after * outputting the character. If the host printer isn't * HOST_LPT_BUSY, then we interrupt immediately; * otherwise, we rely on the printer_status_changed() * call to notify us of when HOST_LPT_BUSY is cleared. */ /* allow not busy at leading edge of ack pulse too */ if ( (get_state(adapter) == STATE_READY || #if defined(NTVDM) get_state(adapter) == STATE_DATA || #endif get_state(adapter) == STATE_OUTA) && !(get_status(adapter) & NOTBUSY) && !(host_lpt_status(adapter) & HOST_LPT_BUSY)) { #if defined(NTVDM) && !defined(MONITOR) set_high(status_reg[adapter], NOTBUSY); #else /* NTVDM && !MONITOR */ set_status(adapter, get_status(adapter) | NOTBUSY); #endif /* NTVDM && !MONITOR */ #ifndef PROD if (io_verbose & PRINTER_VERBOSE) fprintf(trace_file, "\n", adapter); #endif if (get_control(adapter) & IRQ) { ica_hw_interrupt(0, CPU_PRINTER_INT, 1); } } } #ifdef SEGMENTATION /* * The following #include specifies the code segment into which this * module will by placed by the MPW C compiler on the Mac II running * MultiFinder. */ #include "SOFTPC_INIT.seg" #endif /* ** Initialise the printer port required. */ void printer_init IFN1(int,adapter) { io_addr i; io_define_inb( lpt_adapter[adapter], printer_inb ); io_define_outb( lpt_adapter[adapter], printer_outb ); for(i = port_start[adapter]; i < port_end[adapter]; i++) io_connect_port(i,lpt_adapter[adapter],IO_READ_WRITE); #if defined(NTVDM) && defined(MONITOR) /* * If we know the addresses of the 16-bit variables write directly * to them, otherwise save the value until we do. */ if (intel_setup) { set_status(adapter, 0xDF); set_control(adapter, 0xEC); } else #endif /* NTVDM && MONITOR */ { control_reg[adapter] = 0xEC; status_reg[adapter] = 0xDF; } output_reg[adapter] = 0xAA; /* * The call to host_print_doc has been removed since it is * sensible to distinguish between a hard flush (on ctl-alt-del) * or menu reset and a soft flush under user control or at end * of PC application. The calls to host_lpt_close() followed * by host_lpt_open() should already cause a flush to occur, * so no functionality is lost. The first time printer_init is * called host_lpt_close() is not called, but this cannot * matter since host_print_doc() can only be a no-op. */ /* host_print_doc(adapter); */ host_print_auto_feed(adapter, FALSE); #if defined(NTVDM) && defined(MONITOR) if (intel_setup) set_state(adapter, STATE_READY); else #endif /* NTVDM && MONITOR */ state[adapter] = STATE_READY; } /* end of printer_init() */ #if defined(NTVDM) && defined(MONITOR) /* ** Store 16-bit address of status table and fill it with current values. */ #ifdef ANSI void printer_setup_table(sys_addr table_addr) #else /* ANSI */ void printer_setup_table(table_addr) sys_addr table_addr; #endif /* ANSI */ { int i; sys_addr lp16BufSize; unsigned int cbBuf; word lptBasePortAddr[NUM_PARALLEL_PORTS]; if (!intel_setup) { /* * Store the addresses of the tables resident in 16-bit code. These * are: * status register (NUM_PARALLEL_PORTS bytes) * state register (NUM_PARALLEL_PORTS bytes) * control register (NUM_PARALLEL_PORTS bytes) * host_lpt_status (NUM_PARALLEL_PORTS bytes) * * Then transfer any values which have already been set up into the * variables. This is in case printer_init has been called prior to * this function. */ status_addr = table_addr; state_addr = table_addr + NUM_PARALLEL_PORTS; control_addr = table_addr + 2 * NUM_PARALLEL_PORTS; for (i = 0; i < NUM_PARALLEL_PORTS; i++) { set_status(i, status_reg[i]); set_state(i, state[i]); set_control(i, control_reg[i]); lptBasePortAddr[i] = port_start[i]; } /* Let host know where host_lpt_status is stored in 16-bit code. */ host_printer_setup_table(table_addr, NUM_PARALLEL_PORTS, lptBasePortAddr); /* sudeepb 24-Jan-1993 for printing performance for x86 */ lp16BufSize = table_addr + 4 * NUM_PARALLEL_PORTS; cbBuf = (sas_w_at_no_check(lp16BufSize)); lp16BitPrtBuf = table_addr + (4 * NUM_PARALLEL_PORTS) + 2; lp16BitPrtId = lp16BitPrtBuf + cbBuf; lp16BitPrtCount = lp16BitPrtId + 1; lp16BitPrtBusy = lp16BitPrtCount + 2; intel_setup = TRUE; } } #endif /* NTVDM && MONITOR */ #endif #ifdef NTVDM void printer_is_being_closed(int adapter) { #if defined(MONITOR) set_state(adapter, STATE_READY); #else state[adapter] = STATE_READY; #endif } #endif #ifdef PS_FLUSHING /*( =========================== printer_psflush_change ============================ PURPOSE: Handle change of PostScript flush configuration option for a printer port. INPUT: hostID - Configuration item I.D. apply - TRUE if change to be applied OUTPUT: None ALGORITHM: If PostScript flushing is being enabled then; set the PostScript flush enable flag for the port; disable autoflush for the port; else; reset the PostScript flush enable flag for the port; enable autoflush for the port; =============================================================================== )*/ GLOBAL void printer_psflush_change IFN2( IU8, hostID, IBOOL, apply ) { IS32 adapter = hostID - C_LPT1_PSFLUSH; assert1(adapter < NUM_PARALLEL_PORTS,"Bad hostID %d",hostID); if ( apply ) if ( psFlushEnabled[adapter] = (IBOOL)config_inquire(hostID,NULL) ) host_lpt_disable_autoflush(adapter); else host_lpt_enable_autoflush(adapter); } #endif /* PS_FLUSHING */