Module Name:    src
Committed By:   martin
Date:           Wed Jan  6 20:37:56 UTC 2010

Modified Files:
        src/sys/dev/usb: ucom.c

Log Message:
Optimize for higher speeds, e.g. when used as part of a 3G modem.
Contributed anonymously.


To generate a diff of this commit:
cvs rdiff -u -r1.81 -r1.82 src/sys/dev/usb/ucom.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/ucom.c
diff -u src/sys/dev/usb/ucom.c:1.81 src/sys/dev/usb/ucom.c:1.82
--- src/sys/dev/usb/ucom.c:1.81	Sun Dec  6 21:40:31 2009
+++ src/sys/dev/usb/ucom.c	Wed Jan  6 20:37:56 2010
@@ -1,4 +1,4 @@
-/*	$NetBSD: ucom.c,v 1.81 2009/12/06 21:40:31 dyoung Exp $	*/
+/*	$NetBSD: ucom.c,v 1.82 2010/01/06 20:37:56 martin Exp $	*/
 
 /*
  * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ucom.c,v 1.81 2009/12/06 21:40:31 dyoung Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ucom.c,v 1.82 2010/01/06 20:37:56 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -48,6 +48,7 @@
 #include <sys/vnode.h>
 #include <sys/device.h>
 #include <sys/poll.h>
+#include <sys/queue.h>
 #include <sys/kauth.h>
 #if defined(__NetBSD__)
 #include "rnd.h"
@@ -87,6 +88,22 @@
 #define	UCOMDIALOUT(x)		(minor(x) & UCOMDIALOUT_MASK)
 #define	UCOMCALLUNIT(x)		(minor(x) & UCOMCALLUNIT_MASK)
 
+/*
+ * XXX: We can submit multiple input/output buffers to the usb stack
+ * to improve throughput, but the usb stack is too lame to deal with this
+ * in a number of places.
+ */
+#define	UCOM_IN_BUFFS	1
+#define	UCOM_OUT_BUFFS	1
+
+struct ucom_buffer {
+	SIMPLEQ_ENTRY(ucom_buffer) ub_link;
+	usbd_xfer_handle ub_xfer;
+	u_char *ub_data;
+	u_int ub_len;
+	u_int ub_index;
+};
+
 struct ucom_softc {
 	USBBASEDEVICE		sc_dev;		/* base device */
 
@@ -96,18 +113,21 @@
 
 	int			sc_bulkin_no;	/* bulk in endpoint address */
 	usbd_pipe_handle	sc_bulkin_pipe;	/* bulk in pipe */
-	usbd_xfer_handle	sc_ixfer;	/* read request */
-	u_char			*sc_ibuf;	/* read buffer */
 	u_int			sc_ibufsize;	/* read buffer size */
 	u_int			sc_ibufsizepad;	/* read buffer size padded */
+	struct ucom_buffer	sc_ibuff[UCOM_IN_BUFFS];
+	SIMPLEQ_HEAD(, ucom_buffer) sc_ibuff_empty;
+	SIMPLEQ_HEAD(, ucom_buffer) sc_ibuff_full;
 
 	int			sc_bulkout_no;	/* bulk out endpoint address */
 	usbd_pipe_handle	sc_bulkout_pipe;/* bulk out pipe */
-	usbd_xfer_handle	sc_oxfer;	/* write request */
-	u_char			*sc_obuf;	/* write buffer */
 	u_int			sc_obufsize;	/* write buffer size */
-	u_int			sc_opkthdrlen;	/* header length of
-						 * output packet */
+	u_int			sc_opkthdrlen;	/* header length of */
+	struct ucom_buffer	sc_obuff[UCOM_OUT_BUFFS];
+	SIMPLEQ_HEAD(, ucom_buffer) sc_obuff_free;
+	SIMPLEQ_HEAD(, ucom_buffer) sc_obuff_full;
+
+	void			*sc_si;
 
 	struct ucom_methods     *sc_methods;
 	void                    *sc_parent;
@@ -117,6 +137,8 @@
 	u_char			sc_lsr;
 	u_char			sc_msr;
 	u_char			sc_mcr;
+	volatile u_char		sc_rx_stopped;
+	u_char			sc_rx_unblock;
 	u_char			sc_tx_stopped;
 	int			sc_swflags;
 
@@ -143,21 +165,28 @@
 	ucomstop, ucomtty, ucompoll, nommap, ttykqfilter, D_TTY
 };
 
