#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/cons.h>
#include <sys/device.h>
#include <sys/globaldata.h>
#include <sys/kernel.h>
#include <sys/kerneldump.h>
#include <sys/msgbuf.h>
#include <sys/kbio.h>
#include <vm/vm.h>
#include <vm/vm_kern.h>
#include <vm/pmap.h>
#include <machine/atomic.h>
#include <machine/elf.h>
#include <machine/globaldata.h>
#include <machine/md_var.h>
#include <machine/vmparam.h>
#include <machine/minidump.h>
CTASSERT(sizeof(struct kerneldumpheader) == 512);
#define SIZEOF_METADATA (64*1024)
#define MD_ALIGN(x) (((off_t)(x) + PAGE_MASK) & ~PAGE_MASK)
#define DEV_ALIGN(x) roundup2((off_t)(x), DEV_BSIZE)
uint64_t *vm_page_dump;
vm_offset_t vm_page_dump_size;
static struct kerneldumpheader kdh;
static off_t dumplo;
static size_t fragsz;
static void *dump_va;
static size_t counter, progress;
CTASSERT(sizeof(*vm_page_dump) == 8);
static int
is_dumpable(vm_paddr_t pa)
{
int i;
for (i = 0; dump_avail[i].phys_beg || dump_avail[i].phys_end; ++i) {
if (pa >= dump_avail[i].phys_beg && pa < dump_avail[i].phys_end)
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 = dev_ddump(di->priv, dump_va, 0, dumplo, fragsz);
dumplo += fragsz;
fragsz = 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;
int max_iosize;
error = 0;
if ((sz & PAGE_MASK)) {
kprintf("size not page aligned\n");
return (EINVAL);
}
if (ptr != NULL && pa != 0) {
kprintf("can't have both va and pa!\n");
return (EINVAL);
}
if (pa != 0 && (((uintptr_t)pa) & PAGE_MASK) != 0) {
kprintf("address not page aligned\n");
return (EINVAL);
}
if (ptr != NULL) {
error = blk_flush(di);
if (error)
return (error);
}
max_iosize = min(MAXPHYS, di->maxiosize);
while (sz) {
len = max_iosize - fragsz;
if (len > sz)
len = sz;
counter += len;
progress -= len;
if (counter >> 24) {
kprintf(" %ld", PG2MB(progress >> PAGE_SHIFT));
counter &= (1<<24) - 1;
}
if (ptr) {
error = dev_ddump(di->priv, 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_temporary(pa + i,
(i + fragsz) >> PAGE_SHIFT);
}
smp_invltlb();
fragsz += len;
pa += len;
sz -= len;
if (fragsz == max_iosize) {
error = blk_flush(di);
if (error)
return (error);
}
}
}
c = cncheckc();
if (c == 0x03)
return (ECANCELED);
if (c != -1 && c != NOKEY)
kprintf(" (CTRL-C to abort) ");
return (0);
}
static pt_entry_t fakept[NPTEPG];
void
minidumpsys(struct dumperinfo *di)
{
uint64_t dumpsize;
uint64_t ptesize;
vm_offset_t va;
vm_offset_t kern_end;
int error;
uint64_t bits;
uint64_t *pdp, *pd, *pt, pa;
int i, j, k, bit;
int kpdp, klo, khi;
int lpdp = -1;
long lpdpttl = 0;
struct minidumphdr2 mdhdr;
struct mdglobaldata *md;
cnpoll(TRUE);
counter = 0;
ptesize = NPML4EPG * NPDPEPG * 8;
md = (struct mdglobaldata *)globaldata_find(0);
kern_end = KvaEnd;
if (kern_end < (vm_offset_t)&(md[ncpus]))
kern_end = (vm_offset_t)&(md[ncpus]);
pdp = (uint64_t *)PHYS_TO_DMAP(KPDPphys);
for (va = VM_MIN_KERNEL_ADDRESS; va < kern_end; va += NBPDR) {
if (va < VM_MIN_KERNEL_ADDRESS)
break;
i = ((va - VM_MIN_KERNEL_ADDRESS) >> PDPSHIFT) &
(NPML4EPG * NPDPEPG - 1);
if (i != lpdp) {
lpdp = i;
lpdpttl = 0;
}
i = ((va - VM_MIN_KERNEL_ADDRESS) >> PDPSHIFT) &
(NPML4EPG * NPDPEPG - 1);
if ((pdp[i] & kernel_pmap->pmap_bits[PG_V_IDX]) == 0)
continue;
dump_add_page(pdp[i] & PG_FRAME);
lpdpttl += PAGE_SIZE;
pd = (uint64_t *)PHYS_TO_DMAP(pdp[i] & PG_FRAME);
j = ((va >> PDRSHIFT) & ((1ul << NPDEPGSHIFT) - 1));
if ((pd[j] & (kernel_pmap->pmap_bits[PG_PS_IDX] | kernel_pmap->pmap_bits[PG_V_IDX])) ==
(kernel_pmap->pmap_bits[PG_PS_IDX] | kernel_pmap->pmap_bits[PG_V_IDX])) {
lpdpttl += PAGE_SIZE * NPTEPG;
pa = pd[j] & PG_PS_FRAME;
for (k = 0; k < NPTEPG; k++) {
if (is_dumpable(pa))
dump_add_page(pa);
pa += PAGE_SIZE;
}
} else if ((pd[j] & kernel_pmap->pmap_bits[PG_V_IDX]) ==
kernel_pmap->pmap_bits[PG_V_IDX]) {
dump_add_page(pd[j] & PG_FRAME);
lpdpttl += PAGE_SIZE;
pt = (uint64_t *)PHYS_TO_DMAP(pd[j] & PG_FRAME);
for (k = 0; k < NPTEPG; k++) {
if ((pt[k] & kernel_pmap->pmap_bits[PG_V_IDX])
== kernel_pmap->pmap_bits[PG_V_IDX]) {
pa = pt[k] & PG_FRAME;
lpdpttl += PAGE_SIZE;
if (is_dumpable(pa))
dump_add_page(pa);
}
}
} else {
}
}
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 = bsfq(bits);
pa = (((uint64_t)i * sizeof(*vm_page_dump) * NBBY) + bit) * PAGE_SIZE;
if (is_dumpable(pa)) {
dumpsize += PAGE_SIZE;
} else {
dump_drop_page(pa);
}
bits &= ~(1ul << bit);
}
}
dumpsize += PAGE_SIZE;
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;
bzero(&mdhdr, sizeof(mdhdr));
strcpy(mdhdr.magic, MINIDUMP2_MAGIC);
mdhdr.version = MINIDUMP2_VERSION;
mdhdr.msgbufsize = msgbufp->msg_size;
mdhdr.bitmapsize = vm_page_dump_size;
mdhdr.ptesize = ptesize;
mdhdr.kernbase = VM_MIN_KERNEL_ADDRESS;
mdhdr.dmapbase = DMAP_MIN_ADDRESS;
mdhdr.dmapend = DMAP_MAX_ADDRESS;
mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_AMD64_VERSION,
dumpsize, di->blocksize);
kprintf("Physical memory: %jd MB\n", (intmax_t)ptoa(physmem) / 1048576);
kprintf("Dumping %jd MB:", (intmax_t)dumpsize >> 20);
error = dev_ddump(di->priv, &kdh, 0, dumplo, sizeof(kdh));
if (error)
goto fail;
dumplo += sizeof(kdh);
bzero(fakept, sizeof(fakept));
bcopy(&mdhdr, fakept, sizeof(mdhdr));
error = blk_write(di, (char *)fakept, 0, PAGE_SIZE);
if (error)
goto fail;
error = blk_write(di, (char *)msgbufp->msg_ptr, 0, round_page(msgbufp->msg_size));
if (error)
goto fail;
error = blk_write(di, (char *)vm_page_dump, 0, round_page(vm_page_dump_size));
if (error)
goto fail;
pdp = (uint64_t *)PHYS_TO_DMAP(KPDPphys);
kpdp = (KPDPPHYS_KVA >> PDPSHIFT) &
(NPML4EPG * NPDPEPG - 1);
klo = (int)(VM_MIN_KERNEL_ADDRESS >> PDPSHIFT) &
(NPML4EPG * NPDPEPG - 1);
khi = (int)(VM_MAX_KERNEL_ADDRESS >> PDPSHIFT) &
(NPML4EPG * NPDPEPG - 1);
for (i = 0; i < NPML4EPG * NPDPEPG; ++i) {
if (i < klo || i > khi) {
fakept[i & (NPDPEPG - 1)] = 0;
} else {
fakept[i & (NPDPEPG - 1)] = pdp[i - kpdp];
}
if ((i & (NPDPEPG - 1)) == (NPDPEPG - 1)) {
error = blk_write(di, (char *)fakept, 0, PAGE_SIZE);
if (error)
goto fail;
error = blk_flush(di);
if (error)
goto fail;
}
}
for (i = 0; i < vm_page_dump_size / sizeof(*vm_page_dump); i++) {
bits = vm_page_dump[i];
while (bits) {
bit = bsfq(bits);
pa = (((uint64_t)i * sizeof(*vm_page_dump) * NBBY) + bit) * PAGE_SIZE;
error = blk_write(di, 0, pa, PAGE_SIZE);
if (error)
goto fail;
bits &= ~(1ul << bit);
}
}
error = blk_flush(di);
if (error)
goto fail;
error = dev_ddump(di->priv, &kdh, 0, dumplo, sizeof(kdh));
if (error)
goto fail;
dumplo += sizeof(kdh);
dev_ddump(di->priv, NULL, 0, 0, 0);
kprintf("\nDump complete\n");
cnpoll(FALSE);
return;
fail:
cnpoll(FALSE);
if (error < 0)
error = -error;
if (error == ECANCELED)
kprintf("\nDump aborted\n");
else if (error == ENOSPC)
kprintf("\nDump failed. Partition too small.\n");
else
kprintf("\n** DUMP FAILED (ERROR %d) **\n", error);
}
void
dump_add_page(vm_paddr_t pa)
{
int idx, bit;
pa >>= PAGE_SHIFT;
idx = pa >> 6;
bit = pa & 63;
atomic_set_long(&vm_page_dump[idx], 1ul << bit);
}
void
dump_drop_page(vm_paddr_t pa)
{
int idx, bit;
pa >>= PAGE_SHIFT;
idx = pa >> 6;
bit = pa & 63;
atomic_clear_long(&vm_page_dump[idx], 1ul << bit);
}