Merge tag 'exynos-drm-next-for-v5.20' of git://git.kernel.org/pub/scm/linux/kernel...
[linux-2.6-microblaze.git] / kernel / reboot.c
index 4177645..3c35445 100644 (file)
@@ -23,7 +23,7 @@
  * this indicates whether you can reboot with ctrl-alt-del: the default is yes
  */
 
-int C_A_D = 1;
+static int C_A_D = 1;
 struct pid *cad_pid;
 EXPORT_SYMBOL(cad_pid);
 
@@ -48,12 +48,20 @@ 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.
+ * Temporary stub that prevents linkage failure while we're in process
+ * of removing all uses of legacy pm_power_off() around the kernel.
  */
-
-void (*pm_power_off_prepare)(void);
-EXPORT_SYMBOL_GPL(pm_power_off_prepare);
+void __weak (*pm_power_off)(void);
 
 /**
  *     emergency_restart - reboot the system
@@ -281,6 +289,370 @@ 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);
+}
+
+static struct sys_off_handler platform_sys_off_handler;
+
+static struct sys_off_handler *alloc_sys_off_handler(int priority)
+{
+       struct sys_off_handler *handler;
+       gfp_t flags;
+
+       /*
+        * Platforms like m68k can't allocate sys_off handler dynamically
+        * at the early boot time because memory allocator isn't available yet.
+        */
+       if (priority == SYS_OFF_PRIO_PLATFORM) {
+               handler = &platform_sys_off_handler;
+               if (handler->cb_data)
+                       return ERR_PTR(-EBUSY);
+       } else {
+               if (system_state > SYSTEM_RUNNING)
+                       flags = GFP_ATOMIC;
+               else
+                       flags = GFP_KERNEL;
+
+               handler = kzalloc(sizeof(*handler), flags);
+               if (!handler)
+                       return ERR_PTR(-ENOMEM);
+       }
+
+       return handler;
+}
+
+static void free_sys_off_handler(struct sys_off_handler *handler)
+{
+       if (handler == &platform_sys_off_handler)
+               memset(handler, 0, sizeof(*handler));
+       else
+               kfree(handler);
+}
+
+/**
+ *     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 = alloc_sys_off_handler(priority);
+       if (IS_ERR(handler))
+               return handler;
+
+       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:
+               free_sys_off_handler(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) {
+               free_sys_off_handler(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 (IS_ERR_OR_NULL(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);
+
+       free_sys_off_handler(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);
+
+/**
+ *     devm_register_power_off_handler - Register power-off handler
+ *     @dev: Device that registers callback
+ *     @callback: Callback function
+ *     @cb_data: Callback's argument
+ *
+ *     Registers resource-managed sys-off handler with a default priority
+ *     and using power-off mode.
+ *
+ *     Returns zero on success, or error code on failure.
+ */
+int devm_register_power_off_handler(struct device *dev,
+                                   int (*callback)(struct sys_off_data *data),
+                                   void *cb_data)
+{
+       return devm_register_sys_off_handler(dev,
+                                            SYS_OFF_MODE_POWER_OFF,
+                                            SYS_OFF_PRIO_DEFAULT,
+                                            callback, cb_data);
+}
+EXPORT_SYMBOL_GPL(devm_register_power_off_handler);
+
+/**
+ *     devm_register_restart_handler - Register restart handler
+ *     @dev: Device that registers callback
+ *     @callback: Callback function
+ *     @cb_data: Callback's argument
+ *
+ *     Registers resource-managed sys-off handler with a default priority
+ *     and using restart mode.
+ *
+ *     Returns zero on success, or error code on failure.
+ */
+int devm_register_restart_handler(struct device *dev,
+                                 int (*callback)(struct sys_off_data *data),
+                                 void *cb_data)
+{
+       return devm_register_sys_off_handler(dev,
+                                            SYS_OFF_MODE_RESTART,
+                                            SYS_OFF_PRIO_DEFAULT,
+                                            callback, cb_data);
+}
+EXPORT_SYMBOL_GPL(devm_register_restart_handler);
+
+static struct sys_off_handler *platform_power_off_handler;
+
+static int platform_power_off_notify(struct sys_off_data *data)
+{
+       void (*platform_power_power_off_cb)(void) = data->cb_data;
+
+       platform_power_power_off_cb();
+
+       return NOTIFY_DONE;
+}
+
+/**
+ *     register_platform_power_off - Register platform-level power-off callback
+ *     @power_off: Power-off callback
+ *
+ *     Registers power-off callback that will be called as last step
+ *     of the power-off sequence. This callback is expected to be invoked
+ *     for the last resort. Only one platform power-off callback is allowed
+ *     to be registered at a time.
+ *
+ *     Returns zero on success, or error code on failure.
+ */
+int register_platform_power_off(void (*power_off)(void))
+{
+       struct sys_off_handler *handler;
+
+       handler = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
+                                          SYS_OFF_PRIO_PLATFORM,
+                                          platform_power_off_notify,
+                                          power_off);
+       if (IS_ERR(handler))
+               return PTR_ERR(handler);
+
+       platform_power_off_handler = handler;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(register_platform_power_off);
+
+/**
+ *     unregister_platform_power_off - Unregister platform-level power-off callback
+ *     @power_off: Power-off callback
+ *
+ *     Unregisters previously registered platform power-off callback.
+ */
+void unregister_platform_power_off(void (*power_off)(void))
+{
+       if (platform_power_off_handler &&
+           platform_power_off_handler->cb_data == power_off) {
+               unregister_sys_off_handler(platform_power_off_handler);
+               platform_power_off_handler = NULL;
+       }
+}
+EXPORT_SYMBOL_GPL(unregister_platform_power_off);
+
+static int legacy_pm_power_off(struct sys_off_data *data)
+{
+       if (pm_power_off)
+               pm_power_off();
+
+       return NOTIFY_DONE;
+}
+
+static void do_kernel_power_off_prepare(void)
+{
+       blocking_notifier_call_chain(&power_off_prep_handler_list, 0, NULL);
+}
+
+/**
+ *     do_kernel_power_off - Execute kernel power-off handler call chain
+ *
+ *     Expected to be called as last step of the power-off sequence.
+ *
+ *     Powers off the system immediately if a power-off handler function has
+ *     been registered. Otherwise does nothing.
+ */
+void do_kernel_power_off(void)
+{
+       struct sys_off_handler *sys_off = NULL;
+
+       /*
+        * Register sys-off handlers for legacy PM callback. This allows
+        * legacy PM callbacks temporary co-exist with the new sys-off API.
+        *
+        * TODO: Remove legacy handlers once all legacy PM users will be
+        *       switched to the sys-off based APIs.
+        */
+       if (pm_power_off)
+               sys_off = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
+                                                  SYS_OFF_PRIO_DEFAULT,
+                                                  legacy_pm_power_off, NULL);
+
+       atomic_notifier_call_chain(&power_off_handler_list, 0, NULL);
+
+       unregister_sys_off_handler(sys_off);
+}
+
+/**
+ *     kernel_can_power_off - check whether system can be powered off
+ *
+ *     Returns true if power-off handler is registered and system can be
+ *     powered off, false otherwise.
+ */
+bool kernel_can_power_off(void)
+{
+       return !atomic_notifier_call_chain_is_empty(&power_off_handler_list) ||
+               pm_power_off;
+}
+EXPORT_SYMBOL_GPL(kernel_can_power_off);
+
 /**
  *     kernel_power_off - power_off the system
  *
@@ -289,8 +661,7 @@ EXPORT_SYMBOL_GPL(kernel_halt);
 void kernel_power_off(void)
 {
        kernel_shutdown_prepare(SYSTEM_POWER_OFF);
-       if (pm_power_off_prepare)
-               pm_power_off_prepare();
+       do_kernel_power_off_prepare();
        migrate_to_reboot_cpu();
        syscore_shutdown();
        pr_emerg("Power down\n");
@@ -340,7 +711,7 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
        /* Instead of trying to make the power_off code look like
         * halt when pm_power_off is not set do it the easy way.
         */
