Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 119 additions & 22 deletions sys/dev/nmdm/nmdm.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

#include <sys/cdefs.h>
/*
* Pseudo-nulmodem driver
* Pseudo-nullmodem driver
* Mighty handy for use with serial console in Vmware
*/

Expand Down Expand Up @@ -95,36 +95,76 @@ struct nmdmsoftc {
struct nmdmpart ns_part1;
struct nmdmpart ns_part2;
struct mtx ns_mtx;
TAILQ_ENTRY(nmdmsoftc) ns_list;
};

static int nmdm_count = 0;

static void
nmdm_close(struct tty *tp)
static TAILQ_HEAD(, nmdmsoftc) nmdmsoftc_list =
TAILQ_HEAD_INITIALIZER(nmdmsoftc_list);
static struct mtx nmdmsoftc_lock;
MTX_SYSINIT(nmdmsoftc_lock, &nmdmsoftc_lock, "nmdmsoftc list", MTX_DEF);

/*
* Tear down both halves of an nmdm pair. Must be called with tp locked.
* Returns EBUSY (lock still held) if either side is open; returns 0 with
* the lock released on success.
*/
static int
nmdm_destroy(struct tty *tp)
{
struct nmdmpart *np;
struct nmdmpart *np = tty_softc(tp);
struct nmdmpart *onp;
struct tty *otp;

np = tty_softc(tp);
onp = np->np_other;
otp = onp->np_tty;
tty_assert_locked(tp);

/* If second part is opened, do not destroy ourselves. */
if (tty_opened(otp))
return;
/* Cannot destroy while either side is still open. */
if (tty_opened(tp))
return (EBUSY);
onp = np->np_other;
if (onp != NULL && tty_opened(onp->np_tty))
return (EBUSY);

/* Shut down self. */
tty_rel_gone(tp);
/* Mark self as gone; releases the shared tty lock. */
if (!tty_gone(tp))
tty_rel_gone(tp);
else {
tty_unlock(tp);
}

/* Shut down second part. */
/* Both sides share ns_mtx; re-lock to operate on the other side. */
tty_lock(tp);
onp = np->np_other;
if (onp == NULL)
return;
return (0);

otp = onp->np_tty;
tty_rel_gone(otp);
tty_lock(tp);
if (!tty_gone(otp))
tty_rel_gone(otp); /* releases the lock */
else {
tty_unlock(otp);
}

return (0);
}

static void
nmdm_close(struct tty *tp)
{
struct nmdmpart *np = tty_softc(tp);
struct tty *otp = np->np_other->np_tty;

/* Defer teardown until both sides are closed. */
if (tty_opened(otp))
return;

/*
* Neither side is open. nmdm_destroy() releases the shared lock;
* re-acquire via otp (same mutex) to restore the state callers expect.
*/
if (nmdm_destroy(tp) == 0)
tty_lock(otp);
}

static void
Expand All @@ -137,17 +177,24 @@ nmdm_free(void *softc)
taskqueue_drain(taskqueue_swi, &np->np_task);

/*
* The function is called on both parts simultaneously. We serialize
* with help of ns_mtx. The first invocation should return and
* delegate freeing of resources to the second.
* Both parts call nmdm_free(); the first returns after clearing
* np_other and delegates cleanup to the second. nmdmsoftc_lock is
* always acquired before ns_mtx to maintain consistent lock ordering
* with nmdm_unload().
*/
mtx_lock(&nmdmsoftc_lock);
mtx_lock(&ns->ns_mtx);
if (np->np_other != NULL) {
np->np_other->np_other = NULL;
mtx_unlock(&ns->ns_mtx);
mtx_unlock(&nmdmsoftc_lock);
return;
}
TAILQ_REMOVE(&nmdmsoftc_list, ns, ns_list);
mtx_unlock(&ns->ns_mtx);
mtx_unlock(&nmdmsoftc_lock);
mtx_destroy(&ns->ns_mtx);

