From: Domenico Andreoli <[email-masked]>

Code is not commented but it's really simple. The bulk is in
set_machine_reset(), the static data is hidden in get_descr(). The rest
is just a straightforward consequence with the intent to keep it simple.

Cc: Russell King <[email-masked]>
Cc: Arnd Bergmann <[email-masked]>
Cc: Olof Johansson <[email-masked]>
Cc: Catalin Marinas <[email-masked]>
Cc: Will Deacon <[email-masked]>
Cc: Ralf Baechle <[email-masked]>
Cc: [email-masked]
Cc: [email-masked]
Cc: [email-masked]
Signed-off-by: Domenico Andreoli <[email-masked]>
---
 include/linux/machine_reset.h |   79 +++++++++++++++++++++++++
 kernel/Makefile               |    1 +
 kernel/machine_reset.c        |  131 ++++++++++++++++++++++++++++++++++++++++++
 kernel/power/Kconfig          |    4 +
 kernel/reboot.c               |    3 +
 5 files changed, 218 insertions(+)

Index: b/include/linux/machine_reset.h
===================================================================
--- /dev/null
+++ b/include/linux/machine_reset.h
@@ -0,0 +1,79 @@
+/*
+ * Machine reset hooks management
+ *
+ * Copyright (C) 2013 Domenico Andreoli <[email-masked]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/reboot.h>
+
+enum reset_func {
+	RESET_RESTART = 0,
+	RESET_HALT,
+	RESET_POWER_OFF_PREPARE,
+	RESET_POWER_OFF,
+	RESET_CRASH_POWER_OFF,
+	RESET_NR_FUNCS,
+};
+
+struct reset_hook {
+	union {
+		void (*restart)(void *dev, enum reboot_mode, const char *cmd);
+		void (*halt)(void *dev);
+		void (*power_off_prepare)(void *dev);
+		void (*power_off)(void *dev);
+		void (*crash_power_off)(void *dev);
+		void (*handler)(void *dev);
+	};
+	void (*release)(void *dev);
+};
+
+static inline
+void reset_hook_init(struct reset_hook *hook)
+{
+	hook->handler = NULL;
+	hook->release = NULL;
+}
+
+#ifdef CONFIG_MACHINE_RESET
+
+void set_machine_reset(enum reset_func, const struct reset_hook *, void *dev);
+void unset_machine_reset(enum reset_func, const struct reset_hook *);
+int isset_machine_reset(enum reset_func, void *dev);
+
+void default_restart(enum reboot_mode, const char *cmd);
+void default_halt(void);
+void default_power_off_prepare(void);
+void default_power_off(void);
+void default_crash_power_off(void);
+
+#else /* CONFIG_MACHINE_RESET */
+
+static inline
+void set_machine_reset(enum reset_func func,
+                       const struct reset_hook *hook, void *dev)
+{
+	if (hook->release)
+		hook->release(dev);
+}
+
+static inline void unset_machine_reset(enum reset_func _f,
+                                       const struct reset_hook *_h) {}
+static inline int isset_machine_reset(enum reset_func _f, void *_d) { return 0; }
+
+static inline void default_restart(enum reboot_mode _m, const char *_c) {}
+static inline void default_halt(void) {}
+static inline void default_power_off_prepare(void) {}
+static inline void default_power_off(void) {}
+static inline void default_crash_power_off(void) {}
+
+#endif /* CONFIG_MACHINE_RESET */
Index: b/kernel/Makefile
===================================================================
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -111,6 +111,7 @@ obj-$(CONFIG_PADATA) += padata.o
 obj-$(CONFIG_CRASH_DUMP) += crash_dump.o
 obj-$(CONFIG_JUMP_LABEL) += jump_label.o
 obj-$(CONFIG_CONTEXT_TRACKING) += context_tracking.o
+obj-$(CONFIG_MACHINE_RESET) += machine_reset.o
 
 $(obj)/configs.o: $(obj)/config_data.h
 
