soc: qcom: Add snapshot of watchdog driver

This is a snapshot of the soc watchdog, gunyah
watchdog and watchdog core driver as of msm-5.15
commit <908a517b050d> ("Merge "sched/walt: remove
duplicate definitions of rt_task_arrival_time"").

Change-Id: I78134e1b42e0abd74c3b4630c135715ee959afa1
Signed-off-by: Huang Yiwei <quic_hyiwei@quicinc.com>
This commit is contained in:
Huang Yiwei 2022-04-06 17:50:10 +08:00
parent 686aa245d9
commit c06fec2f03
11 changed files with 1822 additions and 0 deletions

View File

@ -245,6 +245,103 @@ config QCOM_LOGBUF_VENDOR_HOOKS
debugging issues which are manifestation
of failure during initial bootup.
config QCOM_WDT_CORE
tristate "Qualcomm Technologies, Inc. Watchdog Support"
depends on ARCH_QCOM
help
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_IRQ_STAT
bool "QCOM IRQ stats"
depends on QCOM_WDT_CORE
help
This give irq stats for top hitter at
watchdog pet, watchdog bark and kernel panics.
This provides additional debug information
for irq counts on cpu and ipi counts.
config QCOM_FORCE_WDOG_BITE_ON_PANIC
bool "QCOM force watchdog bite on panic"
depends on QCOM_WDT_CORE
help
This forces a watchdog bite when the device restarts
due to a kernel panic. On certain MSM SoCs,
this provides additional debugging
information.
config QCOM_WDOG_BITE_EARLY_PANIC
bool "QCOM early panic watchdog bite"
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
context of the crash. If this option is disabled, then bite occurs
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 QCOM_WCNSS_CTRL
tristate "Qualcomm WCNSS control driver"
depends on ARCH_QCOM || COMPILE_TEST

View File

@ -21,6 +21,8 @@ obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
obj-$(CONFIG_QCOM_SMSM) += smsm.o
obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o
obj-$(CONFIG_QCOM_SPM) += spm.o
obj-$(CONFIG_QCOM_WDT_CORE) += qcom_wdt_core.o
obj-$(CONFIG_QCOM_SOC_WATCHDOG) += qcom_soc_wdt.o
obj-$(CONFIG_QCOM_STATS) += qcom_stats.o
obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
obj-$(CONFIG_QCOM_MSM_IPCC) += qcom_ipcc.o

View File

@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*
*/
#include <soc/qcom/watchdog.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/io.h>
#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");
}
static const struct dev_pm_ops qcom_soc_dev_pm_ops = {
#ifdef CONFIG_PM_SLEEP
.suspend_late = qcom_wdt_pet_suspend,
.resume_early = qcom_wdt_pet_resume,
#endif
};
static const struct of_device_id qcom_soc_match_table[] = {
{ .compatible = "qcom,msm-watchdog" },
{}
};
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");

View File

