Loading Documentation/devicetree/bindings/watchdog/qcom-vm-wdt.txt 0 → 100644 +32 −0 Original line number Diff line number Diff line * Qualcomm Technologies, Inc. VM Watchdog Watchdog timer is configured with bark and bite time interval. If Virtual Machine fails to reset the counter during bark interval, a bark interrupt (SPI) is send to the VM. Failing to respond to the bark interrupt causes a bite, when counter reaches programmed bite interval. The bite interrupt is routed to secure world, and causes a system reset. The device tree parameters are: Required properties: - compatible : "qcom,vm-wdt" - reg : base offset and length of the register set. - reg-names : names corresponding to each reg property value. "wdt-base" - physical base address of watchdog timer registers - interrupts : should contain bark and bite irq numbers - qcom,pet-time : Non zero time interval at which watchdog should be pet in ms. - qcom,bark-time : Non zero timeout value for a watchdog bark in ms. Example: qcom,vm-wdt@f9017000 { compatible = "qcom,vm-wdt"; reg = <0x17980000 0x1000>; reg-names = "wdt-base"; interrupts = <0 3 0>, <0 4 0>; qcom,bark-time = <11000>; qcom,pet-time = <10000>; }; MAINTAINERS +1 −0 Original line number Diff line number Diff line Loading @@ -13057,6 +13057,7 @@ S: Maintained F: Documentation/devicetree/bindings/watchdog/ F: Documentation/watchdog/ F: drivers/watchdog/ F: drivers/watchdog/qcom-vm-wdt.c F: include/linux/watchdog.h F: include/uapi/linux/watchdog.h Loading drivers/watchdog/Kconfig +12 −0 Original line number Diff line number Diff line Loading @@ -631,6 +631,18 @@ config QCOM_WDT To compile this driver as a module, choose M here: the module will be called qcom_wdt. config QCOM_VM_WDT tristate "QCOM VM watchdog" depends on HAS_IOMEM select WATCHDOG_CORE help Say Y here to include Watchdog timer support for the watchdog found on QCOM chipsets, for enviroments where hypervisor supports virtualized watchdog register space for each VM. To compile this driver as a module, choose M here: the module will be called qcom_vm_watchdog. config MESON_GXBB_WATCHDOG tristate "Amlogic Meson GXBB SoCs watchdog support" depends on ARCH_MESON Loading drivers/watchdog/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -71,6 +71,7 @@ obj-$(CONFIG_MOXART_WDT) += moxart_wdt.o obj-$(CONFIG_SIRFSOC_WATCHDOG) += sirfsoc_wdt.o obj-$(CONFIG_ST_LPC_WATCHDOG) += st_lpc_wdt.o obj-$(CONFIG_QCOM_WDT) += qcom-wdt.o obj-$(CONFIG_QCOM_VM_WDT) += qcom-vm-wdt.o obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o obj-$(CONFIG_MESON_GXBB_WATCHDOG) += meson_gxbb_wdt.o Loading drivers/watchdog/qcom-vm-wdt.c 0 → 100644 +475 −0 Original line number Diff line number Diff line /* Copyright (c) 2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/cpu.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> #include <linux/kthread.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/wait.h> #include <linux/watchdog.h> #include <linux/of_device.h> #define MASK_SIZE 32 #define QCOM_VM_WDT_HZ 32765 #define QCOM_VM_PET_TIMEOUT 9U #define QCOM_VM_BARK_TIMEOUT 11U enum qcom_vm_wdt_reg { WDT_RST, WDT_CTRL, WDT_STS, WDT_BARK_TIME, WDT_BITE_TIME, }; static const u32 qcom_vm_wdt_reg_offset_data[] = { [WDT_RST] = 0x4, [WDT_CTRL] = 0x8, [WDT_STS] = 0xC, [WDT_BARK_TIME] = 0x10, [WDT_BITE_TIME] = 0x14, }; struct qcom_vm_wdt { struct watchdog_device wdd; void __iomem *base; const u32 *layout; unsigned int bark_irq; unsigned int bite_irq; unsigned int bark_time; unsigned int pet_time; unsigned long long last_pet; struct device *dev; struct notifier_block panic_blk; struct timer_list pet_timer; bool timer_expired; wait_queue_head_t pet_complete; struct task_struct *watchdog_task; cpumask_t alive_mask; struct mutex disable_lock; }; static int enable = 1; module_param(enable, int, 0000); /* Disable the watchdog in hypervisor */ static int hyp_enable = 1; module_param(hyp_enable, int, 0000); static void __iomem *qcom_vm_wdt_addr(struct qcom_vm_wdt *wdt, enum qcom_vm_wdt_reg reg) { return wdt->base + wdt->layout[reg]; } static void dump_cpu_alive_mask(struct qcom_vm_wdt *wdt) { static char alive_mask_buf[MASK_SIZE]; scnprintf(alive_mask_buf, MASK_SIZE, "%*pb1", cpumask_pr_args( &wdt->alive_mask)); dev_info(wdt->dev, "cpu alive mask from last pet %s\n", alive_mask_buf); } static irqreturn_t qcom_vm_wdt_bark_handler(int irq, void *dev_id) { struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)dev_id; unsigned long long t = sched_clock(); dev_info(wdt->dev, "Watchdog bark! Now = %lu\n", (unsigned long) t); dev_info(wdt->dev, "Watchdog last pet at %lu\n", (unsigned long) wdt->last_pet); dump_cpu_alive_mask(wdt); pr_info("Causing a watchdog bite!"); __raw_writel(1 * QCOM_VM_WDT_HZ, qcom_vm_wdt_addr(wdt, WDT_BITE_TIME)); /* Make sure bite time is written before we reset */ mb(); __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); /* Make sure we wait only after reset */ mb(); /* Delay to make sure bite occurs */ msleep(10000); panic("Failed to cause a watchdog bite! - Falling back to kernel panic!"); return IRQ_HANDLED; } static int qcom_vm_wdt_suspend(struct device *dev) { struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)dev_get_drvdata(dev); if (!enable) return 0; __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); /* Make sure watchdog is suspended before setting enable */ mb(); wdt->last_pet = sched_clock(); return 0; } static int qcom_vm_wdt_resume(struct device *dev) { struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)dev_get_drvdata(dev); if (!enable) return 0; __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); /* Make sure watchdog is suspended before setting enable */ mb(); wdt->last_pet = sched_clock(); return 0; } static int qcom_vm_wdt_panic_handler(struct notifier_block *this, unsigned long event, void *ptr) { struct qcom_vm_wdt *wdt = container_of(this, struct qcom_vm_wdt, panic_blk); __raw_writel(QCOM_VM_WDT_HZ * (panic_timeout + 10), qcom_vm_wdt_addr(wdt, WDT_BARK_TIME)); __raw_writel(QCOM_VM_WDT_HZ * (panic_timeout + 10), qcom_vm_wdt_addr(wdt, WDT_BITE_TIME)); __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); /* * Ensure that bark, bite times, and reset is done, before * moving forward. */ mb(); return NOTIFY_DONE; } static void qcom_vm_wdt_disable(struct qcom_vm_wdt *wdt) { if (hyp_enable == 1) __raw_writel(0, qcom_vm_wdt_addr(wdt, WDT_CTRL)); /* Make sure watchdog is disabled before proceeding */ mb(); devm_free_irq(wdt->dev, wdt->bark_irq, wdt); enable = 0; /*Ensure all cpus see update to enable*/ smp_mb(); atomic_notifier_chain_unregister(&panic_notifier_list, &wdt->panic_blk); del_timer_sync(&wdt->pet_timer); /* may be suspended after the first write above */ if (hyp_enable == 1) __raw_writel(0, qcom_vm_wdt_addr(wdt, WDT_CTRL)); /* Make sure watchdog is disabled before setting enable */ mb(); pr_info("QCOM VM Watchdog deactivated.\n"); } static ssize_t qcom_vm_wdt_disable_get(struct device *dev, struct device_attribute *attr, char *buf) { int ret; struct qcom_vm_wdt *wdt = dev_get_drvdata(dev); mutex_lock(&wdt->disable_lock); ret = snprintf(buf, PAGE_SIZE, "%d\n", enable == 0 ? 1 : 0); mutex_unlock(&wdt->disable_lock); return ret; } static ssize_t qcom_vm_wdt_disable_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; u8 disable; struct qcom_vm_wdt *wdt = dev_get_drvdata(dev); ret = kstrtou8(buf, 10, &disable); if (ret) { dev_err(wdt->dev, "invalid user input\n"); return ret; } if (disable == 1) { mutex_lock(&wdt->disable_lock); if (enable == 0) { pr_info("QCOM VM Watchdog already disabled\n"); mutex_unlock(&wdt->disable_lock); return count; } qcom_vm_wdt_disable(wdt); mutex_unlock(&wdt->disable_lock); } else { pr_err("invalid operation, only disable = 1 supported\n"); return -EINVAL; } return count; } static DEVICE_ATTR(disable, 0600, qcom_vm_wdt_disable_get, qcom_vm_wdt_disable_set); static void qcom_vm_wdt_hyp_disable(struct qcom_vm_wdt *wdt) { __raw_writel(1 << 2, qcom_vm_wdt_addr(wdt, WDT_CTRL)); hyp_enable = 0; } static ssize_t qcom_vm_wdt_hyp_disable_get(struct device *dev, struct device_attribute *attr, char *buf) { int ret; struct qcom_vm_wdt *wdt = dev_get_drvdata(dev); mutex_lock(&wdt->disable_lock); ret = snprintf(buf, PAGE_SIZE, "%d\n", hyp_enable == 0 ? 1 : 0); mutex_unlock(&wdt->disable_lock); return ret; } static ssize_t qcom_vm_wdt_hyp_disable_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; u8 disable; struct qcom_vm_wdt *wdt = dev_get_drvdata(dev); ret = kstrtou8(buf, 10, &disable); if (ret) { dev_err(wdt->dev, "invalid user input\n"); return ret; } if (disable == 1) { mutex_lock(&wdt->disable_lock); if (hyp_enable == 0) { pr_info("QCOM VM Watchdog already disabled in Hyp\n"); mutex_unlock(&wdt->disable_lock); return count; } qcom_vm_wdt_hyp_disable(wdt); mutex_unlock(&wdt->disable_lock); } else { pr_err("invalid operation, only hyp_disable = 1 supported\n"); return -EINVAL; } return count; } static DEVICE_ATTR(hyp_disable, 0600, qcom_vm_wdt_hyp_disable_get, qcom_vm_wdt_hyp_disable_set); static int qcom_vm_wdt_start(struct qcom_vm_wdt *wdt) { __raw_writel(0, qcom_vm_wdt_addr(wdt, WDT_CTRL)); __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); __raw_writel((wdt->bark_time / 1000) * QCOM_VM_WDT_HZ, qcom_vm_wdt_addr(wdt, WDT_BARK_TIME)); __raw_writel((wdt->bark_time / 1000 + 3) * QCOM_VM_WDT_HZ, qcom_vm_wdt_addr(wdt, WDT_BITE_TIME)); __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_CTRL)); return 0; } static void keep_alive_response(void *info) { int cpu = smp_processor_id(); struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)info; cpumask_set_cpu(cpu, &wdt->alive_mask); /* Make sure alive mask is cleared and set in order */ smp_mb(); } static int qcom_vm_wdt_ping(struct qcom_vm_wdt *wdt) { int cpu; cpumask_clear(&wdt->alive_mask); for_each_cpu(cpu, cpu_online_mask) { smp_call_function_single(cpu, keep_alive_response, wdt, 1); } __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); wdt->last_pet = sched_clock(); return 0; } static void vm_pet_task_wakeup(unsigned long data) { struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)data; wdt->timer_expired = true; wake_up(&wdt->pet_complete); } static __ref int vm_watchdog_kthread(void *arg) { struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)arg; while (!kthread_should_stop()) { while (wait_event_interruptible( wdt->pet_complete, wdt->timer_expired) != 0) ; if (enable) qcom_vm_wdt_ping(wdt); wdt->timer_expired = false; mod_timer(&wdt->pet_timer, jiffies + msecs_to_jiffies(wdt->pet_time)); } return 0; } static int qcom_vm_wdt_probe(struct platform_device *pdev) { struct qcom_vm_wdt *wdt; struct resource *res; const u32 *regs; int ret, error; if (!pdev->dev.of_node || !enable) return -ENODEV; regs = of_device_get_match_data(&pdev->dev); if (!regs) { dev_err(&pdev->dev, "Unsupported QCOM WDT module\n"); return -ENODEV; } wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); if (!wdt) return -ENOMEM; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wdt-base"); if (!res) { ret = -ENODEV; goto err; } wdt->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(wdt->base)) { ret = PTR_ERR(wdt->base); goto err; } wdt->layout = regs; wdt->dev = &pdev->dev; wdt->bark_irq = platform_get_irq(pdev, 0); wdt->bite_irq = platform_get_irq(pdev, 1); wdt->last_pet = sched_clock(); ret = of_property_read_u32(pdev->dev.of_node, "qcom,bark-time", &wdt->bark_time); if (ret) wdt->bark_time = QCOM_VM_BARK_TIMEOUT * 1000; ret = of_property_read_u32(pdev->dev.of_node, "qcom,pet-time", &wdt->pet_time); if (ret) wdt->pet_time = QCOM_VM_PET_TIMEOUT * 1000; wdt->watchdog_task = kthread_create(vm_watchdog_kthread, wdt, "qcom_vm_watchdog"); if (IS_ERR(wdt->watchdog_task)) { ret = PTR_ERR(wdt->watchdog_task); goto err; } init_waitqueue_head(&wdt->pet_complete); wdt->timer_expired = false; wake_up_process(wdt->watchdog_task); init_timer(&wdt->pet_timer); wdt->pet_timer.data = (unsigned long)wdt; wdt->pet_timer.function = vm_pet_task_wakeup; wdt->pet_timer.expires = jiffies + msecs_to_jiffies(wdt->pet_time); add_timer(&wdt->pet_timer); cpumask_clear(&wdt->alive_mask); wdt->panic_blk.notifier_call = qcom_vm_wdt_panic_handler; atomic_notifier_chain_register(&panic_notifier_list, &wdt->panic_blk); platform_set_drvdata(pdev, wdt); mutex_init(&wdt->disable_lock); error = device_create_file(wdt->dev, &dev_attr_disable); error |= device_create_file(wdt->dev, &dev_attr_hyp_disable); if (error) dev_err(wdt->dev, "cannot create sysfs attribute\n"); qcom_vm_wdt_start(wdt); ret = devm_request_irq(&pdev->dev, wdt->bark_irq, qcom_vm_wdt_bark_handler, IRQF_TRIGGER_RISING, "apps_wdog_bark", wdt); if (hyp_enable == 0) qcom_vm_wdt_hyp_disable(wdt); return 0; err: kfree(wdt); return ret; } static int qcom_vm_wdt_remove(struct platform_device *pdev) { struct qcom_vm_wdt *wdt = platform_get_drvdata(pdev); mutex_lock(&wdt->disable_lock); if (enable) qcom_vm_wdt_disable(wdt); mutex_unlock(&wdt->disable_lock); device_remove_file(wdt->dev, &dev_attr_disable); device_remove_file(wdt->dev, &dev_attr_hyp_disable); dev_info(wdt->dev, "QCOM VM Watchdog Exit - Deactivated\n"); del_timer_sync(&wdt->pet_timer); kthread_stop(wdt->watchdog_task); kfree(wdt); return 0; } static const struct of_device_id qcom_vm_wdt_of_table[] = { { .compatible = "qcom,vm-wdt", .data = qcom_vm_wdt_reg_offset_data }, { }, }; MODULE_DEVICE_TABLE(of, qcom_wdt_of_table); static const struct dev_pm_ops qcom_vm_wdt_dev_pm_ops = { .suspend_noirq = qcom_vm_wdt_suspend, .resume_noirq = qcom_vm_wdt_resume, }; static struct platform_driver qcom_vm_watchdog_driver = { .probe = qcom_vm_wdt_probe, .remove = qcom_vm_wdt_remove, .driver = { .name = KBUILD_MODNAME, .pm = &qcom_vm_wdt_dev_pm_ops, .of_match_table = qcom_vm_wdt_of_table, }, }; module_platform_driver(qcom_vm_watchdog_driver); MODULE_DESCRIPTION("QCOM VM Watchdog Driver"); MODULE_LICENSE("GPL v2"); Loading
Documentation/devicetree/bindings/watchdog/qcom-vm-wdt.txt 0 → 100644 +32 −0 Original line number Diff line number Diff line * Qualcomm Technologies, Inc. VM Watchdog Watchdog timer is configured with bark and bite time interval. If Virtual Machine fails to reset the counter during bark interval, a bark interrupt (SPI) is send to the VM. Failing to respond to the bark interrupt causes a bite, when counter reaches programmed bite interval. The bite interrupt is routed to secure world, and causes a system reset. The device tree parameters are: Required properties: - compatible : "qcom,vm-wdt" - reg : base offset and length of the register set. - reg-names : names corresponding to each reg property value. "wdt-base" - physical base address of watchdog timer registers - interrupts : should contain bark and bite irq numbers - qcom,pet-time : Non zero time interval at which watchdog should be pet in ms. - qcom,bark-time : Non zero timeout value for a watchdog bark in ms. Example: qcom,vm-wdt@f9017000 { compatible = "qcom,vm-wdt"; reg = <0x17980000 0x1000>; reg-names = "wdt-base"; interrupts = <0 3 0>, <0 4 0>; qcom,bark-time = <11000>; qcom,pet-time = <10000>; };
MAINTAINERS +1 −0 Original line number Diff line number Diff line Loading @@ -13057,6 +13057,7 @@ S: Maintained F: Documentation/devicetree/bindings/watchdog/ F: Documentation/watchdog/ F: drivers/watchdog/ F: drivers/watchdog/qcom-vm-wdt.c F: include/linux/watchdog.h F: include/uapi/linux/watchdog.h Loading
drivers/watchdog/Kconfig +12 −0 Original line number Diff line number Diff line Loading @@ -631,6 +631,18 @@ config QCOM_WDT To compile this driver as a module, choose M here: the module will be called qcom_wdt. config QCOM_VM_WDT tristate "QCOM VM watchdog" depends on HAS_IOMEM select WATCHDOG_CORE help Say Y here to include Watchdog timer support for the watchdog found on QCOM chipsets, for enviroments where hypervisor supports virtualized watchdog register space for each VM. To compile this driver as a module, choose M here: the module will be called qcom_vm_watchdog. config MESON_GXBB_WATCHDOG tristate "Amlogic Meson GXBB SoCs watchdog support" depends on ARCH_MESON Loading
drivers/watchdog/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -71,6 +71,7 @@ obj-$(CONFIG_MOXART_WDT) += moxart_wdt.o obj-$(CONFIG_SIRFSOC_WATCHDOG) += sirfsoc_wdt.o obj-$(CONFIG_ST_LPC_WATCHDOG) += st_lpc_wdt.o obj-$(CONFIG_QCOM_WDT) += qcom-wdt.o obj-$(CONFIG_QCOM_VM_WDT) += qcom-vm-wdt.o obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o obj-$(CONFIG_MESON_GXBB_WATCHDOG) += meson_gxbb_wdt.o Loading
drivers/watchdog/qcom-vm-wdt.c 0 → 100644 +475 −0 Original line number Diff line number Diff line /* Copyright (c) 2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/cpu.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> #include <linux/kthread.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/wait.h> #include <linux/watchdog.h> #include <linux/of_device.h> #define MASK_SIZE 32 #define QCOM_VM_WDT_HZ 32765 #define QCOM_VM_PET_TIMEOUT 9U #define QCOM_VM_BARK_TIMEOUT 11U enum qcom_vm_wdt_reg { WDT_RST, WDT_CTRL, WDT_STS, WDT_BARK_TIME, WDT_BITE_TIME, }; static const u32 qcom_vm_wdt_reg_offset_data[] = { [WDT_RST] = 0x4, [WDT_CTRL] = 0x8, [WDT_STS] = 0xC, [WDT_BARK_TIME] = 0x10, [WDT_BITE_TIME] = 0x14, }; struct qcom_vm_wdt { struct watchdog_device wdd; void __iomem *base; const u32 *layout; unsigned int bark_irq; unsigned int bite_irq; unsigned int bark_time; unsigned int pet_time; unsigned long long last_pet; struct device *dev; struct notifier_block panic_blk; struct timer_list pet_timer; bool timer_expired; wait_queue_head_t pet_complete; struct task_struct *watchdog_task; cpumask_t alive_mask; struct mutex disable_lock; }; static int enable = 1; module_param(enable, int, 0000); /* Disable the watchdog in hypervisor */ static int hyp_enable = 1; module_param(hyp_enable, int, 0000); static void __iomem *qcom_vm_wdt_addr(struct qcom_vm_wdt *wdt, enum qcom_vm_wdt_reg reg) { return wdt->base + wdt->layout[reg]; } static void dump_cpu_alive_mask(struct qcom_vm_wdt *wdt) { static char alive_mask_buf[MASK_SIZE]; scnprintf(alive_mask_buf, MASK_SIZE, "%*pb1", cpumask_pr_args( &wdt->alive_mask)); dev_info(wdt->dev, "cpu alive mask from last pet %s\n", alive_mask_buf); } static irqreturn_t qcom_vm_wdt_bark_handler(int irq, void *dev_id) { struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)dev_id; unsigned long long t = sched_clock(); dev_info(wdt->dev, "Watchdog bark! Now = %lu\n", (unsigned long) t); dev_info(wdt->dev, "Watchdog last pet at %lu\n", (unsigned long) wdt->last_pet); dump_cpu_alive_mask(wdt); pr_info("Causing a watchdog bite!"); __raw_writel(1 * QCOM_VM_WDT_HZ, qcom_vm_wdt_addr(wdt, WDT_BITE_TIME)); /* Make sure bite time is written before we reset */ mb(); __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); /* Make sure we wait only after reset */ mb(); /* Delay to make sure bite occurs */ msleep(10000); panic("Failed to cause a watchdog bite! - Falling back to kernel panic!"); return IRQ_HANDLED; } static int qcom_vm_wdt_suspend(struct device *dev) { struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)dev_get_drvdata(dev); if (!enable) return 0; __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); /* Make sure watchdog is suspended before setting enable */ mb(); wdt->last_pet = sched_clock(); return 0; } static int qcom_vm_wdt_resume(struct device *dev) { struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)dev_get_drvdata(dev); if (!enable) return 0; __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); /* Make sure watchdog is suspended before setting enable */ mb(); wdt->last_pet = sched_clock(); return 0; } static int qcom_vm_wdt_panic_handler(struct notifier_block *this, unsigned long event, void *ptr) { struct qcom_vm_wdt *wdt = container_of(this, struct qcom_vm_wdt, panic_blk); __raw_writel(QCOM_VM_WDT_HZ * (panic_timeout + 10), qcom_vm_wdt_addr(wdt, WDT_BARK_TIME)); __raw_writel(QCOM_VM_WDT_HZ * (panic_timeout + 10), qcom_vm_wdt_addr(wdt, WDT_BITE_TIME)); __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); /* * Ensure that bark, bite times, and reset is done, before * moving forward. */ mb(); return NOTIFY_DONE; } static void qcom_vm_wdt_disable(struct qcom_vm_wdt *wdt) { if (hyp_enable == 1) __raw_writel(0, qcom_vm_wdt_addr(wdt, WDT_CTRL)); /* Make sure watchdog is disabled before proceeding */ mb(); devm_free_irq(wdt->dev, wdt->bark_irq, wdt); enable = 0; /*Ensure all cpus see update to enable*/ smp_mb(); atomic_notifier_chain_unregister(&panic_notifier_list, &wdt->panic_blk); del_timer_sync(&wdt->pet_timer); /* may be suspended after the first write above */ if (hyp_enable == 1) __raw_writel(0, qcom_vm_wdt_addr(wdt, WDT_CTRL)); /* Make sure watchdog is disabled before setting enable */ mb(); pr_info("QCOM VM Watchdog deactivated.\n"); } static ssize_t qcom_vm_wdt_disable_get(struct device *dev, struct device_attribute *attr, char *buf) { int ret; struct qcom_vm_wdt *wdt = dev_get_drvdata(dev); mutex_lock(&wdt->disable_lock); ret = snprintf(buf, PAGE_SIZE, "%d\n", enable == 0 ? 1 : 0); mutex_unlock(&wdt->disable_lock); return ret; } static ssize_t qcom_vm_wdt_disable_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; u8 disable; struct qcom_vm_wdt *wdt = dev_get_drvdata(dev); ret = kstrtou8(buf, 10, &disable); if (ret) { dev_err(wdt->dev, "invalid user input\n"); return ret; } if (disable == 1) { mutex_lock(&wdt->disable_lock); if (enable == 0) { pr_info("QCOM VM Watchdog already disabled\n"); mutex_unlock(&wdt->disable_lock); return count; } qcom_vm_wdt_disable(wdt); mutex_unlock(&wdt->disable_lock); } else { pr_err("invalid operation, only disable = 1 supported\n"); return -EINVAL; } return count; } static DEVICE_ATTR(disable, 0600, qcom_vm_wdt_disable_get, qcom_vm_wdt_disable_set); static void qcom_vm_wdt_hyp_disable(struct qcom_vm_wdt *wdt) { __raw_writel(1 << 2, qcom_vm_wdt_addr(wdt, WDT_CTRL)); hyp_enable = 0; } static ssize_t qcom_vm_wdt_hyp_disable_get(struct device *dev, struct device_attribute *attr, char *buf) { int ret; struct qcom_vm_wdt *wdt = dev_get_drvdata(dev); mutex_lock(&wdt->disable_lock); ret = snprintf(buf, PAGE_SIZE, "%d\n", hyp_enable == 0 ? 1 : 0); mutex_unlock(&wdt->disable_lock); return ret; } static ssize_t qcom_vm_wdt_hyp_disable_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; u8 disable; struct qcom_vm_wdt *wdt = dev_get_drvdata(dev); ret = kstrtou8(buf, 10, &disable); if (ret) { dev_err(wdt->dev, "invalid user input\n"); return ret; } if (disable == 1) { mutex_lock(&wdt->disable_lock); if (hyp_enable == 0) { pr_info("QCOM VM Watchdog already disabled in Hyp\n"); mutex_unlock(&wdt->disable_lock); return count; } qcom_vm_wdt_hyp_disable(wdt); mutex_unlock(&wdt->disable_lock); } else { pr_err("invalid operation, only hyp_disable = 1 supported\n"); return -EINVAL; } return count; } static DEVICE_ATTR(hyp_disable, 0600, qcom_vm_wdt_hyp_disable_get, qcom_vm_wdt_hyp_disable_set); static int qcom_vm_wdt_start(struct qcom_vm_wdt *wdt) { __raw_writel(0, qcom_vm_wdt_addr(wdt, WDT_CTRL)); __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); __raw_writel((wdt->bark_time / 1000) * QCOM_VM_WDT_HZ, qcom_vm_wdt_addr(wdt, WDT_BARK_TIME)); __raw_writel((wdt->bark_time / 1000 + 3) * QCOM_VM_WDT_HZ, qcom_vm_wdt_addr(wdt, WDT_BITE_TIME)); __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_CTRL)); return 0; } static void keep_alive_response(void *info) { int cpu = smp_processor_id(); struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)info; cpumask_set_cpu(cpu, &wdt->alive_mask); /* Make sure alive mask is cleared and set in order */ smp_mb(); } static int qcom_vm_wdt_ping(struct qcom_vm_wdt *wdt) { int cpu; cpumask_clear(&wdt->alive_mask); for_each_cpu(cpu, cpu_online_mask) { smp_call_function_single(cpu, keep_alive_response, wdt, 1); } __raw_writel(1, qcom_vm_wdt_addr(wdt, WDT_RST)); wdt->last_pet = sched_clock(); return 0; } static void vm_pet_task_wakeup(unsigned long data) { struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)data; wdt->timer_expired = true; wake_up(&wdt->pet_complete); } static __ref int vm_watchdog_kthread(void *arg) { struct qcom_vm_wdt *wdt = (struct qcom_vm_wdt *)arg; while (!kthread_should_stop()) { while (wait_event_interruptible( wdt->pet_complete, wdt->timer_expired) != 0) ; if (enable) qcom_vm_wdt_ping(wdt); wdt->timer_expired = false; mod_timer(&wdt->pet_timer, jiffies + msecs_to_jiffies(wdt->pet_time)); } return 0; } static int qcom_vm_wdt_probe(struct platform_device *pdev) { struct qcom_vm_wdt *wdt; struct resource *res; const u32 *regs; int ret, error; if (!pdev->dev.of_node || !enable) return -ENODEV; regs = of_device_get_match_data(&pdev->dev); if (!regs) { dev_err(&pdev->dev, "Unsupported QCOM WDT module\n"); return -ENODEV; } wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); if (!wdt) return -ENOMEM; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wdt-base"); if (!res) { ret = -ENODEV; goto err; } wdt->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(wdt->base)) { ret = PTR_ERR(wdt->base); goto err; } wdt->layout = regs; wdt->dev = &pdev->dev; wdt->bark_irq = platform_get_irq(pdev, 0); wdt->bite_irq = platform_get_irq(pdev, 1); wdt->last_pet = sched_clock(); ret = of_property_read_u32(pdev->dev.of_node, "qcom,bark-time", &wdt->bark_time); if (ret) wdt->bark_time = QCOM_VM_BARK_TIMEOUT * 1000; ret = of_property_read_u32(pdev->dev.of_node, "qcom,pet-time", &wdt->pet_time); if (ret) wdt->pet_time = QCOM_VM_PET_TIMEOUT * 1000; wdt->watchdog_task = kthread_create(vm_watchdog_kthread, wdt, "qcom_vm_watchdog"); if (IS_ERR(wdt->watchdog_task)) { ret = PTR_ERR(wdt->watchdog_task); goto err; } init_waitqueue_head(&wdt->pet_complete); wdt->timer_expired = false; wake_up_process(wdt->watchdog_task); init_timer(&wdt->pet_timer); wdt->pet_timer.data = (unsigned long)wdt; wdt->pet_timer.function = vm_pet_task_wakeup; wdt->pet_timer.expires = jiffies + msecs_to_jiffies(wdt->pet_time); add_timer(&wdt->pet_timer); cpumask_clear(&wdt->alive_mask); wdt->panic_blk.notifier_call = qcom_vm_wdt_panic_handler; atomic_notifier_chain_register(&panic_notifier_list, &wdt->panic_blk); platform_set_drvdata(pdev, wdt); mutex_init(&wdt->disable_lock); error = device_create_file(wdt->dev, &dev_attr_disable); error |= device_create_file(wdt->dev, &dev_attr_hyp_disable); if (error) dev_err(wdt->dev, "cannot create sysfs attribute\n"); qcom_vm_wdt_start(wdt); ret = devm_request_irq(&pdev->dev, wdt->bark_irq, qcom_vm_wdt_bark_handler, IRQF_TRIGGER_RISING, "apps_wdog_bark", wdt); if (hyp_enable == 0) qcom_vm_wdt_hyp_disable(wdt); return 0; err: kfree(wdt); return ret; } static int qcom_vm_wdt_remove(struct platform_device *pdev) { struct qcom_vm_wdt *wdt = platform_get_drvdata(pdev); mutex_lock(&wdt->disable_lock); if (enable) qcom_vm_wdt_disable(wdt); mutex_unlock(&wdt->disable_lock); device_remove_file(wdt->dev, &dev_attr_disable); device_remove_file(wdt->dev, &dev_attr_hyp_disable); dev_info(wdt->dev, "QCOM VM Watchdog Exit - Deactivated\n"); del_timer_sync(&wdt->pet_timer); kthread_stop(wdt->watchdog_task); kfree(wdt); return 0; } static const struct of_device_id qcom_vm_wdt_of_table[] = { { .compatible = "qcom,vm-wdt", .data = qcom_vm_wdt_reg_offset_data }, { }, }; MODULE_DEVICE_TABLE(of, qcom_wdt_of_table); static const struct dev_pm_ops qcom_vm_wdt_dev_pm_ops = { .suspend_noirq = qcom_vm_wdt_suspend, .resume_noirq = qcom_vm_wdt_resume, }; static struct platform_driver qcom_vm_watchdog_driver = { .probe = qcom_vm_wdt_probe, .remove = qcom_vm_wdt_remove, .driver = { .name = KBUILD_MODNAME, .pm = &qcom_vm_wdt_dev_pm_ops, .of_match_table = qcom_vm_wdt_of_table, }, }; module_platform_driver(qcom_vm_watchdog_driver); MODULE_DESCRIPTION("QCOM VM Watchdog Driver"); MODULE_LICENSE("GPL v2");