/* IKON Corporation 2617 Western Ave. Seattle, WA 98121 (206) 728-6265 code module for Solaris 2.x driver for PCI DR11-W emulators 1 October, 1996 initial version for SPARC 16 January, 1997 added byte swap and endian mode support for 9060sd and later chips (default is big endian - which will un-swap the swapping that happens between dma and memory) selecting byte swapping for dma will actually turn OFF the big endian mode (with the end result that the bytes are swapped on SPARC) switched to dma channel 1, since the 9060 SD has no dma channel 0 (!) this should still work with two-channel 9060 and future 9080 17 June, 1997 added PLX DMA PCI ADD to GET_REGS ioctl added suspend and resume to detach and attach for system power management modified attach to get driver and device defaults from idr.conf 27 August, 1997 moved dma_handle to soft state added endian reg to saved 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 changed default byte-swap mode for x86 (sparc required dma byte swapping in the plx chip, x86 requires no swap) added save and restore of config register "interrupt line" since plx still clears that during a soft reset, and it was causing interrupts to fail after a driver reload - either add_drv or at boot time 7 June, 2000 convert all IPLX access to PLX accesses - doing accesses via the idr 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 add a dummy read when input or output fifo directly accessed - needed a few 100ns to allow fifo op to complete, especially in box w/write posting buffers 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 DR11-W emulators (10116 & 10118). 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 "idr_reg.h" #include "idr_var.h" #include "idr_io.h" static void *state_head ; /* opaque handle top of state structs */ static volatile u_long read_dump_0; /* dummy variables for register reads used to force cache flush */ 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 idr_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result); static int idr_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int idr_open(dev_t *dev, int openflags, int otyp, cred_t *credp); static int idr_close(dev_t dev, int openflags, int otyp, cred_t *credp); static int idr_read(dev_t dev, struct uio *uiop, cred_t *credp); static int idr_write(dev_t dev, struct uio *uiop, cred_t *credp); static int idr_ioctl(dev_t dev, int cmd, int arg, int flag, cred_t *credp, int *rvalp); static u_int idr_intr(caddr_t arg); static int idr_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); /* These are internal private functions - although strategy is called by physio, it is not made visible externally */ static int idr_strategy(struct buf *bp); static void plx_soft_reset(struct idr_unit *unit_p); /* declare device data access structure for plx and idr 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 this works fine for byte devices like the IKON hardcopy boards, we will see if it holds true for 16 bit data used by the DR11s */ static struct ddi_device_acc_attr slave_attr = { DDI_DEVICE_ATTR_V0, DDI_STRUCTURE_LE_ACC, DDI_STRICTORDER_ACC, } ; /* declare structures used for dynamic loading and unloading of the driver */ static struct cb_ops idr_cb_ops = { idr_open, idr_close, nulldev, /* not a block driver */ nodev, /* no print routine */ nodev, /* no dump routine */ idr_read, idr_write, idr_ioctl, nodev, /* no devmap routine */ nulldev, /* no mmap routine */ nulldev, /* no segmap routine */ nochpoll, /* no chpoll routine */ ddi_prop_op, 0, /* not a STREAMS driver */ D_NEW | D_MP, /* safe for multi-thread/multi-processor */ }; /* NOTE: declare and initialize the dev_ops structure. if it is necessary to prevent the system from unloading the driver on a timed basis (it will if no driver calls are made for some (unknown!) length of time) replace idr_detach with nodev. this will keep the driver from unloading. if this is done, it will be necessary to do a reconfiguration boot any time a new version of the driver is to be loaded. we do not support power operations for this driver/device combination we do allow system-wide suspend/resume operations when this device is not open */ static struct dev_ops idr_ops = { DEVO_REV, /* DEVO_REV indicated by manual */ 0, /* device reference count */ idr_getinfo, nulldev, /* identify no longer required */ nulldev, /* device probe for non-self-id */ idr_attach, idr_detach, /* REPLACE W/nodev IF DRIVER SHOULD STAY ATTACHED */ nodev, /* device reset routine */ &idr_cb_ops, (struct bus_ops *)0, /* bus operations */ nulldev, /* power operations */ }; extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &mod_driverops, MODINFOBANNER, &idr_ops, }; static struct modlinkage modlinkage = { MODREV_1, /* MODREV_1 indicated by manual */ (void *)&modldrv, NULL, /* termination of list of linkage structures */ }; /* register interrupts, map the working registers, and initialize things of a per-instance basis */ static int idr_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { register struct idr_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 some typing */ #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 */ switch(cmd) { case DDI_RESUME: unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance); /* we are being asked to resoter register state due to power up following power management power down */ DPRINT((CE_CONT, "idr_attach: instance %d: restoring state & clearing suspended flag\n", instance)); mutex_enter(&unit_p->mutex); /* restore hardware state we really don't need to save and restore the range counter, since we don't allow suspension while the device is open - but it may be useful for testing the suspend/resume code */ PLX_PUTL(PLX_ENDIAN_REG, unit_p->saved_plx_endian_reg); PLX_PUTL(PLX_INT_CSTAT, unit_p->saved_plx_int_cstat); PLX_PUTL(PLX_EEPROM_USER, unit_p->saved_plx_eeprom_user); IDR_PUTL(IDR_LATCHED_FUNCTIONS, unit_p->saved_latched_functions); IDR_PUTL(IDR_MODE, unit_p->saved_mode); IDR_PUTL(IDR_RANGE_LOW, unit_p->saved_range_low); IDR_PUTL(IDR_RANGE_MID, unit_p->saved_range_mid); IDR_PUTL(IDR_RANGE_HIGH, unit_p->saved_range_high); unit_p->unit_suspended = 0; /* wake up idr_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,"idr_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,"idr_attach: instance %d: ddi_soft_state_zalloc failure!\n",instance) ; return (DDI_FAILURE); } unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance); /* save some stuff that must be valid before any other routines get called! */ 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,"idr_attach: instance %d: ddi_get_iblock_cookie error!\n", instance); return(DDI_FAILURE); } /* initialize MUTEX */ mutex_init(&unit_p->mutex, "idr 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, idr_intr, (caddr_t) unit_p) != DDI_SUCCESS) { cmn_err(CE_CONT,"idr_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 operations */ cv_init(&unit_p->cv,"idr cv", CV_DRIVER, (void *)unit_p->iblock_cookie); /* initialize another cv for use with power management */ cv_init(&unit_p->power_cv,"idr 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,"idr_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,"idr_attach: instance %d: nregs = 0x%x\n", instance, result)); /* map in the plx and idr 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,"idr_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, IDR_REG_RNUMBER, (caddr_t *)&unit_p->idr_base, 0, 0, &slave_attr, &unit_p->idr_acc_handle) != DDI_SUCCESS) { cmn_err(CE_CONT,"idr_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 idr.conf zero is not permitted! */ unit_p->sg_list_length = getprop(dip,"sg_list_length", SG_LIST_LENGTH_DEF); DPRINT((CE_CONT,"idr_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,"idr_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->idr_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 teh 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 = 0x2; /* dr11 requires short word user buf alignment */ unit_p->dma_attr.dma_attr_burstsizes = 0x0; /* no control over this (sbus only???) */ unit_p->dma_attr.dma_attr_minxfer = 0x2; /* min xfer size is one 16 bit word */ 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 = 0x2; /* ??? */ 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,"idr_attach: instance %d: ddi_dma_alloc_handle failure!\n",instance) ; ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->idr_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,"idr_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->idr_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,"idr_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->idr_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,"idr_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->idr_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,"idr_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->idr_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,"idr_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->idr_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, "idr_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 idrx node name */ (void) sprintf(minor_node_name,"idr%d",instance) ; if (ddi_create_minor_node(dip, minor_node_name, S_IFCHR, instance, DDI_PSEUDO, NULL) == DDI_FAILURE) { cmn_err(CE_CONT,"idr_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->idr_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,"idr_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->idr_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,"idr_attach: instance %d: ints registered, node created, regs mapped, plx_base = 0x%x, idr_base = 0x%x\n", instance, unit_p->plx_base, unit_p->idr_base)); 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,"idr_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,"idr_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)); pci_config_teardown(&unit_p->config_acc_handle); /* read default properties from idr.conf check for out of range properties and convert to appropraite bit formats for board registers the saved defaults are in a form used directly by the board and driver - the default properties in idr.conf may be flags that don't map directly into board registers if property isn't defined, use the PROPERTY default from idr_io.h */ /* get and save timer default values */ temp = getprop(dip, "dma_time_def", DMA_TIME_DEF); unit_p->dma_time_def = temp; DPRINT((CE_CONT, "idr_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, "idr_attach: instance %d: rdy_time_def = %d\n", instance, temp)); temp = getprop(dip, "attn_time_def", ATTN_TIME_DEF); unit_p->attn_time_def = temp; DPRINT((CE_CONT, "idr_attach: instance %d: attn_time_def = %d\n", instance, temp)); /* get the default byte swap property in a sparc system, we must tell the plx chip to swap dma bytes, to reverse the byte swapping that the host always does for 16 bit values if the byte swap property says to swap bytes, we turn byte swapping OFF in the plx part in an x86 system, the default is to NOT swap in hardware, so if the byte swap proprety says to swap, we swap */ temp = getprop(dip, "byte_swap_def", BYTE_SWAP_DEF); if(temp & 0xFFFFFFFE) { temp = BYTE_SWAP_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: byte_swap_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: byte_swap_def = %d\n", instance, temp)); if(temp != 0) unit_p->endian_def = DMA_1_BIG_ENDIAN; else unit_p->endian_def = 0; /* start assembling the mode register default */ unit_p->mode_reg_def = 0; temp = getprop(dip, "speed_def", SPEED_DEF); if((temp != 0) && (temp != 1) && (temp != 2) && (temp != 3)) { temp = SPEED_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: speed_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: speed_def = %d\n", instance, temp)); switch(temp) { case 0: unit_p->mode_reg_def = SPEED_0; break; case 1: unit_p->mode_reg_def = SPEED_1; break; case 2: unit_p->mode_reg_def = SPEED_2; break; case 3: unit_p->mode_reg_def = SPEED_3; break; } temp = getprop(dip, "cycle_pol_def", CYCLE_POL_DEF); if(temp & 0xFFFFFFFE) { temp = CYCLE_POL_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: cycle_pol_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: cycle_pol_def = %d\n", instance, temp)); if(temp == 1) unit_p->mode_reg_def |= CYCLE_POLARITY; temp = getprop(dip, "busy_pol_def", BUSY_POL_DEF); if(temp & 0xFFFFFFFE) { temp = CYCLE_POL_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: busy_pol_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: busy_pol_def = %d\n", instance, temp)); if(temp == 1) unit_p->mode_reg_def |= BUSY_POLARITY; /* assemble read pulse default - ALWAYS INCLUDE GO! */ unit_p->read_pulse_def = GO; temp = getprop(dip, "read_cycle_def", READ_CYCLE_DEF); if(temp & 0xFFFFFFFE) { temp = READ_CYCLE_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: read_cycle_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: read_cycle_def = %d\n", instance, temp)); if(temp == 1) unit_p->read_pulse_def |= SOFT_CYCLE; temp = getprop( dip, "read_acf2_def", READ_ACF2_DEF); if(temp & 0xFFFFFFFE) { temp = READ_ACF2_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: read_acf2_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: read_acf2_def = %d\n", instance, temp)); if(temp == 1) unit_p->read_pulse_def |= ACLO_FCN_2; /* assemble write pulse default - ALWAYS INCLUDE GO! */ unit_p->write_pulse_def = GO; temp = getprop(dip, "write_cycle_def", WRITE_CYCLE_DEF); if(temp & 0xFFFFFFFE) { temp = WRITE_CYCLE_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: write_cycle_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: write_cycle_def = %d\n", instance, temp)); if(temp == 1) unit_p->write_pulse_def |= SOFT_CYCLE; /* start compiling the open, read, and write function bit defaults there is a separate property for each function bit for each of the three conditions -- it would be simlpler to combine all 3 bits into one mask, but it wouldn't read as well, and wouldn't allow using the idr_reg.h bit definitions */ /* start with read function bits */ unit_p->read_fcn_def = 0; temp = getprop(dip, "read_f3_def", READ_F3_DEF); if(temp & 0xFFFFFFFE) { temp = READ_F3_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: read_f3_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: read_f3_def = %d\n", instance, temp)); if(temp == 1) unit_p->read_fcn_def |= FUNCTION_3; temp = getprop(dip, "read_f2_def", READ_F2_DEF); if(temp & 0xFFFFFFFE) { temp = READ_F2_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: read_f2_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: read_f2_def = %d\n", instance, temp)); if(temp == 1) unit_p->read_fcn_def |= FUNCTION_2; temp = getprop(dip, "read_f1_def", READ_F1_DEF); if(temp & 0xFFFFFFFE) { temp = READ_F1_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: read_f1_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: read_f1_def = %d\n", instance, temp)); if(temp == 1) unit_p->read_fcn_def |= FUNCTION_1; /* now write function bits */ unit_p->write_fcn_def = 0; temp = getprop(dip, "write_f3_def", WRITE_F3_DEF); if(temp & 0xFFFFFFFE) { temp = WRITE_F3_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: write_f3_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: write_f3_def = %d\n", instance, temp)); if(temp == 1) unit_p->write_fcn_def |= FUNCTION_3; temp = getprop(dip, "write_f2_def", WRITE_F2_DEF); if(temp & 0xFFFFFFFE) { temp = WRITE_F2_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: write_f2_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: write_f2_def = %d\n", instance, temp)); if(temp == 1) unit_p->write_fcn_def |= FUNCTION_2; temp = getprop(dip, "write_f1_def", WRITE_F1_DEF); if(temp & 0xFFFFFFFE) { temp = WRITE_F1_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: write_f1_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: write_f1_def = %d\n", instance, temp)); if(temp == 1) unit_p->write_fcn_def |= FUNCTION_1; /* now the open function bits, which will be pluggen into the latched function register at open time */ unit_p->latch_reg_def = 0; temp = getprop(dip, "open_f3_def", OPEN_F3_DEF); if(temp & 0xFFFFFFFE) { temp = OPEN_F3_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: open_f3_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: open_f3_def = %d\n", instance, temp)); if(temp == 1) unit_p->latch_reg_def |= FUNCTION_3; temp = getprop(dip, "open_f2_def", OPEN_F2_DEF); if(temp & 0xFFFFFFFE) { temp = OPEN_F2_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: open_f2_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: open_f2_def = %d\n", instance, temp)); if(temp == 1) unit_p->latch_reg_def |= FUNCTION_2; temp = getprop(dip, "open_f1_def", OPEN_F1_DEF); if(temp & 0xFFFFFFFE) { temp = OPEN_F1_DEF; cmn_err(CE_WARN, "idr_attach: instance %d: open_f1_def property out of range!\n", instance); } DPRINT((CE_CONT, "idr_attach: instance %d: open_f1_def = %d\n", instance, temp)); if(temp == 1) unit_p->latch_reg_def |= FUNCTION_1; /* do a soft reset of the plx chip - which also clears the idr logic this will also cause an init to be sent to the device */ plx_soft_reset(unit_p); /* clear unit flags */ unit_p->unit_flags = 0 ; /* set attach complete flag for use in open */ unit_p->unit_attached = 1; ddi_report_dev(dip); return (DDI_SUCCESS); break; default: /* unrecognized commands may not be error - so only print if debugging is on */ DPRINT((CE_CONT,"idr_attach: instance %d: unrecognized command\n", instance)); return(DDI_FAILURE); } } /* This is a pretty generic getinfo routine as described in the manual. */ static int idr_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { register int error; register int instance; register struct idr_unit *unit_p; instance = getminor((dev_t)arg); DPRINT((CE_CONT,"idr_getinfo: instance %d: entering idr_getinfo\n", instance)); switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance); if (unit_p == NULL) { *result = NULL; cmn_err(CE_CONT,"idr_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,"idr_getinfo: instance %d: unrecognized info_command!\n", instance) ; } DPRINT((CE_CONT,"idr_getinfo: instance %d: leaving idr_getinfo\n", instance)); return (error); } /* _init, _info, and _fini support loading and unloading the driver. */ int _init(void) { register int error; DPRINT((CE_CONT,"idr (_init): compiled %s, %s\n", __TIME__, __DATE__)); if ((error = ddi_soft_state_init(&state_head, sizeof (struct idr_unit), 1)) != 0) { DPRINT((CE_CONT,"idr (_init): _init error!, error # = 0x%x\n",error)); return(error); } if ((error = mod_install(&modlinkage)) != 0) { DPRINT((CE_CONT,"idr (_init): mod_install error! error # = 0x%x\n",error)); ddi_soft_state_fini(&state_head); } DPRINT((CE_CONT,"idr (_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,"idr (_fini): mod_remove error! error # = 0x%x\n",error)); return (error); } ddi_soft_state_fini(&state_head); DPRINT((CE_CONT,"idr (_fini): _fini done\n")); return (0); } /* When our driver is unloaded, idr_detach cleans up and frees the resources we allocated in idr_attach. */ static int idr_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { register struct idr_unit *unit_p; int instance; instance = ddi_get_instance(dip); unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance); /* test for expected commands */ switch(cmd) { case DDI_SUSPEND: DPRINT((CE_CONT, "idr_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, return success */ if(unit_p->unit_open) { DPRINT((CE_CONT, "idr_attach: instance %d: unit open, returning DDI_FAILURE\n", instance)); mutex_exit(&unit_p->mutex); return(DDI_FAILURE); } else { DPRINT((CE_CONT, "idr_detach: instance %d: saving state & setting suspended flag\n", instance)); unit_p->saved_plx_endian_reg = PLX_GETL(PLX_ENDIAN_REG); 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_latched_functions = IDR_GETL(IDR_LATCHED_FUNCTIONS); unit_p->saved_mode = IDR_GETL(IDR_MODE); unit_p->saved_range_low = IDR_GETL(IDR_RANGE_LOW); unit_p->saved_range_mid = IDR_GETL(IDR_RANGE_MID); unit_p->saved_range_high = IDR_GETL(IDR_RANGE_HIGH); unit_p->unit_suspended = 1; mutex_exit(&unit_p->mutex); return(DDI_SUCCESS); } break; case DDI_DETACH: DPRINT((CE_CONT,"idr_detach: instance %d: command is DDI_DETACH\n", instance)); /* clear attached flag */ unit_p->unit_attached = 0; /* do a soft reset of the plx chip - which also clears the idr logic */ plx_soft_reset(unit_p); /* 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->idr_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,"idr_detach: instance %d: detach complete\n", instance)); return (DDI_SUCCESS); break; default: /* other commands may not be an error - so remain silent unless debugging is on */ DPRINT((CE_CONT, "idr_detach: instance %d: unrecognized command!\n", instance)); return(DDI_FAILURE); } } /* idr_open is called in response to the open() system call */ static int idr_open(dev_t *dev, int openflags, int otyp, cred_t *credp) { int retval = 0; int instance; register struct idr_unit *unit_p; instance = getminor(*dev); /* get unit structure pointer and make sure driver is (completely) attached */ if((unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance)) == NULL) { cmn_err(CE_CONT,"idr_open: instance %d: null unit pointer!\n", instance); return(ENXIO); } if(unit_p->unit_attached == 0) { cmn_err(CE_CONT,"idr open: instance %d: open attempted before attach complete!\n", instance); return(ENXIO); } /* verify that we are being opened as a character device */ if(otyp != OTYP_CHR) { cmn_err(CE_CONT,"idr_open: instance %d: wrong open_type!\n",instance); return(EINVAL); } /* lock unit structure (& flags & registers) */ mutex_enter(&unit_p->mutex); /* 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); /* exclusive open device */ if(unit_p->unit_open != 0) { DPRINT((CE_CONT, "idr_open: instance %d: open called when already open!\n", instance)); retval = EBUSY; } else { /* mark device as open and flag as busy (open) for power management (used in detach) */ unit_p->unit_open = 1; /* initialize DVMA and DR11 logic SOFT RESET WILL ALSO CAUSE AN INIT TO BE SENT TO THE DEVICE! if this causes problems, the plx_soft_reset() should be commented out */ plx_soft_reset(unit_p); /* initialize various defaults using values in soft state soft state values were derived from idr.conf properties in idr_attach() */ /* initialize time out values to defaults */ unit_p->dma_time = (u_long)(drv_usectohz(1000000) * unit_p->dma_time_def); unit_p->attn_time = (u_long)(drv_usectohz(1000000) * unit_p->attn_time_def); unit_p->rdy_time = (u_long)(drv_usectohz(1000000) * unit_p->rdy_time_def); /* set read and write pulse and function values to defaults */ unit_p->read_fcn = unit_p->read_fcn_def; unit_p->write_fcn = unit_p->write_fcn_def; unit_p->read_pulse = unit_p->read_pulse_def; unit_p->write_pulse = unit_p->write_pulse_def; /* set the mode and latch registers to their default values */ IDR_PUTL(IDR_LATCHED_FUNCTIONS,unit_p->latch_reg_def); IDR_PUTL(IDR_MODE,unit_p->mode_reg_def); /* set plx chip (9060sd and later) to default endian mode note that normal default is big_endian to un-swap the byte swapping that the memory path does to 16 bit words in SPARC and little_endian in x86 systems */ PLX_PUTL(PLX_ENDIAN_REG, unit_p->endian_def); /* clear the various flags in unit_flags */ unit_p->unit_flags = 0; DPRINT((CE_CONT,"idr_open: instance %d: open complete\n", instance)); } mutex_exit(&unit_p->mutex); return (retval); } /* idr_close is called after the last process that has the device open calls close() since we are exclusive open, idr_close will always be calles when the user process calls close (or when the user process exits) */ static int idr_close(dev_t dev, int openflags, int otyp, cred_t *credp) { register struct idr_unit *unit_p; /* get unit pointer and make sure it is not null (big trouble at this point!) */ if((unit_p = (struct idr_unit *)ddi_get_soft_state(state_head,getminor(dev))) == NULL) { cmn_err(CE_CONT,"idr_close: null unit pointer!\n"); return(ENXIO); } /* lock unit structure (and flags and registers) */ mutex_enter(&unit_p->mutex); /* reset dma logic, DR11 logic, and flush fifo THIS CAUSES AN INIT TO BE SENT TO THE ATTACHED DEVICE! if the init causes problems the soft reset should be commented out */ plx_soft_reset(unit_p); /* let the next user open the device this also tells power management that we will permit a system suspend */ unit_p->unit_open = 0; mutex_exit(&unit_p->mutex); DPRINT((CE_CONT,"idr_close: instance %d: close complete\n", unit_p->saved_instance)); return (0); } static int idr_read(dev_t dev, struct uio *uiop, cred_t *credp) { register struct idr_unit *unit_p ; int retval ; int instance ; retval = 0 ; instance = getminor(dev); if((unit_p = (struct idr_unit *)ddi_get_soft_state(state_head,instance)) == NULL) { cmn_err(CE_CONT,"idr_read: instance: %d: unit_pointer null!\n",instance) ; return(ENXIO); } DPRINT((CE_CONT,"idr_read: instance %d: entering read routine\n", instance)); /* since we are exclusive open - we shouldn't need a mutex -- we will leave it for strategy to pick up */ /* clear the timeout, multicycle, parity, sig received, and "waiting for" flags. strategy will set the waiting for dma or eor flag. CLEAR_FLAGS = ~(a bunch of flags or'd together) */ unit_p->unit_flags &= IDR_CLEAR_FLAGS; /* set input flag */ unit_p->unit_flags |= IDR_INPUT; /* we will use the system minphys number as a maximum buffer size, and use a buf provided by the system. if the buffer size requested by the read/write call is larger than minphys, physio will make multiple calls to idr_strategy. This may not be appropriate for some applications. if the system minphys is too small, the customer may replace minphys with idr_minphys, which is described below. using a large IDR_MAXPHYS may cause problems with the system's mapping capability - if so it may be necessary to modify idr_strategy to use a sliding window into the buffer. See the Solaris 2.x device driver writing manual for details. if the required read/write buffer is larger than available mapping space, it may be better to use the idr driver's MANUAL mode of operation. sample idr_minphys: #define IDR_MAXPHYS some buffer size static void idr_minphys(struct buf *bp) { bp->b_bcount = (min(bp->b_bcount,IDR_MAXPHYS)); } */ retval = physio(idr_strategy, (struct buf *)NULL, dev, B_READ, minphys, uiop); /* check flags for timeout or multicycle error or sig received most dr11 devices do not check parity on the dr11 cable most ikon dr11s check dr11 cable parity and flag an error we will not report parity errors as read or write errors here, since most applications (that don't support parity) will cause the flag to set often parity errors can be tested for by examining the driver flag word, or the board's flag register using the IDRIO_GET_NEW_FLAGS ioctl if parity checking is desired within the read or write routine, add the following code and re make the driver if(unit_p->unit_flags & IDR_PAR_ERR) { retval = EIO; cmn_err(CE_CONT,"idr_read: istance %d: parity error!\n", instance); } */ if(unit_p->unit_flags & (IDR_DVMA_TIMEOUT|IDR_EOR_TIMEOUT)) { retval = EIO; cmn_err(CE_CONT,"idr_read: instance %d: DMA timeout! \n",instance); } if(unit_p->unit_flags & IDR_MCYL_ERR) { retval = EIO; cmn_err(CE_CONT,"idr_read: instance %d: Multicycle error! \n",instance); } if(unit_p->unit_flags & IDR_SIG_RECEIVED) { retval = EINTR; cmn_err(CE_CONT,"idr_read: instance %d: signal received! \n",instance); } DPRINT((CE_CONT,"idr_read: instance %d: leaving read routine\n", instance)); return (retval); } static int idr_write(dev_t dev, struct uio *uiop, cred_t *credp) { register struct idr_unit *unit_p; int retval; int instance; retval = 0; instance = getminor(dev); if((unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance)) == NULL) { cmn_err(CE_CONT,"idr_write: instance %d: unit pointer NULL!\n", instance); return(ENXIO); } DPRINT((CE_CONT,"idr_write: instance %d: entering write routine\n", instance)); /* we will leave the mutex for strategy */ /* clear the timeout, multicycle error, parity, sig received, and "waiting for" flags strategy will set the waiting for dvma or eor flag. */ unit_p->unit_flags &= IDR_CLEAR_FLAGS; /* clear the input flag (=output) */ unit_p->unit_flags &= ~IDR_INPUT; /* we will use the system minphys number and a system buf - see idr_read */ retval = physio(idr_strategy, (struct buf *)NULL, dev, B_WRITE, minphys, uiop); /* check flags for timeout or multicycle error or sig received and parity error if desired - see idr_read for description of parity error flag */ if(unit_p->unit_flags & (IDR_DVMA_TIMEOUT|IDR_EOR_TIMEOUT)) { retval = EIO; cmn_err(CE_CONT,"idr_write: instance %d: DMA timeout! \n",instance); } if(unit_p->unit_flags & IDR_MCYL_ERR) { retval = EIO; cmn_err(CE_CONT,"idr_write: instance %d: Multicycle error! \n", instance); } if(unit_p->unit_flags & IDR_SIG_RECEIVED) { retval = EINTR; cmn_err(CE_CONT,"idr_write: instance %d: signal received! \n", instance); } DPRINT((CE_CONT,"idr_write: instance %d: leaving write routine\n", instance)); return (retval); } /* note that the ioctl routine treats arg as a pointer */ static int idr_ioctl(dev_t dev, int cmd, int arg, int flag, cred_t *credp, int *rvalp) { register struct idr_unit *unit_p; u_long bits; /* will contain incoming or outgoing arg value */ u_long lt; /* temp long */ int retval = 0; /* return value (errno) for system call */ u_int command; /* extracted ioctl command */ int instance; /* our instance - from softstate */ int waitval; /* value returned by cv_timedwait_sig */ u_long time_now; /* time in ticks since last re-boot */ int i; /* generic counter */ u_int return_array[19]; /* will contain copy of registers to return to caller */ instance = getminor(dev); if((unit_p = (struct idr_unit *)ddi_get_soft_state(state_head, instance)) == NULL) { cmn_err(CE_CONT,"ir_ioctl: instance %d: unit_pointer NULL!\n",instance); return(ENXIO); } DPRINT((CE_CONT,"idr_ioctl: instance %d: entering ioctl routine, command = 0x%x\n", instance, cmd)); /* lock our unit structure */ mutex_enter(&unit_p->mutex); /* get command */ command = cmd & IDRIO_CMD_MASK; /* get first word of arg (even if we are eventually doing a copy out) this saves coding a copyin for each case we can use standard copyin since we don't do recursive ioctl calls */ copyin((caddr_t)arg, (caddr_t)&bits, sizeof(bits)) ; /* use MASK to strip the count from cases */ #define MASK IDRIO_CMD_MASK switch(command) { case MASK & IDRIO_SET_MODE: /* set operating mode using old sbus bit */ /* definitions to preserve compatibility with */ /* existing applications */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at set mode\n", instance)); lt = bits & IDR_SPEED_MASK; /* speed bits are in the same place */ if(bits & IDR_RDYT) lt |= LONG_READY; if(bits & IDR_BDIS) lt |= DISABLE_CRQ_B; if(bits & IDR_CRQP) lt |= CYCLE_POLARITY; if(bits & IDR_BSYP) lt |= BUSY_POLARITY; if(bits & IDR_RDISX) lt |= DISABLE_RANGE; IDR_PUTL(IDR_MODE,lt); /* check dma byte swap mode - request for swapping will turn off big endian mode (SPARC) SPARC swaps bytes between dma and memory, so big_endian = swap again, to give un-swapped data x86 doesn't swap - so SWAP really means swap in the plx hardware */ if(bits & IDR_SWAP) PLX_PUTL(PLX_ENDIAN_REG, PLX_GETL(PLX_ENDIAN_REG) | DMA_1_BIG_ENDIAN); else PLX_PUTL(PLX_ENDIAN_REG, PLX_GETL(PLX_ENDIAN_REG) & ~DMA_1_BIG_ENDIAN); break; case MASK & IDRIO_IMM_FCN: /* set function bits immediately */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at immediate funtion\n", instance)); /* just mask the function bits and stuff them directly into the latched function register - the other register bits should all be either zero or don't care at this point! the pci and sbus boards have the funtions bits in the same places in their respective registers - so no bit fiddling necessary */ IDR_PUTL(IDR_LATCHED_FUNCTIONS, bits & FUNCTION_MASK); break; case MASK & IDRIO_READ_FCN: /* save fcn bits for use at read start */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at read function\n", instance)); unit_p->read_fcn = (bits & FUNCTION_MASK); break; case MASK & IDRIO_WRITE_FCN: /* save fcn bits for use at write start */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at write function\n", instance)); unit_p->write_fcn = (bits & FUNCTION_MASK); break; case MASK & IDRIO_IMM_PULSE: /* issue pulses asap */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at immediate pulse\n", instance)); /* wait for attention off if issuing go pulse wait 100 us max - ?????? */ if(bits & IDR_GO) { i = 0; while((IDR_GETL(IDR_STATUS) & ATTENTION)&&(i<100)) { drv_usecwait(1) ; i++ ; } if(IDR_GETL(IDR_STATUS) & ATTENTION) { cmn_err(CE_CONT,"idr_ioctl: instance %d at IMM_PULSE: attempt to issue GO while ATTENTION true!\n", instance) ; retval = EIO ; goto IOCTLEXIT ; } } /* fiddle bits to maintain sbus driver compatibility */ lt = 0; if(bits & IDR_TERM) lt |= SET_READY; if(bits & IDR_INIT) lt |= DEVICE_INIT; if(bits & IDR_ACF2) lt |= ACLO_FCN_2; if(bits & IDR_CYCL) lt |= SOFT_CYCLE; if(bits & IDR_GO) lt |= GO; IDR_PUTL(IDR_DEVICE_PULSES, lt); /* we will allow a master clear pulse here - since the sbus driver did but it should not be used lightly it clears the idr logic only, not the plx logic we will also permit a reset attention flag pulse - for compatibility */ lt = 0; if(bits & IDR_RATN) lt |= RESET_ATTENTION_FLAG; if(bits & IDR_MCLR) lt |= MASTER_CLEAR; IDR_PUTL(IDR_116_PULSES, lt); break ; case MASK & IDRIO_READ_PULSE: /* save pulses for read start (in pci format) */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at read pulse\n", instance)); /* fiddle bits to maintain sbus driver compatibility */ lt = 0; if(bits & IDR_TERM) lt |= SET_READY; if(bits & IDR_INIT) lt |= DEVICE_INIT; if(bits & IDR_ACF2) lt |= ACLO_FCN_2; if(bits & IDR_CYCL) lt |= SOFT_CYCLE; if(bits & IDR_GO) lt |= GO; unit_p->read_pulse = lt ; break ; case MASK & IDRIO_WRITE_PULSE: /* save pulses for write start */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at write pulse\n", instance)); /* fiddle bits to maintain sbus driver compatibility */ lt = 0; if(bits & IDR_TERM) lt |= SET_READY; if(bits & IDR_INIT) lt |= DEVICE_INIT; if(bits & IDR_ACF2) lt |= ACLO_FCN_2; if(bits & IDR_CYCL) lt |= SOFT_CYCLE; if(bits & IDR_GO) lt |= GO; unit_p->write_pulse = lt ; break ; case MASK & IDRIO_SET_DMA_TIME: /* set dma timeout parameter - in ticks */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at set dma time\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)); break ; case MASK & IDRIO_SET_ATTN_TIME: /* set wait for attention timeout value */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at set attn time\n", instance)); if(bits < ATTN_TIME_MIN) bits = ATTN_TIME_MIN ; if(bits > ATTN_TIME_MAX) bits = ATTN_TIME_MAX ; unit_p->attn_time = (u_long)(bits * drv_usectohz(1000000)); break ; case MASK & IDRIO_SET_RDY_TIME: /* set wait for ready timeout value */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at set rdy time\n", instance)); if(bits < RDY_TIME_MIN) bits = RDY_TIME_MIN ; if(bits > RDY_TIME_MAX) bits = RDY_TIME_MAX ; unit_p->rdy_time = (u_long)(bits * drv_usectohz(1000000)); break ; case MASK & IDRIO_ATTN_WAIT: /* wait for attention to set attf */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at attn wait\n", instance)); /* clear the "waiting-for" flags in the soft state these flags are maintained for compatibility with the sbus driver they are probably not necessary */ unit_p->unit_flags &= IDR_CLEAR_FLAGS; /* if the attention flag is already set, clear it and return */ if(IDR_GETL(IDR_FLAGS) & ATTENTION_FLAG) { DPRINT((CE_CONT,"idr_ioctl: instance %d: at attn wait, attf already set, returning w/out waiting\n", instance)); IDR_PUTL(IDR_116_PULSES, RESET_ATTENTION_FLAG); goto IOCTLEXIT; } /* set "waiting-for" flag */ unit_p->unit_flags |= IDR_ATTN_WAIT; /* get current time in ticks */ if(drv_getparm(LBOLT, &time_now)) { cmn_err(CE_CONT,"idr_ioctl: instance %d: at ATTN_WAIT, drv_getparm error!\n", instance); retval = EIO; goto IOCTLEXIT; } /* enable attention flag interrupts in the idr logic, and local ints in the plx logic we have to or in the idr bits, since the speed bits are also in that register */ IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) | ATTENTION_INT_ENB); PLX_PUTL(PLX_INT_CSTAT, PCI_INTERRUPT_ENABLE | PCI_LOCAL_INT_ENABLE); /* snooze until attention interrupt, timeout, or signal */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at attn wait, time_now = %d, unit_p->attn_time = %d\n", instance, time_now, unit_p->attn_time)); waitval = cv_timedwait_sig(&unit_p->cv, &unit_p->mutex, (long)(time_now + unit_p->attn_time)); /* make sure interrupt enables are off - we may have gotten here via signal or timeout */ IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~ATTENTION_INT_ENB); PLX_PUTL(PLX_INT_CSTAT, 0); /* find out why we woke up */ if(waitval < 0) { /* timeout may not be an error - so no error msg let caller test flags for timeout */ DPRINT((CE_CONT,"idr_ioctl: instance %d: timeout waiting for attention!\n", instance)); unit_p->unit_flags |= IDR_ATTN_TIMEOUT; retval = EIO; goto IOCTLEXIT; } if(waitval == 0) { /* signal is not exactly an error - but send to console log return EINTR to caller can decide what to do */ cmn_err(CE_CONT,"idr_ioctl: instance %d: signal while waiting for attention!\n", instance); unit_p->unit_flags |= IDR_SIG_RECEIVED; retval = EINTR; goto IOCTLEXIT; } /* clear attention flag */ IDR_PUTL(IDR_116_PULSES, RESET_ATTENTION_FLAG); break; case MASK & IDRIO_RDY_WAIT: /* wait for REDY to go true from attn or eor */ /* if already set - just return */ /* look at flags to figure out why redy was */ /* set. if we were waiting for eor and got */ /* an attention also - the attn flag is left */ /* on since it might represent an incoming */ /* attention that followed the block xfer */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at ready wait\n", instance)); unit_p->unit_flags &= IDR_CLEAR_FLAGS; if(IDR_GETL(IDR_STATUS) & READY) { DPRINT((CE_CONT,"idr_ioctl: instance %d: at ready wait - ready already set\n", instance)); goto RDYWAITEXIT; } /* set ready wait flag for future reference and sbus compatibility */ unit_p->unit_flags |= IDR_RDY_WAIT; /* get current time in ticks */ if(drv_getparm(LBOLT, &time_now)) { cmn_err(CE_CONT,"idr_ioctl: instance %d: at RDY_WAIT, drv_getparm error!\n", instance); retval = EIO; goto IOCTLEXIT; } /* enable attention and eor flag interrupts in the idr logic, and local ints in the plx logic we have to or in the idr bits, since the speed bits are also in that register */ IDR_PUTL(IDR_LATCHED_FUNCTIONS, (IDR_GETL(IDR_LATCHED_FUNCTIONS) | ATTENTION_INT_ENB | END_OF_RANGE_INT_ENB)); PLX_PUTL(PLX_INT_CSTAT, (PCI_INTERRUPT_ENABLE | PCI_LOCAL_INT_ENABLE)); /* snooze until attention interrupt, timeout, or signal */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at ready wait, time_now = %d, unit_p->rdy_time = %d\n", instance, time_now, unit_p->rdy_time)); waitval = cv_timedwait_sig(&unit_p->cv, &unit_p->mutex, (long)(time_now + unit_p->rdy_time)); /* make sure interrupt enables are off - we may have gotten here via signal or timeout */ IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~(ATTENTION_INT_ENB | END_OF_RANGE_INT_ENB)); PLX_PUTL(PLX_INT_CSTAT, 0); /* find out why we woke up */ if(waitval < 0) { /* timeout may not be an error - so no error msg let caller test flags for timeout */ DPRINT((CE_CONT,"idr_ioctl: instance %d: timeout waiting for ready!\n", instance)); unit_p->unit_flags |= IDR_RDY_TIMEOUT; retval = EIO; goto IOCTLEXIT; } if(waitval == 0) { /* signal is not exactly an error - but send to console log return EINTR to caller can decide what to do */ cmn_err(CE_CONT,"idr_ioctl: instance %d: signal while waiting for ready!\n", instance); unit_p->unit_flags |= IDR_SIG_RECEIVED; retval = EINTR; goto IOCTLEXIT; } RDYWAITEXIT: /* examine dr11 flag bits to figure out why ready was set if attf is set and eor is not - clear attf */ if((IDR_GETL(IDR_FLAGS) & ATTENTION_FLAG) && !(IDR_GETL(IDR_FLAGS) & END_OF_RANGE_FLAG)) { DPRINT((CE_CONT,"idr_ioctl: instance %d: at ready wait - attf w/out eorf\n", instance)); IDR_PUTL(IDR_116_PULSES, RESET_ATTENTION_FLAG); } /* always clear eorf */ IDR_PUTL(IDR_116_PULSES, RESET_EOR_FLAG); break; case MASK & IDRIO_GET_STATUS: /* return dr11 status register - in sbus format */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at get status\n", instance)); /* get flags and status registers and fiddle bits to look like sbus status register */ bits = 0; lt = IDR_GETL(IDR_FLAGS); if(lt & END_OF_RANGE_FLAG) bits |= IDR_EORF; if(lt & ATTENTION_FLAG) bits |= IDR_ATTF; if(lt & MULTI_CYCLE_ERROR_FLAG) bits |= IDR_MCER; lt = IDR_GETL(IDR_STATUS); if(lt & READY) bits |= IDR_REDY; if(lt & ATTENTION) bits |= IDR_ATTN; /* dr11 status bits are in the same place in sbus and pci */ bits |= (lt & STATUS_MASK); copyout((caddr_t)&bits,(caddr_t)arg,sizeof(bits)); break; case MASK & IDRIO_GET_RANGE: /* gets 24 bit range counter value */ /* in raw form: = word count minus 1 */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at get range\n", instance)); bits = ((IDR_GETL(IDR_RANGE_HIGH) & 0xFF) << 16) | ((IDR_GETL(IDR_RANGE_MID) & 0xFF) << 8) | (IDR_GETL(IDR_RANGE_LOW) & 0xFF); copyout((caddr_t)&bits, (caddr_t)arg, sizeof(bits)); break; case MASK & IDRIO_GET_REGS: /* get most of the board's registers */ DPRINT((CE_CONT,"idr_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_1); /* dma mode reg 1 */ return_array[5] = PLX_GETL(PLX_DMA_PCI_ADD_1); /* dma pci add 1 */ return_array[6] = PLX_GETL(PLX_DMA_LOC_ADD_1); /* dma local add 1 */ return_array[7] = PLX_GETL(PLX_DMA_COUNT_1); /* dma transfer count 1 */ return_array[8] = PLX_GETL(PLX_DMA_DESC_PTR_1); /* dma descriptor pointer 1 */ return_array[9] = PLX_GETL(PLX_DMA_CMD_STAT_BOTH); /* dma command/status - both channels */ return_array[11]= IDR_GETL(IDR_FLAGS) & 0xFF; /* idr flags register */ return_array[12]= IDR_GETL(IDR_STATUS) & 0xFF; /* idr status register */ return_array[13]= IDR_GETL(IDR_MODE) & 0xFF; /* idr mode register */ return_array[14]= IDR_GETL(IDR_RANGE_LOW) & 0xFF; /* dr11 range register low byte */ return_array[15]= IDR_GETL(IDR_RANGE_MID) & 0xFF; /* dr11 range register middle byte */ return_array[16]= IDR_GETL(IDR_RANGE_HIGH) & 0xFF; /* dr11 range register high byte */ return_array[17]= IDR_GETL(IDR_FIFO_STATUS) & 0xFF; /* idr fifo status bits */ /* reading the data in lines requires clearing the input fifo, forcing a dr11 input word into the input fifo, and then reading in get the latched function register here to allow time for fifo write to complete */ IDR_PUTL(IDR_116_PULSES, CLEAR_INPUT_FIFO); IDR_PUTL(IDR_116_PULSES, WRITE_INPUT_FIFO); return_array[10]= IDR_GETL(IDR_LATCHED_FUNCTIONS) & 0xFF; /* idr latched functions register */ return_array[18]= IDR_GETL(IDR_DATA_IN) & 0xFFFF; /* dr11 data input register - 16 bits */ copyout((caddr_t)return_array, (caddr_t)arg, sizeof(return_array)); break; case MASK & IDRIO_GET_FLAGS: /* return unit flags longword to caller */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at get flags, flags = 0x%x\n", instance, unit_p->unit_flags)); bits = unit_p->unit_flags; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(bits)); break; case MASK & IDRIO_DATA_OUT: /* output 16 bit word to data out reg */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at data out, data = 0x%x\n", instance, bits)); /* first we clear the output fifo, then write to it, then force a read from it into the output data latches we do a dummy read to allow time for the fifo write to complete - probably not necessary */ IDR_PUTL(IDR_116_PULSES, CLEAR_OUTPUT_FIFO); IDR_PUTL(IDR_DATA_OUT, bits); read_dump_0 = IDR_GETL(IDR_LATCHED_FUNCTIONS); IDR_PUTL(IDR_116_PULSES, READ_OUTPUT_FIFO); break; case MASK & IDRIO_DATA_IN: /* read dr11 input data register */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at data in\n", instance)); /* clear the input fifo, force a write into it from the data in lines then read the fifo do a dummy read to allow time for the fifo write to complete */ IDR_PUTL(IDR_116_PULSES, CLEAR_INPUT_FIFO); IDR_PUTL(IDR_116_PULSES, WRITE_INPUT_FIFO); read_dump_0 = IDR_GETL(IDR_LATCHED_FUNCTIONS); bits = IDR_GETL(IDR_DATA_IN) & 0xFFFF; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(bits)); break; case MASK & IDRIO_SET_RANGE: /* set dr11 range counter to desired value */ /* count is not adjusted - calling argument */ /* will be plugged directly into range */ /* arg should be word count minus one!!! */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at set range, range = 0x%x\n", instance, bits)); /* make sure that requested count will fit in our range counter */ if(bits > IDR_DR11_MAXBLOCK) { cmn_err(CE_CONT,"idr_ioctl: instance %d at SET_RANGE - range count too big! \n", instance); retval = EINVAL; goto IOCTLEXIT; } IDR_PUTL(IDR_RANGE_HIGH, (bits >> 16)); IDR_PUTL(IDR_RANGE_MID, (bits >> 8)); IDR_PUTL(IDR_RANGE_LOW, bits); break; case MASK & IDRIO_AUTO: /* set automatic mode (default) */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at idr auto\n", instance)); unit_p->unit_flags &= ~IDR_MANUAL; break; case MASK & IDRIO_MANUAL: /* set manual mode */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at idr manual\n", instance)); unit_p->unit_flags |= IDR_MANUAL; break; case MASK & IDRIO_START_READ: /* enables block read in manual mode */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at start read\n", instance)); /* make sure that the "end" flags are clear and reset error flags also clear the input fifo */ IDR_PUTL(IDR_116_PULSES, (RESET_ATTENTION_FLAG | RESET_EOR_FLAG | RESET_ERROR_FLAGS | CLEAR_INPUT_FIFO)); /* make sure dma rqs are off, switch to input mode, and enable dma the plx dma logic will be set up and enabled by strategy */ IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~IDR_DMA_ENABLE); IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) | DMA_INPUT_MODE); IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) | IDR_DMA_ENABLE); break; case MASK & IDRIO_START_WRITE: /* enables block write in man mode */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at start write\n", instance)); /* make sure "end" flags are clean and reset error flags and output fifo */ IDR_PUTL(IDR_116_PULSES, (RESET_ATTENTION_FLAG | RESET_EOR_FLAG | RESET_ERROR_FLAGS | CLEAR_OUTPUT_FIFO)); /* make sure dma rqs are off, switch to output mode, and enable dma the plx dma logic will be set up and enabled by strategy */ IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~IDR_DMA_ENABLE); IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~DMA_INPUT_MODE); IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) | IDR_DMA_ENABLE); break; case MASK & IDRIO_BLOCK_END: /* terminate overall block transfer */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at block end\n", instance)); /* turn off dma enable bit and interrupt masks interrupt masks should already be off */ IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~(END_OF_RANGE_INT_ENB | ATTENTION_INT_ENB | IDR_DMA_ENABLE)); /* make sure ready is true */ IDR_PUTL(IDR_DEVICE_PULSES, SET_READY); /* check for multicycle error or parity error and flag in unit structure parity error will occur frequently when connected to device that don't support parity (most!) so don't do a cmn_err print */ if(IDR_GETL(IDR_FLAGS) & PARITY_ERROR_FLAG) { DPRINT((CE_CONT,"idr_ioctl: instance %d: at BLOCK_END, parity error!\n", instance)); unit_p->unit_flags |= IDR_PAR_ERR; } if(IDR_GETL(IDR_FLAGS) & MULTI_CYCLE_ERROR_FLAG) { cmn_err(CE_CONT,"idr_ioctl: instance %d: at BLOCK_END, multi-cycle error!\n", instance); unit_p->unit_flags |= IDR_MCYL_ERR; } /* just to be sure, force off plx dma and interrupt enable bits */ PLX_PUTL(PLX_INT_CSTAT, 0); PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, 0); break; case MASK & IDRIO_DEV_AND_VEND_ID: /* return vendor id in low 16 bits, device id in high 16 bits */ DPRINT((CE_CONT,"idr_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(bits)); break; case MASK & IDRIO_REVISION_ID: /* return board revision id */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at revision id\n", instance)); bits = unit_p->revision_id; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(bits)); break; case MASK & IDRIO_SET_NEW_MODE: /* direct write to pci dr11 mode register */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at set new mode\n", instance)); IDR_PUTL(IDR_MODE, bits); break; case MASK & IDRIO_GET_NEW_STATUS: /* direct read of pci dr11 status register */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at get new status\n", instance)); bits = IDR_GETL(IDR_STATUS) & 0xFF; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(bits)); break; case MASK & IDRIO_GET_NEW_FLAGS: /* direct read of pci dr11 flags register */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at get new flags\n", instance)); bits = IDR_GETL(IDR_FLAGS) & 0xFF; copyout((caddr_t)&bits, (caddr_t)arg, sizeof(bits)); break; case MASK & IDRIO_MASTER_CLEAR: /* do a total reset of the board - don't try this at home! */ DPRINT((CE_CONT,"idr_ioctl: instance %d: at master clear\n", instance)); /* call soft reset to completely reset the board - including the dr11 mode register this will also cause an init pulse to be sent to the attached device */ plx_soft_reset(unit_p); break; default: /* an unrecognized ioctl command may not be an error - we may get probed by pipe logic w/ a generic TCGETA ioctl. return an error so we don`t look like a terminal, but don`t print an error. */ retval = EINVAL; break; } IOCTLEXIT: /* release mutex */ mutex_exit(&unit_p->mutex) ; return (retval); } /* 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 idr_intr(caddr_t arg) { struct idr_unit *unit_p; /* will point at our soft state */ register struct buf *bp; /* may point to buffer */ volatile u_long temp; /* just what it says */ u_int int_serviced = DDI_INTR_UNCLAIMED; /* tells kernel if our interrupt*/ u_int flags; int instance; /* get unit pointer (do it this way to keep cc and lint happy) */ unit_p = (struct idr_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,"idr_intr: instance %d: entering interrupt routine\n", instance)); /* get plx interrupt control/status register and check for interrupt look 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_1_INTERRUPT))) { DPRINT((CE_CONT,"idr_intr: instance %d: claiming interrupt, INT_CSTAT = 0x%x\n", instance, temp)); /* turn off interrupts now - but leave dma enabled if it is not already off in input mode, it is possible to terminate a block before the dr11 or dma range count is exhausted, by pulsing attention the idr portion of the logic will hold off the attention interrupt until the big fifo is empty, but there may still be a few long words in the plx fifo we will leave dma on (if it is still on) in the plx logic, and turn it off later in strategy */ PLX_PUTL(PLX_INT_CSTAT, 0); IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~(END_OF_RANGE_INT_ENB | ATTENTION_INT_ENB )); /* figure out why we were waiting for an interrupt unfortunately, we can't treat all interrupts the same way */ flags = unit_p->unit_flags & (IDR_DVMA_WAIT | IDR_EOR_WAIT | IDR_ATTN_WAIT | IDR_RDY_WAIT) ; switch(flags) { case IDR_DVMA_WAIT: DPRINT((CE_CONT,"idr_intr: instance %d: at case DVMA_WAIT\n", instance)); /* we are waiting for dma t/c interrupt - attention interrupts will also be enabled, so we will have to figure out what caused the interrupt and whether the transfer is complete */ case IDR_EOR_WAIT: DPRINT((CE_CONT,"idr_intr: instance %d: at case EOR_WAIT\n", instance)); /* or we are waiting for a dr11 end of range in auto output mode, eor is the desired interrupt in input mode, or manual modr (either in or out), dma tc is the desired interrupt */ /* since we know that strategy causes us to be called, we have a buffer pointer, get it from unit structure */ bp = unit_p->buf_p; /* if we are in automatic mode, turn off the idr dma enable now, otherwise, leave it on until block end ioctl */ if(!(unit_p->unit_flags & IDR_MANUAL)) IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~IDR_DMA_ENABLE); /* if we are waiting for an output interrupt, force the dma logic off, since either we are done with dma, or an attention has terminated the dr11 transfer, and any further output data is destined for the bit bucket if we are doing input, the attention or eor interrupt will be held off until the big fifos are empty, but there may still be a few long words in the plx fifos, so leave dma on for now - strategy will clean things up */ if(!(bp->b_flags & B_READ)) PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, 0); if(!((PLX_GETL(PLX_DMA_CMD_STAT_BOTH) & DMA_1_DONE) || (IDR_GETL(IDR_FLAGS) & END_OF_RANGE_FLAG))) { /* if not eor or end of range, we must have been terminated by an attention pulse - in this case, do not save the attn flag */ IDR_PUTL(IDR_116_PULSES, RESET_ATTENTION_FLAG); } if(unit_p->unit_flags & IDR_EOR_WAIT) { /* if waiting for dr11 eor, turn it off - if not, it may be useful later, so leave it on for now */ IDR_PUTL(IDR_116_PULSES, RESET_EOR_FLAG); } /* turn off "waiting for" flags for dma t/c and eor */ unit_p->unit_flags &= ~(IDR_DVMA_WAIT | IDR_EOR_WAIT); break; case IDR_ATTN_WAIT: DPRINT((CE_CONT,"idr_intr: instance %d: at case ATTN_WAIT\n", instance)); /* we are waiting for an attention interrupt i would like to make sure attf is set, but there is no way to flag an error, since no buf, and i can't call cmn_err from high level, so we will just reset the "waiting for" flag the attention flag gets reset in the ioctl code we could do it here, but we will stick with the way it was done in the Sbus driver */ unit_p->unit_flags &= ~IDR_ATTN_WAIT; break; case IDR_RDY_WAIT: DPRINT((CE_CONT,"idr_intr: instance %d: at case RDY_WAIT\n", instance)); /* waiting for ready - either attention or eor like attn wait, we could check for either flag set and report an error if neither was set (serious hardware error) but there is no good way to do so we will just turn off the "waiting for" flag to try to keep some similarity with the Sbus driver */ unit_p->unit_flags &= ~IDR_RDY_WAIT; break; } DPRINT((CE_CONT,"idr_intr: instance %d: signalling waiting routine\n", instance)); int_serviced = DDI_INTR_CLAIMED; cv_signal(&unit_p->cv); } mutex_exit(&unit_p->mutex); DPRINT((CE_CONT,"idr_intr: instance %d: leaving interrupt routine\n", instance)); return(int_serviced); } static int idr_strategy(struct buf *bp) { register struct idr_unit *unit_p; /* points to unit structure */ int instance; /* current 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 */ u_long dma_direction; u_long plx_direction; u_long plx_dma_done_int; u_long plx_local_dma_int; u_long temp_mode; u_long temp_latches; u_long time_now; int waitval; u_long range; int i; /* get instance number from buf and make pointer to soft state */ instance = getminor(bp->b_edev) ; unit_p = (struct idr_unit *)ddi_get_soft_state(state_head,instance) ; DPRINT((CE_CONT,"idr_strategy: instance %d: entering strategy\n", instance)); /* lock our unit structure */ mutex_enter(&unit_p->mutex); /* save buffer pointer for interrupt routine */ unit_p->buf_p = bp; DPRINT((CE_CONT,"idr_strategy: instance %d: buffer virtual address = 0x%x, buffer size = 0x%x\n", instance, bp->b_un.b_addr, bp->b_bcount)); /* make sure dma byte count size is non-zero */ if(bp->b_bcount == 0) { cmn_err(CE_CONT,"idr_strategy: instance %d: DMA byte count 0!\n", instance); bp->b_error |= EINVAL; bp->b_flags |= B_ERROR; goto EXITSTRATEGY; } /* bind handle and get dma first dma cookie for dma hardware */ if(bp->b_flags & B_READ) dma_direction = DDI_DMA_READ; else dma_direction = DDI_DMA_WRITE; if(ddi_dma_buf_bind_handle(unit_p->dma_handle, bp, dma_direction | 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,"idr_strategy: instance %d: ddi_dma_buf_bind_handle failure!\n",instance); goto EXITSTRATEGY; } DPRINT((CE_CONT,"idr_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,"idr_strategy: instance %d: dma cookie count = 0, or too many cookies!\n",instance) ; goto ERRORSTRATEGY ; } /* set plx direction mode for later use - based on flag in buf */ if(bp->b_flags & B_READ) plx_direction = DMA_INPUT; else plx_direction = 0; /* start walking through user buffer cookies, building chaining list */ for(cookie_number = 0; cookie_number < cookie_count; cookie_number++) { /* we aleady have the first cookie get the next cookie - if number is not 0 */ if(cookie_number != 0) { DPRINT((CE_CONT,"idr_strategy: instance %d: cookie number != 0, calling ddi_dma_nextcookie\n", instance)); ddi_dma_nextcookie(unit_p->dma_handle, &dma_cookie); } DPRINT((CE_CONT,"idr_strategy: instance %d: processing cookie #0x%x, phys address = 0x%x, size = 0x%x\n", instance, cookie_number, dma_cookie.dmac_address, dma_cookie.dmac_size)); /* buffer must be 16 bit word aligned and an integer # of words (not odd byte count) */ if((dma_cookie.dmac_address & 1) || (dma_cookie.dmac_size & 1)) { bp->b_error |= EINVAL; bp->b_flags |= B_ERROR; cmn_err(CE_CONT,"idr_strategy: instance %d: buffer segment not word aligned or odd byte count!\n", instance); goto ERRORSTRATEGY; } /* assemble the chain link */ IOPB_PUTL((cookie_number * IOPB_SIZE) + IOPB_PCI_ADDRESS, dma_cookie.dmac_address); /* pci user buf segment address */ IOPB_PUTL((cookie_number * IOPB_SIZE) + IOPB_TRANSFER_SIZE, dma_cookie.dmac_size); /* buf segment size */ IOPB_PUTL((cookie_number * IOPB_SIZE) + IOPB_LOCAL_ADDRESS, IDR_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, this is the last iopb flag it as the last in the list, and don't set a "next iopb" address */ if(cookie_number != (cookie_count -1)) { DPRINT((CE_CONT,"idr_strategy: instance %d: not last iopb, writing pointer to next iopb\n", instance)); IOPB_PUTL((cookie_number * IOPB_SIZE) + IOPB_NEXT_IOPB, DMA_CHAIN_IN_PCI_MEM | plx_direction | DMA_NEXT_ADDRESS_MASK & (unit_p->iopb_phys_base_addr + ((cookie_number + 1) * IOPB_SIZE))); } else { DPRINT((CE_CONT,"idr_strategy: instance %d: last iopb, setting end of chain bit\n", instance)); IOPB_PUTL((cookie_number * IOPB_SIZE) + IOPB_NEXT_IOPB, DMA_CHAIN_IN_PCI_MEM | plx_direction | DMA_END_OF_CHAIN); } } /* 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) cookie_number should contain the actual number of iopbs generated (it is inc'd by 1 at end of loop) */ DPRINT((CE_CONT,"idr_strategy: instance %d: syncing iopb memory, cookie_count = 0x%x, size sync'd = 0x%x\n", instance, cookie_count, cookie_count * IOPB_SIZE)); if((ddi_dma_sync(unit_p->iopb_handle, (off_t)0, (size_t)(cookie_count * IOPB_SIZE), DDI_DMA_SYNC_FORDEV) != DDI_SUCCESS)) { cmn_err(CE_WARN,"idr_strategy: instance %d: ddi_dma_sync failure!\n", instance); bp->b_error |= EIO; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY ; } /* pulse any flags (including errors) off if in auto mode, leave on for later code if manual */ if(!(unit_p->unit_flags & IDR_MANUAL)) IDR_PUTL(IDR_116_PULSES, RESET_EOR_FLAG | RESET_ATTENTION_FLAG | RESET_ERROR_FLAGS); /* if automatic mode, set range counter - check for out of limit count */ if(!(unit_p->unit_flags & IDR_MANUAL)) { DPRINT((CE_CONT,"idr_strategy: instance %d: auto mode, setting range counter to 0x%x (words - 1)\n", instance, (bp->b_bcount >> 1) - 1)); /* dr11 counter wants WORD count minus 1 */ range = (bp->b_bcount >> 1) - 1; if(range > IDR_DR11_MAXBLOCK) { cmn_err(CE_CONT,"idr_strategy: instance %d DR11 range count too large!\n", instance) ; bp->b_error |= EINVAL ; bp->b_flags |= B_ERROR ; goto ERRORSTRATEGY ; } IDR_PUTL(IDR_RANGE_HIGH, range >> 16); IDR_PUTL(IDR_RANGE_MID, range >> 8); IDR_PUTL(IDR_RANGE_LOW, range); /* force off dma enb and direction bit */ IDR_PUTL(IDR_LATCHED_FUNCTIONS,IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~(IDR_DMA_ENABLE | DMA_INPUT_MODE)); if(bp->b_flags & B_READ) { DPRINT((CE_CONT,"idr_strategy: instance %d: auto read mode, setting dvma_wait\n", instance)); /* we are in automatic read mode, so set direction to input and set read function bits to pre-selected pattern also enable attention interrupt, but not eor interrupt */ IDR_PUTL(IDR_LATCHED_FUNCTIONS, (IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~FUNCTION_MASK) | DMA_INPUT_MODE | ATTENTION_INT_ENB | unit_p->read_fcn); /* in automatic read, we will clear the input fifo before starting transfers in manual mode, that is done in start read */ IDR_PUTL(IDR_116_PULSES, CLEAR_INPUT_FIFO); /* set to dma t/c wait - the one to use for input mode */ unit_p->unit_flags |= IDR_DVMA_WAIT ; } else { DPRINT((CE_CONT,"idr_strategy: instance %d: auto write mode, setting eor_wait\n", instance)); /* automatic write mode - leave input mode bit off set write function bits enable eor or attention interrupt */ IDR_PUTL(IDR_LATCHED_FUNCTIONS, (IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~FUNCTION_MASK) | END_OF_RANGE_INT_ENB | ATTENTION_INT_ENB | unit_p->write_fcn); /* in auto write mode, clear the output fifo before transfers begin */ IDR_PUTL(IDR_116_PULSES, CLEAR_OUTPUT_FIFO); /* since automatic output, wait for end-of-range */ unit_p->unit_flags |= IDR_EOR_WAIT ; } /* enable idr logic to request dma transfer we could probably have combined this with above idr dma requests will be enabled in start read or start write if manual mode */ IDR_PUTL(IDR_LATCHED_FUNCTIONS,IDR_GETL(IDR_LATCHED_FUNCTIONS) | IDR_DMA_ENABLE); } else { DPRINT((CE_CONT,"idr_strategy: instance %d: manual mode, setting dvma_wait\n", instance)); /* manual mode - always wait for dma terminal count and always enable attention interrupt direction and idr dma enable bit will be set in start read or start write */ unit_p->unit_flags |= IDR_DVMA_WAIT ; IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) | ATTENTION_INT_ENB); } /* the error flags are not cleared here so they will persist for multiple calls to strategy by physio */ /* we assume that we will always be issuing a go here if in automatic mode in manual mode, issuing go is up to the calling program we first make sure that attention is not on - if it is go will be suppressed by the hardware, and transfer will be aborted attention may be arbitrarily long if generated by software at the other end of the dr11 cable - so we will wait for a "reasonable" number of useconds if we find it on it may be necessary to modify this wait time if the application generates a particularly long attention pulse *************** we will issue the dr11 pulses before starting dma if we are doing output, and after starting dma if doing input the reasoning is as follows: if we do the go before dma is started for input, a very fast device might fill our fifos before we could get dma started -- very unlikely with 16K fifos, but there is a small chance that our code could be pre-empted for a long time (thank you unix) if we start dma first for output, the bus will get very busy, and it is not clear that the p-i/o accesses necessary to issue the function and go bits will get through (this was a serious problem on the sbus - probably not an issue with plx on pci) */ if(!(unit_p->unit_flags & IDR_MANUAL)) { /* test for attention stuck on - it will prevent the block from starting if it is on do it here since we need to check for both input and output modes - in automatic mode */ i = 0; while((IDR_GETL(IDR_STATUS) & ATTENTION)&&(i<100)) { drv_usecwait(1) ; i++ ; } if(IDR_GETL(IDR_STATUS) & ATTENTION) { cmn_err(CE_CONT,"idr_strategy: instance %d attempt to issue GO while ATTENTION true!\n", instance) ; /* DMON may be on at this point - shut down everything!! */ IDR_PUTL(IDR_LATCHED_FUNCTIONS,IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~(IDR_DMA_ENABLE | END_OF_RANGE_INT_ENB | ATTENTION_INT_ENB)); bp->b_error |= EIO ; bp->b_flags |= B_ERROR ; goto ERRORSTRATEGY ; } if(!(bp->b_flags & B_READ)) { /* auto write issues pulses before dma starts */ IDR_PUTL(IDR_DEVICE_PULSES, unit_p->write_pulse | GO); } } /* get current time for use in cv_timedwait_sig drv_getparm returns current time in ticks since last reboot */ if(drv_getparm(LBOLT, &time_now)) { cmn_err(CE_CONT,"idr_strategy: instance %d: drv_getparm error!\n", instance); bp->b_error |= EIO; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY; } /* program plx dma logic on board - in chaining mode do not enable dma done interrupts in dma mode or interrupt control registers if auto out mode (eor interrupt used instead) */ if((unit_p->unit_flags & IDR_MANUAL) || (bp->b_flags & B_READ)) { DPRINT((CE_CONT,"idr_strategy: instance %d: manual read/write or auto read, enabling dma done interrupt\n", instance)); plx_dma_done_int = DMA_DONE_INTERRUPT_ENB; plx_local_dma_int = LOCAL_DMA_1_INT_ENABLE; } else { DPRINT((CE_CONT,"idr_strategy: instance %d: auto write, disabling dma done interrupt\n", instance)); plx_dma_done_int = 0; plx_local_dma_int = 0; } DPRINT((CE_CONT,"idr_strategy: instance %d: starting dma, latched fcn reg = 0x%x\n", instance, IDR_GETL(IDR_LATCHED_FUNCTIONS))); /* make sure that any left over dma done interrupt is cleared */ PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, DMA_1_CLEAR_INTERRUPT); PLX_PUTL(PLX_DMA_MODE_1, DMA_BUS_16_BIT | DMA_WAIT_1 | DMA_BURST_ENABLE | DMA_CHAIN_ENABLE | plx_dma_done_int | DMA_LOCAL_ADD_HOLD | DMA_DEMAND_MODE); /* mode register */ PLX_PUTL(PLX_DMA_DESC_PTR_1,DMA_CHAIN_IN_PCI_MEM | unit_p->iopb_phys_base_addr | plx_direction); /* direction,list in pci mem, list pointer */ PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, DMA_1_ENABLE); /* enable first - PLX BUG */ PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, DMA_1_ENABLE | DMA_1_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 | plx_local_dma_int); /* if automatic and read mode - issue go AFTER starting dma */ if((!(unit_p->unit_flags & IDR_MANUAL)) && (bp->b_flags & B_READ)) IDR_PUTL(IDR_DEVICE_PULSES, unit_p->read_pulse | GO); DPRINT((CE_CONT,"idr_strategy: instance %d: unit_flags = 0x%x, dma mode = 0x%x, int cstat = 0x%x\n", instance, unit_p->unit_flags, PLX_GETL(PLX_DMA_MODE_1), PLX_GETL(PLX_INT_CSTAT))); DPRINT((CE_CONT,"idr_strategy: instance %d: calling cv_timedwait_sig, time_now = %d, 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)); /* turn off idr dma enable if automatic mode */ if(!(unit_p->unit_flags & IDR_MANUAL)) IDR_PUTL(IDR_LATCHED_FUNCTIONS, IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~IDR_DMA_ENABLE); /* make sure interrupt enables are off and pause dma in input mode, plx dma will still be enabled at this point to allow any data in the plx fifo that was left after a block terminated early by attention to make it to memory if the application uses attention to do early block termination, it may be necessary to add some delay here to make sure that the (small) plx fifo has drained there doesn't seem to be any way to determine whether there is data in the plx fifos so for now, we will trust to luck! */ /* spin a while (if necessary) to give dma a chance to finish then pause DMA - if still on */ for(i=0; i< 100; i++) if(PLX_GETL(PLX_DMA_CMD_STAT_BOTH) & DMA_1_DONE) break; PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, 0); IDR_PUTL(IDR_LATCHED_FUNCTIONS,IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~(END_OF_RANGE_INT_ENB | ATTENTION_INT_ENB)); PLX_PUTL(PLX_INT_CSTAT, 0); /* if dma is not done (after sig or timeout or short input block), force a plx dma abort and issue a master clear to flush idr buffers and turn off the dma enable bit in the idr logic 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_1_DONE)) { DPRINT((CE_CONT,"idr_strategy: instance %d: dma not done after cv_timedwait_sig\n", instance)); PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, DMA_1_ABORT); /* get latched functions and mode - so we can restore them after a mclr 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_latches = IDR_GETL(IDR_LATCHED_FUNCTIONS) & ~IDR_DMA_ENABLE; temp_mode = IDR_GETL(IDR_MODE); IDR_PUTL(IDR_116_PULSES, MASTER_CLEAR); /* force write cache to flush - so delay appears on the bus */ read_dump_0 = IDR_GETL(IDR_STATUS); drv_usecwait(5); IDR_PUTL(IDR_116_PULSES, MASTER_CLEAR); IDR_PUTL(IDR_LATCHED_FUNCTIONS, temp_latches); IDR_PUTL(IDR_MODE, temp_mode); /* make sure dma actually says done */ if(!(PLX_GETL(PLX_DMA_CMD_STAT_BOTH) & DMA_1_DONE)) { cmn_err(CE_CONT,"idr_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, (the above is probably not necessary after normal dma done) */ PLX_PUTL(PLX_DMA_CMD_STAT_BOTH, DMA_1_CLEAR_INTERRUPT); /* find out why we woke up if we got here via timeout or signal do a soft reset of the plx and idr logic - saving and restoring the latched functions and mode registers */ if(waitval <= 0) { if(waitval == 0) { /* flag signal received and print error message */ cmn_err(CE_CONT,"idr_strategy: instance %d: signal received while waiting for interrupt!\n",instance); unit_p->unit_flags |= IDR_SIG_RECEIVED; bp->b_error |= EINTR; bp->b_flags |= B_ERROR; } else { /* timeout ! */ cmn_err(CE_CONT,"idr_strategy: instance %d: timeout while waiting for interrupt!\n", instance); unit_p->unit_flags |= IDR_DVMA_TIMEOUT; bp->b_error |= EIO; bp->b_flags |= B_ERROR; } /* save latched functions and mode and do a soft reset, then restore saved registers */ temp_latches = IDR_GETL(IDR_LATCHED_FUNCTIONS); temp_mode = IDR_GETL(IDR_MODE); plx_soft_reset(unit_p); IDR_PUTL(IDR_LATCHED_FUNCTIONS, temp_latches); IDR_PUTL(IDR_MODE, temp_mode); goto ERRORSTRATEGY; } /* normal return from interrupt wait */ /* if automatic mode, check for multicycle error or parity error and flag in unit structure in manual mode, this stuff is done in block_end parity error will occur frequently when connected to device that don't support parity (most!) so don't do a cmn_err print */ if(!(unit_p->unit_flags & IDR_MANUAL)) { if(IDR_GETL(IDR_FLAGS) & PARITY_ERROR_FLAG) { DPRINT((CE_CONT,"idr_strategy: instance %d: parity error!\n", instance)); unit_p->unit_flags |= IDR_PAR_ERR; } if(IDR_GETL(IDR_FLAGS) & MULTI_CYCLE_ERROR_FLAG) { cmn_err(CE_CONT,"idr_strategy: instance %d: multi-cycle error!\n", instance); unit_p->unit_flags |= IDR_MCYL_ERR; } } ERRORSTRATEGY: /* come here if error after alloc handle, and bind handle are all complete, or in normal flow */ ddi_dma_unbind_handle(unit_p->dma_handle); 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) ; } /* do a soft reset of the plx chip - also clears the idr logic */ static void plx_soft_reset(struct idr_unit *unit_p) { u_char uc_temp; /* temp storage */ /* we would like to do a full reset of the plx and idr 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 SAVE AND RESTORE THE INT LINE AFTER SOFT_RESET we probably don't have to clear the config reload bit after writing it, but we will to be sure we also take this opportunity to disable address space 0 pre-fetch, which could have some unpredictable side effects - since we are really a set of volatile registers, not memory */ /* map config register set temporarily so we can read and write the int line reg use the offsets canned in pci.h from sun there is really no good way to return an error from here - so just put it on the console and return without touching anything */ if(pci_config_setup(unit_p->dip, &unit_p->config_acc_handle) != DDI_SUCCESS) { cmn_err(CE_CONT,"idr_attach: instance %d: soft_reset: pci_config_setup error!\n",unit_p->saved_instance) ; return; } uc_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, uc_temp); pci_config_teardown(&unit_p->config_acc_handle); DPRINT((CE_CONT,"idr_??? (plx_soft_reset): instance %d: soft reset and configuration reload complete\n", unit_p->saved_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); /* restore endian mode default */ PLX_PUTL(PLX_ENDIAN_REG, unit_p->endian_def); /* disable address space 0 pre-fetch */ PLX_PUTL(PLX_ADD_0_ROM_DESC, PLX_GETL(PLX_ADD_0_ROM_DESC) | ADD_0_PREFETCH_DISABLE); }