]>
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 | { |
7bb9557b | 208 | struct inode *inode; |
17639f67 WL |
209 | int ret = -ENODEV; |
210 | ||
211 | lockdep_assert_held(&pstore_blk_lock); | |
212 | ||
7bb9557b KC |
213 | psblk_file = filp_open(devpath, O_RDWR | O_DSYNC | O_NOATIME | O_EXCL, 0); |
214 | if (IS_ERR(psblk_file)) { | |
215 | ret = PTR_ERR(psblk_file); | |
216 | pr_err("failed to open '%s': %d!\n", devpath, ret); | |
217 | goto err; | |
17639f67 WL |
218 | } |
219 | ||
7bb9557b KC |
220 | inode = file_inode(psblk_file); |
221 | if (!S_ISBLK(inode->i_mode)) { | |
222 | pr_err("'%s' is not block device!\n", devpath); | |
223 | goto err_fput; | |
17639f67 WL |
224 | } |
225 | ||
7bb9557b | 226 | inode = I_BDEV(psblk_file->f_mapping->host)->bd_inode; |
1d1f6cc5 | 227 | dev->zone.total_size = i_size_read(inode); |
17639f67 | 228 | |
1d1f6cc5 | 229 | ret = __register_pstore_device(dev); |
17639f67 | 230 | if (ret) |
7bb9557b | 231 | goto err_fput; |
17639f67 | 232 | |
17639f67 WL |
233 | return 0; |
234 | ||
7bb9557b KC |
235 | err_fput: |
236 | fput(psblk_file); | |
237 | err: | |
238 | psblk_file = NULL; | |
239 | ||
17639f67 WL |
240 | return ret; |
241 | } | |
242 | ||
1525fb3b WL |
243 | /* get information of pstore/blk */ |
244 | int pstore_blk_get_config(struct pstore_blk_config *info) | |
245 | { | |
246 | strncpy(info->device, blkdev, 80); | |
247 | info->max_reason = max_reason; | |
248 | info->kmsg_size = check_size(kmsg_size, 4096); | |
249 | info->pmsg_size = check_size(pmsg_size, 4096); | |
250 | info->ftrace_size = check_size(ftrace_size, 4096); | |
251 | info->console_size = check_size(console_size, 4096); | |
252 | ||
253 | return 0; | |
254 | } | |
255 | EXPORT_SYMBOL_GPL(pstore_blk_get_config); | |
256 | ||
7bb9557b KC |
257 | |
258 | #ifndef MODULE | |
259 | static const char devname[] = "/dev/pstore-blk"; | |
260 | static __init const char *early_boot_devpath(const char *initial_devname) | |
261 | { | |
262 | /* | |
263 | * During early boot the real root file system hasn't been | |
264 | * mounted yet, and no device nodes are present yet. Use the | |
265 | * same scheme to find the device that we use for mounting | |
266 | * the root file system. | |
267 | */ | |
268 | dev_t dev = name_to_dev_t(initial_devname); | |
269 | ||
270 | if (!dev) { | |
271 | pr_err("failed to resolve '%s'!\n", initial_devname); | |
272 | return initial_devname; | |
273 | } | |
274 | ||
275 | init_unlink(devname); | |
276 | init_mknod(devname, S_IFBLK | 0600, new_encode_dev(dev)); | |
277 | ||
278 | return devname; | |
279 | } | |
280 | #else | |
281 | static inline const char *early_boot_devpath(const char *initial_devname) | |
282 | { | |
283 | return initial_devname; | |
284 | } | |
285 | #endif | |
286 | ||
1d1f6cc5 KC |
287 | static int __init __best_effort_init(void) |
288 | { | |
289 | struct pstore_device_info *best_effort_dev; | |
290 | int ret; | |
291 | ||
292 | /* No best-effort mode requested. */ | |
293 | if (!best_effort) | |
294 | return 0; | |
295 | ||
296 | /* Reject an empty blkdev. */ | |
297 | if (!blkdev[0]) { | |
298 | pr_err("blkdev empty with best_effort=Y\n"); | |
299 | return -EINVAL; | |
300 | } | |
301 | ||
302 | best_effort_dev = kzalloc(sizeof(*best_effort_dev), GFP_KERNEL); | |
303 | if (!best_effort_dev) | |
304 | return -ENOMEM; | |
305 | ||
306 | best_effort_dev->zone.read = psblk_generic_blk_read; | |
307 | best_effort_dev->zone.write = psblk_generic_blk_write; | |
308 | ||
309 | ret = __register_pstore_blk(best_effort_dev, | |
310 | early_boot_devpath(blkdev)); | |
311 | if (ret) | |
312 | kfree(best_effort_dev); | |
313 | else | |
eb05e7d1 | 314 | pr_info("attached %s (%lu) (no dedicated panic_write!)\n", |
1d1f6cc5 KC |
315 | blkdev, best_effort_dev->zone.total_size); |
316 | ||
317 | return ret; | |
318 | } | |
319 | ||
320 | static void __exit __best_effort_exit(void) | |
321 | { | |
322 | /* | |
323 | * Currently, the only user of psblk_file is best_effort, so | |
324 | * we can assume that pstore_device_info is associated with it. | |
325 | * Once there are "real" blk devices, there will need to be a | |
326 | * dedicated pstore_blk_info, etc. | |
327 | */ | |
328 | if (psblk_file) { | |
329 | struct pstore_device_info *dev = pstore_device_info; | |
330 | ||
331 | __unregister_pstore_device(dev); | |
332 | kfree(dev); | |
333 | fput(psblk_file); | |
334 | psblk_file = NULL; | |
335 | } | |
336 | } | |
337 | ||
f8feafea KC |
338 | static int __init pstore_blk_init(void) |
339 | { | |
1d1f6cc5 | 340 | int ret; |
f8feafea KC |
341 | |
342 | mutex_lock(&pstore_blk_lock); | |
1d1f6cc5 | 343 | ret = __best_effort_init(); |
f8feafea KC |
344 | mutex_unlock(&pstore_blk_lock); |
345 | ||
346 | return ret; | |
347 | } | |
348 | late_initcall(pstore_blk_init); | |
349 | ||
17639f67 WL |
350 | static void __exit pstore_blk_exit(void) |
351 | { | |
352 | mutex_lock(&pstore_blk_lock); | |
1d1f6cc5 KC |
353 | __best_effort_exit(); |
354 | /* If we've been asked to unload, unregister any remaining device. */ | |
355 | __unregister_pstore_device(pstore_device_info); | |
17639f67 WL |
356 | mutex_unlock(&pstore_blk_lock); |
357 | } | |
358 | module_exit(pstore_blk_exit); | |
359 | ||
360 | MODULE_LICENSE("GPL"); | |
361 | MODULE_AUTHOR("WeiXiong Liao <liaoweixiong@allwinnertech.com>"); | |
362 | MODULE_AUTHOR("Kees Cook <keescook@chromium.org>"); | |
363 | MODULE_DESCRIPTION("pstore backend for block devices"); |