free(ns, M_NMDM);
atomic_subtract_int(&nmdm_count, 1);
}
Expand Down Expand Up @@ -223,6 +270,11 @@ nmdm_clone(void *arg, struct ucred *cred, char *name, int nameen,
*dev = ns->ns_part2.np_tty->t_dev;

*end = endc;

mtx_lock(&nmdmsoftc_lock);
TAILQ_INSERT_TAIL(&nmdmsoftc_list, ns, ns_list);
mtx_unlock(&nmdmsoftc_lock);

atomic_add_int(&nmdm_count, 1);
}

Expand Down Expand Up @@ -416,6 +468,44 @@ nmdm_outwakeup(struct tty *tp)
taskqueue_enqueue(taskqueue_swi, &np->np_task);
}

static int
nmdm_unload(void)
{
struct nmdmsoftc *ns;
int error;

/*
* Destroy all nmdm pairs that are not in active use. nmdmsoftc_lock
* (outer) is held across tty_lock() (inner) to match the ordering in
* nmdm_free(), preventing WITNESS lock-order violations.
*/
mtx_lock(&nmdmsoftc_lock);
TAILQ_FOREACH(ns, &nmdmsoftc_list, ns_list) {
tty_lock(ns->ns_part1.np_tty);
error = nmdm_destroy(ns->ns_part1.np_tty);
if (error != 0) {
/* Lock held on EBUSY; release before returning. */
tty_unlock(ns->ns_part1.np_tty);
mtx_unlock(&nmdmsoftc_lock);
return (EBUSY);
}
/* nmdm_destroy() released the lock on success. */
}
mtx_unlock(&nmdmsoftc_lock);

/*
* tty_rel_gone() defers destruction through taskqueue_thread via
* destroy_dev_sched_cb() -> tty_dealloc() -> nmdm_free(). Drain
* that queue now so nmdm_count reflects the final state.
*/
destroy_dev_drain_pending();

if (atomic_load_acq_int(&nmdm_count) != 0)
return (EBUSY);

return (0);
}

/*
* Module handling
*/
Expand All @@ -424,8 +514,8 @@ nmdm_modevent(module_t mod, int type, void *data)
{
static eventhandler_tag tag;

switch(type) {
case MOD_LOAD:
switch (type) {
case MOD_LOAD:
tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000);
if (tag == NULL)
return (ENOMEM);
Expand All @@ -434,10 +524,17 @@ nmdm_modevent(module_t mod, int type, void *data)
case MOD_SHUTDOWN:
break;

case MOD_QUIESCE:
if (atomic_load_acq_int(&nmdm_count) != 0)
return (EBUSY);
break;

case MOD_UNLOAD:
if (nmdm_count != 0)
if (nmdm_unload() != 0)
return (EBUSY);

EVENTHANDLER_DEREGISTER(dev_clone, tag);
mtx_destroy(&nmdmsoftc_lock);
break;

default:
Expand Down
14 changes: 14 additions & 0 deletions sys/kern/kern_conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,20 @@ destroy_dev_drain(struct cdevsw *csw)
dev_unlock();
}

/*
* Flush all pending deferred destroy_dev() operations. Drivers that call
* tty_rel_gone() or destroy_dev_sched*() and need a synchronous teardown
* should call this after scheduling destruction and before checking whether
* their devices are gone.
*/
void
destroy_dev_drain_pending(void)
{

taskqueue_drain(taskqueue_thread, &dev_dtr_task);
taskqueue_drain(taskqueue_thread, &dev_dtr_task_giant);
}

#include "opt_ddb.h"
#ifdef DDB
#include <sys/kernel.h>
Expand Down
1 change: 1 addition & 0 deletions sys/sys/conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ void destroy_dev(struct cdev *_dev);
int destroy_dev_sched(struct cdev *dev);
int destroy_dev_sched_cb(struct cdev *dev, void (*cb)(void *), void *arg);
void destroy_dev_drain(struct cdevsw *csw);
void destroy_dev_drain_pending(void);
void dev_copyname(struct cdev *dev, char *path, size_t len);
struct cdevsw *dev_refthread(struct cdev *_dev, int *_ref);
struct cdevsw *devvn_refthread(struct vnode *vp, struct cdev **devp, int *_ref);
Expand Down
Loading