1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2006 Peter Wemm
5 * Copyright (c) 2008 Semihalf, Grzegorz Bernacki
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 *
29 * from: FreeBSD: src/sys/i386/i386/minidump_machdep.c,v 1.6 2008/08/17 23:27:27
30 */
31
32#include <sys/cdefs.h>
33#include "opt_watchdog.h"
34
35#include <sys/param.h>
36#include <sys/systm.h>
37#include <sys/conf.h>
38#include <sys/cons.h>
39#include <sys/kernel.h>
40#include <sys/kerneldump.h>
41#include <sys/msgbuf.h>
42#include <sys/watchdog.h>
43#include <vm/vm.h>
44#include <vm/vm_param.h>
45#include <vm/vm_page.h>
46#include <vm/vm_phys.h>
47#include <vm/vm_dumpset.h>
48#include <vm/pmap.h>
49#include <machine/atomic.h>
50#include <machine/cpu.h>
51#include <machine/elf.h>
52#include <machine/md_var.h>
53#include <machine/minidump.h>
54
55CTASSERT(sizeof(struct kerneldumpheader) == 512);
56
57static struct kerneldumpheader kdh;
58
59/* Handle chunked writes. */
60static size_t fragsz;
61static void *dump_va;
62
63static int
64blk_flush(struct dumperinfo *di)
65{
66	int error;
67
68	if (fragsz == 0)
69		return (0);
70
71	error = dump_append(di, dump_va, fragsz);
72	fragsz = 0;
73	return (error);
74}
75
76static int
77blk_write(struct dumperinfo *di, char *ptr, vm_paddr_t pa, size_t sz)
78{
79	size_t len;
80	int error, i, c;
81	u_int maxdumpsz;
82
83	maxdumpsz = min(di->maxiosize, MAXDUMPPGS * PAGE_SIZE);
84	if (maxdumpsz == 0)	/* seatbelt */
85		maxdumpsz = PAGE_SIZE;
86	error = 0;
87	if (ptr != NULL && pa != 0) {
88		printf("cant have both va and pa!\n");
89		return (EINVAL);
90	}
91	if (pa != 0) {
92		if ((sz % PAGE_SIZE) != 0) {
93			printf("size not page aligned\n");
94			return (EINVAL);
95		}
96		if ((pa & PAGE_MASK) != 0) {
97			printf("address not page aligned\n");
98			return (EINVAL);
99		}
100	}
101	if (ptr != NULL) {
102		/* Flush any pre-existing pa pages before a virtual dump. */
103		error = blk_flush(di);
104		if (error)
105			return (error);
106	}
107	while (sz) {
108		len = maxdumpsz - fragsz;
109		if (len > sz)
110			len = sz;
111
112		dumpsys_pb_progress(len);
113		wdog_kern_pat(WD_LASTVAL);
114
115		if (ptr) {
116			error = dump_append(di, ptr, len);
117			if (error)
118				return (error);
119			ptr += len;
120			sz -= len;
121		} else {
122			for (i = 0; i < len; i += PAGE_SIZE)
123				dump_va = pmap_kenter_temporary(pa + i,
124				    (i + fragsz) >> PAGE_SHIFT);
125			fragsz += len;
126			pa += len;
127			sz -= len;
128			if (fragsz == maxdumpsz) {
129				error = blk_flush(di);
130				if (error)
131					return (error);
132			}
133		}
134
135		/* Check for user abort. */
136		c = cncheckc();
137		if (c == 0x03)
138			return (ECANCELED);
139		if (c != -1)
140			printf(" (CTRL-C to abort) ");
141	}
142
143	return (0);
144}
145
146/* A buffer for general use. Its size must be one page at least. */
147static char dumpbuf[PAGE_SIZE] __aligned(sizeof(uint64_t));
148CTASSERT(sizeof(dumpbuf) % sizeof(pt2_entry_t) == 0);
149
150int
151cpu_minidumpsys(struct dumperinfo *di, const struct minidumpstate *state)
152{
153	struct minidumphdr mdhdr;
154	struct msgbuf *mbp;
155	uint64_t dumpsize, *dump_avail_buf;
156	uint32_t ptesize;
157	uint32_t pa, prev_pa = 0, count = 0;
158	vm_offset_t va, kva_end;
159	int error, i;
160	char *addr;
161
162	/*
163	 * Flush caches.  Note that in the SMP case this operates only on the
164	 * current CPU's L1 cache.  Before we reach this point, code in either
165	 * the system shutdown or kernel debugger has called stop_cpus() to stop
166	 * all cores other than this one.  Part of the ARM handling of
167	 * stop_cpus() is to call wbinv_all() on that core's local L1 cache.  So
168	 * by time we get to here, all that remains is to flush the L1 for the
169	 * current CPU, then the L2.
170	 */
171	dcache_wbinv_poc_all();
172
173	/* Snapshot the KVA upper bound in case it grows. */
174	kva_end = kernel_vm_end;
175
176	/*
177	 * Walk the kernel page table pages, setting the active entries in the
178	 * dump bitmap.
179	 */
180	ptesize = 0;
181	for (va = KERNBASE; va < kva_end; va += PAGE_SIZE) {
182		pa = pmap_dump_kextract(va, NULL);
183		if (pa != 0 && vm_phys_is_dumpable(pa))
184			vm_page_dump_add(state->dump_bitset, pa);
185		ptesize += sizeof(pt2_entry_t);
186	}
187
188	/* Calculate dump size. */
189	mbp = state->msgbufp;
190	dumpsize = ptesize;
191	dumpsize += round_page(mbp->msg_size);
192	dumpsize += round_page(nitems(dump_avail) * sizeof(uint64_t));
193	dumpsize += round_page(BITSET_SIZE(vm_page_dump_pages));
194	VM_PAGE_DUMP_FOREACH(state->dump_bitset, pa) {
195		/* Clear out undumpable pages now if needed */
196		if (vm_phys_is_dumpable(pa))
197			dumpsize += PAGE_SIZE;
198		else
199			vm_page_dump_drop(state->dump_bitset, pa);
200	}
201	dumpsize += PAGE_SIZE;
202
203	dumpsys_pb_init(dumpsize);
204
205	/* Initialize mdhdr */
206	bzero(&mdhdr, sizeof(mdhdr));
207	strcpy(mdhdr.magic, MINIDUMP_MAGIC);
208	mdhdr.version = MINIDUMP_VERSION;
209	mdhdr.msgbufsize = mbp->msg_size;
210	mdhdr.bitmapsize = round_page(BITSET_SIZE(vm_page_dump_pages));
211	mdhdr.ptesize = ptesize;
212	mdhdr.kernbase = KERNBASE;
213	mdhdr.arch = __ARM_ARCH;
214	mdhdr.mmuformat = MINIDUMP_MMU_FORMAT_V6;
215	mdhdr.dumpavailsize = round_page(nitems(dump_avail) * sizeof(uint64_t));
216
217	dump_init_header(di, &kdh, KERNELDUMPMAGIC, KERNELDUMP_ARM_VERSION,
218	    dumpsize);
219
220	error = dump_start(di, &kdh);
221	if (error != 0)
222		goto fail;
223
224	printf("Physical memory: %u MB\n", ptoa((uintmax_t)physmem) / 1048576);
225	printf("Dumping %llu MB:", (long long)dumpsize >> 20);
226
227	/* Dump my header */
228	bzero(dumpbuf, sizeof(dumpbuf));
229	bcopy(&mdhdr, dumpbuf, sizeof(mdhdr));
230	error = blk_write(di, dumpbuf, 0, PAGE_SIZE);
231	if (error)
232		goto fail;
233
234	/* Dump msgbuf up front */
235	error = blk_write(di, mbp->msg_ptr, 0, round_page(mbp->msg_size));
236	if (error)
237		goto fail;
238
239	/* Dump dump_avail.  Make a copy using 64-bit physical addresses. */
240	_Static_assert(nitems(dump_avail) * sizeof(uint64_t) <= sizeof(dumpbuf),
241	    "Large dump_avail not handled");
242	bzero(dumpbuf, sizeof(dumpbuf));
243	dump_avail_buf = (uint64_t *)dumpbuf;
244	for (i = 0; dump_avail[i] != 0 || dump_avail[i + 1] != 0; i += 2) {
245		dump_avail_buf[i] = dump_avail[i];
246		dump_avail_buf[i + 1] = dump_avail[i + 1];
247	}
248	error = blk_write(di, dumpbuf, 0, PAGE_SIZE);
249	if (error)
250		goto fail;
251
252	/* Dump bitmap */
253	error = blk_write(di, (char *)state->dump_bitset, 0,
254	    round_page(BITSET_SIZE(vm_page_dump_pages)));
255	if (error)
256		goto fail;
257
258	/* Dump kernel page table pages */
259	addr = dumpbuf;
260	for (va = KERNBASE; va < kva_end; va += PAGE_SIZE) {
261		pmap_dump_kextract(va, (pt2_entry_t *)addr);
262		addr += sizeof(pt2_entry_t);
263		if (addr == dumpbuf + sizeof(dumpbuf)) {
264			error = blk_write(di, dumpbuf, 0, sizeof(dumpbuf));
265			if (error != 0)
266				goto fail;
267			addr = dumpbuf;
268		}
269	}
270	if (addr != dumpbuf) {
271		error = blk_write(di, dumpbuf, 0, addr - dumpbuf);
272		if (error != 0)
273			goto fail;
274	}
275
276	/* Dump memory chunks */
277	VM_PAGE_DUMP_FOREACH(state->dump_bitset, pa) {
278		if (!count) {
279			prev_pa = pa;
280			count++;
281		} else {
282			if (pa == (prev_pa + count * PAGE_SIZE))
283				count++;
284			else {
285				error = blk_write(di, NULL, prev_pa,
286				    count * PAGE_SIZE);
287				if (error)
288					goto fail;
289				count = 1;
290				prev_pa = pa;
291			}
292		}
293	}
294	if (count) {
295		error = blk_write(di, NULL, prev_pa, count * PAGE_SIZE);
296		if (error)
297			goto fail;
298		count = 0;
299		prev_pa = 0;
300	}
301
302	error = blk_flush(di);
303	if (error)
304		goto fail;
305
306	error = dump_finish(di, &kdh);
307	if (error != 0)
308		goto fail;
309
310	printf("\nDump complete\n");
311	return (0);
312
313fail:
314	if (error < 0)
315		error = -error;
316
317	if (error == ECANCELED)
318		printf("\nDump aborted\n");
319	else if (error == E2BIG || error == ENOSPC) {
320		printf("\nDump failed. Partition too small (about %lluMB were "
321		    "needed this time).\n", (long long)dumpsize >> 20);
322	} else
323		printf("\n** DUMP FAILED (ERROR %d) **\n", error);
324	return (error);
325}
326