1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Coredump functionality for Remoteproc framework. |
4 | * |
5 | * Copyright (c) 2020, The Linux Foundation. All rights reserved. |
6 | */ |
7 | |
8 | #include <linux/completion.h> |
9 | #include <linux/devcoredump.h> |
10 | #include <linux/device.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/remoteproc.h> |
13 | #include "remoteproc_internal.h" |
14 | #include "remoteproc_elf_helpers.h" |
15 | |
16 | struct rproc_coredump_state { |
17 | struct rproc *rproc; |
18 | void *; |
19 | struct completion dump_done; |
20 | }; |
21 | |
22 | /** |
23 | * rproc_coredump_cleanup() - clean up dump_segments list |
24 | * @rproc: the remote processor handle |
25 | */ |
26 | void rproc_coredump_cleanup(struct rproc *rproc) |
27 | { |
28 | struct rproc_dump_segment *entry, *tmp; |
29 | |
30 | list_for_each_entry_safe(entry, tmp, &rproc->dump_segments, node) { |
31 | list_del(entry: &entry->node); |
32 | kfree(objp: entry); |
33 | } |
34 | } |
35 | EXPORT_SYMBOL_GPL(rproc_coredump_cleanup); |
36 | |
37 | /** |
38 | * rproc_coredump_add_segment() - add segment of device memory to coredump |
39 | * @rproc: handle of a remote processor |
40 | * @da: device address |
41 | * @size: size of segment |
42 | * |
43 | * Add device memory to the list of segments to be included in a coredump for |
44 | * the remoteproc. |
45 | * |
46 | * Return: 0 on success, negative errno on error. |
47 | */ |
48 | int rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size) |
49 | { |
50 | struct rproc_dump_segment *segment; |
51 | |
52 | segment = kzalloc(size: sizeof(*segment), GFP_KERNEL); |
53 | if (!segment) |
54 | return -ENOMEM; |
55 | |
56 | segment->da = da; |
57 | segment->size = size; |
58 | |
59 | list_add_tail(new: &segment->node, head: &rproc->dump_segments); |
60 | |
61 | return 0; |
62 | } |
63 | EXPORT_SYMBOL(rproc_coredump_add_segment); |
64 | |
65 | /** |
66 | * rproc_coredump_add_custom_segment() - add custom coredump segment |
67 | * @rproc: handle of a remote processor |
68 | * @da: device address |
69 | * @size: size of segment |
70 | * @dumpfn: custom dump function called for each segment during coredump |
71 | * @priv: private data |
72 | * |
73 | * Add device memory to the list of segments to be included in the coredump |
74 | * and associate the segment with the given custom dump function and private |
75 | * data. |
76 | * |
77 | * Return: 0 on success, negative errno on error. |
78 | */ |
79 | int rproc_coredump_add_custom_segment(struct rproc *rproc, |
80 | dma_addr_t da, size_t size, |
81 | void (*dumpfn)(struct rproc *rproc, |
82 | struct rproc_dump_segment *segment, |
83 | void *dest, size_t offset, |
84 | size_t size), |
85 | void *priv) |
86 | { |
87 | struct rproc_dump_segment *segment; |
88 | |
89 | segment = kzalloc(size: sizeof(*segment), GFP_KERNEL); |
90 | if (!segment) |
91 | return -ENOMEM; |
92 | |
93 | segment->da = da; |
94 | segment->size = size; |
95 | segment->priv = priv; |
96 | segment->dump = dumpfn; |
97 | |
98 | list_add_tail(new: &segment->node, head: &rproc->dump_segments); |
99 | |
100 | return 0; |
101 | } |
102 | EXPORT_SYMBOL(rproc_coredump_add_custom_segment); |
103 | |
104 | /** |
105 | * rproc_coredump_set_elf_info() - set coredump elf information |
106 | * @rproc: handle of a remote processor |
107 | * @class: elf class for coredump elf file |
108 | * @machine: elf machine for coredump elf file |
109 | * |
110 | * Set elf information which will be used for coredump elf file. |
111 | * |
112 | * Return: 0 on success, negative errno on error. |
113 | */ |
114 | int rproc_coredump_set_elf_info(struct rproc *rproc, u8 class, u16 machine) |
115 | { |
116 | if (class != ELFCLASS64 && class != ELFCLASS32) |
117 | return -EINVAL; |
118 | |
119 | rproc->elf_class = class; |
120 | rproc->elf_machine = machine; |
121 | |
122 | return 0; |
123 | } |
124 | EXPORT_SYMBOL(rproc_coredump_set_elf_info); |
125 | |
126 | static void rproc_coredump_free(void *data) |
127 | { |
128 | struct rproc_coredump_state *dump_state = data; |
129 | |
130 | vfree(addr: dump_state->header); |
131 | complete(&dump_state->dump_done); |
132 | } |
133 | |
134 | static void *rproc_coredump_find_segment(loff_t user_offset, |
135 | struct list_head *segments, |
136 | size_t *data_left) |
137 | { |
138 | struct rproc_dump_segment *segment; |
139 | |
140 | list_for_each_entry(segment, segments, node) { |
141 | if (user_offset < segment->size) { |
142 | *data_left = segment->size - user_offset; |
143 | return segment; |
144 | } |
145 | user_offset -= segment->size; |
146 | } |
147 | |
148 | *data_left = 0; |
149 | return NULL; |
150 | } |
151 | |
152 | static void rproc_copy_segment(struct rproc *rproc, void *dest, |
153 | struct rproc_dump_segment *segment, |
154 | size_t offset, size_t size) |
155 | { |
156 | bool is_iomem = false; |
157 | void *ptr; |
158 | |
159 | if (segment->dump) { |
160 | segment->dump(rproc, segment, dest, offset, size); |
161 | } else { |
162 | ptr = rproc_da_to_va(rproc, da: segment->da + offset, len: size, is_iomem: &is_iomem); |
163 | if (!ptr) { |
164 | dev_err(&rproc->dev, |
165 | "invalid copy request for segment %pad with offset %zu and size %zu)\n" , |
166 | &segment->da, offset, size); |
167 | memset(dest, 0xff, size); |
168 | } else { |
169 | if (is_iomem) |
170 | memcpy_fromio(dest, (void const __iomem *)ptr, size); |
171 | else |
172 | memcpy(dest, ptr, size); |
173 | } |
174 | } |
175 | } |
176 | |
177 | static ssize_t rproc_coredump_read(char *buffer, loff_t offset, size_t count, |
178 | void *data, size_t ) |
179 | { |
180 | size_t seg_data, bytes_left = count; |
181 | ssize_t copy_sz; |
182 | struct rproc_dump_segment *seg; |
183 | struct rproc_coredump_state *dump_state = data; |
184 | struct rproc *rproc = dump_state->rproc; |
185 | void *elfcore = dump_state->header; |
186 | |
187 | /* Copy the vmalloc'ed header first. */ |
188 | if (offset < header_sz) { |
189 | copy_sz = memory_read_from_buffer(to: buffer, count, ppos: &offset, |
190 | from: elfcore, available: header_sz); |
191 | |
192 | return copy_sz; |
193 | } |
194 | |
195 | /* |
196 | * Find out the segment memory chunk to be copied based on offset. |
197 | * Keep copying data until count bytes are read. |
198 | */ |
199 | while (bytes_left) { |
200 | seg = rproc_coredump_find_segment(user_offset: offset - header_sz, |
201 | segments: &rproc->dump_segments, |
202 | data_left: &seg_data); |
203 | /* EOF check */ |
204 | if (!seg) { |
205 | dev_info(&rproc->dev, "Ramdump done, %lld bytes read" , |
206 | offset); |
207 | break; |
208 | } |
209 | |
210 | copy_sz = min_t(size_t, bytes_left, seg_data); |
211 | |
212 | rproc_copy_segment(rproc, dest: buffer, segment: seg, offset: seg->size - seg_data, |
213 | size: copy_sz); |
214 | |
215 | offset += copy_sz; |
216 | buffer += copy_sz; |
217 | bytes_left -= copy_sz; |
218 | } |
219 | |
220 | return count - bytes_left; |
221 | } |
222 | |
223 | /** |
224 | * rproc_coredump() - perform coredump |
225 | * @rproc: rproc handle |
226 | * |
227 | * This function will generate an ELF header for the registered segments |
228 | * and create a devcoredump device associated with rproc. Based on the |
229 | * coredump configuration this function will directly copy the segments |
230 | * from device memory to userspace or copy segments from device memory to |
231 | * a separate buffer, which can then be read by userspace. |
232 | * The first approach avoids using extra vmalloc memory. But it will stall |
233 | * recovery flow until dump is read by userspace. |
234 | */ |
235 | void rproc_coredump(struct rproc *rproc) |
236 | { |
237 | struct rproc_dump_segment *segment; |
238 | void *phdr; |
239 | void *ehdr; |
240 | size_t data_size; |
241 | size_t offset; |
242 | void *data; |
243 | u8 class = rproc->elf_class; |
244 | int phnum = 0; |
245 | struct rproc_coredump_state dump_state; |
246 | enum rproc_dump_mechanism dump_conf = rproc->dump_conf; |
247 | |
248 | if (list_empty(head: &rproc->dump_segments) || |
249 | dump_conf == RPROC_COREDUMP_DISABLED) |
250 | return; |
251 | |
252 | if (class == ELFCLASSNONE) { |
253 | dev_err(&rproc->dev, "ELF class is not set\n" ); |
254 | return; |
255 | } |
256 | |
257 | data_size = elf_size_of_hdr(class); |
258 | list_for_each_entry(segment, &rproc->dump_segments, node) { |
259 | /* |
260 | * For default configuration buffer includes headers & segments. |
261 | * For inline dump buffer just includes headers as segments are |
262 | * directly read from device memory. |
263 | */ |
264 | data_size += elf_size_of_phdr(class); |
265 | if (dump_conf == RPROC_COREDUMP_ENABLED) |
266 | data_size += segment->size; |
267 | |
268 | phnum++; |
269 | } |
270 | |
271 | data = vmalloc(size: data_size); |
272 | if (!data) |
273 | return; |
274 | |
275 | ehdr = data; |
276 | |
277 | memset(ehdr, 0, elf_size_of_hdr(class)); |
278 | /* e_ident field is common for both elf32 and elf64 */ |
279 | elf_hdr_init_ident(hdr: ehdr, class); |
280 | |
281 | elf_hdr_set_e_type(class, arg: ehdr, ET_CORE); |
282 | elf_hdr_set_e_machine(class, arg: ehdr, value: rproc->elf_machine); |
283 | elf_hdr_set_e_version(class, arg: ehdr, EV_CURRENT); |
284 | elf_hdr_set_e_entry(class, arg: ehdr, value: rproc->bootaddr); |
285 | elf_hdr_set_e_phoff(class, arg: ehdr, value: elf_size_of_hdr(class)); |
286 | elf_hdr_set_e_ehsize(class, arg: ehdr, value: elf_size_of_hdr(class)); |
287 | elf_hdr_set_e_phentsize(class, arg: ehdr, value: elf_size_of_phdr(class)); |
288 | elf_hdr_set_e_phnum(class, arg: ehdr, value: phnum); |
289 | |
290 | phdr = data + elf_hdr_get_e_phoff(class, arg: ehdr); |
291 | offset = elf_hdr_get_e_phoff(class, arg: ehdr); |
292 | offset += elf_size_of_phdr(class) * elf_hdr_get_e_phnum(class, arg: ehdr); |
293 | |
294 | list_for_each_entry(segment, &rproc->dump_segments, node) { |
295 | memset(phdr, 0, elf_size_of_phdr(class)); |
296 | elf_phdr_set_p_type(class, arg: phdr, PT_LOAD); |
297 | elf_phdr_set_p_offset(class, arg: phdr, value: offset); |
298 | elf_phdr_set_p_vaddr(class, arg: phdr, value: segment->da); |
299 | elf_phdr_set_p_paddr(class, arg: phdr, value: segment->da); |
300 | elf_phdr_set_p_filesz(class, arg: phdr, value: segment->size); |
301 | elf_phdr_set_p_memsz(class, arg: phdr, value: segment->size); |
302 | elf_phdr_set_p_flags(class, arg: phdr, PF_R | PF_W | PF_X); |
303 | elf_phdr_set_p_align(class, arg: phdr, value: 0); |
304 | |
305 | if (dump_conf == RPROC_COREDUMP_ENABLED) |
306 | rproc_copy_segment(rproc, dest: data + offset, segment, offset: 0, |
307 | size: segment->size); |
308 | |
309 | offset += elf_phdr_get_p_filesz(class, arg: phdr); |
310 | phdr += elf_size_of_phdr(class); |
311 | } |
312 | if (dump_conf == RPROC_COREDUMP_ENABLED) { |
313 | dev_coredumpv(dev: &rproc->dev, data, datalen: data_size, GFP_KERNEL); |
314 | return; |
315 | } |
316 | |
317 | /* Initialize the dump state struct to be used by rproc_coredump_read */ |
318 | dump_state.rproc = rproc; |
319 | dump_state.header = data; |
320 | init_completion(x: &dump_state.dump_done); |
321 | |
322 | dev_coredumpm(dev: &rproc->dev, NULL, data: &dump_state, datalen: data_size, GFP_KERNEL, |
323 | read: rproc_coredump_read, free: rproc_coredump_free); |
324 | |
325 | /* |
326 | * Wait until the dump is read and free is called. Data is freed |
327 | * by devcoredump framework automatically after 5 minutes. |
328 | */ |
329 | wait_for_completion(&dump_state.dump_done); |
330 | } |
331 | EXPORT_SYMBOL_GPL(rproc_coredump); |
332 | |
333 | /** |
334 | * rproc_coredump_using_sections() - perform coredump using section headers |
335 | * @rproc: rproc handle |
336 | * |
337 | * This function will generate an ELF header for the registered sections of |
338 | * segments and create a devcoredump device associated with rproc. Based on |
339 | * the coredump configuration this function will directly copy the segments |
340 | * from device memory to userspace or copy segments from device memory to |
341 | * a separate buffer, which can then be read by userspace. |
342 | * The first approach avoids using extra vmalloc memory. But it will stall |
343 | * recovery flow until dump is read by userspace. |
344 | */ |
345 | void rproc_coredump_using_sections(struct rproc *rproc) |
346 | { |
347 | struct rproc_dump_segment *segment; |
348 | void *shdr; |
349 | void *ehdr; |
350 | size_t data_size; |
351 | size_t strtbl_size = 0; |
352 | size_t strtbl_index = 1; |
353 | size_t offset; |
354 | void *data; |
355 | u8 class = rproc->elf_class; |
356 | int shnum; |
357 | struct rproc_coredump_state dump_state; |
358 | unsigned int dump_conf = rproc->dump_conf; |
359 | char *str_tbl = "STR_TBL" ; |
360 | |
361 | if (list_empty(head: &rproc->dump_segments) || |
362 | dump_conf == RPROC_COREDUMP_DISABLED) |
363 | return; |
364 | |
365 | if (class == ELFCLASSNONE) { |
366 | dev_err(&rproc->dev, "ELF class is not set\n" ); |
367 | return; |
368 | } |
369 | |
370 | /* |
371 | * We allocate two extra section headers. The first one is null. |
372 | * Second section header is for the string table. Also space is |
373 | * allocated for string table. |
374 | */ |
375 | data_size = elf_size_of_hdr(class) + 2 * elf_size_of_shdr(class); |
376 | shnum = 2; |
377 | |
378 | /* the extra byte is for the null character at index 0 */ |
379 | strtbl_size += strlen(str_tbl) + 2; |
380 | |
381 | list_for_each_entry(segment, &rproc->dump_segments, node) { |
382 | data_size += elf_size_of_shdr(class); |
383 | strtbl_size += strlen(segment->priv) + 1; |
384 | if (dump_conf == RPROC_COREDUMP_ENABLED) |
385 | data_size += segment->size; |
386 | shnum++; |
387 | } |
388 | |
389 | data_size += strtbl_size; |
390 | |
391 | data = vmalloc(size: data_size); |
392 | if (!data) |
393 | return; |
394 | |
395 | ehdr = data; |
396 | memset(ehdr, 0, elf_size_of_hdr(class)); |
397 | /* e_ident field is common for both elf32 and elf64 */ |
398 | elf_hdr_init_ident(hdr: ehdr, class); |
399 | |
400 | elf_hdr_set_e_type(class, arg: ehdr, ET_CORE); |
401 | elf_hdr_set_e_machine(class, arg: ehdr, value: rproc->elf_machine); |
402 | elf_hdr_set_e_version(class, arg: ehdr, EV_CURRENT); |
403 | elf_hdr_set_e_entry(class, arg: ehdr, value: rproc->bootaddr); |
404 | elf_hdr_set_e_shoff(class, arg: ehdr, value: elf_size_of_hdr(class)); |
405 | elf_hdr_set_e_ehsize(class, arg: ehdr, value: elf_size_of_hdr(class)); |
406 | elf_hdr_set_e_shentsize(class, arg: ehdr, value: elf_size_of_shdr(class)); |
407 | elf_hdr_set_e_shnum(class, arg: ehdr, value: shnum); |
408 | elf_hdr_set_e_shstrndx(class, arg: ehdr, value: 1); |
409 | |
410 | /* |
411 | * The zeroth index of the section header is reserved and is rarely used. |
412 | * Set the section header as null (SHN_UNDEF) and move to the next one. |
413 | */ |
414 | shdr = data + elf_hdr_get_e_shoff(class, arg: ehdr); |
415 | memset(shdr, 0, elf_size_of_shdr(class)); |
416 | shdr += elf_size_of_shdr(class); |
417 | |
418 | /* Initialize the string table. */ |
419 | offset = elf_hdr_get_e_shoff(class, arg: ehdr) + |
420 | elf_size_of_shdr(class) * elf_hdr_get_e_shnum(class, arg: ehdr); |
421 | memset(data + offset, 0, strtbl_size); |
422 | |
423 | /* Fill in the string table section header. */ |
424 | memset(shdr, 0, elf_size_of_shdr(class)); |
425 | elf_shdr_set_sh_type(class, arg: shdr, SHT_STRTAB); |
426 | elf_shdr_set_sh_offset(class, arg: shdr, value: offset); |
427 | elf_shdr_set_sh_size(class, arg: shdr, value: strtbl_size); |
428 | elf_shdr_set_sh_entsize(class, arg: shdr, value: 0); |
429 | elf_shdr_set_sh_flags(class, arg: shdr, value: 0); |
430 | elf_shdr_set_sh_name(class, arg: shdr, value: elf_strtbl_add(name: str_tbl, ehdr, class, index: &strtbl_index)); |
431 | offset += elf_shdr_get_sh_size(class, arg: shdr); |
432 | shdr += elf_size_of_shdr(class); |
433 | |
434 | list_for_each_entry(segment, &rproc->dump_segments, node) { |
435 | memset(shdr, 0, elf_size_of_shdr(class)); |
436 | elf_shdr_set_sh_type(class, arg: shdr, SHT_PROGBITS); |
437 | elf_shdr_set_sh_offset(class, arg: shdr, value: offset); |
438 | elf_shdr_set_sh_addr(class, arg: shdr, value: segment->da); |
439 | elf_shdr_set_sh_size(class, arg: shdr, value: segment->size); |
440 | elf_shdr_set_sh_entsize(class, arg: shdr, value: 0); |
441 | elf_shdr_set_sh_flags(class, arg: shdr, SHF_WRITE); |
442 | elf_shdr_set_sh_name(class, arg: shdr, |
443 | value: elf_strtbl_add(name: segment->priv, ehdr, class, index: &strtbl_index)); |
444 | |
445 | /* No need to copy segments for inline dumps */ |
446 | if (dump_conf == RPROC_COREDUMP_ENABLED) |
447 | rproc_copy_segment(rproc, dest: data + offset, segment, offset: 0, |
448 | size: segment->size); |
449 | offset += elf_shdr_get_sh_size(class, arg: shdr); |
450 | shdr += elf_size_of_shdr(class); |
451 | } |
452 | |
453 | if (dump_conf == RPROC_COREDUMP_ENABLED) { |
454 | dev_coredumpv(dev: &rproc->dev, data, datalen: data_size, GFP_KERNEL); |
455 | return; |
456 | } |
457 | |
458 | /* Initialize the dump state struct to be used by rproc_coredump_read */ |
459 | dump_state.rproc = rproc; |
460 | dump_state.header = data; |
461 | init_completion(x: &dump_state.dump_done); |
462 | |
463 | dev_coredumpm(dev: &rproc->dev, NULL, data: &dump_state, datalen: data_size, GFP_KERNEL, |
464 | read: rproc_coredump_read, free: rproc_coredump_free); |
465 | |
466 | /* Wait until the dump is read and free is called. Data is freed |
467 | * by devcoredump framework automatically after 5 minutes. |
468 | */ |
469 | wait_for_completion(&dump_state.dump_done); |
470 | } |
471 | EXPORT_SYMBOL(rproc_coredump_using_sections); |
472 | |