#include <kmdb/kctl/kctl.h>
#include <kmdb/kctl/kctl_wr.h>
#include <kmdb/kmdb_kctl.h>
#include <kmdb/kmdb_kdi.h>
#include <kmdb/kmdb_auxv.h>
#include <mdb/mdb_errno.h>
#include <sys/sysmacros.h>
#include <sys/reboot.h>
#include <sys/atomic.h>
#include <sys/bootconf.h>
#include <sys/kmdb.h>
#include <sys/kobj.h>
#include <sys/kobj_impl.h>
#include <sys/promimpl.h>
#include <sys/kdi_impl.h>
#include <sys/ctf_api.h>
#include <vm/seg_kmem.h>
kctl_t kctl;
#define KCTL_EXECNAME "/kernel/drv/kmdb"
#if defined(_LP64)
#define KCTL_MEM_GOALSZ (20 * 1024 * 1024)
#else
#define KCTL_MEM_GOALSZ (10 * 1024 * 1024)
#endif
#ifdef __sparc
#define KCTL_PROM_LOCK promif_preprom()
#define KCTL_PROM_UNLOCK promif_postprom()
#else
#define KCTL_PROM_LOCK
#define KCTL_PROM_UNLOCK
#endif
static int
kctl_init(void)
{
if (kobj_kdi.kdi_version != KDI_VERSION) {
kctl_warn("kmdb/kernel version mismatch (expected %d, "
"found %d)", KDI_VERSION, kobj_kdi.kdi_version);
return (-1);
}
sema_init(&kctl.kctl_wr_avail_sem, 0, NULL, SEMA_DRIVER, NULL);
mutex_init(&kctl.kctl_wr_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&kctl.kctl_wr_cv, NULL, CV_DRIVER, NULL);
mutex_init(&kctl.kctl_lock, NULL, MUTEX_DRIVER, NULL);
kctl.kctl_execname = KCTL_EXECNAME;
kctl.kctl_state = KCTL_ST_INACTIVE;
kctl.kctl_dseg = kctl.kctl_mrbase = NULL;
kctl.kctl_dseg_size = kctl.kctl_mrsize = 0;
kctl_dmod_init();
return (0);
}
static void
kctl_fini(void)
{
kctl_dmod_fini();
mutex_destroy(&kctl.kctl_lock);
cv_destroy(&kctl.kctl_wr_cv);
mutex_destroy(&kctl.kctl_wr_lock);
sema_destroy(&kctl.kctl_wr_avail_sem);
}
static uint_t
kctl_set_state(uint_t state)
{
uint_t ostate = kctl.kctl_state;
if (state > ostate) {
kctl_dprintf("new kctl state: %d", state);
kctl.kctl_state = state;
}
return (ostate);
}
static int
kctl_boot_dseg_alloc(caddr_t dsegaddr, size_t dsegsz)
{
while (dsegsz >= 1024*1024) {
size_t sz = MIN(dsegsz, 1024*1024);
if (BOP_ALLOC(kctl.kctl_boot_ops, dsegaddr, sz, BO_NO_ALIGN) !=
dsegaddr)
return (-1);
dsegaddr += sz;
dsegsz -= sz;
}
return (0);
}
static int
kctl_dseg_alloc(caddr_t addr, size_t sz)
{
ASSERT(((uintptr_t)addr & PAGEOFFSET) == 0);
if (hat_getpfnum(kas.a_hat, addr) != PFN_INVALID)
return (EAGAIN);
if (segkmem_xalloc(NULL, addr, sz, VM_NOSLEEP, 0, segkmem_page_create,
NULL) == NULL)
return (ENOMEM);
return (0);
}
static void
kctl_dseg_free(caddr_t addr, size_t sz)
{
ASSERT(((uintptr_t)addr & PAGEOFFSET) == 0);
segkmem_free(NULL, addr, sz);
}
static void
kctl_memavail(void)
{
size_t needed;
caddr_t base;
needed = P2ROUNDUP(kctl.kctl_memgoalsz <= kctl.kctl_dseg_size ? 0 :
kctl.kctl_memgoalsz - kctl.kctl_dseg_size, PAGESIZE);
if (needed == 0)
return;
if ((base = kmem_zalloc(needed, KM_NOSLEEP)) == NULL) {
cmn_err(CE_WARN, "retrying of kmdb allocation of 0x%lx bytes",
(ulong_t)needed);
base = kmem_zalloc(needed, KM_SLEEP);
}
kdi_dvec->dv_memavail(base, needed);
kctl.kctl_mrbase = base;
kctl.kctl_mrsize = needed;
}
void
kctl_cleanup(void)
{
uint_t state = kctl_set_state(KCTL_ST_DEACTIVATING);
kctl_dprintf("cleaning up from state %d", state);
ASSERT(kctl.kctl_boot_loaded == 0);
switch (state) {
case KCTL_ST_ACTIVE:
boothowto &= ~RB_DEBUG;
kdi_dvec = NULL;
case KCTL_ST_DBG_ACTIVATED:
KCTL_PROM_LOCK;
kmdb_deactivate();
KCTL_PROM_UNLOCK;
case KCTL_ST_THREAD_STARTED:
if (curthread != kctl.kctl_wr_thr) {
kctl_wr_thr_stop();
kctl_wr_thr_join();
}
case KCTL_ST_MOD_NOTIFIERS:
kctl_mod_notify_unreg();
case KCTL_ST_KCTL_PREACTIVATED:
kctl_depreactivate_isadep();
case KCTL_ST_INITIALIZED:
case KCTL_ST_DSEG_ALLOCED:
kctl_dseg_free(kctl.kctl_dseg, kctl.kctl_dseg_size);
if (kctl.kctl_mrbase != NULL)
kmem_free(kctl.kctl_mrbase, kctl.kctl_mrsize);
}
kctl.kctl_state = KCTL_ST_INACTIVE;
}
static void
kctl_startup_modules(void)
{
struct modctl *modp;
kctl_dmod_sync();
(void) kctl_wr_process();
kctl_mod_notify_reg();
(void) kctl_set_state(KCTL_ST_MOD_NOTIFIERS);
modp = &modules;
do {
kctl_mod_loaded(modp);
} while ((modp = modp->mod_next) != &modules);
}
static void
kctl_startup_thread(void)
{
kctl_wr_thr_start();
(void) kctl_set_state(KCTL_ST_THREAD_STARTED);
}
static int
kctl_startup_boot(void)
{
struct modctl_list *lp, **lpp;
int rc;
if (kctl_wr_process() < 0) {
kctl_warn("kmdb: failed to load modules");
return (-1);
}
mutex_enter(&mod_lock);
for (lpp = kobj_linkmaps; *lpp != NULL; lpp++) {
for (lp = *lpp; lp != NULL; lp = lp->modl_next) {
if ((rc = kctl_mod_decompress(lp->modl_modp)) != 0) {
kctl_warn("kmdb: failed to decompress CTF data "
"for %s: %s", lp->modl_modp->mod_modname,
ctf_errmsg(rc));
}
}
}
mutex_exit(&mod_lock);
return (0);
}
static int
kctl_startup_preactivate(void *romp, const char *cfg, const char **argv)
{
kmdb_auxv_t kav;
int rc;
kctl_auxv_init(&kav, cfg, argv, romp);
KCTL_PROM_LOCK;
rc = kmdb_init(kctl.kctl_execname, &kav);
KCTL_PROM_UNLOCK;
kctl_auxv_fini(&kav);
if (rc < 0)
return (EMDB_KNOLOAD);
(void) kctl_set_state(KCTL_ST_INITIALIZED);
if (kctl_preactivate_isadep() != 0)
return (EIO);
(void) kctl_set_state(KCTL_ST_KCTL_PREACTIVATED);
return (0);
}
static int
kctl_startup_activate(uint_t flags)
{
kdi_debugvec_t *dvec;
KCTL_PROM_LOCK;
kmdb_activate(&dvec, flags);
KCTL_PROM_UNLOCK;
(void) kctl_set_state(KCTL_ST_DBG_ACTIVATED);
dvec->dv_kctl_modavail = kctl_startup_modules;
dvec->dv_kctl_thravail = kctl_startup_thread;
dvec->dv_kctl_memavail = kctl_memavail;
kctl_activate_isadep(dvec);
kdi_dvec = dvec;
membar_producer();
boothowto |= RB_DEBUG;
(void) kctl_set_state(KCTL_ST_ACTIVE);
return (0);
}
static int
kctl_state_check(uint_t state, uint_t ok_state)
{
if (state == ok_state)
return (0);
if (state == KCTL_ST_INACTIVE)
return (EMDB_KINACTIVE);
else if (kctl.kctl_state > KCTL_ST_INACTIVE &&
kctl.kctl_state < KCTL_ST_ACTIVE)
return (EMDB_KACTIVATING);
else if (kctl.kctl_state == KCTL_ST_ACTIVE)
return (EMDB_KACTIVE);
else if (kctl.kctl_state == KCTL_ST_DEACTIVATING)
return (EMDB_KDEACTIVATING);
else
return (EINVAL);
}
int
kctl_deactivate(void)
{
int rc;
mutex_enter(&kctl.kctl_lock);
if (kctl.kctl_boot_loaded) {
rc = EMDB_KNOUNLOAD;
goto deactivate_done;
}
if ((rc = kctl_state_check(kctl.kctl_state, KCTL_ST_ACTIVE)) != 0)
goto deactivate_done;
kmdb_kdi_set_unload_request();
kmdb_kdi_kmdb_enter();
kctl_wr_thr_join();
deactivate_done:
mutex_exit(&kctl.kctl_lock);
return (rc);
}
int
kctl_boot_activate(struct bootops *ops, void *romp, size_t memsz,
const char **argv)
{
void *old;
#ifdef __lint
{
kctl_boot_activate_f *kba = kctl_boot_activate;
if (kba == NULL)
return (0);
}
#endif
old = kctl_boot_tmpinit();
ASSERT(ops != NULL);
kctl.kctl_boot_ops = ops;
if (kctl_init() < 0)
return (-1);
kctl.kctl_boot_loaded = 1;
kctl_dprintf("beginning kmdb initialization");
if (memsz == 0)
memsz = KCTL_MEM_GOALSZ;
kctl.kctl_dseg = kdi_segdebugbase;
kctl.kctl_dseg_size =
memsz > kdi_segdebugsize ? kdi_segdebugsize : memsz;
kctl.kctl_memgoalsz = memsz;
if (kctl_boot_dseg_alloc(kctl.kctl_dseg, kctl.kctl_dseg_size) < 0) {
kctl_warn("kmdb: failed to allocate %lu-byte debugger area at "
"%p", kctl.kctl_dseg_size, (void *)kctl.kctl_dseg);
return (-1);
}
(void) kctl_set_state(KCTL_ST_DSEG_ALLOCED);
if (kctl_startup_preactivate(romp, NULL, argv) != 0 ||
kctl_startup_activate(KMDB_ACT_F_BOOT)) {
kctl_warn("kmdb: failed to activate");
return (-1);
}
if (kctl_startup_boot() < 0)
return (-1);
kctl_dprintf("finished with kmdb initialization");
kctl_boot_tmpfini(old);
kctl.kctl_boot_ops = NULL;
return (0);
}
int
kctl_modload_activate(size_t memsz, const char *cfg, uint_t flags)
{
int rc;
mutex_enter(&kctl.kctl_lock);
if ((rc = kctl_state_check(kctl.kctl_state, KCTL_ST_INACTIVE)) != 0) {
if ((flags & KMDB_F_AUTO_ENTRY) && rc == EMDB_KACTIVE) {
kmdb_kdi_kmdb_enter();
rc = 0;
}
mutex_exit(&kctl.kctl_lock);
return (rc);
}
kctl.kctl_flags = flags;
if (memsz == 0)
memsz = KCTL_MEM_GOALSZ;
kctl.kctl_dseg = kdi_segdebugbase;
kctl.kctl_dseg_size =
memsz > kdi_segdebugsize ? kdi_segdebugsize : memsz;
kctl.kctl_memgoalsz = memsz;
if ((rc = kctl_dseg_alloc(kctl.kctl_dseg, kctl.kctl_dseg_size)) != 0)
goto activate_fail;
(void) kctl_set_state(KCTL_ST_DSEG_ALLOCED);
if ((rc = kctl_startup_preactivate(NULL, cfg, NULL)) != 0)
goto activate_fail;
kctl_startup_modules();
kctl_startup_thread();
if ((rc = kctl_startup_activate(0)) != 0)
goto activate_fail;
kctl_memavail();
if (kctl.kctl_flags & KMDB_F_AUTO_ENTRY)
kmdb_kdi_kmdb_enter();
mutex_exit(&kctl.kctl_lock);
return (0);
activate_fail:
kctl_cleanup();
mutex_exit(&kctl.kctl_lock);
return (rc);
}
int
kctl_attach(dev_info_t *dip)
{
kctl.kctl_drv_dip = dip;
return (0);
}
int
kctl_detach(void)
{
return (kctl.kctl_state == KCTL_ST_INACTIVE ? 0 : EBUSY);
}
static struct modlmisc modlmisc = {
&mod_miscops,
KMDB_VERSION
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modlmisc,
NULL
};
int
_init(void)
{
if (kctl_init() < 0)
return (EINVAL);
return (mod_install(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
kctl_fini();
return (mod_remove(&modlinkage));
}