Skip to contentSkip to conversation

purgatory: Add basic support for IPMI command execution

RFC PATCH In review Hidehiro Kawai 2 participants 9 messages Jan 20, 2016 – Jan 22, 2016

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover purgatory: Add basic support for IPMI command execution

If the second kernel for crash dumping hangs up while booting, no information related to the first kernel will be saved. This makes crash cause analysis difficult. So, some enterprise users want to save minimal information befor booting the second kernel.

One of the approaches is to use panic notifier call or pstore feature. For example, a panic notifier callback registered by IPMI driver saves the panic message to BMC's SEL before booting the second kernel. Similarly, pstore saves kernel logs to a non-volatile memory on the server. However, since these functionalities run with crashed kernel, they may fail to complete their work and boot the second kernel.

So, another approach; saving minimal information to BMC's SEL in the purgatory. Since the purgatory code doesn't rely on the crashed kernel, we can run it safely after verifying the hash of the code.

This patch set is the first step to the final goal; it provides a basic support for IPMI command execution in purgatory. IPMI specification defines multiple interfaces to BMC, and this patch set uses one of them, KCS I/F, which talks with BMC via I/O port like keyboard controllers. As a use case for that, options to start/stop BMC's watchdog timer before booting the second kernel are also provided. These options are useful for the cases where:

- you want to automatically reboot the server when the second kernel hangs up while booting
- you want to prevent the second kernel from being stopped by the watchdog timer enabled while the first kernel is running

If the BMC doesn't work well, the IPMI command execution can take indefinite time and fail to boot the second kernel. To avoid this, timeout logic based on RTC polling is also implemented.

NOTE: This is an RFC version, so some parts are incomplete; these codes are unconditionally built into the kexec binary, and I/O ports for KCS I/F and timeout (5 seconds) are hard-coded, and etc.

Future plan:
Add an option to save the panic message and instruction pointers to BMC's SEL in purgatory. To realize this, we first need to pass the panic message to the purgatory. Instruction pointers are already passed to the second kernel through ELF notes, so just read them.

---

Hidehiro Kawai (4):
purgatory/ipmi: Support BMC watchdog timer start/stop in purgatory
purgatory: Introduce timeout API
purgatory/x86: Support CMOS RTC
purgatory/ipmi: Add timeout logic to IPMI command processing

 kexec/ipmi.h                   |    9 +
 kexec/kexec.c                  |   18 ++
 kexec/kexec.h                  |    6 +
 purgatory/Makefile             |    5 +
 purgatory/arch/i386/Makefile   |    1 
 purgatory/arch/i386/rtc_cmos.c |  104 ++++++++++++++
 purgatory/arch/x86_64/Makefile |    1 
 purgatory/include/purgatory.h  |    3 
 purgatory/include/time.h       |   33 +++++
 purgatory/ipmi.c               |  293 ++++++++++++++++++++++++++++++++++++++++
 purgatory/purgatory.c          |    4 +
 purgatory/time.c               |   58 ++++++++
 12 files changed, 533 insertions(+), 2 deletions(-)
 create mode 100644 kexec/ipmi.h
 create mode 100644 purgatory/arch/i386/rtc_cmos.c
 create mode 100644 purgatory/include/time.h
 create mode 100644 purgatory/ipmi.c
 create mode 100644 purgatory/time.c
Signature · 9 lines
Hidehiro Kawai
Hitachi, Ltd. Research & Development Group



_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec
#1 lore raw

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

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.

---
 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
kexec/ipmi.h new file +9 -0
new file mode 100644
index 0000000..395a2c7
@@ -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 */
kexec/kexec.c +18 -0
index f0bd527..8a8f268 100644
@@ -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;
kexec/kexec.h +5 -1
index c02ac8f..4638866 100644
@@ -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"
 
purgatory/Makefile +1 -0
index 2b5c061..ae1d2bd 100644
@@ -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
purgatory/include/purgatory.h +3 -0
index 788ce49..9b9ffbe 100644
@@ -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 */
purgatory/ipmi.c new file +232 -0

232 lines not on this page · raw message

purgatory/purgatory.c +4 -0
index 3bbcc09..f60879d 100644
@@ -48,5 +48,9 @@ void purgatory(void)
 			/* loop forever */
 		}
 	}
+
+	if (ipmi_wdt)
+		ipmi_wdt_start_stop();
+
 	post_verification_setup_arch();
 }