@ -0,0 +1,992 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/irqdomain.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/irq.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/panic_notifier.h>
#include <linux/qcom_scm.h>
#include <soc/qcom/minidump.h>
#include <soc/qcom/watchdog.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>
#include <linux/sort.h>
#include <linux/kernel_stat.h>
#include <linux/kallsyms.h>
#include <linux/kdebug.h>
#include <asm/hardirq.h>
#define MASK_SIZE 32
#define COMPARE_RET -1
typedef int (*compare_t) (const void *lhs, const void *rhs);
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_QCOM_IRQ_STAT
static int cmp_irq_info_fn(const void *a, const void *b)
{
struct qcom_irq_info *lhs = (struct qcom_irq_info *)a;
struct qcom_irq_info *rhs = (struct qcom_irq_info *)b;
if (lhs->total_count < rhs->total_count)
return 1;
if (lhs->total_count > rhs->total_count)
return COMPARE_RET;
return 0;
}
static void swap_irq_info_fn(void *a, void *b, int size)
{
struct qcom_irq_info temp;
struct qcom_irq_info *lhs = (struct qcom_irq_info *)a;
struct qcom_irq_info *rhs = (struct qcom_irq_info *)b;
temp = *lhs;
*lhs = *rhs;
*rhs = temp;
}
static struct qcom_irq_info *search(struct qcom_irq_info *key,
struct qcom_irq_info *base,
size_t num, compare_t cmp)
{
struct qcom_irq_info *pivot = NULL;
int result;
while (num > 0) {
pivot = base + (num >> 1);
result = cmp(key, pivot);
if (result == 0)
goto out;
if (result > 0) {
base = pivot + 1;
num--;
}
if (num)
num >>= 1;
}
out:
if (pivot)
pr_debug("*pivot:%u key:%u\n",
pivot->total_count, key->total_count);
return pivot;
}
static void print_irq_stat(struct msm_watchdog_data *wdog_dd)
{
int index;
int cpu, ipi_nr;
struct qcom_irq_info *info;
pr_info("(virq:irq_count)- ");
for (index = 0; index < NR_TOP_HITTERS; index++) {
info = &wdog_dd->irq_counts[index];
pr_cont("%u:%u ", info->irq, info->total_count);
}
pr_cont("\n");
pr_info("(cpu:irq_count)- ");
for_each_possible_cpu(cpu)
pr_cont("%u:%u ", cpu, wdog_dd->tot_irq_count[cpu]);
pr_cont("\n");
pr_info("(ipi:irq_count)- ");
ipi_nr = nr_ipi_get();
for (index = 0; index < ipi_nr; index++) {
info = &wdog_dd->ipi_counts[index];
pr_cont("%u:%u ", info->irq, info->total_count);
}
pr_cont("\n");
}
static void compute_irq_stat(struct work_struct *work)
{
unsigned int count;
int index = 0, cpu, irq, ipi_nr;
struct irq_desc *desc, **desc_ipi_arr;
struct qcom_irq_info *pos;
struct qcom_irq_info *start;
struct qcom_irq_info key = {0};
unsigned int running;
struct msm_watchdog_data *wdog_dd = container_of(work,
struct msm_watchdog_data,
irq_counts_work);
size_t arr_size = ARRAY_SIZE(wdog_dd->irq_counts);
/* avoid parallel execution from bark handler and queued
* irq_counts_work.
*/
running = atomic_xchg(&wdog_dd->irq_counts_running, 1);
if (running)
return;
/* per irq counts */
rcu_read_lock();
for_each_irq_nr(irq) {
desc = irq_to_desc(irq);
if (!desc)
continue;
count = kstat_irqs_usr(irq);
if (!count)
continue;
if (index < arr_size) {
wdog_dd->irq_counts[index].irq = irq;
wdog_dd->irq_counts[index].total_count = count;
for_each_possible_cpu(cpu)
wdog_dd->irq_counts[index].irq_counter[cpu] =
*per_cpu_ptr(desc->kstat_irqs, cpu);
index++;
if (index == arr_size)
sort(wdog_dd->irq_counts, arr_size,
sizeof(*pos), cmp_irq_info_fn,
swap_irq_info_fn);
continue;
}
key.total_count = count;
start = wdog_dd->irq_counts + (arr_size - 1);
pos = search(&key, wdog_dd->irq_counts,
arr_size, cmp_irq_info_fn);
if (pos && (pos->total_count >= key.total_count)) {
if (pos < start)
pos++;
else
pos = NULL;
}
pr_debug("count :%u irq:%u\n", count, irq);
if (pos && pos < start) {
start--;
for (; start >= pos ; start--)
*(start + 1) = *start;
}
if (pos) {
pos->irq = irq;
pos->total_count = count;
for_each_possible_cpu(cpu)
pos->irq_counter[cpu] =
*per_cpu_ptr(desc->kstat_irqs, cpu);
}
}
rcu_read_unlock();
/* per cpu total irq counts */
for_each_possible_cpu(cpu)
wdog_dd->tot_irq_count[cpu] = kstat_cpu_irqs_sum(cpu);
/* per IPI counts */
ipi_nr = nr_ipi_get();
desc_ipi_arr = ipi_desc_get();
for (index = 0; index < ipi_nr; index++) {
wdog_dd->ipi_counts[index].total_count = 0;
wdog_dd->ipi_counts[index].irq = index;
irq = irq_desc_get_irq(desc_ipi_arr[index]);
for_each_possible_cpu(cpu) {
wdog_dd->ipi_counts[index].irq_counter[cpu] =
kstat_irqs_cpu(irq, cpu);
wdog_dd->ipi_counts[index].total_count +=
wdog_dd->ipi_counts[index].irq_counter[cpu];
}
}
print_irq_stat(wdog_dd);
atomic_xchg(&wdog_dd->irq_counts_running, 0);
}
static void queue_irq_counts_work(struct work_struct *irq_counts_work)
{
queue_work(system_unbound_wq, irq_counts_work);
}
#else
static void queue_irq_counts_work(struct work_struct *irq_counts_work) { }
static void compute_irq_stat(struct work_struct *work) { }
#endif
#ifdef CONFIG_PM_SLEEP
/**
* qcom_wdt_pet_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 reset and HW counter will
* freeze when deepest low power mode is entered.
*
*/
int qcom_wdt_pet_suspend(struct device *dev)
{
struct msm_watchdog_data *wdog_data =
(struct msm_watchdog_data *)dev_get_drvdata(dev);
if (!wdog_data)
return 0;
if (wdog_data->user_pet_enabled)
del_timer_sync(&wdog_data->user_pet_timer);
spin_lock(&wdog_data->freeze_lock);
wdog_data->freeze_in_progress = true;
spin_unlock(&wdog_data->freeze_lock);
wdog_data->ops->reset_wdt(wdog_data);
del_timer_sync(&wdog_data->pet_timer);
if (wdog_data->wakeup_irq_enable) {
wdog_data->last_pet = sched_clock();
return 0;
}
wdog_data->ops->disable_wdt(wdog_data);
wdog_data->enabled = false;
wdog_data->last_pet = sched_clock();
return 0;
}
EXPORT_SYMBOL(qcom_wdt_pet_suspend);
/**
* qcom_wdt_pet_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_pet_resume(struct device *dev)
{
struct msm_watchdog_data *wdog_data =
(struct msm_watchdog_data *)dev_get_drvdata(dev);
unsigned long delay_time = 0;
if (!wdog_data)
return 0;
if (wdog_data->user_pet_enabled) {
delay_time = msecs_to_jiffies(wdog_data->bark_time + 3 * 1000);
wdog_data->user_pet_timer.expires = jiffies + delay_time;
add_timer(&wdog_data->user_pet_timer);
}
delay_time = msecs_to_jiffies(wdog_data->pet_time);
spin_lock(&wdog_data->freeze_lock);
wdog_data->pet_timer.expires = jiffies + delay_time;
add_timer(&wdog_data->pet_timer);
wdog_data->freeze_in_progress = false;
spin_unlock(&wdog_data->freeze_lock);
if (wdog_data->wakeup_irq_enable) {
wdog_data->ops->reset_wdt(wdog_data);
wdog_data->last_pet = sched_clock();
return 0;
}
wdog_data->ops->enable_wdt(1, wdog_data);
wdog_data->ops->reset_wdt(wdog_data);
wdog_data->enabled = true;
wdog_data->last_pet = sched_clock();
return 0;
}
EXPORT_SYMBOL(qcom_wdt_pet_resume);
#endif
static void qcom_wdt_reset_on_oops(struct msm_watchdog_data *wdog_dd,
int timeout)
{
wdog_dd->ops->reset_wdt(wdog_dd);
wdog_dd->ops->set_bark_time((timeout + 10) * 1000,
wdog_dd);
wdog_dd->ops->set_bite_time((timeout + 10) * 1000,
wdog_dd);
}
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 {
qcom_wdt_reset_on_oops(wdog_dd, panic_timeout);
}
return NOTIFY_DONE;
}
#ifdef CONFIG_QCOM_MINIDUMP_PANIC_DUMP
static int qcom_wdt_die_handler(struct notifier_block *this,
unsigned long val, void *data)
{
struct msm_watchdog_data *wdog_dd = container_of(this,
struct msm_watchdog_data, die_blk);
qcom_wdt_reset_on_oops(wdog_dd, 5);
return NOTIFY_DONE;
}
static void qcom_wdt_register_die_notifier(struct msm_watchdog_data *wdog_dd)
{
wdog_dd->die_blk.notifier_call = qcom_wdt_die_handler;
wdog_dd->die_blk.priority = INT_MAX - 1;
register_die_notifier(&wdog_dd->die_blk);
}
static void qcom_wdt_unregister_die_notifier(struct msm_watchdog_data *wdog_dd)
{
unregister_die_notifier(&wdog_dd->die_blk);
}
#else
static void qcom_wdt_register_die_notifier(struct msm_watchdog_data *wdog_dd) { }
static void qcom_wdt_unregister_die_notifier(struct msm_watchdog_data *wdog_dd) { }
#endif
static void qcom_wdt_disable(struct msm_watchdog_data *wdog_dd)
{
wdog_dd->ops->disable_wdt(wdog_dd);
if (wdog_dd->irq_ppi) {
disable_percpu_irq(wdog_dd->bark_irq);
free_percpu_irq(wdog_dd->bark_irq,
(void __percpu *)wdog_dd->wdog_cpu_dd);
} else {
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);
qcom_wdt_unregister_die_notifier(wdog_dd);
unregister_restart_handler(&wdog_dd->restart_blk);
del_timer_sync(&wdog_dd->pet_timer);
if (wdog_dd->user_pet_enabled)
del_timer_sync(&wdog_dd->user_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;
unsigned long delay_time = 0;
bool already_enabled = wdog_dd->user_pet_enabled;
ret = strtobool(buf, &wdog_dd->user_pet_enabled);
if (ret) {
dev_err(wdog_dd->dev, "invalid user input\n");
return ret;
}
delay_time = msecs_to_jiffies(wdog_dd->bark_time + 3 * 1000);
if (wdog_dd->user_pet_enabled)
mod_timer(&wdog_dd->user_pet_timer, jiffies + delay_time);
else if (already_enabled)
del_timer_sync(&wdog_dd->user_pet_timer);
__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 ssize_t wakeup_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%d\n", wdog_dd->wakeup_irq_enable);
}
static ssize_t wakeup_enable_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev);
u8 enable;
int ret;
ret = kstrtou8(buf, 10, &enable);
if (ret || enable > 1) {
dev_err(wdog_dd->dev, "invalid user input\n");
return ret ? : -EINVAL;
}
mutex_lock(&wdog_dd->disable_lock);
/* echo 1 > wakeup_enable means wakeup irq is enabled */
wdog_dd->wakeup_irq_enable = enable;
if (wdog_dd->enabled) {
u8 val = BIT(EN);
if (wdog_dd->wakeup_irq_enable)
val |= BIT(UNMASKED_INT_EN);
wdog_dd->ops->enable_wdt(val, wdog_dd);
}
mutex_unlock(&wdog_dd->disable_lock);
return count;
}
static DEVICE_ATTR_ADMIN_RW(wakeup_enable);
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 void qcom_wdt_user_pet_bite(struct timer_list *t)
{
struct msm_watchdog_data *wdog_dd =
from_timer(wdog_dd, t, user_pet_timer);
if (!wdog_dd->user_pet_complete) {
dev_info(wdog_dd->dev, "QCOM Apps Watchdog user pet timeout!\n");
qcom_wdt_trigger_bite();
}
}
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, &param);
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()) {
spin_lock(&wdog_dd->freeze_lock);
if (!wdog_dd->freeze_in_progress)
mod_timer(&wdog_dd->pet_timer,
jiffies + delay_time);
spin_unlock(&wdog_dd->freeze_lock);
}
queue_irq_counts_work(&wdog_dd->irq_counts_work);
}
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);
if (wdog_dd->irq_ppi)
free_percpu((void __percpu *)wdog_dd->wdog_cpu_dd);
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);
if (wdog_dd->user_pet_enabled)
del_timer_sync(&wdog_dd->user_pet_timer);
wdog_dd->timer_expired = true;
wdog_dd->user_pet_complete = true;
kthread_stop(wdog_dd->watchdog_task);
flush_work(&wdog_dd->irq_counts_work);
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;
compute_irq_stat(&wdog_data->irq_counts_work);
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);
if (wdog_dd->freeze_in_progress)
dev_info(wdog_dd->dev, "Suspend in progress\n");
md_dump_process();
qcom_wdt_trigger_bite();
return IRQ_HANDLED;
}
static irqreturn_t qcom_wdt_ppi_bark(int irq, void *dev_id_percpu)
{
void *dev_id = raw_cpu_ptr((void __percpu *)dev_id_percpu);
struct msm_watchdog_data *wdog_dd = *((struct msm_watchdog_data **)dev_id);
return qcom_wdt_bark_handler(irq, 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);
error |= device_create_file(wdog_dd->dev, &dev_attr_wakeup_enable);
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;
void *wdog_cpu_dd_v;
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, "failed to allocate cpu data\n");
return -ENOMEM;
}
wdog_cpu_dd_v = raw_cpu_ptr((void __percpu *)wdog_dd->wdog_cpu_dd);
*((struct msm_watchdog_data **)wdog_cpu_dd_v) = wdog_dd;
ret = request_percpu_irq(wdog_dd->bark_irq, qcom_wdt_ppi_bark,
"apps_wdog_bark",
(void __percpu *)wdog_dd->wdog_cpu_dd);
if (ret) {
dev_err(wdog_dd->dev, "failed to request bark irq\n");
free_percpu((void __percpu *)wdog_dd->wdog_cpu_dd);
return ret;
}
} else {
ret = devm_request_irq(wdog_dd->dev, wdog_dd->bark_irq,
qcom_wdt_bark_handler,
IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND,
"apps_wdog_bark", wdog_dd);
if (ret) {
dev_err(wdog_dd->dev, "failed to request bark irq: %d\n", ret);
return -EINVAL;
}
}
INIT_WORK(&wdog_dd->irq_counts_work, compute_irq_stat);
atomic_set(&wdog_dd->irq_counts_running, 0);
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 = INT_MAX - 1;
wdog_dd->panic_blk.notifier_call = qcom_wdt_panic_handler;
atomic_notifier_chain_register(&panic_notifier_list,
&wdog_dd->panic_blk);
qcom_wdt_register_die_notifier(wdog_dd);
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;
spin_lock_init(&wdog_dd->freeze_lock);
wdog_dd->freeze_in_progress = false;
wake_up_process(wdog_dd->watchdog_task);
timer_setup(&wdog_dd->pet_timer, qcom_wdt_pet_task_wakeup, 0);
wdog_dd->pet_timer.expires = jiffies + delay_time;
add_timer(&wdog_dd->pet_timer);
timer_setup(&wdog_dd->user_pet_timer, qcom_wdt_user_pet_bite, 0);
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) {
atomic_notifier_chain_unregister(&panic_notifier_list,
&wdog_dd->panic_blk);
qcom_wdt_unregister_die_notifier(wdog_dd);
unregister_restart_handler(&wdog_dd->restart_blk);
if (wdog_dd->irq_ppi) {
free_percpu_irq(wdog_dd->bark_irq,
(void __percpu *)wdog_dd->wdog_cpu_dd);
free_percpu((void __percpu *)wdog_dd->wdog_cpu_dd);
}
del_timer_sync(&wdog_dd->pet_timer);
flush_work(&wdog_dd->irq_counts_work);
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 (wdog_dd->irq_ppi)
enable_percpu_irq(wdog_dd->bark_irq, 0);
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->irq_ppi = irq_is_percpu(pdata->bark_irq);
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;
if (!pdev || !wdog_dd || !wdog_dd_name) {
pr_err("wdt_register input incorrect\n");
return -EINVAL;
}
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) {
kthread_stop(wdog_dd->watchdog_task);
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) < 0)
dev_err(wdog_dd->dev, "Failed to add Wdt data in Minidump\n");
return 0;
err:
return ret;
}
EXPORT_SYMBOL(qcom_wdt_register);
MODULE_DESCRIPTION("QCOM Watchdog Driver Core");
MODULE_LICENSE("GPL v2");

