]>
Commit | Line | Data |
---|---|---|
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"); |