diff --git a/arch/arm64/configs/vendor/genericarmv8-64_defconfig b/arch/arm64/configs/vendor/genericarmv8-64_defconfig index 48a27b618b10..20a41e71b934 100644 --- a/arch/arm64/configs/vendor/genericarmv8-64_defconfig +++ b/arch/arm64/configs/vendor/genericarmv8-64_defconfig @@ -278,6 +278,8 @@ CONFIG_DEBUG_INFO=y CONFIG_DEBUG_FS=y CONFIG_MAGIC_SYSRQ=y CONFIG_DEBUG_KERNEL=y +CONFIG_PANIC_ON_OOPS=y +CONFIG_PANIC_TIMEOUT=-1 # CONFIG_SCHED_DEBUG is not set # CONFIG_DEBUG_PREEMPT is not set CONFIG_STACKTRACE=y diff --git a/arch/arm64/configs/vendor/lahaina_GKI.config b/arch/arm64/configs/vendor/lahaina_GKI.config index 608323ddb022..40ae62d56949 100644 --- a/arch/arm64/configs/vendor/lahaina_GKI.config +++ b/arch/arm64/configs/vendor/lahaina_GKI.config @@ -57,7 +57,7 @@ CONFIG_QCOM_LLCC=m CONFIG_QCOM_LAHAINA_LLCC=m CONFIG_QCOM_SHIMA_LLCC=m CONFIG_QCOM_IPCC=m -CONFIG_QCOM_WATCHDOG=m +CONFIG_QCOM_WDT_CORE=m CONFIG_QCOM_FORCE_WDOG_BITE_ON_PANIC=y CONFIG_QCOM_MEMORY_DUMP_V2=m CONFIG_QCOM_SCM=m @@ -232,3 +232,5 @@ CONFIG_SM_DISPCC_SHIMA=m CONFIG_NETFILTER_XT_MATCH_DSCP=y CONFIG_INCREMENTAL_FS=m CONFIG_QCOM_MICRODUMP=m +CONFIG_HH_VIRT_WATCHDOG=m +CONFIG_QCOM_SOC_WATCHDOG=m diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index fda28dc57427..e86ddfa3bcbe 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -783,18 +783,31 @@ config CDSPRM_VTCM_DYNAMIC_DEBUG enabled, the vtcm partition details are sent to the CDSP via rpmsg channel. -config QCOM_WATCHDOG - tristate "Qualcomm Watchdog Support" +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_SOC_WATCHDOG + tristate "Qualcomm Technologies, Inc. Soc Watchdog" + depends on QCOM_WDT_CORE + help + This enables the Qualcomm Technologies, Inc. watchdog module for the + Soc. It provides an interface to perform watchdog actions such as + setting the bark/bite time and also petting the hardware watchdog. To + utilize this the Qualcomm Technologies, Inc. watchdog framework must + also be enabled. config QCOM_FORCE_WDOG_BITE_ON_PANIC bool "QCOM force watchdog bite on panic" - depends on QCOM_WATCHDOG + depends on QCOM_WDT_CORE help This forces a watchdog bite when the device restarts due to a kernel panic. On certain MSM SoCs, @@ -803,7 +816,7 @@ config QCOM_FORCE_WDOG_BITE_ON_PANIC config QCOM_WDOG_BITE_EARLY_PANIC bool "QCOM early panic watchdog bite" - depends on QCOM_WATCHDOG && QCOM_FORCE_WDOG_BITE_ON_PANIC + depends on QCOM_WDT_CORE && QCOM_FORCE_WDOG_BITE_ON_PANIC help This forces a watchdog bite early in panic sequence. On certain MSM SoCs, this provides us additional debugging information at the @@ -811,6 +824,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/Makefile b/drivers/soc/qcom/Makefile index 1d9bf33c9254..e10f95182d22 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -74,7 +74,6 @@ ifdef CONFIG_MSM_RPM_SMD obj-$(CONFIG_QTI_RPM_STATS_LOG) += rpm_master_stat.o endif obj-$(CONFIG_QPNP_PBS) += qpnp-pbs.o -obj-$(CONFIG_QCOM_WATCHDOG) += qcom_watchdog.o obj-$(CONFIG_MSM_SPCOM) += spcom.o obj-$(CONFIG_QCOM_CDSP_RM) += cdsprm.o obj-$(CONFIG_QCOM_FSA4480_I2C) += fsa4480-i2c.o @@ -85,6 +84,8 @@ obj-$(CONFIG_MSM_QBT_HANDLER) += qbt_handler.o obj-$(CONFIG_QTI_CRYPTO_COMMON) += crypto-qti-common.o obj-$(CONFIG_QTI_CRYPTO_TZ) += crypto-qti-tz.o obj-$(CONFIG_QTI_HW_KEY_MANAGER) += hwkm.o crypto-qti-hwkm.o +obj-$(CONFIG_QCOM_WDT_CORE) += qcom_wdt_core.o +obj-$(CONFIG_QCOM_SOC_WATCHDOG) += qcom_soc_wdt.o ifdef CONFIG_DEBUG_FS obj-$(CONFIG_MSM_RPM_SMD) += rpm-smd-debug.o endif diff --git a/drivers/soc/qcom/qcom_soc_wdt.c b/drivers/soc/qcom/qcom_soc_wdt.c new file mode 100644 index 000000000000..302751c1725e --- /dev/null +++ b/drivers/soc/qcom/qcom_soc_wdt.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +#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 WDT_HZ 32765 + +static inline int qcom_soc_set_wdt_bark(u32 time, + struct msm_watchdog_data *wdog_dd) +{ + __raw_writel((time * WDT_HZ)/1000, wdog_dd->base + WDT0_BARK_TIME); + /* Make sure register write is complete before proceeding */ + mb(); + return 0; +} + +static inline int qcom_soc_set_wdt_bite(u32 time, + struct msm_watchdog_data *wdog_dd) +{ + __raw_writel((time * WDT_HZ)/1000, wdog_dd->base + WDT0_BITE_TIME); + /* Make sure register write is complete before proceeding */ + mb(); + return 0; +} + + + +static inline int qcom_soc_reset_wdt(struct msm_watchdog_data *wdog_dd) +{ + __raw_writel(1, wdog_dd->base + WDT0_RST); + /* Make sure register write is complete before proceeding */ + mb(); + return 0; +} + +static inline int qcom_soc_enable_wdt(u32 val, + struct msm_watchdog_data *wdog_dd) +{ + __raw_writel(val, wdog_dd->base + WDT0_EN); + /* Make sure register write is complete before proceeding */ + mb(); + return 0; +} + +static inline int qcom_soc_disable_wdt(struct msm_watchdog_data *wdog_dd) +{ + __raw_writel(0, wdog_dd->base + WDT0_EN); + /* Make sure register write is complete before proceeding */ + mb(); + return 0; +} + +static inline int qcom_soc_show_wdt_status(struct msm_watchdog_data *wdog_dd) +{ + dev_err(wdog_dd->dev, "Wdog - STS: 0x%x, CTL: 0x%x, BARK TIME: 0x%x, BITE TIME: 0x%x\n", + __raw_readl(wdog_dd->base + WDT0_STS), + __raw_readl(wdog_dd->base + WDT0_EN), + __raw_readl(wdog_dd->base + WDT0_BARK_TIME), + __raw_readl(wdog_dd->base + WDT0_BITE_TIME)); + return 0; +} + +static struct qcom_wdt_ops qcom_soc_wdt_ops = { + .set_bark_time = qcom_soc_set_wdt_bark, + .set_bite_time = qcom_soc_set_wdt_bite, + .reset_wdt = qcom_soc_reset_wdt, + .enable_wdt = qcom_soc_enable_wdt, + .disable_wdt = qcom_soc_disable_wdt, + .show_wdt_status = qcom_soc_show_wdt_status +}; + +static int qcom_soc_wdt_probe(struct platform_device *pdev) +{ + struct resource *res; + struct msm_watchdog_data *wdog_dd; + + wdog_dd = devm_kzalloc(&pdev->dev, sizeof(*wdog_dd), GFP_KERNEL); + if (!wdog_dd) + return -ENOMEM; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wdt-base"); + if (!res) + return -ENODEV; + + wdog_dd->base = devm_ioremap_resource(&pdev->dev, res); + if (!wdog_dd->base) { + dev_err(&pdev->dev, "%s cannot map wdog register space\n", + __func__); + return -ENXIO; + } + wdog_dd->ops = &qcom_soc_wdt_ops; + + return qcom_wdt_register(pdev, wdog_dd, "msm-watchdog"); +} + +#ifdef CONFIG_PM_SLEEP +static const struct dev_pm_ops qcom_soc_dev_pm_ops = { + .suspend_noirq = qcom_wdt_suspend, + .resume_noirq = qcom_wdt_resume, +}; +#else +static const struct dev_pm_ops qcom_soc_dev_pm_ops = { +}; +#endif + +static const struct of_device_id qcom_soc_match_table[] = { + { .compatible = "qcom,msm-watchdog" }, + {} +}; + +static struct platform_driver qcom_soc_wdt_driver = { + .probe = qcom_soc_wdt_probe, + .remove = qcom_wdt_remove, + .driver = { + .name = "msm_watchdog", + .pm = &qcom_soc_dev_pm_ops, + .of_match_table = qcom_soc_match_table, + }, +}; + +static int __init init_watchdog(void) +{ + return platform_driver_register(&qcom_soc_wdt_driver); +} + +#if IS_MODULE(CONFIG_QCOM_SOC_WATCHDOG) +module_init(init_watchdog); +#else +pure_initcall(init_watchdog); +#endif + +static __exit void exit_watchdog(void) +{ + platform_driver_unregister(&qcom_soc_wdt_driver); +} +module_exit(exit_watchdog); +MODULE_DESCRIPTION("QCOM Soc Watchdog Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/qcom_watchdog.c b/drivers/soc/qcom/qcom_watchdog.c deleted file mode 100644 index 918274de9ed9..000000000000 --- a/drivers/soc/qcom/qcom_watchdog.c +++ /dev/null @@ -1,786 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * 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 - -#define WDT0_ACCSCSSNBARK_INT 0 -#define TCSR_WDT_CFG 0x30 -#define WDT0_RST 0x04 -#define WDT0_EN 0x08 -#define WDT0_STS 0x0C -#define WDT0_BARK_TIME 0x10 -#define WDT0_BITE_TIME 0x14 - -#define WDOG_ABSENT 0 - -#define EN 0 -#define UNMASKED_INT_EN 1 - -#define MASK_SIZE 32 -#define MAX_CPU_CTX_SIZE 2048 -#define WDT_HZ 32765 - -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 { - unsigned int __iomem phys_base; - size_t size; - void __iomem *base; - struct device *dev; - unsigned int pet_time; - unsigned int bark_time; - unsigned int bark_irq; - unsigned int bite_irq; - bool do_ipi_ping; - bool wakeup_irq_enable; - unsigned long long last_pet; - unsigned int min_slack_ticks; - unsigned long long min_slack_ns; - void *scm_regsave; - cpumask_t alive_mask; - struct mutex disable_lock; - bool irq_ppi; - 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 char alive_mask_buf[MASK_SIZE]; - - scnprintf(alive_mask_buf, MASK_SIZE, "%*pb1", cpumask_pr_args( - &wdog_dd->alive_mask)); - dev_info(wdog_dd->dev, "cpu alive mask from last pet %s\n", - alive_mask_buf); -} - -#ifdef CONFIG_PM_SLEEP -static int msm_watchdog_suspend(struct device *dev) -{ - struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); - if (!enable) - return 0; - __raw_writel(1, wdog_dd->base + WDT0_RST); - 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->enabled = false; - wdog_dd->last_pet = sched_clock(); - return 0; -} -#endif - -#ifdef CONFIG_PM_SLEEP -static int msm_watchdog_resume(struct device *dev) -{ - struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); - if (!enable) - 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->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->enabled = true; - wdog_dd->last_pet = sched_clock(); - return 0; -} -#endif - -static int panic_wdog_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(); - } - if (panic_timeout == 0) { - __raw_writel(0, wdog_dd->base + WDT0_EN); - /* Make sure watchdog is enabled before notifying the caller */ - mb(); - } 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); - } - return NOTIFY_DONE; -} - -static void wdog_disable(struct msm_watchdog_data *wdog_dd) -{ - __raw_writel(0, wdog_dd->base + WDT0_EN); - /* Make sure watchdog is disabled before proceeding */ - mb(); - if (wdog_dd->irq_ppi) { - disable_percpu_irq(wdog_dd->bark_irq); - free_percpu_irq(wdog_dd->bark_irq, wdog_dd->wdog_cpu_dd); - } else - devm_free_irq(wdog_dd->dev, wdog_dd->bark_irq, wdog_dd); - enable = 0; - /*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"); -} - -static int restart_wdog_handler(struct notifier_block *this, - unsigned long event, void *ptr) -{ - struct msm_watchdog_data *wdog_dd = container_of(this, - struct msm_watchdog_data, restart_blk); - if (WDOG_BITE_ON_PANIC && wdog_dd->in_panic) { - /* - * Trigger a watchdog bite here and if this fails, - * device will take the usual restart path. - */ - pr_info("Triggering late bite\n"); - msm_trigger_wdog_bite(); - } - return NOTIFY_DONE; -} - -static ssize_t wdog_disable_get(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); - int ret; - - mutex_lock(&wdog_dd->disable_lock); - ret = scnprintf(buf, PAGE_SIZE, "%d\n", enable == 0 ? 1 : 0); - mutex_unlock(&wdog_dd->disable_lock); - return ret; -} - -static ssize_t wdog_disable_set(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); - u8 disable; - int ret; - - ret = kstrtou8(buf, 10, &disable); - if (ret) { - dev_err(wdog_dd->dev, "invalid user input\n"); - return ret; - } - if (disable == 1) { - mutex_lock(&wdog_dd->disable_lock); - if (enable == 0) { - 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; - } - wdog_disable(wdog_dd); - mutex_unlock(&wdog_dd->disable_lock); - } else { - pr_err("invalid operation, only disable = 1 supported\n"); - return -EINVAL; - } - return count; -} - -static DEVICE_ATTR(disable, 0600, wdog_disable_get, wdog_disable_set); - -/* - * Userspace Watchdog Support: - * Write 1 to the "user_pet_enabled" file to enable hw support for a - * userspace watchdog. - * Userspace is required to pet the watchdog by continuing to write 1 - * to this file in the expected interval. - * Userspace may disable this requirement by writing 0 to this same - * file. - */ -static void __wdog_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, - struct device_attribute *attr, char *buf) -{ - struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); - int ret; - - ret = scnprintf(buf, PAGE_SIZE, "%d\n", - wdog_dd->user_pet_enabled); - return ret; -} - -static ssize_t wdog_user_pet_enabled_set(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); - int ret; - - ret = strtobool(buf, &wdog_dd->user_pet_enabled); - if (ret) { - dev_err(wdog_dd->dev, "invalid user input\n"); - return ret; - } - - __wdog_user_pet(wdog_dd); - - return count; -} - -static DEVICE_ATTR(user_pet_enabled, 0600, wdog_user_pet_enabled_get, - wdog_user_pet_enabled_set); - -static ssize_t wdog_pet_time_get(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); - int ret; - - ret = scnprintf(buf, PAGE_SIZE, "%d\n", wdog_dd->pet_time); - return ret; -} - -static DEVICE_ATTR(pet_time, 0400, wdog_pet_time_get, NULL); - -static void pet_watchdog(struct msm_watchdog_data *wdog_dd) -{ - int slack, i, count, prev_count = 0; - unsigned long long bark_time_ns = wdog_dd->bark_time * 1000000ULL; - unsigned long long slack_ns; - unsigned long long time_ns; - - for (i = 0; i < 2; i++) { - count = - (__raw_readl(wdog_dd->base + WDT0_STS) >> 1) & 0xFFFFF; - if (count != prev_count) { - prev_count = count; - i = 0; - } - } - slack = ((wdog_dd->bark_time * WDT_HZ) / 1000) - count; - if (slack < wdog_dd->min_slack_ticks) - wdog_dd->min_slack_ticks = slack; - __raw_writel(1, wdog_dd->base + WDT0_RST); - time_ns = sched_clock(); - slack_ns = (wdog_dd->last_pet + bark_time_ns) - time_ns; - if (slack_ns < wdog_dd->min_slack_ns) - wdog_dd->min_slack_ns = slack_ns; - wdog_dd->last_pet = time_ns; -} - -static void keep_alive_response(void *info) -{ - struct msm_watchdog_data *wdog_dd = info; - int cpu = smp_processor_id(); - - cpumask_set_cpu(cpu, &wdog_dd->alive_mask); - wdog_dd->ping_end[cpu] = sched_clock(); - /* Make sure alive mask is cleared and set in order */ - smp_mb(); -} - -/* - * 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) -{ - int cpu; - - cpumask_clear(&wdog_dd->alive_mask); - /* 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]) { - wdog_dd->ping_start[cpu] = sched_clock(); - smp_call_function_single(cpu, keep_alive_response, - wdog_dd, 1); - } - } -} - -static void pet_task_wakeup(struct timer_list *t) -{ - struct msm_watchdog_data *wdog_dd = - from_timer(wdog_dd, t, pet_timer); - wdog_dd->timer_expired = true; - wdog_dd->timer_fired = sched_clock(); - wake_up(&wdog_dd->pet_complete); -} - -static __ref int watchdog_kthread(void *arg) -{ - struct msm_watchdog_data *wdog_dd = arg; - unsigned long delay_time = 0; - struct sched_param param = {.sched_priority = MAX_RT_PRIO-1}; - int ret, cpu; - - sched_setscheduler(current, SCHED_FIFO, ¶m); - while (!kthread_should_stop()) { - do { - ret = wait_event_interruptible(wdog_dd->pet_complete, - wdog_dd->timer_expired); - } while (ret != 0); - - wdog_dd->thread_start = sched_clock(); - for_each_cpu(cpu, cpu_present_mask) - wdog_dd->ping_start[cpu] = wdog_dd->ping_end[cpu] = 0; - - if (wdog_dd->do_ipi_ping) - ping_other_cpus(wdog_dd); - - do { - ret = wait_event_interruptible(wdog_dd->pet_complete, - wdog_dd->user_pet_complete); - } while (ret != 0); - - wdog_dd->timer_expired = false; - wdog_dd->user_pet_complete = !wdog_dd->user_pet_enabled; - - if (enable) { - delay_time = msecs_to_jiffies(wdog_dd->pet_time); - pet_watchdog(wdog_dd); - } - /* Check again before scheduling - * Could have been changed on other cpu - */ - 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, - unsigned long action, void *v) -{ - int cpu; - - cpu = raw_smp_processor_id(); - - switch (action) { - case CPU_PM_ENTER: - wdog_data->cpu_idle_pc_state[cpu] = 1; - break; - case CPU_PM_ENTER_FAILED: - case CPU_PM_EXIT: - wdog_data->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) -{ - struct msm_watchdog_data *wdog_dd = platform_get_drvdata(pdev); - - if (!IPI_CORES_IN_LPM) - cpu_pm_unregister_notifier(&wdog_cpu_pm_nb); - - mutex_lock(&wdog_dd->disable_lock); - if (enable) - wdog_disable(wdog_dd); - - mutex_unlock(&wdog_dd->disable_lock); - device_remove_file(wdog_dd->dev, &dev_attr_disable); - if (wdog_dd->irq_ppi) - free_percpu(wdog_dd->wdog_cpu_dd); - irq_dispose_mapping(wdog_dd->bark_irq); - dev_info(wdog_dd->dev, "MSM 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; -} - -void msm_trigger_wdog_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(); - /* 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 - * bite interrupt is affined to all the cores and it may - * not be handled by the same cores which configured - * non-secure bite. So add forever loop here. - */ - while (1) - udelay(1); - -} -EXPORT_SYMBOL(msm_trigger_wdog_bite); - -static irqreturn_t wdog_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", - (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", - (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(); - - return IRQ_HANDLED; -} - -static irqreturn_t wdog_ppi_bark(int irq, void *dev_id) -{ - struct msm_watchdog_data *wdog_dd = dev_id; - return wdog_bark_handler(irq, wdog_dd); -} - -static int init_watchdog_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")) { - 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) -{ - unsigned long delay_time; - uint32_t val; - u64 timeout; - int ret; - - if (wdog_dd->irq_ppi) { - wdog_dd->wdog_cpu_dd = alloc_percpu(struct msm_watchdog_data *); - if (!wdog_dd->wdog_cpu_dd) { - dev_err(wdog_dd->dev, "fail to allocate cpu data\n"); - return; - } - *raw_cpu_ptr(wdog_dd->wdog_cpu_dd) = wdog_dd; - ret = request_percpu_irq(wdog_dd->bark_irq, wdog_ppi_bark, - "apps_wdog_bark", - wdog_dd->wdog_cpu_dd); - if (ret) { - dev_err(wdog_dd->dev, "failed to request bark irq\n"); - free_percpu(wdog_dd->wdog_cpu_dd); - return; - } - } else { - ret = devm_request_irq(wdog_dd->dev, wdog_dd->bark_irq, - wdog_bark_handler, IRQF_TRIGGER_RISING, - "apps_wdog_bark", wdog_dd); - if (ret) { - dev_err(wdog_dd->dev, "failed to request bark irq\n"); - return; - } - } - delay_time = msecs_to_jiffies(wdog_dd->pet_time); - wdog_dd->min_slack_ticks = UINT_MAX; - wdog_dd->min_slack_ns = ULLONG_MAX; - 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->panic_blk.priority = WDOG_BITE_EARLY_PANIC ? INT_MAX - 1 : 0; - wdog_dd->panic_blk.notifier_call = panic_wdog_handler; - atomic_notifier_chain_register(&panic_notifier_list, - &wdog_dd->panic_blk); - wdog_dd->restart_blk.priority = 255; - wdog_dd->restart_blk.notifier_call = restart_wdog_handler; - register_restart_handler(&wdog_dd->restart_blk); - mutex_init(&wdog_dd->disable_lock); - init_waitqueue_head(&wdog_dd->pet_complete); - wdog_dd->timer_expired = false; - 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); - 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); - wdog_dd->last_pet = sched_clock(); - wdog_dd->enabled = true; - - init_watchdog_sysfs(wdog_dd); - - if (wdog_dd->irq_ppi) - enable_percpu_irq(wdog_dd->bark_irq, 0); - if (!IPI_CORES_IN_LPM) - cpu_pm_register_notifier(&wdog_cpu_pm_nb); - dev_info(wdog_dd->dev, "MSM Watchdog Initialized\n"); -} - -static const struct of_device_id msm_wdog_match_table[] = { - { .compatible = "qcom,msm-watchdog" }, - {} -}; - -static void 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); - dev_dbg(pdata->dev, "wdog perform ipi ping %d", pdata->do_ipi_ping); - dev_dbg(pdata->dev, "wdog base address is 0x%lx\n", (unsigned long) - pdata->base); -} - -static int msm_wdog_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); - pdata->bite_irq = platform_get_irq(pdev, 1); - 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"); - pdata->irq_ppi = irq_is_percpu(pdata->bark_irq); - dump_pdata(pdata); - return 0; -} - -static int msm_watchdog_probe(struct platform_device *pdev) -{ - 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; - - 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"); - if (IS_ERR(wdog_dd->watchdog_task)) { - ret = PTR_ERR(wdog_dd->watchdog_task); - goto err; - } - init_watchdog_data(wdog_dd); - - /* Add wdog info to minidump table */ - strlcpy(md_entry.name, "KWDOGDATA", sizeof(md_entry.name)); - md_entry.virt_addr = (uintptr_t)wdog_dd; - md_entry.phys_addr = virt_to_phys(wdog_dd); - md_entry.size = sizeof(*wdog_dd); - if (msm_minidump_add_region(&md_entry)) - dev_err(wdog_dd->dev, "Failed to add Wdt data in Minidump\n"); - return 0; -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_WATCHDOG) -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"); diff --git a/drivers/soc/qcom/qcom_wdt_core.c b/drivers/soc/qcom/qcom_wdt_core.c new file mode 100644 index 000000000000..28255c48b6a8 --- /dev/null +++ b/drivers/soc/qcom/qcom_wdt_core.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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 + +#define MASK_SIZE 32 + +static struct msm_watchdog_data *wdog_data; + +static void qcom_wdt_dump_cpu_alive_mask(struct msm_watchdog_data *wdog_dd) +{ + static char alive_mask_buf[MASK_SIZE]; + + scnprintf(alive_mask_buf, MASK_SIZE, "%*pb1", cpumask_pr_args( + &wdog_dd->alive_mask)); + dev_info(wdog_dd->dev, "cpu alive mask from last pet %s\n", + alive_mask_buf); +} + +#ifdef CONFIG_PM_SLEEP +/** + * 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 (!wdog_dd) + return 0; + wdog_dd->ops->reset_wdt(wdog_dd); + if (wdog_dd->wakeup_irq_enable) { + wdog_dd->last_pet = sched_clock(); + return 0; + } + wdog_dd->ops->disable_wdt(wdog_dd); + wdog_dd->enabled = false; + wdog_dd->last_pet = sched_clock(); + return 0; +} +EXPORT_SYMBOL(qcom_wdt_suspend); + +/** + * 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 (!wdog_dd) + return 0; + if (wdog_dd->wakeup_irq_enable) { + wdog_dd->ops->reset_wdt(wdog_dd); + wdog_dd->last_pet = sched_clock(); + return 0; + } + 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 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"); + qcom_wdt_trigger_bite(); + } + if (panic_timeout == 0) { + wdog_dd->ops->disable_wdt(wdog_dd); + } else { + 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 qcom_wdt_disable(struct msm_watchdog_data *wdog_dd) +{ + wdog_dd->ops->disable_wdt(wdog_dd); + devm_free_irq(wdog_dd->dev, wdog_dd->bark_irq, wdog_dd); + 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); + 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, + unsigned long event, void *ptr) +{ + struct msm_watchdog_data *wdog_dd = container_of(this, + struct msm_watchdog_data, restart_blk); + if (WDOG_BITE_ON_PANIC && wdog_dd->in_panic) { + /* + * Trigger a watchdog bite here and if this fails, + * device will take the usual restart path. + */ + pr_info("Triggering late bite\n"); + qcom_wdt_trigger_bite(); + } + return NOTIFY_DONE; +} + +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); + 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 qcom_wdt_disable_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); + u8 disable; + int ret; + + ret = kstrtou8(buf, 10, &disable); + if (ret) { + dev_err(wdog_dd->dev, "invalid user input\n"); + return ret; + } + if (disable == 1) { + mutex_lock(&wdog_dd->disable_lock); + if (!wdog_dd->enabled) { + dev_err(wdog_dd->dev, "MSM Apps Watchdog already disabled\n"); + mutex_unlock(&wdog_dd->disable_lock); + return count; + } + ret = qcom_scm_sec_wdog_deactivate(); + if (ret) { + dev_err(wdog_dd->dev, + "Failed to deactivate secure wdog, ret = %d\n", + ret); + } + qcom_wdt_disable(wdog_dd); + mutex_unlock(&wdog_dd->disable_lock); + } else { + pr_err("invalid operation, only disable = 1 supported\n"); + return -EINVAL; + } + return count; +} + +static DEVICE_ATTR(disable, 0600, qcom_wdt_disable_get, qcom_wdt_disable_set); + +/* + * Userspace Watchdog Support: + * Write 1 to the "user_pet_enabled" file to enable hw support for a + * userspace watchdog. + * Userspace is required to pet the watchdog by continuing to write 1 + * to this file in the expected interval. + * Userspace may disable this requirement by writing 0 to this same + * file. + */ +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 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); + int ret; + + ret = scnprintf(buf, PAGE_SIZE, "%d\n", + wdog_dd->user_pet_enabled); + return ret; +} + +static ssize_t qcom_wdt_user_pet_enabled_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); + int ret; + + ret = strtobool(buf, &wdog_dd->user_pet_enabled); + if (ret) { + dev_err(wdog_dd->dev, "invalid user input\n"); + return ret; + } + + __qcom_wdt_user_pet(wdog_dd); + + return count; +} + +static DEVICE_ATTR(user_pet_enabled, 0600, qcom_wdt_user_pet_enabled_get, + qcom_wdt_user_pet_enabled_set); + +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); + int ret; + + ret = scnprintf(buf, PAGE_SIZE, "%d\n", wdog_dd->pet_time); + return ret; +} + +static DEVICE_ATTR(pet_time, 0400, qcom_wdt_pet_time_get, NULL); + +static void qcom_wdt_keep_alive_response(void *info) +{ + struct msm_watchdog_data *wdog_dd = info; + int cpu = smp_processor_id(); + + cpumask_set_cpu(cpu, &wdog_dd->alive_mask); + wdog_dd->ping_end[cpu] = sched_clock(); + /* Make sure alive mask is cleared and set in order */ + smp_mb(); +} + +/* + * If this function does not return, it implies one of the + * other cpu's is not responsive. + */ +static void qcom_wdt_ping_other_cpus(struct msm_watchdog_data *wdog_dd) +{ + int cpu; + + cpumask_clear(&wdog_dd->alive_mask); + /* Make sure alive mask is cleared and set in order */ + smp_mb(); + for_each_cpu(cpu, cpu_online_mask) { + if (!wdog_dd->cpu_idle_pc_state[cpu]) { + wdog_dd->ping_start[cpu] = sched_clock(); + smp_call_function_single(cpu, + qcom_wdt_keep_alive_response, + wdog_dd, 1); + } + } +} + +static void qcom_wdt_pet_task_wakeup(struct timer_list *t) +{ + struct msm_watchdog_data *wdog_dd = + from_timer(wdog_dd, t, pet_timer); + wdog_dd->timer_expired = true; + wdog_dd->timer_fired = sched_clock(); + wake_up(&wdog_dd->pet_complete); +} + +static __ref int qcom_wdt_kthread(void *arg) +{ + struct msm_watchdog_data *wdog_dd = arg; + unsigned long delay_time = 0; + struct sched_param param = {.sched_priority = MAX_RT_PRIO-1}; + int ret, cpu; + + sched_setscheduler(current, SCHED_FIFO, ¶m); + while (!kthread_should_stop()) { + do { + ret = wait_event_interruptible(wdog_dd->pet_complete, + wdog_dd->timer_expired); + } while (ret != 0); + + wdog_dd->thread_start = sched_clock(); + for_each_cpu(cpu, cpu_present_mask) + wdog_dd->ping_start[cpu] = wdog_dd->ping_end[cpu] = 0; + + if (wdog_dd->do_ipi_ping) + qcom_wdt_ping_other_cpus(wdog_dd); + + do { + ret = wait_event_interruptible(wdog_dd->pet_complete, + wdog_dd->user_pet_complete); + } while (ret != 0); + + wdog_dd->timer_expired = false; + wdog_dd->user_pet_complete = !wdog_dd->user_pet_enabled; + + if (wdog_dd->enabled) { + delay_time = msecs_to_jiffies(wdog_dd->pet_time); + 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()) + mod_timer(&wdog_dd->pet_timer, jiffies + delay_time); + } + return 0; +} + +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_dd->cpu_idle_pc_state[cpu] = 1; + break; + case CPU_PM_ENTER_FAILED: + case CPU_PM_EXIT: + wdog_dd->cpu_idle_pc_state[cpu] = 0; + break; + } + + return NOTIFY_OK; +} + +/** + * 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_dd->wdog_cpu_pm_nb); + + mutex_lock(&wdog_dd->disable_lock); + 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, "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); + +/** + * 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 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); + /* + * This function induces the non-secure bite and control + * should not return to the calling function. Non-secure + * bite interrupt is affined to all the cores and it may + * not be handled by the same cores which configured + * non-secure bite. So add forever loop here. + */ + while (1) + udelay(1); + +} +EXPORT_SYMBOL(qcom_wdt_trigger_bite); + +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, "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, "QCOM Apps Watchdog last pet at %lu.%06lu\n", + (unsigned long) wdog_dd->last_pet, nanosec_rem / 1000); + if (wdog_dd->do_ipi_ping) + qcom_wdt_dump_cpu_alive_mask(wdog_dd); + + qcom_wdt_trigger_bite(); + + return IRQ_HANDLED; +} + +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 (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 int qcom_wdt_init(struct msm_watchdog_data *wdog_dd, + struct platform_device *pdev) +{ + unsigned long delay_time; + uint32_t val; + 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); + if (ret) { + dev_err(wdog_dd->dev, "failed to request bark irq\n"); + return -EINVAL; + } + delay_time = msecs_to_jiffies(wdog_dd->pet_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 = qcom_wdt_panic_handler; + atomic_notifier_chain_register(&panic_notifier_list, + &wdog_dd->panic_blk); + wdog_dd->restart_blk.priority = 255; + wdog_dd->restart_blk.notifier_call = restart_wdog_handler; + register_restart_handler(&wdog_dd->restart_blk); + mutex_init(&wdog_dd->disable_lock); + init_waitqueue_head(&wdog_dd->pet_complete); + wdog_dd->timer_expired = false; + 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, 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); + 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; + + qcom_wdt_init_sysfs(wdog_dd); + + 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 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); + dev_dbg(pdata->dev, "wdog perform ipi ping %d", pdata->do_ipi_ping); + dev_dbg(pdata->dev, "wdog base address is 0x%lx\n", (unsigned long) + pdata->base); +} + +static void qcom_wdt_dt_to_pdata(struct platform_device *pdev, + struct msm_watchdog_data *pdata) +{ + pdata->bark_irq = platform_get_irq(pdev, 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); +} + +/** + * 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 md_region md_entry; + int ret; + + 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(qcom_wdt_kthread, wdog_dd, + wdog_dd_name); + if (IS_ERR(wdog_dd->watchdog_task)) { + ret = PTR_ERR(wdog_dd->watchdog_task); + goto err; + } + 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)); + md_entry.virt_addr = (uintptr_t)wdog_dd; + md_entry.phys_addr = virt_to_phys(wdog_dd); + md_entry.size = sizeof(*wdog_dd); + if (msm_minidump_add_region(&md_entry)) + dev_err(wdog_dd->dev, "Failed to add Wdt data in Minidump\n"); + return 0; +err: + return ret; +} +EXPORT_SYMBOL(qcom_wdt_register); diff --git a/drivers/virt/haven/Kconfig b/drivers/virt/haven/Kconfig index 86331d4cba33..fdb017f5995f 100644 --- a/drivers/virt/haven/Kconfig +++ b/drivers/virt/haven/Kconfig @@ -90,4 +90,13 @@ config HH_MEM_NOTIFIER machines, and register callbacks that get invoked only when notifications pertaining to their memory resources arrive. +config HH_VIRT_WATCHDOG + tristate "Haven Virtual Watchdog Driver" + depends on QCOM_WDT_CORE + help + This enables the Qualcomm Technologies, Inc. watchdog module for + the Haven hypervisor. It provides an interface to perform watchdog + actions such as setting the bark/bite time and also petting the + watchdog in the hypervisor. + endif diff --git a/drivers/virt/haven/Makefile b/drivers/virt/haven/Makefile index f43f41bda2e8..a57eba1a420b 100644 --- a/drivers/virt/haven/Makefile +++ b/drivers/virt/haven/Makefile @@ -6,3 +6,4 @@ hh_rm_drv-y := hh_rm_core.o hh_rm_iface.o obj-$(CONFIG_HH_DBL) += hh_dbl.o obj-$(CONFIG_HH_IRQ_LEND) += hh_irq_lend.o obj-$(CONFIG_HH_MEM_NOTIFIER) += hh_mem_notifier.o +obj-$(CONFIG_HH_VIRT_WATCHDOG)+= hh_virt_wdt.o \ No newline at end of file diff --git a/drivers/virt/haven/hh_virt_wdt.c b/drivers/virt/haven/hh_virt_wdt.c new file mode 100644 index 000000000000..442f9e5d1f0b --- /dev/null +++ b/drivers/virt/haven/hh_virt_wdt.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +#include +#include +#include +#include +#include +#include + +#define VIRT_WDT_CONTROL \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,\ + ARM_SMCCC_OWNER_VENDOR_HYPERVISOR, 0x0005) +#define VIRT_WDT_STATUS \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,\ + ARM_SMCCC_OWNER_VENDOR_HYPERVISOR, 0x0006) +#define VIRT_WDT_PET \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,\ + ARM_SMCCC_OWNER_VENDOR_HYPERVISOR, 0x0007) +#define VIRT_WDT_SET_TIME \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,\ + ARM_SMCCC_OWNER_VENDOR_HYPERVISOR, 0x0008) +#define VIRT_WDT_NO_CHANGE 0xFFFF + +/** + * hh_wdt_call() - Sends ARM SMCCC 1.1 Calls to the hypervisor + * + * @smc_id: The smc id needed to interact with the watchdog in the hypervisor + * @arg1: A u32 value to be sent to the the hypervisor + * @arg2: A u16 value to be sent to the the hypervisor + * @arg3: A u16 value to be sent to the the hypervisor + * + * The hypervisor takes input via ARM SMCCC Calls. The position of + * these values matter. u16 values are needed to set both the bark + * and bite time. In all other cases only the u32 (arg1) value is required. + * + * return: 0 on success, negative errno on failure. + */ +static struct arm_smccc_res hh_wdt_call(u32 smc_id, u32 arg1, + u16 arg2, u16 arg3) +{ + struct arm_smccc_res res; + + if (smc_id == VIRT_WDT_SET_TIME) + /* virtual watchdog expecting u16 values for bark and bite */ + arm_smccc_1_1_smc(smc_id, arg2, arg3, &res); + else + arm_smccc_1_1_smc(smc_id, arg1, &res); + + return res; +} + +/** + * hh_set_wdt_bark() - Sets the bark time for the virtual watchdog + * + * @time: A u32 value to be converted to milliseconds (u16) + * @wdog_dd: The qcom watchdog data structure + * + * The hypervisor requires both the bark and the bite time in the same + * call. To update one and not the other, the value VIRT_WDT_NO_CHANGE + * is used. + * + * return: 0 on success, negative errno on failure. + */ +static int hh_set_wdt_bark(u32 time, struct msm_watchdog_data *wdog_dd) +{ + struct arm_smccc_res res; + int hret, ret; + u16 bark_time; + + bark_time = (u16) time; + res = hh_wdt_call(VIRT_WDT_SET_TIME, 0, bark_time, VIRT_WDT_NO_CHANGE); + hret = res.a0; + ret = hh_remap_error(hret); + if (hret) { + dev_err(wdog_dd->dev, "failed to set bark time for vDOG, hret = %d ret = %d\n", + hret, ret); + } + + return ret; +} + +/** + * hh_set_wdt_bite() - Sets the bite time for the virtual watchdog + * + * @time: A u32 value to be converted to milliseconds (u16) + * @wdog_dd: The qcom watchdog data structure + * + * The hypervisor requires both the bark and the bite time in the same + * call. To update one and not the other, the value VIRT_WDT_NO_CHANGE + * is used. + * + * return: 0 on success, negative errno on failure. + */ +static int hh_set_wdt_bite(u32 time, struct msm_watchdog_data *wdog_dd) +{ + struct arm_smccc_res res; + int hret, ret; + u16 bite_time; + + bite_time = (u16) time; + res = hh_wdt_call(VIRT_WDT_SET_TIME, 0, VIRT_WDT_NO_CHANGE, bite_time); + hret = res.a0; + ret = hh_remap_error(hret); + if (hret) { + dev_err(wdog_dd->dev, "failed to set bite time for vWDOG, hret = %d ret = %d\n", + hret, ret); + } + + return ret; +} + +/** + * hh_reset_wdt() - Resets the virtual watchdog timer + * + * @wdog_dd: The qcom watchdog data structure + * + * VIRT_WDT_PET is used to reset the virtual watchdog. + * + * return: 0 on success, negative errno on failure. + */ +static int hh_reset_wdt(struct msm_watchdog_data *wdog_dd) +{ + struct arm_smccc_res res; + int hret, ret; + + res = hh_wdt_call(VIRT_WDT_PET, 0, 0, 0); + hret = res.a0; + ret = hh_remap_error(hret); + if (hret) { + dev_err(wdog_dd->dev, "failed to reset vWDOG, hret = %d ret = %d\n", + hret, ret); + } + + return ret; +} + +/** + * hh_enable_wdt() - Enables the virtual watchdog + * + * @wdog_dd: The qcom watchdog data structure + * @state: state value to send to watchdog + * + * VIRT_WDT_CONTROL is used to enable the virtual watchdog. + * Bit 0 is used to enable the watchdog. When this Bit is set to + * 1 the watchdog is enabled. NOTE: Bit 1 must always be set to + * 1 as this bit is reserved in the hypervisor and Bit 1 is + * expected to be 1. If this Bit is not set, the hypervisor will + * return an error. So to enable the watchdog you must use the value 3. + * An error from the hypervisor is expected if you try to enable the + * watchdog when its already enabled. + * + * return: 0 on success, negative errno on failure. + */ +static int hh_enable_wdt(u32 state, struct msm_watchdog_data *wdog_dd) +{ + struct arm_smccc_res res; + int hret, ret; + + if (wdog_dd->enabled) { + dev_err(wdog_dd->dev, "vWDT already enabled\n"); + return 0; + } + res = hh_wdt_call(VIRT_WDT_CONTROL, 3, 0, 0); + hret = res.a0; + ret = hh_remap_error(hret); + if (hret) { + dev_err(wdog_dd->dev, "failed enabling vWDOG, hret = %d ret = %d\n", + hret, ret); + } + + return ret; +} + +/** + * hh_disable_wdt() - Disables the virtual watchdog + * + * @wdog_dd: The qcom watchdog data structure + * + * VIRT_WDT_CONTROL is used to disable the virtual watchdog. + * Bit 0 is used to disable the watchdog. When this Bit is set to + * 0 the watchdog is disabled. NOTE: Bit 1 must always be set to + * 1 as this bit is reserved in the hypervisor and Bit 1 is + * expected to be 1. If this Bit is not set, the hypervisor will + * return an error. So to disable the watchdog you must use the value 2. + * An error from the hypervisor is expected if you try to disable the + * watchdog when its already disabled. + * + * return: 0 on success, negative errno on failure. + */ +static int hh_disable_wdt(struct msm_watchdog_data *wdog_dd) +{ + struct arm_smccc_res res; + int hret, ret; + + if (!wdog_dd->enabled) { + dev_err(wdog_dd->dev, "vWDT already disabled\n"); + return 0; + } + res = hh_wdt_call(VIRT_WDT_CONTROL, 2, 0, 0); + hret = res.a0; + ret = hh_remap_error(hret); + if (hret) { + dev_err(wdog_dd->dev, "failed disabling VDOG, hret = %d ret = %d\n", + hret, ret); + } + + return ret; +} + +/** + * hh_get_wdt_status() - Displays the status of the virtual watchdog + * + * @wdog_dd: The qcom watchdog data structure + * + * VIRT_WDT_STATUS is used to display status of the virtual watchdog. + * + * return: 0 on success, negative errno on failure. + */ +static int hh_show_wdt_status(struct msm_watchdog_data *wdog_dd) +{ + struct arm_smccc_res res; + int hret, ret; + + res = hh_wdt_call(VIRT_WDT_STATUS, 0, 0, 0); + hret = res.a0; + ret = hh_remap_error(hret); + if (hret) { + dev_err(wdog_dd->dev, "failed to get vWDOG status, hret = %d ret = %d\n", + hret, ret); + } else { + dev_err(wdog_dd->dev, + "vWdog-CTL: %d, vWdog-time since last pet: %d, vWdog-expired status: %d\n", + res.a1 & 1, res.a2, (res.a1 >> 31) & 1); + } + + return ret; +} + +static struct qcom_wdt_ops hh_wdt_ops = { + .set_bark_time = hh_set_wdt_bark, + .set_bite_time = hh_set_wdt_bite, + .reset_wdt = hh_reset_wdt, + .enable_wdt = hh_enable_wdt, + .disable_wdt = hh_disable_wdt, + .show_wdt_status = hh_show_wdt_status +}; + +static int hh_wdt_probe(struct platform_device *pdev) +{ + struct msm_watchdog_data *wdog_dd; + + wdog_dd = devm_kzalloc(&pdev->dev, sizeof(*wdog_dd), GFP_KERNEL); + if (!wdog_dd) + return -ENOMEM; + wdog_dd->ops = &hh_wdt_ops; + + return qcom_wdt_register(pdev, wdog_dd, "hh-watchdog"); +} + +#ifdef CONFIG_PM_SLEEP +static const struct dev_pm_ops hh_wdt_dev_pm_ops = { + .suspend_noirq = qcom_wdt_suspend, + .resume_noirq = qcom_wdt_resume, +}; +#else +static const struct dev_pm_ops hh_wdt_dev_pm_ops = { +}; +#endif + +static const struct of_device_id hh_wdt_match_table[] = { + { .compatible = "qcom,hh-watchdog" }, + {} +}; + +static struct platform_driver hh_wdt_driver = { + .probe = hh_wdt_probe, + .remove = qcom_wdt_remove, + .driver = { + .name = "hh-watchdog", + .pm = &hh_wdt_dev_pm_ops, + .of_match_table = hh_wdt_match_table, + }, +}; + +static int __init init_watchdog(void) +{ + return platform_driver_register(&hh_wdt_driver); +} + +#if IS_MODULE(CONFIG_HH_VIRT_WATCHDOG) +module_init(init_watchdog); +#else +pure_initcall(init_watchdog); +#endif + +static __exit void exit_watchdog(void) +{ + platform_driver_unregister(&hh_wdt_driver); +} +module_exit(exit_watchdog); +MODULE_DESCRIPTION("QCOM Haven Watchdog Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/soc/qcom/watchdog.h b/include/soc/qcom/watchdog.h index a69ef8b9c7db..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_WATCHDOG) -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