[RFC PATCH 1/4] purgatory/ipmi: Support BMC watchdog timer start/stop in purgatory

Hidehiro Kawai hidehiro.kawai.ez at hitachi.com
Wed Jan 20 02:37:36 PST 2016


This patch adds an interface to BMC via KCS I/F and a functionality
to start/stop BMC watchdog timer in purgatory.  Starting the watchdog
timer is useful to automatically reboot the server when we fail to
boot the second kernel.  Stopping the watchdog timer is useful to
prevent the second kernel from being stopped by the watchdog timer
enabled while the first kernel is running.

If you specify --ipmi-wdt-start or --ipmi-wdt-stop option to kexec
command, BMC's watchdog timer will start or stop respectively while
executing purgatory.  You can't specify the both options at the same
time.  The start operation doesn't change the parameters of the
watchdog timer such as initial counter and action, you need to set
those parameters in the first OS.  On the other hand, the stop
operation changes the parameters.  You need to reset them when you
want to reuse the watchdog timer.

Signed-off-by: Hidehiro Kawai <hidehiro.kawai.ez at hitachi.com>
---
 kexec/ipmi.h                  |    9 ++
 kexec/kexec.c                 |   18 +++
 kexec/kexec.h                 |    6 +
 purgatory/Makefile            |    1 
 purgatory/include/purgatory.h |    3 +
 purgatory/ipmi.c              |  232 +++++++++++++++++++++++++++++++++++++++++
 purgatory/purgatory.c         |    4 +
 7 files changed, 272 insertions(+), 1 deletion(-)
 create mode 100644 kexec/ipmi.h
 create mode 100644 purgatory/ipmi.c

diff --git a/kexec/ipmi.h b/kexec/ipmi.h
new file mode 100644
index 0000000..395a2c7
--- /dev/null
+++ b/kexec/ipmi.h
@@ -0,0 +1,9 @@
+#ifndef IPMI_H
+#define IPMI_H
+
+/* Options for IPMI code excuted in purgatory */
+#define IPMI_WDT_DO_NOTHING	0
+#define IPMI_WDT_START		(1 << 0)
+#define IPMI_WDT_STOP		(1 << 1)
+
+#endif /* IPMI_H */
diff --git a/kexec/kexec.c b/kexec/kexec.c
index f0bd527..8a8f268 100644
--- a/kexec/kexec.c
+++ b/kexec/kexec.c
@@ -49,6 +49,7 @@
 #include "kexec-sha256.h"
 #include "kexec-zlib.h"
 #include "kexec-lzma.h"
+#include "ipmi.h"
 #include <arch/options.h>
 
 unsigned long long mem_min = 0;
@@ -57,6 +58,7 @@ static unsigned long kexec_flags = 0;
 /* Flags for kexec file (fd) based syscall */
 static unsigned long kexec_file_flags = 0;
 int kexec_debug = 0;
+int opt_ipmi_wdt = IPMI_WDT_DO_NOTHING;
 
 void dbgprint_mem_range(const char *prefix, struct memory_range *mr, int nr_mr)
 {
@@ -643,6 +645,10 @@ static void update_purgatory(struct kexec_info *info)
 	if (!info->rhdr.e_shdr) {
 		return;
 	}
+
+	elf_rel_set_symbol(&info->rhdr, "ipmi_wdt", &opt_ipmi_wdt,
+			   sizeof(opt_ipmi_wdt));
+
 	arch_update_purgatory(info);
 	memset(region, 0, sizeof(region));
 	sha256_starts(&ctx);
@@ -1345,6 +1351,12 @@ int main(int argc, char *argv[])
 		case OPT_KEXEC_FILE_SYSCALL:
 			/* We already parsed it. Nothing to do. */
 			break;
+		case OPT_IPMI_WDT_START:
+			opt_ipmi_wdt |= IPMI_WDT_START;
+			break;
+		case OPT_IPMI_WDT_STOP:
+			opt_ipmi_wdt |= IPMI_WDT_STOP;
+			break;
 		default:
 			break;
 		}
@@ -1370,6 +1382,12 @@ int main(int argc, char *argv[])
 		    "\"--mem-max\" parameter\n");
 	}
 
+	if ((opt_ipmi_wdt & IPMI_WDT_START) &&
+	    (opt_ipmi_wdt & IPMI_WDT_STOP)) {
+		die("You can't specify both --ipmi-wdt-start and "
+		    "--ipmi-wdt-stop\n");
+	}
+
 	fileind = optind;
 	/* Reset getopt for the next pass; called in other source modules */
 	opterr = 1;
diff --git a/kexec/kexec.h b/kexec/kexec.h
index c02ac8f..4638866 100644
--- a/kexec/kexec.h
+++ b/kexec/kexec.h
@@ -224,7 +224,9 @@ extern int file_types;
 #define OPT_LOAD_PRESERVE_CONTEXT 259
 #define OPT_LOAD_JUMP_BACK_HELPER 260
 #define OPT_ENTRY		261
