1 /*-
2  * Copyright (c) 2019 Leandro Lupori
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  * $FreeBSD$
26  */
27 
28 #include <sys/types.h>
29 #include <sys/param.h>
30 
31 #include <sys/cons.h>
32 #include <sys/kerneldump.h>
33 #include <sys/msgbuf.h>
34 #include <sys/proc.h>
35 #include <sys/sysctl.h>
36 
37 #include <vm/vm.h>
38 #include <vm/vm_param.h>
39 #include <vm/vm_page.h>
40 #include <vm/vm_phys.h>
41 #include <vm/pmap.h>
42 
43 #include <machine/atomic.h>
44 #include <machine/dump.h>
45 #include <machine/md_var.h>
46 #include <machine/minidump.h>
47 
48 /* Debugging stuff */
49 #define	MINIDUMP_DEBUG	0
50 #if	MINIDUMP_DEBUG
51 #define dprintf(fmt, ...)	printf(fmt, ## __VA_ARGS__)
52 #define DBG(...)	__VA_ARGS__
53 static size_t total, dumptotal;
54 static void dump_total(const char *id, size_t sz);
55 #else
56 #define dprintf(fmt, ...)
57 #define DBG(...)
58 #define dump_total(...)
59 #endif
60 
61 extern vm_offset_t __startkernel, __endkernel;
62 
63 static int dump_retry_count = 5;
64 SYSCTL_INT(_machdep, OID_AUTO, dump_retry_count, CTLFLAG_RWTUN,
65     &dump_retry_count, 0,
66     "Number of times dump has to retry before bailing out");
67 
68 static struct kerneldumpheader kdh;
69 static char pgbuf[PAGE_SIZE];
70 
71 static struct {
72 	int min_per;
73 	int max_per;
74 	int visited;
75 } progress_track[10] = {
76 	{  0,  10, 0},
77 	{ 10,  20, 0},
78 	{ 20,  30, 0},
79 	{ 30,  40, 0},
80 	{ 40,  50, 0},
81 	{ 50,  60, 0},
82 	{ 60,  70, 0},
83 	{ 70,  80, 0},
84 	{ 80,  90, 0},
85 	{ 90, 100, 0}
86 };
87 
88 static size_t counter, dumpsize, progress;
89 
90 /* Handle chunked writes. */
91 static size_t fragsz;
92 
93 int
94 is_dumpable(vm_paddr_t pa)
95 {
96 	vm_page_t m;
97 	int i;
98 
99 	if ((m = vm_phys_paddr_to_vm_page(pa)) != NULL)
100 		return ((m->flags & PG_NODUMP) == 0);
101 	for (i = 0; dump_avail[i] != 0 || dump_avail[i + 1] != 0; i += 2) {
102 		if (pa >= dump_avail[i] && pa < dump_avail[i + 1])
103 			return (1);
104 	}
105 	return (0);
106 }
107 
108 static void
109 pmap_kenter_temporary(vm_offset_t va, vm_paddr_t pa)
110 {
111 	pmap_kremove(va);
112 	pmap_kenter(va, pa);
113 }
114 
115 static void
116 report_progress(void)
117 {
118 	int sofar, i;
119 
120 	sofar = 100 - ((progress * 100) / dumpsize);
121 	for (i = 0; i < nitems(progress_track); i++) {
122 		if (sofar < progress_track[i].min_per ||
123 		    sofar > progress_track[i].max_per)
124 			continue;
125 		if (progress_track[i].visited)
126 			return;
127 		progress_track[i].visited = 1;
128 		printf("..%d%%", sofar);
129 		return;
130 	}
131 }
132 
133 static int
134 blk_flush(struct dumperinfo *di)
135 {
136 	int error;
137 
138 	if (fragsz == 0)
139 		return (0);
140 
141 	error = dump_append(di, crashdumpmap, 0, fragsz);
142 	DBG(dumptotal += fragsz;)
143 	fragsz = 0;
144 	return (error);
145 }
146 
147 static int
148 blk_write(struct dumperinfo *di, char *ptr, vm_paddr_t pa, size_t sz)
149 {
150 	size_t len, maxdumpsz;
151 	int error, i, c;
152 
153 	maxdumpsz = MIN(di->maxiosize, MAXDUMPPGS * PAGE_SIZE);
154 	if (maxdumpsz == 0)	/* seatbelt */
155 		maxdumpsz = PAGE_SIZE;
156 	error = 0;
157 	if ((sz % PAGE_SIZE) != 0) {
158 		printf("Size not page aligned\n");
159 		return (EINVAL);
160 	}
161 	if (ptr != NULL && pa != 0) {
162 		printf("Can't have both va and pa!\n");
163 		return (EINVAL);
164 	}
165 	if ((pa % PAGE_SIZE) != 0) {
166 		printf("Address not page aligned 0x%lx\n", pa);
167 		return (EINVAL);
168 	}
169 	if (ptr != NULL) {
170 		/*
171 		 * If we're doing a virtual dump, flush any pre-existing
172 		 * pa pages
173 		 */
174 		error = blk_flush(di);
175 		if (error)
176 			return (error);
177 	}
178 	while (sz) {
179 		len = maxdumpsz - fragsz;
180 		if (len > sz)
181 			len = sz;
182 		counter += len;
183 		progress -= len;
184 		if (counter >> 20) {
185 			report_progress();
186 			counter &= (1<<20) - 1;
187 		}
188 
189 		if (ptr) {
190 			error = dump_append(di, ptr, 0, len);
191 			if (error)
192 				return (error);
193 			DBG(dumptotal += len;)
194 			ptr += len;
195 		} else {
196 			for (i = 0; i < len; i += PAGE_SIZE)
197 				pmap_kenter_temporary(
198 				    (vm_offset_t)crashdumpmap + fragsz + i,
199 				    pa + i);
200 
201 			fragsz += len;
202 			pa += len;
203 			if (fragsz == maxdumpsz) {
204 				error = blk_flush(di);
205 				if (error)
206 					return (error);
207 			}
208 		}
209 		sz -= len;
210 
211 		/* Check for user abort. */
212 		c = cncheckc();
213 		if (c == 0x03)
214 			return (ECANCELED);
215 		if (c != -1)
216 			printf(" (CTRL-C to abort) ");
217 	}
218 
219 	return (0);
220 }
221 
222 static int
223 dump_pmap(struct dumperinfo *di)
224 {
225 	void *ctx;
226 	char *buf;
227 	u_long nbytes;
228 	int error;
229 
230 	ctx = dumpsys_dump_pmap_init(sizeof(pgbuf) / PAGE_SIZE);
231 
232 	for (;;) {
233 		buf = dumpsys_dump_pmap(ctx, pgbuf, &nbytes);
234 		if (buf == NULL)
235 			break;
236 		error = blk_write(di, buf, 0, nbytes);
237 		if (error)
238 			return (error);
239 	}
240 
241 	return (0);
242 }
243 
244 int
245 minidumpsys(struct dumperinfo *di)
246 {
247 	vm_paddr_t pa;
248 	int error, i, retry_count;
249 	uint32_t pmapsize;
250 	struct minidumphdr mdhdr;
251 
252 	retry_count = 0;
253 retry:
254 	retry_count++;
255 	fragsz = 0;
256 	DBG(total = dumptotal = 0;)
257 
258 	/* Reset progress */
259 	counter = 0;
260 	for (i = 0; i < nitems(progress_track); i++)
261 		progress_track[i].visited = 0;
262 
263 	/* Build set of dumpable pages from kernel pmap */
264 	pmapsize = dumpsys_scan_pmap();
265 	if (pmapsize % PAGE_SIZE != 0) {
266 		printf("pmapsize not page aligned: 0x%x\n", pmapsize);
267 		return (EINVAL);
268 	}
269 
270 	/* Calculate dump size */
271 	dumpsize = PAGE_SIZE;				/* header */
272 	dumpsize += round_page(msgbufp->msg_size);
273 	dumpsize += round_page(sizeof(dump_avail));
274 	dumpsize += round_page(BITSET_SIZE(vm_page_dump_pages));
275 	dumpsize += pmapsize;
276 	VM_PAGE_DUMP_FOREACH(pa) {
277 		/* Clear out undumpable pages now if needed */
278 		if (is_dumpable(pa))
279 			dumpsize += PAGE_SIZE;
280 		else
281 			dump_drop_page(pa);
282 	}
283 	progress = dumpsize;
284 
285 	/* Initialize mdhdr */
286 	bzero(&mdhdr, sizeof(mdhdr));
287 	strcpy(mdhdr.magic, MINIDUMP_MAGIC);
288 	strncpy(mdhdr.mmu_name, pmap_mmu_name(), sizeof(mdhdr.mmu_name) - 1);
289 	mdhdr.version = MINIDUMP_VERSION;
290 	mdhdr.msgbufsize = msgbufp->msg_size;
291 	mdhdr.bitmapsize = round_page(BITSET_SIZE(vm_page_dump_pages));
292 	mdhdr.pmapsize = pmapsize;
293 	mdhdr.kernbase = VM_MIN_KERNEL_ADDRESS;
294 	mdhdr.kernend = VM_MAX_SAFE_KERNEL_ADDRESS;
295 	mdhdr.dmapbase = DMAP_BASE_ADDRESS;
296 	mdhdr.dmapend = DMAP_MAX_ADDRESS;
297 	mdhdr.hw_direct_map = hw_direct_map;
298 	mdhdr.startkernel = __startkernel;
299 	mdhdr.endkernel = __endkernel;
300 	mdhdr.dumpavailsize = round_page(sizeof(dump_avail));
301 
302 	dump_init_header(di, &kdh, KERNELDUMPMAGIC, KERNELDUMP_POWERPC_VERSION,
303 	    dumpsize);
304 
305 	error = dump_start(di, &kdh);
306 	if (error)
307 		goto fail;
308 
309 	printf("Dumping %lu out of %ju MB:", dumpsize >> 20,
310 	    ptoa((uintmax_t)physmem) / 1048576);
311 
312 	/* Dump minidump header */
313 	bzero(pgbuf, sizeof(pgbuf));
314 	memcpy(pgbuf, &mdhdr, sizeof(mdhdr));
315 	error = blk_write(di, pgbuf, 0, PAGE_SIZE);
316 	if (error)
317 		goto fail;
318 	dump_total("header", PAGE_SIZE);
319 
320 	/* Dump msgbuf up front */
321 	error = blk_write(di, (char *)msgbufp->msg_ptr, 0,
322 	    round_page(msgbufp->msg_size));
323 	dump_total("msgbuf", round_page(msgbufp->msg_size));
324 
325 	/* Dump dump_avail */
326 	_Static_assert(sizeof(dump_avail) <= sizeof(pgbuf),
327 	    "Large dump_avail not handled");
328 	bzero(pgbuf, sizeof(mdhdr));
329 	memcpy(pgbuf, dump_avail, sizeof(dump_avail));
330 	error = blk_write(di, pgbuf, 0, PAGE_SIZE);
331 	if (error)
332 		goto fail;
333 	dump_total("dump_avail", round_page(sizeof(dump_avail)));
334 
335 	/* Dump bitmap */
336 	error = blk_write(di, (char *)vm_page_dump, 0,
337 	    round_page(BITSET_SIZE(vm_page_dump_pages)));
338 	if (error)
339 		goto fail;
340 	dump_total("bitmap", round_page(BITSET_SIZE(vm_page_dump_pages)));
341 
342 	/* Dump kernel page directory pages */
343 	error = dump_pmap(di);
344 	if (error)
345 		goto fail;
346 	dump_total("pmap", pmapsize);
347 
348 	/* Dump memory chunks */
349 	VM_PAGE_DUMP_FOREACH(pa) {
350 		error = blk_write(di, 0, pa, PAGE_SIZE);
351 		if (error)
352 			goto fail;
353 	}
354 
355 	error = blk_flush(di);
356 	if (error)
357 		goto fail;
358 	dump_total("mem_chunks", dumpsize - total);
359 
360 	error = dump_finish(di, &kdh);
361 	if (error)
362 		goto fail;
363 
364 	printf("\nDump complete\n");
365 	return (0);
366 
367 fail:
368 	if (error < 0)
369 		error = -error;
370 
371 	printf("\n");
372 	if (error == ENOSPC) {
373 		printf("Dump map grown while dumping. ");
374 		if (retry_count < dump_retry_count) {
375 			printf("Retrying...\n");
376 			goto retry;
377 		}
378 		printf("Dump failed.\n");
379 	} else if (error == ECANCELED)
380 		printf("Dump aborted\n");
381 	else if (error == E2BIG)
382 		printf("Dump failed. Partition too small.\n");
383 	else
384 		printf("** DUMP FAILED (ERROR %d) **\n", error);
385 	return (error);
386 }
387 
388 #if	MINIDUMP_DEBUG
389 static void
390 dump_total(const char *id, size_t sz)
391 {
392 	total += sz;
393 	dprintf("\n%s=%08lx/%08lx/%08lx\n",
394 		id, sz, total, dumptotal);
395 }
396 #endif
397