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