xref: /linux/drivers/power/reset/reboot-mode.c (revision 1fc5a74b108fc90951890ec513ac81869f5eaff1)
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 
reboot_mode_release_list(struct reboot_mode_sysfs_data * priv)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 
reboot_modes_show(struct device * dev,struct device_attribute * attr,char * buf)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 
get_reboot_mode_magic(struct reboot_mode_driver * reboot,const char * cmd)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 
reboot_mode_notify(struct notifier_block * this,unsigned long mode,void * cmd)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 
reboot_mode_create_device(struct reboot_mode_driver * reboot)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  */
reboot_mode_register(struct reboot_mode_driver * reboot)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 
reboot_mode_match_by_name(struct device * dev,const void * data)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 
reboot_mode_unregister_device(struct reboot_mode_driver * reboot)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  */
reboot_mode_unregister(struct reboot_mode_driver * reboot)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 
devm_reboot_mode_release(struct device * dev,void * res)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  */
devm_reboot_mode_register(struct device * dev,struct reboot_mode_driver * reboot)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 
devm_reboot_mode_match(struct device * dev,void * res,void * data)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  */
devm_reboot_mode_unregister(struct device * dev,struct reboot_mode_driver * reboot)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 
reboot_mode_init(void)342 static int __init reboot_mode_init(void)
343 {
344 	return class_register(&reboot_mode_class);
345 }
346 
reboot_mode_exit(void)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