Module Name:    src
Committed By:   martin
Date:           Thu Jan  7 00:15:20 UTC 2010

Modified Files:
        src/sys/dev/usb: files.usb u3g.c

Log Message:
Split the u3g driver into two parts: u3ginit attaches to those devices
that only come as a umass device in the default configuration and
forces them to reinitialize in 3D mode and detach.
The u3g part attaches to individual interfaces for the 3G functionality,
leaving the umass interface(s) for that driver.
With this change I can use the MMC card in my Huawey stick (as well as
the integrated windows driver CD, which of course is pretty useless) and
the 3G modem at the same time.
Fixes PR 42577.
Code contributed anonymously, minor tweaks (and all bugs) by me.


To generate a diff of this commit:
cvs rdiff -u -r1.93 -r1.94 src/sys/dev/usb/files.usb
cvs rdiff -u -r1.8 -r1.9 src/sys/dev/usb/u3g.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/files.usb
diff -u src/sys/dev/usb/files.usb:1.93 src/sys/dev/usb/files.usb:1.94
--- src/sys/dev/usb/files.usb:1.93	Wed Dec 23 01:04:45 2009
+++ src/sys/dev/usb/files.usb	Thu Jan  7 00:15:20 2010
@@ -1,4 +1,4 @@
-#	$NetBSD: files.usb,v 1.93 2009/12/23 01:04:45 pooka Exp $
+#	$NetBSD: files.usb,v 1.94 2010/01/07 00:15:20 martin Exp $
 #
 # Config file and device description for machine-independent USB code.
 # Included by ports that need it.  Ports that use it must provide
@@ -169,10 +169,12 @@
 attach	ugensa at usbdevif
 file	dev/usb/ugensa.c		ugensa
 
-# Generic Serial Adapter
+# 3G Modem
 device	u3g: ucombus
-attach	u3g at usbdevif
-file	dev/usb/u3g.c			u3g
+attach	u3g at usbifif
+device	u3ginit
+attach	u3ginit at usbdevif
+file	dev/usb/u3g.c			u3g | u3ginit
 
 # YAP phone firmware loader
 device	uyap: ezload

