--- a/sys/netgraph7/ng_device.c +++ b/sys/netgraph7/ng_device.c @@ -88,6 +88,10 @@ struct ngd_private { struct cdev *ngddev; struct mtx ngd_mtx; int unit; + int refs; /* 1 base (node) + one per active cdevsw call */ + int dying; /* teardown began; refuse new callers */ uint16_t flags; #define NGDF_OPEN 0x0001 #define NGDF_RWAIT 0x0002 @@ -190,6 +194,8 @@ ng_device_constructor(node_p node) mtx_init(&priv->ngd_mtx, "ng_device", NULL, MTX_DEF); mtx_init(&priv->readq.ifq_mtx, "ng_device queue", NULL, MTX_DEF); IFQ_SET_MAXLEN(&priv->readq, ifqmaxlen); + priv->refs = 1; /* node's base reference */ + priv->dying = 0; /* Link everything together */ NG_NODE_SET_PRIVATE(node, priv); @@ -292,10 +298,54 @@ ng_device_disconnect(hook_p hook) priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int last; DBG; + /* + * DragonFly's destroy_dev() is asynchronous: it only queues work to + * the devfs core thread and does NOT drain in-flight (or blocked) + * cdevsw method invocations, and an open fd keeps dispatching through + * si_drv1 afterwards. Therefore priv may only be freed when the last + * device method has returned. + */ + mtx_lock(&priv->ngd_mtx); + priv->dying = 1; + if (priv->ngddev != NULL) + priv->ngddev->si_drv1 = NULL; /* no new callers can acquire */ + last = (--priv->refs == 0); + mtx_unlock(&priv->ngd_mtx); + destroy_dev(priv->ngddev); - mtx_destroy(&priv->ngd_mtx); - IF_DRAIN(&priv->readq); - mtx_destroy(&(priv)->readq.ifq_mtx); + if (last) { + mtx_destroy(&priv->ngd_mtx); + IF_DRAIN(&priv->readq); + mtx_destroy(&(priv)->readq.ifq_mtx); - free_unr(ngd_unit, priv->unit); + free_unr(ngd_unit, priv->unit); - kfree(priv, M_NETGRAPH); + kfree(priv, M_NETGRAPH); + } ng_rmnode_self(NG_HOOK_NODE(hook)); @@ -299,6 +349,34 @@ ng_device_disconnect(hook_p hook) return(0); } + +/* + * Acquire/release the private structure from a cdevsw method. Acquire + * serializes with ng_device_disconnect() on ngd_mtx so that priv cannot be + * freed while a method is running, and refuses to hand out dying privs. + */ +static priv_p +ngd_acquire(struct cdev *dev) +{ + priv_p priv = dev->si_drv1; + + if (priv == NULL) + return (NULL); + mtx_lock(&priv->ngd_mtx); + if (priv->dying) { + mtx_unlock(&priv->ngd_mtx); + return (NULL); + } + priv->refs++; + mtx_unlock(&priv->ngd_mtx); + return (priv); +} + +static void +ngd_release(priv_p priv) +{ + int last; + + mtx_lock(&priv->ngd_mtx); + last = (--priv->refs == 0); + mtx_unlock(&priv->ngd_mtx); + if (last) { + IF_DRAIN(&priv->readq); + mtx_destroy(&(priv)->readq.ifq_mtx); + mtx_destroy(&priv->ngd_mtx); + free_unr(ngd_unit, priv->unit); + kfree(priv, M_NETGRAPH); + } +} /****************************************************************************** * Device methods @@ -332,6 +410,9 @@ ngdopen(struct cdev *dev, int flag, int mode, struct thread *td) priv_p priv = (priv_p )dev->si_drv1; DBG; + + if ((priv = ngd_acquire(dev)) == NULL) + return (ENXIO); mtx_lock(&priv->ngd_mtx); priv->flags |= NGDF_OPEN; @@ -340,6 +421,8 @@ ngdopen(struct cdev *dev, int flag, int mode, struct thread *td) return(0); } @@ -349,6 +432,9 @@ ngdclose(struct cdev *dev, int flag, int mode, struct thread *td) priv_p priv = (priv_p )dev->si_drv1; DBG; + + if ((priv = ngd_acquire(dev)) == NULL) + return (ENXIO); + mtx_lock(&priv->ngd_mtx); priv->flags &= ~NGDF_OPEN; mtx_unlock(&priv->ngd_mtx); @@ -409,6 +495,10 @@ ngdread(struct cdev *dev, struct uio *uio, int flag) int len, error = 0; DBG; + + if ((priv = ngd_acquire(dev)) == NULL) + return (ENXIO); /* get an mbuf */ do { @@ -431,6 +521,7 @@ ngdread(struct cdev *dev, struct uio *uio, int flag) if (m) m_freem(m); + ngd_release(priv); return (error); } @@ -452,6 +543,10 @@ ngdwrite(struct cdev *dev, struct uio *uio, int flag) int error = 0; DBG; + + if ((priv = ngd_acquire(dev)) == NULL) + return (ENXIO); if (uio->uio_resid == 0) return (0); @@ -467,6 +562,7 @@ ngdwrite(struct cdev *dev, struct uio *uio, int flag) NG_SEND_DATA_ONLY(error, priv->hook, m); + ngd_release(priv); return (error); } @@ -479,6 +575,10 @@ ngdpoll(struct cdev *dev, int events, struct thread *td) int revents = 0; + if ((priv = ngd_acquire(dev)) == NULL) + return (0); + if (events & (POLLIN | POLLRDNORM) && !IFQ_IS_EMPTY(&priv->readq)) revents |= events & (POLLIN | POLLRDNORM); + ngd_release(priv); return (revents); }