#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dumpsys.c,v 1.17 2022/08/20 23:48:50 riastradh Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/kcore.h>
#include <sys/core.h>
#include <sys/conf.h>
#include <sys/exec.h>
#include <sys/exec_aout.h>
#include <machine/kcore.h>
#include <machine/pmap_private.h>
#include <uvm/uvm_extern.h>
void dodumpsys(void);
void dumpsys(void);
struct pcb dumppcb;
uint32_t dumpmag = 0x8fca0101;
int dumpsize;
long dumplo;
int sparse_dump = 1;
#define dump_headerbuf_size PAGE_SIZE
#define dump_headerbuf_end (dump_headerbuf + dump_headerbuf_size)
#define dump_headerbuf_avail (dump_headerbuf_end - dump_headerbuf_ptr)
#define BYTES_PER_DUMP PAGE_SIZE
static vaddr_t dumpspace;
static paddr_t max_paddr;
static uint8_t *sparse_dump_physmap;
static uint8_t *dump_headerbuf;
static uint8_t *dump_headerbuf_ptr;
static daddr_t dump_header_blkno;
static size_t dump_nmemsegs;
static size_t dump_npages;
static size_t dump_header_size;
static size_t dump_totalbytesleft;
static int cpu_dump(void);
static int cpu_dumpsize(void);
static u_long cpu_dump_mempagecnt(void);
static void dump_misc_init(void);
static void dump_seg_prep(void);
static int dump_seg_iter(int (*)(paddr_t, paddr_t));
static void sparse_dump_reset(void);
static void sparse_dump_mark(vaddr_t, vaddr_t, int);
static void cpu_dump_prep_sparse(void);
static void dump_header_start(void);
static int dump_header_flush(void);
static int dump_header_addbytes(const void*, size_t);
static int dump_header_addseg(paddr_t, paddr_t);
static int dump_header_finish(void);
static int dump_seg_count_range(paddr_t, paddr_t);
static int dumpsys_seg(paddr_t, paddr_t);
extern phys_ram_seg_t mem_clusters[VM_PHYSSEG_MAX];
extern int mem_cluster_cnt;
void
dodumpsys(void)
{
const struct bdevsw *bdev;
int dumpend, psize;
int error;
if (dumpdev == NODEV)
return;
bdev = bdevsw_lookup(dumpdev);
if (bdev == NULL || bdev->d_psize == NULL)
return;
if (dumpsize == 0)
cpu_dumpconf();
if (dumplo <= 0 || dumpsize == 0) {
printf("\ndump to dev %llu,%llu not possible\n",
(unsigned long long)major(dumpdev),
(unsigned long long)minor(dumpdev));
return;
}
printf("\ndumping to dev %llu,%llu offset %ld\n",
(unsigned long long)major(dumpdev),
(unsigned long long)minor(dumpdev), dumplo);
psize = bdev_size(dumpdev);
printf("dump ");
if (psize == -1) {
printf("area unavailable\n");
return;
}
#if 0
while (sget() != NULL);
#endif
dump_seg_prep();
dumpend = dumplo + btodb(dump_header_size) + ctod(dump_npages);
if (dumpend > psize) {
printf("failed: insufficient space (%d < %d)\n",
psize, dumpend);
goto failed;
}
dump_header_start();
if ((error = cpu_dump()) != 0)
goto err;
if ((error = dump_header_finish()) != 0)
goto err;
if (dump_header_blkno != dumplo + btodb(dump_header_size)) {
printf("BAD header size (%ld [written] != %ld [expected])\n",
(long)(dump_header_blkno - dumplo),
(long)btodb(dump_header_size));
goto failed;
}
dump_totalbytesleft = roundup(ptoa(dump_npages), BYTES_PER_DUMP);
error = dump_seg_iter(dumpsys_seg);
if (error == 0 && dump_header_blkno != dumpend) {
printf("BAD dump size (%ld [written] != %ld [expected])\n",
(long)(dumpend - dumplo),
(long)(dump_header_blkno - dumplo));
goto failed;
}
err:
switch (error) {
case ENXIO:
printf("device bad\n");
break;
case EFAULT:
printf("device not ready\n");
break;
case EINVAL:
printf("area improper\n");
break;
case EIO:
printf("i/o error\n");
break;
case EINTR:
printf("aborted from console\n");
break;
case 0:
printf("succeeded\n");
break;
default:
printf("error %d\n", error);
break;
}
failed:
printf("\n\n");
delay(5000000);
}
void
cpu_dumpconf(void)
{
int nblks, dumpblks;
if (dumpdev == NODEV)
goto bad;
nblks = bdev_size(dumpdev);
if (nblks <= ctod(1))
goto bad;
dumpblks = cpu_dumpsize();
if (dumpblks < 0)
goto bad;
dumpblks += ctod(cpu_dump_mempagecnt());
if (dumpblks > (nblks - ctod(1))) {
dumplo = ctod(1);
} else {
dumplo = nblks - dumpblks;
}
dumpsize = cpu_dump_mempagecnt();
dump_misc_init();
return;
bad:
dumpsize = 0;
}
vaddr_t
reserve_dumppages(vaddr_t p)
{
dumpspace = p;
return (p + BYTES_PER_DUMP);
}
static void
dump_misc_init(void)
{
int i;
if (dump_headerbuf != NULL)
return;
for (i = 0; i < mem_cluster_cnt; ++i) {
paddr_t top = mem_clusters[i].start + mem_clusters[i].size;
if (max_paddr < top)
max_paddr = top;
}
#ifdef DUMP_DEBUG
printf("dump_misc_init: max_paddr = %#" PRIxPADDR "\n", max_paddr);
#endif
sparse_dump_physmap = (void*)uvm_km_alloc(kernel_map,
roundup(max_paddr / (PAGE_SIZE * NBBY), PAGE_SIZE),
PAGE_SIZE, UVM_KMF_WIRED|UVM_KMF_ZERO);
dump_headerbuf = (void*)uvm_km_alloc(kernel_map,
dump_headerbuf_size,
PAGE_SIZE, UVM_KMF_WIRED|UVM_KMF_ZERO);
}
static void
sparse_dump_reset(void)
{
memset(sparse_dump_physmap, 0,
roundup(max_paddr / (PAGE_SIZE * NBBY), PAGE_SIZE));
}
static void
sparse_dump_mark(vaddr_t vbegin, vaddr_t vend, int includep)
{
pmap_t pmap;
paddr_t p;
vaddr_t v;
if (includep) {
vbegin = rounddown(vbegin, PAGE_SIZE);
vend = roundup(vend, PAGE_SIZE);
} else {
vbegin = roundup(vbegin, PAGE_SIZE);
vend = rounddown(vend, PAGE_SIZE);
}
pmap = pmap_kernel();
for (v = vbegin; v != vend; v += PAGE_SIZE) {
if (pmap_extract(pmap, v, &p)) {
if (includep)
setbit(sparse_dump_physmap, p/PAGE_SIZE);
else
clrbit(sparse_dump_physmap, p/PAGE_SIZE);
}
}
}
static void
cpu_dump_prep_sparse(void)
{
sparse_dump_reset();
sparse_dump_mark((vaddr_t)PTE_BASE, 0, 1);
}
static int
dump_seg_iter(int (*callback)(paddr_t, paddr_t))
{
int error, i;
#define CALLBACK(start,size) do { \
error = callback(start,size); \
if (error) \
return error; \
} while(0)
for (i = 0; i < mem_cluster_cnt; ++i) {
if (sparse_dump) {
paddr_t p, start, end;
int lastset;
start = mem_clusters[i].start;
end = start + mem_clusters[i].size;
start = rounddown(start, PAGE_SIZE);
lastset = 0;
for (p = start; p < end; p += PAGE_SIZE) {
int thisset = isset(sparse_dump_physmap,
p/PAGE_SIZE);
if (!lastset && thisset)
start = p;
if (lastset && !thisset)
CALLBACK(start, p - start);
lastset = thisset;
}
if (lastset)
CALLBACK(start, p - start);
} else
CALLBACK(mem_clusters[i].start, mem_clusters[i].size);
}
return 0;
#undef CALLBACK
}
static void
dump_seg_prep(void)
{
if (sparse_dump)
cpu_dump_prep_sparse();
dump_nmemsegs = 0;
dump_npages = 0;
dump_seg_iter(dump_seg_count_range);
dump_header_size = ALIGN(sizeof(kcore_seg_t)) +
ALIGN(sizeof(cpu_kcore_hdr_t)) +
ALIGN(dump_nmemsegs * sizeof(phys_ram_seg_t));
dump_header_size = roundup(dump_header_size, dbtob(1));
dumpsize = dump_npages;
}
static int
dump_seg_count_range(paddr_t start, paddr_t size)
{
++dump_nmemsegs;
dump_npages += size / PAGE_SIZE;
return 0;
}
static void
dump_header_start(void)
{
dump_headerbuf_ptr = dump_headerbuf;
dump_header_blkno = dumplo;
}
static int
dump_header_flush(void)
{
const struct bdevsw *bdev;
size_t to_write;
int error;
bdev = bdevsw_lookup(dumpdev);
to_write = roundup(dump_headerbuf_ptr - dump_headerbuf, dbtob(1));
error = bdev->d_dump(dumpdev, dump_header_blkno,
dump_headerbuf, to_write);
dump_header_blkno += btodb(to_write);
dump_headerbuf_ptr = dump_headerbuf;
return error;
}
static int
dump_header_addbytes(const void* vptr, size_t n)
{
const char *ptr = vptr;
int error;
while (n > dump_headerbuf_avail) {
memcpy(dump_headerbuf_ptr, ptr, dump_headerbuf_avail);
ptr += dump_headerbuf_avail;
n -= dump_headerbuf_avail;
dump_headerbuf_ptr = dump_headerbuf_end;
error = dump_header_flush();
if (error)
return error;
}
memcpy(dump_headerbuf_ptr, ptr, n);
dump_headerbuf_ptr += n;
return 0;
}
static int
dump_header_addseg(paddr_t start, paddr_t size)
{
phys_ram_seg_t seg = { start, size };
return dump_header_addbytes(&seg, sizeof(seg));
}
static int
dump_header_finish(void)
{
memset(dump_headerbuf_ptr, 0, dump_headerbuf_avail);
return dump_header_flush();
}
static int
cpu_dumpsize(void)
{
int size;
size = ALIGN(sizeof(kcore_seg_t)) + ALIGN(sizeof(cpu_kcore_hdr_t)) +
ALIGN(mem_cluster_cnt * sizeof(phys_ram_seg_t));
if (roundup(size, dbtob(1)) != dbtob(1))
return (-1);
return (1);
}
static u_long
cpu_dump_mempagecnt(void)
{
u_long i, n;
n = 0;
for (i = 0; i < mem_cluster_cnt; i++)
n += atop(mem_clusters[i].size);
return (n);
}
static int
cpu_dump(void)
{
kcore_seg_t seg;
cpu_kcore_hdr_t cpuhdr;
const struct bdevsw *bdev;
bdev = bdevsw_lookup(dumpdev);
if (bdev == NULL)
return (ENXIO);
CORE_SETMAGIC(seg, KCORE_MAGIC, MID_MACHINE, CORE_CPU);
seg.c_size = dump_header_size - ALIGN(sizeof(seg));
(void)dump_header_addbytes(&seg, ALIGN(sizeof(seg)));
cpuhdr.pdppaddr = PDPpaddr;
if (use_pae == 1)
cpuhdr.pdppaddr |= I386_KCORE_PAE;
cpuhdr.nmemsegs = dump_nmemsegs;
(void)dump_header_addbytes(&cpuhdr, ALIGN(sizeof(cpuhdr)));
return dump_seg_iter(dump_header_addseg);
}
static int
dumpsys_seg(paddr_t maddr, paddr_t bytes)
{
u_long i, m, n;
daddr_t blkno;
const struct bdevsw *bdev;
int (*dump)(dev_t, daddr_t, void *, size_t);
int error;
bdev = bdevsw_lookup(dumpdev);
dump = bdev->d_dump;
blkno = dump_header_blkno;
for (i = 0; i < bytes; i += n, dump_totalbytesleft -= n) {
if ((dump_totalbytesleft % (1024*1024)) == 0)
printf_nolog("%lu ", (unsigned long)
(dump_totalbytesleft / (1024 * 1024)));
n = bytes - i;
if (n > BYTES_PER_DUMP)
n = BYTES_PER_DUMP;
for (m = 0; m < n; m += NBPG)
pmap_kenter_pa(dumpspace + m, maddr + m,
VM_PROT_READ, 0);
pmap_update(pmap_kernel());
error = (*dump)(dumpdev, blkno, (void *)dumpspace, n);
if (error)
return error;
maddr += n;
blkno += btodb(n);
#if 0
if (sget() != NULL)
return EINTR;
#endif
}
dump_header_blkno = blkno;
return 0;
}