Date: Fri, 26 Oct 2012 12:24:38 +0000 (UTC) From: Nick Hibma <n_hibma@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-8@freebsd.org Subject: svn commit: r242128 - stable/8/sys/dev/usb/serial Message-ID: <201210261224.q9QCOcZU097739@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
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); +}
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201210261224.q9QCOcZU097739>