// SPDX-License-Identifier: GPL-2.0
/*
* USB Serial Converter driver
*
* Copyright (C) 2009 - 2013 Johan Hovold (jhovold@gmail.com)
* Copyright (C) 1999 - 2012 Greg Kroah-Hartman (greg@kroah.com)
* Copyright (C) 2000 Peter Berger (pberger@brimson.com)
* Copyright (C) 2000 Al Borchers (borchers@steinerpoint.com)
*
* This driver was originally based on the ACM driver by Armin Fuerst (which was
* based on a driver by Brad Keryan)
*
* See Documentation/usb/usb-serial.rst for more information on using this
* driver
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/seq_file.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/uaccess.h>
#include <linux/serial.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#include <linux/kfifo.h>
#include <linux/idr.h>
#define DRIVER_AUTHOR "Greg Kroah-Hartman <gregkh@linuxfoundation.org>"
#define DRIVER_DESC "USB Serial Driver core"
#define USB_SERIAL_TTY_MAJOR 188
#define USB_SERIAL_TTY_MINORS 512 /* should be enough for a while */
/* There is no MODULE_DEVICE_TABLE for usbserial.c. Instead
the MODULE_DEVICE_TABLE declarations in each serial driver
cause the "hotplug" program to pull in whatever module is necessary
via modprobe, and modprobe will load usbserial because the serial
drivers depend on it.
*/
static DEFINE_IDR(serial_minors);
static DEFINE_MUTEX(table_lock);
static LIST_HEAD(usb_serial_driver_list);
/*
* Look up the serial port structure. If it is found and it hasn't been
* disconnected, return with the parent usb_serial structure's disc_mutex held
* and its refcount incremented. Otherwise return NULL.
*/
struct usb_serial_port *usb_serial_port_get_by_minor(unsigned minor)
{
struct usb_serial *serial;
struct usb_serial_port *port;
mutex_lock(&table_lock);
port = idr_find(&serial_minors, minor);
if (!port)
goto exit;
serial = port->serial;
mutex_lock(&serial->disc_mutex);
if (serial->disconnected) {
mutex_unlock(&serial->disc_mutex);
port = NULL;
} else {
kref_get(&serial->kref);
}
exit:
mutex_unlock(&table_lock);
return port;
}
static int allocate_minors(struct usb_serial *serial, int num_ports)
{
struct usb_serial_port *port;
unsigned int i, j;
int minor;
dev_dbg(&serial->interface->dev, "%s %d\n", __func__, num_ports);
mutex_lock(&table_lock);
for (i = 0; i < num_ports; ++i) {
port = serial->port[i];
minor = idr_alloc(&serial_minors, port, 0,
USB_SERIAL_TTY_MINORS, GFP_KERNEL);
if (minor < 0)
goto error;
port->minor = minor;
port->port_number = i;
}
serial->minors_reserved = 1;
mutex_unlock(&table_lock);
return 0;
error:
/* unwind the already allocated minors */
for (j = 0; j < i; ++j)
idr_remove(&serial_minors, serial->port[j]->minor);
mutex_unlock(&table_lock);
return minor;
}
static void release_minors(struct usb_serial *serial)
{
int i;
mutex_lock(&table_lock);
for (i = 0; i < serial->num_ports; ++i)
idr_remove(&serial_minors, serial->port[i]->minor);
mutex_unlock(&table_lock);
serial->minors_reserved = 0;
}
int usb_serial_claim_interface(struct usb_serial *serial, struct usb_interface *intf)
{
struct usb_driver *driver = serial->type->usb_driver;
int ret;
if (serial->sibling)
return -EBUSY;
ret = usb_driver_claim_interface(driver, intf, serial);
if (ret) {
dev_err(&serial->interface->dev,
"failed to claim sibling interface: %d\n", ret);
return ret;
}
serial->sibling = intf;
return 0;
}
EXPORT_SYMBOL_GPL(usb_serial_claim_interface);
static void release_sibling(struct usb_serial *serial, struct usb_interface *intf)
{
struct usb_driver *driver = serial->type->usb_driver;
struct usb_interface *sibling;
if (!serial->sibling)
return;
if (intf == serial->sibling)
sibling = serial->interface;
else
sibling = serial->sibling;
usb_set_intfdata(sibling, NULL);
usb_driver_release_interface(driver, sibling);
}
static void destroy_serial(struct kref *kref)
{
struct usb_serial *serial;
struct usb_serial_port *port;
int i;
serial = to_usb_serial(kref);
/* return the minor range that this device had */
if (serial->minors_reserved)
release_minors(serial);
if (serial->attached && serial->type->release)
serial->type->release(serial);
/* Now that nothing is using the ports, they can be freed */
for (i = 0; i < serial->num_port_pointers; ++i) {
port = serial->port[i];
if (port) {
port->serial = NULL;
put_device(&port->dev);
}
}
usb_put_intf(serial->interface);
usb_put_dev(serial->dev);
kfree(serial);
}
void usb_serial_put(struct usb_serial *serial)
{
kref_put(&serial->kref, destroy_serial);
}
/*****************************************************************************
* Driver tty interface functions
*****************************************************************************/
/**
* serial_install - install tty
* @driver: the driver (USB in our case)
* @tty: the tty being created
*
* Initialise the termios structure for this tty. We use the default
* USB serial settings but permit them to be overridden by
* serial->type->init_termios on first open.
*
* This is the first place a new tty gets used. Hence this is where we
* acquire references to the usb_serial structure and the driver module,
* where we store a pointer to the port. All these actions are reversed
* in serial_cleanup().
*/
static int serial_install(struct tty_driver *driver, struct tty_struct *tty)
{
int idx = tty->index;
struct usb_serial *serial;
struct usb_serial_port *port;
bool init_termios;
int retval = -ENODEV;
port = usb_serial_port_get_by_minor(idx);
if (!port)
return retval;
serial = port->serial;
if (!try_module_get(serial->type->driver.owner))
goto err_put_serial;
init_termios = (driver->termios[idx] == NULL);
retval = tty_standard_install(driver, tty);
if (retval)
goto err_put_module;
mutex_unlock(&serial->disc_mutex);
/* allow the driver to update the initial settings */
if (init_termios && serial->type->init_termios)
serial->type->init_termios(tty);
tty->driver_data = port;
return retval;
err_put_module:
module_put(serial->type->driver.owner);
err_put_s
|