xref: /freebsd/sys/arm64/arm64/minidump_machdep.c (revision c697fb7f)
1 /*-
2  * Copyright (c) 2006 Peter Wemm
3  * Copyright (c) 2015 The FreeBSD Foundation
4  * All rights reserved.
5  *
6  * This software was developed by Andrew Turner under
7  * sponsorship from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 __FBSDID("$FreeBSD$");
34 
35 #include "opt_watchdog.h"
36 
37 #include "opt_watchdog.h"
38 
39 #include <sys/param.h>
40 #include <sys/systm.h>
41 #include <sys/conf.h>
42 #include <sys/cons.h>
43 #include <sys/kernel.h>
44 #include <sys/kerneldump.h>
45 #include <sys/msgbuf.h>
46 #include <sys/watchdog.h>
47 #include <sys/vmmeter.h>
48 
49 #include <vm/vm.h>
50 #include <vm/vm_param.h>
51 #include <vm/vm_page.h>
52 #include <vm/vm_phys.h>
53 #include <vm/pmap.h>
54 
55 #include <machine/md_var.h>
56 #include <machine/pte.h>
57 #include <machine/minidump.h>
58 
59 CTASSERT(sizeof(struct kerneldumpheader) == 512);
60 
61 uint64_t *vm_page_dump;
62 int vm_page_dump_size;
63 
64 static struct kerneldumpheader kdh;
65 
66 /* Handle chunked writes. */
67 static size_t fragsz;
68 static void *dump_va;
69 static size_t counter, progress, dumpsize;
70 
71 static uint64_t tmpbuffer[PAGE_SIZE / sizeof(uint64_t)];
72 
73 CTASSERT(sizeof(*vm_page_dump) == 8);
74 
75 static int
76 is_dumpable(vm_paddr_t pa)
77 {
78 	vm_page_t m;
79 	int i;
80 
81 	if ((m = vm_phys_paddr_to_vm_page(pa)) != NULL)
82 		return ((m->flags & PG_NODUMP) == 0);
83 	for (i = 0; dump_avail[i] != 0 || dump_avail[i + 1] != 0; i += 2) {
84 		if (pa >= dump_avail[i] && pa < dump_avail[i + 1])
85 			return (1);
86 	}
87 	return (0);
88 }
89 
90 static int
91 blk_flush(struct dumperinfo *di)
92 {
93 	int error;
94 
95 	if (fragsz == 0)
96 		return (0);
97 
98 	error = dump_append(di, dump_va, 0, fragsz);
99 	fragsz = 0;
100 	return (error);
101 }
102 
103 static struct {
104 	int min_per;
105 	int max_per;
106 	int visited;
107 } progress_track[10] = {
108 	{  0,  10, 0},
109 	{ 10,  20, 0},
110 	{ 20,  30, 0},
111 	{ 30,  40, 0},
112 	{ 40,  50, 0},
113 	{ 50,  60, 0},
114 	{ 60,  70, 0},
115 	{ 70,  80, 0},
116 	{ 80,  90, 0},
117 	{ 90, 100, 0}
118 };
119 
120 static void
121 report_progress(size_t progress, size_t dumpsize)
122 {
123 	int sofar, i;
124 
125 	sofar = 100 - ((progress * 100) / dumpsize);
126 	for (i = 0; i < nitems(progress_track); i++) {
127 		if (sofar < progress_track[i].min_per ||
128 		    sofar > progress_track[i].max_per)
129 			continue;
130 		if (progress_track[i].visited)
131 			return;
132 		progress_track[i].visited = 1;
133 		printf("..%d%%", sofar);
134 		return;
135 	}
136 }
137 
138 static int
139 blk_write(struct dumperinfo *di, char *ptr, vm_paddr_t pa, size_t sz)
140 {
141 	size_t len;
142 	int error, c;
143 	u_int maxdumpsz;
144 
145 	maxdumpsz = min(di->maxiosize, MAXDUMPPGS * PAGE_SIZE);
146 	if (maxdumpsz == 0)	/* seatbelt */
147 		maxdumpsz = PAGE_SIZE;
148 	error = 0;
149 	if ((sz % PAGE_SIZE) != 0) {
150 		printf("size not page aligned\n");
151 		return (EINVAL);
152 	}
153 	if (ptr != NULL && pa != 0) {
154 		printf("cant have both va and pa!\n");
155 		return (EINVAL);
156 	}
157 	if ((((uintptr_t)pa) % PAGE_SIZE) != 0) {
158 		printf("address not page aligned %p\n", ptr);
159 		return (EINVAL);
160 	}
161 	if (ptr != NULL) {
162 		/*
163 		 * If we're doing a virtual dump, flush any
164 		 * pre-existing pa pages.
165 		 */
166 		error = blk_flush(di);
167 		if (error)
168 			return (error);
169 	}
170 	while (sz) {
171 		len = maxdumpsz - fragsz;
172 		if (len > sz)
173 			len = sz;
174 		counter += len;
175 		progress -= len;
176 		if (counter >> 22) {
177 			report_progress(progress, dumpsize);
178 			counter &= (1 << 22) - 1;
179 		}
180 
181 		wdog_kern_pat(WD_LASTVAL);
182 
183 		if (ptr) {
184 			error = dump_append(di, ptr, 0, len);
185 			if (error)
186 				return (error);
187 			ptr += len;
188 			sz -= len;
189 		} else {
190 			dump_va = (void *)PHYS_TO_DMAP(pa);
191 			fragsz += len;
192 			pa += len;
193 			sz -= len;
194 			error = blk_flush(di);
195 			if (error)
196 				return (error);
197 		}
198 
199 		/* Check for user abort. */
200 		c = cncheckc();
201 		if (c == 0x03)
202 			return (ECANCELED);
203 		if (c != -1)
204 			printf(" (CTRL-C to abort) ");
205 	}
206 
207 	return (0);
208 }
209 
210 int
211 minidumpsys(struct dumperinfo *di)
212 {
213 	pd_entry_t *l0, *l1, *l2;
214 	pt_entry_t *l3;
215 	uint32_t pmapsize;
216 	vm_offset_t va;
217 	vm_paddr_t pa;
218 	int error;
219 	uint64_t bits;
220 	int i, bit;
221 	int retry_count;
222 	struct minidumphdr mdhdr;
223 
224 	retry_count = 0;
225  retry:
226 	retry_count++;
227 	error = 0;
228 	pmapsize = 0;
229 	for (va = VM_MIN_KERNEL_ADDRESS; va < kernel_vm_end; va += L2_SIZE) {
230 		pmapsize += PAGE_SIZE;
231 		if (!pmap_get_tables(pmap_kernel(), va, &l0, &l1, &l2, &l3))
232 			continue;
233 
234 		/* We should always be using the l2 table for kvm */
235 		if (l2 == NULL)
236 			continue;
237 
238 		if ((*l2 & ATTR_DESCR_MASK) == L2_BLOCK) {
239 			pa = *l2 & ~ATTR_MASK;
240 			for (i = 0; i < Ln_ENTRIES; i++, pa += PAGE_SIZE) {
241 				if (is_dumpable(pa))
242 					dump_add_page(pa);
243 			}
244 		} else if ((*l2 & ATTR_DESCR_MASK) == L2_TABLE) {
245 			for (i = 0; i < Ln_ENTRIES; i++) {
246 				if ((l3[i] & ATTR_DESCR_MASK) != L3_PAGE)
247 					continue;
248 				pa = l3[i] & ~ATTR_MASK;
249 				if (is_dumpable(pa))
250 					dump_add_page(pa);
251 			}
252 		}
253 	}
254 
255 	/* Calculate dump size. */
256 	dumpsize = pmapsize;
257 	dumpsize += round_page(msgbufp->msg_size);
258 	dumpsize += round_page(vm_page_dump_size);
259 	for (i = 0; i < vm_page_dump_size / sizeof(*vm_page_dump); i++) {
260 		bits = vm_page_dump[i];
261 		while (bits) {
262 			bit = ffsl(bits) - 1;
263 			pa = (((uint64_t)i * sizeof(*vm_page_dump) * NBBY) +
264 			    bit) * PAGE_SIZE;
265 			/* Clear out undumpable pages now if needed */
266 			if (is_dumpable(pa))
267 				dumpsize += PAGE_SIZE;
268 			else
269 				dump_drop_page(pa);
270 			bits &= ~(1ul << bit);
271 		}
272 	}
273 	dumpsize += PAGE_SIZE;
274 
275 	progress = dumpsize;
276 
277 	/* Initialize mdhdr */
278 	bzero(&mdhdr, sizeof(mdhdr));
279 	strcpy(mdhdr.magic, MINIDUMP_MAGIC);
280 	mdhdr.version = MINIDUMP_VERSION;
281 	mdhdr.msgbufsize = msgbufp->msg_size;
282 	mdhdr.bitmapsize = vm_page_dump_size;
283 	mdhdr.pmapsize = pmapsize;
284 	mdhdr.kernbase = VM_MIN_KERNEL_ADDRESS;
285 	mdhdr.dmapphys = DMAP_MIN_PHYSADDR;
286 	mdhdr.dmapbase = DMAP_MIN_ADDRESS;
287 	mdhdr.dmapend = DMAP_MAX_ADDRESS;
288 
289 	dump_init_header(di, &kdh, KERNELDUMPMAGIC, KERNELDUMP_AARCH64_VERSION,
290 	    dumpsize);
291 
292 	error = dump_start(di, &kdh);
293 	if (error != 0)
294 		goto fail;
295 
296 	printf("Dumping %llu out of %ju MB:", (long long)dumpsize >> 20,
297 	    ptoa((uintmax_t)physmem) / 1048576);
298 
299 	/* Dump my header */
300 	bzero(&tmpbuffer, sizeof(tmpbuffer));
301 	bcopy(&mdhdr, &tmpbuffer, sizeof(mdhdr));
302 	error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
303 	if (error)
304 		goto fail;
305 
306 	/* Dump msgbuf up front */
307 	error = blk_write(di, (char *)msgbufp->msg_ptr, 0,
308 	    round_page(msgbufp->msg_size));
309 	if (error)
310 		goto fail;
311 
312 	/* Dump bitmap */
313 	error = blk_write(di, (char *)vm_page_dump, 0,
314 	    round_page(vm_page_dump_size));
315 	if (error)
316 		goto fail;
317 
318 	/* Dump kernel page directory pages */
319 	bzero(&tmpbuffer, sizeof(tmpbuffer));
320 	for (va = VM_MIN_KERNEL_ADDRESS; va < kernel_vm_end; va += L2_SIZE) {
321 		if (!pmap_get_tables(pmap_kernel(), va, &l0, &l1, &l2, &l3)) {
322 			/* We always write a page, even if it is zero */
323 			error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
324 			if (error)
325 				goto fail;
326 			/* flush, in case we reuse tmpbuffer in the same block*/
327 			error = blk_flush(di);
328 			if (error)
329 				goto fail;
330 		} else if (l2 == NULL) {
331 			pa = (*l1 & ~ATTR_MASK) | (va & L1_OFFSET);
332 
333 			/* Generate fake l3 entries based upon the l1 entry */
334 			for (i = 0; i < Ln_ENTRIES; i++) {
335 				tmpbuffer[i] = pa + (i * PAGE_SIZE) |
336 				    ATTR_DEFAULT | L3_PAGE;
337 			}
338 			/* We always write a page, even if it is zero */
339 			error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
340 			if (error)
341 				goto fail;
342 			/* flush, in case we reuse tmpbuffer in the same block*/
343 			error = blk_flush(di);
344 			if (error)
345 				goto fail;
346 			bzero(&tmpbuffer, sizeof(tmpbuffer));
347 		} else if ((*l2 & ATTR_DESCR_MASK) == L2_BLOCK) {
348 			/* TODO: Handle an invalid L2 entry */
349 			pa = (*l2 & ~ATTR_MASK) | (va & L2_OFFSET);
350 
351 			/* Generate fake l3 entries based upon the l1 entry */
352 			for (i = 0; i < Ln_ENTRIES; i++) {
353 				tmpbuffer[i] = pa + (i * PAGE_SIZE) |
354 				    ATTR_DEFAULT | L3_PAGE;
355 			}
356 			/* We always write a page, even if it is zero */
357 			error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
358 			if (error)
359 				goto fail;
360 			/* flush, in case we reuse fakepd in the same block */
361 			error = blk_flush(di);
362 			if (error)
363 				goto fail;
364 			bzero(&tmpbuffer, sizeof(tmpbuffer));
365 			continue;
366 		} else {
367 			pa = *l2 & ~ATTR_MASK;
368 
369 			/* We always write a page, even if it is zero */
370 			error = blk_write(di, NULL, pa, PAGE_SIZE);
371 			if (error)
372 				goto fail;
373 		}
374 	}
375 
376 	/* Dump memory chunks */
377 	/* XXX cluster it up and use blk_dump() */
378 	for (i = 0; i < vm_page_dump_size / sizeof(*vm_page_dump); i++) {
379 		bits = vm_page_dump[i];
380 		while (bits) {
381 			bit = ffsl(bits) - 1;
382 			pa = (((uint64_t)i * sizeof(*vm_page_dump) * NBBY) +
383 			    bit) * PAGE_SIZE;
384 			error = blk_write(di, 0, pa, PAGE_SIZE);
385 			if (error)
386 				goto fail;
387 			bits &= ~(1ul << bit);
388 		}
389 	}
390 
391 	error = blk_flush(di);
392 	if (error)
393 		goto fail;
394 
395 	error = dump_finish(di, &kdh);
396 	if (error != 0)
397 		goto fail;
398 
399 	printf("\nDump complete\n");
400 	return (0);
401 
402 fail:
403 	if (error < 0)
404 		error = -error;
405 
406 	printf("\n");
407 	if (error == ENOSPC) {
408 		printf("Dump map grown while dumping. ");
409 		if (retry_count < 5) {
410 			printf("Retrying...\n");
411 			goto retry;
412 		}
413 		printf("Dump failed.\n");
414 	}
415 	else if (error == ECANCELED)
416 		printf("Dump aborted\n");
417 	else if (error == E2BIG) {
418 		printf("Dump failed. Partition too small (about %lluMB were "
419 		    "needed this time).\n", (long long)dumpsize >> 20);
420 	} else
421 		printf("** DUMP FAILED (ERROR %d) **\n", error);
422 	return (error);
423 }
424 
425 void
426 dump_add_page(vm_paddr_t pa)
427 {
428 	int idx, bit;
429 
430 	pa >>= PAGE_SHIFT;
431 	idx = pa >> 6;		/* 2^6 = 64 */
432 	bit = pa & 63;
433 	atomic_set_long(&vm_page_dump[idx], 1ul << bit);
434 }
435 
436 void
437 dump_drop_page(vm_paddr_t pa)
438 {
439 	int idx, bit;
440 
441 	pa >>= PAGE_SHIFT;
442 	idx = pa >> 6;		/* 2^6 = 64 */
443 	bit = pa & 63;
444 	atomic_clear_long(&vm_page_dump[idx], 1ul << bit);
445 }
446