View File

@ -36,4 +36,6 @@ source "drivers/virt/vboxguest/Kconfig"
source "drivers/virt/nitro_enclaves/Kconfig"
source "drivers/virt/acrn/Kconfig"
source "drivers/virt/gunyah/Kconfig"
endif

View File

@ -8,3 +8,4 @@ obj-y += vboxguest/
obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
obj-$(CONFIG_ACRN_HSM) += acrn/
obj-$(CONFIG_GUNYAH_DRIVERS) += gunyah/

View File

@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig GUNYAH_DRIVERS
bool "Gunyah Virtualization drivers"
depends on ARM64
help
The Gunyah drivers are the helper interfaces that runs on the
virtual machines that provides support such as memory/device
sharing, IRQ sharing, IPC/signalling mechanisms, and so on.
Say Y here to enable the drivers needed to work on Gunyah
virtualization environment.
If you say N, all options in this submenu will be skipped and disabled.
if GUNYAH_DRIVERS
config GH_VIRT_WATCHDOG
tristate "Gunyah Virtual Watchdog Driver"
depends on QCOM_WDT_CORE
help
This enables the Qualcomm Technologies, Inc. watchdog module for
the Gunyah 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

View File

@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_GH_VIRT_WATCHDOG)+= gh_virt_wdt.o

