summaryrefslogtreecommitdiff
path: root/drivers/usb/serial/usb-serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/serial/usb-serial.c')
-rw-r--r--drivers/usb/serial/usb-serial.c226
1 files changed, 168 insertions, 58 deletions
diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c
index 27e3bb58c872..98b33b1b5357 100644
--- a/drivers/usb/serial/usb-serial.c
+++ b/drivers/usb/serial/usb-serial.c
@@ -121,6 +121,44 @@ static void release_minors(struct usb_serial *serial)
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;
@@ -243,7 +281,7 @@ static int serial_open(struct tty_struct *tty, struct file *filp)
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
return tty_port_open(&port->port, tty, filp);
}
@@ -272,7 +310,7 @@ static void serial_hangup(struct tty_struct *tty)
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
tty_port_hangup(&port->port);
}
@@ -281,7 +319,7 @@ static void serial_close(struct tty_struct *tty, struct file *filp)
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
tty_port_close(&port->port, tty, filp);
}
@@ -301,7 +339,7 @@ static void serial_cleanup(struct tty_struct *tty)
struct usb_serial *serial;
struct module *owner;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
/* The console is magical. Do not hang up the console hardware
* or there will be tears.
@@ -329,7 +367,7 @@ static int serial_write(struct tty_struct *tty, const unsigned char *buf,
if (port->serial->dev->state == USB_STATE_NOTATTACHED)
goto exit;
- dev_dbg(tty->dev, "%s - %d byte(s)\n", __func__, count);
+ dev_dbg(&port->dev, "%s - %d byte(s)\n", __func__, count);
retval = port->serial->type->write(tty, port, buf, count);
if (retval < 0)
@@ -342,7 +380,7 @@ static int serial_write_room(struct tty_struct *tty)
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
return port->serial->type->write_room(tty);
}
@@ -352,7 +390,7 @@ static int serial_chars_in_buffer(struct tty_struct *tty)
struct usb_serial_port *port = tty->driver_data;
struct usb_serial *serial = port->serial;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (serial->disconnected)
return 0;
@@ -365,7 +403,7 @@ static void serial_wait_until_sent(struct tty_struct *tty, int timeout)
struct usb_serial_port *port = tty->driver_data;
struct usb_serial *serial = port->serial;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (!port->serial->type->wait_until_sent)
return;
@@ -380,7 +418,7 @@ static void serial_throttle(struct tty_struct *tty)
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->throttle)
port->serial->type->throttle(tty);
@@ -390,7 +428,7 @@ static void serial_unthrottle(struct tty_struct *tty)
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->unthrottle)
port->serial->type->unthrottle(tty);
@@ -399,19 +437,62 @@ static void serial_unthrottle(struct tty_struct *tty)
static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss)
{
struct usb_serial_port *port = tty->driver_data;
+ struct tty_port *tport = &port->port;
+ unsigned int close_delay, closing_wait;
+
+ mutex_lock(&tport->mutex);
+
+ close_delay = jiffies_to_msecs(tport->close_delay) / 10;
+ closing_wait = tport->closing_wait;
+ if (closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ closing_wait = jiffies_to_msecs(closing_wait) / 10;
+
+ ss->line = port->minor;
+ ss->close_delay = close_delay;
+ ss->closing_wait = closing_wait;
if (port->serial->type->get_serial)
- return port->serial->type->get_serial(tty, ss);
- return -ENOTTY;
+ port->serial->type->get_serial(tty, ss);
+
+ mutex_unlock(&tport->mutex);
+
+ return 0;
}
static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss)
{
struct usb_serial_port *port = tty->driver_data;
+ struct tty_port *tport = &port->port;
+ unsigned int close_delay, closing_wait;
+ int ret = 0;
+
+ close_delay = msecs_to_jiffies(ss->close_delay * 10);
+ closing_wait = ss->closing_wait;
+ if (closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ closing_wait = msecs_to_jiffies(closing_wait * 10);
+
+ mutex_lock(&tport->mutex);
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if (close_delay != tport->close_delay ||
+ closing_wait != tport->closing_wait) {
+ ret = -EPERM;
+ goto out_unlock;
+ }
+ }
- if (port->serial->type->set_serial)
- return port->serial->type->set_serial(tty, ss);
- return -ENOTTY;
+ if (port->serial->type->set_serial) {
+ ret = port->serial->type->set_serial(tty, ss);
+ if (ret)
+ goto out_unlock;
+ }
+
+ tport->close_delay = close_delay;
+ tport->closing_wait = closing_wait;
+out_unlock:
+ mutex_unlock(&tport->mutex);
+
+ return ret;
}
static int serial_ioctl(struct tty_struct *tty,
@@ -420,7 +501,7 @@ static int serial_ioctl(struct tty_struct *tty,
struct usb_serial_port *port = tty->driver_data;
int retval = -ENOIOCTLCMD;
- dev_dbg(tty->dev, "%s - cmd 0x%04x\n", __func__, cmd);
+ dev_dbg(&port->dev, "%s - cmd 0x%04x\n", __func__, cmd);
switch (cmd) {
case TIOCMIWAIT:
@@ -439,7 +520,7 @@ static void serial_set_termios(struct tty_struct *tty, struct ktermios *old)
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->set_termios)
port->serial->type->set_termios(tty, port, old);
@@ -451,7 +532,7 @@ static int serial_break(struct tty_struct *tty, int break_state)
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->break_ctl)
port->serial->type->break_ctl(tty, break_state);
@@ -498,11 +579,11 @@ static int serial_tiocmget(struct tty_struct *tty)
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->tiocmget)
return port->serial->type->tiocmget(tty);
- return -EINVAL;
+ return -ENOTTY;
}
static int serial_tiocmset(struct tty_struct *tty,
@@ -510,11 +591,11 @@ static int serial_tiocmset(struct tty_struct *tty,
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->tiocmset)
return port->serial->type->tiocmset(tty, set, clear);
- return -EINVAL;
+ return -ENOTTY;
}
static int serial_get_icount(struct tty_struct *tty,
@@ -522,11 +603,11 @@ static int serial_get_icount(struct tty_struct *tty,
{
struct usb_serial_port *port = tty->driver_data;
- dev_dbg(tty->dev, "%s\n", __func__);
+ dev_dbg(&port->dev, "%s\n", __func__);
if (port->serial->type->get_icount)
return port->serial->type->get_icount(tty, icount);
- return -EINVAL;
+ return -ENOTTY;
}
/*
@@ -711,36 +792,48 @@ static const struct tty_port_operations serial_port_ops = {
.shutdown = serial_port_shutdown,
};
-static void find_endpoints(struct usb_serial *serial,
- struct usb_serial_endpoints *epds)
+static void store_endpoint(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds,
+ struct usb_endpoint_descriptor *epd)
{
struct device *dev = &serial->interface->dev;
+ u8 addr = epd->bEndpointAddress;
+
+ if (usb_endpoint_is_bulk_in(epd)) {
+ if (epds->num_bulk_in == ARRAY_SIZE(epds->bulk_in))
+ return;
+ dev_dbg(dev, "found bulk in endpoint %02x\n", addr);
+ epds->bulk_in[epds->num_bulk_in++] = epd;
+ } else if (usb_endpoint_is_bulk_out(epd)) {
+ if (epds->num_bulk_out == ARRAY_SIZE(epds->bulk_out))
+ return;
+ dev_dbg(dev, "found bulk out endpoint %02x\n", addr);
+ epds->bulk_out[epds->num_bulk_out++] = epd;
+ } else if (usb_endpoint_is_int_in(epd)) {
+ if (epds->num_interrupt_in == ARRAY_SIZE(epds->interrupt_in))
+ return;
+ dev_dbg(dev, "found interrupt in endpoint %02x\n", addr);
+ epds->interrupt_in[epds->num_interrupt_in++] = epd;
+ } else if (usb_endpoint_is_int_out(epd)) {
+ if (epds->num_interrupt_out == ARRAY_SIZE(epds->interrupt_out))
+ return;
+ dev_dbg(dev, "found interrupt out endpoint %02x\n", addr);
+ epds->interrupt_out[epds->num_interrupt_out++] = epd;
+ }
+}
+
+static void find_endpoints(struct usb_serial *serial,
+ struct usb_serial_endpoints *epds,
+ struct usb_interface *intf)
+{
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *epd;
unsigned int i;
- BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_in) < USB_MAXENDPOINTS / 2);
- BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_out) < USB_MAXENDPOINTS / 2);
- BUILD_BUG_ON(ARRAY_SIZE(epds->interrupt_in) < USB_MAXENDPOINTS / 2);
- BUILD_BUG_ON(ARRAY_SIZE(epds->interrupt_out) < USB_MAXENDPOINTS / 2);
-
- iface_desc = serial->interface->cur_altsetting;
+ iface_desc = intf->cur_altsetting;
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
epd = &iface_desc->endpoint[i].desc;
-
- if (usb_endpoint_is_bulk_in(epd)) {
- dev_dbg(dev, "found bulk in on endpoint %u\n", i);
- epds->bulk_in[epds->num_bulk_in++] = epd;
- } else if (usb_endpoint_is_bulk_out(epd)) {
- dev_dbg(dev, "found bulk out on endpoint %u\n", i);
- epds->bulk_out[epds->num_bulk_out++] = epd;
- } else if (usb_endpoint_is_int_in(epd)) {
- dev_dbg(dev, "found interrupt in on endpoint %u\n", i);
- epds->interrupt_in[epds->num_interrupt_in++] = epd;
- } else if (usb_endpoint_is_int_out(epd)) {
- dev_dbg(dev, "found interrupt out on endpoint %u\n", i);
- epds->interrupt_out[epds->num_interrupt_out++] = epd;
- }
+ store_endpoint(serial, epds, epd);
}
}
@@ -906,7 +999,7 @@ static int usb_serial_probe(struct usb_interface *interface,
if (retval) {
dev_dbg(ddev, "sub driver rejected device\n");
- goto err_put_serial;
+ goto err_release_sibling;
}
}
@@ -914,10 +1007,12 @@ static int usb_serial_probe(struct usb_interface *interface,
epds = kzalloc(sizeof(*epds), GFP_KERNEL);
if (!epds) {
retval = -ENOMEM;
- goto err_put_serial;
+ goto err_release_sibling;
}
- find_endpoints(serial, epds);
+ find_endpoints(serial, epds, interface);
+ if (serial->sibling)
+ find_endpoints(serial, epds, serial->sibling);
if (epds->num_bulk_in < type->num_bulk_in ||
epds->num_bulk_out < type->num_bulk_out ||
@@ -1065,7 +1160,8 @@ exit:
err_free_epds:
kfree(epds);
-err_put_serial:
+err_release_sibling:
+ release_sibling(serial, interface);
usb_serial_put(serial);
err_put_module:
module_put(type->driver.owner);
@@ -1081,6 +1177,10 @@ static void usb_serial_disconnect(struct usb_interface *interface)
struct usb_serial_port *port;
struct tty_struct *tty;
+ /* sibling interface is cleaning up */
+ if (!serial)
+ return;
+
usb_serial_console_disconnect(serial);
mutex_lock(&serial->disc_mutex);
@@ -1104,6 +1204,8 @@ static void usb_serial_disconnect(struct usb_interface *interface)
if (serial->type->disconnect)
serial->type->disconnect(serial);
+ release_sibling(serial, interface);
+
/* let the last holder of this object cause it to be cleaned up */
usb_serial_put(serial);
dev_info(dev, "device disconnected\n");
@@ -1112,9 +1214,11 @@ static void usb_serial_disconnect(struct usb_interface *interface)
int usb_serial_suspend(struct usb_interface *intf, pm_message_t message)
{
struct usb_serial *serial = usb_get_intfdata(intf);
- int i, r = 0;
+ int i, r;
- serial->suspending = 1;
+ /* suspend when called for first sibling interface */
+ if (serial->suspend_count++)
+ return 0;
/*
* serial->type->suspend() MUST return 0 in system sleep context,
@@ -1124,15 +1228,15 @@ int usb_serial_suspend(struct usb_interface *intf, pm_message_t message)
if (serial->type->suspend) {
r = serial->type->suspend(serial, message);
if (r < 0) {
- serial->suspending = 0;
- goto err_out;
+ serial->suspend_count--;
+ return r;
}
}
for (i = 0; i < serial->num_ports; ++i)
usb_serial_port_poison_urbs(serial->port[i]);
-err_out:
- return r;
+
+ return 0;
}
EXPORT_SYMBOL(usb_serial_suspend);
@@ -1149,9 +1253,12 @@ int usb_serial_resume(struct usb_interface *intf)
struct usb_serial *serial = usb_get_intfdata(intf);
int rv;
+ /* resume when called for last sibling interface */
+ if (--serial->suspend_count)
+ return 0;
+
usb_serial_unpoison_port_urbs(serial);
- serial->suspending = 0;
if (serial->type->resume)
rv = serial->type->resume(serial);
else
@@ -1166,9 +1273,12 @@ static int usb_serial_reset_resume(struct usb_interface *intf)
struct usb_serial *serial = usb_get_intfdata(intf);
int rv;
+ /* resume when called for last sibling interface */
+ if (--serial->suspend_count)
+ return 0;
+
usb_serial_unpoison_port_urbs(serial);
- serial->suspending = 0;
if (serial->type->reset_resume) {
rv = serial->type->reset_resume(serial);
} else {