mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
Move the gpib drivers out of staging and into the "real" part of the kernel. This entails: - Remove the gpib Kconfig menu and Makefile build rule from staging. - Remove gpib/uapi from the header file search path in subdir-ccflags of the gpib Makefile - move the gpib/uapi files to include/uapi/linux - Move the gpib tree out of staging to drivers. - Remove the word "Linux" from the gpib Kconfig file. - Add the gpib Kconfig menu and Makefile build rule to drivers Signed-off-by: Dave Penkler <dpenkler@gmail.com> Link: https://patch.msgid.link/20251117144021.23569-5-dpenkler@gmail.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2026 lines
49 KiB
C
2026 lines
49 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/***************************************************************************
|
|
* This code has been developed at the Department of Physics (University *
|
|
* of Florence, Italy) to support in linux-gpib the open usb-gpib adapter *
|
|
* implemented at the University of Ljubljana (lpvo.fe.uni-lj.si/gpib) *
|
|
* *
|
|
* copyright : (C) 2011 Marcello Carla' *
|
|
***************************************************************************/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
#define dev_fmt pr_fmt
|
|
#define NAME KBUILD_MODNAME
|
|
|
|
/* base module includes */
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/types.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/file.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <linux/usb.h>
|
|
|
|
#include "gpibP.h"
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("GPIB driver for LPVO usb devices");
|
|
|
|
/*
|
|
* Table of devices that work with this driver.
|
|
*
|
|
* Currently, only one device is known to be used in the
|
|
* lpvo_usb_gpib adapter (FTDI 0403:6001).
|
|
* If your adapter uses a different chip, insert a line
|
|
* in the following table with proper <Vendor-id>, <Product-id>.
|
|
*
|
|
* To have your chip automatically handled by the driver,
|
|
* update files "/usr/local/etc/modprobe.d/lpvo_usb_gpib.conf"
|
|
* and /usr/local/etc/udev/rules.d/99-lpvo_usb_gpib.rules.
|
|
*
|
|
*/
|
|
|
|
static const struct usb_device_id skel_table[] = {
|
|
{ USB_DEVICE(0x0403, 0x6001) },
|
|
{ } /* Terminating entry */
|
|
};
|
|
MODULE_DEVICE_TABLE(usb, skel_table);
|
|
|
|
/*
|
|
* *** Diagnostics and Debug ***
|
|
* To enable the diagnostic and debug messages either compile with DEBUG set
|
|
* or control via the dynamic debug mechanisms.
|
|
* The module parameter "debug" controls the sending of debug messages to
|
|
* syslog. By default it is set to 0
|
|
* debug = 0: only attach/detach messages are sent
|
|
* 1: every action is logged
|
|
* 2: extended logging; each single exchanged byte is documented
|
|
* (about twice the log volume of [1])
|
|
* To switch debug level:
|
|
* At module loading: modprobe lpvo_usb_gpib debug={0,1,2}
|
|
* On the fly: echo {0,1,2} > /sys/modules/lpvo_usb_gpib/parameters/debug
|
|
*/
|
|
|
|
static int debug;
|
|
module_param(debug, int, 0644);
|
|
|
|
#define DIA_LOG(level, format, ...) \
|
|
do { if (debug >= (level)) \
|
|
dev_dbg(board->gpib_dev, format, ## __VA_ARGS__); } \
|
|
while (0)
|
|
|
|
#define WQT wait_queue_entry_t
|
|
#define WQH head
|
|
#define WQE entry
|
|
|
|
/* standard and extended command sets of the usb-gpib adapter */
|
|
|
|
#define USB_GPIB_ON "\nIB\n"
|
|
#define USB_GPIB_OFF "\nIBO\n"
|
|
#define USB_GPIB_IBm0 "\nIBm0\n" /* do not assert REN with IFC */
|
|
#define USB_GPIB_IBm1 "\nIBm1\n" /* assert REN with IFC */
|
|
#define USB_GPIB_IBCL "\nIBZ\n"
|
|
#define USB_GPIB_STATUS "\nIBS\n"
|
|
#define USB_GPIB_READ "\nIB?\n"
|
|
#define USB_GPIB_READ_1 "\nIBB\n"
|
|
#define USB_GPIB_EOI "\nIBe0\n"
|
|
#define USB_GPIB_FTMO "\nIBf0\n" /* disable first byte timeout */
|
|
#define USB_GPIB_TTMOZ "\nIBt0\n" /* disable byte timeout */
|
|
|
|
/* incomplete commands */
|
|
|
|
#define USB_GPIB_BTMO "\nIBt" /* set byte timeout */
|
|
#define USB_GPIB_TTMO "\nIBT" /* set total timeout */
|
|
|
|
#define USB_GPIB_DEBUG_ON "\nIBDE\xAA\n"
|
|
#define USB_GPIB_SET_LISTEN "\nIBDT0\n"
|
|
#define USB_GPIB_SET_TALK "\nIBDT1\n"
|
|
#define USB_GPIB_SET_LINES "\nIBDC.\n"
|
|
#define USB_GPIB_SET_DATA "\nIBDM.\n"
|
|
#define USB_GPIB_READ_LINES "\nIBD?C\n"
|
|
#define USB_GPIB_READ_DATA "\nIBD?M\n"
|
|
#define USB_GPIB_READ_BUS "\nIBD??\n"
|
|
|
|
/* command sequences */
|
|
|
|
#define USB_GPIB_UNTALK "\nIBC_\n"
|
|
#define USB_GPIB_UNLISTEN "\nIBC?\n"
|
|
|
|
/* special characters used by the adapter */
|
|
|
|
#define DLE ('\020')
|
|
#define STX ('\02')
|
|
#define ETX ('\03')
|
|
#define ACK ('\06')
|
|
#define NODATA ('\03')
|
|
#define NODAV ('\011')
|
|
|
|
#define IB_BUS_REN 0x01
|
|
#define IB_BUS_IFC 0x02
|
|
#define IB_BUS_NDAC 0x04
|
|
#define IB_BUS_NRFD 0x08
|
|
#define IB_BUS_DAV 0x10
|
|
#define IB_BUS_EOI 0x20
|
|
#define IB_BUS_ATN 0x40
|
|
#define IB_BUS_SRQ 0x80
|
|
|
|
#define INBUF_SIZE 128
|
|
|
|
struct char_buf { /* used by one_char() routine */
|
|
char *inbuf;
|
|
int last;
|
|
int nchar;
|
|
};
|
|
|
|
struct usb_gpib_priv { /* private data to the device */
|
|
u8 eos; /* eos character */
|
|
short eos_flags; /* eos mode */
|
|
int timeout; /* current value for timeout */
|
|
void *dev; /* the usb device private data structure */
|
|
};
|
|
|
|
#define GPIB_DEV (((struct usb_gpib_priv *)board->private_data)->dev)
|
|
|
|
static void show_status(struct gpib_board *board)
|
|
{
|
|
DIA_LOG(2, "# - buffer_length %d\n", board->buffer_length);
|
|
DIA_LOG(2, "# - status %lx\n", board->status);
|
|
DIA_LOG(2, "# - use_count %d\n", board->use_count);
|
|
DIA_LOG(2, "# - pad %x\n", board->pad);
|
|
DIA_LOG(2, "# - sad %x\n", board->sad);
|
|
DIA_LOG(2, "# - timeout %d\n", board->usec_timeout);
|
|
DIA_LOG(2, "# - ppc %d\n", board->parallel_poll_configuration);
|
|
DIA_LOG(2, "# - t1delay %d\n", board->t1_nano_sec);
|
|
DIA_LOG(2, "# - online %d\n", board->online);
|
|
DIA_LOG(2, "# - autopoll %d\n", board->autospollers);
|
|
DIA_LOG(2, "# - autopoll task %p\n", board->autospoll_task);
|
|
DIA_LOG(2, "# - minor %d\n", board->minor);
|
|
DIA_LOG(2, "# - master %d\n", board->master);
|
|
DIA_LOG(2, "# - list %d\n", board->ist);
|
|
}
|
|
|
|
/*
|
|
* GLOBAL VARIABLES: required for
|
|
* pairing among gpib minor and usb minor.
|
|
* MAX_DEV is the max number of usb-gpib adapters; free
|
|
* to change as you like, but no more than 32
|
|
*/
|
|
|
|
#define MAX_DEV 8
|
|
static struct usb_interface *lpvo_usb_interfaces[MAX_DEV]; /* registered interfaces */
|
|
static int usb_minors[MAX_DEV]; /* usb minors */
|
|
static int assigned_usb_minors; /* mask of filled slots */
|
|
static struct mutex minors_lock; /* operations on usb_minors are to be protected */
|
|
|
|
/*
|
|
* usb-skeleton prototypes
|
|
*/
|
|
|
|
struct usb_skel;
|
|
static ssize_t skel_do_write(struct usb_skel *, const char *, size_t);
|
|
static ssize_t skel_do_read(struct usb_skel *, char *, size_t);
|
|
static int skel_do_open(struct gpib_board *, int);
|
|
static int skel_do_release(struct gpib_board *);
|
|
|
|
/*
|
|
* usec_diff : take difference in MICROsec between two 'timespec'
|
|
* (unix time in sec and NANOsec)
|
|
*/
|
|
|
|
static inline int usec_diff(struct timespec64 *a, struct timespec64 *b)
|
|
{
|
|
return ((a->tv_sec - b->tv_sec) * 1000000 +
|
|
(a->tv_nsec - b->tv_nsec) / 1000);
|
|
}
|
|
|
|
/*
|
|
* *** these routines are specific to the usb-gpib adapter ***
|
|
*/
|
|
|
|
/**
|
|
* write_loop() - Send a byte sequence to the adapter
|
|
*
|
|
* @dev: the private device structure
|
|
* @msg: the byte sequence.
|
|
* @leng: the byte sequence length.
|
|
*
|
|
*/
|
|
|
|
static int write_loop(void *dev, char *msg, int leng)
|
|
{
|
|
return skel_do_write(dev, msg, leng);
|
|
}
|
|
|
|
/**
|
|
* send_command() - Send a byte sequence and return a single byte reply.
|
|
*
|
|
* @board: the gpib_board_struct data area for this gpib interface
|
|
* @msg: the byte sequence.
|
|
* @leng: the byte sequence length; can be given as zero and is
|
|
* computed automatically, but if 'msg' contains a zero byte,
|
|
* it has to be given explicitly.
|
|
*/
|
|
|
|
static int send_command(struct gpib_board *board, char *msg, int leng)
|
|
{
|
|
char buffer[64];
|
|
int nchar;
|
|
int retval;
|
|
struct timespec64 before, after;
|
|
|
|
ktime_get_real_ts64 (&before);
|
|
|
|
if (!leng)
|
|
leng = strlen(msg);
|
|
retval = write_loop(GPIB_DEV, msg, leng);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
nchar = skel_do_read(GPIB_DEV, buffer, 64);
|
|
|
|
if (nchar < 0) {
|
|
dev_err(board->gpib_dev, " return from read: %d\n", nchar);
|
|
return nchar;
|
|
} else if (nchar != 1) {
|
|
dev_err(board->gpib_dev, " Irregular reply to command: %s\n", msg);
|
|
return -EIO;
|
|
}
|
|
ktime_get_real_ts64 (&after);
|
|
|
|
DIA_LOG(1, "Sent %d - done %d us.\n", leng, usec_diff(&after, &before));
|
|
|
|
return buffer[0] & 0xff;
|
|
}
|
|
|
|
/*
|
|
* set_control_line() - Set the value of a single gpib control line
|
|
*
|
|
* @board: the gpib_board_struct data area for this gpib interface
|
|
* @line: line mask
|
|
* @value: line new value (0/1)
|
|
*/
|
|
|
|
static int set_control_line(struct gpib_board *board, int line, int value)
|
|
{
|
|
char msg[] = USB_GPIB_SET_LINES;
|
|
int retval;
|
|
int leng = strlen(msg);
|
|
|
|
DIA_LOG(1, "setting line %x to %x\n", line, value);
|
|
|
|
retval = send_command(board, USB_GPIB_READ_LINES, 0);
|
|
|
|
DIA_LOG(1, "old line values: %x\n", retval);
|
|
|
|
if (retval == -EIO)
|
|
return retval;
|
|
|
|
msg[leng - 2] = value ? (retval & ~line) : retval | line;
|
|
|
|
retval = send_command(board, msg, 0);
|
|
|
|
DIA_LOG(1, "operation result: %x\n", retval);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* one_char() - read one single byte from input buffer
|
|
*
|
|
* @board: the gpib_board_struct data area for this gpib interface
|
|
* @char_buf: the routine private data structure
|
|
*/
|
|
|
|
static int one_char(struct gpib_board *board, struct char_buf *b)
|
|
{
|
|
struct timespec64 before, after;
|
|
|
|
if (b->nchar) {
|
|
DIA_LOG(2, "-> %x\n", b->inbuf[b->last - b->nchar]);
|
|
return b->inbuf[b->last - b->nchar--];
|
|
}
|
|
ktime_get_real_ts64 (&before);
|
|
b->nchar = skel_do_read(GPIB_DEV, b->inbuf, INBUF_SIZE);
|
|
b->last = b->nchar;
|
|
ktime_get_real_ts64 (&after);
|
|
|
|
DIA_LOG(2, "read %d bytes in %d usec\n",
|
|
b->nchar, usec_diff(&after, &before));
|
|
|
|
if (b->nchar > 0) {
|
|
DIA_LOG(2, "--> %x\n", b->inbuf[b->last - b->nchar]);
|
|
return b->inbuf[b->last - b->nchar--];
|
|
}
|
|
return -EIO;
|
|
}
|
|
|
|
/**
|
|
* set_timeout() - set single byte / total timeouts on the adapter
|
|
*
|
|
* @board: the gpib_board_struct data area for this gpib interface
|
|
*
|
|
* For sake of speed, the operation is performed only if it
|
|
* modifies the current (saved) value. Minimum allowed timeout
|
|
* is 30 ms (T30ms -> 8); timeout disable (TNONE -> 0) currently
|
|
* not supported.
|
|
*/
|
|
|
|
static void set_timeout(struct gpib_board *board)
|
|
{
|
|
int n, val;
|
|
char command[sizeof(USB_GPIB_TTMO) + 6];
|
|
struct usb_gpib_priv *data = board->private_data;
|
|
|
|
if (data->timeout == board->usec_timeout)
|
|
return;
|
|
|
|
n = (board->usec_timeout + 32767) / 32768;
|
|
if (n < 2)
|
|
n = 2;
|
|
|
|
DIA_LOG(1, "Set timeout to %d us -> %d\n", board->usec_timeout, n);
|
|
|
|
sprintf(command, "%s%d\n", USB_GPIB_BTMO, n > 255 ? 255 : n);
|
|
val = send_command(board, command, 0);
|
|
|
|
if (val == ACK) {
|
|
if (n > 65535)
|
|
n = 65535;
|
|
sprintf(command, "%s%d\n", USB_GPIB_TTMO, n);
|
|
val = send_command(board, command, 0);
|
|
}
|
|
|
|
if (val != ACK)
|
|
dev_err(board->gpib_dev, "error in timeout set: <%s>\n", command);
|
|
else
|
|
data->timeout = board->usec_timeout;
|
|
}
|
|
|
|
/*
|
|
* now the standard interface functions - attach and detach
|
|
*/
|
|
|
|
/**
|
|
* usb_gpib_attach() - activate the usb-gpib converter board
|
|
*
|
|
* @board: the gpib_board_struct data area for this gpib interface
|
|
* @config: firmware data, if any (from gpib_config -I <file>)
|
|
*
|
|
* The channel name is ttyUSBn, with n=0 by default. Other values for n
|
|
* passed with gpib_config -b <n>.
|
|
*
|
|
* In this routine I trust that when an error code is returned
|
|
* detach() will be called. Always.
|
|
*/
|
|
|
|
static int usb_gpib_attach(struct gpib_board *board, const struct gpib_board_config *config)
|
|
{
|
|
int retval, j;
|
|
u32 base = config->ibbase;
|
|
char *device_path;
|
|
int match;
|
|
struct usb_device *udev;
|
|
|
|
DIA_LOG(0, "Board %p -t %s -m %d -a %p -u %d -l %d -b %d\n",
|
|
board, board->interface->name, board->minor, config->device_path,
|
|
config->pci_bus, config->pci_slot, base);
|
|
|
|
board->private_data = NULL; /* to be sure - we can detach before setting */
|
|
|
|
/* identify device to be attached */
|
|
|
|
mutex_lock(&minors_lock);
|
|
|
|
if (config->device_path) {
|
|
/* if config->device_path given, try that first */
|
|
for (j = 0 ; j < MAX_DEV ; j++) {
|
|
if ((assigned_usb_minors & 1 << j) == 0)
|
|
continue;
|
|
udev = usb_get_dev(interface_to_usbdev(lpvo_usb_interfaces[j]));
|
|
device_path = kobject_get_path(&udev->dev.kobj, GFP_KERNEL);
|
|
match = gpib_match_device_path(&lpvo_usb_interfaces[j]->dev,
|
|
config->device_path);
|
|
DIA_LOG(1, "dev. %d: minor %d path: %s --> %d\n", j,
|
|
lpvo_usb_interfaces[j]->minor, device_path, match);
|
|
kfree(device_path);
|
|
if (match)
|
|
break;
|
|
}
|
|
} else if (config->pci_bus != -1 && config->pci_slot != -1) {
|
|
/* second: look for bus and slot */
|
|
for (j = 0 ; j < MAX_DEV ; j++) {
|
|
if ((assigned_usb_minors & 1 << j) == 0)
|
|
continue;
|
|
udev = usb_get_dev(interface_to_usbdev(lpvo_usb_interfaces[j]));
|
|
DIA_LOG(1, "dev. %d: bus %d -> %d dev: %d -> %d\n", j,
|
|
udev->bus->busnum, config->pci_bus, udev->devnum, config->pci_slot);
|
|
if (config->pci_bus == udev->bus->busnum &&
|
|
config->pci_slot == udev->devnum)
|
|
break;
|
|
}
|
|
} else { /* last chance: usb_minor, given as ibbase */
|
|
for (j = 0 ; j < MAX_DEV ; j++) {
|
|
if (usb_minors[j] == base && assigned_usb_minors & 1 << j)
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&minors_lock);
|
|
|
|
if (j == MAX_DEV) {
|
|
dev_err(board->gpib_dev, "Requested device is not registered.\n");
|
|
return -EIO;
|
|
}
|
|
|
|
board->private_data = kzalloc(sizeof(struct usb_gpib_priv), GFP_KERNEL);
|
|
if (!board->private_data)
|
|
return -ENOMEM;
|
|
|
|
retval = skel_do_open(board, usb_minors[j]);
|
|
|
|
DIA_LOG(1, "Skel open: %d\n", retval);
|
|
|
|
if (retval) {
|
|
dev_err(board->gpib_dev, "skel open failed.\n");
|
|
kfree(board->private_data);
|
|
board->private_data = NULL;
|
|
return -ENODEV;
|
|
}
|
|
|
|
show_status(board);
|
|
|
|
retval = send_command(board, USB_GPIB_ON, 0);
|
|
DIA_LOG(1, "USB_GPIB_ON returns %x\n", retval);
|
|
if (retval != ACK)
|
|
return -EIO;
|
|
|
|
/*
|
|
* We must setup debug mode because we need the extended instruction
|
|
* set to cope with the Core (gpib_common) point of view
|
|
*/
|
|
|
|
retval = send_command(board, USB_GPIB_DEBUG_ON, 0);
|
|
DIA_LOG(1, "USB_GPIB_DEBUG_ON returns %x\n", retval);
|
|
if (retval != ACK)
|
|
return -EIO;
|
|
|
|
/*
|
|
* We must keep REN off after an IFC because so it is
|
|
* assumed by the Core
|
|
*/
|
|
|
|
retval = send_command(board, USB_GPIB_IBm0, 0);
|
|
DIA_LOG(1, "USB_GPIB_IBm0 returns %x\n", retval);
|
|
if (retval != ACK)
|
|
return -EIO;
|
|
|
|
retval = set_control_line(board, IB_BUS_REN, 0);
|
|
if (retval != ACK)
|
|
return -EIO;
|
|
|
|
retval = send_command(board, USB_GPIB_FTMO, 0);
|
|
DIA_LOG(1, "USB_GPIB_FTMO returns %x\n", retval);
|
|
if (retval != ACK)
|
|
return -EIO;
|
|
|
|
show_status(board);
|
|
DIA_LOG(0, "attached\n");
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* usb_gpib_detach() - deactivate the usb-gpib converter board
|
|
*
|
|
* @board: the gpib_board data area for this gpib interface
|
|
*
|
|
*/
|
|
|
|
static void usb_gpib_detach(struct gpib_board *board)
|
|
{
|
|
int retval;
|
|
|
|
show_status(board);
|
|
|
|
DIA_LOG(0, "detaching\n");
|
|
|
|
if (board->private_data) {
|
|
if (GPIB_DEV) {
|
|
write_loop(GPIB_DEV, USB_GPIB_OFF, strlen(USB_GPIB_OFF));
|
|
msleep(100);
|
|
DIA_LOG(1, "%s", "GPIB off\n");
|
|
retval = skel_do_release(board);
|
|
DIA_LOG(1, "skel release -> %d\n", retval);
|
|
}
|
|
kfree(board->private_data);
|
|
board->private_data = NULL;
|
|
}
|
|
|
|
DIA_LOG(0, "detached\n");
|
|
}
|
|
|
|
/*
|
|
* Other functions follow in alphabetical order
|
|
*/
|
|
/* command */
|
|
static int usb_gpib_command(struct gpib_board *board,
|
|
u8 *buffer,
|
|
size_t length,
|
|
size_t *bytes_written)
|
|
{
|
|
int i, retval;
|
|
char command[6] = "IBc.\n";
|
|
|
|
DIA_LOG(1, "enter %p\n", board);
|
|
|
|
set_timeout(board);
|
|
|
|
*bytes_written = 0;
|
|
for (i = 0 ; i < length ; i++) {
|
|
command[3] = buffer[i];
|
|
retval = send_command(board, command, 5);
|
|
DIA_LOG(2, "%d ==> %x %x\n", i, buffer[i], retval);
|
|
if (retval != 0x06)
|
|
return retval;
|
|
++(*bytes_written);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* usb_gpib_disable_eos() - Disable END on eos byte (END on EOI only)
|
|
*
|
|
* @board: the gpib_board data area for this gpib interface
|
|
*
|
|
* With the lpvo adapter eos can only be handled via software.
|
|
* Cannot do nothing here, but remember for future use.
|
|
*/
|
|
|
|
static void usb_gpib_disable_eos(struct gpib_board *board)
|
|
{
|
|
((struct usb_gpib_priv *)board->private_data)->eos_flags &= ~REOS;
|
|
DIA_LOG(1, "done: %x\n",
|
|
((struct usb_gpib_priv *)board->private_data)->eos_flags);
|
|
}
|
|
|
|
/**
|
|
* usb_gpib_enable_eos() - Enable END for reads when eos byte is received.
|
|
*
|
|
* @board: the gpib_board data area for this gpib interface
|
|
* @eos_byte: the 'eos' byte
|
|
* @compare_8_bits: if zero ignore eigthth bit when comparing
|
|
*
|
|
*/
|
|
|
|
static int usb_gpib_enable_eos(struct gpib_board *board,
|
|
u8 eos_byte,
|
|
int compare_8_bits)
|
|
{
|
|
struct usb_gpib_priv *pd = (struct usb_gpib_priv *)board->private_data;
|
|
|
|
DIA_LOG(1, "enter with %x\n", eos_byte);
|
|
pd->eos = eos_byte;
|
|
pd->eos_flags = REOS;
|
|
if (compare_8_bits)
|
|
pd->eos_flags |= BIN;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* usb_gpib_go_to_standby() - De-assert ATN
|
|
*
|
|
* @board: the gpib_board data area for this gpib interface
|
|
*/
|
|
|
|
static int usb_gpib_go_to_standby(struct gpib_board *board)
|
|
{
|
|
int retval = set_control_line(board, IB_BUS_ATN, 0);
|
|
|
|
DIA_LOG(1, "done with %x\n", retval);
|
|
|
|
if (retval == ACK)
|
|
return 0;
|
|
return -EIO;
|
|
}
|
|
|
|
/**
|
|
* usb_gpib_interface_clear() - Assert or de-assert IFC
|
|
*
|
|
* @board: the gpib_board data area for this gpib interface
|
|
* @assert: 1: assert IFC; 0: de-assert IFC
|
|
*
|
|
* Currently on the assert request we issue the lpvo IBZ
|
|
* command that cycles IFC low for 100 usec, then we ignore
|
|
* the de-assert request.
|
|
*/
|
|
|
|
static void usb_gpib_interface_clear(struct gpib_board *board, int assert)
|
|
{
|
|
int retval = 0;
|
|
|
|
DIA_LOG(1, "enter with %d\n", assert);
|
|
|
|
if (assert) {
|
|
retval = send_command(board, USB_GPIB_IBCL, 0);
|
|
|
|
set_bit(CIC_NUM, &board->status);
|
|
}
|
|
|
|
DIA_LOG(1, "done with %d %d\n", assert, retval);
|
|
}
|
|
|
|
/**
|
|
* usb_gpib_line_status() - Read the status of the bus lines.
|
|
*
|
|
* @board: the gpib_board data area for this gpib interface
|
|
*
|
|
* We can read all lines.
|
|
*/
|
|
static int usb_gpib_line_status(const struct gpib_board *board)
|
|
{
|
|
int buffer;
|
|
int line_status = VALID_ALL; /* all lines will be read */
|
|
struct list_head *p, *q;
|
|
WQT *item;
|
|
unsigned long flags;
|
|
int sleep = 0;
|
|
|
|
DIA_LOG(1, "%s\n", "request");
|
|
|
|
/*
|
|
* if we are on the wait queue (board->wait), do not hurry
|
|
* reading status line; instead, pause a little
|
|
*/
|
|
|
|
spin_lock_irqsave((spinlock_t *)&board->wait.lock, flags);
|
|
q = (struct list_head *)&board->wait.WQH;
|
|
list_for_each(p, q) {
|
|
item = container_of(p, WQT, WQE);
|
|
if (item->private == current) {
|
|
sleep = 20;
|
|
break;
|
|
}
|
|
/* pid is: ((struct task_struct *) item->private)->pid); */
|
|
}
|
|
spin_unlock_irqrestore((spinlock_t *)&board->wait.lock, flags);
|
|
if (sleep) {
|
|
DIA_LOG(1, "we are on the wait queue - sleep %d ms\n", sleep);
|
|
msleep(sleep);
|
|
}
|
|
|
|
buffer = send_command((struct gpib_board *)board, USB_GPIB_STATUS, 0);
|
|
|
|
if (buffer < 0) {
|
|
dev_err(board->gpib_dev, "line status read failed with %d\n", buffer);
|
|
return -1;
|
|
}
|
|
|
|
if ((buffer & 0x01) == 0)
|
|
line_status |= BUS_REN;
|
|
if ((buffer & 0x02) == 0)
|
|
line_status |= BUS_IFC;
|
|
if ((buffer & 0x04) == 0)
|
|
line_status |= BUS_NDAC;
|
|
if ((buffer & 0x08) == 0)
|
|
line_status |= BUS_NRFD;
|
|
if ((buffer & 0x10) == 0)
|
|
line_status |= BUS_DAV;
|
|
if ((buffer & 0x20) == 0)
|
|
line_status |= BUS_EOI;
|
|
if ((buffer & 0x40) == 0)
|
|
line_status |= BUS_ATN;
|
|
if ((buffer & 0x80) == 0)
|
|
line_status |= BUS_SRQ;
|
|
|
|
DIA_LOG(1, "done with %x %x\n", buffer, line_status);
|
|
|
|
return line_status;
|
|
}
|
|
|
|
/* parallel_poll */
|
|
|
|
static int usb_gpib_parallel_poll(struct gpib_board *board, u8 *result)
|
|
{
|
|
/*
|
|
* request parallel poll asserting ATN | EOI;
|
|
* we suppose ATN already asserted
|
|
*/
|
|
|
|
int retval;
|
|
|
|
DIA_LOG(1, "enter %p\n", board);
|
|
|
|
retval = set_control_line(board, IB_BUS_EOI, 1);
|
|
if (retval != ACK)
|
|
return -EIO;
|
|
|
|
*result = send_command(board, USB_GPIB_READ_DATA, 0);
|
|
|
|
DIA_LOG(1, "done with %x\n", *result);
|
|
|
|
retval = set_control_line(board, IB_BUS_EOI, 0);
|
|
if (retval != 0x06)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* read */
|
|
|
|
static int usb_gpib_read(struct gpib_board *board,
|
|
u8 *buffer,
|
|
size_t length,
|
|
int *end,
|
|
size_t *bytes_read)
|
|
{
|
|
#define MAX_READ_EXCESS 16384
|
|
|
|
struct char_buf b = {NULL, 0};
|
|
|
|
int retval;
|
|
char c, nc;
|
|
int ic;
|
|
struct timespec64 before, after;
|
|
int read_count = MAX_READ_EXCESS;
|
|
struct usb_gpib_priv *pd = (struct usb_gpib_priv *)board->private_data;
|
|
|
|
DIA_LOG(1, "enter %p -> %zu\n", board, length);
|
|
|
|
*bytes_read = 0; /* by default, things go wrong */
|
|
*end = 0;
|
|
|
|
set_timeout(board);
|
|
|
|
/* single byte read has a special handling */
|
|
|
|
if (length == 1) {
|
|
char inbuf[2] = {0, 0};
|
|
|
|
/* read a single character */
|
|
|
|
ktime_get_real_ts64 (&before);
|
|
|
|
retval = write_loop(GPIB_DEV, USB_GPIB_READ_1, strlen(USB_GPIB_READ_1));
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
retval = skel_do_read(GPIB_DEV, inbuf, 1);
|
|
retval += skel_do_read(GPIB_DEV, inbuf + 1, 1);
|
|
|
|
ktime_get_real_ts64 (&after);
|
|
|
|
DIA_LOG(1, "single read: %x %x %x in %d\n", retval,
|
|
inbuf[0], inbuf[1],
|
|
usec_diff(&after, &before));
|
|
|
|
/* good char / last char? */
|
|
|
|
if (retval == 2 && inbuf[1] == ACK) {
|
|
buffer[0] = inbuf[0];
|
|
*bytes_read = 1;
|
|
return 0;
|
|
}
|
|
if (retval < 2)
|
|
return -EIO;
|
|
else
|
|
return -ETIME;
|
|
}
|
|
|
|
/* allocate buffer for multibyte read */
|
|
|
|
b.inbuf = kmalloc(INBUF_SIZE, GFP_KERNEL);
|
|
if (!b.inbuf)
|
|
return -ENOMEM;
|
|
|
|
/* send read command and check <DLE><STX> sequence */
|
|
|
|
retval = write_loop(GPIB_DEV, USB_GPIB_READ, strlen(USB_GPIB_READ));
|
|
if (retval < 0)
|
|
goto read_return;
|
|
|
|
if (one_char(board, &b) != DLE || one_char(board, &b) != STX) {
|
|
dev_err(board->gpib_dev, "wrong <DLE><STX> sequence\n");
|
|
retval = -EIO;
|
|
goto read_return;
|
|
}
|
|
|
|
/* get data flow */
|
|
|
|
while (1) {
|
|
ic = one_char(board, &b);
|
|
if (ic == -EIO) {
|
|
retval = -EIO;
|
|
goto read_return;
|
|
}
|
|
c = ic;
|
|
|
|
if (c == DLE)
|
|
nc = one_char(board, &b);
|
|
if (c != DLE || nc == DLE) {
|
|
/* data byte - store into buffer */
|
|
|
|
if (*bytes_read == length)
|
|
break; /* data overflow */
|
|
if (c == DLE)
|
|
c = nc;
|
|
buffer[(*bytes_read)++] = c;
|
|
if (c == pd->eos) {
|
|
*end = 1;
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
/* we are in the closing <DLE><ETX> sequence */
|
|
c = nc;
|
|
if (c == ETX) {
|
|
c = one_char(board, &b);
|
|
if (c == ACK) {
|
|
*end = 1;
|
|
retval = 0;
|
|
goto read_return;
|
|
} else {
|
|
dev_err(board->gpib_dev, "wrong end of message %x", c);
|
|
retval = -ETIME;
|
|
goto read_return;
|
|
}
|
|
} else {
|
|
dev_err(board->gpib_dev, "lone <DLE> in stream");
|
|
retval = -EIO;
|
|
goto read_return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* we had a data overflow - flush excess data */
|
|
|
|
while (read_count--) {
|
|
if (one_char(board, &b) != DLE)
|
|
continue;
|
|
c = one_char(board, &b);
|
|
if (c == DLE)
|
|
continue;
|
|
if (c == ETX) {
|
|
c = one_char(board, &b);
|
|
if (c == ACK) {
|
|
if (MAX_READ_EXCESS - read_count > 1)
|
|
dev_dbg(board->gpib_dev, "small buffer - maybe some data lost");
|
|
retval = 0;
|
|
goto read_return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
dev_err(board->gpib_dev, "no input end - board in odd state\n");
|
|
retval = -EIO;
|
|
|
|
read_return:
|
|
kfree(b.inbuf);
|
|
|
|
DIA_LOG(1, "done with byte/status: %d %x %d\n", (int)*bytes_read, retval, *end);
|
|
|
|
if (retval == 0 || retval == -ETIME) {
|
|
if (send_command(board, USB_GPIB_UNTALK, sizeof(USB_GPIB_UNTALK)) == 0x06)
|
|
return retval;
|
|
return -EIO;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* remote_enable */
|
|
|
|
static void usb_gpib_remote_enable(struct gpib_board *board, int enable)
|
|
{
|
|
int retval;
|
|
|
|
retval = set_control_line(board, IB_BUS_REN, enable ? 1 : 0);
|
|
if (retval != ACK)
|
|
dev_err(board->gpib_dev, "could not set REN line: %x\n", retval);
|
|
|
|
DIA_LOG(1, "done with %x\n", retval);
|
|
}
|
|
|
|
/* request_system_control */
|
|
|
|
static int usb_gpib_request_system_control(struct gpib_board *board, int request_control)
|
|
{
|
|
if (!request_control)
|
|
return -EINVAL;
|
|
|
|
DIA_LOG(1, "done with %d -> %lx\n", request_control, board->status);
|
|
return 0;
|
|
}
|
|
|
|
/* take_control */
|
|
/* beware: the sync flag is ignored; what is its real meaning? */
|
|
|
|
static int usb_gpib_take_control(struct gpib_board *board, int sync)
|
|
{
|
|
int retval;
|
|
|
|
retval = set_control_line(board, IB_BUS_ATN, 1);
|
|
|
|
DIA_LOG(1, "done with %d %x\n", sync, retval);
|
|
|
|
if (retval == ACK)
|
|
return 0;
|
|
return -EIO;
|
|
}
|
|
|
|
/* update_status */
|
|
|
|
static unsigned int usb_gpib_update_status(struct gpib_board *board,
|
|
unsigned int clear_mask)
|
|
{
|
|
/* There is nothing we can do here, I guess */
|
|
|
|
board->status &= ~clear_mask;
|
|
|
|
DIA_LOG(1, "done with %x %lx\n", clear_mask, board->status);
|
|
|
|
return board->status;
|
|
}
|
|
|
|
/* write */
|
|
/* beware: DLE characters are not escaped - can only send ASCII data */
|
|
|
|
static int usb_gpib_write(struct gpib_board *board,
|
|
u8 *buffer,
|
|
size_t length,
|
|
int send_eoi,
|
|
size_t *bytes_written)
|
|
{
|
|
int retval;
|
|
char *msg;
|
|
|
|
DIA_LOG(1, "enter %p -> %zu\n", board, length);
|
|
|
|
set_timeout(board);
|
|
|
|
msg = kmalloc(length + 8, GFP_KERNEL);
|
|
if (!msg)
|
|
return -ENOMEM;
|
|
|
|
memcpy(msg, "\nIB\020\002", 5);
|
|
memcpy(msg + 5, buffer, length);
|
|
memcpy(msg + 5 + length, "\020\003\n", 3);
|
|
|
|
retval = send_command(board, msg, length + 8);
|
|
kfree(msg);
|
|
|
|
DIA_LOG(1, "<%.*s> -> %x\n", (int)length, buffer, retval);
|
|
|
|
if (retval != ACK)
|
|
return -EPIPE;
|
|
|
|
*bytes_written = length;
|
|
|
|
if (send_command(board, USB_GPIB_UNLISTEN, sizeof(USB_GPIB_UNLISTEN)) != 0x06)
|
|
return -EPIPE;
|
|
|
|
return length;
|
|
}
|
|
|
|
/*
|
|
* *** following functions not implemented yet ***
|
|
*/
|
|
|
|
/* parallel_poll configure */
|
|
|
|
static void usb_gpib_parallel_poll_configure(struct gpib_board *board,
|
|
u8 configuration)
|
|
{
|
|
}
|
|
|
|
/* parallel_poll_response */
|
|
|
|
static void usb_gpib_parallel_poll_response(struct gpib_board *board, int ist)
|
|
{
|
|
}
|
|
|
|
/* primary_address */
|
|
|
|
static int usb_gpib_primary_address(struct gpib_board *board, unsigned int address)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* return_to_local */
|
|
|
|
static void usb_gpib_return_to_local(struct gpib_board *board)
|
|
{
|
|
}
|
|
|
|
/* secondary_address */
|
|
|
|
static int usb_gpib_secondary_address(struct gpib_board *board,
|
|
unsigned int address,
|
|
int enable)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* serial_poll_response */
|
|
|
|
static void usb_gpib_serial_poll_response(struct gpib_board *board, u8 status)
|
|
{
|
|
}
|
|
|
|
/* serial_poll_status */
|
|
|
|
static u8 usb_gpib_serial_poll_status(struct gpib_board *board)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* t1_delay */
|
|
|
|
static int usb_gpib_t1_delay(struct gpib_board *board, unsigned int nano_sec)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* *** module dispatch table and init/exit functions ***
|
|
*/
|
|
|
|
static struct gpib_interface usb_gpib_interface = {
|
|
.name = NAME,
|
|
.attach = usb_gpib_attach,
|
|
.detach = usb_gpib_detach,
|
|
.read = usb_gpib_read,
|
|
.write = usb_gpib_write,
|
|
.command = usb_gpib_command,
|
|
.take_control = usb_gpib_take_control,
|
|
.go_to_standby = usb_gpib_go_to_standby,
|
|
.request_system_control = usb_gpib_request_system_control,
|
|
.interface_clear = usb_gpib_interface_clear,
|
|
.remote_enable = usb_gpib_remote_enable,
|
|
.enable_eos = usb_gpib_enable_eos,
|
|
.disable_eos = usb_gpib_disable_eos,
|
|
.parallel_poll = usb_gpib_parallel_poll,
|
|
.parallel_poll_configure = usb_gpib_parallel_poll_configure,
|
|
.parallel_poll_response = usb_gpib_parallel_poll_response,
|
|
.local_parallel_poll_mode = NULL, // XXX
|
|
.line_status = usb_gpib_line_status,
|
|
.update_status = usb_gpib_update_status,
|
|
.primary_address = usb_gpib_primary_address,
|
|
.secondary_address = usb_gpib_secondary_address,
|
|
.serial_poll_response = usb_gpib_serial_poll_response,
|
|
.serial_poll_status = usb_gpib_serial_poll_status,
|
|
.t1_delay = usb_gpib_t1_delay,
|
|
.return_to_local = usb_gpib_return_to_local,
|
|
.skip_check_for_command_acceptors = 1
|
|
};
|
|
|
|
/*
|
|
* usb_gpib_init_module(), usb_gpib_exit_module()
|
|
*
|
|
* This functions are called every time a new device is detected
|
|
* and registered or is removed and unregistered.
|
|
* We must take note of created and destroyed usb minors to be used
|
|
* when usb_gpib_attach() and usb_gpib_detach() will be called on
|
|
* request by gpib_config.
|
|
*/
|
|
|
|
static int usb_gpib_init_module(struct usb_interface *interface)
|
|
{
|
|
int j, mask, rv;
|
|
|
|
rv = mutex_lock_interruptible(&minors_lock);
|
|
if (rv < 0)
|
|
return rv;
|
|
|
|
if (!assigned_usb_minors) {
|
|
rv = gpib_register_driver(&usb_gpib_interface, THIS_MODULE);
|
|
if (rv) {
|
|
pr_err("gpib_register_driver failed: error = %d\n", rv);
|
|
goto exit;
|
|
}
|
|
} else {
|
|
/*
|
|
* check if minor is already registered - maybe useless, but if
|
|
* it happens the code is inconsistent somewhere
|
|
*/
|
|
|
|
for (j = 0 ; j < MAX_DEV ; j++) {
|
|
if (usb_minors[j] == interface->minor && assigned_usb_minors & 1 << j) {
|
|
pr_err("CODE BUG: USB minor %d registered at %d.\n",
|
|
interface->minor, j);
|
|
rv = -1;
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* find a free slot */
|
|
|
|
for (j = 0 ; j < MAX_DEV ; j++) {
|
|
mask = 1 << j;
|
|
if ((assigned_usb_minors & mask) == 0) {
|
|
usb_minors[j] = interface->minor;
|
|
lpvo_usb_interfaces[j] = interface;
|
|
assigned_usb_minors |= mask;
|
|
rv = 0;
|
|
goto exit;
|
|
}
|
|
}
|
|
pr_err("No slot available for interface %p minor %d\n", interface, interface->minor);
|
|
rv = -1;
|
|
|
|
exit:
|
|
mutex_unlock(&minors_lock);
|
|
return rv;
|
|
}
|
|
|
|
static void usb_gpib_exit_module(int minor)
|
|
{
|
|
int j;
|
|
|
|
mutex_lock(&minors_lock);
|
|
for (j = 0 ; j < MAX_DEV ; j++) {
|
|
if (usb_minors[j] == minor && assigned_usb_minors & 1 << j) {
|
|
assigned_usb_minors &= ~(1 << j);
|
|
usb_minors[j] = -1;
|
|
if (assigned_usb_minors == 0)
|
|
gpib_unregister_driver(&usb_gpib_interface);
|
|
goto exit;
|
|
}
|
|
}
|
|
pr_err("CODE BUG: USB minor %d not found.\n", minor);
|
|
|
|
exit:
|
|
mutex_unlock(&minors_lock);
|
|
}
|
|
|
|
/*
|
|
* Default latency time (16 msec) is too long.
|
|
* We must use 1 msec (best); anyhow, no more than 5 msec.
|
|
*
|
|
* Defines and function taken and modified from the kernel tree
|
|
* (see ftdi_sio.h and ftdi_sio.c).
|
|
*/
|
|
|
|
#define FTDI_SIO_SET_LATENCY_TIMER 9 /* Set the latency timer */
|
|
#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST FTDI_SIO_SET_LATENCY_TIMER
|
|
#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE 0x40
|
|
#define WDR_TIMEOUT 5000 /* default urb timeout */
|
|
#define WDR_SHORT_TIMEOUT 1000 /* shorter urb timeout */
|
|
|
|
#define LATENCY_TIMER 1 /* use a small latency timer: 1 ... 5 msec */
|
|
#define LATENCY_CHANNEL 0 /* channel selection in multichannel devices */
|
|
static int write_latency_timer(struct usb_device *udev)
|
|
{
|
|
int rv = usb_control_msg(udev,
|
|
usb_sndctrlpipe(udev, 0),
|
|
FTDI_SIO_SET_LATENCY_TIMER_REQUEST,
|
|
FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE,
|
|
LATENCY_TIMER, LATENCY_CHANNEL,
|
|
NULL, 0, WDR_TIMEOUT);
|
|
if (rv < 0)
|
|
dev_err(&udev->dev, "Unable to write latency timer: %i\n", rv);
|
|
return rv;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* *
|
|
* The following code is a modified version of the USB Skeleton driver *
|
|
* written by Greg Kroah-Hartman and available in the kernel tree. *
|
|
* *
|
|
* Functions skel_open() and skel_release() have been rewritten and named *
|
|
* skel_do_open() and skel_do_release() to process the attach and detach *
|
|
* requests coming from gpib_config. *
|
|
* *
|
|
* Functions skel_read() and skel_write() have been split into a *
|
|
* skel_do_read() and skel_do_write(), that cover the kernel stuff of read *
|
|
* and write operations, and the original skel_read() and skel_write(), *
|
|
* that handle communication with user space and call their _do_ companion. *
|
|
* *
|
|
* Only the _do_ versions are used by the lpvo_usb_gpib driver; other ones *
|
|
* can be (optionally) maintained in the compilation to have direct access *
|
|
* to a gpib controller for debug and diagnostics. *
|
|
* *
|
|
* To avoid collisions in names, devices in user space have been renamed *
|
|
* lpvo_raw1, lpvo_raw2 .... and the usb driver has been renamed with the *
|
|
* gpib module name. *
|
|
* *
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
* USB Skeleton driver - 2.2
|
|
*
|
|
* Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
|
|
*
|
|
* This driver is based on the 2.6.3 version of drivers/usb/usb-skeleton.c
|
|
* but has been rewritten to be easier to read and use.
|
|
*/
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/kref.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/mutex.h>
|
|
|
|
/* Get a minor range for your devices from the usb maintainer */
|
|
#define USB_SKEL_MINOR_BASE 192
|
|
|
|
/* private defines */
|
|
|
|
#define MAX_TRANSFER (PAGE_SIZE - 512)
|
|
/*
|
|
* MAX_TRANSFER is chosen so that the VM is not stressed by
|
|
* allocations > PAGE_SIZE and the number of packets in a page
|
|
* is an integer 512 is the largest possible packet on EHCI
|
|
*/
|
|
|
|
#define WRITES_IN_FLIGHT 1 /* we do not want more than one pending write */
|
|
#define USER_DEVICE 1 /* compile for device(s) in user space */
|
|
|
|
/* Structure to hold all of our device specific stuff */
|
|
struct usb_skel {
|
|
struct usb_device *udev; /* the usb device for this device */
|
|
struct usb_interface *interface; /* the interface for this device */
|
|
struct semaphore limit_sem; /* limiting the number of writes in progress */
|
|
struct usb_anchor submitted; /* in case need to retract our submissions */
|
|
struct urb *bulk_in_urb; /* the urb to read data with */
|
|
unsigned char *bulk_in_buffer; /* the buffer to receive data */
|
|
size_t bulk_in_size; /* the size of the receive buffer */
|
|
size_t bulk_in_filled; /* number of bytes in the buffer */
|
|
size_t bulk_in_copied; /* already copied to user space */
|
|
__u8 bulk_in_endpoint_addr; /* the address of the bulk in endpoint */
|
|
__u8 bulk_out_endpoint_addr; /* the address of the bulk out endpoint */
|
|
int errors; /* the last request tanked */
|
|
bool ongoing_read; /* a read is going on */
|
|
spinlock_t err_lock; /* lock for errors */
|
|
struct kref kref;
|
|
struct mutex io_mutex; /* synchronize I/O with disconnect */
|
|
wait_queue_head_t bulk_in_wait; /* to wait for an ongoing read */
|
|
};
|
|
|
|
#define to_skel_dev(d) container_of(d, struct usb_skel, kref)
|
|
|
|
static struct usb_driver skel_driver;
|
|
static void skel_draw_down(struct usb_skel *dev);
|
|
|
|
static void skel_delete(struct kref *kref)
|
|
{
|
|
struct usb_skel *dev = to_skel_dev(kref);
|
|
|
|
usb_free_urb(dev->bulk_in_urb);
|
|
usb_put_dev(dev->udev);
|
|
kfree(dev->bulk_in_buffer);
|
|
kfree(dev);
|
|
}
|
|
|
|
/*
|
|
* skel_do_open() - to be called by usb_gpib_attach
|
|
*/
|
|
|
|
static int skel_do_open(struct gpib_board *board, int subminor)
|
|
{
|
|
struct usb_skel *dev;
|
|
struct usb_interface *interface;
|
|
int retval = 0;
|
|
|
|
interface = usb_find_interface(&skel_driver, subminor);
|
|
if (!interface) {
|
|
dev_err(board->gpib_dev, "can't find device for minor %d\n", subminor);
|
|
retval = -ENODEV;
|
|
goto exit;
|
|
}
|
|
|
|
dev = usb_get_intfdata(interface);
|
|
if (!dev) {
|
|
retval = -ENODEV;
|
|
goto exit;
|
|
}
|
|
|
|
retval = usb_autopm_get_interface(interface);
|
|
if (retval)
|
|
goto exit;
|
|
|
|
/* increment our usage count for the device */
|
|
kref_get(&dev->kref);
|
|
|
|
/* save our object in the file's private structure */
|
|
GPIB_DEV = dev;
|
|
|
|
exit:
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* skel_do_release() - to be called by usb_gpib_detach
|
|
*/
|
|
|
|
static int skel_do_release(struct gpib_board *board)
|
|
{
|
|
struct usb_skel *dev;
|
|
|
|
dev = GPIB_DEV;
|
|
if (!dev)
|
|
return -ENODEV;
|
|
|
|
/* allow the device to be autosuspended */
|
|
mutex_lock(&dev->io_mutex);
|
|
if (dev->interface)
|
|
usb_autopm_put_interface(dev->interface);
|
|
mutex_unlock(&dev->io_mutex);
|
|
|
|
/* decrement the count on our device */
|
|
kref_put(&dev->kref, skel_delete);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* read functions
|
|
*/
|
|
|
|
static void skel_read_bulk_callback(struct urb *urb)
|
|
{
|
|
struct usb_skel *dev;
|
|
unsigned long flags;
|
|
|
|
dev = urb->context;
|
|
|
|
spin_lock_irqsave(&dev->err_lock, flags);
|
|
/* sync/async unlink faults aren't errors */
|
|
if (urb->status) {
|
|
if (!(urb->status == -ENOENT ||
|
|
urb->status == -ECONNRESET ||
|
|
urb->status == -ESHUTDOWN))
|
|
dev_err(&dev->interface->dev, "nonzero read bulk status received: %d\n",
|
|
urb->status);
|
|
|
|
dev->errors = urb->status;
|
|
} else {
|
|
dev->bulk_in_filled = urb->actual_length;
|
|
}
|
|
dev->ongoing_read = 0;
|
|
spin_unlock_irqrestore(&dev->err_lock, flags);
|
|
|
|
wake_up_interruptible(&dev->bulk_in_wait);
|
|
}
|
|
|
|
static int skel_do_read_io(struct usb_skel *dev, size_t count)
|
|
{
|
|
int rv;
|
|
|
|
/* prepare a read */
|
|
usb_fill_bulk_urb(dev->bulk_in_urb,
|
|
dev->udev,
|
|
usb_rcvbulkpipe(dev->udev,
|
|
dev->bulk_in_endpoint_addr),
|
|
dev->bulk_in_buffer,
|
|
min(dev->bulk_in_size, count),
|
|
skel_read_bulk_callback,
|
|
dev);
|
|
/* tell everybody to leave the URB alone */
|
|
spin_lock_irq(&dev->err_lock);
|
|
dev->ongoing_read = 1;
|
|
spin_unlock_irq(&dev->err_lock);
|
|
|
|
/* submit bulk in urb, which means no data to deliver */
|
|
dev->bulk_in_filled = 0;
|
|
dev->bulk_in_copied = 0;
|
|
|
|
/* do it */
|
|
rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL);
|
|
if (rv < 0) {
|
|
dev_err(&dev->interface->dev, "failed submitting read urb, error %d\n", rv);
|
|
rv = (rv == -ENOMEM) ? rv : -EIO;
|
|
spin_lock_irq(&dev->err_lock);
|
|
dev->ongoing_read = 0;
|
|
spin_unlock_irq(&dev->err_lock);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* skel_do_read() - read operations from lpvo_usb_gpib
|
|
*/
|
|
|
|
static ssize_t skel_do_read(struct usb_skel *dev, char *buffer, size_t count)
|
|
{
|
|
int rv;
|
|
bool ongoing_io;
|
|
|
|
/* if we cannot read at all, return EOF */
|
|
|
|
if (!dev->bulk_in_urb || !count)
|
|
return 0;
|
|
|
|
restart: /* added to comply with ftdi timeout technique */
|
|
|
|
/* no concurrent readers */
|
|
|
|
rv = mutex_lock_interruptible(&dev->io_mutex);
|
|
if (rv < 0)
|
|
return rv;
|
|
|
|
if (!dev->interface) { /* disconnect() was called */
|
|
rv = -ENODEV;
|
|
goto exit;
|
|
}
|
|
|
|
retry:
|
|
/* if IO is under way, we must not touch things */
|
|
spin_lock_irq(&dev->err_lock);
|
|
ongoing_io = dev->ongoing_read;
|
|
spin_unlock_irq(&dev->err_lock);
|
|
|
|
if (ongoing_io) {
|
|
// /* nonblocking IO shall not wait */
|
|
// /* no file, no O_NONBLOCK; maybe provide when from user space */
|
|
// if (file->f_flags & O_NONBLOCK) {
|
|
// rv = -EAGAIN;
|
|
// goto exit;
|
|
// }
|
|
|
|
/*
|
|
* IO may take forever
|
|
* hence wait in an interruptible state
|
|
*/
|
|
rv = wait_event_interruptible(dev->bulk_in_wait, (!dev->ongoing_read));
|
|
if (rv < 0)
|
|
goto exit;
|
|
}
|
|
|
|
/* errors must be reported */
|
|
rv = dev->errors;
|
|
if (rv < 0) {
|
|
/* any error is reported once */
|
|
dev->errors = 0;
|
|
/* to preserve notifications about reset */
|
|
rv = (rv == -EPIPE) ? rv : -EIO;
|
|
/* report it */
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* if the buffer is filled we may satisfy the read
|
|
* else we need to start IO
|
|
*/
|
|
|
|
if (dev->bulk_in_filled) {
|
|
/* we had read data */
|
|
|
|
size_t available = dev->bulk_in_filled - dev->bulk_in_copied;
|
|
// size_t chunk = min(available, count); /* compute chunk later */
|
|
size_t chunk;
|
|
|
|
if (!available) {
|
|
/*
|
|
* all data has been used
|
|
* actual IO needs to be done
|
|
*/
|
|
/*
|
|
* it seems that requests for less than dev->bulk_in_size
|
|
* are not accepted
|
|
*/
|
|
rv = skel_do_read_io(dev, dev->bulk_in_size);
|
|
if (rv < 0)
|
|
goto exit;
|
|
else
|
|
goto retry;
|
|
}
|
|
|
|
/*
|
|
* data is available - chunk tells us how much shall be copied
|
|
*/
|
|
|
|
/*
|
|
* Condition dev->bulk_in_copied > 0 maybe will never happen. In case,
|
|
* signal the event and copy using the original procedure, i.e., copy
|
|
* first two bytes also
|
|
*/
|
|
|
|
if (dev->bulk_in_copied) {
|
|
chunk = min(available, count);
|
|
memcpy(buffer, dev->bulk_in_buffer + dev->bulk_in_copied, chunk);
|
|
rv = chunk;
|
|
dev->bulk_in_copied += chunk;
|
|
|
|
/* copy discarding first two bytes that contain ftdi chip status */
|
|
|
|
} else {
|
|
/* account for two bytes to be discarded */
|
|
chunk = min(available, count + 2);
|
|
if (chunk < 2) {
|
|
dev_err(&dev->udev->dev, "BAD READ - chunk: %zu\n", chunk);
|
|
rv = -EIO;
|
|
goto exit;
|
|
}
|
|
|
|
memcpy(buffer, dev->bulk_in_buffer + 2, chunk - 2);
|
|
rv = chunk;
|
|
dev->bulk_in_copied += chunk;
|
|
}
|
|
|
|
/*
|
|
* if we are asked for more than we have,
|
|
* we start IO but don't wait
|
|
*
|
|
* No, no read ahead allowed; if the case, more data will be
|
|
* asked for by the lpvo_usb_gpib layer.
|
|
*/
|
|
// if (available < count)
|
|
// skel_do_read_io(dev, dev->bulk_in_size);
|
|
} else {
|
|
/* no data in the buffer */
|
|
rv = skel_do_read_io(dev, dev->bulk_in_size);
|
|
if (rv < 0)
|
|
goto exit;
|
|
else
|
|
goto retry;
|
|
}
|
|
exit:
|
|
mutex_unlock(&dev->io_mutex);
|
|
if (rv == 2)
|
|
goto restart; /* ftdi chip returns two status bytes after a latency anyhow */
|
|
|
|
if (rv > 0)
|
|
return rv - 2; /* account for 2 discarded bytes in a valid buffer */
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* write functions
|
|
*/
|
|
|
|
static void skel_write_bulk_callback(struct urb *urb)
|
|
{
|
|
struct usb_skel *dev;
|
|
unsigned long flags;
|
|
|
|
dev = urb->context;
|
|
|
|
/* sync/async unlink faults aren't errors */
|
|
if (urb->status) {
|
|
if (!(urb->status == -ENOENT ||
|
|
urb->status == -ECONNRESET ||
|
|
urb->status == -ESHUTDOWN))
|
|
dev_err(&dev->interface->dev,
|
|
"nonzero write bulk status received: %d\n", urb->status);
|
|
|
|
spin_lock_irqsave(&dev->err_lock, flags);
|
|
dev->errors = urb->status;
|
|
spin_unlock_irqrestore(&dev->err_lock, flags);
|
|
}
|
|
|
|
/* free up our allocated buffer */
|
|
usb_free_coherent(urb->dev, urb->transfer_buffer_length,
|
|
urb->transfer_buffer, urb->transfer_dma);
|
|
up(&dev->limit_sem);
|
|
}
|
|
|
|
/*
|
|
* skel_do_write() - write operations from lpvo_usb_gpib
|
|
*/
|
|
|
|
static ssize_t skel_do_write(struct usb_skel *dev, const char *buffer, size_t count)
|
|
{
|
|
int retval = 0;
|
|
struct urb *urb = NULL;
|
|
char *buf = NULL;
|
|
size_t writesize = min_t(size_t, count, (size_t)MAX_TRANSFER);
|
|
|
|
/* verify that we actually have some data to write */
|
|
if (count == 0)
|
|
goto exit;
|
|
|
|
/*
|
|
* limit the number of URBs in flight to stop a user from using up all
|
|
* RAM
|
|
*/
|
|
/* Only one URB is used, because we can't have a pending write() and go on */
|
|
|
|
// if (!(file->f_flags & O_NONBLOCK)) { /* no NONBLOCK provided */
|
|
if (down_interruptible(&dev->limit_sem)) {
|
|
retval = -ERESTARTSYS;
|
|
goto exit;
|
|
}
|
|
// } else {
|
|
// if (down_trylock(&dev->limit_sem)) {
|
|
// retval = -EAGAIN;
|
|
// goto exit;
|
|
// }
|
|
// }
|
|
|
|
spin_lock_irq(&dev->err_lock);
|
|
retval = dev->errors;
|
|
if (retval < 0) {
|
|
/* any error is reported once */
|
|
dev->errors = 0;
|
|
/* to preserve notifications about reset */
|
|
retval = (retval == -EPIPE) ? retval : -EIO;
|
|
}
|
|
spin_unlock_irq(&dev->err_lock);
|
|
if (retval < 0)
|
|
goto error;
|
|
|
|
/* create a urb, and a buffer for it, and copy the data to the urb */
|
|
urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (!urb) {
|
|
retval = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL,
|
|
&urb->transfer_dma);
|
|
if (!buf) {
|
|
retval = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
memcpy(buf, buffer, count);
|
|
|
|
/* this lock makes sure we don't submit URBs to gone devices */
|
|
mutex_lock(&dev->io_mutex);
|
|
if (!dev->interface) { /* disconnect() was called */
|
|
mutex_unlock(&dev->io_mutex);
|
|
retval = -ENODEV;
|
|
goto error;
|
|
}
|
|
|
|
/* initialize the urb properly */
|
|
usb_fill_bulk_urb(urb, dev->udev,
|
|
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpoint_addr),
|
|
buf, writesize, skel_write_bulk_callback, dev);
|
|
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
|
usb_anchor_urb(urb, &dev->submitted);
|
|
|
|
/* send the data out the bulk port */
|
|
retval = usb_submit_urb(urb, GFP_KERNEL);
|
|
mutex_unlock(&dev->io_mutex);
|
|
if (retval) {
|
|
dev_err(&dev->interface->dev, "failed submitting write urb, error %d\n", retval);
|
|
goto error_unanchor;
|
|
}
|
|
|
|
/*
|
|
* release our reference to this urb, the USB core will eventually free
|
|
* it entirely
|
|
*/
|
|
usb_free_urb(urb);
|
|
|
|
return writesize;
|
|
|
|
error_unanchor:
|
|
usb_unanchor_urb(urb);
|
|
error:
|
|
if (urb) {
|
|
usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma);
|
|
usb_free_urb(urb);
|
|
}
|
|
up(&dev->limit_sem);
|
|
|
|
exit:
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* services for the user space devices
|
|
*/
|
|
|
|
#if USER_DEVICE /* conditional compilation of user space device */
|
|
|
|
static int skel_flush(struct file *file, fl_owner_t id)
|
|
{
|
|
struct usb_skel *dev;
|
|
int res;
|
|
|
|
dev = file->private_data;
|
|
if (!dev)
|
|
return -ENODEV;
|
|
|
|
/* wait for io to stop */
|
|
mutex_lock(&dev->io_mutex);
|
|
skel_draw_down(dev);
|
|
|
|
/* read out errors, leave subsequent opens a clean slate */
|
|
spin_lock_irq(&dev->err_lock);
|
|
res = dev->errors ? (dev->errors == -EPIPE ? -EPIPE : -EIO) : 0;
|
|
dev->errors = 0;
|
|
spin_unlock_irq(&dev->err_lock);
|
|
|
|
mutex_unlock(&dev->io_mutex);
|
|
|
|
return res;
|
|
}
|
|
|
|
static int skel_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct usb_skel *dev;
|
|
struct usb_interface *interface;
|
|
int subminor;
|
|
int retval = 0;
|
|
|
|
subminor = iminor(inode);
|
|
|
|
interface = usb_find_interface(&skel_driver, subminor);
|
|
if (!interface) {
|
|
pr_err("can't find device for minor %d\n", subminor);
|
|
retval = -ENODEV;
|
|
goto exit;
|
|
}
|
|
|
|
dev = usb_get_intfdata(interface);
|
|
if (!dev) {
|
|
retval = -ENODEV;
|
|
goto exit;
|
|
}
|
|
|
|
retval = usb_autopm_get_interface(interface);
|
|
if (retval)
|
|
goto exit;
|
|
|
|
/* increment our usage count for the device */
|
|
kref_get(&dev->kref);
|
|
|
|
/* save our object in the file's private structure */
|
|
file->private_data = dev;
|
|
|
|
exit:
|
|
return retval;
|
|
}
|
|
|
|
static int skel_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct usb_skel *dev;
|
|
|
|
dev = file->private_data;
|
|
if (!dev)
|
|
return -ENODEV;
|
|
|
|
/* allow the device to be autosuspended */
|
|
mutex_lock(&dev->io_mutex);
|
|
if (dev->interface)
|
|
usb_autopm_put_interface(dev->interface);
|
|
mutex_unlock(&dev->io_mutex);
|
|
|
|
/* decrement the count on our device */
|
|
kref_put(&dev->kref, skel_delete);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* user space access to read function
|
|
*/
|
|
|
|
static ssize_t skel_read(struct file *file, char __user *buffer, size_t count,
|
|
loff_t *ppos)
|
|
{
|
|
struct usb_skel *dev;
|
|
char *buf;
|
|
ssize_t rv;
|
|
|
|
dev = file->private_data;
|
|
|
|
buf = kmalloc(count, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
rv = skel_do_read(dev, buf, count);
|
|
|
|
if (rv > 0) {
|
|
if (copy_to_user(buffer, buf, rv)) {
|
|
kfree(buf);
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
kfree(buf);
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* user space access to write function
|
|
*/
|
|
|
|
static ssize_t skel_write(struct file *file, const char __user *user_buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct usb_skel *dev;
|
|
char *buf;
|
|
ssize_t rv;
|
|
|
|
dev = file->private_data;
|
|
|
|
buf = kmalloc(count, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(buf, user_buffer, count)) {
|
|
kfree(buf);
|
|
return -EFAULT;
|
|
}
|
|
|
|
rv = skel_do_write(dev, buf, count);
|
|
kfree(buf);
|
|
return rv;
|
|
}
|
|
#endif
|
|
|
|
static const struct file_operations skel_fops = {
|
|
.owner = THIS_MODULE,
|
|
#if USER_DEVICE
|
|
.read = skel_read,
|
|
.write = skel_write,
|
|
.open = skel_open,
|
|
.release = skel_release,
|
|
.flush = skel_flush,
|
|
.llseek = noop_llseek,
|
|
#endif
|
|
};
|
|
|
|
/*
|
|
* usb class driver info in order to get a minor number from the usb core,
|
|
* and to have the device registered with the driver core
|
|
*/
|
|
#if USER_DEVICE
|
|
static struct usb_class_driver skel_class = {
|
|
.name = "lpvo_raw%d",
|
|
.fops = &skel_fops,
|
|
.minor_base = USB_SKEL_MINOR_BASE,
|
|
};
|
|
#endif
|
|
|
|
static int skel_probe(struct usb_interface *interface,
|
|
const struct usb_device_id *id)
|
|
{
|
|
struct usb_skel *dev;
|
|
struct usb_endpoint_descriptor *bulk_in, *bulk_out;
|
|
int retval;
|
|
char *device_path;
|
|
|
|
mutex_init(&minors_lock); /* required for handling minor numbers table */
|
|
|
|
/* allocate memory for our device state and initialize it */
|
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
kref_init(&dev->kref);
|
|
sema_init(&dev->limit_sem, WRITES_IN_FLIGHT);
|
|
mutex_init(&dev->io_mutex);
|
|
spin_lock_init(&dev->err_lock);
|
|
init_usb_anchor(&dev->submitted);
|
|
init_waitqueue_head(&dev->bulk_in_wait);
|
|
|
|
dev->udev = usb_get_dev(interface_to_usbdev(interface));
|
|
dev->interface = interface;
|
|
|
|
/* set up the endpoint information */
|
|
/* use only the first bulk-in and bulk-out endpoints */
|
|
retval = usb_find_common_endpoints(interface->cur_altsetting,
|
|
&bulk_in, &bulk_out, NULL, NULL);
|
|
if (retval) {
|
|
dev_err(&interface->dev,
|
|
"Could not find both bulk-in and bulk-out endpoints\n");
|
|
goto error;
|
|
}
|
|
|
|
dev->bulk_in_size = usb_endpoint_maxp(bulk_in);
|
|
dev->bulk_in_endpoint_addr = bulk_in->bEndpointAddress;
|
|
dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL);
|
|
if (!dev->bulk_in_buffer) {
|
|
retval = -ENOMEM;
|
|
goto error;
|
|
}
|
|
dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (!dev->bulk_in_urb) {
|
|
retval = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
dev->bulk_out_endpoint_addr = bulk_out->bEndpointAddress;
|
|
|
|
/* save our data pointer in this interface device */
|
|
usb_set_intfdata(interface, dev);
|
|
|
|
/* let the world know */
|
|
|
|
device_path = kobject_get_path(&dev->udev->dev.kobj, GFP_KERNEL);
|
|
dev_dbg(&interface->dev, "New lpvo_usb_device -> bus: %d dev: %d path: %s\n",
|
|
dev->udev->bus->busnum, dev->udev->devnum, device_path);
|
|
kfree(device_path);
|
|
|
|
#if USER_DEVICE
|
|
/* we can register the device now, as it is ready */
|
|
retval = usb_register_dev(interface, &skel_class);
|
|
if (retval) {
|
|
/* something prevented us from registering this driver */
|
|
dev_err(&interface->dev,
|
|
"Not able to get a minor for this device.\n");
|
|
usb_set_intfdata(interface, NULL);
|
|
goto error;
|
|
}
|
|
#endif
|
|
|
|
write_latency_timer(dev->udev); /* adjust the latency timer */
|
|
|
|
usb_gpib_init_module(interface); /* last, init the lpvo for this minor */
|
|
|
|
return 0;
|
|
|
|
error:
|
|
/* this frees allocated memory */
|
|
kref_put(&dev->kref, skel_delete);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void skel_disconnect(struct usb_interface *interface)
|
|
{
|
|
struct usb_skel *dev;
|
|
int minor = interface->minor;
|
|
|
|
usb_gpib_exit_module(minor); /* first, disactivate the lpvo */
|
|
|
|
dev = usb_get_intfdata(interface);
|
|
usb_set_intfdata(interface, NULL);
|
|
|
|
#if USER_DEVICE
|
|
/* give back our minor */
|
|
usb_deregister_dev(interface, &skel_class);
|
|
#endif
|
|
|
|
/* prevent more I/O from starting */
|
|
mutex_lock(&dev->io_mutex);
|
|
dev->interface = NULL;
|
|
mutex_unlock(&dev->io_mutex);
|
|
|
|
usb_kill_anchored_urbs(&dev->submitted);
|
|
|
|
/* decrement our usage count */
|
|
kref_put(&dev->kref, skel_delete);
|
|
}
|
|
|
|
static void skel_draw_down(struct usb_skel *dev)
|
|
{
|
|
int time;
|
|
|
|
time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000);
|
|
if (!time)
|
|
usb_kill_anchored_urbs(&dev->submitted);
|
|
usb_kill_urb(dev->bulk_in_urb);
|
|
}
|
|
|
|
static int skel_suspend(struct usb_interface *intf, pm_message_t message)
|
|
{
|
|
struct usb_skel *dev = usb_get_intfdata(intf);
|
|
|
|
if (!dev)
|
|
return 0;
|
|
skel_draw_down(dev);
|
|
return 0;
|
|
}
|
|
|
|
static int skel_resume(struct usb_interface *intf)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int skel_pre_reset(struct usb_interface *intf)
|
|
{
|
|
struct usb_skel *dev = usb_get_intfdata(intf);
|
|
|
|
mutex_lock(&dev->io_mutex);
|
|
skel_draw_down(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int skel_post_reset(struct usb_interface *intf)
|
|
{
|
|
struct usb_skel *dev = usb_get_intfdata(intf);
|
|
|
|
/* we are sure no URBs are active - no locking needed */
|
|
dev->errors = -EPIPE;
|
|
mutex_unlock(&dev->io_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct usb_driver skel_driver = {
|
|
.name = NAME,
|
|
.probe = skel_probe,
|
|
.disconnect = skel_disconnect,
|
|
.suspend = skel_suspend,
|
|
.resume = skel_resume,
|
|
.pre_reset = skel_pre_reset,
|
|
.post_reset = skel_post_reset,
|
|
.id_table = skel_table,
|
|
.supports_autosuspend = 1,
|
|
};
|
|
|
|
module_usb_driver(skel_driver);
|