]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/arm64/arm64/minidump_machdep.c
Sparsify the vm_page_dump bitmap
[FreeBSD/FreeBSD.git] / sys / arm64 / arm64 / minidump_machdep.c
1 /*-
2  * Copyright (c) 2006 Peter Wemm
3  * Copyright (c) 2015 The FreeBSD Foundation
4  * All rights reserved.
5  *
6  * This software was developed by Andrew Turner under
7  * sponsorship from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31
32 #include <sys/cdefs.h>
33 __FBSDID("$FreeBSD$");
34
35 #include "opt_watchdog.h"
36
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/conf.h>
40 #include <sys/cons.h>
41 #include <sys/kernel.h>
42 #include <sys/kerneldump.h>
43 #include <sys/msgbuf.h>
44 #include <sys/watchdog.h>
45 #include <sys/vmmeter.h>
46
47 #include <vm/vm.h>
48 #include <vm/vm_param.h>
49 #include <vm/vm_page.h>
50 #include <vm/vm_phys.h>
51 #include <vm/pmap.h>
52
53 #include <machine/md_var.h>
54 #include <machine/pte.h>
55 #include <machine/minidump.h>
56
57 CTASSERT(sizeof(struct kerneldumpheader) == 512);
58
59 static struct kerneldumpheader kdh;
60
61 /* Handle chunked writes. */
62 static size_t fragsz;
63 static void *dump_va;
64 static size_t counter, progress, dumpsize;
65
66 static uint64_t tmpbuffer[Ln_ENTRIES];
67
68 static int
69 is_dumpable(vm_paddr_t pa)
70 {
71         vm_page_t m;
72         int i;
73
74         if ((m = vm_phys_paddr_to_vm_page(pa)) != NULL)
75                 return ((m->flags & PG_NODUMP) == 0);
76         for (i = 0; dump_avail[i] != 0 || dump_avail[i + 1] != 0; i += 2) {
77                 if (pa >= dump_avail[i] && pa < dump_avail[i + 1])
78                         return (1);
79         }
80         return (0);
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, dump_va, 0, fragsz);
92         fragsz = 0;
93         return (error);
94 }
95
96 static struct {
97         int min_per;
98         int max_per;
99         int visited;
100 } progress_track[10] = {
101         {  0,  10, 0},
102         { 10,  20, 0},
103         { 20,  30, 0},
104         { 30,  40, 0},
105         { 40,  50, 0},
106         { 50,  60, 0},
107         { 60,  70, 0},
108         { 70,  80, 0},
109         { 80,  90, 0},
110         { 90, 100, 0}
111 };
112
113 static void
114 report_progress(size_t progress, size_t dumpsize)
115 {
116         int sofar, i;
117
118         sofar = 100 - ((progress * 100) / dumpsize);
119         for (i = 0; i < nitems(progress_track); i++) {
120                 if (sofar < progress_track[i].min_per ||
121                     sofar > progress_track[i].max_per)
122                         continue;
123                 if (progress_track[i].visited)
124                         return;
125                 progress_track[i].visited = 1;
126                 printf("..%d%%", sofar);
127                 return;
128         }
129 }
130
131 static int
132 blk_write(struct dumperinfo *di, char *ptr, vm_paddr_t pa, size_t sz)
133 {
134         size_t len;
135         int error, c;
136         u_int maxdumpsz;
137
138         maxdumpsz = min(di->maxiosize, MAXDUMPPGS * PAGE_SIZE);
139         if (maxdumpsz == 0)     /* seatbelt */
140                 maxdumpsz = PAGE_SIZE;
141         error = 0;
142         if ((sz % PAGE_SIZE) != 0) {
143                 printf("size not page aligned\n");
144                 return (EINVAL);
145         }
146         if (ptr != NULL && pa != 0) {
147                 printf("cant have both va and pa!\n");
148                 return (EINVAL);
149         }
150         if ((((uintptr_t)pa) % PAGE_SIZE) != 0) {
151                 printf("address not page aligned %p\n", ptr);
152                 return (EINVAL);
153         }
154         if (ptr != NULL) {
155                 /*
156                  * If we're doing a virtual dump, flush any
157                  * pre-existing pa pages.
158                  */
159                 error = blk_flush(di);
160                 if (error)
161                         return (error);
162         }
163         while (sz) {
164                 len = maxdumpsz - fragsz;
165                 if (len > sz)
166                         len = sz;
167                 counter += len;
168                 progress -= len;
169                 if (counter >> 22) {
170                         report_progress(progress, dumpsize);
171                         counter &= (1 << 22) - 1;
172                 }
173
174                 wdog_kern_pat(WD_LASTVAL);
175
176                 if (ptr) {
177                         error = dump_append(di, ptr, 0, len);
178                         if (error)
179                                 return (error);
180                         ptr += len;
181                         sz -= len;
182                 } else {
183                         dump_va = (void *)PHYS_TO_DMAP(pa);
184                         fragsz += len;
185                         pa += len;
186                         sz -= len;
187                         error = blk_flush(di);
188                         if (error)
189                                 return (error);
190                 }
191
192                 /* Check for user abort. */
193                 c = cncheckc();
194                 if (c == 0x03)
195                         return (ECANCELED);
196                 if (c != -1)
197                         printf(" (CTRL-C to abort) ");
198         }
199
200         return (0);
201 }
202
203 int
204 minidumpsys(struct dumperinfo *di)
205 {
206         struct minidumphdr mdhdr;
207         pd_entry_t *l0, *l1, *l2;
208         pt_entry_t *l3;
209         vm_offset_t va;
210         vm_paddr_t pa;
211         uint32_t pmapsize;
212         int error, i, j, retry_count;
213
214         retry_count = 0;
215  retry:
216         retry_count++;
217         error = 0;
218         pmapsize = 0;
219         for (va = VM_MIN_KERNEL_ADDRESS; va < kernel_vm_end; va += L2_SIZE) {
220                 pmapsize += PAGE_SIZE;
221                 if (!pmap_get_tables(pmap_kernel(), va, &l0, &l1, &l2, &l3))
222                         continue;
223
224                 if ((*l1 & ATTR_DESCR_MASK) == L1_BLOCK) {
225                         pa = *l1 & ~ATTR_MASK;
226                         for (i = 0; i < Ln_ENTRIES * Ln_ENTRIES;
227                             i++, pa += PAGE_SIZE)
228                                 if (is_dumpable(pa))
229                                         dump_add_page(pa);
230                         pmapsize += (Ln_ENTRIES - 1) * PAGE_SIZE;
231                         va += L1_SIZE - L2_SIZE;
232                 } else if ((*l2 & ATTR_DESCR_MASK) == L2_BLOCK) {
233                         pa = *l2 & ~ATTR_MASK;
234                         for (i = 0; i < Ln_ENTRIES; i++, pa += PAGE_SIZE) {
235                                 if (is_dumpable(pa))
236                                         dump_add_page(pa);
237                         }
238                 } else if ((*l2 & ATTR_DESCR_MASK) == L2_TABLE) {
239                         for (i = 0; i < Ln_ENTRIES; i++) {
240                                 if ((l3[i] & ATTR_DESCR_MASK) != L3_PAGE)
241                                         continue;
242                                 pa = l3[i] & ~ATTR_MASK;
243                                 if (is_dumpable(pa))
244                                         dump_add_page(pa);
245                         }
246                 }
247         }
248
249         /* Calculate dump size. */
250         dumpsize = pmapsize;
251         dumpsize += round_page(msgbufp->msg_size);
252         dumpsize += round_page(sizeof(dump_avail));
253         dumpsize += round_page(BITSET_SIZE(vm_page_dump_pages));
254         VM_PAGE_DUMP_FOREACH(pa) {
255                 if (is_dumpable(pa))
256                         dumpsize += PAGE_SIZE;
257                 else
258                         dump_drop_page(pa);
259         }
260         dumpsize += PAGE_SIZE;
261
262         progress = dumpsize;
263
264         /* Initialize mdhdr */
265         bzero(&mdhdr, sizeof(mdhdr));
266         strcpy(mdhdr.magic, MINIDUMP_MAGIC);
267         mdhdr.version = MINIDUMP_VERSION;
268         mdhdr.msgbufsize = msgbufp->msg_size;
269         mdhdr.bitmapsize = round_page(BITSET_SIZE(vm_page_dump_pages));
270         mdhdr.pmapsize = pmapsize;
271         mdhdr.kernbase = VM_MIN_KERNEL_ADDRESS;
272         mdhdr.dmapphys = DMAP_MIN_PHYSADDR;
273         mdhdr.dmapbase = DMAP_MIN_ADDRESS;
274         mdhdr.dmapend = DMAP_MAX_ADDRESS;
275         mdhdr.dumpavailsize = round_page(sizeof(dump_avail));
276
277         dump_init_header(di, &kdh, KERNELDUMPMAGIC, KERNELDUMP_AARCH64_VERSION,
278             dumpsize);
279
280         error = dump_start(di, &kdh);
281         if (error != 0)
282                 goto fail;
283
284         printf("Dumping %llu out of %ju MB:", (long long)dumpsize >> 20,
285             ptoa((uintmax_t)physmem) / 1048576);
286
287         /* Dump my header */
288         bzero(&tmpbuffer, sizeof(tmpbuffer));
289         bcopy(&mdhdr, &tmpbuffer, sizeof(mdhdr));
290         error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
291         if (error)
292                 goto fail;
293
294         /* Dump msgbuf up front */
295         error = blk_write(di, (char *)msgbufp->msg_ptr, 0,
296             round_page(msgbufp->msg_size));
297         if (error)
298                 goto fail;
299
300         /* Dump dump_avail */
301         _Static_assert(sizeof(dump_avail) <= sizeof(tmpbuffer),
302             "Large dump_avail not handled");
303         bzero(tmpbuffer, sizeof(tmpbuffer));
304         memcpy(tmpbuffer, dump_avail, sizeof(dump_avail));
305         error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
306         if (error)
307                 goto fail;
308
309         /* Dump bitmap */
310         error = blk_write(di, (char *)vm_page_dump, 0,
311             round_page(BITSET_SIZE(vm_page_dump_pages)));
312         if (error)
313                 goto fail;
314
315         /* Dump kernel page directory pages */
316         bzero(&tmpbuffer, sizeof(tmpbuffer));
317         for (va = VM_MIN_KERNEL_ADDRESS; va < kernel_vm_end; va += L2_SIZE) {
318                 if (!pmap_get_tables(pmap_kernel(), va, &l0, &l1, &l2, &l3)) {
319                         /* We always write a page, even if it is zero */
320                         error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
321                         if (error)
322                                 goto fail;
323                         /* flush, in case we reuse tmpbuffer in the same block*/
324                         error = blk_flush(di);
325                         if (error)
326                                 goto fail;
327                 } else if ((*l1 & ATTR_DESCR_MASK) == L1_BLOCK) {
328                         /*
329                          * Handle a 1GB block mapping: write out 512 fake L2
330                          * pages.
331                          */
332                         pa = (*l1 & ~ATTR_MASK) | (va & L1_OFFSET);
333
334                         for (i = 0; i < Ln_ENTRIES; i++) {
335                                 for (j = 0; j < Ln_ENTRIES; j++) {
336                                         tmpbuffer[j] = pa + i * L2_SIZE +
337                                             j * PAGE_SIZE | ATTR_DEFAULT |
338                                             L3_PAGE;
339                                 }
340                                 error = blk_write(di, (char *)&tmpbuffer, 0,
341                                     PAGE_SIZE);
342                                 if (error)
343                                         goto fail;
344                         }
345                         /* flush, in case we reuse tmpbuffer in the same block*/
346                         error = blk_flush(di);
347                         if (error)
348                                 goto fail;
349                         bzero(&tmpbuffer, sizeof(tmpbuffer));
350                         va += L1_SIZE - L2_SIZE;
351                 } else if ((*l2 & ATTR_DESCR_MASK) == L2_BLOCK) {
352                         pa = (*l2 & ~ATTR_MASK) | (va & L2_OFFSET);
353
354                         /* Generate fake l3 entries based upon the l1 entry */
355                         for (i = 0; i < Ln_ENTRIES; i++) {
356                                 tmpbuffer[i] = pa + (i * PAGE_SIZE) |
357                                     ATTR_DEFAULT | L3_PAGE;
358                         }
359                         error = blk_write(di, (char *)&tmpbuffer, 0, PAGE_SIZE);
360                         if (error)
361                                 goto fail;
362                         /* flush, in case we reuse fakepd in the same block */
363                         error = blk_flush(di);
364                         if (error)
365                                 goto fail;
366                         bzero(&tmpbuffer, sizeof(tmpbuffer));
367                         continue;
368                 } else {
369                         pa = *l2 & ~ATTR_MASK;
370
371                         error = blk_write(di, NULL, pa, PAGE_SIZE);
372                         if (error)
373                                 goto fail;
374                 }
375         }
376
377         /* Dump memory chunks */
378         VM_PAGE_DUMP_FOREACH(pa) {
379                 error = blk_write(di, 0, pa, PAGE_SIZE);
380                 if (error)
381                         goto fail;
382         }
383
384         error = blk_flush(di);
385         if (error)
386                 goto fail;
387
388         error = dump_finish(di, &kdh);
389         if (error != 0)
390                 goto fail;
391
392         printf("\nDump complete\n");
393         return (0);
394
395 fail:
396         if (error < 0)
397                 error = -error;
398
399         printf("\n");
400         if (error == ENOSPC) {
401                 printf("Dump map grown while dumping. ");
402                 if (retry_count < 5) {
403                         printf("Retrying...\n");
404                         goto retry;
405                 }
406                 printf("Dump failed.\n");
407         }
408         else if (error == ECANCELED)
409                 printf("Dump aborted\n");
410         else if (error == E2BIG) {
411                 printf("Dump failed. Partition too small (about %lluMB were "
412                     "needed this time).\n", (long long)dumpsize >> 20);
413         } else
414                 printf("** DUMP FAILED (ERROR %d) **\n", error);
415         return (error);
416 }