From: "Jan Schreiber" Subject: USB network control mode driver uncm(4) To: Date: Mon, 05 Oct 2026 18:38:11 +0000 Hi, I noticed newer Android devices (since Android 6 if I'm not mistaken) do not use urndis(4) for USB tethering any more. They now use CDC NCM which is missing a driver in OpenBSD. Below is my first go at it. I moved some defines and structs out of mbim.h into usbcdc.h to not include everything for mobile broadband. (No functional change for umb(4)). uncm(4) is based on umb(4). I tested the diff with my Google Pixel 9 Pro running GrapheneOS on amd64. If this is the right direction I'm happy to send a manpage in the next run. Known limitations: - Only the first NDP in an NTB is processed (wNextNdpIndex is not followed). Same as umb(4); my device sends one NDP per NTB. I have no device that sends more, so I left it for now. - The NETWORK_CONNECTION notification is handled, but the Pixel never sends one, so the link state stays "unknown" with this device and that code path is untested. (Again I do not have a device that sends the notification.) - One RX transfer in flight, like umb(4). I tested it and reached 320Mbps via fast.com - max speed at my location is 333Mbps and the phone was connected via wifi. The mac address part took a while and looks a bit weird with the 0x0409 value, but it needs a valid language value that is recognized in the Linux gadget stack (0x0409 is basically en-us). If this diff is too big to review I can split it up into two parts, the headers and the driver in itself (plus the manpage down the line). Jan diff --git sys/arch/amd64/conf/GENERIC sys/arch/amd64/conf/GENERIC index 24dcce2008d..b669fbe8e2c 100644 --- sys/arch/amd64/conf/GENERIC +++ sys/arch/amd64/conf/GENERIC @@ -324,6 +324,7 @@ kue* at uhub? # Kawasaki KL5KUSB101B based Ethernet smsc* at uhub? # SMSC LAN95xx Ethernet cdce* at uhub? # CDC Ethernet urndis* at uhub? # Remote NDIS Ethernet +uncm* at uhub? # Network Controll Model upl* at uhub? # Prolific PL2301/PL2302 host-to-host `network' ugl* at uhub? # Genesys Logic GL620USB-A host-to-host `network' udav* at uhub? # Davicom DM9601 based Ethernet diff --git sys/dev/usb/files.usb sys/dev/usb/files.usb index 88fff1b7c0f..84be1628818 100644 --- sys/dev/usb/files.usb +++ sys/dev/usb/files.usb @@ -260,6 +260,11 @@ device urndis: ether, ifnet, ifmedia attach urndis at uhub file dev/usb/if_urndis.c urndis +# NCM Ethernet +device uncm: ether, ifnet, ifmedia +attach uncm at uhub +file dev/usb/if_uncm.c uncm + # Moschip MCS7x30 Ethernet device mos: ether, ifnet, mii, ifmedia attach mos at uhub diff --git sys/dev/usb/if_uncm.c sys/dev/usb/if_uncm.c new file mode 100644 index 00000000000..8be9fdf7589 --- /dev/null +++ sys/dev/usb/if_uncm.c @@ -0,0 +1,1226 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2016 genua mbH + * Copyright (c) 2026 Jan Schreiber + * 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. + */ + +/* + * Based on if_umb.c + */ + +/* + * USB CDC NCM 1.0 specification: + * https://www.usb.org/sites/default/files/NCM10_012011.zip + */ + +#include "bpfilter.h" + +#include +#include +#include +#include +#include +#include + +#if NBPFILTER > 0 +#include +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef UNCM_DEBUG +#define DPRINTF(x...) \ + do { if (uncm_debug) log(LOG_DEBUG, x); } while (0) +#define DPRINTFN(n, x...) \ + do { if (uncm_debug >= (n)) log(LOG_DEBUG, x); } while (0) +int uncm_debug = 0; +#else +#define DPRINTF(x...) do { } while (0) +#define DPRINTFN(n, x...) do { } while (0) +#endif + +#define DEVNAM(sc) (((struct uncm_softc *)(sc))->sc_dev.dv_xname) + +struct uncm_softc { + struct device sc_dev; + struct arpcom sc_ac; +#define GET_IFP(sc) (&(sc)->sc_ac.ac_if) + struct usbd_device *sc_udev; + + uint32_t sc_maxpktlen; + unsigned int sc_ncm_supported_formats; + int sc_ncm_format; + + int sc_maxdgram; + int sc_align; + int sc_ndp_div; + int sc_ndp_remainder; + + uint8_t sc_ctrl_ifaceno; + struct usbd_interface *sc_ctrl_iface; + struct usbd_pipe *sc_ctrl_pipe; + struct usb_cdc_notification sc_intr_msg; + struct usbd_interface *sc_data_iface; + + int sc_data_ifaceidx; + + int sc_rx_ep; + struct usbd_xfer *sc_rx_xfer; + void *sc_rx_buf; + int sc_rx_bufsz; + struct usbd_pipe *sc_rx_pipe; + unsigned int sc_rx_nerr; + + int sc_tx_ep; + struct usbd_xfer *sc_tx_xfer; + void *sc_tx_buf; + int sc_tx_bufsz; + struct usbd_pipe *sc_tx_pipe; + struct mbuf_list sc_tx_ml; + uint32_t sc_tx_seq; + + struct usb_task sc_link_task; + int sc_link; + uint64_t sc_baudrate; +}; + +#define UNCM_RX_NTB_MAX 16384 + +int uncm_match(struct device *, void *, void *); +void uncm_attach(struct device *, struct device *, void *); +int uncm_detach(struct device *, int); +void uncm_ntb_setup(struct uncm_softc *); +void uncm_ntb_setup_format(struct uncm_softc *); +void uncm_ntb_setup_input_size(struct uncm_softc *); +void uncm_get_eaddr(struct uncm_softc *, int); +int uncm_alloc_xfers(struct uncm_softc *); +void uncm_free_xfers(struct uncm_softc *); +int uncm_ioctl(struct ifnet *, u_long, caddr_t); +void uncm_init(struct uncm_softc *); +void uncm_stop(struct uncm_softc *); +void uncm_start(struct ifnet *); +void uncm_watchdog(struct ifnet *); +void uncm_rx(struct uncm_softc *); +void uncm_rxeof(struct usbd_xfer *, void *, usbd_status); +int uncm_encap(struct uncm_softc *, int); +void uncm_txeof(struct usbd_xfer *, void *, usbd_status); +void uncm_decap(struct uncm_softc *, struct usbd_xfer *); +void uncm_intr(struct usbd_xfer *, void *, usbd_status); +void uncm_link_task(void *); + +int uncm_xfer_tout = USBD_DEFAULT_TIMEOUT; + +struct cfdriver uncm_cd = { + NULL, "uncm", DV_IFNET +}; + +const struct cfattach uncm_ca = { + sizeof(struct uncm_softc), + uncm_match, + uncm_attach, + uncm_detach, + NULL, +}; + +int +uncm_match(struct device *parent, void *match, void *aux) +{ + struct usb_attach_arg *uaa = aux; + usb_interface_descriptor_t *id; + + if (uaa->iface == NULL) + return UMATCH_NONE; + + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) + return UMATCH_NONE; + + if (id->bInterfaceClass == UICLASS_CDC && + id->bInterfaceSubClass == UISUBCLASS_NETWORK_CONTROL_MODEL) + return UMATCH_IFACECLASS_IFACESUBCLASS; + + return UMATCH_NONE; +} + +void +uncm_attach(struct device *parent, struct device *self, void *aux) +{ + struct uncm_softc *sc = (struct uncm_softc *)self; + struct usb_attach_arg *uaa = aux; + usbd_status status; + struct usbd_desc_iter iter; + const usb_descriptor_t *desc; + struct usb_cdc_union_descriptor *ud; + struct usb_cdc_ethernet_descriptor *ed_eth; + struct usb_cdc_ncm_descriptor *nd; + usb_interface_descriptor_t *id; + usb_config_descriptor_t *cd; + usb_endpoint_descriptor_t *ed; + usb_interface_assoc_descriptor_t *ad; + struct ifnet *ifp; + int current_ifaceno = -1; + int data_ifaceno = -1; + int mac_idx = -1; + int ctrl_ep; + int altnum, i, s; + uint32_t maxpktlen; + + sc->sc_udev = uaa->device; + sc->sc_ctrl_iface = uaa->iface; + sc->sc_ctrl_ifaceno = uaa->ifaceno; + sc->sc_maxpktlen = ETHER_MAX_LEN - ETHER_CRC_LEN; + sc->sc_link = LINK_STATE_UNKNOWN; + ml_init(&sc->sc_tx_ml); + usb_init_task(&sc->sc_link_task, uncm_link_task, sc, + USB_TASK_TYPE_GENERIC); + usbd_desc_iter_init(sc->sc_udev, &iter); + while ((desc = usbd_desc_iter_next(&iter))) { + if (desc->bDescriptorType == UDESC_IFACE_ASSOC) { + ad = (usb_interface_assoc_descriptor_t *)desc; + if (ad->bFirstInterface == uaa->ifaceno && + ad->bInterfaceCount > 1) { + /* Fall back to the interface following control. */ + data_ifaceno = uaa->ifaceno + 1; + } + continue; + } + if (desc->bDescriptorType == UDESC_INTERFACE) { + id = (usb_interface_descriptor_t *)desc; + current_ifaceno = id->bInterfaceNumber; + continue; + } + if (current_ifaceno != uaa->ifaceno) + continue; + if (desc->bDescriptorType != UDESC_CS_INTERFACE) + continue; + switch (desc->bDescriptorSubtype) { + case UDESCSUB_CDC_UNION: + if (desc->bLength < sizeof(*ud)) + break; + ud = (struct usb_cdc_union_descriptor *)desc; + data_ifaceno = ud->bSlaveInterface[0]; + break; + case UDESCSUB_CDC_ENF: + if (desc->bLength < sizeof(*ed_eth)) + break; + ed_eth = (struct usb_cdc_ethernet_descriptor *)desc; + mac_idx = ed_eth->iMacAddress; + maxpktlen = UGETW(ed_eth->wMaxSegmentSize); + if (maxpktlen >= ETHER_MIN_LEN && + maxpktlen <= ETHER_MAX_LEN - ETHER_CRC_LEN) + sc->sc_maxpktlen = maxpktlen; + break; + case UDESCSUB_CDC_NCM: + if (desc->bLength < sizeof(*nd)) + break; + nd = (struct usb_cdc_ncm_descriptor *)desc; + DPRINTFN(2, "%s: NCM version %x.%x, caps 0x%02x\n", + DEVNAM(sc), UGETW(nd->bcdNcmVersion) >> 8, + UGETW(nd->bcdNcmVersion) & 0xff, + nd->bmNetworkCapabilities); + break; + default: + break; + } + } + if (data_ifaceno == -1) { + printf("%s: no data interface number\n", DEVNAM(sc)); + goto fail; + } + + for (i = 0; i < uaa->nifaces; i++) { + if (usbd_iface_claimed(sc->sc_udev, i)) + continue; + id = usbd_get_interface_descriptor(uaa->ifaces[i]); + if (id != NULL && id->bInterfaceNumber == data_ifaceno) { + sc->sc_data_iface = uaa->ifaces[i]; + sc->sc_data_ifaceidx = i; + usbd_claim_iface(sc->sc_udev, i); + } + } + if (sc->sc_data_iface == NULL) { + printf("%s: no data interface found\n", DEVNAM(sc)); + goto fail; + } + + id = usbd_get_interface_descriptor(sc->sc_ctrl_iface); + ctrl_ep = -1; + for (i = 0; i < id->bNumEndpoints && ctrl_ep == -1; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_ctrl_iface, i); + if (ed == NULL) + break; + if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT && + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) + ctrl_ep = ed->bEndpointAddress; + } + if (ctrl_ep == -1) { + printf("%s: missing interrupt endpoint\n", DEVNAM(sc)); + goto fail; + } + + cd = usbd_get_config_descriptor(sc->sc_udev); + altnum = usbd_get_no_alts(cd, data_ifaceno); + for (i = 0; i < altnum; i++) { + id = usbd_find_idesc(cd, sc->sc_data_ifaceidx, i); + if (id == NULL) + continue; + if (id->bInterfaceClass == UICLASS_CDC_DATA && + id->bNumEndpoints == 2) + break; + } + if (i == altnum || id == NULL) { + printf("%s: missing alt setting for interface #%d\n", + DEVNAM(sc), data_ifaceno); + goto fail; + } + status = usbd_set_interface(sc->sc_data_iface, i); + if (status) { + printf("%s: select alt setting %d for interface #%d " + "failed: %s\n", DEVNAM(sc), i, data_ifaceno, + usbd_errstr(status)); + goto fail; + } + + id = usbd_get_interface_descriptor(sc->sc_data_iface); + sc->sc_rx_ep = sc->sc_tx_ep = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + if ((ed = usbd_interface2endpoint_descriptor(sc->sc_data_iface, + i)) == NULL) + break; + if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK && + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) + sc->sc_rx_ep = ed->bEndpointAddress; + else if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK && + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT) + sc->sc_tx_ep = ed->bEndpointAddress; + } + if (sc->sc_rx_ep == -1 || sc->sc_tx_ep == -1) { + printf("%s: missing bulk endpoints\n", DEVNAM(sc)); + goto fail; + } + + DPRINTFN(2, "%s: ctrl-ifno#%d: ep-ctrl=%d, data-ifno#%d: ep-rx=%d, " + "ep-tx=%d\n", DEVNAM(sc), sc->sc_ctrl_ifaceno, + UE_GET_ADDR(ctrl_ep), data_ifaceno, + UE_GET_ADDR(sc->sc_rx_ep), UE_GET_ADDR(sc->sc_tx_ep)); + + if (usbd_open_pipe_intr(sc->sc_ctrl_iface, ctrl_ep, USBD_SHORT_XFER_OK, + &sc->sc_ctrl_pipe, sc, &sc->sc_intr_msg, sizeof(sc->sc_intr_msg), + uncm_intr, USBD_DEFAULT_INTERVAL)) { + printf("%s: failed to open control pipe\n", DEVNAM(sc)); + goto fail; + } + + sc->sc_ncm_format = NCM_FORMAT_NTB16; + uncm_ntb_setup(sc); + uncm_ntb_setup_format(sc); + if (sc->sc_ncm_supported_formats == 0) + goto fail; + uncm_ntb_setup_input_size(sc); + DPRINTFN(2, "%s: rx/tx size %d/%d\n", DEVNAM(sc), + sc->sc_rx_bufsz, sc->sc_tx_bufsz); + + ifp = GET_IFP(sc); + uncm_get_eaddr(sc, mac_idx); + + s = splnet(); + printf("%s: address %s\n", DEVNAM(sc), + ether_sprintf(sc->sc_ac.ac_enaddr)); + + ifp->if_softc = sc; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = uncm_ioctl; + ifp->if_start = uncm_start; + ifp->if_watchdog = uncm_watchdog; + strlcpy(ifp->if_xname, DEVNAM(sc), IFNAMSIZ); + ifp->if_hardmtu = sc->sc_maxpktlen - ETHER_HDR_LEN; + + if_attach(ifp); + ether_ifattach(ifp); + splx(s); + return; + +fail: + usbd_deactivate(sc->sc_udev); + return; +} + +int +uncm_detach(struct device *self, int flags) +{ + struct uncm_softc *sc = (struct uncm_softc *)self; + struct ifnet *ifp = GET_IFP(sc); + int s; + + s = splnet(); + if (ifp->if_flags & IFF_RUNNING) + uncm_stop(sc); + + usb_rem_wait_task(sc->sc_udev, &sc->sc_link_task); + if (sc->sc_ctrl_pipe) { + usbd_close_pipe(sc->sc_ctrl_pipe); + sc->sc_ctrl_pipe = NULL; + } + if (ifp->if_softc != NULL) { + ether_ifdetach(ifp); + if_detach(ifp); + } + splx(s); + return 0; +} + +static int +uncm_hexval(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + +void +uncm_get_eaddr(struct uncm_softc *sc, int idx) +{ + struct ifnet *ifp = GET_IFP(sc); + usb_string_descriptor_t us; + int i, len, hi, lo; + + if (idx <= 0) + goto fake; + + if (usbd_get_string_desc(sc->sc_udev, idx, USB_LANGID_EN_US, &us, + &len) || len < 2 + ETHER_ADDR_LEN * 2 * 2) + goto fake; + + for (i = 0; i < ETHER_ADDR_LEN; i++) { + hi = uncm_hexval(UGETW(us.bString[2 * i])); + lo = uncm_hexval(UGETW(us.bString[2 * i + 1])); + if (hi < 0 || lo < 0) + goto fake; + sc->sc_ac.ac_enaddr[i] = (hi << 4) | lo; + } + if (ETHER_IS_MULTICAST(sc->sc_ac.ac_enaddr) || + ETHER_IS_ANYADDR(sc->sc_ac.ac_enaddr)) + goto fake; + return; + +fake: + ether_fakeaddr(ifp); +} + +void +uncm_ntb_setup(struct uncm_softc *sc) +{ + usb_device_request_t req; + struct ncm_ntb_parameters np; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = NCM_GET_NTB_PARAMETERS; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ctrl_ifaceno); + USETW(req.wLength, sizeof(np)); + if (usbd_do_request(sc->sc_udev, &req, &np) == USBD_NORMAL_COMPLETION && + UGETW(np.wLength) == sizeof(np)) { + sc->sc_rx_bufsz = MIN(UGETDW(np.dwNtbInMaxSize), UINT16_MAX); + sc->sc_tx_bufsz = MIN(UGETDW(np.dwNtbOutMaxSize), UINT16_MAX); + sc->sc_maxdgram = UGETW(np.wNtbOutMaxDatagrams); + sc->sc_align = UGETW(np.wNdpOutAlignment); + sc->sc_ndp_div = UGETW(np.wNdpOutDivisor); + sc->sc_ndp_remainder = UGETW(np.wNdpOutPayloadRemainder); + + if (!powerof2(sc->sc_align) || sc->sc_align == 0 || + sc->sc_align >= sc->sc_tx_bufsz) + sc->sc_align = sizeof(uint32_t); + if (!powerof2(sc->sc_ndp_div) || sc->sc_ndp_div == 0 || + sc->sc_ndp_div >= sc->sc_tx_bufsz) + sc->sc_ndp_div = sizeof(uint32_t); + if (sc->sc_ndp_remainder >= sc->sc_ndp_div) + sc->sc_ndp_remainder = 0; + DPRINTF("%s: NCM align=%d div=%d rem=%d in=%u out=%u " + "maxdgram=%d\n", DEVNAM(sc), sc->sc_align, sc->sc_ndp_div, + sc->sc_ndp_remainder, UGETDW(np.dwNtbInMaxSize), + UGETDW(np.dwNtbOutMaxSize), sc->sc_maxdgram); + sc->sc_ncm_supported_formats = UGETW(np.bmNtbFormatsSupported); + } else { + sc->sc_rx_bufsz = sc->sc_tx_bufsz = 8 * 1024; + sc->sc_maxdgram = 0; + sc->sc_align = sc->sc_ndp_div = sizeof(uint32_t); + sc->sc_ndp_remainder = 0; + DPRINTF("%s: align=default div=default rem=default\n", + DEVNAM(sc)); + sc->sc_ncm_supported_formats = NCM_FORMAT_NTB16_MASK; + } + + if (sc->sc_rx_bufsz < sizeof(struct ncm_header16) + + sizeof(struct ncm_pointer16) + + sizeof(struct ncm_pointer16_dgram) || + sc->sc_tx_bufsz < sizeof(struct ncm_header16) + + sizeof(struct ncm_pointer16) + + sizeof(struct ncm_pointer16_dgram) || + ((sc->sc_ncm_supported_formats & NCM_FORMAT_NTB32_MASK) && + (sc->sc_rx_bufsz < sizeof(struct ncm_header32) + + sizeof(struct ncm_pointer32) + + sizeof(struct ncm_pointer32_dgram) || + sc->sc_tx_bufsz < sizeof(struct ncm_header32) + + sizeof(struct ncm_pointer32) + + sizeof(struct ncm_pointer32_dgram)))) { + DPRINTF("%s: invalid NTB size %d/%d\n", DEVNAM(sc), + sc->sc_rx_bufsz, sc->sc_tx_bufsz); + sc->sc_ncm_supported_formats = 0; + } +} + +void +uncm_ntb_setup_format(struct uncm_softc *sc) +{ + usb_device_request_t req; + uWord wFmt; + uint16_t fmt; + + if (sc->sc_ncm_supported_formats == 0) + goto fail; + + if (sc->sc_ncm_supported_formats == NCM_FORMAT_NTB16_MASK) { + DPRINTF("%s: only NTB16 format supported\n", DEVNAM(sc)); + sc->sc_ncm_format = NCM_FORMAT_NTB16; + return; + } + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = NCM_GET_NTB_FORMAT; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ctrl_ifaceno); + USETW(req.wLength, sizeof(wFmt)); + if (usbd_do_request(sc->sc_udev, &req, wFmt) != USBD_NORMAL_COMPLETION) + goto fail; + fmt = UGETW(wFmt); + if ((sc->sc_ncm_supported_formats & (1UL << fmt)) == 0) + goto fail; + if (fmt != NCM_FORMAT_NTB16 && fmt != NCM_FORMAT_NTB32) + goto fail; + sc->sc_ncm_format = fmt; + + DPRINTF("%s: using NCM format %d, supported 0x%x\n", + DEVNAM(sc), sc->sc_ncm_format, sc->sc_ncm_supported_formats); + return; + +fail: + DPRINTF("%s: cannot set up NCM format\n", DEVNAM(sc)); + sc->sc_ncm_supported_formats = 0; +} + +void +uncm_ntb_setup_input_size(struct uncm_softc *sc) +{ + usb_device_request_t req; + uDWord dwSize; + + if (sc->sc_rx_bufsz <= UNCM_RX_NTB_MAX) + return; + + USETDW(dwSize, UNCM_RX_NTB_MAX); + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = NCM_SET_NTB_INPUT_SIZE; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ctrl_ifaceno); + USETW(req.wLength, sizeof(dwSize)); + if (usbd_do_request(sc->sc_udev, &req, dwSize) == + USBD_NORMAL_COMPLETION) + sc->sc_rx_bufsz = UNCM_RX_NTB_MAX; + else + DPRINTF("%s: SET_NTB_INPUT_SIZE refused, keeping %d\n", + DEVNAM(sc), sc->sc_rx_bufsz); +} + +int +uncm_alloc_xfers(struct uncm_softc *sc) +{ + if (sc->sc_rx_xfer == NULL) { + if ((sc->sc_rx_xfer = usbd_alloc_xfer(sc->sc_udev)) != NULL) + sc->sc_rx_buf = usbd_alloc_buffer(sc->sc_rx_xfer, + sc->sc_rx_bufsz); + } + + if (sc->sc_tx_xfer == NULL) { + if ((sc->sc_tx_xfer = usbd_alloc_xfer(sc->sc_udev)) != NULL) + sc->sc_tx_buf = usbd_alloc_buffer(sc->sc_tx_xfer, + sc->sc_tx_bufsz); + } + + return (sc->sc_rx_buf != NULL && sc->sc_tx_buf != NULL); +} + +void +uncm_free_xfers(struct uncm_softc *sc) +{ + if (sc->sc_rx_xfer != NULL) { + usbd_free_xfer(sc->sc_rx_xfer); + sc->sc_rx_xfer = NULL; + sc->sc_rx_buf = NULL; + } + + if (sc->sc_tx_xfer != NULL) { + usbd_free_xfer(sc->sc_tx_xfer); + sc->sc_tx_xfer = NULL; + sc->sc_tx_buf = NULL; + } + + ml_purge(&sc->sc_tx_ml); +} + +void +uncm_init(struct uncm_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + + splassert(IPL_NET); + + if (ifp->if_flags & IFF_RUNNING) + return; + + sc->sc_tx_seq = 0; + if (!uncm_alloc_xfers(sc)) { + printf("%s: allocation of xfers failed\n", DEVNAM(sc)); + uncm_free_xfers(sc); + return; + } + if (usbd_open_pipe(sc->sc_data_iface, sc->sc_rx_ep, + USBD_EXCLUSIVE_USE, &sc->sc_rx_pipe)) { + printf("%s: open rx pipe failed\n", DEVNAM(sc)); + goto fail; + } + if (usbd_open_pipe(sc->sc_data_iface, sc->sc_tx_ep, + USBD_EXCLUSIVE_USE, &sc->sc_tx_pipe)) { + printf("%s: open tx pipe failed\n", DEVNAM(sc)); + goto fail; + } + + ifp->if_flags |= IFF_RUNNING; + ifq_clr_oactive(&ifp->if_snd); + uncm_rx(sc); + return; + +fail: + uncm_stop(sc); +} + +void +uncm_stop(struct uncm_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + + splassert(IPL_NET); + + ifp->if_flags &= ~IFF_RUNNING; + ifq_clr_oactive(&ifp->if_snd); + ifp->if_timer = 0; + if (sc->sc_rx_pipe) { + usbd_close_pipe(sc->sc_rx_pipe); + sc->sc_rx_pipe = NULL; + } + if (sc->sc_tx_pipe) { + usbd_close_pipe(sc->sc_tx_pipe); + sc->sc_tx_pipe = NULL; + } + uncm_free_xfers(sc); +} + +int +uncm_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct uncm_softc *sc = ifp->if_softc; + int s, error = 0; + + if (usbd_is_dying(sc->sc_udev)) + return ENXIO; + + s = splnet(); + switch (cmd) { + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + if (!(ifp->if_flags & IFF_RUNNING)) + uncm_init(sc); + break; + + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_flags & IFF_RUNNING) + error = ENETRESET; + else + uncm_init(sc); + } else { + if (ifp->if_flags & IFF_RUNNING) + uncm_stop(sc); + } + break; + + default: + error = ether_ioctl(ifp, &sc->sc_ac, cmd, data); + break; + } + + if (error == ENETRESET) + error = 0; + + splx(s); + return error; +} + +static inline int +uncm_align(size_t bufsz, int offs, int alignment, int remainder) +{ + size_t m = alignment - 1; + int newoff; + + newoff = (((size_t)offs + m) & ~m) - alignment + remainder; + if (newoff < offs) + newoff += alignment; + if (newoff > bufsz) + newoff = bufsz; + return newoff - offs; +} + +static inline int +uncm_padding(void *buf, size_t bufsz, int offs, int alignment, int remainder) +{ + int nb; + + nb = uncm_align(bufsz, offs, alignment, remainder); + if (nb > 0) + memset(buf + offs, 0, nb); + return nb; +} + +void +uncm_start(struct ifnet *ifp) +{ + struct uncm_softc *sc = ifp->if_softc; + struct mbuf *m = NULL; + int ndgram = 0; + int offs, len, mlen; + int maxoverhead; + + if (usbd_is_dying(sc->sc_udev) || + !(ifp->if_flags & IFF_RUNNING) || + ifq_is_oactive(&ifp->if_snd)) + return; + + KASSERT(ml_empty(&sc->sc_tx_ml)); + KASSERT(sc->sc_ncm_format == NCM_FORMAT_NTB16 || + sc->sc_ncm_format == NCM_FORMAT_NTB32); + + switch (sc->sc_ncm_format) { + case NCM_FORMAT_NTB16: + offs = sizeof(struct ncm_header16); + offs += uncm_align(sc->sc_tx_bufsz, offs, sc->sc_align, 0); + offs += sizeof(struct ncm_pointer16); + maxoverhead = sizeof(struct ncm_pointer16_dgram); + break; + case NCM_FORMAT_NTB32: + offs = sizeof(struct ncm_header32); + offs += uncm_align(sc->sc_tx_bufsz, offs, sc->sc_align, 0); + offs += sizeof(struct ncm_pointer32); + maxoverhead = sizeof(struct ncm_pointer32_dgram); + break; + } + + maxoverhead += sc->sc_ndp_div - 1; + + len = 0; + while (1) { + m = ifq_deq_begin(&ifp->if_snd); + if (m == NULL) + break; + + mlen = maxoverhead + m->m_pkthdr.len; + if ((sc->sc_maxdgram != 0 && ndgram >= sc->sc_maxdgram) || + (offs + len + mlen > sc->sc_tx_bufsz)) { + ifq_deq_rollback(&ifp->if_snd, m); + break; + } + ifq_deq_commit(&ifp->if_snd, m); + + ndgram++; + len += mlen; + ml_enqueue(&sc->sc_tx_ml, m); + +#if NBPFILTER > 0 + if (ifp->if_bpf) + bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT); +#endif + } + if (ml_empty(&sc->sc_tx_ml)) + return; + if (uncm_encap(sc, ndgram)) { + ifq_set_oactive(&ifp->if_snd); + ifp->if_timer = (2 * uncm_xfer_tout) / 1000; + } +} + +void +uncm_watchdog(struct ifnet *ifp) +{ + struct uncm_softc *sc = ifp->if_softc; + + if (usbd_is_dying(sc->sc_udev)) + return; + + ifp->if_oerrors++; + printf("%s: watchdog timeout\n", DEVNAM(sc)); + usbd_abort_pipe(sc->sc_tx_pipe); +} + +void +uncm_rx(struct uncm_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + usbd_status err; + + usbd_setup_xfer(sc->sc_rx_xfer, sc->sc_rx_pipe, sc, sc->sc_rx_buf, + sc->sc_rx_bufsz, USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, uncm_rxeof); + err = usbd_transfer(sc->sc_rx_xfer); + if (err != USBD_IN_PROGRESS) { + ifp->if_ierrors++; + DPRINTF("%s: start rx error: %s\n", DEVNAM(sc), + usbd_errstr(err)); + } +} + +void +uncm_rxeof(struct usbd_xfer *xfer, void *priv, usbd_status status) +{ + struct uncm_softc *sc = priv; + struct ifnet *ifp = GET_IFP(sc); + + if (usbd_is_dying(sc->sc_udev) || !(ifp->if_flags & IFF_RUNNING)) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + DPRINTF("%s: rx error: %s\n", DEVNAM(sc), usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_rx_pipe); + if (++sc->sc_rx_nerr > 100) { + log(LOG_ERR, "%s: too many rx errors, disabling\n", + DEVNAM(sc)); + usbd_deactivate(sc->sc_udev); + } + } else { + sc->sc_rx_nerr = 0; + uncm_decap(sc, xfer); + } + + uncm_rx(sc); +} + +int +uncm_encap(struct uncm_softc *sc, int ndgram) +{ + struct ncm_header16 *hdr16 = NULL; + struct ncm_header32 *hdr32 = NULL; + struct ncm_pointer16 *ptr16 = NULL; + struct ncm_pointer32 *ptr32 = NULL; + struct ncm_pointer16_dgram *dgram16 = NULL; + struct ncm_pointer32_dgram *dgram32 = NULL; + int offs = 0, plen = 0; + int dgoffs = 0, poffs; + struct mbuf *m; + usbd_status err; + + KASSERT(sc->sc_ncm_format == NCM_FORMAT_NTB16 || + sc->sc_ncm_format == NCM_FORMAT_NTB32); + + switch (sc->sc_ncm_format) { + case NCM_FORMAT_NTB16: + hdr16 = sc->sc_tx_buf; + USETDW(hdr16->dwSignature, NCM_HDR16_SIG); + USETW(hdr16->wHeaderLength, sizeof(*hdr16)); + USETW(hdr16->wSequence, sc->sc_tx_seq); + USETW(hdr16->wBlockLength, 0); + offs = sizeof(*hdr16); + break; + case NCM_FORMAT_NTB32: + hdr32 = sc->sc_tx_buf; + USETDW(hdr32->dwSignature, NCM_HDR32_SIG); + USETW(hdr32->wHeaderLength, sizeof(*hdr32)); + USETW(hdr32->wSequence, sc->sc_tx_seq); + USETDW(hdr32->dwBlockLength, 0); + offs = sizeof(*hdr32); + break; + } + offs += uncm_padding(sc->sc_tx_buf, sc->sc_tx_bufsz, offs, + sc->sc_align, 0); + poffs = offs; + + switch (sc->sc_ncm_format) { + case NCM_FORMAT_NTB16: + USETW(hdr16->wNdpIndex, poffs); + ptr16 = (struct ncm_pointer16 *)(sc->sc_tx_buf + poffs); + plen = sizeof(*ptr16) + ndgram * sizeof(*dgram16); + USETDW(ptr16->dwSignature, NCM_NDP16_SIG_NOCRC); + USETW(ptr16->wLength, plen); + USETW(ptr16->wNextNdpIndex, 0); + dgram16 = ptr16->dgram; + break; + case NCM_FORMAT_NTB32: + USETDW(hdr32->dwNdpIndex, poffs); + ptr32 = (struct ncm_pointer32 *)(sc->sc_tx_buf + poffs); + plen = sizeof(*ptr32) + ndgram * sizeof(*dgram32); + USETDW(ptr32->dwSignature, NCM_NDP32_SIG_NOCRC); + USETW(ptr32->wLength, plen); + USETW(ptr32->wReserved6, 0); + USETDW(ptr32->dwNextNdpIndex, 0); + USETDW(ptr32->dwReserved12, 0); + dgram32 = ptr32->dgram; + break; + } + dgoffs = offs + plen; + + sc->sc_tx_seq++; + while ((m = ml_dequeue(&sc->sc_tx_ml)) != NULL) { + dgoffs += uncm_padding(sc->sc_tx_buf, sc->sc_tx_bufsz, dgoffs, + sc->sc_ndp_div, sc->sc_ndp_remainder); + switch (sc->sc_ncm_format) { + case NCM_FORMAT_NTB16: + USETW(dgram16->wDatagramIndex, dgoffs); + USETW(dgram16->wDatagramLen, m->m_pkthdr.len); + dgram16++; + break; + case NCM_FORMAT_NTB32: + USETDW(dgram32->dwDatagramIndex, dgoffs); + USETDW(dgram32->dwDatagramLen, m->m_pkthdr.len); + dgram32++; + break; + } + m_copydata(m, 0, m->m_pkthdr.len, sc->sc_tx_buf + dgoffs); + dgoffs += m->m_pkthdr.len; + m_freem(m); + } + offs = dgoffs; + + switch (sc->sc_ncm_format) { + case NCM_FORMAT_NTB16: + USETW(dgram16->wDatagramIndex, 0); + USETW(dgram16->wDatagramLen, 0); + USETW(hdr16->wBlockLength, offs); + KASSERT(dgram16 - ptr16->dgram == ndgram); + break; + case NCM_FORMAT_NTB32: + USETDW(dgram32->dwDatagramIndex, 0); + USETDW(dgram32->dwDatagramLen, 0); + USETDW(hdr32->dwBlockLength, offs); + KASSERT(dgram32 - ptr32->dgram == ndgram); + break; + } + + DPRINTFN(3, "%s: encap %d bytes, %d dgrams\n", DEVNAM(sc), offs, + ndgram); + KASSERT(offs <= sc->sc_tx_bufsz); + + usbd_setup_xfer(sc->sc_tx_xfer, sc->sc_tx_pipe, sc, sc->sc_tx_buf, offs, + USBD_FORCE_SHORT_XFER | USBD_NO_COPY, uncm_xfer_tout, uncm_txeof); + err = usbd_transfer(sc->sc_tx_xfer); + if (err != USBD_IN_PROGRESS) { + GET_IFP(sc)->if_oerrors++; + DPRINTF("%s: start tx error: %s\n", DEVNAM(sc), + usbd_errstr(err)); + return 0; + } + return 1; +} + +void +uncm_txeof(struct usbd_xfer *xfer, void *priv, usbd_status status) +{ + struct uncm_softc *sc = priv; + struct ifnet *ifp = GET_IFP(sc); + int s; + + s = splnet(); + ifq_clr_oactive(&ifp->if_snd); + ifp->if_timer = 0; + + if (status != USBD_NORMAL_COMPLETION) { + if (status != USBD_NOT_STARTED && status != USBD_CANCELLED) { + ifp->if_oerrors++; + DPRINTF("%s: tx error: %s\n", DEVNAM(sc), + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_tx_pipe); + } + } + if (ifq_empty(&ifp->if_snd) == 0) + uncm_start(ifp); + + splx(s); +} + +/* + * XXX Only the first NDP is processed; wNextNdpIndex not followed + * (same as umb(4)). + */ +void +uncm_decap(struct uncm_softc *sc, struct usbd_xfer *xfer) +{ + struct ifnet *ifp = GET_IFP(sc); + int s; + void *buf; + uint32_t len; + char *dp; + struct ncm_header16 *hdr16; + struct ncm_header32 *hdr32; + struct ncm_pointer16 *ptr16; + struct ncm_pointer16_dgram *dgram16; + struct ncm_pointer32_dgram *dgram32; + uint32_t hsig, psig; + uint32_t ptrlen, dgentryoff; + uint64_t blen, ptroff, doff, dlen; + struct mbuf_list ml = MBUF_LIST_INITIALIZER(); + struct mbuf *m; + + usbd_get_xfer_status(xfer, NULL, &buf, &len, NULL); + DPRINTFN(4, "%s: recv %d bytes\n", DEVNAM(sc), len); + s = splnet(); + if (len < sizeof(*hdr16)) + goto toosmall; + + hdr16 = (struct ncm_header16 *)buf; + hsig = UGETDW(hdr16->dwSignature); + + switch (hsig) { + case NCM_HDR16_SIG: + blen = UGETW(hdr16->wBlockLength); + ptroff = UGETW(hdr16->wNdpIndex); + if (UGETW(hdr16->wHeaderLength) != sizeof(*hdr16)) { + DPRINTF("%s: bad header len %d for NTH16 (exp %zu)\n", + DEVNAM(sc), UGETW(hdr16->wHeaderLength), + sizeof(*hdr16)); + goto fail; + } + break; + case NCM_HDR32_SIG: + if (len < sizeof(*hdr32)) + goto toosmall; + hdr32 = (struct ncm_header32 *)hdr16; + blen = UGETDW(hdr32->dwBlockLength); + ptroff = UGETDW(hdr32->dwNdpIndex); + if (UGETW(hdr32->wHeaderLength) != sizeof(*hdr32)) { + DPRINTF("%s: bad header len %d for NTH32 (exp %zu)\n", + DEVNAM(sc), UGETW(hdr32->wHeaderLength), + sizeof(*hdr32)); + goto fail; + } + break; + default: + DPRINTF("%s: unsupported NCM header signature (0x%08x)\n", + DEVNAM(sc), hsig); + goto fail; + } + if (blen != 0 && len < blen) { + DPRINTF("%s: bad NTB len (%llu) for %d bytes of data\n", + DEVNAM(sc), blen, len); + goto fail; + } + + if ((uint64_t)len < ptroff + sizeof(*ptr16)) + goto toosmall; + ptr16 = (struct ncm_pointer16 *)(buf + ptroff); + psig = UGETDW(ptr16->dwSignature); + ptrlen = UGETW(ptr16->wLength); + if ((uint64_t)len < (uint64_t)ptrlen + ptroff) + goto toosmall; + + switch (psig) { + case NCM_NDP16_SIG_NOCRC: + if (hsig != NCM_HDR16_SIG) + goto badsig; + dgentryoff = offsetof(struct ncm_pointer16, dgram); + if (ptrlen < dgentryoff + sizeof(*dgram16)) + goto toosmall; + break; + case NCM_NDP32_SIG_NOCRC: + if (hsig != NCM_HDR32_SIG) + goto badsig; + dgentryoff = offsetof(struct ncm_pointer32, dgram); + if (ptrlen < dgentryoff + sizeof(*dgram32)) + goto toosmall; + break; + case NCM_NDP16_SIG_CRC: + case NCM_NDP32_SIG_CRC: + default: + badsig: + DPRINTF("%s: unsupported NCM pointer signature (0x%08x)\n", + DEVNAM(sc), psig); + goto fail; + } + + while (dgentryoff < ptrlen) { + switch (hsig) { + case NCM_HDR16_SIG: + if (ptrlen - dgentryoff < sizeof(*dgram16)) + goto done; + dgram16 = (struct ncm_pointer16_dgram *) + (buf + ptroff + dgentryoff); + dgentryoff += sizeof(*dgram16); + dlen = UGETW(dgram16->wDatagramLen); + doff = UGETW(dgram16->wDatagramIndex); + break; + case NCM_HDR32_SIG: + if (ptrlen - dgentryoff < sizeof(*dgram32)) + goto done; + dgram32 = (struct ncm_pointer32_dgram *) + (buf + ptroff + dgentryoff); + dgentryoff += sizeof(*dgram32); + dlen = UGETDW(dgram32->dwDatagramLen); + doff = UGETDW(dgram32->dwDatagramIndex); + break; + default: + ifp->if_ierrors++; + goto done; + } + + if (dlen == 0 || doff == 0) + break; + if ((uint64_t)len < dlen + doff || + (blen != 0 && blen < dlen + doff)) { + DPRINTF("%s: datagram too large (%llu @ off %llu)\n", + DEVNAM(sc), dlen, doff); + ifp->if_ierrors++; + continue; + } + if (dlen < ETHER_HDR_LEN || dlen > sc->sc_maxpktlen) { + ifp->if_ierrors++; + continue; + } + + dp = buf + doff; + DPRINTFN(3, "%s: decap %llu bytes\n", DEVNAM(sc), dlen); + m = m_devget(dp, (int)dlen, ETHER_ALIGN); + if (m == NULL) { + ifp->if_iqdrops++; + continue; + } + ml_enqueue(&ml, m); + } +done: + if_input(ifp, &ml); + splx(s); + return; +toosmall: + DPRINTF("%s: packet too small (%d)\n", DEVNAM(sc), len); +fail: + ifp->if_ierrors++; + splx(s); +} + +void +uncm_intr(struct usbd_xfer *xfer, void *priv, usbd_status status) +{ + struct uncm_softc *sc = priv; + struct ifnet *ifp = GET_IFP(sc); + uint32_t up, down; + int total_len; + + if (usbd_is_dying(sc->sc_udev)) + return; + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF("%s: notification error: %s\n", DEVNAM(sc), + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_ctrl_pipe); + return; + } + usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); + if (total_len < UCDC_NOTIFICATION_LENGTH) { + DPRINTF("%s: short notification (%d<%d)\n", DEVNAM(sc), + total_len, UCDC_NOTIFICATION_LENGTH); + return; + } + if (sc->sc_intr_msg.bmRequestType != UCDC_NOTIFICATION) { + DPRINTF("%s: unexpected notification (type=0x%02x)\n", + DEVNAM(sc), sc->sc_intr_msg.bmRequestType); + return; + } + + switch (sc->sc_intr_msg.bNotification) { + case UCDC_N_NETWORK_CONNECTION: + if (ifp->if_flags & IFF_DEBUG) + log(LOG_DEBUG, "%s: network %sconnected\n", DEVNAM(sc), + UGETW(sc->sc_intr_msg.wValue) ? "" : "dis"); + sc->sc_link = UGETW(sc->sc_intr_msg.wValue) ? + LINK_STATE_UP : LINK_STATE_DOWN; + usb_add_task(sc->sc_udev, &sc->sc_link_task); + break; + case UCDC_N_CONNECTION_SPEED_CHANGE: + if (total_len >= UCDC_NOTIFICATION_LENGTH + 8) { + down = UGETDW(&sc->sc_intr_msg.data[0]); + up = UGETDW(&sc->sc_intr_msg.data[4]); + sc->sc_baudrate = MIN(up, down); + DPRINTFN(2, "%s: speed down %u up %u\n", DEVNAM(sc), + down, up); + usb_add_task(sc->sc_udev, &sc->sc_link_task); + } + break; + default: + DPRINTF("%s: unexpected notification (0x%02x)\n", + DEVNAM(sc), sc->sc_intr_msg.bNotification); + break; + } +} + +void +uncm_link_task(void *arg) +{ + struct uncm_softc *sc = arg; + struct ifnet *ifp = GET_IFP(sc); + int s; + + if (usbd_is_dying(sc->sc_udev)) + return; + + s = splnet(); + ifp->if_baudrate = sc->sc_baudrate; + if (sc->sc_link != LINK_STATE_UNKNOWN && + ifp->if_link_state != sc->sc_link) { + if (ifp->if_flags & IFF_DEBUG) + log(LOG_DEBUG, "%s: link state changed from %s to %s\n", + DEVNAM(sc), + LINK_STATE_IS_UP(ifp->if_link_state) ? "up" : "down", + LINK_STATE_IS_UP(sc->sc_link) ? "up" : "down"); + ifp->if_link_state = sc->sc_link; + if_link_state_change(ifp); + if (LINK_STATE_IS_UP(sc->sc_link) && + (ifp->if_flags & IFF_RUNNING) && !ifq_empty(&ifp->if_snd)) + uncm_start(ifp); + } + splx(s); +} + diff --git sys/dev/usb/mbim.h sys/dev/usb/mbim.h index 2f6d30a82e4..60ce897cbef 100644 --- sys/dev/usb/mbim.h +++ sys/dev/usb/mbim.h @@ -601,97 +601,6 @@ struct mbim_descriptor { uByte bmNetworkCapabilities; } __packed; -/* - * NCM Parameters - */ -#define NCM_GET_NTB_PARAMETERS 0x80 -#define NCM_GET_NTB_FORMAT 0x83 /* Current format returned as uWord */ -#define NCM_SET_NTB_FORMAT 0x84 /* Desired format is in wValue */ - -#define NCM_FORMAT_NTB16 0x00 -#define NCM_FORMAT_NTB32 0x01 - -struct ncm_ntb_parameters { - uWord wLength; - uWord bmNtbFormatsSupported; -#define NCM_FORMAT_NTB16_MASK (1U << NCM_FORMAT_NTB16) -#define NCM_FORMAT_NTB32_MASK (1U << NCM_FORMAT_NTB32) - uDWord dwNtbInMaxSize; - uWord wNdpInDivisor; - uWord wNdpInPayloadRemainder; - uWord wNdpInAlignment; - uWord wReserved1; - uDWord dwNtbOutMaxSize; - uWord wNdpOutDivisor; - uWord wNdpOutPayloadRemainder; - uWord wNdpOutAlignment; - uWord wNtbOutMaxDatagrams; -} __packed; - -/* - * NCM Encoding - */ -struct ncm_header16 { -#define NCM_HDR16_SIG 0x484d434e - uDWord dwSignature; - uWord wHeaderLength; - uWord wSequence; - uWord wBlockLength; - uWord wNdpIndex; -} __packed; - -struct ncm_header32 { -#define NCM_HDR32_SIG 0x686d636e - uDWord dwSignature; - uWord wHeaderLength; - uWord wSequence; - uDWord dwBlockLength; - uDWord dwNdpIndex; -} __packed; - - -#define MBIM_NCM_NTH_SIDSHIFT 24 -#define MBIM_NCM_NTH_GETSID(s) (((s) > MBIM_NCM_NTH_SIDSHIFT) & 0xff) - -struct ncm_pointer16_dgram { - uWord wDatagramIndex; - uWord wDatagramLen; -} __packed; - -struct ncm_pointer16 { -#define MBIM_NCM_NTH16_IPS 0x00535049 -#define MBIM_NCM_NTH16_ISISG(s) (((s) & 0x00ffffff) == MBIM_NCM_NTH16_IPS) -#define MBIM_NCM_NTH16_SIG(s) \ - ((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH16_IPS) - uDWord dwSignature; - uWord wLength; - uWord wNextNdpIndex; - - /* Minimum is two datagrams, but can be more */ - struct ncm_pointer16_dgram dgram[1]; -} __packed; - -struct ncm_pointer32_dgram { - uDWord dwDatagramIndex; - uDWord dwDatagramLen; -} __packed; - -struct ncm_pointer32 { -#define MBIM_NCM_NTH32_IPS 0x00737069 -#define MBIM_NCM_NTH32_ISISG(s) \ - (((s) & 0x00ffffff) == MBIM_NCM_NTH32_IPS) -#define MBIM_NCM_NTH32_SIG(s) \ - ((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH32_IPS) - uDWord dwSignature; - uWord wLength; - uWord wReserved6; - uDWord dwNextNdpIndex; - uDWord dwReserved12; - - /* Minimum is two datagrams, but can be more */ - struct ncm_pointer32_dgram dgram[1]; -} __packed; - #endif /* _KERNEL */ #endif /* _MBIM_H_ */ diff --git sys/dev/usb/usb.h sys/dev/usb/usb.h index d8e18d3b340..30641c40776 100644 --- sys/dev/usb/usb.h +++ sys/dev/usb/usb.h @@ -297,8 +297,9 @@ struct usb_string_descriptor { uWord bString[126]; } __packed; typedef struct usb_string_descriptor usb_string_descriptor_t; -#define USB_MAX_STRING_LEN 127 -#define USB_LANGUAGE_TABLE 0 /* # of the string language id table */ +#define USB_MAX_STRING_LEN 127 +#define USB_LANGUAGE_TABLE 0 /* # of the string language id table */ +#define USB_LANGID_EN_US 0x0409 /* English (United States) */ /* Hub specific request */ #define UR_GET_BUS_STATE 0x02 diff --git sys/dev/usb/usbcdc.h sys/dev/usb/usbcdc.h index 1ac2a35640a..665dad121a2 100644 --- sys/dev/usb/usbcdc.h +++ sys/dev/usb/usbcdc.h @@ -52,6 +52,7 @@ #define UDESCSUB_CDC_CCMF 14 /* CAPI Control Management */ #define UDESCSUB_CDC_ENF 15 /* Ethernet Networking */ #define UDESCSUB_CDC_ANF 16 /* ATM Networking */ +#define UDESCSUB_CDC_NCM 26 /* Network Control Model */ struct usb_cdc_header_descriptor { uByte bLength; @@ -100,6 +101,20 @@ struct usb_cdc_ethernet_descriptor { uByte bNumberPowerFilters; } __packed; +struct usb_cdc_ncm_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uWord bcdNcmVersion; + uByte bmNetworkCapabilities; +#define UCDC_NCM_NCAP_ETH_FILTER 0x01 +#define UCDC_NCM_NCAP_NET_ADDRESS 0x02 +#define UCDC_NCM_NCAP_ENCAP_COMMAND 0x04 +#define UCDC_NCM_NCAP_MAX_DATAGRAM 0x08 +#define UCDC_NCM_NCAP_CRC_MODE 0x10 +#define UCDC_NCM_NCAP_NTB_INPUT_SIZE 0x20 +} __packed; + #define UCDC_SEND_ENCAPSULATED_COMMAND 0x00 #define UCDC_GET_ENCAPSULATED_RESPONSE 0x01 #define UCDC_SET_COMM_FEATURE 0x02 @@ -176,4 +191,105 @@ struct usb_cdc_connection_speed { #define UCDC_N_SERIAL_DSR 0x02 #define UCDC_N_SERIAL_DCD 0x01 +/* + * Network Control Model (NCM 1.0) + */ + +#define NCM_GET_NTB_PARAMETERS 0x80 +#define NCM_GET_NET_ADDRESS 0x81 +#define NCM_SET_NET_ADDRESS 0x82 +#define NCM_GET_NTB_FORMAT 0x83 /* Current format returned as uWord */ +#define NCM_SET_NTB_FORMAT 0x84 /* Desired format is in wValue */ +#define NCM_GET_NTB_INPUT_SIZE 0x85 +#define NCM_SET_NTB_INPUT_SIZE 0x86 +#define NCM_GET_MAX_DATAGRAM_SIZE 0x87 +#define NCM_SET_MAX_DATAGRAM_SIZE 0x88 +#define NCM_GET_CRC_MODE 0x89 +#define NCM_SET_CRC_MODE 0x8a + +#define NCM_NDP16_SIG_NOCRC 0x304d434e +#define NCM_NDP16_SIG_CRC 0x314d434e +#define NCM_NDP32_SIG_NOCRC 0x306d636e +#define NCM_NDP32_SIG_CRC 0x316d636e + +#define NCM_FORMAT_NTB16 0x00 +#define NCM_FORMAT_NTB32 0x01 + +struct ncm_ntb_parameters { + uWord wLength; + uWord bmNtbFormatsSupported; +#define NCM_FORMAT_NTB16_MASK (1U << NCM_FORMAT_NTB16) +#define NCM_FORMAT_NTB32_MASK (1U << NCM_FORMAT_NTB32) + uDWord dwNtbInMaxSize; + uWord wNdpInDivisor; + uWord wNdpInPayloadRemainder; + uWord wNdpInAlignment; + uWord wReserved1; + uDWord dwNtbOutMaxSize; + uWord wNdpOutDivisor; + uWord wNdpOutPayloadRemainder; + uWord wNdpOutAlignment; + uWord wNtbOutMaxDatagrams; +} __packed; + +struct ncm_header16 { +#define NCM_HDR16_SIG 0x484d434e + uDWord dwSignature; + uWord wHeaderLength; + uWord wSequence; + uWord wBlockLength; + uWord wNdpIndex; +} __packed; + +struct ncm_header32 { +#define NCM_HDR32_SIG 0x686d636e + uDWord dwSignature; + uWord wHeaderLength; + uWord wSequence; + uDWord dwBlockLength; + uDWord dwNdpIndex; +} __packed; + + +#define MBIM_NCM_NTH_SIDSHIFT 24 +#define MBIM_NCM_NTH_GETSID(s) (((s) > MBIM_NCM_NTH_SIDSHIFT) & 0xff) + +struct ncm_pointer16_dgram { + uWord wDatagramIndex; + uWord wDatagramLen; +} __packed; + +struct ncm_pointer16 { +#define MBIM_NCM_NTH16_IPS 0x00535049 +#define MBIM_NCM_NTH16_ISISG(s) (((s) & 0x00ffffff) == MBIM_NCM_NTH16_IPS) +#define MBIM_NCM_NTH16_SIG(s) \ + ((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH16_IPS) + uDWord dwSignature; + uWord wLength; + uWord wNextNdpIndex; + + /* Minimum is two datagrams, but can be more */ + struct ncm_pointer16_dgram dgram[1]; +} __packed; + +struct ncm_pointer32_dgram { + uDWord dwDatagramIndex; + uDWord dwDatagramLen; +} __packed; + +struct ncm_pointer32 { +#define MBIM_NCM_NTH32_IPS 0x00737069 +#define MBIM_NCM_NTH32_ISISG(s) \ + (((s) & 0x00ffffff) == MBIM_NCM_NTH32_IPS) +#define MBIM_NCM_NTH32_SIG(s) \ + ((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH32_IPS) + uDWord dwSignature; + uWord wLength; + uWord wReserved6; + uDWord dwNextNdpIndex; + uDWord dwReserved12; + + /* Minimum is two datagrams, but can be more */ + struct ncm_pointer32_dgram dgram[1]; +} __packed; #endif /* _USBCDC_H_ */