xref: /freebsd/sys/arm64/arm64/minidump_machdep.c (revision 10ff414c)
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 <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/conf.h>
40 #include <sys/cons.h>
41 #include <sys/kernel.h>
42 #include <sys/kerneldump.h>
43 #include <sys/msgbuf.h>
44 #include <sys/watchdog.h>
45 #include <sys/vmmeter.h>
46 
47 #include <vm/vm.h>
48 #include <vm/vm_param.h>
49 #include <vm/vm_page.h>
50 #include <vm/vm_phys.h>
51 #include <vm/vm_dumpset.h>
52 #include <vm/pmap.h>
53 
54 #include <machine/md_var.h>
55 #include <machine/pte.h>
56 #include <machine/minidump.h>
57 
58 CTASSERT(sizeof(struct kerneldumpheader) == 512);
59 
60 static struct kerneldumpheader kdh;
61 
62 /* Handle chunked writes. */
63 static size_t fragsz;
64 static void *dump_va;
65 static size_t dumpsize;
66 
67 static uint64_t tmpbuffer[Ln_ENTRIES];
68 
69 static int
70 blk_flush(struct dumperinfo *di)
71 {
72 	int error;
73 
74 	if (fragsz == 0)
75 		return (0);
76 
77 	error = dump_append(di, dump_va, 0, fragsz);
78 	fragsz = 0;
79 	return (error);
80 }
81 
82 static int
83 blk_write(struct dumperinfo *di, char *ptr, vm_paddr_t pa, size_t sz)
84 {
85 	size_t len;
86 	int error, c;
87 	u_int maxdumpsz;
88 
89 	maxdumpsz = min(di->maxiosize, MAXDUMPPGS * PAGE_SIZE);
90 	if (maxdumpsz == 0)	/* seatbelt */
91 		maxdumpsz = PAGE_SIZE;
92 	error = 0;
93 	if ((sz % PAGE_SIZE) != 0) {
94 		printf("size not page aligned\n");
95 		return (EINVAL);
96 	}
97 	if (ptr != NULL && pa != 0) {
98 		printf("cant have both va and pa!\n");
99 		return (EINVAL);
100 	}
101 	if ((((uintptr_t)pa) % PAGE_SIZE) != 0) {
102 		printf("address not page aligned %p\n", ptr);
103 		return (EINVAL);
104 	}
105 	if (ptr != NULL) {
106 		/*
107 		 * If we're doing a virtual dump, flush any
108 		 * pre-existing pa pages.
109 		 */
110 		error = blk_flush(di);
111 		if (error)
112 			return (error);
113 	}
114 	while (sz) {
115 		len = maxdumpsz - fragsz;
116 		if (len > sz)
117 			len = sz;
118 
119 		dumpsys_pb_progress(len);
120 		wdog_kern_pat(WD_LASTVAL);
121 
122 		if (ptr) {
123 			error = dump_append(di, ptr, 0, len);
124 			if (error)
125 				return (error);
126 			ptr += len;
127 			sz -= len;
128 		} else {
129 			dump_va = (void *)PHYS_TO_DMAP(pa);
130 			fragsz += len;
131 			pa += len;
132 			sz -= len;
133 			error = blk_flush(di);
134 			if (error)
135 				return (error);
136 		}
137 
138 		/* Check for user abort. */
139 		c = cncheckc();
140 		if (c == 0x03)
141 			return (ECANCELED);
142 		if (c != -1)
143 			printf(" (CTRL-C to abort) ");
144 	}
145 
146 	return (0);
147 }
148 
149 int
150 minidumpsys(struct dumperinfo *di)
151 {
152 	struct minidumphdr mdhdr;
153 	pd_entry_t *l0, *l1, *l2;
154 	pt_entry_t *l3;
155 	vm_offset_t va;
156 	vm_paddr_t pa;
157 	uint32_t pmapsize;
158 	int error, i, j, retry_count;
159 
160 	retry_count = 0;
161  retry:
162 	retry_count++;
163 	error = 0;
164 	pmapsize = 0;
165 	for (va = VM_MIN_KERNEL_ADDRESS; va < kernel_vm_end; va += L2_SIZE) {
166 		pmapsize += PAGE_SIZE;
167 		if (!pmap_get_tables(pmap_kernel(), va, &l0, &l1, &l2, &l3))
168 			continue;
169 
170 		if ((*l1 & ATTR_DESCR_MASK) == L1_BLOCK) {
171 			pa = *l1 & ~ATTR_MASK;
172 			for (i = 0; i < Ln_ENTRIES * Ln_ENTRIES;
173 			    i++, pa += PAGE_SIZE)
174 				if (vm_phys_is_dumpable(pa))
175 					dump_add_page(pa);
176 			pmapsize += (Ln_ENTRIES - 1) * PAGE_SIZE;
177 			va += L1_SIZE - L2_SIZE;
178 		} else if ((*l2 & ATTR_DESCR_MASK) == L2_BLOCK) {
179 			pa = *l2 & ~ATTR_MASK;
180 			for (i = 0; i < Ln_ENTRIES; i++, pa += PAGE_SIZE) {
181 				if (vm_phys_is_dumpable(pa))
182 					dump_add_page(pa);
183 			}
184 		} else if ((*l2 & ATTR_DESCR_MASK) == L2_TABLE) {
185 			for (i = 0; i < Ln_ENTRIES; i++) {
186 				if ((l3[i] & ATTR_DESCR_MASK) != L3_PAGE)
187 					continue;
188 				pa = l3[i] & ~ATTR_MASK;
189 				if (vm_phys_is_dumpable(pa))
190 					dump_add_page(pa);
191 			}
192 		}
193 	}
194 
195 	/* Calculate dump size. */
196 	dumpsize = pmapsize;
197 	dumpsize += round_page(msgbufp->msg_size);
198 	dumpsize += round_page(sizeof(dump_avail));
199 	dumpsize += round_page(BITSET_SIZE(vm_page_dump_pages));
200 	VM_PAGE_DUMP_FOREACH(pa) {
201 		if (vm_phys_is_dumpable(pa))
202 			dumpsize += PAGE_SIZE;
203 		else
204 			dump_drop_page(pa);
205 	}
206 	dumpsize += PAGE_SIZE;
207 
208 	dumpsys_pb_init(dumpsize);
209 
210 	/* Initialize mdhdr */
211 	bzero(&mdhdr, sizeof(mdhdr));
212 	strcpy(mdhdr.magic, MINIDUMP_MAGIC);
213 	mdhdr.version = MINIDUMP_VERSION;
214 	mdhdr.msgbufsize = msgbufp->msg_size;
215 	mdhdr.bitmapsize = round_page(BITSET_SIZE(vm_page_dump_pages));
216 	mdhdr.pmapsize = pmapsize;
217 	mdhdr.kernbase = VM_MIN_KERNEL_ADDRESS;
218 	mdhdr.dmapphys = DMAP_MIN_PHYSADDR;
219 	mdhdr.dmapbase = DMAP_MIN_ADDRESS;
220 	mdhdr.dmapend = DMAP_MAX_ADDRESS;
221 	mdhdr.dumpavailsize = round_page(sizeof(dump_avail));
222 
223 	dump_init_header(di, &kdh, KERNELDUMPMAGIC, KERNELDUMP_AARCH64_VERSION,
224 	    dumpsize);
225 
226 	error = dump_start(di, &kdh);
227 	if (error != 0)
228 		goto fail;
229 
230 	printf("Dumping %llu out of %ju MB:", (long long)dumpsize >> 20,
231 	    ptoa((uintmax_t)physmem) / 1048576);
232 
233 	/* Dump my header */
234 	bzero(&tmpbuffer, sizeof(tmpbuffer));
235 	bcopy(&mdhdr, &tmpbuffer, sizeof(mdhdr));
236 	error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
237 	if (error)
238 		goto fail;
239 
240 	/* Dump msgbuf up front */
241 	error = blk_write(di, (char *)msgbufp->msg_ptr, 0,
242 	    round_page(msgbufp->msg_size));
243 	if (error)
244 		goto fail;
245 
246 	/* Dump dump_avail */
247 	_Static_assert(sizeof(dump_avail) <= sizeof(tmpbuffer),
248 	    "Large dump_avail not handled");
249 	bzero(tmpbuffer, sizeof(tmpbuffer));
250 	memcpy(tmpbuffer, dump_avail, sizeof(dump_avail));
251 	error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
252 	if (error)
253 		goto fail;
254 
255 	/* Dump bitmap */
256 	error = blk_write(di, (char *)vm_page_dump, 0,
257 	    round_page(BITSET_SIZE(vm_page_dump_pages)));
258 	if (error)
259 		goto fail;
260 
261 	/* Dump kernel page directory pages */
262 	bzero(&tmpbuffer, sizeof(tmpbuffer));
263 	for (va = VM_MIN_KERNEL_ADDRESS; va < kernel_vm_end; va += L2_SIZE) {
264 		if (!pmap_get_tables(pmap_kernel(), va, &l0, &l1, &l2, &l3)) {
265 			/* We always write a page, even if it is zero */
266 			error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
267 			if (error)
268 				goto fail;
269 			/* flush, in case we reuse tmpbuffer in the same block*/
270 			error = blk_flush(di);
271 			if (error)
272 				goto fail;
273 		} else if ((*l1 & ATTR_DESCR_MASK) == L1_BLOCK) {
274 			/*
275 			 * Handle a 1GB block mapping: write out 512 fake L2
276 			 * pages.
277 			 */
278 			pa = (*l1 & ~ATTR_MASK) | (va & L1_OFFSET);
279 
280 			for (i = 0; i < Ln_ENTRIES; i++) {
281 				for (j = 0; j < Ln_ENTRIES; j++) {
282 					tmpbuffer[j] = pa + i * L2_SIZE +
283 					    j * PAGE_SIZE | ATTR_DEFAULT |
284 					    L3_PAGE;
285 				}
286 				error = blk_write(di, (char *)&tmpbuffer, 0,
287 				    PAGE_SIZE);
288 				if (error)
289 					goto fail;
290 			}
291 			/* flush, in case we reuse tmpbuffer in the same block*/
292 			error = blk_flush(di);
293 			if (error)
294 				goto fail;
295 			bzero(&tmpbuffer, sizeof(tmpbuffer));
296 			va += L1_SIZE - L2_SIZE;
297 		} else if ((*l2 & ATTR_DESCR_MASK) == L2_BLOCK) {
298 			pa = (*l2 & ~ATTR_MASK) | (va & L2_OFFSET);
299 
300 			/* Generate fake l3 entries based upon the l1 entry */
301 			for (i = 0; i < Ln_ENTRIES; i++) {
302 				tmpbuffer[i] = pa + (i * PAGE_SIZE) |
303 				    ATTR_DEFAULT | L3_PAGE;
304 			}
305 			error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
306 			if (error)
307 				goto fail;
308 			/* flush, in case we reuse fakepd in the same block */
309 			error = blk_flush(di);
310 			if (error)
311 				goto fail;
312 			bzero(&tmpbuffer, sizeof(tmpbuffer));
313 			continue;
314 		} else {
315 			pa = *l2 & ~ATTR_MASK;
316 
317 			error = blk_write(di, NULL, pa, PAGE_SIZE);
318 			if (error)
319 				goto fail;
320 		}
321 	}
322 
323 	/* Dump memory chunks */
324 	VM_PAGE_DUMP_FOREACH(pa) {
325 		error = blk_write(di, 0, pa, PAGE_SIZE);
326 		if (error)
327 			goto fail;
328 	}
329 
330 	error = blk_flush(di);
331 	if (error)
332 		goto fail;
333 
334 	error = dump_finish(di, &kdh);
335 	if (error != 0)
336 		goto fail;
337 
338 	printf("\nDump complete\n");
339 	return (0);
340 
341 fail:
342 	if (error < 0)
343 		error = -error;
344 
345 	printf("\n");
346 	if (error == ENOSPC) {
347 		printf("Dump map grown while dumping. ");
348 		if (retry_count < 5) {
349 			printf("Retrying...\n");
350 			goto retry;
351 		}
352 		printf("Dump failed.\n");
353 	}
354 	else if (error == ECANCELED)
355 		printf("Dump aborted\n");
356 	else if (error == E2BIG) {
357 		printf("Dump failed. Partition too small (about %lluMB were "
358 		    "needed this time).\n", (long long)dumpsize >> 20);
359 	} else
360 		printf("** DUMP FAILED (ERROR %d) **\n", error);
361 	return (error);
362 }
363