_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec

#2 lore raw

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

A general note here. It does not appear that you implement the error recovery states in your state machine. If the system fails in the middle of doing an IPMI operation, it is likely to fail.

If you do this you will need to detect and abort any running operation. Implementing the full state machine is probably the best approach, it should handle this, though it is rather complex.

-corey

Quoted 401 lines · Hidehiro Kawai

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.

---
  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

kexec/ipmi.h new file +9 -0
new file mode 100644
index 0000000..395a2c7
@@ -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 */
kexec/kexec.c +18 -0
index f0bd527..8a8f268 100644
@@ -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;
kexec/kexec.h +5 -1
index c02ac8f..4638866 100644
@@ -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"
 
purgatory/Makefile +1 -0
index 2b5c061..ae1d2bd 100644
@@ -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
purgatory/include/purgatory.h +3 -0
index 788ce49..9b9ffbe 100644
@@ -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 */
purgatory/ipmi.c new file +232 -0
new file mode 100644
index 0000000..0acb853
@@ -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();
+}
purgatory/purgatory.c +0 -0
index 3bbcc09..f60879d 100644

purgatory/purgatory.c void purgatory(void) · line 56

Quoted 9 lines
purgatory/purgatory.c +4 -0
@@ -48,5 +48,9 @@ void purgatory(void)
  			/* loop forever */
  		}
  	}
+
+	if (ipmi_wdt)
+		ipmi_wdt_start_stop();
+
  	post_verification_setup_arch();
  }

_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec

#3 lore raw

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

Quoted 3 lines

A general note here. It does not appear that you implement the error recovery states in your state machine. If the system fails in the middle of doing an IPMI operation, it is likely to fail.

The reason why I din't implement the error handling is that I think the error rate is low and it may take many seconds (but I don't have any statistical data, that's my anticipation).

The most important thing is to start booting the 2nd kernel surely and as soon as possible. For example, if a user uses a feature like fence_kdump and if the execution of fence_kdump gets delayed, the crashed host will be shot down by other host waiting for the notification from fence_kdump.

Also, to keep the code simple is important for the reliability.

Anyway, I'll rethink whether I can implement the error handling in simple logic or not.

Quoted 5 lines

If you do this you will need to detect and abort any running operation. Implementing the full state machine is probably the best approach, it should handle this, though it is rather complex.

-corey

Regards,
--
Hidehiro Kawai
Hitachi, Ltd. Research & Development Group

_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec

#4 lore raw

RFC PATCH 2/4 purgatory: Introduce timeout API

In reply to Hidehiro Kawai

This patch introduces timeout API for purgatory code.

Because we don't want to use interrupts in purgatory, this timeout API is based on polling of the clock.

If you want to start a timer, call init_timeout() with seconds to the timeout and an instance of struct timeout_info which manages a virtual timer. Then, you call EXIT_ON_TIMEOUT() macro periodically to check if the timeout happens. If it happens, the control jumps to timed_out label for timeout handling. This means you need to implement timed_out label in each caller function.

To make this timeout API work, an actual implementation of get_unix_time() which returns a UNIX epoch time is needed.

---
 purgatory/Makefile       |    4 ++-
 purgatory/include/time.h |   33 ++++++++++++++++++++++++++
 purgatory/time.c         |   58 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 94 insertions(+), 1 deletion(-)
 create mode 100644 purgatory/include/time.h
 create mode 100644 purgatory/time.c
purgatory/Makefile +3 -1
index ae1d2bd..80caeab 100644
@@ -13,10 +13,12 @@ PURGATORY_SRCS += purgatory/ipmi.c
 PURGATORY_SRCS += purgatory/purgatory.c
 PURGATORY_SRCS += purgatory/printf.c
 PURGATORY_SRCS += purgatory/string.c
+PURGATORY_SRCS += purgatory/time.c
 PURGATORY_MAP = purgatory/purgatory.map
 
 dist += purgatory/Makefile $(PURGATORY_SRCS)				\
-	purgatory/include/purgatory.h purgatory/include/string.h
+	purgatory/include/purgatory.h purgatory/include/string.h	\
+	purgatory/include/time.h
 
 include $(srcdir)/purgatory/arch/alpha/Makefile
 include $(srcdir)/purgatory/arch/arm/Makefile
