From fe35637b0a9f8837a63ec4ffc52831de2af98fe4 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 4 Jul 2016 09:08:51 -0300 Subject: [PATCH] [media] dvb_frontend: eliminate blocking wait in dvb_unregister_frontend() The wait_event() call in dvb_unregister_frontend() waits synchronously for other tasks to free a file descriptor, but it does that while holding several mutexes. That alone is a bad idea, but if one user process happens to keep a (defunct) file descriptor open indefinitely, the kernel will correctly detect a hung task: INFO: task kworker/0:1:314 blocked for more than 30 seconds. Not tainted 4.7.0-rc1-hosting+ #50 "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. kworker/0:1 D ffff88003daf7a50 0 314 2 0x00000000 Workqueue: usb_hub_wq hub_event ffff88003daf7a50 0000000000000296 ffff88003daf7a30 ffff88003fc13f98 ffff88003dadce00 ffff88003daf8000 ffff88003e3fc010 ffff88003d48d4f8 ffff88003e3b5030 ffff88003e3f8898 ffff88003daf7a68 ffffffff810cf860 Call Trace: [] schedule+0x30/0x80 [] dvb_unregister_frontend+0x93/0xc0 [] ? __wake_up_common+0x80/0x80 [] dvb_usb_adapter_frontend_exit+0x37/0x70 [] dvb_usb_exit+0x34/0xb0 [] dvb_usb_device_exit+0x3a/0x50 [] pctv452e_usb_disconnect+0x52/0x60 [] usb_unbind_interface+0x67/0x1e0 [] ? __blocking_notifier_call_chain+0x53/0x70 [] __device_release_driver+0x77/0x110 [] device_release_driver+0x23/0x30 [] bus_remove_device+0x10d/0x150 [] device_del+0x13b/0x260 [] ? usb_remove_ep_devs+0x1a/0x30 [] usb_disable_device+0x9e/0x1e0 [] usb_disconnect+0x89/0x260 [] hub_event+0x30d/0xfc0 [] process_one_work+0x1c5/0x4a0 [] ? process_one_work+0x15c/0x4a0 [] worker_thread+0x49/0x480 [] ? process_one_work+0x4a0/0x4a0 [] ? process_one_work+0x4a0/0x4a0 [] kthread+0xee/0x110 [] ret_from_fork+0x1f/0x40 [] ? __kthread_unpark+0x70/0x70 5 locks held by kworker/0:1/314: #0: ("usb_hub_wq"){......}, at: [] process_one_work+0x15c/0x4a0 #1: ((&hub->events)){......}, at: [] process_one_work+0x15c/0x4a0 #2: (&dev->mutex){......}, at: [] hub_event+0x4b/0xfc0 #3: (&dev->mutex){......}, at: [] usb_disconnect+0x52/0x260 #4: (&dev->mutex){......}, at: [] device_release_driver+0x1b/0x30 This patch removes the blocking wait, and postpones the kfree() call until all file handles have been closed by using struct kref. Signed-off-by: Max Kellermann Signed-off-by: Mauro Carvalho Chehab --- drivers/media/dvb-core/dvb_frontend.c | 33 ++++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core/dvb_frontend.c index c0142614c408..be99c8dbc5f8 100644 --- a/drivers/media/dvb-core/dvb_frontend.c +++ b/drivers/media/dvb-core/dvb_frontend.c @@ -99,6 +99,7 @@ MODULE_PARM_DESC(dvb_mfe_wait_time, "Wait up to seconds on open( static DEFINE_MUTEX(frontend_mutex); struct dvb_frontend_private { + struct kref refcount; /* thread/frontend values */ struct dvb_device *dvbdev; @@ -137,6 +138,23 @@ struct dvb_frontend_private { #endif }; +static void dvb_frontend_private_free(struct kref *ref) +{ + struct dvb_frontend_private *fepriv = + container_of(ref, struct dvb_frontend_private, refcount); + kfree(fepriv); +} + +static void dvb_frontend_private_put(struct dvb_frontend_private *fepriv) +{ + kref_put(&fepriv->refcount, dvb_frontend_private_free); +} + +static void dvb_frontend_private_get(struct dvb_frontend_private *fepriv) +{ + kref_get(&fepriv->refcount); +} + static void dvb_frontend_wakeup(struct dvb_frontend *fe); static int dtv_get_frontend(struct dvb_frontend *fe, struct dtv_frontend_properties *c, @@ -2543,6 +2561,8 @@ static int dvb_frontend_open(struct inode *inode, struct file *file) fepriv->events.eventr = fepriv->events.eventw = 0; } + dvb_frontend_private_get(fepriv); + if (adapter->mfe_shared) mutex_unlock (&adapter->mfe_lock); return ret; @@ -2591,6 +2611,8 @@ static int dvb_frontend_release(struct inode *inode, struct file *file) fe->ops.ts_bus_ctrl(fe, 0); } + dvb_frontend_private_put(fepriv); + return ret; } @@ -2679,6 +2701,8 @@ int dvb_register_frontend(struct dvb_adapter* dvb, } fepriv = fe->frontend_priv; + kref_init(&fepriv->refcount); + sema_init(&fepriv->sem, 1); init_waitqueue_head (&fepriv->wait_queue); init_waitqueue_head (&fepriv->events.wait_queue); @@ -2713,18 +2737,11 @@ int dvb_unregister_frontend(struct dvb_frontend* fe) mutex_lock(&frontend_mutex); dvb_frontend_stop (fe); - mutex_unlock(&frontend_mutex); - - if (fepriv->dvbdev->users < -1) - wait_event(fepriv->dvbdev->wait_queue, - fepriv->dvbdev->users==-1); - - mutex_lock(&frontend_mutex); dvb_unregister_device (fepriv->dvbdev); /* fe is invalid now */ - kfree(fepriv); mutex_unlock(&frontend_mutex); + dvb_frontend_private_put(fepriv); return 0; } EXPORT_SYMBOL(dvb_unregister_frontend); -- 2.34.1