18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * adutux - driver for ADU devices from Ontrak Control Systems
48c2ecf20Sopenharmony_ci * This is an experimental driver. Use at your own risk.
58c2ecf20Sopenharmony_ci * This driver is not supported by Ontrak Control Systems.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright (c) 2003 John Homppi (SCO, leave this notice here)
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * derived from the Lego USB Tower driver 0.56:
108c2ecf20Sopenharmony_ci * Copyright (c) 2003 David Glance <davidgsf@sourceforge.net>
118c2ecf20Sopenharmony_ci *               2001 Juergen Stuber <stuber@loria.fr>
128c2ecf20Sopenharmony_ci * that was derived from USB Skeleton driver - 0.5
138c2ecf20Sopenharmony_ci * Copyright (c) 2001 Greg Kroah-Hartman (greg@kroah.com)
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci */
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <linux/kernel.h>
208c2ecf20Sopenharmony_ci#include <linux/sched/signal.h>
218c2ecf20Sopenharmony_ci#include <linux/errno.h>
228c2ecf20Sopenharmony_ci#include <linux/slab.h>
238c2ecf20Sopenharmony_ci#include <linux/module.h>
248c2ecf20Sopenharmony_ci#include <linux/usb.h>
258c2ecf20Sopenharmony_ci#include <linux/mutex.h>
268c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci#define DRIVER_AUTHOR "John Homppi"
298c2ecf20Sopenharmony_ci#define DRIVER_DESC "adutux (see www.ontrak.net)"
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci/* Define these values to match your device */
328c2ecf20Sopenharmony_ci#define ADU_VENDOR_ID 0x0a07
338c2ecf20Sopenharmony_ci#define ADU_PRODUCT_ID 0x0064
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci/* table of devices that work with this driver */
368c2ecf20Sopenharmony_cistatic const struct usb_device_id device_table[] = {
378c2ecf20Sopenharmony_ci	{ USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID) },		/* ADU100 */
388c2ecf20Sopenharmony_ci	{ USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+20) },	/* ADU120 */
398c2ecf20Sopenharmony_ci	{ USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+30) },	/* ADU130 */
408c2ecf20Sopenharmony_ci	{ USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+100) },	/* ADU200 */
418c2ecf20Sopenharmony_ci	{ USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+108) },	/* ADU208 */
428c2ecf20Sopenharmony_ci	{ USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+118) },	/* ADU218 */
438c2ecf20Sopenharmony_ci	{ } /* Terminating entry */
448c2ecf20Sopenharmony_ci};
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, device_table);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci#ifdef CONFIG_USB_DYNAMIC_MINORS
498c2ecf20Sopenharmony_ci#define ADU_MINOR_BASE	0
508c2ecf20Sopenharmony_ci#else
518c2ecf20Sopenharmony_ci#define ADU_MINOR_BASE	67
528c2ecf20Sopenharmony_ci#endif
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci/* we can have up to this number of device plugged in at once */
558c2ecf20Sopenharmony_ci#define MAX_DEVICES	16
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci#define COMMAND_TIMEOUT	(2*HZ)
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci/*
608c2ecf20Sopenharmony_ci * The locking scheme is a vanilla 3-lock:
618c2ecf20Sopenharmony_ci *   adu_device.buflock: A spinlock, covers what IRQs touch.
628c2ecf20Sopenharmony_ci *   adutux_mutex:       A Static lock to cover open_count. It would also cover
638c2ecf20Sopenharmony_ci *                       any globals, but we don't have them in 2.6.
648c2ecf20Sopenharmony_ci *   adu_device.mtx:     A mutex to hold across sleepers like copy_from_user.
658c2ecf20Sopenharmony_ci *                       It covers all of adu_device, except the open_count
668c2ecf20Sopenharmony_ci *                       and what .buflock covers.
678c2ecf20Sopenharmony_ci */
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci/* Structure to hold all of our device specific stuff */
708c2ecf20Sopenharmony_cistruct adu_device {
718c2ecf20Sopenharmony_ci	struct mutex		mtx;
728c2ecf20Sopenharmony_ci	struct usb_device *udev; /* save off the usb device pointer */
738c2ecf20Sopenharmony_ci	struct usb_interface *interface;
748c2ecf20Sopenharmony_ci	unsigned int		minor; /* the starting minor number for this device */
758c2ecf20Sopenharmony_ci	char			serial_number[8];
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	int			open_count; /* number of times this port has been opened */
788c2ecf20Sopenharmony_ci	unsigned long		disconnected:1;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	char		*read_buffer_primary;
818c2ecf20Sopenharmony_ci	int			read_buffer_length;
828c2ecf20Sopenharmony_ci	char		*read_buffer_secondary;
838c2ecf20Sopenharmony_ci	int			secondary_head;
848c2ecf20Sopenharmony_ci	int			secondary_tail;
858c2ecf20Sopenharmony_ci	spinlock_t		buflock;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	wait_queue_head_t	read_wait;
888c2ecf20Sopenharmony_ci	wait_queue_head_t	write_wait;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	char		*interrupt_in_buffer;
918c2ecf20Sopenharmony_ci	struct usb_endpoint_descriptor *interrupt_in_endpoint;
928c2ecf20Sopenharmony_ci	struct urb	*interrupt_in_urb;
938c2ecf20Sopenharmony_ci	int			read_urb_finished;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	char		*interrupt_out_buffer;
968c2ecf20Sopenharmony_ci	struct usb_endpoint_descriptor *interrupt_out_endpoint;
978c2ecf20Sopenharmony_ci	struct urb	*interrupt_out_urb;
988c2ecf20Sopenharmony_ci	int			out_urb_finished;
998c2ecf20Sopenharmony_ci};
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(adutux_mutex);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_cistatic struct usb_driver adu_driver;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cistatic inline void adu_debug_data(struct device *dev, const char *function,
1068c2ecf20Sopenharmony_ci				  int size, const unsigned char *data)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	dev_dbg(dev, "%s - length = %d, data = %*ph\n",
1098c2ecf20Sopenharmony_ci		function, size, size, data);
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci/*
1138c2ecf20Sopenharmony_ci * adu_abort_transfers
1148c2ecf20Sopenharmony_ci *      aborts transfers and frees associated data structures
1158c2ecf20Sopenharmony_ci */
1168c2ecf20Sopenharmony_cistatic void adu_abort_transfers(struct adu_device *dev)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci	unsigned long flags;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	if (dev->disconnected)
1218c2ecf20Sopenharmony_ci		return;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	/* shutdown transfer */
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	/* XXX Anchor these instead */
1268c2ecf20Sopenharmony_ci	spin_lock_irqsave(&dev->buflock, flags);
1278c2ecf20Sopenharmony_ci	if (!dev->read_urb_finished) {
1288c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&dev->buflock, flags);
1298c2ecf20Sopenharmony_ci		usb_kill_urb(dev->interrupt_in_urb);
1308c2ecf20Sopenharmony_ci	} else
1318c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&dev->buflock, flags);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	spin_lock_irqsave(&dev->buflock, flags);
1348c2ecf20Sopenharmony_ci	if (!dev->out_urb_finished) {
1358c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&dev->buflock, flags);
1368c2ecf20Sopenharmony_ci		wait_event_timeout(dev->write_wait, dev->out_urb_finished,
1378c2ecf20Sopenharmony_ci			COMMAND_TIMEOUT);
1388c2ecf20Sopenharmony_ci		usb_kill_urb(dev->interrupt_out_urb);
1398c2ecf20Sopenharmony_ci	} else
1408c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&dev->buflock, flags);
1418c2ecf20Sopenharmony_ci}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic void adu_delete(struct adu_device *dev)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	/* free data structures */
1468c2ecf20Sopenharmony_ci	usb_free_urb(dev->interrupt_in_urb);
1478c2ecf20Sopenharmony_ci	usb_free_urb(dev->interrupt_out_urb);
1488c2ecf20Sopenharmony_ci	kfree(dev->read_buffer_primary);
1498c2ecf20Sopenharmony_ci	kfree(dev->read_buffer_secondary);
1508c2ecf20Sopenharmony_ci	kfree(dev->interrupt_in_buffer);
1518c2ecf20Sopenharmony_ci	kfree(dev->interrupt_out_buffer);
1528c2ecf20Sopenharmony_ci	usb_put_dev(dev->udev);
1538c2ecf20Sopenharmony_ci	kfree(dev);
1548c2ecf20Sopenharmony_ci}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_cistatic void adu_interrupt_in_callback(struct urb *urb)
1578c2ecf20Sopenharmony_ci{
1588c2ecf20Sopenharmony_ci	struct adu_device *dev = urb->context;
1598c2ecf20Sopenharmony_ci	int status = urb->status;
1608c2ecf20Sopenharmony_ci	unsigned long flags;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	adu_debug_data(&dev->udev->dev, __func__,
1638c2ecf20Sopenharmony_ci		       urb->actual_length, urb->transfer_buffer);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	spin_lock_irqsave(&dev->buflock, flags);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	if (status != 0) {
1688c2ecf20Sopenharmony_ci		if ((status != -ENOENT) && (status != -ECONNRESET) &&
1698c2ecf20Sopenharmony_ci			(status != -ESHUTDOWN)) {
1708c2ecf20Sopenharmony_ci			dev_dbg(&dev->udev->dev,
1718c2ecf20Sopenharmony_ci				"%s : nonzero status received: %d\n",
1728c2ecf20Sopenharmony_ci				__func__, status);
1738c2ecf20Sopenharmony_ci		}
1748c2ecf20Sopenharmony_ci		goto exit;
1758c2ecf20Sopenharmony_ci	}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	if (urb->actual_length > 0 && dev->interrupt_in_buffer[0] != 0x00) {
1788c2ecf20Sopenharmony_ci		if (dev->read_buffer_length <
1798c2ecf20Sopenharmony_ci		    (4 * usb_endpoint_maxp(dev->interrupt_in_endpoint)) -
1808c2ecf20Sopenharmony_ci		     (urb->actual_length)) {
1818c2ecf20Sopenharmony_ci			memcpy (dev->read_buffer_primary +
1828c2ecf20Sopenharmony_ci				dev->read_buffer_length,
1838c2ecf20Sopenharmony_ci				dev->interrupt_in_buffer, urb->actual_length);
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci			dev->read_buffer_length += urb->actual_length;
1868c2ecf20Sopenharmony_ci			dev_dbg(&dev->udev->dev,"%s reading  %d\n", __func__,
1878c2ecf20Sopenharmony_ci				urb->actual_length);
1888c2ecf20Sopenharmony_ci		} else {
1898c2ecf20Sopenharmony_ci			dev_dbg(&dev->udev->dev,"%s : read_buffer overflow\n",
1908c2ecf20Sopenharmony_ci				__func__);
1918c2ecf20Sopenharmony_ci		}
1928c2ecf20Sopenharmony_ci	}
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ciexit:
1958c2ecf20Sopenharmony_ci	dev->read_urb_finished = 1;
1968c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&dev->buflock, flags);
1978c2ecf20Sopenharmony_ci	/* always wake up so we recover from errors */
1988c2ecf20Sopenharmony_ci	wake_up_interruptible(&dev->read_wait);
1998c2ecf20Sopenharmony_ci}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_cistatic void adu_interrupt_out_callback(struct urb *urb)
2028c2ecf20Sopenharmony_ci{
2038c2ecf20Sopenharmony_ci	struct adu_device *dev = urb->context;
2048c2ecf20Sopenharmony_ci	int status = urb->status;
2058c2ecf20Sopenharmony_ci	unsigned long flags;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	adu_debug_data(&dev->udev->dev, __func__,
2088c2ecf20Sopenharmony_ci		       urb->actual_length, urb->transfer_buffer);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	if (status != 0) {
2118c2ecf20Sopenharmony_ci		if ((status != -ENOENT) &&
2128c2ecf20Sopenharmony_ci		    (status != -ESHUTDOWN) &&
2138c2ecf20Sopenharmony_ci		    (status != -ECONNRESET)) {
2148c2ecf20Sopenharmony_ci			dev_dbg(&dev->udev->dev,
2158c2ecf20Sopenharmony_ci				"%s :nonzero status received: %d\n", __func__,
2168c2ecf20Sopenharmony_ci				status);
2178c2ecf20Sopenharmony_ci		}
2188c2ecf20Sopenharmony_ci		return;
2198c2ecf20Sopenharmony_ci	}
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	spin_lock_irqsave(&dev->buflock, flags);
2228c2ecf20Sopenharmony_ci	dev->out_urb_finished = 1;
2238c2ecf20Sopenharmony_ci	wake_up(&dev->write_wait);
2248c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&dev->buflock, flags);
2258c2ecf20Sopenharmony_ci}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_cistatic int adu_open(struct inode *inode, struct file *file)
2288c2ecf20Sopenharmony_ci{
2298c2ecf20Sopenharmony_ci	struct adu_device *dev = NULL;
2308c2ecf20Sopenharmony_ci	struct usb_interface *interface;
2318c2ecf20Sopenharmony_ci	int subminor;
2328c2ecf20Sopenharmony_ci	int retval;
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	subminor = iminor(inode);
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	retval = mutex_lock_interruptible(&adutux_mutex);
2378c2ecf20Sopenharmony_ci	if (retval)
2388c2ecf20Sopenharmony_ci		goto exit_no_lock;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	interface = usb_find_interface(&adu_driver, subminor);
2418c2ecf20Sopenharmony_ci	if (!interface) {
2428c2ecf20Sopenharmony_ci		pr_err("%s - error, can't find device for minor %d\n",
2438c2ecf20Sopenharmony_ci		       __func__, subminor);
2448c2ecf20Sopenharmony_ci		retval = -ENODEV;
2458c2ecf20Sopenharmony_ci		goto exit_no_device;
2468c2ecf20Sopenharmony_ci	}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	dev = usb_get_intfdata(interface);
2498c2ecf20Sopenharmony_ci	if (!dev) {
2508c2ecf20Sopenharmony_ci		retval = -ENODEV;
2518c2ecf20Sopenharmony_ci		goto exit_no_device;
2528c2ecf20Sopenharmony_ci	}
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	/* check that nobody else is using the device */
2558c2ecf20Sopenharmony_ci	if (dev->open_count) {
2568c2ecf20Sopenharmony_ci		retval = -EBUSY;
2578c2ecf20Sopenharmony_ci		goto exit_no_device;
2588c2ecf20Sopenharmony_ci	}
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci	++dev->open_count;
2618c2ecf20Sopenharmony_ci	dev_dbg(&dev->udev->dev, "%s: open count %d\n", __func__,
2628c2ecf20Sopenharmony_ci		dev->open_count);
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	/* save device in the file's private structure */
2658c2ecf20Sopenharmony_ci	file->private_data = dev;
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	/* initialize in direction */
2688c2ecf20Sopenharmony_ci	dev->read_buffer_length = 0;
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	/* fixup first read by having urb waiting for it */
2718c2ecf20Sopenharmony_ci	usb_fill_int_urb(dev->interrupt_in_urb, dev->udev,
2728c2ecf20Sopenharmony_ci			 usb_rcvintpipe(dev->udev,
2738c2ecf20Sopenharmony_ci					dev->interrupt_in_endpoint->bEndpointAddress),
2748c2ecf20Sopenharmony_ci			 dev->interrupt_in_buffer,
2758c2ecf20Sopenharmony_ci			 usb_endpoint_maxp(dev->interrupt_in_endpoint),
2768c2ecf20Sopenharmony_ci			 adu_interrupt_in_callback, dev,
2778c2ecf20Sopenharmony_ci			 dev->interrupt_in_endpoint->bInterval);
2788c2ecf20Sopenharmony_ci	dev->read_urb_finished = 0;
2798c2ecf20Sopenharmony_ci	if (usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL))
2808c2ecf20Sopenharmony_ci		dev->read_urb_finished = 1;
2818c2ecf20Sopenharmony_ci	/* we ignore failure */
2828c2ecf20Sopenharmony_ci	/* end of fixup for first read */
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	/* initialize out direction */
2858c2ecf20Sopenharmony_ci	dev->out_urb_finished = 1;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	retval = 0;
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ciexit_no_device:
2908c2ecf20Sopenharmony_ci	mutex_unlock(&adutux_mutex);
2918c2ecf20Sopenharmony_ciexit_no_lock:
2928c2ecf20Sopenharmony_ci	return retval;
2938c2ecf20Sopenharmony_ci}
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_cistatic void adu_release_internal(struct adu_device *dev)
2968c2ecf20Sopenharmony_ci{
2978c2ecf20Sopenharmony_ci	/* decrement our usage count for the device */
2988c2ecf20Sopenharmony_ci	--dev->open_count;
2998c2ecf20Sopenharmony_ci	dev_dbg(&dev->udev->dev, "%s : open count %d\n", __func__,
3008c2ecf20Sopenharmony_ci		dev->open_count);
3018c2ecf20Sopenharmony_ci	if (dev->open_count <= 0) {
3028c2ecf20Sopenharmony_ci		adu_abort_transfers(dev);
3038c2ecf20Sopenharmony_ci		dev->open_count = 0;
3048c2ecf20Sopenharmony_ci	}
3058c2ecf20Sopenharmony_ci}
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_cistatic int adu_release(struct inode *inode, struct file *file)
3088c2ecf20Sopenharmony_ci{
3098c2ecf20Sopenharmony_ci	struct adu_device *dev;
3108c2ecf20Sopenharmony_ci	int retval = 0;
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	if (file == NULL) {
3138c2ecf20Sopenharmony_ci		retval = -ENODEV;
3148c2ecf20Sopenharmony_ci		goto exit;
3158c2ecf20Sopenharmony_ci	}
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	dev = file->private_data;
3188c2ecf20Sopenharmony_ci	if (dev == NULL) {
3198c2ecf20Sopenharmony_ci		retval = -ENODEV;
3208c2ecf20Sopenharmony_ci		goto exit;
3218c2ecf20Sopenharmony_ci	}
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	mutex_lock(&adutux_mutex); /* not interruptible */
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci	if (dev->open_count <= 0) {
3268c2ecf20Sopenharmony_ci		dev_dbg(&dev->udev->dev, "%s : device not opened\n", __func__);
3278c2ecf20Sopenharmony_ci		retval = -ENODEV;
3288c2ecf20Sopenharmony_ci		goto unlock;
3298c2ecf20Sopenharmony_ci	}
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	adu_release_internal(dev);
3328c2ecf20Sopenharmony_ci	if (dev->disconnected) {
3338c2ecf20Sopenharmony_ci		/* the device was unplugged before the file was released */
3348c2ecf20Sopenharmony_ci		if (!dev->open_count)	/* ... and we're the last user */
3358c2ecf20Sopenharmony_ci			adu_delete(dev);
3368c2ecf20Sopenharmony_ci	}
3378c2ecf20Sopenharmony_ciunlock:
3388c2ecf20Sopenharmony_ci	mutex_unlock(&adutux_mutex);
3398c2ecf20Sopenharmony_ciexit:
3408c2ecf20Sopenharmony_ci	return retval;
3418c2ecf20Sopenharmony_ci}
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_cistatic ssize_t adu_read(struct file *file, __user char *buffer, size_t count,
3448c2ecf20Sopenharmony_ci			loff_t *ppos)
3458c2ecf20Sopenharmony_ci{
3468c2ecf20Sopenharmony_ci	struct adu_device *dev;
3478c2ecf20Sopenharmony_ci	size_t bytes_read = 0;
3488c2ecf20Sopenharmony_ci	size_t bytes_to_read = count;
3498c2ecf20Sopenharmony_ci	int retval = 0;
3508c2ecf20Sopenharmony_ci	int timeout = 0;
3518c2ecf20Sopenharmony_ci	int should_submit = 0;
3528c2ecf20Sopenharmony_ci	unsigned long flags;
3538c2ecf20Sopenharmony_ci	DECLARE_WAITQUEUE(wait, current);
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_ci	dev = file->private_data;
3568c2ecf20Sopenharmony_ci	if (mutex_lock_interruptible(&dev->mtx))
3578c2ecf20Sopenharmony_ci		return -ERESTARTSYS;
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	/* verify that the device wasn't unplugged */
3608c2ecf20Sopenharmony_ci	if (dev->disconnected) {
3618c2ecf20Sopenharmony_ci		retval = -ENODEV;
3628c2ecf20Sopenharmony_ci		pr_err("No device or device unplugged %d\n", retval);
3638c2ecf20Sopenharmony_ci		goto exit;
3648c2ecf20Sopenharmony_ci	}
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci	/* verify that some data was requested */
3678c2ecf20Sopenharmony_ci	if (count == 0) {
3688c2ecf20Sopenharmony_ci		dev_dbg(&dev->udev->dev, "%s : read request of 0 bytes\n",
3698c2ecf20Sopenharmony_ci			__func__);
3708c2ecf20Sopenharmony_ci		goto exit;
3718c2ecf20Sopenharmony_ci	}
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	timeout = COMMAND_TIMEOUT;
3748c2ecf20Sopenharmony_ci	dev_dbg(&dev->udev->dev, "%s : about to start looping\n", __func__);
3758c2ecf20Sopenharmony_ci	while (bytes_to_read) {
3768c2ecf20Sopenharmony_ci		size_t data_in_secondary = dev->secondary_tail - dev->secondary_head;
3778c2ecf20Sopenharmony_ci		dev_dbg(&dev->udev->dev,
3788c2ecf20Sopenharmony_ci			"%s : while, data_in_secondary=%zu, status=%d\n",
3798c2ecf20Sopenharmony_ci			__func__, data_in_secondary,
3808c2ecf20Sopenharmony_ci			dev->interrupt_in_urb->status);
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci		if (data_in_secondary) {
3838c2ecf20Sopenharmony_ci			/* drain secondary buffer */
3848c2ecf20Sopenharmony_ci			size_t amount = min(bytes_to_read, data_in_secondary);
3858c2ecf20Sopenharmony_ci			if (copy_to_user(buffer, dev->read_buffer_secondary+dev->secondary_head, amount)) {
3868c2ecf20Sopenharmony_ci				retval = -EFAULT;
3878c2ecf20Sopenharmony_ci				goto exit;
3888c2ecf20Sopenharmony_ci			}
3898c2ecf20Sopenharmony_ci			dev->secondary_head += amount;
3908c2ecf20Sopenharmony_ci			bytes_read += amount;
3918c2ecf20Sopenharmony_ci			bytes_to_read -= amount;
3928c2ecf20Sopenharmony_ci		} else {
3938c2ecf20Sopenharmony_ci			/* we check the primary buffer */
3948c2ecf20Sopenharmony_ci			spin_lock_irqsave (&dev->buflock, flags);
3958c2ecf20Sopenharmony_ci			if (dev->read_buffer_length) {
3968c2ecf20Sopenharmony_ci				/* we secure access to the primary */
3978c2ecf20Sopenharmony_ci				char *tmp;
3988c2ecf20Sopenharmony_ci				dev_dbg(&dev->udev->dev,
3998c2ecf20Sopenharmony_ci					"%s : swap, read_buffer_length = %d\n",
4008c2ecf20Sopenharmony_ci					__func__, dev->read_buffer_length);
4018c2ecf20Sopenharmony_ci				tmp = dev->read_buffer_secondary;
4028c2ecf20Sopenharmony_ci				dev->read_buffer_secondary = dev->read_buffer_primary;
4038c2ecf20Sopenharmony_ci				dev->read_buffer_primary = tmp;
4048c2ecf20Sopenharmony_ci				dev->secondary_head = 0;
4058c2ecf20Sopenharmony_ci				dev->secondary_tail = dev->read_buffer_length;
4068c2ecf20Sopenharmony_ci				dev->read_buffer_length = 0;
4078c2ecf20Sopenharmony_ci				spin_unlock_irqrestore(&dev->buflock, flags);
4088c2ecf20Sopenharmony_ci				/* we have a free buffer so use it */
4098c2ecf20Sopenharmony_ci				should_submit = 1;
4108c2ecf20Sopenharmony_ci			} else {
4118c2ecf20Sopenharmony_ci				/* even the primary was empty - we may need to do IO */
4128c2ecf20Sopenharmony_ci				if (!dev->read_urb_finished) {
4138c2ecf20Sopenharmony_ci					/* somebody is doing IO */
4148c2ecf20Sopenharmony_ci					spin_unlock_irqrestore(&dev->buflock, flags);
4158c2ecf20Sopenharmony_ci					dev_dbg(&dev->udev->dev,
4168c2ecf20Sopenharmony_ci						"%s : submitted already\n",
4178c2ecf20Sopenharmony_ci						__func__);
4188c2ecf20Sopenharmony_ci				} else {
4198c2ecf20Sopenharmony_ci					/* we must initiate input */
4208c2ecf20Sopenharmony_ci					dev_dbg(&dev->udev->dev,
4218c2ecf20Sopenharmony_ci						"%s : initiate input\n",
4228c2ecf20Sopenharmony_ci						__func__);
4238c2ecf20Sopenharmony_ci					dev->read_urb_finished = 0;
4248c2ecf20Sopenharmony_ci					spin_unlock_irqrestore(&dev->buflock, flags);
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_ci					usb_fill_int_urb(dev->interrupt_in_urb, dev->udev,
4278c2ecf20Sopenharmony_ci							usb_rcvintpipe(dev->udev,
4288c2ecf20Sopenharmony_ci								dev->interrupt_in_endpoint->bEndpointAddress),
4298c2ecf20Sopenharmony_ci							 dev->interrupt_in_buffer,
4308c2ecf20Sopenharmony_ci							 usb_endpoint_maxp(dev->interrupt_in_endpoint),
4318c2ecf20Sopenharmony_ci							 adu_interrupt_in_callback,
4328c2ecf20Sopenharmony_ci							 dev,
4338c2ecf20Sopenharmony_ci							 dev->interrupt_in_endpoint->bInterval);
4348c2ecf20Sopenharmony_ci					retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);
4358c2ecf20Sopenharmony_ci					if (retval) {
4368c2ecf20Sopenharmony_ci						dev->read_urb_finished = 1;
4378c2ecf20Sopenharmony_ci						if (retval == -ENOMEM) {
4388c2ecf20Sopenharmony_ci							retval = bytes_read ? bytes_read : -ENOMEM;
4398c2ecf20Sopenharmony_ci						}
4408c2ecf20Sopenharmony_ci						dev_dbg(&dev->udev->dev,
4418c2ecf20Sopenharmony_ci							"%s : submit failed\n",
4428c2ecf20Sopenharmony_ci							__func__);
4438c2ecf20Sopenharmony_ci						goto exit;
4448c2ecf20Sopenharmony_ci					}
4458c2ecf20Sopenharmony_ci				}
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci				/* we wait for I/O to complete */
4488c2ecf20Sopenharmony_ci				set_current_state(TASK_INTERRUPTIBLE);
4498c2ecf20Sopenharmony_ci				add_wait_queue(&dev->read_wait, &wait);
4508c2ecf20Sopenharmony_ci				spin_lock_irqsave(&dev->buflock, flags);
4518c2ecf20Sopenharmony_ci				if (!dev->read_urb_finished) {
4528c2ecf20Sopenharmony_ci					spin_unlock_irqrestore(&dev->buflock, flags);
4538c2ecf20Sopenharmony_ci					timeout = schedule_timeout(COMMAND_TIMEOUT);
4548c2ecf20Sopenharmony_ci				} else {
4558c2ecf20Sopenharmony_ci					spin_unlock_irqrestore(&dev->buflock, flags);
4568c2ecf20Sopenharmony_ci					set_current_state(TASK_RUNNING);
4578c2ecf20Sopenharmony_ci				}
4588c2ecf20Sopenharmony_ci				remove_wait_queue(&dev->read_wait, &wait);
4598c2ecf20Sopenharmony_ci
4608c2ecf20Sopenharmony_ci				if (timeout <= 0) {
4618c2ecf20Sopenharmony_ci					dev_dbg(&dev->udev->dev,
4628c2ecf20Sopenharmony_ci						"%s : timeout\n", __func__);
4638c2ecf20Sopenharmony_ci					retval = bytes_read ? bytes_read : -ETIMEDOUT;
4648c2ecf20Sopenharmony_ci					goto exit;
4658c2ecf20Sopenharmony_ci				}
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci				if (signal_pending(current)) {
4688c2ecf20Sopenharmony_ci					dev_dbg(&dev->udev->dev,
4698c2ecf20Sopenharmony_ci						"%s : signal pending\n",
4708c2ecf20Sopenharmony_ci						__func__);
4718c2ecf20Sopenharmony_ci					retval = bytes_read ? bytes_read : -EINTR;
4728c2ecf20Sopenharmony_ci					goto exit;
4738c2ecf20Sopenharmony_ci				}
4748c2ecf20Sopenharmony_ci			}
4758c2ecf20Sopenharmony_ci		}
4768c2ecf20Sopenharmony_ci	}
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_ci	retval = bytes_read;
4798c2ecf20Sopenharmony_ci	/* if the primary buffer is empty then use it */
4808c2ecf20Sopenharmony_ci	spin_lock_irqsave(&dev->buflock, flags);
4818c2ecf20Sopenharmony_ci	if (should_submit && dev->read_urb_finished) {
4828c2ecf20Sopenharmony_ci		dev->read_urb_finished = 0;
4838c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&dev->buflock, flags);
4848c2ecf20Sopenharmony_ci		usb_fill_int_urb(dev->interrupt_in_urb, dev->udev,
4858c2ecf20Sopenharmony_ci				 usb_rcvintpipe(dev->udev,
4868c2ecf20Sopenharmony_ci					dev->interrupt_in_endpoint->bEndpointAddress),
4878c2ecf20Sopenharmony_ci				dev->interrupt_in_buffer,
4888c2ecf20Sopenharmony_ci				usb_endpoint_maxp(dev->interrupt_in_endpoint),
4898c2ecf20Sopenharmony_ci				adu_interrupt_in_callback,
4908c2ecf20Sopenharmony_ci				dev,
4918c2ecf20Sopenharmony_ci				dev->interrupt_in_endpoint->bInterval);
4928c2ecf20Sopenharmony_ci		if (usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL) != 0)
4938c2ecf20Sopenharmony_ci			dev->read_urb_finished = 1;
4948c2ecf20Sopenharmony_ci		/* we ignore failure */
4958c2ecf20Sopenharmony_ci	} else {
4968c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&dev->buflock, flags);
4978c2ecf20Sopenharmony_ci	}
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_ciexit:
5008c2ecf20Sopenharmony_ci	/* unlock the device */
5018c2ecf20Sopenharmony_ci	mutex_unlock(&dev->mtx);
5028c2ecf20Sopenharmony_ci
5038c2ecf20Sopenharmony_ci	return retval;
5048c2ecf20Sopenharmony_ci}
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_cistatic ssize_t adu_write(struct file *file, const __user char *buffer,
5078c2ecf20Sopenharmony_ci			 size_t count, loff_t *ppos)
5088c2ecf20Sopenharmony_ci{
5098c2ecf20Sopenharmony_ci	DECLARE_WAITQUEUE(waita, current);
5108c2ecf20Sopenharmony_ci	struct adu_device *dev;
5118c2ecf20Sopenharmony_ci	size_t bytes_written = 0;
5128c2ecf20Sopenharmony_ci	size_t bytes_to_write;
5138c2ecf20Sopenharmony_ci	size_t buffer_size;
5148c2ecf20Sopenharmony_ci	unsigned long flags;
5158c2ecf20Sopenharmony_ci	int retval;
5168c2ecf20Sopenharmony_ci
5178c2ecf20Sopenharmony_ci	dev = file->private_data;
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci	retval = mutex_lock_interruptible(&dev->mtx);
5208c2ecf20Sopenharmony_ci	if (retval)
5218c2ecf20Sopenharmony_ci		goto exit_nolock;
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ci	/* verify that the device wasn't unplugged */
5248c2ecf20Sopenharmony_ci	if (dev->disconnected) {
5258c2ecf20Sopenharmony_ci		retval = -ENODEV;
5268c2ecf20Sopenharmony_ci		pr_err("No device or device unplugged %d\n", retval);
5278c2ecf20Sopenharmony_ci		goto exit;
5288c2ecf20Sopenharmony_ci	}
5298c2ecf20Sopenharmony_ci
5308c2ecf20Sopenharmony_ci	/* verify that we actually have some data to write */
5318c2ecf20Sopenharmony_ci	if (count == 0) {
5328c2ecf20Sopenharmony_ci		dev_dbg(&dev->udev->dev, "%s : write request of 0 bytes\n",
5338c2ecf20Sopenharmony_ci			__func__);
5348c2ecf20Sopenharmony_ci		goto exit;
5358c2ecf20Sopenharmony_ci	}
5368c2ecf20Sopenharmony_ci
5378c2ecf20Sopenharmony_ci	while (count > 0) {
5388c2ecf20Sopenharmony_ci		add_wait_queue(&dev->write_wait, &waita);
5398c2ecf20Sopenharmony_ci		set_current_state(TASK_INTERRUPTIBLE);
5408c2ecf20Sopenharmony_ci		spin_lock_irqsave(&dev->buflock, flags);
5418c2ecf20Sopenharmony_ci		if (!dev->out_urb_finished) {
5428c2ecf20Sopenharmony_ci			spin_unlock_irqrestore(&dev->buflock, flags);
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci			mutex_unlock(&dev->mtx);
5458c2ecf20Sopenharmony_ci			if (signal_pending(current)) {
5468c2ecf20Sopenharmony_ci				dev_dbg(&dev->udev->dev, "%s : interrupted\n",
5478c2ecf20Sopenharmony_ci					__func__);
5488c2ecf20Sopenharmony_ci				set_current_state(TASK_RUNNING);
5498c2ecf20Sopenharmony_ci				retval = -EINTR;
5508c2ecf20Sopenharmony_ci				goto exit_onqueue;
5518c2ecf20Sopenharmony_ci			}
5528c2ecf20Sopenharmony_ci			if (schedule_timeout(COMMAND_TIMEOUT) == 0) {
5538c2ecf20Sopenharmony_ci				dev_dbg(&dev->udev->dev,
5548c2ecf20Sopenharmony_ci					"%s - command timed out.\n", __func__);
5558c2ecf20Sopenharmony_ci				retval = -ETIMEDOUT;
5568c2ecf20Sopenharmony_ci				goto exit_onqueue;
5578c2ecf20Sopenharmony_ci			}
5588c2ecf20Sopenharmony_ci			remove_wait_queue(&dev->write_wait, &waita);
5598c2ecf20Sopenharmony_ci			retval = mutex_lock_interruptible(&dev->mtx);
5608c2ecf20Sopenharmony_ci			if (retval) {
5618c2ecf20Sopenharmony_ci				retval = bytes_written ? bytes_written : retval;
5628c2ecf20Sopenharmony_ci				goto exit_nolock;
5638c2ecf20Sopenharmony_ci			}
5648c2ecf20Sopenharmony_ci
5658c2ecf20Sopenharmony_ci			dev_dbg(&dev->udev->dev,
5668c2ecf20Sopenharmony_ci				"%s : in progress, count = %zd\n",
5678c2ecf20Sopenharmony_ci				__func__, count);
5688c2ecf20Sopenharmony_ci		} else {
5698c2ecf20Sopenharmony_ci			spin_unlock_irqrestore(&dev->buflock, flags);
5708c2ecf20Sopenharmony_ci			set_current_state(TASK_RUNNING);
5718c2ecf20Sopenharmony_ci			remove_wait_queue(&dev->write_wait, &waita);
5728c2ecf20Sopenharmony_ci			dev_dbg(&dev->udev->dev, "%s : sending, count = %zd\n",
5738c2ecf20Sopenharmony_ci				__func__, count);
5748c2ecf20Sopenharmony_ci
5758c2ecf20Sopenharmony_ci			/* write the data into interrupt_out_buffer from userspace */
5768c2ecf20Sopenharmony_ci			buffer_size = usb_endpoint_maxp(dev->interrupt_out_endpoint);
5778c2ecf20Sopenharmony_ci			bytes_to_write = count > buffer_size ? buffer_size : count;
5788c2ecf20Sopenharmony_ci			dev_dbg(&dev->udev->dev,
5798c2ecf20Sopenharmony_ci				"%s : buffer_size = %zd, count = %zd, bytes_to_write = %zd\n",
5808c2ecf20Sopenharmony_ci				__func__, buffer_size, count, bytes_to_write);
5818c2ecf20Sopenharmony_ci
5828c2ecf20Sopenharmony_ci			if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write) != 0) {
5838c2ecf20Sopenharmony_ci				retval = -EFAULT;
5848c2ecf20Sopenharmony_ci				goto exit;
5858c2ecf20Sopenharmony_ci			}
5868c2ecf20Sopenharmony_ci
5878c2ecf20Sopenharmony_ci			/* send off the urb */
5888c2ecf20Sopenharmony_ci			usb_fill_int_urb(
5898c2ecf20Sopenharmony_ci				dev->interrupt_out_urb,
5908c2ecf20Sopenharmony_ci				dev->udev,
5918c2ecf20Sopenharmony_ci				usb_sndintpipe(dev->udev, dev->interrupt_out_endpoint->bEndpointAddress),
5928c2ecf20Sopenharmony_ci				dev->interrupt_out_buffer,
5938c2ecf20Sopenharmony_ci				bytes_to_write,
5948c2ecf20Sopenharmony_ci				adu_interrupt_out_callback,
5958c2ecf20Sopenharmony_ci				dev,
5968c2ecf20Sopenharmony_ci				dev->interrupt_out_endpoint->bInterval);
5978c2ecf20Sopenharmony_ci			dev->interrupt_out_urb->actual_length = bytes_to_write;
5988c2ecf20Sopenharmony_ci			dev->out_urb_finished = 0;
5998c2ecf20Sopenharmony_ci			retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL);
6008c2ecf20Sopenharmony_ci			if (retval < 0) {
6018c2ecf20Sopenharmony_ci				dev->out_urb_finished = 1;
6028c2ecf20Sopenharmony_ci				dev_err(&dev->udev->dev, "Couldn't submit "
6038c2ecf20Sopenharmony_ci					"interrupt_out_urb %d\n", retval);
6048c2ecf20Sopenharmony_ci				goto exit;
6058c2ecf20Sopenharmony_ci			}
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci			buffer += bytes_to_write;
6088c2ecf20Sopenharmony_ci			count -= bytes_to_write;
6098c2ecf20Sopenharmony_ci
6108c2ecf20Sopenharmony_ci			bytes_written += bytes_to_write;
6118c2ecf20Sopenharmony_ci		}
6128c2ecf20Sopenharmony_ci	}
6138c2ecf20Sopenharmony_ci	mutex_unlock(&dev->mtx);
6148c2ecf20Sopenharmony_ci	return bytes_written;
6158c2ecf20Sopenharmony_ci
6168c2ecf20Sopenharmony_ciexit:
6178c2ecf20Sopenharmony_ci	mutex_unlock(&dev->mtx);
6188c2ecf20Sopenharmony_ciexit_nolock:
6198c2ecf20Sopenharmony_ci	return retval;
6208c2ecf20Sopenharmony_ci
6218c2ecf20Sopenharmony_ciexit_onqueue:
6228c2ecf20Sopenharmony_ci	remove_wait_queue(&dev->write_wait, &waita);
6238c2ecf20Sopenharmony_ci	return retval;
6248c2ecf20Sopenharmony_ci}
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_ci/* file operations needed when we register this driver */
6278c2ecf20Sopenharmony_cistatic const struct file_operations adu_fops = {
6288c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
6298c2ecf20Sopenharmony_ci	.read  = adu_read,
6308c2ecf20Sopenharmony_ci	.write = adu_write,
6318c2ecf20Sopenharmony_ci	.open = adu_open,
6328c2ecf20Sopenharmony_ci	.release = adu_release,
6338c2ecf20Sopenharmony_ci	.llseek = noop_llseek,
6348c2ecf20Sopenharmony_ci};
6358c2ecf20Sopenharmony_ci
6368c2ecf20Sopenharmony_ci/*
6378c2ecf20Sopenharmony_ci * usb class driver info in order to get a minor number from the usb core,
6388c2ecf20Sopenharmony_ci * and to have the device registered with devfs and the driver core
6398c2ecf20Sopenharmony_ci */
6408c2ecf20Sopenharmony_cistatic struct usb_class_driver adu_class = {
6418c2ecf20Sopenharmony_ci	.name = "usb/adutux%d",
6428c2ecf20Sopenharmony_ci	.fops = &adu_fops,
6438c2ecf20Sopenharmony_ci	.minor_base = ADU_MINOR_BASE,
6448c2ecf20Sopenharmony_ci};
6458c2ecf20Sopenharmony_ci
6468c2ecf20Sopenharmony_ci/*
6478c2ecf20Sopenharmony_ci * adu_probe
6488c2ecf20Sopenharmony_ci *
6498c2ecf20Sopenharmony_ci * Called by the usb core when a new device is connected that it thinks
6508c2ecf20Sopenharmony_ci * this driver might be interested in.
6518c2ecf20Sopenharmony_ci */
6528c2ecf20Sopenharmony_cistatic int adu_probe(struct usb_interface *interface,
6538c2ecf20Sopenharmony_ci		     const struct usb_device_id *id)
6548c2ecf20Sopenharmony_ci{
6558c2ecf20Sopenharmony_ci	struct usb_device *udev = interface_to_usbdev(interface);
6568c2ecf20Sopenharmony_ci	struct adu_device *dev = NULL;
6578c2ecf20Sopenharmony_ci	int retval = -ENOMEM;
6588c2ecf20Sopenharmony_ci	int in_end_size;
6598c2ecf20Sopenharmony_ci	int out_end_size;
6608c2ecf20Sopenharmony_ci	int res;
6618c2ecf20Sopenharmony_ci
6628c2ecf20Sopenharmony_ci	/* allocate memory for our device state and initialize it */
6638c2ecf20Sopenharmony_ci	dev = kzalloc(sizeof(struct adu_device), GFP_KERNEL);
6648c2ecf20Sopenharmony_ci	if (!dev)
6658c2ecf20Sopenharmony_ci		return -ENOMEM;
6668c2ecf20Sopenharmony_ci
6678c2ecf20Sopenharmony_ci	mutex_init(&dev->mtx);
6688c2ecf20Sopenharmony_ci	spin_lock_init(&dev->buflock);
6698c2ecf20Sopenharmony_ci	dev->udev = usb_get_dev(udev);
6708c2ecf20Sopenharmony_ci	init_waitqueue_head(&dev->read_wait);
6718c2ecf20Sopenharmony_ci	init_waitqueue_head(&dev->write_wait);
6728c2ecf20Sopenharmony_ci
6738c2ecf20Sopenharmony_ci	res = usb_find_common_endpoints_reverse(interface->cur_altsetting,
6748c2ecf20Sopenharmony_ci			NULL, NULL,
6758c2ecf20Sopenharmony_ci			&dev->interrupt_in_endpoint,
6768c2ecf20Sopenharmony_ci			&dev->interrupt_out_endpoint);
6778c2ecf20Sopenharmony_ci	if (res) {
6788c2ecf20Sopenharmony_ci		dev_err(&interface->dev, "interrupt endpoints not found\n");
6798c2ecf20Sopenharmony_ci		retval = res;
6808c2ecf20Sopenharmony_ci		goto error;
6818c2ecf20Sopenharmony_ci	}
6828c2ecf20Sopenharmony_ci
6838c2ecf20Sopenharmony_ci	in_end_size = usb_endpoint_maxp(dev->interrupt_in_endpoint);
6848c2ecf20Sopenharmony_ci	out_end_size = usb_endpoint_maxp(dev->interrupt_out_endpoint);
6858c2ecf20Sopenharmony_ci
6868c2ecf20Sopenharmony_ci	dev->read_buffer_primary = kmalloc((4 * in_end_size), GFP_KERNEL);
6878c2ecf20Sopenharmony_ci	if (!dev->read_buffer_primary)
6888c2ecf20Sopenharmony_ci		goto error;
6898c2ecf20Sopenharmony_ci
6908c2ecf20Sopenharmony_ci	/* debug code prime the buffer */
6918c2ecf20Sopenharmony_ci	memset(dev->read_buffer_primary, 'a', in_end_size);
6928c2ecf20Sopenharmony_ci	memset(dev->read_buffer_primary + in_end_size, 'b', in_end_size);
6938c2ecf20Sopenharmony_ci	memset(dev->read_buffer_primary + (2 * in_end_size), 'c', in_end_size);
6948c2ecf20Sopenharmony_ci	memset(dev->read_buffer_primary + (3 * in_end_size), 'd', in_end_size);
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_ci	dev->read_buffer_secondary = kmalloc((4 * in_end_size), GFP_KERNEL);
6978c2ecf20Sopenharmony_ci	if (!dev->read_buffer_secondary)
6988c2ecf20Sopenharmony_ci		goto error;
6998c2ecf20Sopenharmony_ci
7008c2ecf20Sopenharmony_ci	/* debug code prime the buffer */
7018c2ecf20Sopenharmony_ci	memset(dev->read_buffer_secondary, 'e', in_end_size);
7028c2ecf20Sopenharmony_ci	memset(dev->read_buffer_secondary + in_end_size, 'f', in_end_size);
7038c2ecf20Sopenharmony_ci	memset(dev->read_buffer_secondary + (2 * in_end_size), 'g', in_end_size);
7048c2ecf20Sopenharmony_ci	memset(dev->read_buffer_secondary + (3 * in_end_size), 'h', in_end_size);
7058c2ecf20Sopenharmony_ci
7068c2ecf20Sopenharmony_ci	dev->interrupt_in_buffer = kmalloc(in_end_size, GFP_KERNEL);
7078c2ecf20Sopenharmony_ci	if (!dev->interrupt_in_buffer)
7088c2ecf20Sopenharmony_ci		goto error;
7098c2ecf20Sopenharmony_ci
7108c2ecf20Sopenharmony_ci	/* debug code prime the buffer */
7118c2ecf20Sopenharmony_ci	memset(dev->interrupt_in_buffer, 'i', in_end_size);
7128c2ecf20Sopenharmony_ci
7138c2ecf20Sopenharmony_ci	dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL);
7148c2ecf20Sopenharmony_ci	if (!dev->interrupt_in_urb)
7158c2ecf20Sopenharmony_ci		goto error;
7168c2ecf20Sopenharmony_ci	dev->interrupt_out_buffer = kmalloc(out_end_size, GFP_KERNEL);
7178c2ecf20Sopenharmony_ci	if (!dev->interrupt_out_buffer)
7188c2ecf20Sopenharmony_ci		goto error;
7198c2ecf20Sopenharmony_ci	dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
7208c2ecf20Sopenharmony_ci	if (!dev->interrupt_out_urb)
7218c2ecf20Sopenharmony_ci		goto error;
7228c2ecf20Sopenharmony_ci
7238c2ecf20Sopenharmony_ci	if (!usb_string(udev, udev->descriptor.iSerialNumber, dev->serial_number,
7248c2ecf20Sopenharmony_ci			sizeof(dev->serial_number))) {
7258c2ecf20Sopenharmony_ci		dev_err(&interface->dev, "Could not retrieve serial number\n");
7268c2ecf20Sopenharmony_ci		retval = -EIO;
7278c2ecf20Sopenharmony_ci		goto error;
7288c2ecf20Sopenharmony_ci	}
7298c2ecf20Sopenharmony_ci	dev_dbg(&interface->dev,"serial_number=%s", dev->serial_number);
7308c2ecf20Sopenharmony_ci
7318c2ecf20Sopenharmony_ci	/* we can register the device now, as it is ready */
7328c2ecf20Sopenharmony_ci	usb_set_intfdata(interface, dev);
7338c2ecf20Sopenharmony_ci
7348c2ecf20Sopenharmony_ci	retval = usb_register_dev(interface, &adu_class);
7358c2ecf20Sopenharmony_ci
7368c2ecf20Sopenharmony_ci	if (retval) {
7378c2ecf20Sopenharmony_ci		/* something prevented us from registering this driver */
7388c2ecf20Sopenharmony_ci		dev_err(&interface->dev, "Not able to get a minor for this device.\n");
7398c2ecf20Sopenharmony_ci		usb_set_intfdata(interface, NULL);
7408c2ecf20Sopenharmony_ci		goto error;
7418c2ecf20Sopenharmony_ci	}
7428c2ecf20Sopenharmony_ci
7438c2ecf20Sopenharmony_ci	dev->minor = interface->minor;
7448c2ecf20Sopenharmony_ci
7458c2ecf20Sopenharmony_ci	/* let the user know what node this device is now attached to */
7468c2ecf20Sopenharmony_ci	dev_info(&interface->dev, "ADU%d %s now attached to /dev/usb/adutux%d\n",
7478c2ecf20Sopenharmony_ci		 le16_to_cpu(udev->descriptor.idProduct), dev->serial_number,
7488c2ecf20Sopenharmony_ci		 (dev->minor - ADU_MINOR_BASE));
7498c2ecf20Sopenharmony_ci
7508c2ecf20Sopenharmony_ci	return 0;
7518c2ecf20Sopenharmony_ci
7528c2ecf20Sopenharmony_cierror:
7538c2ecf20Sopenharmony_ci	adu_delete(dev);
7548c2ecf20Sopenharmony_ci	return retval;
7558c2ecf20Sopenharmony_ci}
7568c2ecf20Sopenharmony_ci
7578c2ecf20Sopenharmony_ci/*
7588c2ecf20Sopenharmony_ci * adu_disconnect
7598c2ecf20Sopenharmony_ci *
7608c2ecf20Sopenharmony_ci * Called by the usb core when the device is removed from the system.
7618c2ecf20Sopenharmony_ci */
7628c2ecf20Sopenharmony_cistatic void adu_disconnect(struct usb_interface *interface)
7638c2ecf20Sopenharmony_ci{
7648c2ecf20Sopenharmony_ci	struct adu_device *dev;
7658c2ecf20Sopenharmony_ci
7668c2ecf20Sopenharmony_ci	dev = usb_get_intfdata(interface);
7678c2ecf20Sopenharmony_ci
7688c2ecf20Sopenharmony_ci	usb_deregister_dev(interface, &adu_class);
7698c2ecf20Sopenharmony_ci
7708c2ecf20Sopenharmony_ci	usb_poison_urb(dev->interrupt_in_urb);
7718c2ecf20Sopenharmony_ci	usb_poison_urb(dev->interrupt_out_urb);
7728c2ecf20Sopenharmony_ci
7738c2ecf20Sopenharmony_ci	mutex_lock(&adutux_mutex);
7748c2ecf20Sopenharmony_ci	usb_set_intfdata(interface, NULL);
7758c2ecf20Sopenharmony_ci
7768c2ecf20Sopenharmony_ci	mutex_lock(&dev->mtx);	/* not interruptible */
7778c2ecf20Sopenharmony_ci	dev->disconnected = 1;
7788c2ecf20Sopenharmony_ci	mutex_unlock(&dev->mtx);
7798c2ecf20Sopenharmony_ci
7808c2ecf20Sopenharmony_ci	/* if the device is not opened, then we clean up right now */
7818c2ecf20Sopenharmony_ci	if (!dev->open_count)
7828c2ecf20Sopenharmony_ci		adu_delete(dev);
7838c2ecf20Sopenharmony_ci
7848c2ecf20Sopenharmony_ci	mutex_unlock(&adutux_mutex);
7858c2ecf20Sopenharmony_ci}
7868c2ecf20Sopenharmony_ci
7878c2ecf20Sopenharmony_ci/* usb specific object needed to register this driver with the usb subsystem */
7888c2ecf20Sopenharmony_cistatic struct usb_driver adu_driver = {
7898c2ecf20Sopenharmony_ci	.name = "adutux",
7908c2ecf20Sopenharmony_ci	.probe = adu_probe,
7918c2ecf20Sopenharmony_ci	.disconnect = adu_disconnect,
7928c2ecf20Sopenharmony_ci	.id_table = device_table,
7938c2ecf20Sopenharmony_ci};
7948c2ecf20Sopenharmony_ci
7958c2ecf20Sopenharmony_cimodule_usb_driver(adu_driver);
7968c2ecf20Sopenharmony_ci
7978c2ecf20Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR);
7988c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC);
7998c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
800