/* IKON Corporation 2617 Western Ave. Seattle, WA 98121 (206) 728-6265 code module for Solaris 2.0 driver for Sbus DR11-W emulator Model 10103 21 October, 1992 BETA release 2 November, 1992 changed all CE_WARN to CE_CONT in cmn_err calls 12 November, 1992 added modinfobanner define so i wouldn`t forget to update modinfo banner when driver mods made 16 November, 1992 added comments to int rountine about possibility of "interrupt not serviced" warning from kernel (harmless & very unlikely!) 10 December, 1992 added test for err_pending to top of int routine that decides if an int is ours. we were only testing int_pending -which doesn't set when err_pending causes an interrupt - so we were not claiming our error interrupts, and putting the kernel into a loop also corrected a stupid bug in the strategy routine. ddi_dma_buf_setup was being called with a write flag regardless of wheter a read or a write was being performed. this didn't bother the desktop machines, but the MP units got VERY upset! (this caused the error int that showed up the above problem!) 16 September, 1994 replaced drv_usecwait(1) calls that follow all ikon register writes with calls to idr_delay. the write buffers on sparc 5 & 20 were causing register writes to be issued back-to-back at times, since the drv_usecwait call only delays between writes to the write buffer, not from the write buffer to the board. the 10103 requires 300ns after each write to the "ikon" portion of the board for the internal write to complete. back-to-back writes cause corruption of the first write's data. the new delay routine does two reads of the ikon portion of the board being written to to force the write buffer to flush, and to allow enough time for the write to complete. 1 November, 1994 fixed a bug in IDRIO_RDY_WAIT ioctl */ #define MODINFOBANNER "IKON DR11 - 1 Nov, 1994" /************************************************************************ * * * 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. * ************************************************************************/ /*================ INCLUDED FILES ===================================*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "idr_io.h" #include "idr_reg.h" #include "idr_var.h" static void *state_head ; /* opaque handle top of state structs */ static volatile u_char delay_dump_0; /* dummy variables for delay reads */ static volatile u_char delay_dump_1; /* These are the entry points into our driver that are called when the driver is loaded, during a system call, or in response to an interrupt. */ static int idr_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result); static int idr_identify(dev_info_t *dip); static int idr_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int idr_open(dev_t *dev, int openflags, int otyp, cred_t *credp); static int idr_close(dev_t dev, int openflags, int otyp, cred_t *credp); static int idr_read(dev_t dev, struct uio *uiop, cred_t *credp); static int idr_write(dev_t dev, struct uio *uiop, cred_t *credp); static int idr_ioctl(dev_t dev, int cmd, int arg, int flag, cred_t *credp, int *rvalp); static u_int idr_intr(); static int idr_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); /* These are internal private functions - although strategy is called by physio, it is not made visible externally */ static int idr_strategy(struct buf *bp) ; static void idr_timeout(struct idr_unit *unit_p) ; static void idr_dma_init(struct idr_unit *unit_p) ; static void idr_delay(volatile register struct idr_regs *iregs_p); /* When our driver is loaded or unloaded, the system calls our _init or _fini routine with a modlinkage structure. The modlinkage structure contains: modlinkage-> modldrv-> dev_ops-> cb_ops cb_ops contains the normal driver entry points and is roughly equivalent to the cdevsw & bdevsw structures in previous releases. dev_ops contains, in addition to the pointer to cb_ops, the routines that support loading and unloading our driver. */ static struct cb_ops idr_cb_ops = { idr_open, idr_close, nulldev, /* not a block driver */ nodev, /* no print routine */ nodev, /* no dump routine */ idr_read, idr_write, idr_ioctl, nodev, /* no devmap routine */ nulldev, /* no mmap routine */ nulldev, /* no segmap routine */ nochpoll, /* no chpoll routine */ ddi_prop_op, 0, /* not a STREAMS driver */ D_NEW | D_MP, /* safe for multi-thread/multi-processor */ }; /* NOTE: declare and initialize the dev_ops structure. if it is necessary to prevent the system from unloading the driver on a timed basis (it will if no driver calls are made for some (unknown!) length of time) replace idr_detach with nodev. this will keep the driver from unloading. if this is done, it will be necessary to do a reconfiguration boot any time a new version of the driver is to be loaded. */ static struct dev_ops idr_ops = { DEVO_REV, /* DEVO_REV indicated by manual */ 0, /* device reference count */ idr_getinfo, idr_identify, nulldev, /* device probe for non-self-id */ idr_attach, idr_detach, /* REPLACE W/nodev IF DRIVER SHOULD STAY ATTACHED */ nodev, /* device reset routine */ &idr_cb_ops, (struct bus_ops *)0, /* bus operations */ }; extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &mod_driverops, MODINFOBANNER, &idr_ops, }; static struct modlinkage modlinkage = { MODREV_1, /* MODREV_1 indicated by manual */ (void *)&modldrv, NULL, /* termination of list of linkage structures */ }; /* When our driver is loaded, idr_identify() is called with a dev_info_t full of information from the FCode on our board. */ static int idr_identify(dev_info_t *dip) { char *dev_name; dev_name = ddi_get_name(dip); if (strcmp(dev_name, IDR_NAME) == 0) { return (DDI_IDENTIFIED); } else { return (DDI_NOT_IDENTIFIED); } } /* idr_attach gets called if idr_identify returns DDI_IDENTIFIED This is a very paranoid attatch routine. We take all the knowledge we have about our board and check it against what has been filled in for us from our FCode. */ static int idr_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { register struct idr_unit *unit_p; int instance; int result; int error; char minor_node_name[6] ; if (cmd != DDI_ATTACH) return (DDI_FAILURE); instance = ddi_get_instance(dip); /* we have room for only 6 characters in node name - inculding null at end of string */ if((instance < 0) || (instance > 9)) { cmn_err(CE_CONT,"idr_attach: instance %d: instance out of range!\n",instance) ; return (DDI_FAILURE) ; } if (ddi_soft_state_zalloc(state_head, instance) != 0) { cmn_err(CE_CONT,"idr_attach: instance %d: ddi_soft_state_zalloc failure!\n",instance) ; return (DDI_FAILURE); } unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance); ddi_set_driver_private(dip, (caddr_t)unit_p); unit_p->dip = dip; /* interrupt number is 0 (we only use one interrupt level) ddi_idevice_cookie_t is not used a magic cookie for later use in MUTEXes will be returned in unit_p->iblock_cookie */ if (ddi_add_intr(dip, 0, &unit_p->iblock_cookie, (ddi_idevice_cookie_t *) NULL, idr_intr, (caddr_t) instance) != DDI_SUCCESS) { cmn_err(CE_CONT,"idr_attach: instance %d: ddi_add_intr error!\n",instance) ; return (DDI_FAILURE); } /* initialize MUTEX; we will use this MUTEX later on to lock our instance structure */ mutex_init(&unit_p->mutex, "idr mutex", MUTEX_DRIVER, (void *)&unit_p->iblock_cookie); /* we have a block of dma registers and a block of working registers check to make sure nreg==2 and the registers are the correct sizes */ error = ddi_dev_nregs(dip,&result); if (error == DDI_FAILURE || result != 2) { mutex_destroy(&unit_p->mutex); ddi_remove_intr(unit_p->dip, 0, unit_p->iblock_cookie); ddi_soft_state_free(state_head, instance); cmn_err(CE_CONT,"idr_attach: instance %d: ddi_dev_nreg error!\n",instance) ; return (DDI_FAILURE); } ddi_dev_regsize(dip, 0, (off_t *)&result); if (result != sizeof (struct dmaio_regs)) { mutex_destroy(&unit_p->mutex); ddi_remove_intr(unit_p->dip, 0, unit_p->iblock_cookie); ddi_soft_state_free(state_head, instance); cmn_err(CE_CONT,"idr_attach: instance %d: ddi_dev_regsize error!\n",instance) ; return (DDI_FAILURE); } ddi_dev_regsize(dip, 1, (off_t *)&result); if (result != sizeof (struct idr_regs)) { mutex_destroy(&unit_p->mutex); ddi_remove_intr(unit_p->dip, 0, unit_p->iblock_cookie); ddi_soft_state_free(state_head, instance); cmn_err(CE_CONT,"idr_attach: instance %d: ddi_dev_regsize error!\n",instance) ; return (DDI_FAILURE); } /* the registers look OK, so let's map them in so we can use them */ if (ddi_map_regs(dip, 0, (caddr_t *)&(unit_p->dma_regs_p), 0, sizeof (struct dmaio_regs)) != DDI_SUCCESS) { mutex_destroy(&unit_p->mutex); ddi_remove_intr(unit_p->dip, 0, unit_p->iblock_cookie); ddi_soft_state_free(state_head, instance); cmn_err(CE_CONT,"idr_attach: instance %d: ddi_map_regs error!\n",instance) ; return (DDI_FAILURE); } if (ddi_map_regs(dip, 1, (caddr_t *)&(unit_p->idr_regs_p), 0, sizeof (struct idr_regs)) != DDI_SUCCESS) { mutex_destroy(&unit_p->mutex); ddi_remove_intr(unit_p->dip, 0, unit_p->iblock_cookie); ddi_soft_state_free(state_head, instance); ddi_unmap_regs(dip, 0, (caddr_t *)&(unit_p->dma_regs_p), 0, sizeof (struct dmaio_regs)); cmn_err(CE_CONT,"idr_attach: instance %d: ddi_map_regs error!\n",instance) ; return (DDI_FAILURE); } /* ddi_create_minor_node creates an entry in an internal kernel table; the actual entry in the file system is created by drvconfig(1) when you run add_drv(1); DDI_PSEUDO seems like a strange node type - it was recommended by the Sbus folks! we will save the instance as our minor # for future availability to write and ioctl routines use string format to generate idrx node name */ sprintf(minor_node_name,"idr%d",instance) ; if (ddi_create_minor_node(dip, minor_node_name, S_IFCHR, instance, DDI_PSEUDO, NULL) == DDI_FAILURE) { mutex_destroy(&unit_p->mutex); ddi_remove_intr(unit_p->dip, 0, unit_p->iblock_cookie); ddi_soft_state_free(state_head, instance); ddi_unmap_regs(dip, 0, (caddr_t *)&(unit_p->dma_regs_p), 0, sizeof (struct dmaio_regs)); ddi_unmap_regs(dip, 1, (caddr_t *)&(unit_p->idr_regs_p), 0, sizeof (struct idr_regs)); cmn_err(CE_CONT,"idr_attach: instance %d: ddi_create_minor_node error!\n",instance) ; return (DDI_FAILURE); } /* clear unit flags */ unit_p->unit_flags = 0 ; /* reset DVMA and DR11 logic */ idr_dma_init(unit_p) ; /* we have commented out the attach message to avoid lots of console messages as the driver is loaded and unloaded (arbitrarily) by the kernel cmn_err(CE_CONT,"idr_attach: instance %d: attached!\n",instance) ; */ ddi_report_dev(dip); return (DDI_SUCCESS); } /* This is a pretty generic getinfo routine as described in the manual. */ static int idr_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { register int error; register struct idr_unit *unit_p; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, getminor((dev_t)arg)); if (unit_p == NULL) { *result = NULL; cmn_err(CE_CONT,"idr_getinfo: null unit_pointer!\n") ; error = DDI_FAILURE; } else { mutex_enter(&unit_p->mutex); *result = unit_p->dip; mutex_exit(&unit_p->mutex); error = DDI_SUCCESS; } break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)getminor((dev_t)arg); error = DDI_SUCCESS; break; default: *result = NULL; error = DDI_FAILURE; cmn_err(CE_CONT,"idr_getinfo: unrecognized info_command!\n") ; } return (error); } /* _init, _info, and _fini support loading and unloading the driver. */ static int _init(void) { register int error; if ((error = ddi_soft_state_init(&state_head, sizeof (struct idr_unit), 1)) != 0) return (error); if ((error = mod_install(&modlinkage)) != 0) ddi_soft_state_fini(&state_head); return (error); } static int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } static int _fini(void) { int status; if ((status = mod_remove(&modlinkage)) != 0) return (status); ddi_soft_state_fini(&state_head); return (status); } /* When our driver is unloaded, idr_detach cleans up and frees the resources we allocated in idr_attach. */ static int idr_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { register struct idr_unit *unit_p; /* will point to this */ int instance; if (cmd != DDI_DETACH) return (DDI_FAILURE); instance = ddi_get_instance(dip); unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance); /* reset the DVMA and DR11 logis */ idr_dma_init(unit_p) ; /* Remove the minor node created in attach */ ddi_remove_minor_node(dip, NULL); ddi_unmap_regs(dip, 0, (caddr_t *)&unit_p->dma_regs_p, 0, sizeof (struct dmaio_regs)); ddi_unmap_regs(dip, 1, (caddr_t *)&unit_p->idr_regs_p, 0, sizeof (struct idr_regs)); ddi_remove_intr(unit_p->dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); ddi_soft_state_free(state_head, instance); /* message print commented out so we don't clutter up the console as the driver is loaded and unloaded by the kernel cmn_err(CE_CONT,"idr_detach: instance %d: detached!\n",instance) ; */ return (DDI_SUCCESS); } /* idr_open is called in response to the open() system call */ static int idr_open(dev_t *dev, int openflags, int otyp, cred_t *credp) { int retval = 0; int instance ; register struct idr_unit *unit_p ; instance = getminor(*dev) ; unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance) ; /* verify instance structure */ if(unit_p == NULL) { cmn_err(CE_CONT,"idr_open: null unit pointer!\n") ; return(ENXIO) ; } /* verify that we are bieng opened as a character device */ if(otyp != OTYP_CHR) { cmn_err(CE_CONT,"idr_open: instance %d: wrong open_type!\n",instance) ; return(EINVAL) ; } /* lock unit structure (& flags & registers) */ mutex_enter(&unit_p->mutex) ; /* exclusive open device */ if(unit_p->unit_open != FALSE) { retval = EBUSY ; } else { /* mark device as open */ unit_p->unit_open = TRUE ; /* initialize DVMA and DR11 logic */ idr_dma_init(unit_p) ; /* initialize time out values to defaults */ unit_p->dma_time = HZ * DMA_TIME_DEF ; unit_p->attn_time = HZ * ATTN_TIME_DEF ; unit_p->rdy_time = HZ * RDY_TIME_DEF ; /* set read and write pulse and function values to defaults */ unit_p->read_fcn = READ_FCN_DEF ; unit_p->write_fcn = WRITE_FCN_DEF ; unit_p->read_pulse = READ_PULSE_DEF ; unit_p->write_pulse = WRITE_PULSE_DEF ; /* set the mode and latch registers to their default values */ unit_p->idr_regs_p->Idr_mode = MODE_REG_DEF ; idr_delay(unit_p->idr_regs_p) ; unit_p->idr_regs_p->Idr_latch = LATCH_REG_DEF ; idr_delay(unit_p->idr_regs_p) ; /* clear the various flags in unit_flags */ unit_p->unit_flags = 0 ; } mutex_exit(&unit_p->mutex) ; return (retval); } /* idr_close is called after the last process that has the device open calls close() since we are exclusive open, idr_close will always be calles when the user process calls close (or when the user process exits) */ static int idr_close(dev_t dev, int openflags, int otyp, cred_t *credp) { register struct idr_unit *unit_p ; unit_p = (struct idr_unit *)ddi_get_soft_state(state_head,getminor(dev)) ; /* verify instance structure - if this fails we are really in big trouble! */ if(unit_p == NULL) { cmn_err(CE_CONT,"idr_close: null unit pointer!\n") ; return(ENXIO) ; } /* lock unit structure (and flags and registers) */ mutex_enter(&unit_p->mutex) ; /* reset dma logic, DR11 logic, and flush fifo */ idr_dma_init(unit_p) ; unit_p->unit_open = FALSE ; mutex_exit(&unit_p->mutex) ; return (0); } static int idr_read(dev_t dev, struct uio *uiop, cred_t *credp) { register struct idr_unit *unit_p ; int retval ; int instance ; retval = 0 ; instance = getminor(dev) ; unit_p = (struct idr_unit *)ddi_get_soft_state(state_head,instance) ; if(unit_p == NULL) { cmn_err(CE_CONT,"idr_read: instance: %d: unit_pointer null!\n",instance) ; return(ENXIO) ; } /* since we are exclusive open - we shouldn't need a mutex -- we will leave it for strategy to pick up */ /* clear the timeout, multicycle, sig received, and "waiting for" flags. strategy will set the waiting for dma or eor flag. CLEAR_FLAGS = ~(a bunch of flags or'd together) */ unit_p->unit_flags &= IDR_CLEAR_FLAGS ; /* set input flag */ unit_p->unit_flags |= IDR_INPUT ; /* we will use the system minphys number as a maximum buffer size, and use a buf provided by the system. if the buffer size requested by the read/write call is larger than minphys, physio will make multiple calls to idr_strategy. This may not be appropriate for some applications. if the system minphys is too small, the customer may replace minphys with idr_minphys, which is described below. using a large IDR_MAXPHYS may cause problems with the system's mapping capability - if so it may be necessary to modify idr_strategy to use a sliding window into the buffer. See the Solaris 2.0 device driver writing manual for details. if the required read/write buffer is larger than available mapping space, it may be better to use the idr driver's MANUAL mode of operation. sample idr_minphys: #define IDR_MAXPHYS some buffer size static void idr_minphys(struct buf *bp) { bp->b_bcount = (min(bp->b_bcount,IDR_MAXPHYS)) ; } */ retval = physio(idr_strategy, (struct buf *)NULL, dev, B_READ, minphys, uiop) ; /* check flags for timeout or multicycle error or sig received */ if(unit_p->unit_flags & (IDR_DVMA_TIMEOUT|IDR_EOR_TIMEOUT)) { retval = EIO ; cmn_err(CE_CONT,"idr_read: instance %d: DMA timeout! \n",instance) ; } if(unit_p->unit_flags & IDR_MCYL_ERR) { retval = EIO ; cmn_err(CE_CONT,"idr_read: instance %d: Multicycle error! \n",instance) ; } if(unit_p->unit_flags & IDR_SIG_RECEIVED) { retval = EINTR ; cmn_err(CE_CONT,"idr_read: instance %d: signal received! \n",instance) ; } return (retval); } static int idr_write(dev_t dev, struct uio *uiop, cred_t *credp) { register struct idr_unit *unit_p ; int retval ; int instance ; retval = 0 ; instance = getminor(dev) ; unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance) ; if(unit_p == NULL) { cmn_err(CE_CONT,"idr_write: instance %d: unit pointer NULL!\n", instance) ; return(ENXIO) ; } /* we will leave the mutex for strategy */ /* clear the timeout, multicycle error, sig received, and "waiting for" flags strategy will set the waiting for dvma or eor flag. */ unit_p->unit_flags &= IDR_CLEAR_FLAGS ; /* clear the input flag (=output) */ unit_p->unit_flags &= ~IDR_INPUT ; /* we will use the system minphys number and a system buf - see idr_read */ retval = physio(idr_strategy, (struct buf *)NULL, dev, B_WRITE, minphys, uiop) ; /* check flags for timeout or multicycle error or sig received */ if(unit_p->unit_flags & (IDR_DVMA_TIMEOUT|IDR_EOR_TIMEOUT)) { retval = EIO ; cmn_err(CE_CONT,"idr_write: instance %d: DMA timeout! \n",instance) ; } if(unit_p->unit_flags & IDR_MCYL_ERR) { retval = EIO ; cmn_err(CE_CONT,"idr_write: instance %d: Multicycle error! \n", instance) ; } if(unit_p->unit_flags & IDR_SIG_RECEIVED) { retval = EINTR ; cmn_err(CE_CONT,"idr_write: instance %d: signal received! \n", instance) ; } return (retval); } /* note that the ioctl routine treats arg as a pointer! we also note that the following code needs another level of indentation, but it was copied from an earlier driver, and we are too lazy to make the changes. */ static int idr_ioctl(dev_t dev, int cmd, int arg, int flag, cred_t *credp, int *rvalp) { register struct idr_unit *unit_p ; volatile register struct dmaio_regs *dregs_p; volatile register struct idr_regs *iregs_p ; u_long bits ; /* will contain incoming or outgoing arg value */ u_char ct ; /* temp char */ int retval ; /* return value (errno) for system call*/ int command ; int sleepval ; int instance ; int timeout_id ; int i ; /* generic counter */ u_int return_array[11] ; /* will contain copy of registers to return to caller */ retval = 0 ; instance = getminor(dev) ; unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance) ; if(unit_p == NULL) { cmn_err(CE_CONT,"ir_ioctl: instance %d: unit_pointer NULL!\n",instance) ; return(ENXIO) ; } /* lock our unit structure */ mutex_enter(&unit_p->mutex) ; iregs_p = unit_p->idr_regs_p ; /*points at ikon regs */ dregs_p = unit_p->dma_regs_p; /*points at dmaio regs */ command = cmd & IDRIO_CMD_MASK ; /* get command */ /* get first word of arg (even if we are eventually doing a copy out) */ copyin((caddr_t)arg, (caddr_t)&bits, sizeof(bits)) ; /* note our arg is a pointer! */ #define MASK IDRIO_CMD_MASK /* use to strip the count from cases */ switch(command) { /* go do the deed */ case MASK & IDRIO_SET_MODE: /* set mode reg bits & rdis bit in latch reg */ /* mode bits are in bottom 8 of bits, rdis */ /* is l shifted by 8 in bits (rdis*256) */ iregs_p->Idr_mode = (u_char)bits ; /* set mode reg */ idr_delay(iregs_p) ; iregs_p->Idr_latch &= (u_char)~IDR_RDIS ; /* mask out rdis bit */ idr_delay(iregs_p) ; /* set rdis if necessary */ iregs_p->Idr_latch |= (u_char)(IDR_RDIS & (bits >> 8)) ; idr_delay(iregs_p) ; break ; case MASK & IDRIO_IMM_FCN: /* set function bits immediately */ /* read latch into ct & calculate result & then write to latch */ /* reg all at one shot, rather than first masking out in latch */ /* reg to keep fcn bits from going off then on again - some */ /* devices may be sensitive to this */ ct = (u_char)(iregs_p->Idr_latch & ~IDR_FMASK) ; iregs_p->Idr_latch = ct | (u_char)(bits & IDR_FMASK) ; idr_delay(iregs_p) ; break ; case MASK & IDRIO_READ_FCN: /* save fcn bits for use at read start */ unit_p->read_fcn = (bits & IDR_FMASK) ; break ; case MASK & IDRIO_WRITE_FCN: /* save fcn bits for use at write start */ unit_p->write_fcn = (bits & IDR_FMASK) ; break ; case MASK & IDRIO_IMM_PULSE: /* issue pulses asap */ if(bits & IDR_GO) { /* wait for attn off if issuing go */ /* wait 100uS max - ????????????? */ i = 0; while((iregs_p->Idr_p_stat & IDR_ATTN)&&(i<100)) { drv_usecwait(1) ; i++ ; } if(iregs_p->Idr_p_stat & IDR_ATTN) { cmn_err(CE_CONT,"idr_ioctl: instance %d at IMM_PULSE: attempt to issue GO while ATTENTION true!\n", instance) ; retval = EIO ; goto IOCTLEXIT ; } } iregs_p->Idr_p_stat = (u_char)bits ; idr_delay(iregs_p) ; break ; case MASK & IDRIO_READ_PULSE: /* save pulses for read start */ unit_p->read_pulse = bits ; break ; case MASK & IDRIO_WRITE_PULSE: /* save pulses for write start */ unit_p->write_pulse = bits ; break ; case MASK & IDRIO_SET_DMA_TIME: /* set dma timeout parameter */ 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 */ unit_p->dma_time = (u_int)(bits * HZ) ; /* set ticks */ break ; case MASK & IDRIO_SET_ATTN_TIME: /* set wait for attn timeout value */ /* or fifo empty & dev rdy wait */ if(bits < ATTN_TIME_MIN) bits = ATTN_TIME_MIN ; /* not less than */ if(bits > ATTN_TIME_MAX) bits = ATTN_TIME_MAX ; /* or more than..*/ unit_p->attn_time = (u_int)(bits * HZ) ; /* ticks*/ break ; case MASK & IDRIO_SET_RDY_TIME: /* set wait for ready timeout value */ /* or fifo empty & dev rdy wait */ if(bits < RDY_TIME_MIN) bits = RDY_TIME_MIN ; /* not less than */ if(bits > RDY_TIME_MAX) bits = RDY_TIME_MAX ; /* or more than..*/ unit_p->rdy_time = (u_int)(bits * HZ) ; /* ticks*/ break ; case MASK & IDRIO_ATTN_WAIT: /* wait for attention to set attf - looking */ /* for an EDGE on attention. if flag is */ /* already set - return clear flag */ /* the interrupt routine will not touch */ /* the flags - we will clear here on return */ unit_p->unit_flags &= IDR_CLEAR_FLAGS ; if(iregs_p->Idr_p_stat & IDR_ATTF) { /* test for attf already set*/ iregs_p->Idr_p_stat = IDR_RATN ; /* clear flag */ idr_delay(iregs_p) ; goto IOCTLEXIT ; } unit_p->unit_flags |= IDR_ATTN_WAIT ; /*set wait flag*/ /* start timeout routine ticking - will call idr_timeout if timeout */ timeout_id = timeout(idr_timeout, (void *)unit_p, unit_p->attn_time) ; iregs_p->Idr_latch |= IDR_ATTM ; /* enable attf interrupts */ idr_delay(iregs_p) ; dregs_p->Dmacsr = DMAIO_INT_ENABLE ; /* master int enb for board */ sleepval = cv_wait_sig(&unit_p->cv, &unit_p->mutex) ; /* sleep on cv -until int or */ /* timeout or signal */ dregs_p->Dmacsr &= ~DMAIO_INT_ENABLE ; /* disable interrupts */ iregs_p->Idr_latch &= ~IDR_ATTM ; /* attention mask off */ idr_delay(iregs_p) ; untimeout(timeout_id) ; /* turn off timer */ /* check for signal received during sleep */ if(sleepval == 0) { cmn_err(CE_CONT,"idr_ioctl: instance %d: at ATTN wait: signal received! \n",instance) ; retval = EINTR ; goto IOCTLEXIT ; } /* check for timeout return */ if(unit_p->unit_flags & IDR_ATTN_TIMEOUT) { /* attn timeout may not be an error */ retval = EIO ; goto IOCTLEXIT ; } iregs_p->Idr_p_stat = IDR_RATN ; /* clear attn flag here */ idr_delay(iregs_p) ; break ; case MASK & IDRIO_RDY_WAIT: /* wait for REDY to go true from attn or eor */ /* if already set - just return */ /* look at flags to figure out why redy was */ /* set. if we were waiting for eor and got */ /* an attention also - the attn flag is left */ /* on since it might represent an incoming */ /* attention that followed the block xfer */ unit_p->unit_flags &= IDR_CLEAR_FLAGS ; if(iregs_p->Idr_p_stat & IDR_REDY) { /* test for redy already set*/ goto RDYWAITEXIT ; } unit_p->unit_flags |= IDR_RDY_WAIT ; /*set wait flag*/ /* start timeout routine ticking - will call idr_timeout if timeout */ timeout_id = timeout(idr_timeout, (void *)unit_p, unit_p->rdy_time) ; iregs_p->Idr_latch |= (IDR_ATTM | IDR_EORM) ; /* enb attn or eor int */ idr_delay(iregs_p) ; dregs_p->Dmacsr = DMAIO_INT_ENABLE ; /* master int enb for board */ sleepval = cv_wait_sig(&unit_p->cv, &unit_p->mutex) ; /* sleep on cv -until int or */ /* timeout or signal */ dregs_p->Dmacsr &= ~DMAIO_INT_ENABLE ; iregs_p->Idr_latch &= ~(IDR_ATTM | IDR_EORM) ; idr_delay(iregs_p) ; untimeout(timeout_id) ; /* check for signal received during sleep */ if(sleepval == 0) { cmn_err(CE_CONT,"idr_ioctl: instance %d: at RDY wait: signal received! \n",instance) ; retval = EINTR ; goto IOCTLEXIT ; } /* check for timeout return */ if(unit_p->unit_flags & IDR_RDY_TIMEOUT) { /* timeout may not be an error */ retval = EIO ; goto IOCTLEXIT ; } RDYWAITEXIT: /* examine dr11 flag bits to figure out why ready was set */ /* if attf is set and eor is not - clear attf */ if((iregs_p->Idr_p_stat & IDR_ATTF) && !(iregs_p->Idr_p_stat & IDR_EORF)) { iregs_p->Idr_p_stat = IDR_RATN ; idr_delay(iregs_p) ; } iregs_p->Idr_p_stat = IDR_REOR ; /* always clear eorf*/ idr_delay(iregs_p) ; break ; case MASK & IDRIO_GET_STATUS: /* return dr11 status register */ bits = (u_long)iregs_p->Idr_p_stat ; copyout((caddr_t)&bits,(caddr_t)arg,sizeof(bits)) ; break ; case MASK & IDRIO_GET_RANGE: /* gets 24 bit range counter value */ /* in raw form: = word count minus 1 */ bits = (iregs_p->Idr_rhi << 16) | (iregs_p->Idr_rmid << 8) | iregs_p->Idr_rlow ; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(bits)) ; break ; case MASK & IDRIO_GET_REGS: /* get all the board's registers*/ iregs_p->Idr_latch |= IDR_DMIN ; /* force input mode */ idr_delay(iregs_p) ; return_array[0] = dregs_p->Dmacsr ; /* get dma csr */ return_array[1] = dregs_p->Dmaar ; /* get add register */ return_array[2] = dregs_p->Dmabcr ; /* get count register */ return_array[3] = (u_long)iregs_p->Idr_mode ; /* get mode reg */ return_array[4] = (u_long)iregs_p->Idr_latch ; /* get latch reg */ return_array[5] = (u_long)iregs_p->Idr_p_stat ; /* status register */ return_array[6] = (u_long)iregs_p->Idr_dhi ; /* data high byte */ return_array[7] = (u_long)iregs_p->Idr_dlow ; /* data low byte */ return_array[8] = (u_long)iregs_p->Idr_rhi ; /* range hi byte */ return_array[9] = (u_long)iregs_p->Idr_rmid ; /* middle range byte */ return_array[10] = (u_long)iregs_p->Idr_rlow ; /* low range byte */ copyout((caddr_t)return_array, (caddr_t)arg, sizeof(return_array)) ; break ; case MASK & IDRIO_GET_FLAGS: /* return unit flags to caller */ bits = unit_p->unit_flags ; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(bits)) ; break ; case MASK & IDRIO_DATA_OUT: /* output 16 bit word to data out reg */ iregs_p->Idr_latch &= ~IDR_DMIN ; /* force output mode */ idr_delay(iregs_p) ; iregs_p->Idr_dhi = (u_char)(bits >> 8) ; /* high byte */ idr_delay(iregs_p) ; iregs_p->Idr_dlow = (u_char)bits ; /* low byte */ idr_delay(iregs_p) ; break ; case MASK & IDRIO_DATA_IN: /* read dr11 input data register */ iregs_p->Idr_latch |= IDR_DMIN ; /* force input mode */ idr_delay(iregs_p) ; bits = (u_long)((iregs_p->Idr_dhi << 8) | iregs_p->Idr_dlow) ; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(bits)) ; break ; case MASK & IDRIO_SET_RANGE: /* set dr11 range counter to desired value */ /* count is not adjusted - calling argument*/ /* will be plugged directly into range */ /* arg should be word count minus one!!! */ if(bits > IDR_DR11_MAXBLOCK) { cmn_err(CE_CONT,"idr_ioctl: instance %d at SET_RANGE - desired range count greater than maximum allowed! \n", instance) ; retval = EINVAL ; goto IOCTLEXIT ; } iregs_p->Idr_rhi = (u_char)(bits >> 16) ; idr_delay(iregs_p) ; iregs_p->Idr_rmid = (u_char)(bits >> 8) ; idr_delay(iregs_p) ; iregs_p->Idr_rlow = (u_char)bits ; idr_delay(iregs_p) ; break ; case MASK & IDRIO_AUTO: /* set automatic mode (default) */ unit_p->unit_flags &= ~IDR_MANUAL ; break ; case MASK & IDRIO_MANUAL: /* set manual mode */ unit_p->unit_flags |= IDR_MANUAL ; break ; case MASK & IDRIO_START_READ: /* enables block read in man mode */ iregs_p->Idr_p_stat = (IDR_REOR | IDR_RATN) ; /* force fags off */ idr_delay(iregs_p) ; iregs_p->Idr_latch &= ~IDR_DMON ; /* make sure dmon is off! */ idr_delay(iregs_p) ; iregs_p->Idr_latch |= IDR_DMIN ; /* set to input mode */ idr_delay(iregs_p) ; iregs_p->Idr_latch |= IDR_DMON ; /* now enable w/DMON */ idr_delay(iregs_p) ; break ; case MASK & IDRIO_START_WRITE: /* enables block write in man mode*/ iregs_p->Idr_p_stat = (IDR_REOR | IDR_RATN) ; /* force fags off */ idr_delay(iregs_p) ; iregs_p->Idr_latch &= ~IDR_DMON ; /* make sure dmon is off! */ idr_delay(iregs_p) ; iregs_p->Idr_latch &= ~IDR_DMIN ; /* set to output mode */ idr_delay(iregs_p) ; iregs_p->Idr_latch |= IDR_DMON ; /* now enable w/DMON */ idr_delay(iregs_p) ; break ; case MASK & IDRIO_BLOCK_END: /* disable overall block transfer */ /* turn off dmon and int masks in dr11 logic */ iregs_p->Idr_latch &= ~(IDR_DMON | IDR_EORM | IDR_ATTM) ; idr_delay(iregs_p) ; iregs_p->Idr_p_stat = IDR_TERM ; /* make sure REDY true */ idr_delay(iregs_p) ; dregs_p->Dmacsr = DMAIO_DEV_RESET ; /* force DVMA logic off!*/ dregs_p->Dmacsr = 0 ; dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; /* old chip req'd this */ /* to clear TC bit */ break ; default: /* an unrecognized ioctl command may not be an error - we may get probed by pipe logic w/ a generic TCGETA ioctl. return an error so we don`t look like a terminal, but don`t print an error. */ retval = EINVAL; break; } IOCTLEXIT: /* release mutex */ mutex_exit(&unit_p->mutex) ; return (retval); } /* poor indentation is left over from an earlier driver */ static u_int idr_intr(caddr_t instance) { register struct idr_unit *unit_p ; volatile register struct dmaio_regs *dregs_p ; volatile register struct idr_regs *iregs_p ; register struct buf *bp; u_int int_serviced = DDI_INTR_UNCLAIMED ; int i ; u_int flags ; /* unit array flags for this unit */ /* get unit pointer */ unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, (u_int)instance) ; /* lock unit array and associated hardware */ mutex_enter(&unit_p->mutex) ; /* Set pointer to DVMA & idr registers. Set pointer to buf structure */ dregs_p = unit_p->dma_regs_p ; iregs_p = unit_p->idr_regs_p ; bp = unit_p->buf_p ; /************************************************************************/ /* */ /* the dvma controller is capable of screaming data to and from */ /* the fifos at 3 sbus clocks per transfer. this is so fast that */ /* slave accesses from the cpu to the DR11 registers are locked */ /* out during this time(if the fifos are accepting or delivering */ /* data at full speed). if the cpu tries to read or write a dr11 */ /* register during full speed dvma, it will probably get a timeout */ /* error, which will cause a KERNEL PANIC(!) if it happens within */ /* driver!! it is absulutely necessary that the dvma enable bit be */ /* OFF when the driver accesses the DR11 registers in the interrupt*/ /* code - and in most other places as well. the problem is that */ /* in input mode, we may get an attention interrupt while there */ /* is still data in the dvma cache. the interrupt code will need */ /* to check the draining bits and leave dvma on until the caches */ /* are empty. for output, we can just hammer the enable bit off */ /* as soon as we get into the interrupt routine. */ /* */ /* a possible complication occurs if a stray attention pulse */ /* arrives just as we are starting an block. it will set */ /* the attf flag - which will cause an interrupt when enabled. */ /* if the external device requests input transfers, we could still */ /* end up screaming fifo data into memory (very unlikely, since a */ /* attention pulse would not normally be followed with dma data!) */ /* or - more likely - if we are in output mode, the dvma chip will */ /* fill the empty output fifo at full speed, and if we try to read */ /* or write the dr11 regs in the int routine, we will get into */ /* trouble. */ /* */ /* during normal input operation, any external interrupts are held */ /* off until all data has been emptied from the fifo - which */ /* avoids the problem. in normal output it is legit to just shut */ /* off dvma when the int is received. in input mode - we will */ /* check for cache draining before turning off dvma. */ /************************************************************************/ /* get dma controller status and see if out unit is interrupting IF THE CV_WAIT_SIG() CALL IN STRATEGY OR IOCTL WAS AWAKENED BY A SIGNAL OR TIMEOUT JUST AS THE DONE OR READY (OR WHATEVER) INTERRUPT ARRIVED, IT IS POSSIBLE THAT THIS CODE WAS ENTERED, AND BLOCKED ON THE MUTEX WHILE STRATEGY OR IOCTL TURNED OFF INTERRUPTS AND FLUSHED OUT THE CSR. WHEN THE INTERRUPT MUTEX UNBLOCKED, THE DMAIO_INT_PENDING BIT WOULD NO LONGER BE SET. THIS IS ACTUALLY WHAT WE WANT, SINCE WE HAVE FLAGGED A TIMEOUT, OR SIGNAL, AND DO NOT WANT TO PROCESS AN INTERRUPT. IT ALSO HAS THE EFFECT OF CAUSING THE FOLLOWING CODE TO BE SKIPPED (ALSO DESIRABLE) BUT that CAUSES US TO RETURN DDI_INTR_UNCLAIMED WHICH MAY CAUSE THE KERNEL TO DISPLAY AN "sbus interrupt not serviced" WARNING. AS LONG AS THE KERNEL JUST DISPLAYS A WARNING, AND DOES NOT PANIC - THIS IS A HARMLESS EVENT. IT IS VERY DIFFICULT - PERHAPS IMPOSSIBLE - TO COMPLETELY AVOID THIS RACE CONDITION. IT IS ALSO EXTREMELY UNLIKELY THAT IT WILL OCCUR IN THE FIRST PLACE, SINCE A PROPERLY CHOSEN TIMEOUT SHOULD NOT TERMINATE DMA THAT IS SUCCESFULLY PROCEEDING, AND SIGNALS WILL MOST LIKELY ONLY BE SENT TO HUNG PROCESSES. */ if(dregs_p->Dmacsr & (DMAIO_INT_PENDING | DMAIO_ERR_PENDING)) { int_serviced = DDI_INTR_CLAIMED ; /* check for multicycle error - don't set B_ERROR since bp might not point to a buffer read/write code will check for MCYL_ERR and set EIO at higher level we will check here since multicycle errors occur on the dr11 side, and may not be associated with specific DVMA activity - so DVMA, EOR, and RDY wait should be tested. Multicycle err during attnwait is probably meaningless- but setting the flag shouldn't cause a problem */ if(iregs_p->Idr_p_stat & IDR_MCER) unit_p->unit_flags |= IDR_MCYL_ERR ; /* ATTENTION OR EOR INTERRUPT MAY SLIVER OFF WHEN DMON IS TURNED OFF SINCE A FIFO RESET MAY CAUSE A SPIKE ON FIFO NOT EMPTY - WHICH HOLDS OFF INTERRUPTS. MAKE SURE INTERRUPTS ARE OFF BEFORE DMON TURNED OFF!!! also, since the Sbus pull-up resistors are so light, it takes time for the int lines to rise to the off state, and we don't want the kernel to re-enable while a line is still on (or seeming to be on). */ /* turn off ints as soon as possible to give light pull-ups on Sbus int lines time to pull int high -- in addition to this, in all cases there will be a dummy register read followed by a delay - so there should be plenty of time for the int line to go high */ dregs_p->Dmacsr &= ~DMAIO_INT_ENABLE ; flags = unit_p->unit_flags & (IDR_DVMA_WAIT | IDR_EOR_WAIT | IDR_ATTN_WAIT | IDR_RDY_WAIT) ; switch(flags) { case IDR_DVMA_WAIT: /* we are waiting for dma t/c interrupt */ /* attn interrupts will also be enabled */ /* so we will have to check for who */ /* caused the int - and whether the xfer*/ /* is complete */ case IDR_EOR_WAIT: /* or maybe we are waiting for an eor */ /* in auto output mode, eor is the */ /* desired interrupt. in input mode, or*/ /* manual mode, tc is the correct one */ if(!(bp->b_flags & B_READ)) { /* if we are waiting for an output int */ /* force off the DVMA controller. if */ /* this is an attention terminated out */ /* we could still be screaming data into*/ /* the output data fifo - and we might */ /* get a slave access timeout when we */ /* attempt to access the DR11 registers */ /* if this is an input, eor and attn */ /* inputs will be held off until all */ /* data is transferred into fifo anyway */ /* so we don't(and shouldn't) turn off */ /* the DVMA controller may be cache data*/ /* still to be transferred to memory */ dregs_p->Dmacsr &= ~DMAIO_DMA_ENABLE ; } /*--- check for error pending ---*/ if(dregs_p->Dmacsr & DMAIO_ERR_PENDING) { cmn_err(CE_CONT,"idr_intr: instance %d: error pending - flush_bfr issued! \n", instance) ; bp->b_error |= EIO ; bp->b_flags |= B_ERROR ; /* what else to do ??? */ } /* attention ints held off until fifo empty in input mode - so even if */ /* we don't get to t/c, all data will be in cache by the time we get */ /* the interrupt. also - the interrupt will force auto-drain of cache */ /* wait until cache drained - 100us max */ i = 0; while((dregs_p->Dmacsr & DMAIO_DRAINING)&&(i<100)) { drv_usecwait(1) ; i++ ; } if(dregs_p->Dmacsr & DMAIO_DRAINING) { /* hasn't drained yet - flag error */ cmn_err(CE_CONT,"indr_intr: instance %d: DVMA cache failed to drain!\n", instance) ; bp->b_error |= EIO ; bp->b_flags |= B_ERROR ; } /* we will force dvma enable bit off here - so it will be safe to touch */ /* the dr11 registers. it is already off if output, and if input, */ /* the cache had better be drained by now!!!! */ dregs_p->Dmacsr &= ~DMAIO_DMA_ENABLE ; if(!(dregs_p->Dmacsr & DMAIO_TC | (iregs_p->Idr_p_stat & IDR_EORF))) { /* we must have */ /* an attn that */ /* stopped xfers*/ iregs_p->Idr_p_stat = IDR_RATN ; /* if attn terminated - do */ /* NOT save attn flag */ idr_delay(iregs_p) ; } if(unit_p->unit_flags & IDR_EOR_WAIT) { iregs_p->Idr_p_stat = IDR_REOR ; /* if waiting for eor */ /* turn it off. if not, */ /* it may be useful */ /* later so leave alone */ idr_delay(iregs_p) ; } /*--- turn off "waiting-for-dma & eor" flags bit ---*/ unit_p->unit_flags &= ~(IDR_DVMA_WAIT | IDR_EOR_WAIT) ; /* turn off DR11 interrupt masks */ iregs_p->Idr_latch &= ~(IDR_EORM | IDR_ATTM) ; idr_delay(iregs_p) ; /* pulse TERM & turn off DMON if auto mode */ if(!(unit_p->unit_flags & IDR_MANUAL)) { iregs_p->Idr_p_stat = IDR_TERM ; idr_delay(iregs_p) ; iregs_p->Idr_latch &= ~IDR_DMON ; idr_delay(iregs_p) ; } /* do a flush-bfr to turn off tc, err pending, and int pending followed by a dummy read to flush write buffer, and a delay to let int line float to false */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; delay_dump_0 = dregs_p->Dmaar; drv_usecwait(2); cv_signal(&unit_p->cv) ; break ; case IDR_ATTN_WAIT: /* waiting for attention interrupt */ /* make sure that the ATTF int flag is set - GROSS ERROR if not */ if(!(iregs_p->Idr_p_stat & IDR_ATTF)) { cmn_err(CE_CONT,"idr_intr: instance %d: ATTN_WAIT interrupt w/ \ ATTF not set \n", instance) ; /* no way to pass this up */ /* to higher level !!! */ } /*--- clear the "waiting-for" flag ---*/ unit_p->unit_flags &= ~IDR_ATTN_WAIT ; /*--- turn off dr11 int enable bit ---*/ iregs_p->Idr_latch &= ~IDR_ATTM ; idr_delay(iregs_p) ; /* do a flush-bfr to turn off tc, err pending, and int pending followed by a dummy read to flush write buffer, and a delay to let int line float to false */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; delay_dump_0 = dregs_p->Dmaar; drv_usecwait(2); cv_signal(&unit_p->cv) ; break ; case IDR_RDY_WAIT: /* waiting for READY - either EOR or ATTN int */ /* make sure that the ATTF or EOR flag is set - GROSS ERROR if not */ if(!(iregs_p->Idr_p_stat & (IDR_ATTF | IDR_EORF))) { cmn_err(CE_CONT,"idr_intr: instance %d: RDY_WAIT interrupt w/ no flag set!\n", instance) ; /* no way to pass this up */ /* to higher level !!! */ } /*--- clear the "waiting-for" flag ---*/ unit_p->unit_flags &= ~IDR_RDY_WAIT ; /*--- turn off dr11 int enable bits ---*/ iregs_p->Idr_latch &= ~(IDR_ATTM | IDR_EORM) ; idr_delay(iregs_p) ; /* do a flush-bfr to turn off tc, err pending, and int pending followed by a dummy read to flush write buffer, and a delay to let int line float to false */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; delay_dump_0 = dregs_p->Dmaar; drv_usecwait(2); cv_signal(&unit_p->cv) ; break ; 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. ****/ cmn_err(CE_CONT,"idr_intr: instance %d: at case default - interrupt when not waiting! \n", instance); /*--- turn off idr int enable bits ---*/ iregs_p->Idr_latch &= ~(IDR_EORM | IDR_ATTM) ; idr_delay(iregs_p) ; /* do a flush-bfr to turn off tc, err pending, and int pending followed by a dummy read to flush write buffer, and a delay to let int line float to false */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; delay_dump_0 = dregs_p->Dmaar; drv_usecwait(2); cv_signal(&unit_p->cv) ; break ; } } /* end of if interrupting */ mutex_exit(&unit_p->mutex) ; return(int_serviced) ; } /* end of idr_intr */ static int idr_strategy(struct buf *bp) { register struct idr_unit *unit_p ; volatile register struct dmaio_regs *dregs_p ; volatile register struct idr_regs *iregs_p ; int instance ; int timeout_id ; int i ; u_char ct ; int dma_flags ; ddi_dma_handle_t dma_handle ; ddi_dma_cookie_t dma_cookie ; u_long range ; /* dr11 range counter */ u_long csr ; /* storage for outgoing csr */ int sleepval ; /* value returned from sleep */ static ddi_dma_lim_t dma_lim = { DMA_LIM_LOWER_ADD, DMA_LIM_UPPER_ADD, DMA_LIM_COUNTER, DMA_LIM_BURST_SIZE, DMA_LIM_MIN_XFER, DMA_LIM_SPEED, } ; /* get instance number from buf - and various useful pointers */ instance = getminor(bp->b_edev) ; unit_p = (struct idr_unit *)ddi_get_soft_state(state_head,instance) ; dregs_p = unit_p->dma_regs_p ; iregs_p = unit_p->idr_regs_p ; /* lock our unit structure and hardware pointers */ mutex_enter(&unit_p->mutex) ; /* save buffer pointer for interrupt routine */ unit_p->buf_p = bp ; /*---- clear the dr11 interrupt flags!! ----*/ iregs_p->Idr_latch &= ~(IDR_ATTM | IDR_EORM) ; idr_delay(iregs_p) ; /*---- pulse flags off if auto mode - leave for other levels if not----*/ if(!(unit_p->unit_flags & IDR_MANUAL)) { iregs_p->Idr_p_stat = IDR_RATN | IDR_REOR ; idr_delay(iregs_p) ; } /*---- force off int and dma enable & flush to clear t/c ----*/ dregs_p->Dmacsr &= ~(DMAIO_INT_ENABLE | DMAIO_DMA_ENABLE) ; dregs_p->Dmacsr |= DMAIO_FLUSH_BFR ; /* check DVMA byte count size */ if(bp->b_bcount > IDR_DMA_MAXBLOCK) { cmn_err(CE_CONT,"idr_strategy: instance %d: DVMA byte count too large!\n",instance) ; bp->b_error |= EINVAL ; bp->b_flags |= B_ERROR ; goto EXITSTRATEGY ; } /* set up dma - using our own dma limits structure */ dma_flags = DDI_DMA_WRITE ; if(bp->b_flags & B_READ) dma_flags = DDI_DMA_READ ; if(ddi_dma_buf_setup((dev_info_t *)unit_p->dip, bp, dma_flags, NULL, 0, &dma_lim, (ddi_dma_handle_t *)&dma_handle) != DDI_DMA_MAPPED) { bp->b_error |= EIO ; bp->b_flags |= B_ERROR ; cmn_err(CE_CONT,"idr_strategy: instance %d: ddi_dma_buf_setup failure!\n",instance) ; goto EXITSTRATEGY ; } /* get dma cookie for dma hardware */ if(ddi_dma_htoc(dma_handle, (off_t)0,(ddi_dma_cookie_t *)&dma_cookie) != DDI_SUCCESS) { bp->b_error |= EIO ; bp->b_flags |= B_ERROR ; ddi_dma_free(dma_handle) ; cmn_err(CE_CONT,"idr_strategy: instance %d: ddi_dma_htoc failure!\n",instance) ; goto EXITSTRATEGY ; } /* make sure that the mapped buffer doesn`t cross a 16Mbyte boundary. only the lower 24 bits of our add register can increment (thanks sun & lsi). ddi_dma_buf_setup is supposed to look at our dma_lim structure, and avoid such mappings, but the book says that it is NOT GUARANTEED!!! we will check for this and post an error if it happens - we don't have any graceful way at this point of breaking up the buffer and doing multiple dma blocks */ if(((dma_cookie.dmac_address & DMA_LIM_COUNTER) + bp->b_bcount) > (DMA_LIM_COUNTER + 1)) { bp->b_error |= EIO ; bp->b_flags |= B_ERROR ; ddi_dma_free(dma_handle) ; cmn_err(CE_CONT,"idr_strategy: instance %d: dma mapping crosses 16Mbyte boundary!\n",instance) ; goto EXITSTRATEGY ; } /* Write the DVMA start address and byte count to the hardware. */ dregs_p->Dmaar = dma_cookie.dmac_address ; dregs_p->Dmabcr = bp->b_bcount ; /* if automatic, set dr11 range counter, but check for max word count */ if(!(unit_p->unit_flags & IDR_MANUAL)) { /* automatic */ range = (bp->b_bcount >> 1) - 1 ; /* make word count -1 */ if(range > IDR_DR11_MAXBLOCK) { cmn_err(CE_CONT,"idr_strategy: instance %d DR11 range count too large!\n", instance) ; bp->b_error |= EINVAL ; bp->b_flags |= B_ERROR ; goto EXITSTRATEGY ; } iregs_p->Idr_rhi = (u_char)(range >> 16) ; /* set up range */ idr_delay(iregs_p) ; iregs_p->Idr_rmid = (u_char)(range >> 8) ; idr_delay(iregs_p) ; iregs_p->Idr_rlow = (u_char)range ; idr_delay(iregs_p) ; iregs_p->Idr_latch &= (u_char)~(IDR_DMIN | IDR_DMON) ; /* dmin & dmon off */ idr_delay(iregs_p) ; if(bp->b_flags & B_READ) { /* set read function bits and force DMIN to input */ ct = iregs_p->Idr_latch & ~IDR_FMASK ; iregs_p->Idr_latch = (u_char)(IDR_DMIN | unit_p->read_fcn | ct) ; idr_delay(iregs_p) ; unit_p->unit_flags |= IDR_DVMA_WAIT ; /* set csr value to be issued later */ csr = DMAIO_FASTER | DMAIO_COUNT_ENABLE | DMAIO_DMA_ENABLE | DMAIO_DMA_MEM_WRITE | DMAIO_INT_ENABLE ; /* set attm but not eorm for read */ iregs_p->Idr_latch |= IDR_ATTM ; idr_delay(iregs_p) ; } /* end of if read */ else { /* set write fcn bits - leave dmin off */ /* set write function bits and leave DMIN to off */ ct = iregs_p->Idr_latch & ~IDR_FMASK ; iregs_p->Idr_latch = (u_char)(unit_p->write_fcn | ct) ; idr_delay(iregs_p) ; /* if output and automatic, wait for dr11 eor */ unit_p->unit_flags |= IDR_EOR_WAIT ; /* set csr value, to be issued later */ csr = DMAIO_TCI_DIS | DMAIO_FASTER | DMAIO_COUNT_ENABLE | DMAIO_DMA_ENABLE | DMAIO_INT_ENABLE ; /* disable tci - eor will interrupt */ /* set attm and eorm for write */ iregs_p->Idr_latch |= (IDR_ATTM | IDR_EORM) ; } /* end of else */ iregs_p->Idr_latch |= IDR_DMON ; /* enable dr11 block */ idr_delay(iregs_p) ; } /* end of if automatic (!manual) */ else { /* else --- manual */ unit_p->unit_flags |= IDR_DVMA_WAIT ; /* dvma wait for r&w */ if(bp->b_flags & B_READ) { /* set csr for read */ csr = DMAIO_FASTER | DMAIO_COUNT_ENABLE | DMAIO_DMA_ENABLE | DMAIO_INT_ENABLE | DMAIO_DMA_MEM_WRITE ; } else { /* set csr for write */ csr = DMAIO_FASTER | DMAIO_COUNT_ENABLE | DMAIO_DMA_ENABLE | DMAIO_INT_ENABLE ; } /* end of if write */ iregs_p->Idr_latch |= IDR_ATTM ; /* attention always enabled */ idr_delay(iregs_p) ; } /* end of else (if manual) */ /*--- 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. ---*/ /* it is assumed that go will be issued here if auto mode. go */ /* is required to enable the DR11 logic. in manual mode go will*/ /* be handled by issuing pulses directly. we will force go on */ /* here - FIRST MAKE SURE THAT ATTENTION IS NOT ON - attention */ /* disables dma. attention may be a software generated pulse */ /* from the other end - and may be multiple microseconds long. */ /* wait for a reasonable amount of time - THIS MAY NEED TO BE */ /* MODIFIED FOR A PARTICULAR APPLICATION - or the calling */ /* program could wait for attention to go away before calling */ /* read or write. attention here in manual mode could be legit! */ /************************ this is a little tricky ***************/ /* */ /* we will issue the DR11 pulses before starting DVMA if we are */ /* doing output, and after starting DVMA if input. */ /* */ /* the reasoning is as follows: if we do the go before DVMA is */ /* started for input, a very fast device, especially one that */ /* just stuffs data at us without the ability to wait for a */ /* handshake between words may fill up the input fifo before we */ /* get the dvma logic fired up - particularly if this code gets */ /* interrupted or otherwise delayed. */ /* */ /* HOWEVER - if we start dvma first for output, it will start */ /* screaming data at the fifo at three sbus clocks per byte, and*/ /* the CPU will not be able to access the D channel to do the */ /* slave write to the DR11 logic to issue the go pulse!!!! */ /* the result is a data access timeout, and a very unsightly */ /* kernel PANIC. */ /****************************************************************/ if(!(unit_p->unit_flags & IDR_MANUAL)) { /* test here for attention - for both input and output modes */ i = 0; while((iregs_p->Idr_p_stat & IDR_ATTN)&&(i<100)) { drv_usecwait(1) ; i++ ; } if(iregs_p->Idr_p_stat & IDR_ATTN) { cmn_err(CE_CONT,"idr_strategy: instance %d attempt to issue GO while ATTENTION true!", instance) ; /* DMON may be on at this point - shut down everything!! */ iregs_p->Idr_latch &= ~(IDR_DMON | IDR_ATTM | IDR_EORM) ; idr_delay(iregs_p) ; iregs_p->Idr_p_stat = IDR_TERM ; idr_delay(iregs_p) ; ddi_dma_free(dma_handle) ; bp->b_error |= EIO ; bp->b_flags |= B_ERROR ; goto EXITSTRATEGY ; } if(!(bp->b_flags & B_READ)) { /* auto write-issue pulses B4 DVMA on */ iregs_p->Idr_p_stat =(u_char)(unit_p->write_pulse | IDR_GO) ; idr_delay(iregs_p) ; } } /* fire up the dvma logic and start timer */ timeout_id = timeout(idr_timeout,(void *)unit_p,unit_p->dma_time) ; dregs_p->Dmacsr = csr ; /* if automatic and read mode - issue go AFTER starting DVMA!!!! do not do a delay call after GO, since data could be screaming and we might get a bus retry timeout! */ if(!(unit_p->unit_flags & IDR_MANUAL)){ if(bp->b_flags & B_READ) { iregs_p->Idr_p_stat =(u_char)(unit_p->read_pulse | IDR_GO) ; } } /* sleep until done, timeout, or signal */ sleepval = cv_wait_sig(&unit_p->cv, &unit_p->mutex) ; dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; /* disable DVMA chip ints and DVMA enb and flush buffer */ /* basically a no-op unless we got here by signal */ /* we will also do a dummy read to force the write buffer to deliver the DMAIO_FLUSH_BFR command, and then delay to allow the interrupt line to float false -- it is unlikely, but an interrupt could occur after a timeout, or after a return from cv_wait_sig via a signal */ delay_dump_0 = dregs_p->Dmaar; drv_usecwait(2); /****** we must also do flush in int code and timeout code. if int pending bit is left on and another card interrupts on the same level, and we are polled first, there is a chance of an endless loop! ******/ iregs_p->Idr_latch &= ~(IDR_EORM | IDR_ATTM) ; /* and turn off internal dr11 int mask bits */ untimeout(timeout_id) ; /* timer off */ if(sleepval == 0) { /* if awakened by signal */ unit_p->unit_flags |= IDR_SIG_RECEIVED ; /* alert the troops */ dregs_p->Dmacsr = DMAIO_DEV_RESET ; /* hammer the DVMA chip off - just flush might do it */ dregs_p->Dmacsr = 0 ; dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; iregs_p->Idr_latch &= ~(IDR_DMON | IDR_ATTM | IDR_EORM) ; idr_delay(iregs_p) ; iregs_p->Idr_p_stat = IDR_TERM ; idr_delay(iregs_p) ; bp->b_error |= EINTR ; bp->b_flags |= B_ERROR ; } ddi_dma_free(dma_handle) ; /* release DVMA space */ EXITSTRATEGY: /* report # of bytes transferred - it will be garbage if we got here via an error - but the retval of read or write will be replaced w/ -1 anyway in the case of an error - so it shouldn't matter */ bp->b_resid = dregs_p->Dmabcr ; biodone(bp) ; /* tell physio we are done */ mutex_exit(&unit_p->mutex) ; return(0) ; } /* end of stratety */ /* initialize the LSI dma chip and flush the idr fifo - also clears the latched function register */ static void idr_dma_init(struct idr_unit *unit_p) { /*-- set and clear the dma chip's reset bit. if left set the system will panic when a dma xfer is attempted --*/ unit_p->dma_regs_p->Dmacsr = DMAIO_DEV_RESET ; unit_p->dma_regs_p->Dmacsr = 0 ; /*-- pulse the Dmaio flush bit - reset doesn't clear the t/c flag ---*/ unit_p->dma_regs_p->Dmacsr = DMAIO_FLUSH_BFR ; /*-- master clear the DR11 logic 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. ---*/ unit_p->idr_regs_p->Idr_p_stat = IDR_MCLR ; idr_delay(unit_p->idr_regs_p) ; } /* end of idr_dma_init */ /* function idr_timeout() called by the real time clock interrupt handler if timeout is reached. will issue iodone or wakeup, depending on what we were waiting for. also sets timeout flag in unit_array and sets B_ERROR in buf if we are waiting for dma to complete. */ static void idr_timeout(struct idr_unit *unit_p) { struct dmaio_regs *dregs_p ; struct idr_regs *iregs_p ; struct buf *bp ; u_int flags ; mutex_enter(&unit_p->mutex) ; /* Set pointer to DVMA & idr registers. Set pointer to buf structure. */ dregs_p = unit_p->dma_regs_p ; iregs_p = unit_p->idr_regs_p ; bp = unit_p->buf_p ; /*--- turn off interrupts so we don't get rudely interrupted!! ---*/ /*--- and dvma so data being screamed to and from fifo won't ---*/ /*--- cause a slave access timeout!!! ---*/ /*--- because we are completely paranoid, we will follow the ---*/ /*--- int off with a dummy read and a delay to make sure that ---*/ /*--- the int line has plenty of time to float to false ---*/ dregs_p->Dmacsr &= ~(DMAIO_INT_ENABLE | DMAIO_DMA_ENABLE) ; delay_dump_0 = dregs_p->Dmaar; drv_usecwait(2); /* check for multicycle error - don't set B_ERROR since bp might not point to a buffer read/write code will check for MCYL_ERR and set EIO at higher level we will check here since multicycle errors occur on the dr11 side, and may not be associated with specific DVMA activity - so DVMA, EOR, and RDY wait should be tested. Multicycle err during attnwait is probably meaningless- but setting the flag shouldn't cause a problem */ if(iregs_p->Idr_p_stat & IDR_MCER) unit_p->unit_flags |= IDR_MCYL_ERR ; flags = unit_p->unit_flags & (IDR_DVMA_WAIT | IDR_EOR_WAIT | IDR_ATTN_WAIT | IDR_RDY_WAIT) ; switch(flags) { case IDR_DVMA_WAIT: /* we are waiting for dma t/c interrupt */ case IDR_EOR_WAIT: /* or waiting for EOR */ if(unit_p->unit_flags & IDR_DVMA_WAIT) { unit_p->unit_flags |= IDR_DVMA_TIMEOUT ; } else { unit_p->unit_flags |= IDR_EOR_TIMEOUT ; } /*--- turn off "waiting-for-dma & eor" flags bit ---*/ unit_p->unit_flags &= ~(IDR_DVMA_WAIT | IDR_EOR_WAIT) ; /*--- turn off dma enb and int enable bits ---*/ dregs_p->Dmacsr &= ~(DMAIO_DMA_ENABLE | DMAIO_INT_ENABLE) ; /*--- do a flush-bfr to turn off tc, err pending, and int pending */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; /* turn off DR11 interrupt masks & shut off DMON */ iregs_p->Idr_latch &= ~(IDR_EORM | IDR_ATTM | IDR_DMON ) ; idr_delay(iregs_p) ; /* issue term and pulse off any flags */ iregs_p->Idr_p_stat = IDR_TERM | IDR_RATN | IDR_REOR ; idr_delay(iregs_p) ; bp->b_error |= EIO ; bp->b_flags |= B_ERROR ; /* tell physio we didn't make it */ cv_signal(&unit_p->cv) ; /* wake up strategy */ break ; case IDR_RDY_WAIT: /* waiting for READY - eor or attn int */ /*--- clear appropriate "waiting-for" flag ---*/ unit_p->unit_flags &= ~IDR_RDY_WAIT ; /*--- set appropriate "waiting for" timeout ---*/ unit_p->unit_flags |= IDR_RDY_TIMEOUT ; /*--- turn off idr int enable bits ---*/ iregs_p->Idr_latch &= ~(IDR_EORM | IDR_ATTM); /*--- do a flush-bfr to turn off tc, err pending, and int pending */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; /*--- wake up the code that is waiting for READY ---*/ /*--- this may or may not be an error!! ---*/ cv_signal(&unit_p->cv) ; break ; case IDR_ATTN_WAIT: /* waiting for ATTENTION */ /*--- clear appropriate "waiting-for" flag ---*/ unit_p->unit_flags &= ~IDR_ATTN_WAIT ; /*--- set appropriate "waiting for" timeout ---*/ unit_p->unit_flags |= IDR_ATTN_TIMEOUT ; /*--- turn off idr int enable bits ---*/ iregs_p->Idr_latch &= ~(IDR_EORM | IDR_ATTM); idr_delay(iregs_p); /*--- do a flush-bfr to turn off tc, err pending, and int pending */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; /*--- wake up the code that is waiting for ATTENTION ---*/ /*--- this may or may not be an error!! ---*/ cv_signal(&unit_p->cv) ; break ; default: /* if we get here things are really cooked !!! */ cmn_err(CE_CONT,"idr_timeout: instance %d: at case default: timeout when not waiting on anything!!!!\n",getminor(bp->b_edev)) ; /*--- do a flush-bfr to turn off tc, err pending, and int pending */ dregs_p->Dmacsr = DMAIO_FLUSH_BFR ; break ; } mutex_exit(&unit_p->mutex) ; } /* end of timeout */ /* internal delay routine does a pair of reads to "ikon" registers to flush write buffers to bus, and allow time for writes to finish internal to "ikon" logic */ static void idr_delay(volatile register struct idr_regs *iregs_p) { delay_dump_0 = iregs_p->Idr_rlow; delay_dump_1 = iregs_p->Idr_rhi; }