-Static void	ucom_cleanup(struct ucom_softc *);
-Static void	ucom_hwiflow(struct ucom_softc *);
-Static int	ucomparam(struct tty *, struct termios *);
-Static void	ucomstart(struct tty *);
-Static void	ucom_shutdown(struct ucom_softc *);
-Static int	ucom_do_ioctl(struct ucom_softc *, u_long, void *,
+static void	ucom_cleanup(struct ucom_softc *);
+static int	ucomparam(struct tty *, struct termios *);
+static int	ucomhwiflow(struct tty *, int);
+static void	ucomstart(struct tty *);
+static void	ucom_shutdown(struct ucom_softc *);
+static int	ucom_do_ioctl(struct ucom_softc *, u_long, void *,
 			      int, struct lwp *);
-Static void	ucom_dtr(struct ucom_softc *, int);
-Static void	ucom_rts(struct ucom_softc *, int);
-Static void	ucom_break(struct ucom_softc *, int);
-Static usbd_status ucomstartread(struct ucom_softc *);
-Static void	ucomreadcb(usbd_xfer_handle, usbd_private_handle, usbd_status);
-Static void	ucomwritecb(usbd_xfer_handle, usbd_private_handle, usbd_status);
-Static void	tiocm_to_ucom(struct ucom_softc *, u_long, int);
-Static int	ucom_to_tiocm(struct ucom_softc *);
+static void	ucom_dtr(struct ucom_softc *, int);
+static void	ucom_rts(struct ucom_softc *, int);
+static void	ucom_break(struct ucom_softc *, int);
+static void	tiocm_to_ucom(struct ucom_softc *, u_long, int);
+static int	ucom_to_tiocm(struct ucom_softc *);
+
+static void	ucomreadcb(usbd_xfer_handle, usbd_private_handle, usbd_status);
+static void	ucom_submit_write(struct ucom_softc *, struct ucom_buffer *);
+static void	ucom_write_status(struct ucom_softc *, struct ucom_buffer *,
+			usbd_status);
+
+static void	ucomwritecb(usbd_xfer_handle, usbd_private_handle, usbd_status);
+static void	ucom_read_complete(struct ucom_softc *);
+static usbd_status ucomsubmitread(struct ucom_softc *, struct ucom_buffer *);
+static void	ucom_softintr(void *);
 
 USB_DECLARE_DRIVER(ucom);
 
@@ -189,9 +218,21 @@
 	sc->sc_parent = uca->arg;
 	sc->sc_portno = uca->portno;
 
+	sc->sc_lsr = 0;
+	sc->sc_msr = 0;
+	sc->sc_mcr = 0;
+	sc->sc_tx_stopped = 0;
+	sc->sc_swflags = 0;
+	sc->sc_opening = 0;
+	sc->sc_refcnt = 0;
+	sc->sc_dying = 0;
+
+	sc->sc_si = softint_establish(SOFTINT_NET, ucom_softintr, sc);
+
 	tp = ttymalloc();
 	tp->t_oproc = ucomstart;
 	tp->t_param = ucomparam;
+	tp->t_hwiflow = ucomhwiflow;
 	sc->sc_tty = tp;
 
 	DPRINTF(("ucom_attach: tty_attach %p\n", tp));
@@ -212,7 +253,7 @@
 	struct ucom_softc *sc = device_private(self);
 	struct tty *tp = sc->sc_tty;
 	int maj, mn;
-	int s;
+	int s, i;
 
 	DPRINTF(("ucom_detach: sc=%p flags=%d tp=%p, pipe=%d,%d\n",
 		 sc, flags, tp, sc->sc_bulkin_no, sc->sc_bulkout_no));
@@ -238,6 +279,8 @@
 		/* Wait for processes to go away. */
 		usb_detach_wait(USBDEV(sc->sc_dev));
 	}
+
+	softint_disestablish(sc->sc_si);
 	splx(s);
 
 	/* locate the major number */
@@ -257,6 +300,16 @@
 		sc->sc_tty = NULL;
 	}
 
+	for (i = 0; i < UCOM_IN_BUFFS; i++) {
+		if (sc->sc_ibuff[i].ub_xfer != NULL)
+			usbd_free_xfer(sc->sc_ibuff[i].ub_xfer);
+	}
+
+	for (i = 0; i < UCOM_OUT_BUFFS; i++) {
+		if (sc->sc_obuff[i].ub_xfer != NULL)
+			usbd_free_xfer(sc->sc_obuff[i].ub_xfer);
+	}
+
 	/* Detach the random source */
 #if defined(__NetBSD__) && NRND > 0
 	rnd_detach_source(&sc->sc_rndsource);
