/* IKON Corporation 2617 Western Ave. Seattle, WA 98121 (206) 728-6465 code module for Solaris 2.x driver for PCI hardcopy boards 1 August, 1996 initial version for SPARC 30 Sept, 1996 changed inclue for pci.h from "pci.h" to removed static from _init _info and _fini 3 Oct, 1996 changed the way we use dummy variables when doing a read to force the write cache to flush - now using static volatile global variables to try to keep the optimizer from deleting the reads 10 Oct, 1996 fixed bad bug in ihcp_high_intr() that used the following: temp = IHCP_GETL(PLX_INT_CSTAT) to test for interrupts - unfortunately it should have been ..(IPLX...) so we read 0xFFFFFFFF from outer space, and claimed interrupts that weren't ours .. it is amazing that it worked at all .. also - we weren't turning off the soft_intr_req flag in our soft state from within our soft_intr routine - so it was possible that we would accept a soft intr that wasn't ours 17 June, 1997 added suspend and resume to detach and attach for system power management modified attach to get driver and device defaults from ihcp.conf - so changing defaults doesn't require re-compiling the driver added support for versatec burst mode - only available on some new plotters - requires pld218B or later on 115/117 27 August, 1997 moved dma_handle to soft state moved ddi_dma_alloc_handle to attach & fixed cast of dip in call added remove_minor_node to detach code fixed error in dma_attr that had no effect on sparc, but crashed x86 kernel 15 September, 1997 Added dma chaining to support scatter/gather dma on x86 moved dma_attr struct to soft state & added dma_attr for iobps both are now per-instance (not shared) since they are filled in dynamically at attach time Added code to save and restore the interrupt line reg in the configuration register set - during a soft reset of the plx chip. Plx clears this reg during a soft reset and it was causing interrupts to fail after a rem_drv add_drv sequence, or after auto load of a driver during boot. 7 June, 2000 convert all IPLX access to PLX accesses - doing accesses via the ihcp space may have been allowing pre-fetch from plx registers disable address space 0 pre-fetch make int/cstat read in high intr routine volatile protect high-level int code w/soft mutex - THIS FIXED THE TIMEOUT PROBLEM, BUT IT DOESN'T FOLLOW THE SUN EXAMPLE start DMA in two steps to avoid PLX bug pause DMA after cv_wait_sig in stragety - looping a while if not done 16 June, 2000 chucked the whole high-level interrupt thing in favor of a single mutex. this to try to solve weird problems on some machines that caused wakeups to be missed, or not issued at all ONE REASON WE HAD A HIGH-LEVEL HANDLER WAS THAT THE HIGH-LEVEL TEST IN 2.6 ONLY TOLD US IF WE WERE HIGHER THAN THE SCHEDULER NOT IF WE WERE AT THE SAME LEVEL - AND BEING AT THE SAME LEVEL CAUSED A RECURSIVE MUTEX PANIC - HOPEFULLY, THIS IS FIXED BY NOW */ /************************************************************************ * * * 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 PCI 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. * ************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ihcp_io.h" #include "ihcp_reg.h" #include "ihcp_var.h" /* opaque handle - top of soft state structure list */ static void *state_head; /* declare dummy variables used to the left of = when we read a board register to force the write cache to flush a register write to the bus */ static volatile u_long read_dump_0; static volatile u_long read_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 ihcp_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result); static int ihcp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int ihcp_open(dev_t *dev, int openflags, int otyp, cred_t *credp); static int ihcp_close(dev_t dev, int openflags, int otyp, cred_t *credp); static int ihcp_write(dev_t dev, struct uio *uiop, cred_t *credp); static int ihcp_ioctl(dev_t dev, int cmd, int arg, int flag, cred_t *credp, int *rvalp); static u_int ihcp_intr(caddr_t arg); static int ihcp_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 ihcp_strategy(struct buf *bp) ; /* declare device data access structure for plx and ihcp registers and config registers we will try to treat each register as 32 bits wide - so we don't want any byte swapping however, it seems that the sparc machines swizzle the bytes between host and pci if we say never swap (at least for 32 bit accesses) - so we will use the little endian flag if the hardware really does swizzle bytes between pci and host - including memory, this should help dma by getting the data bytes in the right order! */ static struct ddi_device_acc_attr slave_attr = { DDI_DEVICE_ATTR_V0, DDI_STRUCTURE_LE_ACC, DDI_STRICTORDER_ACC, } ; /* declare structures used for dynamic loding and unloading of the driver */ static struct cb_ops ihcp_cb_ops = { ihcp_open, ihcp_close, nulldev, /* not a block driver */ nodev, /* no print routine */ nodev, /* no dump routine */ nodev, /* no read routine - write-only device */ ihcp_write, ihcp_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 ihcp_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. we do not provide power management for this driver/device combination we do allow system-wide suspend/resume operations when our device is not open */ static struct dev_ops ihcp_ops = { DEVO_REV, /* DEVO_REV indicated by manual */ 0, /* device reference count */ ihcp_getinfo, nulldev, /* identify routine is obsolete */ nulldev, /* device probe for non-self-id */ ihcp_attach, ihcp_detach, /* REPLACE W/nodev IF DRIVER SHOULD STAY ATTACHED */ nodev, /* device reset routine */ &ihcp_cb_ops, (struct bus_ops *)0, /* bus operations */ nulldev, /* power operations */ }; extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &mod_driverops, MODINFOBANNER, &ihcp_ops, }; static struct modlinkage modlinkage = { MODREV_1, /* MODREV_1 indicated by manual */ (void *)&modldrv, NULL, /* termination of list of linkage structures */ }; /* register interrupts, map the working registers, and initialize things on a per-instance basis */ static int ihcp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { register struct ihcp_unit *unit_p; int instance; int result; char minor_node_name[16] ; u_long temp; size_t iopb_mem_length; /* actual size of iopb area allocated */ ddi_dma_cookie_t iopb_cookie; /* dma cookie for iopb area */ uint_t iopb_cookie_count; /* dma cookie count for iopb area */ /* save a little typing when getting properties from ihcp.conf */ #define getprop(devi, name, def) ddi_getprop(DDI_DEV_T_ANY, (devi),\ DDI_PROP_DONTPASS, (name), (def)) instance = ddi_get_instance(dip); /* test for expected commands other commands may not be errors, so only print message if debugging is on */ switch(cmd) { case DDI_RESUME: unit_p = (struct ihcp_unit *)ddi_get_soft_state(state_head, instance); /* we are being asked to restore register state due to power up following power management power down */ DPRINT((CE_CONT,"ihcp_attach: instance %d: command is DDI_RESUME\n", instance)); mutex_enter(&unit_p->mutex); DPRINT((CE_CONT,"ihcp_attach: instance %d: restoring state & clearing suspended flag\n",instance)); /* restore hardware state */ PLX_PUTL(PLX_INT_CSTAT, unit_p->saved_plx_int_cstat); PLX_PUTL(PLX_EEPROM_USER, unit_p->saved_plx_eeprom_user); IHCP_PUTL(IHCP_MODE, unit_p->saved_mode); IHCP_PUTL(IHCP_DEVICE_CONTROL, unit_p->saved_device_control); IHCP_PUTL(IHCP_INTERFACE_CONTROL, unit_p->saved_interface_control); IHCP_PUTL(IHCP_AUTO_LTR_LOW, unit_p->saved_auto_ltr_low); IHCP_PUTL(IHCP_AUTO_LTR_HIGH, unit_p->saved_auto_ltr_high); unit_p->unit_suspended = 0; /* wake up ihcp_open() which may be waiting on suspended flag */ cv_broadcast(&unit_p->power_cv); mutex_exit(&unit_p->mutex); return(DDI_SUCCESS); break; case DDI_ATTACH: DPRINT((CE_CONT,"ihcp_attach: instance %d: command is DDI_ATTACH\n", instance)); /* allocate soft state structure and add to list */ /* the ever increasing list of things to undo in the case of an error is pretty obnoxious it would save a lot of typing to set a flag when each task was complete, and only undo the things that have flags set in the event of an error, but that would probably require a bunch of goto statements, and that seems untidy also */ if (ddi_soft_state_zalloc(state_head, instance) != 0) { cmn_err(CE_CONT,"ihcp_attach: instance %d: ddi_soft_state_zalloc failure!\n",instance) ; return (DDI_FAILURE); } unit_p = (struct ihcp_unit *)ddi_get_soft_state(state_head, instance); ddi_set_driver_private(dip, (caddr_t)unit_p); unit_p->dip = dip; unit_p->saved_instance = instance ; /* this version of the driver uses a single mutex, and does not support high-level interrupts the previous version had problems on some S7 and S8 x86 machines, whith strange interactions between strategy, the high interrupt routine, and the soft interrupt routine we will use ddi_get_iblock_cookie rather than getting our cookie via ddi_add_intr to avoid a potential race that can occur if the interrupt routine gets registered and polled before our mutex is initialized */ /* test for high-level interrupt, and tilt if so */ if(ddi_intr_hilevel(dip, 0)) { ddi_soft_state_free(state_head, instance); cmn_err(CE_CONT, "idr_attach: instance %d: high-level interrupts not supported!\n", instance); return(DDI_FAILURE); } /* get iblock cookie for interrupt we use only one interrupt level */ if(ddi_get_iblock_cookie(dip, 0, &unit_p->iblock_cookie) != DDI_SUCCESS) { ddi_soft_state_free(state_head, instance); cmn_err(CE_CONT,"ihcp_attach: instance %d: ddi_get_iblock_cookie error!\n", instance); return(DDI_FAILURE); } /* initialize MUTEX */ mutex_init(&unit_p->mutex, "ihcp mutex", MUTEX_DRIVER, (void *)unit_p->iblock_cookie); /* register the interrupt */ if(ddi_add_intr(dip, 0, &unit_p->iblock_cookie, (ddi_idevice_cookie_t *) NULL, ihcp_intr, (caddr_t) unit_p) != DDI_SUCCESS) { cmn_err(CE_CONT,"ihcp_attach: instance %d: ddi_add_intr error!\n",instance) ; mutex_destroy(&unit_p->mutex); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* initialize conditional variable for normal driver operations */ cv_init(&unit_p->cv,"ihcp cv", CV_DRIVER, (void *)unit_p->iblock_cookie); /* initialize another cv for use with power management */ cv_init(&unit_p->power_cv,"ihcp power cv", CV_DRIVER, (void *)unit_p->iblock_cookie); /* get the number of device regsiters since plx may fiddle with this in later chip revs, we won't check the number, just save it for possible diagnostic purposes */ if(ddi_dev_nregs(dip, &result) != DDI_SUCCESS) { cmn_err(CE_CONT,"ihcp_attach: instance %d: ddi_dev_nregs error!\n", instance); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return(DDI_FAILURE); } DPRINT((CE_CONT,"ihcp_attach: instance %d: nregs = 0x%x\n", instance, result)); /* map in the plx and ihcp registers set length and offset to so entire reg ranges are mapped this is necessary since plx may change their register ranges */ if (ddi_regs_map_setup(dip, PLX_REG_RNUMBER, (caddr_t *)&unit_p->plx_base, 0, 0, &slave_attr, &unit_p->plx_acc_handle) != DDI_SUCCESS) { cmn_err(CE_CONT,"ihcp_attach: instance %d: ddi_regs_map_setup error!\n",instance) ; ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } if (ddi_regs_map_setup(dip, IHCP_REG_RNUMBER, (caddr_t *)&unit_p->ihcp_base, 0, 0, &slave_attr, &unit_p->ihcp_acc_handle) != DDI_SUCCESS) { cmn_err(CE_CONT,"ihcp_attach: instance %d: ddi_regs_map_setup error!\n",instance) ; ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* the x86 version of this driver operates the board in dma chaining mode this is necessary since the x86 architecture does not include an i/o mmu to translate a contiguous buffer virtual address into a scatter/gather access to physical memory pages the bus master logic on the board uses a chaining list to access scattered physical memory pages within the user data buffer the chaining list is stored in a chunk of contiguous kernel memory that is allocated and mapped in attach (to save the overhead in strategy) the size of the kernel memory required is the size of a single entry in the list - one iopb (i/o parameter block) * the length of the list the list length is set by the user (sys admin) in ihcp.conf, and must be non-zero, and not so large that it exceeds, or heavily impacts, available kerner memory each link entry corresponds to a max of 4k bytes of user buffer (in the x86 architecture) */ /* read size of scatter-gather list (number of 16-byte links in chain) from ihcp.conf zero is not permitted! */ unit_p->sg_list_length = getprop(dip,"sg_list_length", SG_LIST_LENGTH_DEF); DPRINT((CE_CONT,"ihcp_attach: instance %d: sg_list_length = 0x%x\n",instance,unit_p->sg_list_length)); if(unit_p->sg_list_length == 0) { cmn_err(CE_CONT,"ihcp_attach: instance %d: scatter/gather list length = 0!\n",instance) ; ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* fill in user buf and iopb dma_attr structures based on lint length requested and plx chip capabilities (one pair of attr structs per instance) dma chaining will be used to transfer the user buffer to the device the number of links in the chain is determined by the sg_list_length property the iopb memory will be a single contiguous block or kernel memory (for simplicity) */ unit_p->dma_attr.dma_attr_version = DMA_ATTR_V0; /* version number spec'd by sun */ unit_p->dma_attr.dma_attr_addr_lo = 0x0; /* lower address boundary */ unit_p->dma_attr.dma_attr_addr_hi = 0xFFFFFFFF; /* upper boundary - 32 bit max */ unit_p->dma_attr.dma_attr_count_max = 0x7FFFFF; /* max dma block size */ unit_p->dma_attr.dma_attr_align = 0x1; /* don't care about user buf alignment */ unit_p->dma_attr.dma_attr_burstsizes = 0x0; /* no control over this (sbus only???) */ unit_p->dma_attr.dma_attr_minxfer = 0x1; /* min xfer size is one byte */ unit_p->dma_attr.dma_attr_maxxfer = 0x7FFFFF * unit_p->sg_list_length; /* total dma max = max block * list length */ unit_p->dma_attr.dma_attr_seg = 0xFFFFFFFF; /* no segmentation restriction for pci board */ unit_p->dma_attr.dma_attr_sgllen = unit_p->sg_list_length; /* number of links in chain */ unit_p->dma_attr.dma_attr_granular = 0x1; /* ??? */ unit_p->dma_attr.dma_attr_flags = 0x0; /* sun says 0 for now */ unit_p->iopb_attr.dma_attr_version = DMA_ATTR_V0; /* version number spec'd by sun */ unit_p->iopb_attr.dma_attr_addr_lo = 0x0; /* lower address boundary */ unit_p->iopb_attr.dma_attr_addr_hi = 0xFFFFFFFF; /* upper boundary - 32 bit max */ unit_p->iopb_attr.dma_attr_count_max = 0x7FFFFF; /* max dma block size */ unit_p->iopb_attr.dma_attr_align = IOPB_SIZE; /* plx requires aligned links */ unit_p->iopb_attr.dma_attr_burstsizes = 0x0; /* no control over this (sbus only???) */ unit_p->iopb_attr.dma_attr_minxfer = IOPB_SIZE; /* min xfer size is one iopb */ unit_p->iopb_attr.dma_attr_maxxfer = 0x7FFFFF; /* single block for iopb access */ unit_p->iopb_attr.dma_attr_seg = 0xFFFFFFFF; /* no segmentation restriction for pci board */ unit_p->iopb_attr.dma_attr_sgllen = 1; /* must be single contiguous block */ unit_p->iopb_attr.dma_attr_granular = 0x1; /* ??? */ unit_p->iopb_attr.dma_attr_flags = 0x0; /* sun says 0 for now */ /* allocate dma handle for user data buffer */ if(ddi_dma_alloc_handle(dip, &unit_p->dma_attr, DDI_DMA_DONTWAIT, (caddr_t)0, &unit_p->dma_handle) != DDI_SUCCESS) { cmn_err(CE_CONT,"ihcp_attach: instance %d: (buf) ddi_dma_alloc_handle failure!\n",instance) ; ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* allocate dma handle for iopbs (io paramater blocks a.k.a. dma chain links) */ if(ddi_dma_alloc_handle(dip, &unit_p->iopb_attr, DDI_DMA_DONTWAIT, (caddr_t)0, &unit_p->iopb_handle) != DDI_SUCCESS) { cmn_err(CE_CONT,"ihcp_attach: instance %d: (iopb) ddi_dma_alloc_handle failure!\n",instance) ; ddi_dma_free_handle(&unit_p->dma_handle); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* allocate kernel memory space for the i/o control blocks it will be declared CONSISTENT to force a single contiguous block - to simplify the later contstruction of the chaining list */ if(ddi_dma_mem_alloc(unit_p->iopb_handle, (size_t)(unit_p->sg_list_length * IOPB_SIZE), &slave_attr, DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, (caddr_t)0, (caddr_t *)&unit_p->iopb_base, &iopb_mem_length, &unit_p->iopb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_CONT,"ihcp_attach: instance %d: ddi_dma_mem_alloc failure!\n",instance) ; ddi_dma_free_handle(&unit_p->iopb_handle); ddi_dma_free_handle(&unit_p->dma_handle); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* bind handle to allocated iopb memory strategy will actually construct the linked list AND DO A DMA SYNC FOR DEVICE */ if(ddi_dma_addr_bind_handle(unit_p->iopb_handle, (struct as *)NULL, (caddr_t)unit_p->iopb_base, iopb_mem_length, DDI_DMA_WRITE | DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, (caddr_t)0, &iopb_cookie, &iopb_cookie_count) != DDI_DMA_MAPPED) { cmn_err(CE_CONT,"ihcp_attach: instance %d: ddi_dma_addr_bind_handle failure!\n",instance) ; ddi_dma_mem_free(&unit_p->iopb_acc_handle); ddi_dma_free_handle(&unit_p->iopb_handle); ddi_dma_free_handle(&unit_p->dma_handle); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* since we require contiguous space for iopb memory, there must be exactly one cookie! */ if(iopb_cookie_count != 1) { cmn_err(CE_CONT,"ihcp_attach: instance %d: too many iopb cookies!\n",instance) ; ddi_dma_unbind_handle(unit_p->iopb_handle); ddi_dma_mem_free(&unit_p->iopb_acc_handle); ddi_dma_free_handle(&unit_p->iopb_handle); ddi_dma_free_handle(&unit_p->dma_handle); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* make absolutely certain that the iopb space is quad long word aligned! */ if((iopb_cookie.dmac_address & 0xF) != 0) { cmn_err(CE_CONT,"ihcp_attach: instance %d: iopb memory not quad long word aligned!\n",instance) ; ddi_dma_unbind_handle(unit_p->iopb_handle); ddi_dma_mem_free(&unit_p->iopb_acc_handle); ddi_dma_free_handle(&unit_p->iopb_handle); ddi_dma_free_handle(&unit_p->dma_handle); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* save physical address of iopb space for later use by strategy */ unit_p->iopb_phys_base_addr = iopb_cookie.dmac_address; DPRINT((CE_CONT, "ihcp_attach: instance %d: iopb memory space allocated and mapped - phys addr = 0x%x, iopb_base = 0x%x, size = 0x%x\n", instance, iopb_cookie.dmac_address, unit_p->iopb_base, iopb_cookie.dmac_size)); /* 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 but we don't seem to fit the other possibilities we will save the instance as our minor # for future availability to write and ioctl routines use string format to generate ihcpx node name */ (void) sprintf(minor_node_name,"ihcp%d",instance) ; if (ddi_create_minor_node(dip, minor_node_name, S_IFCHR, instance, DDI_PSEUDO, NULL) == DDI_FAILURE) { cmn_err(CE_CONT,"ihcp_attach: instance %d: ddi_create_minor_node error!\n",instance) ; ddi_dma_unbind_handle(unit_p->iopb_handle); ddi_dma_mem_free(&unit_p->iopb_acc_handle); ddi_dma_free_handle(&unit_p->iopb_handle); ddi_dma_free_handle(&unit_p->dma_handle); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* map config register set temporarily so we can get device id and revision level and assigned interrupt line use the offsets canned in pci.h from sun */ if(pci_config_setup(dip, &unit_p->config_acc_handle) != DDI_SUCCESS) { cmn_err(CE_CONT,"ihcp_attach: instance %d: pci_config_setup error!\n",instance) ; ddi_remove_minor_node(dip, NULL); ddi_dma_unbind_handle(unit_p->iopb_handle); ddi_dma_mem_free(&unit_p->iopb_acc_handle); ddi_dma_free_handle(&unit_p->iopb_handle); ddi_dma_free_handle(&unit_p->dma_handle); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } DPRINT((CE_CONT,"ihcp_attach: instance %d: ints registered, node created, regs mapped, plx_base = 0x%x, ihcp_base = 0x%x\n", instance, unit_p->plx_base, unit_p->ihcp_base)); /* get and save device and vendor ids */ unit_p->dev_and_vendor_id = pci_config_getl(unit_p->config_acc_handle, (u_long)PCI_CONF_VENID); unit_p->revision_id = REV_ID_MASK & pci_config_getl(unit_p->config_acc_handle, (u_long)PCI_CONF_REVID); DPRINT((CE_CONT,"ihcp_attach: instance %d: vendor & device id = 0x%08x, revision id = 0x%02x\n", instance, unit_p->dev_and_vendor_id, unit_p->revision_id)); unit_p->int_line = pci_config_getb(unit_p->config_acc_handle, (u_long)PCI_CONF_ILINE); DPRINT((CE_CONT,"ihcp_attach: instance %d: interrupt line = 0x%x\n", unit_p->int_line)); /* solaris may not enable the board to respond to non-config pci accesses, so or (probably don't need to or) the appropriate bits into the config command reg to enable slave io and mem access, and master access */ temp = pci_config_getl(unit_p->config_acc_handle, (u_long)PCI_CONF_COMM); pci_config_putl(unit_p->config_acc_handle, (u_long)PCI_CONF_COMM, (u_long)(temp | PCI_COMM_IO | PCI_COMM_MAE | PCI_COMM_ME)); /* we would like to do a full reset of the plx and ihcp logic here, but.... it isn't easy! soft reset clears the local config registers, which turns off add space 0 accesses so we will force an eeprom reload, and wait a while (0.1s) to avoid too many retry cycles while the eeprom is reloading - we will use delay, not drv_usec_wait, so we don't tie up the cpu just doing a delay in the code doesn't get it, since write caching may wipe out some or all of the delay!! do a read before the delay to force the buffer to flush the write that causes the eeprom reload an earlier rev cleared the int line byte in the configuration registers during soft reset, but that is not supposed to be true anymore IT SEEMS THAT IT IS STILL TRUE - SO WE WILL USE CONFIG REG ACCESS TO SAVE THE INT LINE BEFORE THE SOFT RESET, AND RESTORE IT AFTER we probably don't have to clear the config reload bit after writing it, but we will to be sure */ temp = pci_config_getb(unit_p->config_acc_handle, (u_long)PCI_CONF_ILINE); PLX_PUTL(PLX_EEPROM_USER,(PLX_GETL(PLX_EEPROM_USER) | PLX_SOFT_RESET)); PLX_PUTL(PLX_EEPROM_USER,(PLX_GETL(PLX_EEPROM_USER) & ~PLX_SOFT_RESET)); PLX_PUTL(PLX_EEPROM_USER,(PLX_GETL(PLX_EEPROM_USER) | CONFIGURATION_RELOAD)); read_dump_0 = PLX_GETL(PLX_INT_CSTAT); delay((long)drv_usectohz((clock_t)100000)); PLX_PUTL(PLX_EEPROM_USER,(PLX_GETL(PLX_EEPROM_USER) & ~CONFIGURATION_RELOAD)); pci_config_putb(unit_p->config_acc_handle, (u_long)PCI_CONF_ILINE, (u_short)temp); DPRINT((CE_CONT,"ihcp_attach: instance %d: soft reset and configuration releoad complete\n", instance)); /* soft reset leaves pci master int enable on, so turn it off for safety don't bother with oring bits - just hammer them all off! */ PLX_PUTL(PLX_INT_CSTAT, 0); pci_config_teardown(&unit_p->config_acc_handle); /* disable address space 0 pre-fetch */ PLX_PUTL(PLX_ADD_0_ROM_DESC, PLX_GETL(PLX_ADD_0_ROM_DESC) | ADD_0_PREFETCH_DISABLE); /* get the board strapping and save it zeros rest of flag register at same time */ unit_p->unit_flags = INTERFACE_STRAP_MASK & IHCP_GETL(IHCP_DEVICE_STATUS); DPRINT((CE_CONT,"ihcp_attach: instance %d: interface strapping = 0x%x\n", instance, IHCP_BOARD_MASK & unit_p->unit_flags)); /* set the data byte ordering default mode the native ordering for the board is low byte first if high byte first mode is selected, the single byte data out and command out bytes must be sent to the fifos in the HIGH byte of the longword! ordering is set here, and stored in the unit structure, since it will affect all later accesses to the 8 bit data and command registes we only take action if high_byte_first is selected, since the preceeding reset should have set us to low_byte_first mode setting high byte first mode requires turning on the byte_swizzle bit in the ihcp logic, and TURNING OFF the user output bit in the plx logic */ /* some driver defaults are read from ihcp.conf (speed and print/plot mode) others (byte ordering) are left in ihcp_io.h get default properties and save in soft state the third arg is the value used if the property is not found */ temp = getprop(dip,"vers_speed_def",VERS_SPEED_DEF); if((temp !=0) && (temp !=1) && (temp != 2) && (temp != 3)) { temp = VERS_SPEED_DEF; cmn_err(CE_WARN,"ihcp_attach: instance %d: vers_speed_def property out of range!\n",instance); } DPRINT((CE_CONT,"ihcp_attach: instance %d: vers_speed_def = %d\n",instance,temp)); switch(temp) { case 0: unit_p->vers_speed_def = SPEED_0; break; case 1: unit_p->vers_speed_def = SPEED_1; break; case 2: unit_p->vers_speed_def = SPEED_2; break; case 3: unit_p->vers_speed_def = SPEED_3; break; } temp = getprop(dip,"cent_speed_def",CENT_SPEED_DEF); if((temp !=0) && (temp !=1) && (temp != 2) && (temp != 3)) { temp = CENT_SPEED_DEF; cmn_err(CE_WARN,"ihcp_attach: instance %d: cent_speed_def property out of range!\n",instance); } DPRINT((CE_CONT,"ihcp_attach: instance %d: cent_speed_def = %d\n",instance,temp)); switch(temp) { case 0: unit_p->cent_speed_def = SPEED_0; break; case 1: unit_p->cent_speed_def = SPEED_1; break; case 2: unit_p->cent_speed_def = SPEED_2; break; case 3: unit_p->cent_speed_def = SPEED_3; break; } temp = getprop(dip,"mode_def",MODE_DEF); if((temp !=0) && (temp !=1)) { temp = MODE_DEF; cmn_err(CE_WARN,"ihcp_attach: instance %d: mode_def property out of range!\n",instance); } if(temp == 1) unit_p->mode_def = PLOT_MODE; else unit_p->mode_def = NORM_PRINT_MODE; DPRINT((CE_CONT,"ihcp_attach: instance %d: mode_def = %d\n",instance,temp)); temp = getprop(dip,"dma_time_def",DMA_TIME_DEF); unit_p->dma_time_def = temp; DPRINT((CE_CONT,"ihcp_attach: instance %d: dma_time_def = %d\n",instance,temp)); temp = getprop(dip,"rdy_time_def",RDY_TIME_DEF); unit_p->rdy_time_def = temp; DPRINT((CE_CONT,"ihcp_attach: instance %d: rdy_time_def = %d\n",instance,temp)); if(ORDER_DEF == HIGH_BYTE_FIRST) { DPRINT((CE_CONT,"ihcp_attach: instance %d: setting ordering mode to high byte first\n", instance)); IHCP_PUTL(IHCP_MODE, (IHCP_GETL(IHCP_MODE) | BYTE_SWIZZLE)); PLX_PUTL(PLX_EEPROM_USER, (PLX_GETL(PLX_EEPROM_USER) & ~USER_OUTPUT)); } unit_p->byte_order = ORDER_DEF; /* set the default handshake speeds for versatec and centronics boards (as determined by strapping), respectively, and the default print/plot mode for versatec boards the print/plot mode is also saved in the unit structure, since it may be referred to later - and isn't visible in the board's registers NOTE THAT THE SAME DEFAULTS ARE USED FOR ALL BOARDS OF THE SAME TYPE!!!!!! */ if((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { /* versatec - set speed and print/plot mode */ DPRINT((CE_CONT,"ihcp_attach: instance %d: setting default versatec speed to 0x%x\n", instance, unit_p->vers_speed_def)); DPRINT((CE_CONT,"ihcp_attach: instance %d: setting default versatec print/plot mode to 0x%x\n", instance, unit_p->mode_def)); IHCP_PUTL(IHCP_MODE,((IHCP_GETL(IHCP_MODE) & ~SPEED_MASK) | unit_p->vers_speed_def)); temp = SET_VERSATEC_MODE | unit_p->mode_def; if(unit_p->byte_order == HIGH_BYTE_FIRST) temp = temp << 24; IHCP_PUTL(IHCP_COMMAND_OUT, temp); unit_p->mode = unit_p->mode_def ; } else { /* centronics - set speed only */ DPRINT((CE_CONT,"ihcp_attach: instance %d: setting default centronics speed to 0x%x\n", instance, unit_p->cent_speed_def)); IHCP_PUTL(IHCP_MODE,((IHCP_GETL(IHCP_MODE) & ~SPEED_MASK) | unit_p->cent_speed_def)); } /* set saved data streaming mode - for old vme getregs call compatibility, since we can't read data streaming status from the logic on the other side of the fifo in the pci card */ unit_p->data_stream_mode = DATA_STREAMING_OFF; /* set attach complete flag for use in open */ unit_p->unit_attached = 1; ddi_report_dev(dip); return (DDI_SUCCESS); break; default: DPRINT((CE_CONT,"ihcp_attach: instance %d: unrecognized command\n", instance)); return(DDI_FAILURE); } } /* This is a pretty generic getinfo routine as described in the manual. */ static int ihcp_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { register int error; register int instance; register struct ihcp_unit *unit_p; instance = getminor((dev_t)arg); DPRINT((CE_CONT,"ihcp_getinfo: instance %d: entering ihcp_getinfo\n", instance)); switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: unit_p = (struct ihcp_unit *)ddi_get_soft_state(state_head, instance); if (unit_p == NULL) { *result = NULL; cmn_err(CE_CONT,"ihcp_getinfo: instance %d: null unit_pointer!\n", instance); error = DDI_FAILURE; } else { *result = unit_p->dip; error = DDI_SUCCESS; } break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)instance; error = DDI_SUCCESS; break; default: *result = NULL; error = DDI_FAILURE; cmn_err(CE_CONT,"ihcp_getinfo: instance %d: unrecognized info_command!\n", instance) ; } DPRINT((CE_CONT,"ihcp_getinfo: instance %d: leaving ihcp_getinfo\n", instance)); return (error); } /* _init, _info, and _fini support loading and unloading the driver. */ int _init(void) { register int error; DPRINT((CE_CONT,"ihcp (_init): compiled %s, %s\n", __TIME__, __DATE__)); if ((error = ddi_soft_state_init(&state_head, sizeof (struct ihcp_unit), 1)) != 0) { DPRINT((CE_CONT,"ihcp (_init): _init error!, error # = 0x%x\n",error)); return(error); } if ((error = mod_install(&modlinkage)) != 0) { DPRINT((CE_CONT,"ihcp (_init): mod_install error! error # = 0x%x\n",error)); ddi_soft_state_fini(&state_head); } DPRINT((CE_CONT,"ihcp (_init): _init done, return value = 0x%x\n",error)); return(error); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int _fini(void) { int error; if ((error = mod_remove(&modlinkage)) != 0) { DPRINT((CE_CONT,"ihcp (_fini): mod_remove error! error # = 0x%x\n",error)); return (error); } ddi_soft_state_fini(&state_head); DPRINT((CE_CONT,"ihcp (_fini): _fini done\n")); return (0); } /* When our driver is unloaded, ihcp_detach cleans up and frees the resources we allocated in ihcp_attach. */ static int ihcp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { register struct ihcp_unit *unit_p; /* will point to this */ int instance; instance = ddi_get_instance(dip); unit_p = (struct ihcp_unit *)ddi_get_soft_state(state_head, instance); /* test for expected commands other commands may not be errors, so only print message if debugging is on */ switch(cmd) { case DDI_SUSPEND: DPRINT((CE_CONT,"ihcp_detach: instance %d: command is DDI_SUSPEND\n",instance)); mutex_enter(&unit_p->mutex); /* check for open - if so, return failure, if not save state, flag as suspended, and return success */ if(unit_p->unit_open) { DPRINT((CE_CONT,"ihcp_detach: instance %d: unit open, returning DDI_FAILURE\n", instance)); mutex_exit(&unit_p->mutex); return(DDI_FAILURE); } else { DPRINT((CE_CONT,"ihcp_detach: instance %d: saving state & setting suspended flag\n", instance)); unit_p->saved_plx_int_cstat = PLX_GETL(PLX_INT_CSTAT); unit_p->saved_plx_eeprom_user = PLX_GETL(PLX_EEPROM_USER); unit_p->saved_mode = IHCP_GETL(IHCP_MODE); unit_p->saved_device_control = IHCP_GETL(IHCP_DEVICE_CONTROL); unit_p->saved_interface_control = IHCP_GETL(IHCP_INTERFACE_CONTROL); unit_p->saved_auto_ltr_low = IHCP_GETL(IHCP_AUTO_LTR_LOW); unit_p->saved_auto_ltr_high = IHCP_GETL(IHCP_AUTO_LTR_HIGH); unit_p->unit_suspended = 1; mutex_exit(&unit_p->mutex); return(DDI_SUCCESS); } break; case DDI_DETACH: DPRINT((CE_CONT,"ihcp_detach: instance %d: command is DDI_DETACH\n", instance)); /* clear attached flag */ unit_p->unit_attached = 0; /* make sure dma and interrupts are off - we won't do a soft reset here, on the chance that data may still be in the fifo attach will do a soft reset - hopefully it won`t clobber a previous job that was still in the fifo when a timed detach was done! */ /* fist disable all plx interrupt enable bits then the ihcp masks */ PLX_PUTL(PLX_INT_CSTAT, 0); IHCP_PUTL(IHCP_INTERRUPT_MASK, 0); /* now make sure dma channel(s) are off for now we will access dma registers via the ihcp address window later chip revisions may require testing for rev level, and accessing via the plx map we will just force all zeros into both channel's registers */ PLX_PUTL(PLX_DMA_CMD_STAT_BOTH,0); /* this is as good a place as any to do phony references to the read dump varaibles to try to absolutely guarantee that the optimizer won't remove our cache flush reads and to try to shut lint up */ read_dump_0 += read_dump_1; /* free the various resources that were allocated in attach */ ddi_remove_minor_node(dip, NULL); ddi_dma_unbind_handle(unit_p->iopb_handle); ddi_dma_mem_free(&unit_p->iopb_acc_handle); ddi_dma_free_handle(&unit_p->iopb_handle); ddi_dma_free_handle(&unit_p->dma_handle); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->iblock_cookie); mutex_destroy(&unit_p->mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); ddi_soft_state_free(state_head, instance); DPRINT((CE_CONT,"ihcp_detach: instance %d: detach complete\n", instance)); return (DDI_SUCCESS); break; default: DPRINT((CE_CONT,"ihcp_detach: instance %d: unrecognized command\n", instance)); return(DDI_FAILURE); } } /* ihcp_open is called in response to the open(2) system call */ static int ihcp_open(dev_t *dev, int openflags, int otyp, cred_t *credp) { int retval = 0; int instance ; register struct ihcp_unit *unit_p; instance = getminor(*dev) ; /* get unit structure pointer and make sure driver is (completely) attached */ if((unit_p = (struct ihcp_unit *)ddi_get_soft_state(state_head, instance)) == NULL) { cmn_err(CE_CONT,"ihcp_open: instance %d: null unit pointer!\n", instance); return(ENXIO); } if(unit_p->unit_attached == 0) { cmn_err(CE_CONT,"ihcp open: instance %d: open attempted before attach complete!\n", instance); return(ENXIO); } /* Verify we are being opened as a character device */ if (otyp != OTYP_CHR) { cmn_err(CE_CONT,"ihcp_open: instance %d: wrong open_type!\n",instance); return (EINVAL); } /* Verify that we are being opened for write - WRITE ONLY DEVICE!! */ if(!(openflags & FWRITE)) { cmn_err(CE_CONT,"ihcp_open: instance %d: write only device!\n",instance); return(ENOTTY) ; } /* lock unit structure (& flags & registers) */ mutex_enter(&unit_p->mutex); /* start MUTEX */ /* check for system suspended (powered down) - if so, wait for cv_broadcast from resume code in attach routine */ while(unit_p->unit_suspended) cv_wait(&unit_p->power_cv, &unit_p->mutex); /* Only allow a single open */ if (unit_p->unit_open != 0) { DPRINT((CE_CONT,"ihcp_open: instance %d: open called when already open!\n", instance)); retval = EBUSY; } else { /* mark device as open and flag as busy for power management (used in detach) */ unit_p->unit_open = 1; /* clear unit flags - but not board type */ unit_p->unit_flags &= IHCP_CLEAR_FLAGS ; /* set default timeouts here timeout parameters are stored as ticks */ unit_p->dma_time = (u_long)(drv_usectohz(1000000) * unit_p->dma_time_def) ; unit_p->fifo_time = (u_long)(drv_usectohz(1000000) * unit_p->rdy_time_def) ; DPRINT((CE_CONT,"ihcp_open: instance %d: open complete\n", instance)); } mutex_exit(&unit_p->mutex); /* end MUTEX */ return (retval); } /* ihcp_close is called after the last process that has the device open calls close(2) since we enforce exclusive open, ihcp_close will always be called when the user process calls close(2) */ static int ihcp_close(dev_t dev, int openflags, int otyp, cred_t *credp) { register struct ihcp_unit *unit_p; unit_p = (struct ihcp_unit *)ddi_get_soft_state(state_head, getminor(dev)); /* Verify instance structure - if this fails, we are really in trouble! */ if (unit_p == NULL) { cmn_err(CE_CONT,"ihcp_close: null unit_pointer!\n"); return (ENXIO); } /* lock unit structure (and flags and registers) */ mutex_enter(&unit_p->mutex); /* start MUTEX */ /* let the next person open the device and flag as not busy for power management (used in detach) */ unit_p->unit_open = 0; /* we don't reset the board and device here - that might kill a job that was in the device but not yet complete - reset in detach */ mutex_exit(&unit_p->mutex); /* end MUTEX */ DPRINT((CE_CONT,"ihcp_close: instance %d: close complete\n", getminor(dev))); return (0); } static int ihcp_write(dev_t dev, struct uio *uiop, cred_t *credp) { register struct ihcp_unit *unit_p; int retval = 0; int instance ; instance = getminor(dev) ; unit_p = (struct ihcp_unit *)ddi_get_soft_state(state_head,instance) ; DPRINT((CE_CONT,"ihcp_write: instance %d: entering write routine\n", instance)); if (unit_p == NULL) { cmn_err(CE_CONT,"ihcp_write: instance %d: unit_pointer NULL!\n",instance) ; return(ENXIO) ; } /* we are exclusive open - we will leave the mutex for strategy so no mutex_enter(&unit_p->soft_mutex) */ /* clear the timeout and sig rx'd flags -- also clears "waiting for" flags strategy will set the "waiting for dma" flag. */ unit_p->unit_flags &= IHCP_CLEAR_FLAGS ; /* call physio (and strategy) to start the write. NULL means use bufferr from pool */ retval = physio(ihcp_strategy,(struct buf *)NULL, dev, B_WRITE, minphys, uiop) ; /* check flags for timeout error or signal received */ if(unit_p->unit_flags & IHCP_DMA_TIMEOUT) { retval = EIO ; cmn_err(CE_CONT,"ihcp_write: instance %d: DMA timeout! \n",instance) ; } if(unit_p->unit_flags & IHCP_SIG_RECEIVED) { retval = EINTR ; cmn_err(CE_CONT,"ihcp_write: instance %d: signal received! \n",instance) ; } /* we did not aquire a mutex within write so no mutex_exit(&unit_p->soft_mutex) ; */ DPRINT((CE_CONT,"ihcp_write: instance %d: leaving write routine\n", instance)); return (retval); } static int ihcp_ioctl(dev_t dev, int cmd, int arg, int flag, cred_t *credp, int *rvalp) { register struct ihcp_unit *unit_p ; /* points at our unit struct */ u_long bits ; /* will contain incoming or outgoing argument value */ u_char ct ; /* temp char */ u_short st ; /* temp short */ u_long lt ; /* temp long */ int count, i, t ; /* temp working variables */ int retval = 0 ; /* return value (errno) */ int instance ; /* our instance */ int command ; int waitval ; /* value returned by cv_timedwait_sig */ u_long time_now; /* time in ticks since last re-boot */ u_int return_array[19] ; /* will contain copy of registers to return to caller */ u_char data_array[256] ; /* used to contain p-i/o data out values after copyin */ instance = getminor(dev) ; unit_p = (struct ihcp_unit *)ddi_get_soft_state(state_head,instance) ; DPRINT((CE_CONT,"ihcp_ioctl: instance %d: entering ioctl routine, command = 0x%x\n", instance, cmd)); if (unit_p == NULL) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: unit_pointer NULL!\n",instance) ; return(ENXIO) ; } /* lock our unit structure */ mutex_enter(&unit_p->mutex) ; /* get command and argument size encoded in arg */ command = cmd & IHCPIO_CMD_MASK ; count = (cmd & IHCPIO_COUNT_MASK) >> 16 ; /* get first word of argument (even if we are eventually doing a copyout operation) - this saves a copyin in each case we can use standard copyin since we don't do recursive ioctl calls */ copyin((caddr_t)arg, (caddr_t)&bits, sizeof(bits)) ; DPRINT((CE_CONT,"ihcp_ioctl: instance %d: arg = 0x%x, *arg = 0x%x\n", instance, arg, bits)); /* defice mask used to strip the count and upper control bits from cases */ #define MASK IHCPIO_CMD_MASK switch(command) { case MASK & IHCPIO_DEV_RESET: /* send ~ 1us reset pulse to attached device */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at DEV_RESET\n", instance)); /* set and reset device clear bit, with a delay so the pulse is long enough for even slow devices usecwait will make the cpu delay, but doesn't guarantee that the writes won't stack in a write buffer (although with at least one read between them so we will do another read before the wait to force the buffer to flush */ IHCP_PUTL(IHCP_INTERFACE_CONTROL,(IHCP_GETL(IHCP_INTERFACE_CONTROL) | RESET_DEVICE)); read_dump_0 = IHCP_GETL(IHCP_MODE); drv_usecwait(1); IHCP_PUTL(IHCP_INTERFACE_CONTROL,(IHCP_GETL(IHCP_INTERFACE_CONTROL) & ~RESET_DEVICE)); break ; case MASK & IHCPIO_SET_CONFIG: /* set speed and if to use busy */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at SET_CONFIG\n", instance)); /* mask to pertinent bits only the calling program will use the old sbus speed definitions we will have to convert them to the pci card bits here */ bits &= ( IHCPIO_V_BURST | IHCPIO_BUSY_HANDSHAKE | IHCPIO_4_EDGE | IHCPIO_IGNORE_BUSY | IHCPIO_SPEED3) ; /* if request is a centronics function and board is strapped for versatec - error! */ if(((bits & (IHCPIO_BUSY_HANDSHAKE | IHCPIO_4_EDGE | IHCPIO_IGNORE_BUSY)) != 0) && ((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT)) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at SET_CONFIG - attempt to set",instance) ; cmn_err(CE_CONT," centronics mode on board strapped for versatec! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* if request is a versatec only function and board is strapped for centronics - error! */ if(((bits & IHCPIO_V_BURST) != 0) && ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT)) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at SET_CONFIG - attempt to set",instance) ; cmn_err(CE_CONT," versatec mode on board strapped for centronics! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* shuffle bits to match new board */ t = SPEED_MASK & (3 - ((bits & IHCPIO_SPEED3) >> 2)); if(bits & IHCPIO_BUSY_HANDSHAKE) t |= BUSY_NOT_ACK; if(bits & IHCPIO_4_EDGE) t |= FOUR_EDGE_HANDSHAKE; if(bits & IHCPIO_IGNORE_BUSY) t |= IGNORE_BUSY; if(bits & IHCPIO_V_BURST) t |= V_BURST; /* plug into mode register - being careful not to disturb other bits */ IHCP_PUTL(IHCP_MODE,(IHCP_GETL(IHCP_MODE) & (BYTE_SWIZZLE | REVERSE_DATA_ENB)) | t); break ; case MASK & IHCPIO_SET_DMATIME: /* set dma timeout parameter */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at SET_DMATIME\n", instance)); 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_long)(bits * drv_usectohz(1000000)) ; /* set ticks */ break ; case MASK & IHCPIO_SET_FIFOTIME: /* set timeout for fifo <1/2 full */ /* or fifo empty & dev rdy wait */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at SET_FIFOTIME\n", instance)); if(bits < FIFO_TIME_MIN) bits = FIFO_TIME_MIN ; /* not less than */ if(bits > FIFO_TIME_MAX) bits = FIFO_TIME_MAX ; /* or more than..*/ unit_p->fifo_time = (u_long)(bits * drv_usectohz(1000000)) ; /* ticks*/ break ; case MASK & IHCPIO_GET_REGS: /* get all the board's registers*/ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at GET_REGS\n", instance)); return_array[0] = unit_p->dev_and_vendor_id; /* device and vendor id */ return_array[1] = unit_p->revision_id; /* revision id */ return_array[2] = PLX_GETL(PLX_INT_CSTAT); /* interrupt control-status */ return_array[3] = PLX_GETL(PLX_EEPROM_USER); /* eeprom control and user bits */ return_array[4] = PLX_GETL(PLX_DMA_MODE_0); /* dma mode reg 0 */ return_array[5] = PLX_GETL(PLX_DMA_PCI_ADD_0); /* dma pci add 0 */ return_array[6] = PLX_GETL(PLX_DMA_LOC_ADD_0); /* dma local add 0 */ return_array[7] = PLX_GETL(PLX_DMA_COUNT_0); /* dma transfer count 0 */ return_array[8] = PLX_GETL(PLX_DMA_DESC_PTR_0); /* dma descriptor pointer 0 */ return_array[9] = PLX_GETL(PLX_DMA_CMD_STAT_BOTH); /* dma command/status - both channels */ return_array[10]= IHCP_GETL(IHCP_INTERRUPT_MASK); /* ihcp interrupt mask reg */ return_array[11]= IHCP_GETL(IHCP_MODE); /* ihcp mode reg */ return_array[12]= IHCP_GETL(IHCP_DEVICE_CONTROL); /* device control reg */ return_array[13]= IHCP_GETL(IHCP_INTERFACE_CONTROL); /* interface control */ return_array[14]= IHCP_GETL(IHCP_INTERFACE_STATUS); /* interface status */ return_array[15]= IHCP_GETL(IHCP_DEVICE_STATUS); /* device status */ return_array[16]= IHCP_GETL(IHCP_REVERSE_DATA); /* reverse data in reg */ return_array[17]= IHCP_GETL(IHCP_AUTO_LTR_LOW); /* auto ltr count low byte */ return_array[18]= IHCP_GETL(IHCP_AUTO_LTR_HIGH); /* auto ltr count high byte */ copyout((caddr_t)return_array, (caddr_t)arg, sizeof(return_array)) ; break ; case MASK & IHCPIO_GET_STATUS: /* get formatted version of status*/ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at GET_STATUS\n", instance)); /* get device status bits */ ct = IHCP_GETL(IHCP_DEVICE_STATUS) & (FAULT | ONLINE | PAPER_OUT | CENTRONICS_BUSY | VERSATEC_READY); /* shuffle status bits to match sbus format */ bits = 0; if(ct & FAULT) bits |= IHCPIO_DEV_FAULT; if(ct & ONLINE) bits |= IHCPIO_DEV_SEL; if(ct & PAPER_OUT) bits |= IHCPIO_DEV_POUT; if(ct & CENTRONICS_BUSY) bits |= IHCPIO_DEV_BUSY; if(ct & VERSATEC_READY) bits |= IHCPIO_DEV_RDY; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(u_long)) ; break ; case MASK & IHCPIO_GET_BOARD: /* get board type */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at GET_BOARD\n", instance)); bits = IHCP_GETL(IHCP_DEVICE_STATUS) & INTERFACE_STRAP_MASK; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(u_long)) ; break ; case MASK & IHCPIO_SET_VMODE: /* set versatec mode - both leave rdy ff on on pci card */ case MASK & IHCPIO_SET_VMODEX: DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at SET_VMODE\n", instance)); /* make sure the board is strapped for versatec */ if((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at SET_VMODE - attempt to do versatec",instance) ; cmn_err(CE_CONT," mode set on board strapped for centronics!\n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this command talks to the fifo - make sure there is room we could wait for room with an interrupt, but the way the board is to be used, there should always be room */ if((IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at SET_VMODE - no room in fifo for",instance) ; cmn_err(CE_CONT," versatec mode set! \n") ; retval = EIO ; goto IOCTLEXIT ; } /* bottom two bits contain print/plot/spp control - same for sbus and pci command byte will be in low byte unless byte swizzling selected */ lt = SET_VERSATEC_MODE | (bits & 0x03); if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); /* save mode in unit structure */ unit_p->mode = bits & 0x03; break ; case MASK & IHCPIO_V_CMD: /* issue versatec pulse command */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at V_CMD\n", instance)); /* make sure the board is strapped for versatec - can't do this w/centr */ if((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at V_CMD - attempt to issue versatec",instance) ; cmn_err(CE_CONT," command to board strapped for centronics!\n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this command talks to the fifo - make sure there is room !! */ if((IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at V_CMD - no room in fifo for",instance) ; cmn_err(CE_CONT," versatec command! \n") ; retval = EIO ; goto IOCTLEXIT ; } /* bottom two bits contain command - we have to shuffle them for compatibility with sbus driver */ if(bits == IHCPIO_VLTR) lt = VERSATEC_LINE_TERM; if(bits == IHCPIO_VEOT) lt = VERSATEC_EOT; if(bits == IHCPIO_VFED) lt = VERSATEC_FORM_FEED; if(bits == IHCPIO_VCLR) lt = VERSATEC_CLEAR; DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at V_CMD, command sent = 0x%x\n", instance, lt)); if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); break ; case MASK & IHCPIO_DATA_OUT: /* output chars from arg - # of chars in bottom of cmd */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at DATA_OUT, byte count = 0x%x\n", instance, count)); /* make sure # of characterss is <=255 that is the max # of characters in arg array - for historical reasons we will check for room in fifo before each byte but will not wait on an interrupt if the fifo is full - we will flag an error and return each byte will be sent by writing to the 8 bit data out register this is much less effecient than writing quad bytes to the 32 bit data out register, or to the burst out range it is not the intent of this driver to use this ioctl for large data blocks, or frequent data transfers - those funtions should be served via the write call this ioctl should be used for the output of a few bytes - perhaps as a header to a write call, and should be used between write calls, or waits for fifo less than half full if this ioctl is used repeatedly, without interspersed dma transfers, which leave the fifo approximately half full, or without wait for half full ioctls the fifo may eventually overflow it would be possible to incorporate a wait for less than half full interrupt in the data out loop, but that would be overkill, considering the intended use of this ioctl */ if(count > 255) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at DATA_OUT - too many bytes!\n",instance) ; retval = EINVAL ; goto IOCTLEXIT ; } copyin((caddr_t)arg,(caddr_t)data_array,count) ; for(i=0; ibyte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_8_BIT_DATA_OUT, lt); } break ; case MASK & IHCPIO_RDY_WAIT: /* wait for rdy & fifo empty - or timeout */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at RDY_WAIT, wait time = %d (ticks)\n", instance, unit_p->fifo_time)); /* if already ready and fifo empty, just return first clear any stale flags */ unit_p->unit_flags &= IHCP_CLEAR_FLAGS; if(IHCP_GETL(IHCP_INTERFACE_STATUS) & DEVICE_AND_INT_READY) goto IOCTLEXIT; /* drv_getparm returns current time since last reboot - in ticks */ if(drv_getparm(LBOLT, &time_now)) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at RDY_WAIT, drv_getparm error!\n", instance); retval = EIO; goto IOCTLEXIT; } /* set flags to indicate for what we wait */ unit_p->unit_flags |= IHCP_RDY_WAIT; /* make sure no left over interrupt flag, then enable plx and ihcp interrupts for device and interface ready we don't or into ihcp or plx mask reg since we know exactly which ints to enb */ IHCP_PUTL(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); IHCP_PUTL(IHCP_INTERRUPT_MASK, DEV_AND_INT_RDY_INT_ENB); PLX_PUTL(PLX_INT_CSTAT, (PCI_INTERRUPT_ENABLE | PCI_LOCAL_INT_ENABLE)); /* snooze until ready interrupt, timeout, or signal */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at RDY_WAIT, time_now = %d, unit_p->fifo_time = %d\n", instance, time_now, unit_p->fifo_time)); waitval = cv_timedwait_sig(&unit_p->cv, &unit_p->mutex, (long)(time_now + unit_p->fifo_time)); /* make sure interrupt enables are off and clear interrupt flag in case we got here via timeout just as interrupt set the flag */ IHCP_PUTL(IHCP_INTERRUPT_MASK, 0); PLX_PUTL(PLX_INT_CSTAT, 0); IHCP_PUTL(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); /* find out why we woke up */ if(waitval < 0) { /* timeout may not be error so no error message - caller can test flags for timeout we will set flag bit to stay compatible with sbus driver that had its own timeout routine */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at RDY_WAIT, timeout while waiting!\n", instance)); unit_p->unit_flags |= IHCP_RDY_TIMEOUT; 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 */ cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at RDY_WAIT - signal while waiting",instance) ; cmn_err(CE_CONT," for ready & fifo empty!\n") ; unit_p->unit_flags |= IHCP_SIG_RECEIVED; retval = EINTR ; goto IOCTLEXIT; } break ; case MASK & IHCPIO_HALF_WAIT: /* wait for fifo fifo_time)); /* if already less than half full, just return first clear any stale flags */ unit_p->unit_flags &= IHCP_CLEAR_FLAGS; if(IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_HALF_FULL) goto IOCTLEXIT; /* drv_getparm returns current time since last reboot - in ticks */ if(drv_getparm(LBOLT, &time_now)) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at RDY_WAIT, drv_getparm error!\n", instance); retval = EIO; goto IOCTLEXIT; } /* set flags to indicate for what we wait */ unit_p->unit_flags |= IHCP_HALF_WAIT; /* clear any left over interrupt flag, then enable plx and ihcp interrupt for fifo less than half full we don't or into ihcp or plx mask reg since we know exactly which ints to enb */ IHCP_PUTL(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); IHCP_PUTL(IHCP_INTERRUPT_MASK, FIFO_NOT_HALF_INT_ENB); PLX_PUTL(PLX_INT_CSTAT, (PCI_INTERRUPT_ENABLE | PCI_LOCAL_INT_ENABLE)); /* snooze until ready interrupt, timeout, or signal */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at HALF_WAIT, time_now = %d, unit_p->fifo_time = %d\n", instance, time_now, unit_p->fifo_time)); waitval = cv_timedwait_sig(&unit_p->cv, &unit_p->mutex, (long)(time_now + unit_p->fifo_time)); /* make sure interrupt enables are off and clear flag in case we got here via timeout just as interrupt happened */ IHCP_PUTL(IHCP_INTERRUPT_MASK, 0); PLX_PUTL(PLX_INT_CSTAT, 0); IHCP_PUTL(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); /* find out why we woke up */ if(waitval < 0) { /* timeout may not be error so no error message - caller can test flags for timeout we will set flag bit to stay compatible with sbus driver that had its own timeout routine */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at HALF_WAIT, timeout while waiting!\n", instance)); unit_p->unit_flags |= IHCP_HALF_TIMEOUT; 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 */ cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at HALF_WAIT - signal while waiting",instance) ; cmn_err(CE_CONT," for fifo not half full!\n") ; unit_p->unit_flags |= IHCP_SIG_RECEIVED; retval = EINTR ; goto IOCTLEXIT; } break ; case MASK & IHCPIO_STREAM_ON: /* set streaming mode if 10106 */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at STREAM_ON\n", instance)); if((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at STREAM_ON - attempt to set streaming",instance) ; cmn_err(CE_CONT," mode on board strapped for versatec! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this command talks to the fifo - make sure there is room !! */ if((IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at STREAM_ON - no room in fifo for",instance) ; cmn_err(CE_CONT," stream on command! \n") ; retval = EIO ; goto IOCTLEXIT ; } lt = SET_IO_MODE | DATA_STREAMING_MODE; if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); /* save streaming mode for compatibility with old vme ioctls */ unit_p->data_stream_mode = DATA_STREAMING_ON; break ; case MASK & IHCPIO_STREAM_OFF: /* turn streaming off (if 10106) */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at STREAM_OFF\n", instance)); if((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at STREAM_OFF - attempt to clear",instance) ; cmn_err(CE_CONT," streaming mode on board strapped for versatec! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this command talks to the fifo - make sure there is room !! */ if((IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at STREAM_OFF - no room in fifo for",instance) ; cmn_err(CE_CONT," stream off command! \n") ; retval = EIO ; goto IOCTLEXIT ; } /* set_io_mode w/ nothing else or'd in turns off streaming and auto ltr - we won't try to do streaming and auto ltr at same time in this driver */ lt = SET_IO_MODE ; if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); /* save streaming mode for compatibility with old vme ioctls */ unit_p->data_stream_mode = DATA_STREAMING_OFF; break ; case MASK & IHCPIO_GET_FLAGS: /* get unit flag longword */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at GET_FLAGS\n", instance)); copyout((caddr_t)&unit_p->unit_flags,(caddr_t)arg,sizeof(u_long)) ; break ; case MASK & IHCPIO_GET_FIFO: /* get formatted fifo flags */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at GET_FIFO\n", instance)); /* get fifo status bits and swizzle and complement to match sbus driver format */ lt = IHCP_GETL(IHCP_INTERFACE_STATUS); bits = 0; if(!(lt & FIFO_NOT_FULL)) bits |= IHCPIO_FIFO_FULL; if(!(lt & FIFO_NOT_HALF_FULL)) bits |= IHCPIO_FIFO_HALF; if(lt & FIFO_EMPTY) bits |= IHCPIO_FIFO_EMPTY; copyout((caddr_t)&bits,(caddr_t)arg,sizeof(u_long)) ; break ; case MASK & IHCPIO_MASTER_CLEAR: /* master clear board - not for use by wimps! */ /* clear ihcp logic and restore all defaults */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at MASTER_CLEAR\n", instance)); IHCP_PUTL(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); /* make absolutely sure that dma and interrupts are off in plx chip */ PLX_PUTL(PLX_INT_CSTAT, 0); PLX_PUTL(PLX_DMA_CMD_STAT_BOTH,0); /* set and clear mclr bit - other bits in this register are also treated like pulses, and do not persist, so we don't need to or in a bit, then and it out */ IHCP_PUTL(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); /* set and clear the device reset bit, with a delay and write buffer flush to guarantee the delay the mode read between puts should force the write buffer to flush */ IHCP_PUTL(IHCP_INTERFACE_CONTROL,(IHCP_GETL(IHCP_INTERFACE_CONTROL) | RESET_DEVICE)); read_dump_0 = IHCP_GETL(IHCP_MODE); drv_usecwait(1); IHCP_PUTL(IHCP_INTERFACE_CONTROL,(IHCP_GETL(IHCP_INTERFACE_CONTROL) & ~RESET_DEVICE)); /* set to default print mode, default handshake speed, and default byte ordering we don't have to check for fifo space, since we just cleared it print mode is set only if the board is strapped for versatec */ if(ORDER_DEF == HIGH_BYTE_FIRST) { IHCP_PUTL(IHCP_MODE, (IHCP_GETL(IHCP_MODE) | BYTE_SWIZZLE)); PLX_PUTL(PLX_EEPROM_USER, (PLX_GETL(PLX_EEPROM_USER) & ~USER_OUTPUT)); unit_p->byte_order = ORDER_DEF; } if((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { /* versatec - set speed and print/plot mode */ IHCP_PUTL(IHCP_MODE,((IHCP_GETL(IHCP_MODE) & ~SPEED_MASK) | unit_p->vers_speed_def)); lt = SET_VERSATEC_MODE | unit_p->mode_def; if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); unit_p->mode = unit_p->mode_def ; } else { /* centronics - set speed only */ IHCP_PUTL(IHCP_MODE,((IHCP_GETL(IHCP_MODE) & ~SPEED_MASK) | unit_p->cent_speed_def)); /* clear saved data streaming mode */ unit_p->data_stream_mode = DATA_STREAMING_OFF; } break ; case MASK & IHCPIO_SOFT_ACK: /* software ack - looks like an ack from the dev*/ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at SOFT_ACK\n", instance)); /* set and clear soft ack bit, as in mclr */ IHCP_PUTL(IHCP_INTERFACE_CONTROL, SOFTWARE_ACK); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); break ; case MASK & IHCPIO_AUTO_LTR_COUNT: /* set byte count for auto ltr function */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at AUTO_LTR_COUNT, byte count = %d\n", instance, bits)); if((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at AUTO_LTR_COUNT - attempt to set",instance) ; cmn_err(CE_CONT," auto ltr count on board strapped for centronics! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* auto ltr count is 16 bit register, accessed as two separate bytes the count register takes count minus 1 */ IHCP_PUTL(IHCP_AUTO_LTR_LOW, (bits - 1) & 0xFF); IHCP_PUTL(IHCP_AUTO_LTR_HIGH, ((bits -1) >> 8) & 0xFF); break; case MASK & IHCPIO_AUTO_LTR_ON: /* enable auto ltr operation */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at AUTO_LTR_ON\n", instance)); if((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at AUTO_LTR_ON - attempt to set",instance) ; cmn_err(CE_CONT," auto ltr mode on board strapped for centronics! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this command talks to the fifo - make sure there is room !! */ if((IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at AUTO_LTR_ON - no room in fifo for",instance) ; cmn_err(CE_CONT," auto ltr on command! \n") ; retval = EIO ; goto IOCTLEXIT ; } lt = SET_IO_MODE | AUTO_LTR_MODE; if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); break ; case MASK & IHCPIO_AUTO_LTR_OFF: /* disable auto ltr operation */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at AUTO_LTR_OFF\n", instance)); if((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at AUTO_LTR_OFF - attempt to clear",instance) ; cmn_err(CE_CONT," auto ltr mode on board strapped for centronics! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this command talks to the fifo - make sure there is room !! */ if((IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at AUTO_LTR_OFF - no room in fifo for",instance) ; cmn_err(CE_CONT," auto ltr off command! \n") ; retval = EIO ; goto IOCTLEXIT ; } /* set io mode with no other bits clears auto ltr and data streaming */ lt = SET_IO_MODE; if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); break ; case MASK & IHCPIO_DEV_AND_VEND_ID: /* return vendor id in low 16 bits, device id in high 16 bits */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at DEV_AND_VEND_ID\n", instance)); bits = unit_p->dev_and_vendor_id; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(u_long)); break; case MASK & IHCPIO_REVISION_ID: /* return board revision id */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at REVISION_ID\n", instance)); bits = unit_p->revision_id; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(u_long)); break; case MASK & IHCPIO_LITTLE_ENDIAN: /* data will go to plotter low byte first */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at LITTLE_ENDIAN\n", instance)); /* low first byte ordering (the hardware default) is set by turning off the byte swizzle bit in the ihcp logic, and TURNING ON the user output bit in the plx logic] */ IHCP_PUTL(IHCP_MODE, (IHCP_GETL(IHCP_MODE) & ~BYTE_SWIZZLE)); PLX_PUTL(PLX_EEPROM_USER, (PLX_GETL(PLX_EEPROM_USER) | USER_OUTPUT)); unit_p->byte_order = LOW_BYTE_FIRST; break; case MASK & IHCPIO_BIG_ENDIAN: /* data will go to plotter high byte first */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at BIG_ENDIAN\n", instance)); /* turn on the ihcp byte swizzle bit and TURN OFF the plx user output bit */ IHCP_PUTL(IHCP_MODE, (IHCP_GETL(IHCP_MODE) | BYTE_SWIZZLE)); PLX_PUTL(PLX_EEPROM_USER, (PLX_GETL(PLX_EEPROM_USER) & ~USER_OUTPUT)); unit_p->byte_order = HIGH_BYTE_FIRST; break; case MASK & IHCPIO_DIRECT_MODE: /* direct user write of mode register - use carefully! */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at DIRECT_MODE\n", instance)); IHCP_PUTL(IHCP_MODE, bits); break; case MASK & IHCPIO_DEVICE_CONTROL: /* direct write to device control register */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at DEVICE_CONTROL\n", instance)); IHCP_PUTL(IHCP_DEVICE_CONTROL, bits); break; case MASK & IHCPIO_INTERFACE_STATUS: /* direct read of interface status register */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at INTERFACE_STATUS\n", instance)); bits = IHCP_GETL(IHCP_INTERFACE_STATUS); copyout((caddr_t)&bits, (caddr_t)arg, sizeof(u_long)); break; case MASK & IHCPIO_DEVICE_STATUS: /* direct read of device status register */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at DEVICE_STATUS\n", instance)); bits = IHCP_GETL(IHCP_DEVICE_STATUS); copyout((caddr_t)&bits, (caddr_t)arg, sizeof(u_long)); break; case MASK & IHCPIO_REVERSE_DATA: /* direct read of reverse data register - requires */ /* fiddling of tri-state bit, and others to actually */ /* do anything useful with this */ DPRINT((CE_CONT,"ihcp_ioctl: istance %d: at REVERSE_DATA\n", instance)); bits = IHCP_GETL(IHCP_REVERSE_DATA); copyout((caddr_t)&bits, (caddr_t)arg, sizeof(u_long)); break; /* ioctls for compatibility with versatec and sun drivers for VME and Sbus boards */ case MASK & LPSETVERSATEC: /* compatibility - set versatec port */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at LPSETVERSATEC\n", instance)); /* make sure the board is a versatec type - can't do this w/centr */ if((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPSETVERSATEC - attempt to select",instance) ; cmn_err(CE_CONT," versatec port on board strapped for centronics! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } break ; case MASK & LPSETCENTRONICS: /* compatibility - set centr port */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at LPSETCENTRONICS\n", instance)); if((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPSETCENTRONICS - attempt to select",instance) ; cmn_err(CE_CONT," centronics port on board strapped for versatec! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } break ; case MASK & LPCOMMAND: /* compatibility - old style cmds */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at LPCOMMAND\n", instance)); /* 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 */ /* assert long reset signal */ if(bits & LPC_LRST) IHCP_PUTL(IHCP_INTERFACE_CONTROL, IHCP_GETL(IHCP_INTERFACE_CONTROL) | RESET_DEVICE); /* de-assert long reset */ if(bits & LPC_DRST) IHCP_PUTL(IHCP_INTERFACE_CONTROL, IHCP_GETL(IHCP_INTERFACE_CONTROL) & ~RESET_DEVICE);; /* test for option port request - better be strapped for centronics!! */ if(bits & LPC_SOPT) if((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPCOMMAND - attempt to select",instance) ; cmn_err(CE_CONT," centronics port on board strapped for versatec! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* test for versatec port selection */ if(bits & LPC_SVPT) if((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPCOMMAND - attempt to select",instance) ; cmn_err(CE_CONT," versatec port board strapped for centronics! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* enable data streaming - if board is strapped for centronics! */ if(bits & LPC_DSTR) { if((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPCOMMAND - attempt to set",instance) ; cmn_err(CE_CONT," streaming mode on board strapped for versatec! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this command talks to the fifo - make sure there is room !! */ if((IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPCOMMAND - no room in fifo for",instance) ; cmn_err(CE_CONT," stream on command! \n") ; retval = EIO ; goto IOCTLEXIT ; } lt = SET_IO_MODE | DATA_STREAMING_MODE; if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); /* save for posterity */ unit_p->data_stream_mode = DATA_STREAMING_ON; } /* reset data streaming bit */ if(bits & LPC_DDST) { if((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPCOMMAND - attempt to clear",instance) ; cmn_err(CE_CONT," streaming mode on non-centronics unit! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } if((IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPCOMMAND - no room in fifo for",instance) ; cmn_err(CE_CONT," stream off command! \n") ; retval = EIO ; goto IOCTLEXIT ; } /* set_io_mode w/ nothing else or'd in turns off streaming and auto ltr - we won't try to do streaming and auto ltr at same time in this driver */ lt = SET_IO_MODE ; if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); /* save streaming mode for later getregs call */ unit_p->data_stream_mode = DATA_STREAMING_OFF; } /* test for versatec print/plot mode changes - make sure we are a versatec board !! */ if(bits & LPC_PRINTMASK) { if((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPCOMMAND - attempt to do",instance) ; cmn_err(CE_CONT," versatec mode set on board strapped for centronics! \n") ; retval = ENOTTY ; goto IOCTLEXIT ; } /* this cmd talks to the fifo - make sure there is room !! */ if((IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPCOMMAND - no room in fifo",instance) ; cmn_err(CE_CONT," for versatec mode set! \n") ; retval = EIO ; goto IOCTLEXIT ; } /* get current mode setting */ lt = unit_p->mode ; /* now look at individual command bits and twiddle mode */ if(bits & LPC_VSPP) lt |= SPP_MODE ; /* set spp mode */ if(bits & LPC_DSPP) lt &= ~SPP_MODE ; /* clear spp */ if(bits & LPC_VPLT) lt |= PLOT_MODE ; /* set plot mode*/ if(bits & LPC_DVPT) lt &= ~PLOT_MODE ; /* clear plot */ if(bits & LPC_PRNT) lt = NORM_PRINT_MODE ; /* 0 = normal print mode */ /* save final mode - without mode set command bit - in unit structure */ unit_p->mode = lt; /* write final mode to fifo - remember to write to high byte if big-endian data mode selected */ lt |= SET_VERSATEC_MODE; if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); } /* check for software ack pulse request */ if(bits & LPC_SACK) { /* set and clear soft ack bit, as in mclr */ IHCP_PUTL(IHCP_INTERFACE_CONTROL, SOFTWARE_ACK); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); } /* check for master clear request - the old vme board also issued a reset to the device when mclr was pulsed, so we will do that also we will also restore the default print/plot mode (if versatec), the device handshake speed, and the byte ordering */ if(bits & LPC_MCLR) { IHCP_PUTL(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); /* make absolutely sure that dma and interrupts are off in plx chip */ PLX_PUTL(PLX_INT_CSTAT, 0); PLX_PUTL(PLX_DMA_CMD_STAT_BOTH,0); /* set and clear mclr bit - other bits in this register are also treated like pulses, and do not persist, so we don't need to or in a bit, then and it out */ IHCP_PUTL(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); /* set and clear the device reset bit, with a delay and write buffer flush to guarantee the delay the mode read between puts should force the write buffer to flush */ IHCP_PUTL(IHCP_INTERFACE_CONTROL,(IHCP_GETL(IHCP_INTERFACE_CONTROL) | RESET_DEVICE)); read_dump_0 = IHCP_GETL(IHCP_MODE); drv_usecwait(1); IHCP_PUTL(IHCP_INTERFACE_CONTROL,(IHCP_GETL(IHCP_INTERFACE_CONTROL) & ~RESET_DEVICE)); /* set to default print mode, default handshake speed, and default byte ordering we don't have to check for fifo space, since we just cleared it print mode is set only if the board is strapped for versatec */ if(ORDER_DEF == HIGH_BYTE_FIRST) { IHCP_PUTL(IHCP_MODE, (IHCP_GETL(IHCP_MODE) | BYTE_SWIZZLE)); PLX_PUTL(PLX_EEPROM_USER, (PLX_GETL(PLX_EEPROM_USER) & ~USER_OUTPUT)); unit_p->byte_order = ORDER_DEF; } if((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { /* versatec - set speed and print/plot mode */ IHCP_PUTL(IHCP_MODE,((IHCP_GETL(IHCP_MODE) & ~SPEED_MASK) | unit_p->vers_speed_def)); lt = SET_VERSATEC_MODE | unit_p->mode_def; if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); unit_p->mode = unit_p->mode_def ; } else { /* centronics - set speed only */ IHCP_PUTL(IHCP_MODE,((IHCP_GETL(IHCP_MODE) & ~SPEED_MASK) | unit_p->cent_speed_def)); } /* clear saved data streaming mode */ unit_p->data_stream_mode = DATA_STREAMING_OFF; } /* 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((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPCOMMAND - attempt to do",instance) ; cmn_err(CE_CONT," versatec pulsed command to board strapped for centronics! \n") ; 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((IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT,"ihcp_ioctl: instance %d: at LPCOMMAND - no room in fifo",instance) ; cmn_err(CE_CONT," for versatec pulsed command! \n") ; retval = EIO ; goto IOCTLEXIT ; } /* shuffle vme style commands to match pci board */ if(bits & LPC_VLTR) lt = VERSATEC_LINE_TERM; if(bits & LPC_VEOT) lt = VERSATEC_EOT; if(bits & LPC_VTFF) lt = VERSATEC_FORM_FEED; if(bits & LPC_VCLR) lt = VERSATEC_CLEAR; if(unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUTL(IHCP_COMMAND_OUT, lt); } break ; case MASK & LPGETREGS: /* old style (VMEbus) get regs command */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at LPGETREGS\n", instance)); /* start work on interface status register */ st = 0; /* get interface status register and fiddle interface and device ready bits */ ct = (u_char)IHCP_GETL(IHCP_INTERFACE_STATUS); if(ct & DEVICE_AND_INT_READY) st |= OLD_DIRY; if(ct & DEVICE_READY) st |= OLD_DVRY; /* get remembered data streaming mode */ if(unit_p->data_stream_mode == DATA_STREAMING_ON) st |= OLD_DSTR; /* get board type from unit struct if centronics, flag as old style option port */ if((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) st |= OLD_SOPT; /* put result in top of long temp */ lt = (u_long)(st << 16) ; /* 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((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { /* work on centronics part get the device status register and shuffle bit positions and polarities */ ct = (u_char)IHCP_GETL(IHCP_DEVICE_STATUS); if(ct & VERSATEC_READY) st |= OLD_OACK; if(!(ct & CENTRONICS_BUSY)) st |= OLD_ONBY; if(!(ct & PAPER_OUT)) st |= OLD_OPPR; if(!(ct & ONLINE)) st |= OLD_ONSL; if(ct & FAULT) st |= OLD_OFLT; /* look for device reset latch */ if(IHCP_GETL(IHCP_INTERFACE_CONTROL) & RESET_DEVICE) st |= OLD_OLRS ; /* long reset */ } else { /* work on versatec part */ if((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_VERS_TTL) st |= OLD_VTTL; /* get device status and twiddle bits */ ct = (u_char)IHCP_GETL(IHCP_DEVICE_STATUS); if(ct & VERSATEC_READY) st |= OLD_VRDY ; /* vers ready*/ if(ct & PAPER_OUT) st |= OLD_VPPR ; /* paper ok */ if(ct & ONLINE) st |= OLD_VONL ; /* vers onlin*/ /* get current mode from unit structure */ ct = unit_p->mode ; if(ct & SPP_MODE) st |= OLD_VSPP ; /* spp mode */ if(ct & PLOT_MODE) st |= OLD_VPLT ; /* plot mode */ } lt |= st ; /* save in bottom of long temp */ copyout((caddr_t)<,(caddr_t)arg,sizeof(u_long)) ; break; case MASK & LPSETTIMVAL: /* set timeout value - old style - for dma and fifo empty */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at LPSETTIMVAL\n", instance)); /* 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 ; unit_p->fifo_time = (u_long)((bits * drv_usectohz(1000000))/50) ; unit_p->dma_time = (u_long)((bits * drv_usectohz(1000000))/50) ; break; case MASK & LPGETTIMVAL: /* get current timeout (in ticks ?) */ DPRINT((CE_CONT,"ihcp_ioctl: instance %d: at LPGETTIMVAL\n", instance)); /* return # of fiftieths of a second */ lt = (unit_p->dma_time/drv_usectohz(1000000)) * 50 ; copyout((caddr_t)<,(caddr_t)arg,sizeof(u_long)) ; break; default: /* we don't print an error message here if we are involved in a pipe, the pipe logic may send us a generic ioctl to determine if we are a terminal we have to return an error or we will give a false terminal indication. */ retval = EINVAL; break; } IOCTLEXIT: mutex_exit(&unit_p->mutex) ; return (retval); } /* this is the interrupt catcher - there is no high level or soft int routine in this version of the driver it quiets the board and does a cv_sig to wake up the top half of the driver the argument is a pointer at the soft state structure for this instance */ static u_int ihcp_intr(caddr_t arg) { struct ihcp_unit *unit_p; /* will point at our soft state */ volatile u_long temp; /* just what it says */ u_int int_serviced = DDI_INTR_UNCLAIMED; int instance; unit_p = (struct ihcp_unit *)arg; instance = unit_p->saved_instance; /* grab mutex to protect intr code from strategy and v/v */ mutex_enter(&unit_p->mutex); DPRINT((CE_CONT,"ihcp_intr: instance %d: entering interrupt routine\n", instance)); /* get plx interrupt control/status register and check for interrupt check for master interrupt enable bit and either local int rq or dma int rq */ temp = PLX_GETL(PLX_INT_CSTAT); if((temp & PCI_INTERRUPT_ENABLE) && ((temp & LOCAL_INTERRUPT) || (temp & DMA_0_INTERRUPT))) { DPRINT((CE_CONT,"ihcp_intr: instance %d: claiming interrupt, INT_CSTAT = 0x%x\n", instance, temp)); /* force off dma enable, interrupt enables, and ihcp interrupt mask bits which shoule release the interrupt request then (after masks are off) toggle reset int flag bit on & off to clear interrupt flag (which is a latch) and do the same to the dma done interrupt */ PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, 0); PLX_PUTL(PLX_INT_CSTAT, 0); IHCP_PUTL(IHCP_INTERRUPT_MASK, 0); PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, DMA_CLEAR_INTERRUPT); IHCP_PUTL(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); /* do register reads to get the previous writes to flush to the pci bus so the writes that clear the interrupt will be complete before we exit - we will hope that the optimizer doesn't delete these! */ read_dump_0 = PLX_GETL(PLX_EEPROM_USER); read_dump_1 = IHCP_GETL(IHCP_MODE); /* in the sbus handler, we did a drv_usecwait here to give the interrupt line time to rise the _GETL will take some time to complete since the plx chip is fairly slow for single reads, so there should be plenty of time for the interrupt line to rise */ int_serviced = DDI_INTR_CLAIMED ; cv_signal(&unit_p->cv); } mutex_exit(&unit_p->mutex) ; DPRINT((CE_CONT,"ihcp_intr: instance %d: leaving interrupt routine\n", instance)); return(int_serviced) ; } static int ihcp_strategy(struct buf *bp) { register struct ihcp_unit *unit_p; /* points to unit structure */ int instance; ddi_dma_cookie_t dma_cookie; /* dma cookie will be filled in by buf_bind_handle */ u_int cookie_count; /* number of cookies returned by buf_bind_handle */ int cookie_number; /* number of cookie that we are working on */ int iopb_number; /* number of iobp that we are working on */ caddr_t mapped_address; /* virtual add of user buffer that we will use internal to the driver */ int buffer_size; /* total size of user buffer */ u_long dma_address; /* modified(maybe) cookie address to put in chain link */ u_long dma_count; /* modified(maybe) count that we will put in chain link */ int index; /* useful counter/pointer */ int count; /* also useful counter */ u_long temp; /* useful temporary */ u_long time_now; /* for timeout calculations */ int waitval; /* returned from cv_timedwait_sig */ /* get instance number from buffer and get pointer to soft state */ instance = getminor(bp->b_edev) ; unit_p = (struct ihcp_unit *)ddi_get_soft_state(state_head,instance) ; DPRINT((CE_CONT,"ihcp_strategy: instance %d: entering strategy\n", instance)); /* lock unit structure and pointers */ mutex_enter(&unit_p->mutex) ; /* save buffer pointer in unit structure for error returns from interrupt routine */ unit_p->buf_p = bp ; /* we will map the buffer into kernel space so we can access it from within the driver code - this is necessary since we may have to write individual bytes to fifo to handle un-aligned buffers or non mod 4 byte counts i am assured that this will not confuse the later dma mapping remember to map_out when done or if error! also save the total buffer size */ bp_mapin(bp); mapped_address = bp->b_un.b_addr; buffer_size = bp->b_bcount; DPRINT((CE_CONT,"ihcp_strategy: instance %d: mapped_address = 0x%x, buffer size = 0x%x\n", instance, mapped_address, buffer_size)); /* bind dma handle and get first dma cookie for dma hardware */ if(ddi_dma_buf_bind_handle(unit_p->dma_handle, bp, DDI_DMA_WRITE | DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, (caddr_t)0, &dma_cookie, &cookie_count) != DDI_DMA_MAPPED) { bp->b_error |= EIO ; bp->b_flags |= B_ERROR ; bp_mapout(bp); cmn_err(CE_CONT,"ihcp_strategy: instance %d: ddi_dma_buf_bind_handle failure!\n",instance) ; goto EXITSTRATEGY ; } DPRINT((CE_CONT,"ihcp_strategy: instance %d: dma cookie count = 0x%x\n", instance, cookie_count)); /* make sure that the cookie count is not zero and does not exceed our specified scatter/gather list length */ if((cookie_count == 0) || (cookie_count > unit_p->sg_list_length)) { bp->b_error |= EIO; bp->b_flags |= B_ERROR; cmn_err(CE_CONT,"ihcp_strategy: instance %d: dma cookie count = 0, or too many cookies!\n",instance) ; goto ERRORSTRATEGY ; } /* get working address and count of first cookie - that may get modified */ dma_address = dma_cookie.dmac_address; dma_count = dma_cookie.dmac_size; DPRINT((CE_CONT,"ihcp_strategy: instance %d: first cookie dmac_address = 0x%x, dmac_size = 0x%x\n", instance, dma_address, dma_count)); /* the plx dma mechanism, and our 32 bit wide fifos, require that all dma transfers be long word aligned, and be multiples of long words we will test for mis-alignment if so the leading bytes will be output to the fifo one byte at a time then we will test for enough remaining bytes to do at least a one long word dma transfer if enough, build tha chain list, dma_sync the list, and do the dma xfer and wait for done if not, wait for fifo less than half full then, after either, test for trailing bytes and output as needed the end result of all of the above is that any pass through strategy will leave the fifo approximately half full or less we don't test for half full as we output individual bytes, we test for full and flag an error if we overflow it is not the intent of this driver to allow the entire data transfer requirements of the device to be met via the data out ioctl -- it is intended that write and strategy be the major data movers, with the ioctl used for short data or control bursts interspersed with write() calls done this way, it should not be necessary to deal with waiting for empty, or interrupts in the data out ioctl */ /* set flags to indicate dma wait - even if we don't wait, just so flags will reflect that we were in strategy error flags are not cleared here so they will survive multiple calls to strategy by physio */ unit_p->unit_flags |= IHCP_DMA_WAIT; /* calculate number of mis-aligned bytes at buffer start limit # to actual link size!! */ count = 0x03 & (4 - (0x03 & dma_address)); if(count > dma_count) count = dma_count; DPRINT((CE_CONT,"ihcp_strategy: instance %d: number of initial unaligned bytes = 0x%x\n", instance, count)); /* output any leading bytes mapped address and first cookie dma address will be incremented to indicate new start add for dma, and first cookie dma count and total buf size count will be decremented */ for(index=0; indexb_error |= EIO; bp->b_flags |= B_ERROR; cmn_err(CE_CONT,"ihcp_strategy: instance %d: fifo full while transferring leading bytes!\n",instance) ; goto ERRORSTRATEGY ; } temp = *mapped_address; if(unit_p->byte_order == HIGH_BYTE_FIRST) temp = temp << 24; IHCP_PUTL(IHCP_8_BIT_DATA_OUT, temp); } /* we should now have an aligned buffer for the first dma chain link, with 0 or more bytes in it if total buffer size (remaining) is less than 4, skip dma stuff, and wait for 3) { DPRINT((CE_CONT,"ihcp_strategy: instance %d: dma required, dma buffer virtual address = 0x%x, total buffer size = 0x%x\n", instance, mapped_address, buffer_size)); /* start walking through user buffer cookies, building the chaining list we have to keep track of separate cookie number and chain link number, since the 1st link size could be reduced to zero by output of leading non-aligned bytes, and we don't know what plx does with a 0 count */ iopb_number = 0; for(cookie_number = 0; cookie_number < cookie_count; cookie_number++) { /* we aleady have the first cookie (modified-maybe) get the next cookie - if number is not 0 and move info to our varaibles (we probably shouldn't modify cookie itsself */ if(cookie_number != 0) { DPRINT((CE_CONT,"ihcp_strategy: instance %d: cookie number != 0, calling ddi_dma_nextcookie\n", instance)); ddi_dma_nextcookie(unit_p->dma_handle, &dma_cookie); dma_address = dma_cookie.dmac_address; dma_count = dma_cookie.dmac_size; } DPRINT((CE_CONT,"ihcp_strategy: instance %d: processing cookie #0x%x, phys address = 0x%x, size = 0x%x\n", instance, cookie_number, dma_address, dma_count)); /* if the dma size in this cookie is less than 4, don't make an iopb for it this had better only happen (sometimes) for the first and last cookie - which may be the same */ if(dma_count > 3) { DPRINT((CE_CONT,"ihcp_strategy: instance %d: dma count > 3, building iopb number 0x%x\n", instance, iopb_number)); /* at this point, all dma addresses should be aligned, although the count in the last cookie may not be a multiple of four any non-aligned cookie, or any non mod 4 count (except the last one) is an error */ if((dma_address & 0x3) != 0) { cmn_err(CE_WARN,"ihcp_strategy: instance %d: cookie dma address not aligned!\n", instance); bp->b_error |= EIO; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY ; } if(((dma_count & 0x3) != 0) && (cookie_number != (cookie_count -1))) { cmn_err(CE_WARN,"ihcp_strategy: instance %d: cookie size not 0 mod 4 and not last cookie!\n", instance); bp->b_error |= EIO; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY; } /* assemble the chain link */ IOPB_PUTL((iopb_number * IOPB_SIZE) + IOPB_PCI_ADDRESS, dma_address); /* pci user buf segment address */ IOPB_PUTL((iopb_number * IOPB_SIZE) + IOPB_TRANSFER_SIZE, dma_count & 0xFFFFFFFC); /* buf segment size */ IOPB_PUTL((iopb_number * IOPB_SIZE) + IOPB_LOCAL_ADDRESS, IHCP_32_BIT_DATA_OUT); /* data destination */ /* the next descriptor pointer element of the iopb includes the location bit (pci or local memory), the end of chain bit, the interrupt after terminal count bit (not used), the direction of transfer bit (0 = output to device), and the physical location of the next iopb if this is the last cookie, OR if the next cookie will have a count less than 4 bytes (so no dma for it), this is the last iopb flag it as the last in the list, and don't set a "next iopb" address the test at the top of the loop will catch the next cookie size < 4 and skip iopb generation code for the last cookie */ if((cookie_number != (cookie_count -1)) && ((buffer_size - (dma_count & 0xFFFFFFFC)) > 3)) { DPRINT((CE_CONT,"ihcp_strategy: instance %d: not last iopb, writing pointer to next cookie\n", instance)); IOPB_PUTL((iopb_number * IOPB_SIZE) + IOPB_NEXT_IOPB, DMA_CHAIN_IN_PCI_MEM | DMA_NEXT_ADDRESS_MASK & (unit_p->iopb_phys_base_addr + ((iopb_number + 1) * IOPB_SIZE))); } else { DPRINT((CE_CONT,"ihcp_strategy: instance %d: last iopb, setting end of chain bit\n", instance)); IOPB_PUTL((iopb_number * IOPB_SIZE) + IOPB_NEXT_IOPB, DMA_CHAIN_IN_PCI_MEM | DMA_END_OF_CHAIN); } /* reduce total buffer size by each cookie count (masked) and bump virtual address by masked size */ mapped_address += (dma_count & 0xFFFFFFFC); buffer_size -= (dma_count & 0xFFFFFFFC); DPRINT((CE_CONT,"ihcp_strategy: instance %d: done with iopb number 0x%x, new mapped add = 0x%x, new buffer size = 0x%x\n", instance, iopb_number, mapped_address, buffer_size)); iopb_number++; } /* end of if dma_count > 3 */ } /* end of for ... */ /* since we have touched iopb memory, we must make sure current data is available to plx dma logic we don't have to worry about this for the user buffer, it is handled by buf_bind_handle we will only ask to sync the part of the iopb space that we have touched (for efficiency) iopb_number should contain the actual number of iopbs generated (it is inc'd by 1 at end of loop) */ DPRINT((CE_CONT,"ihcp_strategy: instance %d: syncing iopb memory, iopb_count = 0x%x, size sync'd = 0x%x\n", instance, iopb_number, iopb_number * IOPB_SIZE)); if((ddi_dma_sync(unit_p->iopb_handle, (off_t)0, (size_t)(iopb_number * IOPB_SIZE), DDI_DMA_SYNC_FORDEV) != DDI_SUCCESS)) { cmn_err(CE_WARN,"ihcp_strategy: instance %d: ddi_dma_sync failure!\n", instance); bp->b_error |= EIO; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY ; } /* program plx dma logic on board - in chaining mode make sure that any left over dma done interrupt is cleared */ PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, DMA_CLEAR_INTERRUPT); PLX_PUTL(PLX_DMA_MODE_0, DMA_BUS_32_BIT | DMA_WAIT_1 | DMA_BURST_ENABLE | DMA_CHAIN_ENABLE | DMA_DONE_INTERRUPT_ENB | DMA_LOCAL_ADD_HOLD | DMA_DEMAND_MODE); /* mode register */ PLX_PUTL(PLX_DMA_DESC_PTR_0, DMA_CHAIN_IN_PCI_MEM | unit_p->iopb_phys_base_addr); /* output, list in pci mem, list ptr*/ PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, DMA_ENABLE); /* enb first to avoid PLX bug */ PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, DMA_ENABLE | DMA_START); /* actually fire up xfer */ /* the 9060 drives the local interrupt out true when a dma interrupt happens we have to loop that output back to the local interrupt input external to the chip to get a dma interrupt to the pci bus */ PLX_PUTL(PLX_INT_CSTAT, PCI_INTERRUPT_ENABLE | PCI_LOCAL_INT_ENABLE | LOCAL_INT_OUT_ENABLE | LOCAL_DMA_0_INT_ENABLE); } else { DPRINT((CE_CONT,"ihcp_strategy: instance %d: no dma, waiting for <1/2 full\n", instance)); /* we need to wait for fifo less than half full it may already be less than half full - if so sneak around the wait code */ if(IHCP_GETL(IHCP_INTERFACE_STATUS) & FIFO_NOT_HALF_FULL) goto NOWAIT; DPRINT((CE_CONT,"ihcp_strategy: instance %d: not <1/2 full, setting up for interrupt\n", instance)); /* clear any left over interrupt flag, then enable plx and ihcp interrupt for fifo less than half full we don't or into ihcp or plx mask reg since we know exactly which ints to enb */ IHCP_PUTL(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); IHCP_PUTL(IHCP_INTERRUPT_MASK, FIFO_NOT_HALF_INT_ENB); PLX_PUTL(PLX_INT_CSTAT, (PCI_INTERRUPT_ENABLE | PCI_LOCAL_INT_ENABLE)); } /* we arrive here after setting up for a dma wait, or a less than half full wait */ /* drv_getparm returns current time since last reboot - in ticks */ if(drv_getparm(LBOLT, &time_now)) { cmn_err(CE_CONT,"ihcp_strategy: instance %d: drv_getparm error!\n", instance); bp->b_error |= EIO; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY ; } /* snooze until done or dma_time = %d\n", instance, time_now, unit_p->dma_time)); waitval = cv_timedwait_sig(&unit_p->cv, &unit_p->mutex, (long)(time_now + unit_p->dma_time)); /* make sure interrupt enables are off and pause dma (which will normally already be done) */ PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, 0); IHCP_PUTL(IHCP_INTERRUPT_MASK, 0); PLX_PUTL(PLX_INT_CSTAT, 0); /* if dma is not done (after sig or timeout), force an abort and issue a master clear to flush ihcp buffers abort may take some time to complete, so use delay and register read to give it some time */ if(!(PLX_GETL(PLX_DMA_CMD_STAT_BOTH) & DMA_DONE)) { DPRINT((CE_CONT,"ihcp_strategy: instance %d: dma not done after cv_timedwait_sig\n", instance)); PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, DMA_ABORT); /* get mode - so we can restore it after a mclr mclr clears the fifos, and also makes them assert dma rq but...mclr holds off dma rq while mclr is asserted, and since the 9060 likes a synchronous dma rq, we will wait a while before hitting mclr 10us of delay seems like a lot, but we will only be in this code following an error it is not clear whether the 9060 needs dma rq in order to finish an abort if it does, it may transfer a word or two after the mclr, so we will repeat the mclr later */ drv_usecwait(5); temp = IHCP_GETL(IHCP_MODE); IHCP_PUTL(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); /* force write cache to flush - so delay appears on the bus */ read_dump_0 = IHCP_GETL(IHCP_INTERRUPT_MASK); drv_usecwait(5); IHCP_PUTL(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); IHCP_PUTL(IHCP_MODE, temp); /* make sure dma actually says done */ if(!(PLX_GETL(PLX_DMA_CMD_STAT_BOTH) & DMA_DONE)) { cmn_err(CE_CONT,"ihcp_strategy: instance %d: dma not done after abort!\n", instance); bp->b_error |= EIO; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY ; } } /* now we can safely make sure that the done interrupt is clear, and reset the ihcp interrupt flag (the above is probably not necessary after normal dma done) */ PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, DMA_CLEAR_INTERRUPT); IHCP_PUTL(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUTL(IHCP_INTERFACE_CONTROL, 0); DPRINT((CE_CONT,"ihcp_strategy: instance %d: cv_timedait_sig returns 0x%x\n", instance, waitval)); /* find out why we woke up */ if(waitval < 0) { /* timeout is always an error so print message and set error flag bits flags for timeout */ DPRINT((CE_CONT,"ihcp_strategy: instance %d: timeout while waiting for interrupt!\n", instance)); unit_p->unit_flags |= IHCP_DMA_TIMEOUT; bp->b_error |= EIO; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY ; } if(waitval == 0) { /* flag signal received and print error message */ cmn_err(CE_CONT,"ihcp_strategy: instance %d: signal received while waiting for interrupt\n",instance) ; unit_p->unit_flags |= IHCP_SIG_RECEIVED; bp->b_error |= EINTR; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY ; } NOWAIT: /* now we need to test for trailing bytes that didn't fit into exact number of long words it is possible to get here with remaining byte count == 0, either after dma or if leading byte output logic exhausted the byte count buffer size should show the remaining bytes - if any, and mapped address should point to the next byte to output */ DPRINT((CE_CONT,"ihcp_strategy: instance %d: number of trailing bytes = 0x%x, buffer address = 0x%x\n", instance, buffer_size, mapped_address)); for(index=0; indexb_error |= EIO; bp->b_flags |= B_ERROR; cmn_err(CE_CONT,"ihcp_strategy: instance %d: fifo full while transferring trailing bytes!\n",instance) ; goto ERRORSTRATEGY ; } temp = *mapped_address; if(unit_p->byte_order == HIGH_BYTE_FIRST) temp = temp << 24; IHCP_PUTL(IHCP_8_BIT_DATA_OUT, temp); } ERRORSTRATEGY: /* come here if error after mapin and bind handle are all complete, or in normal flow */ ddi_dma_unbind_handle(unit_p->dma_handle); bp_mapout(bp); EXITSTRATEGY: /* come here if error before all the mappings are done or in normal flow if error - be sure to do the appropriate unmapping(s) before arriving here */ /* we can't read back the range counter in all versions of the plx chip so we will just set resid to 0! */ bp->b_resid = 0; /* tell physio that we are done */ biodone(bp) ; /* unlock unit structure */ mutex_exit(&unit_p->mutex) ; return(0) ; }