Merge tag 'armsoc-cleanup' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
[cascardo/linux.git] / drivers / watchdog / sun4v_wdt.c
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");