purgatory/include/time.h new file +33 -0
new file mode 100644
index 0000000..edaf692
@@ -0,0 +1,33 @@
+#ifndef TIME_H
+#define TIME_H
+
+typedef long long time64_t;
+
+struct timeout_info {
+	time64_t start;
+	time64_t end;
+	int timed_out;
+};
+
+time64_t date2unix(int year, int mon, int day, int hour, int min, int sec);
+int check_timeout(struct timeout_info *toi);
+void init_timeout(struct timeout_info *toi, time64_t left);
+
+/**
+ * Check if the timeout happens, and if so, jump to timed_out label.
+ *
+ * @toi: an instance of struct timeout_info initialized by init_timeout().
+ * @check_clock: if this value is 0, this macro doesn't check the actual
+ *               clock.  This is useful for the exit path after timeout.
+ */
+#define EXIT_ON_TIMEOUT(toi, check_clock)       \
+do {                                    	\
+	if ((toi)->timed_out)			\
+		goto timed_out;			\
+	if (check_timeout((toi))) {		\
+		(toi)->timed_out = 1;		\
+		goto timed_out;			\
+	}					\
+} while (0)
+
+#endif /* TIME_H */
purgatory/time.c new file +58 -0

58 lines not on this page · raw message

_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec

#5 lore raw

RFC PATCH 3/4 purgatory/x86: Support CMOS RTC

In reply to Hidehiro Kawai

Support CMOS RTC based on MC148618A RTC by overriding get_unix_time() function. This is needed for the timeout API previously added.

---
 purgatory/arch/i386/Makefile   |    1 
 purgatory/arch/i386/rtc_cmos.c |  104 ++++++++++++++++++++++++++++++++++++++++
 purgatory/arch/x86_64/Makefile |    1 
 3 files changed, 106 insertions(+)
 create mode 100644 purgatory/arch/i386/rtc_cmos.c
purgatory/arch/i386/Makefile +1 -0
index 1532219..cb77328 100644
@@ -13,6 +13,7 @@ i386_PURGATORY_SRCS += purgatory/arch/i386/console-x86.c
 i386_PURGATORY_SRCS += purgatory/arch/i386/vga.c
 i386_PURGATORY_SRCS += purgatory/arch/i386/pic.c
 i386_PURGATORY_SRCS += purgatory/arch/i386/crashdump_backup.c
+i386_PURGATORY_SRCS += purgatory/arch/i386/rtc_cmos.c
 
 dist += purgatory/arch/i386/Makefile $(i386_PURGATORY_SRCS)	\
 	purgatory/arch/i386/purgatory-x86.h			\
purgatory/arch/i386/rtc_cmos.c new file +104 -0

104 lines not on this page · raw message

purgatory/arch/x86_64/Makefile +1 -0
index 7300937..bca1f71 100644
@@ -22,5 +22,6 @@ x86_64_PURGATORY_SRCS += purgatory/arch/i386/crashdump_backup.c
 x86_64_PURGATORY_SRCS += purgatory/arch/i386/console-x86.c
 x86_64_PURGATORY_SRCS += purgatory/arch/i386/vga.c
 x86_64_PURGATORY_SRCS += purgatory/arch/i386/pic.c
+x86_64_PURGATORY_SRCS += purgatory/arch/i386/rtc_cmos.c
 
 x86_64_PURGATORY_EXTRA_CFLAGS = -mcmodel=large

_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec

#6 lore raw

RFC PATCH 4/4 purgatory/ipmi: Add timeout logic to IPMI command processing

In reply to Hidehiro Kawai

If you enable the IPMI command issuing features and your BMC is malfunctioning, you may loops indefinitely while processing IPMI commands. To guarantee that the second kernel starts to boot in fixed seconds, this patch introduces a timeout for the total processing time of IPMI commands. The timeout is 5 seconds by default.

---
 purgatory/ipmi.c |   65 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 63 insertions(+), 2 deletions(-)
purgatory/ipmi.c +63 -2
index 0acb853..f4a093a 100644
@@ -1,6 +1,7 @@
 #include <sys/io.h>
 #include <purgatory.h>
 #include "../kexec/ipmi.h"
+#include "time.h"
 
 #define KCS_PORT_DEFAULT	0xca2
 #define KCS_PORT_DATA		(kcs_port + 0)
