diff --git a/sys/conf/files b/sys/conf/files index 853c0c8e969a..0dc814e0934a 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1918,6 +1918,7 @@ dev/usb/net/if_kue.c optional kue dev/usb/net/if_mos.c optional mos dev/usb/net/if_rue.c optional rue dev/usb/net/if_udav.c optional udav +dev/usb/net/if_usie.c optional usie dev/usb/net/usb_ethernet.c optional aue | axe | cdce | cue | kue | mos | \ rue | udav dev/usb/net/uhso.c optional uhso @@ -1970,8 +1971,8 @@ dev/usb/serial/uvscom.c optional uvscom dev/usb/serial/usb_serial.c optional ucom | u3g | uark | ubsa | ubser | \ uchcom | ucycom | ufoma | uftdi | \ ugensa | uipaq | umcs | umct | \ - umodem | umoscom | uplcom | uslcom | \ - uvisor | uvscom + umodem | umoscom | uplcom | usie | \ + uslcom | uvisor | uvscom # # USB misc drivers # diff --git a/sys/dev/usb/net/if_usie.c b/sys/dev/usb/net/if_usie.c new file mode 100644 index 000000000000..528b391846c0 --- /dev/null +++ b/sys/dev/usb/net/if_usie.c @@ -0,0 +1,1587 @@ +/*- + * Copyright (c) 2011 Anybots Inc + * written by Akinori Furukoshi + * reviewed by Hans Petter Selasky + * - ucom part is based on u3g.c + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR usie_debug +#include +#include +#include + +#include + +#include + +#ifdef USB_DEBUG +static int usie_debug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, usie, CTLFLAG_RW, 0, "sierra USB modem"); +SYSCTL_INT(_hw_usb_usie, OID_AUTO, debug, CTLFLAG_RW, &usie_debug, 0, + "usie debug level"); +#endif + +/* Sierra Wireless Direct IP modems */ +static const STRUCT_USB_HOST_ID usie_devs[] = { +#define USIE_DEV(v, d) { \ + USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##d) } + USIE_DEV(SIERRA, MC8700), + USIE_DEV(SIERRA, TRUINSTALL), + USIE_DEV(AIRPRIME, USB308), +#undef USIE_DEV +}; + +static device_probe_t usie_probe; +static device_attach_t usie_attach; +static device_detach_t usie_detach; + +static void usie_uc_update_line_state(struct ucom_softc *, uint8_t); +static void usie_uc_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); +static void usie_uc_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void usie_uc_cfg_set_rts(struct ucom_softc *, uint8_t); +static void usie_uc_cfg_open(struct ucom_softc *); +static void usie_uc_cfg_close(struct ucom_softc *); +static void usie_uc_start_read(struct ucom_softc *); +static void usie_uc_stop_read(struct ucom_softc *); +static void usie_uc_start_write(struct ucom_softc *); +static void usie_uc_stop_write(struct ucom_softc *); + +static usb_callback_t usie_uc_tx_callback; +static usb_callback_t usie_uc_rx_callback; +static usb_callback_t usie_uc_status_callback; +static usb_callback_t usie_if_tx_callback; +static usb_callback_t usie_if_rx_callback; +static usb_callback_t usie_if_status_callback; + +static void usie_if_sync_to(void *); +static void usie_if_sync_cb(void *, int); +static void usie_if_status_cb(void *, int); + +static void usie_if_start(struct ifnet *); +static int usie_if_output(struct ifnet *, struct mbuf *, struct sockaddr *, struct route *); +static void usie_if_init(void *); +static void usie_if_stop(struct usie_softc *); +static int usie_if_ioctl(struct ifnet *, u_long, caddr_t); + +static int usie_do_request(struct usie_softc *, struct usb_device_request *, void *); +static int usie_if_cmd(struct usie_softc *, uint8_t); +static void usie_cns_req(struct usie_softc *, uint32_t, uint16_t); +static void usie_cns_rsp(struct usie_softc *, struct usie_cns *); +static void usie_hip_rsp(struct usie_softc *, uint8_t *, uint32_t); +static int usie_driver_loaded(struct module *, int, void *); + +static const struct usb_config usie_uc_config[USIE_UC_N_XFER] = { + [USIE_UC_STATUS] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &usie_uc_status_callback, + }, + [USIE_UC_RX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = USIE_BUFSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1,}, + .callback = &usie_uc_rx_callback, + }, + [USIE_UC_TX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = USIE_BUFSIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &usie_uc_tx_callback, + } +}; + +static const struct usb_config usie_if_config[USIE_IF_N_XFER] = { + [USIE_IF_STATUS] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &usie_if_status_callback, + }, + [USIE_IF_RX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = USIE_BUFSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &usie_if_rx_callback, + }, + [USIE_IF_TX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = MAX(USIE_BUFSIZE, MCLBYTES), + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &usie_if_tx_callback, + } +}; + +static device_method_t usie_methods[] = { + DEVMETHOD(device_probe, usie_probe), + DEVMETHOD(device_attach, usie_attach), + DEVMETHOD(device_detach, usie_detach), + {0, 0} +}; + +static driver_t usie_driver = { + .name = "usie", + .methods = usie_methods, + .size = sizeof(struct usie_softc), +}; + +static devclass_t usie_devclass; +static eventhandler_tag usie_etag; + +DRIVER_MODULE(usie, uhub, usie_driver, usie_devclass, usie_driver_loaded, 0); +MODULE_DEPEND(usie, ucom, 1, 1, 1); +MODULE_DEPEND(usie, usb, 1, 1, 1); +MODULE_VERSION(usie, 1); + +static const struct ucom_callback usie_uc_callback = { + .ucom_cfg_get_status = &usie_uc_cfg_get_status, + .ucom_cfg_set_dtr = &usie_uc_cfg_set_dtr, + .ucom_cfg_set_rts = &usie_uc_cfg_set_rts, + .ucom_cfg_open = &usie_uc_cfg_open, + .ucom_cfg_close = &usie_uc_cfg_close, + .ucom_start_read = &usie_uc_start_read, + .ucom_stop_read = &usie_uc_stop_read, + .ucom_start_write = &usie_uc_start_write, + .ucom_stop_write = &usie_uc_stop_write, +}; + +static void +usie_autoinst(void *arg, struct usb_device *udev, + struct usb_attach_arg *uaa) +{ + struct usb_interface *iface; + struct usb_interface_descriptor *id; + struct usb_device_request req; + int err; + + if (uaa->dev_state != UAA_DEV_READY) + return; + + iface = usbd_get_iface(udev, 0); + if (iface == NULL) + return; + + id = iface->idesc; + if (id == NULL || id->bInterfaceClass != UICLASS_MASS) + return; + + if (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa) != 0) + return; /* no device match */ + + if (bootverbose) { + DPRINTF("Ejecting %s %s\n", + usb_get_manufacturer(udev), + usb_get_product(udev)); + } + req.bmRequestType = UT_VENDOR; + req.bRequest = UR_SET_INTERFACE; + USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); + USETW(req.wIndex, UHF_PORT_CONNECTION); + USETW(req.wLength, 0); + + /* at this moment there is no mutex */ + err = usbd_do_request_flags(udev, NULL, &req, + NULL, 0, NULL, 250 /* ms */ ); + + /* success, mark the udev as disappearing */ + if (err == 0) + uaa->dev_state = UAA_DEV_EJECTING; +} + +static int +usie_probe(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != USIE_CNFG_INDEX) + return (ENXIO); + if (uaa->info.bIfaceIndex != USIE_IFACE_INDEX) + return (ENXIO); + if (uaa->info.bInterfaceClass != UICLASS_VENDOR) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa)); +} + +static int +usie_attach(device_t self) +{ + struct usie_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + struct ifnet *ifp; + struct usb_interface *iface; + struct usb_interface_descriptor *id; + struct usb_device_request req; + int err; + uint16_t fwattr; + uint8_t iface_index; + uint8_t ifidx; + uint8_t start; + + device_set_usb_desc(self); + sc->sc_udev = uaa->device; + sc->sc_dev = self; + + mtx_init(&sc->sc_mtx, "usie", MTX_NETWORK_LOCK, MTX_DEF); + + TASK_INIT(&sc->sc_if_status_task, 0, usie_if_status_cb, sc); + TASK_INIT(&sc->sc_if_sync_task, 0, usie_if_sync_cb, sc); + + usb_callout_init_mtx(&sc->sc_if_sync_ch, &sc->sc_mtx, 0); + + mtx_lock(&sc->sc_mtx); + + /* set power mode to D0 */ + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = USIE_POWER; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + if (usie_do_request(sc, &req, NULL)) { + mtx_unlock(&sc->sc_mtx); + goto detach; + } + /* read fw attr */ + fwattr = 0; + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = USIE_FW_ATTR; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(fwattr)); + if (usie_do_request(sc, &req, &fwattr)) { + mtx_unlock(&sc->sc_mtx); + goto detach; + } + mtx_unlock(&sc->sc_mtx); + + /* check DHCP supports */ + DPRINTF("fwattr=%x\n", fwattr); + if (!(fwattr & USIE_FW_DHCP)) { + device_printf(self, "DHCP is not supported. A firmware upgrade might be needed.\n"); + } + + /* find available interfaces */ + sc->sc_nucom = 0; + for (ifidx = 0; ifidx < USIE_IFACE_MAX; ifidx++) { + iface = usbd_get_iface(uaa->device, ifidx); + if (iface == NULL) + break; + + id = usbd_get_interface_descriptor(iface); + if ((id == NULL) || (id->bInterfaceClass != UICLASS_VENDOR)) + continue; + + /* setup Direct IP transfer */ + if (id->bInterfaceNumber >= 7 && id->bNumEndpoints == 3) { + sc->sc_if_ifnum = id->bInterfaceNumber; + iface_index = ifidx; + + DPRINTF("ifnum=%d, ifidx=%d\n", + sc->sc_if_ifnum, ifidx); + + err = usbd_transfer_setup(uaa->device, + &iface_index, sc->sc_if_xfer, usie_if_config, + USIE_IF_N_XFER, sc, &sc->sc_mtx); + + if (err == 0) + continue; + + device_printf(self, + "could not allocate USB transfers on " + "iface_index=%d, err=%s\n", + iface_index, usbd_errstr(err)); + goto detach; + } + + /* setup ucom */ + if (sc->sc_nucom >= USIE_UCOM_MAX) + continue; + + usbd_set_parent_iface(uaa->device, ifidx, + uaa->info.bIfaceIndex); + + DPRINTF("NumEndpoints=%d bInterfaceNumber=%d\n", + id->bNumEndpoints, id->bInterfaceNumber); + + if (id->bNumEndpoints == 2) { + sc->sc_uc_xfer[sc->sc_nucom][0] = NULL; + start = 1; + } else + start = 0; + + err = usbd_transfer_setup(uaa->device, &ifidx, + sc->sc_uc_xfer[sc->sc_nucom] + start, + usie_uc_config + start, USIE_UC_N_XFER - start, + &sc->sc_ucom[sc->sc_nucom], &sc->sc_mtx); + + if (err != 0) { + DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(err)); + continue; + } + + mtx_lock(&sc->sc_mtx); + for (; start < USIE_UC_N_XFER; start++) + usbd_xfer_set_stall(sc->sc_uc_xfer[sc->sc_nucom][start]); + mtx_unlock(&sc->sc_mtx); + + sc->sc_uc_ifnum[sc->sc_nucom] = id->bInterfaceNumber; + + sc->sc_nucom++; /* found a port */ + } + + if (sc->sc_nucom == 0) { + device_printf(self, "no comports found\n"); + goto detach; + } + + err = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, + sc->sc_nucom, sc, &usie_uc_callback, &sc->sc_mtx); + + if (err != 0) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + DPRINTF("Found %d interfaces.\n", sc->sc_nucom); + + /* setup ifnet (Direct IP) */ + sc->sc_ifp = ifp = if_alloc(IFT_OTHER); + + if (ifp == NULL) { + device_printf(self, "Could not allocate a network interface\n"); + goto detach; + } + if_initname(ifp, "usie", device_get_unit(self)); + + ifp->if_softc = sc; + ifp->if_mtu = USIE_MTU_MAX; + ifp->if_flags |= IFF_NOARP; + ifp->if_init = usie_if_init; + ifp->if_ioctl = usie_if_ioctl; + ifp->if_start = usie_if_start; + ifp->if_output = usie_if_output; + IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); + ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; + IFQ_SET_READY(&ifp->if_snd); + + if_attach(ifp); + bpfattach(ifp, DLT_RAW, 0); + + if (fwattr & USIE_PM_AUTO) { + usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE); + DPRINTF("enabling automatic suspend and resume\n"); + } else { + usbd_set_power_mode(uaa->device, USB_POWER_MODE_ON); + DPRINTF("USB power is always ON\n"); + } + + DPRINTF("device attached\n"); + return (0); + +detach: + usie_detach(self); + return (ENOMEM); +} + +static int +usie_detach(device_t self) +{ + struct usie_softc *sc = device_get_softc(self); + uint8_t x; + + /* detach ifnet */ + if (sc->sc_ifp != NULL) { + usie_if_stop(sc); + usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER); + bpfdetach(sc->sc_ifp); + if_detach(sc->sc_ifp); + if_free(sc->sc_ifp); + sc->sc_ifp = NULL; + } + /* detach ucom */ + if (sc->sc_nucom > 0) + ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); + + /* stop all USB transfers */ + usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER); + + for (x = 0; x != USIE_UCOM_MAX; x++) + usbd_transfer_unsetup(sc->sc_uc_xfer[x], USIE_UC_N_XFER); + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static void +usie_uc_update_line_state(struct ucom_softc *ucom, uint8_t ls) +{ + struct usie_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + if (sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS] == NULL) + return; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = USIE_LINK_STATE; + USETW(req.wValue, ls); + USETW(req.wIndex, sc->sc_uc_ifnum[ucom->sc_subunit]); + USETW(req.wLength, 0); + + DPRINTF("sc_uc_ifnum=%d\n", sc->sc_uc_ifnum[ucom->sc_subunit]); + + usie_do_request(sc, &req, NULL); +} + +static void +usie_uc_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct usie_softc *sc = ucom->sc_parent; + + *msr = sc->sc_msr; + *lsr = sc->sc_lsr; +} + +static void +usie_uc_cfg_set_dtr(struct ucom_softc *ucom, uint8_t flag) +{ + uint8_t dtr; + + dtr = flag ? USIE_LS_DTR : 0; + usie_uc_update_line_state(ucom, dtr); +} + +static void +usie_uc_cfg_set_rts(struct ucom_softc *ucom, uint8_t flag) +{ + uint8_t rts; + + rts = flag ? USIE_LS_RTS : 0; + usie_uc_update_line_state(ucom, rts); +} + +static void +usie_uc_cfg_open(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + /* usbd_transfer_start() is NULL safe */ + + usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]); +} + +static void +usie_uc_cfg_close(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]); +} + +static void +usie_uc_start_read(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]); +} + +static void +usie_uc_stop_read(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]); +} + +static void +usie_uc_start_write(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]); +} + +static void +usie_uc_stop_write(struct ucom_softc *ucom) +{ + struct usie_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]); +} + +static void +usie_uc_rx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucom_softc *ucom = usbd_xfer_softc(xfer); + struct usie_softc *sc = ucom->sc_parent; + struct usb_page_cache *pc; + uint32_t actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + + /* handle CnS response */ + if (ucom == sc->sc_ucom && actlen >= USIE_HIPCNS_MIN) { + + DPRINTF("transferred=%u\n", actlen); + + /* check if it is really CnS reply */ + usbd_copy_out(pc, 0, sc->sc_resp_temp, 1); + + if (sc->sc_resp_temp[0] == USIE_HIP_FRM_CHR) { + + /* verify actlen */ + if (actlen > USIE_BUFSIZE) + actlen = USIE_BUFSIZE; + + /* get complete message */ + usbd_copy_out(pc, 0, sc->sc_resp_temp, actlen); + usie_hip_rsp(sc, sc->sc_resp_temp, actlen); + + /* need to fall though */ + goto tr_setup; + } + /* else call ucom_put_data() */ + } + /* standard ucom transfer */ + ucom_put_data(ucom, pc, 0, actlen); + + /* fall though */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +usie_uc_tx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucom_softc *ucom = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + + /* handle CnS request */ + struct mbuf *m = usbd_xfer_get_priv(xfer); + + if (m != NULL) { + usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len); + usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len); + usbd_xfer_set_priv(xfer, NULL); + usbd_transfer_submit(xfer); + m_freem(m); + break; + } + /* standard ucom transfer */ + if (ucom_get_data(ucom, pc, 0, USIE_BUFSIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +usie_uc_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_page_cache *pc; + struct { + struct usb_device_request req; + uint16_t param; + } st; + uint32_t actlen; + uint16_t param; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(4, "info received, actlen=%u\n", actlen); + + if (actlen < sizeof(st)) { + DPRINTF("data too short actlen=%u\n", actlen); + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &st, sizeof(st)); + + if (st.req.bmRequestType == 0xa1 && st.req.bRequest == 0x20) { + struct ucom_softc *ucom = usbd_xfer_softc(xfer); + struct usie_softc *sc = ucom->sc_parent; + + param = le16toh(st.param); + DPRINTF("param=%x\n", param); + sc->sc_msr = sc->sc_lsr = 0; + sc->sc_msr |= (param & USIE_DCD) ? SER_DCD : 0; + sc->sc_msr |= (param & USIE_DSR) ? SER_DSR : 0; + sc->sc_msr |= (param & USIE_RI) ? SER_RI : 0; + sc->sc_msr |= (param & USIE_CTS) ? 0 : SER_CTS; + sc->sc_msr |= (param & USIE_RTS) ? SER_RTS : 0; + sc->sc_msr |= (param & USIE_DTR) ? SER_DTR : 0; + } + /* fall though */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF("USB transfer error, %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +usie_if_rx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usie_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m0; + struct mbuf *m = NULL; + struct usie_desc *rxd; + uint32_t actlen; + uint16_t err; + uint16_t pkt; + uint16_t ipl; + uint16_t len; + uint16_t diff; + uint8_t pad; + uint8_t ipv; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(15, "rx done, actlen=%u\n", actlen); + + if (actlen < sizeof(struct usie_hip)) { + DPRINTF("data too short %u\n", actlen); + goto tr_setup; + } + m = sc->sc_rxm; + sc->sc_rxm = NULL; + + /* fall though */ + case USB_ST_SETUP: +tr_setup: + + if (sc->sc_rxm == NULL) { + sc->sc_rxm = m_getjcl(M_DONTWAIT, MT_DATA, M_PKTHDR, + MJUMPAGESIZE /* could be bigger than MCLBYTES */ ); + } + if (sc->sc_rxm == NULL) { + DPRINTF("could not allocate Rx mbuf\n"); + ifp->if_ierrors++; + usbd_xfer_set_stall(xfer); + usbd_xfer_set_frames(xfer, 0); + } else { + /* + * Directly loading a mbuf cluster into DMA to + * save some data copying. This works because + * there is only one cluster. + */ + usbd_xfer_set_frame_data(xfer, 0, + mtod(sc->sc_rxm, caddr_t), MIN(MJUMPAGESIZE, USIE_RXSZ_MAX)); + usbd_xfer_set_frames(xfer, 1); + } + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF("USB transfer error, %s\n", usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + ifp->if_ierrors++; + goto tr_setup; + } + if (sc->sc_rxm != NULL) { + m_freem(sc->sc_rxm); + sc->sc_rxm = NULL; + } + break; + } + + if (m == NULL) + return; + + mtx_unlock(&sc->sc_mtx); + + m->m_pkthdr.len = m->m_len = actlen; + + err = pkt = 0; + + /* HW can aggregate multiple frames in a single USB xfer */ + for (;;) { + rxd = mtod(m, struct usie_desc *); + + len = be16toh(rxd->hip.len) & USIE_HIP_IP_LEN_MASK; + pad = (rxd->hip.id & USIE_HIP_PAD) ? 1 : 0; + ipl = (len - pad - ETHER_HDR_LEN); + if (ipl >= len) { + DPRINTF("Corrupt frame\n"); + m_freem(m); + break; + } + diff = sizeof(struct usie_desc) + ipl + pad; + + if (((rxd->hip.id & USIE_HIP_MASK) != USIE_HIP_IP) || + (be16toh(rxd->desc_type) & USIE_TYPE_MASK) != USIE_IP_RX) { + DPRINTF("received wrong type of packet\n"); + m->m_data += diff; + m->m_pkthdr.len = (m->m_len -= diff); + err++; + if (m->m_pkthdr.len > 0) + continue; + m_freem(m); + break; + } + switch (be16toh(rxd->ethhdr.ether_type)) { + case ETHERTYPE_IP: + ipv = NETISR_IP; + break; +#ifdef INET6 + case ETHERTYPE_IPV6: + ipv = NETISR_IPV6; + break; +#endif + default: + DPRINTF("unsupported ether type\n"); + err++; + break; + } + + /* the last packet */ + if (m->m_pkthdr.len <= diff) { + m->m_data += (sizeof(struct usie_desc) + pad); + m->m_pkthdr.len = m->m_len = ipl; + m->m_pkthdr.rcvif = ifp; + BPF_MTAP(sc->sc_ifp, m); + netisr_dispatch(ipv, m); + break; + } + /* copy aggregated frames to another mbuf */ + m0 = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (__predict_false(m0 == NULL)) { + DPRINTF("could not allocate mbuf\n"); + err++; + m_freem(m); + break; + } + m_copydata(m, sizeof(struct usie_desc) + pad, ipl, mtod(m0, caddr_t)); + m0->m_pkthdr.rcvif = ifp; + m0->m_pkthdr.len = m0->m_len = ipl; + + BPF_MTAP(sc->sc_ifp, m0); + netisr_dispatch(ipv, m0); + + m->m_data += diff; + m->m_pkthdr.len = (m->m_len -= diff); + } + + mtx_lock(&sc->sc_mtx); + + ifp->if_ierrors += err; + ifp->if_ipackets += pkt; +} + +static void +usie_if_tx_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usie_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + uint16_t size; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(11, "transfer complete\n"); + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_opackets++; + + /* fall though */ + case USB_ST_SETUP: +tr_setup: + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + break; + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + if (m->m_pkthdr.len > (MCLBYTES - ETHER_HDR_LEN + + ETHER_CRC_LEN - sizeof(sc->sc_txd))) { + DPRINTF("packet len is too big: %d\n", + m->m_pkthdr.len); + break; + } + pc = usbd_xfer_get_frame(xfer, 0); + + sc->sc_txd.hip.len = htobe16(m->m_pkthdr.len + + ETHER_HDR_LEN + ETHER_CRC_LEN); + size = sizeof(sc->sc_txd); + + usbd_copy_in(pc, 0, &sc->sc_txd, size); + usbd_m_copy_in(pc, size, m, 0, m->m_pkthdr.len); + usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len + + size + ETHER_CRC_LEN); + + BPF_MTAP(ifp, m); + + m_freem(m); + + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF("USB transfer error, %s\n", + usbd_errstr(error)); + ifp->if_oerrors++; + + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + ifp->if_ierrors++; + goto tr_setup; + } + break; + } +} + +static void +usie_if_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usie_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + struct usb_cdc_notification cdc; + uint32_t actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(4, "info received, actlen=%d\n", actlen); + + /* usb_cdc_notification - .data[16] */ + if (actlen < (sizeof(cdc) - 16)) { + DPRINTF("data too short %d\n", actlen); + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &cdc, (sizeof(cdc) - 16)); + + DPRINTFN(4, "bNotification=%x\n", cdc.bNotification); + + if (cdc.bNotification & UCDC_N_RESPONSE_AVAILABLE) { + taskqueue_enqueue(taskqueue_thread, + &sc->sc_if_status_task); + } + /* fall though */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF("USB transfer error, %s\n", + usbd_errstr(error)); + + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +usie_if_sync_to(void *arg) +{ + struct usie_softc *sc = arg; + + taskqueue_enqueue(taskqueue_thread, &sc->sc_if_sync_task); +} + +static void +usie_if_sync_cb(void *arg, int pending) +{ + struct usie_softc *sc = arg; + + mtx_lock(&sc->sc_mtx); + + /* call twice */ + usie_if_cmd(sc, USIE_HIP_SYNC2M); + usie_if_cmd(sc, USIE_HIP_SYNC2M); + + usb_callout_reset(&sc->sc_if_sync_ch, 2 * hz, usie_if_sync_to, sc); + + mtx_unlock(&sc->sc_mtx); +} + +static void +usie_if_status_cb(void *arg, int pending) +{ + struct usie_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct usb_device_request req; + struct usie_hip *hip; + struct usie_lsi *lsi; + uint16_t actlen; + uint8_t ntries; + uint8_t pad; + + mtx_lock(&sc->sc_mtx); + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_if_ifnum); + USETW(req.wLength, sizeof(sc->sc_status_temp)); + + for (ntries = 0; ntries != 10; ntries++) { + int err; + + err = usbd_do_request_flags(sc->sc_udev, + &sc->sc_mtx, &req, sc->sc_status_temp, USB_SHORT_XFER_OK, + &actlen, USB_DEFAULT_TIMEOUT); + + if (err == 0) + break; + + DPRINTF("Control request failed: %s %d/10\n", + usbd_errstr(err), ntries); + + usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10)); + } + + if (ntries == 10) { + mtx_unlock(&sc->sc_mtx); + DPRINTF("Timeout\n"); + return; + } + + hip = (struct usie_hip *)sc->sc_status_temp; + + pad = (hip->id & USIE_HIP_PAD) ? 1 : 0; + + DPRINTF("hip.id=%x hip.len=%d actlen=%u pad=%d\n", + hip->id, be16toh(hip->len), actlen, pad); + + switch (hip->id & USIE_HIP_MASK) { + case USIE_HIP_SYNC2H: + usie_if_cmd(sc, USIE_HIP_SYNC2M); + break; + case USIE_HIP_RESTR: + usb_callout_stop(&sc->sc_if_sync_ch); + break; + case USIE_HIP_UMTS: + lsi = (struct usie_lsi *)( + sc->sc_status_temp + sizeof(struct usie_hip) + pad); + + DPRINTF("lsi.proto=%x lsi.len=%d\n", lsi->proto, + be16toh(lsi->len)); + + if (lsi->proto != USIE_LSI_UMTS) + break; + + if (lsi->area == USIE_LSI_AREA_NO || + lsi->area == USIE_LSI_AREA_NODATA) { + device_printf(sc->sc_dev, "no service available\n"); + break; + } + if (lsi->state == USIE_LSI_STATE_IDLE) { + DPRINTF("lsi.state=%x\n", lsi->state); + break; + } + DPRINTF("ctx=%x\n", hip->param); + sc->sc_txd.hip.param = hip->param; + + sc->sc_net.addr_len = lsi->pdp_addr_len; + memcpy(&sc->sc_net.dns1_addr, &lsi->dns1_addr, 16); + memcpy(&sc->sc_net.dns2_addr, &lsi->dns2_addr, 16); + memcpy(sc->sc_net.pdp_addr, lsi->pdp_addr, 16); + memcpy(sc->sc_net.gw_addr, lsi->gw_addr, 16); + ifp->if_flags |= IFF_UP; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + device_printf(sc->sc_dev, "IP Addr=%d.%d.%d.%d\n", + *lsi->pdp_addr, *(lsi->pdp_addr + 1), + *(lsi->pdp_addr + 2), *(lsi->pdp_addr + 3)); + device_printf(sc->sc_dev, "Gateway Addr=%d.%d.%d.%d\n", + *lsi->gw_addr, *(lsi->gw_addr + 1), + *(lsi->gw_addr + 2), *(lsi->gw_addr + 3)); + device_printf(sc->sc_dev, "Prim NS Addr=%d.%d.%d.%d\n", + *lsi->dns1_addr, *(lsi->dns1_addr + 1), + *(lsi->dns1_addr + 2), *(lsi->dns1_addr + 3)); + device_printf(sc->sc_dev, "Scnd NS Addr=%d.%d.%d.%d\n", + *lsi->dns2_addr, *(lsi->dns2_addr + 1), + *(lsi->dns2_addr + 2), *(lsi->dns2_addr + 3)); + + usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI); + break; + + case USIE_HIP_RCGI: + /* ignore, workaround for sloppy windows */ + break; + default: + DPRINTF("undefined msgid: %x\n", hip->id); + break; + } + + mtx_unlock(&sc->sc_mtx); +} + +static void +usie_if_start(struct ifnet *ifp) +{ + struct usie_softc *sc = ifp->if_softc; + + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + DPRINTF("Not running\n"); + return; + } + mtx_lock(&sc->sc_mtx); + usbd_transfer_start(sc->sc_if_xfer[USIE_IF_TX]); + mtx_unlock(&sc->sc_mtx); + + DPRINTFN(3, "interface started\n"); +} + +static int +usie_if_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, + struct route *ro) +{ + int err; + + DPRINTF("proto=%x\n", dst->sa_family); + + switch (dst->sa_family) { +#ifdef INET6 + case AF_INET6; + /* fall though */ +#endif + case AF_INET: + break; + + /* silently drop dhclient packets */ + case AF_UNSPEC: + m_freem(m); + return (0); + + /* drop other packet types */ + default: + m_freem(m); + return (EAFNOSUPPORT); + } + + err = (ifp->if_transmit)(ifp, m); + if (err) { + ifp->if_oerrors++; + return (ENOBUFS); + } + ifp->if_opackets++; + + return (0); +} + +static void +usie_if_init(void *arg) +{ + struct usie_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + uint8_t i; + + mtx_lock(&sc->sc_mtx); + + /* write tx descriptor */ + sc->sc_txd.hip.id = USIE_HIP_CTX; + sc->sc_txd.hip.param = 0; /* init value */ + sc->sc_txd.desc_type = htobe16(USIE_IP_TX); + + for (i = 0; i != USIE_IF_N_XFER; i++) + usbd_xfer_set_stall(sc->sc_if_xfer[i]); + + usbd_transfer_start(sc->sc_uc_xfer[USIE_HIP_IF][USIE_UC_RX]); + usbd_transfer_start(sc->sc_if_xfer[USIE_IF_STATUS]); + usbd_transfer_start(sc->sc_if_xfer[USIE_IF_RX]); + + /* if not running, initiate the modem */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + usie_cns_req(sc, USIE_CNS_ID_INIT, USIE_CNS_OB_LINK_UPDATE); + + mtx_unlock(&sc->sc_mtx); + + DPRINTF("ifnet initialized\n"); +} + +static void +usie_if_stop(struct usie_softc *sc) +{ + usb_callout_drain(&sc->sc_if_sync_ch); + + mtx_lock(&sc->sc_mtx); + + /* usie_cns_req() clears IFF_* flags */ + usie_cns_req(sc, USIE_CNS_ID_STOP, USIE_CNS_OB_LINK_UPDATE); + + usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_TX]); + usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_RX]); + usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_STATUS]); + + /* shutdown device */ + usie_if_cmd(sc, USIE_HIP_DOWN); + + mtx_unlock(&sc->sc_mtx); +} + +static int +usie_if_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct usie_softc *sc = ifp->if_softc; + struct ieee80211req *ireq; + struct ieee80211req_sta_info si; + struct ifmediareq *ifmr; + + switch (cmd) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + usie_if_init(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + usie_if_stop(sc); + } + break; + + case SIOCSIFCAP: + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + device_printf(sc->sc_dev, + "Connect to the network first.\n"); + break; + } + mtx_lock(&sc->sc_mtx); + usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI); + mtx_unlock(&sc->sc_mtx); + break; + + case SIOCG80211: + ireq = (struct ieee80211req *)data; + + if (ireq->i_type != IEEE80211_IOC_STA_INFO) + break; + + memset(&si, 0, sizeof(si)); + si.isi_len = sizeof(si); + /* + * ifconfig expects RSSI in 0.5dBm units + * relative to the noise floor. + */ + si.isi_rssi = 2 * sc->sc_rssi; + if (copyout(&si, (uint8_t *)ireq->i_data + 8, + sizeof(struct ieee80211req_sta_info))) + DPRINTF("copyout failed\n"); + DPRINTF("80211\n"); + break; + + case SIOCGIFMEDIA: /* to fool ifconfig */ + ifmr = (struct ifmediareq *)data; + ifmr->ifm_count = 1; + DPRINTF("media\n"); + break; + + case SIOCSIFADDR: + case SIOCSIFDSTADDR: + break; + + default: + return (EINVAL); + } + return (0); +} + +static int +usie_do_request(struct usie_softc *sc, struct usb_device_request *req, + void *data) +{ + int err = 0; + int ntries; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + for (ntries = 0; ntries != 10; ntries++) { + err = usbd_do_request(sc->sc_udev, + &sc->sc_mtx, req, data); + if (err == 0) + break; + + DPRINTF("Control request failed: %s %d/10\n", + usbd_errstr(err), ntries); + + usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10)); + } + return (err); +} + +static int +usie_if_cmd(struct usie_softc *sc, uint8_t cmd) +{ + struct usb_device_request req; + struct usie_hip msg; + + msg.len = 0; + msg.id = cmd; + msg.param = 0; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_if_ifnum); + USETW(req.wLength, sizeof(msg)); + + DPRINTF("cmd=%x\n", cmd); + + return (usie_do_request(sc, &req, &msg)); +} + +static void +usie_cns_req(struct usie_softc *sc, uint32_t id, uint16_t obj) +{ + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + struct usb_xfer *xfer; + struct usie_hip *hip; + struct usie_cns *cns; + uint8_t *param; + uint8_t *tmp; + uint8_t cns_len; + + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (__predict_false(m == NULL)) { + DPRINTF("could not allocate mbuf\n"); + ifp->if_ierrors++; + return; + } + /* to align usie_hip{} on 32 bit */ + m->m_data += 3; + param = mtod(m, uint8_t *); + *param++ = USIE_HIP_FRM_CHR; + hip = (struct usie_hip *)param; + cns = (struct usie_cns *)(hip + 1); + + tmp = param + USIE_HIPCNS_MIN - 2; + + switch (obj) { + case USIE_CNS_OB_LINK_UPDATE: + cns_len = 2; + cns->op = USIE_CNS_OP_SET; + *tmp++ = 1; /* profile ID, always use 1 for now */ + *tmp++ = id == USIE_CNS_ID_INIT ? 1 : 0; + break; + + case USIE_CNS_OB_PROF_WRITE: + cns_len = 245; + cns->op = USIE_CNS_OP_SET; + *tmp++ = 1; /* profile ID, always use 1 for now */ + *tmp++ = 2; + memcpy(tmp, &sc->sc_net, 34); + memset(tmp + 35, 0, 245 - 36); + tmp += 243; + break; + + case USIE_CNS_OB_RSSI: + cns_len = 0; + cns->op = USIE_CNS_OP_REQ; + break; + + default: + DPRINTF("unsupported CnS object type\n"); + return; + } + *tmp = USIE_HIP_FRM_CHR; + + hip->len = htobe16(sizeof(struct usie_cns) + cns_len); + hip->id = USIE_HIP_CNS2M; + hip->param = 0; /* none for CnS */ + + cns->obj = htobe16(obj); + cns->id = htobe32(id); + cns->len = cns_len; + cns->rsv0 = cns->rsv1 = 0; /* always '0' */ + + param = (uint8_t *)(cns + 1); + + DPRINTF("param: %16D\n", param, ":"); + + m->m_pkthdr.len = m->m_len = USIE_HIPCNS_MIN + cns_len + 2; + + xfer = sc->sc_uc_xfer[USIE_HIP_IF][USIE_UC_TX]; + + if (usbd_xfer_get_priv(xfer) == NULL) { + usbd_xfer_set_priv(xfer, m); + usbd_transfer_start(xfer); + } else { + DPRINTF("Dropped CNS event\n"); + m_freem(m); + } +} + +static void +usie_cns_rsp(struct usie_softc *sc, struct usie_cns *cns) +{ + struct ifnet *ifp = sc->sc_ifp; + + DPRINTF("received CnS\n"); + + switch (be16toh(cns->obj)) { + case USIE_CNS_OB_LINK_UPDATE: + if (be32toh(cns->id) & USIE_CNS_ID_INIT) + usie_if_sync_to(sc); + else if (be32toh(cns->id) & USIE_CNS_ID_STOP) { + ifp->if_flags &= ~IFF_UP; + ifp->if_drv_flags &= + ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + } else + DPRINTF("undefined link update\n"); + break; + + case USIE_CNS_OB_RSSI: + sc->sc_rssi = be16toh(*(int16_t *)(cns + 1)); + if (sc->sc_rssi <= 0) + device_printf(sc->sc_dev, "No signal\n"); + else { + device_printf(sc->sc_dev, "RSSI=%ddBm\n", + sc->sc_rssi - 110); + } + break; + + case USIE_CNS_OB_PROF_WRITE: + break; + + case USIE_CNS_OB_PDP_READ: + break; + + default: + DPRINTF("undefined CnS\n"); + break; + } +} + +static void +usie_hip_rsp(struct usie_softc *sc, uint8_t *rsp, uint32_t len) +{ + struct usie_hip *hip; + struct usie_cns *cns; + uint32_t i; + uint32_t j; + uint32_t off; + uint8_t tmp[USIE_HIPCNS_MAX] __aligned(4); + + for (off = 0; (off + USIE_HIPCNS_MIN) <= len; off++) { + + uint8_t pad; + + while ((off < len) && (rsp[off] == USIE_HIP_FRM_CHR)) + off++; + + /* Unstuff the bytes */ + for (i = j = 0; ((i + off) < len) && + (j < USIE_HIPCNS_MAX); i++) { + + if (rsp[i + off] == USIE_HIP_FRM_CHR) + break; + + if (rsp[i + off] == USIE_HIP_ESC_CHR) { + if ((i + off + 1) >= len) + break; + tmp[j++] = rsp[i++ + off + 1] ^ 0x20; + } else { + tmp[j++] = rsp[i + off]; + } + } + + off += i; + + DPRINTF("frame len=%d\n", j); + + if (j < sizeof(struct usie_hip)) { + DPRINTF("too little data\n"); + break; + } + /* + * Make sure we are not reading the stack if something + * is wrong. + */ + memset(tmp + j, 0, sizeof(tmp) - j); + + hip = (struct usie_hip *)tmp; + + DPRINTF("hip: len=%d msgID=%02x, param=%02x\n", + be16toh(hip->len), hip->id, hip->param); + + pad = (hip->id & USIE_HIP_PAD) ? 1 : 0; + + if ((hip->id & USIE_HIP_MASK) == USIE_HIP_CNS2H) { + cns = (struct usie_cns *)(((uint8_t *)(hip + 1)) + pad); + + if (j < (sizeof(struct usie_cns) + + sizeof(struct usie_hip) + pad)) { + DPRINTF("too little data\n"); + break; + } + DPRINTF("cns: obj=%04x, op=%02x, rsv0=%02x, " + "app=%08x, rsv1=%02x, len=%d\n", + be16toh(cns->obj), cns->op, cns->rsv0, + be32toh(cns->id), cns->rsv1, cns->len); + + if (cns->op & USIE_CNS_OP_ERR) + DPRINTF("CnS error response\n"); + else + usie_cns_rsp(sc, cns); + + i = sizeof(struct usie_hip) + pad + sizeof(struct usie_cns); + j = cns->len; + } else { + i = sizeof(struct usie_hip) + pad; + j = be16toh(hip->len); + } +#ifdef USB_DEBUG + if (usie_debug == 0) + continue; + + while (i < USIE_HIPCNS_MAX && j > 0) { + DPRINTF("param[0x%02x] = 0x%02x\n", i, tmp[i]); + i++; + j--; + } +#endif + } +} + +static int +usie_driver_loaded(struct module *mod, int what, void *arg) +{ + switch (what) { + case MOD_LOAD: + /* register autoinstall handler */ + usie_etag = EVENTHANDLER_REGISTER(usb_dev_configured, + usie_autoinst, NULL, EVENTHANDLER_PRI_ANY); + break; + case MOD_UNLOAD: + EVENTHANDLER_DEREGISTER(usb_dev_configured, usie_etag); + break; + default: + return (EOPNOTSUPP); + } + return (0); +} + diff --git a/sys/dev/usb/net/if_usievar.h b/sys/dev/usb/net/if_usievar.h new file mode 100644 index 000000000000..9ba0dc8b12ac --- /dev/null +++ b/sys/dev/usb/net/if_usievar.h @@ -0,0 +1,256 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2011 Anybots Inc + * written by Akinori Furukoshi + * - ucom part is based on u3g.c + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +#ifndef _IF_USEVAR_H_ +#define _IF_USEVAR_H_ + +#define USIE_DCD 0x0001 +#define USIE_DSR 0x0002 +#define USIE_DTR 0x0004 +#define USIE_RI 0x0008 +#define USIE_CTS 0x0100 +#define USIE_RTS 0x0200 + +#define USIE_HIP_FRM_CHR 0x7e +#define USIE_HIP_ESC_CHR 0x7d +#define USIE_HIP_IF 0 + +#define USIE_HIPCNS_MIN 16 /* HIP + CnS + 2 framing char */ +#define USIE_HIPCNS_MAX 261 /* HIP + max CnS 255 + 2 framing char */ + +#define USIE_CNFG_INDEX 0 +#define USIE_IFACE_INDEX 0 +#define USIE_IFACE_MAX 12 +#define USIE_BUFSIZE 2048 +#define USIE_MTU_MAX 1500 +#define USIE_RXSZ_MAX 4096 + +/* USB control pipe request */ +#define USIE_POWER 0x00 +#define USIE_FW_ATTR 0x06 +#define USIE_NMEA 0x07 +#define USIE_LINK_STATE 0x22 + +/* firmware attr flags */ +#define USIE_PM_AUTO (1 << 1) +#define USIE_FW_DHCP (1 << 3) /* DHCP capable */ + +/* line state flags */ +#define USIE_LS_DTR (1 << 0) +#define USIE_LS_RTS (1 << 1) + +/* Host Interface Porotocol Header */ +struct usie_hip { + uint16_t len; +#define USIE_HIP_LEN_MASK 0x3fff +#define USIE_HIP_IP_LEN_MASK 0x07ff + + uint8_t id; +#define USIE_HIP_PAD (1 << 7) +#define USIE_HIP_MASK 0x7f +#define USIE_HIP_SYNC2M 0x20 /* host -> modem */ +#define USIE_HIP_DOWN 0x26 +#define USIE_HIP_CNS2M 0x2b /* h -> m */ +#define USIE_HIP_CTX 0x3f +#define USIE_HIP_SYNC2H 0x60 /* h <- m */ +#define USIE_HIP_RESTR 0x62 +#define USIE_HIP_RCGI 0x64 +#define USIE_HIP_CNS2H 0x6b /* h <- m */ +#define USIE_HIP_UMTS 0x78 +#define USIE_HIP_IP 0x7f + + uint8_t param; +} __packed __aligned(4); + +/* Control and Status Header */ +struct usie_cns { + uint16_t obj; /* object type */ +#define USIE_CNS_OB_RSSI 0x1001 /* read RSSI */ +#define USIE_CNS_OB_HW_DISABLE 0x1011 /* disable h/w */ +#define USIE_CNS_OB_PW_SW 0x1071 /* power on/off */ +#define USIE_CNS_OB_PROF_WRITE 0x7003 /* write profile */ +#define USIE_CNS_OB_LINK_UPDATE 0x7004 /* dis/connect */ +#define USIE_CNS_OB_PDP_READ 0x7006 /* read out IP addr */ + + uint8_t op; /* operation type */ +#define USIE_CNS_OP_ERR (1 << 7)/* | == error */ +#define USIE_CNS_OP_REQ 0x01 /* host -> modem */ +#define USIE_CNS_OP_RSP 0x02 /* h <- m */ +#define USIE_CNS_OP_SET 0x03 /* h -> m */ +#define USIE_CNS_OP_ACK 0x04 /* h <- m */ +#define USIE_CNS_OP_NOTIF_ON 0x05 /* h -> m */ +#define USIE_CNS_OP_RSP_ON 0x06 /* h <- m */ +#define USIE_CNS_OP_NOTIF 0x07 /* h <- m */ +#define USIE_CNS_OP_NOTIF_OFF 0x08 /* h -> m */ +#define USIE_CNS_OP_RSP_OFF 0x09 /* h <- m */ +#define USIE_CNS_OP_REQ_CHG 0x0a /* h -> m */ +#define USIE_CNS_OP_RSP_CHG 0x0b /* h <- m */ + + uint8_t rsv0; /* reserved, always '0' */ + uint32_t id; /* caller ID */ +/* + * .id is to identify calling functions + * h/w responses with the same .id used in request. Only '0' is reserved + * for notification (asynchronous message generated by h/w without any + * request). All other values are user defineable. + */ +#define USIE_CNS_ID_NOTIF 0x00000000 /* reserved */ +#define USIE_CNS_ID_INIT 0x00000001 +#define USIE_CNS_ID_STOP 0x00000002 +#define USIE_CNS_ID_DNS 0x00000003 +#define USIE_CNS_ID_RSSI 0x00000004 + + uint8_t rsv1; /* reserved, always '0' */ + uint8_t len; /* length of param */ +} __packed; + +/* + * CnS param attached to struct usie_cns + * usie_cns.len is total size of this param + * max 255 + */ +#define USIE_CNS_PM_UP 0x01 +#define USIE_CNS_PM_DOWN 0x00 + +/* Link Sense Indication data structure */ +struct usie_lsi { + uint8_t proto; +#define USIE_LSI_UMTS 0x01 + + uint8_t pad0; + uint16_t len; + uint8_t area; +#define USIE_LSI_AREA_NO 0x00 +#define USIE_LSI_AREA_NODATA 0x01 + + uint8_t pad1[41]; + uint8_t state; +#define USIE_LSI_STATE_IDLE 0x00 + + uint8_t pad2[33]; + uint8_t type; +#define USIE_LSI_IP4 0x00 + + uint8_t pdp_addr_len; /* PDP addr */ + uint8_t pdp_addr[16]; + uint8_t pad3[23]; + uint8_t dns1_addr_len; /* DNS addr */ + uint8_t dns1_addr[16]; + uint8_t dns2_addr_len; + uint8_t dns2_addr[16]; + uint8_t wins1_addr_len; /* Wins addr */ + uint8_t wins1_addr[16]; + uint8_t wins2_addr_len; + uint8_t wins2_addr[16]; + uint8_t pad4[4]; + uint8_t gw_addr_len; /* GW addr */ + uint8_t gw_addr[16]; + uint8_t rsv[8]; +} __packed; + +struct usie_net_info { + uint8_t addr_len; + uint8_t pdp_addr[16]; + uint8_t dns1_addr[16]; + uint8_t dns2_addr[16]; + uint8_t gw_addr[16]; +} __packed; + +/* Tx/Rx IP packet descriptor */ +struct usie_desc { + struct usie_hip hip; + uint16_t desc_type; +#define USIE_TYPE_MASK 0x03ff +#define USIE_IP_TX 0x0002 +#define USIE_IP_RX 0x0202 + + struct ether_header ethhdr; +} __packed; + +enum { + USIE_UC_STATUS, + USIE_UC_RX, + USIE_UC_TX, + USIE_UC_N_XFER +}; + +enum { + USIE_IF_STATUS, + USIE_IF_RX, + USIE_IF_TX, + USIE_IF_N_XFER +}; + +struct usie_softc { + struct ucom_super_softc sc_super_ucom; + +#define USIE_UCOM_MAX 6 + struct ucom_softc sc_ucom[USIE_UCOM_MAX]; + uint8_t sc_uc_ifnum[USIE_UCOM_MAX]; + + struct mtx sc_mtx; + + struct task sc_if_status_task; + struct task sc_if_sync_task; + struct usb_callout sc_if_sync_ch; + + struct usie_net_info sc_net; + + struct usie_desc sc_txd; + + struct usb_xfer *sc_uc_xfer[USIE_UCOM_MAX][USIE_UC_N_XFER]; + struct usb_xfer *sc_if_xfer[USIE_IF_N_XFER]; + + struct ifnet *sc_ifp; + struct usb_device *sc_udev; + device_t sc_dev; + + struct mbuf *sc_rxm; + + uint16_t sc_if_ifnum; + + int16_t sc_rssi; + + uint8_t sc_msr; + uint8_t sc_lsr; + uint8_t sc_nucom; + + uint8_t sc_resp_temp[USIE_BUFSIZE] __aligned(4); + uint8_t sc_status_temp[USIE_BUFSIZE] __aligned(4); +}; + +/* Some code assumptions */ + +extern uint8_t usie_assert[((sizeof(struct usie_hip) + + sizeof(struct usie_lsi) + 1) <= USIE_BUFSIZE) ? 1 : -1]; + +extern uint8_t ucdc_assert[(sizeof(struct usb_cdc_notification) + >= 16) ? 1 : -1]; + +#endif /* _IF_USEVAR_H_ */ diff --git a/sys/dev/usb/usbdevs b/sys/dev/usb/usbdevs index aed83e11ef2d..496586ecabc8 100644 --- a/sys/dev/usb/usbdevs +++ b/sys/dev/usb/usbdevs @@ -900,6 +900,7 @@ product AIRPLUS MCD650 0x3198 MCD650 modem /* AirPrime products */ product AIRPRIME PC5220 0x0112 CDMA Wireless PC Card +product AIRPRIME USB308 0x68A3 USB308 HSPA+ USB Modem /* AirTies products */ product AIRTIES RT3070 0x2310 RT3070 diff --git a/sys/modules/usb/Makefile b/sys/modules/usb/Makefile index 6288d66235e4..abcfcbebc257 100644 --- a/sys/modules/usb/Makefile +++ b/sys/modules/usb/Makefile @@ -27,7 +27,7 @@ SUBDIR = usb SUBDIR += ehci musb ohci uhci xhci uss820dci ${_at91dci} ${_atmegadci} -SUBDIR += rum run uath upgt ural zyd ${_urtw} +SUBDIR += rum run uath upgt usie ural zyd ${_urtw} SUBDIR += atp uhid ukbd ums udbp ufm uep SUBDIR += ucom u3g uark ubsa ubser uchcom ucycom ufoma uftdi ugensa uipaq ulpt \ umct umcs umodem umoscom uplcom uslcom uvisor uvscom diff --git a/sys/modules/usb/usie/Makefile b/sys/modules/usb/usie/Makefile new file mode 100644 index 000000000000..f7d7c07da280 --- /dev/null +++ b/sys/modules/usb/usie/Makefile @@ -0,0 +1,35 @@ +# +# $FreeBSD$ +# +# Copyright (c) 2011 Hans Petter Selasky. All rights reserved. +# +# 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 AUTHOR 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 AUTHOR 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. +# + +.PATH: ${.CURDIR}/../../../dev/usb/net + +KMOD = usie +SRCS = if_usie.c +SRCS += opt_bus.h opt_usb.h device_if.h bus_if.h \ + usb_if.h usbdevs.h opt_inet.h + +.include