109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2f7a94db4SPhilipp Hachtmann /*
3646f919eSPhilipp Hachtmann * Watchdog driver for z/VM and LPAR using the diag 288 interface.
4f7a94db4SPhilipp Hachtmann *
5f7a94db4SPhilipp Hachtmann * Under z/VM, expiration of the watchdog will send a "system restart" command
6f7a94db4SPhilipp Hachtmann * to CP.
7f7a94db4SPhilipp Hachtmann *
8646f919eSPhilipp Hachtmann * The command can be altered using the module parameter "cmd". This is
9646f919eSPhilipp Hachtmann * not recommended because it's only supported on z/VM but not whith LPAR.
10646f919eSPhilipp Hachtmann *
11646f919eSPhilipp Hachtmann * On LPAR, the watchdog will always trigger a system restart. the module
12646f919eSPhilipp Hachtmann * paramter cmd is meaningless here.
13646f919eSPhilipp Hachtmann *
14f7a94db4SPhilipp Hachtmann *
15f7a94db4SPhilipp Hachtmann * Copyright IBM Corp. 2004, 2013
16f7a94db4SPhilipp Hachtmann * Author(s): Arnd Bergmann (arndb@de.ibm.com)
17f7a94db4SPhilipp Hachtmann * Philipp Hachtmann (phacht@de.ibm.com)
18f7a94db4SPhilipp Hachtmann *
19f7a94db4SPhilipp Hachtmann */
20f7a94db4SPhilipp Hachtmann
21f7a94db4SPhilipp Hachtmann #define KMSG_COMPONENT "diag288_wdt"
22f7a94db4SPhilipp Hachtmann #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
23f7a94db4SPhilipp Hachtmann
24f7a94db4SPhilipp Hachtmann #include <linux/init.h>
25f7a94db4SPhilipp Hachtmann #include <linux/kernel.h>
26f7a94db4SPhilipp Hachtmann #include <linux/module.h>
27f7a94db4SPhilipp Hachtmann #include <linux/moduleparam.h>
28f7a94db4SPhilipp Hachtmann #include <linux/slab.h>
29f7a94db4SPhilipp Hachtmann #include <linux/watchdog.h>
30f7a94db4SPhilipp Hachtmann #include <asm/ebcdic.h>
311ec2772eSMartin Schwidefsky #include <asm/diag.h>
32f7a94db4SPhilipp Hachtmann #include <linux/io.h>
33f7a94db4SPhilipp Hachtmann
34f7a94db4SPhilipp Hachtmann #define MAX_CMDLEN 240
35f7a94db4SPhilipp Hachtmann #define DEFAULT_CMD "SYSTEM RESTART"
36f7a94db4SPhilipp Hachtmann
37f7a94db4SPhilipp Hachtmann #define MIN_INTERVAL 15 /* Minimal time supported by diag88 */
38f7a94db4SPhilipp Hachtmann #define MAX_INTERVAL 3600 /* One hour should be enough - pure estimation */
39f7a94db4SPhilipp Hachtmann
40f7a94db4SPhilipp Hachtmann #define WDT_DEFAULT_TIMEOUT 30
41f7a94db4SPhilipp Hachtmann
42f7a94db4SPhilipp Hachtmann /* Function codes - init, change, cancel */
43f7a94db4SPhilipp Hachtmann #define WDT_FUNC_INIT 0
44f7a94db4SPhilipp Hachtmann #define WDT_FUNC_CHANGE 1
45f7a94db4SPhilipp Hachtmann #define WDT_FUNC_CANCEL 2
46f7a94db4SPhilipp Hachtmann #define WDT_FUNC_CONCEAL 0x80000000
47f7a94db4SPhilipp Hachtmann
48646f919eSPhilipp Hachtmann /* Action codes for LPAR watchdog */
49646f919eSPhilipp Hachtmann #define LPARWDT_RESTART 0
50646f919eSPhilipp Hachtmann
51f7a94db4SPhilipp Hachtmann static char wdt_cmd[MAX_CMDLEN] = DEFAULT_CMD;
52f7a94db4SPhilipp Hachtmann static bool conceal_on;
53f7a94db4SPhilipp Hachtmann static bool nowayout_info = WATCHDOG_NOWAYOUT;
54f7a94db4SPhilipp Hachtmann
55f7a94db4SPhilipp Hachtmann MODULE_LICENSE("GPL");
56f7a94db4SPhilipp Hachtmann MODULE_AUTHOR("Arnd Bergmann <arndb@de.ibm.com>");
57f7a94db4SPhilipp Hachtmann MODULE_AUTHOR("Philipp Hachtmann <phacht@de.ibm.com>");
58f7a94db4SPhilipp Hachtmann
59f7a94db4SPhilipp Hachtmann MODULE_DESCRIPTION("System z diag288 Watchdog Timer");
60f7a94db4SPhilipp Hachtmann
61f7a94db4SPhilipp Hachtmann module_param_string(cmd, wdt_cmd, MAX_CMDLEN, 0644);
62f7a94db4SPhilipp Hachtmann MODULE_PARM_DESC(cmd, "CP command that is run when the watchdog triggers (z/VM only)");
63f7a94db4SPhilipp Hachtmann
64f7a94db4SPhilipp Hachtmann module_param_named(conceal, conceal_on, bool, 0644);
65f7a94db4SPhilipp Hachtmann MODULE_PARM_DESC(conceal, "Enable the CONCEAL CP option while the watchdog is active (z/VM only)");
66f7a94db4SPhilipp Hachtmann
67f7a94db4SPhilipp Hachtmann module_param_named(nowayout, nowayout_info, bool, 0444);
68f7a94db4SPhilipp Hachtmann MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default = CONFIG_WATCHDOG_NOWAYOUT)");
69f7a94db4SPhilipp Hachtmann
70f7a94db4SPhilipp Hachtmann MODULE_ALIAS("vmwatchdog");
71f7a94db4SPhilipp Hachtmann
72221f748aSAlexander Egorenkov static char *cmd_buf;
73221f748aSAlexander Egorenkov
diag288(unsigned int func,unsigned int timeout,unsigned long action,unsigned int len)74*20e6ce48SAlexander Egorenkov static int diag288(unsigned int func, unsigned int timeout,
75f7a94db4SPhilipp Hachtmann unsigned long action, unsigned int len)
76f7a94db4SPhilipp Hachtmann {
77c24def73SAlexander Egorenkov union register_pair r1 = { .even = func, .odd = timeout, };
78c24def73SAlexander Egorenkov union register_pair r3 = { .even = action, .odd = len, };
79f7a94db4SPhilipp Hachtmann int err;
80f7a94db4SPhilipp Hachtmann
8137900851SAlexander Egorenkov diag_stat_inc(DIAG_STAT_X288);
8237900851SAlexander Egorenkov
83f7a94db4SPhilipp Hachtmann err = -EINVAL;
84f7a94db4SPhilipp Hachtmann asm volatile(
85c24def73SAlexander Egorenkov " diag %[r1],%[r3],0x288\n"
86c24def73SAlexander Egorenkov "0: la %[err],0\n"
87f7a94db4SPhilipp Hachtmann "1:\n"
88f7a94db4SPhilipp Hachtmann EX_TABLE(0b, 1b)
89c24def73SAlexander Egorenkov : [err] "+d" (err)
90c24def73SAlexander Egorenkov : [r1] "d" (r1.pair), [r3] "d" (r3.pair)
91c24def73SAlexander Egorenkov : "cc", "memory");
92f7a94db4SPhilipp Hachtmann return err;
93f7a94db4SPhilipp Hachtmann }
94f7a94db4SPhilipp Hachtmann
diag288_str(unsigned int func,unsigned int timeout,char * cmd)95*20e6ce48SAlexander Egorenkov static int diag288_str(unsigned int func, unsigned int timeout, char *cmd)
96f7a94db4SPhilipp Hachtmann {
97221f748aSAlexander Egorenkov ssize_t len;
98221f748aSAlexander Egorenkov
99221f748aSAlexander Egorenkov len = strscpy(cmd_buf, cmd, MAX_CMDLEN);
100221f748aSAlexander Egorenkov if (len < 0)
101221f748aSAlexander Egorenkov return len;
102221f748aSAlexander Egorenkov ASCEBC(cmd_buf, MAX_CMDLEN);
103221f748aSAlexander Egorenkov EBC_TOUPPER(cmd_buf, MAX_CMDLEN);
104221f748aSAlexander Egorenkov
105*20e6ce48SAlexander Egorenkov return diag288(func, timeout, virt_to_phys(cmd_buf), len);
106646f919eSPhilipp Hachtmann }
107646f919eSPhilipp Hachtmann
wdt_start(struct watchdog_device * dev)108f7a94db4SPhilipp Hachtmann static int wdt_start(struct watchdog_device *dev)
109f7a94db4SPhilipp Hachtmann {
110f7a94db4SPhilipp Hachtmann int ret;
111f7a94db4SPhilipp Hachtmann unsigned int func;
112f7a94db4SPhilipp Hachtmann
113f7a94db4SPhilipp Hachtmann if (MACHINE_IS_VM) {
114f7a94db4SPhilipp Hachtmann func = conceal_on ? (WDT_FUNC_INIT | WDT_FUNC_CONCEAL)
115f7a94db4SPhilipp Hachtmann : WDT_FUNC_INIT;
116*20e6ce48SAlexander Egorenkov ret = diag288_str(func, dev->timeout, wdt_cmd);
117f7a94db4SPhilipp Hachtmann WARN_ON(ret != 0);
118b2527d20SXu Wang } else {
119*20e6ce48SAlexander Egorenkov ret = diag288(WDT_FUNC_INIT, dev->timeout, LPARWDT_RESTART, 0);
120646f919eSPhilipp Hachtmann }
121646f919eSPhilipp Hachtmann
122f7a94db4SPhilipp Hachtmann if (ret) {
123f7a94db4SPhilipp Hachtmann pr_err("The watchdog cannot be activated\n");
124f7a94db4SPhilipp Hachtmann return ret;
125f7a94db4SPhilipp Hachtmann }
126f7a94db4SPhilipp Hachtmann return 0;
127f7a94db4SPhilipp Hachtmann }
128f7a94db4SPhilipp Hachtmann
wdt_stop(struct watchdog_device * dev)129f7a94db4SPhilipp Hachtmann static int wdt_stop(struct watchdog_device *dev)
130f7a94db4SPhilipp Hachtmann {
131*20e6ce48SAlexander Egorenkov return diag288(WDT_FUNC_CANCEL, 0, 0, 0);
132f7a94db4SPhilipp Hachtmann }
133f7a94db4SPhilipp Hachtmann
wdt_ping(struct watchdog_device * dev)134f7a94db4SPhilipp Hachtmann static int wdt_ping(struct watchdog_device *dev)
135f7a94db4SPhilipp Hachtmann {
136f7a94db4SPhilipp Hachtmann int ret;
137f7a94db4SPhilipp Hachtmann unsigned int func;
138f7a94db4SPhilipp Hachtmann
139f7a94db4SPhilipp Hachtmann if (MACHINE_IS_VM) {
140f7a94db4SPhilipp Hachtmann /*
141f7a94db4SPhilipp Hachtmann * It seems to be ok to z/VM to use the init function to
142646f919eSPhilipp Hachtmann * retrigger the watchdog. On LPAR WDT_FUNC_CHANGE must
143646f919eSPhilipp Hachtmann * be used when the watchdog is running.
144f7a94db4SPhilipp Hachtmann */
145f7a94db4SPhilipp Hachtmann func = conceal_on ? (WDT_FUNC_INIT | WDT_FUNC_CONCEAL)
146f7a94db4SPhilipp Hachtmann : WDT_FUNC_INIT;
147f7a94db4SPhilipp Hachtmann
148*20e6ce48SAlexander Egorenkov ret = diag288_str(func, dev->timeout, wdt_cmd);
149f7a94db4SPhilipp Hachtmann WARN_ON(ret != 0);
150b2527d20SXu Wang } else {
151*20e6ce48SAlexander Egorenkov ret = diag288(WDT_FUNC_CHANGE, dev->timeout, 0, 0);
152b2527d20SXu Wang }
153646f919eSPhilipp Hachtmann
154f7a94db4SPhilipp Hachtmann if (ret)
155f7a94db4SPhilipp Hachtmann pr_err("The watchdog timer cannot be started or reset\n");
156f7a94db4SPhilipp Hachtmann return ret;
157f7a94db4SPhilipp Hachtmann }
158f7a94db4SPhilipp Hachtmann
wdt_set_timeout(struct watchdog_device * dev,unsigned int new_to)159f7a94db4SPhilipp Hachtmann static int wdt_set_timeout(struct watchdog_device * dev, unsigned int new_to)
160f7a94db4SPhilipp Hachtmann {
161f7a94db4SPhilipp Hachtmann dev->timeout = new_to;
162f7a94db4SPhilipp Hachtmann return wdt_ping(dev);
163f7a94db4SPhilipp Hachtmann }
164f7a94db4SPhilipp Hachtmann
165b893e344SBhumika Goyal static const struct watchdog_ops wdt_ops = {
166f7a94db4SPhilipp Hachtmann .owner = THIS_MODULE,
167f7a94db4SPhilipp Hachtmann .start = wdt_start,
168f7a94db4SPhilipp Hachtmann .stop = wdt_stop,
169f7a94db4SPhilipp Hachtmann .ping = wdt_ping,
170f7a94db4SPhilipp Hachtmann .set_timeout = wdt_set_timeout,
171f7a94db4SPhilipp Hachtmann };
172f7a94db4SPhilipp Hachtmann
173323edb2eSJulia Lawall static const struct watchdog_info wdt_info = {
1749ec6cb80SXu Wang .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
175f7a94db4SPhilipp Hachtmann .firmware_version = 0,
176f7a94db4SPhilipp Hachtmann .identity = "z Watchdog",
177f7a94db4SPhilipp Hachtmann };
178f7a94db4SPhilipp Hachtmann
179f7a94db4SPhilipp Hachtmann static struct watchdog_device wdt_dev = {
180f7a94db4SPhilipp Hachtmann .parent = NULL,
181f7a94db4SPhilipp Hachtmann .info = &wdt_info,
182f7a94db4SPhilipp Hachtmann .ops = &wdt_ops,
183f7a94db4SPhilipp Hachtmann .bootstatus = 0,
184f7a94db4SPhilipp Hachtmann .timeout = WDT_DEFAULT_TIMEOUT,
185f7a94db4SPhilipp Hachtmann .min_timeout = MIN_INTERVAL,
186f7a94db4SPhilipp Hachtmann .max_timeout = MAX_INTERVAL,
187f7a94db4SPhilipp Hachtmann };
188f7a94db4SPhilipp Hachtmann
diag288_init(void)189f7a94db4SPhilipp Hachtmann static int __init diag288_init(void)
190f7a94db4SPhilipp Hachtmann {
191f7a94db4SPhilipp Hachtmann int ret;
192f7a94db4SPhilipp Hachtmann
193f7a94db4SPhilipp Hachtmann watchdog_set_nowayout(&wdt_dev, nowayout_info);
194f7a94db4SPhilipp Hachtmann
195f7a94db4SPhilipp Hachtmann if (MACHINE_IS_VM) {
196221f748aSAlexander Egorenkov cmd_buf = kmalloc(MAX_CMDLEN, GFP_KERNEL);
197221f748aSAlexander Egorenkov if (!cmd_buf) {
198fe8973a3SAlexander Egorenkov pr_err("The watchdog cannot be initialized\n");
199fe8973a3SAlexander Egorenkov return -ENOMEM;
200fe8973a3SAlexander Egorenkov }
201221f748aSAlexander Egorenkov
202*20e6ce48SAlexander Egorenkov ret = diag288_str(WDT_FUNC_INIT, MIN_INTERVAL, "BEGIN");
203fe8973a3SAlexander Egorenkov if (ret != 0) {
204f7a94db4SPhilipp Hachtmann pr_err("The watchdog cannot be initialized\n");
205221f748aSAlexander Egorenkov kfree(cmd_buf);
206f7a94db4SPhilipp Hachtmann return -EINVAL;
207f7a94db4SPhilipp Hachtmann }
208b2527d20SXu Wang } else {
209*20e6ce48SAlexander Egorenkov if (diag288(WDT_FUNC_INIT, WDT_DEFAULT_TIMEOUT,
210*20e6ce48SAlexander Egorenkov LPARWDT_RESTART, 0)) {
211646f919eSPhilipp Hachtmann pr_err("The watchdog cannot be initialized\n");
212646f919eSPhilipp Hachtmann return -EINVAL;
213646f919eSPhilipp Hachtmann }
214f7a94db4SPhilipp Hachtmann }
215f7a94db4SPhilipp Hachtmann
216*20e6ce48SAlexander Egorenkov if (diag288(WDT_FUNC_CANCEL, 0, 0, 0)) {
217f7a94db4SPhilipp Hachtmann pr_err("The watchdog cannot be deactivated\n");
218f7a94db4SPhilipp Hachtmann return -EINVAL;
219f7a94db4SPhilipp Hachtmann }
220f7a94db4SPhilipp Hachtmann
221f102dd16SAlexander Egorenkov return watchdog_register_device(&wdt_dev);
222f7a94db4SPhilipp Hachtmann }
223f7a94db4SPhilipp Hachtmann
diag288_exit(void)224f7a94db4SPhilipp Hachtmann static void __exit diag288_exit(void)
225f7a94db4SPhilipp Hachtmann {
226f7a94db4SPhilipp Hachtmann watchdog_unregister_device(&wdt_dev);
227221f748aSAlexander Egorenkov kfree(cmd_buf);
228f7a94db4SPhilipp Hachtmann }
229f7a94db4SPhilipp Hachtmann
230f7a94db4SPhilipp Hachtmann module_init(diag288_init);
231f7a94db4SPhilipp Hachtmann module_exit(diag288_exit);
232