@@ -41,6 +42,8 @@ const unsigned char cmd_stop_wdt[] = {
 	0xff,
 };
 
+static struct timeout_info ipmi_to; /* Total timeout for IPMI operations */
+
 static inline unsigned char read_status(void)
 {
 	return inb(KCS_PORT_STATUS);
@@ -49,29 +52,50 @@ static inline unsigned char read_status(void)
 unsigned char wait_out(void)
 {
 	unsigned char status;
+	static int count = 0;
 
 	do {
+		count++;
+		if (count % 1024 == 0)
+			EXIT_ON_TIMEOUT(&ipmi_to, 1);
+
 		status = read_status();
 	} while ((status & KCS_STATUS_OBF) == 0);
 
 	return status;
+
+timed_out:
+	return 0xff;
 }
 
 unsigned char wait_in(void)
 {
 	unsigned char status;
+	static int count = 0;
 
 	do {
+		count++;
+		if (count % 1024 == 0)
+			EXIT_ON_TIMEOUT(&ipmi_to, 1);
+
 		status = read_status();
 	} while (status & KCS_STATUS_IBF);
 
 	return status;
+
+timed_out:
+	return 0xff;
 }
 
 unsigned char read_data(void)
 {
 	wait_out();
+	EXIT_ON_TIMEOUT(&ipmi_to, 0);
+
 	return inb(KCS_PORT_DATA);
+
+timed_out:
+	return 0xff;
 }
 
 void clear_obf(void)
@@ -105,12 +129,17 @@ int write_ipmi_cmd(const unsigned char *cmd, int size)
 	int i;
 
 	wait_in();
+	EXIT_ON_TIMEOUT(&ipmi_to, 0);
+
 	status = write_cmd(KCS_CMD_WRITE_START);
+	EXIT_ON_TIMEOUT(&ipmi_to, 0);
+
 	if (GET_STATUS_STATE(status) != KCS_WRITE_STATE)
 		return -1;
 
 	for (i = 0; i < size - 1; i++) {
 		status = write_data(cmd[i]);
+		EXIT_ON_TIMEOUT(&ipmi_to, 0);
 
 		if (GET_STATUS_STATE(status) != KCS_WRITE_STATE)
 			return -1;
@@ -118,12 +147,18 @@ int write_ipmi_cmd(const unsigned char *cmd, int size)
 
 	/* last write */
 	status = write_cmd(KCS_CMD_WRITE_END);
+	EXIT_ON_TIMEOUT(&ipmi_to, 0);
+
 	if (GET_STATUS_STATE(status) != KCS_WRITE_STATE)
 		return -1;
 
 	write_data(cmd[i]);
+	EXIT_ON_TIMEOUT(&ipmi_to, 0);
 
 	return 0;
+
+timed_out:
+	return -1;
 }
 
 /*
@@ -140,11 +175,17 @@ unsigned char read_result(void)
 
 	while (1) {
 		state = GET_STATUS_STATE(wait_in());
+		EXIT_ON_TIMEOUT(&ipmi_to, 0);
+
 		if (state == KCS_READ_STATE) {
 			data[count] = read_data();
+			EXIT_ON_TIMEOUT(&ipmi_to, 0);
+
 			outb(KCS_CMD_READ, KCS_PORT_DATA);
 		} else if (state == KCS_IDLE_STATE) {
 			data[count] = read_data();
+			EXIT_ON_TIMEOUT(&ipmi_to, 0);
+
 			break;
 		} else {
 			/*
@@ -164,6 +205,9 @@ unsigned char read_result(void)
 	}
 
 	return data[2]; /* Return completion code */
+
+timed_out:
+	return 0xff;
 }
 
 /*
@@ -190,15 +234,22 @@ int issue_ipmi_cmd(const unsigned char *cmd, int size)
 	 */
 	for (i = 0; i < 3; i++) {
 		ret = write_ipmi_cmd(cmd, size);
+		EXIT_ON_TIMEOUT(&ipmi_to, 0);
+
 		if (ret < 0)
 			continue;
 
 		comp_code = read_result();
+		EXIT_ON_TIMEOUT(&ipmi_to, 0);
+
 		if (comp_code == 0)
 			break; /* succeeded */
 	}
 
 	return (i < 3) ? 0 : -1;
+
+timed_out:
+	return -1;
 }
 
 int do_start_wdt(void)
