soc: qcom: Handle device hangs during suspend/resume

As cpus can serve ipis and timers during suspend and
resume; msm watchdog would periodically pet hardware,
even when some of suspend/resume callbacks are stuck,
due to software deadlocks. This can cause device hangs,
particularly when noirq callbacks are stuck. To recover
from this, register late suspend (and early resume)
callbacks in msm watchdog; these callbacks would
disable/enable watchdog pet.

Change-Id: Id12251f2bb6565b04a59802a53b6c3cadd017327
Signed-off-by: Neeraj Upadhyay <neeraju@codeaurora.org>
Signed-off-by: Prateek Sood <prsood@codeaurora.org>
This commit is contained in:
Neeraj Upadhyay 2020-09-03 09:27:18 +05:30 committed by Gerrit - the friendly Code Review server
parent 62ea1d38b6
commit 49148c6626
4 changed files with 71 additions and 5 deletions
drivers
include/soc/qcom

View File

@ -105,6 +105,13 @@ static int qcom_soc_wdt_probe(struct platform_device *pdev)
return qcom_wdt_register(pdev, wdog_dd, "msm-watchdog");
}
static const struct dev_pm_ops qcom_soc_dev_pm_ops = {
#ifdef CONFIG_PM_SLEEP
.suspend_late = qcom_wdt_pet_suspend,
.resume_early = qcom_wdt_pet_resume,
#endif
};
static const struct of_device_id qcom_soc_match_table[] = {
{ .compatible = "qcom,msm-watchdog" },
{}
@ -115,6 +122,7 @@ static struct platform_driver qcom_soc_wdt_driver = {
.remove = qcom_wdt_remove,
.driver = {
.name = "msm_watchdog",
.pm = &qcom_soc_dev_pm_ops,
.of_match_table = qcom_soc_match_table,
},
};

View File

@ -89,6 +89,45 @@ static void qcom_wdt_resume(void)
wdog_data->last_pet = sched_clock();
return;
}
int qcom_wdt_pet_suspend(struct device *dev)
{
struct msm_watchdog_data *wdog_dd =
(struct msm_watchdog_data *)dev_get_drvdata(dev);
if (!wdog_dd)
return 0;
wdog_dd->ops->reset_wdt(wdog_dd);
wdog_dd->last_pet = sched_clock();
spin_lock(&wdog_dd->freeze_lock);
wdog_dd->freeze_in_progress = true;
spin_unlock(&wdog_dd->freeze_lock);
del_timer_sync(&wdog_dd->pet_timer);
return 0;
}
EXPORT_SYMBOL(qcom_wdt_pet_suspend);
int qcom_wdt_pet_resume(struct device *dev)
{
struct msm_watchdog_data *wdog_dd =
(struct msm_watchdog_data *)dev_get_drvdata(dev);
unsigned long delay_time = 0;
if (!wdog_dd)
return 0;
delay_time = msecs_to_jiffies(wdog_dd->pet_time);
wdog_dd->ops->reset_wdt(wdog_dd);
wdog_dd->last_pet = sched_clock();
spin_lock(&wdog_dd->freeze_lock);
wdog_dd->pet_timer.expires = jiffies + delay_time;
add_timer(&wdog_dd->pet_timer);
wdog_dd->freeze_in_progress = false;
spin_unlock(&wdog_dd->freeze_lock);
return 0;
}
EXPORT_SYMBOL(qcom_wdt_pet_resume);
#endif
static struct syscore_ops qcom_wdt_syscore_ops = {
@ -339,8 +378,13 @@ static __ref int qcom_wdt_kthread(void *arg)
/* Check again before scheduling
* Could have been changed on other cpu
*/
if (!kthread_should_stop())
mod_timer(&wdog_dd->pet_timer, jiffies + delay_time);
if (!kthread_should_stop()) {
spin_lock(&wdog_dd->freeze_lock);
if (!wdog_dd->freeze_in_progress)
mod_timer(&wdog_dd->pet_timer,
jiffies + delay_time);
spin_unlock(&wdog_dd->freeze_lock);
}
}
return 0;
}
@ -473,9 +517,9 @@ static int qcom_wdt_init(struct msm_watchdog_data *wdog_dd,
int ret;
ret = devm_request_irq(wdog_dd->dev, wdog_dd->bark_irq,
qcom_wdt_bark_handler,
IRQF_TRIGGER_RISING,
"apps_wdog_bark", wdog_dd);
qcom_wdt_bark_handler,
IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND,
"apps_wdog_bark", wdog_dd);
if (ret) {
dev_err(wdog_dd->dev, "failed to request bark irq\n");
return -EINVAL;
@ -495,6 +539,8 @@ static int qcom_wdt_init(struct msm_watchdog_data *wdog_dd,
wdog_dd->timer_expired = false;
wdog_dd->user_pet_complete = true;
wdog_dd->user_pet_enabled = false;
spin_lock_init(&wdog_dd->freeze_lock);
wdog_dd->freeze_in_progress = false;
wake_up_process(wdog_dd->watchdog_task);
timer_setup(&wdog_dd->pet_timer, qcom_wdt_pet_task_wakeup, 0);
wdog_dd->pet_timer.expires = jiffies + delay_time;

View File

@ -261,6 +261,13 @@ static int hh_wdt_probe(struct platform_device *pdev)
return qcom_wdt_register(pdev, wdog_dd, "hh-watchdog");
}
static const struct dev_pm_ops hh_wdt_dev_pm_ops = {
#ifdef CONFIG_PM_SLEEP
.suspend_late = qcom_wdt_pet_suspend,
.resume_early = qcom_wdt_pet_resume,
#endif
};
static const struct of_device_id hh_wdt_match_table[] = {
{ .compatible = "qcom,hh-watchdog" },
{}
@ -271,6 +278,7 @@ static struct platform_driver hh_wdt_driver = {
.remove = qcom_wdt_remove,
.driver = {
.name = "hh-watchdog",
.pm = &hh_wdt_dev_pm_ops,
.of_match_table = hh_wdt_match_table,
},
};

View File

@ -123,12 +123,16 @@ struct msm_watchdog_data {
unsigned long long ping_start[NR_CPUS];
unsigned long long ping_end[NR_CPUS];
int cpu_idle_pc_state[NR_CPUS];
bool freeze_in_progress;
spinlock_t freeze_lock;
};
extern void qcom_wdt_trigger_bite(void);
int qcom_wdt_register(struct platform_device *pdev,
struct msm_watchdog_data *wdog_dd,
char *wdog_dd_name);
int qcom_wdt_pet_suspend(struct device *dev);
int qcom_wdt_pet_resume(struct device *dev);
int qcom_wdt_remove(struct platform_device *pdev);
#else
static inline void qcom_wdt_trigger_bite(void) { }