View File

@ -0,0 +1,305 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*
*/
#include <soc/qcom/watchdog.h>
#include <linux/arm-smccc.h>
#include <linux/gunyah/gh_errno.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#define VIRT_WDT_CONTROL \
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,\
ARM_SMCCC_OWNER_VENDOR_HYP, 0x0005)
#define VIRT_WDT_STATUS \
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,\
ARM_SMCCC_OWNER_VENDOR_HYP, 0x0006)
#define VIRT_WDT_PET \
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,\
ARM_SMCCC_OWNER_VENDOR_HYP, 0x0007)
#define VIRT_WDT_SET_TIME \
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,\
ARM_SMCCC_OWNER_VENDOR_HYP, 0x0008)
#define VIRT_WDT_NO_CHANGE 0xFFFF
/**
* gh_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 gh_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;
}
/**
* gh_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 gh_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 = gh_wdt_call(VIRT_WDT_SET_TIME, 0, bark_time, VIRT_WDT_NO_CHANGE);
hret = res.a0;
ret = gh_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;
}
/**
* gh_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 gh_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 = gh_wdt_call(VIRT_WDT_SET_TIME, 0, VIRT_WDT_NO_CHANGE, bite_time);
hret = res.a0;
ret = gh_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;
}
/**
* gh_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 gh_reset_wdt(struct msm_watchdog_data *wdog_dd)
{
struct arm_smccc_res res;
int hret, ret;
res = gh_wdt_call(VIRT_WDT_PET, 0, 0, 0);
hret = res.a0;
ret = gh_remap_error(hret);
if (hret) {
dev_err(wdog_dd->dev, "failed to reset vWDOG, hret = %d ret = %d\n",
hret, ret);
}
return ret;
}
/**
* gh_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 gh_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 = gh_wdt_call(VIRT_WDT_CONTROL, 3, 0, 0);
hret = res.a0;
ret = gh_remap_error(hret);
if (hret) {
dev_err(wdog_dd->dev, "failed enabling vWDOG, hret = %d ret = %d\n",
hret, ret);
}
return ret;
}
/**
* gh_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 gh_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 = gh_wdt_call(VIRT_WDT_CONTROL, 2, 0, 0);
hret = res.a0;
ret = gh_remap_error(hret);
if (hret) {
dev_err(wdog_dd->dev, "failed disabling VDOG, hret = %d ret = %d\n",
hret, ret);
}
return ret;
}
/**
* gh_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 gh_show_wdt_status(struct msm_watchdog_data *wdog_dd)
{
struct arm_smccc_res res;
int hret, ret;
res = gh_wdt_call(VIRT_WDT_STATUS, 0, 0, 0);
hret = res.a0;
ret = gh_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 gh_wdt_ops = {
.set_bark_time = gh_set_wdt_bark,
.set_bite_time = gh_set_wdt_bite,
.reset_wdt = gh_reset_wdt,
.enable_wdt = gh_enable_wdt,
.disable_wdt = gh_disable_wdt,
.show_wdt_status = gh_show_wdt_status
};
static int gh_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 = &gh_wdt_ops;
return qcom_wdt_register(pdev, wdog_dd, "gh-watchdog");
}
static const struct dev_pm_ops gh_wdt_dev_pm_ops = {
#ifdef CONFIG_PM_SLEEP
.suspend_late = qcom_wdt_pet_suspend,
.resume_early = qcom_wdt_pet_resume,
#endif
};
static const struct of_device_id gh_wdt_match_table[] = {
{ .compatible = "qcom,gh-watchdog" },
{ .compatible = "qcom,hh-watchdog" },
{}
};
static struct platform_driver gh_wdt_driver = {
.probe = gh_wdt_probe,
.remove = qcom_wdt_remove,
.driver = {
.name = "gh-watchdog",
.pm = &gh_wdt_dev_pm_ops,
.of_match_table = gh_wdt_match_table,
},
};
static int __init init_watchdog(void)
{
return platform_driver_register(&gh_wdt_driver);
}
#if IS_MODULE(CONFIG_GH_VIRT_WATCHDOG)
module_init(init_watchdog);
#else
pure_initcall(init_watchdog);
#endif
static __exit void exit_watchdog(void)
{
platform_driver_unregister(&gh_wdt_driver);
}
module_exit(exit_watchdog);
MODULE_DESCRIPTION("QCOM Gunyah Watchdog Driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*
*/
#ifndef __GH_ERRNO_H
#define __GH_ERRNO_H
#include <linux/errno.h>
#define GH_ERROR_OK 0
#define GH_ERROR_UNIMPLEMENTED -1
#define GH_ERROR_RETRY -2
#define GH_ERROR_ARG_INVAL 1
#define GH_ERROR_ARG_SIZE 2
#define GH_ERROR_ARG_ALIGN 3
#define GH_ERROR_NOMEM 10
#define GH_ERROR_ADDR_OVFL 20
#define GH_ERROR_ADDR_UNFL 21
#define GH_ERROR_ADDR_INVAL 22
#define GH_ERROR_DENIED 30
#define GH_ERROR_BUSY 31
#define GH_ERROR_IDLE 32
#define GH_ERROR_IRQ_BOUND 40
#define GH_ERROR_IRQ_UNBOUND 41
#define GH_ERROR_CSPACE_CAP_NULL 50
#define GH_ERROR_CSPACE_CAP_REVOKED 51
#define GH_ERROR_CSPACE_WRONG_OBJ_TYPE 52
#define GH_ERROR_CSPACE_INSUF_RIGHTS 53
#define GH_ERROR_CSPACE_FULL 54
#define GH_ERROR_MSGQUEUE_EMPTY 60
#define GH_ERROR_MSGQUEUE_FULL 61
static inline int gh_remap_error(int gh_error)
{
switch (gh_error) {
case GH_ERROR_OK:
return 0;
case GH_ERROR_NOMEM:
return -ENOMEM;
case GH_ERROR_DENIED:
case GH_ERROR_CSPACE_CAP_NULL:
case GH_ERROR_CSPACE_CAP_REVOKED:
case GH_ERROR_CSPACE_WRONG_OBJ_TYPE:
case GH_ERROR_CSPACE_INSUF_RIGHTS:
case GH_ERROR_CSPACE_FULL:
return -EACCES;
case GH_ERROR_BUSY:
case GH_ERROR_IDLE:
return -EBUSY;
case GH_ERROR_IRQ_BOUND:
case GH_ERROR_IRQ_UNBOUND:
case GH_ERROR_MSGQUEUE_FULL:
case GH_ERROR_MSGQUEUE_EMPTY:
return -EPERM;
case GH_ERROR_UNIMPLEMENTED:
case GH_ERROR_ARG_INVAL:
case GH_ERROR_ARG_SIZE:
case GH_ERROR_ARG_ALIGN:
case GH_ERROR_ADDR_OVFL:
case GH_ERROR_ADDR_UNFL:
case GH_ERROR_ADDR_INVAL:
default:
return -EINVAL;
}
}
#endif

