/* IKON Corporation 2617 Western Ave. Seattle, WA 98121 (206) 728-6465 */ /* 28 November 1990 initial working version */ /* 2 January 1991 changed name looked for in identify routine from "ihcp" to IKON,ihcp" to conform to SUN's latest example of how things should be done. 29 January 1991 changed default dma and fifo timeout to 30 sec added minimum timeout test separate from default added ioctl commands for compatibility with SUN and Versatec VME drivers 4 February 1991 added additional ioctl commands for timeout set and register readback that should have been included in the 29 Jan stuff. 10 September 1991 changed the ihcp_ip argument in the adddma call to dev_intr_p->int_pri to correct error in sun's sample driver. 18 December 1991 changed from using local ihcp_minphys() to sytstem's minphys(). this is to avoid unnecessary limits on the maximum dma block size. added ability to break driver sleeps with signals to allow long timeouts without locking out the operators ability to terminate hung sleeps. the driver no longer initializes the board during open() and close() sequences. since a dmainit() call would flush the fifo, it was necessary to wait until the fifo was empty before doing the init. the problem was that the kernel called close() when the process was killed - or when the calling program exited - and the fifo wait would hang until timeout, since (apparently) the close() can't catch signals when called by the kernel in this way. it will be the responsibility of the calling program to wait for fifo empty and device ready before exiting if the application requires this. also fixed a bad bug. the code around the sleeps needs to be bracketed with interrupt protection to avoid a window between int enabling and calling the sleep. the bug would cause a sleep until timeout if the desired interrupt happened within the window. 7 January 1992 Added DELAY(1) macro before each return in interrupt code to try to allow enough time for the Sbus interrupt line to rise slowly to the off state before the kernel re-enables interrupts. This problem occurs because of the open-collector style interrupt lines with light pull-ups. The delay is distasteful, but since the interrupt routine is invoked only once per DVMA block transfer, the performance penalty in probably too small to measure. 21 February 1992 The earlier modifications to the driver's sleep code awakened on old bug that was inherited from the sample driver. The interrupt priority (already shifted for insertion into the cpu's control register) was stored in a u_char. Unfortunately, the necessary bits were in bits outside the range of a single byte!!!!! The result was that the START_CRITICAL routine didn't raise the int priority, and small dma transfers would cause the wakeup in the interrupt routine to be issued before we were actually sleeping. The result was a timeout. The fix was to make the interrupt_pri element of the unit structure a u_int. 27 February 1992 There turned out to be a problem in the interrupt code as well. We didn't do a flush in the int code to turn off the int pending bit. No problem unless someone else on the same level who was also later in the interrupt chain generated an interrupt. We would falsely call our routine from poll, and get trapped in an endless loop. The fix is to issue a flush_buffer in the int code, and in the timeout code as well. 13 April 1992 Added code to set the board(s) speed and mode to defaults defined in ihcpio.c. This allows a user to select a higher operating speed or different mode (plot, most likely) without having to make ioctl calls to the driver. This allows optimum speed when using cat or lpr, rather than customized application code. Note that the defaults apply to all cards handled by this driver. If different mode and/or speed settings are required for each board the driver will need to be modified on a board-by-board basis. The defaults are set at attach time. */ /************************************************************************ * * * This driver is provided at no charge to IKON's customers * * in the hope that it will assist them in understanding and * * using IKON's Sbus hardcopy interface products. This code * * is intended to be a working and (relatively!) bug free driver * * when running on the machine and OS rev available to IKON. * * IKON will attempt to keep this code running on current OS and * * hardware from SUN - and others - but does not guarantee this. * * The user is encouraged to contact IKON with comments, * * suggestions, and BUG REPORTS. * ************************************************************************/ /*--- much of the code and comments in this driver are left over from the sample driver provided by sun in the Sbus device drivers manual. WE DON'T CLAIM TO UNDERSTAND ALL OF IT!!!!!!! ---*/ /*================ INCLUDED FILES ===================================*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ihcpio.h" /*================ minor device number encoding ========================*/ #define IHCP_UNIT(dev) minor(dev) /*================ DEBUGGING SWITCHES ===================================*/ /* There are three levels of messages from the ihcp driver: IHCP_ERROR_MSG: Errors that should not happen IHCP_DEBUG_MSG: Status messages useful when debugging IHCP_TRACK_MSG: Path and data tracking through the driver When IHCP_DEBUG and IHCP_TRACK are not defined, the code compacts in the loadable case. The loadable case is recommended. */ #ifdef IHCP_TRACK # define IHCP_DEBUG #endif /*VARARGS0*/ static void no_message() {} /*VARARGS0*/ extern printf() ; #ifdef IHCP_DEBUG # define IHCP_ERROR_MSG log # define IHCP_DEBUG_MSG log #ifdef IHCP_TRACK # define IHCP_TRACK_MSG log #else IHCP_TRACK # define IHCP_TRACK_MSG no_message #endif IHCP_TRACK #else IHCP_DEBUG # define IHCP_ERROR_MSG log # define IHCP_DEBUG_MSG no_message # define IHCP_TRACK_MSG no_message #endif IHCP_DEBUG /*================ DATA STRUCTURES ===================================*/ struct dmaio_regs { u_long Dmacsr ; /* control and status register */ u_long Dmaar ; /* address register */ u_long Dmabcr ; /* byte count register */ u_long dma_unused ; /* unused register */ }; /* THERE IS PROBABLY A MORE ELEGANT WAY TO ACCOMPLISH THE FOLLOWING - BUT RATHER THAN WORRY ABOUT ALIGNMENT - WE WILL DO IT THE HARD WAY */ struct ihcp_regs { /* hardcopy working registers */ u_char Ihcp_lf ; /* latched functions */ u_char ihcp_unused0 ; /* pad to next reg */ u_char ihcp_unused1 ; u_char ihcp_unused2 ; u_char ihcp_unused3[12] ; /* 16 byte offset between regs */ u_char Ihcp_is_do ; /* interface status/data out */ u_char ihcp_unused4 ; u_char ihcp_unused5 ; u_char ihcp_unused6 ; u_char ihcp_unused7[12] ; u_char Ihcp_ds_co ; /* device status/command out */ u_char ihcp_unused8 ; u_char ihcp_unused9 ; u_char ihcp_unused10 ; u_char ihcp_unused11[12] ; u_char Ihcp_ri ; /* reset interrupt flag */ u_char ihcp_unused12 ; u_char ihcp_unused13 ; u_char ihcp_unused14 ; u_char ihcp_unused15[12] ; u_char Ihcp_sa ; /* software ack */ u_char ihcp_unused16 ; u_char ihcp_unused17 ; u_char ihcp_unused18 ; u_char ihcp_unused19[12] ; u_char Ihcp_mc ; /* master clear board */ u_char ihcp_unused20 ; u_char ihcp_unused21 ; u_char ihcp_unused22 ; u_char ihcp_unused23[12] ; u_char Ihcp_test0 ; /* factory use !!! */ u_char ihcp_unused24 ; u_char ihcp_unused25 ; u_char ihcp_unused26 ; u_char ihcp_unused27[12] ; u_char Ihcp_test1 ; /* more factory use. more !!! */ u_char ihcp_unused28 ; u_char ihcp_unused29 ; u_char ihcp_unused30 ; u_char ihcp_unused31[12] ; /* pad to 128 bytes. */ } ; struct ihcp_unit { struct dev_info *devinfo_p ; /* Node for this unit */ struct dmaio_regs *dma_regs_p ; /* device control regs */ struct ihcp_regs *ihcp_regs_p ; /* points to working regs */ int saved_spl ; /* Saved spl while in critical sections for this unit only. At all other times the value is minus one */ u_char unit_open ; /* 1 = open */ u_char open_inhibit ; /* 1 = inhibit */ u_char mode ; /* copy of print/plot mode */ u_char spare_0 ; /* alignment byte */ u_int interrupt_pri ; /* Highest interrupt pri for this unit (spl) */ u_int unit_flags ; /* our flag bits here */ u_int dma_time ; /* dma timeout # in secs */ u_int fifo_time ; /* empty and <1/2 full # */ struct buf buf ; /* buf for physio */ } ; /* interrupt_pri was originally a u_char - which wasn't wide enough to hold the necessary bits!!!!!! this was left over from the sample code!!!!! I think */ /*================ DATA DECLARATIONS ===================================*/ static struct ihcp_unit *ihcp_unit_array = NULL ; static u_char nihcps_counting = 0 ; static u_char nihcps = 0 ; static int logprie = LOG_ERR; static int logprid = LOG_INFO; static int logprit = LOG_DEBUG; extern int hz ; /* number of ticks per secont */ /*================ PROCEDURE DECLARATIONS ==============================*/ void START_CRITICAL() ; void END_CRITICAL() ; void see_devinfo() ; void ihcp_decommission() ; char* get_property() ; void ihcp_strategy() ; int ihcp_identify() ; int ihcp_attach() ; void ihcp_intr() ; int ihcp_poll() ; int ihcp_rdy_wait() ; int ihcp_half_wait() ; int ihcp_timeout() ; void ihcp_dma_init() ; int ihcp_open() ; int ihcp_close() ; int ihcp_read() ; int ihcp_write() ; int ihcp_ioctl() ; int ihpc_vdcmd() ; /*================ AUTOCONFIG DECLARATIONS ==============================*/ /* See notes on p 134 of "Writing SBus Device Drivers" */ struct dev_ops ihcp_ops = { 1, /* rev no of dev_ops structure */ ihcp_identify, ihcp_attach, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; /* other values in ihcp_ops are filled in later */ /* For reference: the dev_ops struct This structure identifies the driver entry points struct dev_ops { int devo_rev; int (*devo_identify)(); ... confirm device id int (*devo_attach)(); ... attach routine of driver int (*devo_open)(); int (*devo_close)(); int (*devo_read)(); int (*devo_write)(); int (*devo_strategy)(); int (*devo_dump)(); int (*devo_psize)(); int (*devo_ioctl)(); int (*devo_reset)(); int (*devo_mmap)(); }; */ /*================ AUTOCONFIGURATION SUPPORT ============================*/ /*------------------------------------ Function: ihcp_identify(name) This routine is called by the kernel autoconfig code with the namestring fetched from the ID PROM as its argument. Its purpose is to determine whether this is the right driver to work with the named device. Static Vars: This routine counts the number of times it returns acceptance so that it can size its array of unit structures accordingly. The count is kept at the variable nihcps_counting. Inputs: A pointer to a string which is a device name as fetched from the`= SBus ID PROM. In our case 'ihcp'. Returns: 1 on successful match and acceptance. 0 if not -------------------------------------*/ static int ihcp_identify(name) char *name ; { IHCP_TRACK_MSG(logprit, "Entering ihcp_identify, name: %s \n", name) ; if ((strcmp(name, "IKON,ihcp")) == 0) { /*-- Limit the number of units that we accept. Unlikely that this check is really needed */ if (nihcps_counting < MAX_NIHCPS) { nihcps_counting++ ; IHCP_TRACK_MSG(logprit, "Leaving ihcp_identify, ACCECPTED (#%d) \n", nihcps_counting) ; return(1) ; /*-- good match: accepted */ } else { IHCP_ERROR_MSG(logprie, "ihcp_identify: too many ihcp units (#%d) \n", nihcps_counting) ; } } IHCP_TRACK_MSG(logprit, "Leaving ihcp_identify, REJECTED \n") ; return(0) ; /*-- No match or over limit: device is rejected */ } /*------------------------------------ Function: ihcp_attach(kdevinfo_p) This routine is called by the kernel autoconfig code once for each node of the devinfo tree that this driver services (once for each of its units). Actions performed are: 1. Maps the device registers into kernal virtual memory 2. Puts the driver on any needed interrupt service chains 3. Turns on interrupts, if desired 4. Initializes the device 5. Allocates the unit structures Inputs: kdevinfo_p A pointer to the devinfo structure that gives access to the name and unit number Returns: 0 on successful attach -1 if not -------------------------------------*/ static int ihcp_attach(kdevinfo_p) register struct dev_info *kdevinfo_p ; { struct dev_reg *dev_reg_p ; struct dev_intr *dev_intr_p ; u_int counter ; u_int unit_no ; u_int dsize ; int ip, ihcp_ip ; int t ; u_char ct ; /* The static variable unit_count is the source of unit numbers to assign to the devinfo structure. It is the responsibility of the attach routine to fill in the value of kdevinfo_p->devi_unit */ static int unit_count = 0 ; IHCP_TRACK_MSG(logprit, "Entering ihcp_attach, kdevinfo_p: hex %x \n", kdevinfo_p) ; if (kdevinfo_p == NULL) { /*-- Should this unlikely event occur, make the attach fail: a physical device should have been found by the PROM, and therefore have a devinfo pointer. The code below requires it */ IHCP_ERROR_MSG(logprie, "ihcp_attach: kdevinfo_p is NULL! \n") ; goto ATTACH_FAILED ; } /*-- Allocate zeroed memory for the unit structures */ if (ihcp_unit_array == NULL) { nihcps = nihcps_counting ; ihcp_unit_array = (struct ihcp_unit *) kmem_zalloc(nihcps * sizeof (struct ihcp_unit)); } IHCP_TRACK_MSG(logprit, "At Attach point zz1.1zyyyy, ihcp_unit_array IS 0x%x\n",ihcp_unit_array); /* Assign a unit structure and fill in the value of devinfo_p->devi_unit */ unit_no = unit_count++ ; kdevinfo_p->devi_unit = unit_no ; /*-- save the devinfo pointer for decommissioning */ ihcp_unit_array[unit_no].devinfo_p = kdevinfo_p ; /*-- initialize saved_spl */ ihcp_unit_array[unit_no].saved_spl = -1 ; IHCP_TRACK_MSG(logprit, "At ihcp_unit_array[].saved_spl IS 0x%x\n",ihcp_unit_array[unit_no].saved_spl); /*-- look at the properties in the devinfo structure. Demonstrate how to get other properties by getting the value of the name property the hard way */ see_devinfo(kdevinfo_p) ; get_property(kdevinfo_p, "name", "NO NAME PROPERTY?") ; IHCP_TRACK_MSG(logprit, "At Attach point zz1zyyyy\n"); /*--- map in the board's registers. there are two sets of regs, the DMAIO chip's registers, and the IKON registers. There had better be EXACTLY two sets of registers!!!!!!! ---*/ dev_reg_p = kdevinfo_p->devi_reg ; IHCP_TRACK_MSG(logprit, "At Attach point zz2zyyyy\n"); /*-- dev_reg_p may be null, but only if devi_nreg is zero. we will stick with the loop type register mapping code used in the sample driver. it might make more sense to just code two mapping operations, but we will leave that as an exercise for the user - who is no doubt much better at this than the poor IKON folks. ---*/ if (kdevinfo_p->devi_nreg != 2) { IHCP_ERROR_MSG(logprie, "ihcp_attach: nreg != 2!! \n") ; goto ATTACH_FAILED ; } for (counter = 1; counter <= kdevinfo_p->devi_nreg; ++counter, ++dev_reg_p) { IHCP_TRACK_MSG(logprit, "At Attach point zz3zyyyy\n"); if (dev_reg_p == NULL) { /* not very likely */ IHCP_ERROR_MSG(logprie, "ihcp_attach: dev_reg_p is NULL! \n") ; goto ATTACH_FAILED ; } /* map in both sets of registers - the dma chip's and the ikon stuff */ switch(counter) { case 1: /*-- DMAio regs ---*/ /*-- Check that the size of the register set is the same as the size of the structure that overlays it */ dsize = sizeof(struct dmaio_regs) ; /*--- for (t=0; t<10000; t++) t=t ; ---*/ /* kill some time (in sample!?) */ IHCP_TRACK_MSG(logprit, "At Attach point zz6zyyyy\n"); if (dsize != dev_reg_p->reg_size) { IHCP_ERROR_MSG(logprie, "Struct (%d bytes) != reg (%d bytes) !\n", dsize , dev_reg_p->reg_size) ; IHCP_TRACK_MSG(logprit, "At Attach point zz7zyyyy\n"); goto ATTACH_FAILED ; } /* --- map the structure into kernel virtual space */ IHCP_TRACK_MSG(logprit, "At Attach point zz9zyyyy\n"); ihcp_unit_array[unit_no].dma_regs_p = (struct dmaio_regs *) map_regs(dev_reg_p->reg_addr, dsize, dev_reg_p->reg_bustype) ; IHCP_TRACK_MSG(logprit, "At Attach point zz10zyyyy, regs_p is 0x%x\n", ihcp_unit_array[unit_no].dma_regs_p); if (ihcp_unit_array[unit_no].dma_regs_p == NULL) { IHCP_TRACK_MSG(logprit, "At Attach point zz11zyyyy\n"); IHCP_ERROR_MSG(logprie, "ihcp_attach: map regs failed\n") ; goto ATTACH_FAILED ; } break ; /* end case 1 */ case 2: /*-- ihcp working registers */ dsize = sizeof(struct ihcp_regs) ; IHCP_TRACK_MSG(logprit, "At Attach point zz26zyyyy\n"); if (dsize != dev_reg_p->reg_size) { IHCP_ERROR_MSG(logprie, "Struct (%d bytes) != reg (%d bytes) !\n", dsize , dev_reg_p->reg_size) ; IHCP_TRACK_MSG(logprit, "At Attach point zz27zyyyy\n"); goto ATTACH_FAILED ; } /* --- map the structure into kernel virtual space */ IHCP_TRACK_MSG(logprit, "At Attach point zz28zyyyy\n"); ihcp_unit_array[unit_no].ihcp_regs_p = (struct ihcp_regs *) map_regs(dev_reg_p->reg_addr, dsize, dev_reg_p->reg_bustype) ; if (ihcp_unit_array[unit_no].ihcp_regs_p == NULL) { IHCP_TRACK_MSG(logprit, "At Attach point zz29zyyyy\n"); IHCP_ERROR_MSG(logprie, "ihcp_attach: map regs failed\n") ; goto ATTACH_FAILED ; } break ; /* end case 2 */ default: /* if we get here we are really sick !!!! */ IHCP_ERROR_MSG(logprie, "ihcp_attach: too many registers (#%d) \n", counter) ; goto ATTACH_FAILED ; } /* end switch */ } /* end for (counter == 1 etc. */ /* Next install the interrupt vectors, if any -----*/ if (kdevinfo_p->devi_nintr == 0) goto SKIP_INTR_INSTALLATION ; /*-- For devices that issue interrupts the driver installs itself on the interrupt chain of each interrupt level that the hardware uses */ dev_intr_p = kdevinfo_p->devi_intr ; IHCP_TRACK_MSG(logprit, "At Attach point zz12zyyyy\n"); if (dev_intr_p == NULL) { /* not very likely */ IHCP_TRACK_MSG(logprit, "At Attach point zz12.5zyyyy dev_intr_p is NULL!\n"); IHCP_ERROR_MSG(logprie, "ihcp_attach: dev_intr_p is NULL! \n") ; goto ATTACH_FAILED ; } /*-- dev_intr_p may be null, but only if devi_nintr is zero */ for (counter = 1; counter <= kdevinfo_p->devi_nintr; ++counter, ++dev_intr_p) { IHCP_TRACK_MSG(logprit, "At Attach point zz13zyyyy\n"); /*-- Convert the hardware interrupt priority level (ipl, 0-15) into an spl for critical sections of code. The routine ipltospl returns a value that is properly shifted for direct insertion into the psr. If there is more than one interrupt level, use the highest one. Note that each unit may use a different level */ IHCP_TRACK_MSG(logprit, "At 14.1 dev_intr_p is 0x%x\n",dev_intr_p); ip = ipltospl(dev_intr_p->int_pri) ; IHCP_TRACK_MSG(logprit, "At Attach point zz14.2zyyyy\n"); ihcp_ip = ihcp_unit_array[unit_no].interrupt_pri ; IHCP_TRACK_MSG(logprit, "At Attach point zz14.4zyyyy\n"); if (ip > ihcp_ip) { ihcp_unit_array[unit_no].interrupt_pri = ip ; IHCP_DEBUG_MSG(logprid, "Set interrupt pri to hex %x for unit %d. \n", ip, unit_no) ; } IHCP_TRACK_MSG(logprit, "ihcp_attach: final interrupt_pri = 0x%x\n",ihcp_unit_array[unit_no].interrupt_pri) ; /*-- if the device can do DVMA then the DVMA routines need to know the highest interrupt level at which the device can interrupt. The routine adddma is used to gather this info */ IHCP_TRACK_MSG(logprit, "At Attach point zz15zyyyy\n"); /**** addma arg in original was ihcp_ip - which followed sun's example which was the wrong type !!!! corrected 10 Sept 1991 ****/ adddma(dev_intr_p->int_pri) ; IHCP_TRACK_MSG(logprit, "At Attach point zz16zyyyy\n"); START_CRITICAL(unit_no) ; /*-- addintr() does the registration. Duplicate registrations (same level from several units) is ignored except for data collection for vmstat. addintr() panics if this registration would make too many devices on the interrupt chain. So it goes */ addintr(dev_intr_p->int_pri, ihcp_poll, kdevinfo_p->devi_name, kdevinfo_p->devi_unit) ; END_CRITICAL(unit_no) ; IHCP_TRACK_MSG(logprit, "At Attach point zz17zyyyy\n"); IHCP_DEBUG_MSG(logprid, "Installed ihcp_poll at SBus int pri level hex %x\n", dev_intr_p->int_pri) ; } /* end for (counter == 1 etc. */ SKIP_INTR_INSTALLATION: /*-- The routine report_dev() writes a line at the console and system log to announce the attachment of the driver. This will happen at modload time, one line for each unit. Call report_dev() before contacting the device so the message is there if the system freezes at this point */ report_dev(kdevinfo_p) ; IHCP_TRACK_MSG(logprit, "At Attach point xx18zyyyy\n") ; /*--- initialize unit_flags - set all to 0 ---*/ ihcp_unit_array[unit_no].unit_flags = 0 ; /*--- initialize time-out values to default ---*/ t = hz * DMA_TIME_DEF ; ihcp_unit_array[unit_no].dma_time = t ; t = hz * FIFO_TIME_DEF ; ihcp_unit_array[unit_no].fifo_time = t ; /*--- get the board's interface type flag and save it ---*/ ct = ihcp_unit_array[unit_no].ihcp_regs_p->Ihcp_ds_co & 0xE0 ; ihcp_unit_array[unit_no].unit_flags |= ct ; /*--- call dma init to reset the LSI chip & clear the fifo & board ---*/ ihcp_dma_init(unit_no) ; /*--- if versatec type board, set to the default mode - the board doesn't do this automatically. We want (usually) to emulate the VME board & driver. (the VME board defaults to normal print mode). note that the same default mode is used for all versatec type boards! ---*/ if(ct != IHCPIO_CENT) ihcp_unit_array[unit_no].ihcp_regs_p->Ihcp_ds_co = IHCP_MODESET_RYON | MODE_DEF ; /* above will send 0 to mode reg and leave ready on -- but will not be sent until device is ready, so fifo may have one item in it until then */ /*--- set mode byte in unit structure to default mode ---*/ ihcp_unit_array[unit_no].mode = MODE_DEF ; /*--- set the default speed into the latched function register. note that the same default is used for all boards !!! ---*/ ihcp_unit_array[unit_no].ihcp_regs_p->Ihcp_lf = SPEED_DEF ; /*--- interupts are not enabled until we are waiting for DMA end or fifo empty and dev ready ---*/ IHCP_TRACK_MSG(logprit, "ATTACH SUCCEEDED.vi\n") ; return(0) ; ATTACH_FAILED: IHCP_TRACK_MSG(logprit, "ATTACH FAILED.vi\n") ; ihcp_unit_array[unit_no].open_inhibit = 1 ; /* prevent open */ return(-1) ; } /* end ihcp_attach() */ /*================ DRIVER ENTRY POINTS ===================================*/ /*------------------------------------ Function: ihcp_open(dev, flags) Open system call. Allow an open for write only. Inputs: dev The compound device number. Only the minor portion is of value to the driver. openflags Flags encoding the open mode requested. Returns: 0 on success, errno on failure. -------------------------------------*/ int ihcp_open(dev, openflags) dev_t dev; int openflags; { u_char unit_no; u_short retval = 0; /*-- return value (errno) for system call*/ unit_no = IHCP_UNIT(dev); IHCP_TRACK_MSG(logprit, "ihcp%d: Entering ihcp_open, flags %d.\n", unit_no, openflags); /*-- Check for how many are allocated. At this point, n ihcp must be set.*/ if (unit_no >= nihcps) { retval = ENXIO; goto EXITOPEN; } /*-- Check for allocation of unit structures.*/ if (ihcp_unit_array == NULL) { retval = ENXIO; /* attach failled ?? */ goto EXITOPEN; } /*-- Check if the open mode requested is permissible. The ihcp is a write only device. ---*/ if (ihcp_unit_array[unit_no].open_inhibit !=0 ) { IHCP_DEBUG_MSG(logprid, "ihcp%d open inhibit.\n", unit_no); retval = ENXIO; goto EXITOPEN; } if (!(openflags & FWRITE)) { uprintf("ihcp%d: write only device.\n", unit_no); retval = ENOTTY; goto EXITOPEN; } if(ihcp_unit_array[unit_no].unit_open !=0) { /* exclusive open! */ uprintf("ihcp%d: already open!\n",unit_no); retval = EBUSY ; /*THIS MAY NOT BE THE RIGHT ERRNO*/ goto EXITOPEN ; } /**** we no longer initialize the board here - since close doesn't check for ready and we don't want to blow away fifo data from the previous use of the driver. if it is desired to set the board and plotter to norman print mode when opened - use the procedure used in the attach routine. ihcp_dma_init(unit_no) ; ****/ /*--- clear the various flags in unit_flags ( but not board type ) ---*/ ihcp_unit_array[unit_no].unit_flags &= IHCP_CLEAR_FLAGS ; /*-- Finally, mark the ihcp as opened.*/ ihcp_unit_array[unit_no].unit_open = 1; EXITOPEN: IHCP_TRACK_MSG(logprit, "Leaving ihcp_open, unit %d: errno %d.\n", unit_no, retval); return (retval); } /*------------------------------------ Function: ihcp_close(dev, openflags) Close the device. Inputs: dev The compound device number. Only the minor portion is of value to the driver. openflags Flags encloding the open mode requested, and possibly granted. Returns: 0 on success, errno on failure. -------------------------------------*/ int ihcp_close(dev, openflags) dev_t dev; int openflags; { u_char unit_no; u_short retval = 0; /*-- return value (errno) for system call*/ unit_no = IHCP_UNIT(dev); IHCP_TRACK_MSG(logprit, "Entering ihcp_close, unit number %d, flags %d.\n", unit_no, openflags); /**** we no longer test for ready or init the board here, since if ready test hangs, it can't catch the kill signal if called by the kernel at kill or when the calling program terminates.. rdyval = ihcp_rdy_wait(unit_no) ; if(rdyval < 0) { retval = EIO ; IHCP_ERROR_MSG(logprie, "ihcp_close: unit ihcp%d: timeout waiting for ready! \ DMA and interrupt logic reset, fifo flushed, \ unit marked closed\n",unit_no) ; } if(rdyval > 0) { retval = EINTR ; IHCP_ERROR_MSG(logprie, "ihcp_close: unit ihcp%d: signal while waiting for ready! \ DMA and interrupt logic reset, fifo flushed, \ unit marked closed\n",unit_no) ; } ihcp_dma_init(unit_no) ; ****/ /*-- Mark unit closed.*/ ihcp_unit_array[unit_no].unit_open = 0; IHCP_TRACK_MSG(logprit, "Leaving ihcp_close, unit %d: errno %d.\n", unit_no, retval); return (retval); } /*------------------------------------ Function: ihcp_read(dev, uioptr) Read data to the ihcp. Block until the read is finished. Inputs: dev The compound device number. Only the minor portion is of value to the driver. uioptr Pointer to the uio structure which may contain a table of iovec structures. Returns: 0 on success, errno on failure -------------------------------------*/ int ihcp_read(dev, uioptr) dev_t dev; struct uio *uioptr; { u_char unit_no; u_short retval; retval = 0; /*-- return value (errno) for system call*/ unit_no = IHCP_UNIT(dev); IHCP_TRACK_MSG(logprit, "Entering ihcp_read, unit number %d.\n", unit_no); uioptr = uioptr ; /* try to keep lint quiet */ /* ihcp is a write only device */ retval = ENOTTY ; IHCP_ERROR_MSG(logprie,"ihcp_read: unit ihcp%d: write only device! \n",unit_no) ; IHCP_TRACK_MSG(logprit, "Leaving ihcp_read, unit %d: errno %d.\n", unit_no, retval); return (retval); } /*------------------------------------ Function: ihcp_write(dev, uioptr) Write data to the ihcp. Block until the write is finished. Inputs: dev The compound device number. Only the minor portion is of value to the driver. uioptr Pointer to the uio structure which may contain a table of iovec structures. Returns: 0 on success, errno on failure -------------------------------------*/ int ihcp_write(dev, uioptr) dev_t dev; struct uio *uioptr; { u_char unit_no; u_short retval; retval = 0; /*-- return value (errno) for system call*/ unit_no = IHCP_UNIT(dev); IHCP_TRACK_MSG(logprit, "Entering ihcp_write, unit number %d.\n", unit_no); if (unit_no >= nihcps) { retval = ENXIO; goto EXITWRITE; } /*--- clear the timeout and sig rx'd flags -- also clears "waiting for" flags strategy will set the "waiting for dma" flag. ---*/ ihcp_unit_array[unit_no].unit_flags &= IHCP_CLEAR_FLAGS ; retval = physio(ihcp_strategy, &ihcp_unit_array[unit_no].buf, dev, B_WRITE, minphys, uioptr); /*--- check flags for timeout error or signal received ---*/ if(ihcp_unit_array[unit_no].unit_flags & IHCP_DMA_TIMEOUT) { retval = EIO ; IHCP_ERROR_MSG(logprie, "ihcp_write: unit ihcp%d: DMA timeout! \n",unit_no) ; } if(ihcp_unit_array[unit_no].unit_flags & IHCP_SIG_RECEIVED) { retval = EINTR ; IHCP_ERROR_MSG(logprie, "ihcp_write: unit ihcp%d: signal received! \n",unit_no) ; } EXITWRITE: IHCP_TRACK_MSG(logprit, "Leaving ihcp_write, unit %d: errno %d.\n", unit_no, retval); return (retval); } /*------------------------------------ Function: ihcp_ioctl(dev, cmd, arg, flag) Handle control requests. Inputs: dev The compound device number. Only the minor portion is of value to the driver. cmd Device specific. SEE iohcpio.h arg Char pointer to caller's data buffer flag Not used by the ihcp driver. Returns: 0 on success, errno on failure. -------------------------------------*/ /*ARGSUSED*/ int ihcp_ioctl(dev, cmd, arg, flag) dev_t dev; int cmd; caddr_t arg; int flag; { union pointers { u_short *shptr ; u_long *lgptr ; caddr_t chptr ; } upt ; struct dmaio_regs *dregs_p; struct ihcp_regs *iregs_p ; u_long *lg_p ; u_short *sh_p ; u_long bits ; u_char ct ; /* temp char */ u_short st ; /* temp short */ int count, i, t ; u_short retval ; /*-- return value (errno) for system call*/ u_char unit_no; u_int command ; int waitval ; /* value returned by rdy or half wait */ /*-------------------- */ retval = 0; /* init to OK */ upt.chptr = arg ; /* char pointer at arg */ lg_p = upt.lgptr ; /* and long pointer */ sh_p = upt.shptr ; /* and short pointer */ unit_no = IHCP_UNIT(dev); /* unit number */ iregs_p = ihcp_unit_array[unit_no].ihcp_regs_p ;/*points at ikon regs */ dregs_p = ihcp_unit_array[unit_no].dma_regs_p; /*points at dmaio regs */ command = cmd & IHCPIO_CMD_MASK ; /* get command */ count = (cmd & IHCPIO_COUNT_MASK) >> 16 ; /*arg size for data out */ bits = *lg_p ; /* get long arg[0] */ IHCP_TRACK_MSG(logprit, "Entering ihcp_ioctl, ihcp_unit_array is 0x%x .\n", ihcp_unit_array) ; IHCP_TRACK_MSG(logprit, "Entering ihcp_ioctl, unit number %d.\n", unit_no); IHCP_TRACK_MSG(logprit, "nihcps is 0x%x\n", nihcps) ; IHCP_TRACK_MSG(logprit, "ihcp_unit_array is 0x%x \n", ihcp_unit_array ) ; IHCP_TRACK_MSG(logprit, "At ihcp_ioctl, cmd is 0x%x, arg(pointer) is 0x%x .\n", cmd, arg); IHCP_TRACK_MSG(logprit, "At ihcp_ioctl, bits = 0x%x\n",bits) ; IHCP_TRACK_MSG(logprit, "At ihcp_ioctl, dregs_p is 0x%x, iregs_p is 0x%x .\n", dregs_p, iregs_p); #define MASK IHCPIO_CMD_MASK /* use to strip the count from cases */ switch(command) { /* go do the deed */ case MASK & IHCPIO_DEV_RESET: /* send reset to attached device */ IHCP_TRACK_MSG(logprit,"ichp_ioctl: at DEV_RESET \n") ; iregs_p->Ihcp_lf |= IHCP_LONG_RESET ; /* set long reset bit */ for(t=0; t<10; t++) t=t ; /* give it some length */ iregs_p->Ihcp_lf &= ~IHCP_LONG_RESET ; /* clear it */ break ; case MASK & IHCPIO_SET_CONFIG: /* set speed and if to use busy */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at SET_CONFIG \n") ; bits &= (IHCP_IGNORE_BUSY | IHCP_SPEED3) ; /* pertinent bits only*/ if((bits & IHCPIO_IGNORE_BUSY) != 0) { /* if ig busy requested */ if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { /* not centronics */ IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at SET_CONFIG - attempt to set ignore busy mode on non-centronics unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } } iregs_p->Ihcp_lf = (u_char)bits ;/* legal bits - set speed and */ /* ignore busy mode if requested*/ break ; case MASK & IHCPIO_SET_DMATIME: /* set dma timeout parameter */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at SET_DMATIME \n") ; if(bits < DMA_TIME_MIN ) bits = DMA_TIME_MIN ; /* not less than min*/ if(bits > DMA_TIME_MAX ) bits = DMA_TIME_MAX ; /* or more than max */ ihcp_unit_array[unit_no].dma_time = (u_int)(bits * hz) ; /* set ticks */ break ; case MASK & IHCPIO_SET_FIFOTIME: /* set timeout for fifo <1/2 full */ /* or fifo empty & dev rdy wait */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at SET_FIFOTIME \n") ; if(bits < FIFO_TIME_MIN) bits = FIFO_TIME_MIN ; /* not less than */ if(bits > FIFO_TIME_MAX) bits = FIFO_TIME_MAX ; /* or more than..*/ ihcp_unit_array[unit_no].fifo_time = (u_int)(bits * hz) ; /* ticks*/ break ; case MASK & IHCPIO_GET_REGS: /* get all the board's registers*/ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at GET_REGS \n") ; *lg_p = dregs_p->Dmacsr ; /* get dma csr */ *(lg_p + 1) = dregs_p->Dmaar ; /* get add register */ *(lg_p + 2) = dregs_p->Dmabcr ; /* get count register */ *(lg_p + 3) = (u_long)iregs_p->Ihcp_lf ;/* get latched functions*/ *(lg_p + 4) = (u_long)iregs_p->Ihcp_is_do ; /* interface status */ *(lg_p + 5) = (u_long)iregs_p->Ihcp_ds_co ; /* device status */ break ; case MASK & IHCPIO_GET_STATUS: /* get formatted version of status*/ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at GET_STATUS \n") ; ct = iregs_p->Ihcp_ds_co & 0x1F ; /* get device status bits */ /*--- now complement where necessary to make pretty status rpt */ *lg_p = (u_long)( ct ^ (IHCPIO_DEV_BUSY | IHCPIO_DEV_POUT | IHCPIO_DEV_SEL)) ; break ; case MASK & IHCPIO_GET_BOARD: /* get board type */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at GET_BOARD \n") ; *lg_p = (u_long)(ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK); break ; case MASK & IHCPIO_SET_VMODE:/* set versatec mode - leave rdy ff on */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at SET_VMODE \n") ; /* make sure the board is a versatec type - can't do this w/centr */ if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at SET_VMODE - attempt to do versatec mode set on centronics type unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this command talks to the fifo - make sure there is room !! */ if((iregs_p->Ihcp_is_do & IHCP_FIFO_NOT_FULL) == 0) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at SET_VMODE - no room in fifo for versatec mode set! \n",unit_no) ; retval = EIO ; goto IOCTLEXIT ; } /* bottom two bits contain print/plot/spp control */ iregs_p->Ihcp_ds_co = IHCP_MODESET_RYON | ((u_char)bits & 0x03); /* save mode in unit structure */ ihcp_unit_array[unit_no].mode = (u_char)bits & 0x03 ; break ; case MASK & IHCPIO_SET_VMODEX:/* set versatec mode - leave rdy ff off */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at SET_VMODEX \n") ; /* make sure the board is a versatec type - can't do this w/centr */ if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at SET_VMODEX - attempt to do versatec mode set on centronics type unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this command talks to the fifo - make sure there is room !! */ if((iregs_p->Ihcp_is_do & IHCP_FIFO_NOT_FULL) == 0) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at SET_VMODEX - no room in fifo for versated mode set!\n",unit_no) ; retval = EIO ; goto IOCTLEXIT ; } /* bottom two bits contain print/plot/spp control */ iregs_p->Ihcp_ds_co =IHCP_MODESET_RYOFF | ((u_char)bits & 0x03); /* save mode in unit structure */ ihcp_unit_array[unit_no].mode = (u_char)bits & 0x03 ; break ; case MASK & IHCPIO_V_CMD: /* issue versatec pulse command */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at V_CMD \n") ; /* make sure the board is a versatec type - can't do this w/centr */ if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at V_CMD - attempt to issue versatec command to centronics type unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this command talks to the fifo - make sure there is room !! */ if((iregs_p->Ihcp_is_do & IHCP_FIFO_NOT_FULL) == 0) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at V_CMD - no room in fifo for versatec command!\n",unit_no) ; retval = EIO ; goto IOCTLEXIT ; } /* bottom two bits contain command */ iregs_p->Ihcp_ds_co = ((u_char)bits & 0x03); break ; case MASK & IHCPIO_DATA_OUT: /* output chars from arg */ /* # of chars in bottom of cmd */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at DATA_OUT \n") ; /* make sure # of chars (in bits) is <=255 - we know it is positive since we masked it. it must be <=255 since that is the max allowed for arg, and that is the most that can be stuffed into the fifo when it is <= half full. we will check to make sure that the fifo is not full before sending each byte. after dma, half full may be true - (fifo will not have more than 1/2 + 1 byte in it) - so we can't check 1/2 full flag and then just barf data at it!!!. */ if(count > 255) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at DATA_OUT - too many bytes in arg!\n",unit_no) ; retval = EINVAL ; goto IOCTLEXIT ; } for(i=0; iIhcp_is_do & IHCP_FIFO_NOT_FULL)) { retval = EIO ; IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at DATA_OUT - fifo full while transferring data bytes!\n",unit_no) ; IHCP_ERROR_MSG(logprie, "0x%x bytes requested, 0x%x bytes transferred\n", count,i) ; break ; } iregs_p->Ihcp_is_do = *( arg + i ) ; } break ; case MASK & IHCPIO_RDY_WAIT: /* wait for rdy & fifo empty - or timeout */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at RDY_WAIT \n") ; waitval = ihcp_rdy_wait(unit_no) ; if(waitval < 0) { /* timeout may not be error */ /* so put message in debug */ /* log. return EIO so caller*/ /* can decide what to do!! */ IHCP_DEBUG_MSG(logprid, "ihcp_ioctl: at RDY_WAIT - timeout waiting for ready & fifo empty! \n") ; retval = EIO ; goto IOCTLEXIT ; } if(waitval > 0) { /* signal is not error but send to*/ /* console */ /* log. return EINTR so caller*/ /* can decide what to do!! */ IHCP_ERROR_MSG(logprie, "ihcp_ioctl: at RDY_WAIT - signal while waiting for ready & fifo empty! \n") ; retval = EINTR ; goto IOCTLEXIT ; } break ; case MASK & IHCPIO_HALF_WAIT:/* wait for fifo 0) { /* signal is not error but send to*/ /* console */ /* log. return EINTR so caller*/ /* can decide what to do!! */ IHCP_ERROR_MSG(logprie, "ihcp_ioctl: at HALF_WAIT - signal while waiting for fifo Ihcp_lf |= IHCP_DATA_STREAM ; /* or in bit */ break ; case MASK & IHCPIO_STREAM_OFF: /* turn streaming off (if 10106) */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at STREAM_OFF \n") ; if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at STREAM_OFF - attempt to clear streaming mode on non-centronics unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } iregs_p->Ihcp_lf &= ~IHCP_DATA_STREAM ; /* clear it */ break ; case MASK & IHCPIO_GET_FLAGS: /* get unit flag longword */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at GET_FLAGS \n") ; *lg_p = ihcp_unit_array[unit_no].unit_flags ; break ; case MASK & IHCPIO_GET_FIFO: /* get formatted fifo flags */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at GET_FIFO \n") ; ct = iregs_p->Ihcp_is_do & 0x1C ; /* get fifo bits */ *lg_p = (u_long)(ct ^ (IHCP_FIFO_NOT_FULL | IHCP_FIFO_NOT_HALF | IHCP_FIFO_NOT_EMPTY)) ; /*complement bits*/ break ; case MASK & LPSETVERSATEC: /* compatibility - set versatec port */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at LPSETVERSATEC \n") ; /* make sure the board is a versatec type - can't do this w/centr */ if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at LPSETVERSATEC - attempt to select versatec port on centronics type unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } break ; case MASK & LPSETCENTRONICS: /* compatibility - set centr port */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at LPSETCENTRONICS \n") ; if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at LPSETCENTRONICS - attempt to select centronics port on versatec-type unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } break ; case MASK & LPCOMMAND: /* compatibility - old style cmds */ /* as much as possible, allow multiple command bits in the argument. this is probably not the way it was intended, but we want to protect ourselves against non-reasonable uses of the old calls */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at LPCOMMAND \n") ; /* assert long reset signal */ if(bits & LPC_LRST) iregs_p->Ihcp_lf |= IHCP_LONG_RESET ; /* de-assert long reset */ if(bits & LPC_DRST) iregs_p->Ihcp_lf &= ~IHCP_LONG_RESET ; /* test for option port request - better be a 106 board !! */ if(bits & LPC_SOPT) if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at LPCOMMAND - attempt to select centronics port on versatec-type unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } /* test for versatec port selection */ if(bits & LPC_SVPT) if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at LPCOMMAND - attempt to select versatec port on centronics type unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } /* enable data streaming - if this is a 106 board ! */ if(bits & LPC_DSTR) { if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at LPCOMMAND - attempt to set streaming mode on non-centronics unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } iregs_p->Ihcp_lf |= IHCP_DATA_STREAM ; /* or in bit */ } /* reset data streaming bit */ if(bits & LPC_DDST) { if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at LPCOMMAND - attempt to clear streaming mode on non-centronics unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } iregs_p->Ihcp_lf &= ~IHCP_DATA_STREAM ; /* clear it */ } /* test for versatec print/plot mode changes - make sure we are a versatec board !! */ if(bits & LPC_PRINTMASK) { /* make sure the board is a versatec type - can't do w/centr */ if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at LPCOMMAND - attempt to do versatec mode set on centronics type unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this cmd talks to the fifo - make sure there is room !! */ if((iregs_p->Ihcp_is_do & IHCP_FIFO_NOT_FULL) == 0) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at LPCOMMAND - no room in fifo for versatec mode set! \n",unit_no) ; retval = EIO ; goto IOCTLEXIT ; } /* get current mode setting */ ct = ihcp_unit_array[unit_no].mode ; /* now look at individual command bits and twiddle mode */ if(bits & LPC_VSPP) ct |= IHCP_SPP_MODE ; /* set spp mode */ if(bits & LPC_DSPP) ct &= ~IHCP_SPP_MODE ; /* clear spp */ if(bits & LPC_VPLT) ct |= IHCP_PLOT_MODE ; /* set plot mode*/ if(bits & LPC_DVPT) ct &= ~IHCP_PLOT_MODE ; /* clear plot */ if(bits & LPC_PRNT) ct = 0 ; /* 0 = normal print mode */ /* write the final mode selection to the fifo */ iregs_p->Ihcp_ds_co = IHCP_MODESET_RYON | ct ; /* save result in unit structure */ ihcp_unit_array[unit_no].mode = ct ; } /* end of mode set if */ /* check for software ack pulse request */ if(bits & LPC_SACK) iregs_p->Ihcp_sa = 0 ; /* any value will do*/ /* check for master clear request */ if(bits & LPC_MCLR) { /* this gets more complicated!!! */ iregs_p->Ihcp_mc = 0 ; /* issue mclr to board */ iregs_p->Ihcp_lf |= IHCP_LONG_RESET ; /* reset to device */ for(t=0;t<10;t++) t=t ; /* stretch it */ iregs_p->Ihcp_lf &= ~IHCP_LONG_RESET ; /* clear it */ /*--- if versatec type board, set to normal print mode - the board doesn't do this automatically, and we want to look as much like the existing VME driver as possible ---*/ if(ct != IHCPIO_CENT) ihcp_unit_array[unit_no].ihcp_regs_p->Ihcp_ds_co = IHCP_MODESET_RYON ; /* above will send 0 to mode reg and leave ready on -- we know fifo has room since we just did a master clear */ /*--- set mode byte in unit structure to 0 (normal print mode) ---*/ ihcp_unit_array[unit_no].mode = 0 ; } /* end of mclr if */ /* look for pulsed command request that has to go through fifo */ if(bits & (LPC_VCLR | LPC_VTFF | LPC_VEOT | LPC_VLTR)) { /* make sure the board is a versatec type - can't do w/centr */ if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at LPCOMMAND - attempt to do versatec pulsed command to centronics type unit! \n",unit_no) ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this cmd talks to the fifo - make sure there is room !! */ /* WE WILL ASSUME THAT THERE WILL ONLY BE ONE PULSE PER CALL */ if((iregs_p->Ihcp_is_do & IHCP_FIFO_NOT_FULL) == 0) { IHCP_ERROR_MSG(logprie, "ihcp_ioctl: unit ihcp%d: at LPCOMMAND - no room in fifo for versatec pulsed command! \n",unit_no) ; retval = EIO ; goto IOCTLEXIT ; } if(bits & LPC_VCLR) iregs_p->Ihcp_ds_co = IHCP_VCLR ; if(bits & LPC_VTFF) iregs_p->Ihcp_ds_co = IHCP_VFED ; if(bits & LPC_VEOT) iregs_p->Ihcp_ds_co = IHCP_VEOT ; if(bits & LPC_VLTR) iregs_p->Ihcp_ds_co = IHCP_VLTR ; } /* end of pulsed command if */ break ; case MASK & LPGETREGS: /* old style (VMEbus) get regs command */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at LPGETREGS \n") ; st = 0 ; /* init temp u_short-used to collect bits */ /* start work on interface status register */ ct = iregs_p->Ihcp_is_do ; /* get interface status reg */ if(ct & IHCP_FIFO_DEV_RDY) st |= OLD_DIRY ; /* int ready */ if(ct & IHCP_DEV_RDY) st |= OLD_DVRY ; /* dev ready */ ct = iregs_p->Ihcp_lf ; /* get latched functions */ if(ct & IHCP_DATA_STREAM) st |= OLD_DSTR ; /* data stream */ if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) st |= OLD_SOPT ; /* centronics */ *sh_p = st ; /* stuff result in first u_short of argument */ /* do the same ugly things with the device status register -- it has two halves: versatec and centronics -- only work on the appropriate half */ st = 0 ; if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { /* work on centronics part. get the device status reg */ ct = iregs_p->Ihcp_ds_co ; if(ct & IHCP_VRDY_CACK) st |= OLD_OACK ; /* ack on */ if(ct & IHCP_C_NOT_BUSY) st |= OLD_ONBY ; /* not busy */ if(ct & IHCP_PAPER_OK) st |= OLD_OPPR ; /* paper pres */ if(ct & IHCP_OFFLINE) st |= OLD_ONSL ; /* offline */ if(ct & IHCP_C_FAULT) st |= OLD_OFLT ; /* fault */ ct = iregs_p->Ihcp_lf ; /* get latched reg */ if(ct & IHCP_LONG_RESET) st |= OLD_OLRS ; /* long reset */ } else { /* work on versatec part */ if((ihcp_unit_array[unit_no].unit_flags & IHCP_BOARD_MASK) == IHCPIO_VERS_TTL) st |= OLD_VTTL ; /* ttl mode*/ ct = iregs_p->Ihcp_ds_co ; /* get dev status reg */ if(ct & IHCP_VRDY_CACK) st |= OLD_VRDY ; /* vers ready*/ if(ct & IHCP_PAPER_OK) st |= OLD_VPPR ; /* paper ok */ if(!(ct & IHCP_OFFLINE)) st |= OLD_VONL ; /* vers onlin*/ /* get current mode from unit struct - don't use the board's mode bits, since they are on the other side of the fifo and may not be the same as the input side */ ct = ihcp_unit_array[unit_no].mode ; if(ct & IHCP_SPP_MODE) st |= OLD_VSPP ; /* spp mode */ if(ct & IHCP_PLOT_MODE) st |= OLD_VPLT ; /* plot mode */ } *(sh_p +1) = st ; /* put result in 2nd short of arg */ break; case MASK & LPSETTIMVAL: /* set timeout value - old style - for dma and fifo empty at same time */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at LPSETTIMVAL \n") ; /* old style timeout call used fiftieths of a second as argument*/ if(bits < DMA_TIME_MIN * 50) bits = DMA_TIME_MIN * 50 ; if(bits > DMA_TIME_MAX * 50) bits = DMA_TIME_MAX * 50 ; /* old style used fiftieths of a second - probably will not be the same on Sbus machines(sun4c hz = 100) */ ihcp_unit_array[unit_no].fifo_time = (bits * hz)/50 ; ihcp_unit_array[unit_no].dma_time = (bits * hz)/50 ; break; case MASK & LPGETTIMVAL: /* get current timeout (in ticks ?) */ IHCP_TRACK_MSG(logprit,"ihcp_ioctl: at LPGETTIMVAL \n") ; /* return # of fiftieths of a second */ *lg_p = (ihcp_unit_array[unit_no].dma_time * 50)/hz ; break; default: IHCP_DEBUG_MSG(logprid, "ihcp_ioctl: bad command \n"); retval = EINVAL; break; } IOCTLEXIT: IHCP_TRACK_MSG(logprit, "Leaving ihcp_ioctl, unit %d: errno %d.\n", unit_no, retval); return (retval); } /*================ BOTTOM HALF INTERRUPT HANDLER ========================*/ /*------------------------------------ Function: ihcp_intr(unit_no) Interrupt routine for the DVMA hardware - terminal count & error - and the fifo status interrupts - Dmacsr ; flags = ihcp_unit_array[unit_no].unit_flags ; #ifdef IHCP_TRACK printf( "IHCP_INTR: entering interrupt handler \n") ; printf( "IHCP_INTR: ihcp_unit_array is 0x%x .\n", ihcp_unit_array) ; printf( "IHCP_INTR: unit number %d.\n", unit_no) ; printf( "IHCP_INTR: bp is 0x%x\n", bp) ; printf( "IHCP_INTR: csr is 0x%x, dvmamap is 0x%x \n", csr, dvmamap ) ; printf( "IHCP_INTR: unit_flags is 0x%x\n",flags) ; #endif IHCP_TRACK /*--- this code is much simplified from earlier versions. higher level code is left to do the iodone &/or error stuff. ---*/ flags &= (IHCP_DMA_WAIT | IHCP_RDY_WAIT | IHCP_HALF_WAIT) ; /*--- each case in this switch is complete - that is, it doesn't count on later code to disable ints, or do the return. I prefer to keep the wakeup as close to the return(s) as possible, so each case contains all the goodies. this also makes it easier to modify a case without unexpected side effects. ---*/ switch(flags) { case IHCP_DMA_WAIT: /* we are waiting for dma t/c interrupt */ #ifdef IHCP_TRACK printf("IHCP INTR: case DMA_WAIT \n") ; #endif IHCP_TRACK /*--- check for error pending ---*/ if(csr & DMAIO_ERR_PENDING) { printf("IHCP INTR: unit ihcp%d: error pending - flush_bfr issued! \n",unit_no) ; bp->b_flags |= B_ERROR ; /* what else to do ??? */ } /*--- better be t/c bit set ---*/ if(!(csr & DMAIO_TC)) { printf("IHCP INTR: unit ihcp%d: waiting-for-dma w/ tc not set! \n",unit_no) ; bp->b_flags |= B_ERROR ; /* hardware error!!! */ } /*--- turn off dma enb bit (should already be off) ---*/ dregs_p->Dmacsr &=~DMAIO_DMA_ENABLE ; /*--- turn off Dmaio interrutp enable bit ---*/ dregs_p->Dmacsr &= ~DMAIO_INT_ENABLE ; /* do a flush buffer to clear interrupt pending bit - if we leave it until strategy, and someone else is on this level, and we are 1st in the polling chain, we may get polled and falsely enter this routine, but since we don't flush we will loop forever!!!!! */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; /* actually, =flush also turns off dma & ints! */ DELAY(1) ; /* allow time for the int line to rise (slowly!) */ wakeup(bp) ; /* wake up strategy */ return ; case IHCP_HALF_WAIT: /* waiting for fifo less than 1/2 full */ case IHCP_RDY_WAIT: /* waiting for fifo empty & dev rdy int */ #ifdef IHCP_TRACK printf("IHCP INTR: at case RDY_WAIT or HALF_WAIT \n") ; #endif IHCP_TRACK /* make sure that the int flag is set - GROSS ERROR if not */ if(!(iregs_p->Ihcp_is_do & IHCP_INTFLAG)) { printf("IHCP INTR: unit ihcp%d: waiting-for-rdy interrupt w/ no\ interrupt flag set \n",unit_no) ; /* no way to pass this up */ /* to higher level !!! */ } /*--- turn off Dmaio interrupt enable bit ---*/ dregs_p->Dmacsr &= ~DMAIO_INT_ENABLE ; dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; /* actually, =flush also turns off dma & ints! */ DELAY(1) ; /* allow for slow int lines */ /*--- wake up the code that is waiting for fifo/rdy interrupt ---*/ wakeup(bp) ; return ; default: /**** we got an actual interrupt when not waiting for anything!!! since no bp assigned - we don't have any way to flag it - so just print a message, and try to turn off the offending interrupt. ****/ printf("IHCP INTR: unit ihcp%d: at case default - interrupt when not waiting! \n",unit_no); /*--- turn off ihcp int enable bits ---*/ iregs_p->Ihcp_lf &= ~(IHCP_RINT_ENB | IHCP_HALF_WAIT) ; /*--- issue reset int flag - after clearing enables ---*/ iregs_p->Ihcp_ri = 0 ; /*--- turn off int flag ---*/ iregs_p->Ihcp_ri = 0 ; /*set csr to flush-bfr to turn off tc, err pend, and int pend & enb's */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; DELAY(1) ; /* allow for slow int lines */ return; } } /*------------------------------------ Function: Handle an interrupt or interrupts that may or may not be from one or more of the ihcp units. This routine is safely declared static because it is explicitly registered by address. Inputs: None. Returns:: 1 if one or more interrupts has been serviced 0 if not -------------------------------------*/ static int ihcp_poll() { struct dmaio_regs *dregs_p; u_long csr ; /* control and status register */ u_int ihcp_unit_no; u_int int_serviced = 0; #ifdef IHCP_TRACK printf( "IHCP_POLL: ihcp_unit_array is 0x%x .\n", ihcp_unit_array) ; printf( "IHCP_POLL: nihcps is 0x%x\n", nihcps) ; #endif IHCP_TRACK /*-- March through the units, checking for an interrupt pending.*/ for (ihcp_unit_no = 0; ihcp_unit_no < nihcps; ++ihcp_unit_no) { dregs_p = ihcp_unit_array[ihcp_unit_no].dma_regs_p ; csr = dregs_p->Dmacsr ; #ifdef IHCP_TRACK printf( "IHCP_POLL: csr is 0x%x\n", csr) ; #endif IHCP_TRACK if ( csr & DMAIO_INT_PENDING) { /*-- Mark that we found an interrupting device. Call the interrupt service routine.*/ int_serviced = 1; (void) ihcp_intr(ihcp_unit_no); } } return (int_serviced); } /*================ LOADABLE DEVICE DRIVER SUPPORT ========================*/ extern int nulldev(); extern int seltrue(); /*------- Block device entry points for this driver. These are in bdevsw[] in sun/conf.c for preconfigured drivers.*/ #ifdef NOT_IN_CHARACTER_ONLY_DRIVERS struct bdevsw ihcp_bdevsw = { ihcp_open, ihcp_close, ihcp_strategy, ihcp_dump, ihcp_size, 0 }; #endif NOT_IN_CHARACTER_ONLY_DRIVERS /*------ Character device entry points for this driver. These are in cdevsw[] in sun/conf.c for preconfigured drivers.*/ struct cdevsw ihcp_cdevsw = { ihcp_open, ihcp_close, ihcp_read, ihcp_write, ihcp_ioctl, nulldev, seltrue, 0, 0 }; /*----- The vd driver is a non-loadable pseudo driver that loads loadable modules. The following initialized structure is used by the vd driver to locate entry points and data when loading and configuring the loadable driver. The fields are explained below. VDMAGIC_DRV is the magic number in the Drv_magic field that tells the vd driver that this loadable module is a device driver. If there was no physical SBus device associated with this driver, then VDMAGIC_PSEUDO would be used to indicate that this is a pseudo driver. Drv_name is a pointer to a string which is the name of the driver. Drv_dev_ops is a pointer to the dev_ops structure defined in sun/openprom.h which contains all of the entry points to driver routines which are known outside of the driver. The driver is responsible for having the dev_ops structure initialized. The address of Drv_bdevsw can be NULL if there is no block device for this driver. The address of Drv_cdevsw can be NULL if there is no character device for this driver. If the driver has a preconfigured location in the bedvsw or cdevsw structures, then that location index for the bdevsw goes in Drv_blockmajor, and that location index for the cdevsw goes in Drv_charmajor. This is the case for ll devices that are already supported by sun/conf.c. For drivers that do not have a preconfigured location in the bdevsw, Drv_vlockmajor should be 0, and the system will choose a major device number (location index) when the module is loaded by the vd driver. For drivers that do not have a preconfigured location in the cdevsw, Drv_charmajor should be 0, and the system will choose a major device number (location index) when the module is loaded by the vd driver.*/ struct vdldrv ihcp_drv = { VDMAGIC_DRV, /* Drv_magic */ "ihcp\0", /* *Drv_name */ &ihcp_ops, /* *Drv_dev_ops init to &ihcp_ops */ NULL, /* *Drv_bdevsw */ &ihcp_cdevsw, /* *Drv_cdevsw init to &ihcp_cdevsw */ 0, /* Drv_blockmajor */ 0 /* Drv_charmajor */ }; /*------------------------------------ Function: ihcp_vdcmd(command, vdp, vdi, vds) Support for a loadable device driver. Required by the utilities modload modstat & modunload. The vd driver is a non-loadable pseudo driver that loads loadable modules. The vd driver is used by modload modstat & modunload. All aloadable modules are required to supply a routine that is called by the vd driver when a module is loaded, unloaded, or the object of a VDSTAT status request ioctl on the vd driver. On the modload man page this routine is referred to as the entry point. Note that the default entry point name "xxxinit()" should never be used, as the names will collide if two or more drivers use this name. Inputs: command Identifies why this module was called. During loading (command == VDLOAD) the driver should do any initialization that is wants to do, get any desired config- uration information that may be available from modload, and then fill in the linkage pointer as shown below. Before unloading (command == VDUNLOAD) the driver is called to determine if it is OK to be unloaded, and to allow the driver to do any cleanup that it needs to do. During a status request (command == VDSTAT) the driver is called to fill in any type specific information into vds_modinfo. vdp Is a pointer to the vddrv structure. This is where the vd driver (think of it as the kernel) collects all information about a loaded module. The driver being loaded will fill in vddrv.vdd_vdtab, which is a pointer to the vdldrv structure initialized above. Since this pointer is not passed to the kernel until the VDLOAD command is handled by this interface routine, these initializations need not be static. vdi Is a pointer to one of the following structures: If the command is VDLOAD: The vdioctl_load structure contains configuration information if any was passed in from modload. If the command is VDUNLOAD: The vdioctl_unload structure contains only the module id. If the command is VDSTAT: The vdioctl_stat structure contains hooks to allow you to build an elaborate status function if you wish. vds Is a pointer to a status structure that may be filled in when the command is VDSTAT. This pointer is NULL when command has a value other than VDSTAT. For more information see /usr/include/sun/vddrv.h. Return: 0 on success, errno value on failure. -------------------------------------*/ int ihcp_vdcmd(command, vdp, vdi, vds) int command; /*-- Identify why module called*/ struct vddrv *vdp; /*-- Pointer to kernel's structure*/ struct vdioctl_load *vdi; /*-- Pointer to struct vdioctl_* */ struct vdstat *vds; /*-- ID and status information*/ { struct vdconf *vdconf_p; char *string_p; u_int ihcp_unit_no; int status = 0; IHCP_TRACK_MSG(logprit,"---------------(%d)-------------------- \n", vdp->vdd_id); IHCP_TRACK_MSG(logprit,"Entering ihcp_vdcmd, vdp 0x%x, vdi 0x%x,\ vds 0x%x.\n", vdp, vdi, vds); switch (command) { case VDLOAD: IHCP_TRACK_MSG(logprit, "Command is VDLOAD.\n"); /*-- For the driver of an SBus device, there are generally only two things to do in the VDLOAD step. The first is to get any available configruation information that may have passed in from a modload configuration file. The second is to fill in the linkage pointer to the vdldrv structure. Most initialization of the driver or its devices must wait until the driver's attach() routine is called because this is when (1) the count of physical devices is known as all calls to the driver's identify() routine have taken place, and (2) the dev_info pointer and therefore the devices themselves become available. Get the configuration info from the vd driver if any was passed in from modload. Some of this code cannot be useful to the ihcp driver. It is included for illustration. */ if (vdi !=NULL) { IHCP_TRACK_MSG(logprit, "vdi_id: %d\n", vdi->vdi_id); IHCP_TRACK_MSG(logprit, "vdi_status: %d\n", vdi->vdi_status); IHCP_TRACK_MSG(logprit, "vdi_mmapaddr: 0x%x\n", vdi->vdi_mmapaddr); IHCP_TRACK_MSG(logprit, "vdi_symtabsize: %d\n", vdi->vdi_symtabsize); IHCP_TRACK_MSG(logprit, "vdi_userconf: 0x%x\n", vdi->vdi_userconf); if (vdi->vdi_userconf != NULL) { vdconf_p = vdi->vdi_userconf; if (vdconf_p->vdc_type == VDCEND) { IHCP_TRACK_MSG(logprit,"No config info available.\n"); } while (vdconf_p->vdc_type != VDCEND) { switch (vdconf_p->vdc_type) { case VDCCONTROLLER: string_p = "(VDCCONTROLLER)"; break; case VDCDEVICE: string_p = "(VDCDEVICE)"; break; case VDCBLOCKMAJOR: string_p = "(VDCBLOCKMAJOR)"; break; case VDCCHARMAJOR: string_p = "(VDCCHARMAJOR)"; break; case VDCSYSCALLNUM: string_p = "(VDCSYSCALLNUM)"; break; default: IHCP_DEBUG_MSG(logprid, "vdconf\ switch error!\n"); string_p = "(SWITCH ERROR!)"; break; } /* end switch() */ IHCP_TRACK_MSG(logprit, "vdc_type: %d %s %s 0x%x\n", vdconf_p->vdc_type, string_p, "vdc_data:", vdconf_p->vdc_data); /*-- Move to next element of array*/ vdconf_p++; } /* end while (vdconf_p->vdc_type != VDCEND) */ } /* end if (vdi->vdi_userconf != NULL) */ else { IHCP_DEBUG_MSG(logprid, "No config info,\ vdi_userconf is NULL\n"); } } /* end if (vdi !=NULL) */ if (vdi == NULL) { IHCP_DEBUG_MSG(logprid, "No config info, vdi is NULL\n"); } /*-- Configure the ihcp driver from the available information. If no information was passed in from modload, then use the default info from this file. DEVICE SPECIFIC Fill in pointer to the vdldrv structure, which must be already initialized at this point.*/ vdp->vdd_vdtab = (struct vdlinkage *)&ihcp_drv; IHCP_TRACK_MSG(logprit, "Done VDLOAD.\n"); break; /* end case VDLOAD: */ case VDUNLOAD: IHCP_TRACK_MSG(logprit, "Command is VDUNLOAD.\n"); /*Check if the driver is in a condition to allow safe unloading. If it is, then cleanly shut the driver down, clean up, and release all dynamically allocated space. BE SURE to remove the driver from any interrupt chains that it is on.*/ for (ihcp_unit_no = 0; ihcp_unit_no < nihcps; ihcp_unit_no++) { /*-- Inhibit further opens on the unit*/ ihcp_unit_array[ihcp_unit_no].open_inhibit = 1; /*-- If any any unit is open, cannot unload.*/ if (ihcp_unit_array[ihcp_unit_no].unit_open !=0) { status = EBUSY; } } /*-- If it looks good so far, turn off interrupts, remove registration of interrupt vector, release all memory, and permit unload.*/ if (status == 0) { (void) ihcp_decommission(); } else { /*-- Refuse unload, return to business as usual.*/ for (ihcp_unit_no = 0; ihcp_unit_no < nihcps; ihcp_unit_no++) { /*-- Release the open inhibits*/ ihcp_unit_array[ihcp_unit_no].open_inhibit = 0; } } IHCP_TRACK_MSG(logprit, "Done VDUNLOAD.\n"); break; /* end case VDUNLOAD: */ case VDSTAT: IHCP_TRACK_MSG(logprit, "Command is VDSTAT.\n"); IHCP_TRACK_MSG(logprit, "nihcps is 0x%x\n", nihcps) ; IHCP_TRACK_MSG(logprit, "ihcp_unit_array is 0x%x \n", ihcp_unit_array ) ; /* DEVICE SPECIFIC */ IHCP_TRACK_MSG(logprit, "Done VDSTAT.\n"); break; /* end case VDSTAT: */ default: IHCP_TRACK_MSG(logprit, "ihcp_vdcmd: unknown command 0x%x\n",command); status = EINVAL; break; /* end case default */ } /* end switch (command) */ IHCP_TRACK_MSG(logprit, "Leaving ihcp_vdcmd, status (errno): %d.\n", status); return (status); } /*====================== PRIVATE SUPPORT ROUTINES ======================*/ /*------------------------------------ Function: START_CRITICAL(unit_no) Protect a critical section of code from interruption by the unit whose data structures are currently being serviced. In IHCP_DEBUG mode, assertion checking is done to assure the proper pairing of START_CRITICAL() and END_CRITICAL() calls. Input: unit_no The current unit number, an index into the ihcp_unit_array. Returns: None -------------------------------------*/ static void START_CRITICAL(unit_no) register u_int unit_no; { register int temp_saved_spl; IHCP_TRACK_MSG(logprit, "Entering START_CRITICAL: unit %d\n", unit_no); /*-- Raise the priority level to (n) and return the previous n */ temp_saved_spl = splr(ihcp_unit_array[unit_no].interrupt_pri); #ifdef IHCP_DEBUG /*-- Do assertion checking. This is a critical section*/ if (ihcp_unit_array[unit_no].saved_spl != (-1)) { IHCP_DEBUG_MSG(logprid, "START_CRITICAL: ASSERT failed,\ saved_spl: %d\n", ihcp_unit_array[unit_no].saved_spl); } #endif IHCP_DEBUG ihcp_unit_array[unit_no].saved_spl = temp_saved_spl; IHCP_TRACK_MSG(logprit, "Leaving START_CRITICAL: unit %d.\n", unit_no); return; } /*------------------------------------ Function: END_CRITICAL(unit_no) End the protection of code from interruption by the unit whose data structures are currently being serviced. In IHCP_DEBUG mode, assertion checking is done to assure the proper pairing of START_CRITICAL() and END_CRITICAL calls. Inputs: unit_no The current unit number, an index into the ihcp_unit. Returns: None. -------------------------------------*/ static void END_CRITICAL(unit_no) register u_int unit_no; { register int temp_saved_spl; IHCP_TRACK_MSG(logprit, "Entering END_CRITICAL: unit %d\n", unit_no); temp_saved_spl = ihcp_unit_array[unit_no].saved_spl; #ifdef IHCP_DEBUG /*-- Do assertion checking. This is a critical section*/ if (ihcp_unit_array[unit_no].saved_spl == (-1)) { IHCP_DEBUG_MSG(logprid, "END_CRITICAL: ASSERT failed, saved_spl: %d\n", ihcp_unit_array[unit_no].saved_spl); } ihcp_unit_array[unit_no].saved_spl = (-1); #endif IHCP_DEBUG /*-- Release the critical section last. The above is a critical section in itself.*/ /*-- Restore the previous priority level */ splx(temp_saved_spl); IHCP_TRACK_MSG(logprit, "Leaving END_CRITICAL: unit %d\n", unit_no); return; } /*------------------------------------ Function: see_devinfo(devinfo_p) Show interesting information stored in this node of the devinfo tree for developing and debugging. Inputs: devinfo_p Pointer to a node of the tree. Returns: None. -------------------------------------*/ static void see_devinfo(devinfo_p) register struct dev_info *devinfo_p; { struct dev_reg *dev_reg_p; struct dev_intr *dev_intr_p; u_int counter; IHCP_TRACK_MSG(logprit, "Entering see_devinfo: devinfo_p 0x%x\n", devinfo_p); IHCP_DEBUG_MSG(logprid, "devinfo_p->devi_name: %s\n", devinfo_p->devi_name); IHCP_DEBUG_MSG(logprid, "devinfo_p->devi_unit: %d\n", devinfo_p->devi_unit); IHCP_DEBUG_MSG(logprid, "devinfo_p->devi_nreg: %d\n", devinfo_p->devi_nreg); IHCP_DEBUG_MSG(logprid, "devinfo_p->devi_reg: 0x%x\n", devinfo_p->devi_reg); /*-- Show all device registers.*/ dev_reg_p = devinfo_p->devi_reg; /*-- dev_reg_p may be NULL, but only if devi_nreg is zero*/ for (counter = 1; counter <= devinfo_p->devi_nreg; ++counter, ++dev_reg_p) { if (dev_reg_p == NULL) break; IHCP_DEBUG_MSG(logprid, "REGISTER #%d: devi_reg->reg_bustype:\ 0x%x\n", counter, dev_reg_p->reg_bustype); IHCP_DEBUG_MSG(logprid, "REGISTER #%d: devi_reg->reg_addr:\ 0x%x\n", counter, dev_reg_p->reg_addr); IHCP_DEBUG_MSG(logprid, "REGISTER #%d: devi_reg->reg_size:\ 0x%x\n", counter, dev_reg_p->reg_size); } IHCP_DEBUG_MSG(logprid, "devinfo_p->devi_nintr: %d\n", devinfo_p->devi_nintr); IHCP_DEBUG_MSG(logprid, "devinfo_p->devi_intr: %d\n", devinfo_p->devi_intr); /*-- Show all the interrupt data.*/ dev_intr_p = devinfo_p->devi_intr; /*-- dev_intr_p may be NULL, but only if devi_nintr is zero*/ for (counter = 1; counter <= devinfo_p->devi_nintr; ++counter, ++dev_intr_p) { if (dev_intr_p == NULL) { break; } IHCP_DEBUG_MSG(logprid, "INTERRUPT #%d: devi_intr->int_pri: 0x%x\n", counter, dev_intr_p->int_pri); IHCP_DEBUG_MSG(logprid, "INTERRUPT #%d: devi_intr->int_vec: 0x%x\n", counter, dev_intr_p->int_vec); } IHCP_DEBUG_MSG(logprid, "deviinfo_p->devi_data: 0x%x\n", devinfo_p->devi_data); IHCP_DEBUG_MSG(logprid, "deviinfo_p->devi_nodeid: 0x%x\n", devinfo_p->devi_nodeid); IHCP_DEBUG_MSG(logprid, "Leaving see_devinfo: devinfo_p 0x%x\n", devinfo_p); return; } /*------------------------------------ Function: ihcp_decomission() Turn off interrupts, remove registration of all interrupt vectors, and relaease all memory. Note that the info for the interrupt levels came from the FCode PROM on each device, and therefore may vary from unit to unit. Be sure to remove all interrupt vectors, or the kernel will bus error if the device interrupts while the driver is unloaded. Inputs: None. Returns: None -------------------------------------*/ static void ihcp_decommission() { struct dev_intr *dev_intr_p; u_int ihcp_unit_no; int t ; int counter; IHCP_TRACK_MSG(logprit, "Entering ihcp_decommission.\n"); IHCP_TRACK_MSG(logprit, "At Decomm point zzxxx11yyyy\n"); /*-- Turn off interrupts for each unit.*/ IHCP_TRACK_MSG(logprit, "nihcps is 0x%x\n", nihcps) ; IHCP_TRACK_MSG(logprit, "ihcp_unit_array is 0x%x \n", ihcp_unit_array ) ; if (nihcps == 0) goto SKIP_DECOMM ; IHCP_TRACK_MSG(logprit, "[]Dmacsr is 0x%x \n", ihcp_unit_array[0].dma_regs_p->Dmacsr) ; IHCP_TRACK_MSG(logprit, "At Decomm point zzxxx15yyyy\n"); for (t=0; t<10000; t++) t=t ; /* kill some time */ /* WHY!!!!!!! */ for (ihcp_unit_no = 0; ihcp_unit_no < nihcps; ihcp_unit_no++) { /*-- ALL FIELDS CLEARED, idle state*/ ihcp_dma_init(ihcp_unit_no) ; } IHCP_TRACK_MSG(logprit, "At Decomm point zzxxx2yyyy\n"); /*--Everything that we need to know is in the unit structures. From there we access the devinfo struct to find the interrupt levels that we are (might be) installed on. Since duplicate registrations with addintr() are ingnored, duplicate unregistrations cannot be considered an error. March through and do everything meticulously. Do not take short cuts or you die.*/ for (ihcp_unit_no = 0; ihcp_unit_no < nihcps; ihcp_unit_no++) { IHCP_TRACK_MSG(logprit, "At Decomm point zzxxx3yyyy\n"); START_CRITICAL(ihcp_unit_no); IHCP_TRACK_MSG(logprit, "At Decomm point zzxxx4yyyy\n"); dev_intr_p = ihcp_unit_array[ihcp_unit_no]. devinfo_p->devi_intr; /*-- dev_intr_p may be NULL, but only if devi_nintr is zero*/ IHCP_TRACK_MSG(logprit, "At Decomm point zzxxx5yyyy\n"); for (counter = ihcp_unit_array[ihcp_unit_no].devinfo_p->devi_nintr; counter > 0; --counter, ++dev_intr_p){ if (dev_intr_p == NULL) { /*-- A "can't happen"*/ IHCP_ERROR_MSG(logprie, "ihcp_decommission ASSERT FAILED.\n"); break; } IHCP_TRACK_MSG(logprit, "At Decomm point zzxxx6yyyy\n"); (void) remintr(dev_intr_p->int_pri, ihcp_poll); } IHCP_TRACK_MSG(logprit, "At Decomm point zzxxx7yyyy\n"); END_CRITICAL(ihcp_unit_no); } /*-- Release the memory allocated for the unit structures*/ IHCP_TRACK_MSG(logprit, "At Decomm point zzxxx8yyyy\n"); if ((ihcp_unit_array !=NULL) && (nihcps >0)) { IHCP_TRACK_MSG(logprit, "At Decomm point zzxxx9yyyy\n"); (void) kmem_free ((caddr_t)ihcp_unit_array, (u_long)(nihcps * sizeof (struct ihcp_unit))); ihcp_unit_array = NULL; } SKIP_DECOMM: IHCP_TRACK_MSG(logprit, "Leaving ihcp_decommission.\n"); return; } /*------------------------------------ Function: get_property(devinfo_p, propname, defaultvalue) Get a property from the FCode PROM on the SBus card, or if the property is not available, substitute the assigned default. Only string valued properties are handled here. You have to know the name of a property that you want to get. There is no way to get all defined properties. The kernel support routine getlongprop() allows drivers to retrieve the value of a property for arbitrary sized properties. A pointer to the value of 'name' in the property list for 'devi' is returned. If no value is found, NULL is returned. The space is allocated using kmem_zalloc, so it is assumed that this routine is NOT called from an interrupt routine. If getlongprop() is called often, you should consider recycling the space with kmem_free(). Inputs: propname A pointer to string which is the assigned name of the desired property. defaultvalue A pointer to string which is the default value to use if the named property is not defined in the devinfo structure. Returns: A pointer to the value string selected. This pointer may have any value that was passed in for the default, including a NULL string or a NULL pointer. -------------------------------------*/ static char * get_property(devinfo_p, propname, defaultvalue) struct dev_info *devinfo_p; char *propname; char *defaultvalue; { char *ret_getlongprop; char *propval; char *msg_string; IHCP_TRACK_MSG(logprit, "Entering get_property: devinfo_p: 0x%x\n", devinfo_p); ret_getlongprop = (char *) getlongprop(devinfo_p->devi_nodeid, propname); if (ret_getlongprop == NULL) { propval = defaultvalue; msg_string = "default"; } else { propval = ret_getlongprop; msg_string = "from prom"; } IHCP_TRACK_MSG(logprit, "Leaving get_property: %s=%s (%s)\n", propname, propval, msg_string); } /*------------------------------------ Function: ihcp_strategy(bp) Setup and start a transfer on the device. IKON hardcopy boards are write only for DVMA. Inputs: bp Pointer to the buf struct in use. Returns: None -------------------------------------*/ static void ihcp_strategy(bp) register struct buf *bp; { struct dmaio_regs *dregs_p; u_int unit_no ; u_long csr ; /* control and status register */ u_long ar ; /* address register */ u_long bcr ; /* byte count register */ int sleepval ; /* saves value returned by sleep */ /*-- Get the unit number to locate our data structures. Set pointer to DVMA registers.*/ unit_no = IHCP_UNIT(bp->b_dev) ; dregs_p = ihcp_unit_array[unit_no].dma_regs_p ; IHCP_TRACK_MSG(logprit, "IHCP_STRGY: ihcp_unit_array is 0x%x .\n", ihcp_unit_array) ; IHCP_TRACK_MSG(logprit, "IHCP_STRGY: unit number %d.\n", unit_no) ; IHCP_TRACK_MSG(logprit, "IHCP_STRGY: dvmamap is 0x%x\n", dvmamap ) ; bcr = bp->b_bcount; if(bcr > DMA_MAXBLOCK) { IHCP_ERROR_MSG(logprie,"ihcp_strategy: unit ihcp%d: DVMA byte count too large! \n",unit_no) ; bp->b_flags = B_ERROR ; goto EXITSTRATEGY ; } /*-- Get DVMA bus resource, sleeping if necessary.*/ bp->b_mbinfo = mb_mapalloc(dvmamap, bp, 0, ((int (*) ())0), (caddr_t)0); /*-- Add the offset into DVMA space to the start of DVMA space. .*/ ar = (u_long) (MBI_ADDR(bp->b_mbinfo) + DVMA); IHCP_TRACK_MSG(logprit, "IHCP_STRGY: bp->b_mbinfo is 0x%x\n", bp->b_mbinfo) ; IHCP_TRACK_MSG(logprit, "IHCP_STRGY: DVMA is 0x%x\n", DVMA) ; IHCP_TRACK_MSG(logprit, "IHCP_STRGY: ar is 0x%x, bcr is 0x%x\n", ar, bcr) ; /*-- Write the DVMA start address and byte count to the hardware.*/ dregs_p->Dmaar = ar; IHCP_TRACK_MSG(logprit, "IHCP_STRGY: checkpoint xxxx1 Wrote ar\n") ; dregs_p->Dmabcr = bcr ; IHCP_TRACK_MSG(logprit, "IHCP_STRGY: checkpoint xxxx2 Wrote bcr\n") ; /*--- waiting for dma in unit_flags. error flags not cleared here so they will last between multiple calls to strategy by physio. this way if physio breaks up a buffer into multiple buffers, a timeout will still show up in flags when we get back to the _write routine. ---*/ ihcp_unit_array[unit_no].unit_flags |= IHCP_DMA_WAIT ; /*--- start the timeout counter - it will call ihcp_timeout() ---*/ timeout(ihcp_timeout,(caddr_t)bp,ihcp_unit_array[unit_no].dma_time) ; /*-- Enable interrupts so we will know when the DVMA is done. Start the DVMA in memory read mode ---*/ START_CRITICAL(unit_no) ; csr = (DMAIO_INT_ENABLE | DMAIO_DMA_ENABLE | DMAIO_COUNT_ENABLE ) ; dregs_p->Dmacsr = csr ; /* sleep until done interrupt, signal, or timeout */ sleepval = sleep(bp,(IHCPPRI | PCATCH)) ; dregs_p->Dmacsr &= ~DMAIO_DMA_ENABLE ; /* do 1st in case signal arrives*/ /* while dma is running!!!! */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; /* clears enables & cleans up */ /* flush must also be done in interrupt and timeout code to avoid endless loops if int pending left on! */ END_CRITICAL(unit_no) ; untimeout(ihcp_timeout,bp) ; /* turn off timer */ bp->b_resid = dregs_p->Dmabcr ; /* set remaining byte count */ (void)mb_mapfree(dvmamap,&bp->b_mbinfo) ; /* free DVMA resource */ /* check for signal termination or timeout and flag accordingly */ if(sleepval != 0) { ihcp_unit_array[unit_no].unit_flags |= IHCP_SIG_RECEIVED ; bp->b_flags |= B_ERROR ; } if(ihcp_unit_array[unit_no].unit_flags & IHCP_DMA_TIMEOUT) { bp->b_flags |= B_ERROR ; } EXITSTRATEGY: (void)iodone(bp) ; /* tell physio that we are done */ return; } /* end of stratety */ /*--- initialize the LSI dma chip and flush the ihcp fifo - also clears the latched function register ---*/ static void ihcp_dma_init(unit_no) u_int unit_no ; { /*-- set and clear the dma chip's reset bit. if left set the system will panic when a dma xfer is attempted --*/ ihcp_unit_array[unit_no].dma_regs_p->Dmacsr = DMAIO_DEV_RESET ; ihcp_unit_array[unit_no].dma_regs_p->Dmacsr = 0 ; /*-- pulse the Dmaio flush bit - reset doesn't clear the t/c flag ---*/ ihcp_unit_array[unit_no].dma_regs_p->Dmacsr = DMAIO_FLUSH_BFR ; /*-- master clear the IKON registers and fifo. this does not send a reset to the device. that is done by the application code if needed. it is also done when the bus is reset. ---*/ ihcp_unit_array[unit_no].ihcp_regs_p->Ihcp_mc = 0 ; /*any value works*/ } /* end of ihcp_dma_init */ /*----- function ihcp_timeout() called by the real time clock interrupt handler if timeout is reached. will issue wakeup. also sets timeout flag in unit_array much simplified from earlier version. leaves most bookkeeping to higher level code. -----*/ static int ihcp_timeout(bp) register struct buf *bp ; { struct dmaio_regs *dregs_p ; u_int unit_no ; u_int flags ; unit_no = IHCP_UNIT(bp->b_dev) ; /* get unit number */ /*-- Set pointer to DVMA registers. Set pointer to buf structure.*/ dregs_p = ihcp_unit_array[unit_no].dma_regs_p ; START_CRITICAL(unit_no) ; /* we are already in the clock's */ /* interrupt handler, so this is */ /* probably not necessary. */ /* IT SHOULD BE SAFE TO DO SINCE */ /* THE SPLR IN START CRIT WON'T */ /* LOWER THE PRIORITY LEVEL ???? */ /*--- turn off interrupts so we don't get rudely interrupted!! ---*/ dregs_p->Dmacsr &= ~DMAIO_INT_ENABLE ; END_CRITICAL(unit_no) ; flags = ihcp_unit_array[unit_no].unit_flags ; #ifdef IHCP_TRACK printf("IHCP TIMEOUT: called with flags = 0x%x\n",flags) ; #endif IHCP_TRACK flags &= (IHCP_DMA_WAIT | IHCP_RDY_WAIT | IHCP_HALF_WAIT) ; /*--- each case in this switch is complete - that is, it doesn't count on later code to disable ints, or do the return. I prefer to keep the wakeup as close to the return(s) as possible, so each case contains all the goodies. this also makes it easier to modify a case without unexpected side effects. ---*/ switch(flags) { case IHCP_DMA_WAIT: /* we are waiting for dma t/c interrupt */ #ifdef IHCP_TRACK printf("IHCP TIMEOUT: case DMA_WAIT \n") ; #endif IHCP_TRACK /*--- set timeout waiting for dma flag bit ---*/ ihcp_unit_array[unit_no].unit_flags |= IHCP_DMA_TIMEOUT ; /* turn off dma and int enable bits */ dregs_p->Dmacsr &= ~(DMAIO_DMA_ENABLE | DMAIO_INT_ENABLE) ; /* do flush buffer to clear int pending */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; wakeup(bp) ; /* wake up strategy */ return(0) ; case IHCP_HALF_WAIT: /* waiting for fifoDmacsr &= ~(DMAIO_DMA_ENABLE | DMAIO_INT_ENABLE) ; /* do flush buffer to clear int pending */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; wakeup(bp) ; return(0) ; default: /* if we get here things are really cooked !!! */ /* turn off dma and int enable bits */ dregs_p->Dmacsr &= ~(DMAIO_DMA_ENABLE | DMAIO_INT_ENABLE) ; /* do flush buffer to clear int pending */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; return(0) ; } } static int ihcp_rdy_wait(unit_no) /* wait for fifo empty & device ready */ /* returns 0 if condition true, */ /* -1 if timed out, +1 if signal int'd */ u_int unit_no ; { struct buf *bp ; struct dmaio_regs *dregs_p; struct ihcp_regs *iregs_p; u_short retval ; retval = 0 ; /*--- make register pointers ---*/ dregs_p = ihcp_unit_array[unit_no].dma_regs_p ; iregs_p = ihcp_unit_array[unit_no].ihcp_regs_p ; /*--- make a buffer pointer ---*/ bp = &ihcp_unit_array[unit_no].buf ; IHCP_TRACK_MSG(logprit, "at ihcp_rdy_wait \n") ; /*--- clear waiting-for and timeout flags in unit array ---*/ ihcp_unit_array[unit_no].unit_flags &= IHCP_CLEAR_FLAGS ; /*--- see if already ready & fifo empty - if so just exit w/retval=0 ---*/ if(iregs_p->Ihcp_is_do & IHCP_FIFO_DEV_RDY) return(retval) ; /*--- if not, set waiting-for flag and fire up interrupts ---*/ ihcp_unit_array[unit_no].unit_flags |= IHCP_RDY_WAIT ; /*--- start the timeout counter - it will call ihcp_timeout() ---*/ timeout(ihcp_timeout,(caddr_t)bp,ihcp_unit_array[unit_no].fifo_time) ; START_CRITICAL(unit_no) ; /*--- enable fifo empty and device ready interrupt ---*/ iregs_p->Ihcp_lf |= IHCP_RINT_ENB ; /*--- enable external interrupts in DMAIO chip ---*/ dregs_p->Dmacsr = (DMAIO_INT_ENABLE) ; /* sleep on bp until rdy, timeout, or signal */ if(sleep(bp,(IHCPPRI | PCATCH))) retval = 1 ; /* flags sig reveived */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; /* turns off everything! */ END_CRITICAL(unit_no) ; untimeout(ihcp_timeout,bp) ; /* stop watchdog timer */ iregs_p->Ihcp_lf &= ~(IHCP_RINT_ENB | IHCP_HINT_ENB) ; /* ikon enb's off*/ iregs_p->Ihcp_ri = 0 ; /* clear flag AFTER enb's turned off !!!!!!!!!!!!*/ /*--- check timeout flags ---*/ if(ihcp_unit_array[unit_no].unit_flags & IHCP_RDY_TIMEOUT) retval = -1 ; return(retval); } /* end of ihcp_rdy_wait */ static int ihcp_half_wait(unit_no) /* wait for fifo Ihcp_is_do & IHCP_FIFO_NOT_HALF) return(retval) ; /*--- if not, set waiting-for flag and fire up interrupts ---*/ ihcp_unit_array[unit_no].unit_flags |= IHCP_HALF_WAIT ; /*--- start the timeout counter - it will call ihcp_timeout() ---*/ timeout(ihcp_timeout,(caddr_t)bp,ihcp_unit_array[unit_no].fifo_time) ; START_CRITICAL(unit_no) ; /*--- enable fifo half full interrupt ---*/ iregs_p->Ihcp_lf |= IHCP_HINT_ENB ; /*--- enable external interrupts in DMAIO chip ---*/ dregs_p->Dmacsr = (DMAIO_INT_ENABLE) ; /* sleep on bp until fifo <1/2 full, timeout, or signal */ if(sleep(bp,(IHCPPRI | PCATCH))) retval = 1 ; /* flags sig reveived */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; /* turns off everything! */ END_CRITICAL(unit_no) ; untimeout(ihcp_timeout,bp) ; /* stop watchdog timer */ iregs_p->Ihcp_lf &= ~(IHCP_RINT_ENB | IHCP_HINT_ENB) ; /* ikon enb's off*/ iregs_p->Ihcp_ri = 0 ; /* clear flag AFTER enb's turned off !!!!!!!!!!!!*/ /*--- check timeout flags ---*/ if(ihcp_unit_array[unit_no].unit_flags & IHCP_HALF_TIMEOUT) retval = -1 ; return(retval); } /* end of ihcp_half_wait() */