#include "opt_ddb.h"
#include "opt_ddb_trace.h"
#include "opt_panic.h"
#include "use_gpio.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/eventhandler.h>
#include <sys/buf.h>
#include <sys/disk.h>
#include <sys/diskslice.h>
#include <sys/reboot.h>
#include <sys/proc.h>
#include <sys/caps.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/vnode.h>
#include <sys/kernel.h>
#include <sys/kerneldump.h>
#include <sys/kthread.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/queue.h>
#include <sys/sysctl.h>
#include <sys/vkernel.h>
#include <sys/conf.h>
#include <sys/sysmsg.h>
#include <sys/device.h>
#include <sys/cons.h>
#include <sys/kbio.h>
#include <sys/shm.h>
#include <sys/kern_syscall.h>
#include <vm/vm_map.h>
#include <vm/pmap.h>
#include <sys/thread2.h>
#include <sys/buf2.h>
#include <sys/mplock2.h>
#include <machine/cpu.h>
#include <machine/clock.h>
#include <machine/md_var.h>
#include <machine/smp.h>
#include <machine/vmparam.h>
#include <machine/thread.h>
#include <sys/signalvar.h>
#if defined(WDOG_DISABLE_ON_PANIC)
#include <sys/wdog.h>
#endif
#include <dev/acpica/acpi_pvpanic/panic_notifier.h>
#if (NGPIO > 0) && defined(ERROR_LED_ON_PANIC)
#include <dev/misc/gpio/gpio.h>
#endif
#ifndef PANIC_REBOOT_WAIT_TIME
#define PANIC_REBOOT_WAIT_TIME 15
#endif
#include <machine/stdarg.h>
#ifdef DDB
#include <ddb/ddb.h>
#ifdef DDB_UNATTENDED
int debugger_on_panic = 0;
#else
int debugger_on_panic = 1;
#endif
SYSCTL_INT(_debug, OID_AUTO, debugger_on_panic, CTLFLAG_RW,
&debugger_on_panic, 0, "Run debugger on kernel panic");
#ifdef DDB_TRACE
int trace_on_panic = 1;
#else
int trace_on_panic = 0;
#endif
SYSCTL_INT(_debug, OID_AUTO, trace_on_panic, CTLFLAG_RW,
&trace_on_panic, 0, "Print stack trace on kernel panic");
#endif
static int sync_on_panic = 0;
SYSCTL_INT(_kern, OID_AUTO, sync_on_panic, CTLFLAG_RW,
&sync_on_panic, 0, "Do a sync before rebooting from a panic");
SYSCTL_NODE(_kern, OID_AUTO, shutdown, CTLFLAG_RW, 0, "Shutdown environment");
const char *panicstr;
__read_mostly int dumping;
static struct dumperinfo dumper;
__read_frequently globaldata_t panic_cpu_gd;
struct lwkt_tokref panic_tokens[LWKT_MAXTOKENS];
int panic_tokens_count;
__read_mostly int bootverbose = 0;
SYSCTL_INT(_debug, OID_AUTO, bootverbose, CTLFLAG_RW,
&bootverbose, 0, "Verbose kernel messages");
int cold = 1;
int dumplo;
u_int64_t dumplo64;
static void boot (int) __dead2;
static int setdumpdev (cdev_t dev);
static void poweroff_wait (void *, int);
static void print_uptime (void);
static void shutdown_halt (void *junk, int howto);
static void shutdown_panic (void *junk, int howto);
static void shutdown_reset (void *junk, int howto);
static int shutdown_busycount1(struct buf *bp, void *info);
static int shutdown_busycount2(struct buf *bp, void *info);
static void shutdown_cleanup_proc(struct proc *p);
static void
shutdown_conf(void *unused)
{
EVENTHANDLER_REGISTER(shutdown_final, poweroff_wait, NULL, SHUTDOWN_PRI_FIRST);
EVENTHANDLER_REGISTER(shutdown_final, shutdown_halt, NULL, SHUTDOWN_PRI_LAST + 100);
EVENTHANDLER_REGISTER(shutdown_final, shutdown_panic, NULL, SHUTDOWN_PRI_LAST + 100);
EVENTHANDLER_REGISTER(shutdown_final, shutdown_reset, NULL, SHUTDOWN_PRI_LAST + 200);
}
SYSINIT(shutdown_conf, SI_BOOT2_MACHDEP, SI_ORDER_ANY, shutdown_conf, NULL);
int
sys_reboot(struct sysmsg *sysmsg, const struct reboot_args *uap)
{
int error;
if ((error = caps_priv_check_self(SYSCAP_NOREBOOT | __SYSCAP_WHEELOK)))
return (error);
get_mplock();
boot(uap->opt);
rel_mplock();
return (0);
}
static int shutdown_howto = 0;
void
shutdown_nice(int howto)
{
shutdown_howto = howto;
if (initproc != NULL) {
ksignal(initproc, SIGINT);
} else {
boot(RB_NOSYNC);
}
return;
}
static int waittime = -1;
struct pcb dumppcb;
struct thread *dumpthread;
static void
print_uptime(void)
{
int f;
struct timespec ts;
getnanouptime(&ts);
kprintf("Uptime: ");
f = 0;
if (ts.tv_sec >= 86400) {
kprintf("%ldd", ts.tv_sec / 86400);
ts.tv_sec %= 86400;
f = 1;
}
if (f || ts.tv_sec >= 3600) {
kprintf("%ldh", ts.tv_sec / 3600);
ts.tv_sec %= 3600;
f = 1;
}
if (f || ts.tv_sec >= 60) {
kprintf("%ldm", ts.tv_sec / 60);
ts.tv_sec %= 60;
f = 1;
}
kprintf("%lds\n", ts.tv_sec);
}
static void
boot(int howto)
{
if (curthread->td_release)
curthread->td_release(curthread);
lwkt_setpri_self(TDPRI_MAX);
howto |= shutdown_howto;
if (!CPUMASK_ISUP(smp_active_mask)) {
kprintf("boot() called on cpu#%d\n", mycpu->gd_cpuid);
}
if (panicstr == NULL && mycpu->gd_cpuid != 0) {
kprintf("Switching to cpu #0 for shutdown\n");
lwkt_setcpu_self(globaldata_find(0));
}
EVENTHANDLER_INVOKE(shutdown_pre_sync, howto);
if (panicstr == NULL) {
shutdown_cleanup_proc(curproc);
shutdown_cleanup_proc(&proc0);
if (initproc) {
if (initproc != curproc) {
ksignal(initproc, SIGSTOP);
tsleep(boot, 0, "shutdn", hz / 20);
}
shutdown_cleanup_proc(initproc);
}
vfs_cache_setroot(NULL, NULL);
}
if (!cold && (howto & RB_NOSYNC) == 0 && waittime < 0) {
int iter, nbusy, pbusy;
int zcount;
waittime = 0;
zcount = 0;
kprintf("\nsyncing disks... ");
sys_sync(NULL, NULL);
for (iter = pbusy = 0; iter < 20 + zcount; iter++) {
if (iter <= 10)
nbusy = scan_all_buffers(shutdown_busycount1,
&iter);
else
nbusy = scan_all_buffers(shutdown_busycount2,
&iter);
kprintf("%d ", nbusy);
if (nbusy == 0) {
if (++zcount == 3)
break;
} else {
zcount = 0;
}
if (nbusy < pbusy) {
if (iter > 10)
iter = 10;
else
iter = 0;
}
pbusy = nbusy;
if (iter > 5)
bio_ops_sync(NULL);
sys_sync(NULL, NULL);
tsleep(boot, 0, "shutdn", hz * iter / 20 + 1);
}
kprintf("\n");
if (zcount < 3) {
kprintf("giving up on %d buffers\n", nbusy);
#ifdef DDB
if (debugger_on_panic)
Debugger("busy buffer problem");
#endif
tsleep(boot, 0, "shutdn", hz * 5 + 1);
} else {
kprintf("done\n");
if (panicstr == NULL)
vfs_unmountall(1);
}
tsleep(boot, 0, "shutdn", hz / 10 + 1);
}
print_uptime();
crit_enter();
if ((howto & (RB_HALT|RB_DUMP)) == RB_DUMP && !cold) {
dumpsys();
}
EVENTHANDLER_INVOKE(shutdown_post_sync, howto);
EVENTHANDLER_INVOKE(shutdown_final, howto);
for(;;) ;
}
static int
shutdown_busycount1(struct buf *bp, void *info __unused)
{
struct vnode *vp;
if ((vp = bp->b_vp) != NULL && vp->v_tag == VT_TMPFS)
return (0);
if ((bp->b_flags & B_INVAL) == 0 && BUF_LOCKINUSE(bp))
return(1);
if ((bp->b_flags & (B_DELWRI | B_INVAL)) == B_DELWRI)
return (1);
return (0);
}
static int
shutdown_busycount2(struct buf *bp, void *info)
{
struct vnode *vp;
int *iterp = info;
const char *mpath;
if ((vp = bp->b_vp) != NULL) {
if (vp->v_tag == VT_TMPFS)
return (0);
if (vp->v_tag == VT_NFS)
return (0);
if (vp->v_tag == VT_MFS)
return (0);
if (vp->v_tag == VT_SMBFS)
return (0);
}
if (((bp->b_flags & B_INVAL) == 0 && BUF_LOCKINUSE(bp)) ||
((bp->b_flags & (B_DELWRI|B_INVAL)) == B_DELWRI)) {
if (bp->b_vp == NULL ||
bio_track_active(&bp->b_vp->v_track_write) == 0) {
return (0);
}
if (*iterp > 15) {
mpath = "?";
if (bp->b_vp->v_mount)
mpath = bp->b_vp->v_mount->mnt_stat.f_mntonname;
kprintf("%p on %s, flags:%08x, loffset:%jd, "
"doffset:%jd\n",
bp,
mpath,
bp->b_flags,
(intmax_t)bp->b_loffset,
(intmax_t)bp->b_bio2.bio_offset);
}
return(1);
}
return(0);
}
static void
shutdown_halt(void *junk, int howto)
{
if (howto & RB_HALT) {
kprintf("\n");
kprintf("The operating system has halted.\n");
#ifdef _KERNEL_VIRTUAL
cpu_halt();
#else
kprintf("Please press any key to reboot.\n\n");
cnpoll(TRUE);
switch (cngetc()) {
case -1:
cpu_halt();
default:
howto &= ~RB_HALT;
break;
}
#endif
}
}
static void
shutdown_panic(void *junk, int howto)
{
int loop;
int c;
if (howto & RB_DUMP) {
if (PANIC_REBOOT_WAIT_TIME != 0) {
if (PANIC_REBOOT_WAIT_TIME != -1) {
kprintf("Automatic reboot in %d seconds - "
"press a key on the console to abort\n",
PANIC_REBOOT_WAIT_TIME);
for (loop = PANIC_REBOOT_WAIT_TIME * 10;
loop > 0; --loop) {
DELAY(1000 * 100);
c = cncheckc();
if (c != -1 && c != NOKEY)
break;
}
if (!loop)
return;
}
} else {
return;
}
kprintf("--> Press a key on the console to reboot,\n");
kprintf("--> or switch off the system now.\n");
cngetc();
}
}
static void
shutdown_reset(void *junk, int howto)
{
kprintf("Rebooting...\n");
DELAY(1000000);
cpu_reset();
}
static
void
shutdown_cleanup_proc(struct proc *p)
{
struct filedesc *fdp;
struct vmspace *vm;
if (p == NULL)
return;
if ((fdp = p->p_fd) != NULL) {
kern_closefrom(0);
if (fdp->fd_cdir) {
cache_drop(&fdp->fd_ncdir);
vrele(fdp->fd_cdir);
fdp->fd_cdir = NULL;
}
if (fdp->fd_rdir) {
cache_drop(&fdp->fd_nrdir);
vrele(fdp->fd_rdir);
fdp->fd_rdir = NULL;
}
if (fdp->fd_jdir) {
cache_drop(&fdp->fd_njdir);
vrele(fdp->fd_jdir);
fdp->fd_jdir = NULL;
}
}
if (p->p_vkernel)
vkernel_exit(p);
if (p->p_textvp) {
vrele(p->p_textvp);
p->p_textvp = NULL;
}
if (p->p_textnch.ncp)
cache_drop(&p->p_textnch);
vm = p->p_vmspace;
if (vm != NULL) {
pmap_remove_pages(vmspace_pmap(vm),
VM_MIN_USER_ADDRESS,
VM_MAX_USER_ADDRESS);
vm_map_remove(&vm->vm_map,
VM_MIN_USER_ADDRESS,
VM_MAX_USER_ADDRESS);
}
}
__attribute__((__used__))
static u_long const dumpmag = 0x8fca0101UL;
__attribute__((__used__))
static int dumpsize = 0;
static int dodump = 1;
SYSCTL_INT(_machdep, OID_AUTO, do_dump, CTLFLAG_RW, &dodump, 0,
"Try to perform coredump on kernel panic");
void
mkdumpheader(struct kerneldumpheader *kdh, char *magic, uint32_t archver,
uint64_t dumplen, uint32_t blksz)
{
bzero(kdh, sizeof(*kdh));
strncpy(kdh->magic, magic, sizeof(kdh->magic));
strncpy(kdh->architecture, MACHINE_ARCH, sizeof(kdh->architecture));
kdh->version = htod32(KERNELDUMPVERSION);
kdh->architectureversion = htod32(archver);
kdh->dumplength = htod64(dumplen);
kdh->dumptime = htod64(time_second);
kdh->blocksize = htod32(blksz);
strncpy(kdh->hostname, hostname, sizeof(kdh->hostname));
strncpy(kdh->versionstring, version, sizeof(kdh->versionstring));
if (panicstr != NULL)
strncpy(kdh->panicstring, panicstr, sizeof(kdh->panicstring));
kdh->parity = kerneldump_parity(kdh);
}
static int
setdumpdev(cdev_t dev)
{
int error;
int doopen;
if (dev == NULL) {
disk_dumpconf(NULL, 0);
dumpdev = NULL;
return (0);
}
doopen = (dev->si_sysref.refcnt == 1);
if (doopen) {
error = dev_dopen(dev, FREAD, S_IFCHR,
proc0.p_ucred, NULL, NULL);
if (error)
return (error);
}
error = disk_dumpconf(dev, 1);
if (error == 0)
dumpdev = dev;
return error;
}
static void dump_conf (void *dummy);
static void
dump_conf(void *dummy)
{
char *path;
cdev_t dev;
int _dummy;
path = kmalloc(MNAMELEN, M_TEMP, M_WAITOK);
if (TUNABLE_STR_FETCH("dumpdev", path, MNAMELEN) != 0) {
sync_devs();
tsleep(&_dummy, 0, "syncer", hz*2);
dev = kgetdiskbyname(path);
if (dev != NULL)
dumpdev = dev;
}
kfree(path, M_TEMP);
if (setdumpdev(dumpdev) != 0)
dumpdev = NULL;
}
SYSINIT(dump_conf, SI_SUB_DUMP_CONF, SI_ORDER_FIRST, dump_conf, NULL);
static int
sysctl_kern_dumpdev(SYSCTL_HANDLER_ARGS)
{
int error;
dev_t ndumpdev;
ndumpdev = devid_from_dev(dumpdev);
error = sysctl_handle_opaque(oidp, &ndumpdev, sizeof ndumpdev, req);
if (error == 0 && req->newptr != NULL)
error = setdumpdev(dev_from_devid(ndumpdev, 0));
return (error);
}
SYSCTL_PROC(_kern, KERN_DUMPDEV, dumpdev, CTLTYPE_OPAQUE|CTLFLAG_RW,
0, sizeof dumpdev, sysctl_kern_dumpdev, "T,udev_t", "");
static struct panicerinfo *panic_notifier;
int
set_panic_notifier(struct panicerinfo *info)
{
if (info == NULL)
panic_notifier = NULL;
else if (panic_notifier != NULL)
return 1;
else
panic_notifier = info;
return 0;
}
void
panic(const char *fmt, ...)
{
int bootopt, newpanic;
globaldata_t gd = mycpu;
thread_t td = gd->gd_curthread;
__va_list ap;
static char buf[256];
for (;;) {
globaldata_t xgd = panic_cpu_gd;
if (xgd && xgd != gd) {
crit_enter();
++mycpu->gd_trap_nesting_level;
if (mycpu->gd_trap_nesting_level < 25) {
kprintf("SECONDARY PANIC ON CPU %d THREAD %p\n",
mycpu->gd_cpuid, td);
}
td->td_release = NULL;
for (;;) {
lwkt_deschedule_self(td);
lwkt_switch();
}
}
if (xgd && xgd == gd)
break;
if (atomic_cmpset_ptr(&panic_cpu_gd, NULL, gd))
break;
}
kvcreinitspin();
if (panicstr == NULL) {
bcopy(td->td_toks_array, panic_tokens, sizeof(panic_tokens));
panic_tokens_count = td->td_toks_stop - &td->td_toks_base;
}
lwkt_relalltokens(td);
td->td_toks_stop = &td->td_toks_base;
if (gd->gd_spinlocks)
kprintf("panic with %d spinlocks held\n", gd->gd_spinlocks);
gd->gd_spinlocks = 0;
bootopt = RB_AUTOBOOT | RB_DUMP;
if (sync_on_panic == 0)
bootopt |= RB_NOSYNC;
newpanic = 0;
if (panicstr) {
bootopt |= RB_NOSYNC;
} else {
panicstr = fmt;
newpanic = 1;
}
__va_start(ap, fmt);
kvsnprintf(buf, sizeof(buf), fmt, ap);
if (panicstr == fmt)
panicstr = buf;
__va_end(ap);
if (panic_notifier != NULL)
panic_notifier->notifier(panic_notifier->arg);
kprintf("panic: %s\n", buf);
kprintf("cpuid = %d\n", mycpu->gd_cpuid);
#if (NGPIO > 0) && defined(ERROR_LED_ON_PANIC)
led_switch("error", 1);
#endif
#if defined(WDOG_DISABLE_ON_PANIC)
wdog_disable();
#endif
savectx(&dumppcb);
dumpthread = curthread;
#if defined(DDB)
if (newpanic && trace_on_panic)
print_backtrace(6);
if (debugger_on_panic)
Debugger("panic");
else
#endif
if (newpanic)
stop_cpus(mycpu->gd_other_cpus);
boot(bootopt);
}
#ifndef POWEROFF_DELAY
# define POWEROFF_DELAY 5000
#endif
static int poweroff_delay = POWEROFF_DELAY;
SYSCTL_INT(_kern_shutdown, OID_AUTO, poweroff_delay, CTLFLAG_RW,
&poweroff_delay, 0, "");
static void
poweroff_wait(void *junk, int howto)
{
if(!(howto & RB_POWEROFF) || poweroff_delay <= 0)
return;
DELAY(poweroff_delay * 1000);
}
static int kproc_shutdown_wait = 60;
SYSCTL_INT(_kern_shutdown, OID_AUTO, kproc_shutdown_wait, CTLFLAG_RW,
&kproc_shutdown_wait, 0, "");
void
shutdown_kproc(void *arg, int howto)
{
struct thread *td;
struct proc *p;
int error;
if (panicstr)
return;
td = (struct thread *)arg;
if ((p = td->td_proc) != NULL) {
kprintf("Waiting (max %d seconds) for system process `%s' to stop...",
kproc_shutdown_wait, p->p_comm);
} else {
kprintf("Waiting (max %d seconds) for system thread %s to stop...",
kproc_shutdown_wait, td->td_comm);
}
error = suspend_kproc(td, kproc_shutdown_wait * hz);
if (error == EWOULDBLOCK)
kprintf("timed out\n");
else
kprintf("stopped\n");
}
int
set_dumper(struct dumperinfo *di)
{
if (di == NULL) {
bzero(&dumper, sizeof(dumper));
return 0;
}
if (dumper.dumper != NULL)
return (EBUSY);
dumper = *di;
return 0;
}
void
dumpsys(void)
{
#if defined (_KERNEL_VIRTUAL)
kprintf("vkernels don't support dumps\n");
return;
#endif
if (dumper.dumper != NULL && !dumping) {
dumping++;
md_dumpsys(&dumper);
}
}
__read_frequently int dump_stop_usertds = 0;
static
void
need_user_resched_remote(void *dummy)
{
need_user_resched();
}
void
dump_reactivate_cpus(void)
{
globaldata_t gd;
int cpu, seq;
dump_stop_usertds = 1;
need_user_resched();
for (cpu = 0; cpu < ncpus; cpu++) {
gd = globaldata_find(cpu);
seq = lwkt_send_ipiq(gd, need_user_resched_remote, NULL);
lwkt_wait_ipiq(gd, seq);
}
restart_cpus(stopped_cpus);
}