xref: /OK3568_Linux_fs/kernel/drivers/power/reset/reboot-mode.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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/init.h>
8 #include <linux/kernel.h>
9 #include <linux/kobject.h>
10 #include <linux/module.h>
11 #include <linux/of.h>
12 #include <linux/reboot.h>
13 #include <linux/reboot-mode.h>
14 #include <linux/sysfs.h>
15 
16 #define PREFIX "mode-"
17 
18 struct mode_info {
19 	const char *mode;
20 	u32 magic;
21 	struct list_head list;
22 };
23 
24 static const char *boot_mode = "coldboot";
25 
boot_mode_show(struct kobject * kobj,struct kobj_attribute * attr,char * buf)26 static ssize_t boot_mode_show(struct kobject *kobj, struct kobj_attribute *attr,
27 			      char *buf)
28 {
29 	return scnprintf(buf, PAGE_SIZE, "%s\n", boot_mode);
30 }
31 
32 static struct kobj_attribute kobj_boot_mode = __ATTR_RO(boot_mode);
33 
get_reboot_mode_magic(struct reboot_mode_driver * reboot,const char * cmd)34 static int get_reboot_mode_magic(struct reboot_mode_driver *reboot,
35 				 const char *cmd)
36 {
37 	const char *normal = "normal";
38 	int magic = 0;
39 	struct mode_info *info;
40 
41 	if (!cmd || !cmd[0])
42 		cmd = normal;
43 
44 	list_for_each_entry(info, &reboot->head, list) {
45 		if (!strcmp(info->mode, cmd)) {
46 			magic = info->magic;
47 			break;
48 		}
49 	}
50 
51 	return magic;
52 }
53 
54 static int last_magic;
55 
reboot_mode_write(struct reboot_mode_driver * reboot,const void * cmd)56 static void reboot_mode_write(struct reboot_mode_driver *reboot,
57 			      const void *cmd)
58 {
59 	int magic;
60 
61 	magic = get_reboot_mode_magic(reboot, cmd);
62 	if (!magic)
63 		magic = get_reboot_mode_magic(reboot, NULL);
64 	if (magic) {
65 		reboot->write(reboot, magic);
66 		last_magic = magic;
67 	}
68 }
69 
reboot_mode_notify(struct notifier_block * this,unsigned long mode,void * cmd)70 static int reboot_mode_notify(struct notifier_block *this,
71 			      unsigned long mode, void *cmd)
72 {
73 	struct reboot_mode_driver *reboot;
74 
75 	reboot = container_of(this, struct reboot_mode_driver, reboot_notifier);
76 	reboot_mode_write(reboot, cmd);
77 
78 	return NOTIFY_DONE;
79 }
80 
reboot_mode_pre_restart_notify(struct notifier_block * this,unsigned long mode,void * cmd)81 static int reboot_mode_pre_restart_notify(struct notifier_block *this,
82 			      unsigned long mode, void *cmd)
83 {
84 	struct reboot_mode_driver *reboot;
85 
86 	reboot = container_of(this, struct reboot_mode_driver, pre_restart_notifier);
87 	if (cmd || !last_magic)
88 		reboot_mode_write(reboot, cmd);
89 
90 	return NOTIFY_DONE;
91 }
92 
reboot_mode_panic_notify(struct notifier_block * this,unsigned long ev,void * ptr)93 static int reboot_mode_panic_notify(struct notifier_block *this,
94 				      unsigned long ev, void *ptr)
95 {
96 	struct reboot_mode_driver *reboot;
97 	const char *cmd = "panic";
98 
99 	reboot = container_of(this, struct reboot_mode_driver, panic_notifier);
100 	reboot_mode_write(reboot, cmd);
101 
102 	return NOTIFY_DONE;
103 }
104 
boot_mode_parse(struct reboot_mode_driver * reboot)105 static int boot_mode_parse(struct reboot_mode_driver *reboot)
106 {
107 	struct mode_info *info;
108 	unsigned int magic = reboot->read(reboot);
109 
110 	list_for_each_entry(info, &reboot->head, list) {
111 		if (info->magic == magic) {
112 			boot_mode = info->mode;
113 			break;
114 		}
115 	}
116 
117 	return 0;
118 }
119 
120 /**
121  * reboot_mode_register - register a reboot mode driver
122  * @reboot: reboot mode driver
123  *
124  * Returns: 0 on success or a negative error code on failure.
125  */
reboot_mode_register(struct reboot_mode_driver * reboot)126 int reboot_mode_register(struct reboot_mode_driver *reboot)
127 {
128 	struct mode_info *info;
129 	struct property *prop;
130 	struct device_node *np = reboot->dev->of_node;
131 	size_t len = strlen(PREFIX);
132 	int ret;
133 
134 	INIT_LIST_HEAD(&reboot->head);
135 
136 	for_each_property_of_node(np, prop) {
137 		if (strncmp(prop->name, PREFIX, len))
138 			continue;
139 
140 		info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL);
141 		if (!info) {
142 			ret = -ENOMEM;
143 			goto error;
144 		}
145 
146 		if (of_property_read_u32(np, prop->name, &info->magic)) {
147 			dev_err(reboot->dev, "reboot mode %s without magic number\n",
148 				info->mode);
149 			devm_kfree(reboot->dev, info);
150 			continue;
151 		}
152 
153 		info->mode = kstrdup_const(prop->name + len, GFP_KERNEL);
154 		if (!info->mode) {
155 			ret =  -ENOMEM;
156 			goto error;
157 		} else if (info->mode[0] == '\0') {
158 			kfree_const(info->mode);
159 			ret = -EINVAL;
160 			dev_err(reboot->dev, "invalid mode name(%s): too short!\n",
161 				prop->name);
162 			goto error;
163 		}
164 
165 		list_add_tail(&info->list, &reboot->head);
166 	}
167 
168 	boot_mode_parse(reboot);
169 	reboot->reboot_notifier.notifier_call = reboot_mode_notify;
170 	reboot->pre_restart_notifier.notifier_call = reboot_mode_pre_restart_notify;
171 	reboot->panic_notifier.notifier_call = reboot_mode_panic_notify;
172 	register_reboot_notifier(&reboot->reboot_notifier);
173 	register_pre_restart_handler(&reboot->pre_restart_notifier);
174 	atomic_notifier_chain_register(&panic_notifier_list,
175 				       &reboot->panic_notifier);
176 	ret = sysfs_create_file(kernel_kobj, &kobj_boot_mode.attr);
177 
178 	return ret;
179 
180 error:
181 	list_for_each_entry(info, &reboot->head, list)
182 		kfree_const(info->mode);
183 
184 	return ret;
185 }
186 EXPORT_SYMBOL_GPL(reboot_mode_register);
187 
188 /**
189  * reboot_mode_unregister - unregister a reboot mode driver
190  * @reboot: reboot mode driver
191  */
reboot_mode_unregister(struct reboot_mode_driver * reboot)192 int reboot_mode_unregister(struct reboot_mode_driver *reboot)
193 {
194 	struct mode_info *info;
195 
196 	unregister_reboot_notifier(&reboot->reboot_notifier);
197 
198 	list_for_each_entry(info, &reboot->head, list)
199 		kfree_const(info->mode);
200 
201 	return 0;
202 }
203 EXPORT_SYMBOL_GPL(reboot_mode_unregister);
204 
devm_reboot_mode_release(struct device * dev,void * res)205 static void devm_reboot_mode_release(struct device *dev, void *res)
206 {
207 	reboot_mode_unregister(*(struct reboot_mode_driver **)res);
208 }
209 
210 /**
211  * devm_reboot_mode_register() - resource managed reboot_mode_register()
212  * @dev: device to associate this resource with
213  * @reboot: reboot mode driver
214  *
215  * Returns: 0 on success or a negative error code on failure.
216  */
devm_reboot_mode_register(struct device * dev,struct reboot_mode_driver * reboot)217 int devm_reboot_mode_register(struct device *dev,
218 			      struct reboot_mode_driver *reboot)
219 {
220 	struct reboot_mode_driver **dr;
221 	int rc;
222 
223 	dr = devres_alloc(devm_reboot_mode_release, sizeof(*dr), GFP_KERNEL);
224 	if (!dr)
225 		return -ENOMEM;
226 
227 	rc = reboot_mode_register(reboot);
228 	if (rc) {
229 		devres_free(dr);
230 		return rc;
231 	}
232 
233 	*dr = reboot;
234 	devres_add(dev, dr);
235 
236 	return 0;
237 }
238 EXPORT_SYMBOL_GPL(devm_reboot_mode_register);
239 
devm_reboot_mode_match(struct device * dev,void * res,void * data)240 static int devm_reboot_mode_match(struct device *dev, void *res, void *data)
241 {
242 	struct reboot_mode_driver **p = res;
243 
244 	if (WARN_ON(!p || !*p))
245 		return 0;
246 
247 	return *p == data;
248 }
249 
250 /**
251  * devm_reboot_mode_unregister() - resource managed reboot_mode_unregister()
252  * @dev: device to associate this resource with
253  * @reboot: reboot mode driver
254  */
devm_reboot_mode_unregister(struct device * dev,struct reboot_mode_driver * reboot)255 void devm_reboot_mode_unregister(struct device *dev,
256 				 struct reboot_mode_driver *reboot)
257 {
258 	WARN_ON(devres_release(dev,
259 			       devm_reboot_mode_release,
260 			       devm_reboot_mode_match, reboot));
261 }
262 EXPORT_SYMBOL_GPL(devm_reboot_mode_unregister);
263 
264 MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
265 MODULE_DESCRIPTION("System reboot mode core library");
266 MODULE_LICENSE("GPL v2");
267