-       if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
+       if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !kernel_can_power_off())
                cmd = LINUX_REBOOT_CMD_HALT;
 
        mutex_lock(&system_transition_mutex);
@@ -417,7 +788,8 @@ void ctrl_alt_del(void)
                kill_cad_pid(SIGINT, 1);
 }
 
-char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff";
+#define POWEROFF_CMD_PATH_LEN  256
+static char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff";
 static const char reboot_cmd[] = "/sbin/reboot";
 
 static int run_cmd(const char *cmd)
@@ -447,11 +819,9 @@ static int __orderly_reboot(void)
        ret = run_cmd(reboot_cmd);
 
        if (ret) {
-               printk_prefer_direct_enter();
                pr_warn("Failed to start orderly reboot: forcing the issue\n");
                emergency_sync();
                kernel_restart(NULL);
-               printk_prefer_direct_exit();
        }
 
        return ret;
@@ -464,7 +834,6 @@ static int __orderly_poweroff(bool force)
        ret = run_cmd(poweroff_cmd);
 
        if (ret && force) {
-               printk_prefer_direct_enter();
                pr_warn("Failed to start orderly shutdown: forcing the issue\n");
 
                /*
@@ -474,7 +843,6 @@ static int __orderly_poweroff(bool force)
                 */
                emergency_sync();
                kernel_power_off();
-               printk_prefer_direct_exit();
        }
 
        return ret;