@@ -303,8 +356,9 @@
 	int unit = UCOMUNIT(dev);
 	usbd_status err;
 	struct ucom_softc *sc = device_lookup_private(&ucom_cd, unit);
+	struct ucom_buffer *ub;
 	struct tty *tp;
-	int s;
+	int s, i;
 	int error;
 
 	if (sc == NULL)
@@ -388,15 +442,12 @@
 		ucom_dtr(sc, 1);
 		ucom_rts(sc, 1);		
 
-		/* XXX CLR(sc->sc_rx_flags, RX_ANY_BLOCK);*/
-		ucom_hwiflow(sc);
-
 		DPRINTF(("ucomopen: open pipes in=%d out=%d\n",
 			 sc->sc_bulkin_no, sc->sc_bulkout_no));
 
 		/* Open the bulk pipes */
-		err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkin_no, 0,
-				     &sc->sc_bulkin_pipe);
+		err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkin_no,
+				     USBD_EXCLUSIVE_USE, &sc->sc_bulkin_pipe);
 		if (err) {
 			DPRINTF(("%s: open bulk in error (addr %d), err=%s\n",
 				 USBDEVNAME(sc->sc_dev), sc->sc_bulkin_no,
@@ -414,35 +465,56 @@
 			goto fail_1;
 		}
 
-		/* Allocate a request and an input buffer and start reading. */
-		sc->sc_ixfer = usbd_alloc_xfer(sc->sc_udev);
-		if (sc->sc_ixfer == NULL) {
-			error = ENOMEM;
-			goto fail_2;
-		}
+		sc->sc_rx_unblock = 0;
+		sc->sc_rx_stopped = 0;
+		sc->sc_tx_stopped = 0;
+
+		memset(sc->sc_ibuff, 0, sizeof(sc->sc_ibuff));
+		memset(sc->sc_obuff, 0, sizeof(sc->sc_obuff));
+
+		SIMPLEQ_INIT(&sc->sc_ibuff_empty);
+		SIMPLEQ_INIT(&sc->sc_ibuff_full);
+		SIMPLEQ_INIT(&sc->sc_obuff_free);
+		SIMPLEQ_INIT(&sc->sc_obuff_full);
+
+		/* Allocate input buffers */
+		for (ub = &sc->sc_ibuff[0]; ub != &sc->sc_ibuff[UCOM_IN_BUFFS];
+		    ub++) {
+			ub->ub_xfer = usbd_alloc_xfer(sc->sc_udev);
+			if (ub->ub_xfer == NULL) {
+				error = ENOMEM;
+				goto fail_2;
+			}
+			ub->ub_data = usbd_alloc_buffer(ub->ub_xfer,
+			    sc->sc_ibufsizepad);
+			if (ub->ub_data == NULL) {
+				error = ENOMEM;
+				goto fail_2;
+			}
 
-		sc->sc_ibuf = usbd_alloc_buffer(sc->sc_ixfer,
-						sc->sc_ibufsizepad);
-		if (sc->sc_ibuf == NULL) {
-			error = ENOMEM;
-			goto fail_3;
+			if (ucomsubmitread(sc, ub) != USBD_NORMAL_COMPLETION) {
+				error = EIO;
+				goto fail_2;
+			}
 		}
 
-		sc->sc_oxfer = usbd_alloc_xfer(sc->sc_udev);
-		if (sc->sc_oxfer == NULL) {
-			error = ENOMEM;
-			goto fail_3;
-		}
+		for (ub = &sc->sc_obuff[0]; ub != &sc->sc_obuff[UCOM_OUT_BUFFS];
+		    ub++) {
+			ub->ub_xfer = usbd_alloc_xfer(sc->sc_udev);
+			if (ub->ub_xfer == NULL) {
+				error = ENOMEM;
+				goto fail_2;
+			}
+			ub->ub_data = usbd_alloc_buffer(ub->ub_xfer,
+			    sc->sc_obufsize);
+			if (ub->ub_data == NULL) {
+				error = ENOMEM;
+				goto fail_2;
+			}
 
-		sc->sc_obuf = usbd_alloc_buffer(sc->sc_oxfer,
-						sc->sc_obufsize +
-						sc->sc_opkthdrlen);
-		if (sc->sc_obuf == NULL) {
-			error = ENOMEM;
-			goto fail_4;
+			SIMPLEQ_INSERT_TAIL(&sc->sc_obuff_free, ub, ub_link);
 		}
 
-		ucomstartread(sc);
 	}
 	sc->sc_opening = 0;
 	wakeup(&sc->sc_opening);
@@ -458,13 +530,24 @@
 
 	return (0);
 
-fail_4:
-	usbd_free_xfer(sc->sc_oxfer);
-	sc->sc_oxfer = NULL;
-fail_3:
-	usbd_free_xfer(sc->sc_ixfer);
-	sc->sc_ixfer = NULL;
 fail_2:
+	usbd_abort_pipe(sc->sc_bulkin_pipe);
+	for (i = 0; i < UCOM_IN_BUFFS; i++) {
+		if (sc->sc_ibuff[i].ub_xfer != NULL) {
+			usbd_free_xfer(sc->sc_ibuff[i].ub_xfer);
+			sc->sc_ibuff[i].ub_xfer = NULL;
+			sc->sc_ibuff[i].ub_data = NULL;
+		}
+	}
+	usbd_abort_pipe(sc->sc_bulkout_pipe);
+	for (i = 0; i < UCOM_OUT_BUFFS; i++) {
+		if (sc->sc_obuff[i].ub_xfer != NULL) {
+			usbd_free_xfer(sc->sc_obuff[i].ub_xfer);
+			sc->sc_obuff[i].ub_xfer = NULL;
+			sc->sc_obuff[i].ub_data = NULL;
+		}
+	}
+
 	usbd_close_pipe(sc->sc_bulkout_pipe);
 	sc->sc_bulkout_pipe = NULL;
 fail_1:
@@ -477,6 +560,8 @@
 	return (error);
 
 bad:
+	s = spltty();
+	CLR(tp->t_state, TS_BUSY);
 	if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
 		/*
 		 * We failed to open the device, and nobody else had it opened.
@@ -484,6 +569,7 @@
 		 */
 		ucom_cleanup(sc);
 	}
+	splx(s);
 
 	return (error);
 }
@@ -493,12 +579,15 @@
 {
 	struct ucom_softc *sc = device_lookup_private(&ucom_cd, UCOMUNIT(dev));
 	struct tty *tp = sc->sc_tty;
+	int s;
 
 	DPRINTF(("ucomclose: unit=%d\n", UCOMUNIT(dev)));
 	if (!ISSET(tp->t_state, TS_ISOPEN))
 		return (0);
 
+	s = spltty();
 	sc->sc_refcnt++;
+	CLR(tp->t_state, TS_BUSY);
 
 	(*tp->t_linesw->l_close)(tp, flag);
 	ttyclose(tp);
@@ -517,6 +606,7 @@
 
 	if (--sc->sc_refcnt < 0)
 		usb_detach_wakeup(USBDEV(sc->sc_dev));
+	splx(s);
 
 	return (0);
 }
@@ -594,7 +684,7 @@
 	return (error);
 }
 
