reboot: allow to specify reboot mode via sysfs
authorMatteo Croce <mcroce@microsoft.com>
Wed, 16 Dec 2020 04:46:57 +0000 (20:46 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 16 Dec 2020 06:46:19 +0000 (22:46 -0800)
The kernel cmdline reboot= option offers some sort of control on how the
reboot is issued.

We don't always know in advance what type of reboot to perform.

Sometimes a warm reboot is preferred to persist certain memory regions
across the reboot.  Others a cold one is needed to apply a future system
update that makes a memory memory model change, like changing the base
page size or resizing a persistent memory region.

Or simply we want to enable reboot_force because we noticed that
something bad happened.

Add handles in sysfs to allow setting these reboot options, so they can
be changed when the system is booted, other than at boot time.

The handlers are under <sysfs>/kernel/reboot, can be read to get the
current configuration and written to alter it.

# cd /sys/kernel/reboot/

# grep . *
cpu:0
force:0
mode:cold
type:acpi

# echo 2 >cpu
# echo yes >force
# echo soft >mode
# echo bios >type

# grep . *
cpu:2
force:1
mode:soft
type:bios

Before setting anything, check for CAP_SYS_BOOT capability, so it's
possible to allow an unpriviledged process to change these settings simply
by relaxing the handles permissions, without opening them to the world.

[natechancellor@gmail.com: fix variable assignments in type_store]
Link: https://lkml.kernel.org/r/20201112035023.974748-1-natechancellor@gmail.com
Link: https://github.com/ClangBuiltLinux/linux/issues/1197
Link: https://lkml.kernel.org/r/20201110202746.9690-1-mcroce@linux.microsoft.com
Signed-off-by: Matteo Croce <mcroce@microsoft.com>
Signed-off-by: Nathan Chancellor <natechancellor@gmail.com>
Reviewed-by: Petr Mladek <pmladek@suse.com>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Pavel Tatashin <pasha.tatashin@soleen.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Tyler Hicks <tyhicks@linux.microsoft.com>
Cc: Nathan Chancellor <natechancellor@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Documentation/ABI/testing/sysfs-kernel-reboot [new file with mode: 0644]
kernel/reboot.c

diff --git a/Documentation/ABI/testing/sysfs-kernel-reboot b/Documentation/ABI/testing/sysfs-kernel-reboot
new file mode 100644 (file)
index 0000000..837330f
--- /dev/null
@@ -0,0 +1,32 @@
+What:          /sys/kernel/reboot
+Date:          November 2020
+KernelVersion: 5.11
+Contact:       Matteo Croce <mcroce@microsoft.com>
+Description:   Interface to set the kernel reboot behavior, similarly to
+               what can be done via the reboot= cmdline option.
+               (see Documentation/admin-guide/kernel-parameters.txt)
+
+What:          /sys/kernel/reboot/mode
+Date:          November 2020
+KernelVersion: 5.11
+Contact:       Matteo Croce <mcroce@microsoft.com>
+Description:   Reboot mode. Valid values are: cold warm hard soft gpio
+
+What:          /sys/kernel/reboot/type
+Date:          November 2020
+KernelVersion: 5.11
+Contact:       Matteo Croce <mcroce@microsoft.com>
+Description:   Reboot type. Valid values are: bios acpi kbd triple efi pci
+
+What:          /sys/kernel/reboot/cpu
+Date:          November 2020
+KernelVersion: 5.11
+Contact:       Matteo Croce <mcroce@microsoft.com>
+Description:   CPU number to use to reboot.
+
+What:          /sys/kernel/reboot/force
+Date:          November 2020
+KernelVersion: 5.11
+Contact:       Matteo Croce <mcroce@microsoft.com>
+Description:   Don't wait for any other CPUs on reboot and
+               avoid anything that could hang.
index aa3bfd6c673b1b80eeb3f4a9c136de7167c24c91..940cbb784e1780963c0fcb313aadf3576800edf9 100644 (file)
@@ -600,3 +600,209 @@ static int __init reboot_setup(char *str)
        return 1;
 }
 __setup("reboot=", reboot_setup);
