ca0bb079 |
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 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"); |