diff --git a/sys/dev/snp/snp.c b/sys/dev/snp/snp.c index e6a7eaf..d349926 100644 --- a/sys/dev/snp/snp.c +++ b/sys/dev/snp/snp.c @@ -118,7 +118,7 @@ static struct clonedevs *snpclones; static struct tty *snpdevtotty(struct cdev *dev); static void snp_clone(void *arg, struct ucred *cred, char *name, int namelen, struct cdev **dev); -static int snp_detach(struct snoop *snp); +static void snp_detach(struct snoop *snp); static int snp_down(struct snoop *snp); static int snp_in(struct snoop *snp, char *buf, int n); static int snp_modevent(module_t mod, int what, void *arg); @@ -422,7 +422,7 @@ snpopen(struct cdev *dev, int flag, int mode, struct thread *td) } -static int +static void snp_detach(struct snoop *snp) { struct tty *tp; @@ -451,8 +451,6 @@ detach_notty: selwakeuppri(&snp->snp_sel, PZERO + 1); if ((snp->snp_flags & SNOOP_OPEN) == 0) free(snp, M_SNP); - - return (0); } static int @@ -467,8 +465,9 @@ snpclose(struct cdev *dev, int flags, int fmt, struct thread *td) snp->snp_flags &= ~SNOOP_OPEN; dev->si_drv1 = NULL; destroy_dev(dev); + snp_detach(snp); - return (snp_detach(snp)); + return (0); } static int @@ -481,8 +480,9 @@ snp_down(struct snoop *snp) snp->snp_blen = SNOOP_MINLEN; } snp->snp_flags |= SNOOP_DOWN; + snp_detach(snp); - return (snp_detach(snp)); + return (0); } static int @@ -616,10 +616,9 @@ snp_clone(void *arg, struct ucred *cred, char *name, int namelen, return; i = clone_create(&snpclones, &snp_cdevsw, &u, dev, 0); if (i) - *dev = make_dev(&snp_cdevsw, unit2minor(u), - UID_ROOT, GID_WHEEL, 0600, "snp%d", u); + *dev = make_dev_credf(MAKEDEV_REF, &snp_cdevsw, unit2minor(u), + NULL, UID_ROOT, GID_WHEEL, 0600, "snp%d", u); if (*dev != NULL) { - dev_ref(*dev); (*dev)->si_flags |= SI_CHEAPCLONE; } } @@ -640,7 +639,9 @@ snp_modevent(module_t mod, int type, void *data) if (!LIST_EMPTY(&snp_sclist)) return (EBUSY); EVENTHANDLER_DEREGISTER(dev_clone, eh_tag); + drain_dev_clone_events(); clone_cleanup(&snpclones); + destroy_dev_drain(&snp_cdevsw); ldisc_deregister(snooplinedisc); break; default: diff --git a/sys/fs/devfs/devfs_int.h b/sys/fs/devfs/devfs_int.h index a547a66..2ea7c7c 100644 --- a/sys/fs/devfs/devfs_int.h +++ b/sys/fs/devfs/devfs_int.h @@ -47,11 +47,14 @@ 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; }; struct cdev *devfs_alloc(void); @@ -62,6 +65,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 54f5d59..f90406f 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; diff --git a/sys/kern/kern_conf.c b/sys/kern/kern_conf.c index 4ebd277..f436b1d 100644 --- a/sys/kern/kern_conf.c +++ b/sys/kern/kern_conf.c @@ -39,9 +39,11 @@ __FBSDID("$FreeBSD: src/sys/kern/kern_conf.c,v 1.203 2007/06/19 13:19:23 kib Exp #include #include #include +#include #include #include #include +#include #include #include @@ -50,9 +52,12 @@ static MALLOC_DEFINE(M_DEVT, "cdev", "cdev storage"); struct mtx devmtx; static void destroy_devl(struct cdev *dev); -static struct cdev *make_dev_credv(struct cdevsw *devsw, int minornr, - struct ucred *cr, uid_t uid, gid_t gid, int mode, const char *fmt, - va_list ap); +static void destroy_dev_sched(struct cdev *dev); + +static struct cdev *make_dev_credv(int flags, + struct cdevsw *devsw, int minornr, + struct ucred *cr, uid_t uid, gid_t gid, int mode, const char *fmt, + va_list ap); static struct cdev_priv_list cdevp_free_list = TAILQ_HEAD_INITIALIZER(cdevp_free_list); @@ -143,12 +148,18 @@ struct cdevsw * dev_refthread(struct cdev *dev) { struct cdevsw *csw; + struct cdev_priv *cdp; mtx_assert(&devmtx, MA_NOTOWNED); dev_lock(); csw = dev->si_devsw; - if (csw != NULL) - dev->si_threadcount++; + if (csw != NULL) { + cdp = dev->si_priv; + if ((cdp->cdp_flags & CDP_SCHED_DTR) == 0) + dev->si_threadcount++; + else + csw = NULL; + } dev_unlock(); return (csw); } @@ -157,15 +168,19 @@ struct cdevsw * devvn_refthread(struct vnode *vp, struct cdev **devp) { struct cdevsw *csw; + struct cdev_priv *cdp; mtx_assert(&devmtx, MA_NOTOWNED); csw = NULL; dev_lock(); *devp = vp->v_rdev; if (*devp != NULL) { - csw = (*devp)->si_devsw; - if (csw != NULL) - (*devp)->si_threadcount++; + cdp = (*devp)->si_priv; + if ((cdp->cdp_flags & CDP_SCHED_DTR) == 0) { + csw = (*devp)->si_devsw; + if (csw != NULL) + (*devp)->si_threadcount++; + } } dev_unlock(); return (csw); @@ -554,8 +569,9 @@ prep_cdevsw(struct cdevsw *devsw) dev_unlock(); } -static struct cdev * -make_dev_credv(struct cdevsw *devsw, int minornr, struct ucred *cr, uid_t uid, +struct cdev * +make_dev_credv(int flags, struct cdevsw *devsw, int minornr, + struct ucred *cr, uid_t uid, gid_t gid, int mode, const char *fmt, va_list ap) { struct cdev *dev; @@ -569,6 +585,8 @@ make_dev_credv(struct cdevsw *devsw, int minornr, struct ucred *cr, uid_t uid, dev = devfs_alloc(); dev_lock(); dev = newdev(devsw, minornr, dev); + if (flags & MAKEDEV_REF) + dev_refl(dev); if (dev->si_flags & SI_CHEAPCLONE && dev->si_flags & SI_NAMED) { /* @@ -611,7 +629,7 @@ make_dev(struct cdevsw *devsw, int minornr, uid_t uid, gid_t gid, int mode, va_list ap; va_start(ap, fmt); - dev = make_dev_credv(devsw, minornr, NULL, uid, gid, mode, fmt, ap); + dev = make_dev_credv(0, devsw, minornr, NULL, uid, gid, mode, fmt, ap); va_end(ap); return (dev); } @@ -624,7 +642,23 @@ make_dev_cred(struct cdevsw *devsw, int minornr, struct ucred *cr, uid_t uid, va_list ap; va_start(ap, fmt); - dev = make_dev_credv(devsw, minornr, cr, uid, gid, mode, fmt, ap); + dev = make_dev_credv(0, devsw, minornr, cr, uid, gid, mode, fmt, ap); + va_end(ap); + + return (dev); +} + +struct cdev * +make_dev_credf(int flags, struct cdevsw *devsw, int minornr, + struct ucred *cr, uid_t uid, + gid_t gid, int mode, const char *fmt, ...) +{ + struct cdev *dev; + va_list ap; + + va_start(ap, fmt); + dev = make_dev_credv(flags, devsw, minornr, cr, uid, gid, mode, + fmt, ap); va_end(ap); return (dev); @@ -728,8 +762,10 @@ destroy_devl(struct cdev *dev) LIST_REMOVE(dev, si_list); /* If cdevsw has no more struct cdev *'s, clean it */ - if (LIST_EMPTY(&csw->d_devs)) + if (LIST_EMPTY(&csw->d_devs)) { fini_cdevsw(csw); + wakeup(&csw->d_devs); + } } dev->si_flags &= ~SI_ALIAS; dev->si_refcount--; /* Avoid race with dev_rel() */ @@ -744,10 +780,16 @@ destroy_devl(struct cdev *dev) void destroy_dev(struct cdev *dev) { + struct cdevsw *csw; dev_lock(); - destroy_devl(dev); - dev_unlock_and_free(); + csw = dev->si_devsw; + if ((csw != NULL && csw->d_purge != NULL) || + dev->si_threadcount == 0) { + destroy_devl(dev); + dev_unlock_and_free(); + } else + destroy_dev_sched(dev); } const char * @@ -915,21 +957,99 @@ clone_create(struct clonedevs **cdp, struct cdevsw *csw, int *up, struct cdev ** void clone_cleanup(struct clonedevs **cdp) { - struct cdev *dev, *tdev; + struct cdev *dev; + struct cdev_priv *cp; struct clonedevs *cd; cd = *cdp; if (cd == NULL) return; dev_lock(); - LIST_FOREACH_SAFE(dev, &cd->head, si_clone, tdev) { + while (!LIST_EMPTY(&cd->head)) { + dev = LIST_FIRST(&cd->head); + LIST_REMOVE(dev, si_clone); KASSERT(dev->si_flags & SI_CLONELIST, ("Dev %p(%s) should be on clonelist", dev, dev->si_name)); - KASSERT(dev->si_flags & SI_NAMED, - ("Driver has goofed in cloning underways udev %x", dev->si_drv0)); - destroy_devl(dev); + dev->si_flags &= ~SI_CLONELIST; + cp = dev->si_priv; + if (!(cp->cdp_flags & CDP_SCHED_DTR)) { + cp->cdp_flags |= CDP_SCHED_DTR; + KASSERT(dev->si_flags & SI_NAMED, + ("Driver has goofed in cloning underways udev %x", dev->si_drv0)); + destroy_devl(dev); + } } dev_unlock(); free(cd, M_DEVBUF); *cdp = NULL; } + +static TAILQ_HEAD(, cdev_priv) dev_ddtr = + TAILQ_HEAD_INITIALIZER(dev_ddtr); +static struct task dev_dtr_task; + +static void +destroy_dev_tq(void *ctx, int pending) +{ + struct cdev_priv *cp; + struct cdev *dev; + + dev_lock(); + while (!TAILQ_EMPTY(&dev_ddtr)) { + cp = TAILQ_FIRST(&dev_ddtr); + dev = &cp->cdp_c; + KASSERT(cp->cdp_flags & CDP_SCHED_DTR, + ("cdev %p in dev_destroy_tq without CDP_SCHED_DTR", cp)); + TAILQ_REMOVE(&dev_ddtr, cp, cdp_dtr_list); + destroy_devl(dev); + dev_unlock(); + dev_rel(dev); + dev_lock(); + } + dev_unlock(); +} + +static void +destroy_dev_sched(struct cdev *dev) +{ + struct cdev_priv *cp; + + cp = dev->si_priv; + if (cp->cdp_flags & CDP_SCHED_DTR) { + dev_unlock(); + return; + } + dev_refl(dev); + cp->cdp_flags |= CDP_SCHED_DTR; + TAILQ_INSERT_TAIL(&dev_ddtr, cp, cdp_dtr_list); + dev_unlock(); + taskqueue_enqueue(taskqueue_swi_giant, &dev_dtr_task); +} + +void +destroy_dev_drain(struct cdevsw *csw) +{ + + dev_lock(); + while (!LIST_EMPTY(&csw->d_devs)) { + msleep(&csw->d_devs, &devmtx, PRIBIO, "devscd", hz/10); + } + dev_unlock(); +} + +void +drain_dev_clone_events(void) +{ + + sx_xlock(&clone_drain_lock); + sx_xunlock(&clone_drain_lock); +} + +static void +devdtr_init(void *dummy __unused) +{ + + TASK_INIT(&dev_dtr_task, 0, destroy_dev_tq, NULL); +} + +SYSINIT(devdtr, SI_SUB_DEVFS, SI_ORDER_SECOND, devdtr_init, NULL); diff --git a/sys/kern/tty_pts.c b/sys/kern/tty_pts.c index 41ec1cb..14810cb 100644 --- a/sys/kern/tty_pts.c +++ b/sys/kern/tty_pts.c @@ -217,14 +217,15 @@ static void pty_release(struct pt_desc *pt) { + mtx_lock(&pt_mtx); KASSERT(pt->pt_ptc_open == 0 && pt->pt_pts_open == 0, ("pty_release: pts/%d freed while open\n", pt->pt_num)); KASSERT(pt->pt_devs == NULL && pt->pt_devc == NULL, ("pty_release: pts/%d freed whith non-null struct cdev\n", pt->pt_num)); - mtx_assert(&pt_mtx, MA_OWNED); nb_allocated--; LIST_REMOVE(pt, pt_list); LIST_INSERT_HEAD(&pt_free_list, pt, pt_list); + mtx_unlock(&pt_mtx); } /* @@ -235,6 +236,7 @@ pty_release(struct pt_desc *pt) static void pty_maybecleanup(struct pt_desc *pt) { + struct cdev *pt_devs, *pt_devc; if (pt->pt_ptc_open || pt->pt_pts_open) return; @@ -245,16 +247,16 @@ pty_maybecleanup(struct pt_desc *pt) if (bootverbose) printf("destroying pty %d\n", pt->pt_num); - destroy_dev(pt->pt_devs); - destroy_dev(pt->pt_devc); + pt_devs = pt->pt_devs; + pt_devc = pt->pt_devc; pt->pt_devs = pt->pt_devc = NULL; pt->pt_tty->t_dev = NULL; + pt_devc->si_drv1 = NULL; ttyrel(pt->pt_tty); pt->pt_tty = NULL; - - mtx_lock(&pt_mtx); + destroy_dev(pt_devs); + destroy_dev(pt_devc); pty_release(pt); - mtx_unlock(&pt_mtx); } /*ARGSUSED*/ @@ -391,6 +393,8 @@ ptcopen(struct cdev *dev, int flag, int devtype, struct thread *td) struct cdev *devs; pt = dev->si_drv1; + if (pt == NULL) + return (EIO); /* * In case we have destroyed the struct tty at the last connect time, * we need to recreate it. @@ -866,9 +870,12 @@ pty_clone(void *arg, struct ucred *cred, char *name, int namelen, if (strcmp(name, "ptmx") != 0) return; + mtx_lock(&Giant); pt = pty_new(); - if (pt == NULL) + if (pt == NULL) { + mtx_unlock(&Giant); return; + } /* * XXX: Lack of locking here considered worrying. We expose the @@ -881,14 +888,14 @@ pty_clone(void *arg, struct ucred *cred, char *name, int namelen, * opened, or some way to tell devfs that "this had better be for * an open() or we won't create a device". */ - pt->pt_devc = devc = make_dev_cred(&ptc_cdevsw, + pt->pt_devc = devc = make_dev_credf(MAKEDEV_REF, &ptc_cdevsw, NUM_TO_MINOR(pt->pt_num), cred, UID_ROOT, GID_WHEEL, 0666, "pty/%d", pt->pt_num); - dev_ref(devc); devc->si_drv1 = pt; devc->si_tty = pt->pt_tty; *dev = devc; + mtx_unlock(&Giant); if (bootverbose) printf("pty_clone: allocated pty %d to uid %d\n", pt->pt_num, diff --git a/sys/kern/tty_pty.c b/sys/kern/tty_pty.c index a7a18a1..97ab520 100644 --- a/sys/kern/tty_pty.c +++ b/sys/kern/tty_pty.c @@ -785,9 +785,8 @@ pty_clone(void *arg, struct ucred *cr, char *name, int namelen, u += name[4] - 'a' + 10; else return; - *dev = make_dev_cred(&ptc_cdevsw, u, cr, + *dev = make_dev_credf(MAKEDEV_REF, &ptc_cdevsw, u, cr, UID_ROOT, GID_WHEEL, 0666, "pty%c%r", names[u / 32], u % 32); - dev_ref(*dev); (*dev)->si_flags |= SI_CHEAPCLONE; return; } diff --git a/sys/kern/tty_tty.c b/sys/kern/tty_tty.c index 40e8d34..b2696e8 100644 --- a/sys/kern/tty_tty.c +++ b/sys/kern/tty_tty.c @@ -31,8 +31,12 @@ __FBSDID("$FreeBSD: src/sys/kern/tty_tty.c,v 1.59 2006/09/27 16:41:15 mbr Exp $" #include #include #include +#include #include +#include +#include + static d_open_t cttyopen; static struct cdevsw ctty_cdevsw = { @@ -60,6 +64,11 @@ ctty_clone(void *arg, struct ucred *cred, char *name, int namelen, return; if (strcmp(name, "tty")) return; + sx_sunlock(&clone_drain_lock); + mtx_lock(&Giant); + sx_slock(&proctree_lock); + sx_slock(&clone_drain_lock); + dev_lock(); if (!(curthread->td_proc->p_flag & P_CONTROLT)) *dev = ctty; else if (curthread->td_proc->p_session->s_ttyvp == NULL) @@ -70,7 +79,10 @@ ctty_clone(void *arg, struct ucred *cred, char *name, int namelen, *dev = ctty; } else *dev = curthread->td_proc->p_session->s_ttyvp->v_rdev; - dev_ref(*dev); + dev_refl(*dev); + dev_unlock(); + sx_sunlock(&proctree_lock); + mtx_unlock(&Giant); } static void diff --git a/sys/sys/conf.h b/sys/sys/conf.h index 61c190f..4bc06b2 100644 --- a/sys/sys/conf.h +++ b/sys/sys/conf.h @@ -245,6 +245,8 @@ int clone_create(struct clonedevs **, struct cdevsw *, int *unit, struct cdev ** int count_dev(struct cdev *_dev); void destroy_dev(struct cdev *_dev); +void destroy_dev_drain(struct cdevsw *csw); +void drain_dev_clone_events(void); struct cdevsw *dev_refthread(struct cdev *_dev); struct cdevsw *devvn_refthread(struct vnode *vp, struct cdev **devp); void dev_relthread(struct cdev *_dev); @@ -258,6 +260,12 @@ struct cdev *make_dev(struct cdevsw *_devsw, int _minor, uid_t _uid, gid_t _gid, struct cdev *make_dev_cred(struct cdevsw *_devsw, int _minor, struct ucred *_cr, uid_t _uid, gid_t _gid, int _perms, const char *_fmt, ...) __printflike(7, 8); +#define MAKEDEV_REF 0x1 +#define MAKEDEV_WHTOUT 0x2 +struct cdev *make_dev_credf(int flags, + struct cdevsw *devsw, int minornr, + struct ucred *cr, uid_t uid, gid_t gid, int mode, + const char *fmt, ...) __printflike(8, 9); struct cdev *make_dev_alias(struct cdev *_pdev, const char *_fmt, ...) __printflike(2, 3); int dev2unit(struct cdev *_dev); void dev_lock(void);