Author: n_hibma
Date: Fri Oct 26 12:24:38 2012
New Revision: 242128
URL: http://svn.freebsd.org/changeset/base/242128

Log:
  MFC 241555:
  
        Implement modem control in u3g. Tested on Option GTM382W, Huawei
        E220, and Sierra Wireless MC8790V. Also implement the .ucom_poll
        method.
  
        Note: It resolves ppp hanging during the PPp> phase.

Modified:
  stable/8/sys/dev/usb/serial/u3g.c
Directory Properties:
  stable/8/sys/   (props changed)
  stable/8/sys/dev/   (props changed)
  stable/8/sys/dev/usb/   (props changed)

Modified: stable/8/sys/dev/usb/serial/u3g.c
==============================================================================
--- stable/8/sys/dev/usb/serial/u3g.c   Fri Oct 26 11:41:57 2012        
(r242127)
+++ stable/8/sys/dev/usb/serial/u3g.c   Fri Oct 26 12:24:38 2012        
(r242128)
@@ -53,6 +53,7 @@
 #include <dev/usb/usb.h>
 #include <dev/usb/usbdi.h>
 #include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_cdc.h>
 #include "usbdevs.h"
 
 #define        USB_DEBUG_VAR u3g_debug
@@ -99,6 +100,7 @@ SYSCTL_INT(_hw_usb_u3g, OID_AUTO, debug,
 enum {
        U3G_BULK_WR,
        U3G_BULK_RD,
+       U3G_INTR,
        U3G_N_TRANSFER,
 };
 
@@ -107,12 +109,15 @@ struct u3g_softc {
        struct ucom_softc sc_ucom[U3G_MAXPORTS];
 
        struct usb_xfer *sc_xfer[U3G_MAXPORTS][U3G_N_TRANSFER];
+       uint8_t sc_iface[U3G_MAXPORTS];                 /* local status 
register */
+       uint8_t sc_lsr[U3G_MAXPORTS];                   /* local status 
register */
+       uint8_t sc_msr[U3G_MAXPORTS];                   /* u3g status register 
*/
+       uint16_t sc_line[U3G_MAXPORTS];                 /* line status */
+
        struct usb_device *sc_udev;
        struct mtx sc_mtx;
 
-       uint8_t sc_lsr;                 /* local status register */
-       uint8_t sc_msr;                 /* U3G status register */
-       uint8_t sc_numports;
+       uint8_t sc_numports;
 };
 
 static device_probe_t u3g_probe;
@@ -121,11 +126,16 @@ static device_detach_t u3g_detach;
 
 static usb_callback_t u3g_write_callback;
 static usb_callback_t u3g_read_callback;
+static usb_callback_t u3g_intr_callback;
 
+static void u3g_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *);
+static void u3g_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void u3g_cfg_set_rts(struct ucom_softc *, uint8_t);
 static void u3g_start_read(struct ucom_softc *ucom);
 static void u3g_stop_read(struct ucom_softc *ucom);
 static void u3g_start_write(struct ucom_softc *ucom);
 static void u3g_stop_write(struct ucom_softc *ucom);
+static void u3g_poll(struct ucom_softc *ucom);
 
 
 static void u3g_test_autoinst(void *, struct usb_device *,
@@ -153,13 +163,26 @@ static const struct usb_config u3g_confi
                .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
                .callback = &u3g_read_callback,
        },
+
+       [U3G_INTR] = {
+               .type = UE_INTERRUPT,
+               .endpoint = UE_ADDR_ANY,
+               .direction = UE_DIR_IN,
+               .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,},
+               .bufsize = 0,   /* use wMaxPacketSize */
+               .callback = &u3g_intr_callback,
+       },
 };
 
 static const struct ucom_callback u3g_callback = {
+       .ucom_cfg_get_status = &u3g_cfg_get_status,
+       .ucom_cfg_set_dtr = &u3g_cfg_set_dtr,
+       .ucom_cfg_set_rts = &u3g_cfg_set_rts,
        .ucom_start_read = &u3g_start_read,
        .ucom_stop_read = &u3g_stop_read,
        .ucom_start_write = &u3g_start_write,
        .ucom_stop_write = &u3g_stop_write,
+       .ucom_poll = &u3g_poll,
 };
 
 static device_method_t u3g_methods[] = {
@@ -839,6 +862,15 @@ u3g_attach(device_t dev)
                        continue;
                }
 
+               iface = usbd_get_iface(uaa->device, i);
+               id = usbd_get_interface_descriptor(iface);
+               sc->sc_iface[nports] = id->bInterfaceNumber;
+
+               if (bootverbose && sc->sc_xfer[nports][U3G_INTR]) {
+                       device_printf(dev, "port %d supports modem control",
+                                     nports);
+               }
+
                /* set stall by default */
                mtx_lock(&sc->sc_mtx);
                usbd_xfer_set_stall(sc->sc_xfer[nports][U3G_BULK_WR]);
@@ -896,6 +928,9 @@ u3g_start_read(struct ucom_softc *ucom)
 {
        struct u3g_softc *sc = ucom->sc_parent;
 
+       /* start interrupt endpoint (if configured) */
+       usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_INTR]);
+
        /* start read endpoint */
        usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_RD]);
        return;
