diff options
Diffstat (limited to 'fs/pstore/ram.c')
-rw-r--r-- | fs/pstore/ram.c | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/fs/pstore/ram.c b/fs/pstore/ram.c new file mode 100644 index 00000000000..9123cce28c1 --- /dev/null +++ b/fs/pstore/ram.c | |||
@@ -0,0 +1,383 @@ | |||
1 | /* | ||
2 | * RAM Oops/Panic logger | ||
3 | * | ||
4 | * Copyright (C) 2010 Marco Stornelli <marco.stornelli@gmail.com> | ||
5 | * Copyright (C) 2011 Kees Cook <keescook@chromium.org> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License | ||
9 | * version 2 as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, but | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | * General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
19 | * 02110-1301 USA | ||
20 | * | ||
21 | */ | ||
22 | |||
23 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
24 | |||
25 | #include <linux/kernel.h> | ||
26 | #include <linux/err.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/pstore.h> | ||
29 | #include <linux/time.h> | ||
30 | #include <linux/io.h> | ||
31 | #include <linux/ioport.h> | ||
32 | #include <linux/platform_device.h> | ||
33 | #include <linux/slab.h> | ||
34 | #include <linux/pstore_ram.h> | ||
35 | |||
36 | #define RAMOOPS_KERNMSG_HDR "====" | ||
37 | #define MIN_MEM_SIZE 4096UL | ||
38 | |||
39 | static ulong record_size = MIN_MEM_SIZE; | ||
40 | module_param(record_size, ulong, 0400); | ||
41 | MODULE_PARM_DESC(record_size, | ||
42 | "size of each dump done on oops/panic"); | ||
43 | |||
44 | static ulong mem_address; | ||
45 | module_param(mem_address, ulong, 0400); | ||
46 | MODULE_PARM_DESC(mem_address, | ||
47 | "start of reserved RAM used to store oops/panic logs"); | ||
48 | |||
49 | static ulong mem_size; | ||
50 | module_param(mem_size, ulong, 0400); | ||
51 | MODULE_PARM_DESC(mem_size, | ||
52 | "size of reserved RAM used to store oops/panic logs"); | ||
53 | |||
54 | static int dump_oops = 1; | ||
55 | module_param(dump_oops, int, 0600); | ||
56 | MODULE_PARM_DESC(dump_oops, | ||
57 | "set to 1 to dump oopses, 0 to only dump panics (default 1)"); | ||
58 | |||
59 | static int ramoops_ecc; | ||
60 | module_param_named(ecc, ramoops_ecc, int, 0600); | ||
61 | MODULE_PARM_DESC(ramoops_ecc, | ||
62 | "set to 1 to enable ECC support"); | ||
63 | |||
64 | struct ramoops_context { | ||
65 | struct persistent_ram_zone **przs; | ||
66 | phys_addr_t phys_addr; | ||
67 | unsigned long size; | ||
68 | size_t record_size; | ||
69 | int dump_oops; | ||
70 | bool ecc; | ||
71 | unsigned int count; | ||
72 | unsigned int max_count; | ||
73 | unsigned int read_count; | ||
74 | struct pstore_info pstore; | ||
75 | }; | ||
76 | |||
77 | static struct platform_device *dummy; | ||
78 | static struct ramoops_platform_data *dummy_data; | ||
79 | |||
80 | static int ramoops_pstore_open(struct pstore_info *psi) | ||
81 | { | ||
82 | struct ramoops_context *cxt = psi->data; | ||
83 | |||
84 | cxt->read_count = 0; | ||
85 | return 0; | ||
86 | } | ||
87 | |||
88 | static ssize_t ramoops_pstore_read(u64 *id, enum pstore_type_id *type, | ||
89 | struct timespec *time, | ||
90 | char **buf, | ||
91 | struct pstore_info *psi) | ||
92 | { | ||
93 | ssize_t size; | ||
94 | struct ramoops_context *cxt = psi->data; | ||
95 | struct persistent_ram_zone *prz; | ||
96 | |||
97 | if (cxt->read_count >= cxt->max_count) | ||
98 | return -EINVAL; | ||
99 | |||
100 | *id = cxt->read_count++; | ||
101 | prz = cxt->przs[*id]; | ||
102 | |||
103 | /* Only supports dmesg output so far. */ | ||
104 | *type = PSTORE_TYPE_DMESG; | ||
105 | /* TODO(kees): Bogus time for the moment. */ | ||
106 | time->tv_sec = 0; | ||
107 | time->tv_nsec = 0; | ||
108 | |||
109 | size = persistent_ram_old_size(prz); | ||
110 | *buf = kmalloc(size, GFP_KERNEL); | ||
111 | if (*buf == NULL) | ||
112 | return -ENOMEM; | ||
113 | memcpy(*buf, persistent_ram_old(prz), size); | ||
114 | |||
115 | return size; | ||
116 | } | ||
117 | |||
118 | static size_t ramoops_write_kmsg_hdr(struct persistent_ram_zone *prz) | ||
119 | { | ||
120 | char *hdr; | ||
121 | struct timeval timestamp; | ||
122 | size_t len; | ||
123 | |||
124 | do_gettimeofday(×tamp); | ||
125 | hdr = kasprintf(GFP_ATOMIC, RAMOOPS_KERNMSG_HDR "%lu.%lu\n", | ||
126 | (long)timestamp.tv_sec, (long)timestamp.tv_usec); | ||
127 | WARN_ON_ONCE(!hdr); | ||
128 | len = hdr ? strlen(hdr) : 0; | ||
129 | persistent_ram_write(prz, hdr, len); | ||
130 | kfree(hdr); | ||
131 | |||
132 | return len; | ||
133 | } | ||
134 | |||
135 | static int ramoops_pstore_write(enum pstore_type_id type, | ||
136 | enum kmsg_dump_reason reason, | ||
137 | u64 *id, | ||
138 | unsigned int part, | ||
139 | size_t size, struct pstore_info *psi) | ||
140 | { | ||
141 | struct ramoops_context *cxt = psi->data; | ||
142 | struct persistent_ram_zone *prz = cxt->przs[cxt->count]; | ||
143 | size_t hlen; | ||
144 | |||
145 | /* Currently ramoops is designed to only store dmesg dumps. */ | ||
146 | if (type != PSTORE_TYPE_DMESG) | ||
147 | return -EINVAL; | ||
148 | |||
149 | /* Out of the various dmesg dump types, ramoops is currently designed | ||
150 | * to only store crash logs, rather than storing general kernel logs. | ||
151 | */ | ||
152 | if (reason != KMSG_DUMP_OOPS && | ||
153 | reason != KMSG_DUMP_PANIC) | ||
154 | return -EINVAL; | ||
155 | |||
156 | /* Skip Oopes when configured to do so. */ | ||
157 | if (reason == KMSG_DUMP_OOPS && !cxt->dump_oops) | ||
158 | return -EINVAL; | ||
159 | |||
160 | /* Explicitly only take the first part of any new crash. | ||
161 | * If our buffer is larger than kmsg_bytes, this can never happen, | ||
162 | * and if our buffer is smaller than kmsg_bytes, we don't want the | ||
163 | * report split across multiple records. | ||
164 | */ | ||
165 | if (part != 1) | ||
166 | return -ENOSPC; | ||
167 | |||
168 | hlen = ramoops_write_kmsg_hdr(prz); | ||
169 | if (size + hlen > prz->buffer_size) | ||
170 | size = prz->buffer_size - hlen; | ||
171 | persistent_ram_write(prz, cxt->pstore.buf, size); | ||
172 | |||
173 | cxt->count = (cxt->count + 1) % cxt->max_count; | ||
174 | |||
175 | return 0; | ||
176 | } | ||
177 | |||
178 | static int ramoops_pstore_erase(enum pstore_type_id type, u64 id, | ||
179 | struct pstore_info *psi) | ||
180 | { | ||
181 | struct ramoops_context *cxt = psi->data; | ||
182 | |||
183 | if (id >= cxt->max_count) | ||
184 | return -EINVAL; | ||
185 | |||
186 | persistent_ram_free_old(cxt->przs[id]); | ||
187 | |||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | static struct ramoops_context oops_cxt = { | ||
192 | .pstore = { | ||
193 | .owner = THIS_MODULE, | ||
194 | .name = "ramoops", | ||
195 | .open = ramoops_pstore_open, | ||
196 | .read = ramoops_pstore_read, | ||
197 | .write = ramoops_pstore_write, | ||
198 | .erase = ramoops_pstore_erase, | ||
199 | }, | ||
200 | }; | ||
201 | |||
202 | static int __init ramoops_probe(struct platform_device *pdev) | ||
203 | { | ||
204 | struct device *dev = &pdev->dev; | ||
205 | struct ramoops_platform_data *pdata = pdev->dev.platform_data; | ||
206 | struct ramoops_context *cxt = &oops_cxt; | ||
207 | int err = -EINVAL; | ||
208 | int i; | ||
209 | |||
210 | /* Only a single ramoops area allowed at a time, so fail extra | ||
211 | * probes. | ||
212 | */ | ||
213 | if (cxt->max_count) | ||
214 | goto fail_out; | ||
215 | |||
216 | if (!pdata->mem_size || !pdata->record_size) { | ||
217 | pr_err("The memory size and the record size must be " | ||
218 | "non-zero\n"); | ||
219 | goto fail_out; | ||
220 | } | ||
221 | |||
222 | pdata->mem_size = rounddown_pow_of_two(pdata->mem_size); | ||
223 | pdata->record_size = rounddown_pow_of_two(pdata->record_size); | ||
224 | |||
225 | /* Check for the minimum memory size */ | ||
226 | if (pdata->mem_size < MIN_MEM_SIZE && | ||
227 | pdata->record_size < MIN_MEM_SIZE) { | ||
228 | pr_err("memory size too small, minimum is %lu\n", | ||
229 | MIN_MEM_SIZE); | ||
230 | goto fail_out; | ||
231 | } | ||
232 | |||
233 | if (pdata->mem_size < pdata->record_size) { | ||
234 | pr_err("The memory size must be larger than the " | ||
235 | "records size\n"); | ||
236 | goto fail_out; | ||
237 | } | ||
238 | |||
239 | cxt->max_count = pdata->mem_size / pdata->record_size; | ||
240 | cxt->count = 0; | ||
241 | cxt->size = pdata->mem_size; | ||
242 | cxt->phys_addr = pdata->mem_address; | ||
243 | cxt->record_size = pdata->record_size; | ||
244 | cxt->dump_oops = pdata->dump_oops; | ||
245 | cxt->ecc = pdata->ecc; | ||
246 | |||
247 | cxt->przs = kzalloc(sizeof(*cxt->przs) * cxt->max_count, GFP_KERNEL); | ||
248 | if (!cxt->przs) { | ||
249 | err = -ENOMEM; | ||
250 | dev_err(dev, "failed to initialize a prz array\n"); | ||
251 | goto fail_out; | ||
252 | } | ||
253 | |||
254 | for (i = 0; i < cxt->max_count; i++) { | ||
255 | size_t sz = cxt->record_size; | ||
256 | phys_addr_t start = cxt->phys_addr + sz * i; | ||
257 | |||
258 | cxt->przs[i] = persistent_ram_new(start, sz, cxt->ecc); | ||
259 | if (IS_ERR(cxt->przs[i])) { | ||
260 | err = PTR_ERR(cxt->przs[i]); | ||
261 | dev_err(dev, "failed to request mem region (0x%zx@0x%llx): %d\n", | ||
262 | sz, (unsigned long long)start, err); | ||
263 | goto fail_przs; | ||
264 | } | ||
265 | } | ||
266 | |||
267 | cxt->pstore.data = cxt; | ||
268 | cxt->pstore.bufsize = cxt->przs[0]->buffer_size; | ||
269 | cxt->pstore.buf = kmalloc(cxt->pstore.bufsize, GFP_KERNEL); | ||
270 | spin_lock_init(&cxt->pstore.buf_lock); | ||
271 | if (!cxt->pstore.buf) { | ||
272 | pr_err("cannot allocate pstore buffer\n"); | ||
273 | goto fail_clear; | ||
274 | } | ||
275 | |||
276 | err = pstore_register(&cxt->pstore); | ||
277 | if (err) { | ||
278 | pr_err("registering with pstore failed\n"); | ||
279 | goto fail_buf; | ||
280 | } | ||
281 | |||
282 | /* | ||
283 | * Update the module parameter variables as well so they are visible | ||
284 | * through /sys/module/ramoops/parameters/ | ||
285 | */ | ||
286 | mem_size = pdata->mem_size; | ||
287 | mem_address = pdata->mem_address; | ||
288 | record_size = pdata->record_size; | ||
289 | dump_oops = pdata->dump_oops; | ||
290 | |||
291 | pr_info("attached 0x%lx@0x%llx (%ux0x%zx), ecc: %s\n", | ||
292 | cxt->size, (unsigned long long)cxt->phys_addr, | ||
293 | cxt->max_count, cxt->record_size, | ||
294 | ramoops_ecc ? "on" : "off"); | ||
295 | |||
296 | return 0; | ||
297 | |||
298 | fail_buf: | ||
299 | kfree(cxt->pstore.buf); | ||
300 | fail_clear: | ||
301 | cxt->pstore.bufsize = 0; | ||
302 | cxt->max_count = 0; | ||
303 | fail_przs: | ||
304 | for (i = 0; cxt->przs[i]; i++) | ||
305 | persistent_ram_free(cxt->przs[i]); | ||
306 | kfree(cxt->przs); | ||
307 | fail_out: | ||
308 | return err; | ||
309 | } | ||
310 | |||
311 | static int __exit ramoops_remove(struct platform_device *pdev) | ||
312 | { | ||
313 | #if 0 | ||
314 | /* TODO(kees): We cannot unload ramoops since pstore doesn't support | ||
315 | * unregistering yet. | ||
316 | */ | ||
317 | struct ramoops_context *cxt = &oops_cxt; | ||
318 | |||
319 | iounmap(cxt->virt_addr); | ||
320 | release_mem_region(cxt->phys_addr, cxt->size); | ||
321 | cxt->max_count = 0; | ||
322 | |||
323 | /* TODO(kees): When pstore supports unregistering, call it here. */ | ||
324 | kfree(cxt->pstore.buf); | ||
325 | cxt->pstore.bufsize = 0; | ||
326 | |||
327 | return 0; | ||
328 | #endif | ||
329 | return -EBUSY; | ||
330 | } | ||
331 | |||
332 | static struct platform_driver ramoops_driver = { | ||
333 | .remove = __exit_p(ramoops_remove), | ||
334 | .driver = { | ||
335 | .name = "ramoops", | ||
336 | .owner = THIS_MODULE, | ||
337 | }, | ||
338 | }; | ||
339 | |||
340 | static int __init ramoops_init(void) | ||
341 | { | ||
342 | int ret; | ||
343 | ret = platform_driver_probe(&ramoops_driver, ramoops_probe); | ||
344 | if (ret == -ENODEV) { | ||
345 | /* | ||
346 | * If we didn't find a platform device, we use module parameters | ||
347 | * building platform data on the fly. | ||
348 | */ | ||
349 | pr_info("platform device not found, using module parameters\n"); | ||
350 | dummy_data = kzalloc(sizeof(struct ramoops_platform_data), | ||
351 | GFP_KERNEL); | ||
352 | if (!dummy_data) | ||
353 | return -ENOMEM; | ||
354 | dummy_data->mem_size = mem_size; | ||
355 | dummy_data->mem_address = mem_address; | ||
356 | dummy_data->record_size = record_size; | ||
357 | dummy_data->dump_oops = dump_oops; | ||
358 | dummy_data->ecc = ramoops_ecc; | ||
359 | dummy = platform_create_bundle(&ramoops_driver, ramoops_probe, | ||
360 | NULL, 0, dummy_data, | ||
361 | sizeof(struct ramoops_platform_data)); | ||
362 | |||
363 | if (IS_ERR(dummy)) | ||
364 | ret = PTR_ERR(dummy); | ||
365 | else | ||
366 | ret = 0; | ||
367 | } | ||
368 | |||
369 | return ret; | ||
370 | } | ||
371 | |||
372 | static void __exit ramoops_exit(void) | ||
373 | { | ||
374 | platform_driver_unregister(&ramoops_driver); | ||
375 | kfree(dummy_data); | ||
376 | } | ||
377 | |||
378 | module_init(ramoops_init); | ||
379 | module_exit(ramoops_exit); | ||
380 | |||
381 | MODULE_LICENSE("GPL"); | ||
382 | MODULE_AUTHOR("Marco Stornelli <marco.stornelli@gmail.com>"); | ||
383 | MODULE_DESCRIPTION("RAM Oops/Panic logger/driver"); | ||