1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd 4 */ 5 6 #include <linux/device.h> 7 #include <linux/err.h> 8 #include <linux/init.h> 9 #include <linux/kernel.h> 10 #include <linux/list.h> 11 #include <linux/module.h> 12 #include <linux/of.h> 13 #include <linux/reboot.h> 14 #include <linux/reboot-mode.h> 15 #include <linux/slab.h> 16 #include <linux/string.h> 17 18 #define PREFIX "mode-" 19 20 struct mode_info { 21 const char *mode; 22 u32 magic; 23 struct list_head list; 24 }; 25 26 struct reboot_mode_sysfs_data { 27 struct device *reboot_mode_device; 28 struct list_head head; 29 }; 30 31 static inline void reboot_mode_release_list(struct reboot_mode_sysfs_data *priv) 32 { 33 struct mode_info *info; 34 struct mode_info *next; 35 36 list_for_each_entry_safe(info, next, &priv->head, list) { 37 list_del(&info->list); 38 kfree_const(info->mode); 39 kfree(info); 40 } 41 } 42 43 static ssize_t reboot_modes_show(struct device *dev, struct device_attribute *attr, char *buf) 44 { 45 struct reboot_mode_sysfs_data *priv; 46 struct mode_info *sysfs_info; 47 ssize_t size = 0; 48 49 priv = dev_get_drvdata(dev); 50 if (!priv) 51 return -ENODATA; 52 53 list_for_each_entry(sysfs_info, &priv->head, list) 54 size += sysfs_emit_at(buf, size, "%s ", sysfs_info->mode); 55 56 if (!size) 57 return -ENODATA; 58 59 return size + sysfs_emit_at(buf, size - 1, "\n"); 60 } 61 static DEVICE_ATTR_RO(reboot_modes); 62 63 static struct attribute *reboot_mode_attrs[] = { 64 &dev_attr_reboot_modes.attr, 65 NULL, 66 }; 67 ATTRIBUTE_GROUPS(reboot_mode); 68 69 static const struct class reboot_mode_class = { 70 .name = "reboot-mode", 71 .dev_groups = reboot_mode_groups, 72 }; 73 74 static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot, 75 const char *cmd) 76 { 77 const char *normal = "normal"; 78 struct mode_info *info; 79 char cmd_[110]; 80 81 if (!cmd) 82 cmd = normal; 83 84 list_for_each_entry(info, &reboot->head, list) 85 if (!strcmp(info->mode, cmd)) 86 return info->magic; 87 88 /* try to match again, replacing characters impossible in DT */ 89 if (strscpy(cmd_, cmd, sizeof(cmd_)) == -E2BIG) 90 return 0; 91 92 strreplace(cmd_, ' ', '-'); 93 strreplace(cmd_, ',', '-'); 94 strreplace(cmd_, '/', '-'); 95 96 list_for_each_entry(info, &reboot->head, list) 97 if (!strcmp(info->mode, cmd_)) 98 return info->magic; 99 100 return 0; 101 } 102 103 static int reboot_mode_notify(struct notifier_block *this, 104 unsigned long mode, void *cmd) 105 { 106 struct reboot_mode_driver *reboot; 107 unsigned int magic; 108 109 reboot = container_of(this, struct reboot_mode_driver, reboot_notifier); 110 magic = get_reboot_mode_magic(reboot, cmd); 111 if (magic) 112 reboot->write(reboot, magic); 113 114 return NOTIFY_DONE; 115 } 116 117 static int reboot_mode_create_device(struct reboot_mode_driver *reboot) 118 { 119 struct reboot_mode_sysfs_data *priv; 120 struct mode_info *sysfs_info; 121 struct mode_info *info; 122 int ret; 123 124 priv = kzalloc_obj(*priv, GFP_KERNEL); 125 if (!priv) 126 return -ENOMEM; 127 128 INIT_LIST_HEAD(&priv->head); 129 130 list_for_each_entry(info, &reboot->head, list) { 131 sysfs_info = kzalloc_obj(*sysfs_info, GFP_KERNEL); 132 if (!sysfs_info) { 133 ret = -ENOMEM; 134 goto error; 135 } 136 137 sysfs_info->mode = kstrdup_const(info->mode, GFP_KERNEL); 138 if (!sysfs_info->mode) { 139 kfree(sysfs_info); 140 ret = -ENOMEM; 141 goto error; 142 } 143 144 list_add_tail(&sysfs_info->list, &priv->head); 145 } 146 147 priv->reboot_mode_device = device_create(&reboot_mode_class, NULL, 0, 148 (void *)priv, "%s", 149 reboot->dev->driver->name); 150 if (IS_ERR(priv->reboot_mode_device)) { 151 ret = PTR_ERR(priv->reboot_mode_device); 152 goto error; 153 } 154 155 return 0; 156 157 error: 158 reboot_mode_release_list(priv); 159 kfree(priv); 160 return ret; 161 } 162 163 /** 164 * reboot_mode_register - register a reboot mode driver 165 * @reboot: reboot mode driver 166 * 167 * Returns: 0 on success or a negative error code on failure. 168 */ 169 int reboot_mode_register(struct reboot_mode_driver *reboot) 170 { 171 struct mode_info *info; 172 struct property *prop; 173 struct device_node *np = reboot->dev->of_node; 174 size_t len = strlen(PREFIX); 175 int ret; 176 177 INIT_LIST_HEAD(&reboot->head); 178 179 for_each_property_of_node(np, prop) { 180 if (strncmp(prop->name, PREFIX, len)) 181 continue; 182 183 info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL); 184 if (!info) { 185 ret = -ENOMEM; 186 goto error; 187 } 188 189 if (of_property_read_u32(np, prop->name, &info->magic)) { 190 dev_err(reboot->dev, "reboot mode %s without magic number\n", 191 info->mode); 192 devm_kfree(reboot->dev, info); 193 continue; 194 } 195 196 info->mode = kstrdup_const(prop->name + len, GFP_KERNEL); 197 if (!info->mode) { 198 ret = -ENOMEM; 199 goto error; 200 } else if (info->mode[0] == '\0') { 201 kfree_const(info->mode); 202 ret = -EINVAL; 203 dev_err(reboot->dev, "invalid mode name(%s): too short!\n", 204 prop->name); 205 goto error; 206 } 207 208 list_add_tail(&info->list, &reboot->head); 209 } 210 211 reboot->reboot_notifier.notifier_call = reboot_mode_notify; 212 register_reboot_notifier(&reboot->reboot_notifier); 213 214 ret = reboot_mode_create_device(reboot); 215 if (ret) 216 goto error; 217 218 return 0; 219 220 error: 221 reboot_mode_unregister(reboot); 222 return ret; 223 } 224 EXPORT_SYMBOL_GPL(reboot_mode_register); 225 226 static int reboot_mode_match_by_name(struct device *dev, const void *data) 227 { 228 const char *name = data; 229 230 if (!dev || !data) 231 return 0; 232 233 return dev_name(dev) && strcmp(dev_name(dev), name) == 0; 234 } 235 236 static inline void reboot_mode_unregister_device(struct reboot_mode_driver *reboot) 237 { 238 struct reboot_mode_sysfs_data *priv; 239 struct device *reboot_mode_device; 240 241 reboot_mode_device = class_find_device(&reboot_mode_class, NULL, reboot->dev->driver->name, 242 reboot_mode_match_by_name); 243 244 if (!reboot_mode_device) 245 return; 246 247 priv = dev_get_drvdata(reboot_mode_device); 248 device_unregister(reboot_mode_device); 249 250 if (!priv) 251 return; 252 253 reboot_mode_release_list(priv); 254 kfree(priv); 255 } 256 257 /** 258 * reboot_mode_unregister - unregister a reboot mode driver 259 * @reboot: reboot mode driver 260 */ 261 int reboot_mode_unregister(struct reboot_mode_driver *reboot) 262 { 263 struct mode_info *info; 264 265 unregister_reboot_notifier(&reboot->reboot_notifier); 266 reboot_mode_unregister_device(reboot); 267 268 list_for_each_entry(info, &reboot->head, list) 269 kfree_const(info->mode); 270 271 return 0; 272 } 273 EXPORT_SYMBOL_GPL(reboot_mode_unregister); 274 275 static void devm_reboot_mode_release(struct device *dev, void *res) 276 { 277 reboot_mode_unregister(*(struct reboot_mode_driver **)res); 278 } 279 280 /** 281 * devm_reboot_mode_register() - resource managed reboot_mode_register() 282 * @dev: device to associate this resource with 283 * @reboot: reboot mode driver 284 * 285 * Returns: 0 on success or a negative error code on failure. 286 */ 287 int devm_reboot_mode_register(struct device *dev, 288 struct reboot_mode_driver *reboot) 289 { 290 struct reboot_mode_driver **dr; 291 int rc; 292 293 dr = devres_alloc(devm_reboot_mode_release, sizeof(*dr), GFP_KERNEL); 294 if (!dr) 295 return -ENOMEM; 296 297 rc = reboot_mode_register(reboot); 298 if (rc) { 299 devres_free(dr); 300 return rc; 301 } 302 303 *dr = reboot; 304 devres_add(dev, dr); 305 306 return 0; 307 } 308 EXPORT_SYMBOL_GPL(devm_reboot_mode_register); 309 310 static int devm_reboot_mode_match(struct device *dev, void *res, void *data) 311 { 312 struct reboot_mode_driver **p = res; 313 314 if (WARN_ON(!p || !*p)) 315 return 0; 316 317 return *p == data; 318 } 319 320 /** 321 * devm_reboot_mode_unregister() - resource managed reboot_mode_unregister() 322 * @dev: device to associate this resource with 323 * @reboot: reboot mode driver 324 */ 325 void devm_reboot_mode_unregister(struct device *dev, 326 struct reboot_mode_driver *reboot) 327 { 328 WARN_ON(devres_release(dev, 329 devm_reboot_mode_release, 330 devm_reboot_mode_match, reboot)); 331 } 332 EXPORT_SYMBOL_GPL(devm_reboot_mode_unregister); 333 334 static int __init reboot_mode_init(void) 335 { 336 return class_register(&reboot_mode_class); 337 } 338 339 static void __exit reboot_mode_exit(void) 340 { 341 class_unregister(&reboot_mode_class); 342 } 343 344 subsys_initcall(reboot_mode_init); 345 module_exit(reboot_mode_exit); 346 347 MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>"); 348 MODULE_DESCRIPTION("System reboot mode core library"); 349 MODULE_LICENSE("GPL v2"); 350