xref: /openbmc/linux/drivers/watchdog/diag288_wdt.c (revision 9a87ffc99ec8eb8d35eed7c4f816d75f5cc9662e)
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