From 3ac856c100f4eccdd8e4f56704f32ebdd5f4dd80 Mon Sep 17 00:00:00 2001 From: Olivier Sobrie Date: Fri, 30 Jan 2015 13:21:53 +0100 Subject: [PATCH 01/11] hso: remove useless header file timer.h No timer related function is used in this driver. Signed-off-by: Olivier Sobrie Signed-off-by: David S. Miller --- drivers/net/usb/hso.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index 9c5aa922a9f4b..73549bb65a489 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -58,7 +58,6 @@ #include #include #include -#include #include #include #include From 29bd3bc1194c624ce863cab2a7da9bc1f0c3b47b Mon Sep 17 00:00:00 2001 From: Olivier Sobrie Date: Fri, 30 Jan 2015 13:21:54 +0100 Subject: [PATCH 02/11] hso: fix crash when device disappears while serial port is open When the device disappear, the function hso_disconnect() is called to perform cleanup. In the cleanup function, hso_free_interface() calls tty_port_tty_hangup() in view of scheduling a work to hang up the tty if needed. If the port was not open then hso_serial_ref_free() is called directly to cleanup everything. Otherwise, hso_serial_ref_free() is called when the last fd associated to the port is closed. For each open port, tty_release() will call the close method, hso_serial_close(), which drops the last kref and call hso_serial_ref_free() which unregisters, destroys the tty port and finally frees the structure in which the tty_port structure is included. Later, in tty_release(), more precisely when release_tty() is called, the tty_port previously freed is accessed to cancel the tty buf workqueue and it leads to a crash. In view of avoiding this crash, we add a cleanup method that is called at the end of the hangup process and we drop the last kref in this function when all the ports have been closed, when tty_port is no more needed and when it is safe to free the structure containing the tty_port structure. Signed-off-by: Olivier Sobrie Signed-off-by: David S. Miller --- drivers/net/usb/hso.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index 73549bb65a489..191c1fac08b67 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -1270,7 +1270,6 @@ static int hso_serial_open(struct tty_struct *tty, struct file *filp) goto err_out; D1("Opening %d", serial->minor); - kref_get(&serial->parent->ref); /* setup */ tty->driver_data = serial; @@ -1289,7 +1288,8 @@ static int hso_serial_open(struct tty_struct *tty, struct file *filp) if (result) { hso_stop_serial_device(serial->parent); serial->port.count--; - kref_put(&serial->parent->ref, hso_serial_ref_free); + } else { + kref_get(&serial->parent->ref); } } else { D1("Port was already open"); @@ -1339,8 +1339,6 @@ static void hso_serial_close(struct tty_struct *tty, struct file *filp) usb_autopm_put_interface(serial->parent->interface); mutex_unlock(&serial->parent->mutex); - - kref_put(&serial->parent->ref, hso_serial_ref_free); } /* close the requested serial port */ @@ -1391,6 +1389,16 @@ static int hso_serial_write_room(struct tty_struct *tty) return room; } +static void hso_serial_cleanup(struct tty_struct *tty) +{ + struct hso_serial *serial = tty->driver_data; + + if (!serial) + return; + + kref_put(&serial->parent->ref, hso_serial_ref_free); +} + /* setup the term */ static void hso_serial_set_termios(struct tty_struct *tty, struct ktermios *old) { @@ -3214,6 +3222,7 @@ static const struct tty_operations hso_serial_ops = { .close = hso_serial_close, .write = hso_serial_write, .write_room = hso_serial_write_room, + .cleanup = hso_serial_cleanup, .ioctl = hso_serial_ioctl, .set_termios = hso_serial_set_termios, .chars_in_buffer = hso_serial_chars_in_buffer, From 295fc56f465ee8e013b2889e42094da9b2bd7125 Mon Sep 17 00:00:00 2001 From: Olivier Sobrie Date: Fri, 30 Jan 2015 13:21:55 +0100 Subject: [PATCH 03/11] hso: fix memory leak when device disconnects In the disconnect path, tx_buffer should freed like tx_data to avoid a memory leak when the device disconnects. Signed-off-by: Olivier Sobrie Signed-off-by: David S. Miller --- drivers/net/usb/hso.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index 191c1fac08b67..d855cead39788 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -2253,6 +2253,7 @@ static void hso_serial_common_free(struct hso_serial *serial) /* unlink and free TX URB */ usb_free_urb(serial->tx_urb); + kfree(serial->tx_buffer); kfree(serial->tx_data); tty_port_destroy(&serial->port); } From 2e6d01ff759c5f0fc831694c01c477a6c0ebf7b1 Mon Sep 17 00:00:00 2001 From: Olivier Sobrie Date: Fri, 30 Jan 2015 13:21:56 +0100 Subject: [PATCH 04/11] hso: fix memory leak in hso_create_rfkill() When the rfkill interface was created, a buffer containing the name of the rfkill node was allocated. This buffer was never freed when the device disappears. To fix the problem, we put the name given to rfkill_alloc() in the hso_net structure. Signed-off-by: Olivier Sobrie Signed-off-by: David S. Miller --- drivers/net/usb/hso.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index d855cead39788..e1cfe19c72d5a 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -153,6 +153,7 @@ struct hso_net { struct hso_device *parent; struct net_device *net; struct rfkill *rfkill; + char name[8]; struct usb_endpoint_descriptor *in_endp; struct usb_endpoint_descriptor *out_endp; @@ -2467,27 +2468,20 @@ static void hso_create_rfkill(struct hso_device *hso_dev, { struct hso_net *hso_net = dev2net(hso_dev); struct device *dev = &hso_net->net->dev; - char *rfkn; - rfkn = kzalloc(20, GFP_KERNEL); - if (!rfkn) - dev_err(dev, "%s - Out of memory\n", __func__); - - snprintf(rfkn, 20, "hso-%d", + snprintf(hso_net->name, sizeof(hso_net->name), "hso-%d", interface->altsetting->desc.bInterfaceNumber); - hso_net->rfkill = rfkill_alloc(rfkn, + hso_net->rfkill = rfkill_alloc(hso_net->name, &interface_to_usbdev(interface)->dev, RFKILL_TYPE_WWAN, &hso_rfkill_ops, hso_dev); if (!hso_net->rfkill) { dev_err(dev, "%s - Out of memory\n", __func__); - kfree(rfkn); return; } if (rfkill_register(hso_net->rfkill) < 0) { rfkill_destroy(hso_net->rfkill); - kfree(rfkn); hso_net->rfkill = NULL; dev_err(dev, "%s - Failed to register rfkill\n", __func__); return; From 799276791f5e7d9174143823e2fefce649ce8429 Mon Sep 17 00:00:00 2001 From: Olivier Sobrie Date: Fri, 30 Jan 2015 13:21:57 +0100 Subject: [PATCH 05/11] hso: fix small indentation error Simply remove the useless extra tab. Signed-off-by: Olivier Sobrie Signed-off-by: David S. Miller --- drivers/net/usb/hso.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index e1cfe19c72d5a..0c115f81db860 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -2206,8 +2206,8 @@ static int hso_stop_serial_device(struct hso_device *hso_dev) for (i = 0; i < serial->num_rx_urbs; i++) { if (serial->rx_urb[i]) { - usb_kill_urb(serial->rx_urb[i]); - serial->rx_urb_filled[i] = 0; + usb_kill_urb(serial->rx_urb[i]); + serial->rx_urb_filled[i] = 0; } } serial->curr_rx_urb_idx = 0; From f6516b697c8a1772c1b4ca0be30764e70c3143dd Mon Sep 17 00:00:00 2001 From: Olivier Sobrie Date: Fri, 30 Jan 2015 13:21:58 +0100 Subject: [PATCH 06/11] hso: rename hso_dev into serial in hso_free_interface() In other functions of the driver, variables of type "struct hso_serial" are denoted by "serial" and variables of type "struct hso_device" are denoted by "hso_dev". This patch makes the hso_free_interface() consistent with these notations. Signed-off-by: Olivier Sobrie Signed-off-by: David S. Miller --- drivers/net/usb/hso.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index 0c115f81db860..fc310303bed82 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -3114,17 +3114,17 @@ static void hso_serial_ref_free(struct kref *ref) static void hso_free_interface(struct usb_interface *interface) { - struct hso_serial *hso_dev; + struct hso_serial *serial; int i; for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) { if (serial_table[i] && (serial_table[i]->interface == interface)) { - hso_dev = dev2ser(serial_table[i]); - tty_port_tty_hangup(&hso_dev->port, false); - mutex_lock(&hso_dev->parent->mutex); - hso_dev->parent->usb_gone = 1; - mutex_unlock(&hso_dev->parent->mutex); + serial = dev2ser(serial_table[i]); + tty_port_tty_hangup(&serial->port, false); + mutex_lock(&serial->parent->mutex); + serial->parent->usb_gone = 1; + mutex_unlock(&serial->parent->mutex); kref_put(&serial_table[i]->ref, hso_serial_ref_free); } } From 26c1f1f544450d850971173725fe2f256ea2508b Mon Sep 17 00:00:00 2001 From: Olivier Sobrie Date: Fri, 30 Jan 2015 13:21:59 +0100 Subject: [PATCH 07/11] hso: replace reset_device work by usb_queue_reset_device() There is no need for a dedicated reset work in the hso driver since there is already a reset work foreseen in usb_interface that does the same. Signed-off-by: Olivier Sobrie Signed-off-by: David S. Miller --- drivers/net/usb/hso.c | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index fc310303bed82..1e85ae76539e4 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -274,7 +274,6 @@ struct hso_device { u8 usb_gone; struct work_struct async_get_intf; struct work_struct async_put_intf; - struct work_struct reset_device; struct usb_device *usb; struct usb_interface *interface; @@ -340,7 +339,6 @@ static void async_put_intf(struct work_struct *data); static int hso_put_activity(struct hso_device *hso_dev); static int hso_get_activity(struct hso_device *hso_dev); static void tiocmget_intr_callback(struct urb *urb); -static void reset_device(struct work_struct *data); /*****************************************************************************/ /* Helping functions */ /*****************************************************************************/ @@ -696,7 +694,7 @@ static void handle_usb_error(int status, const char *function, case -ETIMEDOUT: explanation = "protocol error"; if (hso_dev) - schedule_work(&hso_dev->reset_device); + usb_queue_reset_device(hso_dev->interface); break; default: explanation = "unknown status"; @@ -2347,7 +2345,6 @@ static struct hso_device *hso_create_device(struct usb_interface *intf, INIT_WORK(&hso_dev->async_get_intf, async_get_intf); INIT_WORK(&hso_dev->async_put_intf, async_put_intf); - INIT_WORK(&hso_dev->reset_device, reset_device); return hso_dev; } @@ -3085,26 +3082,6 @@ static int hso_resume(struct usb_interface *iface) return result; } -static void reset_device(struct work_struct *data) -{ - struct hso_device *hso_dev = - container_of(data, struct hso_device, reset_device); - struct usb_device *usb = hso_dev->usb; - int result; - - if (hso_dev->usb_gone) { - D1("No reset during disconnect\n"); - } else { - result = usb_lock_device_for_reset(usb, hso_dev->interface); - if (result < 0) - D1("unable to lock device for reset: %d\n", result); - else { - usb_reset_device(usb); - usb_unlock_device(usb); - } - } -} - static void hso_serial_ref_free(struct kref *ref) { struct hso_device *hso_dev = container_of(ref, struct hso_device, ref); From 69b377b31be622762ebde5e5e63e8bed2e22bcea Mon Sep 17 00:00:00 2001 From: Olivier Sobrie Date: Fri, 30 Jan 2015 13:22:00 +0100 Subject: [PATCH 08/11] hso: move tty_unregister outside hso_serial_common_free() The function hso_serial_common_free() is called either by the cleanup method of the tty or by the usb disconnect method. In the former case, the usb_disconnect() has been already called and the sysfs group associated to the device has been removed. By calling tty_unregister directly from the usb_disconnect() method, we avoid a warning due to the removal of the sysfs group of the usb device. Example of warning: ------------[ cut here ]------------ WARNING: CPU: 0 PID: 778 at fs/sysfs/group.c:225 sysfs_remove_group+0x50/0x94() sysfs group c0645a88 not found for kobject 'ttyHS5' Modules linked in: CPU: 0 PID: 778 Comm: kworker/0:3 Tainted: G W 3.18.0+ #105 Workqueue: events release_one_tty [] (unwind_backtrace) from [] (show_stack+0x14/0x1c) [] (show_stack) from [] (warn_slowpath_common+0x5c/0x7c) [] (warn_slowpath_common) from [] (warn_slowpath_fmt+0x30/0x40) [] (warn_slowpath_fmt) from [] (sysfs_remove_group+0x50/0x94) [] (sysfs_remove_group) from [] (device_del+0x30/0x190) [] (device_del) from [] (device_unregister+0xc/0x18) [] (device_unregister) from [] (device_destroy+0x30/0x3c) [] (device_destroy) from [] (tty_unregister_device+0x2c/0x5c) [] (tty_unregister_device) from [] (hso_serial_common_free+0x2c/0x88) [] (hso_serial_common_free) from [] (hso_serial_ref_free+0x3c/0xb8) [] (hso_serial_ref_free) from [] (release_one_tty+0x30/0x84) [] (release_one_tty) from [] (process_one_work+0x21c/0x3c8) [] (process_one_work) from [] (worker_thread+0x3d8/0x560) [] (worker_thread) from [] (kthread+0xc0/0xcc) [] (kthread) from [] (ret_from_fork+0x14/0x24) ---[ end trace cb88537fdc8fa208 ]--- Signed-off-by: Olivier Sobrie Signed-off-by: David S. Miller --- drivers/net/usb/hso.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index 1e85ae76539e4..5d885888658e3 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -2234,14 +2234,17 @@ static int hso_stop_serial_device(struct hso_device *hso_dev) return 0; } -static void hso_serial_common_free(struct hso_serial *serial) +static void hso_serial_tty_unregister(struct hso_serial *serial) { - int i; - if (serial->parent->dev) device_remove_file(serial->parent->dev, &dev_attr_hsotype); tty_unregister_device(tty_drv, serial->minor); +} + +static void hso_serial_common_free(struct hso_serial *serial) +{ + int i; for (i = 0; i < serial->num_rx_urbs; i++) { /* unlink and free RX URB */ @@ -2323,6 +2326,7 @@ static int hso_serial_common_create(struct hso_serial *serial, int num_urbs, return 0; exit: + hso_serial_tty_unregister(serial); hso_serial_common_free(serial); return -1; } @@ -2683,6 +2687,7 @@ static struct hso_device *hso_create_bulk_serial_device( return hso_dev; exit2: + hso_serial_tty_unregister(serial); hso_serial_common_free(serial); exit: hso_free_tiomget(serial); @@ -3102,6 +3107,7 @@ static void hso_free_interface(struct usb_interface *interface) mutex_lock(&serial->parent->mutex); serial->parent->usb_gone = 1; mutex_unlock(&serial->parent->mutex); + hso_serial_tty_unregister(serial); kref_put(&serial_table[i]->ref, hso_serial_ref_free); } } From 301d3b7e109e28171d99948467448fd12ebfba06 Mon Sep 17 00:00:00 2001 From: Olivier Sobrie Date: Fri, 30 Jan 2015 13:22:01 +0100 Subject: [PATCH 09/11] hso: update serial_table in usb disconnect method The serial_table is used to map the minor number of the usb serial device to its associated context. The table is updated in the probe method and in hso_serial_ref_free() which is called either from the tty cleanup method or from the usb disconnect method. This patch ensures that the serial_table is updated in the disconnect method and no more from the cleanup method to avoid the following potential race condition. - hso_disconnect() is called for usb interface "x". Because the serial port was open and because the cleanup method of the tty_port hasn't been called yet, hso_serial_ref_free() is not run. - hso_probe() is called and fails for a new hso serial usb interface "y". The function hso_free_interface() is called and iterates over the element of serial_table to find the device associated to the usb interface context. If the usb interface context of usb interface "y" has been created at the same place as for usb interface "x", then the cleanup functions are called for usb interfaces "x" and "y" and hso_serial_ref_free() is called for both interfaces. - release_tty() is called for serial port linked to usb interface "x" and possibly crash because the tty_port structure contained in the hso_device structure has been freed. Signed-off-by: Olivier Sobrie Signed-off-by: David S. Miller --- drivers/net/usb/hso.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index 5d885888658e3..cb33fb4e9a80f 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -2597,7 +2597,6 @@ static void hso_free_serial_device(struct hso_device *hso_dev) if (!serial) return; - set_serial_by_index(serial->minor, NULL); hso_serial_common_free(serial); @@ -3109,6 +3108,7 @@ static void hso_free_interface(struct usb_interface *interface) mutex_unlock(&serial->parent->mutex); hso_serial_tty_unregister(serial); kref_put(&serial_table[i]->ref, hso_serial_ref_free); + set_serial_by_index(i, NULL); } } From cc491970f5cef560b9e5bf037f0c9dd1e4d6a4bd Mon Sep 17 00:00:00 2001 From: Olivier Sobrie Date: Fri, 30 Jan 2015 13:22:02 +0100 Subject: [PATCH 10/11] hso: add missing cancel_work_sync in disconnect() For hso serial devices, two cancel_work_sync were missing in the disconnect method. Signed-off-by: Olivier Sobrie Signed-off-by: David S. Miller --- drivers/net/usb/hso.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index cb33fb4e9a80f..e94a023953573 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -3106,6 +3106,8 @@ static void hso_free_interface(struct usb_interface *interface) mutex_lock(&serial->parent->mutex); serial->parent->usb_gone = 1; mutex_unlock(&serial->parent->mutex); + cancel_work_sync(&serial_table[i]->async_put_intf); + cancel_work_sync(&serial_table[i]->async_get_intf); hso_serial_tty_unregister(serial); kref_put(&serial_table[i]->ref, hso_serial_ref_free); set_serial_by_index(i, NULL); From 38121067b10268385ca00978d1c1a241cd5eadfb Mon Sep 17 00:00:00 2001 From: Olivier Sobrie Date: Fri, 30 Jan 2015 13:22:03 +0100 Subject: [PATCH 11/11] hso: fix rfkill name conflicts By using only the usb interface number for the rfkill name, we might have a name conflicts in case two similar hso devices are connected. In this patch, the name of the hso rfkill interface embed the value of a counter that is incremented each time a new rfkill interface is added. Suggested-by: Dan Williams Signed-off-by: Olivier Sobrie Signed-off-by: David S. Miller --- drivers/net/usb/hso.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index e94a023953573..7833bd1d97914 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -153,7 +153,7 @@ struct hso_net { struct hso_device *parent; struct net_device *net; struct rfkill *rfkill; - char name[8]; + char name[24]; struct usb_endpoint_descriptor *in_endp; struct usb_endpoint_descriptor *out_endp; @@ -2469,9 +2469,10 @@ static void hso_create_rfkill(struct hso_device *hso_dev, { struct hso_net *hso_net = dev2net(hso_dev); struct device *dev = &hso_net->net->dev; + static u32 rfkill_counter; snprintf(hso_net->name, sizeof(hso_net->name), "hso-%d", - interface->altsetting->desc.bInterfaceNumber); + rfkill_counter++); hso_net->rfkill = rfkill_alloc(hso_net->name, &interface_to_usbdev(interface)->dev,