summaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
authorJack Pham <quic_jackp@quicinc.com>2023-01-09 23:12:18 -0800
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2023-01-17 17:06:18 +0100
commitfac4b8633fd682ecc8e9cff61cb3e33374a1c7e5 (patch)
tree852bd83204f169eb29c327238c56410bed201b02 /drivers/usb
parentbd449ad8cee9d4b523abbdfa73e1a2a08333f331 (diff)
downloadlinux-fac4b8633fd682ecc8e9cff61cb3e33374a1c7e5.tar.bz2
usb: ucsi: Ensure connector delayed work items are flushed
During ucsi_unregister() when destroying a connector's workqueue, there may still be pending delayed work items that haven't been scheduled yet. Because queue_delayed_work() uses a separate timer to schedule a work item, the destroy_workqueue() call is not aware of any pending items. Hence when a pending item's timer expires it would then try to queue on a dangling workqueue pointer. Fix this by keeping track of all work items in a list, so that prior to destroying the workqueue any pending items can be flushed. Do this by calling mod_delayed_work() as that will cause pending items to get queued immediately, which then allows the ensuing destroy_workqueue() to implicitly drain all currently queued items to completion and free themselves. Fixes: b9aa02ca39a4 ("usb: typec: ucsi: Add polling mechanism for partner tasks like alt mode checking") Suggested-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Co-developed-by: Linyu Yuan <quic_linyyuan@quicinc.com> Signed-off-by: Linyu Yuan <quic_linyyuan@quicinc.com> Signed-off-by: Jack Pham <quic_jackp@quicinc.com> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Link: https://lore.kernel.org/r/20230110071218.26261-1-quic_jackp@quicinc.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/typec/ucsi/ucsi.c24
-rw-r--r--drivers/usb/typec/ucsi/ucsi.h1
2 files changed, 22 insertions, 3 deletions
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index eabe519013e7..1292241d581a 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -187,6 +187,7 @@ EXPORT_SYMBOL_GPL(ucsi_send_command);
struct ucsi_work {
struct delayed_work work;
+ struct list_head node;
unsigned long delay;
unsigned int count;
struct ucsi_connector *con;
@@ -202,6 +203,7 @@ static void ucsi_poll_worker(struct work_struct *work)
mutex_lock(&con->lock);
if (!con->partner) {
+ list_del(&uwork->node);
mutex_unlock(&con->lock);
kfree(uwork);
return;
@@ -209,10 +211,12 @@ static void ucsi_poll_worker(struct work_struct *work)
ret = uwork->cb(con);
- if (uwork->count-- && (ret == -EBUSY || ret == -ETIMEDOUT))
+ if (uwork->count-- && (ret == -EBUSY || ret == -ETIMEDOUT)) {
queue_delayed_work(con->wq, &uwork->work, uwork->delay);
- else
+ } else {
+ list_del(&uwork->node);
kfree(uwork);
+ }
mutex_unlock(&con->lock);
}
@@ -236,6 +240,7 @@ static int ucsi_partner_task(struct ucsi_connector *con,
uwork->con = con;
uwork->cb = cb;
+ list_add_tail(&uwork->node, &con->partner_tasks);
queue_delayed_work(con->wq, &uwork->work, delay);
return 0;
@@ -1056,6 +1061,7 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
INIT_WORK(&con->work, ucsi_handle_connector_change);
init_completion(&con->complete);
mutex_init(&con->lock);
+ INIT_LIST_HEAD(&con->partner_tasks);
con->num = index + 1;
con->ucsi = ucsi;
@@ -1420,8 +1426,20 @@ void ucsi_unregister(struct ucsi *ucsi)
ucsi_unregister_altmodes(&ucsi->connector[i],
UCSI_RECIPIENT_CON);
ucsi_unregister_port_psy(&ucsi->connector[i]);
- if (ucsi->connector[i].wq)
+
+ if (ucsi->connector[i].wq) {
+ struct ucsi_work *uwork;
+
+ mutex_lock(&ucsi->connector[i].lock);
+ /*
+ * queue delayed items immediately so they can execute
+ * and free themselves before the wq is destroyed
+ */
+ list_for_each_entry(uwork, &ucsi->connector[i].partner_tasks, node)
+ mod_delayed_work(ucsi->connector[i].wq, &uwork->work, 0);
+ mutex_unlock(&ucsi->connector[i].lock);
destroy_workqueue(ucsi->connector[i].wq);
+ }
typec_unregister_port(ucsi->connector[i].port);
}
diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
index c968474ee547..60ce9fb6e745 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -322,6 +322,7 @@ struct ucsi_connector {
struct work_struct work;
struct completion complete;
struct workqueue_struct *wq;
+ struct list_head partner_tasks;
struct typec_port *port;
struct typec_partner *partner;