@@ -35,6 +35,33 @@ extern void (*pm_power_off)(void);
extern void (*pm_power_off_prepare)(void);
struct device; /* we have a circular dep with device.h */
+
+/*
+ * Data structures and callbacks to manage power-off handlers
+ */
+enum power_off_priority {
+ POWER_OFF_PRIORITY_FALLBACK = 0,
+ POWER_OFF_PRIORITY_LOW,
+ POWER_OFF_PRIORITY_DEFAULT,
+ POWER_OFF_PRIORITY_HIGH,
+ POWER_OFF_PRIORITY_HIGHEST
+};
+
+struct power_off_handler_block {
+ void (*handler)(struct power_off_handler_block *);
+ struct power_off_handler_block __rcu *next;
+ enum power_off_priority priority;
+};
+
+int register_power_off_handler(struct power_off_handler_block *);
+int devm_register_power_off_handler(struct device *,
+ struct power_off_handler_block *);
+int register_power_off_handler_simple(void (*function)(void),
+ enum power_off_priority priority);
+int unregister_power_off_handler(struct power_off_handler_block *);
+void do_kernel_power_off(void);
+bool have_kernel_power_off(void);
+
#ifdef CONFIG_VT_CONSOLE_SLEEP
extern void pm_vt_switch_required(struct device *dev, bool required);
extern void pm_vt_switch_unregister(struct device *dev);
@@ -2,6 +2,7 @@
ccflags-$(CONFIG_PM_DEBUG) := -DDEBUG
obj-y += qos.o
+obj-y += power_off_handler.o
obj-$(CONFIG_PM) += main.o
obj-$(CONFIG_VT_CONSOLE_SLEEP) += console.o
obj-$(CONFIG_FREEZER) += process.o
new file mode 100644
@@ -0,0 +1,312 @@
+/*
+ * kernel/power/power_off_handler.c - Power-off handling functions
+ *
+ * Copyright (c) 2014 Guenter Roeck
+ *
+ * List management code derived from kernel/notifier.c.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#define pr_fmt(fmt) "power-off: " fmt
+
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/kallsyms.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+/*
+ * List of handlers for kernel code which wants to be called
+ * to power off the system.
+ */
+static struct power_off_handler_block __rcu *power_off_handler_list;
+static DEFINE_SPINLOCK(power_off_handler_lock);
+
+/*
+ * Internal function to register power-off handler.
+ * Must be called with power-off spinlock acquired.
+ */
+static void _register_power_off_handler(struct power_off_handler_block *p)
+{
+ struct power_off_handler_block **pl = &power_off_handler_list;
+
+ WARN_ON(p->priority < POWER_OFF_PRIORITY_FALLBACK ||
+ p->priority > POWER_OFF_PRIORITY_HIGHEST);
+
+ while ((*pl) != NULL) {
+ if (p->priority > (*pl)->priority)
+ break;
+ pl = &((*pl)->next);
+ }
+ p->next = *pl;
+ rcu_assign_pointer(*pl, p);
+}
+
+/*
+ * Internal function to unregister a power-off handler.
+ * Must be called with power-off spinlock acquired.
+ */
+static int _unregister_power_off_handler(struct power_off_handler_block *p)
+{
+ struct power_off_handler_block **pl = &power_off_handler_list;
+
+ while ((*pl) != NULL) {
+ if ((*pl) == p) {
+ rcu_assign_pointer(*pl, p->next);
+ return 0;
+ }
+ pl = &((*pl)->next);
+ }
+ return -ENOENT;
+}
+
+/**
+ * register_power_off_handler - Register function to be called to power off
+ * the system
+ * @pb: Info about handler function to be called
+ * @pb->handler: Power-off handler function.
+ * @pb->priority: Handler priority.
+ * The following priorities are available.
+ * %POWER_OFF_PRIORITY_FALLBACK
+ * Power-off handler of last resort,
+ * with limited or no power-off capabilities,
+ * such as power-off handlers which do not
+ * really power off the system but loop forever
+ * or stop the CPU.
+ * %POWER_OFF_PRIORITY_LOW
+ * Secondary power-off handler. Use if multiple
+ * power-off handlers may exist in the system
+ * and this one is expected to be used as
+ * back-up or if the primary power-off handler
+ * fails for some reason.
+ * %POWER_OFF_PRIORITY_DEFAULT
+ * Default power-off handler; use if no other
+ * power-off handler is expected to be available,
+ * and/or if power-off functionality is
+ * sufficient to power off the entire system.
+ * %POWER_OFF_PRIORITY_HIGH
+ * Preferred power-off handler. Use if multiple
+ * power-off handlers may exist in the system
+ * and this one is expected to be called in
+ * this case.
+ * %POWER_OFF_PRIORITY_HIGHEST
+ * Highest priority power-off handler, will
+ * preempt all other power-off handlers.
+ *
+ * Registers a function with code to be called to power off the
+ * system.
+ *
+ * Registered functions will be called from machine_power_off as last
+ * step of the power-off sequence. Registered functions are expected
+ * to power off the system immediately. If more than one function is
+ * registered, the power-off handler priority selects which function
+ * will be called first.
+ *
+ * Power-off handlers may be registered from architecture code or from
+ * drivers. A typical use case would be a system where power off
+ * functionality is provided through a multi-function chip or through
+ * a programmable power controller. Multiple power-off handlers may exist;
+ * for example, one power-off handler might power off the entire system,
+ * while another only powers off the CPU card. In such cases, the
+ * power-off handler which only powers off part of the hardware is
+ * expected to register with low priority to ensure that it only
+ * runs if no other means to power off the system are available.
+ *
+ * Always returns zero.
+ */
+int register_power_off_handler(struct power_off_handler_block *pb)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&power_off_handler_lock, flags);
+ _register_power_off_handler(pb);
+ spin_unlock_irqrestore(&power_off_handler_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(register_power_off_handler);
+
+/**
+ * unregister_power_off_handler - Unregister previously registered
+ * power-off handler
+ * @pb: Hook to be unregistered
+ *
+ * Unregisters a previously registered power-off handler function.
+ *
+ * Returns zero on success, or -%ENOENT on failure.
+ */
+int unregister_power_off_handler(struct power_off_handler_block *pb)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&power_off_handler_lock, flags);
+ ret = _unregister_power_off_handler(pb);
+ spin_unlock_irqrestore(&power_off_handler_lock, flags);
+
+ return ret;
+}
+EXPORT_SYMBOL(unregister_power_off_handler);
+
+struct _power_off_handler_data {
+ void (*handler)(void);
+ struct power_off_handler_block power_off_hb;
+};
+
+static void _power_off_handler(struct power_off_handler_block *this)
+{
+ struct _power_off_handler_data *poh =
+ container_of(this, struct _power_off_handler_data,
+ power_off_hb);
+
+ poh->handler();
+}
+
+static struct _power_off_handler_data power_off_handler_data;
+
+/**
+ * register_power_off_handler_simple - Register function to be called
+ * to power off the system
+ * @handler: Function to be called to power off the system
+ * @priority: Handler priority. For available priorities see
+ * register_power_off_handler.
+ *
+ * This is a simplified version of register_power_off_handler. It does
+ * not take a power-off handler as argument, but a function pointer.
+ * The function registers a power-off handler with specified priority.
+ * Power-off handlers registered with this function can not be
+ * unregistered, and only a single power-off handler can be installed
+ * using it.
+ *
+ * This function must not be called from modules and is therefore
+ * not exported.
+ *
+ * Returns -%EBUSY if a power-off handler has already been registered
+ * using register_power_off_handler_simple. Otherwise returns zero.
+ */
+int register_power_off_handler_simple(void (*handler)(void),
+ enum power_off_priority priority)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&power_off_handler_lock, flags);
+
+ if (power_off_handler_data.handler) {
+ pr_warn("Power-off function already registered (%ps), cannot register %ps",
+ power_off_handler_data.handler, handler);
+ ret = -EBUSY;
+ goto abort;
+ }
+
+ power_off_handler_data.handler = handler;
+ power_off_handler_data.power_off_hb.handler = _power_off_handler;
+ power_off_handler_data.power_off_hb.priority = priority;
+
+ _register_power_off_handler(&power_off_handler_data.power_off_hb);
+abort:
+ spin_unlock_irqrestore(&power_off_handler_lock, flags);
+ return ret;
+}
+
+/* Device managed power-off handler registration */
+
+static void devm_power_off_release(struct device *dev, void *res)
+{
+ struct power_off_handler_block *pb;
+
+ pb = *(struct power_off_handler_block **)res;
+ unregister_power_off_handler(pb);
+}
+
+/**
+ * devm_register_power_off_handler - Register function to be called
+ * to power off the system
+ * @dev: The device registering the power-off handler.
+ * @pb: Structure providing a pointer to the power-off
+ * handler function as well as the power-off handler
+ * priority.
+ *
+ * This is the device managed version of register_power_off_handler.
+ *
+ * Returns -%EINVAL if dev is NULL. Returns -%ENOMEM if the system is
+ * out of memory. Otherwise returns zero.
+ */
+int devm_register_power_off_handler(struct device *dev,
+ struct power_off_handler_block *pb)
+{
+ struct power_off_handler_block **ptr;
+ unsigned long flags;
+
+ if (!dev)
+ return -EINVAL;
+
+ ptr = devres_alloc(devm_power_off_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return -ENOMEM;
+
+ spin_lock_irqsave(&power_off_handler_lock, flags);
+ _register_power_off_handler(pb);
+ spin_unlock_irqrestore(&power_off_handler_lock, flags);
+
+ *ptr = pb;
+ devres_add(dev, ptr);
+ return 0;
+}
+EXPORT_SYMBOL(devm_register_power_off_handler);
+
+/**
+ * do_kernel_power_off - Execute kernel power-off handler call chain
+ *
+ * Calls functions registered with register_power_off_handler.
+ *
+ * Expected to be called from machine_power_off as last step of
+ * the power-off sequence.
+ *
+ * Powers the system off immediately if a power-off handler function
+ * has been registered. Otherwise does nothing.
+ */
+void do_kernel_power_off(void)
+{
+ struct power_off_handler_block *p, *next_p;
+
+ /*
+ * No locking. This code can be called from both atomic and non-atomic
+ * context, with interrupts enabled or disabled, depending on the
+ * architecture and platform.
+ *
+ * Power-off handler registration and de-registration are executed in
+ * atomic context with interrupts disabled, which guarantees that
+ * do_kernel_power_off() will not be called while a power-off handler
+ * is installed or removed.
+ * There is a theoretic risc that a power-off handler is installed or
+ * removed while the call chain is traversed, but we'll have to accept
+ * that risk.
+ */
+
+ p = rcu_dereference_raw(power_off_handler_list);
+ while (p) {
+ next_p = rcu_dereference_raw(p->next);
+ p->handler(p);
+ p = next_p;
+ }
+}
+
+/**
+ * have_kernel_power_off() - Check if kernel power-off handler is available
+ *
+ * Returns true if a kernel power-off handler is available, false otherwise.
+ */
+bool have_kernel_power_off(void)
+{
+ return pm_power_off != NULL ||
+ rcu_dereference_raw(power_off_handler_list) != NULL;
+}
+EXPORT_SYMBOL(have_kernel_power_off);