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;
}