mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-20 11:11:24 +00:00
Implement modem control in u3g. Tested on Option GTM382W, Huawei E220,
and Sierra Wireless MC8790V. Also implement the .ucom_poll method. Note: This makes it possible to use lqr/echo in ppp.conf. And it resolves ppp hanging during the PPp> phase. Reviewed by: hps MFC after: 1 week
This commit is contained in:
parent
82ed933c6f
commit
da016e84de
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=241555
@ -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, CTLFLAG_RW,
|
||||
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;
|
||||
@ -122,12 +127,17 @@ static void u3g_free_softc(struct u3g_softc *);
|
||||
|
||||
static usb_callback_t u3g_write_callback;
|
||||
static usb_callback_t u3g_read_callback;
|
||||
static usb_callback_t u3g_intr_callback;
|
||||
|
||||
static void u3g_free(struct ucom_softc *ucom);
|
||||
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_free(struct ucom_softc *ucom);
|
||||
|
||||
|
||||
static void u3g_test_autoinst(void *, struct usb_device *,
|
||||
@ -155,13 +165,26 @@ static const struct usb_config u3g_config[U3G_N_TRANSFER] = {
|
||||
.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,
|
||||
.ucom_free = &u3g_free,
|
||||
};
|
||||
|
||||
@ -849,6 +872,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]);
|
||||
@ -926,6 +958,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]);
|
||||
}
|
||||
@ -935,6 +970,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]);
|
||||
}
|
||||
@ -1012,3 +1050,134 @@ u3g_read_callback(struct usb_xfer *xfer, usb_error_t error)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user