162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * ipmi_watchdog.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * A watchdog timer based upon the IPMI interface.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author: MontaVista Software, Inc.
862306a36Sopenharmony_ci *         Corey Minyard <minyard@mvista.com>
962306a36Sopenharmony_ci *         source@mvista.com
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * Copyright 2002 MontaVista Software Inc.
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define pr_fmt(fmt) "IPMI Watchdog: " fmt
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <linux/moduleparam.h>
1862306a36Sopenharmony_ci#include <linux/ipmi.h>
1962306a36Sopenharmony_ci#include <linux/ipmi_smi.h>
2062306a36Sopenharmony_ci#include <linux/mutex.h>
2162306a36Sopenharmony_ci#include <linux/watchdog.h>
2262306a36Sopenharmony_ci#include <linux/miscdevice.h>
2362306a36Sopenharmony_ci#include <linux/init.h>
2462306a36Sopenharmony_ci#include <linux/completion.h>
2562306a36Sopenharmony_ci#include <linux/kdebug.h>
2662306a36Sopenharmony_ci#include <linux/kstrtox.h>
2762306a36Sopenharmony_ci#include <linux/rwsem.h>
2862306a36Sopenharmony_ci#include <linux/errno.h>
2962306a36Sopenharmony_ci#include <linux/uaccess.h>
3062306a36Sopenharmony_ci#include <linux/notifier.h>
3162306a36Sopenharmony_ci#include <linux/nmi.h>
3262306a36Sopenharmony_ci#include <linux/reboot.h>
3362306a36Sopenharmony_ci#include <linux/wait.h>
3462306a36Sopenharmony_ci#include <linux/poll.h>
3562306a36Sopenharmony_ci#include <linux/string.h>
3662306a36Sopenharmony_ci#include <linux/ctype.h>
3762306a36Sopenharmony_ci#include <linux/delay.h>
3862306a36Sopenharmony_ci#include <linux/atomic.h>
3962306a36Sopenharmony_ci#include <linux/sched/signal.h>
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#ifdef CONFIG_X86
4262306a36Sopenharmony_ci/*
4362306a36Sopenharmony_ci * This is ugly, but I've determined that x86 is the only architecture
4462306a36Sopenharmony_ci * that can reasonably support the IPMI NMI watchdog timeout at this
4562306a36Sopenharmony_ci * time.  If another architecture adds this capability somehow, it
4662306a36Sopenharmony_ci * will have to be a somewhat different mechanism and I have no idea
4762306a36Sopenharmony_ci * how it will work.  So in the unlikely event that another
4862306a36Sopenharmony_ci * architecture supports this, we can figure out a good generic
4962306a36Sopenharmony_ci * mechanism for it at that time.
5062306a36Sopenharmony_ci */
5162306a36Sopenharmony_ci#include <asm/kdebug.h>
5262306a36Sopenharmony_ci#include <asm/nmi.h>
5362306a36Sopenharmony_ci#define HAVE_DIE_NMI
5462306a36Sopenharmony_ci#endif
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci/*
5762306a36Sopenharmony_ci * The IPMI command/response information for the watchdog timer.
5862306a36Sopenharmony_ci */
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci/* values for byte 1 of the set command, byte 2 of the get response. */
6162306a36Sopenharmony_ci#define WDOG_DONT_LOG		(1 << 7)
6262306a36Sopenharmony_ci#define WDOG_DONT_STOP_ON_SET	(1 << 6)
6362306a36Sopenharmony_ci#define WDOG_SET_TIMER_USE(byte, use) \
6462306a36Sopenharmony_ci	byte = ((byte) & 0xf8) | ((use) & 0x7)
6562306a36Sopenharmony_ci#define WDOG_GET_TIMER_USE(byte) ((byte) & 0x7)
6662306a36Sopenharmony_ci#define WDOG_TIMER_USE_BIOS_FRB2	1
6762306a36Sopenharmony_ci#define WDOG_TIMER_USE_BIOS_POST	2
6862306a36Sopenharmony_ci#define WDOG_TIMER_USE_OS_LOAD		3
6962306a36Sopenharmony_ci#define WDOG_TIMER_USE_SMS_OS		4
7062306a36Sopenharmony_ci#define WDOG_TIMER_USE_OEM		5
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci/* values for byte 2 of the set command, byte 3 of the get response. */
7362306a36Sopenharmony_ci#define WDOG_SET_PRETIMEOUT_ACT(byte, use) \
7462306a36Sopenharmony_ci	byte = ((byte) & 0x8f) | (((use) & 0x7) << 4)
7562306a36Sopenharmony_ci#define WDOG_GET_PRETIMEOUT_ACT(byte) (((byte) >> 4) & 0x7)
7662306a36Sopenharmony_ci#define WDOG_PRETIMEOUT_NONE		0
7762306a36Sopenharmony_ci#define WDOG_PRETIMEOUT_SMI		1
7862306a36Sopenharmony_ci#define WDOG_PRETIMEOUT_NMI		2
7962306a36Sopenharmony_ci#define WDOG_PRETIMEOUT_MSG_INT		3
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci/* Operations that can be performed on a pretimout. */
8262306a36Sopenharmony_ci#define WDOG_PREOP_NONE		0
8362306a36Sopenharmony_ci#define WDOG_PREOP_PANIC	1
8462306a36Sopenharmony_ci/* Cause data to be available to read.  Doesn't work in NMI mode. */
8562306a36Sopenharmony_ci#define WDOG_PREOP_GIVE_DATA	2
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci/* Actions to perform on a full timeout. */
8862306a36Sopenharmony_ci#define WDOG_SET_TIMEOUT_ACT(byte, use) \
8962306a36Sopenharmony_ci	byte = ((byte) & 0xf8) | ((use) & 0x7)
9062306a36Sopenharmony_ci#define WDOG_GET_TIMEOUT_ACT(byte) ((byte) & 0x7)
9162306a36Sopenharmony_ci#define WDOG_TIMEOUT_NONE		0
9262306a36Sopenharmony_ci#define WDOG_TIMEOUT_RESET		1
9362306a36Sopenharmony_ci#define WDOG_TIMEOUT_POWER_DOWN		2
9462306a36Sopenharmony_ci#define WDOG_TIMEOUT_POWER_CYCLE	3
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci/*
9762306a36Sopenharmony_ci * Byte 3 of the get command, byte 4 of the get response is the
9862306a36Sopenharmony_ci * pre-timeout in seconds.
9962306a36Sopenharmony_ci */
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci/* Bits for setting byte 4 of the set command, byte 5 of the get response. */
10262306a36Sopenharmony_ci#define WDOG_EXPIRE_CLEAR_BIOS_FRB2	(1 << 1)
10362306a36Sopenharmony_ci#define WDOG_EXPIRE_CLEAR_BIOS_POST	(1 << 2)
10462306a36Sopenharmony_ci#define WDOG_EXPIRE_CLEAR_OS_LOAD	(1 << 3)
10562306a36Sopenharmony_ci#define WDOG_EXPIRE_CLEAR_SMS_OS	(1 << 4)
10662306a36Sopenharmony_ci#define WDOG_EXPIRE_CLEAR_OEM		(1 << 5)
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/*
10962306a36Sopenharmony_ci * Setting/getting the watchdog timer value.  This is for bytes 5 and
11062306a36Sopenharmony_ci * 6 (the timeout time) of the set command, and bytes 6 and 7 (the
11162306a36Sopenharmony_ci * timeout time) and 8 and 9 (the current countdown value) of the
11262306a36Sopenharmony_ci * response.  The timeout value is given in seconds (in the command it
11362306a36Sopenharmony_ci * is 100ms intervals).
11462306a36Sopenharmony_ci */
11562306a36Sopenharmony_ci#define WDOG_SET_TIMEOUT(byte1, byte2, val) \
11662306a36Sopenharmony_ci	(byte1) = (((val) * 10) & 0xff), (byte2) = (((val) * 10) >> 8)
11762306a36Sopenharmony_ci#define WDOG_GET_TIMEOUT(byte1, byte2) \
11862306a36Sopenharmony_ci	(((byte1) | ((byte2) << 8)) / 10)
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci#define IPMI_WDOG_RESET_TIMER		0x22
12162306a36Sopenharmony_ci#define IPMI_WDOG_SET_TIMER		0x24
12262306a36Sopenharmony_ci#define IPMI_WDOG_GET_TIMER		0x25
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci#define IPMI_WDOG_TIMER_NOT_INIT_RESP	0x80
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic DEFINE_MUTEX(ipmi_watchdog_mutex);
12762306a36Sopenharmony_cistatic bool nowayout = WATCHDOG_NOWAYOUT;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic struct ipmi_user *watchdog_user;
13062306a36Sopenharmony_cistatic int watchdog_ifnum;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci/* Default the timeout to 10 seconds. */
13362306a36Sopenharmony_cistatic int timeout = 10;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci/* The pre-timeout is disabled by default. */
13662306a36Sopenharmony_cistatic int pretimeout;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci/* Default timeout to set on panic */
13962306a36Sopenharmony_cistatic int panic_wdt_timeout = 255;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci/* Default action is to reset the board on a timeout. */
14262306a36Sopenharmony_cistatic unsigned char action_val = WDOG_TIMEOUT_RESET;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic char action[16] = "reset";
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cistatic unsigned char preaction_val = WDOG_PRETIMEOUT_NONE;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic char preaction[16] = "pre_none";
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic unsigned char preop_val = WDOG_PREOP_NONE;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic char preop[16] = "preop_none";
15362306a36Sopenharmony_cistatic DEFINE_SPINLOCK(ipmi_read_lock);
15462306a36Sopenharmony_cistatic char data_to_read;
15562306a36Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(read_q);
15662306a36Sopenharmony_cistatic struct fasync_struct *fasync_q;
15762306a36Sopenharmony_cistatic atomic_t pretimeout_since_last_heartbeat;
15862306a36Sopenharmony_cistatic char expect_close;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic int ifnum_to_use = -1;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci/* Parameters to ipmi_set_timeout */
16362306a36Sopenharmony_ci#define IPMI_SET_TIMEOUT_NO_HB			0
16462306a36Sopenharmony_ci#define IPMI_SET_TIMEOUT_HB_IF_NECESSARY	1
16562306a36Sopenharmony_ci#define IPMI_SET_TIMEOUT_FORCE_HB		2
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic int ipmi_set_timeout(int do_heartbeat);
16862306a36Sopenharmony_cistatic void ipmi_register_watchdog(int ipmi_intf);
16962306a36Sopenharmony_cistatic void ipmi_unregister_watchdog(int ipmi_intf);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci/*
17262306a36Sopenharmony_ci * If true, the driver will start running as soon as it is configured
17362306a36Sopenharmony_ci * and ready.
17462306a36Sopenharmony_ci */
17562306a36Sopenharmony_cistatic int start_now;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic int set_param_timeout(const char *val, const struct kernel_param *kp)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	char *endp;
18062306a36Sopenharmony_ci	int  l;
18162306a36Sopenharmony_ci	int  rv = 0;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	if (!val)
18462306a36Sopenharmony_ci		return -EINVAL;
18562306a36Sopenharmony_ci	l = simple_strtoul(val, &endp, 0);
18662306a36Sopenharmony_ci	if (endp == val)
18762306a36Sopenharmony_ci		return -EINVAL;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	*((int *)kp->arg) = l;
19062306a36Sopenharmony_ci	if (watchdog_user)
19162306a36Sopenharmony_ci		rv = ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	return rv;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic const struct kernel_param_ops param_ops_timeout = {
19762306a36Sopenharmony_ci	.set = set_param_timeout,
19862306a36Sopenharmony_ci	.get = param_get_int,
19962306a36Sopenharmony_ci};
20062306a36Sopenharmony_ci#define param_check_timeout param_check_int
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_citypedef int (*action_fn)(const char *intval, char *outval);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic int action_op(const char *inval, char *outval);
20562306a36Sopenharmony_cistatic int preaction_op(const char *inval, char *outval);
20662306a36Sopenharmony_cistatic int preop_op(const char *inval, char *outval);
20762306a36Sopenharmony_cistatic void check_parms(void);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic int set_param_str(const char *val, const struct kernel_param *kp)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	action_fn  fn = (action_fn) kp->arg;
21262306a36Sopenharmony_ci	int        rv = 0;
21362306a36Sopenharmony_ci	char       valcp[16];
21462306a36Sopenharmony_ci	char       *s;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	strscpy(valcp, val, 16);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	s = strstrip(valcp);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	rv = fn(s, NULL);
22162306a36Sopenharmony_ci	if (rv)
22262306a36Sopenharmony_ci		goto out;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	check_parms();
22562306a36Sopenharmony_ci	if (watchdog_user)
22662306a36Sopenharmony_ci		rv = ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci out:
22962306a36Sopenharmony_ci	return rv;
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic int get_param_str(char *buffer, const struct kernel_param *kp)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	action_fn fn = (action_fn) kp->arg;
23562306a36Sopenharmony_ci	int rv, len;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	rv = fn(NULL, buffer);
23862306a36Sopenharmony_ci	if (rv)
23962306a36Sopenharmony_ci		return rv;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	len = strlen(buffer);
24262306a36Sopenharmony_ci	buffer[len++] = '\n';
24362306a36Sopenharmony_ci	buffer[len] = 0;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	return len;
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic int set_param_wdog_ifnum(const char *val, const struct kernel_param *kp)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	int rv = param_set_int(val, kp);
25262306a36Sopenharmony_ci	if (rv)
25362306a36Sopenharmony_ci		return rv;
25462306a36Sopenharmony_ci	if ((ifnum_to_use < 0) || (ifnum_to_use == watchdog_ifnum))
25562306a36Sopenharmony_ci		return 0;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	ipmi_unregister_watchdog(watchdog_ifnum);
25862306a36Sopenharmony_ci	ipmi_register_watchdog(ifnum_to_use);
25962306a36Sopenharmony_ci	return 0;
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_cistatic const struct kernel_param_ops param_ops_wdog_ifnum = {
26362306a36Sopenharmony_ci	.set = set_param_wdog_ifnum,
26462306a36Sopenharmony_ci	.get = param_get_int,
26562306a36Sopenharmony_ci};
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci#define param_check_wdog_ifnum param_check_int
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_cistatic const struct kernel_param_ops param_ops_str = {
27062306a36Sopenharmony_ci	.set = set_param_str,
27162306a36Sopenharmony_ci	.get = get_param_str,
27262306a36Sopenharmony_ci};
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cimodule_param(ifnum_to_use, wdog_ifnum, 0644);
27562306a36Sopenharmony_ciMODULE_PARM_DESC(ifnum_to_use, "The interface number to use for the watchdog "
27662306a36Sopenharmony_ci		 "timer.  Setting to -1 defaults to the first registered "
27762306a36Sopenharmony_ci		 "interface");
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_cimodule_param(timeout, timeout, 0644);
28062306a36Sopenharmony_ciMODULE_PARM_DESC(timeout, "Timeout value in seconds.");
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_cimodule_param(pretimeout, timeout, 0644);
28362306a36Sopenharmony_ciMODULE_PARM_DESC(pretimeout, "Pretimeout value in seconds.");
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_cimodule_param(panic_wdt_timeout, timeout, 0644);
28662306a36Sopenharmony_ciMODULE_PARM_DESC(panic_wdt_timeout, "Timeout value on kernel panic in seconds.");
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cimodule_param_cb(action, &param_ops_str, action_op, 0644);
28962306a36Sopenharmony_ciMODULE_PARM_DESC(action, "Timeout action. One of: "
29062306a36Sopenharmony_ci		 "reset, none, power_cycle, power_off.");
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_cimodule_param_cb(preaction, &param_ops_str, preaction_op, 0644);
29362306a36Sopenharmony_ciMODULE_PARM_DESC(preaction, "Pretimeout action.  One of: "
29462306a36Sopenharmony_ci		 "pre_none, pre_smi, pre_nmi, pre_int.");
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_cimodule_param_cb(preop, &param_ops_str, preop_op, 0644);
29762306a36Sopenharmony_ciMODULE_PARM_DESC(preop, "Pretimeout driver operation.  One of: "
29862306a36Sopenharmony_ci		 "preop_none, preop_panic, preop_give_data.");
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_cimodule_param(start_now, int, 0444);
30162306a36Sopenharmony_ciMODULE_PARM_DESC(start_now, "Set to 1 to start the watchdog as"
30262306a36Sopenharmony_ci		 "soon as the driver is loaded.");
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_cimodule_param(nowayout, bool, 0644);
30562306a36Sopenharmony_ciMODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
30662306a36Sopenharmony_ci		 "(default=CONFIG_WATCHDOG_NOWAYOUT)");
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci/* Default state of the timer. */
30962306a36Sopenharmony_cistatic unsigned char ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci/* Is someone using the watchdog?  Only one user is allowed. */
31262306a36Sopenharmony_cistatic unsigned long ipmi_wdog_open;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci/*
31562306a36Sopenharmony_ci * If set to 1, the heartbeat command will set the state to reset and
31662306a36Sopenharmony_ci * start the timer.  The timer doesn't normally run when the driver is
31762306a36Sopenharmony_ci * first opened until the heartbeat is set the first time, this
31862306a36Sopenharmony_ci * variable is used to accomplish this.
31962306a36Sopenharmony_ci */
32062306a36Sopenharmony_cistatic int ipmi_start_timer_on_heartbeat;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci/* IPMI version of the BMC. */
32362306a36Sopenharmony_cistatic unsigned char ipmi_version_major;
32462306a36Sopenharmony_cistatic unsigned char ipmi_version_minor;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci/* If a pretimeout occurs, this is used to allow only one panic to happen. */
32762306a36Sopenharmony_cistatic atomic_t preop_panic_excl = ATOMIC_INIT(-1);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci#ifdef HAVE_DIE_NMI
33062306a36Sopenharmony_cistatic int testing_nmi;
33162306a36Sopenharmony_cistatic int nmi_handler_registered;
33262306a36Sopenharmony_ci#endif
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_cistatic int __ipmi_heartbeat(void);
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci/*
33762306a36Sopenharmony_ci * We use a mutex to make sure that only one thing can send a set a
33862306a36Sopenharmony_ci * message at one time.  The mutex is claimed when a message is sent
33962306a36Sopenharmony_ci * and freed when both the send and receive messages are free.
34062306a36Sopenharmony_ci */
34162306a36Sopenharmony_cistatic atomic_t msg_tofree = ATOMIC_INIT(0);
34262306a36Sopenharmony_cistatic DECLARE_COMPLETION(msg_wait);
34362306a36Sopenharmony_cistatic void msg_free_smi(struct ipmi_smi_msg *msg)
34462306a36Sopenharmony_ci{
34562306a36Sopenharmony_ci	if (atomic_dec_and_test(&msg_tofree)) {
34662306a36Sopenharmony_ci		if (!oops_in_progress)
34762306a36Sopenharmony_ci			complete(&msg_wait);
34862306a36Sopenharmony_ci	}
34962306a36Sopenharmony_ci}
35062306a36Sopenharmony_cistatic void msg_free_recv(struct ipmi_recv_msg *msg)
35162306a36Sopenharmony_ci{
35262306a36Sopenharmony_ci	if (atomic_dec_and_test(&msg_tofree)) {
35362306a36Sopenharmony_ci		if (!oops_in_progress)
35462306a36Sopenharmony_ci			complete(&msg_wait);
35562306a36Sopenharmony_ci	}
35662306a36Sopenharmony_ci}
35762306a36Sopenharmony_cistatic struct ipmi_smi_msg smi_msg = INIT_IPMI_SMI_MSG(msg_free_smi);
35862306a36Sopenharmony_cistatic struct ipmi_recv_msg recv_msg = INIT_IPMI_RECV_MSG(msg_free_recv);
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_cistatic int __ipmi_set_timeout(struct ipmi_smi_msg  *smi_msg,
36162306a36Sopenharmony_ci			      struct ipmi_recv_msg *recv_msg,
36262306a36Sopenharmony_ci			      int                  *send_heartbeat_now)
36362306a36Sopenharmony_ci{
36462306a36Sopenharmony_ci	struct kernel_ipmi_msg            msg;
36562306a36Sopenharmony_ci	unsigned char                     data[6];
36662306a36Sopenharmony_ci	int                               rv;
36762306a36Sopenharmony_ci	struct ipmi_system_interface_addr addr;
36862306a36Sopenharmony_ci	int                               hbnow = 0;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	data[0] = 0;
37262306a36Sopenharmony_ci	WDOG_SET_TIMER_USE(data[0], WDOG_TIMER_USE_SMS_OS);
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	if (ipmi_watchdog_state != WDOG_TIMEOUT_NONE) {
37562306a36Sopenharmony_ci		if ((ipmi_version_major > 1) ||
37662306a36Sopenharmony_ci		    ((ipmi_version_major == 1) && (ipmi_version_minor >= 5))) {
37762306a36Sopenharmony_ci			/* This is an IPMI 1.5-only feature. */
37862306a36Sopenharmony_ci			data[0] |= WDOG_DONT_STOP_ON_SET;
37962306a36Sopenharmony_ci		} else {
38062306a36Sopenharmony_ci			/*
38162306a36Sopenharmony_ci			 * In ipmi 1.0, setting the timer stops the watchdog, we
38262306a36Sopenharmony_ci			 * need to start it back up again.
38362306a36Sopenharmony_ci			 */
38462306a36Sopenharmony_ci			hbnow = 1;
38562306a36Sopenharmony_ci		}
38662306a36Sopenharmony_ci	}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	data[1] = 0;
38962306a36Sopenharmony_ci	WDOG_SET_TIMEOUT_ACT(data[1], ipmi_watchdog_state);
39062306a36Sopenharmony_ci	if ((pretimeout > 0) && (ipmi_watchdog_state != WDOG_TIMEOUT_NONE)) {
39162306a36Sopenharmony_ci	    WDOG_SET_PRETIMEOUT_ACT(data[1], preaction_val);
39262306a36Sopenharmony_ci	    data[2] = pretimeout;
39362306a36Sopenharmony_ci	} else {
39462306a36Sopenharmony_ci	    WDOG_SET_PRETIMEOUT_ACT(data[1], WDOG_PRETIMEOUT_NONE);
39562306a36Sopenharmony_ci	    data[2] = 0; /* No pretimeout. */
39662306a36Sopenharmony_ci	}
39762306a36Sopenharmony_ci	data[3] = 0;
39862306a36Sopenharmony_ci	WDOG_SET_TIMEOUT(data[4], data[5], timeout);
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
40162306a36Sopenharmony_ci	addr.channel = IPMI_BMC_CHANNEL;
40262306a36Sopenharmony_ci	addr.lun = 0;
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	msg.netfn = 0x06;
40562306a36Sopenharmony_ci	msg.cmd = IPMI_WDOG_SET_TIMER;
40662306a36Sopenharmony_ci	msg.data = data;
40762306a36Sopenharmony_ci	msg.data_len = sizeof(data);
40862306a36Sopenharmony_ci	rv = ipmi_request_supply_msgs(watchdog_user,
40962306a36Sopenharmony_ci				      (struct ipmi_addr *) &addr,
41062306a36Sopenharmony_ci				      0,
41162306a36Sopenharmony_ci				      &msg,
41262306a36Sopenharmony_ci				      NULL,
41362306a36Sopenharmony_ci				      smi_msg,
41462306a36Sopenharmony_ci				      recv_msg,
41562306a36Sopenharmony_ci				      1);
41662306a36Sopenharmony_ci	if (rv)
41762306a36Sopenharmony_ci		pr_warn("set timeout error: %d\n", rv);
41862306a36Sopenharmony_ci	else if (send_heartbeat_now)
41962306a36Sopenharmony_ci		*send_heartbeat_now = hbnow;
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	return rv;
42262306a36Sopenharmony_ci}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_cistatic int _ipmi_set_timeout(int do_heartbeat)
42562306a36Sopenharmony_ci{
42662306a36Sopenharmony_ci	int send_heartbeat_now;
42762306a36Sopenharmony_ci	int rv;
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	if (!watchdog_user)
43062306a36Sopenharmony_ci		return -ENODEV;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	atomic_set(&msg_tofree, 2);
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	rv = __ipmi_set_timeout(&smi_msg,
43562306a36Sopenharmony_ci				&recv_msg,
43662306a36Sopenharmony_ci				&send_heartbeat_now);
43762306a36Sopenharmony_ci	if (rv) {
43862306a36Sopenharmony_ci		atomic_set(&msg_tofree, 0);
43962306a36Sopenharmony_ci		return rv;
44062306a36Sopenharmony_ci	}
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	wait_for_completion(&msg_wait);
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	if ((do_heartbeat == IPMI_SET_TIMEOUT_FORCE_HB)
44562306a36Sopenharmony_ci		|| ((send_heartbeat_now)
44662306a36Sopenharmony_ci		    && (do_heartbeat == IPMI_SET_TIMEOUT_HB_IF_NECESSARY)))
44762306a36Sopenharmony_ci		rv = __ipmi_heartbeat();
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	return rv;
45062306a36Sopenharmony_ci}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_cistatic int ipmi_set_timeout(int do_heartbeat)
45362306a36Sopenharmony_ci{
45462306a36Sopenharmony_ci	int rv;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	mutex_lock(&ipmi_watchdog_mutex);
45762306a36Sopenharmony_ci	rv = _ipmi_set_timeout(do_heartbeat);
45862306a36Sopenharmony_ci	mutex_unlock(&ipmi_watchdog_mutex);
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	return rv;
46162306a36Sopenharmony_ci}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_cistatic atomic_t panic_done_count = ATOMIC_INIT(0);
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_cistatic void panic_smi_free(struct ipmi_smi_msg *msg)
46662306a36Sopenharmony_ci{
46762306a36Sopenharmony_ci	atomic_dec(&panic_done_count);
46862306a36Sopenharmony_ci}
46962306a36Sopenharmony_cistatic void panic_recv_free(struct ipmi_recv_msg *msg)
47062306a36Sopenharmony_ci{
47162306a36Sopenharmony_ci	atomic_dec(&panic_done_count);
47262306a36Sopenharmony_ci}
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_cistatic struct ipmi_smi_msg panic_halt_heartbeat_smi_msg =
47562306a36Sopenharmony_ci	INIT_IPMI_SMI_MSG(panic_smi_free);
47662306a36Sopenharmony_cistatic struct ipmi_recv_msg panic_halt_heartbeat_recv_msg =
47762306a36Sopenharmony_ci	INIT_IPMI_RECV_MSG(panic_recv_free);
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_cistatic void panic_halt_ipmi_heartbeat(void)
48062306a36Sopenharmony_ci{
48162306a36Sopenharmony_ci	struct kernel_ipmi_msg             msg;
48262306a36Sopenharmony_ci	struct ipmi_system_interface_addr addr;
48362306a36Sopenharmony_ci	int rv;
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	/*
48662306a36Sopenharmony_ci	 * Don't reset the timer if we have the timer turned off, that
48762306a36Sopenharmony_ci	 * re-enables the watchdog.
48862306a36Sopenharmony_ci	 */
48962306a36Sopenharmony_ci	if (ipmi_watchdog_state == WDOG_TIMEOUT_NONE)
49062306a36Sopenharmony_ci		return;
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
49362306a36Sopenharmony_ci	addr.channel = IPMI_BMC_CHANNEL;
49462306a36Sopenharmony_ci	addr.lun = 0;
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	msg.netfn = 0x06;
49762306a36Sopenharmony_ci	msg.cmd = IPMI_WDOG_RESET_TIMER;
49862306a36Sopenharmony_ci	msg.data = NULL;
49962306a36Sopenharmony_ci	msg.data_len = 0;
50062306a36Sopenharmony_ci	atomic_add(2, &panic_done_count);
50162306a36Sopenharmony_ci	rv = ipmi_request_supply_msgs(watchdog_user,
50262306a36Sopenharmony_ci				      (struct ipmi_addr *) &addr,
50362306a36Sopenharmony_ci				      0,
50462306a36Sopenharmony_ci				      &msg,
50562306a36Sopenharmony_ci				      NULL,
50662306a36Sopenharmony_ci				      &panic_halt_heartbeat_smi_msg,
50762306a36Sopenharmony_ci				      &panic_halt_heartbeat_recv_msg,
50862306a36Sopenharmony_ci				      1);
50962306a36Sopenharmony_ci	if (rv)
51062306a36Sopenharmony_ci		atomic_sub(2, &panic_done_count);
51162306a36Sopenharmony_ci}
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_cistatic struct ipmi_smi_msg panic_halt_smi_msg =
51462306a36Sopenharmony_ci	INIT_IPMI_SMI_MSG(panic_smi_free);
51562306a36Sopenharmony_cistatic struct ipmi_recv_msg panic_halt_recv_msg =
51662306a36Sopenharmony_ci	INIT_IPMI_RECV_MSG(panic_recv_free);
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci/*
51962306a36Sopenharmony_ci * Special call, doesn't claim any locks.  This is only to be called
52062306a36Sopenharmony_ci * at panic or halt time, in run-to-completion mode, when the caller
52162306a36Sopenharmony_ci * is the only CPU and the only thing that will be going is these IPMI
52262306a36Sopenharmony_ci * calls.
52362306a36Sopenharmony_ci */
52462306a36Sopenharmony_cistatic void panic_halt_ipmi_set_timeout(void)
52562306a36Sopenharmony_ci{
52662306a36Sopenharmony_ci	int send_heartbeat_now;
52762306a36Sopenharmony_ci	int rv;
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	/* Wait for the messages to be free. */
53062306a36Sopenharmony_ci	while (atomic_read(&panic_done_count) != 0)
53162306a36Sopenharmony_ci		ipmi_poll_interface(watchdog_user);
53262306a36Sopenharmony_ci	atomic_add(2, &panic_done_count);
53362306a36Sopenharmony_ci	rv = __ipmi_set_timeout(&panic_halt_smi_msg,
53462306a36Sopenharmony_ci				&panic_halt_recv_msg,
53562306a36Sopenharmony_ci				&send_heartbeat_now);
53662306a36Sopenharmony_ci	if (rv) {
53762306a36Sopenharmony_ci		atomic_sub(2, &panic_done_count);
53862306a36Sopenharmony_ci		pr_warn("Unable to extend the watchdog timeout\n");
53962306a36Sopenharmony_ci	} else {
54062306a36Sopenharmony_ci		if (send_heartbeat_now)
54162306a36Sopenharmony_ci			panic_halt_ipmi_heartbeat();
54262306a36Sopenharmony_ci	}
54362306a36Sopenharmony_ci	while (atomic_read(&panic_done_count) != 0)
54462306a36Sopenharmony_ci		ipmi_poll_interface(watchdog_user);
54562306a36Sopenharmony_ci}
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_cistatic int __ipmi_heartbeat(void)
54862306a36Sopenharmony_ci{
54962306a36Sopenharmony_ci	struct kernel_ipmi_msg msg;
55062306a36Sopenharmony_ci	int rv;
55162306a36Sopenharmony_ci	struct ipmi_system_interface_addr addr;
55262306a36Sopenharmony_ci	int timeout_retries = 0;
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_cirestart:
55562306a36Sopenharmony_ci	/*
55662306a36Sopenharmony_ci	 * Don't reset the timer if we have the timer turned off, that
55762306a36Sopenharmony_ci	 * re-enables the watchdog.
55862306a36Sopenharmony_ci	 */
55962306a36Sopenharmony_ci	if (ipmi_watchdog_state == WDOG_TIMEOUT_NONE)
56062306a36Sopenharmony_ci		return 0;
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_ci	atomic_set(&msg_tofree, 2);
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
56562306a36Sopenharmony_ci	addr.channel = IPMI_BMC_CHANNEL;
56662306a36Sopenharmony_ci	addr.lun = 0;
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci	msg.netfn = 0x06;
56962306a36Sopenharmony_ci	msg.cmd = IPMI_WDOG_RESET_TIMER;
57062306a36Sopenharmony_ci	msg.data = NULL;
57162306a36Sopenharmony_ci	msg.data_len = 0;
57262306a36Sopenharmony_ci	rv = ipmi_request_supply_msgs(watchdog_user,
57362306a36Sopenharmony_ci				      (struct ipmi_addr *) &addr,
57462306a36Sopenharmony_ci				      0,
57562306a36Sopenharmony_ci				      &msg,
57662306a36Sopenharmony_ci				      NULL,
57762306a36Sopenharmony_ci				      &smi_msg,
57862306a36Sopenharmony_ci				      &recv_msg,
57962306a36Sopenharmony_ci				      1);
58062306a36Sopenharmony_ci	if (rv) {
58162306a36Sopenharmony_ci		atomic_set(&msg_tofree, 0);
58262306a36Sopenharmony_ci		pr_warn("heartbeat send failure: %d\n", rv);
58362306a36Sopenharmony_ci		return rv;
58462306a36Sopenharmony_ci	}
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci	/* Wait for the heartbeat to be sent. */
58762306a36Sopenharmony_ci	wait_for_completion(&msg_wait);
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	if (recv_msg.msg.data[0] == IPMI_WDOG_TIMER_NOT_INIT_RESP)  {
59062306a36Sopenharmony_ci		timeout_retries++;
59162306a36Sopenharmony_ci		if (timeout_retries > 3) {
59262306a36Sopenharmony_ci			pr_err("Unable to restore the IPMI watchdog's settings, giving up\n");
59362306a36Sopenharmony_ci			rv = -EIO;
59462306a36Sopenharmony_ci			goto out;
59562306a36Sopenharmony_ci		}
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci		/*
59862306a36Sopenharmony_ci		 * The timer was not initialized, that means the BMC was
59962306a36Sopenharmony_ci		 * probably reset and lost the watchdog information.  Attempt
60062306a36Sopenharmony_ci		 * to restore the timer's info.  Note that we still hold
60162306a36Sopenharmony_ci		 * the heartbeat lock, to keep a heartbeat from happening
60262306a36Sopenharmony_ci		 * in this process, so must say no heartbeat to avoid a
60362306a36Sopenharmony_ci		 * deadlock on this mutex
60462306a36Sopenharmony_ci		 */
60562306a36Sopenharmony_ci		rv = _ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
60662306a36Sopenharmony_ci		if (rv) {
60762306a36Sopenharmony_ci			pr_err("Unable to send the command to set the watchdog's settings, giving up\n");
60862306a36Sopenharmony_ci			goto out;
60962306a36Sopenharmony_ci		}
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci		/* Might need a heartbeat send, go ahead and do it. */
61262306a36Sopenharmony_ci		goto restart;
61362306a36Sopenharmony_ci	} else if (recv_msg.msg.data[0] != 0) {
61462306a36Sopenharmony_ci		/*
61562306a36Sopenharmony_ci		 * Got an error in the heartbeat response.  It was already
61662306a36Sopenharmony_ci		 * reported in ipmi_wdog_msg_handler, but we should return
61762306a36Sopenharmony_ci		 * an error here.
61862306a36Sopenharmony_ci		 */
61962306a36Sopenharmony_ci		rv = -EINVAL;
62062306a36Sopenharmony_ci	}
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_ciout:
62362306a36Sopenharmony_ci	return rv;
62462306a36Sopenharmony_ci}
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_cistatic int _ipmi_heartbeat(void)
62762306a36Sopenharmony_ci{
62862306a36Sopenharmony_ci	int rv;
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_ci	if (!watchdog_user)
63162306a36Sopenharmony_ci		return -ENODEV;
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci	if (ipmi_start_timer_on_heartbeat) {
63462306a36Sopenharmony_ci		ipmi_start_timer_on_heartbeat = 0;
63562306a36Sopenharmony_ci		ipmi_watchdog_state = action_val;
63662306a36Sopenharmony_ci		rv = _ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
63762306a36Sopenharmony_ci	} else if (atomic_cmpxchg(&pretimeout_since_last_heartbeat, 1, 0)) {
63862306a36Sopenharmony_ci		/*
63962306a36Sopenharmony_ci		 * A pretimeout occurred, make sure we set the timeout.
64062306a36Sopenharmony_ci		 * We don't want to set the action, though, we want to
64162306a36Sopenharmony_ci		 * leave that alone (thus it can't be combined with the
64262306a36Sopenharmony_ci		 * above operation.
64362306a36Sopenharmony_ci		 */
64462306a36Sopenharmony_ci		rv = _ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
64562306a36Sopenharmony_ci	} else {
64662306a36Sopenharmony_ci		rv = __ipmi_heartbeat();
64762306a36Sopenharmony_ci	}
64862306a36Sopenharmony_ci
64962306a36Sopenharmony_ci	return rv;
65062306a36Sopenharmony_ci}
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_cistatic int ipmi_heartbeat(void)
65362306a36Sopenharmony_ci{
65462306a36Sopenharmony_ci	int rv;
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	mutex_lock(&ipmi_watchdog_mutex);
65762306a36Sopenharmony_ci	rv = _ipmi_heartbeat();
65862306a36Sopenharmony_ci	mutex_unlock(&ipmi_watchdog_mutex);
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	return rv;
66162306a36Sopenharmony_ci}
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_cistatic const struct watchdog_info ident = {
66462306a36Sopenharmony_ci	.options	= 0,	/* WDIOF_SETTIMEOUT, */
66562306a36Sopenharmony_ci	.firmware_version = 1,
66662306a36Sopenharmony_ci	.identity	= "IPMI"
66762306a36Sopenharmony_ci};
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_cistatic int ipmi_ioctl(struct file *file,
67062306a36Sopenharmony_ci		      unsigned int cmd, unsigned long arg)
67162306a36Sopenharmony_ci{
67262306a36Sopenharmony_ci	void __user *argp = (void __user *)arg;
67362306a36Sopenharmony_ci	int i;
67462306a36Sopenharmony_ci	int val;
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	switch (cmd) {
67762306a36Sopenharmony_ci	case WDIOC_GETSUPPORT:
67862306a36Sopenharmony_ci		i = copy_to_user(argp, &ident, sizeof(ident));
67962306a36Sopenharmony_ci		return i ? -EFAULT : 0;
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_ci	case WDIOC_SETTIMEOUT:
68262306a36Sopenharmony_ci		i = copy_from_user(&val, argp, sizeof(int));
68362306a36Sopenharmony_ci		if (i)
68462306a36Sopenharmony_ci			return -EFAULT;
68562306a36Sopenharmony_ci		timeout = val;
68662306a36Sopenharmony_ci		return _ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci	case WDIOC_GETTIMEOUT:
68962306a36Sopenharmony_ci		i = copy_to_user(argp, &timeout, sizeof(timeout));
69062306a36Sopenharmony_ci		if (i)
69162306a36Sopenharmony_ci			return -EFAULT;
69262306a36Sopenharmony_ci		return 0;
69362306a36Sopenharmony_ci
69462306a36Sopenharmony_ci	case WDIOC_SETPRETIMEOUT:
69562306a36Sopenharmony_ci		i = copy_from_user(&val, argp, sizeof(int));
69662306a36Sopenharmony_ci		if (i)
69762306a36Sopenharmony_ci			return -EFAULT;
69862306a36Sopenharmony_ci		pretimeout = val;
69962306a36Sopenharmony_ci		return _ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
70062306a36Sopenharmony_ci
70162306a36Sopenharmony_ci	case WDIOC_GETPRETIMEOUT:
70262306a36Sopenharmony_ci		i = copy_to_user(argp, &pretimeout, sizeof(pretimeout));
70362306a36Sopenharmony_ci		if (i)
70462306a36Sopenharmony_ci			return -EFAULT;
70562306a36Sopenharmony_ci		return 0;
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci	case WDIOC_KEEPALIVE:
70862306a36Sopenharmony_ci		return _ipmi_heartbeat();
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ci	case WDIOC_SETOPTIONS:
71162306a36Sopenharmony_ci		i = copy_from_user(&val, argp, sizeof(int));
71262306a36Sopenharmony_ci		if (i)
71362306a36Sopenharmony_ci			return -EFAULT;
71462306a36Sopenharmony_ci		if (val & WDIOS_DISABLECARD) {
71562306a36Sopenharmony_ci			ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
71662306a36Sopenharmony_ci			_ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
71762306a36Sopenharmony_ci			ipmi_start_timer_on_heartbeat = 0;
71862306a36Sopenharmony_ci		}
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ci		if (val & WDIOS_ENABLECARD) {
72162306a36Sopenharmony_ci			ipmi_watchdog_state = action_val;
72262306a36Sopenharmony_ci			_ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
72362306a36Sopenharmony_ci		}
72462306a36Sopenharmony_ci		return 0;
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_ci	case WDIOC_GETSTATUS:
72762306a36Sopenharmony_ci		val = 0;
72862306a36Sopenharmony_ci		i = copy_to_user(argp, &val, sizeof(val));
72962306a36Sopenharmony_ci		if (i)
73062306a36Sopenharmony_ci			return -EFAULT;
73162306a36Sopenharmony_ci		return 0;
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_ci	default:
73462306a36Sopenharmony_ci		return -ENOIOCTLCMD;
73562306a36Sopenharmony_ci	}
73662306a36Sopenharmony_ci}
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_cistatic long ipmi_unlocked_ioctl(struct file *file,
73962306a36Sopenharmony_ci				unsigned int cmd,
74062306a36Sopenharmony_ci				unsigned long arg)
74162306a36Sopenharmony_ci{
74262306a36Sopenharmony_ci	int ret;
74362306a36Sopenharmony_ci
74462306a36Sopenharmony_ci	mutex_lock(&ipmi_watchdog_mutex);
74562306a36Sopenharmony_ci	ret = ipmi_ioctl(file, cmd, arg);
74662306a36Sopenharmony_ci	mutex_unlock(&ipmi_watchdog_mutex);
74762306a36Sopenharmony_ci
74862306a36Sopenharmony_ci	return ret;
74962306a36Sopenharmony_ci}
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_cistatic ssize_t ipmi_write(struct file *file,
75262306a36Sopenharmony_ci			  const char  __user *buf,
75362306a36Sopenharmony_ci			  size_t      len,
75462306a36Sopenharmony_ci			  loff_t      *ppos)
75562306a36Sopenharmony_ci{
75662306a36Sopenharmony_ci	int rv;
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci	if (len) {
75962306a36Sopenharmony_ci		if (!nowayout) {
76062306a36Sopenharmony_ci			size_t i;
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci			/* In case it was set long ago */
76362306a36Sopenharmony_ci			expect_close = 0;
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci			for (i = 0; i != len; i++) {
76662306a36Sopenharmony_ci				char c;
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_ci				if (get_user(c, buf + i))
76962306a36Sopenharmony_ci					return -EFAULT;
77062306a36Sopenharmony_ci				if (c == 'V')
77162306a36Sopenharmony_ci					expect_close = 42;
77262306a36Sopenharmony_ci			}
77362306a36Sopenharmony_ci		}
77462306a36Sopenharmony_ci		rv = ipmi_heartbeat();
77562306a36Sopenharmony_ci		if (rv)
77662306a36Sopenharmony_ci			return rv;
77762306a36Sopenharmony_ci	}
77862306a36Sopenharmony_ci	return len;
77962306a36Sopenharmony_ci}
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_cistatic ssize_t ipmi_read(struct file *file,
78262306a36Sopenharmony_ci			 char        __user *buf,
78362306a36Sopenharmony_ci			 size_t      count,
78462306a36Sopenharmony_ci			 loff_t      *ppos)
78562306a36Sopenharmony_ci{
78662306a36Sopenharmony_ci	int          rv = 0;
78762306a36Sopenharmony_ci	wait_queue_entry_t wait;
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	if (count <= 0)
79062306a36Sopenharmony_ci		return 0;
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	/*
79362306a36Sopenharmony_ci	 * Reading returns if the pretimeout has gone off, and it only does
79462306a36Sopenharmony_ci	 * it once per pretimeout.
79562306a36Sopenharmony_ci	 */
79662306a36Sopenharmony_ci	spin_lock_irq(&ipmi_read_lock);
79762306a36Sopenharmony_ci	if (!data_to_read) {
79862306a36Sopenharmony_ci		if (file->f_flags & O_NONBLOCK) {
79962306a36Sopenharmony_ci			rv = -EAGAIN;
80062306a36Sopenharmony_ci			goto out;
80162306a36Sopenharmony_ci		}
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci		init_waitqueue_entry(&wait, current);
80462306a36Sopenharmony_ci		add_wait_queue(&read_q, &wait);
80562306a36Sopenharmony_ci		while (!data_to_read && !signal_pending(current)) {
80662306a36Sopenharmony_ci			set_current_state(TASK_INTERRUPTIBLE);
80762306a36Sopenharmony_ci			spin_unlock_irq(&ipmi_read_lock);
80862306a36Sopenharmony_ci			schedule();
80962306a36Sopenharmony_ci			spin_lock_irq(&ipmi_read_lock);
81062306a36Sopenharmony_ci		}
81162306a36Sopenharmony_ci		remove_wait_queue(&read_q, &wait);
81262306a36Sopenharmony_ci
81362306a36Sopenharmony_ci		if (signal_pending(current)) {
81462306a36Sopenharmony_ci			rv = -ERESTARTSYS;
81562306a36Sopenharmony_ci			goto out;
81662306a36Sopenharmony_ci		}
81762306a36Sopenharmony_ci	}
81862306a36Sopenharmony_ci	data_to_read = 0;
81962306a36Sopenharmony_ci
82062306a36Sopenharmony_ci out:
82162306a36Sopenharmony_ci	spin_unlock_irq(&ipmi_read_lock);
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_ci	if (rv == 0) {
82462306a36Sopenharmony_ci		if (copy_to_user(buf, &data_to_read, 1))
82562306a36Sopenharmony_ci			rv = -EFAULT;
82662306a36Sopenharmony_ci		else
82762306a36Sopenharmony_ci			rv = 1;
82862306a36Sopenharmony_ci	}
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_ci	return rv;
83162306a36Sopenharmony_ci}
83262306a36Sopenharmony_ci
83362306a36Sopenharmony_cistatic int ipmi_open(struct inode *ino, struct file *filep)
83462306a36Sopenharmony_ci{
83562306a36Sopenharmony_ci	switch (iminor(ino)) {
83662306a36Sopenharmony_ci	case WATCHDOG_MINOR:
83762306a36Sopenharmony_ci		if (test_and_set_bit(0, &ipmi_wdog_open))
83862306a36Sopenharmony_ci			return -EBUSY;
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_ci
84162306a36Sopenharmony_ci		/*
84262306a36Sopenharmony_ci		 * Don't start the timer now, let it start on the
84362306a36Sopenharmony_ci		 * first heartbeat.
84462306a36Sopenharmony_ci		 */
84562306a36Sopenharmony_ci		ipmi_start_timer_on_heartbeat = 1;
84662306a36Sopenharmony_ci		return stream_open(ino, filep);
84762306a36Sopenharmony_ci
84862306a36Sopenharmony_ci	default:
84962306a36Sopenharmony_ci		return (-ENODEV);
85062306a36Sopenharmony_ci	}
85162306a36Sopenharmony_ci}
85262306a36Sopenharmony_ci
85362306a36Sopenharmony_cistatic __poll_t ipmi_poll(struct file *file, poll_table *wait)
85462306a36Sopenharmony_ci{
85562306a36Sopenharmony_ci	__poll_t mask = 0;
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci	poll_wait(file, &read_q, wait);
85862306a36Sopenharmony_ci
85962306a36Sopenharmony_ci	spin_lock_irq(&ipmi_read_lock);
86062306a36Sopenharmony_ci	if (data_to_read)
86162306a36Sopenharmony_ci		mask |= (EPOLLIN | EPOLLRDNORM);
86262306a36Sopenharmony_ci	spin_unlock_irq(&ipmi_read_lock);
86362306a36Sopenharmony_ci
86462306a36Sopenharmony_ci	return mask;
86562306a36Sopenharmony_ci}
86662306a36Sopenharmony_ci
86762306a36Sopenharmony_cistatic int ipmi_fasync(int fd, struct file *file, int on)
86862306a36Sopenharmony_ci{
86962306a36Sopenharmony_ci	int result;
87062306a36Sopenharmony_ci
87162306a36Sopenharmony_ci	result = fasync_helper(fd, file, on, &fasync_q);
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_ci	return (result);
87462306a36Sopenharmony_ci}
87562306a36Sopenharmony_ci
87662306a36Sopenharmony_cistatic int ipmi_close(struct inode *ino, struct file *filep)
87762306a36Sopenharmony_ci{
87862306a36Sopenharmony_ci	if (iminor(ino) == WATCHDOG_MINOR) {
87962306a36Sopenharmony_ci		if (expect_close == 42) {
88062306a36Sopenharmony_ci			mutex_lock(&ipmi_watchdog_mutex);
88162306a36Sopenharmony_ci			ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
88262306a36Sopenharmony_ci			_ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
88362306a36Sopenharmony_ci			mutex_unlock(&ipmi_watchdog_mutex);
88462306a36Sopenharmony_ci		} else {
88562306a36Sopenharmony_ci			pr_crit("Unexpected close, not stopping watchdog!\n");
88662306a36Sopenharmony_ci			ipmi_heartbeat();
88762306a36Sopenharmony_ci		}
88862306a36Sopenharmony_ci		clear_bit(0, &ipmi_wdog_open);
88962306a36Sopenharmony_ci	}
89062306a36Sopenharmony_ci
89162306a36Sopenharmony_ci	expect_close = 0;
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_ci	return 0;
89462306a36Sopenharmony_ci}
89562306a36Sopenharmony_ci
89662306a36Sopenharmony_cistatic const struct file_operations ipmi_wdog_fops = {
89762306a36Sopenharmony_ci	.owner   = THIS_MODULE,
89862306a36Sopenharmony_ci	.read    = ipmi_read,
89962306a36Sopenharmony_ci	.poll    = ipmi_poll,
90062306a36Sopenharmony_ci	.write   = ipmi_write,
90162306a36Sopenharmony_ci	.unlocked_ioctl = ipmi_unlocked_ioctl,
90262306a36Sopenharmony_ci	.compat_ioctl	= compat_ptr_ioctl,
90362306a36Sopenharmony_ci	.open    = ipmi_open,
90462306a36Sopenharmony_ci	.release = ipmi_close,
90562306a36Sopenharmony_ci	.fasync  = ipmi_fasync,
90662306a36Sopenharmony_ci	.llseek  = no_llseek,
90762306a36Sopenharmony_ci};
90862306a36Sopenharmony_ci
90962306a36Sopenharmony_cistatic struct miscdevice ipmi_wdog_miscdev = {
91062306a36Sopenharmony_ci	.minor		= WATCHDOG_MINOR,
91162306a36Sopenharmony_ci	.name		= "watchdog",
91262306a36Sopenharmony_ci	.fops		= &ipmi_wdog_fops
91362306a36Sopenharmony_ci};
91462306a36Sopenharmony_ci
91562306a36Sopenharmony_cistatic void ipmi_wdog_msg_handler(struct ipmi_recv_msg *msg,
91662306a36Sopenharmony_ci				  void                 *handler_data)
91762306a36Sopenharmony_ci{
91862306a36Sopenharmony_ci	if (msg->msg.cmd == IPMI_WDOG_RESET_TIMER &&
91962306a36Sopenharmony_ci			msg->msg.data[0] == IPMI_WDOG_TIMER_NOT_INIT_RESP)
92062306a36Sopenharmony_ci		pr_info("response: The IPMI controller appears to have been reset, will attempt to reinitialize the watchdog timer\n");
92162306a36Sopenharmony_ci	else if (msg->msg.data[0] != 0)
92262306a36Sopenharmony_ci		pr_err("response: Error %x on cmd %x\n",
92362306a36Sopenharmony_ci		       msg->msg.data[0],
92462306a36Sopenharmony_ci		       msg->msg.cmd);
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_ci	ipmi_free_recv_msg(msg);
92762306a36Sopenharmony_ci}
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_cistatic void ipmi_wdog_pretimeout_handler(void *handler_data)
93062306a36Sopenharmony_ci{
93162306a36Sopenharmony_ci	if (preaction_val != WDOG_PRETIMEOUT_NONE) {
93262306a36Sopenharmony_ci		if (preop_val == WDOG_PREOP_PANIC) {
93362306a36Sopenharmony_ci			if (atomic_inc_and_test(&preop_panic_excl))
93462306a36Sopenharmony_ci				panic("Watchdog pre-timeout");
93562306a36Sopenharmony_ci		} else if (preop_val == WDOG_PREOP_GIVE_DATA) {
93662306a36Sopenharmony_ci			unsigned long flags;
93762306a36Sopenharmony_ci
93862306a36Sopenharmony_ci			spin_lock_irqsave(&ipmi_read_lock, flags);
93962306a36Sopenharmony_ci			data_to_read = 1;
94062306a36Sopenharmony_ci			wake_up_interruptible(&read_q);
94162306a36Sopenharmony_ci			kill_fasync(&fasync_q, SIGIO, POLL_IN);
94262306a36Sopenharmony_ci			spin_unlock_irqrestore(&ipmi_read_lock, flags);
94362306a36Sopenharmony_ci		}
94462306a36Sopenharmony_ci	}
94562306a36Sopenharmony_ci
94662306a36Sopenharmony_ci	/*
94762306a36Sopenharmony_ci	 * On some machines, the heartbeat will give an error and not
94862306a36Sopenharmony_ci	 * work unless we re-enable the timer.  So do so.
94962306a36Sopenharmony_ci	 */
95062306a36Sopenharmony_ci	atomic_set(&pretimeout_since_last_heartbeat, 1);
95162306a36Sopenharmony_ci}
95262306a36Sopenharmony_ci
95362306a36Sopenharmony_cistatic void ipmi_wdog_panic_handler(void *user_data)
95462306a36Sopenharmony_ci{
95562306a36Sopenharmony_ci	static int panic_event_handled;
95662306a36Sopenharmony_ci
95762306a36Sopenharmony_ci	/*
95862306a36Sopenharmony_ci	 * On a panic, if we have a panic timeout, make sure to extend
95962306a36Sopenharmony_ci	 * the watchdog timer to a reasonable value to complete the
96062306a36Sopenharmony_ci	 * panic, if the watchdog timer is running.  Plus the
96162306a36Sopenharmony_ci	 * pretimeout is meaningless at panic time.
96262306a36Sopenharmony_ci	 */
96362306a36Sopenharmony_ci	if (watchdog_user && !panic_event_handled &&
96462306a36Sopenharmony_ci	    ipmi_watchdog_state != WDOG_TIMEOUT_NONE) {
96562306a36Sopenharmony_ci		/* Make sure we do this only once. */
96662306a36Sopenharmony_ci		panic_event_handled = 1;
96762306a36Sopenharmony_ci
96862306a36Sopenharmony_ci		timeout = panic_wdt_timeout;
96962306a36Sopenharmony_ci		pretimeout = 0;
97062306a36Sopenharmony_ci		panic_halt_ipmi_set_timeout();
97162306a36Sopenharmony_ci	}
97262306a36Sopenharmony_ci}
97362306a36Sopenharmony_ci
97462306a36Sopenharmony_cistatic const struct ipmi_user_hndl ipmi_hndlrs = {
97562306a36Sopenharmony_ci	.ipmi_recv_hndl           = ipmi_wdog_msg_handler,
97662306a36Sopenharmony_ci	.ipmi_watchdog_pretimeout = ipmi_wdog_pretimeout_handler,
97762306a36Sopenharmony_ci	.ipmi_panic_handler       = ipmi_wdog_panic_handler
97862306a36Sopenharmony_ci};
97962306a36Sopenharmony_ci
98062306a36Sopenharmony_cistatic void ipmi_register_watchdog(int ipmi_intf)
98162306a36Sopenharmony_ci{
98262306a36Sopenharmony_ci	int rv = -EBUSY;
98362306a36Sopenharmony_ci
98462306a36Sopenharmony_ci	if (watchdog_user)
98562306a36Sopenharmony_ci		goto out;
98662306a36Sopenharmony_ci
98762306a36Sopenharmony_ci	if ((ifnum_to_use >= 0) && (ifnum_to_use != ipmi_intf))
98862306a36Sopenharmony_ci		goto out;
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_ci	watchdog_ifnum = ipmi_intf;
99162306a36Sopenharmony_ci
99262306a36Sopenharmony_ci	rv = ipmi_create_user(ipmi_intf, &ipmi_hndlrs, NULL, &watchdog_user);
99362306a36Sopenharmony_ci	if (rv < 0) {
99462306a36Sopenharmony_ci		pr_crit("Unable to register with ipmi\n");
99562306a36Sopenharmony_ci		goto out;
99662306a36Sopenharmony_ci	}
99762306a36Sopenharmony_ci
99862306a36Sopenharmony_ci	rv = ipmi_get_version(watchdog_user,
99962306a36Sopenharmony_ci			      &ipmi_version_major,
100062306a36Sopenharmony_ci			      &ipmi_version_minor);
100162306a36Sopenharmony_ci	if (rv) {
100262306a36Sopenharmony_ci		pr_warn("Unable to get IPMI version, assuming 1.0\n");
100362306a36Sopenharmony_ci		ipmi_version_major = 1;
100462306a36Sopenharmony_ci		ipmi_version_minor = 0;
100562306a36Sopenharmony_ci	}
100662306a36Sopenharmony_ci
100762306a36Sopenharmony_ci	rv = misc_register(&ipmi_wdog_miscdev);
100862306a36Sopenharmony_ci	if (rv < 0) {
100962306a36Sopenharmony_ci		ipmi_destroy_user(watchdog_user);
101062306a36Sopenharmony_ci		watchdog_user = NULL;
101162306a36Sopenharmony_ci		pr_crit("Unable to register misc device\n");
101262306a36Sopenharmony_ci	}
101362306a36Sopenharmony_ci
101462306a36Sopenharmony_ci#ifdef HAVE_DIE_NMI
101562306a36Sopenharmony_ci	if (nmi_handler_registered) {
101662306a36Sopenharmony_ci		int old_pretimeout = pretimeout;
101762306a36Sopenharmony_ci		int old_timeout = timeout;
101862306a36Sopenharmony_ci		int old_preop_val = preop_val;
101962306a36Sopenharmony_ci
102062306a36Sopenharmony_ci		/*
102162306a36Sopenharmony_ci		 * Set the pretimeout to go off in a second and give
102262306a36Sopenharmony_ci		 * ourselves plenty of time to stop the timer.
102362306a36Sopenharmony_ci		 */
102462306a36Sopenharmony_ci		ipmi_watchdog_state = WDOG_TIMEOUT_RESET;
102562306a36Sopenharmony_ci		preop_val = WDOG_PREOP_NONE; /* Make sure nothing happens */
102662306a36Sopenharmony_ci		pretimeout = 99;
102762306a36Sopenharmony_ci		timeout = 100;
102862306a36Sopenharmony_ci
102962306a36Sopenharmony_ci		testing_nmi = 1;
103062306a36Sopenharmony_ci
103162306a36Sopenharmony_ci		rv = ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
103262306a36Sopenharmony_ci		if (rv) {
103362306a36Sopenharmony_ci			pr_warn("Error starting timer to test NMI: 0x%x.  The NMI pretimeout will likely not work\n",
103462306a36Sopenharmony_ci				rv);
103562306a36Sopenharmony_ci			rv = 0;
103662306a36Sopenharmony_ci			goto out_restore;
103762306a36Sopenharmony_ci		}
103862306a36Sopenharmony_ci
103962306a36Sopenharmony_ci		msleep(1500);
104062306a36Sopenharmony_ci
104162306a36Sopenharmony_ci		if (testing_nmi != 2) {
104262306a36Sopenharmony_ci			pr_warn("IPMI NMI didn't seem to occur.  The NMI pretimeout will likely not work\n");
104362306a36Sopenharmony_ci		}
104462306a36Sopenharmony_ci out_restore:
104562306a36Sopenharmony_ci		testing_nmi = 0;
104662306a36Sopenharmony_ci		preop_val = old_preop_val;
104762306a36Sopenharmony_ci		pretimeout = old_pretimeout;
104862306a36Sopenharmony_ci		timeout = old_timeout;
104962306a36Sopenharmony_ci	}
105062306a36Sopenharmony_ci#endif
105162306a36Sopenharmony_ci
105262306a36Sopenharmony_ci out:
105362306a36Sopenharmony_ci	if ((start_now) && (rv == 0)) {
105462306a36Sopenharmony_ci		/* Run from startup, so start the timer now. */
105562306a36Sopenharmony_ci		start_now = 0; /* Disable this function after first startup. */
105662306a36Sopenharmony_ci		ipmi_watchdog_state = action_val;
105762306a36Sopenharmony_ci		ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
105862306a36Sopenharmony_ci		pr_info("Starting now!\n");
105962306a36Sopenharmony_ci	} else {
106062306a36Sopenharmony_ci		/* Stop the timer now. */
106162306a36Sopenharmony_ci		ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
106262306a36Sopenharmony_ci		ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
106362306a36Sopenharmony_ci	}
106462306a36Sopenharmony_ci}
106562306a36Sopenharmony_ci
106662306a36Sopenharmony_cistatic void ipmi_unregister_watchdog(int ipmi_intf)
106762306a36Sopenharmony_ci{
106862306a36Sopenharmony_ci	int rv;
106962306a36Sopenharmony_ci	struct ipmi_user *loc_user = watchdog_user;
107062306a36Sopenharmony_ci
107162306a36Sopenharmony_ci	if (!loc_user)
107262306a36Sopenharmony_ci		return;
107362306a36Sopenharmony_ci
107462306a36Sopenharmony_ci	if (watchdog_ifnum != ipmi_intf)
107562306a36Sopenharmony_ci		return;
107662306a36Sopenharmony_ci
107762306a36Sopenharmony_ci	/* Make sure no one can call us any more. */
107862306a36Sopenharmony_ci	misc_deregister(&ipmi_wdog_miscdev);
107962306a36Sopenharmony_ci
108062306a36Sopenharmony_ci	watchdog_user = NULL;
108162306a36Sopenharmony_ci
108262306a36Sopenharmony_ci	/*
108362306a36Sopenharmony_ci	 * Wait to make sure the message makes it out.  The lower layer has
108462306a36Sopenharmony_ci	 * pointers to our buffers, we want to make sure they are done before
108562306a36Sopenharmony_ci	 * we release our memory.
108662306a36Sopenharmony_ci	 */
108762306a36Sopenharmony_ci	while (atomic_read(&msg_tofree))
108862306a36Sopenharmony_ci		msg_free_smi(NULL);
108962306a36Sopenharmony_ci
109062306a36Sopenharmony_ci	mutex_lock(&ipmi_watchdog_mutex);
109162306a36Sopenharmony_ci
109262306a36Sopenharmony_ci	/* Disconnect from IPMI. */
109362306a36Sopenharmony_ci	rv = ipmi_destroy_user(loc_user);
109462306a36Sopenharmony_ci	if (rv)
109562306a36Sopenharmony_ci		pr_warn("error unlinking from IPMI: %d\n",  rv);
109662306a36Sopenharmony_ci
109762306a36Sopenharmony_ci	/* If it comes back, restart it properly. */
109862306a36Sopenharmony_ci	ipmi_start_timer_on_heartbeat = 1;
109962306a36Sopenharmony_ci
110062306a36Sopenharmony_ci	mutex_unlock(&ipmi_watchdog_mutex);
110162306a36Sopenharmony_ci}
110262306a36Sopenharmony_ci
110362306a36Sopenharmony_ci#ifdef HAVE_DIE_NMI
110462306a36Sopenharmony_cistatic int
110562306a36Sopenharmony_ciipmi_nmi(unsigned int val, struct pt_regs *regs)
110662306a36Sopenharmony_ci{
110762306a36Sopenharmony_ci	/*
110862306a36Sopenharmony_ci	 * If we get here, it's an NMI that's not a memory or I/O
110962306a36Sopenharmony_ci	 * error.  We can't truly tell if it's from IPMI or not
111062306a36Sopenharmony_ci	 * without sending a message, and sending a message is almost
111162306a36Sopenharmony_ci	 * impossible because of locking.
111262306a36Sopenharmony_ci	 */
111362306a36Sopenharmony_ci
111462306a36Sopenharmony_ci	if (testing_nmi) {
111562306a36Sopenharmony_ci		testing_nmi = 2;
111662306a36Sopenharmony_ci		return NMI_HANDLED;
111762306a36Sopenharmony_ci	}
111862306a36Sopenharmony_ci
111962306a36Sopenharmony_ci	/* If we are not expecting a timeout, ignore it. */
112062306a36Sopenharmony_ci	if (ipmi_watchdog_state == WDOG_TIMEOUT_NONE)
112162306a36Sopenharmony_ci		return NMI_DONE;
112262306a36Sopenharmony_ci
112362306a36Sopenharmony_ci	if (preaction_val != WDOG_PRETIMEOUT_NMI)
112462306a36Sopenharmony_ci		return NMI_DONE;
112562306a36Sopenharmony_ci
112662306a36Sopenharmony_ci	/*
112762306a36Sopenharmony_ci	 * If no one else handled the NMI, we assume it was the IPMI
112862306a36Sopenharmony_ci	 * watchdog.
112962306a36Sopenharmony_ci	 */
113062306a36Sopenharmony_ci	if (preop_val == WDOG_PREOP_PANIC) {
113162306a36Sopenharmony_ci		/* On some machines, the heartbeat will give
113262306a36Sopenharmony_ci		   an error and not work unless we re-enable
113362306a36Sopenharmony_ci		   the timer.   So do so. */
113462306a36Sopenharmony_ci		atomic_set(&pretimeout_since_last_heartbeat, 1);
113562306a36Sopenharmony_ci		if (atomic_inc_and_test(&preop_panic_excl))
113662306a36Sopenharmony_ci			nmi_panic(regs, "pre-timeout");
113762306a36Sopenharmony_ci	}
113862306a36Sopenharmony_ci
113962306a36Sopenharmony_ci	return NMI_HANDLED;
114062306a36Sopenharmony_ci}
114162306a36Sopenharmony_ci#endif
114262306a36Sopenharmony_ci
114362306a36Sopenharmony_cistatic int wdog_reboot_handler(struct notifier_block *this,
114462306a36Sopenharmony_ci			       unsigned long         code,
114562306a36Sopenharmony_ci			       void                  *unused)
114662306a36Sopenharmony_ci{
114762306a36Sopenharmony_ci	static int reboot_event_handled;
114862306a36Sopenharmony_ci
114962306a36Sopenharmony_ci	if ((watchdog_user) && (!reboot_event_handled)) {
115062306a36Sopenharmony_ci		/* Make sure we only do this once. */
115162306a36Sopenharmony_ci		reboot_event_handled = 1;
115262306a36Sopenharmony_ci
115362306a36Sopenharmony_ci		if (code == SYS_POWER_OFF || code == SYS_HALT) {
115462306a36Sopenharmony_ci			/* Disable the WDT if we are shutting down. */
115562306a36Sopenharmony_ci			ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
115662306a36Sopenharmony_ci			ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
115762306a36Sopenharmony_ci		} else if (ipmi_watchdog_state != WDOG_TIMEOUT_NONE) {
115862306a36Sopenharmony_ci			/* Set a long timer to let the reboot happen or
115962306a36Sopenharmony_ci			   reset if it hangs, but only if the watchdog
116062306a36Sopenharmony_ci			   timer was already running. */
116162306a36Sopenharmony_ci			if (timeout < 120)
116262306a36Sopenharmony_ci				timeout = 120;
116362306a36Sopenharmony_ci			pretimeout = 0;
116462306a36Sopenharmony_ci			ipmi_watchdog_state = WDOG_TIMEOUT_RESET;
116562306a36Sopenharmony_ci			ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
116662306a36Sopenharmony_ci		}
116762306a36Sopenharmony_ci	}
116862306a36Sopenharmony_ci	return NOTIFY_OK;
116962306a36Sopenharmony_ci}
117062306a36Sopenharmony_ci
117162306a36Sopenharmony_cistatic struct notifier_block wdog_reboot_notifier = {
117262306a36Sopenharmony_ci	.notifier_call	= wdog_reboot_handler,
117362306a36Sopenharmony_ci	.next		= NULL,
117462306a36Sopenharmony_ci	.priority	= 0
117562306a36Sopenharmony_ci};
117662306a36Sopenharmony_ci
117762306a36Sopenharmony_cistatic void ipmi_new_smi(int if_num, struct device *device)
117862306a36Sopenharmony_ci{
117962306a36Sopenharmony_ci	ipmi_register_watchdog(if_num);
118062306a36Sopenharmony_ci}
118162306a36Sopenharmony_ci
118262306a36Sopenharmony_cistatic void ipmi_smi_gone(int if_num)
118362306a36Sopenharmony_ci{
118462306a36Sopenharmony_ci	ipmi_unregister_watchdog(if_num);
118562306a36Sopenharmony_ci}
118662306a36Sopenharmony_ci
118762306a36Sopenharmony_cistatic struct ipmi_smi_watcher smi_watcher = {
118862306a36Sopenharmony_ci	.owner    = THIS_MODULE,
118962306a36Sopenharmony_ci	.new_smi  = ipmi_new_smi,
119062306a36Sopenharmony_ci	.smi_gone = ipmi_smi_gone
119162306a36Sopenharmony_ci};
119262306a36Sopenharmony_ci
119362306a36Sopenharmony_cistatic int action_op(const char *inval, char *outval)
119462306a36Sopenharmony_ci{
119562306a36Sopenharmony_ci	if (outval)
119662306a36Sopenharmony_ci		strcpy(outval, action);
119762306a36Sopenharmony_ci
119862306a36Sopenharmony_ci	if (!inval)
119962306a36Sopenharmony_ci		return 0;
120062306a36Sopenharmony_ci
120162306a36Sopenharmony_ci	if (strcmp(inval, "reset") == 0)
120262306a36Sopenharmony_ci		action_val = WDOG_TIMEOUT_RESET;
120362306a36Sopenharmony_ci	else if (strcmp(inval, "none") == 0)
120462306a36Sopenharmony_ci		action_val = WDOG_TIMEOUT_NONE;
120562306a36Sopenharmony_ci	else if (strcmp(inval, "power_cycle") == 0)
120662306a36Sopenharmony_ci		action_val = WDOG_TIMEOUT_POWER_CYCLE;
120762306a36Sopenharmony_ci	else if (strcmp(inval, "power_off") == 0)
120862306a36Sopenharmony_ci		action_val = WDOG_TIMEOUT_POWER_DOWN;
120962306a36Sopenharmony_ci	else
121062306a36Sopenharmony_ci		return -EINVAL;
121162306a36Sopenharmony_ci	strcpy(action, inval);
121262306a36Sopenharmony_ci	return 0;
121362306a36Sopenharmony_ci}
121462306a36Sopenharmony_ci
121562306a36Sopenharmony_cistatic int preaction_op(const char *inval, char *outval)
121662306a36Sopenharmony_ci{
121762306a36Sopenharmony_ci	if (outval)
121862306a36Sopenharmony_ci		strcpy(outval, preaction);
121962306a36Sopenharmony_ci
122062306a36Sopenharmony_ci	if (!inval)
122162306a36Sopenharmony_ci		return 0;
122262306a36Sopenharmony_ci
122362306a36Sopenharmony_ci	if (strcmp(inval, "pre_none") == 0)
122462306a36Sopenharmony_ci		preaction_val = WDOG_PRETIMEOUT_NONE;
122562306a36Sopenharmony_ci	else if (strcmp(inval, "pre_smi") == 0)
122662306a36Sopenharmony_ci		preaction_val = WDOG_PRETIMEOUT_SMI;
122762306a36Sopenharmony_ci#ifdef HAVE_DIE_NMI
122862306a36Sopenharmony_ci	else if (strcmp(inval, "pre_nmi") == 0)
122962306a36Sopenharmony_ci		preaction_val = WDOG_PRETIMEOUT_NMI;
123062306a36Sopenharmony_ci#endif
123162306a36Sopenharmony_ci	else if (strcmp(inval, "pre_int") == 0)
123262306a36Sopenharmony_ci		preaction_val = WDOG_PRETIMEOUT_MSG_INT;
123362306a36Sopenharmony_ci	else
123462306a36Sopenharmony_ci		return -EINVAL;
123562306a36Sopenharmony_ci	strcpy(preaction, inval);
123662306a36Sopenharmony_ci	return 0;
123762306a36Sopenharmony_ci}
123862306a36Sopenharmony_ci
123962306a36Sopenharmony_cistatic int preop_op(const char *inval, char *outval)
124062306a36Sopenharmony_ci{
124162306a36Sopenharmony_ci	if (outval)
124262306a36Sopenharmony_ci		strcpy(outval, preop);
124362306a36Sopenharmony_ci
124462306a36Sopenharmony_ci	if (!inval)
124562306a36Sopenharmony_ci		return 0;
124662306a36Sopenharmony_ci
124762306a36Sopenharmony_ci	if (strcmp(inval, "preop_none") == 0)
124862306a36Sopenharmony_ci		preop_val = WDOG_PREOP_NONE;
124962306a36Sopenharmony_ci	else if (strcmp(inval, "preop_panic") == 0)
125062306a36Sopenharmony_ci		preop_val = WDOG_PREOP_PANIC;
125162306a36Sopenharmony_ci	else if (strcmp(inval, "preop_give_data") == 0)
125262306a36Sopenharmony_ci		preop_val = WDOG_PREOP_GIVE_DATA;
125362306a36Sopenharmony_ci	else
125462306a36Sopenharmony_ci		return -EINVAL;
125562306a36Sopenharmony_ci	strcpy(preop, inval);
125662306a36Sopenharmony_ci	return 0;
125762306a36Sopenharmony_ci}
125862306a36Sopenharmony_ci
125962306a36Sopenharmony_cistatic void check_parms(void)
126062306a36Sopenharmony_ci{
126162306a36Sopenharmony_ci#ifdef HAVE_DIE_NMI
126262306a36Sopenharmony_ci	int do_nmi = 0;
126362306a36Sopenharmony_ci	int rv;
126462306a36Sopenharmony_ci
126562306a36Sopenharmony_ci	if (preaction_val == WDOG_PRETIMEOUT_NMI) {
126662306a36Sopenharmony_ci		do_nmi = 1;
126762306a36Sopenharmony_ci		if (preop_val == WDOG_PREOP_GIVE_DATA) {
126862306a36Sopenharmony_ci			pr_warn("Pretimeout op is to give data but NMI pretimeout is enabled, setting pretimeout op to none\n");
126962306a36Sopenharmony_ci			preop_op("preop_none", NULL);
127062306a36Sopenharmony_ci			do_nmi = 0;
127162306a36Sopenharmony_ci		}
127262306a36Sopenharmony_ci	}
127362306a36Sopenharmony_ci	if (do_nmi && !nmi_handler_registered) {
127462306a36Sopenharmony_ci		rv = register_nmi_handler(NMI_UNKNOWN, ipmi_nmi, 0,
127562306a36Sopenharmony_ci						"ipmi");
127662306a36Sopenharmony_ci		if (rv) {
127762306a36Sopenharmony_ci			pr_warn("Can't register nmi handler\n");
127862306a36Sopenharmony_ci			return;
127962306a36Sopenharmony_ci		} else
128062306a36Sopenharmony_ci			nmi_handler_registered = 1;
128162306a36Sopenharmony_ci	} else if (!do_nmi && nmi_handler_registered) {
128262306a36Sopenharmony_ci		unregister_nmi_handler(NMI_UNKNOWN, "ipmi");
128362306a36Sopenharmony_ci		nmi_handler_registered = 0;
128462306a36Sopenharmony_ci	}
128562306a36Sopenharmony_ci#endif
128662306a36Sopenharmony_ci}
128762306a36Sopenharmony_ci
128862306a36Sopenharmony_cistatic int __init ipmi_wdog_init(void)
128962306a36Sopenharmony_ci{
129062306a36Sopenharmony_ci	int rv;
129162306a36Sopenharmony_ci
129262306a36Sopenharmony_ci	if (action_op(action, NULL)) {
129362306a36Sopenharmony_ci		action_op("reset", NULL);
129462306a36Sopenharmony_ci		pr_info("Unknown action '%s', defaulting to reset\n", action);
129562306a36Sopenharmony_ci	}
129662306a36Sopenharmony_ci
129762306a36Sopenharmony_ci	if (preaction_op(preaction, NULL)) {
129862306a36Sopenharmony_ci		preaction_op("pre_none", NULL);
129962306a36Sopenharmony_ci		pr_info("Unknown preaction '%s', defaulting to none\n",
130062306a36Sopenharmony_ci			preaction);
130162306a36Sopenharmony_ci	}
130262306a36Sopenharmony_ci
130362306a36Sopenharmony_ci	if (preop_op(preop, NULL)) {
130462306a36Sopenharmony_ci		preop_op("preop_none", NULL);
130562306a36Sopenharmony_ci		pr_info("Unknown preop '%s', defaulting to none\n", preop);
130662306a36Sopenharmony_ci	}
130762306a36Sopenharmony_ci
130862306a36Sopenharmony_ci	check_parms();
130962306a36Sopenharmony_ci
131062306a36Sopenharmony_ci	register_reboot_notifier(&wdog_reboot_notifier);
131162306a36Sopenharmony_ci
131262306a36Sopenharmony_ci	rv = ipmi_smi_watcher_register(&smi_watcher);
131362306a36Sopenharmony_ci	if (rv) {
131462306a36Sopenharmony_ci#ifdef HAVE_DIE_NMI
131562306a36Sopenharmony_ci		if (nmi_handler_registered)
131662306a36Sopenharmony_ci			unregister_nmi_handler(NMI_UNKNOWN, "ipmi");
131762306a36Sopenharmony_ci#endif
131862306a36Sopenharmony_ci		unregister_reboot_notifier(&wdog_reboot_notifier);
131962306a36Sopenharmony_ci		pr_warn("can't register smi watcher\n");
132062306a36Sopenharmony_ci		return rv;
132162306a36Sopenharmony_ci	}
132262306a36Sopenharmony_ci
132362306a36Sopenharmony_ci	pr_info("driver initialized\n");
132462306a36Sopenharmony_ci
132562306a36Sopenharmony_ci	return 0;
132662306a36Sopenharmony_ci}
132762306a36Sopenharmony_ci
132862306a36Sopenharmony_cistatic void __exit ipmi_wdog_exit(void)
132962306a36Sopenharmony_ci{
133062306a36Sopenharmony_ci	ipmi_smi_watcher_unregister(&smi_watcher);
133162306a36Sopenharmony_ci	ipmi_unregister_watchdog(watchdog_ifnum);
133262306a36Sopenharmony_ci
133362306a36Sopenharmony_ci#ifdef HAVE_DIE_NMI
133462306a36Sopenharmony_ci	if (nmi_handler_registered)
133562306a36Sopenharmony_ci		unregister_nmi_handler(NMI_UNKNOWN, "ipmi");
133662306a36Sopenharmony_ci#endif
133762306a36Sopenharmony_ci
133862306a36Sopenharmony_ci	unregister_reboot_notifier(&wdog_reboot_notifier);
133962306a36Sopenharmony_ci}
134062306a36Sopenharmony_cimodule_exit(ipmi_wdog_exit);
134162306a36Sopenharmony_cimodule_init(ipmi_wdog_init);
134262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
134362306a36Sopenharmony_ciMODULE_AUTHOR("Corey Minyard <minyard@mvista.com>");
134462306a36Sopenharmony_ciMODULE_DESCRIPTION("watchdog timer based upon the IPMI interface.");
1345