Index: src/sys/dev/usb/u3g.c
diff -u src/sys/dev/usb/u3g.c:1.8 src/sys/dev/usb/u3g.c:1.9
--- src/sys/dev/usb/u3g.c:1.8	Thu Nov 12 19:52:14 2009
+++ src/sys/dev/usb/u3g.c	Thu Jan  7 00:15:20 2010
@@ -1,4 +1,32 @@
-/*	$NetBSD: u3g.c,v 1.8 2009/11/12 19:52:14 dyoung Exp $	*/
+/*	$NetBSD: u3g.c,v 1.9 2010/01/07 00:15:20 martin Exp $	*/
+
+/*-
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
 
 /*
  * Copyright (c) 2008 AnyWi Technologies
@@ -22,20 +50,15 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: u3g.c,v 1.8 2009/11/12 19:52:14 dyoung Exp $");
+__KERNEL_RCSID(0, "$NetBSD: u3g.c,v 1.9 2010/01/07 00:15:20 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/kernel.h>
 #include <sys/malloc.h>
-#include <sys/module.h>
 #include <sys/bus.h>
-#include <sys/ioccom.h>
-#include <sys/fcntl.h>
 #include <sys/conf.h>
 #include <sys/tty.h>
-#include <sys/file.h>
-#include <sys/selinfo.h>
 
 #include <dev/usb/usb.h>
 #include <dev/usb/usbdi.h>
@@ -46,38 +69,116 @@
 
 #include "usbdevs.h"
 
-#define U3GBUFSZ        1024
-#define U3G_MAXPORTS           4
+/*
+ * We read/write data from/to the device in 4KB chunks to maximise
+ * performance.
+ */
+#define U3G_BUFF_SIZE	4096
+
+/*
+ * Some 3G devices (the Huawei E160/E220 springs to mind here) buffer up
+ * data internally even when the USB pipes are closed. So on first open,
+ * we can receive a large chunk of stale data.
+ *
+ * This causes a real problem because the default TTYDEF_LFLAG (applied
+ * on first open) has the ECHO flag set, resulting in all the stale data
+ * being echoed straight back to the device by the tty(4) layer. Some
+ * devices (again, the Huawei E160/E220 for example) react to this spew
+ * by going catatonic.
+ *
+ * All this happens before the application gets a chance to disable ECHO.
+ *
+ * We work around this by ignoring all data received from the device for
+ * a period of two seconds, or until the application starts sending data -
+ * whichever comes first.
+ */
+#define	U3G_PURGE_SECS	2
+
+/*
+ * Define bits for the virtual modem control pins.
+ * The input pin states are reported via the interrupt pipe on some devices.
+ */
+#define	U3G_OUTPIN_DTR	(1u << 0)
+#define	U3G_OUTPIN_RTS	(1u << 1)
+#define	U3G_INPIN_DCD	(1u << 0)
+#define	U3G_INPIN_DSR	(1u << 1)
+#define	U3G_INPIN_RI	(1u << 3)
+
+/*
+ * USB request to set the output pin status
+ */
+#define	U3G_SET_PIN	0x22
 
 struct u3g_softc {
-	device_t                    sc_ucom[U3G_MAXPORTS];
-	device_t                    sc_dev;
-	usbd_device_handle          sc_udev;
-	u_char                      sc_msr;
-	u_char                      sc_lsr;
-	u_char                      numports;
-
-	usbd_interface_handle       sc_intr_iface;   /* interrupt interface */
-#ifdef U3G_DEBUG
-	int                         sc_intr_number;  /* interrupt number */
-	usbd_pipe_handle            sc_intr_pipe;    /* interrupt pipe */
-	u_char                      *sc_intr_buf;    /* interrupt buffer */
-#endif
-	int                         sc_isize;
-	bool                        sc_pseudodev;
+	device_t		sc_dev;
+	usbd_device_handle	sc_udev;
+	bool			sc_dying;	/* We're going away */
+
+	device_t		sc_ucom;	/* Child ucom(4) handle */
+	int			sc_ifaceno;	/* Device interface number */
+
+	bool			sc_open;	/* Device is in use */
+	bool			sc_purging;	/* Purging stale data */
+	struct timeval		sc_purge_start;	/* Control duration of purge */
+
+	u_char			sc_msr;		/* Emulated 'msr' */
+	uint16_t		sc_outpins;	/* Output pin state */
+
+	usbd_pipe_handle	sc_intr_pipe;	/* Interrupt pipe */
+	u_char			*sc_intr_buff;	/* Interrupt buffer */
 };
 
+/*
+ * The device driver has two personalities. The first uses the 'usbdevif'
+ * interface attribute so that a match will claim the entire USB device
+ * for itself. This is used for when a device needs to be mode-switched
+ * and ensures any other interfaces present cannot be claimed by other
+ * drivers while the mode-switch is in progress.
+ *
+ * The second personality uses the 'usbifif' interface attribute so that
+ * it can claim the 3G modem interfaces for itself, leaving others (such
+ * as the mass storage interfaces on some devices) for other drivers.
+ */
+static int u3ginit_match(device_t, cfdata_t, void *);
+static void u3ginit_attach(device_t, device_t, void *);
+static int u3ginit_detach(device_t, int);
+
+CFATTACH_DECL2_NEW(u3ginit, 0, u3ginit_match,
+    u3ginit_attach, u3ginit_detach, NULL, NULL, NULL);
+
+
+static int u3g_match(device_t, cfdata_t, void *);
+static void u3g_attach(device_t, device_t, void *);
+static int u3g_detach(device_t, int);
+static int u3g_activate(device_t, enum devact);
+static void u3g_childdet(device_t, device_t);
+
+CFATTACH_DECL2_NEW(u3g, sizeof(struct u3g_softc), u3g_match,
+    u3g_attach, u3g_detach, u3g_activate, NULL, u3g_childdet);
+
+
+static void u3g_intr(usbd_xfer_handle, usbd_private_handle, usbd_status);
+static void u3g_get_status(void *, int, u_char *, u_char *);
+static void u3g_set(void *, int, int, int);
+static int  u3g_open(void *, int);
+static void u3g_close(void *, int);
+static void u3g_read(void *, int, u_char **, uint32_t *);
+static void u3g_write(void *, int, u_char *, u_char *, u_int32_t *);
+
 struct ucom_methods u3g_methods = {
+	u3g_get_status,
+	u3g_set,
 	NULL,
 	NULL,
-	NULL,
-	NULL,
-	NULL,
-	NULL,
-	NULL,
-	NULL,
+	u3g_open,
+	u3g_close,
+	u3g_read,
+	u3g_write,
 };
 
