diff options
Diffstat (limited to 'drivers/gpib/common/iblib.c')
| -rw-r--r-- | drivers/gpib/common/iblib.c | 717 |
1 files changed, 717 insertions, 0 deletions
diff --git a/drivers/gpib/common/iblib.c b/drivers/gpib/common/iblib.c new file mode 100644 index 000000000000..7cbb6a467177 --- /dev/null +++ b/drivers/gpib/common/iblib.c @@ -0,0 +1,717 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * copyright : (C) 2001, 2002 by Frank Mori Hess + ***************************************************************************/ + +#define dev_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "ibsys.h" +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/vmalloc.h> + +/* + * IBCAC + * Return to the controller active state from the + * controller standby state, i.e., turn ATN on. Note + * that in order to enter the controller active state + * from the controller idle state, ibsic must be called. + * If sync is non-zero, attempt to take control synchronously. + * If fallback_to_async is non-zero, try to take control asynchronously + * if synchronous attempt fails. + */ +int ibcac(struct gpib_board *board, int sync, int fallback_to_async) +{ + int status = ibstatus(board); + int retval; + + if ((status & CIC) == 0) + return -EINVAL; + + if (status & ATN) + return 0; + + if (sync && (status & LACS) == 0) + /* + * tcs (take control synchronously) can only possibly work when + * controller is listener. Error code also needs to be -ETIMEDOUT + * or it will giveout without doing fallback. + */ + retval = -ETIMEDOUT; + else + retval = board->interface->take_control(board, sync); + + if (retval < 0 && fallback_to_async) { + if (sync && retval == -ETIMEDOUT) + retval = board->interface->take_control(board, 0); + } + board->interface->update_status(board, 0); + + return retval; +} + +/* + * After ATN is asserted, it should cause any connected devices + * to start listening for command bytes and leave acceptor idle state. + * So if ATN is asserted and neither NDAC or NRFD are asserted, + * then there are no devices and ibcmd should error out immediately. + * Some gpib hardware sees itself asserting NDAC/NRFD when it + * is controller in charge, in which case this check will + * do nothing useful (but shouldn't cause any harm either). + * Drivers that don't need this check (ni_usb for example) may + * set the skip_check_for_command_acceptors flag in their + * gpib_interface_struct to avoid useless overhead. + */ +static int check_for_command_acceptors(struct gpib_board *board) +{ + int lines; + + if (board->interface->skip_check_for_command_acceptors) + return 0; + if (!board->interface->line_status) + return 0; + + udelay(2); // allow time for devices to respond to ATN if it was just asserted + + lines = board->interface->line_status(board); + if (lines < 0) + return lines; + + if ((lines & VALID_NRFD) && (lines & VALID_NDAC)) { + if ((lines & BUS_NRFD) == 0 && (lines & BUS_NDAC) == 0) + return -ENOTCONN; + } + + return 0; +} + +/* + * IBCMD + * Write cnt command bytes from buf to the GPIB. The + * command operation terminates only on I/O complete. + * + * NOTE: + * 1. Prior to beginning the command, the interface is + * placed in the controller active state. + * 2. Before calling ibcmd for the first time, ibsic + * must be called to initialize the GPIB and enable + * the interface to leave the controller idle state. + */ +int ibcmd(struct gpib_board *board, u8 *buf, size_t length, size_t *bytes_written) +{ + ssize_t ret = 0; + int status; + + *bytes_written = 0; + + status = ibstatus(board); + + if ((status & CIC) == 0) + return -EINVAL; + + os_start_timer(board, board->usec_timeout); + + ret = ibcac(board, 1, 1); + if (ret == 0) { + ret = check_for_command_acceptors(board); + if (ret == 0) + ret = board->interface->command(board, buf, length, bytes_written); + } + + os_remove_timer(board); + + if (io_timed_out(board)) + ret = -ETIMEDOUT; + + return ret; +} + +/* + * IBGTS + * Go to the controller standby state from the controller + * active state, i.e., turn ATN off. + */ + +int ibgts(struct gpib_board *board) +{ + int status = ibstatus(board); + int retval; + + if ((status & CIC) == 0) + return -EINVAL; + + retval = board->interface->go_to_standby(board); /* go to standby */ + + board->interface->update_status(board, 0); + + return retval; +} + +static int autospoll_wait_should_wake_up(struct gpib_board *board) +{ + int retval; + + mutex_lock(&board->big_gpib_mutex); + + retval = board->master && board->autospollers > 0 && + !atomic_read(&board->stuck_srq) && + test_and_clear_bit(SRQI_NUM, &board->status); + + mutex_unlock(&board->big_gpib_mutex); + return retval; +} + +static int autospoll_thread(void *board_void) +{ + struct gpib_board *board = board_void; + int retval = 0; + + dev_dbg(board->gpib_dev, "entering autospoll thread\n"); + + while (1) { + wait_event_interruptible(board->wait, + kthread_should_stop() || + autospoll_wait_should_wake_up(board)); + dev_dbg(board->gpib_dev, "autospoll wait satisfied\n"); + if (kthread_should_stop()) + break; + + mutex_lock(&board->big_gpib_mutex); + /* make sure we are still good after we have lock */ + if (board->autospollers <= 0 || board->master == 0) { + mutex_unlock(&board->big_gpib_mutex); + continue; + } + mutex_unlock(&board->big_gpib_mutex); + + if (try_module_get(board->provider_module)) { + retval = autopoll_all_devices(board); + module_put(board->provider_module); + } else { + dev_err(board->gpib_dev, "try_module_get() failed!\n"); + } + if (retval <= 0) { + dev_err(board->gpib_dev, "stuck SRQ\n"); + + atomic_set(&board->stuck_srq, 1); // XXX could be better + set_bit(SRQI_NUM, &board->status); + } + } + return retval; +} + +int ibonline(struct gpib_board *board) +{ + int retval; + + if (board->online) + return -EBUSY; + if (!board->interface) + return -ENODEV; + retval = gpib_allocate_board(board); + if (retval < 0) + return retval; + + board->dev = NULL; + board->local_ppoll_mode = 0; + retval = board->interface->attach(board, &board->config); + if (retval < 0) { + board->interface->detach(board); + return retval; + } + /* + * nios2nommu on 2.6.11 uclinux kernel has weird problems + * with autospoll thread causing huge slowdowns + */ +#ifndef CONFIG_NIOS2 + board->autospoll_task = kthread_run(&autospoll_thread, board, + "gpib%d_autospoll_kthread", board->minor); + retval = IS_ERR(board->autospoll_task); + if (retval) { + dev_err(board->gpib_dev, "failed to create autospoll thread\n"); + board->interface->detach(board); + return retval; + } +#endif + board->online = 1; + dev_dbg(board->gpib_dev, "board online\n"); + + return 0; +} + +/* XXX need to make sure board is generally not in use (grab board lock?) */ +int iboffline(struct gpib_board *board) +{ + int retval; + + if (board->online == 0) + return 0; + if (!board->interface) + return -ENODEV; + + if (board->autospoll_task && !IS_ERR(board->autospoll_task)) { + retval = kthread_stop(board->autospoll_task); + if (retval) + dev_err(board->gpib_dev, "kthread_stop returned %i\n", retval); + board->autospoll_task = NULL; + } + + board->interface->detach(board); + gpib_deallocate_board(board); + board->online = 0; + dev_dbg(board->gpib_dev, "board offline\n"); + + return 0; +} + +/* + * IBLINES + * Poll the GPIB control lines and return their status in buf. + * + * LSB (bits 0-7) - VALID lines mask (lines that can be monitored). + * Next LSB (bits 8-15) - STATUS lines mask (lines that are currently set). + * + */ +int iblines(const struct gpib_board *board, short *lines) +{ + int retval; + + *lines = 0; + if (!board->interface->line_status) + return 0; + retval = board->interface->line_status(board); + if (retval < 0) + return retval; + *lines = retval; + return 0; +} + +/* + * IBRD + * Read up to 'length' bytes of data from the GPIB into buf. End + * on detection of END (EOI and or EOS) and set 'end_flag'. + * + * NOTE: + * 1. The interface is placed in the controller standby + * state prior to beginning the read. + * 2. Prior to calling ibrd, the intended devices as well + * as the interface board itself must be addressed by + * calling ibcmd. + */ + +int ibrd(struct gpib_board *board, u8 *buf, size_t length, int *end_flag, size_t *nbytes) +{ + ssize_t ret = 0; + int retval; + size_t bytes_read; + + *nbytes = 0; + *end_flag = 0; + if (length == 0) + return 0; + + if (board->master) { + retval = ibgts(board); + if (retval < 0) + return retval; + } + /* + * XXX resetting timer here could cause timeouts take longer than they should, + * since read_ioctl calls this + * function in a loop, there is probably a similar problem with writes/commands + */ + os_start_timer(board, board->usec_timeout); + + do { + ret = board->interface->read(board, buf, length - *nbytes, end_flag, &bytes_read); + if (ret < 0) + goto ibrd_out; + + buf += bytes_read; + *nbytes += bytes_read; + if (need_resched()) + schedule(); + } while (ret == 0 && *nbytes > 0 && *nbytes < length && *end_flag == 0); +ibrd_out: + os_remove_timer(board); + + return ret; +} + +/* + * IBRPP + * Conduct a parallel poll and return the byte in buf. + * + * NOTE: + * 1. Prior to conducting the poll the interface is placed + * in the controller active state. + */ +int ibrpp(struct gpib_board *board, u8 *result) +{ + int retval = 0; + + os_start_timer(board, board->usec_timeout); + retval = ibcac(board, 1, 1); + if (retval) + return -1; + + retval = board->interface->parallel_poll(board, result); + + os_remove_timer(board); + return retval; +} + +int ibppc(struct gpib_board *board, u8 configuration) +{ + configuration &= 0x1f; + board->interface->parallel_poll_configure(board, configuration); + board->parallel_poll_configuration = configuration; + + return 0; +} + +int ibrsv2(struct gpib_board *board, u8 status_byte, int new_reason_for_service) +{ + int board_status = ibstatus(board); + const unsigned int MSS = status_byte & request_service_bit; + + if ((board_status & CIC)) + return -EINVAL; + + if (MSS == 0 && new_reason_for_service) + return -EINVAL; + + if (board->interface->serial_poll_response2) { + board->interface->serial_poll_response2(board, status_byte, new_reason_for_service); + // fall back on simpler serial_poll_response if the behavior would be the same + } else if (board->interface->serial_poll_response && + (MSS == 0 || (MSS && new_reason_for_service))) { + board->interface->serial_poll_response(board, status_byte); + } else { + return -EOPNOTSUPP; + } + + return 0; +} + +/* + * IBSIC + * Send IFC for at least 100 microseconds. + * + * NOTE: + * 1. Ibsic must be called prior to the first call to + * ibcmd in order to initialize the bus and enable the + * interface to leave the controller idle state. + */ +int ibsic(struct gpib_board *board, unsigned int usec_duration) +{ + if (board->master == 0) + return -EINVAL; + + if (usec_duration < 100) + usec_duration = 100; + if (usec_duration > 1000) + usec_duration = 1000; + + dev_dbg(board->gpib_dev, "sending interface clear, delay = %ius\n", usec_duration); + board->interface->interface_clear(board, 1); + udelay(usec_duration); + board->interface->interface_clear(board, 0); + + return 0; +} + +int ibrsc(struct gpib_board *board, int request_control) +{ + int retval; + + if (!board->interface->request_system_control) + return -EPERM; + + retval = board->interface->request_system_control(board, request_control); + + if (retval) + return retval; + + board->master = request_control != 0; + + return 0; +} + +/* + * IBSRE + * Send REN true if v is non-zero or false if v is zero. + */ +int ibsre(struct gpib_board *board, int enable) +{ + if (board->master == 0) + return -EINVAL; + + board->interface->remote_enable(board, enable); /* set or clear REN */ + if (!enable) + usleep_range(100, 150); + + return 0; +} + +/* + * IBPAD + * change the GPIB address of the interface board. The address + * must be 0 through 30. ibonl resets the address to PAD. + */ +int ibpad(struct gpib_board *board, unsigned int addr) +{ + if (addr > MAX_GPIB_PRIMARY_ADDRESS) + return -EINVAL; + + board->pad = addr; + if (board->online) + board->interface->primary_address(board, board->pad); + dev_dbg(board->gpib_dev, "set primary addr to %i\n", board->pad); + return 0; +} + +/* + * IBSAD + * change the secondary GPIB address of the interface board. + * The address must be 0 through 30, or negative disables. ibonl resets the + * address to SAD. + */ +int ibsad(struct gpib_board *board, int addr) +{ + if (addr > MAX_GPIB_SECONDARY_ADDRESS) + return -EINVAL; + board->sad = addr; + if (board->online) { + if (board->sad >= 0) + board->interface->secondary_address(board, board->sad, 1); + else + board->interface->secondary_address(board, 0, 0); + } + dev_dbg(board->gpib_dev, "set secondary addr to %i\n", board->sad); + + return 0; +} + +/* + * IBEOS + * Set the end-of-string modes for I/O operations to v. + * + */ +int ibeos(struct gpib_board *board, int eos, int eosflags) +{ + int retval; + + if (eosflags & ~EOS_MASK) + return -EINVAL; + if (eosflags & REOS) { + retval = board->interface->enable_eos(board, eos, eosflags & BIN); + } else { + board->interface->disable_eos(board); + retval = 0; + } + return retval; +} + +int ibstatus(struct gpib_board *board) +{ + return general_ibstatus(board, NULL, 0, 0, NULL); +} + +int general_ibstatus(struct gpib_board *board, const struct gpib_status_queue *device, + int clear_mask, int set_mask, struct gpib_descriptor *desc) +{ + int status = 0; + short line_status; + + if (board->private_data) { + status = board->interface->update_status(board, clear_mask); + /* + * XXX should probably stop having drivers use TIMO bit in + * board->status to avoid confusion + */ + status &= ~TIMO; + /* get real SRQI status if we can */ + if (iblines(board, &line_status) == 0) { + if ((line_status & VALID_SRQ)) { + if ((line_status & BUS_SRQ)) + status |= SRQI; + else + status &= ~SRQI; + } + } + } + if (device) + if (num_status_bytes(device)) + status |= RQS; + + if (desc) { + if (set_mask & CMPL) + atomic_set(&desc->io_in_progress, 0); + else if (clear_mask & CMPL) + atomic_set(&desc->io_in_progress, 1); + + if (atomic_read(&desc->io_in_progress)) + status &= ~CMPL; + else + status |= CMPL; + } + if (num_gpib_events(&board->event_queue)) + status |= EVENT; + else + status &= ~EVENT; + + return status; +} + +struct wait_info { + struct gpib_board *board; + struct timer_list timer; + int timed_out; + unsigned long usec_timeout; +}; + +static void wait_timeout(struct timer_list *t) +{ + struct wait_info *winfo = timer_container_of(winfo, t, timer); + + winfo->timed_out = 1; + wake_up_interruptible(&winfo->board->wait); +} + +static void init_wait_info(struct wait_info *winfo) +{ + winfo->board = NULL; + winfo->timed_out = 0; + timer_setup_on_stack(&winfo->timer, wait_timeout, 0); +} + +static int wait_satisfied(struct wait_info *winfo, struct gpib_status_queue *status_queue, + int wait_mask, int *status, struct gpib_descriptor *desc) +{ + struct gpib_board *board = winfo->board; + int temp_status; + + if (mutex_lock_interruptible(&board->big_gpib_mutex)) + return -ERESTARTSYS; + + temp_status = general_ibstatus(board, status_queue, 0, 0, desc); + + mutex_unlock(&board->big_gpib_mutex); + + if (winfo->timed_out) + temp_status |= TIMO; + else + temp_status &= ~TIMO; + if (wait_mask & temp_status) { + *status = temp_status; + return 1; + } +// XXX does wait for END work? + return 0; +} + +/* install timer interrupt handler */ +static void start_wait_timer(struct wait_info *winfo) +/* Starts the timeout task */ +{ + winfo->timed_out = 0; + + if (winfo->usec_timeout > 0) + mod_timer(&winfo->timer, jiffies + usec_to_jiffies(winfo->usec_timeout)); +} + +static void remove_wait_timer(struct wait_info *winfo) +{ + timer_delete_sync(&winfo->timer); + timer_destroy_on_stack(&winfo->timer); +} + +/* + * IBWAIT + * Check or wait for a GPIB event to occur. The mask argument + * is a bit vector corresponding to the status bit vector. It + * has a bit set for each condition which can terminate the wait + * If the mask is 0 then + * no condition is waited for. + */ +int ibwait(struct gpib_board *board, int wait_mask, int clear_mask, int set_mask, + int *status, unsigned long usec_timeout, struct gpib_descriptor *desc) +{ + int retval = 0; + struct gpib_status_queue *status_queue; + struct wait_info winfo; + + if (desc->is_board) + status_queue = NULL; + else + status_queue = get_gpib_status_queue(board, desc->pad, desc->sad); + + if (wait_mask == 0) { + *status = general_ibstatus(board, status_queue, clear_mask, set_mask, desc); + return 0; + } + + mutex_unlock(&board->big_gpib_mutex); + + init_wait_info(&winfo); + winfo.board = board; + winfo.usec_timeout = usec_timeout; + start_wait_timer(&winfo); + + if (wait_event_interruptible(board->wait, wait_satisfied(&winfo, status_queue, + wait_mask, status, desc))) { + dev_dbg(board->gpib_dev, "wait interrupted\n"); + retval = -ERESTARTSYS; + } + remove_wait_timer(&winfo); + + if (retval) + return retval; + if (mutex_lock_interruptible(&board->big_gpib_mutex)) + return -ERESTARTSYS; + + /* make sure we only clear status bits that we are reporting */ + if (*status & clear_mask || set_mask) + general_ibstatus(board, status_queue, *status & clear_mask, set_mask, NULL); + + return 0; +} + +/* + * IBWRT + * Write cnt bytes of data from buf to the GPIB. The write + * operation terminates only on I/O complete. + * + * NOTE: + * 1. Prior to beginning the write, the interface is + * placed in the controller standby state. + * 2. Prior to calling ibwrt, the intended devices as + * well as the interface board itself must be + * addressed by calling ibcmd. + */ +int ibwrt(struct gpib_board *board, u8 *buf, size_t cnt, int send_eoi, size_t *bytes_written) +{ + int ret = 0; + int retval; + + if (cnt == 0) + return 0; + + if (board->master) { + retval = ibgts(board); + if (retval < 0) + return retval; + } + os_start_timer(board, board->usec_timeout); + ret = board->interface->write(board, buf, cnt, send_eoi, bytes_written); + + if (io_timed_out(board)) + ret = -ETIMEDOUT; + + os_remove_timer(board); + + return ret; +} + |
