diff options
Diffstat (limited to 'drivers/usb/serial/usb-serial.c')
| -rw-r--r-- | drivers/usb/serial/usb-serial.c | 341 |
1 files changed, 244 insertions, 97 deletions
diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index cfcfd5ab06ce..99188c92068b 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -21,15 +21,18 @@ #include <linux/errno.h> #include <linux/init.h> #include <linux/slab.h> +#include <linux/smp_lock.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 "pl2303.h" @@ -136,21 +139,17 @@ static void destroy_serial(struct kref *kref) dbg("%s - %s", __func__, serial->type->description); - serial->type->shutdown(serial); - /* return the minor range that this device had */ if (serial->minor != SERIAL_TTY_NO_MINOR) return_serial(serial); - for (i = 0; i < serial->num_ports; ++i) - serial->port[i]->port.count = 0; + serial->type->release(serial); - /* the ports are cleaned up and released in port_release() */ - for (i = 0; i < serial->num_ports; ++i) - if (serial->port[i]->dev.parent != NULL) { - device_unregister(&serial->port[i]->dev); - serial->port[i] = NULL; - } + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (port) + put_device(&port->dev); + } /* If this is a "fake" port, we have to clean it up here, as it will * not get cleaned up in port_release() as it was never registered with @@ -159,9 +158,8 @@ static void destroy_serial(struct kref *kref) for (i = serial->num_ports; i < serial->num_port_pointers; ++i) { port = serial->port[i]; - if (!port) - continue; - port_free(port); + if (port) + port_free(port); } } @@ -186,7 +184,8 @@ static int serial_open (struct tty_struct *tty, struct file *filp) struct usb_serial *serial; struct usb_serial_port *port; unsigned int portNumber; - int retval; + int retval = 0; + int first = 0; dbg("%s", __func__); @@ -197,16 +196,24 @@ static int serial_open (struct tty_struct *tty, struct file *filp) return -ENODEV; } + mutex_lock(&serial->disc_mutex); portNumber = tty->index - serial->minor; port = serial->port[portNumber]; - if (!port) { + if (!port || serial->disconnected) retval = -ENODEV; - goto bailout_kref_put; - } + else + get_device(&port->dev); + /* + * Note: Our locking order requirement does not allow port->mutex + * to be acquired while serial->disc_mutex is held. + */ + mutex_unlock(&serial->disc_mutex); + if (retval) + goto bailout_serial_put; if (mutex_lock_interruptible(&port->mutex)) { retval = -ERESTARTSYS; - goto bailout_kref_put; + goto bailout_port_put; } ++port->port.count; @@ -216,8 +223,9 @@ static int serial_open (struct tty_struct *tty, struct file *filp) tty->driver_data = port; tty_port_tty_set(&port->port, tty); - if (port->port.count == 1) { - + /* If the console is attached, the device is already open */ + if (port->port.count == 1 && !port->console) { + first = 1; /* lock this module before we call it * this may fail, which means we must bail out, * safe because we are called with BKL held */ @@ -226,33 +234,113 @@ static int serial_open (struct tty_struct *tty, struct file *filp) goto bailout_mutex_unlock; } - retval = usb_autopm_get_interface(serial->interface); + mutex_lock(&serial->disc_mutex); + if (serial->disconnected) + retval = -ENODEV; + else + retval = usb_autopm_get_interface(serial->interface); if (retval) goto bailout_module_put; + /* only call the device specific open if this * is the first time the port is opened */ retval = serial->type->open(tty, port, filp); if (retval) goto bailout_interface_put; + mutex_unlock(&serial->disc_mutex); + set_bit(ASYNCB_INITIALIZED, &port->port.flags); } - mutex_unlock(&port->mutex); - return 0; - + /* Now do the correct tty layer semantics */ + retval = tty_port_block_til_ready(&port->port, tty, filp); + if (retval == 0) { + if (!first) + usb_serial_put(serial); + return 0; + } + mutex_lock(&port->mutex); + if (first == 0) + goto bailout_mutex_unlock; + /* Undo the initial port actions */ + mutex_lock(&serial->disc_mutex); bailout_interface_put: usb_autopm_put_interface(serial->interface); bailout_module_put: + mutex_unlock(&serial->disc_mutex); module_put(serial->type->driver.owner); bailout_mutex_unlock: port->port.count = 0; tty->driver_data = NULL; tty_port_tty_set(&port->port, NULL); mutex_unlock(&port->mutex); -bailout_kref_put: +bailout_port_put: + put_device(&port->dev); +bailout_serial_put: usb_serial_put(serial); return retval; } +/** + * serial_do_down - shut down hardware + * @port: port to shut down + * + * Shut down a USB port unless it is the console. We never shut down the + * console hardware as it will always be in use. + * + * Don't free any resources at this point + */ +static void serial_do_down(struct usb_serial_port *port) +{ + struct usb_serial_driver *drv = port->serial->type; + struct usb_serial *serial; + struct module *owner; + + /* The console is magical, do not hang up the console hardware + or there will be tears */ + if (port->console) + return; + + mutex_lock(&port->mutex); + serial = port->serial; + owner = serial->type->driver.owner; + + if (drv->close) + drv->close(port); + + mutex_unlock(&port->mutex); +} + +/** + * serial_do_free - free resources post close/hangup + * @port: port to free up + * + * Do the resource freeing and refcount dropping for the port. We must + * be careful about ordering and we must avoid freeing up the console. + */ + +static void serial_do_free(struct usb_serial_port *port) +{ + struct usb_serial *serial; + struct module *owner; + + /* The console is magical, do not hang up the console hardware + or there will be tears */ + if (port->console) + return; + + serial = port->serial; + owner = serial->type->driver.owner; + put_device(&port->dev); + /* Mustn't dereference port any more */ + mutex_lock(&serial->disc_mutex); + if (!serial->disconnected) + usb_autopm_put_interface(serial->interface); + mutex_unlock(&serial->disc_mutex); + usb_serial_put(serial); + /* Mustn't dereference serial any more */ + module_put(owner); +} + static void serial_close(struct tty_struct *tty, struct file *filp) { struct usb_serial_port *port = tty->driver_data; @@ -262,44 +350,39 @@ static void serial_close(struct tty_struct *tty, struct file *filp) dbg("%s - port %d", __func__, port->number); - mutex_lock(&port->mutex); - - if (port->port.count == 0) { - mutex_unlock(&port->mutex); + /* FIXME: + This leaves a very narrow race. Really we should do the + serial_do_free() on tty->shutdown(), but tty->shutdown can + be called from IRQ context and serial_do_free can sleep. + + The right fix is probably to make the tty free (which is rare) + and thus tty->shutdown() occur via a work queue and simplify all + the drivers that use it. + */ + if (tty_hung_up_p(filp)) { + /* serial_hangup already called serial_down at this point. + Another user may have already reopened the port but + serial_do_free is refcounted */ + serial_do_free(port); return; } - if (port->port.count == 1) - /* only call the device specific close if this - * port is being closed by the last owner. Ensure we do - * this before we drop the port count. The call is protected - * by the port mutex - */ - port->serial->type->close(tty, port, filp); - - if (port->port.count == (port->console ? 2 : 1)) { - struct tty_struct *tty = tty_port_tty_get(&port->port); - if (tty) { - /* We must do this before we drop the port count to - zero. */ - if (tty->driver_data) - tty->driver_data = NULL; - tty_port_tty_set(&port->port, NULL); - tty_kref_put(tty); - } - } + if (tty_port_close_start(&port->port, tty, filp) == 0) + return; - if (port->port.count == 1) { - mutex_lock(&port->serial->disc_mutex); - if (!port->serial->disconnected) - usb_autopm_put_interface(port->serial->interface); - mutex_unlock(&port->serial->disc_mutex); - module_put(port->serial->type->driver.owner); - } - --port->port.count; + serial_do_down(port); + tty_port_close_end(&port->port, tty); + tty_port_tty_set(&port->port, NULL); + serial_do_free(port); +} - mutex_unlock(&port->mutex); - usb_serial_put(port->serial); +static void serial_hangup(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + serial_do_down(port); + tty_port_hangup(&port->port); + /* We must not free port yet - the USB serial layer depends on it's + continued existence */ } static int serial_write(struct tty_struct *tty, const unsigned char *buf, @@ -338,7 +421,6 @@ static int serial_chars_in_buffer(struct tty_struct *tty) struct usb_serial_port *port = tty->driver_data; dbg("%s = port %d", __func__, port->number); - WARN_ON(!port->port.count); /* if the device was unplugged then any remaining characters fell out of the connector ;) */ if (port->serial->disconnected) @@ -416,57 +498,52 @@ static int serial_break(struct tty_struct *tty, int break_state) return 0; } -static int serial_read_proc(char *page, char **start, off_t off, int count, - int *eof, void *data) +static int serial_proc_show(struct seq_file *m, void *v) { struct usb_serial *serial; - int length = 0; int i; - off_t begin = 0; char tmp[40]; dbg("%s", __func__); - length += sprintf(page, "usbserinfo:1.0 driver:2.0\n"); - for (i = 0; i < SERIAL_TTY_MINORS && length < PAGE_SIZE; ++i) { + seq_puts(m, "usbserinfo:1.0 driver:2.0\n"); + for (i = 0; i < SERIAL_TTY_MINORS; ++i) { serial = usb_serial_get_by_index(i); if (serial == NULL) continue; - length += sprintf(page+length, "%d:", i); + seq_printf(m, "%d:", i); if (serial->type->driver.owner) - length += sprintf(page+length, " module:%s", + seq_printf(m, " module:%s", module_name(serial->type->driver.owner)); - length += sprintf(page+length, " name:\"%s\"", + seq_printf(m, " name:\"%s\"", serial->type->description); - length += sprintf(page+length, " vendor:%04x product:%04x", + seq_printf(m, " vendor:%04x product:%04x", le16_to_cpu(serial->dev->descriptor.idVendor), le16_to_cpu(serial->dev->descriptor.idProduct)); - length += sprintf(page+length, " num_ports:%d", - serial->num_ports); - length += sprintf(page+length, " port:%d", - i - serial->minor + 1); + seq_printf(m, " num_ports:%d", serial->num_ports); + seq_printf(m, " port:%d", i - serial->minor + 1); usb_make_path(serial->dev, tmp, sizeof(tmp)); - length += sprintf(page+length, " path:%s", tmp); + seq_printf(m, " path:%s", tmp); - length += sprintf(page+length, "\n"); - if ((length + begin) > (off + count)) { - usb_serial_put(serial); - goto done; - } - if ((length + begin) < off) { - begin += length; - length = 0; - } + seq_putc(m, '\n'); usb_serial_put(serial); } - *eof = 1; -done: - if (off >= (length + begin)) - return 0; - *start = page + (off-begin); - return (count < begin+length-off) ? count : begin+length-off; + return 0; } +static int serial_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, serial_proc_show, NULL); +} + +static const struct file_operations serial_proc_fops = { + .owner = THIS_MODULE, + .open = serial_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + static int serial_tiocmget(struct tty_struct *tty, struct file *file) { struct usb_serial_port *port = tty->driver_data; @@ -548,7 +625,13 @@ static void kill_traffic(struct usb_serial_port *port) static void port_free(struct usb_serial_port *port) { + /* + * Stop all the traffic before cancelling the work, so that + * nobody will restart it by calling usb_serial_port_softint. + */ kill_traffic(port); + cancel_work_sync(&port->work); + usb_free_urb(port->read_urb); usb_free_urb(port->write_urb); usb_free_urb(port->interrupt_in_urb); @@ -557,7 +640,6 @@ static void port_free(struct usb_serial_port *port) kfree(port->bulk_out_buffer); kfree(port->interrupt_in_buffer); kfree(port->interrupt_out_buffer); - flush_scheduled_work(); /* port->work */ kfree(port); } @@ -631,6 +713,29 @@ static struct usb_serial_driver *search_serial_device( return NULL; } +static int serial_carrier_raised(struct tty_port *port) +{ + struct usb_serial_port *p = container_of(port, struct usb_serial_port, port); + struct usb_serial_driver *drv = p->serial->type; + if (drv->carrier_raised) + return drv->carrier_raised(p); + /* No carrier control - don't block */ + return 1; +} + +static void serial_dtr_rts(struct tty_port *port, int on) +{ + struct usb_serial_port *p = container_of(port, struct usb_serial_port, port); + struct usb_serial_driver *drv = p->serial->type; + if (drv->dtr_rts) + drv->dtr_rts(p, on); +} + +static const struct tty_port_operations serial_port_ops = { + .carrier_raised = serial_carrier_raised, + .dtr_rts = serial_dtr_rts, +}; + int usb_serial_probe(struct usb_interface *interface, const struct usb_device_id *id) { @@ -824,6 +929,7 @@ int usb_serial_probe(struct usb_interface *interface, if (!port) goto probe_error; tty_port_init(&port->port); + port->port.ops = &serial_port_ops; port->serial = serial; spin_lock_init(&port->lock); mutex_init(&port->mutex); @@ -957,6 +1063,7 @@ int usb_serial_probe(struct usb_interface *interface, if (retval > 0) { /* quietly accept this device, but don't bind to a serial port as it's about to disappear */ + serial->num_ports = 0; goto exit; } } @@ -977,10 +1084,15 @@ int usb_serial_probe(struct usb_interface *interface, dev_set_name(&port->dev, "ttyUSB%d", port->number); dbg ("%s - registering %s", __func__, dev_name(&port->dev)); + port->dev_state = PORT_REGISTERING; retval = device_register(&port->dev); - if (retval) + if (retval) { dev_err(&port->dev, "Error registering port device, " "continuing\n"); + port->dev_state = PORT_UNREGISTERED; + } else { + port->dev_state = PORT_REGISTERED; + } } usb_serial_console_init(debug, minor); @@ -1042,20 +1154,43 @@ void usb_serial_disconnect(struct usb_interface *interface) usb_set_intfdata(interface, NULL); /* must set a flag, to signal subdrivers */ serial->disconnected = 1; + mutex_unlock(&serial->disc_mutex); + for (i = 0; i < serial->num_ports; ++i) { port = serial->port[i]; if (port) { struct tty_struct *tty = tty_port_tty_get(&port->port); if (tty) { + /* The hangup will occur asynchronously but + the object refcounts will sort out all the + cleanup */ tty_hangup(tty); tty_kref_put(tty); } kill_traffic(port); + cancel_work_sync(&port->work); + if (port->dev_state == PORT_REGISTERED) { + + /* Make sure the port is bound so that the + * driver's port_remove method is called. + */ + if (!port->dev.driver) { + int rc; + + port->dev.driver = + &serial->type->driver; + rc = device_bind_driver(&port->dev); + } + port->dev_state = PORT_UNREGISTERING; + device_del(&port->dev); + port->dev_state = PORT_UNREGISTERED; + } } } + serial->type->disconnect(serial); + /* let the last holder of this object * cause it to be cleaned up */ - mutex_unlock(&serial->disc_mutex); usb_serial_put(serial); dev_info(dev, "device disconnected\n"); } @@ -1067,6 +1202,8 @@ int usb_serial_suspend(struct usb_interface *intf, pm_message_t message) struct usb_serial_port *port; int i, r = 0; + serial->suspending = 1; + for (i = 0; i < serial->num_ports; ++i) { port = serial->port[i]; if (port) @@ -1083,10 +1220,15 @@ EXPORT_SYMBOL(usb_serial_suspend); int usb_serial_resume(struct usb_interface *intf) { struct usb_serial *serial = usb_get_intfdata(intf); + int rv; + serial->suspending = 0; if (serial->type->resume) - return serial->type->resume(serial); - return 0; + rv = serial->type->resume(serial); + else + rv = usb_serial_generic_resume(serial); + + return rv; } EXPORT_SYMBOL(usb_serial_resume); @@ -1094,6 +1236,7 @@ static const struct tty_operations serial_ops = { .open = serial_open, .close = serial_close, .write = serial_write, + .hangup = serial_hangup, .write_room = serial_write_room, .ioctl = serial_ioctl, .set_termios = serial_set_termios, @@ -1101,11 +1244,12 @@ static const struct tty_operations serial_ops = { .unthrottle = serial_unthrottle, .break_ctl = serial_break, .chars_in_buffer = serial_chars_in_buffer, - .read_proc = serial_read_proc, .tiocmget = serial_tiocmget, .tiocmset = serial_tiocmset, + .proc_fops = &serial_proc_fops, }; + struct tty_driver *usb_serial_tty_driver; static int __init usb_serial_init(void) @@ -1221,8 +1365,8 @@ static void fixup_generic(struct usb_serial_driver *device) set_to_generic_if_null(device, chars_in_buffer); set_to_generic_if_null(device, read_bulk_callback); set_to_generic_if_null(device, write_bulk_callback); - set_to_generic_if_null(device, shutdown); - set_to_generic_if_null(device, resume); + set_to_generic_if_null(device, disconnect); + set_to_generic_if_null(device, release); } int usb_serial_register(struct usb_serial_driver *driver) @@ -1230,6 +1374,9 @@ int usb_serial_register(struct usb_serial_driver *driver) /* must be called with BKL held */ int retval; + if (usb_disabled()) + return -ENODEV; + fixup_generic(driver); if (!driver->description) |
