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