@@ -532,8 +900,6 @@ EXPORT_SYMBOL_GPL(orderly_reboot);
  */
 static void hw_failure_emergency_poweroff_func(struct work_struct *work)
 {
-       printk_prefer_direct_enter();
-
        /*
         * We have reached here after the emergency shutdown waiting period has
         * expired. This means orderly_poweroff has not been able to shut off
@@ -550,8 +916,6 @@ static void hw_failure_emergency_poweroff_func(struct work_struct *work)
         */
        pr_emerg("Hardware protection shutdown failed. Trying emergency restart\n");
        emergency_restart();
-
-       printk_prefer_direct_exit();
 }
 
 static DECLARE_DELAYED_WORK(hw_failure_emergency_poweroff_work,
@@ -590,13 +954,11 @@ void hw_protection_shutdown(const char *reason, int ms_until_forced)
 {
        static atomic_t allow_proceed = ATOMIC_INIT(1);
 
-       printk_prefer_direct_enter();
-
        pr_emerg("HARDWARE PROTECTION shutdown (%s)\n", reason);
 
        /* Shutdown should be initiated only once. */
        if (!atomic_dec_and_test(&allow_proceed))
-               goto out;
+               return;
 
        /*
         * Queue a backup emergency shutdown in the event of
@@ -604,8 +966,6 @@ void hw_protection_shutdown(const char *reason, int ms_until_forced)
         */
        hw_failure_emergency_poweroff(ms_until_forced);
        orderly_poweroff(true);
-out:
-       printk_prefer_direct_exit();
 }
 EXPORT_SYMBOL_GPL(hw_protection_shutdown);
 
@@ -879,6 +1239,33 @@ static struct attribute *reboot_attrs[] = {
        NULL,
 };
 
+#ifdef CONFIG_SYSCTL
+static struct ctl_table kern_reboot_table[] = {
+       {
+               .procname       = "poweroff_cmd",
+               .data           = &poweroff_cmd,
+               .maxlen         = POWEROFF_CMD_PATH_LEN,
+               .mode           = 0644,
+               .proc_handler   = proc_dostring,
+       },
+       {
+               .procname       = "ctrl-alt-del",
+               .data           = &C_A_D,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec,
+       },
+       { }
+};
+
+static void __init kernel_reboot_sysctls_init(void)
+{
+       register_sysctl_init("kernel", kern_reboot_table);
+}
+#else
+#define kernel_reboot_sysctls_init() do { } while (0)
+#endif /* CONFIG_SYSCTL */
+
 static const struct attribute_group reboot_attr_group = {
        .attrs = reboot_attrs,
 };
@@ -898,6 +1285,8 @@ static int __init reboot_ksysfs_init(void)
                return ret;
        }
 
+       kernel_reboot_sysctls_init();
+
        return 0;
 }
 late_initcall(reboot_ksysfs_init);