Merge remote-tracking branch 'remotes/powerpc/topic/ppc-kvm' into kvm-ppc-next
[linux-2.6-microblaze.git] / drivers / watchdog / sun4v_wdt.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
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.
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
76 static const struct watchdog_ops sun4v_wdt_ops = {
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");