15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
268b44eaeSGreg Kroah-Hartman /*
368b44eaeSGreg Kroah-Hartman  * Symbol USB barcode to serial driver
468b44eaeSGreg Kroah-Hartman  *
5a85796eeSJohan Hovold  * Copyright (C) 2013 Johan Hovold <jhovold@gmail.com>
668b44eaeSGreg Kroah-Hartman  * Copyright (C) 2009 Greg Kroah-Hartman <gregkh@suse.de>
768b44eaeSGreg Kroah-Hartman  * Copyright (C) 2009 Novell Inc.
868b44eaeSGreg Kroah-Hartman  */
968b44eaeSGreg Kroah-Hartman 
1068b44eaeSGreg Kroah-Hartman #include <linux/kernel.h>
1168b44eaeSGreg Kroah-Hartman #include <linux/tty.h>
125a0e3ad6STejun Heo #include <linux/slab.h>
1368b44eaeSGreg Kroah-Hartman #include <linux/tty_driver.h>
1468b44eaeSGreg Kroah-Hartman #include <linux/tty_flip.h>
1568b44eaeSGreg Kroah-Hartman #include <linux/module.h>
1668b44eaeSGreg Kroah-Hartman #include <linux/usb.h>
1768b44eaeSGreg Kroah-Hartman #include <linux/usb/serial.h>
1868b44eaeSGreg Kroah-Hartman #include <linux/uaccess.h>
1968b44eaeSGreg Kroah-Hartman 
207d40d7e8SNémeth Márton static const struct usb_device_id id_table[] = {
2168b44eaeSGreg Kroah-Hartman 	{ USB_DEVICE(0x05e0, 0x0600) },
2268b44eaeSGreg Kroah-Hartman 	{ },
2368b44eaeSGreg Kroah-Hartman };
2468b44eaeSGreg Kroah-Hartman MODULE_DEVICE_TABLE(usb, id_table);
2568b44eaeSGreg Kroah-Hartman 
2668b44eaeSGreg Kroah-Hartman struct symbol_private {
2768b44eaeSGreg Kroah-Hartman 	spinlock_t lock;	/* protects the following flags */
2868b44eaeSGreg Kroah-Hartman 	bool throttled;
2968b44eaeSGreg Kroah-Hartman 	bool actually_throttled;
3068b44eaeSGreg Kroah-Hartman };
3168b44eaeSGreg Kroah-Hartman 
symbol_int_callback(struct urb * urb)3268b44eaeSGreg Kroah-Hartman static void symbol_int_callback(struct urb *urb)
3368b44eaeSGreg Kroah-Hartman {
34cced926fSJohan Hovold 	struct usb_serial_port *port = urb->context;
35a85796eeSJohan Hovold 	struct symbol_private *priv = usb_get_serial_port_data(port);
3668b44eaeSGreg Kroah-Hartman 	unsigned char *data = urb->transfer_buffer;
3768b44eaeSGreg Kroah-Hartman 	int status = urb->status;
385e02bfcfSJohn Ogness 	unsigned long flags;
3968b44eaeSGreg Kroah-Hartman 	int result;
4068b44eaeSGreg Kroah-Hartman 	int data_length;
4168b44eaeSGreg Kroah-Hartman 
4268b44eaeSGreg Kroah-Hartman 	switch (status) {
4368b44eaeSGreg Kroah-Hartman 	case 0:
4468b44eaeSGreg Kroah-Hartman 		/* success */
4568b44eaeSGreg Kroah-Hartman 		break;
4668b44eaeSGreg Kroah-Hartman 	case -ECONNRESET:
4768b44eaeSGreg Kroah-Hartman 	case -ENOENT:
4868b44eaeSGreg Kroah-Hartman 	case -ESHUTDOWN:
4968b44eaeSGreg Kroah-Hartman 		/* this urb is terminated, clean up */
50e4083ea5SGreg Kroah-Hartman 		dev_dbg(&port->dev, "%s - urb shutting down with status: %d\n",
5168b44eaeSGreg Kroah-Hartman 			__func__, status);
5268b44eaeSGreg Kroah-Hartman 		return;
5368b44eaeSGreg Kroah-Hartman 	default:
54e4083ea5SGreg Kroah-Hartman 		dev_dbg(&port->dev, "%s - nonzero urb status received: %d\n",
5568b44eaeSGreg Kroah-Hartman 			__func__, status);
5668b44eaeSGreg Kroah-Hartman 		goto exit;
5768b44eaeSGreg Kroah-Hartman 	}
5868b44eaeSGreg Kroah-Hartman 
5959d33f2fSGreg Kroah-Hartman 	usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
6068b44eaeSGreg Kroah-Hartman 
6168b44eaeSGreg Kroah-Hartman 	/*
6268b44eaeSGreg Kroah-Hartman 	 * Data from the device comes with a 1 byte header:
6368b44eaeSGreg Kroah-Hartman 	 *
648ae25a35SPhilipp Hachtmann 	 * <size of data> <data>...
6568b44eaeSGreg Kroah-Hartman 	 */
668ae25a35SPhilipp Hachtmann 	if (urb->actual_length > 1) {
678ae25a35SPhilipp Hachtmann 		data_length = data[0];
688ae25a35SPhilipp Hachtmann 		if (data_length > (urb->actual_length - 1))
698ae25a35SPhilipp Hachtmann 			data_length = urb->actual_length - 1;
702e124b4aSJiri Slaby 		tty_insert_flip_string(&port->port, &data[1], data_length);
712e124b4aSJiri Slaby 		tty_flip_buffer_push(&port->port);
7268b44eaeSGreg Kroah-Hartman 	} else {
73d9a38a87SJohan Hovold 		dev_dbg(&port->dev, "%s - short packet\n", __func__);
7468b44eaeSGreg Kroah-Hartman 	}
7568b44eaeSGreg Kroah-Hartman 
7668b44eaeSGreg Kroah-Hartman exit:
775e02bfcfSJohn Ogness 	spin_lock_irqsave(&priv->lock, flags);
7868b44eaeSGreg Kroah-Hartman 
7968b44eaeSGreg Kroah-Hartman 	/* Continue trying to always read if we should */
8068b44eaeSGreg Kroah-Hartman 	if (!priv->throttled) {
81cced926fSJohan Hovold 		result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC);
8268b44eaeSGreg Kroah-Hartman 		if (result)
8368b44eaeSGreg Kroah-Hartman 			dev_err(&port->dev,
8468b44eaeSGreg Kroah-Hartman 			    "%s - failed resubmitting read urb, error %d\n",
8568b44eaeSGreg Kroah-Hartman 							__func__, result);
8668b44eaeSGreg Kroah-Hartman 	} else
8768b44eaeSGreg Kroah-Hartman 		priv->actually_throttled = true;
885e02bfcfSJohn Ogness 	spin_unlock_irqrestore(&priv->lock, flags);
8968b44eaeSGreg Kroah-Hartman }
9068b44eaeSGreg Kroah-Hartman 
symbol_open(struct tty_struct * tty,struct usb_serial_port * port)91a509a7e4SAlan Cox static int symbol_open(struct tty_struct *tty, struct usb_serial_port *port)
9268b44eaeSGreg Kroah-Hartman {
93951d3793SPhilipp Hachtmann 	struct symbol_private *priv = usb_get_serial_port_data(port);
9468b44eaeSGreg Kroah-Hartman 	unsigned long flags;
9568b44eaeSGreg Kroah-Hartman 	int result = 0;
9668b44eaeSGreg Kroah-Hartman 
9768b44eaeSGreg Kroah-Hartman 	spin_lock_irqsave(&priv->lock, flags);
9868b44eaeSGreg Kroah-Hartman 	priv->throttled = false;
9968b44eaeSGreg Kroah-Hartman 	priv->actually_throttled = false;
10068b44eaeSGreg Kroah-Hartman 	spin_unlock_irqrestore(&priv->lock, flags);
10168b44eaeSGreg Kroah-Hartman 
10268b44eaeSGreg Kroah-Hartman 	/* Start reading from the device */
103cced926fSJohan Hovold 	result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
10468b44eaeSGreg Kroah-Hartman 	if (result)
10568b44eaeSGreg Kroah-Hartman 		dev_err(&port->dev,
10668b44eaeSGreg Kroah-Hartman 			"%s - failed resubmitting read urb, error %d\n",
10768b44eaeSGreg Kroah-Hartman 			__func__, result);
10868b44eaeSGreg Kroah-Hartman 	return result;
10968b44eaeSGreg Kroah-Hartman }
11068b44eaeSGreg Kroah-Hartman 
symbol_close(struct usb_serial_port * port)111335f8514SAlan Cox static void symbol_close(struct usb_serial_port *port)
11268b44eaeSGreg Kroah-Hartman {
113cced926fSJohan Hovold 	usb_kill_urb(port->interrupt_in_urb);
11468b44eaeSGreg Kroah-Hartman }
11568b44eaeSGreg Kroah-Hartman 
symbol_throttle(struct tty_struct * tty)11668b44eaeSGreg Kroah-Hartman static void symbol_throttle(struct tty_struct *tty)
11768b44eaeSGreg Kroah-Hartman {
11868b44eaeSGreg Kroah-Hartman 	struct usb_serial_port *port = tty->driver_data;
119951d3793SPhilipp Hachtmann 	struct symbol_private *priv = usb_get_serial_port_data(port);
12068b44eaeSGreg Kroah-Hartman 
12163832515SOliver Neukum 	spin_lock_irq(&priv->lock);
12268b44eaeSGreg Kroah-Hartman 	priv->throttled = true;
12363832515SOliver Neukum 	spin_unlock_irq(&priv->lock);
12468b44eaeSGreg Kroah-Hartman }
12568b44eaeSGreg Kroah-Hartman 
symbol_unthrottle(struct tty_struct * tty)12668b44eaeSGreg Kroah-Hartman static void symbol_unthrottle(struct tty_struct *tty)
12768b44eaeSGreg Kroah-Hartman {
12868b44eaeSGreg Kroah-Hartman 	struct usb_serial_port *port = tty->driver_data;
129951d3793SPhilipp Hachtmann 	struct symbol_private *priv = usb_get_serial_port_data(port);
13068b44eaeSGreg Kroah-Hartman 	int result;
131b2a5cf1bSOliver Neukum 	bool was_throttled;
13268b44eaeSGreg Kroah-Hartman 
13363832515SOliver Neukum 	spin_lock_irq(&priv->lock);
13468b44eaeSGreg Kroah-Hartman 	priv->throttled = false;
135b2a5cf1bSOliver Neukum 	was_throttled = priv->actually_throttled;
13668b44eaeSGreg Kroah-Hartman 	priv->actually_throttled = false;
13763832515SOliver Neukum 	spin_unlock_irq(&priv->lock);
13868b44eaeSGreg Kroah-Hartman 
139b2a5cf1bSOliver Neukum 	if (was_throttled) {
140cced926fSJohan Hovold 		result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
14168b44eaeSGreg Kroah-Hartman 		if (result)
14268b44eaeSGreg Kroah-Hartman 			dev_err(&port->dev,
14368b44eaeSGreg Kroah-Hartman 				"%s - failed submitting read urb, error %d\n",
14468b44eaeSGreg Kroah-Hartman 							__func__, result);
14568b44eaeSGreg Kroah-Hartman 	}
146b2a5cf1bSOliver Neukum }
14768b44eaeSGreg Kroah-Hartman 
symbol_port_probe(struct usb_serial_port * port)148a85796eeSJohan Hovold static int symbol_port_probe(struct usb_serial_port *port)
149f9c99bb8SAlan Stern {
150a85796eeSJohan Hovold 	struct symbol_private *priv;
151a85796eeSJohan Hovold 
152a85796eeSJohan Hovold 	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
153a85796eeSJohan Hovold 	if (!priv)
154a85796eeSJohan Hovold 		return -ENOMEM;
155a85796eeSJohan Hovold 
156a85796eeSJohan Hovold 	spin_lock_init(&priv->lock);
157a85796eeSJohan Hovold 
158a85796eeSJohan Hovold 	usb_set_serial_port_data(port, priv);
159a85796eeSJohan Hovold 
160a85796eeSJohan Hovold 	return 0;
161a85796eeSJohan Hovold }
162a85796eeSJohan Hovold 
symbol_port_remove(struct usb_serial_port * port)163*c5d1448fSUwe Kleine-König static void symbol_port_remove(struct usb_serial_port *port)
164a85796eeSJohan Hovold {
165a85796eeSJohan Hovold 	struct symbol_private *priv = usb_get_serial_port_data(port);
166f9c99bb8SAlan Stern 
16768b44eaeSGreg Kroah-Hartman 	kfree(priv);
16868b44eaeSGreg Kroah-Hartman }
16968b44eaeSGreg Kroah-Hartman 
17068b44eaeSGreg Kroah-Hartman static struct usb_serial_driver symbol_device = {
17168b44eaeSGreg Kroah-Hartman 	.driver = {
17268b44eaeSGreg Kroah-Hartman 		.owner =	THIS_MODULE,
17368b44eaeSGreg Kroah-Hartman 		.name =		"symbol",
17468b44eaeSGreg Kroah-Hartman 	},
17568b44eaeSGreg Kroah-Hartman 	.id_table =		id_table,
17668b44eaeSGreg Kroah-Hartman 	.num_ports =		1,
177e2cd017fSJohan Hovold 	.num_interrupt_in =	1,
178a85796eeSJohan Hovold 	.port_probe =		symbol_port_probe,
179a85796eeSJohan Hovold 	.port_remove =		symbol_port_remove,
18068b44eaeSGreg Kroah-Hartman 	.open =			symbol_open,
18168b44eaeSGreg Kroah-Hartman 	.close =		symbol_close,
18268b44eaeSGreg Kroah-Hartman 	.throttle = 		symbol_throttle,
18368b44eaeSGreg Kroah-Hartman 	.unthrottle =		symbol_unthrottle,
184cced926fSJohan Hovold 	.read_int_callback =	symbol_int_callback,
18568b44eaeSGreg Kroah-Hartman };
18668b44eaeSGreg Kroah-Hartman 
187d860322fSAlan Stern static struct usb_serial_driver * const serial_drivers[] = {
188d860322fSAlan Stern 	&symbol_device, NULL
189d860322fSAlan Stern };
190d860322fSAlan Stern 
19168e24113SGreg Kroah-Hartman module_usb_serial_driver(serial_drivers, id_table);
19268b44eaeSGreg Kroah-Hartman 
193627cfa89SJohan Hovold MODULE_LICENSE("GPL v2");
194