Module Name: src Committed By: riastradh Date: Mon Mar 28 12:42:45 UTC 2022
Modified Files: src/sys/dev/usb: uhid.c Log Message: uhid(4): Use d_cfdriver/devtounit/cancel to avoid open/detach races. - Split uhidclose into separate uhidcancel and uhidclose parts. uhidcancel interrupts pending I/O operations (open, read, write, ioctl, &c.); uhidclose doesn't run until all I/O operations are done. - Handle case where, owing to revoke(2), uhidcancel/uhidclose run concurrently with a uhidopen that hasn't yet noticed that there isn't actually a device. - Handle case where, owing to revoke(2), uhidread might be cancelled by mere revoke, not by detach, so it has to wake up when the device is closing, not (just) when dying (but dying will lead to closing so no need to check for dying). - Omit needless reference-counting goo. vdevgone takes care of this for us by cancelling all I/O operations with uhidcancel, waiting for I/O operations to drain, closing the device, and waiting until it is closed if that is already happening concurrently. - Name the closed/changing/open states rather than using 0/1/2. - Omit needless sc_dying. To generate a diff of this commit: cvs rdiff -u -r1.119 -r1.120 src/sys/dev/usb/uhid.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/dev/usb/uhid.c diff -u src/sys/dev/usb/uhid.c:1.119 src/sys/dev/usb/uhid.c:1.120 --- src/sys/dev/usb/uhid.c:1.119 Sun Sep 26 15:07:17 2021 +++ src/sys/dev/usb/uhid.c Mon Mar 28 12:42:45 2022 @@ -1,4 +1,4 @@ -/* $NetBSD: uhid.c,v 1.119 2021/09/26 15:07:17 thorpej Exp $ */ +/* $NetBSD: uhid.c,v 1.120 2022/03/28 12:42:45 riastradh Exp $ */ /* * Copyright (c) 1998, 2004, 2008, 2012 The NetBSD Foundation, Inc. @@ -35,7 +35,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: uhid.c,v 1.119 2021/09/26 15:07:17 thorpej Exp $"); +__KERNEL_RCSID(0, "$NetBSD: uhid.c,v 1.120 2022/03/28 12:42:45 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_compat_netbsd.h" @@ -89,7 +89,6 @@ struct uhid_softc { kmutex_t sc_lock; kcondvar_t sc_cv; - kcondvar_t sc_detach_cv; int sc_isize; int sc_osize; @@ -104,10 +103,13 @@ struct uhid_softc { volatile uint32_t sc_state; /* driver state */ #define UHID_IMMED 0x02 /* return read data immediately */ - int sc_refcnt; int sc_raw; - u_char sc_open; - u_char sc_dying; + enum { + UHID_CLOSED, + UHID_OPENING, + UHID_OPEN, + } sc_open; + bool sc_closing; }; #define UHIDUNIT(dev) (minor(dev)) @@ -115,6 +117,7 @@ struct uhid_softc { #define UHID_BSIZE 1020 /* buffer size */ static dev_type_open(uhidopen); +static dev_type_cancel(uhidcancel); static dev_type_close(uhidclose); static dev_type_read(uhidread); static dev_type_write(uhidwrite); @@ -124,6 +127,7 @@ static dev_type_kqfilter(uhidkqfilter); const struct cdevsw uhid_cdevsw = { .d_open = uhidopen, + .d_cancel = uhidcancel, .d_close = uhidclose, .d_read = uhidread, .d_write = uhidwrite, @@ -134,22 +138,19 @@ const struct cdevsw uhid_cdevsw = { .d_mmap = nommap, .d_kqfilter = uhidkqfilter, .d_discard = nodiscard, + .d_cfdriver = &uhid_cd, + .d_devtounit = dev_minor_unit, .d_flag = D_OTHER }; -Static void uhid_intr(struct uhidev *, void *, u_int); - -Static int uhid_do_read(struct uhid_softc *, struct uio *, int); -Static int uhid_do_write(struct uhid_softc *, struct uio *, int); -Static int uhid_do_ioctl(struct uhid_softc*, u_long, void *, int, struct lwp *); +static void uhid_intr(struct uhidev *, void *, u_int); static int uhid_match(device_t, cfdata_t, void *); static void uhid_attach(device_t, device_t, void *); static int uhid_detach(device_t, int); -static int uhid_activate(device_t, enum devact); CFATTACH_DECL_NEW(uhid, sizeof(struct uhid_softc), uhid_match, uhid_attach, - uhid_detach, uhid_activate); + uhid_detach, NULL); static int uhid_match(device_t parent, cfdata_t match, void *aux) @@ -194,7 +195,6 @@ uhid_attach(device_t parent, device_t se mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB); cv_init(&sc->sc_cv, "uhidrea"); - cv_init(&sc->sc_detach_cv, "uhiddet"); if (!pmf_device_register(self, NULL, NULL)) aprint_error_dev(self, "couldn't establish power handler\n"); @@ -203,20 +203,6 @@ uhid_attach(device_t parent, device_t se } static int -uhid_activate(device_t self, enum devact act) -{ - struct uhid_softc *sc = device_private(self); - - switch (act) { - case DVACT_DEACTIVATE: - sc->sc_dying = 1; - return 0; - default: - return EOPNOTSUPP; - } -} - -static int uhid_detach(device_t self, int flags) { struct uhid_softc *sc = device_private(self); @@ -224,24 +210,6 @@ uhid_detach(device_t self, int flags) DPRINTF(("uhid_detach: sc=%p flags=%d\n", sc, flags)); - /* Prevent new I/O operations, and interrupt any pending reads. */ - mutex_enter(&sc->sc_lock); - sc->sc_dying = 1; - cv_broadcast(&sc->sc_cv); - mutex_exit(&sc->sc_lock); - - /* Interrupt any pending uhidev_write. */ - uhidev_stop(&sc->sc_hdev); - - /* Wait for I/O operations to complete. */ - mutex_enter(&sc->sc_lock); - while (sc->sc_refcnt) { - DPRINTF(("%s: open=%d refcnt=%d\n", __func__, - sc->sc_open, sc->sc_refcnt)); - cv_wait(&sc->sc_detach_cv, &sc->sc_lock); - } - mutex_exit(&sc->sc_lock); - pmf_device_deregister(self); /* locate the major number */ @@ -251,35 +219,16 @@ uhid_detach(device_t self, int flags) mn = device_unit(self); vdevgone(maj, mn, mn, VCHR); - /* - * Wait for close to finish. - * - * XXX I assumed that vdevgone would synchronously call close, - * and not return before it has completed, but empirically the - * assertion of sc->sc_open == 0 below fires if we don't wait - * here. Someone^TM should carefully examine vdevgone to - * ascertain what it guarantees, and audit all other users of - * it accordingly. - */ - mutex_enter(&sc->sc_lock); - while (sc->sc_open) { - DPRINTF(("%s: open=%d\n", __func__, sc->sc_open)); - cv_wait(&sc->sc_detach_cv, &sc->sc_lock); - } - mutex_exit(&sc->sc_lock); - - KASSERT(sc->sc_open == 0); - KASSERT(sc->sc_refcnt == 0); + KASSERTMSG(sc->sc_open == UHID_CLOSED, "open=%d", (int)sc->sc_open); cv_destroy(&sc->sc_cv); - cv_destroy(&sc->sc_detach_cv); mutex_destroy(&sc->sc_lock); seldestroy(&sc->sc_rsel); return 0; } -void +static void uhid_intr(struct uhidev *addr, void *data, u_int len) { struct uhid_softc *sc = (struct uhid_softc *)addr; @@ -316,29 +265,23 @@ uhid_intr(struct uhidev *addr, void *dat static int uhidopen(dev_t dev, int flag, int mode, struct lwp *l) { - struct uhid_softc *sc; + struct uhid_softc *sc = device_lookup_private(&uhid_cd, UHIDUNIT(dev)); int error; - sc = device_lookup_private(&uhid_cd, UHIDUNIT(dev)); + DPRINTF(("uhidopen: sc=%p\n", sc)); if (sc == NULL) return ENXIO; - DPRINTF(("uhidopen: sc=%p\n", sc)); - /* - * Try to open. If dying, or if already open (or opening), - * fail -- opens are exclusive. + * Try to open. If closing in progress, give up. If already + * open (or opening), fail -- opens are exclusive. */ mutex_enter(&sc->sc_lock); - if (sc->sc_dying) { - mutex_exit(&sc->sc_lock); - return ENXIO; - } - if (sc->sc_open) { + if (sc->sc_open != UHID_CLOSED || sc->sc_closing) { mutex_exit(&sc->sc_lock); return EBUSY; } - sc->sc_open = 1; + sc->sc_open = UHID_OPENING; atomic_store_relaxed(&sc->sc_state, 0); mutex_exit(&sc->sc_lock); @@ -366,17 +309,12 @@ uhidopen(dev_t dev, int flag, int mode, /* We are open for business. */ mutex_enter(&sc->sc_lock); - sc->sc_open = 2; + sc->sc_open = UHID_OPEN; + cv_broadcast(&sc->sc_cv); mutex_exit(&sc->sc_lock); return 0; -fail2: __unused - mutex_enter(&sc->sc_lock); - KASSERT(sc->sc_open == 2); - sc->sc_open = 1; - mutex_exit(&sc->sc_lock); - uhidev_close(&sc->sc_hdev); fail1: selnotify(&sc->sc_rsel, POLLHUP, 0); mutex_enter(&proc_lock); atomic_store_relaxed(&sc->sc_async, NULL); @@ -387,27 +325,54 @@ fail1: selnotify(&sc->sc_rsel, POLLHUP, } clfree(&sc->sc_q); fail0: mutex_enter(&sc->sc_lock); - KASSERT(sc->sc_open == 1); - sc->sc_open = 0; - cv_broadcast(&sc->sc_detach_cv); + KASSERT(sc->sc_open == UHID_OPENING); + sc->sc_open = UHID_CLOSED; + cv_broadcast(&sc->sc_cv); atomic_store_relaxed(&sc->sc_state, 0); mutex_exit(&sc->sc_lock); return error; } static int -uhidclose(dev_t dev, int flag, int mode, struct lwp *l) +uhidcancel(dev_t dev, int flag, int mode, struct lwp *l) { - struct uhid_softc *sc; + struct uhid_softc *sc = device_lookup_private(&uhid_cd, UHIDUNIT(dev)); - sc = device_lookup_private(&uhid_cd, UHIDUNIT(dev)); + DPRINTF(("uhidcancel: sc=%p\n", sc)); + if (sc == NULL) + return 0; + + /* + * Mark it closing, wake any waiters, and suspend output. + * After this point, no new xfers can be submitted. + */ + mutex_enter(&sc->sc_lock); + sc->sc_closing = true; + cv_broadcast(&sc->sc_cv); + mutex_exit(&sc->sc_lock); + + uhidev_stop(&sc->sc_hdev); + + return 0; +} + +static int +uhidclose(dev_t dev, int flag, int mode, struct lwp *l) +{ + struct uhid_softc *sc = device_lookup_private(&uhid_cd, UHIDUNIT(dev)); DPRINTF(("uhidclose: sc=%p\n", sc)); + if (sc == NULL) + return 0; - /* We are closing up shop. Prevent new opens until we're done. */ mutex_enter(&sc->sc_lock); - KASSERT(sc->sc_open == 2); - sc->sc_open = 1; + KASSERT(sc->sc_closing); + KASSERTMSG(sc->sc_open == UHID_OPEN || sc->sc_open == UHID_CLOSED, + "sc_open=%d", sc->sc_open); + if (sc->sc_open == UHID_CLOSED) + goto out; + + /* Release the lock while we free things. */ mutex_exit(&sc->sc_lock); /* Prevent further interrupts. */ @@ -428,11 +393,13 @@ uhidclose(dev_t dev, int flag, int mode, } clfree(&sc->sc_q); - /* All set. We are now closed. */ mutex_enter(&sc->sc_lock); - KASSERT(sc->sc_open == 1); - sc->sc_open = 0; - cv_broadcast(&sc->sc_detach_cv); + + /* All set. We are now closed. */ + sc->sc_open = UHID_CLOSED; +out: KASSERT(sc->sc_open == UHID_CLOSED); + sc->sc_closing = false; + cv_broadcast(&sc->sc_cv); atomic_store_relaxed(&sc->sc_state, 0); mutex_exit(&sc->sc_lock); @@ -440,47 +407,9 @@ uhidclose(dev_t dev, int flag, int mode, } static int -uhid_enter(dev_t dev, struct uhid_softc **scp) -{ - struct uhid_softc *sc; - int error; - - /* XXX need to hold reference to device */ - sc = device_lookup_private(&uhid_cd, UHIDUNIT(dev)); - if (sc == NULL) - return ENXIO; - - mutex_enter(&sc->sc_lock); - KASSERT(sc->sc_open == 2); - if (sc->sc_dying) { - error = ENXIO; - } else if (sc->sc_refcnt == INT_MAX) { - error = EBUSY; - } else { - *scp = sc; - sc->sc_refcnt++; - error = 0; - } - mutex_exit(&sc->sc_lock); - - return error; -} - -static void -uhid_exit(struct uhid_softc *sc) -{ - - mutex_enter(&sc->sc_lock); - KASSERT(sc->sc_open == 2); - KASSERT(sc->sc_refcnt > 0); - if (--sc->sc_refcnt == 0) - cv_broadcast(&sc->sc_detach_cv); - mutex_exit(&sc->sc_lock); -} - -Static int -uhid_do_read(struct uhid_softc *sc, struct uio *uio, int flag) +uhidread(dev_t dev, struct uio *uio, int flag) { + struct uhid_softc *sc = device_lookup_private(&uhid_cd, UHIDUNIT(dev)); int error = 0; int extra; size_t length; @@ -506,7 +435,7 @@ uhid_do_read(struct uhid_softc *sc, stru mutex_exit(&sc->sc_lock); return EWOULDBLOCK; } - if (sc->sc_dying) { + if (sc->sc_closing) { mutex_exit(&sc->sc_lock); return EIO; } @@ -540,22 +469,9 @@ uhid_do_read(struct uhid_softc *sc, stru } static int -uhidread(dev_t dev, struct uio *uio, int flag) -{ - struct uhid_softc *sc; - int error; - - error = uhid_enter(dev, &sc); - if (error) - return error; - error = uhid_do_read(sc, uio, flag); - uhid_exit(sc); - return error; -} - -Static int -uhid_do_write(struct uhid_softc *sc, struct uio *uio, int flag) +uhidwrite(dev_t dev, struct uio *uio, int flag) { + struct uhid_softc *sc = device_lookup_private(&uhid_cd, UHIDUNIT(dev)); int error; int size; usbd_status err; @@ -594,24 +510,10 @@ uhid_do_write(struct uhid_softc *sc, str return error; } -int -uhidwrite(dev_t dev, struct uio *uio, int flag) -{ - struct uhid_softc *sc; - int error; - - error = uhid_enter(dev, &sc); - if (error) - return error; - error = uhid_do_write(sc, uio, flag); - uhid_exit(sc); - return error; -} - -int -uhid_do_ioctl(struct uhid_softc *sc, u_long cmd, void *addr, - int flag, struct lwp *l) +static int +uhidioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l) { + struct uhid_softc *sc = device_lookup_private(&uhid_cd, UHIDUNIT(dev)); struct usb_ctl_report_desc *rd; struct usb_ctl_report *re; u_char buffer[UHID_CHUNK]; @@ -788,28 +690,11 @@ uhid_do_ioctl(struct uhid_softc *sc, u_l } static int -uhidioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l) -{ - struct uhid_softc *sc; - int error; - - error = uhid_enter(dev, &sc); - if (error) - return error; - error = uhid_do_ioctl(sc, cmd, addr, flag, l); - uhid_exit(sc); - return error; -} - -static int uhidpoll(dev_t dev, int events, struct lwp *l) { - struct uhid_softc *sc; + struct uhid_softc *sc = device_lookup_private(&uhid_cd, UHIDUNIT(dev)); int revents = 0; - if (uhid_enter(dev, &sc) != 0) - return POLLHUP; - mutex_enter(&sc->sc_lock); if (events & (POLLOUT | POLLWRNORM)) revents |= events & (POLLOUT | POLLWRNORM); @@ -821,7 +706,6 @@ uhidpoll(dev_t dev, int events, struct l } mutex_exit(&sc->sc_lock); - uhid_exit(sc); return revents; } @@ -865,13 +749,9 @@ static const struct filterops uhidread_f static int uhidkqfilter(dev_t dev, struct knote *kn) { - struct uhid_softc *sc; + struct uhid_softc *sc = device_lookup_private(&uhid_cd, UHIDUNIT(dev)); int error; - error = uhid_enter(dev, &sc); - if (error) - return error; - switch (kn->kn_filter) { case EVFILT_READ: kn->kn_fop = &uhidread_filtops; @@ -887,9 +767,8 @@ uhidkqfilter(dev_t dev, struct knote *kn default: error = EINVAL; - goto out; + break; } -out: uhid_exit(sc); return error; }