Index: b/kernel/machine_reset.c
===================================================================
--- /dev/null
+++ b/kernel/machine_reset.c
@@ -0,0 +1,131 @@
+/*
+ * Machine reset hooks management
+ *
+ * Copyright (C) 2013 Domenico Andreoli <[email-masked]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/export.h>
+#include <linux/spinlock.h>
+#include <linux/reboot.h>
+#include <linux/machine_reset.h>
+
+struct reset_descr {
+	struct reset_hook hook;
+	void *dev;
+};
+static DEFINE_SPINLOCK(descr_lock);
+
+static struct reset_descr *get_descr(enum reset_func func)
+{
+	static struct reset_descr descr[RESET_NR_FUNCS];
+
+	BUG_ON(func >= RESET_NR_FUNCS);
+	spin_lock(&descr_lock);
+	return &descr[func];
+}
+
+static void put_descr(struct reset_descr *descr)
+{
+	spin_unlock(&descr_lock);
+}
+
+#define RESET_FUNC(_func, _member, _args...)                           \
+{                                                                      \
+	struct reset_descr *descr = get_descr(_func);                  \
+	typeof(descr->hook._member) member = descr->hook._member;      \
+	if (member) {                                                  \
+		pr_debug("machine_reset: %s (%pf)\n", #_func, member); \
+		member(descr->dev , ##_args);                          \
+	}                                                              \
+	put_descr(descr);                                              \
+	pr_emerg("machine_reset: %s: FAILED\n", #_func);               \
+	while (1);                                                     \
+}
+
+void default_restart(enum reboot_mode reboot_mode, const char *cmd)
+{
+	RESET_FUNC(RESET_RESTART, restart, reboot_mode, cmd);
+}
+
+void default_halt(void)
+{
+	RESET_FUNC(RESET_HALT, halt);
+}
+
+void default_power_off_prepare(void)
+{
+	RESET_FUNC(RESET_POWER_OFF_PREPARE, power_off_prepare);
+}
+
+void default_power_off(void)
+{
+	RESET_FUNC(RESET_POWER_OFF, power_off);
+}
+
+void default_crash_power_off(void)
+{
+	RESET_FUNC(RESET_CRASH_POWER_OFF, crash_power_off);
+}
+
+void set_machine_reset(enum reset_func func,
+                       const struct reset_hook *hook, void *new_dev)
+{
+	struct reset_descr *descr;
+	void (*new_handler)(void *) = hook ? hook->handler : NULL;
+	void (*new_release)(void *) = hook ? hook->release : NULL;
+	void (*old_release)(void *) = NULL;
+	void *old_dev;
+
+	descr = get_descr(func);
+	old_release = descr->hook.release;
+	old_dev = descr->dev;
+	reset_hook_init(&descr->hook);
+	descr->hook.handler = new_handler;
+	descr->hook.release = new_release;
+	descr->dev = new_dev;
+	put_descr(descr);
+
+	if (old_release)
+		old_release(old_dev);
+}
+EXPORT_SYMBOL_GPL(set_machine_reset);
+
+void unset_machine_reset(enum reset_func func, const struct reset_hook *hook)
+{
+	struct reset_descr *descr;
+	void (*old_release)(void *) = NULL;
+	void *old_dev;
+
+	BUG_ON(!hook || !hook->handler);
+
+	descr = get_descr(func);
+	if (descr->hook.handler == hook->handler) {
+		old_release = descr->hook.release;
+		old_dev = descr->dev;
+		reset_hook_init(&descr->hook);
+	}
+	put_descr(descr);
+
+	if (old_release)
+		old_release(old_dev);
+}
+EXPORT_SYMBOL_GPL(unset_machine_reset);
+
+int isset_machine_reset(enum reset_func func, void *dev)
+{
+	struct reset_descr *descr = get_descr(func);
+	int ret = descr->hook.handler && (!dev || descr->dev == dev);
+	put_descr(descr);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(isset_machine_reset);
Index: b/kernel/power/Kconfig
===================================================================
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -293,3 +293,7 @@ config PM_GENERIC_DOMAINS_RUNTIME
 config CPU_PM
 	bool
 	depends on SUSPEND || CPU_IDLE
+
+config MACHINE_RESET
+	bool
+	default n
Index: b/kernel/reboot.c
===================================================================
--- a/kernel/reboot.c
+++ b/kernel/reboot.c
@@ -12,6 +12,7 @@
 #include <linux/kmod.h>
 #include <linux/kmsg_dump.h>
 #include <linux/reboot.h>
+#include <linux/machine_reset.h>
 #include <linux/suspend.h>
 #include <linux/syscalls.h>
 #include <linux/syscore_ops.h>
@@ -178,6 +179,8 @@ void kernel_power_off(void)
 	kernel_shutdown_prepare(SYSTEM_POWER_OFF);
 	if (pm_power_off_prepare)
 		pm_power_off_prepare();
+	else
+		default_power_off_prepare();
 	migrate_to_reboot_cpu();
 	syscore_shutdown();
 	pr_emerg("Power down\n");