From 065a08cb14c7010acf863fb9e9d58e78f01cb836 Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Wed, 12 Aug 2020 00:34:33 -0700 Subject: [PATCH] soc: qcom: Create framework for qcom watchdogs Separate core watchdog functionality from the qcom watchdog driver to create a framework for other qcom watchdogs. This will reduce the duplication of code for future watchdog drivers and allow them to take advantage of this framework. Change-Id: I1234af9d92de6841bd09157042a4fbba1745f11b Signed-off-by: Branden Bonaby --- drivers/soc/qcom/Kconfig | 58 +++- drivers/soc/qcom/qcom_wdt_core.c | 480 +++++++++++-------------------- include/soc/qcom/watchdog.h | 118 +++++++- kernel/locking/spinlock_debug.c | 2 +- 4 files changed, 343 insertions(+), 315 deletions(-) diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index c4b1efe0b9d3..d69c1182e2ec 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -787,10 +787,13 @@ config QCOM_WDT_CORE tristate "Qualcomm Technologies, Inc. Watchdog Support" depends on ARCH_QCOM help - This enables the watchdog module. It causes kernel panic if the - watchdog times out. It allows for detection of cpu hangs and - deadlocks. It does not run during the bootup process, so it will - not catch any early lockups. + This enables the watchdog framework for Qualcomm Technologies, Inc. + devices. It causes a kernel panic if the watchdog times out. It allows + for the detection of cpu hangs and deadlocks. It does not run during the + bootup process, so it will not catch any early lockups. Enabling this + only enables the framework, an individual Qualcomm Technologies, Inc. + watchdog module must be loaded along with this for watchdog + functionality. config QCOM_FORCE_WDOG_BITE_ON_PANIC bool "QCOM force watchdog bite on panic" @@ -811,6 +814,53 @@ config QCOM_WDOG_BITE_EARLY_PANIC later in panic, which permits more of the restart sequence to run (e.g. more dmesg to flushed to console). +config QCOM_WATCHDOG_BARK_TIME + depends on QCOM_WDT_CORE + int "Qualcomm Technologies, Inc. Watchdog bark time in ms" + default 11000 + range 11000 11000 + help + The amount of time, in milliseconds, that should elapse after + a watchdog timer reset before a bark interrupt is sent from the + watchdog. + +config QCOM_WATCHDOG_PET_TIME + depends on QCOM_WDT_CORE + int "Qualcomm Technologies, Inc. Watchdog pet time in ms" + default 9360 + range 9360 9360 + help + The amount of time, in milliseconds, that should elapse before + a watchdog pet is initiated to reset the watchdog timer to 0. + +config QCOM_WATCHDOG_IPI_PING + depends on QCOM_WDT_CORE + bool "Qualcomm Technologies, Inc. Watchdog ipi ping" + default y + help + This boolean flag gives the watchdog driver the ability to send a + keep-alive ping to other cpu's if it is set to 1. Otherwise, when + it is set to 0 no keep alive pings will be sent. + +config QCOM_WATCHDOG_WAKEUP_ENABLE + depends on QCOM_WDT_CORE + bool "Qualcomm Technologies, Inc. Watchdog wakeup enable" + default y + help + This boolean flag allows the non secure watchdog counter to freeze + and unfreeze automatically across the system suspend and resume + path. + +config QCOM_WATCHDOG_USERSPACE_PET + depends on QCOM_WDT_CORE + bool "Qualcomm Technologies, Inc. Watchdog user pet enable" + default n + help + This boolean flag allows enabling the userspace-watchdog feature. + This feature requires userspace to pet the watchdog every in an + interval that matches the time set in the pet-time config. + The feature is supported through device sysfs files. + config MSM_SPCOM depends on QCOM_GLINK tristate "Secure Processor Communication over RPMSG" diff --git a/drivers/soc/qcom/qcom_wdt_core.c b/drivers/soc/qcom/qcom_wdt_core.c index fbbb3205dcc2..28255c48b6a8 100644 --- a/drivers/soc/qcom/qcom_wdt_core.c +++ b/drivers/soc/qcom/qcom_wdt_core.c @@ -2,103 +2,29 @@ /* * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ - -#include -#include #include #include -#include #include +#include #include #include #include -#include -#include -#include -#include #include #include #include #include #include -#include -#include #include +#include #include +#include +#include -#define WDT0_RST 0x04 -#define WDT0_EN 0x08 -#define WDT0_STS 0x0C -#define WDT0_BARK_TIME 0x10 -#define WDT0_BITE_TIME 0x14 - -#define EN 0 -#define UNMASKED_INT_EN 1 - -#define MASK_SIZE 32 -#define WDT_HZ 32765 +#define MASK_SIZE 32 static struct msm_watchdog_data *wdog_data; -/* - * user_pet_enable: - * Require userspace to write to a sysfs file every pet_time milliseconds. - * Disabled by default on boot. - */ -struct msm_watchdog_data { - void __iomem *base; - struct device *dev; - unsigned int pet_time; - unsigned int bark_time; - unsigned int bark_irq; - bool do_ipi_ping; - bool wakeup_irq_enable; - unsigned long long last_pet; - cpumask_t alive_mask; - struct mutex disable_lock; - bool in_panic; - struct msm_watchdog_data __percpu **wdog_cpu_dd; - struct notifier_block panic_blk; - struct notifier_block restart_blk; - - bool enabled; - bool user_pet_enabled; - - struct task_struct *watchdog_task; - struct timer_list pet_timer; - wait_queue_head_t pet_complete; - - bool timer_expired; - bool user_pet_complete; - unsigned long long timer_fired; - unsigned long long thread_start; - unsigned long long ping_start[NR_CPUS]; - unsigned long long ping_end[NR_CPUS]; - int cpu_idle_pc_state[NR_CPUS]; -}; - -/* - * On the kernel command line specify - * qcom_watchdog.enable=1 to enable the watchdog - * By default watchdog is turned on - */ -static int enable = 1; -module_param(enable, int, 0000); - -/* - * Watchdog ipi optimization: - * Does not ping cores in low power mode at pet time to save power. - * This feature is enabled by default. - * - * Can be turned off, by enabling CONFIG_QCOM_WDOG_IPI_ENABLE. - */ -#ifdef CONFIG_QCOM_WDOG_IPI_ENABLE -#define IPI_CORES_IN_LPM 1 -#else -#define IPI_CORES_IN_LPM 0 -#endif - -static void dump_cpu_alive_mask(struct msm_watchdog_data *wdog_dd) +static void qcom_wdt_dump_cpu_alive_mask(struct msm_watchdog_data *wdog_dd) { static char alive_mask_buf[MASK_SIZE]; @@ -109,92 +35,97 @@ static void dump_cpu_alive_mask(struct msm_watchdog_data *wdog_dd) } #ifdef CONFIG_PM_SLEEP -static int msm_watchdog_suspend(struct device *dev) +/** + * qcom_wdt_suspend() - Suspends qcom watchdog functionality. + * + * @dev: qcom watchdog device structure + * + * All watchdogs should have the ability to be suspended, this + * will cause the watchdog counter to be frozen. + * + */ +int qcom_wdt_suspend(struct device *dev) { struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); - if (!enable) + + if (!wdog_dd) return 0; - __raw_writel(1, wdog_dd->base + WDT0_RST); + wdog_dd->ops->reset_wdt(wdog_dd); if (wdog_dd->wakeup_irq_enable) { - /* Make sure register write is complete before proceeding */ - mb(); wdog_dd->last_pet = sched_clock(); return 0; } - __raw_writel(0, wdog_dd->base + WDT0_EN); - /* Make sure watchdog is suspended before setting enable */ - mb(); + wdog_dd->ops->disable_wdt(wdog_dd); wdog_dd->enabled = false; wdog_dd->last_pet = sched_clock(); return 0; } -#endif +EXPORT_SYMBOL(qcom_wdt_suspend); -#ifdef CONFIG_PM_SLEEP -static int msm_watchdog_resume(struct device *dev) +/** + * qcom_wdt_resume() - Resumes qcom watchdog after a suspend. + * + * @dev: qcom watchdog device structure + * + * All watchdogs should have the ability to be resumed after a suspend. + * This will cause the watchdog counter to be reset and resumed. + * + */ +int qcom_wdt_resume(struct device *dev) { struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); - if (!enable) + + if (!wdog_dd) return 0; if (wdog_dd->wakeup_irq_enable) { - __raw_writel(1, wdog_dd->base + WDT0_RST); - /* Make sure register write is complete before proceeding */ - mb(); + wdog_dd->ops->reset_wdt(wdog_dd); wdog_dd->last_pet = sched_clock(); return 0; } - __raw_writel(1, wdog_dd->base + WDT0_EN); - __raw_writel(1, wdog_dd->base + WDT0_RST); - /* Make sure watchdog is reset before setting enable */ - mb(); + wdog_dd->ops->enable_wdt(1, wdog_dd); + wdog_dd->ops->reset_wdt(wdog_dd); wdog_dd->enabled = true; wdog_dd->last_pet = sched_clock(); return 0; } +EXPORT_SYMBOL(qcom_wdt_resume); #endif -static int panic_wdog_handler(struct notifier_block *this, +static int qcom_wdt_panic_handler(struct notifier_block *this, unsigned long event, void *ptr) { struct msm_watchdog_data *wdog_dd = container_of(this, struct msm_watchdog_data, panic_blk); + wdog_dd->in_panic = true; if (WDOG_BITE_EARLY_PANIC) { pr_info("Triggering early bite\n"); - msm_trigger_wdog_bite(); + qcom_wdt_trigger_bite(); } if (panic_timeout == 0) { - __raw_writel(0, wdog_dd->base + WDT0_EN); - /* Make sure watchdog is enabled before notifying the caller */ - mb(); + wdog_dd->ops->disable_wdt(wdog_dd); } else { - __raw_writel(WDT_HZ * (panic_timeout + 10), - wdog_dd->base + WDT0_BARK_TIME); - __raw_writel(WDT_HZ * (panic_timeout + 10), - wdog_dd->base + WDT0_BITE_TIME); - __raw_writel(1, wdog_dd->base + WDT0_RST); + wdog_dd->ops->set_bark_time((panic_timeout + 10) * 1000, + wdog_dd); + wdog_dd->ops->set_bite_time((panic_timeout + 10) * 1000, + wdog_dd); + wdog_dd->ops->reset_wdt(wdog_dd); } return NOTIFY_DONE; } -static void wdog_disable(struct msm_watchdog_data *wdog_dd) +static void qcom_wdt_disable(struct msm_watchdog_data *wdog_dd) { - __raw_writel(0, wdog_dd->base + WDT0_EN); - /* Make sure watchdog is disabled before proceeding */ - mb(); + wdog_dd->ops->disable_wdt(wdog_dd); devm_free_irq(wdog_dd->dev, wdog_dd->bark_irq, wdog_dd); - enable = 0; + wdog_dd->enabled = false; /*Ensure all cpus see update to enable*/ smp_mb(); atomic_notifier_chain_unregister(&panic_notifier_list, &wdog_dd->panic_blk); del_timer_sync(&wdog_dd->pet_timer); - /* may be suspended after the first write above */ - __raw_writel(0, wdog_dd->base + WDT0_EN); - /* Make sure watchdog is disabled before setting enable */ - mb(); - wdog_dd->enabled = false; - dev_err(wdog_dd->dev, "MSM Apps Watchdog deactivated\n"); + wdog_dd->ops->disable_wdt(wdog_dd); + dev_err(wdog_dd->dev, "QCOM Apps Watchdog deactivated\n"); } static int restart_wdog_handler(struct notifier_block *this, @@ -208,24 +139,26 @@ static int restart_wdog_handler(struct notifier_block *this, * device will take the usual restart path. */ pr_info("Triggering late bite\n"); - msm_trigger_wdog_bite(); + qcom_wdt_trigger_bite(); } return NOTIFY_DONE; } -static ssize_t wdog_disable_get(struct device *dev, +static ssize_t qcom_wdt_disable_get(struct device *dev, struct device_attribute *attr, char *buf) { struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); int ret; + int disable_val; mutex_lock(&wdog_dd->disable_lock); - ret = scnprintf(buf, PAGE_SIZE, "%d\n", enable == 0 ? 1 : 0); + disable_val = wdog_dd->enabled ? 0 : 1; + ret = scnprintf(buf, PAGE_SIZE, "%d\n", disable_val); mutex_unlock(&wdog_dd->disable_lock); return ret; } -static ssize_t wdog_disable_set(struct device *dev, +static ssize_t qcom_wdt_disable_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -240,21 +173,18 @@ static ssize_t wdog_disable_set(struct device *dev, } if (disable == 1) { mutex_lock(&wdog_dd->disable_lock); - if (enable == 0) { + if (!wdog_dd->enabled) { dev_err(wdog_dd->dev, "MSM Apps Watchdog already disabled\n"); mutex_unlock(&wdog_dd->disable_lock); return count; } - disable = 1; - ret = qcom_scm_sec_wdog_deactivate(); if (ret) { dev_err(wdog_dd->dev, - "Failed to deactivate secure wdog\n"); - mutex_unlock(&wdog_dd->disable_lock); - return -EIO; + "Failed to deactivate secure wdog, ret = %d\n", + ret); } - wdog_disable(wdog_dd); + qcom_wdt_disable(wdog_dd); mutex_unlock(&wdog_dd->disable_lock); } else { pr_err("invalid operation, only disable = 1 supported\n"); @@ -263,7 +193,7 @@ static ssize_t wdog_disable_set(struct device *dev, return count; } -static DEVICE_ATTR(disable, 0600, wdog_disable_get, wdog_disable_set); +static DEVICE_ATTR(disable, 0600, qcom_wdt_disable_get, qcom_wdt_disable_set); /* * Userspace Watchdog Support: @@ -274,13 +204,13 @@ static DEVICE_ATTR(disable, 0600, wdog_disable_get, wdog_disable_set); * Userspace may disable this requirement by writing 0 to this same * file. */ -static void __wdog_user_pet(struct msm_watchdog_data *wdog_dd) +static void __qcom_wdt_user_pet(struct msm_watchdog_data *wdog_dd) { wdog_dd->user_pet_complete = true; wake_up(&wdog_dd->pet_complete); } -static ssize_t wdog_user_pet_enabled_get(struct device *dev, +static ssize_t qcom_wdt_user_pet_enabled_get(struct device *dev, struct device_attribute *attr, char *buf) { struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); @@ -291,7 +221,7 @@ static ssize_t wdog_user_pet_enabled_get(struct device *dev, return ret; } -static ssize_t wdog_user_pet_enabled_set(struct device *dev, +static ssize_t qcom_wdt_user_pet_enabled_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -304,15 +234,15 @@ static ssize_t wdog_user_pet_enabled_set(struct device *dev, return ret; } - __wdog_user_pet(wdog_dd); + __qcom_wdt_user_pet(wdog_dd); return count; } -static DEVICE_ATTR(user_pet_enabled, 0600, wdog_user_pet_enabled_get, - wdog_user_pet_enabled_set); +static DEVICE_ATTR(user_pet_enabled, 0600, qcom_wdt_user_pet_enabled_get, + qcom_wdt_user_pet_enabled_set); -static ssize_t wdog_pet_time_get(struct device *dev, +static ssize_t qcom_wdt_pet_time_get(struct device *dev, struct device_attribute *attr, char *buf) { struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); @@ -322,9 +252,9 @@ static ssize_t wdog_pet_time_get(struct device *dev, return ret; } -static DEVICE_ATTR(pet_time, 0400, wdog_pet_time_get, NULL); +static DEVICE_ATTR(pet_time, 0400, qcom_wdt_pet_time_get, NULL); -static void keep_alive_response(void *info) +static void qcom_wdt_keep_alive_response(void *info) { struct msm_watchdog_data *wdog_dd = info; int cpu = smp_processor_id(); @@ -339,7 +269,7 @@ static void keep_alive_response(void *info) * If this function does not return, it implies one of the * other cpu's is not responsive. */ -static void ping_other_cpus(struct msm_watchdog_data *wdog_dd) +static void qcom_wdt_ping_other_cpus(struct msm_watchdog_data *wdog_dd) { int cpu; @@ -347,15 +277,16 @@ static void ping_other_cpus(struct msm_watchdog_data *wdog_dd) /* Make sure alive mask is cleared and set in order */ smp_mb(); for_each_cpu(cpu, cpu_online_mask) { - if (!wdog_data->cpu_idle_pc_state[cpu]) { + if (!wdog_dd->cpu_idle_pc_state[cpu]) { wdog_dd->ping_start[cpu] = sched_clock(); - smp_call_function_single(cpu, keep_alive_response, + smp_call_function_single(cpu, + qcom_wdt_keep_alive_response, wdog_dd, 1); } } } -static void pet_task_wakeup(struct timer_list *t) +static void qcom_wdt_pet_task_wakeup(struct timer_list *t) { struct msm_watchdog_data *wdog_dd = from_timer(wdog_dd, t, pet_timer); @@ -364,7 +295,7 @@ static void pet_task_wakeup(struct timer_list *t) wake_up(&wdog_dd->pet_complete); } -static __ref int watchdog_kthread(void *arg) +static __ref int qcom_wdt_kthread(void *arg) { struct msm_watchdog_data *wdog_dd = arg; unsigned long delay_time = 0; @@ -383,7 +314,7 @@ static __ref int watchdog_kthread(void *arg) wdog_dd->ping_start[cpu] = wdog_dd->ping_end[cpu] = 0; if (wdog_dd->do_ipi_ping) - ping_other_cpus(wdog_dd); + qcom_wdt_ping_other_cpus(wdog_dd); do { ret = wait_event_interruptible(wdog_dd->pet_complete, @@ -393,86 +324,89 @@ static __ref int watchdog_kthread(void *arg) wdog_dd->timer_expired = false; wdog_dd->user_pet_complete = !wdog_dd->user_pet_enabled; - if (enable) { + if (wdog_dd->enabled) { delay_time = msecs_to_jiffies(wdog_dd->pet_time); - __raw_writel(1, wdog_dd->base + WDT0_RST); - /* Make sure register write is complete */ - mb(); + wdog_dd->ops->reset_wdt(wdog_dd); wdog_dd->last_pet = sched_clock(); } /* Check again before scheduling * Could have been changed on other cpu */ - if(!kthread_should_stop()) + if (!kthread_should_stop()) mod_timer(&wdog_dd->pet_timer, jiffies + delay_time); } return 0; } -static int wdog_cpu_pm_notify(struct notifier_block *self, +static int qcom_wdt_cpu_pm_notify(struct notifier_block *this, unsigned long action, void *v) { + struct msm_watchdog_data *wdog_dd = container_of(this, + struct msm_watchdog_data, wdog_cpu_pm_nb); int cpu; cpu = raw_smp_processor_id(); switch (action) { case CPU_PM_ENTER: - wdog_data->cpu_idle_pc_state[cpu] = 1; + wdog_dd->cpu_idle_pc_state[cpu] = 1; break; case CPU_PM_ENTER_FAILED: case CPU_PM_EXIT: - wdog_data->cpu_idle_pc_state[cpu] = 0; + wdog_dd->cpu_idle_pc_state[cpu] = 0; break; } return NOTIFY_OK; } -static struct notifier_block wdog_cpu_pm_nb = { - .notifier_call = wdog_cpu_pm_notify, -}; - -static int msm_watchdog_remove(struct platform_device *pdev) +/** + * qcom_wdt_remove() - Removes the watchdog and stops it's kthread. + * + * @pdev: watchdog platform_device + * + * Upon the removal of the module all watchdog data along with the kthread + * will be cleaned up and the watchdog device will be removed from memory. + * + */ +int qcom_wdt_remove(struct platform_device *pdev) { struct msm_watchdog_data *wdog_dd = platform_get_drvdata(pdev); if (!IPI_CORES_IN_LPM) - cpu_pm_unregister_notifier(&wdog_cpu_pm_nb); + cpu_pm_unregister_notifier(&wdog_dd->wdog_cpu_pm_nb); mutex_lock(&wdog_dd->disable_lock); - if (enable) - wdog_disable(wdog_dd); + if (wdog_dd->enabled) + qcom_wdt_disable(wdog_dd); mutex_unlock(&wdog_dd->disable_lock); device_remove_file(wdog_dd->dev, &dev_attr_disable); irq_dispose_mapping(wdog_dd->bark_irq); - dev_info(wdog_dd->dev, "MSM Watchdog Exit - Deactivated\n"); + dev_info(wdog_dd->dev, "QCOM Apps Watchdog Exit - Deactivated\n"); del_timer_sync(&wdog_dd->pet_timer); wdog_dd->timer_expired = true; wdog_dd->user_pet_complete = true; kthread_stop(wdog_dd->watchdog_task); return 0; } +EXPORT_SYMBOL(qcom_wdt_remove); -void msm_trigger_wdog_bite(void) +/** + * qcom_wdt_trigger_bite - Executes a watchdog bite. + * + * Return: function will not return, to allow for the bite to occur + */ +void qcom_wdt_trigger_bite(void) { if (!wdog_data) return; - dev_err(wdog_data->dev, "Causing a watchdog bite!\n"); - __raw_writel(1, wdog_data->base + WDT0_BITE_TIME); - /* Mke sure bite time is written before we reset */ - mb(); - __raw_writel(1, wdog_data->base + WDT0_RST); - /* Make sure we wait only after reset */ - mb(); + dev_err(wdog_data->dev, "Causing a QCOM Apps Watchdog bite!\n"); + wdog_data->ops->show_wdt_status(wdog_data); + wdog_data->ops->set_bite_time(1, wdog_data); + wdog_data->ops->reset_wdt(wdog_data); /* Delay to make sure bite occurs */ mdelay(10000); - dev_err(wdog_data->dev, "Wdog - STS: 0x%x, CTL: 0x%x, BARK TIME: 0x%x, BITE TIME: 0x%x\n", - __raw_readl(wdog_data->base + WDT0_STS), - __raw_readl(wdog_data->base + WDT0_EN), - __raw_readl(wdog_data->base + WDT0_BARK_TIME), - __raw_readl(wdog_data->base + WDT0_BITE_TIME)); /* * This function induces the non-secure bite and control * should not return to the calling function. Non-secure @@ -484,68 +418,65 @@ void msm_trigger_wdog_bite(void) udelay(1); } -EXPORT_SYMBOL(msm_trigger_wdog_bite); +EXPORT_SYMBOL(qcom_wdt_trigger_bite); -static irqreturn_t wdog_bark_handler(int irq, void *dev_id) +static irqreturn_t qcom_wdt_bark_handler(int irq, void *dev_id) { struct msm_watchdog_data *wdog_dd = dev_id; unsigned long nanosec_rem; unsigned long long t = sched_clock(); nanosec_rem = do_div(t, 1000000000); - dev_info(wdog_dd->dev, "Watchdog bark! Now = %lu.%06lu\n", + dev_info(wdog_dd->dev, "QCOM Apps Watchdog bark! Now = %lu.%06lu\n", (unsigned long) t, nanosec_rem / 1000); nanosec_rem = do_div(wdog_dd->last_pet, 1000000000); - dev_info(wdog_dd->dev, "Watchdog last pet at %lu.%06lu\n", + dev_info(wdog_dd->dev, "QCOM Apps Watchdog last pet at %lu.%06lu\n", (unsigned long) wdog_dd->last_pet, nanosec_rem / 1000); if (wdog_dd->do_ipi_ping) - dump_cpu_alive_mask(wdog_dd); - msm_trigger_wdog_bite(); + qcom_wdt_dump_cpu_alive_mask(wdog_dd); + + qcom_wdt_trigger_bite(); return IRQ_HANDLED; } -static int init_watchdog_sysfs(struct msm_watchdog_data *wdog_dd) +static int qcom_wdt_init_sysfs(struct msm_watchdog_data *wdog_dd) { int error = 0; error |= device_create_file(wdog_dd->dev, &dev_attr_disable); - - if (of_property_read_bool(wdog_dd->dev->of_node, - "qcom,userspace-watchdog")) { + if (QCOM_WATCHDOG_USERSPACE_PET) { error |= device_create_file(wdog_dd->dev, &dev_attr_pet_time); error |= device_create_file(wdog_dd->dev, &dev_attr_user_pet_enabled); } - if (error) dev_err(wdog_dd->dev, "cannot create sysfs attribute\n"); return error; } -static void init_watchdog_data(struct msm_watchdog_data *wdog_dd) +static int qcom_wdt_init(struct msm_watchdog_data *wdog_dd, + struct platform_device *pdev) { unsigned long delay_time; uint32_t val; - u64 timeout; int ret; ret = devm_request_irq(wdog_dd->dev, wdog_dd->bark_irq, - wdog_bark_handler, IRQF_TRIGGER_RISING, - "apps_wdog_bark", wdog_dd); + qcom_wdt_bark_handler, + IRQF_TRIGGER_RISING, + "apps_wdog_bark", wdog_dd); if (ret) { dev_err(wdog_dd->dev, "failed to request bark irq\n"); - return; + return -EINVAL; } delay_time = msecs_to_jiffies(wdog_dd->pet_time); - timeout = (wdog_dd->bark_time * WDT_HZ)/1000; - __raw_writel(timeout, wdog_dd->base + WDT0_BARK_TIME); - __raw_writel(timeout + 3*WDT_HZ, wdog_dd->base + WDT0_BITE_TIME); - + wdog_dd->ops->set_bark_time(wdog_dd->bark_time, wdog_dd); + wdog_dd->ops->set_bite_time(wdog_dd->bark_time + 3 * 1000, wdog_dd); wdog_dd->panic_blk.priority = WDOG_BITE_EARLY_PANIC ? INT_MAX - 1 : 0; - wdog_dd->panic_blk.notifier_call = panic_wdog_handler; + wdog_dd->panic_blk.notifier_call = qcom_wdt_panic_handler; atomic_notifier_chain_register(&panic_notifier_list, &wdog_dd->panic_blk); wdog_dd->restart_blk.priority = 255; @@ -557,31 +488,33 @@ static void init_watchdog_data(struct msm_watchdog_data *wdog_dd) wdog_dd->user_pet_complete = true; wdog_dd->user_pet_enabled = false; wake_up_process(wdog_dd->watchdog_task); - timer_setup(&wdog_dd->pet_timer, pet_task_wakeup, 0); + timer_setup(&wdog_dd->pet_timer, qcom_wdt_pet_task_wakeup, 0); wdog_dd->pet_timer.expires = jiffies + delay_time; add_timer(&wdog_dd->pet_timer); - val = BIT(EN); if (wdog_dd->wakeup_irq_enable) val |= BIT(UNMASKED_INT_EN); - __raw_writel(val, wdog_dd->base + WDT0_EN); - __raw_writel(1, wdog_dd->base + WDT0_RST); + ret = wdog_dd->ops->enable_wdt(val, wdog_dd); + if (ret) { + dev_err(wdog_dd->dev, "Failed Initializing QCOM Apps Watchdog\n"); + return ret; + } + wdog_dd->ops->reset_wdt(wdog_dd); wdog_dd->last_pet = sched_clock(); wdog_dd->enabled = true; - init_watchdog_sysfs(wdog_dd); + qcom_wdt_init_sysfs(wdog_dd); - if (!IPI_CORES_IN_LPM) - cpu_pm_register_notifier(&wdog_cpu_pm_nb); - dev_info(wdog_dd->dev, "MSM Watchdog Initialized\n"); + if (!IPI_CORES_IN_LPM) { + wdog_dd->wdog_cpu_pm_nb.notifier_call = qcom_wdt_cpu_pm_notify; + cpu_pm_register_notifier(&wdog_dd->wdog_cpu_pm_nb); + } + dev_info(wdog_dd->dev, "QCOM Apps Watchdog Initialized\n"); + + return 0; } -static const struct of_device_id msm_wdog_match_table[] = { - { .compatible = "qcom,msm-watchdog" }, - {} -}; - -static void dump_pdata(struct msm_watchdog_data *pdata) +static void qcom_wdt_dump_pdata(struct msm_watchdog_data *pdata) { dev_dbg(pdata->dev, "wdog bark_time %d", pdata->bark_time); dev_dbg(pdata->dev, "wdog pet_time %d", pdata->pet_time); @@ -590,78 +523,49 @@ static void dump_pdata(struct msm_watchdog_data *pdata) pdata->base); } -static int msm_wdog_dt_to_pdata(struct platform_device *pdev, - struct msm_watchdog_data *pdata) +static void qcom_wdt_dt_to_pdata(struct platform_device *pdev, + struct msm_watchdog_data *pdata) { - struct device_node *node = pdev->dev.of_node; - struct resource *res; - int ret; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wdt-base"); - if (!res) - return -ENODEV; - - pdata->base = devm_ioremap_resource(&pdev->dev, res); - if (!pdata->base) { - dev_err(&pdev->dev, "%s cannot map wdog register space\n", - __func__); - return -ENXIO; - } - pdata->bark_irq = platform_get_irq(pdev, 0); - ret = of_property_read_u32(node, "qcom,bark-time", &pdata->bark_time); - if (ret) { - dev_err(&pdev->dev, "reading bark time failed\n"); - return -ENXIO; - } - ret = of_property_read_u32(node, "qcom,pet-time", &pdata->pet_time); - if (ret) { - dev_err(&pdev->dev, "reading pet time failed\n"); - return -ENXIO; - } - pdata->do_ipi_ping = of_property_read_bool(node, "qcom,ipi-ping"); - if (!pdata->bark_time) { - dev_err(&pdev->dev, "%s watchdog bark time not setup\n", - __func__); - return -ENXIO; - } - if (!pdata->pet_time) { - dev_err(&pdev->dev, "%s watchdog pet time not setup\n", - __func__); - return -ENXIO; - } - pdata->wakeup_irq_enable = of_property_read_bool(node, - "qcom,wakeup-enable"); - dump_pdata(pdata); - return 0; + pdata->bark_time = QCOM_WATCHDOG_BARK_TIME; + pdata->pet_time = QCOM_WATCHDOG_PET_TIME; + pdata->do_ipi_ping = QCOM_WATCHDOG_IPI_PING; + pdata->wakeup_irq_enable = QCOM_WATCHDOG_WAKEUP_ENABLE; + qcom_wdt_dump_pdata(pdata); } -static int msm_watchdog_probe(struct platform_device *pdev) +/** + * qcom_wdt_register() - Creates QCOM Apps watchdog device. + * + * @pdev: watchdog platform_device + * @ops: watchdog operations + * + * All QCOM Apps watchdogs should be created the same way, this acts + * as a framework for this purpose. + * + * 0 on success, negative errno on failure. + */ +int qcom_wdt_register(struct platform_device *pdev, + struct msm_watchdog_data *wdog_dd, + char *wdog_dd_name) { - struct msm_watchdog_data *wdog_dd; struct md_region md_entry; int ret; - if (!enable) - return -ENODEV; - wdog_dd = devm_kzalloc(&pdev->dev, sizeof(*wdog_dd), GFP_KERNEL); - if (!wdog_dd) - return -ENOMEM; - ret = msm_wdog_dt_to_pdata(pdev, wdog_dd); - if (ret) - goto err; - + qcom_wdt_dt_to_pdata(pdev, wdog_dd); wdog_data = wdog_dd; wdog_dd->dev = &pdev->dev; platform_set_drvdata(pdev, wdog_dd); cpumask_clear(&wdog_dd->alive_mask); - wdog_dd->watchdog_task = kthread_create(watchdog_kthread, wdog_dd, - "msm_watchdog"); + wdog_dd->watchdog_task = kthread_create(qcom_wdt_kthread, wdog_dd, + wdog_dd_name); if (IS_ERR(wdog_dd->watchdog_task)) { ret = PTR_ERR(wdog_dd->watchdog_task); goto err; } - init_watchdog_data(wdog_dd); + ret = qcom_wdt_init(wdog_dd, pdev); + if (ret) + goto err; /* Add wdog info to minidump table */ strlcpy(md_entry.name, "KWDOGDATA", sizeof(md_entry.name)); @@ -674,42 +578,4 @@ static int msm_watchdog_probe(struct platform_device *pdev) err: return ret; } - -#ifdef CONFIG_PM_SLEEP -static const struct dev_pm_ops msm_watchdog_dev_pm_ops = { - .suspend_noirq = msm_watchdog_suspend, - .resume_noirq = msm_watchdog_resume, -}; -#else -static const struct dev_pm_ops msm_watchdog_dev_pm_ops = { -}; -#endif - -static struct platform_driver msm_watchdog_driver = { - .probe = msm_watchdog_probe, - .remove = msm_watchdog_remove, - .driver = { - .name = "msm_watchdog", - .pm = &msm_watchdog_dev_pm_ops, - .of_match_table = msm_wdog_match_table, - }, -}; - -static int __init init_watchdog(void) -{ - return platform_driver_register(&msm_watchdog_driver); -} -#if IS_MODULE(CONFIG_QCOM_WDT_CORE) -module_init(init_watchdog); -#else -pure_initcall(init_watchdog); -#endif - -static __exit void exit_watchdog(void) -{ - platform_driver_unregister(&msm_watchdog_driver); -} -module_exit(exit_watchdog); - -MODULE_DESCRIPTION("MSM Watchdog Driver"); -MODULE_LICENSE("GPL v2"); +EXPORT_SYMBOL(qcom_wdt_register); diff --git a/include/soc/qcom/watchdog.h b/include/soc/qcom/watchdog.h index 358548d73615..4040a22f4edf 100644 --- a/include/soc/qcom/watchdog.h +++ b/include/soc/qcom/watchdog.h @@ -18,10 +18,122 @@ #define WDOG_BITE_EARLY_PANIC 0 #endif -#if IS_ENABLED(CONFIG_QCOM_WDT_CORE) -void msm_trigger_wdog_bite(void); +/* + * Watchdog ipi optimization: + * Does not ping cores in low power mode at pet time to save power. + * This feature is enabled by default. + * + * Can be turned off, by enabling CONFIG_QCOM_WDOG_IPI_ENABLE. + */ +#ifdef CONFIG_QCOM_WDOG_IPI_ENABLE +#define IPI_CORES_IN_LPM 1 #else -static inline void msm_trigger_wdog_bite(void) { } +#define IPI_CORES_IN_LPM 0 +#endif + +#if IS_ENABLED(CONFIG_QCOM_WDT_CORE) +#include + +/** + * The enable constant that can be used between the core framework and the + * watchdog driver. + */ +#define EN 0 +#define UNMASKED_INT_EN 1 + +/* Watchdog property values */ +#define QCOM_WATCHDOG_BARK_TIME CONFIG_QCOM_WATCHDOG_BARK_TIME +#define QCOM_WATCHDOG_PET_TIME CONFIG_QCOM_WATCHDOG_PET_TIME + +#ifdef CONFIG_QCOM_WATCHDOG_IPI_PING +#define QCOM_WATCHDOG_IPI_PING 1 +#else +#define QCOM_WATCHDOG_IPI_PING 0 +#endif + +#ifdef CONFIG_QCOM_WATCHDOG_WAKEUP_ENABLE +#define QCOM_WATCHDOG_WAKEUP_ENABLE 1 +#else +#define QCOM_WATCHDOG_WAKEUP_ENABLE 0 +#endif + +#ifdef CONFIG_QCOM_WATCHDOG_USERSPACE_PET +#define QCOM_WATCHDOG_USERSPACE_PET 1 +#else +#define QCOM_WATCHDOG_USERSPACE_PET 0 +#endif + +struct qcom_wdt_ops; +struct msm_watchdog_data; + +/** qcom_wdt_ops - The msm-watchdog-devices operations + * + * @set_bark_time: The routine for setting the watchdog bark time. + * @set_bite_time: The routine for setting the watchdog bite time. + * @reset_wdt: The routine for resetting the watchdog timer. + * @enable_wdt: The routine for enabling the watchdog. + * @disable_wdt: The routine for disabling the watchdog. + * @show_wdt_status: The routine that shows the status of the watchdog. + * + * The qcom_wdt_ops structure contains a list of operations that are + * used to control the watchdog. + */ +struct qcom_wdt_ops { + int (*set_bark_time)(u32 time, struct msm_watchdog_data *wdog_dd); + int (*set_bite_time)(u32 time, struct msm_watchdog_data *wdog_dd); + int (*reset_wdt)(struct msm_watchdog_data *wdog_dd); + int (*enable_wdt)(u32 val, struct msm_watchdog_data *wdog_dd); + int (*disable_wdt)(struct msm_watchdog_data *wdog_dd); + int (*show_wdt_status)(struct msm_watchdog_data *wdog_dd); +}; + +/* + * user_pet_enable: + * Require userspace to write to a sysfs file every pet_time milliseconds. + * Disabled by default on boot. + */ +struct msm_watchdog_data { + void __iomem *base; + struct device *dev; + struct qcom_wdt_ops *ops; + unsigned int pet_time; + unsigned int bark_time; + unsigned int bark_irq; + bool do_ipi_ping; + bool in_panic; + bool wakeup_irq_enable; + unsigned long long last_pet; + cpumask_t alive_mask; + struct mutex disable_lock; + struct notifier_block panic_blk; + struct notifier_block wdog_cpu_pm_nb; + struct notifier_block restart_blk; + + bool enabled; + bool user_pet_enabled; + + struct task_struct *watchdog_task; + struct timer_list pet_timer; + wait_queue_head_t pet_complete; + + bool timer_expired; + bool user_pet_complete; + unsigned long long timer_fired; + unsigned long long thread_start; + unsigned long long ping_start[NR_CPUS]; + unsigned long long ping_end[NR_CPUS]; + int cpu_idle_pc_state[NR_CPUS]; +}; + +extern void qcom_wdt_trigger_bite(void); +int qcom_wdt_suspend(struct device *dev); +int qcom_wdt_resume(struct device *dev); +int qcom_wdt_register(struct platform_device *pdev, + struct msm_watchdog_data *wdog_dd, + char *wdog_dd_name); +int qcom_wdt_remove(struct platform_device *pdev); +#else +static inline void qcom_wdt_trigger_bite(void) { } #endif #endif diff --git a/kernel/locking/spinlock_debug.c b/kernel/locking/spinlock_debug.c index 1cb23cae1b91..4f002cd1c7d8 100644 --- a/kernel/locking/spinlock_debug.c +++ b/kernel/locking/spinlock_debug.c @@ -68,7 +68,7 @@ static void spin_dump(raw_spinlock_t *lock, const char *msg) READ_ONCE(lock->owner_cpu)); #ifdef CONFIG_DEBUG_SPINLOCK_BITE_ON_BUG - msm_trigger_wdog_bite(); + qcom_wdt_trigger_bite(); #elif defined(CONFIG_DEBUG_SPINLOCK_PANIC_ON_BUG) BUG(); #endif