+
+#ifdef CONFIG_SYSFS
+
+#define REBOOT_COLD_STR                "cold"
+#define REBOOT_WARM_STR                "warm"
+#define REBOOT_HARD_STR                "hard"
+#define REBOOT_SOFT_STR                "soft"
+#define REBOOT_GPIO_STR                "gpio"
+#define REBOOT_UNDEFINED_STR   "undefined"
+
+#define BOOT_TRIPLE_STR                "triple"
+#define BOOT_KBD_STR           "kbd"
+#define BOOT_BIOS_STR          "bios"
+#define BOOT_ACPI_STR          "acpi"
+#define BOOT_EFI_STR           "efi"
+#define BOOT_CF9_FORCE_STR     "cf9_force"
+#define BOOT_CF9_SAFE_STR      "cf9_safe"
+
+static ssize_t mode_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+       const char *val;
+
+       switch (reboot_mode) {
+       case REBOOT_COLD:
+               val = REBOOT_COLD_STR;
+               break;
+       case REBOOT_WARM:
+               val = REBOOT_WARM_STR;
+               break;
+       case REBOOT_HARD:
+               val = REBOOT_HARD_STR;
+               break;
+       case REBOOT_SOFT:
+               val = REBOOT_SOFT_STR;
+               break;
+       case REBOOT_GPIO:
+               val = REBOOT_GPIO_STR;
+               break;
+       default:
+               val = REBOOT_UNDEFINED_STR;
+       }
+
+       return sprintf(buf, "%s\n", val);
+}
+static ssize_t mode_store(struct kobject *kobj, struct kobj_attribute *attr,
+                         const char *buf, size_t count)
+{
+       if (!capable(CAP_SYS_BOOT))
+               return -EPERM;
+
+       if (!strncmp(buf, REBOOT_COLD_STR, strlen(REBOOT_COLD_STR)))
+               reboot_mode = REBOOT_COLD;
+       else if (!strncmp(buf, REBOOT_WARM_STR, strlen(REBOOT_WARM_STR)))
+               reboot_mode = REBOOT_WARM;
+       else if (!strncmp(buf, REBOOT_HARD_STR, strlen(REBOOT_HARD_STR)))
+               reboot_mode = REBOOT_HARD;
+       else if (!strncmp(buf, REBOOT_SOFT_STR, strlen(REBOOT_SOFT_STR)))
+               reboot_mode = REBOOT_SOFT;
+       else if (!strncmp(buf, REBOOT_GPIO_STR, strlen(REBOOT_GPIO_STR)))
+               reboot_mode = REBOOT_GPIO;
+       else
+               return -EINVAL;
+
+       return count;
+}
+static struct kobj_attribute reboot_mode_attr = __ATTR_RW(mode);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+       const char *val;
+
+       switch (reboot_type) {
+       case BOOT_TRIPLE:
+               val = BOOT_TRIPLE_STR;
+               break;
+       case BOOT_KBD:
+               val = BOOT_KBD_STR;
+               break;
+       case BOOT_BIOS:
+               val = BOOT_BIOS_STR;
+               break;
+       case BOOT_ACPI:
+               val = BOOT_ACPI_STR;
+               break;
+       case BOOT_EFI:
+               val = BOOT_EFI_STR;
+               break;
+       case BOOT_CF9_FORCE:
+               val = BOOT_CF9_FORCE_STR;
+               break;
+       case BOOT_CF9_SAFE:
+               val = BOOT_CF9_SAFE_STR;
+               break;
+       default:
+               val = REBOOT_UNDEFINED_STR;
+       }
+
+       return sprintf(buf, "%s\n", val);
+}
+static ssize_t type_store(struct kobject *kobj, struct kobj_attribute *attr,
+                         const char *buf, size_t count)
+{
+       if (!capable(CAP_SYS_BOOT))
+               return -EPERM;
+
+       if (!strncmp(buf, BOOT_TRIPLE_STR, strlen(BOOT_TRIPLE_STR)))
+               reboot_type = BOOT_TRIPLE;
+       else if (!strncmp(buf, BOOT_KBD_STR, strlen(BOOT_KBD_STR)))
+               reboot_type = BOOT_KBD;
+       else if (!strncmp(buf, BOOT_BIOS_STR, strlen(BOOT_BIOS_STR)))
+               reboot_type = BOOT_BIOS;
+       else if (!strncmp(buf, BOOT_ACPI_STR, strlen(BOOT_ACPI_STR)))
+               reboot_type = BOOT_ACPI;
+       else if (!strncmp(buf, BOOT_EFI_STR, strlen(BOOT_EFI_STR)))
+               reboot_type = BOOT_EFI;
+       else if (!strncmp(buf, BOOT_CF9_FORCE_STR, strlen(BOOT_CF9_FORCE_STR)))
+               reboot_type = BOOT_CF9_FORCE;
+       else if (!strncmp(buf, BOOT_CF9_SAFE_STR, strlen(BOOT_CF9_SAFE_STR)))
+               reboot_type = BOOT_CF9_SAFE;
+       else
+               return -EINVAL;
+
+       return count;
+}
+static struct kobj_attribute reboot_type_attr = __ATTR_RW(type);
+
+static ssize_t cpu_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d\n", reboot_cpu);
+}
+static ssize_t cpu_store(struct kobject *kobj, struct kobj_attribute *attr,
+                         const char *buf, size_t count)
+{
+       unsigned int cpunum;
+       int rc;
+
+       if (!capable(CAP_SYS_BOOT))
+               return -EPERM;
+
+       rc = kstrtouint(buf, 0, &cpunum);
+
+       if (rc)
+               return rc;
+
+       if (cpunum >= num_possible_cpus())
+               return -ERANGE;
+
+       reboot_cpu = cpunum;
+
+       return count;
+}
+static struct kobj_attribute reboot_cpu_attr = __ATTR_RW(cpu);
+
+static ssize_t force_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d\n", reboot_force);
+}
+static ssize_t force_store(struct kobject *kobj, struct kobj_attribute *attr,
+                         const char *buf, size_t count)
+{
+       bool res;
+
+       if (!capable(CAP_SYS_BOOT))
+               return -EPERM;
+
+       if (kstrtobool(buf, &res))
+               return -EINVAL;
+
+       reboot_force = res;
+
+       return count;
+}
+static struct kobj_attribute reboot_force_attr = __ATTR_RW(force);
+
+static struct attribute *reboot_attrs[] = {
+       &reboot_mode_attr.attr,
+       &reboot_type_attr.attr,
+       &reboot_cpu_attr.attr,
+       &reboot_force_attr.attr,
+       NULL,
+};
+
+static const struct attribute_group reboot_attr_group = {
+       .attrs = reboot_attrs,
+};
+
+static int __init reboot_ksysfs_init(void)
+{
+       struct kobject *reboot_kobj;
+       int ret;
+
+       reboot_kobj = kobject_create_and_add("reboot", kernel_kobj);
+       if (!reboot_kobj)
+               return -ENOMEM;
+
+       ret = sysfs_create_group(reboot_kobj, &reboot_attr_group);
+       if (ret) {
+               kobject_put(reboot_kobj);
+               return ret;
+       }
+
+       return 0;
+}
+late_initcall(reboot_ksysfs_init);
+
+#endif