1 /* 2 * sun4v watchdog timer 3 * (c) Copyright 2016 Oracle Corporation 4 * 5 * Implement a simple watchdog driver using the built-in sun4v hypervisor 6 * watchdog support. If time expires, the hypervisor stops or bounces 7 * the guest domain. 8 * 9 * This program is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU General Public License 11 * as published by the Free Software Foundation; either version 12 * 2 of the License, or (at your option) any later version. 13 */ 14 15 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 16 17 #include <linux/errno.h> 18 #include <linux/init.h> 19 #include <linux/kernel.h> 20 #include <linux/module.h> 21 #include <linux/moduleparam.h> 22 #include <linux/watchdog.h> 23 #include <asm/hypervisor.h> 24 #include <asm/mdesc.h> 25 26 #define WDT_TIMEOUT 60 27 #define WDT_MAX_TIMEOUT 31536000 28 #define WDT_MIN_TIMEOUT 1 29 #define WDT_DEFAULT_RESOLUTION_MS 1000 /* 1 second */ 30 31 static unsigned int timeout; 32 module_param(timeout, uint, 0); 33 MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" 34 __MODULE_STRING(WDT_TIMEOUT) ")"); 35 36 static bool nowayout = WATCHDOG_NOWAYOUT; 37 module_param(nowayout, bool, S_IRUGO); 38 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 39 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 40 41 static int sun4v_wdt_stop(struct watchdog_device *wdd) 42 { 43 sun4v_mach_set_watchdog(0, NULL); 44 45 return 0; 46 } 47 48 static int sun4v_wdt_ping(struct watchdog_device *wdd) 49 { 50 int hverr; 51 52 /* 53 * HV watchdog timer will round up the timeout 54 * passed in to the nearest multiple of the 55 * watchdog resolution in milliseconds. 56 */ 57 hverr = sun4v_mach_set_watchdog(wdd->timeout * 1000, NULL); 58 if (hverr == HV_EINVAL) 59 return -EINVAL; 60 61 return 0; 62 } 63 64 static int sun4v_wdt_set_timeout(struct watchdog_device *wdd, 65 unsigned int timeout) 66 { 67 wdd->timeout = timeout; 68 69 return 0; 70 } 71 72 static const struct watchdog_info sun4v_wdt_ident = { 73 .options = WDIOF_SETTIMEOUT | 74 WDIOF_MAGICCLOSE | 75 WDIOF_KEEPALIVEPING, 76 .identity = "sun4v hypervisor watchdog", 77 .firmware_version = 0, 78 }; 79 80 static const struct watchdog_ops sun4v_wdt_ops = { 81 .owner = THIS_MODULE, 82 .start = sun4v_wdt_ping, 83 .stop = sun4v_wdt_stop, 84 .ping = sun4v_wdt_ping, 85 .set_timeout = sun4v_wdt_set_timeout, 86 }; 87 88 static struct watchdog_device wdd = { 89 .info = &sun4v_wdt_ident, 90 .ops = &sun4v_wdt_ops, 91 .min_timeout = WDT_MIN_TIMEOUT, 92 .max_timeout = WDT_MAX_TIMEOUT, 93 .timeout = WDT_TIMEOUT, 94 }; 95 96 static int __init sun4v_wdt_init(void) 97 { 98 struct mdesc_handle *handle; 99 u64 node; 100 const u64 *value; 101 int err = 0; 102 unsigned long major = 1, minor = 1; 103 104 /* 105 * There are 2 properties that can be set from the control 106 * domain for the watchdog. 107 * watchdog-resolution 108 * watchdog-max-timeout 109 * 110 * We can expect a handle to be returned otherwise something 111 * serious is wrong. Correct to return -ENODEV here. 112 */ 113 114 handle = mdesc_grab(); 115 if (!handle) 116 return -ENODEV; 117 118 node = mdesc_node_by_name(handle, MDESC_NODE_NULL, "platform"); 119 err = -ENODEV; 120 if (node == MDESC_NODE_NULL) 121 goto out_release; 122 123 /* 124 * This is a safe way to validate if we are on the right 125 * platform. 126 */ 127 if (sun4v_hvapi_register(HV_GRP_CORE, major, &minor)) 128 goto out_hv_unreg; 129 130 /* Allow value of watchdog-resolution up to 1s (default) */ 131 value = mdesc_get_property(handle, node, "watchdog-resolution", NULL); 132 err = -EINVAL; 133 if (value) { 134 if (*value == 0 || 135 *value > WDT_DEFAULT_RESOLUTION_MS) 136 goto out_hv_unreg; 137 } 138 139 value = mdesc_get_property(handle, node, "watchdog-max-timeout", NULL); 140 if (value) { 141 /* 142 * If the property value (in ms) is smaller than 143 * min_timeout, return -EINVAL. 144 */ 145 if (*value < wdd.min_timeout * 1000) 146 goto out_hv_unreg; 147 148 /* 149 * If the property value is smaller than 150 * default max_timeout then set watchdog max_timeout to 151 * the value of the property in seconds. 152 */ 153 if (*value < wdd.max_timeout * 1000) 154 wdd.max_timeout = *value / 1000; 155 } 156 157 watchdog_init_timeout(&wdd, timeout, NULL); 158 159 watchdog_set_nowayout(&wdd, nowayout); 160 161 err = watchdog_register_device(&wdd); 162 if (err) 163 goto out_hv_unreg; 164 165 pr_info("initialized (timeout=%ds, nowayout=%d)\n", 166 wdd.timeout, nowayout); 167 168 mdesc_release(handle); 169 170 return 0; 171 172 out_hv_unreg: 173 sun4v_hvapi_unregister(HV_GRP_CORE); 174 175 out_release: 176 mdesc_release(handle); 177 return err; 178 } 179 180 static void __exit sun4v_wdt_exit(void) 181 { 182 sun4v_hvapi_unregister(HV_GRP_CORE); 183 watchdog_unregister_device(&wdd); 184 } 185 186 module_init(sun4v_wdt_init); 187 module_exit(sun4v_wdt_exit); 188 189 MODULE_AUTHOR("Wim Coekaerts <wim.coekaerts@oracle.com>"); 190 MODULE_DESCRIPTION("sun4v watchdog driver"); 191 MODULE_LICENSE("GPL"); 192