mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-31 22:23:05 +00:00

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>
205 lines
4.6 KiB
C
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");
|