Commit | Line | Data |
---|---|---|
457c8996 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
666bfddb VG |
2 | /* |
3 | * fs/proc/vmcore.c Interface for accessing the crash | |
4 | * dump from the system's previous life. | |
5 | * Heavily borrowed from fs/proc/kcore.c | |
6 | * Created by: Hariprasad Nellitheertha (hari@in.ibm.com) | |
7 | * Copyright (C) IBM Corporation, 2004. All rights reserved | |
8 | * | |
9 | */ | |
10 | ||
666bfddb | 11 | #include <linux/mm.h> |
2f96b8c1 | 12 | #include <linux/kcore.h> |
666bfddb | 13 | #include <linux/user.h> |
666bfddb VG |
14 | #include <linux/elf.h> |
15 | #include <linux/elfcore.h> | |
afeacc8c | 16 | #include <linux/export.h> |
5a0e3ad6 | 17 | #include <linux/slab.h> |
666bfddb | 18 | #include <linux/highmem.h> |
87ebdc00 | 19 | #include <linux/printk.h> |
57c8a661 | 20 | #include <linux/memblock.h> |
666bfddb VG |
21 | #include <linux/init.h> |
22 | #include <linux/crash_dump.h> | |
23 | #include <linux/list.h> | |
c6c40533 | 24 | #include <linux/moduleparam.h> |
2724273e | 25 | #include <linux/mutex.h> |
83086978 | 26 | #include <linux/vmalloc.h> |
9cb21813 | 27 | #include <linux/pagemap.h> |
7c0f6ba6 | 28 | #include <linux/uaccess.h> |
992b649a LJ |
29 | #include <linux/mem_encrypt.h> |
30 | #include <asm/pgtable.h> | |
666bfddb | 31 | #include <asm/io.h> |
2f96b8c1 | 32 | #include "internal.h" |
666bfddb VG |
33 | |
34 | /* List representing chunks of contiguous memory areas and their offsets in | |
35 | * vmcore file. | |
36 | */ | |
37 | static LIST_HEAD(vmcore_list); | |
38 | ||
39 | /* Stores the pointer to the buffer containing kernel elf core headers. */ | |
40 | static char *elfcorebuf; | |
41 | static size_t elfcorebuf_sz; | |
f2bdacdd | 42 | static size_t elfcorebuf_sz_orig; |
666bfddb | 43 | |
087350c9 HD |
44 | static char *elfnotes_buf; |
45 | static size_t elfnotes_sz; | |
7efe48df RL |
46 | /* Size of all notes minus the device dump notes */ |
47 | static size_t elfnotes_orig_sz; | |
087350c9 | 48 | |
666bfddb VG |
49 | /* Total size of vmcore file. */ |
50 | static u64 vmcore_size; | |
51 | ||
a05e16ad | 52 | static struct proc_dir_entry *proc_vmcore; |
666bfddb | 53 | |
2724273e RL |
54 | #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP |
55 | /* Device Dump list and mutex to synchronize access to list */ | |
56 | static LIST_HEAD(vmcoredd_list); | |
57 | static DEFINE_MUTEX(vmcoredd_mutex); | |
c6c40533 KS |
58 | |
59 | static bool vmcoredd_disabled; | |
60 | core_param(novmcoredd, vmcoredd_disabled, bool, 0); | |
2724273e RL |
61 | #endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ |
62 | ||
7efe48df RL |
63 | /* Device Dump Size */ |
64 | static size_t vmcoredd_orig_sz; | |
65 | ||
997c136f OH |
66 | /* |
67 | * Returns > 0 for RAM pages, 0 for non-RAM pages, < 0 on error | |
68 | * The called function has to take care of module refcounting. | |
69 | */ | |
70 | static int (*oldmem_pfn_is_ram)(unsigned long pfn); | |
71 | ||
72 | int register_oldmem_pfn_is_ram(int (*fn)(unsigned long pfn)) | |
73 | { | |
74 | if (oldmem_pfn_is_ram) | |
75 | return -EBUSY; | |
76 | oldmem_pfn_is_ram = fn; | |
77 | return 0; | |
78 | } | |
79 | EXPORT_SYMBOL_GPL(register_oldmem_pfn_is_ram); | |
80 | ||
81 | void unregister_oldmem_pfn_is_ram(void) | |
82 | { | |
83 | oldmem_pfn_is_ram = NULL; | |
84 | wmb(); | |
85 | } | |
86 | EXPORT_SYMBOL_GPL(unregister_oldmem_pfn_is_ram); | |
87 | ||
88 | static int pfn_is_ram(unsigned long pfn) | |
89 | { | |
90 | int (*fn)(unsigned long pfn); | |
91 | /* pfn is ram unless fn() checks pagetype */ | |
92 | int ret = 1; | |
93 | ||
94 | /* | |
95 | * Ask hypervisor if the pfn is really ram. | |
96 | * A ballooned page contains no data and reading from such a page | |
97 | * will cause high load in the hypervisor. | |
98 | */ | |
99 | fn = oldmem_pfn_is_ram; | |
100 | if (fn) | |
101 | ret = fn(pfn); | |
102 | ||
103 | return ret; | |
104 | } | |
105 | ||
666bfddb | 106 | /* Reads a page from the oldmem device from given offset. */ |
ae7eb82a TJB |
107 | ssize_t read_from_oldmem(char *buf, size_t count, |
108 | u64 *ppos, int userbuf, | |
109 | bool encrypted) | |
666bfddb VG |
110 | { |
111 | unsigned long pfn, offset; | |
112 | size_t nr_bytes; | |
113 | ssize_t read = 0, tmp; | |
114 | ||
115 | if (!count) | |
116 | return 0; | |
117 | ||
118 | offset = (unsigned long)(*ppos % PAGE_SIZE); | |
119 | pfn = (unsigned long)(*ppos / PAGE_SIZE); | |
666bfddb VG |
120 | |
121 | do { | |
122 | if (count > (PAGE_SIZE - offset)) | |
123 | nr_bytes = PAGE_SIZE - offset; | |
124 | else | |
125 | nr_bytes = count; | |
126 | ||
997c136f OH |
127 | /* If pfn is not ram, return zeros for sparse dump files */ |
128 | if (pfn_is_ram(pfn) == 0) | |
129 | memset(buf, 0, nr_bytes); | |
130 | else { | |
992b649a LJ |
131 | if (encrypted) |
132 | tmp = copy_oldmem_page_encrypted(pfn, buf, | |
133 | nr_bytes, | |
134 | offset, | |
135 | userbuf); | |
136 | else | |
137 | tmp = copy_oldmem_page(pfn, buf, nr_bytes, | |
138 | offset, userbuf); | |
139 | ||
997c136f OH |
140 | if (tmp < 0) |
141 | return tmp; | |
142 | } | |
666bfddb VG |
143 | *ppos += nr_bytes; |
144 | count -= nr_bytes; | |
145 | buf += nr_bytes; | |
146 | read += nr_bytes; | |
147 | ++pfn; | |
148 | offset = 0; | |
149 | } while (count); | |
150 | ||
151 | return read; | |
152 | } | |
153 | ||
be8a8d06 MH |
154 | /* |
155 | * Architectures may override this function to allocate ELF header in 2nd kernel | |
156 | */ | |
157 | int __weak elfcorehdr_alloc(unsigned long long *addr, unsigned long long *size) | |
158 | { | |
159 | return 0; | |
160 | } | |
161 | ||
162 | /* | |
163 | * Architectures may override this function to free header | |
164 | */ | |
165 | void __weak elfcorehdr_free(unsigned long long addr) | |
166 | {} | |
167 | ||
168 | /* | |
169 | * Architectures may override this function to read from ELF header | |
170 | */ | |
171 | ssize_t __weak elfcorehdr_read(char *buf, size_t count, u64 *ppos) | |
172 | { | |
ae7eb82a | 173 | return read_from_oldmem(buf, count, ppos, 0, false); |
be8a8d06 MH |
174 | } |
175 | ||
176 | /* | |
177 | * Architectures may override this function to read from notes sections | |
178 | */ | |
179 | ssize_t __weak elfcorehdr_read_notes(char *buf, size_t count, u64 *ppos) | |
180 | { | |
4eb5fec3 | 181 | return read_from_oldmem(buf, count, ppos, 0, mem_encrypt_active()); |
be8a8d06 MH |
182 | } |
183 | ||
9cb21813 MH |
184 | /* |
185 | * Architectures may override this function to map oldmem | |
186 | */ | |
187 | int __weak remap_oldmem_pfn_range(struct vm_area_struct *vma, | |
188 | unsigned long from, unsigned long pfn, | |
189 | unsigned long size, pgprot_t prot) | |
190 | { | |
992b649a | 191 | prot = pgprot_encrypted(prot); |
9cb21813 MH |
192 | return remap_pfn_range(vma, from, pfn, size, prot); |
193 | } | |
194 | ||
cf089611 BP |
195 | /* |
196 | * Architectures which support memory encryption override this. | |
197 | */ | |
198 | ssize_t __weak | |
199 | copy_oldmem_page_encrypted(unsigned long pfn, char *buf, size_t csize, | |
200 | unsigned long offset, int userbuf) | |
201 | { | |
202 | return copy_oldmem_page(pfn, buf, csize, offset, userbuf); | |
203 | } | |
204 | ||
9cb21813 MH |
205 | /* |
206 | * Copy to either kernel or user space | |
207 | */ | |
208 | static int copy_to(void *target, void *src, size_t size, int userbuf) | |
209 | { | |
210 | if (userbuf) { | |
211 | if (copy_to_user((char __user *) target, src, size)) | |
212 | return -EFAULT; | |
213 | } else { | |
214 | memcpy(target, src, size); | |
215 | } | |
216 | return 0; | |
217 | } | |
218 | ||
7efe48df RL |
219 | #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP |
220 | static int vmcoredd_copy_dumps(void *dst, u64 start, size_t size, int userbuf) | |
221 | { | |
222 | struct vmcoredd_node *dump; | |
223 | u64 offset = 0; | |
224 | int ret = 0; | |
225 | size_t tsz; | |
226 | char *buf; | |
227 | ||
228 | mutex_lock(&vmcoredd_mutex); | |
229 | list_for_each_entry(dump, &vmcoredd_list, list) { | |
230 | if (start < offset + dump->size) { | |
231 | tsz = min(offset + (u64)dump->size - start, (u64)size); | |
232 | buf = dump->buf + start - offset; | |
233 | if (copy_to(dst, buf, tsz, userbuf)) { | |
234 | ret = -EFAULT; | |
235 | goto out_unlock; | |
236 | } | |
237 | ||
238 | size -= tsz; | |
239 | start += tsz; | |
240 | dst += tsz; | |
241 | ||
242 | /* Leave now if buffer filled already */ | |
243 | if (!size) | |
244 | goto out_unlock; | |
245 | } | |
246 | offset += dump->size; | |
247 | } | |
248 | ||
249 | out_unlock: | |
250 | mutex_unlock(&vmcoredd_mutex); | |
251 | return ret; | |
252 | } | |
253 | ||
a2036a1e | 254 | #ifdef CONFIG_MMU |
7efe48df RL |
255 | static int vmcoredd_mmap_dumps(struct vm_area_struct *vma, unsigned long dst, |
256 | u64 start, size_t size) | |
257 | { | |
258 | struct vmcoredd_node *dump; | |
259 | u64 offset = 0; | |
260 | int ret = 0; | |
261 | size_t tsz; | |
262 | char *buf; | |
263 | ||
264 | mutex_lock(&vmcoredd_mutex); | |
265 | list_for_each_entry(dump, &vmcoredd_list, list) { | |
266 | if (start < offset + dump->size) { | |
267 | tsz = min(offset + (u64)dump->size - start, (u64)size); | |
268 | buf = dump->buf + start - offset; | |
269 | if (remap_vmalloc_range_partial(vma, dst, buf, tsz)) { | |
270 | ret = -EFAULT; | |
271 | goto out_unlock; | |
272 | } | |
273 | ||
274 | size -= tsz; | |
275 | start += tsz; | |
276 | dst += tsz; | |
277 | ||
278 | /* Leave now if buffer filled already */ | |
279 | if (!size) | |
280 | goto out_unlock; | |
281 | } | |
282 | offset += dump->size; | |
283 | } | |
284 | ||
285 | out_unlock: | |
286 | mutex_unlock(&vmcoredd_mutex); | |
287 | return ret; | |
288 | } | |
a2036a1e | 289 | #endif /* CONFIG_MMU */ |
7efe48df RL |
290 | #endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ |
291 | ||
666bfddb VG |
292 | /* Read from the ELF header and then the crash dump. On error, negative value is |
293 | * returned otherwise number of bytes read are returned. | |
294 | */ | |
9cb21813 MH |
295 | static ssize_t __read_vmcore(char *buffer, size_t buflen, loff_t *fpos, |
296 | int userbuf) | |
666bfddb VG |
297 | { |
298 | ssize_t acc = 0, tmp; | |
80e8ff63 | 299 | size_t tsz; |
b27eb186 HD |
300 | u64 start; |
301 | struct vmcore *m = NULL; | |
666bfddb VG |
302 | |
303 | if (buflen == 0 || *fpos >= vmcore_size) | |
304 | return 0; | |
305 | ||
306 | /* trim buflen to not go beyond EOF */ | |
307 | if (buflen > vmcore_size - *fpos) | |
308 | buflen = vmcore_size - *fpos; | |
309 | ||
310 | /* Read ELF core header */ | |
311 | if (*fpos < elfcorebuf_sz) { | |
087350c9 | 312 | tsz = min(elfcorebuf_sz - (size_t)*fpos, buflen); |
9cb21813 | 313 | if (copy_to(buffer, elfcorebuf + *fpos, tsz, userbuf)) |
666bfddb VG |
314 | return -EFAULT; |
315 | buflen -= tsz; | |
316 | *fpos += tsz; | |
317 | buffer += tsz; | |
318 | acc += tsz; | |
319 | ||
320 | /* leave now if filled buffer already */ | |
321 | if (buflen == 0) | |
322 | return acc; | |
323 | } | |
324 | ||
087350c9 HD |
325 | /* Read Elf note segment */ |
326 | if (*fpos < elfcorebuf_sz + elfnotes_sz) { | |
327 | void *kaddr; | |
328 | ||
7efe48df RL |
329 | /* We add device dumps before other elf notes because the |
330 | * other elf notes may not fill the elf notes buffer | |
331 | * completely and we will end up with zero-filled data | |
332 | * between the elf notes and the device dumps. Tools will | |
333 | * then try to decode this zero-filled data as valid notes | |
334 | * and we don't want that. Hence, adding device dumps before | |
335 | * the other elf notes ensure that zero-filled data can be | |
336 | * avoided. | |
337 | */ | |
338 | #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP | |
339 | /* Read device dumps */ | |
340 | if (*fpos < elfcorebuf_sz + vmcoredd_orig_sz) { | |
341 | tsz = min(elfcorebuf_sz + vmcoredd_orig_sz - | |
342 | (size_t)*fpos, buflen); | |
343 | start = *fpos - elfcorebuf_sz; | |
344 | if (vmcoredd_copy_dumps(buffer, start, tsz, userbuf)) | |
345 | return -EFAULT; | |
346 | ||
347 | buflen -= tsz; | |
348 | *fpos += tsz; | |
349 | buffer += tsz; | |
350 | acc += tsz; | |
351 | ||
352 | /* leave now if filled buffer already */ | |
353 | if (!buflen) | |
354 | return acc; | |
355 | } | |
356 | #endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ | |
357 | ||
358 | /* Read remaining elf notes */ | |
087350c9 | 359 | tsz = min(elfcorebuf_sz + elfnotes_sz - (size_t)*fpos, buflen); |
7efe48df | 360 | kaddr = elfnotes_buf + *fpos - elfcorebuf_sz - vmcoredd_orig_sz; |
9cb21813 | 361 | if (copy_to(buffer, kaddr, tsz, userbuf)) |
087350c9 | 362 | return -EFAULT; |
7efe48df | 363 | |
087350c9 HD |
364 | buflen -= tsz; |
365 | *fpos += tsz; | |
366 | buffer += tsz; | |
367 | acc += tsz; | |
368 | ||
369 | /* leave now if filled buffer already */ | |
370 | if (buflen == 0) | |
371 | return acc; | |
372 | } | |
373 | ||
b27eb186 HD |
374 | list_for_each_entry(m, &vmcore_list, list) { |
375 | if (*fpos < m->offset + m->size) { | |
0b50a2d8 DY |
376 | tsz = (size_t)min_t(unsigned long long, |
377 | m->offset + m->size - *fpos, | |
378 | buflen); | |
b27eb186 | 379 | start = m->paddr + *fpos - m->offset; |
992b649a | 380 | tmp = read_from_oldmem(buffer, tsz, &start, |
4eb5fec3 | 381 | userbuf, mem_encrypt_active()); |
b27eb186 HD |
382 | if (tmp < 0) |
383 | return tmp; | |
384 | buflen -= tsz; | |
385 | *fpos += tsz; | |
386 | buffer += tsz; | |
387 | acc += tsz; | |
388 | ||
389 | /* leave now if filled buffer already */ | |
390 | if (buflen == 0) | |
391 | return acc; | |
666bfddb | 392 | } |
666bfddb | 393 | } |
b27eb186 | 394 | |
666bfddb VG |
395 | return acc; |
396 | } | |
397 | ||
9cb21813 MH |
398 | static ssize_t read_vmcore(struct file *file, char __user *buffer, |
399 | size_t buflen, loff_t *fpos) | |
400 | { | |
401 | return __read_vmcore((__force char *) buffer, buflen, fpos, 1); | |
402 | } | |
403 | ||
404 | /* | |
405 | * The vmcore fault handler uses the page cache and fills data using the | |
406 | * standard __vmcore_read() function. | |
407 | * | |
408 | * On s390 the fault handler is used for memory regions that can't be mapped | |
409 | * directly with remap_pfn_range(). | |
410 | */ | |
36f06204 | 411 | static vm_fault_t mmap_vmcore_fault(struct vm_fault *vmf) |
9cb21813 MH |
412 | { |
413 | #ifdef CONFIG_S390 | |
11bac800 | 414 | struct address_space *mapping = vmf->vma->vm_file->f_mapping; |
9cb21813 MH |
415 | pgoff_t index = vmf->pgoff; |
416 | struct page *page; | |
417 | loff_t offset; | |
418 | char *buf; | |
419 | int rc; | |
420 | ||
421 | page = find_or_create_page(mapping, index, GFP_KERNEL); | |
422 | if (!page) | |
423 | return VM_FAULT_OOM; | |
424 | if (!PageUptodate(page)) { | |
09cbfeaf | 425 | offset = (loff_t) index << PAGE_SHIFT; |
9cb21813 MH |
426 | buf = __va((page_to_pfn(page) << PAGE_SHIFT)); |
427 | rc = __read_vmcore(buf, PAGE_SIZE, &offset, 0); | |
428 | if (rc < 0) { | |
429 | unlock_page(page); | |
09cbfeaf | 430 | put_page(page); |
b5c21237 | 431 | return vmf_error(rc); |
9cb21813 MH |
432 | } |
433 | SetPageUptodate(page); | |
434 | } | |
435 | unlock_page(page); | |
436 | vmf->page = page; | |
437 | return 0; | |
438 | #else | |
439 | return VM_FAULT_SIGBUS; | |
440 | #endif | |
441 | } | |
442 | ||
443 | static const struct vm_operations_struct vmcore_mmap_ops = { | |
444 | .fault = mmap_vmcore_fault, | |
445 | }; | |
446 | ||
83086978 | 447 | /** |
2724273e RL |
448 | * vmcore_alloc_buf - allocate buffer in vmalloc memory |
449 | * @sizez: size of buffer | |
83086978 HD |
450 | * |
451 | * If CONFIG_MMU is defined, use vmalloc_user() to allow users to mmap | |
452 | * the buffer to user-space by means of remap_vmalloc_range(). | |
453 | * | |
454 | * If CONFIG_MMU is not defined, use vzalloc() since mmap_vmcore() is | |
455 | * disabled and there's no need to allow users to mmap the buffer. | |
456 | */ | |
2724273e | 457 | static inline char *vmcore_alloc_buf(size_t size) |
83086978 HD |
458 | { |
459 | #ifdef CONFIG_MMU | |
2724273e | 460 | return vmalloc_user(size); |
83086978 | 461 | #else |
2724273e | 462 | return vzalloc(size); |
83086978 HD |
463 | #endif |
464 | } | |
465 | ||
466 | /* | |
467 | * Disable mmap_vmcore() if CONFIG_MMU is not defined. MMU is | |
468 | * essential for mmap_vmcore() in order to map physically | |
469 | * non-contiguous objects (ELF header, ELF note segment and memory | |
470 | * regions in the 1st kernel pointed to by PT_LOAD entries) into | |
471 | * virtually contiguous user-space in ELF layout. | |
472 | */ | |
11e376a3 | 473 | #ifdef CONFIG_MMU |
0692dedc VK |
474 | /* |
475 | * remap_oldmem_pfn_checked - do remap_oldmem_pfn_range replacing all pages | |
476 | * reported as not being ram with the zero page. | |
477 | * | |
478 | * @vma: vm_area_struct describing requested mapping | |
479 | * @from: start remapping from | |
480 | * @pfn: page frame number to start remapping to | |
481 | * @size: remapping size | |
482 | * @prot: protection bits | |
483 | * | |
484 | * Returns zero on success, -EAGAIN on failure. | |
485 | */ | |
486 | static int remap_oldmem_pfn_checked(struct vm_area_struct *vma, | |
487 | unsigned long from, unsigned long pfn, | |
488 | unsigned long size, pgprot_t prot) | |
489 | { | |
490 | unsigned long map_size; | |
491 | unsigned long pos_start, pos_end, pos; | |
492 | unsigned long zeropage_pfn = my_zero_pfn(0); | |
493 | size_t len = 0; | |
494 | ||
495 | pos_start = pfn; | |
496 | pos_end = pfn + (size >> PAGE_SHIFT); | |
497 | ||
498 | for (pos = pos_start; pos < pos_end; ++pos) { | |
499 | if (!pfn_is_ram(pos)) { | |
500 | /* | |
501 | * We hit a page which is not ram. Remap the continuous | |
502 | * region between pos_start and pos-1 and replace | |
503 | * the non-ram page at pos with the zero page. | |
504 | */ | |
505 | if (pos > pos_start) { | |
506 | /* Remap continuous region */ | |
507 | map_size = (pos - pos_start) << PAGE_SHIFT; | |
508 | if (remap_oldmem_pfn_range(vma, from + len, | |
509 | pos_start, map_size, | |
510 | prot)) | |
511 | goto fail; | |
512 | len += map_size; | |
513 | } | |
514 | /* Remap the zero page */ | |
515 | if (remap_oldmem_pfn_range(vma, from + len, | |
516 | zeropage_pfn, | |
517 | PAGE_SIZE, prot)) | |
518 | goto fail; | |
519 | len += PAGE_SIZE; | |
520 | pos_start = pos + 1; | |
521 | } | |
522 | } | |
523 | if (pos > pos_start) { | |
524 | /* Remap the rest */ | |
525 | map_size = (pos - pos_start) << PAGE_SHIFT; | |
526 | if (remap_oldmem_pfn_range(vma, from + len, pos_start, | |
527 | map_size, prot)) | |
528 | goto fail; | |
529 | } | |
530 | return 0; | |
531 | fail: | |
897ab3e0 | 532 | do_munmap(vma->vm_mm, from, len, NULL); |
0692dedc VK |
533 | return -EAGAIN; |
534 | } | |
535 | ||
536 | static int vmcore_remap_oldmem_pfn(struct vm_area_struct *vma, | |
537 | unsigned long from, unsigned long pfn, | |
538 | unsigned long size, pgprot_t prot) | |
539 | { | |
540 | /* | |
541 | * Check if oldmem_pfn_is_ram was registered to avoid | |
542 | * looping over all pages without a reason. | |
543 | */ | |
544 | if (oldmem_pfn_is_ram) | |
545 | return remap_oldmem_pfn_checked(vma, from, pfn, size, prot); | |
546 | else | |
547 | return remap_oldmem_pfn_range(vma, from, pfn, size, prot); | |
548 | } | |
549 | ||
83086978 HD |
550 | static int mmap_vmcore(struct file *file, struct vm_area_struct *vma) |
551 | { | |
552 | size_t size = vma->vm_end - vma->vm_start; | |
553 | u64 start, end, len, tsz; | |
554 | struct vmcore *m; | |
555 | ||
556 | start = (u64)vma->vm_pgoff << PAGE_SHIFT; | |
557 | end = start + size; | |
558 | ||
559 | if (size > vmcore_size || end > vmcore_size) | |
560 | return -EINVAL; | |
561 | ||
562 | if (vma->vm_flags & (VM_WRITE | VM_EXEC)) | |
563 | return -EPERM; | |
564 | ||
565 | vma->vm_flags &= ~(VM_MAYWRITE | VM_MAYEXEC); | |
566 | vma->vm_flags |= VM_MIXEDMAP; | |
9cb21813 | 567 | vma->vm_ops = &vmcore_mmap_ops; |
83086978 HD |
568 | |
569 | len = 0; | |
570 | ||
571 | if (start < elfcorebuf_sz) { | |
572 | u64 pfn; | |
573 | ||
574 | tsz = min(elfcorebuf_sz - (size_t)start, size); | |
575 | pfn = __pa(elfcorebuf + start) >> PAGE_SHIFT; | |
576 | if (remap_pfn_range(vma, vma->vm_start, pfn, tsz, | |
577 | vma->vm_page_prot)) | |
578 | return -EAGAIN; | |
579 | size -= tsz; | |
580 | start += tsz; | |
581 | len += tsz; | |
582 | ||
583 | if (size == 0) | |
584 | return 0; | |
585 | } | |
586 | ||
587 | if (start < elfcorebuf_sz + elfnotes_sz) { | |
588 | void *kaddr; | |
589 | ||
7efe48df RL |
590 | /* We add device dumps before other elf notes because the |
591 | * other elf notes may not fill the elf notes buffer | |
592 | * completely and we will end up with zero-filled data | |
593 | * between the elf notes and the device dumps. Tools will | |
594 | * then try to decode this zero-filled data as valid notes | |
595 | * and we don't want that. Hence, adding device dumps before | |
596 | * the other elf notes ensure that zero-filled data can be | |
597 | * avoided. This also ensures that the device dumps and | |
598 | * other elf notes can be properly mmaped at page aligned | |
599 | * address. | |
600 | */ | |
601 | #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP | |
602 | /* Read device dumps */ | |
603 | if (start < elfcorebuf_sz + vmcoredd_orig_sz) { | |
604 | u64 start_off; | |
605 | ||
606 | tsz = min(elfcorebuf_sz + vmcoredd_orig_sz - | |
607 | (size_t)start, size); | |
608 | start_off = start - elfcorebuf_sz; | |
609 | if (vmcoredd_mmap_dumps(vma, vma->vm_start + len, | |
610 | start_off, tsz)) | |
611 | goto fail; | |
612 | ||
613 | size -= tsz; | |
614 | start += tsz; | |
615 | len += tsz; | |
616 | ||
617 | /* leave now if filled buffer already */ | |
618 | if (!size) | |
619 | return 0; | |
620 | } | |
621 | #endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ | |
622 | ||
623 | /* Read remaining elf notes */ | |
83086978 | 624 | tsz = min(elfcorebuf_sz + elfnotes_sz - (size_t)start, size); |
7efe48df | 625 | kaddr = elfnotes_buf + start - elfcorebuf_sz - vmcoredd_orig_sz; |
83086978 HD |
626 | if (remap_vmalloc_range_partial(vma, vma->vm_start + len, |
627 | kaddr, tsz)) | |
628 | goto fail; | |
7efe48df | 629 | |
83086978 HD |
630 | size -= tsz; |
631 | start += tsz; | |
632 | len += tsz; | |
633 | ||
634 | if (size == 0) | |
635 | return 0; | |
636 | } | |
637 | ||
638 | list_for_each_entry(m, &vmcore_list, list) { | |
639 | if (start < m->offset + m->size) { | |
640 | u64 paddr = 0; | |
641 | ||
0b50a2d8 DY |
642 | tsz = (size_t)min_t(unsigned long long, |
643 | m->offset + m->size - start, size); | |
83086978 | 644 | paddr = m->paddr + start - m->offset; |
0692dedc VK |
645 | if (vmcore_remap_oldmem_pfn(vma, vma->vm_start + len, |
646 | paddr >> PAGE_SHIFT, tsz, | |
647 | vma->vm_page_prot)) | |
83086978 HD |
648 | goto fail; |
649 | size -= tsz; | |
650 | start += tsz; | |
651 | len += tsz; | |
652 | ||
653 | if (size == 0) | |
654 | return 0; | |
655 | } | |
656 | } | |
657 | ||
658 | return 0; | |
659 | fail: | |
897ab3e0 | 660 | do_munmap(vma->vm_mm, vma->vm_start, len, NULL); |
83086978 HD |
661 | return -EAGAIN; |
662 | } | |
663 | #else | |
664 | static int mmap_vmcore(struct file *file, struct vm_area_struct *vma) | |
665 | { | |
666 | return -ENOSYS; | |
667 | } | |
668 | #endif | |
669 | ||
5aa140c2 | 670 | static const struct file_operations proc_vmcore_operations = { |
666bfddb | 671 | .read = read_vmcore, |
c227e690 | 672 | .llseek = default_llseek, |
83086978 | 673 | .mmap = mmap_vmcore, |
666bfddb VG |
674 | }; |
675 | ||
676 | static struct vmcore* __init get_new_element(void) | |
677 | { | |
2f6d3110 | 678 | return kzalloc(sizeof(struct vmcore), GFP_KERNEL); |
666bfddb VG |
679 | } |
680 | ||
44c752fe RL |
681 | static u64 get_vmcore_size(size_t elfsz, size_t elfnotesegsz, |
682 | struct list_head *vc_list) | |
666bfddb | 683 | { |
666bfddb | 684 | u64 size; |
591ff716 | 685 | struct vmcore *m; |
72658e9d | 686 | |
591ff716 HD |
687 | size = elfsz + elfnotesegsz; |
688 | list_for_each_entry(m, vc_list, list) { | |
689 | size += m->size; | |
72658e9d VG |
690 | } |
691 | return size; | |
692 | } | |
693 | ||
087350c9 HD |
694 | /** |
695 | * update_note_header_size_elf64 - update p_memsz member of each PT_NOTE entry | |
696 | * | |
697 | * @ehdr_ptr: ELF header | |
698 | * | |
699 | * This function updates p_memsz member of each PT_NOTE entry in the | |
700 | * program header table pointed to by @ehdr_ptr to real size of ELF | |
701 | * note segment. | |
702 | */ | |
703 | static int __init update_note_header_size_elf64(const Elf64_Ehdr *ehdr_ptr) | |
666bfddb | 704 | { |
087350c9 HD |
705 | int i, rc=0; |
706 | Elf64_Phdr *phdr_ptr; | |
666bfddb | 707 | Elf64_Nhdr *nhdr_ptr; |
666bfddb | 708 | |
087350c9 | 709 | phdr_ptr = (Elf64_Phdr *)(ehdr_ptr + 1); |
666bfddb | 710 | for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) { |
666bfddb | 711 | void *notes_section; |
666bfddb VG |
712 | u64 offset, max_sz, sz, real_sz = 0; |
713 | if (phdr_ptr->p_type != PT_NOTE) | |
714 | continue; | |
666bfddb VG |
715 | max_sz = phdr_ptr->p_memsz; |
716 | offset = phdr_ptr->p_offset; | |
717 | notes_section = kmalloc(max_sz, GFP_KERNEL); | |
718 | if (!notes_section) | |
719 | return -ENOMEM; | |
be8a8d06 | 720 | rc = elfcorehdr_read_notes(notes_section, max_sz, &offset); |
666bfddb VG |
721 | if (rc < 0) { |
722 | kfree(notes_section); | |
723 | return rc; | |
724 | } | |
725 | nhdr_ptr = notes_section; | |
38dfac84 | 726 | while (nhdr_ptr->n_namesz != 0) { |
666bfddb | 727 | sz = sizeof(Elf64_Nhdr) + |
34b47764 WC |
728 | (((u64)nhdr_ptr->n_namesz + 3) & ~3) + |
729 | (((u64)nhdr_ptr->n_descsz + 3) & ~3); | |
38dfac84 GP |
730 | if ((real_sz + sz) > max_sz) { |
731 | pr_warn("Warning: Exceeded p_memsz, dropping PT_NOTE entry n_namesz=0x%x, n_descsz=0x%x\n", | |
732 | nhdr_ptr->n_namesz, nhdr_ptr->n_descsz); | |
733 | break; | |
734 | } | |
666bfddb VG |
735 | real_sz += sz; |
736 | nhdr_ptr = (Elf64_Nhdr*)((char*)nhdr_ptr + sz); | |
737 | } | |
666bfddb | 738 | kfree(notes_section); |
087350c9 | 739 | phdr_ptr->p_memsz = real_sz; |
38dfac84 GP |
740 | if (real_sz == 0) { |
741 | pr_warn("Warning: Zero PT_NOTE entries found\n"); | |
38dfac84 | 742 | } |
666bfddb VG |
743 | } |
744 | ||
087350c9 HD |
745 | return 0; |
746 | } | |
747 | ||
748 | /** | |
749 | * get_note_number_and_size_elf64 - get the number of PT_NOTE program | |
750 | * headers and sum of real size of their ELF note segment headers and | |
751 | * data. | |
752 | * | |
753 | * @ehdr_ptr: ELF header | |
754 | * @nr_ptnote: buffer for the number of PT_NOTE program headers | |
755 | * @sz_ptnote: buffer for size of unique PT_NOTE program header | |
756 | * | |
757 | * This function is used to merge multiple PT_NOTE program headers | |
758 | * into a unique single one. The resulting unique entry will have | |
759 | * @sz_ptnote in its phdr->p_mem. | |
760 | * | |
761 | * It is assumed that program headers with PT_NOTE type pointed to by | |
762 | * @ehdr_ptr has already been updated by update_note_header_size_elf64 | |
763 | * and each of PT_NOTE program headers has actual ELF note segment | |
764 | * size in its p_memsz member. | |
765 | */ | |
766 | static int __init get_note_number_and_size_elf64(const Elf64_Ehdr *ehdr_ptr, | |
767 | int *nr_ptnote, u64 *sz_ptnote) | |
768 | { | |
769 | int i; | |
770 | Elf64_Phdr *phdr_ptr; | |
771 | ||
772 | *nr_ptnote = *sz_ptnote = 0; | |
773 | ||
774 | phdr_ptr = (Elf64_Phdr *)(ehdr_ptr + 1); | |
775 | for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) { | |
776 | if (phdr_ptr->p_type != PT_NOTE) | |
777 | continue; | |
778 | *nr_ptnote += 1; | |
779 | *sz_ptnote += phdr_ptr->p_memsz; | |
780 | } | |
781 | ||
782 | return 0; | |
783 | } | |
784 | ||
785 | /** | |
786 | * copy_notes_elf64 - copy ELF note segments in a given buffer | |
787 | * | |
788 | * @ehdr_ptr: ELF header | |
789 | * @notes_buf: buffer into which ELF note segments are copied | |
790 | * | |
791 | * This function is used to copy ELF note segment in the 1st kernel | |
792 | * into the buffer @notes_buf in the 2nd kernel. It is assumed that | |
793 | * size of the buffer @notes_buf is equal to or larger than sum of the | |
794 | * real ELF note segment headers and data. | |
795 | * | |
796 | * It is assumed that program headers with PT_NOTE type pointed to by | |
797 | * @ehdr_ptr has already been updated by update_note_header_size_elf64 | |
798 | * and each of PT_NOTE program headers has actual ELF note segment | |
799 | * size in its p_memsz member. | |
800 | */ | |
801 | static int __init copy_notes_elf64(const Elf64_Ehdr *ehdr_ptr, char *notes_buf) | |
802 | { | |
803 | int i, rc=0; | |
804 | Elf64_Phdr *phdr_ptr; | |
805 | ||
806 | phdr_ptr = (Elf64_Phdr*)(ehdr_ptr + 1); | |
807 | ||
808 | for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) { | |
809 | u64 offset; | |
810 | if (phdr_ptr->p_type != PT_NOTE) | |
811 | continue; | |
812 | offset = phdr_ptr->p_offset; | |
be8a8d06 MH |
813 | rc = elfcorehdr_read_notes(notes_buf, phdr_ptr->p_memsz, |
814 | &offset); | |
087350c9 HD |
815 | if (rc < 0) |
816 | return rc; | |
817 | notes_buf += phdr_ptr->p_memsz; | |
818 | } | |
819 | ||
820 | return 0; | |
821 | } | |
822 | ||
823 | /* Merges all the PT_NOTE headers into one. */ | |
824 | static int __init merge_note_headers_elf64(char *elfptr, size_t *elfsz, | |
825 | char **notes_buf, size_t *notes_sz) | |
826 | { | |
827 | int i, nr_ptnote=0, rc=0; | |
828 | char *tmp; | |
829 | Elf64_Ehdr *ehdr_ptr; | |
830 | Elf64_Phdr phdr; | |
831 | u64 phdr_sz = 0, note_off; | |
832 | ||
833 | ehdr_ptr = (Elf64_Ehdr *)elfptr; | |
834 | ||
835 | rc = update_note_header_size_elf64(ehdr_ptr); | |
836 | if (rc < 0) | |
837 | return rc; | |
838 | ||
839 | rc = get_note_number_and_size_elf64(ehdr_ptr, &nr_ptnote, &phdr_sz); | |
840 | if (rc < 0) | |
841 | return rc; | |
842 | ||
843 | *notes_sz = roundup(phdr_sz, PAGE_SIZE); | |
2724273e | 844 | *notes_buf = vmcore_alloc_buf(*notes_sz); |
087350c9 HD |
845 | if (!*notes_buf) |
846 | return -ENOMEM; | |
847 | ||
848 | rc = copy_notes_elf64(ehdr_ptr, *notes_buf); | |
849 | if (rc < 0) | |
850 | return rc; | |
851 | ||
666bfddb VG |
852 | /* Prepare merged PT_NOTE program header. */ |
853 | phdr.p_type = PT_NOTE; | |
854 | phdr.p_flags = 0; | |
855 | note_off = sizeof(Elf64_Ehdr) + | |
856 | (ehdr_ptr->e_phnum - nr_ptnote +1) * sizeof(Elf64_Phdr); | |
087350c9 | 857 | phdr.p_offset = roundup(note_off, PAGE_SIZE); |
666bfddb VG |
858 | phdr.p_vaddr = phdr.p_paddr = 0; |
859 | phdr.p_filesz = phdr.p_memsz = phdr_sz; | |
860 | phdr.p_align = 0; | |
861 | ||
862 | /* Add merged PT_NOTE program header*/ | |
863 | tmp = elfptr + sizeof(Elf64_Ehdr); | |
864 | memcpy(tmp, &phdr, sizeof(phdr)); | |
865 | tmp += sizeof(phdr); | |
866 | ||
867 | /* Remove unwanted PT_NOTE program headers. */ | |
868 | i = (nr_ptnote - 1) * sizeof(Elf64_Phdr); | |
869 | *elfsz = *elfsz - i; | |
870 | memmove(tmp, tmp+i, ((*elfsz)-sizeof(Elf64_Ehdr)-sizeof(Elf64_Phdr))); | |
f2bdacdd HD |
871 | memset(elfptr + *elfsz, 0, i); |
872 | *elfsz = roundup(*elfsz, PAGE_SIZE); | |
666bfddb VG |
873 | |
874 | /* Modify e_phnum to reflect merged headers. */ | |
875 | ehdr_ptr->e_phnum = ehdr_ptr->e_phnum - nr_ptnote + 1; | |
876 | ||
7efe48df RL |
877 | /* Store the size of all notes. We need this to update the note |
878 | * header when the device dumps will be added. | |
879 | */ | |
880 | elfnotes_orig_sz = phdr.p_memsz; | |
881 | ||
666bfddb VG |
882 | return 0; |
883 | } | |
884 | ||
087350c9 HD |
885 | /** |
886 | * update_note_header_size_elf32 - update p_memsz member of each PT_NOTE entry | |
887 | * | |
888 | * @ehdr_ptr: ELF header | |
889 | * | |
890 | * This function updates p_memsz member of each PT_NOTE entry in the | |
891 | * program header table pointed to by @ehdr_ptr to real size of ELF | |
892 | * note segment. | |
893 | */ | |
894 | static int __init update_note_header_size_elf32(const Elf32_Ehdr *ehdr_ptr) | |
72658e9d | 895 | { |
087350c9 HD |
896 | int i, rc=0; |
897 | Elf32_Phdr *phdr_ptr; | |
72658e9d | 898 | Elf32_Nhdr *nhdr_ptr; |
72658e9d | 899 | |
087350c9 | 900 | phdr_ptr = (Elf32_Phdr *)(ehdr_ptr + 1); |
72658e9d | 901 | for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) { |
72658e9d | 902 | void *notes_section; |
72658e9d VG |
903 | u64 offset, max_sz, sz, real_sz = 0; |
904 | if (phdr_ptr->p_type != PT_NOTE) | |
905 | continue; | |
72658e9d VG |
906 | max_sz = phdr_ptr->p_memsz; |
907 | offset = phdr_ptr->p_offset; | |
908 | notes_section = kmalloc(max_sz, GFP_KERNEL); | |
909 | if (!notes_section) | |
910 | return -ENOMEM; | |
be8a8d06 | 911 | rc = elfcorehdr_read_notes(notes_section, max_sz, &offset); |
72658e9d VG |
912 | if (rc < 0) { |
913 | kfree(notes_section); | |
914 | return rc; | |
915 | } | |
916 | nhdr_ptr = notes_section; | |
38dfac84 | 917 | while (nhdr_ptr->n_namesz != 0) { |
72658e9d | 918 | sz = sizeof(Elf32_Nhdr) + |
34b47764 WC |
919 | (((u64)nhdr_ptr->n_namesz + 3) & ~3) + |
920 | (((u64)nhdr_ptr->n_descsz + 3) & ~3); | |
38dfac84 GP |
921 | if ((real_sz + sz) > max_sz) { |
922 | pr_warn("Warning: Exceeded p_memsz, dropping PT_NOTE entry n_namesz=0x%x, n_descsz=0x%x\n", | |
923 | nhdr_ptr->n_namesz, nhdr_ptr->n_descsz); | |
924 | break; | |
925 | } | |
72658e9d VG |
926 | real_sz += sz; |
927 | nhdr_ptr = (Elf32_Nhdr*)((char*)nhdr_ptr + sz); | |
928 | } | |
72658e9d | 929 | kfree(notes_section); |
087350c9 | 930 | phdr_ptr->p_memsz = real_sz; |
38dfac84 GP |
931 | if (real_sz == 0) { |
932 | pr_warn("Warning: Zero PT_NOTE entries found\n"); | |
38dfac84 | 933 | } |
087350c9 HD |
934 | } |
935 | ||
936 | return 0; | |
937 | } | |
938 | ||
939 | /** | |
940 | * get_note_number_and_size_elf32 - get the number of PT_NOTE program | |
941 | * headers and sum of real size of their ELF note segment headers and | |
942 | * data. | |
943 | * | |
944 | * @ehdr_ptr: ELF header | |
945 | * @nr_ptnote: buffer for the number of PT_NOTE program headers | |
946 | * @sz_ptnote: buffer for size of unique PT_NOTE program header | |
947 | * | |
948 | * This function is used to merge multiple PT_NOTE program headers | |
949 | * into a unique single one. The resulting unique entry will have | |
950 | * @sz_ptnote in its phdr->p_mem. | |
951 | * | |
952 | * It is assumed that program headers with PT_NOTE type pointed to by | |
953 | * @ehdr_ptr has already been updated by update_note_header_size_elf32 | |
954 | * and each of PT_NOTE program headers has actual ELF note segment | |
955 | * size in its p_memsz member. | |
956 | */ | |
957 | static int __init get_note_number_and_size_elf32(const Elf32_Ehdr *ehdr_ptr, | |
958 | int *nr_ptnote, u64 *sz_ptnote) | |
959 | { | |
960 | int i; | |
961 | Elf32_Phdr *phdr_ptr; | |
962 | ||
963 | *nr_ptnote = *sz_ptnote = 0; | |
964 | ||
965 | phdr_ptr = (Elf32_Phdr *)(ehdr_ptr + 1); | |
966 | for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) { | |
967 | if (phdr_ptr->p_type != PT_NOTE) | |
968 | continue; | |
969 | *nr_ptnote += 1; | |
970 | *sz_ptnote += phdr_ptr->p_memsz; | |
72658e9d VG |
971 | } |
972 | ||
087350c9 HD |
973 | return 0; |
974 | } | |
975 | ||
976 | /** | |
977 | * copy_notes_elf32 - copy ELF note segments in a given buffer | |
978 | * | |
979 | * @ehdr_ptr: ELF header | |
980 | * @notes_buf: buffer into which ELF note segments are copied | |
981 | * | |
982 | * This function is used to copy ELF note segment in the 1st kernel | |
983 | * into the buffer @notes_buf in the 2nd kernel. It is assumed that | |
984 | * size of the buffer @notes_buf is equal to or larger than sum of the | |
985 | * real ELF note segment headers and data. | |
986 | * | |
987 | * It is assumed that program headers with PT_NOTE type pointed to by | |
988 | * @ehdr_ptr has already been updated by update_note_header_size_elf32 | |
989 | * and each of PT_NOTE program headers has actual ELF note segment | |
990 | * size in its p_memsz member. | |
991 | */ | |
992 | static int __init copy_notes_elf32(const Elf32_Ehdr *ehdr_ptr, char *notes_buf) | |
993 | { | |
994 | int i, rc=0; | |
995 | Elf32_Phdr *phdr_ptr; | |
996 | ||
997 | phdr_ptr = (Elf32_Phdr*)(ehdr_ptr + 1); | |
998 | ||
999 | for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) { | |
1000 | u64 offset; | |
1001 | if (phdr_ptr->p_type != PT_NOTE) | |
1002 | continue; | |
1003 | offset = phdr_ptr->p_offset; | |
be8a8d06 MH |
1004 | rc = elfcorehdr_read_notes(notes_buf, phdr_ptr->p_memsz, |
1005 | &offset); | |
087350c9 HD |
1006 | if (rc < 0) |
1007 | return rc; | |
1008 | notes_buf += phdr_ptr->p_memsz; | |
1009 | } | |
1010 | ||
1011 | return 0; | |
1012 | } | |
1013 | ||
1014 | /* Merges all the PT_NOTE headers into one. */ | |
1015 | static int __init merge_note_headers_elf32(char *elfptr, size_t *elfsz, | |
1016 | char **notes_buf, size_t *notes_sz) | |
1017 | { | |
1018 | int i, nr_ptnote=0, rc=0; | |
1019 | char *tmp; | |
1020 | Elf32_Ehdr *ehdr_ptr; | |
1021 | Elf32_Phdr phdr; | |
1022 | u64 phdr_sz = 0, note_off; | |
1023 | ||
1024 | ehdr_ptr = (Elf32_Ehdr *)elfptr; | |
1025 | ||
1026 | rc = update_note_header_size_elf32(ehdr_ptr); | |
1027 | if (rc < 0) | |
1028 | return rc; | |
1029 | ||
1030 | rc = get_note_number_and_size_elf32(ehdr_ptr, &nr_ptnote, &phdr_sz); | |
1031 | if (rc < 0) | |
1032 | return rc; | |
1033 | ||
1034 | *notes_sz = roundup(phdr_sz, PAGE_SIZE); | |
2724273e | 1035 | *notes_buf = vmcore_alloc_buf(*notes_sz); |
087350c9 HD |
1036 | if (!*notes_buf) |
1037 | return -ENOMEM; | |
1038 | ||
1039 | rc = copy_notes_elf32(ehdr_ptr, *notes_buf); | |
1040 | if (rc < 0) | |
1041 | return rc; | |
1042 | ||
72658e9d VG |
1043 | /* Prepare merged PT_NOTE program header. */ |
1044 | phdr.p_type = PT_NOTE; | |
1045 | phdr.p_flags = 0; | |
1046 | note_off = sizeof(Elf32_Ehdr) + | |
1047 | (ehdr_ptr->e_phnum - nr_ptnote +1) * sizeof(Elf32_Phdr); | |
087350c9 | 1048 | phdr.p_offset = roundup(note_off, PAGE_SIZE); |
72658e9d VG |
1049 | phdr.p_vaddr = phdr.p_paddr = 0; |
1050 | phdr.p_filesz = phdr.p_memsz = phdr_sz; | |
1051 | phdr.p_align = 0; | |
1052 | ||
1053 | /* Add merged PT_NOTE program header*/ | |
1054 | tmp = elfptr + sizeof(Elf32_Ehdr); | |
1055 | memcpy(tmp, &phdr, sizeof(phdr)); | |
1056 | tmp += sizeof(phdr); | |
1057 | ||
1058 | /* Remove unwanted PT_NOTE program headers. */ | |
1059 | i = (nr_ptnote - 1) * sizeof(Elf32_Phdr); | |
1060 | *elfsz = *elfsz - i; | |
1061 | memmove(tmp, tmp+i, ((*elfsz)-sizeof(Elf32_Ehdr)-sizeof(Elf32_Phdr))); | |
f2bdacdd HD |
1062 | memset(elfptr + *elfsz, 0, i); |
1063 | *elfsz = roundup(*elfsz, PAGE_SIZE); | |
72658e9d VG |
1064 | |
1065 | /* Modify e_phnum to reflect merged headers. */ | |
1066 | ehdr_ptr->e_phnum = ehdr_ptr->e_phnum - nr_ptnote + 1; | |
1067 | ||
7efe48df RL |
1068 | /* Store the size of all notes. We need this to update the note |
1069 | * header when the device dumps will be added. | |
1070 | */ | |
1071 | elfnotes_orig_sz = phdr.p_memsz; | |
1072 | ||
72658e9d VG |
1073 | return 0; |
1074 | } | |
1075 | ||
666bfddb VG |
1076 | /* Add memory chunks represented by program headers to vmcore list. Also update |
1077 | * the new offset fields of exported program headers. */ | |
1078 | static int __init process_ptload_program_headers_elf64(char *elfptr, | |
1079 | size_t elfsz, | |
087350c9 | 1080 | size_t elfnotes_sz, |
666bfddb VG |
1081 | struct list_head *vc_list) |
1082 | { | |
1083 | int i; | |
1084 | Elf64_Ehdr *ehdr_ptr; | |
1085 | Elf64_Phdr *phdr_ptr; | |
1086 | loff_t vmcore_off; | |
1087 | struct vmcore *new; | |
1088 | ||
1089 | ehdr_ptr = (Elf64_Ehdr *)elfptr; | |
1090 | phdr_ptr = (Elf64_Phdr*)(elfptr + sizeof(Elf64_Ehdr)); /* PT_NOTE hdr */ | |
1091 | ||
087350c9 HD |
1092 | /* Skip Elf header, program headers and Elf note segment. */ |
1093 | vmcore_off = elfsz + elfnotes_sz; | |
666bfddb VG |
1094 | |
1095 | for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) { | |
7f614cd1 HD |
1096 | u64 paddr, start, end, size; |
1097 | ||
666bfddb VG |
1098 | if (phdr_ptr->p_type != PT_LOAD) |
1099 | continue; | |
1100 | ||
7f614cd1 HD |
1101 | paddr = phdr_ptr->p_offset; |
1102 | start = rounddown(paddr, PAGE_SIZE); | |
1103 | end = roundup(paddr + phdr_ptr->p_memsz, PAGE_SIZE); | |
1104 | size = end - start; | |
1105 | ||
666bfddb VG |
1106 | /* Add this contiguous chunk of memory to vmcore list.*/ |
1107 | new = get_new_element(); | |
1108 | if (!new) | |
1109 | return -ENOMEM; | |
7f614cd1 HD |
1110 | new->paddr = start; |
1111 | new->size = size; | |
666bfddb VG |
1112 | list_add_tail(&new->list, vc_list); |
1113 | ||
1114 | /* Update the program header offset. */ | |
7f614cd1 HD |
1115 | phdr_ptr->p_offset = vmcore_off + (paddr - start); |
1116 | vmcore_off = vmcore_off + size; | |
666bfddb VG |
1117 | } |
1118 | return 0; | |
1119 | } | |
1120 | ||
72658e9d VG |
1121 | static int __init process_ptload_program_headers_elf32(char *elfptr, |
1122 | size_t elfsz, | |
087350c9 | 1123 | size_t elfnotes_sz, |
72658e9d VG |
1124 | struct list_head *vc_list) |
1125 | { | |
1126 | int i; | |
1127 | Elf32_Ehdr *ehdr_ptr; | |
1128 | Elf32_Phdr *phdr_ptr; | |
1129 | loff_t vmcore_off; | |
1130 | struct vmcore *new; | |
1131 | ||
1132 | ehdr_ptr = (Elf32_Ehdr *)elfptr; | |
1133 | phdr_ptr = (Elf32_Phdr*)(elfptr + sizeof(Elf32_Ehdr)); /* PT_NOTE hdr */ | |
1134 | ||
087350c9 HD |
1135 | /* Skip Elf header, program headers and Elf note segment. */ |
1136 | vmcore_off = elfsz + elfnotes_sz; | |
72658e9d VG |
1137 | |
1138 | for (i = 0; i < ehdr_ptr->e_phnum; i++, phdr_ptr++) { | |
7f614cd1 HD |
1139 | u64 paddr, start, end, size; |
1140 | ||
72658e9d VG |
1141 | if (phdr_ptr->p_type != PT_LOAD) |
1142 | continue; | |
1143 | ||
7f614cd1 HD |
1144 | paddr = phdr_ptr->p_offset; |
1145 | start = rounddown(paddr, PAGE_SIZE); | |
1146 | end = roundup(paddr + phdr_ptr->p_memsz, PAGE_SIZE); | |
1147 | size = end - start; | |
1148 | ||
72658e9d VG |
1149 | /* Add this contiguous chunk of memory to vmcore list.*/ |
1150 | new = get_new_element(); | |
1151 | if (!new) | |
1152 | return -ENOMEM; | |
7f614cd1 HD |
1153 | new->paddr = start; |
1154 | new->size = size; | |
72658e9d VG |
1155 | list_add_tail(&new->list, vc_list); |
1156 | ||
1157 | /* Update the program header offset */ | |
7f614cd1 HD |
1158 | phdr_ptr->p_offset = vmcore_off + (paddr - start); |
1159 | vmcore_off = vmcore_off + size; | |
72658e9d VG |
1160 | } |
1161 | return 0; | |
1162 | } | |
1163 | ||
666bfddb | 1164 | /* Sets offset fields of vmcore elements. */ |
7efe48df RL |
1165 | static void set_vmcore_list_offsets(size_t elfsz, size_t elfnotes_sz, |
1166 | struct list_head *vc_list) | |
666bfddb VG |
1167 | { |
1168 | loff_t vmcore_off; | |
666bfddb VG |
1169 | struct vmcore *m; |
1170 | ||
087350c9 HD |
1171 | /* Skip Elf header, program headers and Elf note segment. */ |
1172 | vmcore_off = elfsz + elfnotes_sz; | |
666bfddb VG |
1173 | |
1174 | list_for_each_entry(m, vc_list, list) { | |
1175 | m->offset = vmcore_off; | |
1176 | vmcore_off += m->size; | |
1177 | } | |
1178 | } | |
1179 | ||
f2bdacdd | 1180 | static void free_elfcorebuf(void) |
72658e9d | 1181 | { |
f2bdacdd HD |
1182 | free_pages((unsigned long)elfcorebuf, get_order(elfcorebuf_sz_orig)); |
1183 | elfcorebuf = NULL; | |
087350c9 HD |
1184 | vfree(elfnotes_buf); |
1185 | elfnotes_buf = NULL; | |
72658e9d VG |
1186 | } |
1187 | ||
666bfddb VG |
1188 | static int __init parse_crash_elf64_headers(void) |
1189 | { | |
1190 | int rc=0; | |
1191 | Elf64_Ehdr ehdr; | |
1192 | u64 addr; | |
1193 | ||
1194 | addr = elfcorehdr_addr; | |
1195 | ||
1196 | /* Read Elf header */ | |
be8a8d06 | 1197 | rc = elfcorehdr_read((char *)&ehdr, sizeof(Elf64_Ehdr), &addr); |
666bfddb VG |
1198 | if (rc < 0) |
1199 | return rc; | |
1200 | ||
1201 | /* Do some basic Verification. */ | |
1202 | if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0 || | |
1203 | (ehdr.e_type != ET_CORE) || | |
9833c394 | 1204 | !vmcore_elf64_check_arch(&ehdr) || |
666bfddb VG |
1205 | ehdr.e_ident[EI_CLASS] != ELFCLASS64 || |
1206 | ehdr.e_ident[EI_VERSION] != EV_CURRENT || | |
1207 | ehdr.e_version != EV_CURRENT || | |
1208 | ehdr.e_ehsize != sizeof(Elf64_Ehdr) || | |
1209 | ehdr.e_phentsize != sizeof(Elf64_Phdr) || | |
1210 | ehdr.e_phnum == 0) { | |
87ebdc00 | 1211 | pr_warn("Warning: Core image elf header is not sane\n"); |
666bfddb VG |
1212 | return -EINVAL; |
1213 | } | |
1214 | ||
1215 | /* Read in all elf headers. */ | |
f2bdacdd HD |
1216 | elfcorebuf_sz_orig = sizeof(Elf64_Ehdr) + |
1217 | ehdr.e_phnum * sizeof(Elf64_Phdr); | |
1218 | elfcorebuf_sz = elfcorebuf_sz_orig; | |
1219 | elfcorebuf = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, | |
1220 | get_order(elfcorebuf_sz_orig)); | |
666bfddb VG |
1221 | if (!elfcorebuf) |
1222 | return -ENOMEM; | |
1223 | addr = elfcorehdr_addr; | |
be8a8d06 | 1224 | rc = elfcorehdr_read(elfcorebuf, elfcorebuf_sz_orig, &addr); |
f2bdacdd HD |
1225 | if (rc < 0) |
1226 | goto fail; | |
666bfddb VG |
1227 | |
1228 | /* Merge all PT_NOTE headers into one. */ | |
087350c9 HD |
1229 | rc = merge_note_headers_elf64(elfcorebuf, &elfcorebuf_sz, |
1230 | &elfnotes_buf, &elfnotes_sz); | |
f2bdacdd HD |
1231 | if (rc) |
1232 | goto fail; | |
666bfddb | 1233 | rc = process_ptload_program_headers_elf64(elfcorebuf, elfcorebuf_sz, |
087350c9 | 1234 | elfnotes_sz, &vmcore_list); |
f2bdacdd HD |
1235 | if (rc) |
1236 | goto fail; | |
087350c9 | 1237 | set_vmcore_list_offsets(elfcorebuf_sz, elfnotes_sz, &vmcore_list); |
666bfddb | 1238 | return 0; |
f2bdacdd HD |
1239 | fail: |
1240 | free_elfcorebuf(); | |
1241 | return rc; | |
666bfddb VG |
1242 | } |
1243 | ||
72658e9d VG |
1244 | static int __init parse_crash_elf32_headers(void) |
1245 | { | |
1246 | int rc=0; | |
1247 | Elf32_Ehdr ehdr; | |
1248 | u64 addr; | |
1249 | ||
1250 | addr = elfcorehdr_addr; | |
1251 | ||
1252 | /* Read Elf header */ | |
be8a8d06 | 1253 | rc = elfcorehdr_read((char *)&ehdr, sizeof(Elf32_Ehdr), &addr); |
72658e9d VG |
1254 | if (rc < 0) |
1255 | return rc; | |
1256 | ||
1257 | /* Do some basic Verification. */ | |
1258 | if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0 || | |
1259 | (ehdr.e_type != ET_CORE) || | |
e55d5312 | 1260 | !vmcore_elf32_check_arch(&ehdr) || |
72658e9d VG |
1261 | ehdr.e_ident[EI_CLASS] != ELFCLASS32|| |
1262 | ehdr.e_ident[EI_VERSION] != EV_CURRENT || | |
1263 | ehdr.e_version != EV_CURRENT || | |
1264 | ehdr.e_ehsize != sizeof(Elf32_Ehdr) || | |
1265 | ehdr.e_phentsize != sizeof(Elf32_Phdr) || | |
1266 | ehdr.e_phnum == 0) { | |
87ebdc00 | 1267 | pr_warn("Warning: Core image elf header is not sane\n"); |
72658e9d VG |
1268 | return -EINVAL; |
1269 | } | |
1270 | ||
1271 | /* Read in all elf headers. */ | |
f2bdacdd HD |
1272 | elfcorebuf_sz_orig = sizeof(Elf32_Ehdr) + ehdr.e_phnum * sizeof(Elf32_Phdr); |
1273 | elfcorebuf_sz = elfcorebuf_sz_orig; | |
1274 | elfcorebuf = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, | |
1275 | get_order(elfcorebuf_sz_orig)); | |
72658e9d VG |
1276 | if (!elfcorebuf) |
1277 | return -ENOMEM; | |
1278 | addr = elfcorehdr_addr; | |
be8a8d06 | 1279 | rc = elfcorehdr_read(elfcorebuf, elfcorebuf_sz_orig, &addr); |
f2bdacdd HD |
1280 | if (rc < 0) |
1281 | goto fail; | |
72658e9d VG |
1282 | |
1283 | /* Merge all PT_NOTE headers into one. */ | |
087350c9 HD |
1284 | rc = merge_note_headers_elf32(elfcorebuf, &elfcorebuf_sz, |
1285 | &elfnotes_buf, &elfnotes_sz); | |
f2bdacdd HD |
1286 | if (rc) |
1287 | goto fail; | |
72658e9d | 1288 | rc = process_ptload_program_headers_elf32(elfcorebuf, elfcorebuf_sz, |
087350c9 | 1289 | elfnotes_sz, &vmcore_list); |
f2bdacdd HD |
1290 | if (rc) |
1291 | goto fail; | |
087350c9 | 1292 | set_vmcore_list_offsets(elfcorebuf_sz, elfnotes_sz, &vmcore_list); |
72658e9d | 1293 | return 0; |
f2bdacdd HD |
1294 | fail: |
1295 | free_elfcorebuf(); | |
1296 | return rc; | |
72658e9d VG |
1297 | } |
1298 | ||
666bfddb VG |
1299 | static int __init parse_crash_elf_headers(void) |
1300 | { | |
1301 | unsigned char e_ident[EI_NIDENT]; | |
1302 | u64 addr; | |
1303 | int rc=0; | |
1304 | ||
1305 | addr = elfcorehdr_addr; | |
be8a8d06 | 1306 | rc = elfcorehdr_read(e_ident, EI_NIDENT, &addr); |
666bfddb VG |
1307 | if (rc < 0) |
1308 | return rc; | |
1309 | if (memcmp(e_ident, ELFMAG, SELFMAG) != 0) { | |
87ebdc00 | 1310 | pr_warn("Warning: Core image elf header not found\n"); |
666bfddb VG |
1311 | return -EINVAL; |
1312 | } | |
1313 | ||
1314 | if (e_ident[EI_CLASS] == ELFCLASS64) { | |
1315 | rc = parse_crash_elf64_headers(); | |
1316 | if (rc) | |
1317 | return rc; | |
72658e9d VG |
1318 | } else if (e_ident[EI_CLASS] == ELFCLASS32) { |
1319 | rc = parse_crash_elf32_headers(); | |
1320 | if (rc) | |
1321 | return rc; | |
666bfddb | 1322 | } else { |
87ebdc00 | 1323 | pr_warn("Warning: Core image elf header is not sane\n"); |
666bfddb VG |
1324 | return -EINVAL; |
1325 | } | |
591ff716 HD |
1326 | |
1327 | /* Determine vmcore size. */ | |
1328 | vmcore_size = get_vmcore_size(elfcorebuf_sz, elfnotes_sz, | |
1329 | &vmcore_list); | |
1330 | ||
666bfddb VG |
1331 | return 0; |
1332 | } | |
1333 | ||
2724273e RL |
1334 | #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP |
1335 | /** | |
1336 | * vmcoredd_write_header - Write vmcore device dump header at the | |
1337 | * beginning of the dump's buffer. | |
1338 | * @buf: Output buffer where the note is written | |
1339 | * @data: Dump info | |
1340 | * @size: Size of the dump | |
1341 | * | |
1342 | * Fills beginning of the dump's buffer with vmcore device dump header. | |
1343 | */ | |
1344 | static void vmcoredd_write_header(void *buf, struct vmcoredd_data *data, | |
1345 | u32 size) | |
1346 | { | |
1347 | struct vmcoredd_header *vdd_hdr = (struct vmcoredd_header *)buf; | |
1348 | ||
1349 | vdd_hdr->n_namesz = sizeof(vdd_hdr->name); | |
1350 | vdd_hdr->n_descsz = size + sizeof(vdd_hdr->dump_name); | |
1351 | vdd_hdr->n_type = NT_VMCOREDD; | |
1352 | ||
1353 | strncpy((char *)vdd_hdr->name, VMCOREDD_NOTE_NAME, | |
1354 | sizeof(vdd_hdr->name)); | |
1355 | memcpy(vdd_hdr->dump_name, data->dump_name, sizeof(vdd_hdr->dump_name)); | |
1356 | } | |
1357 | ||
7efe48df RL |
1358 | /** |
1359 | * vmcoredd_update_program_headers - Update all Elf program headers | |
1360 | * @elfptr: Pointer to elf header | |
1361 | * @elfnotesz: Size of elf notes aligned to page size | |
1362 | * @vmcoreddsz: Size of device dumps to be added to elf note header | |
1363 | * | |
1364 | * Determine type of Elf header (Elf64 or Elf32) and update the elf note size. | |
1365 | * Also update the offsets of all the program headers after the elf note header. | |
1366 | */ | |
1367 | static void vmcoredd_update_program_headers(char *elfptr, size_t elfnotesz, | |
1368 | size_t vmcoreddsz) | |
1369 | { | |
1370 | unsigned char *e_ident = (unsigned char *)elfptr; | |
1371 | u64 start, end, size; | |
1372 | loff_t vmcore_off; | |
1373 | u32 i; | |
1374 | ||
1375 | vmcore_off = elfcorebuf_sz + elfnotesz; | |
1376 | ||
1377 | if (e_ident[EI_CLASS] == ELFCLASS64) { | |
1378 | Elf64_Ehdr *ehdr = (Elf64_Ehdr *)elfptr; | |
1379 | Elf64_Phdr *phdr = (Elf64_Phdr *)(elfptr + sizeof(Elf64_Ehdr)); | |
1380 | ||
1381 | /* Update all program headers */ | |
1382 | for (i = 0; i < ehdr->e_phnum; i++, phdr++) { | |
1383 | if (phdr->p_type == PT_NOTE) { | |
1384 | /* Update note size */ | |
1385 | phdr->p_memsz = elfnotes_orig_sz + vmcoreddsz; | |
1386 | phdr->p_filesz = phdr->p_memsz; | |
1387 | continue; | |
1388 | } | |
1389 | ||
1390 | start = rounddown(phdr->p_offset, PAGE_SIZE); | |
1391 | end = roundup(phdr->p_offset + phdr->p_memsz, | |
1392 | PAGE_SIZE); | |
1393 | size = end - start; | |
1394 | phdr->p_offset = vmcore_off + (phdr->p_offset - start); | |
1395 | vmcore_off += size; | |
1396 | } | |
1397 | } else { | |
1398 | Elf32_Ehdr *ehdr = (Elf32_Ehdr *)elfptr; | |
1399 | Elf32_Phdr *phdr = (Elf32_Phdr *)(elfptr + sizeof(Elf32_Ehdr)); | |
1400 | ||
1401 | /* Update all program headers */ | |
1402 | for (i = 0; i < ehdr->e_phnum; i++, phdr++) { | |
1403 | if (phdr->p_type == PT_NOTE) { | |
1404 | /* Update note size */ | |
1405 | phdr->p_memsz = elfnotes_orig_sz + vmcoreddsz; | |
1406 | phdr->p_filesz = phdr->p_memsz; | |
1407 | continue; | |
1408 | } | |
1409 | ||
1410 | start = rounddown(phdr->p_offset, PAGE_SIZE); | |
1411 | end = roundup(phdr->p_offset + phdr->p_memsz, | |
1412 | PAGE_SIZE); | |
1413 | size = end - start; | |
1414 | phdr->p_offset = vmcore_off + (phdr->p_offset - start); | |
1415 | vmcore_off += size; | |
1416 | } | |
1417 | } | |
1418 | } | |
1419 | ||
1420 | /** | |
1421 | * vmcoredd_update_size - Update the total size of the device dumps and update | |
1422 | * Elf header | |
1423 | * @dump_size: Size of the current device dump to be added to total size | |
1424 | * | |
1425 | * Update the total size of all the device dumps and update the Elf program | |
1426 | * headers. Calculate the new offsets for the vmcore list and update the | |
1427 | * total vmcore size. | |
1428 | */ | |
1429 | static void vmcoredd_update_size(size_t dump_size) | |
1430 | { | |
1431 | vmcoredd_orig_sz += dump_size; | |
1432 | elfnotes_sz = roundup(elfnotes_orig_sz, PAGE_SIZE) + vmcoredd_orig_sz; | |
1433 | vmcoredd_update_program_headers(elfcorebuf, elfnotes_sz, | |
1434 | vmcoredd_orig_sz); | |
1435 | ||
1436 | /* Update vmcore list offsets */ | |
1437 | set_vmcore_list_offsets(elfcorebuf_sz, elfnotes_sz, &vmcore_list); | |
1438 | ||
1439 | vmcore_size = get_vmcore_size(elfcorebuf_sz, elfnotes_sz, | |
1440 | &vmcore_list); | |
1441 | proc_vmcore->size = vmcore_size; | |
1442 | } | |
1443 | ||
2724273e RL |
1444 | /** |
1445 | * vmcore_add_device_dump - Add a buffer containing device dump to vmcore | |
1446 | * @data: dump info. | |
1447 | * | |
1448 | * Allocate a buffer and invoke the calling driver's dump collect routine. | |
1449 | * Write Elf note at the beginning of the buffer to indicate vmcore device | |
1450 | * dump and add the dump to global list. | |
1451 | */ | |
1452 | int vmcore_add_device_dump(struct vmcoredd_data *data) | |
1453 | { | |
1454 | struct vmcoredd_node *dump; | |
1455 | void *buf = NULL; | |
1456 | size_t data_size; | |
1457 | int ret; | |
1458 | ||
c6c40533 KS |
1459 | if (vmcoredd_disabled) { |
1460 | pr_err_once("Device dump is disabled\n"); | |
1461 | return -EINVAL; | |
1462 | } | |
1463 | ||
2724273e RL |
1464 | if (!data || !strlen(data->dump_name) || |
1465 | !data->vmcoredd_callback || !data->size) | |
1466 | return -EINVAL; | |
1467 | ||
1468 | dump = vzalloc(sizeof(*dump)); | |
1469 | if (!dump) { | |
1470 | ret = -ENOMEM; | |
1471 | goto out_err; | |
1472 | } | |
1473 | ||
1474 | /* Keep size of the buffer page aligned so that it can be mmaped */ | |
1475 | data_size = roundup(sizeof(struct vmcoredd_header) + data->size, | |
1476 | PAGE_SIZE); | |
1477 | ||
1478 | /* Allocate buffer for driver's to write their dumps */ | |
1479 | buf = vmcore_alloc_buf(data_size); | |
1480 | if (!buf) { | |
1481 | ret = -ENOMEM; | |
1482 | goto out_err; | |
1483 | } | |
1484 | ||
1485 | vmcoredd_write_header(buf, data, data_size - | |
1486 | sizeof(struct vmcoredd_header)); | |
1487 | ||
1488 | /* Invoke the driver's dump collection routing */ | |
1489 | ret = data->vmcoredd_callback(data, buf + | |
1490 | sizeof(struct vmcoredd_header)); | |
1491 | if (ret) | |
1492 | goto out_err; | |
1493 | ||
1494 | dump->buf = buf; | |
1495 | dump->size = data_size; | |
1496 | ||
1497 | /* Add the dump to driver sysfs list */ | |
1498 | mutex_lock(&vmcoredd_mutex); | |
1499 | list_add_tail(&dump->list, &vmcoredd_list); | |
1500 | mutex_unlock(&vmcoredd_mutex); | |
1501 | ||
7efe48df | 1502 | vmcoredd_update_size(data_size); |
2724273e RL |
1503 | return 0; |
1504 | ||
1505 | out_err: | |
1506 | if (buf) | |
1507 | vfree(buf); | |
1508 | ||
1509 | if (dump) | |
1510 | vfree(dump); | |
1511 | ||
1512 | return ret; | |
1513 | } | |
1514 | EXPORT_SYMBOL(vmcore_add_device_dump); | |
1515 | #endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ | |
1516 | ||
1517 | /* Free all dumps in vmcore device dump list */ | |
1518 | static void vmcore_free_device_dumps(void) | |
1519 | { | |
1520 | #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP | |
1521 | mutex_lock(&vmcoredd_mutex); | |
1522 | while (!list_empty(&vmcoredd_list)) { | |
1523 | struct vmcoredd_node *dump; | |
1524 | ||
1525 | dump = list_first_entry(&vmcoredd_list, struct vmcoredd_node, | |
1526 | list); | |
1527 | list_del(&dump->list); | |
1528 | vfree(dump->buf); | |
1529 | vfree(dump); | |
1530 | } | |
1531 | mutex_unlock(&vmcoredd_mutex); | |
1532 | #endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ | |
1533 | } | |
1534 | ||
666bfddb VG |
1535 | /* Init function for vmcore module. */ |
1536 | static int __init vmcore_init(void) | |
1537 | { | |
1538 | int rc = 0; | |
1539 | ||
be8a8d06 MH |
1540 | /* Allow architectures to allocate ELF header in 2nd kernel */ |
1541 | rc = elfcorehdr_alloc(&elfcorehdr_addr, &elfcorehdr_size); | |
1542 | if (rc) | |
1543 | return rc; | |
1544 | /* | |
1545 | * If elfcorehdr= has been passed in cmdline or created in 2nd kernel, | |
1546 | * then capture the dump. | |
1547 | */ | |
85a0ee34 | 1548 | if (!(is_vmcore_usable())) |
666bfddb VG |
1549 | return rc; |
1550 | rc = parse_crash_elf_headers(); | |
1551 | if (rc) { | |
87ebdc00 | 1552 | pr_warn("Kdump: vmcore not initialized\n"); |
666bfddb VG |
1553 | return rc; |
1554 | } | |
be8a8d06 MH |
1555 | elfcorehdr_free(elfcorehdr_addr); |
1556 | elfcorehdr_addr = ELFCORE_ADDR_ERR; | |
666bfddb | 1557 | |
5aa140c2 | 1558 | proc_vmcore = proc_create("vmcore", S_IRUSR, NULL, &proc_vmcore_operations); |
666bfddb VG |
1559 | if (proc_vmcore) |
1560 | proc_vmcore->size = vmcore_size; | |
1561 | return 0; | |
1562 | } | |
abaf3787 | 1563 | fs_initcall(vmcore_init); |
16257393 MS |
1564 | |
1565 | /* Cleanup function for vmcore module. */ | |
1566 | void vmcore_cleanup(void) | |
1567 | { | |
16257393 | 1568 | if (proc_vmcore) { |
a8ca16ea | 1569 | proc_remove(proc_vmcore); |
16257393 MS |
1570 | proc_vmcore = NULL; |
1571 | } | |
1572 | ||
1573 | /* clear the vmcore list. */ | |
593bc695 | 1574 | while (!list_empty(&vmcore_list)) { |
16257393 MS |
1575 | struct vmcore *m; |
1576 | ||
593bc695 | 1577 | m = list_first_entry(&vmcore_list, struct vmcore, list); |
16257393 MS |
1578 | list_del(&m->list); |
1579 | kfree(m); | |
1580 | } | |
f2bdacdd | 1581 | free_elfcorebuf(); |
2724273e RL |
1582 | |
1583 | /* clear vmcore device dump list */ | |
1584 | vmcore_free_device_dumps(); | |
16257393 | 1585 | } |