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 | } | |
35 | ||
36 | /** | |
37 | * rproc_coredump_add_segment() - add segment of device memory to coredump | |
38 | * @rproc: handle of a remote processor | |
39 | * @da: device address | |
40 | * @size: size of segment | |
41 | * | |
42 | * Add device memory to the list of segments to be included in a coredump for | |
43 | * the remoteproc. | |
44 | * | |
45 | * Return: 0 on success, negative errno on error. | |
46 | */ | |
47 | int rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size) | |
48 | { | |
49 | struct rproc_dump_segment *segment; | |
50 | ||
51 | segment = kzalloc(sizeof(*segment), GFP_KERNEL); | |
52 | if (!segment) | |
53 | return -ENOMEM; | |
54 | ||
55 | segment->da = da; | |
56 | segment->size = size; | |
57 | ||
58 | list_add_tail(&segment->node, &rproc->dump_segments); | |
59 | ||
60 | return 0; | |
61 | } | |
62 | EXPORT_SYMBOL(rproc_coredump_add_segment); | |
63 | ||
64 | /** | |
65 | * rproc_coredump_add_custom_segment() - add custom coredump segment | |
66 | * @rproc: handle of a remote processor | |
67 | * @da: device address | |
68 | * @size: size of segment | |
69 | * @dumpfn: custom dump function called for each segment during coredump | |
70 | * @priv: private data | |
71 | * | |
72 | * Add device memory to the list of segments to be included in the coredump | |
73 | * and associate the segment with the given custom dump function and private | |
74 | * data. | |
75 | * | |
76 | * Return: 0 on success, negative errno on error. | |
77 | */ | |
78 | int rproc_coredump_add_custom_segment(struct rproc *rproc, | |
79 | dma_addr_t da, size_t size, | |
80 | void (*dumpfn)(struct rproc *rproc, | |
81 | struct rproc_dump_segment *segment, | |
76abf9ce RB |
82 | void *dest, size_t offset, |
83 | size_t size), | |
2c010cc3 RB |
84 | void *priv) |
85 | { | |
86 | struct rproc_dump_segment *segment; | |
87 | ||
88 | segment = kzalloc(sizeof(*segment), GFP_KERNEL); | |
89 | if (!segment) | |
90 | return -ENOMEM; | |
91 | ||
92 | segment->da = da; | |
93 | segment->size = size; | |
94 | segment->priv = priv; | |
95 | segment->dump = dumpfn; | |
96 | ||
97 | list_add_tail(&segment->node, &rproc->dump_segments); | |
98 | ||
99 | return 0; | |
100 | } | |
101 | EXPORT_SYMBOL(rproc_coredump_add_custom_segment); | |
102 | ||
103 | /** | |
104 | * rproc_coredump_set_elf_info() - set coredump elf information | |
105 | * @rproc: handle of a remote processor | |
106 | * @class: elf class for coredump elf file | |
107 | * @machine: elf machine for coredump elf file | |
108 | * | |
109 | * Set elf information which will be used for coredump elf file. | |
110 | * | |
111 | * Return: 0 on success, negative errno on error. | |
112 | */ | |
113 | int rproc_coredump_set_elf_info(struct rproc *rproc, u8 class, u16 machine) | |
114 | { | |
115 | if (class != ELFCLASS64 && class != ELFCLASS32) | |
116 | return -EINVAL; | |
117 | ||
118 | rproc->elf_class = class; | |
119 | rproc->elf_machine = machine; | |
120 | ||
121 | return 0; | |
122 | } | |
123 | EXPORT_SYMBOL(rproc_coredump_set_elf_info); | |
124 | ||
c9731988 RB |
125 | static void rproc_coredump_free(void *data) |
126 | { | |
127 | struct rproc_coredump_state *dump_state = data; | |
128 | ||
129 | vfree(dump_state->header); | |
130 | complete(&dump_state->dump_done); | |
131 | } | |
132 | ||
133 | static void *rproc_coredump_find_segment(loff_t user_offset, | |
134 | struct list_head *segments, | |
135 | size_t *data_left) | |
136 | { | |
137 | struct rproc_dump_segment *segment; | |
138 | ||
139 | list_for_each_entry(segment, segments, node) { | |
140 | if (user_offset < segment->size) { | |
141 | *data_left = segment->size - user_offset; | |
142 | return segment; | |
143 | } | |
144 | user_offset -= segment->size; | |
145 | } | |
146 | ||
147 | *data_left = 0; | |
148 | return NULL; | |
149 | } | |
150 | ||
151 | static void rproc_copy_segment(struct rproc *rproc, void *dest, | |
152 | struct rproc_dump_segment *segment, | |
153 | size_t offset, size_t size) | |
154 | { | |
155 | void *ptr; | |
156 | ||
157 | if (segment->dump) { | |
158 | segment->dump(rproc, segment, dest, offset, size); | |
159 | } else { | |
160 | ptr = rproc_da_to_va(rproc, segment->da + offset, size); | |
161 | if (!ptr) { | |
162 | dev_err(&rproc->dev, | |
163 | "invalid copy request for segment %pad with offset %zu and size %zu)\n", | |
164 | &segment->da, offset, size); | |
165 | memset(dest, 0xff, size); | |
166 | } else { | |
167 | memcpy(dest, ptr, size); | |
168 | } | |
169 | } | |
170 | } | |
171 | ||
172 | static ssize_t rproc_coredump_read(char *buffer, loff_t offset, size_t count, | |
173 | void *data, size_t header_sz) | |
174 | { | |
175 | size_t seg_data, bytes_left = count; | |
176 | ssize_t copy_sz; | |
177 | struct rproc_dump_segment *seg; | |
178 | struct rproc_coredump_state *dump_state = data; | |
179 | struct rproc *rproc = dump_state->rproc; | |
180 | void *elfcore = dump_state->header; | |
181 | ||
182 | /* Copy the vmalloc'ed header first. */ | |
183 | if (offset < header_sz) { | |
184 | copy_sz = memory_read_from_buffer(buffer, count, &offset, | |
185 | elfcore, header_sz); | |
186 | ||
187 | return copy_sz; | |
188 | } | |
189 | ||
190 | /* | |
191 | * Find out the segment memory chunk to be copied based on offset. | |
192 | * Keep copying data until count bytes are read. | |
193 | */ | |
194 | while (bytes_left) { | |
195 | seg = rproc_coredump_find_segment(offset - header_sz, | |
196 | &rproc->dump_segments, | |
197 | &seg_data); | |
198 | /* EOF check */ | |
199 | if (!seg) { | |
200 | dev_info(&rproc->dev, "Ramdump done, %lld bytes read", | |
201 | offset); | |
202 | break; | |
203 | } | |
204 | ||
205 | copy_sz = min_t(size_t, bytes_left, seg_data); | |
206 | ||
207 | rproc_copy_segment(rproc, buffer, seg, seg->size - seg_data, | |
208 | copy_sz); | |
209 | ||
210 | offset += copy_sz; | |
211 | buffer += copy_sz; | |
212 | bytes_left -= copy_sz; | |
213 | } | |
214 | ||
215 | return count - bytes_left; | |
216 | } | |
217 | ||
2c010cc3 RB |
218 | /** |
219 | * rproc_coredump() - perform coredump | |
220 | * @rproc: rproc handle | |
221 | * | |
222 | * This function will generate an ELF header for the registered segments | |
c9731988 RB |
223 | * and create a devcoredump device associated with rproc. Based on the |
224 | * coredump configuration this function will directly copy the segments | |
225 | * from device memory to userspace or copy segments from device memory to | |
226 | * a separate buffer, which can then be read by userspace. | |
227 | * The first approach avoids using extra vmalloc memory. But it will stall | |
228 | * recovery flow until dump is read by userspace. | |
2c010cc3 RB |
229 | */ |
230 | void rproc_coredump(struct rproc *rproc) | |
231 | { | |
232 | struct rproc_dump_segment *segment; | |
233 | void *phdr; | |
234 | void *ehdr; | |
235 | size_t data_size; | |
236 | size_t offset; | |
237 | void *data; | |
2c010cc3 RB |
238 | u8 class = rproc->elf_class; |
239 | int phnum = 0; | |
c9731988 RB |
240 | struct rproc_coredump_state dump_state; |
241 | enum rproc_dump_mechanism dump_conf = rproc->dump_conf; | |
2c010cc3 | 242 | |
c9731988 RB |
243 | if (list_empty(&rproc->dump_segments) || |
244 | dump_conf == RPROC_COREDUMP_DISABLED) | |
2c010cc3 RB |
245 | return; |
246 | ||
247 | if (class == ELFCLASSNONE) { | |
248 | dev_err(&rproc->dev, "Elf class is not set\n"); | |
249 | return; | |
250 | } | |
251 | ||
252 | data_size = elf_size_of_hdr(class); | |
253 | list_for_each_entry(segment, &rproc->dump_segments, node) { | |
c9731988 RB |
254 | /* |
255 | * For default configuration buffer includes headers & segments. | |
256 | * For inline dump buffer just includes headers as segments are | |
257 | * directly read from device memory. | |
258 | */ | |
259 | data_size += elf_size_of_phdr(class); | |
260 | if (dump_conf == RPROC_COREDUMP_DEFAULT) | |
261 | data_size += segment->size; | |
2c010cc3 RB |
262 | |
263 | phnum++; | |
264 | } | |
265 | ||
266 | data = vmalloc(data_size); | |
267 | if (!data) | |
268 | return; | |
269 | ||
270 | ehdr = data; | |
271 | ||
272 | memset(ehdr, 0, elf_size_of_hdr(class)); | |
273 | /* e_ident field is common for both elf32 and elf64 */ | |
274 | elf_hdr_init_ident(ehdr, class); | |
275 | ||
276 | elf_hdr_set_e_type(class, ehdr, ET_CORE); | |
277 | elf_hdr_set_e_machine(class, ehdr, rproc->elf_machine); | |
278 | elf_hdr_set_e_version(class, ehdr, EV_CURRENT); | |
279 | elf_hdr_set_e_entry(class, ehdr, rproc->bootaddr); | |
280 | elf_hdr_set_e_phoff(class, ehdr, elf_size_of_hdr(class)); | |
281 | elf_hdr_set_e_ehsize(class, ehdr, elf_size_of_hdr(class)); | |
282 | elf_hdr_set_e_phentsize(class, ehdr, elf_size_of_phdr(class)); | |
283 | elf_hdr_set_e_phnum(class, ehdr, phnum); | |
284 | ||
285 | phdr = data + elf_hdr_get_e_phoff(class, ehdr); | |
286 | offset = elf_hdr_get_e_phoff(class, ehdr); | |
287 | offset += elf_size_of_phdr(class) * elf_hdr_get_e_phnum(class, ehdr); | |
288 | ||
289 | list_for_each_entry(segment, &rproc->dump_segments, node) { | |
290 | memset(phdr, 0, elf_size_of_phdr(class)); | |
291 | elf_phdr_set_p_type(class, phdr, PT_LOAD); | |
292 | elf_phdr_set_p_offset(class, phdr, offset); | |
293 | elf_phdr_set_p_vaddr(class, phdr, segment->da); | |
294 | elf_phdr_set_p_paddr(class, phdr, segment->da); | |
295 | elf_phdr_set_p_filesz(class, phdr, segment->size); | |
296 | elf_phdr_set_p_memsz(class, phdr, segment->size); | |
297 | elf_phdr_set_p_flags(class, phdr, PF_R | PF_W | PF_X); | |
298 | elf_phdr_set_p_align(class, phdr, 0); | |
299 | ||
c9731988 RB |
300 | if (dump_conf == RPROC_COREDUMP_DEFAULT) |
301 | rproc_copy_segment(rproc, data + offset, segment, 0, | |
302 | segment->size); | |
2c010cc3 RB |
303 | |
304 | offset += elf_phdr_get_p_filesz(class, phdr); | |
305 | phdr += elf_size_of_phdr(class); | |
306 | } | |
c9731988 RB |
307 | if (dump_conf == RPROC_COREDUMP_DEFAULT) { |
308 | dev_coredumpv(&rproc->dev, data, data_size, GFP_KERNEL); | |
309 | return; | |
310 | } | |
311 | ||
312 | /* Initialize the dump state struct to be used by rproc_coredump_read */ | |
313 | dump_state.rproc = rproc; | |
314 | dump_state.header = data; | |
315 | init_completion(&dump_state.dump_done); | |
316 | ||
317 | dev_coredumpm(&rproc->dev, NULL, &dump_state, data_size, GFP_KERNEL, | |
318 | rproc_coredump_read, rproc_coredump_free); | |
2c010cc3 | 319 | |
c9731988 RB |
320 | /* |
321 | * Wait until the dump is read and free is called. Data is freed | |
322 | * by devcoredump framework automatically after 5 minutes. | |
323 | */ | |
324 | wait_for_completion(&dump_state.dump_done); | |
2c010cc3 | 325 | } |