Module Name:    src
Committed By:   plunky
Date:           Wed Jan 11 17:27:45 UTC 2012

Modified Files:
        src/sys/dev/bluetooth: bthidev.c btkbd.c

Log Message:
offset processing of input reports to a kernel thread, to avoid
locking issues when a child device needs to call back into the
Bluetooth stack (eg when caps-lock is pressed, and wskbd wants
to change a LED)

(as discussed with Radoslaw Kujawa)


To generate a diff of this commit:
cvs rdiff -u -r1.20 -r1.21 src/sys/dev/bluetooth/bthidev.c
cvs rdiff -u -r1.12 -r1.13 src/sys/dev/bluetooth/btkbd.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/bluetooth/bthidev.c
diff -u src/sys/dev/bluetooth/bthidev.c:1.20 src/sys/dev/bluetooth/bthidev.c:1.21
--- src/sys/dev/bluetooth/bthidev.c:1.20	Sat Dec 31 01:16:09 2011
+++ src/sys/dev/bluetooth/bthidev.c	Wed Jan 11 17:27:45 2012
@@ -1,4 +1,4 @@
-/*	$NetBSD: bthidev.c,v 1.20 2011/12/31 01:16:09 rkujawa Exp $	*/
+/*	$NetBSD: bthidev.c,v 1.21 2012/01/11 17:27:45 plunky Exp $	*/
 
 /*-
  * Copyright (c) 2006 Itronix Inc.
@@ -32,16 +32,19 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: bthidev.c,v 1.20 2011/12/31 01:16:09 rkujawa Exp $");
+__KERNEL_RCSID(0, "$NetBSD: bthidev.c,v 1.21 2012/01/11 17:27:45 plunky Exp $");
 
 #include <sys/param.h>
+#include <sys/condvar.h>
 #include <sys/conf.h>
 #include <sys/device.h>
 #include <sys/fcntl.h>
 #include <sys/kernel.h>
+#include <sys/kthread.h>
 #include <sys/queue.h>
 #include <sys/malloc.h>
 #include <sys/mbuf.h>
+#include <sys/mutex.h>
 #include <sys/proc.h>
 #include <sys/socketvar.h>
 #include <sys/systm.h>
@@ -83,6 +86,12 @@ struct bthidev_softc {
 	struct l2cap_channel	*sc_int;	/* interrupt channel */
 	struct l2cap_channel	*sc_int_l;	/* interrupt listen */
 
+	MBUFQ_HEAD()		sc_inq;		/* input queue */
+	kmutex_t		sc_lock;	/* input queue lock */
+	kcondvar_t		sc_cv;		/* input queue trigger */
+	lwp_t			*sc_lwp;	/* input queue processor */
+	int			sc_detach;
+
 	LIST_HEAD(,bthidev)	sc_list;	/* child list */
 
 	callout_t		sc_reconnect;
