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
55 CTASSERT(sizeof(struct kerneldumpheader) == 512);
56
57 static struct kerneldumpheader kdh;
58
59 /* Handle chunked writes. */
60 static size_t fragsz;
61 static void *dump_va;
62
63 static int
blk_flush(struct dumperinfo * di)64 blk_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
76 static int
blk_write(struct dumperinfo * di,char * ptr,vm_paddr_t pa,size_t sz)77 blk_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. */
147 static char dumpbuf[PAGE_SIZE] __aligned(sizeof(uint64_t));
148 CTASSERT(sizeof(dumpbuf) % sizeof(pt2_entry_t) == 0);
149
150 int
cpu_minidumpsys(struct dumperinfo * di,const struct minidumpstate * state)151 cpu_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
313 fail:
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