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  */
26 
27 #include <sys/types.h>
28 #include <sys/param.h>
29 
30 #include <sys/cons.h>
31 #include <sys/kerneldump.h>
32 #include <sys/msgbuf.h>
33 #include <sys/proc.h>
34 #include <sys/sysctl.h>
35 
36 #include <vm/vm.h>
37 #include <vm/vm_param.h>
38 #include <vm/vm_page.h>
39 #include <vm/vm_phys.h>
40 #include <vm/vm_dumpset.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 size_t dumpsize;
72 
73 /* Handle chunked writes. */
74 static size_t fragsz;
75 
76 static void
77 pmap_kenter_temporary(vm_offset_t va, vm_paddr_t pa)
78 {
79 	pmap_kremove(va);
80 	pmap_kenter(va, pa);
81 }
82 
83 static int
84 blk_flush(struct dumperinfo *di)
85 {
86 	int error;
87 
88 	if (fragsz == 0)
89 		return (0);
90 
91 	error = dump_append(di, crashdumpmap, fragsz);
92 	DBG(dumptotal += fragsz;)
93 	fragsz = 0;
94 	return (error);
95 }
96 
97 static int
98 blk_write(struct dumperinfo *di, char *ptr, vm_paddr_t pa, size_t sz)
99 {
100 	size_t len, maxdumpsz;
101 	int error, i, c;
102 
103 	maxdumpsz = MIN(di->maxiosize, MAXDUMPPGS * PAGE_SIZE);
104 	if (maxdumpsz == 0)	/* seatbelt */
105 		maxdumpsz = PAGE_SIZE;
106 	error = 0;
107 	if ((sz % PAGE_SIZE) != 0) {
108 		printf("Size not page aligned\n");
109 		return (EINVAL);
110 	}
111 	if (ptr != NULL && pa != 0) {
112 		printf("Can't have both va and pa!\n");
113 		return (EINVAL);
114 	}
115 	if ((pa % PAGE_SIZE) != 0) {
116 		printf("Address not page aligned 0x%lx\n", pa);
117 		return (EINVAL);
118 	}
119 	if (ptr != NULL) {
120 		/*
121 		 * If we're doing a virtual dump, flush any pre-existing
122 		 * pa pages
123 		 */
124 		error = blk_flush(di);
125 		if (error)
126 			return (error);
127 	}
128 	while (sz) {
129 		len = maxdumpsz - fragsz;
130 		if (len > sz)
131 			len = sz;
132 
133 		dumpsys_pb_progress(len);
134 
135 		if (ptr) {
136 			error = dump_append(di, ptr, len);
137 			if (error)
138 				return (error);
139 			DBG(dumptotal += len;)
140 			ptr += len;
141 		} else {
142 			for (i = 0; i < len; i += PAGE_SIZE)
143 				pmap_kenter_temporary(
144 				    (vm_offset_t)crashdumpmap + fragsz + i,
145 				    pa + i);
146 
147 			fragsz += len;
148 			pa += len;
149 			if (fragsz == maxdumpsz) {
150 				error = blk_flush(di);
151 				if (error)
152 					return (error);
153 			}
154 		}
155 		sz -= len;
156 
157 		/* Check for user abort. */
158 		c = cncheckc();
159 		if (c == 0x03)
160 			return (ECANCELED);
161 		if (c != -1)
162 			printf(" (CTRL-C to abort) ");
163 	}
164 
165 	return (0);
166 }
167 
168 static int
169 dump_pmap(struct dumperinfo *di)
170 {
171 	void *ctx;
172 	char *buf;
173 	u_long nbytes;
174 	int error;
175 
176 	ctx = dumpsys_dump_pmap_init(sizeof(pgbuf) / PAGE_SIZE);
177 
178 	for (;;) {
179 		buf = dumpsys_dump_pmap(ctx, pgbuf, &nbytes);
180 		if (buf == NULL)
181 			break;
182 		error = blk_write(di, buf, 0, nbytes);
183 		if (error)
184 			return (error);
185 	}
186 
187 	return (0);
188 }
189 
190 int
191 cpu_minidumpsys(struct dumperinfo *di, const struct minidumpstate *state)
192 {
193 	vm_paddr_t pa;
194 	int error, retry_count;
195 	uint32_t pmapsize;
196 	struct minidumphdr mdhdr;
197 	struct msgbuf *mbp;
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(state->dump_bitset);
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 	mbp = state->msgbufp;
214 	dumpsize = PAGE_SIZE;				/* header */
215 	dumpsize += round_page(mbp->msg_size);
216 	dumpsize += round_page(sizeof(dump_avail));
217 	dumpsize += round_page(BITSET_SIZE(vm_page_dump_pages));
218 	dumpsize += pmapsize;
219 	VM_PAGE_DUMP_FOREACH(state->dump_bitset, pa) {
220 		/* Clear out undumpable pages now if needed */
221 		if (vm_phys_is_dumpable(pa))
222 			dumpsize += PAGE_SIZE;
223 		else
224 			vm_page_dump_drop(state->dump_bitset, pa);
225 	}
226 	dumpsys_pb_init(dumpsize);
227 
228 	/* Initialize mdhdr */
229 	bzero(&mdhdr, sizeof(mdhdr));
230 	strcpy(mdhdr.magic, MINIDUMP_MAGIC);
231 	strncpy(mdhdr.mmu_name, pmap_mmu_name(), sizeof(mdhdr.mmu_name) - 1);
232 	mdhdr.version = MINIDUMP_VERSION;
233 	mdhdr.msgbufsize = mbp->msg_size;
234 	mdhdr.bitmapsize = round_page(BITSET_SIZE(vm_page_dump_pages));
235 	mdhdr.pmapsize = pmapsize;
236 	mdhdr.kernbase = VM_MIN_KERNEL_ADDRESS;
237 	mdhdr.kernend = VM_MAX_SAFE_KERNEL_ADDRESS;
238 	mdhdr.dmapbase = DMAP_BASE_ADDRESS;
239 	mdhdr.dmapend = DMAP_MAX_ADDRESS;
240 	mdhdr.hw_direct_map = hw_direct_map;
241 	mdhdr.startkernel = __startkernel;
242 	mdhdr.endkernel = __endkernel;
243 	mdhdr.dumpavailsize = round_page(sizeof(dump_avail));
244 
245 	dump_init_header(di, &kdh, KERNELDUMPMAGIC, KERNELDUMP_POWERPC_VERSION,
246 	    dumpsize);
247 
248 	error = dump_start(di, &kdh);
249 	if (error)
250 		goto fail;
251 
252 	printf("Dumping %lu out of %ju MB:", dumpsize >> 20,
253 	    ptoa((uintmax_t)physmem) / 1048576);
254 
255 	/* Dump minidump header */
256 	bzero(pgbuf, sizeof(pgbuf));
257 	memcpy(pgbuf, &mdhdr, sizeof(mdhdr));
258 	error = blk_write(di, pgbuf, 0, PAGE_SIZE);
259 	if (error)
260 		goto fail;
261 	dump_total("header", PAGE_SIZE);
262 
263 	/* Dump msgbuf up front */
264 	error = blk_write(di, mbp->msg_ptr, 0, round_page(mbp->msg_size));
265 	dump_total("msgbuf", round_page(mbp->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(state->dump_bitset, 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