Edit

IABSD.fr/src/sys/dev/kcov.c

Branch :

  • Show log

    Commit

  • Author : gnezdo
    Date : 2025-02-02 21:05:12
    Hash : db5d28f0
    Message : Bump kcov buffer size limit This should repair syzkaller which has bumped its use beyond our limit. claudio@ and anton@ agree it's the way

  • sys/dev/kcov.c
  • /*	$OpenBSD: kcov.c,v 1.51 2025/02/02 21:05:12 gnezdo Exp $	*/
    
    /*
     * Copyright (c) 2018 Anton Lindqvist <anton@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/param.h>
    #include <sys/systm.h>
    #include <sys/proc.h>
    #include <sys/kcov.h>
    #include <sys/malloc.h>
    #include <sys/mutex.h>
    #include <sys/pool.h>
    #include <sys/stdint.h>
    #include <sys/queue.h>
    
    /* kcov_vnode() */
    #include <sys/conf.h>
    #include <sys/vnode.h>
    #include <sys/specdev.h>
    
    #include <uvm/uvm_extern.h>
    
    #define KCOV_BUF_MEMB_SIZE	sizeof(uintptr_t)
    #define KCOV_BUF_MAX_NMEMB	(512 << 10)
    
    #define KCOV_CMP_CONST		0x1
    #define KCOV_CMP_SIZE(x)	((x) << 1)
    
    #define KCOV_STATE_NONE		0
    #define KCOV_STATE_READY	1
    #define KCOV_STATE_TRACE	2
    #define KCOV_STATE_DYING	3
    
    #define KCOV_STRIDE_TRACE_PC	1
    #define KCOV_STRIDE_TRACE_CMP	4
    
    /*
     * Coverage structure.
     *
     * Locking:
     * 	I	immutable after creation
     *	M	kcov_mtx
     *	a	atomic operations
     */
    struct kcov_dev {
    	int		 kd_state;	/* [M] */
    	int		 kd_mode;	/* [M] */
    	int		 kd_unit;	/* [I] D_CLONE unique device minor */
    	int		 kd_intr;	/* [M] currently used in interrupt */
    	uintptr_t	*kd_buf;	/* [a] traced coverage */
    	size_t		 kd_nmemb;	/* [I] */
    	size_t		 kd_size;	/* [I] */
    
    	struct kcov_remote *kd_kr;	/* [M] */
    
    	TAILQ_ENTRY(kcov_dev)	kd_entry;	/* [M] */
    };
    
    /*
     * Remote coverage structure.
     *
     * Locking:
     * 	I	immutable after creation
     *	M	kcov_mtx
     */
    struct kcov_remote {
    	struct kcov_dev *kr_kd;	/* [M] */
    	void *kr_id;		/* [I] */
    	int kr_subsystem;	/* [I] */
    	int kr_nsections;	/* [M] # threads in remote section */
    	int kr_state;		/* [M] */
    
    	TAILQ_ENTRY(kcov_remote) kr_entry;	/* [M] */
    };
    
    /*
     * Per CPU coverage structure used to track coverage when executing in a remote
     * interrupt context.
     *
     * Locking:
     * 	I	immutable after creation
     *	M	kcov_mtx
     */
    struct kcov_cpu {
    	struct kcov_dev  kc_kd;
    	struct kcov_dev *kc_kd_save;	/* [M] previous kcov_dev */
    	int kc_cpuid;			/* [I] cpu number */
    
    	TAILQ_ENTRY(kcov_cpu) kc_entry;	/* [I] */
    };
    
    void kcovattach(int);
    
    int kd_init(struct kcov_dev *, unsigned long);
    void kd_free(struct kcov_dev *);
    struct kcov_dev *kd_lookup(int);
    void kd_copy(struct kcov_dev *, struct kcov_dev *);
    
    struct kcov_remote *kcov_remote_register_locked(int, void *);
    int kcov_remote_attach(struct kcov_dev *, struct kio_remote_attach *);
    void kcov_remote_detach(struct kcov_dev *, struct kcov_remote *);
    void kr_free(struct kcov_remote *);
    void kr_barrier(struct kcov_remote *);
    struct kcov_remote *kr_lookup(int, void *);
    
    static struct kcov_dev *kd_curproc(int);
    static struct kcov_cpu *kd_curcpu(void);
    static uint64_t kd_claim(struct kcov_dev *, int, int);
    
    TAILQ_HEAD(, kcov_dev) kd_list = TAILQ_HEAD_INITIALIZER(kd_list);
    TAILQ_HEAD(, kcov_remote) kr_list = TAILQ_HEAD_INITIALIZER(kr_list);
    TAILQ_HEAD(, kcov_cpu) kc_list = TAILQ_HEAD_INITIALIZER(kc_list);
    
    int kcov_cold = 1;
    int kr_cold = 1;
    struct mutex kcov_mtx = MUTEX_INITIALIZER(IPL_MPFLOOR);
    struct pool kr_pool;
    
    static inline int
    inintr(struct cpu_info *ci)
    {
    	return (ci->ci_idepth > 0);
    }
    
    /*
     * Compiling the kernel with the `-fsanitize-coverage=trace-pc' option will
     * cause the following function to be called upon function entry and before
     * each block of instructions that maps to a single line in the original source
     * code.
     *
     * If kcov is enabled for the current thread, the kernel program counter will
     * be stored in its corresponding coverage buffer.
     */
    void
    __sanitizer_cov_trace_pc(void)
    {
    	struct kcov_dev *kd;
    	uint64_t idx;
    
    	kd = kd_curproc(KCOV_MODE_TRACE_PC);
    	if (kd == NULL)
    		return;
    
    	if ((idx = kd_claim(kd, KCOV_STRIDE_TRACE_PC, 1)))
    		kd->kd_buf[idx] = (uintptr_t)__builtin_return_address(0);
    }
    
    /*
     * Compiling the kernel with the `-fsanitize-coverage=trace-cmp' option will
     * cause the following function to be called upon integer comparisons and switch
     * statements.
     *
     * If kcov is enabled for the current thread, the comparison will be stored in
     * its corresponding coverage buffer.
     */
    void
    trace_cmp(struct kcov_dev *kd, uint64_t type, uint64_t arg1, uint64_t arg2,
        uintptr_t pc)
    {
    	uint64_t idx;
    
    	if ((idx = kd_claim(kd, KCOV_STRIDE_TRACE_CMP, 1))) {
    		kd->kd_buf[idx] = type;
    		kd->kd_buf[idx + 1] = arg1;
    		kd->kd_buf[idx + 2] = arg2;
    		kd->kd_buf[idx + 3] = pc;
    	}
    }
    
    #define TRACE_CMP(type, arg1, arg2) do {				\
    	struct kcov_dev *kd;						\
    	if ((kd = kd_curproc(KCOV_MODE_TRACE_CMP)) == NULL)		\
    		return;							\
    	trace_cmp(kd, (type), (arg1), (arg2),				\
    	    (uintptr_t)__builtin_return_address(0));			\
    } while (0)
    
    void
    __sanitizer_cov_trace_cmp1(uint8_t arg1, uint8_t arg2)
    {
    	TRACE_CMP(KCOV_CMP_SIZE(0), arg1, arg2);
    }
    
    void
    __sanitizer_cov_trace_cmp2(uint16_t arg1, uint16_t arg2)
    {
    	TRACE_CMP(KCOV_CMP_SIZE(1), arg1, arg2);
    }
    
    void
    __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2)
    {
    	TRACE_CMP(KCOV_CMP_SIZE(2), arg1, arg2);
    }
    
    void
    __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2)
    {
    	TRACE_CMP(KCOV_CMP_SIZE(3), arg1, arg2);
    }
    
    void
    __sanitizer_cov_trace_const_cmp1(uint8_t arg1, uint8_t arg2)
    {
    	TRACE_CMP(KCOV_CMP_SIZE(0) | KCOV_CMP_CONST, arg1, arg2);
    }
    
    void
    __sanitizer_cov_trace_const_cmp2(uint16_t arg1, uint16_t arg2)
    {
    	TRACE_CMP(KCOV_CMP_SIZE(1) | KCOV_CMP_CONST, arg1, arg2);
    }
    
    void
    __sanitizer_cov_trace_const_cmp4(uint32_t arg1, uint32_t arg2)
    {
    	TRACE_CMP(KCOV_CMP_SIZE(2) | KCOV_CMP_CONST, arg1, arg2);
    }
    
    void
    __sanitizer_cov_trace_const_cmp8(uint64_t arg1, uint64_t arg2)
    {
    	TRACE_CMP(KCOV_CMP_SIZE(3) | KCOV_CMP_CONST, arg1, arg2);
    }
    
    void
    __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases)
    {
    	struct kcov_dev *kd;
    	uint64_t i, nbits, ncases, type;
    	uintptr_t pc;
    
    	kd = kd_curproc(KCOV_MODE_TRACE_CMP);
    	if (kd == NULL)
    		return;
    
    	pc = (uintptr_t)__builtin_return_address(0);
    	ncases = cases[0];
    	nbits = cases[1];
    
    	switch (nbits) {
    	case 8:
    		type = KCOV_CMP_SIZE(0);
    		break;
    	case 16:
    		type = KCOV_CMP_SIZE(1);
    		break;
    	case 32:
    		type = KCOV_CMP_SIZE(2);
    		break;
    	case 64:
    		type = KCOV_CMP_SIZE(3);
    		break;
    	default:
    		return;
    	}
    	type |= KCOV_CMP_CONST;
    
    	for (i = 0; i < ncases; i++)
    		trace_cmp(kd, type, cases[i + 2], val, pc);
    }
    
    void
    kcovattach(int count)
    {
    	struct kcov_cpu *kc;
    	int error, i;
    
    	pool_init(&kr_pool, sizeof(struct kcov_remote), 0, IPL_MPFLOOR, PR_WAITOK,
    	    "kcovpl", NULL);
    
    	kc = mallocarray(ncpusfound, sizeof(*kc), M_DEVBUF, M_WAITOK | M_ZERO);
    	mtx_enter(&kcov_mtx);
    	for (i = 0; i < ncpusfound; i++) {
    		kc[i].kc_cpuid = i;
    		error = kd_init(&kc[i].kc_kd, KCOV_BUF_MAX_NMEMB);
    		KASSERT(error == 0);
    		TAILQ_INSERT_TAIL(&kc_list, &kc[i], kc_entry);
    	}
    	mtx_leave(&kcov_mtx);
    
    	kr_cold = 0;
    }
    
    int
    kcovopen(dev_t dev, int flag, int mode, struct proc *p)
    {
    	struct kcov_dev *kd;
    
    	kd = malloc(sizeof(*kd), M_SUBPROC, M_WAITOK | M_ZERO);
    	kd->kd_unit = minor(dev);
    	mtx_enter(&kcov_mtx);
    	KASSERT(kd_lookup(kd->kd_unit) == NULL);
    	TAILQ_INSERT_TAIL(&kd_list, kd, kd_entry);
    	if (kcov_cold)
    		kcov_cold = 0;
    	mtx_leave(&kcov_mtx);
    	return (0);
    }
    
    int
    kcovclose(dev_t dev, int flag, int mode, struct proc *p)
    {
    	struct kcov_dev *kd;
    
    	mtx_enter(&kcov_mtx);
    
    	kd = kd_lookup(minor(dev));
    	if (kd == NULL) {
    		mtx_leave(&kcov_mtx);
    		return (ENXIO);
    	}
    
    	TAILQ_REMOVE(&kd_list, kd, kd_entry);
    	if (kd->kd_state == KCOV_STATE_TRACE && kd->kd_kr == NULL) {
    		/*
    		 * Another thread is currently using the kcov descriptor,
    		 * postpone freeing to kcov_exit().
    		 */
    		kd->kd_state = KCOV_STATE_DYING;
    		kd->kd_mode = KCOV_MODE_NONE;
    	} else {
    		kd_free(kd);
    	}
    
    	mtx_leave(&kcov_mtx);
    	return (0);
    }
    
    int
    kcovioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
    {
    	struct kcov_dev *kd;
    	int mode;
    	int error = 0;
    
    	mtx_enter(&kcov_mtx);
    
    	kd = kd_lookup(minor(dev));
    	if (kd == NULL) {
    		mtx_leave(&kcov_mtx);
    		return (ENXIO);
    	}
    
    	switch (cmd) {
    	case KIOSETBUFSIZE:
    		error = kd_init(kd, *((unsigned long *)data));
    		break;
    	case KIOENABLE:
    		/* Only one kcov descriptor can be enabled per thread. */
    		if (p->p_kd != NULL) {
    			error = EBUSY;
    			break;
    		}
    		if (kd->kd_state != KCOV_STATE_READY) {
    			error = ENXIO;
    			break;
    		}
    		mode = *((int *)data);
    		if (mode != KCOV_MODE_TRACE_PC && mode != KCOV_MODE_TRACE_CMP) {
    			error = EINVAL;
    			break;
    		}
    		kd->kd_state = KCOV_STATE_TRACE;
    		kd->kd_mode = mode;
    		/* Remote coverage is mutually exclusive. */
    		if (kd->kd_kr == NULL)
    			p->p_kd = kd;
    		break;
    	case KIODISABLE:
    		/* Only the enabled thread may disable itself. */
    		if ((p->p_kd != kd && kd->kd_kr == NULL)) {
    			error = EPERM;
    			break;
    		}
    		if (kd->kd_state != KCOV_STATE_TRACE) {
    			error = ENXIO;
    			break;
    		}
    		kd->kd_state = KCOV_STATE_READY;
    		kd->kd_mode = KCOV_MODE_NONE;
    		if (kd->kd_kr != NULL)
    			kr_barrier(kd->kd_kr);
    		p->p_kd = NULL;
    		break;
    	case KIOREMOTEATTACH:
    		error = kcov_remote_attach(kd,
    		    (struct kio_remote_attach *)data);
    		break;
    	default:
    		error = ENOTTY;
    	}
    	mtx_leave(&kcov_mtx);
    
    	return (error);
    }
    
    paddr_t
    kcovmmap(dev_t dev, off_t offset, int prot)
    {
    	struct kcov_dev *kd;
    	paddr_t pa = -1;
    	vaddr_t va;
    
    	mtx_enter(&kcov_mtx);
    
    	kd = kd_lookup(minor(dev));
    	if (kd == NULL)
    		goto out;
    
    	if (offset < 0 || offset >= kd->kd_nmemb * KCOV_BUF_MEMB_SIZE)
    		goto out;
    
    	va = (vaddr_t)kd->kd_buf + offset;
    	if (pmap_extract(pmap_kernel(), va, &pa) == FALSE)
    		pa = -1;
    
    out:
    	mtx_leave(&kcov_mtx);
    	return (pa);
    }
    
    void
    kcov_exit(struct proc *p)
    {
    	struct kcov_dev *kd;
    
    	mtx_enter(&kcov_mtx);
    
    	kd = p->p_kd;
    	if (kd == NULL) {
    		mtx_leave(&kcov_mtx);
    		return;
    	}
    
    	if (kd->kd_state == KCOV_STATE_DYING) {
    		p->p_kd = NULL;
    		kd_free(kd);
    	} else {
    		kd->kd_state = KCOV_STATE_READY;
    		kd->kd_mode = KCOV_MODE_NONE;
    		if (kd->kd_kr != NULL)
    			kr_barrier(kd->kd_kr);
    		p->p_kd = NULL;
    	}
    
    	mtx_leave(&kcov_mtx);
    }
    
    /*
     * Returns non-zero if the given vnode refers to a kcov device.
     */
    int
    kcov_vnode(struct vnode *vp)
    {
    	return (vp->v_type == VCHR &&
    	    cdevsw[major(vp->v_rdev)].d_open == kcovopen);
    }
    
    struct kcov_dev *
    kd_lookup(int unit)
    {
    	struct kcov_dev *kd;
    
    	MUTEX_ASSERT_LOCKED(&kcov_mtx);
    
    	TAILQ_FOREACH(kd, &kd_list, kd_entry) {
    		if (kd->kd_unit == unit)
    			return (kd);
    	}
    	return (NULL);
    }
    
    void
    kd_copy(struct kcov_dev *dst, struct kcov_dev *src)
    {
    	uint64_t idx, nmemb;
    	int stride;
    
    	MUTEX_ASSERT_LOCKED(&kcov_mtx);
    	KASSERT(dst->kd_mode == src->kd_mode);
    
    	nmemb = src->kd_buf[0];
    	if (nmemb == 0)
    		return;
    	stride = src->kd_mode == KCOV_MODE_TRACE_CMP ? KCOV_STRIDE_TRACE_CMP :
    	    KCOV_STRIDE_TRACE_PC;
    	idx = kd_claim(dst, stride, nmemb);
    	if (idx == 0)
    		return;
    	memcpy(&dst->kd_buf[idx], &src->kd_buf[1],
    	    stride * nmemb * KCOV_BUF_MEMB_SIZE);
    }
    
    int
    kd_init(struct kcov_dev *kd, unsigned long nmemb)
    {
    	void *buf;
    	size_t size;
    	int error;
    
    	KASSERT(kd->kd_buf == NULL);
    
    	if (kd->kd_state != KCOV_STATE_NONE)
    		return (EBUSY);
    
    	if (nmemb == 0 || nmemb > KCOV_BUF_MAX_NMEMB)
    		return (EINVAL);
    
    	size = roundup(nmemb * KCOV_BUF_MEMB_SIZE, PAGE_SIZE);
    	mtx_leave(&kcov_mtx);
    	buf = km_alloc(size, &kv_any, &kp_zero, &kd_waitok);
    	if (buf == NULL) {
    		error = ENOMEM;
    		goto err;
    	}
    	/* km_malloc() can sleep, ensure the race was won. */
    	if (kd->kd_state != KCOV_STATE_NONE) {
    		error = EBUSY;
    		goto err;
    	}
    	mtx_enter(&kcov_mtx);
    	kd->kd_buf = buf;
    	/* The first element is reserved to hold the number of used elements. */
    	kd->kd_nmemb = nmemb - 1;
    	kd->kd_size = size;
    	kd->kd_state = KCOV_STATE_READY;
    	return (0);
    
    err:
    	if (buf != NULL)
    		km_free(buf, size, &kv_any, &kp_zero);
    	mtx_enter(&kcov_mtx);
    	return (error);
    }
    
    void
    kd_free(struct kcov_dev *kd)
    {
    	struct kcov_remote *kr;
    
    	MUTEX_ASSERT_LOCKED(&kcov_mtx);
    
    	kr = kd->kd_kr;
    	if (kr != NULL)
    		kcov_remote_detach(kd, kr);
    
    	if (kd->kd_buf != NULL) {
    		mtx_leave(&kcov_mtx);
    		km_free(kd->kd_buf, kd->kd_size, &kv_any, &kp_zero);
    		mtx_enter(&kcov_mtx);
    	}
    	free(kd, M_SUBPROC, sizeof(*kd));
    }
    
    static struct kcov_dev *
    kd_curproc(int mode)
    {
    	struct cpu_info *ci;
    	struct kcov_dev *kd;
    
    	/*
    	 * Do not trace before kcovopen() has been called at least once.
    	 * At this point, all secondary CPUs have booted and accessing curcpu()
    	 * is safe.
    	 */
    	if (__predict_false(kcov_cold))
    		return (NULL);
    
    	ci = curcpu();
    	kd = ci->ci_curproc->p_kd;
    	if (__predict_true(kd == NULL) || kd->kd_mode != mode)
    		return (NULL);
    
    	/*
    	 * Do not trace if the kernel has panicked. This could happen if curproc
    	 * had kcov enabled while panicking.
    	 */
    	if (__predict_false(panicstr || db_active))
    		return (NULL);
    
    	/* Do not trace in interrupt context unless this is a remote section. */
    	if (inintr(ci) && kd->kd_intr == 0)
    		return (NULL);
    
    	return (kd);
    
    }
    
    static struct kcov_cpu *
    kd_curcpu(void)
    {
    	struct kcov_cpu *kc;
    	unsigned int cpuid = cpu_number();
    
    	TAILQ_FOREACH(kc, &kc_list, kc_entry) {
    		if (kc->kc_cpuid == cpuid)
    			return (kc);
    	}
    	return (NULL);
    }
    
    /*
     * Claim stride times nmemb number of elements in the coverage buffer. Returns
     * the index of the first claimed element. If the claim cannot be fulfilled,
     * zero is returned.
     */
    static uint64_t
    kd_claim(struct kcov_dev *kd, int stride, int nmemb)
    {
    	uint64_t idx, was;
    
    	idx = kd->kd_buf[0];
    	for (;;) {
    		if (stride * (idx + nmemb) > kd->kd_nmemb)
    			return (0);
    
    		was = atomic_cas_ulong(&kd->kd_buf[0], idx, idx + nmemb);
    		if (was == idx)
    			return (idx * stride + 1);
    		idx = was;
    	}
    }
    
    void
    kcov_remote_enter(int subsystem, void *id)
    {
    	struct cpu_info *ci;
    	struct kcov_cpu *kc;
    	struct kcov_dev *kd;
    	struct kcov_remote *kr;
    	struct proc *p;
    
    	mtx_enter(&kcov_mtx);
    	kr = kr_lookup(subsystem, id);
    	if (kr == NULL || kr->kr_state != KCOV_STATE_READY)
    		goto out;
    	kd = kr->kr_kd;
    	if (kd == NULL || kd->kd_state != KCOV_STATE_TRACE)
    		goto out;
    	ci = curcpu();
    	p = ci->ci_curproc;
    	if (inintr(ci)) {
    		/*
    		 * XXX we only expect to be called from softclock interrupts at
    		 * this point.
    		 */
    		kc = kd_curcpu();
    		if (kc == NULL || kc->kc_kd.kd_intr == 1)
    			goto out;
    		kc->kc_kd.kd_state = KCOV_STATE_TRACE;
    		kc->kc_kd.kd_mode = kd->kd_mode;
    		kc->kc_kd.kd_intr = 1;
    		kc->kc_kd_save = p->p_kd;
    		kd = &kc->kc_kd;
    		/* Reset coverage buffer. */
    		kd->kd_buf[0] = 0;
    	} else {
    		KASSERT(p->p_kd == NULL);
    	}
    	kr->kr_nsections++;
    	p->p_kd = kd;
    
    out:
    	mtx_leave(&kcov_mtx);
    }
    
    void
    kcov_remote_leave(int subsystem, void *id)
    {
    	struct cpu_info *ci;
    	struct kcov_cpu *kc;
    	struct kcov_remote *kr;
    	struct proc *p;
    
    	mtx_enter(&kcov_mtx);
    	ci = curcpu();
    	p = ci->ci_curproc;
    	if (p->p_kd == NULL)
    		goto out;
    	kr = kr_lookup(subsystem, id);
    	if (kr == NULL)
    		goto out;
    	if (inintr(ci)) {
    		kc = kd_curcpu();
    		if (kc == NULL || kc->kc_kd.kd_intr == 0)
    			goto out;
    
    		/*
    		 * Stop writing to the coverage buffer associated with this CPU
    		 * before copying its contents.
    		 */
    		p->p_kd = kc->kc_kd_save;
    		kc->kc_kd_save = NULL;
    
    		kd_copy(kr->kr_kd, &kc->kc_kd);
    		kc->kc_kd.kd_state = KCOV_STATE_READY;
    		kc->kc_kd.kd_mode = KCOV_MODE_NONE;
    		kc->kc_kd.kd_intr = 0;
    	} else {
    		KASSERT(p->p_kd == kr->kr_kd);
    		p->p_kd = NULL;
    	}
    	if (--kr->kr_nsections == 0)
    		wakeup(kr);
    out:
    	mtx_leave(&kcov_mtx);
    }
    
    void
    kcov_remote_register(int subsystem, void *id)
    {
    	mtx_enter(&kcov_mtx);
    	kcov_remote_register_locked(subsystem, id);
    	mtx_leave(&kcov_mtx);
    }
    
    void
    kcov_remote_unregister(int subsystem, void *id)
    {
    	struct kcov_remote *kr;
    
    	mtx_enter(&kcov_mtx);
    	kr = kr_lookup(subsystem, id);
    	if (kr != NULL)
    		kr_free(kr);
    	mtx_leave(&kcov_mtx);
    }
    
    struct kcov_remote *
    kcov_remote_register_locked(int subsystem, void *id)
    {
    	struct kcov_remote *kr, *tmp;
    
    	/* Do not allow registrations before the pool is initialized. */
    	KASSERT(kr_cold == 0);
    
    	/*
    	 * Temporarily release the mutex since the allocation could end up
    	 * sleeping.
    	 */
    	mtx_leave(&kcov_mtx);
    	kr = pool_get(&kr_pool, PR_WAITOK | PR_ZERO);
    	kr->kr_subsystem = subsystem;
    	kr->kr_id = id;
    	kr->kr_state = KCOV_STATE_NONE;
    	mtx_enter(&kcov_mtx);
    
    	for (;;) {
    		tmp = kr_lookup(subsystem, id);
    		if (tmp == NULL)
    			break;
    		if (tmp->kr_state != KCOV_STATE_DYING) {
    			pool_put(&kr_pool, kr);
    			return (NULL);
    		}
    		/*
    		 * The remote could already be deregistered while another
    		 * thread is currently inside a kcov remote section.
    		 */
    		msleep_nsec(tmp, &kcov_mtx, PWAIT, "kcov", INFSLP);
    	}
    	TAILQ_INSERT_TAIL(&kr_list, kr, kr_entry);
    	return (kr);
    }
    
    int
    kcov_remote_attach(struct kcov_dev *kd, struct kio_remote_attach *arg)
    {
    	struct kcov_remote *kr = NULL;
    
    	MUTEX_ASSERT_LOCKED(&kcov_mtx);
    
    	if (kd->kd_state != KCOV_STATE_READY)
    		return (ENXIO);
    
    	if (arg->subsystem == KCOV_REMOTE_COMMON) {
    		kr = kcov_remote_register_locked(KCOV_REMOTE_COMMON,
    		    curproc->p_p);
    		if (kr == NULL)
    			return (EBUSY);
    	} else {
    		return (EINVAL);
    	}
    
    	kr->kr_state = KCOV_STATE_READY;
    	kr->kr_kd = kd;
    	kd->kd_kr = kr;
    	return (0);
    }
    
    void
    kcov_remote_detach(struct kcov_dev *kd, struct kcov_remote *kr)
    {
    	MUTEX_ASSERT_LOCKED(&kcov_mtx);
    
    	KASSERT(kd == kr->kr_kd);
    	if (kr->kr_subsystem == KCOV_REMOTE_COMMON) {
    		kr_free(kr);
    	} else {
    		kr->kr_state = KCOV_STATE_NONE;
    		kr_barrier(kr);
    		kd->kd_kr = NULL;
    		kr->kr_kd = NULL;
    	}
    }
    
    void
    kr_free(struct kcov_remote *kr)
    {
    	MUTEX_ASSERT_LOCKED(&kcov_mtx);
    
    	kr->kr_state = KCOV_STATE_DYING;
    	kr_barrier(kr);
    	if (kr->kr_kd != NULL)
    		kr->kr_kd->kd_kr = NULL;
    	kr->kr_kd = NULL;
    	TAILQ_REMOVE(&kr_list, kr, kr_entry);
    	/* Notify thread(s) waiting in kcov_remote_register(). */
    	wakeup(kr);
    	pool_put(&kr_pool, kr);
    }
    
    void
    kr_barrier(struct kcov_remote *kr)
    {
    	MUTEX_ASSERT_LOCKED(&kcov_mtx);
    
    	while (kr->kr_nsections > 0)
    		msleep_nsec(kr, &kcov_mtx, PWAIT, "kcovbar", INFSLP);
    }
    
    struct kcov_remote *
    kr_lookup(int subsystem, void *id)
    {
    	struct kcov_remote *kr;
    
    	MUTEX_ASSERT_LOCKED(&kcov_mtx);
    
    	TAILQ_FOREACH(kr, &kr_list, kr_entry) {
    		if (kr->kr_subsystem == subsystem && kr->kr_id == id)
    			return (kr);
    	}
    	return (NULL);
    }