edac: fix edac core deadlock when removing a device
When deleting an edac device, we have to wait for its edac_dev.work to be completed before deleting the whole edac_dev structure. Since we have no idea which work in current edac_poller's workqueue is the work we are conerned about, we wait for all work in the edac_poller's workqueue to be proceseed. This is done via flush_cpu_workqueue() which inserts a wq_barrier into the tail of the workqueue and then sleeping on the completion of this wq_barrier. The edac_poller will wake up sleepers when it is found. EDAC core creates only one kernel worker thread, edac_poller, to run the works of all current edac devices. They share the same callback function of edac_device_workq_function(), which would grab the mutex of device_ctls_mutex first before it checks the device. This is exactly where edac_poller and rmmod would have a great chance to deadlock. In below call trace of rmmod > ... > edac_device_del_device > edac_device_workq_teardown > flush_workqueue > flush_cpu_workqueue, device_ctls_mutex would have already been grabbed by edac_device_del_device(). So, on one hand rmmod would sleep on the completion of a wq_barrier, holding device_ctls_mutex; on the other hand edac_poller would be blocked on the same mutex when it's running any one of works of existing edac evices(Note, this edac_dev.work is likely to be totally irrelevant to the one that is being removed right now)and never would have a chance to run the work of above wq_barrier to wake rmmod up. edac_device_workq_teardown() should not be called within the critical region of device_ctls_mutex. Just like is done in edac_pci_del_device() and edac_mc_del_mc(), where edac_pci_workq_teardown() and edac_mc_workq_teardown() are called after related mutex are released. Moreover, an edac_dev.work should check first if it is being removed. If this is the case, then it should bail out immediately. Since not all of existing edac devices are to be removed, this "shutting flag" should be contained to edac device being removed. The current edac_dev.op_state can be used to serve this purpose. The original deadlock problem and the solution have been witnessed and tested on actual hardware. Without the solution, rmmod an edac driver would result in below deadlock: root@localhost:/root> rmmod mv64x60_edac EDAC DEBUG: mv64x60_dma_err_remove() EDAC DEBUG: edac_device_del_device() EDAC DEBUG: find_edac_device_by_dev() (hang for a moment) INFO: task edac-poller:2030 blocked for more than 120 seconds. "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. edac-poller D 00000000 0 2030 2 Call Trace: [df159dc0] [c0071e3c] free_hot_cold_page+0x17c/0x304 (unreliable) [df159e80] [c000a024] __switch_to+0x6c/0xa0 [df159ea0] [c03587d8] schedule+0x2f4/0x4d8 [df159f00] [c03598a8] __mutex_lock_slowpath+0xa0/0x174 [df159f40] [e1030434] edac_device_workq_function+0x28/0xd8 [edac_core] [df159f60] [c003beb4] run_workqueue+0x114/0x218 [df159f90] [c003c674] worker_thread+0x5c/0xc8 [df159fd0] [c004106c] kthread+0x5c/0xa0 [df159ff0] [c0013538] original_kernel_thread+0x44/0x60 INFO: task rmmod:2062 blocked for more than 120 seconds. "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. rmmod D 0ff2c9fc 0 2062 1839 Call Trace: [df119c00] [c0437a74] 0xc0437a74 (unreliable) [df119cc0] [c000a024] __switch_to+0x6c/0xa0 [df119ce0] [c03587d8] schedule+0x2f4/0x4d8 [df119d40] [c03591dc] schedule_timeout+0xb0/0xf4 Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
20ca9b3f4c
commit
d519c8d9cc
|
@ -394,6 +394,12 @@ static void edac_device_workq_function(struct work_struct *work_req)
|
||||||
|
|
||||||
mutex_lock(&device_ctls_mutex);
|
mutex_lock(&device_ctls_mutex);
|
||||||
|
|
||||||
|
/* If we are being removed, bail out immediately */
|
||||||
|
if (edac_dev->op_state == OP_OFFLINE) {
|
||||||
|
mutex_unlock(&device_ctls_mutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Only poll controllers that are running polled and have a check */
|
/* Only poll controllers that are running polled and have a check */
|
||||||
if ((edac_dev->op_state == OP_RUNNING_POLL) &&
|
if ((edac_dev->op_state == OP_RUNNING_POLL) &&
|
||||||
(edac_dev->edac_check != NULL)) {
|
(edac_dev->edac_check != NULL)) {
|
||||||
|
@ -585,14 +591,14 @@ struct edac_device_ctl_info *edac_device_del_device(struct device *dev)
|
||||||
/* mark this instance as OFFLINE */
|
/* mark this instance as OFFLINE */
|
||||||
edac_dev->op_state = OP_OFFLINE;
|
edac_dev->op_state = OP_OFFLINE;
|
||||||
|
|
||||||
/* clear workq processing on this instance */
|
|
||||||
edac_device_workq_teardown(edac_dev);
|
|
||||||
|
|
||||||
/* deregister from global list */
|
/* deregister from global list */
|
||||||
del_edac_device_from_global_list(edac_dev);
|
del_edac_device_from_global_list(edac_dev);
|
||||||
|
|
||||||
mutex_unlock(&device_ctls_mutex);
|
mutex_unlock(&device_ctls_mutex);
|
||||||
|
|
||||||
|
/* clear workq processing on this instance */
|
||||||
|
edac_device_workq_teardown(edac_dev);
|
||||||
|
|
||||||
/* Tear down the sysfs entries for this instance */
|
/* Tear down the sysfs entries for this instance */
|
||||||
edac_device_remove_sysfs(edac_dev);
|
edac_device_remove_sysfs(edac_dev);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue