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 /*
49  * bit to physical address
50  *
51  * bm - bitmap
52  * i - bitmap entry index
53  * bit - bit number
54  */
55 #define BTOP(bm, i, bit) \
56 	(((uint64_t)(i) * sizeof(*(bm)) * NBBY + (bit)) * PAGE_SIZE)
57 
58 /* Debugging stuff */
59 #define	MINIDUMP_DEBUG	0
60 #if	MINIDUMP_DEBUG
61 #define dprintf(fmt, ...)	printf(fmt, ## __VA_ARGS__)
62 #define DBG(...)	__VA_ARGS__
63 static size_t total, dumptotal;
64 static void dump_total(const char *id, size_t sz);
65 #else
66 #define dprintf(fmt, ...)
67 #define DBG(...)
68 #define dump_total(...)
69 #endif
70 
71 
72 extern vm_offset_t __startkernel, __endkernel;
73 
74 int vm_page_dump_size;
75 uint64_t *vm_page_dump;
76 
77 static int dump_retry_count = 5;
78 SYSCTL_INT(_machdep, OID_AUTO, dump_retry_count, CTLFLAG_RWTUN,
79     &dump_retry_count, 0,
80     "Number of times dump has to retry before bailing out");
81 
82 static struct kerneldumpheader kdh;
83 static char pgbuf[PAGE_SIZE];
84 
85 static struct {
86 	int min_per;
87 	int max_per;
88 	int visited;
89 } progress_track[10] = {
90 	{  0,  10, 0},
91 	{ 10,  20, 0},
92 	{ 20,  30, 0},
93 	{ 30,  40, 0},
94 	{ 40,  50, 0},
95 	{ 50,  60, 0},
96 	{ 60,  70, 0},
97 	{ 70,  80, 0},
98 	{ 80,  90, 0},
99 	{ 90, 100, 0}
100 };
101 
102 static size_t counter, dumpsize, progress;
103 
104 /* Handle chunked writes. */
105 static size_t fragsz;
106 
107 void
108 dump_add_page(vm_paddr_t pa)
109 {
110 	int idx, bit;
111 
112 	pa >>= PAGE_SHIFT;
113 	idx = pa >> 6;		/* 2^6 = 64 */
114 	bit = pa & 63;
115 	atomic_set_long(&vm_page_dump[idx], 1ul << bit);
116 }
117 
118 void
119 dump_drop_page(vm_paddr_t pa)
120 {
121 	int idx, bit;
122 
123 	pa >>= PAGE_SHIFT;
124 	idx = pa >> 6;		/* 2^6 = 64 */
125 	bit = pa & 63;
126 	atomic_clear_long(&vm_page_dump[idx], 1ul << bit);
127 }
128 
129 int
130 is_dumpable(vm_paddr_t pa)
131 {
132 	vm_page_t m;
133 	int i;
134 
135 	if ((m = vm_phys_paddr_to_vm_page(pa)) != NULL)
136 		return ((m->flags & PG_NODUMP) == 0);
137 	for (i = 0; dump_avail[i] != 0 || dump_avail[i + 1] != 0; i += 2) {
138 		if (pa >= dump_avail[i] && pa < dump_avail[i + 1])
139 			return (1);
140 	}
141 	return (0);
142 }
143 
144 static void
145 pmap_kenter_temporary(vm_offset_t va, vm_paddr_t pa)
146 {
147 	pmap_kremove(va);
148 	pmap_kenter(va, pa);
149 }
150 
151 static void
152 report_progress(void)
153 {
154 	int sofar, i;
155 
156 	sofar = 100 - ((progress * 100) / dumpsize);
157 	for (i = 0; i < nitems(progress_track); i++) {
158 		if (sofar < progress_track[i].min_per ||
159 		    sofar > progress_track[i].max_per)
160 			continue;
161 		if (progress_track[i].visited)
162 			return;
163 		progress_track[i].visited = 1;
164 		printf("..%d%%", sofar);
165 		return;
166 	}
167 }
168 
169 static int
170 blk_flush(struct dumperinfo *di)
171 {
172 	int error;
173 
174 	if (fragsz == 0)
175 		return (0);
176 
177 	error = dump_append(di, crashdumpmap, 0, fragsz);
178 	DBG(dumptotal += fragsz;)
179 	fragsz = 0;
180 	return (error);
181 }
182 
183 static int
184 blk_write(struct dumperinfo *di, char *ptr, vm_paddr_t pa, size_t sz)
185 {
186 	size_t len, maxdumpsz;
187 	int error, i, c;
188 
189 	maxdumpsz = MIN(di->maxiosize, MAXDUMPPGS * PAGE_SIZE);
190 	if (maxdumpsz == 0)	/* seatbelt */
191 		maxdumpsz = PAGE_SIZE;
192 	error = 0;
193 	if ((sz % PAGE_SIZE) != 0) {
194 		printf("Size not page aligned\n");
195 		return (EINVAL);
196 	}
197 	if (ptr != NULL && pa != 0) {
198 		printf("Can't have both va and pa!\n");
199 		return (EINVAL);
200 	}
201 	if ((pa % PAGE_SIZE) != 0) {
202 		printf("Address not page aligned 0x%lx\n", pa);
203 		return (EINVAL);
204 	}
205 	if (ptr != NULL) {
206 		/*
207 		 * If we're doing a virtual dump, flush any pre-existing
208 		 * pa pages
209 		 */
210 		error = blk_flush(di);
211 		if (error)
212 			return (error);
213 	}
214 	while (sz) {
215 		len = maxdumpsz - fragsz;
216 		if (len > sz)
217 			len = sz;
218 		counter += len;
219 		progress -= len;
220 		if (counter >> 20) {
221 			report_progress();
222 			counter &= (1<<20) - 1;
223 		}
224 
225 		if (ptr) {
226 			error = dump_append(di, ptr, 0, len);
227 			if (error)
228 				return (error);
229 			DBG(dumptotal += len;)
230 			ptr += len;
231 		} else {
232 			for (i = 0; i < len; i += PAGE_SIZE)
233 				pmap_kenter_temporary(
234 				    (vm_offset_t)crashdumpmap + fragsz + i,
235 				    pa + i);
236 
237 			fragsz += len;
238 			pa += len;
239 			if (fragsz == maxdumpsz) {
240 				error = blk_flush(di);
241 				if (error)
242 					return (error);
243 			}
244 		}
245 		sz -= len;
246 
247 		/* Check for user abort. */
248 		c = cncheckc();
249 		if (c == 0x03)
250 			return (ECANCELED);
251 		if (c != -1)
252 			printf(" (CTRL-C to abort) ");
253 	}
254 
255 	return (0);
256 }
257 
258 static int
259 dump_pmap(struct dumperinfo *di)
260 {
261 	void *ctx;
262 	char *buf;
263 	u_long nbytes;
264 	int error;
265 
266 	ctx = dumpsys_dump_pmap_init(sizeof(pgbuf) / PAGE_SIZE);
267 
268 	for (;;) {
269 		buf = dumpsys_dump_pmap(ctx, pgbuf, &nbytes);
270 		if (buf == NULL)
271 			break;
272 		error = blk_write(di, buf, 0, nbytes);
273 		if (error)
274 			return (error);
275 	}
276 
277 	return (0);
278 }
279 
280 int
281 minidumpsys(struct dumperinfo *di)
282 {
283 	vm_paddr_t pa;
284 	int bit, error, i, retry_count;
285 	uint32_t pmapsize;
286 	uint64_t bits;
287 	struct minidumphdr mdhdr;
288 
289 	retry_count = 0;
290 retry:
291 	retry_count++;
292 	fragsz = 0;
293 	DBG(total = dumptotal = 0;)
294 
295 	/* Reset progress */
296 	counter = 0;
297 	for (i = 0; i < nitems(progress_track); i++)
298 		progress_track[i].visited = 0;
299 
300 	/* Build set of dumpable pages from kernel pmap */
301 	pmapsize = dumpsys_scan_pmap();
302 	if (pmapsize % PAGE_SIZE != 0) {
303 		printf("pmapsize not page aligned: 0x%x\n", pmapsize);
304 		return (EINVAL);
305 	}
306 
307 	/* Calculate dump size */
308 	dumpsize = PAGE_SIZE;				/* header */
309 	dumpsize += round_page(msgbufp->msg_size);
310 	dumpsize += round_page(vm_page_dump_size);
311 	dumpsize += pmapsize;
312 	for (i = 0; i < vm_page_dump_size / sizeof(*vm_page_dump); i++) {
313 		bits = vm_page_dump[i];
314 		/* TODO optimize with bit manipulation instructions */
315 		if (bits == 0)
316 			continue;
317 		for (bit = 0; bit < 64; bit++) {
318 			if ((bits & (1ul<<bit)) == 0)
319 				continue;
320 
321 			pa = BTOP(vm_page_dump, i, bit);
322 			/* Clear out undumpable pages now if needed */
323 			if (is_dumpable(pa))
324 				dumpsize += PAGE_SIZE;
325 			else
326 				dump_drop_page(pa);
327 		}
328 	}
329 	progress = dumpsize;
330 
331 	/* Initialize mdhdr */
332 	bzero(&mdhdr, sizeof(mdhdr));
333 	strcpy(mdhdr.magic, MINIDUMP_MAGIC);
334 	strncpy(mdhdr.mmu_name, pmap_mmu_name(), sizeof(mdhdr.mmu_name) - 1);
335 	mdhdr.version = MINIDUMP_VERSION;
336 	mdhdr.msgbufsize = msgbufp->msg_size;
337 	mdhdr.bitmapsize = vm_page_dump_size;
338 	mdhdr.pmapsize = pmapsize;
339 	mdhdr.kernbase = VM_MIN_KERNEL_ADDRESS;
340 	mdhdr.kernend = VM_MAX_SAFE_KERNEL_ADDRESS;
341 	mdhdr.dmapbase = DMAP_BASE_ADDRESS;
342 	mdhdr.dmapend = DMAP_MAX_ADDRESS;
343 	mdhdr.hw_direct_map = hw_direct_map;
344 	mdhdr.startkernel = __startkernel;
345 	mdhdr.endkernel = __endkernel;
346 
347 	dump_init_header(di, &kdh, KERNELDUMPMAGIC, KERNELDUMP_POWERPC_VERSION,
348 	    dumpsize);
349 
350 	error = dump_start(di, &kdh);
351 	if (error)
352 		goto fail;
353 
354 	printf("Dumping %lu out of %ju MB:", dumpsize >> 20,
355 	    ptoa((uintmax_t)physmem) / 1048576);
356 
357 	/* Dump minidump header */
358 	bzero(pgbuf, sizeof(pgbuf));
359 	memcpy(pgbuf, &mdhdr, sizeof(mdhdr));
360 	error = blk_write(di, pgbuf, 0, PAGE_SIZE);
361 	if (error)
362 		goto fail;
363 	dump_total("header", PAGE_SIZE);
364 
365 	/* Dump msgbuf up front */
366 	error = blk_write(di, (char *)msgbufp->msg_ptr, 0,
367 	    round_page(msgbufp->msg_size));
368 	dump_total("msgbuf", round_page(msgbufp->msg_size));
369 
370 	/* Dump bitmap */
371 	error = blk_write(di, (char *)vm_page_dump, 0,
372 	    round_page(vm_page_dump_size));
373 	if (error)
374 		goto fail;
375 	dump_total("bitmap", round_page(vm_page_dump_size));
376 
377 	/* Dump kernel page directory pages */
378 	error = dump_pmap(di);
379 	if (error)
380 		goto fail;
381 	dump_total("pmap", pmapsize);
382 
383 	/* Dump memory chunks */
384 	for (i = 0; i < vm_page_dump_size / sizeof(*vm_page_dump); i++) {
385 		bits = vm_page_dump[i];
386 		/* TODO optimize with bit manipulation instructions */
387 		if (bits == 0)
388 			continue;
389 		for (bit = 0; bit < 64; bit++) {
390 			if ((bits & (1ul<<bit)) == 0)
391 				continue;
392 
393 			pa = BTOP(vm_page_dump, i, bit);
394 			error = blk_write(di, 0, pa, PAGE_SIZE);
395 			if (error)
396 				goto fail;
397 		}
398 	}
399 
400 	error = blk_flush(di);
401 	if (error)
402 		goto fail;
403 	dump_total("mem_chunks", dumpsize - total);
404 
405 	error = dump_finish(di, &kdh);
406 	if (error)
407 		goto fail;
408 
409 	printf("\nDump complete\n");
410 	return (0);
411 
412 fail:
413 	if (error < 0)
414 		error = -error;
415 
416 	printf("\n");
417 	if (error == ENOSPC) {
418 		printf("Dump map grown while dumping. ");
419 		if (retry_count < dump_retry_count) {
420 			printf("Retrying...\n");
421 			goto retry;
422 		}
423 		printf("Dump failed.\n");
424 	} else if (error == ECANCELED)
425 		printf("Dump aborted\n");
426 	else if (error == E2BIG)
427 		printf("Dump failed. Partition too small.\n");
428 	else
429 		printf("** DUMP FAILED (ERROR %d) **\n", error);
430 	return (error);
431 }
432 
433 #if	MINIDUMP_DEBUG
434 static void
435 dump_total(const char *id, size_t sz)
436 {
437 	total += sz;
438 	dprintf("\n%s=%08lx/%08lx/%08lx\n",
439 		id, sz, total, dumptotal);
440 }
441 #endif
442