-Static int
+static int
 ucom_do_ioctl(struct ucom_softc *sc, u_long cmd, void *data,
 	      int flag, struct lwp *l)
 {
@@ -676,7 +766,7 @@
 	return (error);
 }
 
-Static void
+static void
 tiocm_to_ucom(struct ucom_softc *sc, u_long how, int ttybits)
 {
 	u_char combits;
@@ -708,7 +798,7 @@
 		ucom_rts(sc, (sc->sc_mcr & UMCR_RTS) != 0);
 }
 
-Static int
+static int
 ucom_to_tiocm(struct ucom_softc *sc)
 {
 	u_char combits;
@@ -739,7 +829,7 @@
 	return (ttybits);
 }
 
-Static void
+static void
 ucom_break(struct ucom_softc *sc, int onoff)
 {
 	DPRINTF(("ucom_break: onoff=%d\n", onoff));
@@ -749,7 +839,7 @@
 		    UCOM_SET_BREAK, onoff);
 }
 
-Static void
+static void
 ucom_dtr(struct ucom_softc *sc, int onoff)
 {
 	DPRINTF(("ucom_dtr: onoff=%d\n", onoff));
@@ -759,7 +849,7 @@
 		    UCOM_SET_DTR, onoff);
 }
 
-Static void
+static void
 ucom_rts(struct ucom_softc *sc, int onoff)
 {
 	DPRINTF(("ucom_rts: onoff=%d\n", onoff));
@@ -789,7 +879,7 @@
 	}
 }
 
