diff options
-rw-r--r-- | sys/fs/devfs/devfs_int.h | 1 | ||||
-rw-r--r-- | sys/kern/kern_conf.c | 52 | ||||
-rw-r--r-- | sys/sys/conf.h | 1 |
3 files changed, 47 insertions, 7 deletions
diff --git a/sys/fs/devfs/devfs_int.h b/sys/fs/devfs/devfs_int.h index ce55416..6c57109 100644 --- a/sys/fs/devfs/devfs_int.h +++ b/sys/fs/devfs/devfs_int.h @@ -56,6 +56,7 @@ struct cdev_priv { u_int cdp_flags; #define CDP_ACTIVE (1 << 0) #define CDP_SCHED_DTR (1 << 1) +#define CDP_UNREF_DTR (1 << 2) u_int cdp_inuse; u_int cdp_maxdirent; diff --git a/sys/kern/kern_conf.c b/sys/kern/kern_conf.c index b1ae7a1..b2835e2 100644 --- a/sys/kern/kern_conf.c +++ b/sys/kern/kern_conf.c @@ -116,6 +116,8 @@ dev_free_devlocked(struct cdev *cdev) mtx_assert(&devmtx, MA_OWNED); cdp = cdev2priv(cdev); + KASSERT((cdp->cdp_flags & CDP_UNREF_DTR) == 0, + ("destroy_dev() was not called after delist_dev(%p)", cdev)); TAILQ_INSERT_HEAD(&cdevp_free_list, cdp, cdp_list); } @@ -1035,6 +1037,7 @@ destroy_devl(struct cdev *dev) { struct cdevsw *csw; struct cdev_privdata *p; + struct cdev_priv *cdp; mtx_assert(&devmtx, MA_OWNED); KASSERT(dev->si_flags & SI_NAMED, @@ -1043,13 +1046,22 @@ destroy_devl(struct cdev *dev) ("WARNING: Driver mistake: destroy_dev on eternal %d\n", dev2unit(dev))); - devfs_destroy(dev); + cdp = cdev2priv(dev); + if ((cdp->cdp_flags & CDP_UNREF_DTR) == 0) { + /* + * Avoid race with dev_rel(), e.g. from the populate + * loop. If CDP_UNREF_DTR flag is set, the reference + * to be dropped at the end of destroy_devl() was + * already taken by delist_dev_locked(). + */ + dev_refl(dev); + + devfs_destroy(dev); + } /* Remove name marking */ dev->si_flags &= ~SI_NAMED; - dev->si_refcount++; /* Avoid race with dev_rel() */ - /* If we are a child, remove us from the parents list */ if (dev->si_flags & SI_CHILD) { LIST_REMOVE(dev, si_siblings); @@ -1105,13 +1117,39 @@ destroy_devl(struct cdev *dev) } } dev->si_flags &= ~SI_ALIAS; - dev->si_refcount--; /* Avoid race with dev_rel() */ + cdp->cdp_flags &= ~CDP_UNREF_DTR; + dev->si_refcount--; - if (dev->si_refcount > 0) { + if (dev->si_refcount > 0) LIST_INSERT_HEAD(&dead_cdevsw.d_devs, dev, si_list); - } else { + else dev_free_devlocked(dev); - } +} + +static void +delist_dev_locked(struct cdev *dev) +{ + struct cdev_priv *cdp; + struct cdev *child; + + mtx_assert(&devmtx, MA_OWNED); + cdp = cdev2priv(dev); + if ((cdp->cdp_flags & CDP_UNREF_DTR) != 0) + return; + cdp->cdp_flags |= CDP_UNREF_DTR; + dev_refl(dev); + devfs_destroy(dev); + LIST_FOREACH(child, &dev->si_children, si_siblings) + delist_dev_locked(child); +} + +void +delist_dev(struct cdev *dev) +{ + + dev_lock(); + delist_dev_locked(dev); + dev_unlock(); } void diff --git a/sys/sys/conf.h b/sys/sys/conf.h index bb9d1b5..e8c5367 100644 --- a/sys/sys/conf.h +++ b/sys/sys/conf.h @@ -245,6 +245,7 @@ void clone_cleanup(struct clonedevs **); int clone_create(struct clonedevs **, struct cdevsw *, int *unit, struct cdev **dev, int extra); int count_dev(struct cdev *_dev); +void delist_dev(struct cdev *_dev); 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); |