Commit 232edc2f authored by Dmitry Osipenko's avatar Dmitry Osipenko Committed by Rafael J. Wysocki
Browse files

kernel/reboot: Introduce sys-off handler API



In order to support power-off chaining we need to get rid of the global
pm_* variables, replacing them with the new kernel API functions that
support chaining.

Introduce new generic sys-off handler API that brings the following
features:

1. Power-off and restart handlers are registered using same API function
   that supports chaining, hence all power-off and restart modes will
   support chaining using this unified function.

2. Prevents notifier priority collisions by disallowing registration of
   multiple handlers at the non-default priority level.

3. Supports passing opaque user argument to callback, which allows us to
   remove global variables from drivers.

This patch adds support of the following sys-off modes:

- SYS_OFF_MODE_POWER_OFF_PREPARE that replaces global pm_power_off_prepare
  variable and provides chaining support for power-off-prepare handlers.

- SYS_OFF_MODE_POWER_OFF that replaces global pm_power_off variable and
  provides chaining support for power-off handlers.

- SYS_OFF_MODE_RESTART that provides a better restart API, removing a need
  from drivers to have a global scratch variable by utilizing the opaque
  callback argument.

Signed-off-by: default avatarDmitry Osipenko <dmitry.osipenko@collabora.com>
Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
parent c82f898d
Loading
Loading
Loading
Loading
+77 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@
#include <uapi/linux/reboot.h>

struct device;
struct sys_off_handler;

#define SYS_DOWN	0x0001	/* Notify of system down */
#define SYS_RESTART	SYS_DOWN
@@ -62,6 +63,82 @@ extern void machine_shutdown(void);
struct pt_regs;
extern void machine_crash_shutdown(struct pt_regs *);

/*
 * sys-off handler API.
 */

/*
 * Standard sys-off priority levels. Users are expected to set priorities
 * relative to the standard levels.
 *
 * SYS_OFF_PRIO_PLATFORM:	Use this for platform-level handlers.
 *
 * SYS_OFF_PRIO_LOW:		Use this for handler of last resort.
 *
 * SYS_OFF_PRIO_DEFAULT:	Use this for normal handlers.
 *
 * SYS_OFF_PRIO_HIGH:		Use this for higher priority handlers.
 *
 * SYS_OFF_PRIO_FIRMWARE:	Use this if handler uses firmware call.
 */
#define SYS_OFF_PRIO_PLATFORM		-256
#define SYS_OFF_PRIO_LOW		-128
#define SYS_OFF_PRIO_DEFAULT		0
#define SYS_OFF_PRIO_HIGH		192
#define SYS_OFF_PRIO_FIRMWARE		224

enum sys_off_mode {
	/**
	 * @SYS_OFF_MODE_POWER_OFF_PREPARE:
	 *
	 * Handlers prepare system to be powered off. Handlers are
	 * allowed to sleep.
	 */
	SYS_OFF_MODE_POWER_OFF_PREPARE,

	/**
	 * @SYS_OFF_MODE_POWER_OFF:
	 *
	 * Handlers power-off system. Handlers are disallowed to sleep.
	 */
	SYS_OFF_MODE_POWER_OFF,

	/**
	 * @SYS_OFF_MODE_RESTART:
	 *
	 * Handlers restart system. Handlers are disallowed to sleep.
	 */
	SYS_OFF_MODE_RESTART,
};

/**
 * struct sys_off_data - sys-off callback argument
 *
 * @mode: Mode ID. Currently used only by the sys-off restart mode,
 *        see enum reboot_mode for the available modes.
 * @cb_data: User's callback data.
 * @cmd: Command string. Currently used only by the sys-off restart mode,
 *       NULL otherwise.
 */
struct sys_off_data {
	int mode;
	void *cb_data;
	const char *cmd;
};

struct sys_off_handler *
register_sys_off_handler(enum sys_off_mode mode,
			 int priority,
			 int (*callback)(struct sys_off_data *data),
			 void *cb_data);
void unregister_sys_off_handler(struct sys_off_handler *handler);

int devm_register_sys_off_handler(struct device *dev,
				  enum sys_off_mode mode,
				  int priority,
				  int (*callback)(struct sys_off_data *data),
				  void *cb_data);

/*
 * Architecture independent implemenations of sys_reboot commands.
 */
+182 −0
Original line number Diff line number Diff line
@@ -48,6 +48,15 @@ int reboot_cpu;
enum reboot_type reboot_type = BOOT_ACPI;
int reboot_force;

struct sys_off_handler {
	struct notifier_block nb;
	int (*sys_off_cb)(struct sys_off_data *data);
	void *cb_data;
	enum sys_off_mode mode;
	bool blocking;
	void *list;
};

/*
 * If set, this is used for preparing the system to power off.
 */
@@ -281,6 +290,179 @@ void kernel_halt(void)
}
EXPORT_SYMBOL_GPL(kernel_halt);

/*
 *	Notifier list for kernel code which wants to be called
 *	to prepare system for power off.
 */
