Commit | Line | Data |
---|---|---|
2c010cc3 RB |
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 | ||
c9731988 | 8 | #include <linux/completion.h> |
2c010cc3 RB |
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 | ||
c9731988 RB |
16 | struct rproc_coredump_state { |
17 | struct rproc *rproc; | |
18 | void *header; | |
19 | struct completion dump_done; | |
20 | }; | |
21 | ||
2c010cc3 RB |
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->node); | |
32 | kfree(entry); | |
33 | } | |
34 | } | |
f247f08d | 35 | EXPORT_SYMBOL_GPL(rproc_coredump_cleanup); |
2c010cc3 RB |
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(sizeof(*segment), GFP_KERNEL); | |
53 | if (!segment) | |
54 | return -ENOMEM; | |
55 | ||
56 | segment->da = da; | |
57 | segment->size = size; | |
58 | ||
59 | list_add_tail(&segment->node, &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, | |
76abf9ce RB |
83 | void *dest, size_t offset, |
84 | size_t size), | |
2c010cc3 RB |
85 | void *priv) |
86 | { | |
87 | struct rproc_dump_segment *segment; | |
88 | ||
89 | segment = kzalloc(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(&segment->node, &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 | ||
c9731988 RB |
126 | static void rproc_coredump_free(void *data) |
127 | { | |
128 | struct rproc_coredump_state *dump_state = data; | |
129 | ||
130 | vfree(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 | { | |
970675f6 | 156 | bool is_iomem = false; |
c9731988 RB |
157 | void *ptr; |
158 | ||
159 | if (segment->dump) { | |
160 | segment->dump(rproc, segment, dest, offset, size); | |
161 | } else { | |
40df0a91 | 162 | ptr = rproc_da_to_va(rproc, segment->da + offset, size, &is_iomem); |
c9731988 RB |
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 { | |
40df0a91 | 169 | if (is_iomem) |
876e0b26 | 170 | memcpy_fromio(dest, (void const __iomem *)ptr, size); |
40df0a91 PF |
171 | else |
172 | memcpy(dest, ptr, size); | |
c9731988 RB |
173 | } |
174 | } | |
175 | } | |
176 | ||
177 | static ssize_t rproc_coredump_read(char *buffer, loff_t offset, size_t count, | |
178 | void *data, size_t header_sz) | |
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(buffer, count, &offset, | |
190 | elfcore, 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(offset - header_sz, | |
201 | &rproc->dump_segments, | |
202 | &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, buffer, seg, seg->size - seg_data, | |
213 | 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 | ||
2c010cc3 RB |
223 | /** |
224 | * rproc_coredump() - perform coredump | |
225 | * @rproc: rproc handle | |
226 | * | |
227 | * This function will generate an ELF header for the registered segments | |
c9731988 RB |
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. | |
2c010cc3 RB |
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; | |
2c010cc3 RB |
243 | u8 class = rproc->elf_class; |
244 | int phnum = 0; | |
c9731988 RB |
245 | struct rproc_coredump_state dump_state; |
246 | enum rproc_dump_mechanism dump_conf = rproc->dump_conf; | |
2c010cc3 | 247 | |
c9731988 RB |
248 | if (list_empty(&rproc->dump_segments) || |
249 | dump_conf == RPROC_COREDUMP_DISABLED) | |
2c010cc3 RB |
250 | return; |
251 | ||
252 | if (class == ELFCLASSNONE) { | |
70e79866 | 253 | dev_err(&rproc->dev, "ELF class is not set\n"); |
2c010cc3 RB |
254 | return; |
255 | } | |
256 | ||
257 | data_size = elf_size_of_hdr(class); | |
258 | list_for_each_entry(segment, &rproc->dump_segments, node) { | |
c9731988 RB |
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); | |
bf41a091 | 265 | if (dump_conf == RPROC_COREDUMP_ENABLED) |
c9731988 | 266 | data_size += segment->size; |
2c010cc3 RB |
267 | |
268 | phnum++; | |
269 | } | |
270 | ||
271 | data = vmalloc(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(ehdr, class); | |
280 | ||
281 | elf_hdr_set_e_type(class, ehdr, ET_CORE); | |
282 | elf_hdr_set_e_machine(class, ehdr, rproc->elf_machine); | |
283 | elf_hdr_set_e_version(class, ehdr, EV_CURRENT); | |
284 | elf_hdr_set_e_entry(class, ehdr, rproc->bootaddr); | |
285 | elf_hdr_set_e_phoff(class, ehdr, elf_size_of_hdr(class)); | |
286 | elf_hdr_set_e_ehsize(class, ehdr, elf_size_of_hdr(class)); | |
287 | elf_hdr_set_e_phentsize(class, ehdr, elf_size_of_phdr(class)); | |
288 | elf_hdr_set_e_phnum(class, ehdr, phnum); | |
289 | ||
290 | phdr = data + elf_hdr_get_e_phoff(class, ehdr); | |
291 | offset = elf_hdr_get_e_phoff(class, ehdr); | |
292 | offset += elf_size_of_phdr(class) * elf_hdr_get_e_phnum(class, 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, phdr, PT_LOAD); | |
297 | elf_phdr_set_p_offset(class, phdr, offset); | |
298 | elf_phdr_set_p_vaddr(class, phdr, segment->da); | |
299 | elf_phdr_set_p_paddr(class, phdr, segment->da); | |
300 | elf_phdr_set_p_filesz(class, phdr, segment->size); | |
301 | elf_phdr_set_p_memsz(class, phdr, segment->size); | |
302 | elf_phdr_set_p_flags(class, phdr, PF_R | PF_W | PF_X); | |
303 | elf_phdr_set_p_align(class, phdr, 0); | |
304 | ||
bf41a091 | 305 | if (dump_conf == RPROC_COREDUMP_ENABLED) |
c9731988 RB |
306 | rproc_copy_segment(rproc, data + offset, segment, 0, |
307 | segment->size); | |
2c010cc3 RB |
308 | |
309 | offset += elf_phdr_get_p_filesz(class, phdr); | |
310 | phdr += elf_size_of_phdr(class); | |
311 | } | |
bf41a091 | 312 | if (dump_conf == RPROC_COREDUMP_ENABLED) { |
c9731988 RB |
313 | dev_coredumpv(&rproc->dev, data, 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(&dump_state.dump_done); | |
321 | ||
322 | dev_coredumpm(&rproc->dev, NULL, &dump_state, data_size, GFP_KERNEL, | |
323 | rproc_coredump_read, rproc_coredump_free); | |
2c010cc3 | 324 | |
c9731988 RB |
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); | |
2c010cc3 | 330 | } |
f247f08d | 331 | EXPORT_SYMBOL_GPL(rproc_coredump); |
abc72b64 SG |
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(&rproc->dump_segments) || | |
362 | dump_conf == RPROC_COREDUMP_DISABLED) | |
363 | return; | |
364 | ||
365 | if (class == ELFCLASSNONE) { | |
70e79866 | 366 | dev_err(&rproc->dev, "ELF class is not set\n"); |
abc72b64 SG |
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(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(ehdr, class); | |
399 | ||
400 | elf_hdr_set_e_type(class, ehdr, ET_CORE); | |
401 | elf_hdr_set_e_machine(class, ehdr, rproc->elf_machine); | |
402 | elf_hdr_set_e_version(class, ehdr, EV_CURRENT); | |
403 | elf_hdr_set_e_entry(class, ehdr, rproc->bootaddr); | |
404 | elf_hdr_set_e_shoff(class, ehdr, elf_size_of_hdr(class)); | |
405 | elf_hdr_set_e_ehsize(class, ehdr, elf_size_of_hdr(class)); | |
406 | elf_hdr_set_e_shentsize(class, ehdr, elf_size_of_shdr(class)); | |
407 | elf_hdr_set_e_shnum(class, ehdr, shnum); | |
408 | elf_hdr_set_e_shstrndx(class, ehdr, 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, 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, ehdr) + | |
420 | elf_size_of_shdr(class) * elf_hdr_get_e_shnum(class, 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, shdr, SHT_STRTAB); | |
426 | elf_shdr_set_sh_offset(class, shdr, offset); | |
427 | elf_shdr_set_sh_size(class, shdr, strtbl_size); | |
428 | elf_shdr_set_sh_entsize(class, shdr, 0); | |
429 | elf_shdr_set_sh_flags(class, shdr, 0); | |
430 | elf_shdr_set_sh_name(class, shdr, elf_strtbl_add(str_tbl, ehdr, class, &strtbl_index)); | |
431 | offset += elf_shdr_get_sh_size(class, 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, shdr, SHT_PROGBITS); | |
437 | elf_shdr_set_sh_offset(class, shdr, offset); | |
438 | elf_shdr_set_sh_addr(class, shdr, segment->da); | |
439 | elf_shdr_set_sh_size(class, shdr, segment->size); | |
440 | elf_shdr_set_sh_entsize(class, shdr, 0); | |
441 | elf_shdr_set_sh_flags(class, shdr, SHF_WRITE); | |
442 | elf_shdr_set_sh_name(class, shdr, | |
443 | elf_strtbl_add(segment->priv, ehdr, class, &strtbl_index)); | |
444 | ||
445 | /* No need to copy segments for inline dumps */ | |
446 | if (dump_conf == RPROC_COREDUMP_ENABLED) | |
447 | rproc_copy_segment(rproc, data + offset, segment, 0, | |
448 | segment->size); | |
449 | offset += elf_shdr_get_sh_size(class, shdr); | |
450 | shdr += elf_size_of_shdr(class); | |
451 | } | |
452 | ||
453 | if (dump_conf == RPROC_COREDUMP_ENABLED) { | |
454 | dev_coredumpv(&rproc->dev, data, 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(&dump_state.dump_done); | |
462 | ||
463 | dev_coredumpm(&rproc->dev, NULL, &dump_state, data_size, GFP_KERNEL, | |
464 | rproc_coredump_read, 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); |