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 <bbonaby@codeaurora.org>
This commit is contained in:
Branden Bonaby 2020-08-12 00:34:33 -07:00
commit 065a08cb14
4 changed files with 342 additions and 314 deletions

View file

@ -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"

View file

@ -2,103 +2,29 @@
/*
* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/irqdomain.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/percpu.h>
#include <linux/of.h>
#include <linux/cpu_pm.h>
#include <linux/platform_device.h>
#include <linux/wait.h>
#include <linux/reboot.h>
#include <linux/qcom_scm.h>
#include <soc/qcom/minidump.h>
#include <soc/qcom/watchdog.h>
#include <linux/dma-mapping.h>
#include <linux/sched/clock.h>
#include <linux/cpumask.h>
#include <linux/cpu_pm.h>
#include <uapi/linux/sched/types.h>
#include <linux/sched/clock.h>
#include <linux/irq.h>
#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);

View file

@ -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 <linux/platform_device.h>
/**
* 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

View file

@ -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