linux-loongson/drivers/power/reset/reboot-mode.c
André Draszik ca3d2ea523 power: reset: reboot-mode: better compatibility with DT (replace ' ,/')
This driver's purpose is to parse boot modes described in DT, via key
(node name) / value pairs, and to match them to a reboot mode requested
by the kernel. Unfortunately, DT node names can not contain certain
characters, like space ' ' or comma ',' or slash '/', while the
requested reboot mode may.

This is a problem because it makes it impossible to match reboot modes
containing any of those characters.

For example, this makes it impossible to communicate DM verity errors
to the boot loader - DM verity errors trigger a reboot with mode
"dm-verity device corrupted" in drivers/md/dm-verity-target.c and
devices typically have to take action in that case [1]. Changing this
string itself is not feasible, see e.g. discussion in [2], but would
also just cover this one case.

Another example is Android, which may use comma in the reboot mode
string, e.g. as "shutdown,thermal" in [3].

The kernel also shouldn't prescribe what characters are allowed inside
the boot mode string for a user to set. It hasn't done this so far, and
introducing such a restriction would be an interface break and
arbitrarily enforce a random new policy.

Therefore, update this driver to do another round of string matching,
after replacing the common characters mentioned above with dash '-', if
a match hasn't been found without doing said replacement.
This now allows us to have DT entries of e.g.:

    mode-dm-verity-device-corrupted = <...>

and so on.

Link: https://cs.android.com/android/kernel/superproject/+/android14-gs-pixel-6.1:private/google-modules/power/reset/exynos-gs101-reboot.c;l=144 [1]
Link: https://lore.kernel.org/all/CAAFS_9FuSb7PZwQ2itUh_H7ZdhvAEiiX7fhxJ4kmmv9JCaHmkA@mail.gmail.com/ [2]
Link: https://cs.android.com/android/platform/superproject/main/+/main:system/core/init/reboot_utils.cpp;drc=79ad1e2e9bf1628c141c8cd2fbb4f3df61a6ba75;l=122 [3]
Signed-off-by: André Draszik <andre.draszik@linaro.org>
Link: https://lore.kernel.org/r/20250307-reboot-mode-chars-v1-1-d83ff95da524@linaro.org
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
2025-04-28 00:05:31 +02:00

205 lines
4.6 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
*/
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/reboot.h>
#include <linux/reboot-mode.h>
#define PREFIX "mode-"
struct mode_info {
const char *mode;
u32 magic;
struct list_head list;
};
static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot,
const char *cmd)
{
const char *normal = "normal";
struct mode_info *info;
char cmd_[110];
if (!cmd)
cmd = normal;
list_for_each_entry(info, &reboot->head, list)
if (!strcmp(info->mode, cmd))
return info->magic;
/* try to match again, replacing characters impossible in DT */
if (strscpy(cmd_, cmd, sizeof(cmd_)) == -E2BIG)
return 0;
strreplace(cmd_, ' ', '-');
strreplace(cmd_, ',', '-');
strreplace(cmd_, '/', '-');
list_for_each_entry(info, &reboot->head, list)
if (!strcmp(info->mode, cmd_))
return info->magic;
return 0;
}
static int reboot_mode_notify(struct notifier_block *this,
unsigned long mode, void *cmd)
{
struct reboot_mode_driver *reboot;
unsigned int magic;
reboot = container_of(this, struct reboot_mode_driver, reboot_notifier);
magic = get_reboot_mode_magic(reboot, cmd);
if (magic)
reboot->write(reboot, magic);
return NOTIFY_DONE;
}
/**
* reboot_mode_register - register a reboot mode driver
* @reboot: reboot mode driver
*
* Returns: 0 on success or a negative error code on failure.
*/
int reboot_mode_register(struct reboot_mode_driver *reboot)
{
struct mode_info *info;
struct property *prop;
struct device_node *np = reboot->dev->of_node;
size_t len = strlen(PREFIX);
int ret;
INIT_LIST_HEAD(&reboot->head);
for_each_property_of_node(np, prop) {
if (strncmp(prop->name, PREFIX, len))
continue;
info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL);
if (!info) {
ret = -ENOMEM;
goto error;
}
if (of_property_read_u32(np, prop->name, &info->magic)) {
dev_err(reboot->dev, "reboot mode %s without magic number\n",
info->mode);
devm_kfree(reboot->dev, info);
continue;
}
info->mode = kstrdup_const(prop->name + len, GFP_KERNEL);
if (!info->mode) {
ret = -ENOMEM;
goto error;
} else if (info->mode[0] == '\0') {
kfree_const(info->mode);
ret = -EINVAL;
dev_err(reboot->dev, "invalid mode name(%s): too short!\n",
prop->name);
goto error;
}
list_add_tail(&info->list, &reboot->head);
}
reboot->reboot_notifier.notifier_call = reboot_mode_notify;
register_reboot_notifier(&reboot->reboot_notifier);
return 0;
error:
list_for_each_entry(info, &reboot->head, list)
kfree_const(info->mode);
return ret;
}
EXPORT_SYMBOL_GPL(reboot_mode_register);
/**
* reboot_mode_unregister - unregister a reboot mode driver
* @reboot: reboot mode driver
*/
int reboot_mode_unregister(struct reboot_mode_driver *reboot)
{
struct mode_info *info;
unregister_reboot_notifier(&reboot->reboot_notifier);
list_for_each_entry(info, &reboot->head, list)
kfree_const(info->mode);
return 0;
}
EXPORT_SYMBOL_GPL(reboot_mode_unregister);
static void devm_reboot_mode_release(struct device *dev, void *res)
{
reboot_mode_unregister(*(struct reboot_mode_driver **)res);
}
/**
* devm_reboot_mode_register() - resource managed reboot_mode_register()
* @dev: device to associate this resource with
* @reboot: reboot mode driver
*
* Returns: 0 on success or a negative error code on failure.
*/
int devm_reboot_mode_register(struct device *dev,
struct reboot_mode_driver *reboot)
{
struct reboot_mode_driver **dr;
int rc;
dr = devres_alloc(devm_reboot_mode_release, sizeof(*dr), GFP_KERNEL);
if (!dr)
return -ENOMEM;
rc = reboot_mode_register(reboot);
if (rc) {
devres_free(dr);
return rc;
}
*dr = reboot;
devres_add(dev, dr);
return 0;
}
EXPORT_SYMBOL_GPL(devm_reboot_mode_register);
static int devm_reboot_mode_match(struct device *dev, void *res, void *data)
{
struct reboot_mode_driver **p = res;
if (WARN_ON(!p || !*p))
return 0;
return *p == data;
}
/**
* devm_reboot_mode_unregister() - resource managed reboot_mode_unregister()
* @dev: device to associate this resource with
* @reboot: reboot mode driver
*/
void devm_reboot_mode_unregister(struct device *dev,
struct reboot_mode_driver *reboot)
{
WARN_ON(devres_release(dev,
devm_reboot_mode_release,
devm_reboot_mode_match, reboot));
}
EXPORT_SYMBOL_GPL(devm_reboot_mode_unregister);
MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
MODULE_DESCRIPTION("System reboot mode core library");
MODULE_LICENSE("GPL v2");