-#define OPT_MAX			262
+#define OPT_IPMI_WDT_START	262
+#define OPT_IPMI_WDT_STOP	263
+#define OPT_MAX			264
 #define KEXEC_OPTIONS \
 	{ "help",		0, 0, OPT_HELP }, \
 	{ "version",		0, 0, OPT_VERSION }, \
@@ -244,6 +246,8 @@ extern int file_types;
 	{ "reuseinitrd",	0, 0, OPT_REUSE_INITRD }, \
 	{ "kexec-file-syscall",	0, 0, OPT_KEXEC_FILE_SYSCALL }, \
 	{ "debug",		0, 0, OPT_DEBUG }, \
+	{ "ipmi-wdt-start",	0, 0, OPT_IPMI_WDT_START }, \
+	{ "ipmi-wdt-stop",	0, 0, OPT_IPMI_WDT_STOP }, \
 
 #define KEXEC_OPT_STR "h?vdfxyluet:ps"
 
diff --git a/purgatory/Makefile b/purgatory/Makefile
index 2b5c061..ae1d2bd 100644
--- a/purgatory/Makefile
+++ b/purgatory/Makefile
@@ -9,6 +9,7 @@
 
 PURGATORY = purgatory/purgatory.ro
 PURGATORY_SRCS =
+PURGATORY_SRCS += purgatory/ipmi.c
 PURGATORY_SRCS += purgatory/purgatory.c
 PURGATORY_SRCS += purgatory/printf.c
 PURGATORY_SRCS += purgatory/string.c
diff --git a/purgatory/include/purgatory.h b/purgatory/include/purgatory.h
index 788ce49..9b9ffbe 100644
--- a/purgatory/include/purgatory.h
+++ b/purgatory/include/purgatory.h
@@ -1,11 +1,14 @@
 #ifndef PURGATORY_H
 #define PURGATORY_H
 
+extern int ipmi_wdt;
+
 void putchar(int ch);
 void sprintf(char *buffer, const char *fmt, ...)
 	__attribute__ ((format (printf, 2, 3)));
 void printf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
 void setup_arch(void);
 void post_verification_setup_arch(void);
+void ipmi_wdt_start_stop(void);
 
 #endif /* PURGATORY_H */
