From 8e321b79437e9e70ff57785e8508c1d2734d8a7f Mon Sep 17 00:00:00 2001 From: Rafal Jaworowski Date: Thu, 6 Nov 2008 16:20:27 +0000 Subject: [PATCH] Support kernel crash mini dumps on ARM architecture. Obtained from: Juniper Networks, Semihalf --- lib/libkvm/Makefile | 2 +- lib/libkvm/kvm_arm.c | 24 +- lib/libkvm/kvm_minidump_arm.c | 262 ++++++++++++++++++ lib/libkvm/kvm_private.h | 2 +- sys/arm/arm/dump_machdep.c | 18 +- sys/arm/arm/minidump_machdep.c | 483 +++++++++++++++++++++++++++++++++ sys/arm/arm/pmap.c | 21 ++ sys/arm/include/md_var.h | 6 + sys/arm/include/minidump.h | 45 +++ sys/arm/include/pmap.h | 1 + sys/conf/files.arm | 1 + sys/vm/vm_page.c | 2 +- 12 files changed, 858 insertions(+), 9 deletions(-) create mode 100644 lib/libkvm/kvm_minidump_arm.c create mode 100644 sys/arm/arm/minidump_machdep.c create mode 100644 sys/arm/include/minidump.h diff --git a/lib/libkvm/Makefile b/lib/libkvm/Makefile index 3ca8427e199a..52a22caf8f47 100644 --- a/lib/libkvm/Makefile +++ b/lib/libkvm/Makefile @@ -11,7 +11,7 @@ CFLAGS+=-DSUN4V SRCS= kvm.c kvm_${MACHINE_ARCH}.c kvm_cptime.c kvm_file.c kvm_getloadavg.c \ kvm_getswapinfo.c kvm_pcpu.c kvm_proc.c -.if ${MACHINE_ARCH} == "amd64" || ${MACHINE_ARCH} == "i386" +.if ${MACHINE_ARCH} == "amd64" || ${MACHINE_ARCH} == "i386" || ${MACHINE_ARCH} == "arm" SRCS+= kvm_minidump_${MACHINE_ARCH}.c .endif INCS= kvm.h diff --git a/lib/libkvm/kvm_arm.c b/lib/libkvm/kvm_arm.c index 631aff600364..489cfe0545a4 100644 --- a/lib/libkvm/kvm_arm.c +++ b/lib/libkvm/kvm_arm.c @@ -52,10 +52,13 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include "kvm_private.h" +/* minidump must be the first item! */ struct vmstate { + int minidump; /* 1 = minidump mode */ pd_entry_t *l1pt; void *mmapbase; size_t mmapsize; @@ -107,6 +110,8 @@ void _kvm_freevtop(kvm_t *kd) { if (kd->vmst != 0) { + if (kd->vmst->minidump) + return (_kvm_minidump_freevtop(kd)); if (kd->vmst->mmapbase != NULL) munmap(kd->vmst->mmapbase, kd->vmst->mmapsize); free(kd->vmst); @@ -117,13 +122,25 @@ _kvm_freevtop(kvm_t *kd) int _kvm_initvtop(kvm_t *kd) { - struct vmstate *vm = _kvm_malloc(kd, sizeof(*vm)); + struct vmstate *vm; struct nlist nlist[2]; u_long kernbase, physaddr, pa; pd_entry_t *l1pt; Elf32_Ehdr *ehdr; size_t hdrsz; - + char minihdr[8]; + + if (!kd->rawdump) { + if (pread(kd->pmfd, &minihdr, 8, 0) == 8) { + if (memcmp(&minihdr, "minidump", 8) == 0) + return (_kvm_minidump_initvtop(kd)); + } else { + _kvm_err(kd, kd->program, "cannot read header"); + return (-1); + } + } + + vm = _kvm_malloc(kd, sizeof(*vm)); if (vm == 0) { _kvm_err(kd, kd->program, "cannot allocate vm"); return (-1); @@ -193,6 +210,9 @@ _kvm_kvatop(kvm_t *kd, u_long va, off_t *pa) pt_entry_t pte; u_long pte_pa; + if (kd->vmst->minidump) + return (_kvm_minidump_kvatop(kd, va, pa)); + if (vm->l1pt == NULL) return (_kvm_pa2off(kd, va, pa, PAGE_SIZE)); pd = vm->l1pt[L1_IDX(va)]; diff --git a/lib/libkvm/kvm_minidump_arm.c b/lib/libkvm/kvm_minidump_arm.c new file mode 100644 index 000000000000..d48c1bcb8e60 --- /dev/null +++ b/lib/libkvm/kvm_minidump_arm.c @@ -0,0 +1,262 @@ +/*- + * Copyright (c) 2008 Semihalf, Grzegorz Bernacki + * Copyright (c) 2006 Peter Wemm + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * From: FreeBSD: src/lib/libkvm/kvm_minidump_i386.c,v 1.2 2006/06/05 08:51:14 + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * ARM machine dependent routines for kvm and minidumps. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include "kvm_private.h" + +struct hpte { + struct hpte *next; + uint64_t pa; + int64_t off; +}; + +#define HPT_SIZE 1024 + +/* minidump must be the first field */ +struct vmstate { + int minidump; /* 1 = minidump mode */ + struct minidumphdr hdr; + void *hpt_head[HPT_SIZE]; + uint32_t *bitmap; + void *ptemap; +}; + +static void +hpt_insert(kvm_t *kd, uint64_t pa, int64_t off) +{ + struct hpte *hpte; + uint32_t fnv = FNV1_32_INIT; + + fnv = fnv_32_buf(&pa, sizeof(pa), fnv); + fnv &= (HPT_SIZE - 1); + hpte = malloc(sizeof(*hpte)); + hpte->pa = pa; + hpte->off = off; + hpte->next = kd->vmst->hpt_head[fnv]; + kd->vmst->hpt_head[fnv] = hpte; +} + +static int64_t +hpt_find(kvm_t *kd, uint64_t pa) +{ + struct hpte *hpte; + uint32_t fnv = FNV1_32_INIT; + + fnv = fnv_32_buf(&pa, sizeof(pa), fnv); + fnv &= (HPT_SIZE - 1); + for (hpte = kd->vmst->hpt_head[fnv]; hpte != NULL; hpte = hpte->next) + if (pa == hpte->pa) + return (hpte->off); + + return (-1); +} + +static int +inithash(kvm_t *kd, uint32_t *base, int len, off_t off) +{ + uint64_t idx, pa; + uint32_t bit, bits; + + for (idx = 0; idx < len / sizeof(*base); idx++) { + bits = base[idx]; + while (bits) { + bit = ffs(bits) - 1; + bits &= ~(1ul << bit); + pa = (idx * sizeof(*base) * NBBY + bit) * PAGE_SIZE; + hpt_insert(kd, pa, off); + off += PAGE_SIZE; + } + } + return (off); +} + +void +_kvm_minidump_freevtop(kvm_t *kd) +{ + struct vmstate *vm = kd->vmst; + + if (vm->bitmap) + free(vm->bitmap); + if (vm->ptemap) + free(vm->ptemap); + free(vm); + kd->vmst = NULL; +} + +int +_kvm_minidump_initvtop(kvm_t *kd) +{ + u_long pa; + struct vmstate *vmst; + off_t off; + + vmst = _kvm_malloc(kd, sizeof(*vmst)); + if (vmst == 0) { + _kvm_err(kd, kd->program, "cannot allocate vm"); + return (-1); + } + + kd->vmst = vmst; + vmst->minidump = 1; + + if (pread(kd->pmfd, &vmst->hdr, + sizeof(vmst->hdr), 0) != sizeof(vmst->hdr)) { + _kvm_err(kd, kd->program, "cannot read dump header"); + return (-1); + } + + if (strncmp(MINIDUMP_MAGIC, vmst->hdr.magic, + sizeof(vmst->hdr.magic)) != 0) { + _kvm_err(kd, kd->program, "not a minidump for this platform"); + return (-1); + } + if (vmst->hdr.version != MINIDUMP_VERSION) { + _kvm_err(kd, kd->program, "wrong minidump version. " + "Expected %d got %d", MINIDUMP_VERSION, vmst->hdr.version); + return (-1); + } + + /* Skip header and msgbuf */ + off = PAGE_SIZE + round_page(vmst->hdr.msgbufsize); + + vmst->bitmap = _kvm_malloc(kd, vmst->hdr.bitmapsize); + if (vmst->bitmap == NULL) { + _kvm_err(kd, kd->program, "cannot allocate %d bytes for " + "bitmap", vmst->hdr.bitmapsize); + return (-1); + } + + if (pread(kd->pmfd, vmst->bitmap, vmst->hdr.bitmapsize, off) != + vmst->hdr.bitmapsize) { + _kvm_err(kd, kd->program, "cannot read %d bytes for page bitmap", + vmst->hdr.bitmapsize); + return (-1); + } + off += round_page(vmst->hdr.bitmapsize); + + vmst->ptemap = _kvm_malloc(kd, vmst->hdr.ptesize); + if (vmst->ptemap == NULL) { + _kvm_err(kd, kd->program, "cannot allocate %d bytes for " + "ptemap", vmst->hdr.ptesize); + return (-1); + } + + if (pread(kd->pmfd, vmst->ptemap, vmst->hdr.ptesize, off) != + vmst->hdr.ptesize) { + _kvm_err(kd, kd->program, "cannot read %d bytes for ptemap", + vmst->hdr.ptesize); + return (-1); + } + + off += vmst->hdr.ptesize; + + /* Build physical address hash table for sparse pages */ + inithash(kd, vmst->bitmap, vmst->hdr.bitmapsize, off); + + return (0); +} + +int +_kvm_minidump_kvatop(kvm_t *kd, u_long va, off_t *pa) +{ + struct vmstate *vm; + pt_entry_t pte; + u_long offset, pteindex, a; + off_t ofs; + uint32_t *ptemap; + int i; + + if (ISALIVE(kd)) { + _kvm_err(kd, 0, "kvm_kvatop called in live kernel!"); + return (0); + } + + vm = kd->vmst; + ptemap = vm->ptemap; + + if (va >= vm->hdr.kernbase) { + pteindex = (va - vm->hdr.kernbase) >> PAGE_SHIFT; + pte = ptemap[pteindex]; + if (!pte) { + _kvm_err(kd, kd->program, "_kvm_vatop: pte not valid"); + goto invalid; + } + if ((pte & L2_TYPE_MASK) == L2_TYPE_L) { + offset = va & L2_L_OFFSET; + a = pte & L2_L_FRAME; + } else if ((pte & L2_TYPE_MASK) == L2_TYPE_S) { + offset = va & L2_S_OFFSET; + a = pte & L2_S_FRAME; + } else + goto invalid; + + ofs = hpt_find(kd, a); + if (ofs == -1) { + _kvm_err(kd, kd->program, "_kvm_vatop: physical " + "address 0x%lx not in minidump", a); + goto invalid; + } + + *pa = ofs + offset; + return (PAGE_SIZE - offset); + + } else + _kvm_err(kd, kd->program, "_kvm_vatop: virtual address 0x%lx " + "not minidumped", va); + +invalid: + _kvm_err(kd, 0, "invalid address (0x%lx)", va); + return (0); +} diff --git a/lib/libkvm/kvm_private.h b/lib/libkvm/kvm_private.h index e791b41b2fb5..88ff7166b59b 100644 --- a/lib/libkvm/kvm_private.h +++ b/lib/libkvm/kvm_private.h @@ -79,7 +79,7 @@ void _kvm_syserr (kvm_t *kd, const char *program, const char *fmt, ...) __printflike(3, 4); int _kvm_uvatop(kvm_t *, const struct proc *, u_long, u_long *); -#if defined(__amd64__) || defined(__i386__) +#if defined(__amd64__) || defined(__i386__) || defined(__arm__) void _kvm_minidump_freevtop(kvm_t *); int _kvm_minidump_initvtop(kvm_t *); int _kvm_minidump_kvatop(kvm_t *, u_long, off_t *); diff --git a/sys/arm/arm/dump_machdep.c b/sys/arm/arm/dump_machdep.c index 87943c251801..d253e5561b51 100644 --- a/sys/arm/arm/dump_machdep.c +++ b/sys/arm/arm/dump_machdep.c @@ -31,6 +31,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -44,6 +45,11 @@ __FBSDID("$FreeBSD$"); CTASSERT(sizeof(struct kerneldumpheader) == 512); +int do_minidump = 1; +TUNABLE_INT("debug.minidump", &do_minidump); +SYSCTL_INT(_debug, OID_AUTO, minidump, CTLFLAG_RW, &do_minidump, 0, + "Enable mini crash dumps"); + /* * Don't touch the first SIZEOF_METADATA bytes on the dump device. This * is to protect us from metadata and to protect metadata from us. @@ -155,11 +161,10 @@ cb_dumpdata(struct md_pa *mdp, int seqnr, void *arg) vm_offset_t va; uint32_t pgs; size_t counter, sz, chunk; - int c, error, twiddle; + int c, error; error = 0; /* catch case in which chunk size is 0 */ - counter = 0; /* Update twiddle every 16MB */ - twiddle = 0; + counter = 0; va = 0; pgs = mdp->md_size / PAGE_SIZE; pa = mdp->md_start; @@ -264,7 +269,12 @@ dumpsys(struct dumperinfo *di) off_t hdrgap; size_t hdrsz; int error; - + + if (do_minidump) { + minidumpsys(di); + return; + } + bzero(&ehdr, sizeof(ehdr)); ehdr.e_ident[EI_MAG0] = ELFMAG0; ehdr.e_ident[EI_MAG1] = ELFMAG1; diff --git a/sys/arm/arm/minidump_machdep.c b/sys/arm/arm/minidump_machdep.c new file mode 100644 index 000000000000..927a856f0df9 --- /dev/null +++ b/sys/arm/arm/minidump_machdep.c @@ -0,0 +1,483 @@ +/*- + * Copyright (c) 2008 Semihalf, Grzegorz Bernacki + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * from: FreeBSD: src/sys/i386/i386/minidump_machdep.c,v 1.6 2008/08/17 23:27:27 + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CTASSERT(sizeof(struct kerneldumpheader) == 512); + +/* + * Don't touch the first SIZEOF_METADATA bytes on the dump device. This + * is to protect us from metadata and to protect metadata from us. + */ +#define SIZEOF_METADATA (64*1024) + +uint32_t *vm_page_dump; +int vm_page_dump_size; + +static struct kerneldumpheader kdh; +static off_t dumplo; + +/* Handle chunked writes. */ +static size_t fragsz, offset; +static void *dump_va; +static uint64_t counter, progress; + +CTASSERT(sizeof(*vm_page_dump) == 4); + +static int +is_dumpable(vm_paddr_t pa) +{ + int i; + + for (i = 0; dump_avail[i] != 0 || dump_avail[i + 1] != 0; i += 2) { + if (pa >= dump_avail[i] && pa < dump_avail[i + 1]) + return (1); + } + return (0); +} + +#define PG2MB(pgs) (((pgs) + (1 << 8) - 1) >> 8) + +static int +blk_flush(struct dumperinfo *di) +{ + int error; + + if (fragsz == 0) + return (0); + + error = dump_write(di, (char*)dump_va + offset, 0, dumplo, fragsz - offset); + dumplo += (fragsz - offset); + fragsz = 0; + offset = 0; + return (error); +} + +static int +blk_write(struct dumperinfo *di, char *ptr, vm_paddr_t pa, size_t sz) +{ + size_t len; + int error, i, c; + u_int maxdumpsz; + + maxdumpsz = di->maxiosize; + + if (maxdumpsz == 0) /* seatbelt */ + maxdumpsz = PAGE_SIZE; + + error = 0; + + if (ptr != NULL && pa != 0) { + printf("cant have both va and pa!\n"); + return (EINVAL); + } + + if (ptr != NULL) { + /* If we're doing a virtual dump, flush any pre-existing pa pages */ + error = blk_flush(di); + if (error) + return (error); + } + + while (sz) { + if (fragsz == 0) { + offset = pa & PAGE_MASK; + fragsz += offset; + } + len = maxdumpsz - fragsz; + if (len > sz) + len = sz; + counter += len; + progress -= len; + + if (counter >> 22) { + printf(" %lld", PG2MB(progress >> PAGE_SHIFT)); + counter &= (1<<22) - 1; + } + + if (ptr) { + error = dump_write(di, ptr, 0, dumplo, len); + if (error) + return (error); + dumplo += len; + ptr += len; + sz -= len; + } else { + for (i = 0; i < len; i += PAGE_SIZE) + dump_va = pmap_kenter_temp(pa + i, + (i + fragsz) >> PAGE_SHIFT); + fragsz += len; + pa += len; + sz -= len; + if (fragsz == maxdumpsz) { + error = blk_flush(di); + if (error) + return (error); + } + } + + /* Check for user abort. */ + c = cncheckc(); + if (c == 0x03) + return (ECANCELED); + if (c != -1) + printf(" (CTRL-C to abort) "); + } + + return (0); +} + +static int +blk_write_cont(struct dumperinfo *di, vm_paddr_t pa, size_t sz) +{ + int error; + + error = blk_write(di, 0, pa, sz); + if (error) + return (error); + + error = blk_flush(di); + if (error) + return (error); + + return (0); +} + +/* A fake page table page, to avoid having to handle both 4K and 2M pages */ +static pt_entry_t fakept[NPTEPG]; + +void +minidumpsys(struct dumperinfo *di) +{ + struct minidumphdr mdhdr; + uint64_t dumpsize; + uint32_t ptesize; + uint32_t bits; + uint32_t pa, prev_pa = 0, count = 0; + vm_offset_t va; + pd_entry_t *pdp; + pt_entry_t *pt, *ptp; + int i, k, bit, error; + char *addr; + + /* Flush cache */ + cpu_idcache_wbinv_all(); + cpu_l2cache_wbinv_all(); + + counter = 0; + /* Walk page table pages, set bits in vm_page_dump */ + ptesize = 0; + for (va = KERNBASE; va < kernel_vm_end; va += NBPDR) { + /* + * We always write a page, even if it is zero. Each + * page written corresponds to 2MB of space + */ + ptesize += L2_TABLE_SIZE_REAL; + pmap_get_pde_pte(pmap_kernel(), va, &pdp, &ptp); + if (pmap_pde_v(pdp) && pmap_pde_section(pdp)) { + /* This is a section mapping 1M page. */ + pa = (*pdp & L1_S_ADDR_MASK) | (va & ~L1_S_ADDR_MASK); + for (k = 0; k < (L1_S_SIZE / PAGE_SIZE); k++) { + if (is_dumpable(pa)) + dump_add_page(pa); + pa += PAGE_SIZE; + } + continue; + } + if (pmap_pde_v(pdp) && pmap_pde_page(pdp)) { + /* Set bit for each valid page in this 1MB block */ + addr = pmap_kenter_temp(*pdp & L1_C_ADDR_MASK, 0); + pt = (pt_entry_t*)(addr + + (((uint32_t)*pdp & L1_C_ADDR_MASK) & PAGE_MASK)); + for (k = 0; k < 256; k++) { + if ((pt[k] & L2_TYPE_MASK) == L2_TYPE_L) { + pa = (pt[k] & L2_L_FRAME) | + (va & L2_L_OFFSET); + for (i = 0; i < 16; i++) { + if (is_dumpable(pa)) + dump_add_page(pa); + k++; + pa += PAGE_SIZE; + } + } else if ((pt[k] & L2_TYPE_MASK) == L2_TYPE_S) { + pa = (pt[k] & L2_S_FRAME) | + (va & L2_S_OFFSET); + if (is_dumpable(pa)) + dump_add_page(pa); + } + } + } else { + /* Nothing, we're going to dump a null page */ + } + } + + /* Calculate dump size. */ + dumpsize = ptesize; + dumpsize += round_page(msgbufp->msg_size); + dumpsize += round_page(vm_page_dump_size); + + for (i = 0; i < vm_page_dump_size / sizeof(*vm_page_dump); i++) { + bits = vm_page_dump[i]; + while (bits) { + bit = ffs(bits) - 1; + pa = (((uint64_t)i * sizeof(*vm_page_dump) * NBBY) + + bit) * PAGE_SIZE; + /* Clear out undumpable pages now if needed */ + if (is_dumpable(pa)) + dumpsize += PAGE_SIZE; + else + dump_drop_page(pa); + bits &= ~(1ul << bit); + } + } + + dumpsize += PAGE_SIZE; + + /* Determine dump offset on device. */ + if (di->mediasize < SIZEOF_METADATA + dumpsize + sizeof(kdh) * 2) { + error = ENOSPC; + goto fail; + } + + dumplo = di->mediaoffset + di->mediasize - dumpsize; + dumplo -= sizeof(kdh) * 2; + progress = dumpsize; + + /* Initialize mdhdr */ + bzero(&mdhdr, sizeof(mdhdr)); + strcpy(mdhdr.magic, MINIDUMP_MAGIC); + mdhdr.version = MINIDUMP_VERSION; + mdhdr.msgbufsize = msgbufp->msg_size; + mdhdr.bitmapsize = vm_page_dump_size; + mdhdr.ptesize = ptesize; + mdhdr.kernbase = KERNBASE; + + mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_ARM_VERSION, dumpsize, + di->blocksize); + + printf("Physical memory: %u MB\n", ptoa((uintmax_t)physmem) / 1048576); + printf("Dumping %llu MB:", (long long)dumpsize >> 20); + + /* Dump leader */ + error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + if (error) + goto fail; + dumplo += sizeof(kdh); + + /* Dump my header */ + bzero(&fakept, sizeof(fakept)); + bcopy(&mdhdr, &fakept, sizeof(mdhdr)); + error = blk_write(di, (char *)&fakept, 0, PAGE_SIZE); + if (error) + goto fail; + + /* Dump msgbuf up front */ + error = blk_write(di, (char *)msgbufp->msg_ptr, 0, round_page(msgbufp->msg_size)); + if (error) + goto fail; + + /* Dump bitmap */ + error = blk_write(di, (char *)vm_page_dump, 0, + round_page(vm_page_dump_size)); + if (error) + goto fail; + + /* Dump kernel page table pages */ + for (va = KERNBASE; va < kernel_vm_end; va += NBPDR) { + /* We always write a page, even if it is zero */ + pmap_get_pde_pte(pmap_kernel(), va, &pdp, &ptp); + + if (pmap_pde_v(pdp) && pmap_pde_section(pdp)) { + if (count) { + error = blk_write_cont(di, prev_pa, + count * L2_TABLE_SIZE_REAL); + if (error) + goto fail; + count = 0; + prev_pa = 0; + } + /* This is a single 2M block. Generate a fake PTP */ + pa = (*pdp & L1_S_ADDR_MASK) | (va & ~L1_S_ADDR_MASK); + for (k = 0; k < (L1_S_SIZE / PAGE_SIZE); k++) { + fakept[k] = L2_S_PROTO | (pa + (k * PAGE_SIZE)) | + L2_S_PROT(PTE_KERNEL, + VM_PROT_READ | VM_PROT_WRITE); + } + error = blk_write(di, (char *)&fakept, 0, + L2_TABLE_SIZE_REAL); + if (error) + goto fail; + /* Flush, in case we reuse fakept in the same block */ + error = blk_flush(di); + if (error) + goto fail; + continue; + } + if (pmap_pde_v(pdp) && pmap_pde_page(pdp)) { + pa = *pdp & L1_C_ADDR_MASK; + if (!count) { + prev_pa = pa; + count++; + } + else { + if (pa == (prev_pa + count * L2_TABLE_SIZE_REAL)) + count++; + else { + error = blk_write_cont(di, prev_pa, + count * L2_TABLE_SIZE_REAL); + if (error) + goto fail; + count = 1; + prev_pa = pa; + } + } + } else { + if (count) { + error = blk_write_cont(di, prev_pa, + count * L2_TABLE_SIZE_REAL); + if (error) + goto fail; + count = 0; + prev_pa = 0; + } + bzero(fakept, sizeof(fakept)); + error = blk_write(di, (char *)&fakept, 0, + L2_TABLE_SIZE_REAL); + if (error) + goto fail; + /* Flush, in case we reuse fakept in the same block */ + error = blk_flush(di); + if (error) + goto fail; + } + } + + if (count) { + error = blk_write_cont(di, prev_pa, count * L2_TABLE_SIZE_REAL); + if (error) + goto fail; + count = 0; + prev_pa = 0; + } + + /* Dump memory chunks */ + for (i = 0; i < vm_page_dump_size / sizeof(*vm_page_dump); i++) { + bits = vm_page_dump[i]; + while (bits) { + bit = ffs(bits) - 1; + pa = (((uint64_t)i * sizeof(*vm_page_dump) * NBBY) + + bit) * PAGE_SIZE; + if (!count) { + prev_pa = pa; + count++; + } else { + if (pa == (prev_pa + count * PAGE_SIZE)) + count++; + else { + error = blk_write_cont(di, prev_pa, + count * PAGE_SIZE); + if (error) + goto fail; + count = 1; + prev_pa = pa; + } + } + bits &= ~(1ul << bit); + } + } + if (count) { + error = blk_write_cont(di, prev_pa, count * PAGE_SIZE); + if (error) + goto fail; + count = 0; + prev_pa = 0; + } + + /* Dump trailer */ + error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); + if (error) + goto fail; + dumplo += sizeof(kdh); + + /* Signal completion, signoff and exit stage left. */ + dump_write(di, NULL, 0, 0, 0); + printf("\nDump complete\n"); + return; + +fail: + if (error < 0) + error = -error; + + if (error == ECANCELED) + printf("\nDump aborted\n"); + else if (error == ENOSPC) + printf("\nDump failed. Partition too small.\n"); + else + printf("\n** DUMP FAILED (ERROR %d) **\n", error); +} + +void +dump_add_page(vm_paddr_t pa) +{ + int idx, bit; + + pa >>= PAGE_SHIFT; + idx = pa >> 5; /* 2^5 = 32 */ + bit = pa & 31; + atomic_set_int(&vm_page_dump[idx], 1ul << bit); +} + +void +dump_drop_page(vm_paddr_t pa) +{ + int idx, bit; + + pa >>= PAGE_SHIFT; + idx = pa >> 5; /* 2^5 = 32 */ + bit = pa & 31; + atomic_clear_int(&vm_page_dump[idx], 1ul << bit); +} diff --git a/sys/arm/arm/pmap.c b/sys/arm/arm/pmap.c index 66717ea31008..60576aec53db 100644 --- a/sys/arm/arm/pmap.c +++ b/sys/arm/arm/pmap.c @@ -270,6 +270,11 @@ union pmap_cache_state *pmap_cache_state; struct msgbuf *msgbufp = 0; +/* + * Crashdump maps. + */ +static caddr_t crashdumpmap; + extern void bcopy_page(vm_offset_t, vm_offset_t); extern void bzero_page(vm_offset_t); @@ -2455,6 +2460,8 @@ pmap_bootstrap(vm_offset_t firstaddr, vm_offset_t lastaddr, struct pv_addr *l1pt pmap_alloc_specials(&virtual_avail, 1, (vm_offset_t*)&_tmppt, NULL); + pmap_alloc_specials(&virtual_avail, + MAXDUMPPGS, (vm_offset_t *)&crashdumpmap, NULL); SLIST_INIT(&l1_list); TAILQ_INIT(&l1_lru_list); mtx_init(&l1_lru_lock, "l1 list lock", NULL, MTX_DEF); @@ -2792,6 +2799,20 @@ pmap_kenter_section(vm_offset_t va, vm_offset_t pa, int flags) } } +/* + * Make a temporary mapping for a physical address. This is only intended + * to be used for panic dumps. + */ +void * +pmap_kenter_temp(vm_paddr_t pa, int i) +{ + vm_offset_t va; + + va = (vm_offset_t)crashdumpmap + (i * PAGE_SIZE); + pmap_kenter(va, pa); + return ((void *)crashdumpmap); +} + /* * add a wired page to the kva * note that in order for the mapping to take effect -- you diff --git a/sys/arm/include/md_var.h b/sys/arm/include/md_var.h index 32718038535c..6d47a91189dc 100644 --- a/sys/arm/include/md_var.h +++ b/sys/arm/include/md_var.h @@ -35,6 +35,8 @@ extern char sigcode[]; extern int szsigcode; +extern uint32_t *vm_page_dump; +extern int vm_page_dump_size; extern int (*_arm_memcpy)(void *, void *, int, int); extern int (*_arm_bzero)(void *, int, int); @@ -46,7 +48,11 @@ extern int _min_bzero_size; #define SRC_IS_USER 0x2 #define IS_PHYSICAL 0x4 +struct dumperinfo; extern int busdma_swi_pending; void busdma_swi(void); +void dump_add_page(vm_paddr_t); +void dump_drop_page(vm_paddr_t); +void minidumpsys(struct dumperinfo *); #endif /* !_MACHINE_MD_VAR_H_ */ diff --git a/sys/arm/include/minidump.h b/sys/arm/include/minidump.h new file mode 100644 index 000000000000..ad7a90a00edc --- /dev/null +++ b/sys/arm/include/minidump.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 2006 Peter Wemm + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * From: FreeBSD: src/sys/i386/include/minidump.h,v 1.1 2006/04/21 04:28:43 + * $FreeBSD$ + */ + +#ifndef _MACHINE_MINIDUMP_H_ +#define _MACHINE_MINIDUMP_H_ 1 + +#define MINIDUMP_MAGIC "minidump FreeBSD/arm" +#define MINIDUMP_VERSION 1 + +struct minidumphdr { + char magic[24]; + uint32_t version; + uint32_t msgbufsize; + uint32_t bitmapsize; + uint32_t ptesize; + uint32_t kernbase; +}; + +#endif /* _MACHINE_MINIDUMP_H_ */ diff --git a/sys/arm/include/pmap.h b/sys/arm/include/pmap.h index 5eae6526d2ea..4a8d828f9dea 100644 --- a/sys/arm/include/pmap.h +++ b/sys/arm/include/pmap.h @@ -206,6 +206,7 @@ extern vm_offset_t virtual_end; void pmap_bootstrap(vm_offset_t, vm_offset_t, struct pv_addr *); void pmap_kenter(vm_offset_t va, vm_paddr_t pa); void pmap_kenter_nocache(vm_offset_t va, vm_paddr_t pa); +void *pmap_kenter_temp(vm_paddr_t pa, int i); void pmap_kenter_user(vm_offset_t va, vm_paddr_t pa); void pmap_kremove(vm_offset_t); void *pmap_mapdev(vm_offset_t, vm_size_t); diff --git a/sys/conf/files.arm b/sys/conf/files.arm index 621317b3d5c1..bfce5c3f9f7a 100644 --- a/sys/conf/files.arm +++ b/sys/conf/files.arm @@ -30,6 +30,7 @@ arm/arm/intr.c standard arm/arm/locore.S standard no-obj arm/arm/machdep.c standard arm/arm/mem.c optional mem +arm/arm/minidump_machdep.c optional mem arm/arm/nexus.c standard arm/arm/pmap.c standard arm/arm/setcpsr.S standard diff --git a/sys/vm/vm_page.c b/sys/vm/vm_page.c index a7a62f109188..a4ac79bb50c8 100644 --- a/sys/vm/vm_page.c +++ b/sys/vm/vm_page.c @@ -291,7 +291,7 @@ vm_page_startup(vm_offset_t vaddr) bzero((void *)mapped, end - new_end); uma_startup((void *)mapped, boot_pages); -#if defined(__amd64__) || defined(__i386__) +#if defined(__amd64__) || defined(__i386__) || defined(__arm__) /* * Allocate a bitmap to indicate that a random physical page * needs to be included in a minidump.