Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

usb: typec: tcpm: fix tcpm unregister port but leave a pending timer

In current design, when the tcpm port is unregisterd, the kthread_worker
will be destroyed in the last step. Inside the kthread_destroy_worker(),
the worker will flush all the works and wait for them to end. However, if
one of the works calls hrtimer_start(), this hrtimer will be pending until
timeout even though tcpm port is removed. Once the hrtimer timeout, many
strange kernel dumps appear.

Thus, we can first complete kthread_destroy_worker(), then cancel all the
hrtimers. This will guarantee that no hrtimer is pending at the end.

Fixes: 3ed8e1c2ac99 ("usb: typec: tcpm: Migrate workqueue to RT priority for processing events")
cc: <stable@vger.kernel.org>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
Link: https://lore.kernel.org/r/20211209101507.499096-1-xu.yang_2@nxp.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Xu Yang and committed by
Greg Kroah-Hartman
ca4d8344 4c4e162d

+13 -5
+13 -5
drivers/usb/typec/tcpm/tcpm.c
··· 324 324 325 325 bool attached; 326 326 bool connected; 327 + bool registered; 327 328 bool pd_supported; 328 329 enum typec_port_type port_type; 329 330 ··· 6292 6291 { 6293 6292 struct tcpm_port *port = container_of(timer, struct tcpm_port, state_machine_timer); 6294 6293 6295 - kthread_queue_work(port->wq, &port->state_machine); 6294 + if (port->registered) 6295 + kthread_queue_work(port->wq, &port->state_machine); 6296 6296 return HRTIMER_NORESTART; 6297 6297 } 6298 6298 ··· 6301 6299 { 6302 6300 struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_state_machine_timer); 6303 6301 6304 - kthread_queue_work(port->wq, &port->vdm_state_machine); 6302 + if (port->registered) 6303 + kthread_queue_work(port->wq, &port->vdm_state_machine); 6305 6304 return HRTIMER_NORESTART; 6306 6305 } 6307 6306 ··· 6310 6307 { 6311 6308 struct tcpm_port *port = container_of(timer, struct tcpm_port, enable_frs_timer); 6312 6309 6313 - kthread_queue_work(port->wq, &port->enable_frs); 6310 + if (port->registered) 6311 + kthread_queue_work(port->wq, &port->enable_frs); 6314 6312 return HRTIMER_NORESTART; 6315 6313 } 6316 6314 ··· 6319 6315 { 6320 6316 struct tcpm_port *port = container_of(timer, struct tcpm_port, send_discover_timer); 6321 6317 6322 - kthread_queue_work(port->wq, &port->send_discover_work); 6318 + if (port->registered) 6319 + kthread_queue_work(port->wq, &port->send_discover_work); 6323 6320 return HRTIMER_NORESTART; 6324 6321 } 6325 6322 ··· 6408 6403 typec_port_register_altmodes(port->typec_port, 6409 6404 &tcpm_altmode_ops, port, 6410 6405 port->port_altmode, ALTMODE_DISCOVERY_MAX); 6406 + port->registered = true; 6411 6407 6412 6408 mutex_lock(&port->lock); 6413 6409 tcpm_init(port); ··· 6430 6424 { 6431 6425 int i; 6432 6426 6427 + port->registered = false; 6428 + kthread_destroy_worker(port->wq); 6429 + 6433 6430 hrtimer_cancel(&port->send_discover_timer); 6434 6431 hrtimer_cancel(&port->enable_frs_timer); 6435 6432 hrtimer_cancel(&port->vdm_state_machine_timer); ··· 6444 6435 typec_unregister_port(port->typec_port); 6445 6436 usb_role_switch_put(port->role_sw); 6446 6437 tcpm_debugfs_exit(port); 6447 - kthread_destroy_worker(port->wq); 6448 6438 } 6449 6439 EXPORT_SYMBOL_GPL(tcpm_unregister_port); 6450 6440