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/vm_dumpset.h>
42 #include <vm/pmap.h>
43 
44 #include <machine/atomic.h>
45 #include <machine/dump.h>
46 #include <machine/md_var.h>
47 #include <machine/minidump.h>
48 
49 /* Debugging stuff */
50 #define	MINIDUMP_DEBUG	0
51 #if	MINIDUMP_DEBUG
52 #define dprintf(fmt, ...)	printf(fmt, ## __VA_ARGS__)
53 #define DBG(...)	__VA_ARGS__
54 static size_t total, dumptotal;
55 static void dump_total(const char *id, size_t sz);
56 #else
57 #define dprintf(fmt, ...)
58 #define DBG(...)
59 #define dump_total(...)
60 #endif
61 
62 extern vm_offset_t __startkernel, __endkernel;
63 
64 static int dump_retry_count = 5;
65 SYSCTL_INT(_machdep, OID_AUTO, dump_retry_count, CTLFLAG_RWTUN,
66     &dump_retry_count, 0,
67     "Number of times dump has to retry before bailing out");
68 
69 static struct kerneldumpheader kdh;
70 static char pgbuf[PAGE_SIZE];
71 
72 static size_t dumpsize;
73 
74 /* Handle chunked writes. */
75 static size_t fragsz;
76 
77 static void
78 pmap_kenter_temporary(vm_offset_t va, vm_paddr_t pa)
79 {
80 	pmap_kremove(va);
81 	pmap_kenter(va, pa);
82 }
83 
84 static int
85 blk_flush(struct dumperinfo *di)
86 {
87 	int error;
88 
89 	if (fragsz == 0)
90 		return (0);
91 
92 	error = dump_append(di, crashdumpmap, 0, fragsz);
93 	DBG(dumptotal += fragsz;)
94 	fragsz = 0;
95 	return (error);
96 }
97 
98 static int
99 blk_write(struct dumperinfo *di, char *ptr, vm_paddr_t pa, size_t sz)
100 {
101 	size_t len, maxdumpsz;
102 	int error, i, c;
103 
104 	maxdumpsz = MIN(di->maxiosize, MAXDUMPPGS * PAGE_SIZE);
105 	if (maxdumpsz == 0)	/* seatbelt */
106 		maxdumpsz = PAGE_SIZE;
107 	error = 0;
108 	if ((sz % PAGE_SIZE) != 0) {
109 		printf("Size not page aligned\n");
110 		return (EINVAL);
111 	}
112 	if (ptr != NULL && pa != 0) {
113 		printf("Can't have both va and pa!\n");
114 		return (EINVAL);
115 	}
116 	if ((pa % PAGE_SIZE) != 0) {
117 		printf("Address not page aligned 0x%lx\n", pa);
118 		return (EINVAL);
119 	}
120 	if (ptr != NULL) {
121 		/*
122 		 * If we're doing a virtual dump, flush any pre-existing
123 		 * pa pages
124 		 */
125 		error = blk_flush(di);
126 		if (error)
127 			return (error);
128 	}
129 	while (sz) {
130 		len = maxdumpsz - fragsz;
131 		if (len > sz)
132 			len = sz;
133 
134 		dumpsys_pb_progress(len);
135 
136 		if (ptr) {
137 			error = dump_append(di, ptr, 0, len);
138 			if (error)
139 				return (error);
140 			DBG(dumptotal += len;)
141 			ptr += len;
142 		} else {
143 			for (i = 0; i < len; i += PAGE_SIZE)
144 				pmap_kenter_temporary(
145 				    (vm_offset_t)crashdumpmap + fragsz + i,
146 				    pa + i);
147 
148 			fragsz += len;
149 			pa += len;
150 			if (fragsz == maxdumpsz) {
151 				error = blk_flush(di);
152 				if (error)
153 					return (error);
154 			}
155 		}
156 		sz -= len;
157 
158 		/* Check for user abort. */
159 		c = cncheckc();
160 		if (c == 0x03)
161 			return (ECANCELED);
162 		if (c != -1)
163 			printf(" (CTRL-C to abort) ");
164 	}
165 
166 	return (0);
167 }
168 
169 static int
170 dump_pmap(struct dumperinfo *di)
171 {
172 	void *ctx;
173 	char *buf;
174 	u_long nbytes;
175 	int error;
176 
177 	ctx = dumpsys_dump_pmap_init(sizeof(pgbuf) / PAGE_SIZE);
178 
179 	for (;;) {
180 		buf = dumpsys_dump_pmap(ctx, pgbuf, &nbytes);
181 		if (buf == NULL)
182 			break;
183 		error = blk_write(di, buf, 0, nbytes);
184 		if (error)
185 			return (error);
186 	}
187 
188 	return (0);
189 }
190 
191 int
192 minidumpsys(struct dumperinfo *di)
193 {
194 	vm_paddr_t pa;
195 	int error, retry_count;
196 	uint32_t pmapsize;
197 	struct minidumphdr mdhdr;
198 
199 	retry_count = 0;
200 retry:
201 	retry_count++;
202 	fragsz = 0;
203 	DBG(total = dumptotal = 0;)
204 
205 	/* Build set of dumpable pages from kernel pmap */
206 	pmapsize = dumpsys_scan_pmap();
207 	if (pmapsize % PAGE_SIZE != 0) {
208 		printf("pmapsize not page aligned: 0x%x\n", pmapsize);
209 		return (EINVAL);
210 	}
211 
212 	/* Calculate dump size */
213 	dumpsize = PAGE_SIZE;				/* header */
214 	dumpsize += round_page(msgbufp->msg_size);
215 	dumpsize += round_page(sizeof(dump_avail));
216 	dumpsize += round_page(BITSET_SIZE(vm_page_dump_pages));
217 	dumpsize += pmapsize;
218 	VM_PAGE_DUMP_FOREACH(pa) {
219 		/* Clear out undumpable pages now if needed */
220 		if (vm_phys_is_dumpable(pa))
221 			dumpsize += PAGE_SIZE;
222 		else
223 			dump_drop_page(pa);
224 	}
225 	dumpsys_pb_init(dumpsize);
226 
227 	/* Initialize mdhdr */
228 	bzero(&mdhdr, sizeof(mdhdr));
229 	strcpy(mdhdr.magic, MINIDUMP_MAGIC);
230 	strncpy(mdhdr.mmu_name, pmap_mmu_name(), sizeof(mdhdr.mmu_name) - 1);
231 	mdhdr.version = MINIDUMP_VERSION;
232 	mdhdr.msgbufsize = msgbufp->msg_size;
233 	mdhdr.bitmapsize = round_page(BITSET_SIZE(vm_page_dump_pages));
234 	mdhdr.pmapsize = pmapsize;
235 	mdhdr.kernbase = VM_MIN_KERNEL_ADDRESS;
236 	mdhdr.kernend = VM_MAX_SAFE_KERNEL_ADDRESS;
237 	mdhdr.dmapbase = DMAP_BASE_ADDRESS;
238 	mdhdr.dmapend = DMAP_MAX_ADDRESS;
239 	mdhdr.hw_direct_map = hw_direct_map;
240 	mdhdr.startkernel = __startkernel;
241 	mdhdr.endkernel = __endkernel;
242 	mdhdr.dumpavailsize = round_page(sizeof(dump_avail));
243 
244 	dump_init_header(di, &kdh, KERNELDUMPMAGIC, KERNELDUMP_POWERPC_VERSION,
245 	    dumpsize);
246 
247 	error = dump_start(di, &kdh);
248 	if (error)
249 		goto fail;
250 
251 	printf("Dumping %lu out of %ju MB:", dumpsize >> 20,
252 	    ptoa((uintmax_t)physmem) / 1048576);
253 
254 	/* Dump minidump header */
255 	bzero(pgbuf, sizeof(pgbuf));
256 	memcpy(pgbuf, &mdhdr, sizeof(mdhdr));
257 	error = blk_write(di, pgbuf, 0, PAGE_SIZE);
258 	if (error)
259 		goto fail;
260 	dump_total("header", PAGE_SIZE);
261 
262 	/* Dump msgbuf up front */
263 	error = blk_write(di, (char *)msgbufp->msg_ptr, 0,
264 	    round_page(msgbufp->msg_size));
265 	dump_total("msgbuf", round_page(msgbufp->msg_size));
266 
267 	/* Dump dump_avail */
268 	_Static_assert(sizeof(dump_avail) <= sizeof(pgbuf),
269 	    "Large dump_avail not handled");
270 	bzero(pgbuf, sizeof(mdhdr));
271 	memcpy(pgbuf, dump_avail, sizeof(dump_avail));
272 	error = blk_write(di, pgbuf, 0, PAGE_SIZE);
273 	if (error)
274 		goto fail;
275 	dump_total("dump_avail", round_page(sizeof(dump_avail)));
276 
277 	/* Dump bitmap */
278 	error = blk_write(di, (char *)vm_page_dump, 0,
279 	    round_page(BITSET_SIZE(vm_page_dump_pages)));
280 	if (error)
281 		goto fail;
282 	dump_total("bitmap", round_page(BITSET_SIZE(vm_page_dump_pages)));
283 
284 	/* Dump kernel page directory pages */
285 	error = dump_pmap(di);
286 	if (error)
287 		goto fail;
288 	dump_total("pmap", pmapsize);
289 
290 	/* Dump memory chunks */
291 	VM_PAGE_DUMP_FOREACH(pa) {
292 		error = blk_write(di, 0, pa, PAGE_SIZE);
293 		if (error)
294 			goto fail;
295 	}
296 
297 	error = blk_flush(di);
298 	if (error)
299 		goto fail;
300 	dump_total("mem_chunks", dumpsize - total);
301 
302 	error = dump_finish(di, &kdh);
303 	if (error)
304 		goto fail;
305 
306 	printf("\nDump complete\n");
307 	return (0);
308 
309 fail:
310 	if (error < 0)
311 		error = -error;
312 
313 	printf("\n");
314 	if (error == ENOSPC) {
315 		printf("Dump map grown while dumping. ");
316 		if (retry_count < dump_retry_count) {
317 			printf("Retrying...\n");
318 			goto retry;
319 		}
320 		printf("Dump failed.\n");
321 	} else if (error == ECANCELED)
322 		printf("Dump aborted\n");
323 	else if (error == E2BIG)
324 		printf("Dump failed. Partition too small.\n");
325 	else
326 		printf("** DUMP FAILED (ERROR %d) **\n", error);
327 	return (error);
328 }
329 
330 #if	MINIDUMP_DEBUG
331 static void
332 dump_total(const char *id, size_t sz)
333 {
334 	total += sz;
335 	dprintf("\n%s=%08lx/%08lx/%08lx\n",
336 		id, sz, total, dumptotal);
337 }
338 #endif
339