From ed58ffc218c6da02122ac9e081c8169316774587 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Mon, 15 May 2017 18:17:57 -0700 Subject: [PATCH] scsi: ufs: fix unclocked register access When we try to update UFS driver's sysfs attribute hibern8_on_idle_delay_ms (echo > /sys/bus/platform/devices/*.ufshc/hibern8_on_idle_delay_ms) and if auto-hibern8 is enabled, we try to update the new timeout value into auto-hibern8 timer register without making sure the controller clocks are enabled, this may cause the unclocked register access. So we are now making sure that clocks are enabled and we are also making sure that there are no transfers active on controller when we modify the auto hibern8 timeout. Change-Id: I603b3f405d3a69c594edd45037a2ca4535ebd1ca Signed-off-by: Subhash Jadavani --- drivers/scsi/ufs/ufshcd.c | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index a90c51a113d2..5040492964ae 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -365,6 +365,8 @@ static int ufshcd_disable_clocks(struct ufs_hba *hba, bool is_gating_context); static int ufshcd_disable_clocks_skip_ref_clk(struct ufs_hba *hba, bool is_gating_context); +static void ufshcd_hold_all(struct ufs_hba *hba); +static void ufshcd_release_all(struct ufs_hba *hba); static int ufshcd_set_vccq_rail_unused(struct ufs_hba *hba, bool unused); static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba); static inline void ufshcd_save_tstamp_of_last_dme_cmd(struct ufs_hba *hba); @@ -2038,6 +2040,22 @@ out: return; } +static void __ufshcd_set_auto_hibern8_timer(struct ufs_hba *hba, + unsigned long delay_ms) +{ + pm_runtime_get_sync(hba->dev); + ufshcd_hold_all(hba); + ufshcd_scsi_block_requests(hba); + down_write(&hba->lock); + /* wait for all the outstanding requests to finish */ + ufshcd_wait_for_doorbell_clr(hba, U64_MAX); + ufshcd_set_auto_hibern8_timer(hba, delay_ms); + up_write(&hba->lock); + ufshcd_scsi_unblock_requests(hba); + ufshcd_release_all(hba); + pm_runtime_put_sync(hba->dev); +} + static void ufshcd_hibern8_exit_work(struct work_struct *work) { int ret; @@ -2089,19 +2107,32 @@ static ssize_t ufshcd_hibern8_on_idle_delay_store(struct device *dev, { struct ufs_hba *hba = dev_get_drvdata(dev); unsigned long flags, value; + bool change = true; if (kstrtoul(buf, 0, &value)) return -EINVAL; spin_lock_irqsave(hba->host->host_lock, flags); + if (hba->hibern8_on_idle.delay_ms == value) + change = false; + + if (value >= hba->clk_gating.delay_ms_pwr_save || + value >= hba->clk_gating.delay_ms_perf) { + dev_err(hba->dev, "hibern8_on_idle_delay (%lu) can not be >= to clkgate_delay_ms_pwr_save (%lu) and clkgate_delay_ms_perf (%lu)\n", + value, hba->clk_gating.delay_ms_pwr_save, + hba->clk_gating.delay_ms_perf); + spin_unlock_irqrestore(hba->host->host_lock, flags); + return -EINVAL; + } + hba->hibern8_on_idle.delay_ms = value; spin_unlock_irqrestore(hba->host->host_lock, flags); /* Update auto hibern8 timer value if supported */ - if (ufshcd_is_auto_hibern8_supported(hba) && + if (change && ufshcd_is_auto_hibern8_supported(hba) && hba->hibern8_on_idle.is_enabled) - ufshcd_set_auto_hibern8_timer(hba, - hba->hibern8_on_idle.delay_ms); + __ufshcd_set_auto_hibern8_timer(hba, + hba->hibern8_on_idle.delay_ms); return count; } @@ -2131,7 +2162,7 @@ static ssize_t ufshcd_hibern8_on_idle_enable_store(struct device *dev, /* Update auto hibern8 timer value if supported */ if (ufshcd_is_auto_hibern8_supported(hba)) { - ufshcd_set_auto_hibern8_timer(hba, + __ufshcd_set_auto_hibern8_timer(hba, value ? hba->hibern8_on_idle.delay_ms : value); goto update; } -- 2.11.0