+/*
+ * Allegedly supported devices
+ */
 static const struct usb_devno u3g_devs[] = {
 	/* OEM: Option N.V. */
 	{ USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_QUADPLUSUMTS },
@@ -137,17 +238,8 @@
 	{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8781 },
 };
 
-#ifdef U3G_DEBUG
-static void
-u3g_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status)
-{
-	struct u3g_softc *sc = (struct u3g_softc *)priv;
-	aprint_normal_dev(sc->sc_dev, "INTERRUPT CALLBACK\n");
-}
-#endif
-
 static int
-u3g_novatel_reinit(struct usb_attach_arg *uaa)
+u3g_novatel_reinit(usbd_device_handle dev)
 {
 	unsigned char cmd[31];
 	usbd_interface_handle iface;
@@ -180,13 +272,13 @@
 
 
 	/* Move the device into the configured state. */
-	err = usbd_set_config_index(uaa->device, 0, 0);
+	err = usbd_set_config_index(dev, 0, 0);
 	if (err) {
 		aprint_error("u3g: failed to set configuration index\n");
 		return UMATCH_NONE;
 	}
 
-	err = usbd_device2interface_handle(uaa->device, 0, &iface);
+	err = usbd_device2interface_handle(dev, 0, &iface);
 	if (err != 0) {
 		aprint_error("u3g: failed to get interface\n");
 		return UMATCH_NONE;
@@ -215,7 +307,7 @@
 		return UMATCH_NONE;
 	}
 
-	xfer = usbd_alloc_xfer(uaa->device);
+	xfer = usbd_alloc_xfer(dev);
 	if (xfer != NULL) {
 		usbd_setup_xfer(xfer, pipe, NULL, cmd, sizeof(cmd),
 		    USBD_SYNCHRONOUS, USBD_DEFAULT_TIMEOUT, NULL);
@@ -238,7 +330,8 @@
 static int
 u3g_huawei_reinit(usbd_device_handle dev)
 {
-	/* The Huawei device presents itself as a umass device with Windows
+	/*
+	 * The Huawei device presents itself as a umass device with Windows
 	 * drivers on it. After installation of the driver, it reinits into a
 	 * 3G serial device.
 	 */
@@ -247,12 +340,33 @@
 
 	/* Get the config descriptor */
 	cdesc = usbd_get_config_descriptor(dev);
-	if (cdesc == NULL)
-		return (UMATCH_NONE);
+	if (cdesc == NULL) {
+		usb_device_descriptor_t dd;
+
+		if (usbd_get_device_desc(dev, &dd) != 0)
+			return (UMATCH_NONE);
+
+		if (dd.bNumConfigurations != 1)
+			return (UMATCH_NONE);
+
+		if (usbd_set_config_index(dev, 0, 1) != 0)
+			return (UMATCH_NONE);
+
+		cdesc = usbd_get_config_descriptor(dev);
 
-	/* One iface means umass mode, more than 1 (4 usually) means 3G mode */
+		if (cdesc == NULL)
+			return (UMATCH_NONE);
+	}
+
+	/*
+	 * One iface means umass mode, more than 1 (4 usually) means 3G mode.
+	 *
+	 * XXX: We should check the first interface's device class just to be
+	 * sure. If it's a mass storage device, then we can be fairly certain
+	 * it needs a mode-switch.
+	 */
 	if (cdesc->bNumInterface > 1)
-		return (UMATCH_VENDOR_PRODUCT);
+		return (UMATCH_NONE);
 
 	req.bmRequestType = UT_WRITE_DEVICE;
 	req.bRequest = UR_SET_FEATURE;
@@ -262,7 +376,6 @@
 
 	(void) usbd_do_request(dev, &req, 0);
 
-
 	return (UMATCH_HIGHEST); /* Match to prevent umass from attaching */
 }
 
@@ -274,12 +387,6 @@
 	 * reinits into a * 3G serial device.
 	 */
 	usb_device_request_t req;
-	usb_config_descriptor_t *cdesc;
-
-	/* Get the config descriptor */
-	cdesc = usbd_get_config_descriptor(dev);
-	if (cdesc == NULL)
-		return (UMATCH_NONE);
 
 	req.bmRequestType = UT_VENDOR;
 	req.bRequest = UR_SET_INTERFACE;
@@ -292,8 +399,15 @@
 	return (UMATCH_HIGHEST); /* Match to prevent umass from attaching */
 }
 
+
+/*
+ * First personality:
+ *
+ * Claim the entire device if a mode-switch is required.
+ */
+
 static int
-u3g_match(device_t parent, cfdata_t match, void *aux)
+u3ginit_match(device_t parent, cfdata_t match, void *aux)
 {
 	struct usb_attach_arg *uaa = aux;
 
@@ -302,145 +416,190 @@
 
 	if (uaa->vendor == USB_VENDOR_NOVATEL2 &&
 	    uaa->product == USB_PRODUCT_NOVATEL2_MC950D_DRIVER)
-		return u3g_novatel_reinit(uaa);
+		return u3g_novatel_reinit(uaa->device);
 
 	if (uaa->vendor == USB_VENDOR_SIERRA &&
 	    uaa->product == USB_PRODUCT_SIERRA_INSTALLER)
 		return u3g_sierra_reinit(uaa->device);
 
-	if (usb_lookup(u3g_devs, uaa->vendor, uaa->product))
-		return UMATCH_VENDOR_PRODUCT;
-
 	return UMATCH_NONE;
 }
 
 static void
-u3g_attach(device_t parent, device_t self, void *aux)
+u3ginit_attach(device_t parent, device_t self, void *aux)
 {
-	struct u3g_softc *sc = device_private(self);
 	struct usb_attach_arg *uaa = aux;
-	usbd_device_handle dev = uaa->device;
-	usbd_interface_handle iface;
-	usb_interface_descriptor_t *id;
-	usb_endpoint_descriptor_t *ed;
-	usbd_status error;
-	int i, n; 
-	usb_config_descriptor_t *cdesc;
 
 	aprint_naive("\n");
-	aprint_normal("\n");
+	aprint_normal(": Switching to 3G mode\n");
 
 	if (uaa->vendor == USB_VENDOR_NOVATEL2 &&
 	    uaa->product == USB_PRODUCT_NOVATEL2_MC950D_DRIVER) {
 		/* About to disappear... */
-		sc->sc_pseudodev = true;
 		return;
 	}
 
-	sc->sc_dev = self;
-#ifdef U3G_DEBUG
-	sc->sc_intr_number = -1;
-	sc->sc_intr_pipe = NULL;
-#endif
 	/* Move the device into the configured state. */
-	error = usbd_set_config_index(dev, 0, 1);
-	if (error) {
-		aprint_error_dev(self, "failed to set configuration: %s\n",
-			      usbd_errstr(error));
-		return;
-	}
+	(void) usbd_set_config_index(uaa->device, 0, 1);
+}
 
-	/* get the config descriptor */
-	cdesc = usbd_get_config_descriptor(dev);
+static int
+u3ginit_detach(device_t self, int flags)
+{
 
-	if (cdesc == NULL) {
-		aprint_error_dev(self, "failed to get configuration descriptor\n");
-		return;
+	return (0);
+}
+
+
+/*
+ * Second personality:
+ *
+ * Claim only those interfaces required for 3G modem operation.
+ */
+
+static int
+u3g_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct usbif_attach_arg *uaa = aux;
+	usbd_interface_handle iface;
+	usb_interface_descriptor_t *id;
+	usbd_status error;
+
+	if (!usb_lookup(u3g_devs, uaa->vendor, uaa->product))
+		return (UMATCH_NONE);
+
+	error = usbd_device2interface_handle(uaa->device, uaa->ifaceno, &iface);
+	if (error) {
+		printf("u3g_match: failed to get interface, err=%s\n",
+		    usbd_errstr(error));
+		return (UMATCH_NONE);
 	}
 
-	if (uaa->vendor == USB_VENDOR_HUAWEI && cdesc->bNumInterface > 1) {
-		/* About to disappear... */
-		sc->sc_pseudodev = true;
-		return;
+	id = usbd_get_interface_descriptor(iface);
+	if (id == NULL) {
+		printf("u3g_match: failed to get interface descriptor\n");
+		return (UMATCH_NONE);
 	}
 
-	if (uaa->vendor == USB_VENDOR_SIERRA &&
-	    uaa->product == USB_PRODUCT_SIERRA_INSTALLER) {
-		/* About to disappear... */
-		sc->sc_pseudodev = true;
+	/*
+	 * 3G modems generally report vendor-specific class
+	 *
+	 * XXX: this may be too generalised.
+	 */
+	return ((id->bInterfaceClass == UICLASS_VENDOR) ?
+	    UMATCH_VENDOR_PRODUCT : UMATCH_NONE);
+}
+
+static void
+u3g_attach(device_t parent, device_t self, void *aux)
+{
+	struct u3g_softc *sc = device_private(self);
+	struct usbif_attach_arg *uaa = aux;
+	usbd_device_handle dev = uaa->device;
+	usbd_interface_handle iface;
+	usb_interface_descriptor_t *id;
+	usb_endpoint_descriptor_t *ed;
+	struct ucom_attach_args uca;
+	usbd_status error;
+	int n, intr_address, intr_size; 
+
+	aprint_naive("\n");
+	aprint_normal("\n");
+
+	sc->sc_dev = self;
+	sc->sc_dying = false;
+	sc->sc_udev = dev;
+
+	error = usbd_device2interface_handle(dev, uaa->ifaceno, &iface);
+	if (error) {
+		aprint_error_dev(self, "failed to get interface, err=%s\n",
+		    usbd_errstr(error));
 		return;
 	}
 
-	sc->sc_udev = dev;
-	sc->numports = (cdesc->bNumInterface <= U3G_MAXPORTS)?cdesc->bNumInterface:U3G_MAXPORTS;
-	for ( i = 0; i < sc->numports; i++ ) {
-		struct ucom_attach_args uca;
+	id = usbd_get_interface_descriptor(iface);
 
-		error = usbd_device2interface_handle(dev, i, &iface);
-		if (error) {
-			aprint_error_dev(self,
-			    "failed to get interface, err=%s\n",
-			    usbd_errstr(error));
+	uca.info = "3G Modem";
+	uca.ibufsize = U3G_BUFF_SIZE;
+	uca.obufsize = U3G_BUFF_SIZE;
+	uca.ibufsizepad = U3G_BUFF_SIZE;
+	uca.portno = uaa->ifaceno;
+	uca.opkthdrlen = 0;
+	uca.device = dev;
+	uca.iface = iface;
+	uca.methods = &u3g_methods;
+	uca.arg = sc;
+	uca.bulkin = uca.bulkout = -1;
+
+	sc->sc_outpins = 0;
+	sc->sc_msr = UMSR_DSR | UMSR_CTS | UMSR_DCD;
+	sc->sc_ifaceno = uaa->ifaceno;
+	sc->sc_open = false;
+	sc->sc_purging = false;
+
+	intr_address = -1;
+	intr_size = 0;
+
+	for (n = 0; n < id->bNumEndpoints; n++) {
+		ed = usbd_interface2endpoint_descriptor(iface, n);
+		if (ed == NULL) {
+			aprint_error_dev(self, "no endpoint descriptor "
+			    "for %d (interface: %d)\n", n, sc->sc_ifaceno);
+			sc->sc_dying = true;
 			return;
 		}
-		id = usbd_get_interface_descriptor(iface);
 
-		uca.info = "Generic 3G Serial Device";
-		uca.ibufsize = U3GBUFSZ;
-		uca.obufsize = U3GBUFSZ;
-		uca.ibufsizepad = U3GBUFSZ;
-		uca.portno = i;
-		uca.opkthdrlen = 0;
-		uca.device = dev;
-		uca.iface = iface;
-		uca.methods = &u3g_methods;
-		uca.arg = sc;
-
-		uca.bulkin = uca.bulkout = -1;
-		for (n = 0; n < id->bNumEndpoints; n++) {
-			ed = usbd_interface2endpoint_descriptor(iface, n);
-			if (ed == NULL) {
-				aprint_error_dev(self,
-					"could not read endpoint descriptor\n");
-				return;
-			}
-			if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
-			    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
-				uca.bulkin = ed->bEndpointAddress;
-			else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
-			    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
-				uca.bulkout = ed->bEndpointAddress;
-		}
-		if (uca.bulkin == -1 || uca.bulkout == -1) {
-			aprint_error_dev(self, "missing endpoint\n");
-			return;
+		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
+		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) {
+			intr_address = ed->bEndpointAddress;
+			intr_size = UGETW(ed->wMaxPacketSize);
+		} else
+		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
+		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
+			uca.bulkin = ed->bEndpointAddress;
+		} else
+		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
+		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
+			uca.bulkout = ed->bEndpointAddress;
 		}
+	}
 
-		sc->sc_ucom[i] = config_found_sm_loc(self, "ucombus", NULL, &uca,
-						    ucomprint, ucomsubmatch);
+	if (uca.bulkin == -1) {
+		aprint_error_dev(self, "Missing bulk in for interface %d\n",
+		    sc->sc_ifaceno);
+		sc->sc_dying = true;
+		return;
 	}
 
-#ifdef U3G_DEBUG
-	if (sc->sc_intr_number != -1 && sc->sc_intr_pipe == NULL) {
-		sc->sc_intr_buf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK);
-		error = usbd_open_pipe_intr(sc->sc_intr_iface,
-					    sc->sc_intr_number,
-					    USBD_SHORT_XFER_OK,
-					    &sc->sc_intr_pipe,
-					    sc,
-					    sc->sc_intr_buf,
-					    sc->sc_isize,
-					    u3g_intr,
-					    100);
+	if (uca.bulkout == -1) {
+		aprint_error_dev(self, "Missing bulk out for interface %d\n",
+		    sc->sc_ifaceno);
+		sc->sc_dying = true;
+		return;
+	}
+
+	sc->sc_ucom = config_found_sm_loc(self, "ucombus",
+	    NULL, &uca, ucomprint, ucomsubmatch);
+
+	/*
+	 * If the interface has an interrupt pipe, open it immediately so
+	 * that we can track input pin state changes regardless of whether
+	 * the tty(4) device is open or not.
+	 */
+	if (intr_address != -1) {
+		sc->sc_intr_buff = malloc(intr_size, M_USBDEV, M_WAITOK);
+		error = usbd_open_pipe_intr(iface, intr_address,
+		    USBD_SHORT_XFER_OK, &sc->sc_intr_pipe, sc, sc->sc_intr_buff,
+		    intr_size, u3g_intr, 100);
 		if (error) {
-			aprint_error_dev(self,
-			    "cannot open interrupt pipe (addr %d)\n",
-			    sc->sc_intr_number);
+			aprint_error_dev(self, "cannot open interrupt pipe "
+			    "(addr %d)\n", intr_address);
 			return;
 		}
+	} else {
+		sc->sc_intr_pipe = NULL;
+		sc->sc_intr_buff = NULL;
 	}
-#endif
-
 
 	if (!pmf_device_register(self, NULL, NULL))
 		aprint_error_dev(self, "couldn't establish power handler\n");
@@ -450,55 +609,251 @@
 u3g_detach(device_t self, int flags)
 {
 	struct u3g_softc *sc = device_private(self);
-	int rv = 0;
-	int i;
+	int rv;
 
-	if (sc->sc_pseudodev)
+	if (sc->sc_dying)
 		return 0;
 
 	pmf_device_deregister(self);
 
-	for (i = 0; i < sc->numports; i++) {
-		if(sc->sc_ucom[i]) {
-			rv = config_detach(sc->sc_ucom[i], flags);
-			if(rv != 0) {
-				aprint_verbose_dev(self, "Can't deallocat port %d", i);
-				return rv;
-			}
+	if (sc->sc_ucom != NULL) {
+		rv = config_detach(sc->sc_ucom, flags);
+		if (rv != 0) {
+			aprint_verbose_dev(self, "Can't deallocate "
+			    "port (%d)", rv);
 		}
 	}
 
-#ifdef U3G_DEBUG
 	if (sc->sc_intr_pipe != NULL) {
-		int err = usbd_abort_pipe(sc->sc_intr_pipe);
-		if (err)
-			aprint_error_dev(self,
-				"abort interrupt pipe failed: %s\n",
-				usbd_errstr(err));
-		err = usbd_close_pipe(sc->sc_intr_pipe);
-		if (err)
-			aprint_error_dev(self,
-			    "close interrupt pipe failed: %s\n",
-			    usbd_errstr(err));
-		free(sc->sc_intr_buf, M_USBDEV);
+		(void) usbd_abort_pipe(sc->sc_intr_pipe);
+		(void) usbd_close_pipe(sc->sc_intr_pipe);
 		sc->sc_intr_pipe = NULL;
 	}
-#endif
+	if (sc->sc_intr_buff != NULL) {
+		free(sc->sc_intr_buff, M_USBDEV);
+		sc->sc_intr_buff = NULL;
+	}
 
-	return 0;
+	return (0);
 }
 
 static void
 u3g_childdet(device_t self, device_t child)
 {
 	struct u3g_softc *sc = device_private(self);
+
+	if (sc->sc_ucom == child)
+		sc->sc_ucom = NULL;
+}
+
+static int
+u3g_activate(device_t self, enum devact act)
+{
+	struct u3g_softc *sc = device_private(self);
+	int rv;
+
+	switch (act) {
+	case DVACT_DEACTIVATE:
+		if (sc->sc_ucom != NULL && config_deactivate(sc->sc_ucom))
+			rv = -1;
+		else
+			rv = 0;
+		break;
+
+	default:
+		rv = 0;
+		break;
+	}
+
+	return (rv);
+}
+
+static void
+u3g_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status)
+{
+	struct u3g_softc *sc = (struct u3g_softc *)priv;
+	u_char *buf;
+
+	if (sc->sc_dying)
+		return;
+
+	if (status != USBD_NORMAL_COMPLETION) {
+		if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
+			return;
+		usbd_clear_endpoint_stall_async(sc->sc_intr_pipe);
+		return;
+	}
+
+	buf = sc->sc_intr_buff;
+	if (buf[0] == 0xa1 && buf[1] == 0x20) {
+		u_char msr;
+
+		msr = sc->sc_msr & ~(UMSR_DCD | UMSR_DSR | UMSR_RI);
+
+		if (buf[8] & U3G_INPIN_DCD)
+			msr |= UMSR_DCD;
+
+		if (buf[8] & U3G_INPIN_DSR)
+			msr |= UMSR_DSR;
+
+		if (buf[8] & U3G_INPIN_RI)
+			msr |= UMSR_RI;
+
+		if (msr != sc->sc_msr) {
+			sc->sc_msr = msr;
+			if (sc->sc_open)
+				ucom_status_change(device_private(sc->sc_ucom));
+		}
+	}
+}
+
+/*ARGSUSED*/
+static void
+u3g_get_status(void *arg, int portno, u_char *lsr, u_char *msr)
+{
+	struct u3g_softc *sc = arg;
+
+	if (lsr != NULL)
+		*lsr = 0;	/* LSR isn't supported */
+	if (msr != NULL)
+		*msr = sc->sc_msr;
+}
+
+/*ARGSUSED*/
+static void
+u3g_set(void *arg, int portno, int reg, int onoff)
+{
+	struct u3g_softc *sc = arg;
+	usb_device_request_t req;
+	uint16_t mask, new_state;
+	usbd_status err;
+
+	if (sc->sc_dying)
+		return;
+
+	switch (reg) {
+	case UCOM_SET_DTR:
+		mask = U3G_OUTPIN_DTR;
+		break;
+	case UCOM_SET_RTS:
+		mask = U3G_OUTPIN_RTS;
+		break;
+	default:
+		return;
+	}
+
+	new_state = sc->sc_outpins & ~mask;
+	if (onoff)
+		new_state |= mask;
+
+	if (new_state == sc->sc_outpins)
+		return;
+
+	sc->sc_outpins = new_state;
+
+	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+	req.bRequest = U3G_SET_PIN;
+	USETW(req.wValue, new_state);
+	USETW(req.wIndex, sc->sc_ifaceno);
+	USETW(req.wLength, 0);
+
+	err = usbd_do_request(sc->sc_udev, &req, 0);
+	if (err == USBD_STALLED)
+		usbd_clear_endpoint_stall(sc->sc_udev->default_pipe);
+}
+
+/*ARGSUSED*/
+static int 
+u3g_open(void *arg, int portno)
+{
+	struct u3g_softc *sc = arg;
+	usb_device_request_t req;
+	usb_endpoint_descriptor_t *ed;
+	usb_interface_descriptor_t *id;
+	usbd_interface_handle ih;
+	usbd_status err;
 	int i;
 
-	for (i = 0; i < sc->numports; i++) {
-		if (sc->sc_ucom[i] == child)
-			sc->sc_ucom[i] = NULL;
+	if (sc->sc_dying)
+		return (0);
+
+	err = usbd_device2interface_handle(sc->sc_udev, portno, &ih);
+	if (err)
+		return (EIO);
+
+	id = usbd_get_interface_descriptor(ih);
+
+	for (i = 0; i < id->bNumEndpoints; i++) {
+		ed = usbd_interface2endpoint_descriptor(ih, i);
+		if (ed == NULL)	
+			return (EIO);
+
+		if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
+			/* Issue ENDPOINT_HALT request */
+			req.bmRequestType = UT_WRITE_ENDPOINT;
+			req.bRequest = UR_CLEAR_FEATURE;
+			USETW(req.wValue, UF_ENDPOINT_HALT);
+			USETW(req.wIndex, ed->bEndpointAddress);
+			USETW(req.wLength, 0);
+			err = usbd_do_request(sc->sc_udev, &req, 0);
+			if (err)
+				return (EIO);
+		}
 	}
+
+	sc->sc_open = true;
+	sc->sc_purging = true;
+	getmicrotime(&sc->sc_purge_start);
+
+	return (0);
 }
 
-CFATTACH_DECL2_NEW(u3g, sizeof(struct u3g_softc), u3g_match,
-    u3g_attach, u3g_detach, NULL, NULL, u3g_childdet);
+/*ARGSUSED*/
+static void 
+u3g_close(void *arg, int portno)
+{
+	struct u3g_softc *sc = arg;
+
+	sc->sc_open = false;
+}
+
+/*ARGSUSED*/
+static void
+u3g_read(void *arg, int portno, u_char **cpp, uint32_t *ccp)
+{
+	struct u3g_softc *sc = arg;
+	struct timeval curr_tv, diff_tv;
+
+	/*
+	 * If we're not purging input data following first open, do nothing.
+	 */
+	if (sc->sc_purging == false)
+		return;
+
+	/*
+	 * Otherwise check if the purge timeout has expired
+	 */
+	getmicrotime(&curr_tv);
+	timersub(&curr_tv, &sc->sc_purge_start, &diff_tv);
+
+	if (diff_tv.tv_sec >= U3G_PURGE_SECS) {
+		/* Timeout expired. */
+		sc->sc_purging = false;
+	} else {
+		/* Still purging. Adjust the caller's byte count. */
+		*ccp = 0;
+	}
+}
+
+/*ARGSUSED*/
+static void
+u3g_write(void *arg, int portno, u_char *to, u_char *from, u_int32_t *count)
+{
+	struct u3g_softc *sc = arg;
+
+	/*
+	 * Stop purging as soon as the first data is written to the device.
+	 */
+	sc->sc_purging = false;
+	memcpy(to, from, *count);
+}

Reply via email to