From 0ae42a409556e8dd0fe24abe1db9159f80d1d3a0 Mon Sep 17 00:00:00 2001 From: kib Date: Tue, 3 Jul 2007 17:42:37 +0000 Subject: Since rev. 1.199 of sys/kern/kern_conf.c, the thread that calls destroy_dev() from d_close() cdev method would self-deadlock. devfs_close() bump device thread reference counter, and destroy_dev() sleeps, waiting for si_threadcount to reach zero for cdev without d_purge method. destroy_dev_sched() could be used instead from d_close(), to schedule execution of destroy_dev() in another context. The destroy_dev_sched_drain() function can be used to drain the scheduled calls to destroy_dev_sched(). Similarly, drain_dev_clone_events() drains the events clone to make sure no lingering devices are left after dev_clone event handler deregistered. make_dev_credf(MAKEDEV_REF) function should be used from dev_clone event handlers instead of make_dev()/make_dev_cred() to ensure that created device has reference counter bumped before cdev mutex is dropped inside make_dev(). Reviewed by: tegge (early versions), njl (programming interface) Debugging help and testing by: Peter Holm Approved by: re (kensmith) --- sys/fs/devfs/devfs_int.h | 6 ++++++ sys/fs/devfs/devfs_vnops.c | 13 +++++++++++++ 2 files changed, 19 insertions(+) (limited to 'sys/fs/devfs') diff --git a/sys/fs/devfs/devfs_int.h b/sys/fs/devfs/devfs_int.h index 2bd7f99..3848ab4 100644 --- a/sys/fs/devfs/devfs_int.h +++ b/sys/fs/devfs/devfs_int.h @@ -47,11 +47,16 @@ struct cdev_priv { u_int cdp_flags; #define CDP_ACTIVE (1 << 0) +#define CDP_SCHED_DTR (1 << 1) u_int cdp_inuse; u_int cdp_maxdirent; struct devfs_dirent **cdp_dirents; struct devfs_dirent *cdp_dirent0; + + TAILQ_ENTRY(cdev_priv) cdp_dtr_list; + void (*cdp_dtr_cb)(void *); + void *cdp_dtr_cb_arg; }; struct cdev *devfs_alloc(void); @@ -62,6 +67,7 @@ void devfs_destroy(struct cdev *dev); extern struct unrhdr *devfs_inos; extern struct mtx devmtx; extern struct mtx devfs_de_interlock; +extern struct sx clone_drain_lock; extern TAILQ_HEAD(cdev_priv_list, cdev_priv) cdevp_list; #endif /* _KERNEL */ diff --git a/sys/fs/devfs/devfs_vnops.c b/sys/fs/devfs/devfs_vnops.c index 0acf99b..fcf3b3a 100644 --- a/sys/fs/devfs/devfs_vnops.c +++ b/sys/fs/devfs/devfs_vnops.c @@ -75,6 +75,8 @@ static struct fileops devfs_ops_f; struct mtx devfs_de_interlock; MTX_SYSINIT(devfs_de_interlock, &devfs_de_interlock, "devfs interlock", MTX_DEF); +struct sx clone_drain_lock; +SX_SYSINIT(clone_drain_lock, &clone_drain_lock, "clone events drain lock"); static int devfs_fp_check(struct file *fp, struct cdev **devp, struct cdevsw **dswp) @@ -618,8 +620,19 @@ devfs_lookupx(struct vop_lookup_args *ap, int *dm_unlock) break; cdev = NULL; + DEVFS_DMP_HOLD(dmp); + sx_xunlock(&dmp->dm_lock); + sx_slock(&clone_drain_lock); EVENTHANDLER_INVOKE(dev_clone, td->td_ucred, pname, strlen(pname), &cdev); + sx_sunlock(&clone_drain_lock); + sx_xlock(&dmp->dm_lock); + if (DEVFS_DMP_DROP(dmp)) { + *dm_unlock = 0; + sx_xunlock(&dmp->dm_lock); + devfs_unmount_final(dmp); + return (ENOENT); + } if (cdev == NULL) break; -- cgit v1.1