-Static int
+static int
 ucomparam(struct tty *tp, struct termios *t)
 {
 	struct ucom_softc *sc = device_lookup_private(&ucom_cd,
@@ -858,38 +948,32 @@
 	return (0);
 }
 
-/*
- * (un)block input via hw flowcontrol
- */
-Static void
-ucom_hwiflow(struct ucom_softc *sc)
+static int
+ucomhwiflow(struct tty *tp, int block)
 {
-	DPRINTF(("ucom_hwiflow:\n"));
-#if 0
-XXX
-	bus_space_tag_t iot = sc->sc_iot;
-	bus_space_handle_t ioh = sc->sc_ioh;
+	struct ucom_softc *sc = device_lookup_private(&ucom_cd,
+	    UCOMUNIT(tp->t_dev));
+	int old;
 
-	if (sc->sc_mcr_rts == 0)
-		return;
+	old = sc->sc_rx_stopped;
+	sc->sc_rx_stopped = (u_char)block;
 
-	if (ISSET(sc->sc_rx_flags, RX_ANY_BLOCK)) {
-		CLR(sc->sc_mcr, sc->sc_mcr_rts);
-		CLR(sc->sc_mcr_active, sc->sc_mcr_rts);
-	} else {
-		SET(sc->sc_mcr, sc->sc_mcr_rts);
-		SET(sc->sc_mcr_active, sc->sc_mcr_rts);
+	if (old && !block) {
+		int s = splusb();
+		sc->sc_rx_unblock = 1;
+		softint_schedule(sc->sc_si);
+		splx(s);
 	}
-	bus_space_write_1(iot, ioh, com_mcr, sc->sc_mcr_active);
-#endif
+
+	return (1);
 }
 
-Static void
+static void
 ucomstart(struct tty *tp)
 {
 	struct ucom_softc *sc = device_lookup_private(&ucom_cd,
 	    UCOMUNIT(tp->t_dev));
-	usbd_status err;
+	struct ucom_buffer *ub;
 	int s;
 	u_char *data;
 	int cnt;
@@ -905,7 +989,7 @@
 	if (sc->sc_tx_stopped)
 		goto out;
 
-	if (!ttypull(tp)) 
+	if (!ttypull(tp))
 		goto out;
 
 	/* Grab the first contiguous region of buffer space. */
@@ -917,30 +1001,30 @@
 		goto out;
 	}
 
-	SET(tp->t_state, TS_BUSY);
+	ub = SIMPLEQ_FIRST(&sc->sc_obuff_free);
+	KASSERT(ub != NULL);
+	SIMPLEQ_REMOVE_HEAD(&sc->sc_obuff_free, ub_link);
 
-	if (cnt > sc->sc_obufsize) {
-		DPRINTF(("ucomstart: big buffer %d chars\n", cnt));
+	if (SIMPLEQ_FIRST(&sc->sc_obuff_free) == NULL)
+		SET(tp->t_state, TS_BUSY);
+
+	if (cnt > sc->sc_obufsize)
 		cnt = sc->sc_obufsize;
-	}
+
 	if (sc->sc_methods->ucom_write != NULL)
 		sc->sc_methods->ucom_write(sc->sc_parent, sc->sc_portno,
-					   sc->sc_obuf, data, &cnt);
+					   ub->ub_data, data, &cnt);
 	else
-		memcpy(sc->sc_obuf, data, cnt);
+		memcpy(ub->ub_data, data, cnt);
 
-	DPRINTFN(4,("ucomstart: %d chars\n", cnt));
-	usbd_setup_xfer(sc->sc_oxfer, sc->sc_bulkout_pipe,
-			(usbd_private_handle)sc, sc->sc_obuf, cnt,
-			USBD_NO_COPY, USBD_NO_TIMEOUT, ucomwritecb);
-	/* What can we do on error? */
-	err = usbd_transfer(sc->sc_oxfer);
-#ifdef DIAGNOSTIC
-	if (err != USBD_IN_PROGRESS)
-		printf("ucomstart: err=%s\n", usbd_errstr(err));
-#endif
+	ub->ub_len = cnt;
+	ub->ub_index = 0;
+
+	SIMPLEQ_INSERT_TAIL(&sc->sc_obuff_full, ub, ub_link);
+
+	softint_schedule(sc->sc_si);
 
-out:
+ out:
 	splx(s);
 }
 
