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