summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorhselasky <hselasky@FreeBSD.org>2015-02-05 20:49:13 +0000
committerhselasky <hselasky@FreeBSD.org>2015-02-05 20:49:13 +0000
commit34369aa374076807c478e6f5977f216d893c74fc (patch)
treeab0849694fbf99b99b61390be9f21dd68b7ae99d
parent181bf69d80b7bd5865db0ef46e187479e37c79cf (diff)
downloadFreeBSD-src-34369aa374076807c478e6f5977f216d893c74fc.zip
FreeBSD-src-34369aa374076807c478e6f5977f216d893c74fc.tar.gz
MFC r277179, r277199 and r277391:
Add a kernel function to delist our kernel character devices, so that the device name can be re-used right away in case we are destroying the character devices in the background.
-rw-r--r--sys/fs/devfs/devfs_int.h1
-rw-r--r--sys/kern/kern_conf.c52
-rw-r--r--sys/sys/conf.h1
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);
OpenPOWER on IntegriCloud