@@ -906,6 +941,9 @@ u3g_stop_read(struct ucom_softc *ucom)
 {
        struct u3g_softc *sc = ucom->sc_parent;
 
+       /* stop interrupt endpoint (if configured) */
+       usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_INTR]);
+
        /* stop read endpoint */
        usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_RD]);
        return;
@@ -988,3 +1026,134 @@ tr_setup:
        }
        return;
 }
+
+static void
+u3g_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+       struct u3g_softc *sc = ucom->sc_parent;
+
+       *lsr = sc->sc_lsr[ucom->sc_subunit];
+       *msr = sc->sc_msr[ucom->sc_subunit];
+}
+
+static void
+u3g_cfg_set_line(struct ucom_softc *ucom)
+{
+       struct u3g_softc *sc = ucom->sc_parent;
+       struct usb_device_request req;
+
+       req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+       req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
+       USETW(req.wValue, sc->sc_line[ucom->sc_subunit]);
+       req.wIndex[0] = sc->sc_iface[ucom->sc_subunit];
+       req.wIndex[1] = 0;
+       USETW(req.wLength, 0);
+
+       ucom_cfg_do_request(sc->sc_udev, ucom,
+           &req, NULL, 0, 1000);
+}
+
+static void
+u3g_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+       struct u3g_softc *sc = ucom->sc_parent;
+
+       DPRINTF("onoff = %d\n", onoff);
+
+       if (onoff)
+               sc->sc_line[ucom->sc_subunit] |= UCDC_LINE_DTR;
+       else
+               sc->sc_line[ucom->sc_subunit] &= ~UCDC_LINE_DTR;
+
+       u3g_cfg_set_line(ucom);
+}
+
+static void
+u3g_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+       struct u3g_softc *sc = ucom->sc_parent;
+
+       DPRINTF("onoff = %d\n", onoff);
+
+       if (onoff)
+               sc->sc_line[ucom->sc_subunit] |= UCDC_LINE_RTS;
+       else
+               sc->sc_line[ucom->sc_subunit] &= ~UCDC_LINE_RTS;
+
+       u3g_cfg_set_line(ucom);
+}
+
+static void
+u3g_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+       struct ucom_softc *ucom = usbd_xfer_softc(xfer);
+       struct u3g_softc *sc = ucom->sc_parent;
+       struct usb_page_cache *pc;
+       struct usb_cdc_notification pkt;
+       int actlen;
+       uint16_t wLen;
+       uint8_t mstatus;
+
+       usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+       switch (USB_GET_STATE(xfer)) {
+       case USB_ST_TRANSFERRED:
+               if (actlen < 8) {       /* usb_cdc_notification with 2 data 
bytes */
+                       DPRINTF("message too short (expected 8, received 
%d)\n", actlen);
+                       goto tr_setup;
+               }
+               pc = usbd_xfer_get_frame(xfer, 0);
+               usbd_copy_out(pc, 0, &pkt, actlen);
+
+               wLen = UGETW(pkt.wLength);
+               if (wLen < 2) {
+                       DPRINTF("message too short (expected 2 data bytes, 
received %d)\n", wLen);
+                       goto tr_setup;
+               }
+
+               if (pkt.bmRequestType == UCDC_NOTIFICATION
+                   && pkt.bNotification == UCDC_N_SERIAL_STATE) {
+                       /*
+                        * Set the serial state in ucom driver based on
+                        * the bits from the notify message
+                        */
+                       DPRINTF("notify bytes = 0x%02x, 0x%02x\n",
+                           pkt.data[0], pkt.data[1]);
+
+                       /* currently, lsr is always zero. */
+                       sc->sc_lsr[ucom->sc_subunit] = 0;
+                       sc->sc_msr[ucom->sc_subunit] = 0;
+
+                       mstatus = pkt.data[0];
+
+                       if (mstatus & UCDC_N_SERIAL_RI)
+                               sc->sc_msr[ucom->sc_subunit] |= SER_RI;
+                       if (mstatus & UCDC_N_SERIAL_DSR)
+                               sc->sc_msr[ucom->sc_subunit] |= SER_DSR;
+                       if (mstatus & UCDC_N_SERIAL_DCD)
+                               sc->sc_msr[ucom->sc_subunit] |= SER_DCD;
+                       ucom_status_change(ucom);
+               }
+
+       case USB_ST_SETUP:
+tr_setup:
+               usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+               usbd_transfer_submit(xfer);
+               return;
+
+       default:                        /* Error */
+               if (error != USB_ERR_CANCELLED) {
+                       /* try to clear stall first */
+                       usbd_xfer_set_stall(xfer);
+                       goto tr_setup;
+               }
+               return;
+       }
+}
+
+static void
+u3g_poll(struct ucom_softc *ucom)
+{
+       struct u3g_softc *sc = ucom->sc_parent;
+       usbd_transfer_poll(sc->sc_xfer[ucom->sc_subunit], U3G_N_TRANSFER);
+}
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to