Commit | Line | Data |
---|---|---|
17639f67 WL |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Implements pstore backend driver that write to block (or non-block) storage | |
4 | * devices, using the pstore/zone API. | |
5 | */ | |
6 | ||
7 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
8 | ||
9 | #include <linux/kernel.h> | |
10 | #include <linux/module.h> | |
17639f67 WL |
11 | #include <linux/blkdev.h> |
12 | #include <linux/string.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/of_address.h> | |
15 | #include <linux/platform_device.h> | |
16 | #include <linux/pstore_blk.h> | |
7bb9557b KC |
17 | #include <linux/fs.h> |
18 | #include <linux/file.h> | |
19 | #include <linux/init_syscalls.h> | |
17639f67 | 20 | #include <linux/mount.h> |
17639f67 WL |
21 | |
22 | static long kmsg_size = CONFIG_PSTORE_BLK_KMSG_SIZE; | |
23 | module_param(kmsg_size, long, 0400); | |
24 | MODULE_PARM_DESC(kmsg_size, "kmsg dump record size in kbytes"); | |
25 | ||
26 | static int max_reason = CONFIG_PSTORE_BLK_MAX_REASON; | |
27 | module_param(max_reason, int, 0400); | |
28 | MODULE_PARM_DESC(max_reason, | |
29 | "maximum reason for kmsg dump (default 2: Oops and Panic)"); | |
30 | ||
0dc06826 WL |
31 | #if IS_ENABLED(CONFIG_PSTORE_PMSG) |
32 | static long pmsg_size = CONFIG_PSTORE_BLK_PMSG_SIZE; | |
33 | #else | |
34 | static long pmsg_size = -1; | |
35 | #endif | |
36 | module_param(pmsg_size, long, 0400); | |
37 | MODULE_PARM_DESC(pmsg_size, "pmsg size in kbytes"); | |
38 | ||
cc9c4d1b WL |
39 | #if IS_ENABLED(CONFIG_PSTORE_CONSOLE) |
40 | static long console_size = CONFIG_PSTORE_BLK_CONSOLE_SIZE; | |
41 | #else | |
42 | static long console_size = -1; | |
43 | #endif | |
44 | module_param(console_size, long, 0400); | |
45 | MODULE_PARM_DESC(console_size, "console size in kbytes"); | |
46 | ||
34327e9f WL |
47 | #if IS_ENABLED(CONFIG_PSTORE_FTRACE) |
48 | static long ftrace_size = CONFIG_PSTORE_BLK_FTRACE_SIZE; | |
49 | #else | |
50 | static long ftrace_size = -1; | |
51 | #endif | |
52 | module_param(ftrace_size, long, 0400); | |
53 | MODULE_PARM_DESC(ftrace_size, "ftrace size in kbytes"); | |
54 | ||
f8feafea KC |
55 | static bool best_effort; |
56 | module_param(best_effort, bool, 0400); | |
57 | MODULE_PARM_DESC(best_effort, "use best effort to write (i.e. do not require storage driver pstore support, default: off)"); | |
58 | ||
17639f67 WL |
59 | /* |
60 | * blkdev - the block device to use for pstore storage | |
c811659b | 61 | * See Documentation/admin-guide/pstore-blk.rst for details. |
17639f67 WL |
62 | */ |
63 | static char blkdev[80] = CONFIG_PSTORE_BLK_BLKDEV; | |
64 | module_param_string(blkdev, blkdev, 80, 0400); | |
65 | MODULE_PARM_DESC(blkdev, "block device for pstore storage"); | |
66 | ||
67 | /* | |
68 | * All globals must only be accessed under the pstore_blk_lock | |
69 | * during the register/unregister functions. | |
70 | */ | |
71 | static DEFINE_MUTEX(pstore_blk_lock); | |
7bb9557b | 72 | static struct file *psblk_file; |
1d1f6cc5 | 73 | static struct pstore_device_info *pstore_device_info; |
17639f67 | 74 | |
1525fb3b WL |
75 | #define check_size(name, alignsize) ({ \ |
76 | long _##name_ = (name); \ | |
77 | _##name_ = _##name_ <= 0 ? 0 : (_##name_ * 1024); \ | |
78 | if (_##name_ & ((alignsize) - 1)) { \ | |
79 | pr_info(#name " must align to %d\n", \ | |
80 | (alignsize)); \ | |
81 | _##name_ = ALIGN(name, (alignsize)); \ | |
82 | } \ | |
83 | _##name_; \ | |
84 | }) | |
85 | ||
2a03ddbd KC |
86 | #define verify_size(name, alignsize, enabled) { \ |
87 | long _##name_; \ | |
88 | if (enabled) \ | |
89 | _##name_ = check_size(name, alignsize); \ | |
90 | else \ | |
91 | _##name_ = 0; \ | |
92 | /* Synchronize module parameters with resuls. */ \ | |
93 | name = _##name_ / 1024; \ | |
1d1f6cc5 | 94 | dev->zone.name = _##name_; \ |
2a03ddbd KC |
95 | } |
96 | ||
7dcb7848 | 97 | static int __register_pstore_device(struct pstore_device_info *dev) |
17639f67 WL |
98 | { |
99 | int ret; | |
100 | ||
7dcb7848 WL |
101 | lockdep_assert_held(&pstore_blk_lock); |
102 | ||
6eed261f KC |
103 | if (!dev) { |
104 | pr_err("NULL device info\n"); | |
17639f67 | 105 | return -EINVAL; |
6eed261f | 106 | } |
1d1f6cc5 | 107 | if (!dev->zone.total_size) { |
6eed261f KC |
108 | pr_err("zero sized device\n"); |
109 | return -EINVAL; | |
110 | } | |
1d1f6cc5 | 111 | if (!dev->zone.read) { |
6eed261f KC |
112 | pr_err("no read handler for device\n"); |
113 | return -EINVAL; | |
114 | } | |
1d1f6cc5 | 115 | if (!dev->zone.write) { |
6eed261f KC |
116 | pr_err("no write handler for device\n"); |
117 | return -EINVAL; | |
118 | } | |
17639f67 | 119 | |
17639f67 | 120 | /* someone already registered before */ |
1d1f6cc5 | 121 | if (pstore_device_info) |
17639f67 | 122 | return -EBUSY; |
7dcb7848 | 123 | |
17639f67 WL |
124 | /* zero means not limit on which backends to attempt to store. */ |
125 | if (!dev->flags) | |
126 | dev->flags = UINT_MAX; | |
127 | ||
1d1f6cc5 | 128 | /* Copy in module parameters. */ |
17639f67 | 129 | verify_size(kmsg_size, 4096, dev->flags & PSTORE_FLAGS_DMESG); |
0dc06826 | 130 | verify_size(pmsg_size, 4096, dev->flags & PSTORE_FLAGS_PMSG); |
cc9c4d1b | 131 | verify_size(console_size, 4096, dev->flags & PSTORE_FLAGS_CONSOLE); |
34327e9f | 132 | verify_size(ftrace_size, 4096, dev->flags & PSTORE_FLAGS_FTRACE); |
1d1f6cc5 KC |
133 | dev->zone.max_reason = max_reason; |
134 | ||
135 | /* Initialize required zone ownership details. */ | |
136 | dev->zone.name = KBUILD_MODNAME; | |
137 | dev->zone.owner = THIS_MODULE; | |
138 | ||
139 | ret = register_pstore_zone(&dev->zone); | |
140 | if (ret == 0) | |
141 | pstore_device_info = dev; | |
17639f67 | 142 | |
7dcb7848 WL |
143 | return ret; |
144 | } | |
145 | /** | |
146 | * register_pstore_device() - register non-block device to pstore/blk | |
147 | * | |
148 | * @dev: non-block device information | |
149 | * | |
150 | * Return: | |
151 | * * 0 - OK | |
152 | * * Others - something error. | |
153 | */ | |
154 | int register_pstore_device(struct pstore_device_info *dev) | |
155 | { | |
156 | int ret; | |
157 | ||
158 | mutex_lock(&pstore_blk_lock); | |
159 | ret = __register_pstore_device(dev); | |
17639f67 | 160 | mutex_unlock(&pstore_blk_lock); |
7dcb7848 | 161 | |
17639f67 WL |
162 | return ret; |
163 | } | |
7dcb7848 | 164 | EXPORT_SYMBOL_GPL(register_pstore_device); |
17639f67 | 165 | |
7dcb7848 | 166 | static void __unregister_pstore_device(struct pstore_device_info *dev) |
17639f67 | 167 | { |
7dcb7848 | 168 | lockdep_assert_held(&pstore_blk_lock); |
1d1f6cc5 KC |
169 | if (pstore_device_info && pstore_device_info == dev) { |
170 | unregister_pstore_zone(&dev->zone); | |
171 | pstore_device_info = NULL; | |
17639f67 | 172 | } |
7dcb7848 WL |
173 | } |
174 | ||
175 | /** | |
176 | * unregister_pstore_device() - unregister non-block device from pstore/blk | |
177 | * | |
178 | * @dev: non-block device information | |
179 | */ | |
180 | void unregister_pstore_device(struct pstore_device_info *dev) | |
181 | { | |
182 | mutex_lock(&pstore_blk_lock); | |
183 | __unregister_pstore_device(dev); | |
17639f67 WL |
184 | mutex_unlock(&pstore_blk_lock); |
185 | } | |
7dcb7848 | 186 | EXPORT_SYMBOL_GPL(unregister_pstore_device); |
17639f67 | 187 | |
17639f67 WL |
188 | static ssize_t psblk_generic_blk_read(char *buf, size_t bytes, loff_t pos) |
189 | { | |
7bb9557b | 190 | return kernel_read(psblk_file, buf, bytes, &pos); |
17639f67 WL |
191 | } |
192 | ||
193 | static ssize_t psblk_generic_blk_write(const char *buf, size_t bytes, | |
194 | loff_t pos) | |
195 | { | |
17639f67 WL |
196 | /* Console/Ftrace backend may handle buffer until flush dirty zones */ |
197 | if (in_interrupt() || irqs_disabled()) | |
198 | return -EBUSY; | |
7bb9557b | 199 | return kernel_write(psblk_file, buf, bytes, &pos); |
17639f67 WL |
200 | } |
201 | ||
b6f8ed33 CH |
202 | /* |
203 | * This takes its configuration only from the module parameters now. | |
b6f8ed33 | 204 | */ |
1d1f6cc5 KC |
205 | static int __register_pstore_blk(struct pstore_device_info *dev, |
206 | const char *devpath) | |
17639f67 | 207 | { |
17639f67 WL |
208 | int ret = -ENODEV; |
209 | ||
210 | lockdep_assert_held(&pstore_blk_lock); | |
211 | ||
7bb9557b KC |
212 | psblk_file = filp_open(devpath, O_RDWR | O_DSYNC | O_NOATIME | O_EXCL, 0); |
213 | if (IS_ERR(psblk_file)) { | |
214 | ret = PTR_ERR(psblk_file); | |
215 | pr_err("failed to open '%s': %d!\n", devpath, ret); | |
216 | goto err; | |
17639f67 WL |
217 | } |
218 | ||
46461985 | 219 | if (!S_ISBLK(file_inode(psblk_file)->i_mode)) { |
7bb9557b KC |
220 | pr_err("'%s' is not block device!\n", devpath); |
221 | goto err_fput; | |
17639f67 WL |
222 | } |
223 | ||
46461985 CH |
224 | dev->zone.total_size = |
225 | bdev_nr_bytes(I_BDEV(psblk_file->f_mapping->host)); | |
17639f67 | 226 | |
1d1f6cc5 | 227 | ret = __register_pstore_device(dev); |
17639f67 | 228 | if (ret) |
7bb9557b | 229 | goto err_fput; |
17639f67 | 230 | |
17639f67 WL |
231 | return 0; |
232 | ||
7bb9557b KC |
233 | err_fput: |
234 | fput(psblk_file); | |
235 | err: | |
236 | psblk_file = NULL; | |
237 | ||
17639f67 WL |
238 | return ret; |
239 | } | |
240 | ||
1525fb3b WL |
241 | /* get information of pstore/blk */ |
242 | int pstore_blk_get_config(struct pstore_blk_config *info) | |
243 | { | |
244 | strncpy(info->device, blkdev, 80); | |
245 | info->max_reason = max_reason; | |
246 | info->kmsg_size = check_size(kmsg_size, 4096); | |
247 | info->pmsg_size = check_size(pmsg_size, 4096); | |
248 | info->ftrace_size = check_size(ftrace_size, 4096); | |
249 | info->console_size = check_size(console_size, 4096); | |
250 | ||
251 | return 0; | |
252 | } | |
253 | EXPORT_SYMBOL_GPL(pstore_blk_get_config); | |
254 | ||
7bb9557b KC |
255 | |
256 | #ifndef MODULE | |
257 | static const char devname[] = "/dev/pstore-blk"; | |
258 | static __init const char *early_boot_devpath(const char *initial_devname) | |
259 | { | |
260 | /* | |
261 | * During early boot the real root file system hasn't been | |
262 | * mounted yet, and no device nodes are present yet. Use the | |
263 | * same scheme to find the device that we use for mounting | |
264 | * the root file system. | |
265 | */ | |
266 | dev_t dev = name_to_dev_t(initial_devname); | |
267 | ||
268 | if (!dev) { | |
269 | pr_err("failed to resolve '%s'!\n", initial_devname); | |
270 | return initial_devname; | |
271 | } | |
272 | ||
273 | init_unlink(devname); | |
274 | init_mknod(devname, S_IFBLK | 0600, new_encode_dev(dev)); | |
275 | ||
276 | return devname; | |
277 | } | |
278 | #else | |
279 | static inline const char *early_boot_devpath(const char *initial_devname) | |
280 | { | |
281 | return initial_devname; | |
282 | } | |
283 | #endif | |
284 | ||
1d1f6cc5 KC |
285 | static int __init __best_effort_init(void) |
286 | { | |
287 | struct pstore_device_info *best_effort_dev; | |
288 | int ret; | |
289 | ||
290 | /* No best-effort mode requested. */ | |
291 | if (!best_effort) | |
292 | return 0; | |
293 | ||
294 | /* Reject an empty blkdev. */ | |
295 | if (!blkdev[0]) { | |
296 | pr_err("blkdev empty with best_effort=Y\n"); | |
297 | return -EINVAL; | |
298 | } | |
299 | ||
300 | best_effort_dev = kzalloc(sizeof(*best_effort_dev), GFP_KERNEL); | |
301 | if (!best_effort_dev) | |
302 | return -ENOMEM; | |
303 | ||
304 | best_effort_dev->zone.read = psblk_generic_blk_read; | |
305 | best_effort_dev->zone.write = psblk_generic_blk_write; | |
306 | ||
307 | ret = __register_pstore_blk(best_effort_dev, | |
308 | early_boot_devpath(blkdev)); | |
309 | if (ret) | |
310 | kfree(best_effort_dev); | |
311 | else | |
61eb495c | 312 | pr_info("attached %s (%lu) (no dedicated panic_write!)\n", |
1d1f6cc5 KC |
313 | blkdev, best_effort_dev->zone.total_size); |
314 | ||
315 | return ret; | |
316 | } | |
317 | ||
318 | static void __exit __best_effort_exit(void) | |
319 | { | |
320 | /* | |
321 | * Currently, the only user of psblk_file is best_effort, so | |
322 | * we can assume that pstore_device_info is associated with it. | |
323 | * Once there are "real" blk devices, there will need to be a | |
324 | * dedicated pstore_blk_info, etc. | |
325 | */ | |
326 | if (psblk_file) { | |
327 | struct pstore_device_info *dev = pstore_device_info; | |
328 | ||
329 | __unregister_pstore_device(dev); | |
330 | kfree(dev); | |
331 | fput(psblk_file); | |
332 | psblk_file = NULL; | |
333 | } | |
334 | } | |
335 | ||
f8feafea KC |
336 | static int __init pstore_blk_init(void) |
337 | { | |
1d1f6cc5 | 338 | int ret; |
f8feafea KC |
339 | |
340 | mutex_lock(&pstore_blk_lock); | |
1d1f6cc5 | 341 | ret = __best_effort_init(); |
f8feafea KC |
342 | mutex_unlock(&pstore_blk_lock); |
343 | ||
344 | return ret; | |
345 | } | |
346 | late_initcall(pstore_blk_init); | |
347 | ||
17639f67 WL |
348 | static void __exit pstore_blk_exit(void) |
349 | { | |
350 | mutex_lock(&pstore_blk_lock); | |
1d1f6cc5 KC |
351 | __best_effort_exit(); |
352 | /* If we've been asked to unload, unregister any remaining device. */ | |
353 | __unregister_pstore_device(pstore_device_info); | |
17639f67 WL |
354 | mutex_unlock(&pstore_blk_lock); |
355 | } | |
356 | module_exit(pstore_blk_exit); | |
357 | ||
358 | MODULE_LICENSE("GPL"); | |
359 | MODULE_AUTHOR("WeiXiong Liao <liaoweixiong@allwinnertech.com>"); | |
360 | MODULE_AUTHOR("Kees Cook <keescook@chromium.org>"); | |
361 | MODULE_DESCRIPTION("pstore backend for block devices"); |