@@ -207,7 +258,11 @@ int do_start_wdt(void)
 
 	printf("IPMI: starting watchdog timer...");
 	ret = issue_ipmi_cmd(cmd_start_wdt, sizeof(cmd_start_wdt));
-	printf("done\n");
+
+	if (ret == 0)
+		printf("done\n");
+	else
+		printf("failed\n");
 
 	return ret;
 }
@@ -218,13 +273,19 @@ int do_stop_wdt(void)
 
 	printf("IPMI: stopping watchdog timer...");
 	ret = issue_ipmi_cmd(cmd_stop_wdt, sizeof(cmd_stop_wdt));
-	printf("done\n");
+
+	if (ret == 0)
+		printf("done\n");
+	else
+		printf("failed\n");
 
 	return ret;
 }
 
 void ipmi_wdt_start_stop(void)
 {
+	init_timeout(&ipmi_to, 5);
+
 	if (ipmi_wdt & IPMI_WDT_START)
 		do_start_wdt();
 	else if (ipmi_wdt & IPMI_WDT_STOP)

_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec

#7 lore raw
In reply to Hidehiro Kawai

I understand what you are trying to accomplish here, but I'm not sure of the wisdom of this approach. I'll give some more information and the kexec maintainers can decide, I suppose.

The KCS interface given here probably covers ~70% of the systems out there right now. Other systems have:
* KCS interfaces at a different port or in a different place like
memory, PCI,
and with different register sizes and spacing.
* Other standard interfaces. SMIC (probably not relevant), BT
(faster, it does
block transfers) and SSIF (which is IPMI over I2C).
* Those other standard interfaces can be in different places, just
like KCS.
Hundreds of I2C interfaces exist.
* Non-standard interfaces. Power systems have their own IPMI interfaces, for instance. Some systems have IPMI over serial ports, though
hopefully
that has pretty much gone away.

I'd guess that over half of the IPMI SI driver is discovering and handling all the various interface types, locations from all the sources it can come from.

As time goes on that 70% number is decreasing in favour of other faster and more convenient interfaces. I expect that SSIF will become much more popular over time because it has block transfer capability and all the hardware is already there on systems.

This is no different, of course, than any other common hardware interface out there. USB, ATA, etc. But it makes it hard to cover all the possibilities in something like purgatory.

I know how valuable this information can be. It has saved my butt on occasions, which is why I go through the inconvenience of handling it in the IPMI driver. But it seems to me that the failure rate of doing this in the crashing kernel should be pretty low. Not zero of course. But I have no idea what it is.

-corey

Quoted 66 lines · Hidehiro Kawai

If the second kernel for crash dumping hangs up while booting, no information related to the first kernel will be saved. This makes crash cause analysis difficult. So, some enterprise users want to save minimal information befor booting the second kernel.

One of the approaches is to use panic notifier call or pstore feature. For example, a panic notifier callback registered by IPMI driver saves the panic message to BMC's SEL before booting the second kernel. Similarly, pstore saves kernel logs to a non-volatile memory on the server. However, since these functionalities run with crashed kernel, they may fail to complete their work and boot the second kernel.

So, another approach; saving minimal information to BMC's SEL in the purgatory. Since the purgatory code doesn't rely on the crashed kernel, we can run it safely after verifying the hash of the code.

This patch set is the first step to the final goal; it provides a basic support for IPMI command execution in purgatory. IPMI specification defines multiple interfaces to BMC, and this patch set uses one of them, KCS I/F, which talks with BMC via I/O port like keyboard controllers. As a use case for that, options to start/stop BMC's watchdog timer before booting the second kernel are also provided. These options are useful for the cases where:

- you want to automatically reboot the server when the second kernel hangs up while booting
- you want to prevent the second kernel from being stopped by the watchdog timer enabled while the first kernel is running

If the BMC doesn't work well, the IPMI command execution can take indefinite time and fail to boot the second kernel. To avoid this, timeout logic based on RTC polling is also implemented.

NOTE: This is an RFC version, so some parts are incomplete; these codes are unconditionally built into the kexec binary, and I/O ports for KCS I/F and timeout (5 seconds) are hard-coded, and etc.

Future plan:
Add an option to save the panic message and instruction pointers to BMC's SEL in purgatory. To realize this, we first need to pass the panic message to the purgatory. Instruction pointers are already passed to the second kernel through ELF notes, so just read them.

---

Hidehiro Kawai (4):
purgatory/ipmi: Support BMC watchdog timer start/stop in purgatory
purgatory: Introduce timeout API
purgatory/x86: Support CMOS RTC
purgatory/ipmi: Add timeout logic to IPMI command processing

  kexec/ipmi.h                   |    9 +
  kexec/kexec.c                  |   18 ++
  kexec/kexec.h                  |    6 +
  purgatory/Makefile             |    5 +
  purgatory/arch/i386/Makefile   |    1
  purgatory/arch/i386/rtc_cmos.c |  104 ++++++++++++++
  purgatory/arch/x86_64/Makefile |    1
  purgatory/include/purgatory.h  |    3
  purgatory/include/time.h       |   33 +++++
  purgatory/ipmi.c               |  293 ++++++++++++++++++++++++++++++++++++++++
  purgatory/purgatory.c          |    4 +
  purgatory/time.c               |   58 ++++++++
  12 files changed, 533 insertions(+), 2 deletions(-)
Quoted 7 lines

create mode 100644 kexec/ipmi.h
create mode 100644 purgatory/arch/i386/rtc_cmos.c create mode 100644 purgatory/include/time.h create mode 100644 purgatory/ipmi.c create mode 100644 purgatory/time.c

_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec

#8 lore raw

Hello,

Thanks for your comments.

Quoted 6 lines

I understand what you are trying to accomplish here, but I'm not sure of the wisdom of this approach. I'll give some more information and the kexec maintainers can decide, I suppose.

The KCS interface given here probably covers ~70% of the systems out there right now.

I decided to use KCS I/F because I believe it covers relatively high rate of servers and it is the only system interface which one of our targets supports for.

Quoted 4 lines

Other systems have:
* KCS interfaces at a different port or in a different place like
memory, PCI,
and with different register sizes and spacing.

At least, I'm going to add an option to change the base I/O port. I'm not sure about other cases because I don't know how supporting them raises the cover rate and I don't have hardware to test.

Quoted 15 lines

* Other standard interfaces. SMIC (probably not relevant), BT
(faster, it does
block transfers) and SSIF (which is IPMI over I2C).
* Those other standard interfaces can be in different places, just
like KCS.
Hundreds of I2C interfaces exist.
* Non-standard interfaces. Power systems have their own IPMI interfaces, for instance. Some systems have IPMI over serial ports, though
hopefully
that has pretty much gone away.

I'd guess that over half of the IPMI SI driver is discovering and handling all the
various interface types, locations from all the sources it can come from.

Quoted 5 lines

As time goes on that 70% number is decreasing in favour of other faster and more convenient interfaces. I expect that SSIF will become much more popular over time because it has block transfer capability and all the hardware
is already there on systems.

Thanks for this information. I'll check out the BMC implementation of other servers, then I'll decide whether I should support SSIF, too.

Quoted 5 lines

This is no different, of course, than any other common hardware interface out
there. USB, ATA, etc. But it makes it hard to cover all the possibilities in something
like purgatory.

Such kind of device sometimes become a cause of dump failure due to wrong DMA or interrupts. So we shouldn't use them. Also, I think it is not suitable for purgatory to implement their drivers.

Other possible options are to use serial device and NVRAM (like the pstore driver). However, serial port is not always available for this purpose (actually this is our case), and NVRAM is not always equipped. Furthermore, we'd like to start/stop the BMC's watchdog timer before starting the 2nd kernel. This can be done only by IPMI.

Quoted 7 lines

I know how valuable this information can be. It has saved my butt on occasions,
which is why I go through the inconvenience of handling it in the IPMI driver.
But it seems to me that the failure rate of doing this in the crashing kernel should
be pretty low. Not zero of course. But I have no idea what it is.

At first, I tried to use crash_kexec_post_notifiers to call the callback of IPMI driver before jumping to the 2nd kernel. But the feature has a couple of bugs and I tried to fix them:

https://lkml.org/lkml/2015/7/10/316
https://lkml.org/lkml/2015/7/23/864

However, no one supports this bugfix and the discussion didn't make a progress because calling notifiers before kdump is not reliable. So, I suggested to use the purgatory.

Regards,

--
Hidehiro Kawai
Hitachi, Ltd. Research & Development Group

_______________________________________________
kexec mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kexec