/* * ihcp_driver.c * * Tahoma Technology * (formerly Ikon Corporation) * 107 2nd Avenue North * Seattle, WA, USA 98109 * * 206.728.6465 * http://www.tahomatech.com * tahoma@tahomatech.com * * This driver (including source) is placed in the public domain in the * hope that it will prove useful in using and understanding Tahoma Technolgy's * 10115 and 10117 PCI hardcopy interfaces. * * This code is intended to be working and (relatively) bug free driver when * running on the host computers and operating systems available to Tahoma Technology. * Tahoma Technology will attempt to keep the code running on contemporary OSs * and hardware from SUN and others, but does not guarantee this. * * References to Ikon Corporation left in place for compatibility and historical reasons. * * 1 August, 1996 initial version for SPARC * * 30 Sept, 1996 changed inclue for pci.h from "pci.h" to * * removed static from _init _info and _fini * * 3 Oct, 1996 changed the way we use dummy variables when doing a read * to force the write cache to flush - now using static volatile * global variables to try to keep the optimizer from deleting * the reads * * 10 Oct, 1996 fixed bad bug in ihcp_high_intr() that used the following: * temp = IHCP_GET32(PLX_INT_CSTAT) to test for interrupts - * unfortunately it should have been ..(IPLX...) so we read * 0xFFFFFFFF from outer space, and claimed interrupts that * weren't ours .. it is amazing that it worked at all .. * * also - we weren't turning off the soft_intr_req flag in our * soft state from within our soft_intr routine - so it was * possible that we would accept a soft intr that wasn't ours * * 17 June, 1997 added suspend and resume to detach and attach for system * power management * * modified attach to get driver and device defaults from * ihcp.conf - so changing defaults doesn't require re-compiling * the driver * * added support for versatec burst mode - only available on some * new plotters - requires pld218B or later on 115/117 * * 27 August, 1997 moved dma_handle to soft state * * moved ddi_dma_alloc_handle to attach & fixed cast of dip in call * * added remove_minor_node to detach code * * fixed error in dma_attr that had no effect on sparc, but crashed * x86 kernel * * 9 July, 1998 modified for compatibility with 64 bit solaris * * added ignore_minphys parameter * * added code to preserve uio.offset during writes, so we don't * have to "rewind" the iova counter * * added code to restore the plx int line * register after an eeprom reload (not necessary on sparc, but * added for consistency with x86 code) * * 11 November, 1999 fixed bug in the way we wait for PLX configuration eeprom * reload - we had the read-dump after the reload command * * in a system with a short Pci retry timer, this could actually * cause a panic, rather than avoid one (current Sun hardware * does not trigger the problem - this is a potential bug) * * 27 May, 2004 added dummy reads between PCI writes to force write ordering * this shouldn't be a problem since we specify DDI_STRICTORDER_ACC * but the idr driver on some platforms seemed to occasionally * get a write sequence out of order - why is unknown, and may * be a Solaris bug or interference with another driver * these reads are harmless, and guarantee against the problem * (until such time as the actual cause is identified) * * IDR_SYNC_x and PLX_SYNC_x macros used for dummy reads * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ihcp_io.h" #include "ihcp_reg.h" #include "ihcp_var.h" /* * save a little typing when getting properties from ihcp.conf */ #define getprop(devi, name, def) ddi_getprop(DDI_DEV_T_ANY, (devi),\ DDI_PROP_DONTPASS, (name), (def)) static void *state_head; /* opaque handle top of state structs */ /* * declare dummy variables used to the left of = when we read a board * register to force the write cache to flush a register write to the bus */ static volatile uint32_t read_dump_0; static volatile uint32_t 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. * * int should remain 32 bits under 64 bit solaris */ static int ihcp_getinfo(dev_info_t * dip, ddi_info_cmd_t infocmd, void *arg, void **result); static int ihcp_attach(dev_info_t * dip, ddi_attach_cmd_t cmd); static int ihcp_open(dev_t * dev, int openflags, int otyp, cred_t * credp); static int ihcp_close(dev_t dev, int openflags, int otyp, cred_t * credp); static int ihcp_write(dev_t dev, struct uio *uiop, cred_t * credp); static int ihcp_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t * credp, int *rvalp); static u_int ihcp_soft_intr(caddr_t arg); static u_int ihcp_high_intr(caddr_t arg); static int ihcp_detach(dev_info_t * dip, ddi_detach_cmd_t cmd); /* * These are internal private functions - although strategy is called by * physio, it is not made visible externally */ static int ihcp_strategy(struct buf *bp); static void ihcp_minphys(struct buf *bp); /* * declare device data access structure for plx and ihcp registers * and config registers * * we will try to treat each register as 32 bits wide - so we * don't want any byte swapping * * however, it seems that the sparc machines swizzle the bytes * between host and pci if we say never swap (at least for 32 bit accesses) - so we will * use the little endian flag * * if the hardware really does swizzle bytes between pci and host - * including memory, this should help dma by getting the data * bytes in the right order! */ static struct ddi_device_acc_attr slave_attr = { DDI_DEVICE_ATTR_V0, DDI_STRUCTURE_LE_ACC, DDI_STRICTORDER_ACC, }; /* * declare dma attribute structure */ static struct ddi_dma_attr dma_attr = { (uint_t) DMA_ATTR_V0, /* version */ (uint64_t) 0x0, /* lower address boundary */ (uint64_t) 0xFFFFFFFF, /* upper boundary - 32 bit max */ (uint64_t) 0x7FFFFF, /* 23 bit address counter */ (uint64_t) 0x1, /* don't care about alignment */ (uint_t) 0x0, /* burst size - seems to be ignored for pci (sbus only?) */ (uint32_t) 0x1, /* min xfer size is 1 byte */ (uint64_t) 0x7FFFFF, /* single buffer so same as range size */ (uint64_t) 0xFFFFFFFF, /* no segment restriction */ (int) 1, /* for now - no dma chaining */ (uint32_t) 1, /* granularity - not sure about this one! */ (uint_t) 0, /* flags - sun says 0 for now */ }; /* * declare structures used for dynamic loding and unloading of the driver */ static struct cb_ops ihcp_cb_ops = { ihcp_open, ihcp_close, nulldev, /* not a block driver */ nodev, /* no print routine */ nodev, /* no dump routine */ nodev, /* no read routine - write-only device */ ihcp_write, ihcp_ioctl, nodev, /* no devmap routine */ nulldev, /* no mmap routine */ nulldev, /* no segmap routine */ nochpoll, /* no chpoll routine */ ddi_prop_op, (int) 0, /* not a STREAMS driver */ (int) (D_NEW | D_MP), /* safe for multi-thread/multi-processor */ }; /* * NOTE: * declare and initialize the dev_ops structure. if it is necessary to prevent * the system from unloading the driver on a timed basis (it will if no driver calls * are made for some (unknown!) length of time) replace ihcp_detach with nodev. this * will keep the driver from unloading. if this is done, it will be necessary to do * a reconfiguration boot any time a new version of the driver is to be loaded. * * we do not provide power management for this driver/device combination * we do allow system-wide suspend/resume operations when our device is not open */ static struct dev_ops ihcp_ops = { DEVO_REV, /* DEVO_REV indicated by manual */ (int) 0, /* device reference count */ ihcp_getinfo, nulldev, /* identify routine is obsolete */ nulldev, /* device probe for non-self-id */ ihcp_attach, ihcp_detach, /* REPLACE W/nodev IF DRIVER SHOULD STAY ATTACHED */ nodev, /* device reset routine */ &ihcp_cb_ops, (struct bus_ops *) 0, /* bus operations */ nulldev, /* power operations */ }; extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &mod_driverops, MODINFOBANNER, &ihcp_ops, }; static struct modlinkage modlinkage = { (int) MODREV_1, /* MODREV_1 indicated by manual */ (void *) &modldrv, NULL, /* termination of list of linkage structures */ }; /* * register interrupts, map the working registers, and initialize * things on a per-instance basis */ static int ihcp_attach(dev_info_t * dip, ddi_attach_cmd_t cmd) { register struct ihcp_unit *unit_p; int instance; int result; char minor_node_name[16]; uint32_t temp; uint8_t temp8; /* * save a little typing when getting properties from ihcp.conf */ #define getprop(devi, name, def) ddi_getprop(DDI_DEV_T_ANY, (devi),\ DDI_PROP_DONTPASS, (name), (def)) instance = ddi_get_instance(dip); /* * test for expected commands * other commands may not be errors, so only print message if debugging is on */ switch (cmd) { case DDI_RESUME: unit_p = (struct ihcp_unit *) ddi_get_soft_state(state_head, instance); /* * we are being asked to restore register state due to power up following * power management power down */ DPRINT((CE_CONT, "ihcp_attach: instance %d: command is DDI_RESUME\n", instance)); mutex_enter(&unit_p->soft_mutex); DPRINT((CE_CONT, "ihcp_attach: instance %d: restoring state & clearing suspended flag\n", instance)); /* * restore hardware state */ PLX_PUT32(PLX_INT_CSTAT, unit_p->saved_plx_int_cstat); PLX_PUT32(PLX_EEPROM_USER, unit_p->saved_plx_eeprom_user); PLX_SYNC_0; IHCP_PUT32(IHCP_MODE, unit_p->saved_mode); IHCP_PUT32(IHCP_DEVICE_CONTROL, unit_p->saved_device_control); IHCP_PUT32(IHCP_INTERFACE_CONTROL, unit_p->saved_interface_control); IHCP_PUT32(IHCP_AUTO_LTR_LOW, unit_p->saved_auto_ltr_low); IHCP_PUT32(IHCP_AUTO_LTR_HIGH, unit_p->saved_auto_ltr_high); IHCP_SYNC_0; unit_p->unit_suspended = 0; /* * wake up ihcp_open() which may be waiting on suspended flag */ cv_broadcast(&unit_p->power_cv); mutex_exit(&unit_p->soft_mutex); return (DDI_SUCCESS); case DDI_ATTACH: DPRINT((CE_CONT, "ihcp_attach: instance %d: command is DDI_ATTACH\n", instance)); /* * allocate soft state structure * and add to list */ if (ddi_soft_state_zalloc(state_head, instance) != 0) { cmn_err(CE_CONT, "ihcp_attach: instance %d: ddi_soft_state_zalloc failure!\n", instance); return (DDI_FAILURE); } unit_p = (struct ihcp_unit *) ddi_get_soft_state(state_head, instance); ddi_set_driver_private(dip, (caddr_t) unit_p); unit_p->dip = dip; unit_p->saved_instance = instance; unit_p->soft_intr_req = 0; /* * unlike other architectures (sbus or isa) where the interrupt level is determined * by the hardware characteristics of the board, in pci/solaris land, interrupt priority * is assigned to us based on device class code - or so it seems * * we look like a bridge chip (although we should probably be none of the above) * * this causes us to be assigned to the same priority as the scheduler - at least in * one implementation * * the test for high level interrupts doesn't complain since we aren't higher, we are the same * * this causes a recursive mutex enter panic at interrupt time if we end up with the interrupt * code running the same thread as the strategy routine * * we could assign a different priority with a xx.conf file, but this might not solve the * problem in another implementation * * our solution, for now, is to implement a high level interrupt handler which triggers a soft * interrupt (running at a different level) that can grab the mutex that strategy is using * without causing a panic * * 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 */ /* * get iblock cookie for high level interrupt * we use only one interrupt level */ if (ddi_get_iblock_cookie(dip, 0, &unit_p->high_iblock_cookie) != DDI_SUCCESS) { ddi_soft_state_free(state_head, instance); cmn_err(CE_CONT, "ihcp_attach: instance %d: ddi_get_iblock_cookie (high level) error!\n", instance); return (DDI_FAILURE); } /* * initialize high level MUTEX */ mutex_init(&unit_p->high_mutex, "ihcp high mutex", MUTEX_DRIVER, (void *) unit_p->high_iblock_cookie); /* * register the high level interrupt */ if (ddi_add_intr(dip, 0, &unit_p->high_iblock_cookie, (ddi_idevice_cookie_t *) NULL, ihcp_high_intr, (caddr_t) unit_p) != DDI_SUCCESS) { cmn_err(CE_CONT, "ihcp_attach: instance %d: ddi_add_intr (high level) error!\n", instance); mutex_destroy(&unit_p->high_mutex); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* * get an iblock cookie for the soft interrupt */ if (ddi_get_soft_iblock_cookie(dip, DDI_SOFTINT_HIGH, &unit_p->soft_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_CONT, "ihcp_attach: instance %d: ddi_get_iblock_cookie error!\n", instance); ddi_remove_intr(dip, 0, unit_p->high_iblock_cookie); mutex_destroy(&unit_p->high_mutex); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* * initialize the low level mutex */ mutex_init(&unit_p->soft_mutex, "ihcp soft mutex", MUTEX_DRIVER, (void *) unit_p->soft_iblock_cookie); /* * initialize conditional variable for normal driver operations */ cv_init(&unit_p->cv, "ihcp cv", CV_DRIVER, (void *) unit_p->soft_iblock_cookie); /* * initialize another cv for use with power management */ cv_init(&unit_p->power_cv, "ihcp power cv", CV_DRIVER, (void *) unit_p->soft_iblock_cookie); /* * register the soft interrupt */ if (ddi_add_softintr (dip, DDI_SOFTINT_HIGH, &unit_p->soft_intr_id, &unit_p->soft_iblock_cookie, (ddi_idevice_cookie_t *) NULL, ihcp_soft_intr, (caddr_t) unit_p) != DDI_SUCCESS) { cmn_err(CE_CONT, "ihcp_attach: instance %d: ddi_add_softintr error!\n", instance); ddi_remove_intr(dip, 0, unit_p->high_iblock_cookie); mutex_destroy(&unit_p->high_mutex); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); mutex_destroy(&unit_p->soft_mutex); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* * get the number of device registers * since plx may fiddle with this in later chip revs, * we won't check the number, just save it for * possible diagnostic purposes */ if (ddi_dev_nregs(dip, &result) != DDI_SUCCESS) { cmn_err(CE_CONT, "ihcp_attach: instance %d: ddi_dev_nregs error!\n", instance); ddi_remove_intr(dip, 0, unit_p->high_iblock_cookie); mutex_destroy(&unit_p->high_mutex); ddi_remove_softintr(unit_p->soft_intr_id); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); mutex_destroy(&unit_p->soft_mutex); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } DPRINT((CE_CONT, "ihcp_attach: instance %d: nregs = 0x%x\n", instance, result)); /* * map in the plx and ihcp registers * set length and offset to so entire reg ranges are mapped * this is necessary since plx may change their register ranges */ if (ddi_regs_map_setup(dip, PLX_REG_RNUMBER, (caddr_t *) & unit_p->plx_base, 0, 0, &slave_attr, &unit_p->plx_acc_handle) != DDI_SUCCESS) { cmn_err(CE_CONT, "ihcp_attach: instance %d: ddi_regs_map_setup error!\n", instance); ddi_remove_intr(dip, 0, unit_p->high_iblock_cookie); mutex_destroy(&unit_p->high_mutex); ddi_remove_softintr(unit_p->soft_intr_id); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); mutex_destroy(&unit_p->soft_mutex); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } if (ddi_regs_map_setup(dip, IHCP_REG_RNUMBER, (caddr_t *) & unit_p->ihcp_base, 0, 0, &slave_attr, &unit_p->ihcp_acc_handle) != DDI_SUCCESS) { cmn_err(CE_CONT, "ihcp_attach: instance %d: ddi_regs_map_setup error!\n", instance); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_remove_intr(dip, 0, unit_p->high_iblock_cookie); mutex_destroy(&unit_p->high_mutex); ddi_remove_softintr(unit_p->soft_intr_id); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); mutex_destroy(&unit_p->soft_mutex); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* * allocate dma handle */ if (ddi_dma_alloc_handle (dip, &dma_attr, DDI_DMA_DONTWAIT, (caddr_t) 0, &unit_p->dma_handle) != DDI_SUCCESS) { cmn_err(CE_CONT, "ihcp_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->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->high_iblock_cookie); mutex_destroy(&unit_p->high_mutex); ddi_remove_softintr(unit_p->soft_intr_id); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); mutex_destroy(&unit_p->soft_mutex); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } /* * ddi_create_minor_node creates an entry in an internal kernel * table; the actual entry in the file system is created by * drvconfig(1) when you run add_drv(1); * * DDI_PSEUDO seems like a strange node type but we don't seem to fit the other possibilities * * we will save the instance as our minor # for future * availability to write and ioctl routines * * use string format to generate ihcpx node name * */ (void) sprintf(minor_node_name, "ihcp%d", instance); if (ddi_create_minor_node(dip, minor_node_name, S_IFCHR, instance, DDI_PSEUDO, NULL) == DDI_FAILURE) { cmn_err(CE_CONT, "ihcp_attach: instance %d: ddi_create_minor_node error!\n", instance); ddi_dma_free_handle(&unit_p->dma_handle); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->high_iblock_cookie); mutex_destroy(&unit_p->high_mutex); ddi_remove_softintr(unit_p->soft_intr_id); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); mutex_destroy(&unit_p->soft_mutex); 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 save and restore the int line register * use the offsets canned in pci.h from sun */ if (pci_config_setup(dip, &unit_p->config_acc_handle) != DDI_SUCCESS) { cmn_err(CE_CONT, "ihcp_attach: instance %d: pci_config_setup error!\n", instance); ddi_remove_minor_node(dip, NULL); ddi_dma_free_handle(&unit_p->dma_handle); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->high_iblock_cookie); mutex_destroy(&unit_p->high_mutex); ddi_remove_softintr(unit_p->soft_intr_id); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); mutex_destroy(&unit_p->soft_mutex); ddi_soft_state_free(state_head, instance); return (DDI_FAILURE); } DPRINT((CE_CONT, "ihcp_attach: instance %d: ints registered, node created, regs mapped, plx_base = 0x%x, ihcp_base = 0x%x\n", instance, unit_p->plx_base, unit_p->ihcp_base)); /* * get and save device and vendor ids */ unit_p->dev_and_vendor_id = pci_config_get32(unit_p->config_acc_handle, (off_t) PCI_CONF_VENID); unit_p->revision_id = REV_ID_MASK & pci_config_get32(unit_p->config_acc_handle, (off_t) PCI_CONF_REVID); DPRINT((CE_CONT, "ihcp_attach: instance %d: vendor & device id = 0x%08x, revision id = 0x%02x\n", instance, unit_p->dev_and_vendor_id, unit_p->revision_id)); /* * 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_get32(unit_p->config_acc_handle, (off_t) PCI_CONF_COMM); pci_config_put32(unit_p->config_acc_handle, (off_t) PCI_CONF_COMM, (uint32_t) (temp | PCI_COMM_IO | PCI_COMM_MAE | PCI_COMM_ME)); /* * we would like to do a full reset of the plx and ihcp logic here, but.... * it isn't easy! * * soft reset clears the local config registers, which turns off add space 0 accesses * * so we will force an eeprom reload, and wait a while (0.1s) to avoid too many retry cycles * while the eeprom is reloading - we will use delay, not drv_usec_wait, so we don't * tie up the cpu * * do a read before the config reload to try to force the write buffer to flush, * which will (hopefully) cause the delay to be reflected on the bus * * an earlier rev cleared the int line byte in the configuration registers during * soft reset, but that is not supposed to be true anymore * * IN FACT IT IS STILL TRUE, SO WE WILL SAVE AND RESTORE THE ILINE REGISTER * - not really necessary on sparc (now) but we will do it to avoid future problems * and for consistency with x86 code * * we probably don't have to clear the config reload bit after writing it, * but we will to be sure */ temp8 = pci_config_get8(unit_p->config_acc_handle, (off_t) PCI_CONF_ILINE); PLX_PUT32(PLX_EEPROM_USER, (PLX_GET32(PLX_EEPROM_USER) | PLX_SOFT_RESET)); PLX_PUT32(PLX_EEPROM_USER, (PLX_GET32(PLX_EEPROM_USER) & ~PLX_SOFT_RESET)); read_dump_0 = PLX_GET32(PLX_INT_CSTAT); PLX_PUT32(PLX_EEPROM_USER, (PLX_GET32(PLX_EEPROM_USER) | CONFIGURATION_RELOAD)); delay(drv_usectohz((clock_t) 100000)); PLX_PUT32(PLX_EEPROM_USER, (PLX_GET32(PLX_EEPROM_USER) & ~CONFIGURATION_RELOAD)); pci_config_put8(unit_p->config_acc_handle, (off_t) PCI_CONF_ILINE, temp8); DPRINT((CE_CONT, "ihcp_attach: instance %d: soft reset and configuration releoad complete\n", instance)); /* * soft reset leaves pci master int enable on, so turn it off for safety * don't bother with oring bits - just hammer them all off! */ PLX_PUT32(PLX_INT_CSTAT, 0); IHCP_SYNC_1; pci_config_teardown(&unit_p->config_acc_handle); /* * get the board strapping and save it * zeros rest of flag register at same time */ unit_p->unit_flags = INTERFACE_STRAP_MASK & IHCP_GET32(IHCP_DEVICE_STATUS); DPRINT((CE_CONT, "ihcp_attach: instance %d: interface strapping = 0x%x\n", instance, IHCP_BOARD_MASK & unit_p->unit_flags)); /* * set the data byte ordering default mode * the native ordering for the board is low byte first * * if high byte first mode is selected, the single byte data out and command out * bytes must be sent to the fifos in the HIGH byte of the 32 bit word! * * ordering is set here, and stored in the unit structure, since it will affect * all later accesses to the 8 bit data and command registes * * we only take action if high_byte_first is selected, since the preceeding reset * should have set us to low_byte_first mode * * setting high byte first mode requires turning on the byte_swizzle bit in the * ihcp logic, and TURNING OFF the user output bit in the plx logic */ /* * some driver defaults are read from ihcp.conf (speed and print/plot mode) * others (byte ordering) are left in ihcp_io.h * * get default properties and save in soft state * * the third arg to getprop is the value used if the property is not found */ temp = getprop(dip, "ignore_minphys", IGNORE_MINPHYS_DEF); if ((temp != 0) && (temp != 1)) { temp = IGNORE_MINPHYS_DEF; cmn_err(CE_WARN, "ihcp_attach: instance %d: ignore_minphys property out of range!\n", instance); } unit_p->ignore_minphys = temp; DPRINT((CE_CONT, "ihcp_attach: instance %d: ignore_minphys = 0x%x\n", instance, temp)); temp = getprop(dip, "vers_speed_def", VERS_SPEED_DEF); if ((temp != 0) && (temp != 1) && (temp != 2) && (temp != 3)) { temp = VERS_SPEED_DEF; cmn_err(CE_WARN, "ihcp_attach: instance %d: vers_speed_def property out of range!\n", instance); } DPRINT((CE_CONT, "ihcp_attach: instance %d: vers_speed_def = %d\n", instance, temp)); switch (temp) { case 0: unit_p->vers_speed_def = SPEED_0; break; case 1: unit_p->vers_speed_def = SPEED_1; break; case 2: unit_p->vers_speed_def = SPEED_2; break; case 3: unit_p->vers_speed_def = SPEED_3; break; } temp = getprop(dip, "cent_speed_def", CENT_SPEED_DEF); if ((temp != 0) && (temp != 1) && (temp != 2) && (temp != 3)) { temp = CENT_SPEED_DEF; cmn_err(CE_WARN, "ihcp_attach: instance %d: cent_speed_def property out of range!\n", instance); } DPRINT((CE_CONT, "ihcp_attach: instance %d: cent_speed_def = %d\n", instance, temp)); switch (temp) { case 0: unit_p->cent_speed_def = SPEED_0; break; case 1: unit_p->cent_speed_def = SPEED_1; break; case 2: unit_p->cent_speed_def = SPEED_2; break; case 3: unit_p->cent_speed_def = SPEED_3; break; } temp = getprop(dip, "mode_def", MODE_DEF); if ((temp != 0) && (temp != 1)) { temp = MODE_DEF; cmn_err(CE_WARN, "ihcp_attach: instance %d: mode_def property out of range!\n", instance); } if (temp == 1) unit_p->mode_def = PLOT_MODE; else unit_p->mode_def = NORM_PRINT_MODE; DPRINT((CE_CONT, "ihcp_attach: instance %d: mode_def = %d\n", instance, temp)); temp = getprop(dip, "dma_time_def", DMA_TIME_DEF); unit_p->dma_time_def = temp; DPRINT((CE_CONT, "ihcp_attach: instance %d: dma_time_def = %d\n", instance, temp)); temp = getprop(dip, "rdy_time_def", RDY_TIME_DEF); unit_p->rdy_time_def = temp; DPRINT((CE_CONT, "ihcp_attach: instance %d: rdy_time_def = %d\n", instance, temp)); /*CONSTCOND*/ if (ORDER_DEF == HIGH_BYTE_FIRST) { DPRINT((CE_CONT, "ihcp_attach: instance %d: setting ordering mode to high byte first\n", instance)); IHCP_PUT32(IHCP_MODE, (IHCP_GET32(IHCP_MODE) | BYTE_SWIZZLE)); PLX_PUT32(PLX_EEPROM_USER, (PLX_GET32(PLX_EEPROM_USER) & ~USER_OUTPUT)); PLX_SYNC_1; } unit_p->byte_order = ORDER_DEF; /* * set the default handshake speeds for versatec and centronics boards (as determined by strapping), * respectively, and the default print/plot mode for versatec boards * the print/plot mode is also saved in the unit structure, since it may be referred * to later - and isn't visible in the board's registers * * NOTE THAT THE SAME DEFAULTS ARE USED FOR ALL BOARDS OF THE SAME TYPE!!!!!! */ if ((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { /* * versatec - set speed and print/plot mode */ DPRINT((CE_CONT, "ihcp_attach: instance %d: setting default versatec speed to 0x%x\n", instance, unit_p->vers_speed_def)); DPRINT((CE_CONT, "ihcp_attach: instance %d: setting default versatec print/plot mode to 0x%x\n", instance, unit_p->mode_def)); IHCP_PUT32(IHCP_MODE, ((IHCP_GET32(IHCP_MODE) & ~SPEED_MASK) | unit_p-> vers_speed_def)); temp = SET_VERSATEC_MODE | unit_p->mode_def; if (unit_p->byte_order == HIGH_BYTE_FIRST) temp = temp << 24; IHCP_PUT32(IHCP_COMMAND_OUT, temp); IHCP_SYNC_0; unit_p->mode = unit_p->mode_def; } else { /* * centronics - set speed only */ DPRINT((CE_CONT, "ihcp_attach: instance %d: setting default centronics speed to 0x%x\n", instance, unit_p->cent_speed_def)); IHCP_PUT32(IHCP_MODE, ((IHCP_GET32(IHCP_MODE) & ~SPEED_MASK) | unit_p-> cent_speed_def)); IHCP_SYNC_1; } /* * set saved data streaming mode - for old vme getregs call compatibility, since * we can't read data streaming status from the logic on the other side of the * fifo in the pci card */ unit_p->data_stream_mode = DATA_STREAMING_OFF; /* * set attach complete flag for use in open */ unit_p->unit_attached = 1; ddi_report_dev(dip); return (DDI_SUCCESS); default: DPRINT((CE_CONT, "ihcp_attach: instance %d: unrecognized command\n", instance)); return (DDI_FAILURE); } } /* * This is a pretty generic getinfo routine as described in the manual. */ /*ARGSUSED*/ static int ihcp_getinfo(dev_info_t * dip, ddi_info_cmd_t infocmd, void *arg, void **result) { register int error; register int instance; register struct ihcp_unit *unit_p; /*LINTED cast to signed int ok */ instance = (int) getminor((dev_t) arg); DPRINT((CE_CONT, "ihcp_getinfo: instance %d: entering ihcp_getinfo\n", instance)); switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: unit_p = (struct ihcp_unit *) ddi_get_soft_state(state_head, instance); if (unit_p == NULL) { *result = NULL; cmn_err(CE_CONT, "ihcp_getinfo: instance %d: null unit_pointer!\n", instance); error = DDI_FAILURE; } else { *result = unit_p->dip; error = DDI_SUCCESS; } break; case DDI_INFO_DEVT2INSTANCE: /*LINTED cast ok */ *result = (void *) instance; error = DDI_SUCCESS; break; default: *result = NULL; error = DDI_FAILURE; cmn_err(CE_CONT, "ihcp_getinfo: instance %d: unrecognized info_command!\n", instance); } DPRINT((CE_CONT, "ihcp_getinfo: instance %d: leaving ihcp_getinfo\n", instance)); return (error); } /* * _init, _info, and _fini support loading and unloading the driver. */ int _init(void) { register int error; DPRINT((CE_CONT, "ihcp (_init): compiled %s, %s\n", __TIME__, __DATE__)); if ((error = ddi_soft_state_init(&state_head, sizeof(struct ihcp_unit), 1)) != 0) { DPRINT((CE_CONT, "ihcp (_init): _init error!, error # = 0x%x\n", error)); return (error); } if ((error = mod_install(&modlinkage)) != 0) { DPRINT((CE_CONT, "ihcp (_init): mod_install error! error # = 0x%x\n", error)); ddi_soft_state_fini(&state_head); } DPRINT((CE_CONT, "ihcp (_init): _init done, return value = 0x%x\n", error)); return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int _fini(void) { int error; if ((error = mod_remove(&modlinkage)) != 0) { DPRINT((CE_CONT, "ihcp (_fini): mod_remove error! error # = 0x%x\n", error)); return (error); } ddi_soft_state_fini(&state_head); DPRINT((CE_CONT, "ihcp (_fini): _fini done\n")); return (0); } /* * When our driver is unloaded, ihcp_detach cleans up and frees the resources * we allocated in ihcp_attach. */ static int ihcp_detach(dev_info_t * dip, ddi_detach_cmd_t cmd) { register struct ihcp_unit *unit_p; /* will point to this */ int instance; instance = ddi_get_instance(dip); unit_p = (struct ihcp_unit *) ddi_get_soft_state(state_head, instance); /* * test for expected commands * other commands may not be errors, so only print message if debugging is on */ switch (cmd) { case DDI_SUSPEND: DPRINT((CE_CONT, "ihcp_detach: instance %d: command is DDI_SUSPEND\n", instance)); mutex_enter(&unit_p->soft_mutex); /* * check for open - if so, return failure, if not save state, flag as suspended, and return success */ if (unit_p->unit_open) { DPRINT((CE_CONT, "ihcp_detach: instance %d: unit open, returning DDI_FAILURE\n", instance)); mutex_exit(&unit_p->soft_mutex); return (DDI_FAILURE); } else { DPRINT((CE_CONT, "ihcp_detach: instance %d: saving state & setting suspended flag\n", instance)); unit_p->saved_plx_int_cstat = PLX_GET32(PLX_INT_CSTAT); unit_p->saved_plx_eeprom_user = PLX_GET32(PLX_EEPROM_USER); unit_p->saved_mode = IHCP_GET32(IHCP_MODE); unit_p->saved_device_control = IHCP_GET32(IHCP_DEVICE_CONTROL); unit_p->saved_interface_control = IHCP_GET32(IHCP_INTERFACE_CONTROL); unit_p->saved_auto_ltr_low = IHCP_GET32(IHCP_AUTO_LTR_LOW); unit_p->saved_auto_ltr_high = IHCP_GET32(IHCP_AUTO_LTR_HIGH); unit_p->unit_suspended = 1; mutex_exit(&unit_p->soft_mutex); return (DDI_SUCCESS); } case DDI_DETACH: DPRINT((CE_CONT, "ihcp_detach: instance %d: command is DDI_DETACH\n", instance)); /* * clear attached flag */ unit_p->unit_attached = 0; /* * make sure dma and interrupts are off - we won't do a soft reset * here, on the chance that data may still be in the fifo * * attach will do a soft reset - hopefully it won`t clobber a previous * job that was still in the fifo when a timed detach was done! * */ /* * fist disable all plx interrupt enable bits * then the ihcp masks */ PLX_PUT32(PLX_INT_CSTAT, 0); IHCP_PUT32(IHCP_INTERRUPT_MASK, 0); /* * now make sure dma channel(s) are off * for now we will access dma registers via the ihcp address window * later chip revisions may require testing for rev level, and accessing * via the plx map * * we will just force all zeros into both channel's registers */ IPLX_PUT32(IPLX_DMA_CMD_STAT, 0); IHCP_SYNC_0; /* * this is as good a place as any to do phony references to the read dump varaibles * to try to absolutely guarantee that the optimizer won't remove our cache flush reads * * and to try to shut lint up */ read_dump_0 += read_dump_1; /* * free the various resources that were allocated in attach */ ddi_remove_minor_node(dip, NULL); ddi_dma_free_handle(&unit_p->dma_handle); ddi_regs_map_free(&unit_p->plx_acc_handle); ddi_regs_map_free(&unit_p->ihcp_acc_handle); ddi_remove_intr(dip, 0, unit_p->high_iblock_cookie); mutex_destroy(&unit_p->high_mutex); ddi_remove_softintr(unit_p->soft_intr_id); cv_destroy(&unit_p->cv); cv_destroy(&unit_p->power_cv); mutex_destroy(&unit_p->soft_mutex); ddi_soft_state_free(state_head, instance); DPRINT((CE_CONT, "ihcp_detach: instance %d: detach complete\n", instance)); return (DDI_SUCCESS); default: DPRINT((CE_CONT, "ihcp_detach: instance %d: unrecognized command\n", instance)); return (DDI_FAILURE); } } /* * ihcp_open is called in response to the open(2) system call */ /*ARGSUSED*/ static int ihcp_open(dev_t * dev, int openflags, int otyp, cred_t * credp) { int retval = 0; int instance; register struct ihcp_unit *unit_p; /*LINTED cast to int ok */ instance = (int) getminor(*dev); /* * get unit structure pointer and * make sure driver is (completely) attached */ if ((unit_p = (struct ihcp_unit *) ddi_get_soft_state(state_head, instance)) == NULL) { cmn_err(CE_CONT, "ihcp_open: instance %d: null unit pointer!\n", instance); return (ENXIO); } if (unit_p->unit_attached == 0) { cmn_err(CE_CONT, "ihcp open: instance %d: open attempted before attach complete!\n", instance); return (ENXIO); } /* * Verify we are being opened as a character device */ if (otyp != OTYP_CHR) { cmn_err(CE_CONT, "ihcp_open: instance %d: wrong open_type!\n", instance); return (EINVAL); } /* * Verify that we are being opened for write - WRITE ONLY DEVICE!! */ if (!(openflags & FWRITE)) { cmn_err(CE_CONT, "ihcp_open: instance %d: write only device!\n", instance); return (ENOTTY); } /* * lock unit structure (& flags & registers) */ mutex_enter(&unit_p->soft_mutex); /* start MUTEX */ /* * check for system suspended (powered down) - if so, wait for cv_broadcast * from resume code in attach routine */ while (unit_p->unit_suspended) cv_wait(&unit_p->power_cv, &unit_p->soft_mutex); /* * Only allow a single open */ if (unit_p->unit_open != 0) { DPRINT((CE_CONT, "ihcp_open: instance %d: open called when already open!\n", instance)); retval = EBUSY; } else { /* * mark device as open * * and flag as busy for power management (used in detach) */ unit_p->unit_open = 1; /* * clear unit flags - but not board type */ unit_p->unit_flags &= IHCP_CLEAR_FLAGS; /* * set default timeouts here * timeout parameters are stored as ticks */ unit_p->dma_time = (clock_t) (drv_usectohz((clock_t) 1000000) * unit_p->dma_time_def); unit_p->fifo_time = (clock_t) (drv_usectohz((clock_t) 1000000) * unit_p->rdy_time_def); DPRINT((CE_CONT, "ihcp_open: instance %d: open complete\n", instance)); } mutex_exit(&unit_p->soft_mutex); /* end MUTEX */ return (retval); } /* * ihcp_close is called after the last process that has the device open * calls close(2) * * since we enforce exclusive open, ihcp_close will always be called when * the user process calls close(2) */ /*ARGSUSED*/ static int ihcp_close(dev_t dev, int openflags, int otyp, cred_t * credp) { register struct ihcp_unit *unit_p; /*LINTED cast of getminor ok */ unit_p = (struct ihcp_unit *) ddi_get_soft_state(state_head, (int) getminor(dev)); /* * Verify instance structure - if this fails, we are really in trouble! */ if (unit_p == NULL) { cmn_err(CE_CONT, "ihcp_close: null unit_pointer!\n"); return (ENXIO); } /* * lock unit structure (and flags and registers) */ mutex_enter(&unit_p->soft_mutex); /* start MUTEX */ /* * let the next person open the device * * and flag as not busy for power management (used in detach) */ unit_p->unit_open = 0; /* * we don't reset the board and device here - that might kill a job that * was in the device but not yet complete - reset in detach */ mutex_exit(&unit_p->soft_mutex); /* end MUTEX */ DPRINT((CE_CONT, "ihcp_close: instance %d: close complete\n", (int) getminor(dev))); return (0); } /*ARGSUSED*/ static int ihcp_write(dev_t dev, struct uio *uiop, cred_t * credp) { register struct ihcp_unit *unit_p; int retval = 0; int instance; off_t temp_offset; /*LINTED cast to int ok */ instance = (int) getminor(dev); unit_p = (struct ihcp_unit *) ddi_get_soft_state(state_head, instance); DPRINT((CE_CONT, "ihcp_write: instance %d: entering write routine\n", instance)); if (unit_p == NULL) { cmn_err(CE_CONT, "ihcp_write: instance %d: unit_pointer NULL!\n", instance); return (ENXIO); } /* * we are exclusive open - we will leave the mutex for strategy * so no mutex_enter(&unit_p->soft_mutex) */ /* * clear the timeout and sig rx'd flags -- also clears "waiting for" flags * strategy will set the "waiting for dma" flag. */ unit_p->unit_flags &= IHCP_CLEAR_FLAGS; /* * save the offset in uio - we will restore it after the write * if we don't, eventually to iova counter will overflow, and the system * will return errors on further write calls * * this counter may be appropriate for "seekable" devices, but not for us! */ temp_offset = uiop->uio_offset; /* * call physio (and strategy) to start the write. NULL means use bufferr from pool */ retval = physio(ihcp_strategy, (struct buf *) NULL, dev, B_WRITE, ihcp_minphys, uiop); uiop->uio_offset = temp_offset; /* * check flags for timeout error or signal received */ if (unit_p->unit_flags & IHCP_DMA_TIMEOUT) { retval = EIO; cmn_err(CE_CONT, "ihcp_write: instance %d: DMA timeout! \n", instance); } if (unit_p->unit_flags & IHCP_SIG_RECEIVED) { retval = EINTR; cmn_err(CE_CONT, "ihcp_write: instance %d: signal received! \n", instance); } /* * we did not aquire a mutex within write so no mutex_exit(&unit_p->soft_mutex) ; */ DPRINT((CE_CONT, "ihcp_write: instance %d: leaving write routine\n", instance)); return (retval); } /* * ioctl routine - allows fiddling device and driver state, and sending device commands * * the argument arg is a pointer, and all values accessed via this pointer * ARE 32 BIT VALUES OR ARRAYS * except in the case of data_out, which transfers an array of 8 bit characters */ /*ARGSUSED*/ static int ihcp_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t * credp, int *rvalp) { register struct ihcp_unit *unit_p; /* points at our unit struct */ uint32_t bits; /* will contain incoming or outgoing argument value */ uint8_t ct; /* temp char */ uint16_t st; /* temp short */ uint32_t lt; /* temp 32 bit u_int */ int count, i; /* temp working variables */ int retval = 0; /* return value (errno) */ int instance; /* our instance */ int command; clock_t waitval; /* value returned by cv_timedwait_sig */ clock_t time_now; /* time in ticks since last re-boot */ uint32_t return_array[19]; /* will contain copy of registers to return to caller */ u_char data_array[256]; /* used to contain p-i/o data out values after copyin */ /*LINTED cast to int ok */ instance = (int) getminor(dev); unit_p = (struct ihcp_unit *) ddi_get_soft_state(state_head, instance); DPRINT((CE_CONT, "ihcp_ioctl: instance %d: entering ioctl routine, command = 0x%x\n", instance, cmd)); if (unit_p == NULL) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: unit_pointer NULL!\n", instance); return (ENXIO); } /* * lock our unit structure */ mutex_enter(&unit_p->soft_mutex); /* * get command and argument size encoded in arg */ command = cmd & IHCPIO_CMD_MASK; count = (cmd & IHCPIO_COUNT_MASK) >> 16; /* * get first word of argument (even if we are eventually doing a * copyout operation) - this saves a copyin in each case * * we can use standard copyin since we don't do recursive ioctl calls */ copyin((void *) arg, (void *) &bits, sizeof(bits)); DPRINT((CE_CONT, "ihcp_ioctl: instance %d: arg = %p, *arg = 0x%x\n", instance, arg, bits)); /* * defice mask used to strip the count and upper control bits from cases */ #define MASK IHCPIO_CMD_MASK switch (command) { case MASK & IHCPIO_DEV_RESET: /* send ~ 1us reset pulse to attached device */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at DEV_RESET\n", instance)); /* * set and reset device clear bit, with a delay so the pulse is long enough * for even slow devices * * usecwait will make the cpu delay, but doesn't guarantee that the writes won't * stack in a write buffer (although with at least one read between them * * so we will do another read before the wait to force the buffer to flush */ IHCP_PUT32(IHCP_INTERFACE_CONTROL, (IHCP_GET32(IHCP_INTERFACE_CONTROL) | RESET_DEVICE)); read_dump_0 = IHCP_GET32(IHCP_MODE); drv_usecwait((clock_t) 1); IHCP_PUT32(IHCP_INTERFACE_CONTROL, (IHCP_GET32(IHCP_INTERFACE_CONTROL) & ~RESET_DEVICE)); IHCP_SYNC_2; break; case MASK & IHCPIO_SET_CONFIG: /* set speed and if to use busy */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at SET_CONFIG\n", instance)); /* * mask to pertinent bits only * the calling program will use the old sbus speed definitions * we will have to convert them to the pci card bits here */ bits &= (IHCPIO_V_BURST | IHCPIO_BUSY_HANDSHAKE | IHCPIO_4_EDGE | IHCPIO_IGNORE_BUSY | IHCPIO_SPEED3); /* * if request is a centronics function and board is strapped for versatec - error! */ if (((bits & (IHCPIO_BUSY_HANDSHAKE | IHCPIO_4_EDGE | IHCPIO_IGNORE_BUSY)) != 0) && ((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT)) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at SET_CONFIG - attempt to set", instance); cmn_err(CE_CONT, " centronics mode on board strapped for versatec! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * if request is a versatec only function and board is strapped for centronics - error! */ if (((bits & IHCPIO_V_BURST) != 0) && ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT)) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at SET_CONFIG - attempt to set", instance); cmn_err(CE_CONT, " versatec mode on board strapped for centronics! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * shuffle bits to match new board */ lt = SPEED_MASK & (3 - ((bits & IHCPIO_SPEED3) >> 2)); if (bits & IHCPIO_BUSY_HANDSHAKE) lt |= BUSY_NOT_ACK; if (bits & IHCPIO_4_EDGE) lt |= FOUR_EDGE_HANDSHAKE; if (bits & IHCPIO_IGNORE_BUSY) lt |= IGNORE_BUSY; if (bits & IHCPIO_V_BURST) lt |= V_BURST; /* * plug into mode register - being careful not to disturb other bits */ IHCP_PUT32(IHCP_MODE, (IHCP_GET32(IHCP_MODE) & (BYTE_SWIZZLE | REVERSE_DATA_ENB)) | lt); IHCP_SYNC_0; break; case MASK & IHCPIO_SET_DMATIME: /* set dma timeout parameter */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at SET_DMATIME\n", instance)); if (bits < DMA_TIME_MIN) bits = DMA_TIME_MIN; /* not less than min */ if (bits > DMA_TIME_MAX) bits = DMA_TIME_MAX; /* or more than max */ unit_p->dma_time = (clock_t) (bits * drv_usectohz((clock_t) 1000000)); /* set ticks */ break; case MASK & IHCPIO_SET_FIFOTIME: /* set timeout for fifo <1/2 full */ /* or fifo empty & dev rdy wait */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at SET_FIFOTIME\n", instance)); if (bits < FIFO_TIME_MIN) bits = FIFO_TIME_MIN; /* not less than */ if (bits > FIFO_TIME_MAX) bits = FIFO_TIME_MAX; /* or more than.. */ unit_p->fifo_time = (clock_t) (bits * drv_usectohz((clock_t) 1000000)); /* ticks */ break; case MASK & IHCPIO_GET_REGS: /* get all the board's registers */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at GET_REGS\n", instance)); return_array[0] = unit_p->dev_and_vendor_id; /* device and vendor id */ return_array[1] = unit_p->revision_id; /* revision id */ return_array[2] = PLX_GET32(PLX_INT_CSTAT); /* interrupt control-status */ return_array[3] = PLX_GET32(PLX_EEPROM_USER); /* eeprom control and user bits */ return_array[4] = IPLX_GET32(IPLX_DMA_MODE_0); /* dma mode reg 0 */ return_array[5] = IPLX_GET32(IPLX_DMA_PCI_ADD_0); /* dma pci add 0 */ return_array[6] = IPLX_GET32(IPLX_DMA_LOC_ADD_0); /* dma local add 0 */ return_array[7] = IPLX_GET32(IPLX_DMA_COUNT_0); /* dma transfer count 0 */ return_array[8] = IPLX_GET32(IPLX_DMA_DESC_PTR_0); /* dma descriptor pointer 0 */ return_array[9] = IPLX_GET32(IPLX_DMA_CMD_STAT); /* dma command/status - both channels */ return_array[10] = IHCP_GET32(IHCP_INTERRUPT_MASK); /* ihcp interrupt mask reg */ return_array[11] = IHCP_GET32(IHCP_MODE); /* ihcp mode reg */ return_array[12] = IHCP_GET32(IHCP_DEVICE_CONTROL); /* device control reg */ return_array[13] = IHCP_GET32(IHCP_INTERFACE_CONTROL); /* interface control */ return_array[14] = IHCP_GET32(IHCP_INTERFACE_STATUS); /* interface status */ return_array[15] = IHCP_GET32(IHCP_DEVICE_STATUS); /* device status */ return_array[16] = IHCP_GET32(IHCP_REVERSE_DATA); /* reverse data in reg */ return_array[17] = IHCP_GET32(IHCP_AUTO_LTR_LOW); /* auto ltr count low byte */ return_array[18] = IHCP_GET32(IHCP_AUTO_LTR_HIGH); /* auto ltr count high byte */ copyout((void *) return_array, (void *) arg, sizeof(return_array)); break; case MASK & IHCPIO_GET_STATUS: /* get formatted version of status */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at GET_STATUS\n", instance)); /* * get device status bits */ /*LINTED cast to 8 bits ok */ ct = (uint8_t) IHCP_GET32(IHCP_DEVICE_STATUS) & (FAULT | ONLINE | PAPER_OUT | CENTRONICS_BUSY | VERSATEC_READY); /* * shuffle status bits to match sbus format */ bits = 0; if (ct & FAULT) bits |= IHCPIO_DEV_FAULT; if (ct & ONLINE) bits |= IHCPIO_DEV_SEL; if (ct & PAPER_OUT) bits |= IHCPIO_DEV_POUT; if (ct & CENTRONICS_BUSY) bits |= IHCPIO_DEV_BUSY; if (ct & VERSATEC_READY) bits |= IHCPIO_DEV_RDY; copyout((void *) &bits, (void *) arg, sizeof(uint32_t)); break; case MASK & IHCPIO_GET_BOARD: /* get board type */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at GET_BOARD\n", instance)); bits = IHCP_GET32(IHCP_DEVICE_STATUS) & INTERFACE_STRAP_MASK; copyout((void *) &bits, (void *) arg, sizeof(uint32_t)); break; case MASK & IHCPIO_SET_VMODE: /* set versatec mode - both leave rdy ff on on pci card */ case MASK & IHCPIO_SET_VMODEX: DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at SET_VMODE\n", instance)); /* * make sure the board is strapped for versatec */ if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at SET_VMODE - attempt to do versatec", instance); cmn_err(CE_CONT, " mode set on board strapped for centronics!\n"); retval = ENOTTY; goto IOCTLEXIT; } /* * this command talks to the fifo - make sure there is room * we could wait for room with an interrupt, but the way the board is to * be used, there should always be room */ if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at SET_VMODE - no room in fifo for", instance); cmn_err(CE_CONT, " versatec mode set! \n"); retval = EIO; goto IOCTLEXIT; } /* * bottom two bits contain print/plot/spp control - same for sbus and pci * command byte will be in low byte unless byte swizzling selected */ lt = SET_VERSATEC_MODE | (bits & 0x03); if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_1; /* * save mode in unit structure */ unit_p->mode = bits & 0x03; break; case MASK & IHCPIO_V_CMD: /* issue versatec pulse command */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at V_CMD\n", instance)); /* * make sure the board is strapped for versatec - can't do this w/centr */ if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at V_CMD - attempt to issue versatec", instance); cmn_err(CE_CONT, " command to board strapped for centronics!\n"); retval = ENOTTY; goto IOCTLEXIT; } /* * this command talks to the fifo - make sure there is room !! */ if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at V_CMD - no room in fifo for", instance); cmn_err(CE_CONT, " versatec command! \n"); retval = EIO; goto IOCTLEXIT; } /* * bottom two bits contain command - we have to shuffle them * for compatibility with sbus driver */ if (bits == IHCPIO_VLTR) lt = VERSATEC_LINE_TERM; if (bits == IHCPIO_VEOT) lt = VERSATEC_EOT; if (bits == IHCPIO_VFED) lt = VERSATEC_FORM_FEED; if (bits == IHCPIO_VCLR) lt = VERSATEC_CLEAR; DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at V_CMD, command sent = 0x%x\n", instance, lt)); if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_0; break; case MASK & IHCPIO_DATA_OUT: /* output chars from arg - # of chars in bottom of cmd */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at DATA_OUT, byte count = 0x%x\n", instance, count)); /* * make sure # of characterss is <=255 * that is the max # of characters in arg array - for historical reasons * * we will check for room in fifo before each byte but will not wait on * an interrupt if the fifo is full - we will flag an error and return * * each byte will be sent by writing to the 8 bit data out register * this is much less effecient than writing quad bytes to the 32 bit * data out register, or to the burst out range * * it is not the intent of this driver to use this ioctl for large data * blocks, or frequent data transfers - those funtions should be served via * the write call * * this ioctl should be used for the output of a few bytes - perhaps as a * header to a write call, and should be used between write calls, or waits for * fifo less than half full * * if this ioctl is used repeatedly, without interspersed dma transfers, which * leave the fifo approximately half full, or without wait for half full ioctls * the fifo may eventually overflow * * it would be possible to incorporate a wait for less than half full interrupt * in the data out loop, but that would be overkill, considering the intended * use of this ioctl */ if (count > 255) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at DATA_OUT - too many bytes!\n", instance); retval = EINVAL; goto IOCTLEXIT; } copyin((void *) arg, (void *) data_array, count); for (i = 0; i < count; i++) { if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { retval = EIO; cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at DATA_OUT - fifo full while", instance); cmn_err(CE_CONT, " transferring data bytes!\n"); cmn_err(CE_CONT, "0x%x bytes requested, 0x%x bytes transferred\n", count, i); break; } lt = data_array[i]; if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_8_BIT_DATA_OUT, lt); IHCP_SYNC_2; } break; case MASK & IHCPIO_RDY_WAIT: /* wait for rdy & fifo empty - or timeout */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at RDY_WAIT, wait time = %d (ticks)\n", instance, unit_p->fifo_time)); /* * if already ready and fifo empty, just return * first clear any stale flags */ unit_p->unit_flags &= IHCP_CLEAR_FLAGS; if (IHCP_GET32(IHCP_INTERFACE_STATUS) & DEVICE_AND_INT_READY) goto IOCTLEXIT; /* * ddi_get_lbolt returns current time since last reboot - in ticks */ time_now = ddi_get_lbolt(); /* * set flags to indicate for what we wait */ unit_p->unit_flags |= IHCP_RDY_WAIT; /* * make sure no left over interrupt flag, then * enable plx and ihcp interrupts for device and interface ready * we don't or into ihcp or plx mask reg since we know exactly which ints to enb */ IHCP_PUT32(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_0; IHCP_PUT32(IHCP_INTERRUPT_MASK, DEV_AND_INT_RDY_INT_ENB); PLX_PUT32(PLX_INT_CSTAT, (PCI_INTERRUPT_ENABLE | PCI_LOCAL_INT_ENABLE)); PLX_SYNC_0; /* * snooze until ready interrupt, timeout, or signal */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at RDY_WAIT, time_now = %d, unit_p->fifo_time = %d\n", instance, time_now, unit_p->fifo_time)); waitval = cv_timedwait_sig(&unit_p->cv, &unit_p->soft_mutex, (clock_t) (time_now + unit_p->fifo_time)); /* * make sure interrupt enables are off * and clear interrupt flag in case we got here via timeout just * as interrupt set the flag */ IHCP_PUT32(IHCP_INTERRUPT_MASK, 0); PLX_PUT32(PLX_INT_CSTAT, 0); IHCP_SYNC_1; IHCP_PUT32(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_2; /* * find out why we woke up */ if (waitval < 0) { /* * timeout may not be error so no error message - caller can test * flags for timeout * * we will set flag bit to stay compatible with sbus driver that had * its own timeout routine */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at RDY_WAIT, timeout while waiting!\n", instance)); unit_p->unit_flags |= IHCP_RDY_TIMEOUT; retval = EIO; goto IOCTLEXIT; } if (waitval == 0) { /* * signal is not error but send to console log * return EINTR so caller can decide what to do */ cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at RDY_WAIT - signal while waiting", instance); cmn_err(CE_CONT, " for ready & fifo empty!\n"); unit_p->unit_flags |= IHCP_SIG_RECEIVED; retval = EINTR; goto IOCTLEXIT; } break; case MASK & IHCPIO_HALF_WAIT: /* wait for fifo fifo_time)); /* * if already less than half full, just return * first clear any stale flags */ unit_p->unit_flags &= IHCP_CLEAR_FLAGS; if (IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_HALF_FULL) goto IOCTLEXIT; /* * ddi_get_lbolt returns current time since last reboot - in ticks */ time_now = ddi_get_lbolt(); /* * set flags to indicate for what we wait */ unit_p->unit_flags |= IHCP_HALF_WAIT; /* * clear any left over interrupt flag, then * enable plx and ihcp interrupt for fifo less than half full * we don't or into ihcp or plx mask reg since we know exactly which ints to enb */ IHCP_PUT32(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_0; IHCP_PUT32(IHCP_INTERRUPT_MASK, FIFO_NOT_HALF_INT_ENB); PLX_PUT32(PLX_INT_CSTAT, (PCI_INTERRUPT_ENABLE | PCI_LOCAL_INT_ENABLE)); IHCP_SYNC_1; /* * snooze until ready interrupt, timeout, or signal */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at HALF_WAIT, time_now = %d, unit_p->fifo_time = %d\n", instance, time_now, unit_p->fifo_time)); waitval = cv_timedwait_sig(&unit_p->cv, &unit_p->soft_mutex, (clock_t) (time_now + unit_p->fifo_time)); /* * make sure interrupt enables are off and clear flag in case we * got here via timeout just as interrupt happened */ IHCP_PUT32(IHCP_INTERRUPT_MASK, 0); PLX_PUT32(PLX_INT_CSTAT, 0); IHCP_SYNC_2; IHCP_PUT32(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_3; /* * find out why we woke up */ if (waitval < 0) { /* * timeout may not be error so no error message - caller can test * flags for timeout * * we will set flag bit to stay compatible with sbus driver that had * its own timeout routine */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at HALF_WAIT, timeout while waiting!\n", instance)); unit_p->unit_flags |= IHCP_HALF_TIMEOUT; retval = EIO; goto IOCTLEXIT; } if (waitval == 0) { /* * signal is not error but send to console log * return EINTR so caller can decide what to do */ cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at HALF_WAIT - signal while waiting", instance); cmn_err(CE_CONT, " for fifo not half full!\n"); unit_p->unit_flags |= IHCP_SIG_RECEIVED; retval = EINTR; goto IOCTLEXIT; } break; case MASK & IHCPIO_STREAM_ON: /* set streaming mode if 10106 */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at STREAM_ON\n", instance)); if ((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at STREAM_ON - attempt to set streaming", instance); cmn_err(CE_CONT, " mode on board strapped for versatec! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * this command talks to the fifo - make sure there is room !! */ if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at STREAM_ON - no room in fifo for", instance); cmn_err(CE_CONT, " stream on command! \n"); retval = EIO; goto IOCTLEXIT; } lt = SET_IO_MODE | DATA_STREAMING_MODE; if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_0; /* * save streaming mode for compatibility with old vme ioctls */ unit_p->data_stream_mode = DATA_STREAMING_ON; break; case MASK & IHCPIO_STREAM_OFF: /* turn streaming off (if 10106) */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at STREAM_OFF\n", instance)); if ((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at STREAM_OFF - attempt to clear", instance); cmn_err(CE_CONT, " streaming mode on board strapped for versatec! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * this command talks to the fifo - make sure there is room !! */ if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at STREAM_OFF - no room in fifo for", instance); cmn_err(CE_CONT, " stream off command! \n"); retval = EIO; goto IOCTLEXIT; } /* * set_io_mode w/ nothing else or'd in turns off streaming * and auto ltr - we won't try to do streaming and auto ltr * at same time in this driver */ lt = SET_IO_MODE; if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_1; /* * save streaming mode for compatibility with old vme ioctls */ unit_p->data_stream_mode = DATA_STREAMING_OFF; break; case MASK & IHCPIO_GET_FLAGS: /* get unit flag 32 bit longword */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at GET_FLAGS\n", instance)); copyout((void *) &unit_p->unit_flags, (void *) arg, sizeof(uint32_t)); break; case MASK & IHCPIO_GET_FIFO: /* get formatted fifo flags */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at GET_FIFO\n", instance)); /* * get fifo status bits and swizzle and complement * to match sbus driver format */ lt = IHCP_GET32(IHCP_INTERFACE_STATUS); bits = 0; if (!(lt & FIFO_NOT_FULL)) bits |= IHCPIO_FIFO_FULL; if (!(lt & FIFO_NOT_HALF_FULL)) bits |= IHCPIO_FIFO_HALF; if (lt & FIFO_EMPTY) bits |= IHCPIO_FIFO_EMPTY; copyout((void *) &bits, (void *) arg, sizeof(uint32_t)); break; case MASK & IHCPIO_MASTER_CLEAR: /* master clear board - not for use by wimps! */ /* clear ihcp logic and restore all defaults */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at MASTER_CLEAR\n", instance)); IHCP_PUT32(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_0; /* * make absolutely sure that dma and interrupts are off in plx chip */ PLX_PUT32(PLX_INT_CSTAT, 0); IPLX_PUT32(IPLX_DMA_CMD_STAT, 0); IHCP_SYNC_1; /* * set and clear mclr bit - other bits in this register are also * treated like pulses, and do not persist, so we don't need to * or in a bit, then and it out */ IHCP_PUT32(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_SYNC_2; IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_3; /* * set and clear the device reset bit, with a delay and write * buffer flush to guarantee the delay * * the mode read between puts should force the write buffer to flush */ IHCP_PUT32(IHCP_INTERFACE_CONTROL, (IHCP_GET32(IHCP_INTERFACE_CONTROL) | RESET_DEVICE)); read_dump_0 = IHCP_GET32(IHCP_MODE); drv_usecwait((clock_t) 1); IHCP_PUT32(IHCP_INTERFACE_CONTROL, (IHCP_GET32(IHCP_INTERFACE_CONTROL) & ~RESET_DEVICE)); IHCP_SYNC_0; /* * set to default print mode, default handshake speed, and default * byte ordering * * we don't have to check for fifo space, since we just cleared it * * print mode is set only if the board is strapped for versatec * */ /*CONSTCOND*/ if (ORDER_DEF == HIGH_BYTE_FIRST) { IHCP_PUT32(IHCP_MODE, (IHCP_GET32(IHCP_MODE) | BYTE_SWIZZLE)); PLX_PUT32(PLX_EEPROM_USER, (PLX_GET32(PLX_EEPROM_USER) & ~USER_OUTPUT)); unit_p->byte_order = ORDER_DEF; IHCP_SYNC_1; } if ((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { /* * versatec - set speed and print/plot mode */ IHCP_PUT32(IHCP_MODE, ((IHCP_GET32(IHCP_MODE) & ~SPEED_MASK) | unit_p-> vers_speed_def)); IHCP_SYNC_0; lt = SET_VERSATEC_MODE | unit_p->mode_def; if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_1; unit_p->mode = unit_p->mode_def; } else { /* * centronics - set speed only */ IHCP_PUT32(IHCP_MODE, ((IHCP_GET32(IHCP_MODE) & ~SPEED_MASK) | unit_p-> cent_speed_def)); IHCP_SYNC_3; /* * clear saved data streaming mode */ unit_p->data_stream_mode = DATA_STREAMING_OFF; } break; case MASK & IHCPIO_SOFT_ACK: /* software ack - looks like an ack from the dev */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at SOFT_ACK\n", instance)); /* * set and clear soft ack bit, as in mclr */ IHCP_PUT32(IHCP_INTERFACE_CONTROL, SOFTWARE_ACK); IHCP_SYNC_0; IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_1; break; case MASK & IHCPIO_AUTO_LTR_COUNT: /* set byte count for auto ltr function */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at AUTO_LTR_COUNT, byte count = %d\n", instance, bits)); if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at AUTO_LTR_COUNT - attempt to set", instance); cmn_err(CE_CONT, " auto ltr count on board strapped for centronics! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * auto ltr count is 16 bit register, accessed as two * separate bytes * * the count register takes count minus 1 */ IHCP_PUT32(IHCP_AUTO_LTR_LOW, (bits - 1) & 0xFF); IHCP_PUT32(IHCP_AUTO_LTR_HIGH, ((bits - 1) >> 8) & 0xFF); IHCP_SYNC_3; break; case MASK & IHCPIO_AUTO_LTR_ON: /* enable auto ltr operation */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at AUTO_LTR_ON\n", instance)); if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at AUTO_LTR_ON - attempt to set", instance); cmn_err(CE_CONT, " auto ltr mode on board strapped for centronics! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * this command talks to the fifo - make sure there is room !! */ if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at AUTO_LTR_ON - no room in fifo for", instance); cmn_err(CE_CONT, " auto ltr on command! \n"); retval = EIO; goto IOCTLEXIT; } lt = SET_IO_MODE | AUTO_LTR_MODE; if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_0; break; case MASK & IHCPIO_AUTO_LTR_OFF: /* disable auto ltr operation */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at AUTO_LTR_OFF\n", instance)); if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at AUTO_LTR_OFF - attempt to clear", instance); cmn_err(CE_CONT, " auto ltr mode on board strapped for centronics! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * this command talks to the fifo - make sure there is room !! */ if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at AUTO_LTR_OFF - no room in fifo for", instance); cmn_err(CE_CONT, " auto ltr off command! \n"); retval = EIO; goto IOCTLEXIT; } /* * set io mode with no other bits clears auto ltr and data streaming */ lt = SET_IO_MODE; if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_1; break; case MASK & IHCPIO_DEV_AND_VEND_ID: /* return vendor id in low 16 bits, device id in high 16 bits */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at DEV_AND_VEND_ID\n", instance)); bits = unit_p->dev_and_vendor_id; copyout((void *) &bits, (void *) arg, sizeof(uint32_t)); break; case MASK & IHCPIO_REVISION_ID: /* return board revision id */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at REVISION_ID\n", instance)); bits = unit_p->revision_id; copyout((void *) &bits, (void *) arg, sizeof(uint32_t)); break; case MASK & IHCPIO_LITTLE_ENDIAN: /* data will go to plotter low byte first */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at LITTLE_ENDIAN\n", instance)); /* * low first byte ordering (the hardware default) is set by turning off * the byte swizzle bit in the ihcp logic, and TURNING ON * the user output bit in the plx logic] */ IHCP_PUT32(IHCP_MODE, (IHCP_GET32(IHCP_MODE) & ~BYTE_SWIZZLE)); PLX_PUT32(PLX_EEPROM_USER, (PLX_GET32(PLX_EEPROM_USER) | USER_OUTPUT)); PLX_SYNC_0; unit_p->byte_order = LOW_BYTE_FIRST; break; case MASK & IHCPIO_BIG_ENDIAN: /* data will go to plotter high byte first */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at BIG_ENDIAN\n", instance)); /* * turn on the ihcp byte swizzle bit and TURN OFF the plx * user output bit */ IHCP_PUT32(IHCP_MODE, (IHCP_GET32(IHCP_MODE) | BYTE_SWIZZLE)); PLX_PUT32(PLX_EEPROM_USER, (PLX_GET32(PLX_EEPROM_USER) & ~USER_OUTPUT)); PLX_SYNC_1; unit_p->byte_order = HIGH_BYTE_FIRST; break; case MASK & IHCPIO_DIRECT_MODE: /* direct user write of mode register - use carefully! */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at DIRECT_MODE\n", instance)); IHCP_PUT32(IHCP_MODE, bits); IHCP_SYNC_0; break; case MASK & IHCPIO_DEVICE_CONTROL: /* direct write to device control register */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at DEVICE_CONTROL\n", instance)); IHCP_PUT32(IHCP_DEVICE_CONTROL, bits); IHCP_SYNC_1; break; case MASK & IHCPIO_INTERFACE_STATUS: /* direct read of interface status register */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at INTERFACE_STATUS\n", instance)); bits = IHCP_GET32(IHCP_INTERFACE_STATUS); copyout((void *) &bits, (void *) arg, sizeof(uint32_t)); break; case MASK & IHCPIO_DEVICE_STATUS: /* direct read of device status register */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at DEVICE_STATUS\n", instance)); bits = IHCP_GET32(IHCP_DEVICE_STATUS); copyout((void *) &bits, (void *) arg, sizeof(uint32_t)); break; case MASK & IHCPIO_REVERSE_DATA: /* direct read of reverse data register - requires */ /* fiddling of tri-state bit, and others to actually */ /* do anything useful with this */ DPRINT((CE_CONT, "ihcp_ioctl: istance %d: at REVERSE_DATA\n", instance)); bits = IHCP_GET32(IHCP_REVERSE_DATA); copyout((void *) &bits, (void *) arg, sizeof(uint32_t)); break; /* * ioctls for compatibility with versatec and sun drivers for VME and Sbus boards */ case MASK & LPSETVERSATEC: /* compatibility - set versatec port */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at LPSETVERSATEC\n", instance)); /* * make sure the board is a versatec type - can't do this w/centr */ if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPSETVERSATEC - attempt to select", instance); cmn_err(CE_CONT, " versatec port on board strapped for centronics! \n"); retval = ENOTTY; goto IOCTLEXIT; } break; case MASK & LPSETCENTRONICS: /* compatibility - set centr port */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at LPSETCENTRONICS\n", instance)); if ((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPSETCENTRONICS - attempt to select", instance); cmn_err(CE_CONT, " centronics port on board strapped for versatec! \n"); retval = ENOTTY; goto IOCTLEXIT; } break; case MASK & LPCOMMAND: /* compatibility - old style cmds */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at LPCOMMAND\n", instance)); /* * as much as possible, allow multiple command bits in the argument. * this is probably not the way it was intended, but we want to * protect ourselves against non-reasonable uses of the old calls */ /* * assert long reset signal */ if (bits & LPC_LRST) IHCP_PUT32(IHCP_INTERFACE_CONTROL, IHCP_GET32(IHCP_INTERFACE_CONTROL) | RESET_DEVICE); /* * de-assert long reset */ if (bits & LPC_DRST) IHCP_PUT32(IHCP_INTERFACE_CONTROL, IHCP_GET32(IHCP_INTERFACE_CONTROL) & ~RESET_DEVICE); IHCP_SYNC_0; /* * test for option port request - better be strapped for centronics!! */ if (bits & LPC_SOPT) if ((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPCOMMAND - attempt to select", instance); cmn_err(CE_CONT, " centronics port on board strapped for versatec! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * test for versatec port selection */ if (bits & LPC_SVPT) if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPCOMMAND - attempt to select", instance); cmn_err(CE_CONT, " versatec port board strapped for centronics! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * enable data streaming - if board is strapped for centronics! */ if (bits & LPC_DSTR) { if ((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPCOMMAND - attempt to set", instance); cmn_err(CE_CONT, " streaming mode on board strapped for versatec! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * this command talks to the fifo - make sure there is room !! */ if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPCOMMAND - no room in fifo for", instance); cmn_err(CE_CONT, " stream on command! \n"); retval = EIO; goto IOCTLEXIT; } lt = SET_IO_MODE | DATA_STREAMING_MODE; if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_0; /* * save for posterity */ unit_p->data_stream_mode = DATA_STREAMING_ON; } /* reset data streaming bit */ if (bits & LPC_DDST) { if ((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPCOMMAND - attempt to clear", instance); cmn_err(CE_CONT, " streaming mode on non-centronics unit! \n"); retval = ENOTTY; goto IOCTLEXIT; } if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPCOMMAND - no room in fifo for", instance); cmn_err(CE_CONT, " stream off command! \n"); retval = EIO; goto IOCTLEXIT; } /* * set_io_mode w/ nothing else or'd in turns off streaming * and auto ltr - we won't try to do streaming and auto ltr * at same time in this driver */ lt = SET_IO_MODE; if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_1; /* * save streaming mode for later getregs call */ unit_p->data_stream_mode = DATA_STREAMING_OFF; } /* * test for versatec print/plot mode changes - make sure we are * a versatec board !! */ if (bits & LPC_PRINTMASK) { if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPCOMMAND - attempt to do", instance); cmn_err(CE_CONT, " versatec mode set on board strapped for centronics! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * this cmd talks to the fifo - make sure there is room !! */ if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPCOMMAND - no room in fifo", instance); cmn_err(CE_CONT, " for versatec mode set! \n"); retval = EIO; goto IOCTLEXIT; } /* * get current mode setting */ lt = unit_p->mode; /* now look at individual command bits and twiddle mode */ if (bits & LPC_VSPP) lt |= SPP_MODE; /* set spp mode */ if (bits & LPC_DSPP) lt &= ~SPP_MODE; /* clear spp */ if (bits & LPC_VPLT) lt |= PLOT_MODE; /* set plot mode */ if (bits & LPC_DVPT) lt &= ~PLOT_MODE; /* clear plot */ if (bits & LPC_PRNT) lt = NORM_PRINT_MODE; /* 0 = normal print mode */ /* * save final mode - without mode set command bit - * in unit structure */ unit_p->mode = lt; /* * write final mode to fifo - remember to write to high * byte if big-endian data mode selected */ lt |= SET_VERSATEC_MODE; if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_2; } /* * check for software ack pulse request */ if (bits & LPC_SACK) { /* * set and clear soft ack bit, as in mclr */ IHCP_PUT32(IHCP_INTERFACE_CONTROL, SOFTWARE_ACK); IHCP_SYNC_0; IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_1; } /* * check for master clear request - the old vme board also issued a reset * to the device when mclr was pulsed, so we will do that also * * we will also restore the default print/plot mode (if versatec), * the device handshake speed, and the byte ordering */ if (bits & LPC_MCLR) { IHCP_PUT32(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_SYNC_0; IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_1; /* * make absolutely sure that dma and interrupts are off in plx chip */ PLX_PUT32(PLX_INT_CSTAT, 0); IPLX_PUT32(IPLX_DMA_CMD_STAT, 0); PLX_SYNC_0; /* * set and clear mclr bit - other bits in this register are also * treated like pulses, and do not persist, so we don't need to * or in a bit, then and it out */ IHCP_PUT32(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_SYNC_0; IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_1; /* * set and clear the device reset bit, with a delay and write * buffer flush to guarantee the delay * * the mode read between puts should force the write buffer to flush */ IHCP_PUT32(IHCP_INTERFACE_CONTROL, (IHCP_GET32(IHCP_INTERFACE_CONTROL) | RESET_DEVICE)); read_dump_0 = IHCP_GET32(IHCP_MODE); drv_usecwait((clock_t) 1); IHCP_PUT32(IHCP_INTERFACE_CONTROL, (IHCP_GET32(IHCP_INTERFACE_CONTROL) & ~RESET_DEVICE)); IHCP_SYNC_0; /* * set to default print mode, default handshake speed, and default * byte ordering * * we don't have to check for fifo space, since we just cleared it * * print mode is set only if the board is strapped for versatec * */ /*CONSTCOND*/ if (ORDER_DEF == HIGH_BYTE_FIRST) { IHCP_PUT32(IHCP_MODE, (IHCP_GET32(IHCP_MODE) | BYTE_SWIZZLE)); PLX_PUT32(PLX_EEPROM_USER, (PLX_GET32(PLX_EEPROM_USER) & ~USER_OUTPUT)); unit_p->byte_order = ORDER_DEF; PLX_SYNC_0; } if ((unit_p->unit_flags & IHCP_BOARD_MASK) != IHCPIO_CENT) { /* * versatec - set speed and print/plot mode */ IHCP_PUT32(IHCP_MODE, ((IHCP_GET32(IHCP_MODE) & ~SPEED_MASK) | unit_p-> vers_speed_def)); lt = SET_VERSATEC_MODE | unit_p->mode_def; if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_0; unit_p->mode = unit_p->mode_def; } else { /* * centronics - set speed only */ IHCP_PUT32(IHCP_MODE, ((IHCP_GET32(IHCP_MODE) & ~SPEED_MASK) | unit_p-> cent_speed_def)); IHCP_SYNC_3; } /* * clear saved data streaming mode */ unit_p->data_stream_mode = DATA_STREAMING_OFF; } /* * look for pulsed command request that has to go through fifo */ if (bits & (LPC_VCLR | LPC_VTFF | LPC_VEOT | LPC_VLTR)) { /* make sure the board is a versatec type - can't do w/centr */ if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPCOMMAND - attempt to do", instance); cmn_err(CE_CONT, " versatec pulsed command to board strapped for centronics! \n"); retval = ENOTTY; goto IOCTLEXIT; } /* * this cmd talks to the fifo - make sure there is room !! * WE WILL ASSUME THAT THERE WILL ONLY BE ONE PULSE PER CALL */ if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { cmn_err(CE_CONT, "ihcp_ioctl: instance %d: at LPCOMMAND - no room in fifo", instance); cmn_err(CE_CONT, " for versatec pulsed command! \n"); retval = EIO; goto IOCTLEXIT; } /* * shuffle vme style commands to match pci board */ if (bits & LPC_VLTR) lt = VERSATEC_LINE_TERM; if (bits & LPC_VEOT) lt = VERSATEC_EOT; if (bits & LPC_VTFF) lt = VERSATEC_FORM_FEED; if (bits & LPC_VCLR) lt = VERSATEC_CLEAR; if (unit_p->byte_order == HIGH_BYTE_FIRST) lt = lt << 24; IHCP_PUT32(IHCP_COMMAND_OUT, lt); IHCP_SYNC_0; } break; case MASK & LPGETREGS: /* old style (VMEbus) get regs command */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at LPGETREGS\n", instance)); /* * start work on interface status register */ st = 0; /* * get interface status register * and fiddle interface and device ready bits */ /*LINTED cast to 8 bits ok */ ct = (uint8_t) IHCP_GET32(IHCP_INTERFACE_STATUS); if (ct & DEVICE_AND_INT_READY) st |= (uint16_t) OLD_DIRY; if (ct & DEVICE_READY) st |= (uint16_t) OLD_DVRY; /* * get remembered data streaming mode */ if (unit_p->data_stream_mode == DATA_STREAMING_ON) st |= (uint16_t) OLD_DSTR; /* * get board type from unit struct * if centronics, flag as old style option port */ if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) st |= (uint16_t) OLD_SOPT; /* * put result in top of 32 bit temp */ lt = (uint32_t) st << 16; /* * do the same ugly things with the device status register -- * it has two halves: versatec and centronics -- only * work on the appropriate half */ st = 0; if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_CENT) { /* * work on centronics part * get the device status register and * shuffle bit positions and polarities */ /*LINTED cast to 8 bits ok */ ct = (uint8_t) IHCP_GET32(IHCP_DEVICE_STATUS); if (ct & VERSATEC_READY) st |= (uint16_t) OLD_OACK; if (!(ct & CENTRONICS_BUSY)) st |= (uint16_t) OLD_ONBY; if (!(ct & PAPER_OUT)) st |= (uint16_t) OLD_OPPR; if (!(ct & ONLINE)) st |= (uint16_t) OLD_ONSL; if (ct & FAULT) st |= (uint16_t) OLD_OFLT; /* * look for device reset latch */ if (IHCP_GET32(IHCP_INTERFACE_CONTROL) & RESET_DEVICE) st |= (uint16_t) OLD_OLRS; /* long reset */ } else { /* * work on versatec part */ if ((unit_p->unit_flags & IHCP_BOARD_MASK) == IHCPIO_VERS_TTL) st |= (uint16_t) OLD_VTTL; /* * get device status and twiddle bits */ /*LINTED cast to 8 bits ok */ ct = (uint8_t) IHCP_GET32(IHCP_DEVICE_STATUS); if (ct & VERSATEC_READY) st |= (uint16_t) OLD_VRDY; /* vers ready */ if (ct & PAPER_OUT) st |= (uint16_t) OLD_VPPR; /* paper ok */ if (ct & ONLINE) st |= (uint16_t) OLD_VONL; /* vers onlin */ /* * get current mode from unit structure */ /*LINTED cast to 8 bits ok */ ct = (uint8_t) unit_p->mode; if (ct & SPP_MODE) st |= (uint16_t) OLD_VSPP; /* spp mode */ if (ct & PLOT_MODE) st |= (uint16_t) OLD_VPLT; /* plot mode */ } lt |= st; /* save in bottom of 32 bit temp */ copyout((void *) <, (void *) arg, sizeof(uint32_t)); break; case MASK & LPSETTIMVAL: /* set timeout value - old style - for dma and fifo empty */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at LPSETTIMVAL\n", instance)); /* * old style timeout call used fiftieths of a second as argument */ if (bits < DMA_TIME_MIN * 50) bits = DMA_TIME_MIN * 50; if (bits > DMA_TIME_MAX * 50) bits = DMA_TIME_MAX * 50; unit_p->fifo_time = (clock_t) ((bits * drv_usectohz((clock_t) 1000000)) / 50); unit_p->dma_time = (clock_t) ((bits * drv_usectohz((clock_t) 1000000)) / 50); break; case MASK & LPGETTIMVAL: /* get current timeout (in ticks ?) */ DPRINT((CE_CONT, "ihcp_ioctl: instance %d: at LPGETTIMVAL\n", instance)); /* * return # of fiftieths of a second */ /*LINTED cast to 32 bits ok */ lt = (uint32_t) ((unit_p->dma_time / drv_usectohz((clock_t) 1000000)) * 50); copyout((void *) <, (void *) arg, sizeof(uint32_t)); break; default: /* * we don't print an error message here * if we are involved in a pipe, the pipe logic may send * us a generic ioctl to determine if we are a terminal * we have to return an error or we will give a false * terminal indication. */ retval = EINVAL; break; } IOCTLEXIT: mutex_exit(&unit_p->soft_mutex); return (retval); } /* * this is the high level interrupt catcher * it quiets the board down and requests a soft interrupt * * the soft interrupt handler actually does the cv_sig to wake up * the top half of the driver * * this was done to avoid recursive mutex panics if the interrupt * happened to be at the same level as the scheduler * * the argument is a pointer at the soft state structure for this instance * * we aren't allowed to call any system services from within a high level * interrupt handler - except mutex_enter and _exit, and ddi_trigger_softintr */ static u_int ihcp_high_intr(caddr_t arg) { struct ihcp_unit *unit_p; uint32_t temp; /* just what it says */ u_int int_serviced = DDI_INTR_UNCLAIMED; int soft_intr_needed = 0; /*LINTED pointer cast ok */ unit_p = (struct ihcp_unit *) arg; /* * grab high level mutex - locks out soft int routine */ mutex_enter(&unit_p->high_mutex); /* * clear flag that tells soft intr routine that we called it * soft intr code may get polled for other reasons */ unit_p->soft_intr_req = 0; /* * get plx interrupt control/status register and check for interrupt * * check for master interrupt enable bit and either local int rq or dma int rq */ temp = IPLX_GET32(IPLX_INT_CSTAT); if ((temp & PCI_INTERRUPT_ENABLE) && ((temp & LOCAL_INTERRUPT) || (temp & DMA_0_INTERRUPT))) { /* * let the kernel polling routine know that we accept the interrupt * and flag the need to issue a soft intr request - this could probably be * done within the mutex, but the sample code does it outside for * some reason, and we will follow the sample * * also let our soft intr routine that it has been specifically * triggered, not just polled because of someone else's soft interrupt */ int_serviced = DDI_INTR_CLAIMED; soft_intr_needed = 1; unit_p->soft_intr_req = 1; /* * force off dma enable, interrupt enables, and ihcp interrupt mask bits * which shoule release the interrupt request * * then (after masks are off) toggle reset int flag bit on & off to clear * interrupt flag (which is a latch) * and do the same to the dma done interrupt */ IPLX_PUT32(IPLX_DMA_CMD_STAT, 0); PLX_PUT32(PLX_INT_CSTAT, 0); IHCP_PUT32(IHCP_INTERRUPT_MASK, 0); IHCP_SYNC_0; IPLX_PUT32(IPLX_DMA_CMD_STAT, DMA_CLEAR_INTERRUPT); IHCP_PUT32(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_1; /* * do register reads to get the previous writes to flush to the pci bus * so the writes that clear the interrupt will be complete before we * exit - we will hope that the optimizer doesn't delete these! */ read_dump_0 = PLX_GET32(PLX_EEPROM_USER); read_dump_1 = IHCP_GET32(IHCP_MODE); /* * in the sbus handler, we did a drv_usecwait here to give * the interrupt line time to rise * * we would do that here, but we aren't allowed to call system services * * the _GETL will take some time to complete since the plx chip is fairly * slow for single reads, so there should be plenty of time for the * interrupt line to rise */ } mutex_exit(&unit_p->high_mutex); /* * if we decided it was our interrupt, trigger a soft interrupt */ if (soft_intr_needed) ddi_trigger_softintr(unit_p->soft_intr_id); return (int_serviced); } /* * this is the soft interrupt routine that issues the actual wakeup signal * to strategy or ioctl * * we do it this round-about way because the "bios" may assign us an interrupt * priority (based on class code???) that is the same as the scheduler * * this may cause a recursive mutex enter panic if the interrupt code ends * up running on the same thread as the waiting code */ static u_int ihcp_soft_intr(caddr_t arg) { struct ihcp_unit *unit_p; u_int int_serviced = DDI_INTR_UNCLAIMED; int signal_needed = 0; /*LINTED pointer cast ok */ unit_p = (struct ihcp_unit *) arg; DPRINT((CE_CONT, "ihcp_soft_intr: instance %d: entering soft_intr\n", unit_p->saved_instance)); /* * get high level mutex and test for our soft interrupt */ mutex_enter(&unit_p->high_mutex); if (unit_p->soft_intr_req) { DPRINT((CE_CONT, "ihcp_soft_intr: instance %d: processing int rq\n", unit_p->saved_instance)); /* * turn off soft interrupt required flag so we don't accept * a soft intr later that isn't ours */ unit_p->soft_intr_req = 0; /* * tell later code to signal waiting routine * and indicate to soft intr polling code that * we accept the interrupt */ signal_needed = 1; int_serviced = DDI_INTR_CLAIMED; } mutex_exit(&unit_p->high_mutex); if (signal_needed) { DPRINT((CE_CONT, "ihcp_soft_intr: instance %d: sending signal\n", unit_p->saved_instance)); /* * grab the low level mutex so we can safely signal * the top half of the driver */ mutex_enter(&unit_p->soft_mutex); cv_signal(&unit_p->cv); mutex_exit(&unit_p->soft_mutex); } return (int_serviced); } static int ihcp_strategy(struct buf *bp) { register struct ihcp_unit *unit_p; /* points to unit structure */ int instance; ddi_dma_cookie_t dma_cookie; u_int cookie_count; caddr_t mapped_address; /* what we will use internal to the driver */ uint32_t dma_address; /* modified(maybe) cookie address to pass to the board */ uint32_t dma_count; /* count that we will pass to the board */ int index; /* useful counter/pointer */ u_int count; /* also useful counter */ uint32_t temp; /* useful temporary */ clock_t time_now; /* for timeout calculations */ clock_t waitval; /* returned from cv_timedwait_sig */ /* * get instance number from buffer and get pointer to soft state */ /*LINTED cast to int ok */ instance = (int) getminor(bp->b_edev); unit_p = (struct ihcp_unit *) ddi_get_soft_state(state_head, instance); DPRINT((CE_CONT, "ihcp_strategy: instance %d: entering strategy\n", instance)); /* * lock unit structure and pointers */ mutex_enter(&unit_p->soft_mutex); /* * save buffer pointer in unit structure for error returns from interrupt routine */ unit_p->buf_p = bp; /* * check byte count size against what we specified in dma attribute structure - we need * to make sure that our max size isn't exceeded, especially in this first non-chaining * version of the driver */ if ((bp->b_bcount > dma_attr.dma_attr_maxxfer) || (bp->b_bcount == 0)) { cmn_err(CE_CONT, "ihcp_strategy: instance %d: DVMA byte count zero or too large! \n", instance); bp->b_error |= EINVAL; bp->b_flags |= B_ERROR; goto EXITSTRATEGY; } /* * we will map the buffer into kernel space so we can access it * from within the driver code - this is necessary since we may have to * write individual bytes to fifo to handle un-aligned buffers or non * mod 4 byte counts * * i am assured that this will not confuse the later dma mapping * remember to map_out when done or if error! */ bp_mapin(bp); mapped_address = bp->b_un.b_addr; /* * bind dma handle and get dma cookie for dma hardware */ if (ddi_dma_buf_bind_handle (unit_p->dma_handle, bp, DDI_DMA_WRITE | DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, (caddr_t) 0, &dma_cookie, &cookie_count) != DDI_DMA_MAPPED) { bp->b_error |= EIO; bp->b_flags |= B_ERROR; bp_mapout(bp); cmn_err(CE_CONT, "ihcp_strategy: instance %d: ddi_dma_buf_bind_handle failure!\n", instance); goto EXITSTRATEGY; } /* * make sure that only a single cookie was created - there should only be one * since our attribute struct says no chaining, and sparc/pci has an iommu */ if (cookie_count != 1) { bp->b_error |= EIO; bp->b_flags |= B_ERROR; cmn_err(CE_CONT, "ihcp_strategy: instance %d: too many dma cookies!\n", instance); goto ERRORSTRATEGY; } /* * get working address and count - that may get modified * * dma_cookie.dmac_size is type size_t, but we know it will fit in 32 bits since our * dma_attr structure limits count to 23 bits */ dma_address = dma_cookie.dmac_address; /*LINTED cast ok - dma count won't be over 23 bits wide */ dma_count = (uint32_t) dma_cookie.dmac_size; DPRINT((CE_CONT, "ihcp_strategy: instance %d: mapped_address = 0x%x, dmac_address = 0x%x, dmac_size = 0x%x\n", instance, mapped_address, dma_address, dma_count)); /* * the plx dma mechanism, and our 32 bit wide fifos, require that all dma transfers * be 32 bit word aligned, and be multiples of 32 bit words * * we will test for mis-alignment * if so the leading bytes will be output to the fifo one byte at a time * * then we will test for enough remaining bytes to do at least a one 32 bit word * dma transfer * * if enough, do the dma xfer and wait for done * * if not, wait for fifo less than half full * * then, after either, test for trailing bytes and output as needed * * the end result of all of the above is that any pass through strategy will * leave the fifo approximately half full or less * * we don't test for half full as we output individual bytes, we test for full * and flag an error if we overflow * * it is not the intent of this driver to allow the entire data transfer requirements * of the device to be met via the data out ioctl -- it is intended that write and * strategy be the major data movers, with the ioctl used for short data or control * bursts interspersed with write() calls * * done this way, it should not be necessary to deal with waiting for empty, or interrupts * in the data out ioctl */ /* * set flags to indicate dma wait - even if we don't wait, just so flags will reflect * that we were in strategy * error flags are not cleared here so they will survive * multiple calls to strategy by physio */ unit_p->unit_flags |= IHCP_DMA_WAIT; /* * calculate number of mis-aligned bytes at buffer start * limit # to actual buffer size!! */ count = (0x03 & (4 - (0x03 & dma_address))); if (count > dma_count) count = dma_count; DPRINT((CE_CONT, "ihcp_strategy: instance %d: number of initial unaligned bytes = 0x%x\n", instance, count)); /* * output any leading bytes * * mapped address and dma address will be incremented to indicate new * start add for dma, and dma count will be decremented */ for (index = 0; index < count; index++, mapped_address++, dma_address++, dma_count--) { if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { bp->b_error |= EIO; bp->b_flags |= B_ERROR; cmn_err(CE_CONT, "ihcp_strategy: instance %d: fifo full while transferring leading bytes!\n", instance); goto ERRORSTRATEGY; } temp = *mapped_address; if (unit_p->byte_order == HIGH_BYTE_FIRST) temp = temp << 24; IHCP_PUT32(IHCP_8_BIT_DATA_OUT, temp); IHCP_SYNC_0; } /* * we should now have an aligned buffer, with 0 or more bytes in it * if 4 or more bytes, do a dma xfer * * if less than 4, wait for 3) { DPRINT((CE_CONT, "ihcp_strategy: instance %d: doing dma, address = 0x%x, count = 0x%x\n", instance, dma_address, dma_count)); /* * program plx dma logic on board * mask count so we don't try to output any trailing bytes * that don't fit into an even number of 32 bit words */ /* * make sure that any left over dma done interrupt * is cleared */ IPLX_PUT32(IPLX_DMA_CMD_STAT, DMA_CLEAR_INTERRUPT); IHCP_SYNC_0; IPLX_PUT32(IPLX_DMA_COUNT_0, dma_count & 0xFFFFFFFC); /* byte count */ IPLX_PUT32(IPLX_DMA_PCI_ADD_0, dma_address); /* pci address */ IPLX_PUT32(IPLX_DMA_LOC_ADD_0, IHCP_32_BIT_DATA_OUT); /* local bus address */ IPLX_PUT32(IPLX_DMA_MODE_0, DMA_BUS_32_BIT | DMA_WAIT_1 | DMA_BURST_ENABLE | DMA_DONE_INTERRUPT_ENB | DMA_LOCAL_ADD_HOLD | DMA_DEMAND_MODE); /* mode register */ IPLX_PUT32(IPLX_DMA_DESC_PTR_0, 0); /* direction = output */ IHCP_SYNC_1; IPLX_PUT32(IPLX_DMA_CMD_STAT, DMA_ENABLE | DMA_START); /* actually fire up xfer */ /* * the 9060 drives the local interrupt out true when a dma * interrupt happens * * we have to loop that output back to the local interrupt input * external to the chip to get a dma interrupt to the pci bus */ PLX_PUT32(PLX_INT_CSTAT, PCI_INTERRUPT_ENABLE | PCI_LOCAL_INT_ENABLE | LOCAL_INT_OUT_ENABLE | LOCAL_DMA_0_INT_ENABLE); PLX_SYNC_0; } else { DPRINT((CE_CONT, "ihcp_strategy: instance %d: no dma, waiting for <1/2 full\n", instance)); /* * we need to wait for fifo less than half full * it may already be less than half full - if so * sneak around the wait code */ if (IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_HALF_FULL) goto NOWAIT; DPRINT((CE_CONT, "ihcp_strategy: instance %d: not <1/2 full, setting up for interrupt\n", instance)); /* * clear any left over interrupt flag, then * enable plx and ihcp interrupt for fifo less than half full * we don't or into ihcp or plx mask reg since we know exactly which ints to enb */ IHCP_PUT32(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_0; IHCP_PUT32(IHCP_INTERRUPT_MASK, FIFO_NOT_HALF_INT_ENB); PLX_PUT32(PLX_INT_CSTAT, (PCI_INTERRUPT_ENABLE | PCI_LOCAL_INT_ENABLE)); PLX_SYNC_0; } /* * we arrive here after setting up for a dma wait, or a less than half full wait */ /* * ddi_get_lbolt returns current time since last reboot - in ticks */ time_now = ddi_get_lbolt(); /* * snooze until done or dma_time = %d\n", instance, time_now, unit_p->dma_time)); waitval = cv_timedwait_sig(&unit_p->cv, &unit_p->soft_mutex, (clock_t) (time_now + unit_p->dma_time)); /* * make sure interrupt enables are off and pause dma (which will normally * already be done) */ IHCP_PUT32(IHCP_INTERRUPT_MASK, 0); PLX_PUT32(PLX_INT_CSTAT, 0); IHCP_SYNC_0; /* * if dma is not done (after sig or timeout), force an abort * and issue a master clear to flush ihcp buffers * * abort may take some time to complete, so use delay and * register read to give it some time */ if (!(IPLX_GET32(IPLX_DMA_CMD_STAT) & DMA_DONE)) { DPRINT((CE_CONT, "ihcp_strategy: instance %d: dma not done after cv_timedwait_sig\n", instance)); IPLX_PUT32(IPLX_DMA_CMD_STAT, DMA_ABORT); IHCP_SYNC_1; /* * get mode - so we can restore it after a mclr * * mclr clears the fifos, and also makes them assert dma rq * but...mclr holds off dma rq while mclr is asserted, and since the 9060 * likes a synchronous dma rq, we will wait a while before hitting mclr * * 10us of delay seems like a lot, but we will only be in this code * following an error * * it is not clear whether the 9060 needs dma rq in order to * finish an abort * * if it does, it may transfer a word or two after the mclr, * so we will repeat the mclr later */ drv_usecwait((clock_t) 5); temp = IHCP_GET32(IHCP_MODE); IHCP_PUT32(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_0; /* * force write cache to flush - so delay appears on the bus */ read_dump_0 = IHCP_GET32(IHCP_INTERRUPT_MASK); drv_usecwait((clock_t) 5); IHCP_PUT32(IHCP_INTERFACE_CONTROL, MASTER_CLEAR); IHCP_SYNC_1; IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_2; IHCP_PUT32(IHCP_MODE, temp); IHCP_SYNC_3; /* * make sure dma actually says done */ if (!(IPLX_GET32(IPLX_DMA_CMD_STAT) & DMA_DONE)) { cmn_err(CE_CONT, "ihcp_strategy: instance %d: dma not done after abort!\n", instance); bp->b_error |= EIO; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY; } } /* * now we can safely make sure that the done interrupt is clear, * and reset the ihcp interrupt flag * * (the above is probably not necessary after normal dma done) */ IPLX_PUT32(IPLX_DMA_CMD_STAT, DMA_CLEAR_INTERRUPT); IHCP_PUT32(IHCP_INTERFACE_CONTROL, CLEAR_INTERRUPT_FLAG); IHCP_SYNC_0; IHCP_PUT32(IHCP_INTERFACE_CONTROL, 0); IHCP_SYNC_1; DPRINT((CE_CONT, "ihcp_strategy: instance %d: cv_timedait_sig returns 0x%x\n", instance, waitval)); /* * find out why we woke up */ if (waitval < 0) { /* * timeout is always an error so print message and set error flag bits * flags for timeout */ DPRINT((CE_CONT, "ihcp_strategy: instance %d: timeout while waiting for interrupt!\n", instance)); unit_p->unit_flags |= IHCP_DMA_TIMEOUT; bp->b_error |= EIO; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY; } if (waitval == 0) { /* * flag signal received and print error message */ cmn_err(CE_CONT, "ihcp_strategy: instance %d: signal received while waiting for interrupt\n", instance); unit_p->unit_flags |= IHCP_SIG_RECEIVED; bp->b_error |= EINTR; bp->b_flags |= B_ERROR; goto ERRORSTRATEGY; } NOWAIT: /* * now we need to test for trailing bytes that didn't fit into exact number of 32 bit words * * it is possible to get here with remaining byte count == 0, either after dma * or if leading byte output logic exhausted the byte count */ count = dma_count & 0x03; DPRINT((CE_CONT, "ihcp_strategy: instance %d: number of trailing bytes = 0x%x\n", instance, count)); for (index = 0; index < count; index++) { if ((IHCP_GET32(IHCP_INTERFACE_STATUS) & FIFO_NOT_FULL) == 0) { bp->b_error |= EIO; bp->b_flags |= B_ERROR; cmn_err(CE_CONT, "ihcp_strategy: instance %d: fifo full while transferring trailing bytes!\n", instance); goto ERRORSTRATEGY; } temp = *(mapped_address + (dma_count & 0xFFFFFFFC) + index); if (unit_p->byte_order == HIGH_BYTE_FIRST) temp = temp << 24; IHCP_PUT32(IHCP_8_BIT_DATA_OUT, temp); IHCP_SYNC_3; } ERRORSTRATEGY: /* * come here if error after mapin and bind handle * are all complete, or in normal flow */ ddi_dma_unbind_handle(unit_p->dma_handle); bp_mapout(bp); EXITSTRATEGY: /* * come here if error before all the mappings are done or in normal flow * if error - be sure to do the appropriate unmapping(s) before arriving here */ /* * we can't read back the range counter in all versions of the plx chip * so we will just set resid to 0! */ bp->b_resid = 0; /* * tell physio that we are done */ biodone(bp); /* * unlock unit structure */ mutex_exit(&unit_p->soft_mutex); return (0); } /* * ihcp_minphys controls the largest buffer that can be sent to ihcp_strategy by physio. * the buffer is limited to the smallest of: the actual user's buffer, the system minphys * parameter, our IHCP_MAXPHYS. IHCP_MAXPHYS is set to match the size of the plx dma * range counter (since we don't chain, the counter determines the max total buffer size). * * there may be cases (not likely for the ihcp driver) where we don't want to limit ourselves * to the system's minphys, but want to ask for more mapping space * this is done by setting ignore_minphys to 1 in ihcp.conf, and re-loading the driver */ static void ihcp_minphys(struct buf *bp) { register struct ihcp_unit *unit_p; /* points to unit structure */ int instance; /* * get instance number from buffer and get pointer to soft state */ /*LINTED cast to int ok */ instance = (int) getminor(bp->b_edev); unit_p = (struct ihcp_unit *) ddi_get_soft_state(state_head, instance); DPRINT((CE_CONT, "ihcp_minphys: instance %d: bp->b_bcount = 0x%x\n", instance, bp->b_bcount)); if (!unit_p->ignore_minphys) { DPRINT((CE_CONT, "ihcp_minphys: instance %d: calling system minphys\n", instance)); minphys(bp); DPRINT((CE_CONT, "ihcp_minphys: instance %d: bp->b_bcount = 0x%x\n", instance, bp->b_bcount)); } DPRINT((CE_CONT, "ihcp_minphys: instance %d: setting bp->b_count to min(b_bcount,IHCP_MAXPHYS)\n", instance)); bp->b_bcount = min(bp->b_bcount, IHCP_MAXPHYS); DPRINT((CE_CONT, "ihcp_minphys: instance %d: bp->b_bcount = 0x%x\n", instance, bp->b_bcount)); }