aboutsummaryrefslogtreecommitdiff
path: root/hw/ipmi/ipmi-watchdog.c
blob: 201e9d1b8535a3c9d543e4479da392d99330dc62 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/* Copyright 2013-2018 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>
#include <ipmi.h>
#include <lock.h>
#include <opal.h>
#include <device.h>
#include <timer.h>
#include <timebase.h>
#include <pool.h>
#include <skiboot.h>

#define TIMER_USE_DONT_LOG	0x80
#define TIMER_USE_DONT_STOP	0x40
#define TIMER_USE_POST		0x02

/* WDT expiration actions */
#define WDT_PRETIMEOUT_SMI	0x10
#define WDT_RESET_ACTION 	0x01
#define WDT_NO_ACTION		0x00

/* How long to set the overall watchdog timeout for. In units of
 * 100ms. If the timer is not reset within this time the watchdog
 * expiration action will occur. */
#define WDT_TIMEOUT		600

/* How often to reset the timer using schedule_timer(). Too short and
we risk accidentally resetting the system due to opal_run_pollers() not
being called in time, too short and we waste time resetting the wdt
more frequently than necessary. */
#define WDT_MARGIN		300

static struct timer wdt_timer;
static bool wdt_stopped;
static bool wdt_ticking;

static void ipmi_wdt_complete(struct ipmi_msg *msg)
{
	if (msg->cmd == IPMI_CMD(IPMI_RESET_WDT) && wdt_ticking)
		schedule_timer(&wdt_timer, msecs_to_tb(
				       (WDT_TIMEOUT - WDT_MARGIN)*100));

	ipmi_free_msg(msg);
}

static void set_wdt(uint8_t action, uint16_t count, uint8_t pretimeout,
		bool dont_stop)
{
	struct ipmi_msg *ipmi_msg;

	ipmi_msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_SET_WDT,
			      ipmi_wdt_complete, NULL, NULL, 6, 0);
	if (!ipmi_msg) {
		prerror("Unable to allocate set wdt message\n");
		return;
	}
	ipmi_msg->error = ipmi_wdt_complete;
	ipmi_msg->data[0] = TIMER_USE_POST |
		TIMER_USE_DONT_LOG |
		(dont_stop ? TIMER_USE_DONT_STOP : 0);
	ipmi_msg->data[1] = action;			/* Timer Actions */
	ipmi_msg->data[2] = pretimeout;			/* Pre-timeout Interval */
	ipmi_msg->data[3] = 0;				/* Timer Use Flags */
	ipmi_msg->data[4] = count & 0xff;		/* Initial countdown (lsb) */
	ipmi_msg->data[5] = (count >> 8) & 0xff;	/* Initial countdown (msb) */
	ipmi_queue_msg(ipmi_msg);
}

static struct ipmi_msg *wdt_reset_mkmsg(void)
{
	struct ipmi_msg *ipmi_msg;

	ipmi_msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_RESET_WDT,
			      ipmi_wdt_complete, NULL, NULL, 0, 0);
	if (!ipmi_msg) {
		prerror("Unable to allocate reset wdt message\n");
		return NULL;
	}

	return ipmi_msg;
}

static void sync_reset_wdt(void)
{
	struct ipmi_msg *ipmi_msg;

	if ((ipmi_msg = wdt_reset_mkmsg()))
		ipmi_queue_msg_sync(ipmi_msg);
}

static void reset_wdt(struct timer *t __unused, void *data __unused,
		      uint64_t now __unused)
{
	struct ipmi_msg *ipmi_msg;

	if ((ipmi_msg = wdt_reset_mkmsg()))
		ipmi_queue_msg_head(ipmi_msg);
}

void ipmi_wdt_stop(void)
{
	if (!wdt_stopped) {
		/* Make sure the background reset timer is disabled before
		 * stopping the watchdog. If we issue a reset after disabling
		 * the timer, it will be re-enabled. */
		wdt_ticking = false;
		cancel_timer(&wdt_timer);

		/* Configure the watchdog to be disabled and do no action
		 * in case the underlying implementation is buggy and times
		 * out anyway. */
		wdt_stopped = true;
		set_wdt(WDT_NO_ACTION, 100, 0, false);
	}
}

void ipmi_wdt_final_reset(void)
{
	/* We can safely stop the timer prior to setting up our final
	 * watchdog timeout since we have enough margin before the
	 * timeout. */
	wdt_ticking = false;
	cancel_timer(&wdt_timer);

	/*
	 * We're going to wait a little while before requiring
	 * BOOTKERNEL to have IPMI watchdog support so that people
	 * can catch up in their development environments.
	 * If you still read this after 2018, send a patch!
	 */
#if 0
	/* Configure the watchdog and make sure it is still enabled */
	set_wdt(WDT_RESET_ACTION | WDT_PRETIMEOUT_SMI, WDT_TIMEOUT,
		WDT_MARGIN/10, true);
	sync_reset_wdt();
#else
	set_wdt(WDT_NO_ACTION, 100, 0, false);
#endif
	ipmi_set_boot_count();
}

void ipmi_wdt_init(void)
{
	init_timer(&wdt_timer, reset_wdt, NULL);
	set_wdt(WDT_RESET_ACTION, WDT_TIMEOUT, 0, true);

	/* Start the WDT. We do it synchronously to make sure it has
	 * started before skiboot continues booting. Otherwise we
	 * could crash before the wdt has actually been started. */
	wdt_ticking = true;
	sync_reset_wdt();

	return;
}