xref: /linux/drivers/power/reset/reboot-mode.c (revision 5ea5880764cbb164afb17a62e76ca75dc371409d)
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