/* * drivers/watchdog/tegra_hv_wdt.c * * Copyright (c) 2014-2019, NVIDIA CORPORATION. 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 as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include /* The hypervisor monitor service requires the watchdog to be refreshed at * least once every 60 seconds, so set our refresh time to less than half of * that to account for client reboots that may not be communicated to the * monitor service. Note that should all client reboots be communicated to * the monitor service, then the refresh interval can be safely doubled. */ #define REFRESH_INTERVAL_MS (28000) #define IVC_MESSAGE_DATA "x" #define IVC_SUCCESS_CODE sizeof(IVC_MESSAGE_DATA) struct tegra_hv_wdt { struct watchdog_device wdt; struct tegra_hv_ivc_cookie *ivc; struct platform_device *pdev; struct task_struct *thread; wait_queue_head_t notify; struct mutex lock; bool interrupt; bool do_ping; int saved; unsigned int refresh_interval_in_ms; }; static int tegra_hv_wdt_start(struct watchdog_device *wdt) { struct tegra_hv_wdt *hv = container_of(wdt, struct tegra_hv_wdt, wdt); dev_info(&hv->pdev->dev, "device opened\n"); hv->do_ping = true; return 0; } /* * This function is only used when the user does a "magic close" * (by writing 'V' to the /dev/watchdog device.) */ static int tegra_hv_wdt_stop(struct watchdog_device *wdt) { struct tegra_hv_wdt *hv = container_of(wdt, struct tegra_hv_wdt, wdt); dev_info(&hv->pdev->dev, "device closed\n"); hv->do_ping = false; return 0; } static int tegra_hv_wdt_ping(struct watchdog_device *wdt) { struct tegra_hv_wdt *hv = container_of(wdt, struct tegra_hv_wdt, wdt); int ret; mutex_lock(&hv->lock); ret = tegra_hv_ivc_write(hv->ivc, IVC_MESSAGE_DATA, sizeof(IVC_MESSAGE_DATA)); /* * If a write error occurs, we log the problem only on state changes * to error states. This gives us indications when a problem may be * starting but won't flood system logs with duplicate information of * little value if the error condition persists. */ if ((ret != IVC_SUCCESS_CODE) && (ret != hv->saved)) dev_err(&hv->pdev->dev, "ivc write error %d\n", ret); hv->saved = ret; mutex_unlock(&hv->lock); return 0; } static int tegra_hv_wdt_notified(struct tegra_hv_wdt *hv) { int ret; mutex_lock(&hv->lock); ret = tegra_hv_ivc_channel_notified(hv->ivc); mutex_unlock(&hv->lock); return ret; } static int tegra_hv_wdt_loop(void *arg) { struct tegra_hv_wdt *hv = (struct tegra_hv_wdt *)arg; DEFINE_WAIT_FUNC(wait, woken_wake_function); mutex_lock(&hv->lock); tegra_hv_ivc_channel_reset(hv->ivc); mutex_unlock(&hv->lock); /* Wait "infinitely" for the ivc channel to become ready. * * This "wait" was observed to always return immediately, * as the "WDT server" on the other-end is already running by the time * this code is executed on the Linux guest OS during boot. */ add_wait_queue(&hv->notify, &wait); while (tegra_hv_wdt_notified(hv) != 0) { if (!wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT)) { dev_warn(&hv->pdev->dev, "Timed-out waiting for ivc channel. " "Retrying...\n"); } } remove_wait_queue(&hv->notify, &wait); dev_info(&hv->pdev->dev, "ivc channel ready\n"); /* * At this point, the channel has completed reset and we can * now communicate with the monitor service. We no longer have * a need for interrupts (though they shouldn't happen.) */ devm_free_irq(&hv->pdev->dev, hv->ivc->irq, &hv->wdt); hv->interrupt = false; while (!kthread_should_stop()) { if (hv->do_ping) tegra_hv_wdt_ping(&hv->wdt); msleep(hv->refresh_interval_in_ms); } return 0; } static irqreturn_t tegra_hv_wdt_interrupt(int irq, void *data) { struct tegra_hv_wdt *hv = (struct tegra_hv_wdt *)data; wake_up(&hv->notify); return IRQ_HANDLED; } static int tegra_hv_wdt_parse(struct platform_device *pdev, struct device_node **qn, u32 *id, u32 *refresh_interval) { struct device_node *dn; dn = pdev->dev.of_node; if (dn == NULL) { dev_err(&pdev->dev, "failed to find device node\n"); return -EINVAL; } if (of_property_read_u32_index(dn, "ivc", 1, id) != 0) { dev_err(&pdev->dev, "failed to find ivc property\n"); return -EINVAL; } if (of_property_read_u32(dn, "refresh_interval_in_ms", refresh_interval) != 0) { dev_info(&pdev->dev, "Refresh interval not set in DT, using %d milli-second as default\n", REFRESH_INTERVAL_MS); *refresh_interval = REFRESH_INTERVAL_MS; } *qn = of_parse_phandle(dn, "ivc", 0); if (*qn == NULL) { dev_err(&pdev->dev, "failed to find queue node\n"); return -EINVAL; } return 0; } static const struct watchdog_info tegra_hv_wdt_info = { .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, .identity = "Tegra HV WDT", .firmware_version = 1, }; static const struct watchdog_ops tegra_hv_wdt_ops = { .owner = THIS_MODULE, .start = tegra_hv_wdt_start, .stop = tegra_hv_wdt_stop, .ping = tegra_hv_wdt_ping, }; static int tegra_hv_wdt_setup_no_cleanup(struct tegra_hv_wdt *hv, struct platform_device *pdev, struct device_node *qn, unsigned int id, unsigned int refresh_interval) { int errcode; init_waitqueue_head(&hv->notify); mutex_init(&hv->lock); hv->do_ping = true; hv->saved = IVC_SUCCESS_CODE; hv->pdev = pdev; hv->refresh_interval_in_ms = refresh_interval; hv->ivc = tegra_hv_ivc_reserve(qn, id, NULL); if (IS_ERR_OR_NULL(hv->ivc)) { dev_err(&pdev->dev, "failed to reserve ivc %u\n", id); return -EINVAL; } errcode = devm_request_irq(&pdev->dev, hv->ivc->irq, tegra_hv_wdt_interrupt, 0, dev_name(&pdev->dev), (void *)hv); if (errcode < 0) { dev_err(&pdev->dev, "failed to get irq %d\n", hv->ivc->irq); return -EINVAL; } hv->interrupt = true; hv->thread = kthread_create(tegra_hv_wdt_loop, (void *)hv, "tegra-hv-wdt"); if (IS_ERR_OR_NULL(hv->thread)) { dev_err(&pdev->dev, "failed to create kthread\n"); return -EINVAL; } hv->wdt.info = &tegra_hv_wdt_info; hv->wdt.ops = &tegra_hv_wdt_ops; hv->wdt.timeout = refresh_interval; errcode = watchdog_register_device(&hv->wdt); if (errcode < 0) { memset(&hv->wdt, 0, sizeof(hv->wdt)); dev_err(&pdev->dev, "failed to register device\n"); return errcode; } return 0; } static void tegra_hv_wdt_cleanup(struct tegra_hv_wdt *hv) { int errcode; if (!IS_ERR_OR_NULL(hv->thread)) { errcode = kthread_stop(hv->thread); if ((errcode != 0) && (errcode != -EINTR)) dev_err(&hv->pdev->dev, "failed to stop thread\n"); } if (hv->wdt.info) watchdog_unregister_device(&hv->wdt); if (hv->interrupt) devm_free_irq(&hv->pdev->dev, hv->ivc->irq, &hv->wdt); if (!IS_ERR_OR_NULL(hv->ivc)) { if (tegra_hv_ivc_unreserve(hv->ivc) != 0) dev_err(&hv->pdev->dev, "failed to unreserve ivc\n"); } } static int tegra_hv_wdt_probe(struct platform_device *pdev) { struct tegra_hv_wdt *hv; struct device_node *qn; int errcode; u32 id; unsigned int refresh_interval; if (!is_tegra_hypervisor_mode()) { dev_info(&pdev->dev, "hypervisor is not present\n"); return -ENODEV; } errcode = tegra_hv_wdt_parse(pdev, &qn, &id, &refresh_interval); if (errcode < 0) { dev_err(&pdev->dev, "failed to parse device tree\n"); return -ENODEV; } hv = devm_kzalloc(&pdev->dev, sizeof(*hv), GFP_KERNEL); if (!hv) { of_node_put(qn); dev_err(&pdev->dev, "failed to allocate memory\n"); return -ENOMEM; } /* * Note that if the setup function fails, there may be * dangling resources. So, we must clean up after failure. */ errcode = tegra_hv_wdt_setup_no_cleanup(hv, pdev, qn, id, refresh_interval); of_node_put(qn); if (errcode < 0) { tegra_hv_wdt_cleanup(hv); devm_kfree(&pdev->dev, hv); dev_err(&pdev->dev, "failed to setup device\n"); return -EINVAL; } platform_set_drvdata(pdev, hv); errcode = wake_up_process(hv->thread); if (errcode != 1) dev_err(&pdev->dev, "failed to wake up thread\n"); dev_info(&pdev->dev, "id=%u irq=%d peer=%d num=%d size=%d\n", id, hv->ivc->irq, hv->ivc->peer_vmid, hv->ivc->nframes, hv->ivc->frame_size); return 0; } static int tegra_hv_wdt_remove(struct platform_device *pdev) { struct tegra_hv_wdt *hv = platform_get_drvdata(pdev); /* * The below cleanup function will block waiting for the * refresh kthread to exit (if it has already started running.) */ tegra_hv_wdt_cleanup(hv); devm_kfree(&pdev->dev, hv); platform_set_drvdata(pdev, NULL); return 0; } #ifdef CONFIG_PM_SLEEP static int tegra_hv_wdt_suspend(struct platform_device *pdev, pm_message_t state) { struct tegra_hv_wdt *hv = platform_get_drvdata(pdev); return tegra_hv_wdt_ping(&hv->wdt); } #endif static const struct of_device_id tegra_hv_wdt_match[] = { { .compatible = "nvidia,tegra-hv-wdt", }, {} }; static struct platform_driver tegra_hv_wdt_driver = { .probe = tegra_hv_wdt_probe, .remove = tegra_hv_wdt_remove, #ifdef CONFIG_PM_SLEEP .suspend = tegra_hv_wdt_suspend, #endif .driver = { .owner = THIS_MODULE, .name = "tegra_hv_wdt", .of_match_table = of_match_ptr(tegra_hv_wdt_match), }, }; static int __init tegra_hv_wdt_init(void) { return platform_driver_register(&tegra_hv_wdt_driver); } static void __exit tegra_hv_wdt_exit(void) { platform_driver_unregister(&tegra_hv_wdt_driver); } subsys_initcall(tegra_hv_wdt_init); module_exit(tegra_hv_wdt_exit); MODULE_AUTHOR("NVIDIA Corporation"); MODULE_DESCRIPTION("Tegra Hypervisor Watchdog Driver"); MODULE_LICENSE("GPL");