summaryrefslogtreecommitdiffstats
path: root/drivers/power/reset/reboot-mode.c
diff options
context:
space:
mode:
authorEduard Zingerman <eddyz87@gmail.com>2026-09-04 17:05:56 -0700
committerAlexei Starovoitov <ast@kernel.org>2026-09-04 18:17:30 -0700
commit506ada89629ec7059b96ecfb0dc7d33ece0103ca (patch)
tree0b07297409b68d2333fb4f0eae7f3b072eb68d4c /drivers/power/reset/reboot-mode.c
downloadlinux-stable-506ada89629ec7059b96ecfb0dc7d33ece0103ca.tar.gz
linux-stable-506ada89629ec7059b96ecfb0dc7d33ece0103ca.zip
bpf: mark a NULL kfunc argument precisegrafted
check_kfunc_arg() allows bpf_register_is_null() for nullable arguments w/o marking the underlying scalar register precise. Hence a checkpoint created on such a path would prune against arbitrary scalar value. Fixes: 3bda08b63670 ("bpf: Allow NULL buffers in bpf_dynptr_slice(_rw)") Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> Link: https://lore.kernel.org/r/20260904-register-is-null-precise-fixes-v1-5-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Diffstat (limited to 'drivers/power/reset/reboot-mode.c')
-rw-r--r--drivers/power/reset/reboot-mode.c349
1 files changed, 349 insertions, 0 deletions
diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c
new file mode 100644
index 000000000..d20e44db0
--- /dev/null
+++ b/drivers/power/reset/reboot-mode.c
@@ -0,0 +1,349 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/reboot.h>
+#include <linux/reboot-mode.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#define PREFIX "mode-"
+
+struct mode_info {
+ const char *mode;
+ u32 magic;
+ struct list_head list;
+};
+
+struct reboot_mode_sysfs_data {
+ struct device *reboot_mode_device;
+ struct list_head head;
+};
+
+static inline void reboot_mode_release_list(struct reboot_mode_sysfs_data *priv)
+{
+ struct mode_info *info;
+ struct mode_info *next;
+
+ list_for_each_entry_safe(info, next, &priv->head, list) {
+ list_del(&info->list);
+ kfree_const(info->mode);
+ kfree(info);
+ }
+}
+
+static ssize_t reboot_modes_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct reboot_mode_sysfs_data *priv;
+ struct mode_info *sysfs_info;
+ ssize_t size = 0;
+
+ priv = dev_get_drvdata(dev);
+ if (!priv)
+ return -ENODATA;
+
+ list_for_each_entry(sysfs_info, &priv->head, list)
+ size += sysfs_emit_at(buf, size, "%s ", sysfs_info->mode);
+
+ if (!size)
+ return -ENODATA;
+
+ return size + sysfs_emit_at(buf, size - 1, "\n");
+}
+static DEVICE_ATTR_RO(reboot_modes);
+
+static struct attribute *reboot_mode_attrs[] = {
+ &dev_attr_reboot_modes.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(reboot_mode);
+
+static const struct class reboot_mode_class = {
+ .name = "reboot-mode",
+ .dev_groups = reboot_mode_groups,
+};
+
+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;
+}
+
+static int reboot_mode_create_device(struct reboot_mode_driver *reboot)
+{
+ struct reboot_mode_sysfs_data *priv;
+ struct mode_info *sysfs_info;
+ struct mode_info *info;
+ int ret;
+
+ priv = kzalloc_obj(*priv, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&priv->head);
+
+ list_for_each_entry(info, &reboot->head, list) {
+ sysfs_info = kzalloc_obj(*sysfs_info, GFP_KERNEL);
+ if (!sysfs_info) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ sysfs_info->mode = kstrdup_const(info->mode, GFP_KERNEL);
+ if (!sysfs_info->mode) {
+ kfree(sysfs_info);
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ list_add_tail(&sysfs_info->list, &priv->head);
+ }
+
+ priv->reboot_mode_device = device_create(&reboot_mode_class, NULL, 0,
+ (void *)priv, "%s",
+ reboot->dev->driver->name);
+ if (IS_ERR(priv->reboot_mode_device)) {
+ ret = PTR_ERR(priv->reboot_mode_device);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ reboot_mode_release_list(priv);
+ kfree(priv);
+ return ret;
+}
+
+/**
+ * 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);
+
+ ret = reboot_mode_create_device(reboot);
+ if (ret)
+ goto error;
+
+ return 0;
+
+error:
+ reboot_mode_unregister(reboot);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(reboot_mode_register);
+
+static int reboot_mode_match_by_name(struct device *dev, const void *data)
+{
+ const char *name = data;
+
+ if (!dev || !data)
+ return 0;
+
+ return dev_name(dev) && strcmp(dev_name(dev), name) == 0;
+}
+
+static inline void reboot_mode_unregister_device(struct reboot_mode_driver *reboot)
+{
+ struct reboot_mode_sysfs_data *priv;
+ struct device *reboot_mode_device;
+
+ reboot_mode_device = class_find_device(&reboot_mode_class, NULL, reboot->dev->driver->name,
+ reboot_mode_match_by_name);
+
+ if (!reboot_mode_device)
+ return;
+
+ priv = dev_get_drvdata(reboot_mode_device);
+ device_unregister(reboot_mode_device);
+
+ if (!priv)
+ return;
+
+ reboot_mode_release_list(priv);
+ kfree(priv);
+}
+
+/**
+ * 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);
+ reboot_mode_unregister_device(reboot);
+
+ 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);
+
+static int __init reboot_mode_init(void)
+{
+ return class_register(&reboot_mode_class);
+}
+
+static void __exit reboot_mode_exit(void)
+{
+ class_unregister(&reboot_mode_class);
+}
+
+subsys_initcall(reboot_mode_init);
+module_exit(reboot_mode_exit);
+
+MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
+MODULE_DESCRIPTION("System reboot mode core library");
+MODULE_LICENSE("GPL v2");