Index: head/sys/dev/usb/net/if_urndis.c =================================================================== --- head/sys/dev/usb/net/if_urndis.c (revision 275789) +++ head/sys/dev/usb/net/if_urndis.c (revision 275790) @@ -1,1016 +1,1016 @@ /* $OpenBSD: if_urndis.c,v 1.46 2013/12/09 15:45:29 pirofti Exp $ */ /* * Copyright (c) 2010 Jonathan Armani * Copyright (c) 2010 Fabien Romano * Copyright (c) 2010 Michael Knudsen * Copyright (c) 2014 Hans Petter Selasky * All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #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 "usbdevs.h" #define USB_DEBUG_VAR urndis_debug #include #include #include "usb_if.h" #include #include #include static device_probe_t urndis_probe; static device_attach_t urndis_attach; static device_detach_t urndis_detach; static device_suspend_t urndis_suspend; static device_resume_t urndis_resume; static usb_callback_t urndis_bulk_write_callback; static usb_callback_t urndis_bulk_read_callback; static usb_callback_t urndis_intr_read_callback; static uether_fn_t urndis_attach_post; static uether_fn_t urndis_init; static uether_fn_t urndis_stop; static uether_fn_t urndis_start; static uether_fn_t urndis_setmulti; static uether_fn_t urndis_setpromisc; static uint32_t urndis_ctrl_query(struct urndis_softc *, uint32_t, const void **, uint16_t *); static uint32_t urndis_ctrl_set(struct urndis_softc *, uint32_t, struct urndis_set_req *, uint16_t); static uint32_t urndis_ctrl_handle_init(struct urndis_softc *, const struct urndis_comp_hdr *); static uint32_t urndis_ctrl_handle_query(struct urndis_softc *, const struct urndis_comp_hdr *, const void **, uint16_t *); static uint32_t urndis_ctrl_handle_reset(struct urndis_softc *, const struct urndis_comp_hdr *); static uint32_t urndis_ctrl_init(struct urndis_softc *); #ifdef USB_DEBUG static int urndis_debug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, urndis, CTLFLAG_RW, 0, "USB RNDIS-Ethernet"); SYSCTL_INT(_hw_usb_urndis, OID_AUTO, debug, CTLFLAG_RW, &urndis_debug, 0, "Debug level"); #endif static const struct usb_config urndis_config[URNDIS_N_TRANSFER] = { [URNDIS_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 0, .frames = 1, .bufsize = RNDIS_RX_MAXLEN, .flags = {.short_xfer_ok = 1,}, .callback = urndis_bulk_read_callback, .timeout = 0, /* no timeout */ .usb_mode = USB_MODE_HOST, }, [URNDIS_BULK_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 0, .frames = RNDIS_TX_FRAMES_MAX, .bufsize = (RNDIS_TX_FRAMES_MAX * RNDIS_TX_MAXLEN), .flags = { .force_short_xfer = 1, }, .callback = urndis_bulk_write_callback, .timeout = 10000, /* 10 seconds */ .usb_mode = USB_MODE_HOST, }, [URNDIS_INTR_RX] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 1, .bufsize = 0, /* use wMaxPacketSize */ .flags = {.short_xfer_ok = 1,.no_pipe_ok = 1,}, .callback = urndis_intr_read_callback, .timeout = 0, .usb_mode = USB_MODE_HOST, }, }; static device_method_t urndis_methods[] = { /* Device interface */ DEVMETHOD(device_probe, urndis_probe), DEVMETHOD(device_attach, urndis_attach), DEVMETHOD(device_detach, urndis_detach), DEVMETHOD(device_suspend, urndis_suspend), DEVMETHOD(device_resume, urndis_resume), DEVMETHOD_END }; static driver_t urndis_driver = { .name = "urndis", .methods = urndis_methods, .size = sizeof(struct urndis_softc), }; static devclass_t urndis_devclass; DRIVER_MODULE(urndis, uhub, urndis_driver, urndis_devclass, NULL, 0); MODULE_VERSION(urndis, 1); MODULE_DEPEND(urndis, uether, 1, 1, 1); MODULE_DEPEND(urndis, usb, 1, 1, 1); MODULE_DEPEND(urndis, ether, 1, 1, 1); static const struct usb_ether_methods urndis_ue_methods = { .ue_attach_post = urndis_attach_post, .ue_start = urndis_start, .ue_init = urndis_init, .ue_stop = urndis_stop, .ue_setmulti = urndis_setmulti, .ue_setpromisc = urndis_setpromisc, }; static const STRUCT_USB_HOST_ID urndis_host_devs[] = { -#if 0 - /* XXX this entry has a conflict an entry the umodem driver XXX */ - {USB_IFACE_CLASS(UICLASS_CDC), USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), - USB_IFACE_PROTOCOL(0xff)}, -#endif + /* Generic RNDIS class match */ {USB_IFACE_CLASS(UICLASS_WIRELESS), USB_IFACE_SUBCLASS(UISUBCLASS_RF), - USB_IFACE_PROTOCOL(UIPROTO_RNDIS)}, + USB_IFACE_PROTOCOL(UIPROTO_RNDIS)}, {USB_IFACE_CLASS(UICLASS_IAD), USB_IFACE_SUBCLASS(UISUBCLASS_SYNC), - USB_IFACE_PROTOCOL(UIPROTO_ACTIVESYNC)}, + USB_IFACE_PROTOCOL(UIPROTO_ACTIVESYNC)}, + /* HP-WebOS */ + {USB_VENDOR(USB_VENDOR_PALM), USB_IFACE_CLASS(UICLASS_CDC), + USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), + USB_IFACE_PROTOCOL(0xff)}, }; static int urndis_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); return (usbd_lookup_id_by_uaa(urndis_host_devs, sizeof(urndis_host_devs), uaa)); } static void urndis_attach_post(struct usb_ether *ue) { /* no-op */ return; } static int urndis_attach(device_t dev) { struct urndis_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; struct usb_attach_arg *uaa = device_get_ivars(dev); struct usb_cdc_cm_descriptor *cmd; struct { struct urndis_set_req hdr; uint32_t filter; } msg_filter; const void *buf; uint16_t bufsz; uint8_t iface_index[2] = { uaa->info.bIfaceIndex + 1, uaa->info.bIfaceIndex }; int error; uint8_t i; sc->sc_ue.ue_udev = uaa->device; sc->sc_ifaceno_ctl = uaa->info.bIfaceNum; cmd = usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex, UDESC_CS_INTERFACE, 0xFF, UDESCSUB_CDC_CM, 0xFF); if (cmd != 0) { DPRINTF("Call Mode Descriptor found, dataif=%d\n", cmd->bDataInterface); iface_index[0] = cmd->bDataInterface; } device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); /* scan the alternate settings looking for a valid one */ for (i = 0; i != 32; i++) { error = usbd_set_alt_interface_index(uaa->device, iface_index[0], i); if (error != 0) break; error = usbd_transfer_setup(uaa->device, iface_index, sc->sc_xfer, urndis_config, URNDIS_N_TRANSFER, sc, &sc->sc_mtx); if (error == 0) break; } if ((error != 0) || (i == 32)) { device_printf(dev, "No valid alternate " "setting found\n"); goto detach; } URNDIS_LOCK(sc); error = urndis_ctrl_query(sc, OID_802_3_PERMANENT_ADDRESS, &buf, &bufsz); URNDIS_UNLOCK(sc); if (error != (int)RNDIS_STATUS_SUCCESS) { device_printf(dev, "Unable to get hardware address\n"); goto detach; } if (bufsz != ETHER_ADDR_LEN) { device_printf(dev, "Invalid address length: %d bytes\n", bufsz); goto detach; } memcpy(&sc->sc_ue.ue_eaddr, buf, ETHER_ADDR_LEN); /* Initialize packet filter */ sc->sc_filter = RNDIS_PACKET_TYPE_BROADCAST | RNDIS_PACKET_TYPE_ALL_MULTICAST; msg_filter.filter = htole32(sc->sc_filter); URNDIS_LOCK(sc); error = urndis_ctrl_set(sc, OID_GEN_CURRENT_PACKET_FILTER, &msg_filter.hdr, sizeof(msg_filter)); URNDIS_UNLOCK(sc); if (error != (int)RNDIS_STATUS_SUCCESS) { device_printf(dev, "Unable to set data filters\n"); goto detach; } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &urndis_ue_methods; error = uether_ifattach(ue); if (error) { device_printf(dev, "Could not attach interface\n"); goto detach; } URNDIS_LOCK(sc); /* start interrupt endpoint, if any */ usbd_transfer_start(sc->sc_xfer[URNDIS_INTR_RX]); URNDIS_UNLOCK(sc); return (0); /* success */ detach: urndis_detach(dev); return (ENXIO); /* failure */ } static int urndis_detach(device_t dev) { struct urndis_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; /* stop all USB transfers first */ usbd_transfer_unsetup(sc->sc_xfer, URNDIS_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); return (0); } static void urndis_start(struct usb_ether *ue) { struct urndis_softc *sc = uether_getsc(ue); /* * Start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[URNDIS_BULK_TX]); usbd_transfer_start(sc->sc_xfer[URNDIS_BULK_RX]); } static void urndis_init(struct usb_ether *ue) { struct urndis_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); URNDIS_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags |= IFF_DRV_RUNNING; urndis_ctrl_init(sc); /* stall data write direction, which depends on USB mode */ usbd_xfer_set_stall(sc->sc_xfer[URNDIS_BULK_TX]); /* start data transfers */ urndis_start(ue); } static void urndis_stop(struct usb_ether *ue) { struct urndis_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); URNDIS_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[URNDIS_BULK_RX]); usbd_transfer_stop(sc->sc_xfer[URNDIS_BULK_TX]); } static void urndis_setmulti(struct usb_ether *ue) { /* no-op */ return; } static void urndis_setpromisc(struct usb_ether *ue) { /* no-op */ return; } static int urndis_suspend(device_t dev) { device_printf(dev, "Suspending\n"); return (0); } static int urndis_resume(device_t dev) { device_printf(dev, "Resuming\n"); return (0); } static usb_error_t urndis_ctrl_msg(struct urndis_softc *sc, uint8_t rt, uint8_t r, uint16_t index, uint16_t value, void *buf, uint16_t buflen) { usb_device_request_t req; req.bmRequestType = rt; req.bRequest = r; USETW(req.wValue, value); USETW(req.wIndex, index); USETW(req.wLength, buflen); return (usbd_do_request_flags(sc->sc_ue.ue_udev, &sc->sc_mtx, &req, buf, (rt & UT_READ) ? USB_SHORT_XFER_OK : 0, NULL, 2000 /* ms */ )); } static usb_error_t urndis_ctrl_send(struct urndis_softc *sc, void *buf, uint16_t len) { usb_error_t err; err = urndis_ctrl_msg(sc, UT_WRITE_CLASS_INTERFACE, UR_GET_STATUS, sc->sc_ifaceno_ctl, 0, buf, len); DPRINTF("%s\n", usbd_errstr(err)); return (err); } static struct urndis_comp_hdr * urndis_ctrl_recv(struct urndis_softc *sc) { struct urndis_comp_hdr *hdr; usb_error_t err; err = urndis_ctrl_msg(sc, UT_READ_CLASS_INTERFACE, UR_CLEAR_FEATURE, sc->sc_ifaceno_ctl, 0, sc->sc_response_buf, RNDIS_RESPONSE_LEN); if (err != USB_ERR_NORMAL_COMPLETION) return (NULL); hdr = (struct urndis_comp_hdr *)sc->sc_response_buf; DPRINTF("type 0x%x len %u\n", le32toh(hdr->rm_type), le32toh(hdr->rm_len)); if (le32toh(hdr->rm_len) > RNDIS_RESPONSE_LEN) { DPRINTF("ctrl message error: wrong size %u > %u\n", le32toh(hdr->rm_len), RNDIS_RESPONSE_LEN); return (NULL); } return (hdr); } static uint32_t urndis_ctrl_handle(struct urndis_softc *sc, struct urndis_comp_hdr *hdr, const void **buf, uint16_t *bufsz) { uint32_t rval; DPRINTF("\n"); if (buf != NULL && bufsz != NULL) { *buf = NULL; *bufsz = 0; } switch (le32toh(hdr->rm_type)) { case REMOTE_NDIS_INITIALIZE_CMPLT: rval = urndis_ctrl_handle_init(sc, hdr); break; case REMOTE_NDIS_QUERY_CMPLT: rval = urndis_ctrl_handle_query(sc, hdr, buf, bufsz); break; case REMOTE_NDIS_RESET_CMPLT: rval = urndis_ctrl_handle_reset(sc, hdr); break; case REMOTE_NDIS_KEEPALIVE_CMPLT: case REMOTE_NDIS_SET_CMPLT: rval = le32toh(hdr->rm_status); break; default: DPRINTF("ctrl message error: unknown event 0x%x\n", le32toh(hdr->rm_type)); rval = RNDIS_STATUS_FAILURE; break; } return (rval); } static uint32_t urndis_ctrl_handle_init(struct urndis_softc *sc, const struct urndis_comp_hdr *hdr) { const struct urndis_init_comp *msg; msg = (const struct urndis_init_comp *)hdr; DPRINTF("len %u rid %u status 0x%x " "ver_major %u ver_minor %u devflags 0x%x medium 0x%x pktmaxcnt %u " "pktmaxsz %u align %u aflistoffset %u aflistsz %u\n", le32toh(msg->rm_len), le32toh(msg->rm_rid), le32toh(msg->rm_status), le32toh(msg->rm_ver_major), le32toh(msg->rm_ver_minor), le32toh(msg->rm_devflags), le32toh(msg->rm_medium), le32toh(msg->rm_pktmaxcnt), le32toh(msg->rm_pktmaxsz), le32toh(msg->rm_align), le32toh(msg->rm_aflistoffset), le32toh(msg->rm_aflistsz)); if (le32toh(msg->rm_status) != RNDIS_STATUS_SUCCESS) { DPRINTF("init failed 0x%x\n", le32toh(msg->rm_status)); return (le32toh(msg->rm_status)); } if (le32toh(msg->rm_devflags) != RNDIS_DF_CONNECTIONLESS) { DPRINTF("wrong device type (current type: 0x%x)\n", le32toh(msg->rm_devflags)); return (RNDIS_STATUS_FAILURE); } if (le32toh(msg->rm_medium) != RNDIS_MEDIUM_802_3) { DPRINTF("medium not 802.3 (current medium: 0x%x)\n", le32toh(msg->rm_medium)); return (RNDIS_STATUS_FAILURE); } sc->sc_lim_pktsz = le32toh(msg->rm_pktmaxsz); return (le32toh(msg->rm_status)); } static uint32_t urndis_ctrl_handle_query(struct urndis_softc *sc, const struct urndis_comp_hdr *hdr, const void **buf, uint16_t *bufsz) { const struct urndis_query_comp *msg; uint64_t limit; msg = (const struct urndis_query_comp *)hdr; DPRINTF("len %u rid %u status 0x%x " "buflen %u bufoff %u\n", le32toh(msg->rm_len), le32toh(msg->rm_rid), le32toh(msg->rm_status), le32toh(msg->rm_infobuflen), le32toh(msg->rm_infobufoffset)); if (buf != NULL && bufsz != NULL) { *buf = NULL; *bufsz = 0; } if (le32toh(msg->rm_status) != RNDIS_STATUS_SUCCESS) { DPRINTF("query failed 0x%x\n", le32toh(msg->rm_status)); return (le32toh(msg->rm_status)); } limit = le32toh(msg->rm_infobuflen); limit += le32toh(msg->rm_infobufoffset); limit += RNDIS_HEADER_OFFSET; if (limit > (uint64_t)le32toh(msg->rm_len)) { DPRINTF("ctrl message error: invalid query info " "len/offset/end_position(%u/%u/%u) -> " "go out of buffer limit %u\n", le32toh(msg->rm_infobuflen), le32toh(msg->rm_infobufoffset), le32toh(msg->rm_infobuflen) + le32toh(msg->rm_infobufoffset) + RNDIS_HEADER_OFFSET, le32toh(msg->rm_len)); return (RNDIS_STATUS_FAILURE); } if (buf != NULL && bufsz != NULL) { *buf = ((const uint8_t *)msg) + RNDIS_HEADER_OFFSET + le32toh(msg->rm_infobufoffset); *bufsz = le32toh(msg->rm_infobuflen); } return (le32toh(msg->rm_status)); } static uint32_t urndis_ctrl_handle_reset(struct urndis_softc *sc, const struct urndis_comp_hdr *hdr) { const struct urndis_reset_comp *msg; uint32_t rval; msg = (const struct urndis_reset_comp *)hdr; rval = le32toh(msg->rm_status); DPRINTF("len %u status 0x%x " "adrreset %u\n", le32toh(msg->rm_len), rval, le32toh(msg->rm_adrreset)); if (rval != RNDIS_STATUS_SUCCESS) { DPRINTF("reset failed 0x%x\n", rval); return (rval); } if (msg->rm_adrreset != 0) { struct { struct urndis_set_req hdr; uint32_t filter; } msg_filter; msg_filter.filter = htole32(sc->sc_filter); rval = urndis_ctrl_set(sc, OID_GEN_CURRENT_PACKET_FILTER, &msg_filter.hdr, sizeof(msg_filter)); if (rval != RNDIS_STATUS_SUCCESS) { DPRINTF("unable to reset data filters\n"); return (rval); } } return (rval); } static uint32_t urndis_ctrl_init(struct urndis_softc *sc) { struct urndis_init_req msg; struct urndis_comp_hdr *hdr; uint32_t rval; msg.rm_type = htole32(REMOTE_NDIS_INITIALIZE_MSG); msg.rm_len = htole32(sizeof(msg)); msg.rm_rid = htole32(0); msg.rm_ver_major = htole32(1); msg.rm_ver_minor = htole32(1); msg.rm_max_xfersz = htole32(RNDIS_RX_MAXLEN); DPRINTF("type %u len %u rid %u ver_major %u " "ver_minor %u max_xfersz %u\n", le32toh(msg.rm_type), le32toh(msg.rm_len), le32toh(msg.rm_rid), le32toh(msg.rm_ver_major), le32toh(msg.rm_ver_minor), le32toh(msg.rm_max_xfersz)); rval = urndis_ctrl_send(sc, &msg, sizeof(msg)); if (rval != RNDIS_STATUS_SUCCESS) { DPRINTF("init failed\n"); return (rval); } if ((hdr = urndis_ctrl_recv(sc)) == NULL) { DPRINTF("unable to get init response\n"); return (RNDIS_STATUS_FAILURE); } rval = urndis_ctrl_handle(sc, hdr, NULL, NULL); return (rval); } #if 0 static uint32_t urndis_ctrl_halt(struct urndis_softc *sc) { struct urndis_halt_req msg; uint32_t rval; msg.rm_type = htole32(REMOTE_NDIS_HALT_MSG); msg.rm_len = htole32(sizeof(msg)); msg.rm_rid = 0; DPRINTF("type %u len %u rid %u\n", le32toh(msg.rm_type), le32toh(msg.rm_len), le32toh(msg.rm_rid)); rval = urndis_ctrl_send(sc, &msg, sizeof(msg)); if (rval != RNDIS_STATUS_SUCCESS) printf("halt failed\n"); return (rval); } #endif static uint32_t urndis_ctrl_query(struct urndis_softc *sc, uint32_t oid, const void **rbuf, uint16_t *rbufsz) { struct urndis_query_req msg; uint32_t rval; struct urndis_comp_hdr *hdr; msg.rm_type = htole32(REMOTE_NDIS_QUERY_MSG); msg.rm_len = htole32(sizeof(msg)); msg.rm_rid = 0; /* XXX */ msg.rm_oid = htole32(oid); msg.rm_infobuflen = htole32(0); msg.rm_infobufoffset = 0; msg.rm_devicevchdl = 0; DPRINTF("type %u len %u rid %u oid 0x%x " "infobuflen %u infobufoffset %u devicevchdl %u\n", le32toh(msg.rm_type), le32toh(msg.rm_len), le32toh(msg.rm_rid), le32toh(msg.rm_oid), le32toh(msg.rm_infobuflen), le32toh(msg.rm_infobufoffset), le32toh(msg.rm_devicevchdl)); rval = urndis_ctrl_send(sc, &msg, sizeof(msg)); if (rval != RNDIS_STATUS_SUCCESS) { DPRINTF("query failed\n"); return (rval); } if ((hdr = urndis_ctrl_recv(sc)) == NULL) { DPRINTF("unable to get query response\n"); return (RNDIS_STATUS_FAILURE); } rval = urndis_ctrl_handle(sc, hdr, rbuf, rbufsz); return (rval); } static uint32_t urndis_ctrl_set(struct urndis_softc *sc, uint32_t oid, struct urndis_set_req *msg, uint16_t len) { struct urndis_comp_hdr *hdr; uint32_t rval; uint32_t datalen = len - sizeof(*msg); msg->rm_type = htole32(REMOTE_NDIS_SET_MSG); msg->rm_len = htole32(len); msg->rm_rid = 0; /* XXX */ msg->rm_oid = htole32(oid); msg->rm_infobuflen = htole32(datalen); if (datalen != 0) { msg->rm_infobufoffset = htole32(sizeof(*msg) - RNDIS_HEADER_OFFSET); } else { msg->rm_infobufoffset = 0; } msg->rm_devicevchdl = 0; DPRINTF("type %u len %u rid %u oid 0x%x " "infobuflen %u infobufoffset %u devicevchdl %u\n", le32toh(msg->rm_type), le32toh(msg->rm_len), le32toh(msg->rm_rid), le32toh(msg->rm_oid), le32toh(msg->rm_infobuflen), le32toh(msg->rm_infobufoffset), le32toh(msg->rm_devicevchdl)); rval = urndis_ctrl_send(sc, msg, len); if (rval != RNDIS_STATUS_SUCCESS) { DPRINTF("set failed\n"); return (rval); } if ((hdr = urndis_ctrl_recv(sc)) == NULL) { DPRINTF("unable to get set response\n"); return (RNDIS_STATUS_FAILURE); } rval = urndis_ctrl_handle(sc, hdr, NULL, NULL); if (rval != RNDIS_STATUS_SUCCESS) DPRINTF("set failed 0x%x\n", rval); return (rval); } static void urndis_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct urndis_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, 0); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct urndis_packet_msg msg; struct mbuf *m; int actlen; int aframes; int offset; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTFN(1, "received %u bytes in %u frames\n", actlen, aframes); for (offset = 0; actlen >= (uint32_t)sizeof(msg);) { /* copy out header */ usbd_copy_out(pc, offset, &msg, sizeof(msg)); if (le32toh(0x1234567U) != 0x1234567U) { /* swap endianness */ msg.rm_type = le32toh(msg.rm_type); msg.rm_len = le32toh(msg.rm_len); msg.rm_dataoffset = le32toh(msg.rm_dataoffset); msg.rm_datalen = le32toh(msg.rm_datalen); msg.rm_oobdataoffset = le32toh(msg.rm_oobdataoffset); msg.rm_oobdatalen = le32toh(msg.rm_oobdatalen); msg.rm_oobdataelements = le32toh(msg.rm_oobdataelements); msg.rm_pktinfooffset = le32toh(msg.rm_pktinfooffset); msg.rm_pktinfolen = le32toh(msg.rm_pktinfolen); msg.rm_vchandle = le32toh(msg.rm_vchandle); msg.rm_reserved = le32toh(msg.rm_reserved); } DPRINTF("len %u data(off:%u len:%u) " "oobdata(off:%u len:%u nb:%u) perpacket(off:%u len:%u)\n", msg.rm_len, msg.rm_dataoffset, msg.rm_datalen, msg.rm_oobdataoffset, msg.rm_oobdatalen, msg.rm_oobdataelements, msg.rm_pktinfooffset, msg.rm_pktinfooffset); /* sanity check the RNDIS header */ if (msg.rm_type != REMOTE_NDIS_PACKET_MSG) { DPRINTF("invalid type 0x%x != 0x%x\n", msg.rm_type, REMOTE_NDIS_PACKET_MSG); goto tr_setup; } else if (msg.rm_len < (uint32_t)sizeof(msg)) { DPRINTF("invalid msg len %u < %u\n", msg.rm_len, (unsigned)sizeof(msg)); goto tr_setup; } else if (msg.rm_len > (uint32_t)actlen) { DPRINTF("invalid msg len %u > buffer " "len %u\n", msg.rm_len, actlen); goto tr_setup; } else if (msg.rm_dataoffset >= (uint32_t)actlen) { DPRINTF("invalid msg dataoffset %u > buffer " "dataoffset %u\n", msg.rm_dataoffset, actlen); goto tr_setup; } else if (msg.rm_datalen > (uint32_t)actlen) { DPRINTF("invalid msg datalen %u > buffer " "datalen %u\n", msg.rm_datalen, actlen); goto tr_setup; } else if ((msg.rm_dataoffset + msg.rm_datalen + (uint32_t)__offsetof(struct urndis_packet_msg, rm_dataoffset)) > (uint32_t)actlen) { DPRINTF("invalid dataoffset %u larger than %u\n", msg.rm_dataoffset + msg.rm_datalen + (uint32_t)__offsetof(struct urndis_packet_msg, rm_dataoffset), actlen); goto tr_setup; } else if (msg.rm_datalen < (uint32_t)sizeof(struct ether_header)) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); DPRINTF("invalid ethernet size " "%u < %u\n", msg.rm_datalen, (unsigned)sizeof(struct ether_header)); goto tr_setup; } else if (msg.rm_datalen > (uint32_t)MCLBYTES) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); DPRINTF("invalid ethernet size " "%u > %u\n", msg.rm_datalen, (unsigned)MCLBYTES); goto tr_setup; } else if (msg.rm_datalen > (uint32_t)(MHLEN - ETHER_ALIGN)) { m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); } else { m = m_gethdr(M_NOWAIT, MT_DATA); } /* check if we have a buffer */ if (m != NULL) { m_adj(m, ETHER_ALIGN); usbd_copy_out(pc, offset + msg.rm_dataoffset + __offsetof(struct urndis_packet_msg, rm_dataoffset), m->m_data, msg.rm_datalen); /* enqueue */ uether_rxmbuf(&sc->sc_ue, m, msg.rm_datalen); } else { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); } offset += msg.rm_len; actlen -= msg.rm_len; } case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, RNDIS_RX_MAXLEN); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); uether_rxflush(&sc->sc_ue); /* must be last */ break; default: /* Error */ DPRINTFN(1, "error = %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); usbd_xfer_set_frames(xfer, 0); usbd_transfer_submit(xfer); } break; } } static void urndis_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct urndis_packet_msg msg; struct urndis_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct mbuf *m; unsigned x; int actlen; int aframes; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTFN(1, "\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "%u bytes in %u frames\n", actlen, aframes); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: memset(&msg, 0, sizeof(msg)); for (x = 0; x != RNDIS_TX_FRAMES_MAX; x++) { struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, x); usbd_xfer_set_frame_offset(xfer, x * RNDIS_TX_MAXLEN, x); next_pkt: IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; if ((m->m_pkthdr.len + sizeof(msg)) > RNDIS_TX_MAXLEN) { DPRINTF("Too big packet\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); /* Free buffer */ m_freem(m); goto next_pkt; } msg.rm_type = htole32(REMOTE_NDIS_PACKET_MSG); msg.rm_len = htole32(sizeof(msg) + m->m_pkthdr.len); msg.rm_dataoffset = htole32(RNDIS_DATA_OFFSET); msg.rm_datalen = htole32(m->m_pkthdr.len); /* copy in all data */ usbd_copy_in(pc, 0, &msg, sizeof(msg)); usbd_m_copy_in(pc, sizeof(msg), m, 0, m->m_pkthdr.len); usbd_xfer_set_frame_len(xfer, x, sizeof(msg) + m->m_pkthdr.len); /* * If there's a BPF listener, bounce a copy of * this frame to him: */ BPF_MTAP(ifp, m); /* Free buffer */ m_freem(m); } if (x != 0) { usbd_xfer_set_frames(xfer, x); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); /* count output errors */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void urndis_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) { int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("Received %d bytes\n", actlen); /* TODO: decode some indications */ /* FALLTHROUGH */ 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) { /* start clear stall */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } Index: head/sys/dev/usb/serial/umodem.c =================================================================== --- head/sys/dev/usb/serial/umodem.c (revision 275789) +++ head/sys/dev/usb/serial/umodem.c (revision 275790) @@ -1,908 +1,908 @@ /* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */ #include __FBSDID("$FreeBSD$"); /*- * Copyright (c) 2003, M. Warner Losh . * 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. */ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf * http://www.usb.org/developers/devclass_docs/cdc_wmc10.zip */ /* * TODO: * - Add error recovery in various places; the big problem is what * to do in a callback if there is an error. * - Implement a Call Device for modems without multiplexed commands. * */ #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" #include #define USB_DEBUG_VAR umodem_debug #include #include #include #include #ifdef USB_DEBUG static int umodem_debug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, umodem, CTLFLAG_RW, 0, "USB umodem"); SYSCTL_INT(_hw_usb_umodem, OID_AUTO, debug, CTLFLAG_RW, &umodem_debug, 0, "Debug level"); #endif static const STRUCT_USB_HOST_ID umodem_devs[] = { /* Generic Modem class match */ {USB_IFACE_CLASS(UICLASS_CDC), USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), USB_IFACE_PROTOCOL(UIPROTO_CDC_AT)}, {USB_IFACE_CLASS(UICLASS_CDC), USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), USB_IFACE_PROTOCOL(UIPROTO_CDC_NONE)}, /* Huawei Modem class match */ - {USB_IFACE_CLASS(UICLASS_CDC), + {USB_VENDOR(USB_VENDOR_HUAWEI),USB_IFACE_CLASS(UICLASS_CDC), USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), USB_IFACE_PROTOCOL(0xFF)}, /* Kyocera AH-K3001V */ {USB_VPI(USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_AHK3001V, 1)}, {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, 1)}, {USB_VPI(USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_PC5740, 1)}, }; /* * As speeds for umodem devices increase, these numbers will need to * be increased. They should be good for G3 speeds and below. * * TODO: The TTY buffers should be increased! */ #define UMODEM_BUF_SIZE 1024 enum { UMODEM_BULK_WR, UMODEM_BULK_RD, UMODEM_INTR_RD, UMODEM_N_TRANSFER, }; #define UMODEM_MODVER 1 /* module version */ struct umodem_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_xfer *sc_xfer[UMODEM_N_TRANSFER]; struct usb_device *sc_udev; struct mtx sc_mtx; uint16_t sc_line; uint8_t sc_lsr; /* local status register */ uint8_t sc_msr; /* modem status register */ uint8_t sc_ctrl_iface_no; uint8_t sc_data_iface_no; uint8_t sc_iface_index[2]; uint8_t sc_cm_over_data; uint8_t sc_cm_cap; /* CM capabilities */ uint8_t sc_acm_cap; /* ACM capabilities */ }; static device_probe_t umodem_probe; static device_attach_t umodem_attach; static device_detach_t umodem_detach; static void umodem_free_softc(struct umodem_softc *); static usb_callback_t umodem_intr_callback; static usb_callback_t umodem_write_callback; static usb_callback_t umodem_read_callback; static void umodem_free(struct ucom_softc *); static void umodem_start_read(struct ucom_softc *); static void umodem_stop_read(struct ucom_softc *); static void umodem_start_write(struct ucom_softc *); static void umodem_stop_write(struct ucom_softc *); static void umodem_get_caps(struct usb_attach_arg *, uint8_t *, uint8_t *); static void umodem_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static int umodem_pre_param(struct ucom_softc *, struct termios *); static void umodem_cfg_param(struct ucom_softc *, struct termios *); static int umodem_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, struct thread *); static void umodem_cfg_set_dtr(struct ucom_softc *, uint8_t); static void umodem_cfg_set_rts(struct ucom_softc *, uint8_t); static void umodem_cfg_set_break(struct ucom_softc *, uint8_t); static void *umodem_get_desc(struct usb_attach_arg *, uint8_t, uint8_t); static usb_error_t umodem_set_comm_feature(struct usb_device *, uint8_t, uint16_t, uint16_t); static void umodem_poll(struct ucom_softc *ucom); static void umodem_find_data_iface(struct usb_attach_arg *uaa, uint8_t, uint8_t *, uint8_t *); static const struct usb_config umodem_config[UMODEM_N_TRANSFER] = { [UMODEM_BULK_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .if_index = 0, .bufsize = UMODEM_BUF_SIZE, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = &umodem_write_callback, }, [UMODEM_BULK_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .if_index = 0, .bufsize = UMODEM_BUF_SIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &umodem_read_callback, }, [UMODEM_INTR_RD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .if_index = 1, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &umodem_intr_callback, }, }; static const struct ucom_callback umodem_callback = { .ucom_cfg_get_status = &umodem_cfg_get_status, .ucom_cfg_set_dtr = &umodem_cfg_set_dtr, .ucom_cfg_set_rts = &umodem_cfg_set_rts, .ucom_cfg_set_break = &umodem_cfg_set_break, .ucom_cfg_param = &umodem_cfg_param, .ucom_pre_param = &umodem_pre_param, .ucom_ioctl = &umodem_ioctl, .ucom_start_read = &umodem_start_read, .ucom_stop_read = &umodem_stop_read, .ucom_start_write = &umodem_start_write, .ucom_stop_write = &umodem_stop_write, .ucom_poll = &umodem_poll, .ucom_free = &umodem_free, }; static device_method_t umodem_methods[] = { DEVMETHOD(device_probe, umodem_probe), DEVMETHOD(device_attach, umodem_attach), DEVMETHOD(device_detach, umodem_detach), DEVMETHOD_END }; static devclass_t umodem_devclass; static driver_t umodem_driver = { .name = "umodem", .methods = umodem_methods, .size = sizeof(struct umodem_softc), }; DRIVER_MODULE(umodem, uhub, umodem_driver, umodem_devclass, NULL, 0); MODULE_DEPEND(umodem, ucom, 1, 1, 1); MODULE_DEPEND(umodem, usb, 1, 1, 1); MODULE_VERSION(umodem, UMODEM_MODVER); static int umodem_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); int error; DPRINTFN(11, "\n"); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); error = usbd_lookup_id_by_uaa(umodem_devs, sizeof(umodem_devs), uaa); if (error) return (error); return (BUS_PROBE_GENERIC); } static int umodem_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct umodem_softc *sc = device_get_softc(dev); struct usb_cdc_cm_descriptor *cmd; struct usb_cdc_union_descriptor *cud; uint8_t i; int error; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "umodem", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); sc->sc_ctrl_iface_no = uaa->info.bIfaceNum; sc->sc_iface_index[1] = uaa->info.bIfaceIndex; sc->sc_udev = uaa->device; umodem_get_caps(uaa, &sc->sc_cm_cap, &sc->sc_acm_cap); /* get the data interface number */ cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) { cud = usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex, UDESC_CS_INTERFACE, 0xFF, UDESCSUB_CDC_UNION, 0xFF); if ((cud == NULL) || (cud->bLength < sizeof(*cud))) { DPRINTF("Missing descriptor. " "Assuming data interface is next.\n"); if (sc->sc_ctrl_iface_no == 0xFF) { goto detach; } else { uint8_t class_match = 0; /* set default interface number */ sc->sc_data_iface_no = 0xFF; /* try to find the data interface backwards */ umodem_find_data_iface(uaa, uaa->info.bIfaceIndex - 1, &sc->sc_data_iface_no, &class_match); /* try to find the data interface forwards */ umodem_find_data_iface(uaa, uaa->info.bIfaceIndex + 1, &sc->sc_data_iface_no, &class_match); /* check if nothing was found */ if (sc->sc_data_iface_no == 0xFF) goto detach; } } else { sc->sc_data_iface_no = cud->bSlaveInterface[0]; } } else { sc->sc_data_iface_no = cmd->bDataInterface; } device_printf(dev, "data interface %d, has %sCM over " "data, has %sbreak\n", sc->sc_data_iface_no, sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ", sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no "); /* get the data interface too */ for (i = 0;; i++) { struct usb_interface *iface; struct usb_interface_descriptor *id; iface = usbd_get_iface(uaa->device, i); if (iface) { id = usbd_get_interface_descriptor(iface); if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) { sc->sc_iface_index[0] = i; usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); break; } } else { device_printf(dev, "no data interface\n"); goto detach; } } if (usb_test_quirk(uaa, UQ_ASSUME_CM_OVER_DATA)) { sc->sc_cm_over_data = 1; } else { if (sc->sc_cm_cap & USB_CDC_CM_OVER_DATA) { if (sc->sc_acm_cap & USB_CDC_ACM_HAS_FEATURE) { error = umodem_set_comm_feature (uaa->device, sc->sc_ctrl_iface_no, UCDC_ABSTRACT_STATE, UCDC_DATA_MULTIPLEXED); /* ignore any errors */ } sc->sc_cm_over_data = 1; } } error = usbd_transfer_setup(uaa->device, sc->sc_iface_index, sc->sc_xfer, umodem_config, UMODEM_N_TRANSFER, sc, &sc->sc_mtx); if (error) { goto detach; } /* clear stall at first run */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_WR]); usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_RD]); mtx_unlock(&sc->sc_mtx); error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &umodem_callback, &sc->sc_mtx); if (error) { goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); detach: umodem_detach(dev); return (ENXIO); } static void umodem_find_data_iface(struct usb_attach_arg *uaa, uint8_t iface_index, uint8_t *p_data_no, uint8_t *p_match_class) { struct usb_interface_descriptor *id; struct usb_interface *iface; iface = usbd_get_iface(uaa->device, iface_index); /* check for end of interfaces */ if (iface == NULL) return; id = usbd_get_interface_descriptor(iface); /* check for non-matching interface class */ if (id->bInterfaceClass != UICLASS_CDC_DATA || id->bInterfaceSubClass != UISUBCLASS_DATA) { /* if we got a class match then return */ if (*p_match_class) return; } else { *p_match_class = 1; } DPRINTFN(11, "Match at index %u\n", iface_index); *p_data_no = id->bInterfaceNumber; } static void umodem_start_read(struct ucom_softc *ucom) { struct umodem_softc *sc = ucom->sc_parent; /* start interrupt endpoint, if any */ usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_RD]); /* start read endpoint */ usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_RD]); } static void umodem_stop_read(struct ucom_softc *ucom) { struct umodem_softc *sc = ucom->sc_parent; /* stop interrupt endpoint, if any */ usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_RD]); /* stop read endpoint */ usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_RD]); } static void umodem_start_write(struct ucom_softc *ucom) { struct umodem_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_WR]); } static void umodem_stop_write(struct ucom_softc *ucom) { struct umodem_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_WR]); } static void umodem_get_caps(struct usb_attach_arg *uaa, uint8_t *cm, uint8_t *acm) { struct usb_cdc_cm_descriptor *cmd; struct usb_cdc_acm_descriptor *cad; cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) { DPRINTF("no CM desc (faking one)\n"); *cm = USB_CDC_CM_DOES_CM | USB_CDC_CM_OVER_DATA; } else *cm = cmd->bmCapabilities; cad = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM); if ((cad == NULL) || (cad->bLength < sizeof(*cad))) { DPRINTF("no ACM desc\n"); *acm = 0; } else *acm = cad->bmCapabilities; } static void umodem_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct umodem_softc *sc = ucom->sc_parent; DPRINTF("\n"); *lsr = sc->sc_lsr; *msr = sc->sc_msr; } static int umodem_pre_param(struct ucom_softc *ucom, struct termios *t) { return (0); /* we accept anything */ } static void umodem_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct umodem_softc *sc = ucom->sc_parent; struct usb_cdc_line_state ls; struct usb_device_request req; DPRINTF("sc=%p\n", sc); memset(&ls, 0, sizeof(ls)); USETDW(ls.dwDTERate, t->c_ospeed); ls.bCharFormat = (t->c_cflag & CSTOPB) ? UCDC_STOP_BIT_2 : UCDC_STOP_BIT_1; ls.bParityType = (t->c_cflag & PARENB) ? ((t->c_cflag & PARODD) ? UCDC_PARITY_ODD : UCDC_PARITY_EVEN) : UCDC_PARITY_NONE; switch (t->c_cflag & CSIZE) { case CS5: ls.bDataBits = 5; break; case CS6: ls.bDataBits = 6; break; case CS7: ls.bDataBits = 7; break; case CS8: ls.bDataBits = 8; break; } DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n", UGETDW(ls.dwDTERate), ls.bCharFormat, ls.bParityType, ls.bDataBits); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_LINE_CODING; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_ctrl_iface_no; req.wIndex[1] = 0; USETW(req.wLength, sizeof(ls)); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, &ls, 0, 1000); } static int umodem_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, int flag, struct thread *td) { struct umodem_softc *sc = ucom->sc_parent; int error = 0; DPRINTF("cmd=0x%08x\n", cmd); switch (cmd) { case USB_GET_CM_OVER_DATA: *(int *)data = sc->sc_cm_over_data; break; case USB_SET_CM_OVER_DATA: if (*(int *)data != sc->sc_cm_over_data) { /* XXX change it */ } break; default: DPRINTF("unknown\n"); error = ENOIOCTL; break; } return (error); } static void umodem_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct umodem_softc *sc = ucom->sc_parent; struct usb_device_request req; DPRINTF("onoff=%d\n", onoff); if (onoff) sc->sc_line |= UCDC_LINE_DTR; else sc->sc_line &= ~UCDC_LINE_DTR; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_CONTROL_LINE_STATE; USETW(req.wValue, sc->sc_line); req.wIndex[0] = sc->sc_ctrl_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void umodem_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct umodem_softc *sc = ucom->sc_parent; struct usb_device_request req; DPRINTF("onoff=%d\n", onoff); if (onoff) sc->sc_line |= UCDC_LINE_RTS; else sc->sc_line &= ~UCDC_LINE_RTS; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_CONTROL_LINE_STATE; USETW(req.wValue, sc->sc_line); req.wIndex[0] = sc->sc_ctrl_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void umodem_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct umodem_softc *sc = ucom->sc_parent; struct usb_device_request req; uint16_t temp; DPRINTF("onoff=%d\n", onoff); if (sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK) { temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SEND_BREAK; USETW(req.wValue, temp); req.wIndex[0] = sc->sc_ctrl_iface_no; req.wIndex[1] = 0; USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } } static void umodem_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_cdc_notification pkt; struct umodem_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint16_t wLen; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (actlen < 8) { DPRINTF("received short packet, " "%d bytes\n", actlen); goto tr_setup; } if (actlen > (int)sizeof(pkt)) { DPRINTF("truncating message\n"); actlen = sizeof(pkt); } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, &pkt, actlen); actlen -= 8; wLen = UGETW(pkt.wLength); if (actlen > wLen) { actlen = wLen; } if (pkt.bmRequestType != UCDC_NOTIFICATION) { DPRINTF("unknown message type, " "0x%02x, on notify pipe!\n", pkt.bmRequestType); goto tr_setup; } switch (pkt.bNotification) { case UCDC_N_SERIAL_STATE: /* * Set the serial state in ucom driver based on * the bits from the notify message */ if (actlen < 2) { DPRINTF("invalid notification " "length, %d bytes!\n", actlen); break; } DPRINTF("notify bytes = %02x%02x\n", pkt.data[0], pkt.data[1]); /* Currently, lsr is always zero. */ sc->sc_lsr = 0; sc->sc_msr = 0; if (pkt.data[0] & UCDC_N_SERIAL_RI) { sc->sc_msr |= SER_RI; } if (pkt.data[0] & UCDC_N_SERIAL_DSR) { sc->sc_msr |= SER_DSR; } if (pkt.data[0] & UCDC_N_SERIAL_DCD) { sc->sc_msr |= SER_DCD; } ucom_status_change(&sc->sc_ucom); break; default: DPRINTF("unknown notify message: 0x%02x\n", pkt.bNotification); break; } 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 umodem_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct umodem_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: tr_setup: pc = usbd_xfer_get_frame(xfer, 0); if (ucom_get_data(&sc->sc_ucom, pc, 0, UMODEM_BUF_SIZE, &actlen)) { usbd_xfer_set_frame_len(xfer, 0, actlen); 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 umodem_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct umodem_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("actlen=%d\n", actlen); pc = usbd_xfer_get_frame(xfer, 0); ucom_put_data(&sc->sc_ucom, pc, 0, actlen); 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 * umodem_get_desc(struct usb_attach_arg *uaa, uint8_t type, uint8_t subtype) { return (usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex, type, 0xFF, subtype, 0xFF)); } static usb_error_t umodem_set_comm_feature(struct usb_device *udev, uint8_t iface_no, uint16_t feature, uint16_t state) { struct usb_device_request req; struct usb_cdc_abstract_state ast; DPRINTF("feature=%d state=%d\n", feature, state); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_SET_COMM_FEATURE; USETW(req.wValue, feature); req.wIndex[0] = iface_no; req.wIndex[1] = 0; USETW(req.wLength, UCDC_ABSTRACT_STATE_LENGTH); USETW(ast.wState, state); return (usbd_do_request(udev, NULL, &req, &ast)); } static int umodem_detach(device_t dev) { struct umodem_softc *sc = device_get_softc(dev); DPRINTF("sc=%p\n", sc); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UMODEM_N_TRANSFER); device_claim_softc(dev); umodem_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(umodem); static void umodem_free_softc(struct umodem_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void umodem_free(struct ucom_softc *ucom) { umodem_free_softc(ucom->sc_parent); } static void umodem_poll(struct ucom_softc *ucom) { struct umodem_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, UMODEM_N_TRANSFER); }