169
include/soc/qcom/watchdog.h Normal file
View File

@ -0,0 +1,169 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2019-2020 The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef _SOC_QCOM_WATCHDOG_H_
#define _SOC_QCOM_WATCHDOG_H_
#ifdef CONFIG_QCOM_FORCE_WDOG_BITE_ON_PANIC
#define WDOG_BITE_ON_PANIC 1
#else
#define WDOG_BITE_ON_PANIC 0
#endif
#ifdef CONFIG_QCOM_WDOG_BITE_EARLY_PANIC
#define WDOG_BITE_EARLY_PANIC 1
#else
#define WDOG_BITE_EARLY_PANIC 0
#endif
/*
* 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
#if IS_ENABLED(CONFIG_QCOM_WDT_CORE)
#include <linux/platform_device.h>
#include <asm/hardirq.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
#define WDOG_NR_IPI 10
#define NR_TOP_HITTERS 10
struct qcom_wdt_ops;
struct msm_watchdog_data;
/** qcom_irq_info - IRQ stats
*
* @irq: linux/virtual irq numer.
* @total_count: sum of irq occurrence count on all cpu's.
* @irq_counter: irq occurrence count on each cpu.
*/
struct qcom_irq_info {
unsigned int irq;
unsigned int total_count;
unsigned int irq_counter[NR_CPUS];
};
/** 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;
bool irq_ppi;
unsigned long long last_pet;
cpumask_t alive_mask;
struct mutex disable_lock;
struct msm_watchdog_data * __percpu *wdog_cpu_dd;
struct notifier_block panic_blk;
struct notifier_block die_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];
bool freeze_in_progress;
spinlock_t freeze_lock;
struct work_struct irq_counts_work;
struct qcom_irq_info irq_counts[NR_TOP_HITTERS];
struct qcom_irq_info ipi_counts[WDOG_NR_IPI];
unsigned int tot_irq_count[NR_CPUS];
atomic_t irq_counts_running;
struct timer_list user_pet_timer;
};
extern void qcom_wdt_trigger_bite(void);
int qcom_wdt_register(struct platform_device *pdev,
struct msm_watchdog_data *wdog_dd,
char *wdog_dd_name);
int qcom_wdt_pet_suspend(struct device *dev);
int qcom_wdt_pet_resume(struct device *dev);
int qcom_wdt_remove(struct platform_device *pdev);
#else
static inline void qcom_wdt_trigger_bite(void) { }
#endif
#endif