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