static BLOCKING_NOTIFIER_HEAD(power_off_prep_handler_list);

/*
 *	Notifier list for kernel code which wants to be called
 *	to power off system.
 */
static ATOMIC_NOTIFIER_HEAD(power_off_handler_list);

static int sys_off_notify(struct notifier_block *nb,
			  unsigned long mode, void *cmd)
{
	struct sys_off_handler *handler;
	struct sys_off_data data = {};

	handler = container_of(nb, struct sys_off_handler, nb);
	data.cb_data = handler->cb_data;
	data.mode = mode;
	data.cmd = cmd;

	return handler->sys_off_cb(&data);
}

/**
 *	register_sys_off_handler - Register sys-off handler
 *	@mode: Sys-off mode
 *	@priority: Handler priority
 *	@callback: Callback function
 *	@cb_data: Callback argument
 *
 *	Registers system power-off or restart handler that will be invoked
 *	at the step corresponding to the given sys-off mode. Handler's callback
 *	should return NOTIFY_DONE to permit execution of the next handler in
 *	the call chain or NOTIFY_STOP to break the chain (in error case for
 *	example).
 *
 *	Multiple handlers can be registered at the default priority level.
 *
 *	Only one handler can be registered at the non-default priority level,
 *	otherwise ERR_PTR(-EBUSY) is returned.
 *
 *	Returns a new instance of struct sys_off_handler on success, or
 *	an ERR_PTR()-encoded error code otherwise.
 */
struct sys_off_handler *
register_sys_off_handler(enum sys_off_mode mode,
			 int priority,
			 int (*callback)(struct sys_off_data *data),
			 void *cb_data)
{
	struct sys_off_handler *handler;
	int err;

	handler = kzalloc(sizeof(*handler), GFP_KERNEL);
	if (!handler)
		return ERR_PTR(-ENOMEM);

	switch (mode) {
	case SYS_OFF_MODE_POWER_OFF_PREPARE:
		handler->list = &power_off_prep_handler_list;
		handler->blocking = true;
		break;

	case SYS_OFF_MODE_POWER_OFF:
		handler->list = &power_off_handler_list;
		break;

	case SYS_OFF_MODE_RESTART:
		handler->list = &restart_handler_list;
		break;

	default:
		kfree(handler);
		return ERR_PTR(-EINVAL);
	}

	handler->nb.notifier_call = sys_off_notify;
	handler->nb.priority = priority;
	handler->sys_off_cb = callback;
	handler->cb_data = cb_data;
	handler->mode = mode;

	if (handler->blocking) {
		if (priority == SYS_OFF_PRIO_DEFAULT)
			err = blocking_notifier_chain_register(handler->list,
							       &handler->nb);
		else
			err = blocking_notifier_chain_register_unique_prio(handler->list,
									   &handler->nb);
	} else {
		if (priority == SYS_OFF_PRIO_DEFAULT)
			err = atomic_notifier_chain_register(handler->list,
							     &handler->nb);
		else
			err = atomic_notifier_chain_register_unique_prio(handler->list,
									 &handler->nb);
	}

	if (err) {
		kfree(handler);
		return ERR_PTR(err);
	}

	return handler;
}
EXPORT_SYMBOL_GPL(register_sys_off_handler);

/**
 *	unregister_sys_off_handler - Unregister sys-off handler
 *	@handler: Sys-off handler
 *
 *	Unregisters given sys-off handler.
 */
void unregister_sys_off_handler(struct sys_off_handler *handler)
{
	int err;

	if (!handler)
		return;

	if (handler->blocking)
		err = blocking_notifier_chain_unregister(handler->list,
							 &handler->nb);
	else
		err = atomic_notifier_chain_unregister(handler->list,
						       &handler->nb);

	/* sanity check, shall never happen */
	WARN_ON(err);

	kfree(handler);
}
EXPORT_SYMBOL_GPL(unregister_sys_off_handler);

static void devm_unregister_sys_off_handler(void *data)
{
	struct sys_off_handler *handler = data;

	unregister_sys_off_handler(handler);
}

/**
 *	devm_register_sys_off_handler - Register sys-off handler
 *	@dev: Device that registers handler
 *	@mode: Sys-off mode
 *	@priority: Handler priority
 *	@callback: Callback function
 *	@cb_data: Callback argument
 *
 *	Registers resource-managed sys-off handler.
 *
 *	Returns zero on success, or error code on failure.
 */
int devm_register_sys_off_handler(struct device *dev,
				  enum sys_off_mode mode,
				  int priority,
				  int (*callback)(struct sys_off_data *data),
				  void *cb_data)
{
	struct sys_off_handler *handler;

	handler = register_sys_off_handler(mode, priority, callback, cb_data);
	if (IS_ERR(handler))
		return PTR_ERR(handler);

	return devm_add_action_or_reset(dev, devm_unregister_sys_off_handler,
					handler);
}
EXPORT_SYMBOL_GPL(devm_register_sys_off_handler);

/**
 *	kernel_power_off - power_off the system
 *