diff --git a/purgatory/ipmi.c b/purgatory/ipmi.c
new file mode 100644
index 0000000..0acb853
--- /dev/null
+++ b/purgatory/ipmi.c
@@ -0,0 +1,232 @@
+#include <sys/io.h>
+#include <purgatory.h>
+#include "../kexec/ipmi.h"
+
+#define KCS_PORT_DEFAULT	0xca2
+#define KCS_PORT_DATA		(kcs_port + 0)
+#define KCS_PORT_STATUS		(kcs_port + 1)
+#define KCS_PORT_CMD		(kcs_port + 1)
+
+#define KCS_STATUS_OBF		0x1
+#define KCS_STATUS_IBF		0x2
+
+#define KCS_CMD_WRITE_START	0x61
+#define KCS_CMD_WRITE_END	0x62
+#define KCS_CMD_READ		0x68
+
+#define GET_STATUS_STATE(status) (((status) >> 6) & 0x03)
+#define KCS_IDLE_STATE	0x0
+#define KCS_READ_STATE	0x1
+#define KCS_WRITE_STATE	0x2
+#define KCS_ERROR_STATE	0x3
+
+int kcs_port = KCS_PORT_DEFAULT;
+int ipmi_wdt = IPMI_WDT_DO_NOTHING;
+
+/* IPMI command to start BMC watchdog timer */
+const unsigned char cmd_start_wdt[] = {
+	0x06 << 2,	/* App */
+	0x22,		/* Reset Watchdog Timer */
+};
+
+/* IPMI command to stop BMC watchdog timer */
+const unsigned char cmd_stop_wdt[] = {
+	0x06 << 2,	/* App */
+	0x24,		/* Set Watchdog Timer Command */
+	0x84,		/* Timer Use: don't log, and SMS/OS use */
+	0x00,		/* Timer Actions: no action */
+	0x00,		/* Pre-timeout interval: 0 */
+	0x10,		/* Timer Use Expiration flag clear: SMS/OS */
+	0xff,		/* Initial countdown value: 0xffff */
+	0xff,
+};
+
+static inline unsigned char read_status(void)
+{
+	return inb(KCS_PORT_STATUS);
+}
+
+unsigned char wait_out(void)
+{
+	unsigned char status;
+
+	do {
+		status = read_status();
+	} while ((status & KCS_STATUS_OBF) == 0);
+
+	return status;
+}
+
+unsigned char wait_in(void)
+{
+	unsigned char status;
+
+	do {
+		status = read_status();
+	} while (status & KCS_STATUS_IBF);
+
+	return status;
+}
+
+unsigned char read_data(void)
+{
+	wait_out();
+	return inb(KCS_PORT_DATA);
+}
+
+void clear_obf(void)
+{
+	if (inb(KCS_PORT_STATUS) & KCS_STATUS_OBF)
+		read_data();
+}
+
+unsigned char write_data(unsigned char byte)
+{
+	clear_obf();
+	outb(byte, KCS_PORT_DATA);
+	return wait_in();
+}
+
+unsigned char write_cmd(unsigned char byte)
+{
+	clear_obf();
+	outb(byte, KCS_PORT_CMD);
+	return wait_in();
+}
+
+/*
+ * Issue a given IPMI command via KCS I/F.
+ *
+ * Return 0 on success, otherwise -1.
+ */
+int write_ipmi_cmd(const unsigned char *cmd, int size)
+{
+	unsigned char status;
+	int i;
+
+	wait_in();
+	status = write_cmd(KCS_CMD_WRITE_START);
+	if (GET_STATUS_STATE(status) != KCS_WRITE_STATE)
+		return -1;
+
+	for (i = 0; i < size - 1; i++) {
+		status = write_data(cmd[i]);
+
+		if (GET_STATUS_STATE(status) != KCS_WRITE_STATE)
+			return -1;
+	}
+
+	/* last write */
+	status = write_cmd(KCS_CMD_WRITE_END);
+	if (GET_STATUS_STATE(status) != KCS_WRITE_STATE)
+		return -1;
+
+	write_data(cmd[i]);
+
+	return 0;
+}
+
+/*
+ * Read result bytes for the previously issued IPMI command.
+ *
+ * Return the completion code, which is 0 on success.  Otherwise, return
+ * non-zero value.
+ */
+unsigned char read_result(void)
+{
+	unsigned char state;
+	unsigned char data[4] = { 0 };
+	int count = 0;
+
+	while (1) {
+		state = GET_STATUS_STATE(wait_in());
+		if (state == KCS_READ_STATE) {
+			data[count] = read_data();
+			outb(KCS_CMD_READ, KCS_PORT_DATA);
+		} else if (state == KCS_IDLE_STATE) {
+			data[count] = read_data();
+			break;
+		} else {
+			/*
+			 * Error! Set 0xff (unspecified error) as the
+			 * completion code.
+			 */
+			data[2] = 0xff;
+			break;
+		}
+
+		/*
+		 * We are interested only in the completion code in the
+		 * 3rd byte. Skip the following bytes.
+		 */
+		if (count < 3)
+			count++;
+	}
+
+	return data[2]; /* Return completion code */
+}
+
+/*
+ * Issue one IPMI command and check the result.
+ *
+ * Return 0 on success, otherwise -1.
+ */
+int issue_ipmi_cmd(const unsigned char *cmd, int size)
+{
+	int i, ret;
+	unsigned char comp_code;
+
+	/*
+	 * Retry 3 times at most on error.
+	 *
+	 * write_ipmi_cmd() issues WRITE_START KCS command at the beginning,
+	 * which aborts the ongoing IPMI command and starts new one.  So
+	 * basically, write_ipmi_cmd() will succeed anytime.
+	 *
+	 * This command abort can fail on some BMC depending on its KCS I/F
+	 * state, and  we get ERROR_STATE from the status register in that
+	 * case.  However, there is no problem.  We can recover it by
+	 * simply retrying from writing WRITE_START KCS command.
+	 */
+	for (i = 0; i < 3; i++) {
+		ret = write_ipmi_cmd(cmd, size);
+		if (ret < 0)
+			continue;
+
+		comp_code = read_result();
+		if (comp_code == 0)
+			break; /* succeeded */
+	}
+
+	return (i < 3) ? 0 : -1;
+}
+
+int do_start_wdt(void)
+{
+	int ret;
+
+	printf("IPMI: starting watchdog timer...");
+	ret = issue_ipmi_cmd(cmd_start_wdt, sizeof(cmd_start_wdt));
+	printf("done\n");
+
+	return ret;
+}
+
+int do_stop_wdt(void)
+{
+	int ret;
+
+	printf("IPMI: stopping watchdog timer...");
+	ret = issue_ipmi_cmd(cmd_stop_wdt, sizeof(cmd_stop_wdt));
+	printf("done\n");
+
+	return ret;
+}
+
+void ipmi_wdt_start_stop(void)
+{
+	if (ipmi_wdt & IPMI_WDT_START)
+		do_start_wdt();
+	else if (ipmi_wdt & IPMI_WDT_STOP)
+		do_stop_wdt();
+}
diff --git a/purgatory/purgatory.c b/purgatory/purgatory.c
index 3bbcc09..f60879d 100644
--- a/purgatory/purgatory.c
+++ b/purgatory/purgatory.c
@@ -48,5 +48,9 @@ void purgatory(void)
 			/* loop forever */
 		}
 	}
+
+	if (ipmi_wdt)
+		ipmi_wdt_start_stop();
+
 	post_verification_setup_arch();
 }





More information about the kexec mailing list