@@ -964,133 +1048,234 @@
 #endif
 }
 
-Static void
-ucomwritecb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status)
+static void
+ucom_write_status(struct ucom_softc *sc, struct ucom_buffer *ub,
+    usbd_status err)
 {
-	struct ucom_softc *sc = (struct ucom_softc *)p;
 	struct tty *tp = sc->sc_tty;
-	u_int32_t cc;
-	int s;
-
-	DPRINTFN(5,("ucomwritecb: status=%d\n", status));
+	uint32_t cc = ub->ub_len;
 
-	if (status == USBD_CANCELLED || sc->sc_dying)
-		goto error;
+	switch (err) {
+	case USBD_IN_PROGRESS:
+		ub->ub_index = ub->ub_len;
+		break;
+	case USBD_STALLED:
+		ub->ub_index = 0;
+		softint_schedule(sc->sc_si);
+		break;
+	case USBD_NORMAL_COMPLETION:
+		usbd_get_xfer_status(ub->ub_xfer, NULL, NULL, &cc, NULL);
+#if defined(__NetBSD__) && NRND > 0
+		rnd_add_uint32(&sc->sc_rndsource, cc);
+#endif
+		/*FALLTHROUGH*/
+	default:
+		SIMPLEQ_REMOVE_HEAD(&sc->sc_obuff_full, ub_link);
+		SIMPLEQ_INSERT_TAIL(&sc->sc_obuff_free, ub, ub_link);
+		cc -= sc->sc_opkthdrlen;
+
+		CLR(tp->t_state, TS_BUSY);
+		if (ISSET(tp->t_state, TS_FLUSH))
+			CLR(tp->t_state, TS_FLUSH);
+		else
+			ndflush(&tp->t_outq, cc);
+
+		if (err != USBD_CANCELLED && err != USBD_IOERROR &&
+		    !sc->sc_dying) {
+			if ((ub = SIMPLEQ_FIRST(&sc->sc_obuff_full)) != NULL)
+				ucom_submit_write(sc, ub);
 
-	if (status) {
-		DPRINTF(("ucomwritecb: status=%d\n", status));
-		usbd_clear_endpoint_stall_async(sc->sc_bulkin_pipe);
-		/* XXX we should restart after some delay. */
-		goto error;
+			(*tp->t_linesw->l_start)(tp);
+		}
+		break;
 	}
+}
 
-	usbd_get_xfer_status(xfer, NULL, NULL, &cc, NULL);
-#if defined(__NetBSD__) && NRND > 0
-	rnd_add_uint32(&sc->sc_rndsource, cc);
-#endif
-	DPRINTFN(5,("ucomwritecb: cc=%d\n", cc));
-	/* convert from USB bytes to tty bytes */
-	cc -= sc->sc_opkthdrlen;
+/* Call at spltty() */
+static void
+ucom_submit_write(struct ucom_softc *sc, struct ucom_buffer *ub)
+{
+
+	usbd_setup_xfer(ub->ub_xfer, sc->sc_bulkout_pipe,
+	    (usbd_private_handle)sc, ub->ub_data, ub->ub_len,
+	    USBD_NO_COPY, USBD_NO_TIMEOUT, ucomwritecb);
+
+	ucom_write_status(sc, ub, usbd_transfer(ub->ub_xfer));
+}
+
+static void
+ucomwritecb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status)
+{
+	struct ucom_softc *sc = (struct ucom_softc *)p;
+	int s;
 
 	s = spltty();
-	CLR(tp->t_state, TS_BUSY);
-	if (ISSET(tp->t_state, TS_FLUSH))
-		CLR(tp->t_state, TS_FLUSH);
-	else
-		ndflush(&tp->t_outq, cc);
-	(*tp->t_linesw->l_start)(tp);
+
+	ucom_write_status(sc, SIMPLEQ_FIRST(&sc->sc_obuff_full), status);
+
 	splx(s);
-	return;
+}
+
+static void
+ucom_softintr(void *arg)
+{
+	struct ucom_softc *sc = arg;
+	struct tty *tp = sc->sc_tty;
+	struct ucom_buffer *ub;
+	int s;
+
+	if (!ISSET(tp->t_state, TS_ISOPEN))
+		return;
 
-error:
 	s = spltty();
-	CLR(tp->t_state, TS_BUSY);
+
+	ub = SIMPLEQ_FIRST(&sc->sc_obuff_full);
+
+	if (ub != NULL && ub->ub_index == 0)
+		ucom_submit_write(sc, ub);
+
+	if (sc->sc_rx_unblock)
+		ucom_read_complete(sc);
+
 	splx(s);
 }
 