@@ -107,6 +116,8 @@ static int  bthidev_listen(struct bthide
 static int  bthidev_connect(struct bthidev_softc *);
 static int  bthidev_output(struct bthidev *, uint8_t *, int);
 static void bthidev_null(struct bthidev *, uint8_t *, int);
+static void bthidev_process(void *);
+static void bthidev_process_one(struct bthidev_softc *, struct mbuf *);
 
 /* autoconf(9) glue */
 static int  bthidev_match(device_t, cfdata_t, void *);
@@ -188,6 +199,7 @@ bthidev_attach(device_t parent, device_t
 	 */
 	sc->sc_dev = self;
 	LIST_INIT(&sc->sc_list);
+	MBUFQ_INIT(&sc->sc_inq);
 	callout_init(&sc->sc_reconnect, 0);
 	callout_setfunc(&sc->sc_reconnect, bthidev_timeout, sc);
 	sc->sc_state = BTHID_CLOSED;
@@ -196,6 +208,8 @@ bthidev_attach(device_t parent, device_t
 	sc->sc_intpsm = L2CAP_PSM_HID_INTR;
 
 	sockopt_init(&sc->sc_mode, BTPROTO_L2CAP, SO_L2CAP_LM, 0);
+	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
+	cv_init(&sc->sc_cv, device_xname(self));
 
 	/*
 	 * extract config from proplist
@@ -280,6 +294,12 @@ bthidev_attach(device_t parent, device_t
 
 	aprint_normal("\n");
 
+	if (kthread_create(PRI_NONE, KTHREAD_MUSTJOIN, NULL, bthidev_process,
+	    sc, &sc->sc_lwp, "%s", device_xname(self)) != 0) {
+		aprint_error_dev(self, "failed to create input thread\n");
+		return;
+	}
+
 	for (rep = 0 ; rep <= maxid ; rep++) {
 		if (hid_report_size(desc, dlen, hid_feature, rep) == 0
 		    && hid_report_size(desc, dlen, hid_input, rep) == 0
@@ -362,12 +382,25 @@ bthidev_detach(device_t self, int flags)
 
 	mutex_exit(bt_lock);
 
+	/* kill off the input processor */
+	if (sc->sc_lwp != NULL) {
+		mutex_enter(&sc->sc_lock);
+		sc->sc_detach = 1;
+		cv_signal(&sc->sc_cv);
+		mutex_exit(&sc->sc_lock);
+		kthread_join(sc->sc_lwp);
+		sc->sc_lwp = NULL;
+	}
+
 	/* detach children */
 	while ((hidev = LIST_FIRST(&sc->sc_list)) != NULL) {
 		LIST_REMOVE(hidev, sc_next);
 		config_detach(hidev->sc_dev, flags);
 	}
 
+	MBUFQ_DRAIN(&sc->sc_inq);
+	cv_destroy(&sc->sc_cv);
+	mutex_destroy(&sc->sc_lock);
 	sockopt_destroy(&sc->sc_mode);
 
 	return 0;
@@ -545,6 +578,123 @@ bthidev_connect(struct bthidev_softc *sc
 	return 0;
 }
 
+/*
+ * The LWP which processes input reports, forwarding to child devices.
+ * We are always either processing input reports, holding the lock, or
+ * waiting for a signal on condvar.
+ */
+static void
+bthidev_process(void *arg)
+{
+	struct bthidev_softc *sc = arg;
+	struct mbuf *m;
+
+	mutex_enter(&sc->sc_lock);
+	while (sc->sc_detach == 0) {
+		MBUFQ_DEQUEUE(&sc->sc_inq, m);
+		if (m == NULL) {
+			cv_wait(&sc->sc_cv, &sc->sc_lock);
+			continue;
+		}
+
+		mutex_exit(&sc->sc_lock);
+		bthidev_process_one(sc, m);
+		m_freem(m);
+		mutex_enter(&sc->sc_lock);
+	}
+	mutex_exit(&sc->sc_lock);
+	kthread_exit(0);
+}
+
+static void
+bthidev_process_one(struct bthidev_softc *sc, struct mbuf *m)
+{
+	struct bthidev *hidev;
+	uint8_t *data;
+	int len;
+
+	if (sc->sc_state != BTHID_OPEN)
+		return;
+
+	if (m->m_pkthdr.len > m->m_len)
+		aprint_error_dev(sc->sc_dev, "truncating HID report\n");
+
+	len = m->m_len;
+	data = mtod(m, uint8_t *);
+
+	switch (BTHID_TYPE(data[0])) {
+	case BTHID_DATA:
+		/*
+		 * data[0] == type / parameter
+		 * data[1] == id
+		 * data[2..len] == report
+		 */
+		if (len < 3)
+			break;
+
+		LIST_FOREACH(hidev, &sc->sc_list, sc_next)
+			if (data[1] == hidev->sc_id)
+				break;
+
+		if (hidev == NULL) {
+			aprint_error_dev(sc->sc_dev,
+			    "report id %d, len = %d ignored\n", data[1], len - 2);
+
+			break;
+		}
+
+		switch (BTHID_DATA_PARAM(data[0])) {
+		case BTHID_DATA_INPUT:
+			(*hidev->sc_input)(hidev, data + 2, len - 2);
+			break;
+
+		case BTHID_DATA_FEATURE:
+			(*hidev->sc_feature)(hidev, data + 2, len - 2);
+			break;
+
+		default:
+			break;
+		}
+
+		break;
+
+	case BTHID_CONTROL:
+		if (len < 1)
+			break;
+
+		switch (BTHID_DATA_PARAM(data[0])) {
+		case BTHID_CONTROL_UNPLUG:
+			aprint_normal_dev(sc->sc_dev, "unplugged\n");
+
+			mutex_enter(bt_lock);
+			/* close interrupt channel */
+			if (sc->sc_int != NULL) {
+				l2cap_disconnect(sc->sc_int, 0);
+				l2cap_detach(&sc->sc_int);
+				sc->sc_int = NULL;
+			}
+
+			/* close control channel */
+			if (sc->sc_ctl != NULL) {
+				l2cap_disconnect(sc->sc_ctl, 0);
+				l2cap_detach(&sc->sc_ctl);
+				sc->sc_ctl = NULL;
+			}
+			mutex_exit(bt_lock);
+
+			break;
+
+		default:
+			break;
+		}
+
+		break;
+
+	default:
+		break;
+	}
+}
+
 /*****************************************************************************
  *
  *	bluetooth(9) callback methods for L2CAP
@@ -787,85 +937,23 @@ bthidev_linkmode(void *arg, int new)
 }
 
 /*
- * Receive reports from the protocol stack.
+ * Receive reports from the protocol stack. Because this will be called
+ * with bt_lock held, we queue the mbuf and process it with a kernel thread
  */
 static void
 bthidev_input(void *arg, struct mbuf *m)
 {
 	struct bthidev_softc *sc = arg;
-	struct bthidev *hidev;
-	uint8_t *data;
-	int len;
-
-	if (sc->sc_state != BTHID_OPEN)
-		goto release;
-
-	if (m->m_pkthdr.len > m->m_len)
-		aprint_error_dev(sc->sc_dev, "truncating HID report\n");
-
-	len = m->m_len;
-	data = mtod(m, uint8_t *);
-
-	if (BTHID_TYPE(data[0]) == BTHID_DATA) {
-		/*
-		 * data[0] == type / parameter
-		 * data[1] == id
-		 * data[2..len] == report
-		 */
-		if (len < 3)
-			goto release;
-
-		LIST_FOREACH(hidev, &sc->sc_list, sc_next) {
-			if (data[1] == hidev->sc_id) {
-				switch (BTHID_DATA_PARAM(data[0])) {
-				case BTHID_DATA_INPUT:
-					(*hidev->sc_input)(hidev, data + 2, len - 2);
-					break;
-
-				case BTHID_DATA_FEATURE:
-					(*hidev->sc_feature)(hidev, data + 2, len - 2);
-					break;
-
-				default:
-					break;
-				}
-
-				goto release;
-			}
-		}
-		aprint_error_dev(sc->sc_dev, "report id %d, len = %d ignored\n",
-		    data[1], len - 2);
 
-		goto release;
-	}
-
-	if (BTHID_TYPE(data[0]) == BTHID_CONTROL) {
-		if (len < 1)
-			goto release;
-
-		if (BTHID_DATA_PARAM(data[0]) == BTHID_CONTROL_UNPLUG) {
-			aprint_normal_dev(sc->sc_dev, "unplugged\n");
-
-			/* close interrupt channel */
-			if (sc->sc_int != NULL) {
-				l2cap_disconnect(sc->sc_int, 0);
-				l2cap_detach(&sc->sc_int);
-				sc->sc_int = NULL;
-			}
-
-			/* close control channel */
-			if (sc->sc_ctl != NULL) {
-				l2cap_disconnect(sc->sc_ctl, 0);
-				l2cap_detach(&sc->sc_ctl);
-				sc->sc_ctl = NULL;
-			}
-		}
-
-		goto release;
+	if (sc->sc_state != BTHID_OPEN) {
+		m_freem(m);
+		return;
 	}
 
-release:
-	m_freem(m);
+	mutex_enter(&sc->sc_lock);
+	MBUFQ_ENQUEUE(&sc->sc_inq, m);
+	cv_signal(&sc->sc_cv);
+	mutex_exit(&sc->sc_lock);
 }
 
 /*****************************************************************************
@@ -919,8 +1007,9 @@ bthidev_output(struct bthidev *hidev, ui
 	memcpy(mtod(m, uint8_t *) + 2, report, rlen);
 	m->m_pkthdr.len = m->m_len = rlen + 2;
 
-	KASSERT(mutex_owned(bt_lock));
+	mutex_enter(bt_lock);
 	err = l2cap_send(sc->sc_int, m);
+	mutex_exit(bt_lock);
 
 	return err;
 }

Index: src/sys/dev/bluetooth/btkbd.c
diff -u src/sys/dev/bluetooth/btkbd.c:1.12 src/sys/dev/bluetooth/btkbd.c:1.13
--- src/sys/dev/bluetooth/btkbd.c:1.12	Sat Dec 31 01:16:09 2011
+++ src/sys/dev/bluetooth/btkbd.c	Wed Jan 11 17:27:45 2012
@@ -1,4 +1,4 @@
-/*	$NetBSD: btkbd.c,v 1.12 2011/12/31 01:16:09 rkujawa Exp $	*/
+/*	$NetBSD: btkbd.c,v 1.13 2012/01/11 17:27:45 plunky Exp $	*/
 
 /*
  * Copyright (c) 1998 The NetBSD Foundation, Inc.
@@ -66,7 +66,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: btkbd.c,v 1.12 2011/12/31 01:16:09 rkujawa Exp $");
+__KERNEL_RCSID(0, "$NetBSD: btkbd.c,v 1.13 2012/01/11 17:27:45 plunky Exp $");
 
 #include <sys/param.h>
 #include <sys/callout.h>
@@ -378,9 +378,7 @@ btkbd_ioctl(void *self, unsigned long cm
 		break;
 
 	case WSKBDIO_SETLEDS:
-		mutex_enter(bt_lock);
 		btkbd_set_leds(sc, *(int *)data);
-		mutex_exit(bt_lock);
 		break;
 
 	case WSKBDIO_GETLEDS:

Reply via email to