scsi: ufs: fix deadlock between clock scaling and shutdown

There's a deadlock in which shutdown context is waiting for
a mutex acquired by clock-scaling monitor, which in turn is
waiting for the mutex acquired by shutdown context.
Fix this by not allowing clocks to scale after being
suspended & also de-register this device from the devfreq
framework.

As below:
<call stack : init>
-005|current_thread_info(inline)
-005|mutex_set_owner(inline)
-005|mutex_lock
-006|devfreq_monitor_suspend
-007|devfreq_simple_ondemand_handler
-008|devfreq_suspend_device(?)
-009|__ufshcd_suspend_clkscaling(inline)
-009|ufshcd_suspend_clkscaling
-010|ufshcd_shutdown
-011|ufshcd_pltfrm_shutdown(?)
-012|platform_drv_shutdown
-013|device_unlock(inline)
-013|device_shutdown()
-014|kernel_restart_prepare(?)
-015|kernel_restart
-016|SYSC_reboot(inline)
-016|sys_reboot(?, ?, ?)
-017|el0_svc_naked(asm)
-->|exception
-018|NUX:0x538F6C(asm)

<call stack : kworker/u16:4>
-008|rwsem_down_write_failed
    |    sem = -> (
    |      count = -8589934591,
    |      wait_list = (next = , prev = ),
    |      wait_lock = (raw_lock = (owner = 4, next = 4)),
    |      osq = (tail = (counter = 0)),
    |      owner = -> (
    |      comm = "init",
-009|current_thread_info(inline)
-009|rwsem_set_owner(inline)
-009|down_write
-010|ufshcd_clock_scaling_prepare(inline)
-010|ufshcd_devfreq_scale
-011|ufshcd_devfreq_target(?, ?, ?)
-012|update_devfreq
-013|devfreq_monitor
-014|__read_once_size(inline)
-014|static_key_count(inline)
-014|static_key_false(inline)
-014|trace_workqueue_execute_end(inline)
-014|process_one_work
-015|worker_thread
-016|kthread
-017|ret_from_fork(asm)
---|end of frame

Change-Id: Ic1853ef5143cadd95f0a6df474b35ad45fa918e1
Signed-off-by: Asutosh Das <asutoshd@codeaurora.org>
This commit is contained in:
Asutosh Das 2016-12-13 17:07:13 +05:30
parent c7835c5795
commit 3b284d45b4

View file

@ -8790,6 +8790,35 @@ static inline void ufshcd_add_sysfs_nodes(struct ufs_hba *hba)
ufshcd_add_spm_lvl_sysfs_nodes(hba); ufshcd_add_spm_lvl_sysfs_nodes(hba);
} }
static void ufshcd_shutdown_clkscaling(struct ufs_hba *hba)
{
bool suspend = false;
unsigned long flags;
spin_lock_irqsave(hba->host->host_lock, flags);
if (hba->clk_scaling.is_allowed) {
hba->clk_scaling.is_allowed = false;
suspend = true;
}
spin_unlock_irqrestore(hba->host->host_lock, flags);
/**
* Scaling may be scheduled before, hence make sure it
* doesn't race with shutdown
*/
if (ufshcd_is_clkscaling_supported(hba)) {
device_remove_file(hba->dev, &hba->clk_scaling.enable_attr);
cancel_work_sync(&hba->clk_scaling.suspend_work);
cancel_work_sync(&hba->clk_scaling.resume_work);
if (suspend)
ufshcd_suspend_clkscaling(hba);
}
/* Unregister so that devfreq_monitor can't race with shutdown */
if (hba->devfreq)
devfreq_remove_device(hba->devfreq);
}
/** /**
* ufshcd_shutdown - shutdown routine * ufshcd_shutdown - shutdown routine
* @hba: per adapter instance * @hba: per adapter instance
@ -8807,16 +8836,14 @@ int ufshcd_shutdown(struct ufs_hba *hba)
pm_runtime_get_sync(hba->dev); pm_runtime_get_sync(hba->dev);
ufshcd_hold_all(hba); ufshcd_hold_all(hba);
/**
* (1) Set state to shutting down
* (2) Acquire the lock to stop any more requests
* (3) Suspend clock scaling
* (4) Wait for all issued requests to complete
*/
ufshcd_mark_shutdown_ongoing(hba); ufshcd_mark_shutdown_ongoing(hba);
ufshcd_shutdown_clkscaling(hba);
/**
* (1) Acquire the lock to stop any more requests
* (2) Wait for all issued requests to complete
*/
ufshcd_get_write_lock(hba); ufshcd_get_write_lock(hba);
ufshcd_scsi_block_requests(hba); ufshcd_scsi_block_requests(hba);
ufshcd_suspend_clkscaling(hba);
ret = ufshcd_wait_for_doorbell_clr(hba, U64_MAX); ret = ufshcd_wait_for_doorbell_clr(hba, U64_MAX);
if (ret) if (ret)
dev_err(hba->dev, "%s: waiting for DB clear: failed: %d\n", dev_err(hba->dev, "%s: waiting for DB clear: failed: %d\n",
@ -9210,10 +9237,6 @@ static void ufshcd_clk_scaling_resume_work(struct work_struct *work)
clk_scaling.resume_work); clk_scaling.resume_work);
unsigned long irq_flags; unsigned long irq_flags;
/* Let's not resume scaling if shutdown is ongoing */
if (ufshcd_is_shutdown_ongoing(hba))
return;
spin_lock_irqsave(hba->host->host_lock, irq_flags); spin_lock_irqsave(hba->host->host_lock, irq_flags);
if (!hba->clk_scaling.is_suspended) { if (!hba->clk_scaling.is_suspended) {
spin_unlock_irqrestore(hba->host->host_lock, irq_flags); spin_unlock_irqrestore(hba->host->host_lock, irq_flags);