-Static usbd_status
-ucomstartread(struct ucom_softc *sc)
+static void
+ucom_read_complete(struct ucom_softc *sc)
+{
+	int (*rint)(int, struct tty *);
+	struct ucom_buffer *ub;
+	struct tty *tp;
+	int s;
+
+	tp = sc->sc_tty;
+	rint = tp->t_linesw->l_rint;
+	ub = SIMPLEQ_FIRST(&sc->sc_ibuff_full);
+
+	while (ub != NULL && !sc->sc_rx_stopped) {
+
+		s = spltty();
+
+		while (ub->ub_index < ub->ub_len && !sc->sc_rx_stopped) {
+			/* Give characters to tty layer. */
+			if ((*rint)(ub->ub_data[ub->ub_index], tp) == -1) {
+				/* Overflow: drop remainder */
+				ub->ub_index = ub->ub_len;
+			} else
+				ub->ub_index++;
+		}
+
+		splx(s);
+
+		if (ub->ub_index == ub->ub_len) {
+			SIMPLEQ_REMOVE_HEAD(&sc->sc_ibuff_full, ub_link);
+
+			ucomsubmitread(sc, ub);
+
+			ub = SIMPLEQ_FIRST(&sc->sc_ibuff_full);
+		}
+	}
+
+	sc->sc_rx_unblock = (ub != NULL);
+}
+
+static usbd_status
+ucomsubmitread(struct ucom_softc *sc, struct ucom_buffer *ub)
 {
 	usbd_status err;
 
-	DPRINTFN(5,("ucomstartread: start\n"));
-	usbd_setup_xfer(sc->sc_ixfer, sc->sc_bulkin_pipe,
-			(usbd_private_handle)sc,
-			sc->sc_ibuf, sc->sc_ibufsize,
-			USBD_SHORT_XFER_OK | USBD_NO_COPY,
-			USBD_NO_TIMEOUT, ucomreadcb);
-	err = usbd_transfer(sc->sc_ixfer);
-	if (err != USBD_IN_PROGRESS) {
-		DPRINTF(("ucomstartread: err=%s\n", usbd_errstr(err)));
+	usbd_setup_xfer(ub->ub_xfer, sc->sc_bulkin_pipe,
+	    (usbd_private_handle)sc, ub->ub_data, sc->sc_ibufsize,
+	    USBD_SHORT_XFER_OK | USBD_NO_COPY, USBD_NO_TIMEOUT, ucomreadcb);
+
+	if ((err = usbd_transfer(ub->ub_xfer)) != USBD_IN_PROGRESS) {
+		/* XXX: Recover from this, please! */
+		printf("ucomsubmitread: err=%s\n", usbd_errstr(err));
 		return (err);
 	}
+
+	SIMPLEQ_INSERT_TAIL(&sc->sc_ibuff_empty, ub, ub_link);
+
 	return (USBD_NORMAL_COMPLETION);
 }
 
-Static void
+static void
 ucomreadcb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status)
 {
 	struct ucom_softc *sc = (struct ucom_softc *)p;
 	struct tty *tp = sc->sc_tty;
-	int (*rint)(int, struct tty *) = tp->t_linesw->l_rint;
-	usbd_status err;
+	struct ucom_buffer *ub;
 	u_int32_t cc;
 	u_char *cp;
 	int s;
 
-	DPRINTFN(5,("ucomreadcb: status=%d\n", status));
+	ub = SIMPLEQ_FIRST(&sc->sc_ibuff_empty);
+	SIMPLEQ_REMOVE_HEAD(&sc->sc_ibuff_empty, ub_link);
 
 	if (status == USBD_CANCELLED || status == USBD_IOERROR ||
 	    sc->sc_dying) {
 		DPRINTF(("ucomreadcb: dying\n"));
+		ub->ub_index = ub->ub_len = 0;
 		/* Send something to wake upper layer */
 		s = spltty();
-		(*rint)('\n', tp);
-		mutex_spin_enter(&tty_lock);	/* XXX */
-		ttwakeup(tp);
-		mutex_spin_exit(&tty_lock);	/* XXX */
+		if (status != USBD_CANCELLED) {
+			(tp->t_linesw->l_rint)('\n', tp);
+			mutex_spin_enter(&tty_lock);	/* XXX */
+			ttwakeup(tp);
+			mutex_spin_exit(&tty_lock);	/* XXX */
+		}
 		splx(s);
 		return;
 	}
 
-	if (status) {
+	if (status == USBD_STALLED) {
 		usbd_clear_endpoint_stall_async(sc->sc_bulkin_pipe);
-		/* XXX we should restart after some delay. */
+		ucomsubmitread(sc, ub);
+		return;
+	}
+
+	if (status != USBD_NORMAL_COMPLETION) {
+		printf("ucomreadcb: wonky status=%s\n", usbd_errstr(status));
 		return;
 	}
 
 	usbd_get_xfer_status(xfer, NULL, (void *)&cp, &cc, NULL);
+
+	if (cc == 0) {
+		aprint_normal_dev(sc->sc_dev,
+		    "ucomreadcb: zero length xfer!\n");
+	}
+
+	KDASSERT(cp == ub->ub_data);
+
 #if defined(__NetBSD__) && NRND > 0
 	rnd_add_uint32(&sc->sc_rndsource, cc);
 #endif
-	DPRINTFN(5,("ucomreadcb: got %d chars, tp=%p\n", cc, tp));
-	if (sc->sc_methods->ucom_read != NULL)
-		sc->sc_methods->ucom_read(sc->sc_parent, sc->sc_portno,
-					  &cp, &cc);
 
-	s = spltty();
-	/* Give characters to tty layer. */
-	while (cc-- > 0) {
-		DPRINTFN(7,("ucomreadcb: char=0x%02x\n", *cp));
-		if ((*rint)(*cp++, tp) == -1) {
-			/* XXX what should we do? */
-			aprint_error_dev(sc->sc_dev, "lost %d chars\n",
-			       cc);
-			break;
-		}
+	if (sc->sc_opening) {
+		ucomsubmitread(sc, ub);
+		return;
 	}
-	splx(s);
 
-	err = ucomstartread(sc);
-	if (err) {
-		aprint_error_dev(sc->sc_dev, "read start failed\n");
-		/* XXX what should we do now? */
-	}
+	if (sc->sc_methods->ucom_read != NULL) {
+		sc->sc_methods->ucom_read(sc->sc_parent, sc->sc_portno,
+		    &cp, &cc);
+		ub->ub_index = (u_int)(cp - ub->ub_data);
+	} else
+		ub->ub_index = 0;
+
+	ub->ub_len = cc;
+
+	SIMPLEQ_INSERT_TAIL(&sc->sc_ibuff_full, ub, ub_link);
+
+	ucom_read_complete(sc);
 }
 
-Static void
+static void
 ucom_cleanup(struct ucom_softc *sc)
 {
+	struct ucom_buffer *ub;
+
 	DPRINTF(("ucom_cleanup: closing pipes\n"));
 
 	ucom_shutdown(sc);
@@ -1104,13 +1289,19 @@
 		usbd_close_pipe(sc->sc_bulkout_pipe);
 		sc->sc_bulkout_pipe = NULL;
 	}
-	if (sc->sc_ixfer != NULL) {
-		usbd_free_xfer(sc->sc_ixfer);
-		sc->sc_ixfer = NULL;
-	}
-	if (sc->sc_oxfer != NULL) {
-		usbd_free_xfer(sc->sc_oxfer);
-		sc->sc_oxfer = NULL;
+	for (ub = &sc->sc_ibuff[0]; ub != &sc->sc_ibuff[UCOM_IN_BUFFS]; ub++) {
+		if (ub->ub_xfer != NULL) {
+			usbd_free_xfer(ub->ub_xfer);
+			ub->ub_xfer = NULL;
+			ub->ub_data = NULL;
+		}
+	}
+	for (ub = &sc->sc_obuff[0]; ub != &sc->sc_obuff[UCOM_OUT_BUFFS]; ub++){
+		if (ub->ub_xfer != NULL) {
+			usbd_free_xfer(ub->ub_xfer);
+			ub->ub_xfer = NULL;
+			ub->ub_data = NULL;
+		}
 	}
 }
 

Reply via email to