/**************************************************************************** * drivers/usbdev/cdcncm.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /* References: * [CDCNCM1.2] Universal Serial Bus - Communications Class - Subclass * Specification for Ethernet Control Model Devices - Rev 1.2 */ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_BOARD_USBDEV_SERIALSTR # include #endif #include "cdcecm.h" #ifdef CONFIG_NET_CDCNCM /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* The low priority work queue is preferred. If it is not enabled, LPWORK * will be the same as HPWORK. NOTE: Use of the high priority work queue will * have a negative impact on interrupt handling latency and overall system * performance. This should be avoided. */ #define ETHWORK LPWORK /* CONFIG_CDCECM_NINTERFACES determines the number of physical interfaces * that will be supported. */ #ifndef CONFIG_CDCECM_NINTERFACES # define CONFIG_CDCECM_NINTERFACES 1 #endif /* TX timeout = 1 minute */ #define CDCNCM_TXTIMEOUT (60*CLK_TCK) #define CDCNCM_DGRAM_COMBINE_PERIOD 1 #define NTB_DEFAULT_IN_SIZE 16384 #define NTB_OUT_SIZE 16384 #define TX_MAX_NUM_DPE 32 /* NCM Transfer Block Parameter Structure */ #define CDC_NCM_NTB16_SUPPORTED (1 << 0) #define CDC_NCM_NTB32_SUPPORTED (1 << 1) #define FORMATS_SUPPORTED (CDC_NCM_NTB16_SUPPORTED | \ CDC_NCM_NTB32_SUPPORTED) #define CDC_NCM_NTH16_SIGN 0x484D434E /* NCMH */ #define CDC_NCM_NTH32_SIGN 0x686D636E /* ncmh */ #define CDC_NCM_NDP16_NOCRC_SIGN 0x304D434E /* NCM0 */ #define CDC_NCM_NDP32_NOCRC_SIGN 0x306D636E /* ncm0 */ #define CDC_MBIM_NDP16_NOCRC_SIGN 0x00535049 /* IPS : IPS0 for now */ #define CDC_MBIM_NDP32_NOCRC_SIGN 0x00737069 /* ips : ips0 for now */ #define INIT_NDP16_OPTS { \ .nthsign = CDC_NCM_NTH16_SIGN, \ .ndpsign = CDC_NCM_NDP16_NOCRC_SIGN, \ .nthsize = sizeof(struct cdc_ncm_nth16_s), \ .ndpsize = sizeof(struct usb_cdc_ncm_ndp16_s), \ .dpesize = sizeof(struct usb_cdc_ncm_dpe16_s), \ .ndpalign = 4, \ .dgramitemlen = 2, \ .blocklen = 2, \ .ndpindex = 2, \ .reserved1 = 0, \ .reserved2 = 0, \ .nextndpindex = 2, \ } #define INIT_NDP32_OPTS { \ .nthsign = CDC_NCM_NTH32_SIGN, \ .ndpsign = CDC_NCM_NDP32_NOCRC_SIGN, \ .nthsize = sizeof(struct cdc_ncm_nth32_s), \ .ndpsize = sizeof(struct usb_cdc_ncm_ndp32_s), \ .dpesize = sizeof(struct usb_cdc_ncm_dpe32_s), \ .ndpalign = 8, \ .dgramitemlen = 4, \ .blocklen = 4, \ .ndpindex = 4, \ .reserved1 = 2, \ .reserved2 = 4, \ .nextndpindex = 4, \ } #define CDC_NCM_NCAP_ETH_FILTER (1 << 0) #define NCAPS (CDC_NCM_NCAP_ETH_FILTER) #define NCM_ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask)) #define NCM_ALIGN(x, a) NCM_ALIGN_MASK((x), ((typeof(x))(a) - 1)) #define CDC_MBIM_DEVFORMAT "/dev/cdc-wdm%d" #define CDC_MBIM_DEVNAMELEN 16 #define CDC_MBIM_NPOLLWAITERS 2 #define CDCMBIM_MAX_CTRL_MESSAGE 0x1000 #ifndef CONFIG_NET_CDCMBIM # define cdcmbim_mkcfgdesc cdcncm_mkcfgdesc # define cdcmbim_mkstrdesc cdcncm_mkstrdesc # define cdcmbim_classobject cdcncm_classobject # define cdcmbim_uninitialize cdcncm_uninitialize # define CONFIG_CDCMBIM_PRODUCTSTR CONFIG_CDCNCM_PRODUCTSTR #endif /**************************************************************************** * Private Types ****************************************************************************/ enum ncm_notify_state_e { NCM_NOTIFY_NONE, /* Don't notify */ NCM_NOTIFY_CONNECT, /* Issue CONNECT next */ NCM_NOTIFY_SPEED, /* Issue SPEED_CHANGE next */ NCM_NOTIFY_RESPONSE_AVAILABLE, /* Issue RESPONSE_AVAILABLE next */ }; struct ndp_parser_opts_s { uint32_t nthsign; /* NCM Transfer Header signature */ uint32_t ndpsign; /* NCM Datagram Pointer signature */ uint_fast8_t nthsize; /* The length of NTH */ uint_fast8_t ndpsize; /* The length of NDP */ uint_fast8_t dpesize; /* The length of NDP Entry */ uint_fast8_t ndpalign; /* NDP alignment length */ uint_fast8_t dgramitemlen; /* The length of index or length */ uint_fast8_t blocklen; /* The length of current NTB */ uint_fast8_t ndpindex; /* The offset of first NDP in current NTB */ uint_fast8_t reserved1; /* Reserved1 */ uint_fast8_t reserved2; /* Reserved2 */ uint_fast8_t nextndpindex; /* The offset of next NDP in current NTB */ }; /* NTH: NCM Transfer Header * NDP: NCM Datagram Pointer * DPE: NCM Datagram Pointer Entry * +------------+ or +------------+ * | NTH | | NTH | * +------------+ +------------+ * | NDP | | Datagrams | * +------------+ +------------+ * | Datagrams | | NDP | * +------------+ +------------+ * * The layout of the NTB(NCM Transfer Block) structure in the NuttX system * is as follows: * +--------------------------+ * |NTH : nth sign | * | nth len | * | sequence | * | total len | * | ndp index |----+ * +--------------------------+ | * |NDP: ndp sign |<---+ * | ndp len | * | next ndp index| * | Datagram index|----+ * | Datagram len | | * | Datagram index|----|--+ * | Datagram len | | | * | Datagram index|----|--|--+ * | Datagram len | | | | * | 0 | Need to end with two zeros * | 0 | Need to end with two zeros * | ... [32] | | | | * +--------------------------+ | | | * |Datagrams: Datagram1 |<---+ | | * | pad | | | * | Datagram2 |<------+ | * | pad | | * | Datagram3 |<---------+ * +--------------------------+ */ begin_packed_struct struct cdc_ncm_nth16_s { uint32_t sign; uint16_t headerlen; uint16_t seq; uint16_t blocklen; uint16_t ndpindex; } end_packed_struct; begin_packed_struct struct cdc_ncm_nth32_s { uint32_t sign; uint16_t headerlen; uint16_t seq; uint32_t blocklen; uint32_t ndpindex; } end_packed_struct; /* 16-bit NCM Datagram Pointer Entry */ begin_packed_struct struct usb_cdc_ncm_dpe16_s { uint16_t index; uint16_t len; } end_packed_struct; /* 16-bit NCM Datagram Pointer Table */ begin_packed_struct struct usb_cdc_ncm_ndp16_s { uint32_t sign; uint16_t len; uint16_t nextndpindex; /* struct usb_cdc_ncm_dpe16_s dpe16[]; */ } end_packed_struct; /* 32-bit NCM Datagram Pointer Entry */ begin_packed_struct struct usb_cdc_ncm_dpe32_s { uint32_t index; uint32_t len; } end_packed_struct; /* 32-bit NCM Datagram Pointer Table */ begin_packed_struct struct usb_cdc_ncm_ndp32_s { uint32_t sign; uint16_t len; uint16_t reserved1; uint32_t nextndpindex; uint32_t reserved2; /* struct usb_cdc_ncm_dpe32_s dpe32[]; */ } end_packed_struct; begin_packed_struct struct usb_cdc_ncm_ntb_parameters_s { uint16_t len; uint16_t ntbsupported; uint32_t ntbinmaxsize; uint16_t ndpindivisor; uint16_t ndpinpayloadremainder; uint16_t ndpinalignment; uint16_t padding; uint32_t ntboutmaxsize; uint16_t ndpoutdivisor; uint16_t ndpoutpayloadremainder; uint16_t ndpoutalignment; uint16_t ntboutmaxdatagrams; } end_packed_struct; /* The cdcncm_driver_s encapsulates all state information for a single * hardware interface */ struct cdcncm_driver_s { /* USB CDC-NCM device */ struct usbdevclass_driver_s usbdev; /* USB device class vtable */ struct usbdev_devinfo_s devinfo; FAR struct usbdev_req_s *ctrlreq; /* Allocated control request */ FAR struct usbdev_req_s *notifyreq; /* Allocated norify request */ FAR struct usbdev_ep_s *epint; /* Interrupt IN endpoint */ FAR struct usbdev_ep_s *epbulkin; /* Bulk IN endpoint */ FAR struct usbdev_ep_s *epbulkout; /* Bulk OUT endpoint */ uint8_t config; /* Selected configuration number */ FAR struct usbdev_req_s *rdreq; /* Single read request */ bool rxpending; /* Packet available in rdreq */ FAR struct usbdev_req_s *wrreq; /* Single write request */ sem_t wrreq_idle; /* Is the wrreq available? */ bool txdone; /* Did a write request complete? */ enum ncm_notify_state_e notify; /* State of notify */ FAR const struct ndp_parser_opts_s *parseropts; /* Options currently used to parse NTB */ uint32_t ndpsign; /* NDP signature */ int dgramcount; /* The current tx cache dgram count */ FAR uint8_t *dgramaddr; /* The next tx cache dgram address */ bool isncm; /* true:NCM false:MBIM */ /* Network device */ bool bifup; /* true:ifup false:ifdown */ struct work_s irqwork; /* For deferring interrupt work * to the work queue */ struct work_s notifywork; /* For deferring notify work * to the work queue */ struct work_s delaywork; /* For deferring tx work * to the work queue */ /* This holds the information visible to the NuttX network */ struct netdev_lowerhalf_s dev; /* Interface understood by the * network */ netpkt_queue_t rx_queue; /* RX packet queue */ }; /* The cdcmbim_driver_s encapsulates all state information for a single * hardware interface */ struct cdcmbim_driver_s { struct cdcncm_driver_s ncmdriver; /* CDC/NCM driver structure, must keep first */ mutex_t lock; /* Used to maintain mutual exclusive access */ sem_t read_sem; /* Used to wait for data to be readable */ FAR struct pollfd *fds[CDC_MBIM_NPOLLWAITERS]; struct iob_queue_s rx_queue; /* RX control message queue */ struct iob_queue_s tx_queue; /* TX control message queue */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Control interface driver methods */ #ifdef CONFIG_NET_CDCMBIM static ssize_t cdcmbim_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t cdcmbim_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); static int cdcmbim_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup); #endif /* Network Device ***********************************************************/ /* Interrupt handling */ static void cdcncm_receive(FAR struct cdcncm_driver_s *priv); static void cdcncm_txdone(FAR struct cdcncm_driver_s *priv); static void cdcncm_interrupt_work(FAR void *arg); /* NuttX callback functions */ static int cdcncm_ifup(FAR struct netdev_lowerhalf_s *dev); static int cdcncm_ifdown(FAR struct netdev_lowerhalf_s *dev); static int cdcncm_send(struct netdev_lowerhalf_s *dev, netpkt_t *pkt); static FAR netpkt_t *cdcncm_recv(FAR struct netdev_lowerhalf_s *dev); #if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6) static int cdcncm_addmac(FAR struct netdev_lowerhalf_s *dev, FAR const uint8_t *mac); #ifdef CONFIG_NET_MCASTGROUP static int cdcncm_rmmac(FAR struct netdev_lowerhalf_s *dev, FAR const uint8_t *mac); #endif #endif #ifdef CONFIG_NETDEV_IOCTL static int cdcncm_ioctl(FAR struct netdev_lowerhalf_s *dev, int cmd, unsigned long arg); #endif static void cdcncm_notify_worker(FAR void *arg); /* USB Device Class Driver **************************************************/ /* USB Device Class methods */ static int cdcncm_bind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); static void cdcncm_unbind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); static int cdcncm_setup(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev, FAR const struct usb_ctrlreq_s *ctrl, FAR uint8_t *dataout, size_t outlen); static void cdcncm_disconnect(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); /* USB Device Class helpers */ static void cdcncm_ep0incomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req); static void cdcncm_intcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req); static void cdcncm_rdcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req); static void cdcncm_wrcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req); static int cdcncm_mkepdesc(int epidx, FAR struct usb_epdesc_s *epdesc, FAR struct usbdev_devinfo_s *devinfo, uint8_t speed); /**************************************************************************** * Private Data ****************************************************************************/ /* USB Device Class Methods */ static const struct usbdevclass_driverops_s g_usbdevops = { cdcncm_bind, cdcncm_unbind, cdcncm_setup, cdcncm_disconnect, NULL, NULL }; /* File operations for control channel */ #ifdef CONFIG_NET_CDCMBIM static const struct file_operations g_usbdevfops = { NULL, /* open */ NULL, /* close */ cdcmbim_read, /* read */ cdcmbim_write, /* write */ NULL, /* seek */ NULL, /* ioctl */ NULL, /* mmap */ NULL, /* truncate */ cdcmbim_poll /* poll */ }; #endif #ifndef CONFIG_CDCNCM_COMPOSITE static const struct usb_devdesc_s g_ncmdevdesc = { USB_SIZEOF_DEVDESC, USB_DESC_TYPE_DEVICE, { LSBYTE(0x0200), MSBYTE(0x0200) }, USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTO_NONE, CONFIG_CDCNCM_EP0MAXPACKET, { LSBYTE(CONFIG_CDCNCM_VENDORID), MSBYTE(CONFIG_CDCNCM_VENDORID) }, { LSBYTE(CONFIG_CDCNCM_PRODUCTID), MSBYTE(CONFIG_CDCNCM_PRODUCTID) }, { LSBYTE(CDCECM_VERSIONNO), MSBYTE(CDCECM_VERSIONNO) }, CDCECM_MANUFACTURERSTRID, CDCECM_PRODUCTSTRID, CDCECM_SERIALSTRID, CDCECM_NCONFIGS }; # ifdef CONFIG_NET_CDCMBIM static const struct usb_devdesc_s g_mbimdevdesc = { USB_SIZEOF_DEVDESC, USB_DESC_TYPE_DEVICE, { LSBYTE(0x0200), MSBYTE(0x0200) }, USB_CLASS_CDC, CDC_SUBCLASS_MBIM, CDC_PROTO_NONE, CONFIG_CDCNCM_EP0MAXPACKET, { LSBYTE(CONFIG_CDCNCM_VENDORID), MSBYTE(CONFIG_CDCNCM_VENDORID) }, { LSBYTE(CONFIG_CDCNCM_PRODUCTID), MSBYTE(CONFIG_CDCNCM_PRODUCTID) }, { LSBYTE(CDCECM_VERSIONNO), MSBYTE(CDCECM_VERSIONNO) }, CDCECM_MANUFACTURERSTRID, CDCECM_PRODUCTSTRID, CDCECM_SERIALSTRID, CDCECM_NCONFIGS }; # endif /* CONFIG_NET_CDCMBIM */ #endif /* CONFIG_CDCNCM_COMPOSITE */ static const struct ndp_parser_opts_s g_ndp16_opts = INIT_NDP16_OPTS; static const struct ndp_parser_opts_s g_ndp32_opts = INIT_NDP32_OPTS; static const struct usb_cdc_ncm_ntb_parameters_s g_ntbparameters = { .len = sizeof(g_ntbparameters), .ntbsupported = FORMATS_SUPPORTED, .ntbinmaxsize = NTB_DEFAULT_IN_SIZE, .ndpindivisor = 4, .ndpinpayloadremainder = 0, .ndpinalignment = 4, .ntboutmaxsize = NTB_OUT_SIZE, .ndpoutdivisor = 4, .ndpoutpayloadremainder = 0, .ndpoutalignment = 4, }; static const struct netdev_ops_s g_netops = { cdcncm_ifup, /* ifup */ cdcncm_ifdown, /* ifdown */ cdcncm_send, /* transmit */ cdcncm_recv, /* receive */ #ifdef CONFIG_NET_MCASTGROUP cdcncm_addmac, /* addmac */ cdcncm_rmmac, /* rmmac */ #endif #ifdef CONFIG_NETDEV_IOCTL cdcncm_ioctl, /* ioctl */ #endif }; /**************************************************************************** * Inline Functions ****************************************************************************/ /**************************************************************************** * Name: cdcncm_get * * Description: * Read size length data from address and increases the address by the * corresponding size * * Input Parameters: * address - Pointer to address * size - Size of data * * Returned Value: * The read value * ****************************************************************************/ static inline uint32_t cdcncm_get(FAR uint8_t **address, size_t size) { uint32_t value = 0; switch (size) { case 2: value = GETUINT16(*address); break; case 4: value = GETUINT32(*address); break; default: nerr("Wrong size cdcncm_get %zu\n", size); } *address += size; return value; } /**************************************************************************** * Name: cdcncm_put * * Description: * Write size length data to address and increases the address by the * corresponding size * * Input Parameters: * address - Pointer to address * size - Size of data * value - Value of data * * Returned Value: * None * ****************************************************************************/ static inline void cdcncm_put(FAR uint8_t **address, size_t size, uint32_t value) { switch (size) { case 2: PUTUINT16(*address, value); break; case 4: PUTUINT32(*address, value); break; default: uerr("Wrong cdcncm_put\n"); } *address += size; } /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * File operations for control channel ****************************************************************************/ #ifdef CONFIG_NET_CDCMBIM static ssize_t cdcmbim_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { FAR struct inode *inode; FAR struct cdcmbim_driver_s *self; FAR struct iob_s *iob; ssize_t ret; inode = filep->f_inode; self = inode->i_private; for (; ; ) { nxmutex_lock(&self->lock); if ((iob = iob_peek_queue(&self->rx_queue)) != NULL) { ret = iob_copyout((FAR uint8_t *)buffer, iob, buflen, 0); if (ret == iob->io_pktlen) { iob_remove_queue(&self->rx_queue); iob_free_chain(iob); } else if (ret > 0) { iob_trimhead_queue(&self->rx_queue, ret); } break; } else { if (filep->f_oflags & O_NONBLOCK) { ret = -EAGAIN; break; } nxmutex_unlock(&self->lock); nxsem_wait(&self->read_sem); } } nxmutex_unlock(&self->lock); return ret; } static ssize_t cdcmbim_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) { FAR struct inode *inode; FAR struct cdcmbim_driver_s *self; FAR struct iob_s *iob; int ret; inode = filep->f_inode; self = inode->i_private; if (buflen > CDCMBIM_MAX_CTRL_MESSAGE) { buflen = CDCMBIM_MAX_CTRL_MESSAGE; } nxmutex_lock(&self->lock); iob = iob_tryalloc(true); if (iob == NULL) { ret = -ENOMEM; goto errout; } ret = iob_copyin(iob, (FAR uint8_t *)buffer, buflen, 0, true); if (ret < 0) { iob_free_chain(iob); uerr("CDCMBIM copyin failed: %d\n", ret); goto errout; } ret = iob_tryadd_queue(iob, &self->tx_queue); if (ret < 0) { iob_free_chain(iob); uerr("CDCMBIM add tx queue failed: %d\n", ret); goto errout; } uinfo("wrote %zd bytes\n", buflen); DEBUGASSERT(self->ncmdriver.notify == NCM_NOTIFY_RESPONSE_AVAILABLE); work_queue(ETHWORK, &self->ncmdriver.notifywork, cdcncm_notify_worker, self, 0); errout: nxmutex_unlock(&self->lock); return ret < 0 ? ret : buflen; } /**************************************************************************** * Name: usbhost_poll * * Description: * Standard character driver poll method. * ****************************************************************************/ static int cdcmbim_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup) { FAR struct inode *inode; FAR struct cdcmbim_driver_s *self; int ret = OK; int i; DEBUGASSERT(fds); inode = filep->f_inode; self = inode->i_private; /* Make sure that we have exclusive access to the private data structure */ DEBUGASSERT(self); nxmutex_lock(&self->lock); if (setup) { /* This is a request to set up the poll. Find an available slot for * the poll structure reference */ for (i = 0; i < CDC_MBIM_NPOLLWAITERS; i++) { /* Find an available slot */ if (!self->fds[i]) { /* Bind the poll structure and this slot */ self->fds[i] = fds; fds->priv = &self->fds[i]; break; } } if (i >= CDC_MBIM_NPOLLWAITERS) { fds->priv = NULL; ret = -EBUSY; goto errout; } /* Should we immediately notify on any of the requested events? Notify * the POLLIN event if there is a buffered message. */ if (iob_get_queue_entry_count(&self->rx_queue)) { poll_notify(&fds, 1, POLLIN); } } else { /* This is a request to tear down the poll. */ FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv; DEBUGASSERT(slot); /* Remove all memory of the poll setup */ *slot = NULL; fds->priv = NULL; } errout: nxmutex_unlock(&self->lock); return ret; } #endif /**************************************************************************** * Name: cdcncm_transmit_format * * Description: * Format the data to be transmitted to the host in the format specified by * the NCM protocol (Network Control Model) and the NCM NTB (Network * Transfer Block) format. * * Input Parameters: * self - Reference to the driver state structure * pkt - Reference to the packet to be transmitted * * Returned Value: * None * ****************************************************************************/ static void cdcncm_transmit_format(FAR struct cdcncm_driver_s *self, FAR netpkt_t *pkt) { FAR const struct ndp_parser_opts_s *opts = self->parseropts; unsigned int dglen = netpkt_getdatalen(&self->dev, pkt); const int div = g_ntbparameters.ndpindivisor; const int rem = g_ntbparameters.ndpinpayloadremainder; const int dgramidxlen = 2 * opts->dgramitemlen; const int ndpalign = g_ntbparameters.ndpinalignment; FAR uint8_t *tmp; int ncblen; int ndpindex; ncblen = opts->nthsize; ndpindex = NCM_ALIGN(ncblen, ndpalign); if (self->dgramcount == 0) { /* Fill NCB */ tmp = self->wrreq->buf; memset(tmp, 0, ncblen); cdcncm_put(&tmp, 4, opts->nthsign); cdcncm_put(&tmp, 2, opts->nthsize); tmp += 2; /* Skip seq */ tmp += opts->blocklen; /* Skip block len */ cdcncm_put(&tmp, opts->ndpindex, ndpindex); self->dgramaddr = self->wrreq->buf + ndpindex + opts->ndpsize + (TX_MAX_NUM_DPE + 1) * dgramidxlen; self->dgramaddr = (FAR uint8_t *)NCM_ALIGN((uintptr_t)self->dgramaddr, div) + rem; /* Fill NDP */ tmp = self->wrreq->buf + ndpindex; cdcncm_put(&tmp, 4, self->ndpsign); tmp += 2 + opts->reserved1; cdcncm_put(&tmp, opts->nextndpindex, 0); } tmp = self->wrreq->buf + ndpindex + opts->ndpsize + self->dgramcount * dgramidxlen; cdcncm_put(&tmp, opts->dgramitemlen, self->dgramaddr - self->wrreq->buf); cdcncm_put(&tmp, opts->dgramitemlen, dglen); /* Fill IP packet */ netpkt_copyout(&self->dev, self->dgramaddr, pkt, dglen, 0); self->dgramaddr += dglen; self->dgramaddr = (FAR uint8_t *)NCM_ALIGN((uintptr_t)self->dgramaddr, div) + rem; self->dgramcount++; } /**************************************************************************** * Name: cdcncm_transmit_work * * Description: * Send NTB to the USB device for ethernet frame transmission * * Input Parameters: * arg - Reference to the driver state structure * * Returned Value: * None * ****************************************************************************/ static void cdcncm_transmit_work(FAR void *arg) { FAR struct cdcncm_driver_s *self = arg; FAR const struct ndp_parser_opts_s *opts = self->parseropts; FAR uint8_t *tmp; const int dgramidxlen = 2 * opts->dgramitemlen; const int ndpalign = g_ntbparameters.ndpinalignment; int ncblen; int ndpindex; int totallen; /* Wait until the USB device request for Ethernet frame transmissions * becomes available. */ while (nxsem_wait(&self->wrreq_idle) != OK) { } ncblen = opts->nthsize; ndpindex = NCM_ALIGN(ncblen, ndpalign); /* Fill NCB */ tmp = self->wrreq->buf + 8; /* Offset to block length */ totallen = self->dgramaddr - self->wrreq->buf; cdcncm_put(&tmp, opts->blocklen, totallen); /* Fill NDP */ tmp = self->wrreq->buf + ndpindex + 4; /* Offset to ndp length */ cdcncm_put(&tmp, 2, opts->ndpsize + (self->dgramcount + 1) * dgramidxlen); tmp += opts->reserved1 + opts->nextndpindex + opts->reserved2 + self->dgramcount * dgramidxlen; self->dgramcount = 0; cdcncm_put(&tmp, opts->dgramitemlen, 0); cdcncm_put(&tmp, opts->dgramitemlen, 0); self->wrreq->len = totallen; EP_SUBMIT(self->epbulkin, self->wrreq); } /**************************************************************************** * Name: cdcncm_packet_handler * * Description: * Sends a single complete packet to the protocol stack * * Input Parameters: * self - Reference to the driver state structure * * Returned Value: * None * ****************************************************************************/ static int cdcncm_packet_handler(FAR struct cdcncm_driver_s *self, FAR uint8_t *dgram, uint32_t dglen) { FAR netpkt_t *pkt = netpkt_alloc(&self->dev, NETPKT_RX); int ret = -ENOMEM; if (pkt == NULL) { return ret; } ret = netpkt_copyin(&self->dev, pkt, dgram, dglen, 0); if (ret < 0) { netpkt_free(&self->dev, pkt, NETPKT_RX); return ret; } ret = netpkt_tryadd_queue(pkt, &self->rx_queue); if (ret != 0) { netpkt_free(&self->dev, pkt, NETPKT_RX); } return ret; } /**************************************************************************** * Name: cdcncm_receive * * Description: * An interrupt was received indicating the availability of a new RX packet * * Input Parameters: * self - Reference to the driver state structure * * Returned Value: * None * ****************************************************************************/ static void cdcncm_receive(FAR struct cdcncm_driver_s *self) { FAR const struct ndp_parser_opts_s *opts = self->parseropts; FAR uint8_t *tmp = self->rdreq->buf; uint32_t ntbmax = g_ntbparameters.ntboutmaxsize; uint32_t blocklen; uint32_t ndplen; int ndpindex; int dgramcounter; /* Get signature */ if (GETUINT32(tmp) != opts->nthsign) { uerr("Wrong NTH SIGN, skblen %zu\n", self->rdreq->xfrd); return; } tmp += 4; /* Get header len */ if (GETUINT16(tmp) != opts->nthsize) { uerr("Wrong NTB headersize\n"); return; } tmp += 4; /* Skip header len and seq */ blocklen = cdcncm_get(&tmp, opts->blocklen); /* Get block len */ if (blocklen > ntbmax) { uerr("OUT size exceeded\n"); return; } ndpindex = cdcncm_get(&tmp, opts->ndpindex); do { uint32_t index; uint32_t dglen; if (((ndpindex % 4) != 0) || (ndpindex < opts->nthsize) || (ndpindex > (blocklen - opts->ndpsize))) { uerr("Bad index: %#X\n", ndpindex); return; } tmp = self->rdreq->buf + ndpindex; if (GETUINT32(tmp) != self->ndpsign) { uerr("Wrong NDP SIGN\n"); return; } tmp += 4; ndplen = cdcncm_get(&tmp, 2); if ((ndplen < opts->ndpsize + 2 * (opts->dgramitemlen * 2)) || (ndplen % opts->ndpalign != 0)) { uerr("Bad NDP length: %x\n", ndplen); return; } tmp += opts->reserved1; ndpindex = cdcncm_get(&tmp, opts->nextndpindex); tmp += opts->reserved2; ndplen -= opts->ndpsize; dgramcounter = 0; do { index = cdcncm_get(&tmp, opts->dgramitemlen); dglen = cdcncm_get(&tmp, opts->dgramitemlen); /* TODO: support CRC */ /* Check if the packet is a valid size for the network buffer * configuration. */ if (index == 0 || dglen == 0) { break; } dgramcounter++; /* Copy the data from the hardware to self->rx_queue. */ cdcncm_packet_handler(self, self->rdreq->buf + index, dglen); ndplen -= 2 * (opts->dgramitemlen); } while (ndplen > 2 * (opts->dgramitemlen)); } while (ndpindex); } /**************************************************************************** * Name: cdcncm_txdone * * Description: * An interrupt was received indicating that the last TX packet(s) is done * * Input Parameters: * priv - Reference to the driver state structure * * Returned Value: * None * ****************************************************************************/ static void cdcncm_txdone(FAR struct cdcncm_driver_s *priv) { /* In any event, poll the network for new TX data */ netdev_lower_txdone(&priv->dev); } /**************************************************************************** * Name: cdcncm_interrupt_work * * Description: * Perform interrupt related work from the worker thread * * Input Parameters: * arg - The argument passed when work_queue() was called. * * Returned Value: * OK on success * * Assumptions: * Runs on a worker thread. * ****************************************************************************/ static void cdcncm_interrupt_work(FAR void *arg) { FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)arg; irqstate_t flags; /* Check if we received an incoming packet, if so, call cdcncm_receive() */ if (self->rxpending) { cdcncm_receive(self); netdev_lower_rxready(&self->dev); flags = enter_critical_section(); self->rxpending = false; EP_SUBMIT(self->epbulkout, self->rdreq); leave_critical_section(flags); } /* Check if a packet transmission just completed. If so, call * cdcncm_txdone. This may disable further Tx interrupts if there * are no pending transmissions. */ flags = enter_critical_section(); if (self->txdone) { self->txdone = false; leave_critical_section(flags); cdcncm_txdone(self); } else { leave_critical_section(flags); } } /* NuttX netdev callback functions */ /**************************************************************************** * Name: cdcncm_ifup * * Description: * NuttX Callback: Bring up the Ethernet interface when an IP address is * provided * * Input Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * The network is locked. * ****************************************************************************/ static int cdcncm_ifup(FAR struct netdev_lowerhalf_s *dev) { FAR struct cdcncm_driver_s *priv = container_of(dev, struct cdcncm_driver_s, dev); #ifdef CONFIG_NET_IPv4 ninfo("Bringing up: %u.%u.%u.%u\n", ip4_addr1(dev->netdev.d_ipaddr), ip4_addr2(dev->netdev.d_ipaddr), ip4_addr3(dev->netdev.d_ipaddr), ip4_addr4(dev->netdev.d_ipaddr)); #endif #ifdef CONFIG_NET_IPv6 ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n", dev->netdev.d_ipv6addr[0], dev->netdev.d_ipv6addr[1], dev->netdev.d_ipv6addr[2], dev->netdev.d_ipv6addr[3], dev->netdev.d_ipv6addr[4], dev->netdev.d_ipv6addr[5], dev->netdev.d_ipv6addr[6], dev->netdev.d_ipv6addr[7]); #endif priv->bifup = true; return OK; } /**************************************************************************** * Name: cdcncm_ifdown * * Description: * NuttX Callback: Stop the interface. * * Input Parameters: * dev - Reference to the NuttX driver state structure * * Returned Value: * None * * Assumptions: * The network is locked. * ****************************************************************************/ static int cdcncm_ifdown(FAR struct netdev_lowerhalf_s *dev) { FAR struct cdcncm_driver_s *priv = container_of(dev, struct cdcncm_driver_s, dev); irqstate_t flags; /* Disable the Ethernet interrupt */ flags = enter_critical_section(); /* Put the EMAC in its reset, non-operational state. This should be * a known configuration that will guarantee the cdcncm_ifup() always * successfully brings the interface back up. */ /* Mark the device "down" */ priv->bifup = false; leave_critical_section(flags); return OK; } /**************************************************************************** * Name: cdcncm_send * * Description: * Transmit a packet through the USB interface * * Input Parameters: * dev - Reference to the NuttX netdev lowerhalf driver structure * pkt - The packet to be sent * * Returned Value: * OK on success * ****************************************************************************/ static int cdcncm_send(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt) { FAR struct cdcncm_driver_s *self; self = container_of(dev, struct cdcncm_driver_s, dev); cdcncm_transmit_format(self, pkt); netpkt_free(dev, pkt, NETPKT_TX); if ((self->wrreq->buf + NTB_OUT_SIZE - self->dgramaddr < self->dev.netdev.d_pktsize) || self->dgramcount >= TX_MAX_NUM_DPE) { work_cancel(ETHWORK, &self->delaywork); cdcncm_transmit_work(self); } else { work_queue(ETHWORK, &self->delaywork, cdcncm_transmit_work, self, MSEC2TICK(CDCNCM_DGRAM_COMBINE_PERIOD)); } return OK; } /**************************************************************************** * Name: cdcncm_recv * * Description: * Receive a packet from the USB interface * * Input Parameters: * dev - Reference to the NuttX netdev lowerhalf driver structure * * Returned Value: * The received packet, or NULL if no packet is available * ****************************************************************************/ static FAR netpkt_t *cdcncm_recv(FAR struct netdev_lowerhalf_s *dev) { FAR struct cdcncm_driver_s *self; FAR netpkt_t *pkt; self = container_of(dev, struct cdcncm_driver_s, dev); pkt = netpkt_remove_queue(&self->rx_queue); return pkt; } /**************************************************************************** * Name: cdcncm_addmac * * Description: * NuttX Callback: Add the specified MAC address to the hardware multicast * address filtering * * Input Parameters: * dev - Reference to the NuttX driver state structure * mac - The MAC address to be added * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ #if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6) static int cdcncm_addmac(FAR struct netdev_lowerhalf_s *dev, FAR const uint8_t *mac) { return OK; } #endif /**************************************************************************** * Name: cdcncm_rmmac * * Description: * NuttX Callback: Remove the specified MAC address from the hardware * multicast address filtering * * Input Parameters: * dev - Reference to the NuttX driver state structure * mac - The MAC address to be removed * * Returned Value: * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ #ifdef CONFIG_NET_MCASTGROUP static int cdcncm_rmmac(FAR struct netdev_lowerhalf_s *dev, FAR const uint8_t *mac) { return OK; } #endif /**************************************************************************** * Name: cdcncm_ioctl * * Description: * Handle network IOCTL commands directed to this device. * * Input Parameters: * dev - Reference to the NuttX driver state structure * cmd - The IOCTL command * arg - The argument for the IOCTL command * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * The network is locked. * ****************************************************************************/ #ifdef CONFIG_NETDEV_IOCTL static int cdcncm_ioctl(FAR struct netdev_lowerhalf_s *dev, int cmd, unsigned long arg) { return -ENOTTY; } #endif /**************************************************************************** * USB Device Class Helpers ****************************************************************************/ /**************************************************************************** * Name: cdcncm_ep0incomplete * * Description: * Handle completion of EP0 control operations * ****************************************************************************/ static void cdcncm_ep0incomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req) { if (req->result || req->xfrd != req->len) { uerr("result: %hd, xfrd: %zu\n", req->result, req->xfrd); } } /**************************************************************************** * Name: cdcncm_intcomplete * * Description: * Handle completion of interrupt write request. This function probably * executes in the context of an interrupt handler. * ****************************************************************************/ static void cdcncm_intcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req) { FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)ep->priv; if (req->result || req->xfrd != req->len) { uerr("result: %hd, xfrd: %zu\n", req->result, req->xfrd); } if (self->notify != NCM_NOTIFY_NONE) { cdcncm_notify_worker(self); } else if (!self->isncm) { /* After the NIC information is synchronized, subsequent * notifications are all related to the mbim control. */ self->notify = NCM_NOTIFY_RESPONSE_AVAILABLE; } } /**************************************************************************** * Name: cdcncm_rdcomplete * * Description: * Handle completion of read request on the bulk OUT endpoint. * ****************************************************************************/ static void cdcncm_rdcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req) { FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)ep->priv; uinfo("buf: %p, flags 0x%hhx, len %zu, xfrd %zu, result %hd\n", req->buf, req->flags, req->len, req->xfrd, req->result); switch (req->result) { case 0: /* Normal completion */ { DEBUGASSERT(!self->rxpending); self->rxpending = true; work_queue(ETHWORK, &self->irqwork, cdcncm_interrupt_work, self, 0); } break; case -ESHUTDOWN: /* Disconnection */ break; default: /* Some other error occurred */ { uerr("req->result: %hd\n", req->result); EP_SUBMIT(self->epbulkout, self->rdreq); } break; } } /**************************************************************************** * Name: cdcncm_wrcomplete * * Description: * Handle completion of write request. This function probably executes * in the context of an interrupt handler. * ****************************************************************************/ static void cdcncm_wrcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req) { FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)ep->priv; int rc; uinfo("buf: %p, flags 0x%hhx, len %zu, xfrd %zu, result %hd\n", req->buf, req->flags, req->len, req->xfrd, req->result); /* The single USB device write request is available for upcoming * transmissions again. */ rc = nxsem_post(&self->wrreq_idle); if (rc != OK) { nerr("nxsem_post failed! rc: %d\n", rc); } /* Inform the network layer that an Ethernet frame was transmitted. */ self->txdone = true; work_queue(ETHWORK, &self->irqwork, cdcncm_interrupt_work, self, 0); } /**************************************************************************** * Name: cdcncm_resetconfig * * Description: * Mark the device as not configured and disable all endpoints. * ****************************************************************************/ static void cdcncm_resetconfig(FAR struct cdcncm_driver_s *self) { /* Are we configured? */ if (self->config != CDCECM_CONFIGID_NONE) { /* Yes.. but not anymore */ self->config = CDCECM_CONFIGID_NONE; /* Inform the networking layer that the link is down */ cdcncm_ifdown(&self->dev); /* Disable endpoints. This should force completion of all pending * transfers. */ EP_DISABLE(self->epint); EP_DISABLE(self->epbulkin); EP_DISABLE(self->epbulkout); self->notify = NCM_NOTIFY_SPEED; } self->parseropts = &g_ndp16_opts; self->ndpsign = self->isncm ? self->parseropts->ndpsign : CDC_MBIM_NDP16_NOCRC_SIGN; } /**************************************************************************** * Name: cdcncm_setconfig * * Set the device configuration by allocating and configuring endpoints and * by allocating and queue read and write requests. * ****************************************************************************/ static int cdcncm_setconfig(FAR struct cdcncm_driver_s *self, uint8_t config) { struct usb_ss_epdesc_s epdesc; int ret; if (config == self->config) { return OK; } cdcncm_resetconfig(self); if (config == CDCECM_CONFIGID_NONE) { return OK; } if (config != CDCECM_CONFIGID) { return -EINVAL; } cdcncm_mkepdesc(CDCNCM_EP_INTIN_IDX, &epdesc.epdesc, &self->devinfo, self->usbdev.speed); ret = EP_CONFIGURE(self->epint, &epdesc.epdesc, false); if (ret < 0) { goto error; } self->epint->priv = self; cdcncm_mkepdesc(CDCNCM_EP_BULKIN_IDX, &epdesc.epdesc, &self->devinfo, self->usbdev.speed); ret = EP_CONFIGURE(self->epbulkin, &epdesc.epdesc, false); if (ret < 0) { goto error; } self->epbulkin->priv = self; cdcncm_mkepdesc(CDCNCM_EP_BULKOUT_IDX, &epdesc.epdesc, &self->devinfo, self->usbdev.speed); ret = EP_CONFIGURE(self->epbulkout, &epdesc.epdesc, true); if (ret < 0) { goto error; } self->epbulkout->priv = self; /* Queue read requests in the bulk OUT endpoint */ DEBUGASSERT(!self->rxpending); self->rdreq->callback = cdcncm_rdcomplete, ret = EP_SUBMIT(self->epbulkout, self->rdreq); if (ret != OK) { uerr("EP_SUBMIT failed. ret %d\n", ret); goto error; } /* We are successfully configured */ self->config = config; /* Set client's MAC address */ memcpy(self->dev.netdev.d_mac.ether.ether_addr_octet, "\x00\xe0\xde\xad\xbe\xef", IFHWADDRLEN); /* Report link up to networking layer */ if (cdcncm_ifup(&self->dev) == OK) { self->dev.netdev.d_flags |= IFF_UP; } return OK; error: cdcncm_resetconfig(self); return ret; } /**************************************************************************** * Name: cdcncm_notify * ****************************************************************************/ static int cdcncm_notify(FAR struct cdcncm_driver_s *self) { FAR struct usb_ctrlreq_s *req = (FAR struct usb_ctrlreq_s *)self->notifyreq->buf; int ret = 0; switch (self->notify) { case NCM_NOTIFY_NONE: return ret; case NCM_NOTIFY_CONNECT: /* Notifying the host of the NIC modification status */ req->req = NCM_NETWORK_CONNECTION; req->value[0] = LSBYTE(IFF_IS_RUNNING(self->dev.netdev.d_flags)); req->value[1] = MSBYTE(IFF_IS_RUNNING(self->dev.netdev.d_flags)); req->len[0] = 0; req->len[1] = 0; ret = sizeof(*req); self->notify = NCM_NOTIFY_NONE; break; case NCM_NOTIFY_SPEED: { FAR uint32_t *data; req->req = NCM_SPEED_CHANGE; req->value[0] = LSBYTE(0); req->value[1] = MSBYTE(0); req->len[0] = LSBYTE(8); req->len[1] = MSBYTE(8); /* SPEED_CHANGE data is up/down speeds in bits/sec */ data = (FAR uint32_t *)(self->notifyreq->buf + sizeof(*req)); data[0] = self->usbdev.speed == USB_SPEED_HIGH ? CDCECM_HIGH_BITRATE : CDCECM_LOW_BITRATE; data[1] = data[0]; ret = sizeof(*req) + 8; self->notify = NCM_NOTIFY_CONNECT; break; } case NCM_NOTIFY_RESPONSE_AVAILABLE: { req->req = MBIM_RESPONSE_AVAILABLE; req->value[0] = LSBYTE(0); req->value[1] = MSBYTE(0); req->len[0] = LSBYTE(0); req->len[1] = MSBYTE(0); ret = sizeof(*req); self->notify = NCM_NOTIFY_NONE; break; } } req->type = 0xa1; req->index[0] = LSBYTE(self->devinfo.ifnobase); req->index[1] = MSBYTE(self->devinfo.ifnobase); return ret; } /**************************************************************************** * Name: cdcncm_notify_worker * ****************************************************************************/ static void cdcncm_notify_worker(FAR void *arg) { FAR struct cdcncm_driver_s *self = arg; int ret; ret = cdcncm_notify(self); if (ret > 0) { FAR struct usbdev_req_s *notifyreq = self->notifyreq; notifyreq->len = ret; notifyreq->flags = USBDEV_REQFLAGS_NULLPKT; EP_SUBMIT(self->epint, notifyreq); } } /**************************************************************************** * Name: cdcncm_setinterface * ****************************************************************************/ static int cdcncm_setinterface(FAR struct cdcncm_driver_s *self, uint16_t interface, uint16_t altsetting) { if (interface == self->devinfo.ifnobase + 1) { if (altsetting) { self->notify = NCM_NOTIFY_SPEED; } netdev_lower_carrier_on(&self->dev); work_queue(ETHWORK, &self->notifywork, cdcncm_notify_worker, self, MSEC2TICK(100)); } else { uerr("invailid interface %d\n", interface); return -EINVAL; } return OK; } /**************************************************************************** * Name: cdcnm_mkstrdesc * * Description: * Construct a string descriptor * ****************************************************************************/ static int cdcnm_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc, bool isncm) { FAR uint8_t *data = (FAR uint8_t *)(strdesc + 1); FAR const char *str; int len; int ndata; int i; switch (id) { #ifndef CONFIG_CDCNCM_COMPOSITE case 0: { /* Descriptor 0 is the language id */ strdesc->len = 4; strdesc->type = USB_DESC_TYPE_STRING; data[0] = LSBYTE(CDCECM_STR_LANGUAGE); data[1] = MSBYTE(CDCECM_STR_LANGUAGE); return 4; } case CDCECM_MANUFACTURERSTRID: str = CONFIG_CDCNCM_VENDORSTR; break; case CDCECM_PRODUCTSTRID: str = isncm ? CONFIG_CDCNCM_PRODUCTSTR : CONFIG_CDCMBIM_PRODUCTSTR; break; case CDCECM_SERIALSTRID: #ifdef CONFIG_BOARD_USBDEV_SERIALSTR str = board_usbdev_serialstr(); #else str = "0"; #endif break; case CDCECM_CONFIGSTRID: str = "Default"; break; #endif case CDCECM_MACSTRID: str = "020000112233"; break; default: uerr("Unknown string descriptor index: %d\n", id); return -EINVAL; } /* The string is utf16-le. The poor man's utf-8 to utf16-le * conversion below will only handle 7-bit en-us ascii */ len = strlen(str); if (len > (CDCECM_MAXSTRLEN / 2)) { len = (CDCECM_MAXSTRLEN / 2); } for (i = 0, ndata = 0; i < len; i++, ndata += 2) { data[ndata] = str[i]; data[ndata + 1] = 0; } strdesc->len = ndata + 2; strdesc->type = USB_DESC_TYPE_STRING; return strdesc->len; } /**************************************************************************** * Name: cdcncm_mkstrdesc * * Description: * Construct a string descriptor * ****************************************************************************/ static int cdcncm_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc) { return cdcnm_mkstrdesc(id, strdesc, true); } /**************************************************************************** * Name: cdcmbim_mkstrdesc * * Description: * Construct a string descriptor * ****************************************************************************/ #ifdef CONFIG_NET_CDCMBIM static int cdcmbim_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc) { return cdcnm_mkstrdesc(id, strdesc, false); } #endif /**************************************************************************** * Name: cdcecm_mkepcompdesc * * Description: * Construct the endpoint companion descriptor * ****************************************************************************/ #ifdef CONFIG_USBDEV_SUPERSPEED static void cdcncm_mkepcompdesc(int epidx, FAR struct usb_ss_epcompdesc_s *epcompdesc) { switch (epidx) { case CDCNCM_EP_INTIN_IDX: /* Interrupt IN endpoint */ { epcompdesc->len = USB_SIZEOF_SS_EPCOMPDESC; /* Descriptor length */ epcompdesc->type = USB_DESC_TYPE_ENDPOINT_COMPANION; /* Descriptor type */ if (CONFIG_CDCNCM_EPINTIN_MAXBURST >= USB_SS_INT_EP_MAXBURST) { epcompdesc->mxburst = USB_SS_INT_EP_MAXBURST - 1; } else { epcompdesc->mxburst = CONFIG_CDCNCM_EPINTIN_MAXBURST; } epcompdesc->attr = 0; epcompdesc->wbytes[0] = LSBYTE((epcompdesc->mxburst + 1) * CONFIG_CDCNCM_EPINTIN_SSSIZE); epcompdesc->wbytes[1] = MSBYTE((epcompdesc->mxburst + 1) * CONFIG_CDCNCM_EPINTIN_SSSIZE); } break; case CDCNCM_EP_BULKOUT_IDX: { epcompdesc->len = USB_SIZEOF_SS_EPCOMPDESC; /* Descriptor length */ epcompdesc->type = USB_DESC_TYPE_ENDPOINT_COMPANION; /* Descriptor type */ if (CONFIG_CDCNCM_EPBULKOUT_MAXBURST >= USB_SS_BULK_EP_MAXBURST) { epcompdesc->mxburst = USB_SS_BULK_EP_MAXBURST - 1; } else { epcompdesc->mxburst = CONFIG_CDCNCM_EPBULKOUT_MAXBURST; } if (CONFIG_CDCNCM_EPBULKOUT_MAXSTREAM > USB_SS_BULK_EP_MAXSTREAM) { epcompdesc->attr = USB_SS_BULK_EP_MAXSTREAM; } else { epcompdesc->attr = CONFIG_CDCNCM_EPBULKOUT_MAXSTREAM; } epcompdesc->wbytes[0] = 0; epcompdesc->wbytes[1] = 0; } break; case CDCNCM_EP_BULKIN_IDX: { epcompdesc->len = USB_SIZEOF_SS_EPCOMPDESC; /* Descriptor length */ epcompdesc->type = USB_DESC_TYPE_ENDPOINT_COMPANION; /* Descriptor type */ if (CONFIG_CDCNCM_EPBULKIN_MAXBURST >= USB_SS_BULK_EP_MAXBURST) { epcompdesc->mxburst = USB_SS_BULK_EP_MAXBURST - 1; } else { epcompdesc->mxburst = CONFIG_CDCNCM_EPBULKIN_MAXBURST; } if (CONFIG_CDCNCM_EPBULKIN_MAXSTREAM > USB_SS_BULK_EP_MAXSTREAM) { epcompdesc->attr = USB_SS_BULK_EP_MAXSTREAM; } else { epcompdesc->attr = CONFIG_CDCNCM_EPBULKIN_MAXSTREAM; } epcompdesc->wbytes[0] = 0; epcompdesc->wbytes[1] = 0; } break; default: break; } } #endif /**************************************************************************** * Name: cdcncm_mkepdesc * * Description: * Construct the endpoint descriptor * ****************************************************************************/ static int cdcncm_mkepdesc(int epidx, FAR struct usb_epdesc_s *epdesc, FAR struct usbdev_devinfo_s *devinfo, uint8_t speed) { uint16_t intin_mxpktsz = CONFIG_CDCNCM_EPINTIN_FSSIZE; uint16_t bulkout_mxpktsz = CONFIG_CDCNCM_EPBULKOUT_FSSIZE; uint16_t bulkin_mxpktsz = CONFIG_CDCNCM_EPBULKIN_FSSIZE; int len = sizeof(struct usb_epdesc_s); #ifdef CONFIG_USBDEV_SUPERSPEED if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) { /* Maximum packet size (super speed) */ intin_mxpktsz = CONFIG_CDCNCM_EPINTIN_SSSIZE; bulkout_mxpktsz = CONFIG_CDCNCM_EPBULKOUT_SSSIZE; bulkin_mxpktsz = CONFIG_CDCNCM_EPBULKIN_SSSIZE; len += sizeof(struct usb_ss_epcompdesc_s); } else #endif #ifdef CONFIG_USBDEV_DUALSPEED if (speed == USB_SPEED_HIGH) { /* Maximum packet size (high speed) */ intin_mxpktsz = CONFIG_CDCNCM_EPINTIN_HSSIZE; bulkout_mxpktsz = CONFIG_CDCNCM_EPBULKOUT_HSSIZE; bulkin_mxpktsz = CONFIG_CDCNCM_EPBULKIN_HSSIZE; } #else UNUSED(speed); #endif if (epdesc == NULL) { return len; } epdesc->len = USB_SIZEOF_EPDESC; /* Descriptor length */ epdesc->type = USB_DESC_TYPE_ENDPOINT; /* Descriptor type */ switch (epidx) { case CDCNCM_EP_INTIN_IDX: /* Interrupt IN endpoint */ { epdesc->addr = USB_DIR_IN | devinfo->epno[CDCNCM_EP_INTIN_IDX]; epdesc->attr = USB_EP_ATTR_XFER_INT; epdesc->mxpacketsize[0] = LSBYTE(intin_mxpktsz); epdesc->mxpacketsize[1] = MSBYTE(intin_mxpktsz); epdesc->interval = 5; } break; case CDCNCM_EP_BULKIN_IDX: { epdesc->addr = USB_DIR_IN | devinfo->epno[CDCNCM_EP_BULKIN_IDX]; epdesc->attr = USB_EP_ATTR_XFER_BULK; epdesc->mxpacketsize[0] = LSBYTE(bulkin_mxpktsz); epdesc->mxpacketsize[1] = MSBYTE(bulkin_mxpktsz); epdesc->interval = 0; } break; case CDCNCM_EP_BULKOUT_IDX: { epdesc->addr = USB_DIR_OUT | devinfo->epno[CDCNCM_EP_BULKOUT_IDX]; epdesc->attr = USB_EP_ATTR_XFER_BULK; epdesc->mxpacketsize[0] = LSBYTE(bulkout_mxpktsz); epdesc->mxpacketsize[1] = MSBYTE(bulkout_mxpktsz); epdesc->interval = 0; } break; default: DEBUGPANIC(); } #ifdef CONFIG_USBDEV_SUPERSPEED if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) { epdesc++; cdcncm_mkepcompdesc(epidx, (FAR struct usb_ss_epcompdesc_s *)epdesc); } #endif return len; } /**************************************************************************** * Name: cdcnm_mkcfgdesc * * Description: * Construct the config descriptor * ****************************************************************************/ static int16_t cdcnm_mkcfgdesc(FAR uint8_t *desc, FAR struct usbdev_devinfo_s *devinfo, uint8_t speed, uint8_t type, bool isncm) { FAR struct usb_cfgdesc_s *cfgdesc = NULL; int16_t len = 0; int ret; /* Check for switches between high and full speed */ if (type == USB_DESC_TYPE_OTHERSPEEDCONFIG && speed < USB_SPEED_SUPER) { speed = speed == USB_SPEED_HIGH ? USB_SPEED_FULL : USB_SPEED_HIGH; } #ifndef CONFIG_CDCNCM_COMPOSITE if (desc) { cfgdesc = (FAR struct usb_cfgdesc_s *)desc; cfgdesc->len = USB_SIZEOF_CFGDESC; cfgdesc->type = type; cfgdesc->ninterfaces = CDCECM_NINTERFACES; cfgdesc->cfgvalue = CDCECM_CONFIGID; cfgdesc->icfg = devinfo->strbase + CDCECM_CONFIGSTRID; cfgdesc->attr = USB_CONFIG_ATTR_ONE | CDCECM_SELFPOWERED | CDCECM_REMOTEWAKEUP; cfgdesc->mxpower = (CONFIG_USBDEV_MAXPOWER + 1) / 2; desc += USB_SIZEOF_CFGDESC; } len += USB_SIZEOF_CFGDESC; #elif defined(CONFIG_COMPOSITE_IAD) /* Interface association descriptor */ if (desc) { FAR struct usb_iaddesc_s *iaddesc; iaddesc = (FAR struct usb_iaddesc_s *)desc; iaddesc->len = USB_SIZEOF_IADDESC; /* Descriptor length */ iaddesc->type = USB_DESC_TYPE_INTERFACEASSOCIATION; /* Descriptor type */ iaddesc->firstif = devinfo->ifnobase; /* Number of first interface of the function */ iaddesc->nifs = devinfo->ninterfaces; /* Number of interfaces associated with the function */ iaddesc->classid = USB_CLASS_CDC; /* Class code */ iaddesc->subclass = isncm ? CDC_SUBCLASS_NCM : CDC_SUBCLASS_MBIM; /* Sub-class code */ iaddesc->protocol = CDC_PROTO_NONE; /* Protocol code */ iaddesc->ifunction = 0; /* Index to string identifying the function */ desc += USB_SIZEOF_IADDESC; } len += USB_SIZEOF_IADDESC; #endif /* Communications Class Interface */ if (desc) { FAR struct usb_ifdesc_s *ifdesc; ifdesc = (FAR struct usb_ifdesc_s *)desc; ifdesc->len = USB_SIZEOF_IFDESC; ifdesc->type = USB_DESC_TYPE_INTERFACE; ifdesc->ifno = devinfo->ifnobase; ifdesc->alt = 0; ifdesc->neps = 1; ifdesc->classid = USB_CLASS_CDC; ifdesc->subclass = isncm ? CDC_SUBCLASS_NCM : CDC_SUBCLASS_MBIM; ifdesc->protocol = CDC_PROTO_NONE; ifdesc->iif = 0; desc += USB_SIZEOF_IFDESC; } len += USB_SIZEOF_IFDESC; if (desc) { FAR struct cdc_hdr_funcdesc_s *hdrdesc; hdrdesc = (FAR struct cdc_hdr_funcdesc_s *)desc; hdrdesc->size = SIZEOF_HDR_FUNCDESC; hdrdesc->type = USB_DESC_TYPE_CSINTERFACE; hdrdesc->subtype = CDC_DSUBTYPE_HDR; hdrdesc->cdc[0] = LSBYTE(0x0110); hdrdesc->cdc[1] = MSBYTE(0x0110); desc += SIZEOF_HDR_FUNCDESC; } len += SIZEOF_HDR_FUNCDESC; if (desc) { FAR struct cdc_union_funcdesc_s *uniondesc; uniondesc = (FAR struct cdc_union_funcdesc_s *)desc; uniondesc->size = SIZEOF_UNION_FUNCDESC(1); uniondesc->type = USB_DESC_TYPE_CSINTERFACE; uniondesc->subtype = CDC_DSUBTYPE_UNION; uniondesc->master = devinfo->ifnobase; uniondesc->slave[0] = devinfo->ifnobase + 1; desc += SIZEOF_UNION_FUNCDESC(1); } len += SIZEOF_UNION_FUNCDESC(1); if (desc) { FAR struct cdc_ecm_funcdesc_s *ecmdesc; ecmdesc = (FAR struct cdc_ecm_funcdesc_s *)desc; ecmdesc->size = SIZEOF_ECM_FUNCDESC; ecmdesc->type = USB_DESC_TYPE_CSINTERFACE; ecmdesc->subtype = CDC_DSUBTYPE_ECM; ecmdesc->mac = devinfo->strbase + CDCECM_MACSTRID; ecmdesc->stats[0] = 0; ecmdesc->stats[1] = 0; ecmdesc->stats[2] = 0; ecmdesc->stats[3] = 0; ecmdesc->maxseg[0] = LSBYTE(CONFIG_NET_ETH_PKTSIZE); ecmdesc->maxseg[1] = MSBYTE(CONFIG_NET_ETH_PKTSIZE); ecmdesc->nmcflts[0] = LSBYTE(0); ecmdesc->nmcflts[1] = MSBYTE(0); ecmdesc->npwrflts = 0; desc += SIZEOF_ECM_FUNCDESC; } len += SIZEOF_ECM_FUNCDESC; if (isncm) { if (desc) { FAR struct cdc_ncm_funcdesc_s *ncmdesc; ncmdesc = (FAR struct cdc_ncm_funcdesc_s *)desc; ncmdesc->size = SIZEOF_NCM_FUNCDESC; ncmdesc->type = USB_DESC_TYPE_CSINTERFACE; ncmdesc->subtype = CDC_DSUBTYPE_NCM; ncmdesc->version[0] = LSBYTE(CDCECM_VERSIONNO); ncmdesc->version[1] = MSBYTE(CDCECM_VERSIONNO); ncmdesc->netcaps = NCAPS; desc += SIZEOF_NCM_FUNCDESC; } len += SIZEOF_NCM_FUNCDESC; } else { if (desc) { FAR struct cdc_mbim_funcdesc_s *mbimdesc; mbimdesc = (FAR struct cdc_mbim_funcdesc_s *)desc; mbimdesc->size = SIZEOF_MBIM_FUNCDESC; mbimdesc->type = USB_DESC_TYPE_CSINTERFACE; mbimdesc->subtype = CDC_DSUBTYPE_MBIM; mbimdesc->version[0] = LSBYTE(CDCECM_VERSIONNO); mbimdesc->version[1] = MSBYTE(CDCECM_VERSIONNO); mbimdesc->maxctrlmsg[0] = LSBYTE(CDCMBIM_MAX_CTRL_MESSAGE); mbimdesc->maxctrlmsg[1] = MSBYTE(CDCMBIM_MAX_CTRL_MESSAGE); mbimdesc->numfilter = 0x20; mbimdesc->maxfiltersize = 0x80; mbimdesc->maxsegmentsize[0] = LSBYTE(0x800); mbimdesc->maxsegmentsize[1] = LSBYTE(0x800); mbimdesc->netcaps = 0x20; desc += SIZEOF_MBIM_FUNCDESC; } len += SIZEOF_MBIM_FUNCDESC; } ret = cdcncm_mkepdesc(CDCNCM_EP_INTIN_IDX, (FAR struct usb_epdesc_s *)desc, devinfo, speed); if (desc) { desc += ret; } len += ret; /* Data Class Interface */ if (desc) { FAR struct usb_ifdesc_s *ifdesc; ifdesc = (FAR struct usb_ifdesc_s *)desc; ifdesc->len = USB_SIZEOF_IFDESC; ifdesc->type = USB_DESC_TYPE_INTERFACE; ifdesc->ifno = devinfo->ifnobase + 1; ifdesc->alt = 0; ifdesc->neps = 0; ifdesc->classid = USB_CLASS_CDC_DATA; ifdesc->subclass = 0; ifdesc->protocol = isncm ? CDC_DATA_PROTO_NCMNTB : CDC_DATA_PROTO_MBIMNTB; ifdesc->iif = 0; desc += USB_SIZEOF_IFDESC; } len += USB_SIZEOF_IFDESC; if (desc) { FAR struct usb_ifdesc_s *ifdesc; ifdesc = (FAR struct usb_ifdesc_s *)desc; ifdesc->len = USB_SIZEOF_IFDESC; ifdesc->type = USB_DESC_TYPE_INTERFACE; ifdesc->ifno = devinfo->ifnobase + 1; ifdesc->alt = 1; ifdesc->neps = 2; ifdesc->classid = USB_CLASS_CDC_DATA; ifdesc->subclass = 0; ifdesc->protocol = isncm ? CDC_DATA_PROTO_NCMNTB : CDC_DATA_PROTO_MBIMNTB; ifdesc->iif = 0; desc += USB_SIZEOF_IFDESC; } len += USB_SIZEOF_IFDESC; ret = cdcncm_mkepdesc(CDCNCM_EP_BULKIN_IDX, (FAR struct usb_epdesc_s *)desc, devinfo, speed); if (desc) { desc += ret; } len += ret; ret = cdcncm_mkepdesc(CDCNCM_EP_BULKOUT_IDX, (FAR struct usb_epdesc_s *)desc, devinfo, speed); if (desc) { desc += ret; } len += ret; if (cfgdesc) { cfgdesc->totallen[0] = LSBYTE(len); cfgdesc->totallen[1] = MSBYTE(len); } DEBUGASSERT(len <= CDCECM_MXDESCLEN); return len; } /**************************************************************************** * Name: cdcncm_mkcfgdesc * * Description: * Construct the config descriptor * ****************************************************************************/ static int16_t cdcncm_mkcfgdesc(FAR uint8_t *desc, FAR struct usbdev_devinfo_s *devinfo, uint8_t speed, uint8_t type) { return cdcnm_mkcfgdesc(desc, devinfo, speed, type, true); } # ifdef CONFIG_NET_CDCMBIM static int16_t cdcmbim_mkcfgdesc(FAR uint8_t *desc, FAR struct usbdev_devinfo_s *devinfo, uint8_t speed, uint8_t type) { return cdcnm_mkcfgdesc(desc, devinfo, speed, type, false); } # endif /**************************************************************************** * Name: cdcncm_getdescriptor * * Description: * Copy the USB CDC-NCM Device USB Descriptor of a given Type and a given * Index into the provided Descriptor Buffer. * * Input Parameter: * drvr - The USB Device Fuzzer Driver instance. * type - The Type of USB Descriptor requested. * index - The Index of the USB Descriptor requested. * desc - The USB Descriptor is copied into this buffer, which must be at * least CDCECM_MXDESCLEN bytes wide. * * Returned Value: * The size in bytes of the requested USB Descriptor or a negated errno in * case of failure. * ****************************************************************************/ static int cdcncm_getdescriptor(FAR struct cdcncm_driver_s *self, uint8_t type, uint8_t index, FAR void *desc) { switch (type) { #ifndef CONFIG_CDCNCM_COMPOSITE case USB_DESC_TYPE_DEVICE: if (self->isncm) { return usbdev_copy_devdesc(desc, &g_ncmdevdesc, self->usbdev.speed); } # ifdef CONFIG_NET_CDCMBIM else { memcpy(desc, &g_mbimdevdesc, sizeof(g_mbimdevdesc)); return sizeof(g_mbimdevdesc); } # endif break; #endif #ifdef CONFIG_USBDEV_DUALSPEED case USB_DESC_TYPE_OTHERSPEEDCONFIG: #endif /* CONFIG_USBDEV_DUALSPEED */ case USB_DESC_TYPE_CONFIG: return cdcncm_mkcfgdesc((FAR uint8_t *)desc, &self->devinfo, self->usbdev.speed, type); case USB_DESC_TYPE_STRING: return cdcncm_mkstrdesc(index, (FAR struct usb_strdesc_s *)desc); default: uerr("Unsupported descriptor type: 0x%02hhx\n", type); break; } return -ENOTSUP; } /**************************************************************************** * USB Device Class Methods ****************************************************************************/ /**************************************************************************** * Name: cdcncm_bind * * Description: * Invoked when the driver is bound to an USB device * ****************************************************************************/ static int cdcncm_bind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) { FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)driver; int ret = OK; uinfo("\n"); #ifndef CONFIG_CDCNCM_COMPOSITE dev->ep0->priv = self; #endif /* Preallocate control request */ self->ctrlreq = usbdev_allocreq(dev->ep0, CDCECM_MXDESCLEN); if (self->ctrlreq == NULL) { ret = -ENOMEM; goto error; } self->ctrlreq->callback = cdcncm_ep0incomplete; self->epint = DEV_ALLOCEP(dev, USB_DIR_IN | self->devinfo.epno[CDCNCM_EP_INTIN_IDX], true, USB_EP_ATTR_XFER_INT); self->epbulkin = DEV_ALLOCEP(dev, USB_DIR_IN | self->devinfo.epno[CDCNCM_EP_BULKIN_IDX], true, USB_EP_ATTR_XFER_BULK); self->epbulkout = DEV_ALLOCEP(dev, USB_DIR_OUT | self->devinfo.epno[CDCNCM_EP_BULKOUT_IDX], false, USB_EP_ATTR_XFER_BULK); if (!self->epint || !self->epbulkin || !self->epbulkout) { uerr("Failed to allocate endpoints!\n"); ret = -ENODEV; goto error; } self->epint->priv = self; self->epbulkin->priv = self; self->epbulkout->priv = self; /* Pre-allocate notify requests. The buffer size is CDCECM_MXDESCLEN. */ self->notifyreq = usbdev_allocreq(self->epint, CDCECM_MXDESCLEN); if (self->notifyreq == NULL) { uerr("Out of memory\n"); ret = -ENOMEM; goto error; } self->notifyreq->callback = cdcncm_intcomplete; /* Pre-allocate read requests. The buffer size is NTB_DEFAULT_IN_SIZE. */ self->rdreq = usbdev_allocreq(self->epbulkout, NTB_DEFAULT_IN_SIZE); if (self->rdreq == NULL) { uerr("Out of memory\n"); ret = -ENOMEM; goto error; } self->rdreq->callback = cdcncm_rdcomplete; /* Pre-allocate a single write request. Buffer size is NTB_OUT_SIZE */ self->wrreq = usbdev_allocreq(self->epbulkin, NTB_OUT_SIZE); if (self->wrreq == NULL) { uerr("Out of memory\n"); ret = -ENOMEM; goto error; } self->wrreq->callback = cdcncm_wrcomplete; /* The single write request just allocated is available now. */ ret = nxsem_init(&self->wrreq_idle, 0, 1); if (ret != OK) { uerr("nxsem_init failed. ret: %d\n", ret); goto error; } self->txdone = false; #ifndef CONFIG_CDCNCM_COMPOSITE #ifdef CONFIG_USBDEV_SELFPOWERED DEV_SETSELFPOWERED(dev); #endif /* And pull-up the data line for the soft connect function (unless we are * part of a composite device) */ DEV_CONNECT(dev); #endif return OK; error: uerr("cdcncm_bind failed! ret: %d\n", ret); cdcncm_unbind(driver, dev); return ret; } static void cdcncm_unbind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) { FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)driver; #ifdef CONFIG_DEBUG_FEATURES if (!driver || !dev) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return; } #endif /* Make sure that the endpoints have been unconfigured. If * we were terminated gracefully, then the configuration should * already have been reset. If not, then calling cdcacm_resetconfig * should cause the endpoints to immediately terminate all * transfers and return the requests to us (with result == -ESHUTDOWN) */ cdcncm_resetconfig(self); up_mdelay(50); /* Free the interrupt IN endpoint */ if (self->epint) { DEV_FREEEP(dev, self->epint); self->epint = NULL; } /* Free the pre-allocated control request */ if (self->ctrlreq != NULL) { usbdev_freereq(dev->ep0, self->ctrlreq); self->ctrlreq = NULL; } /* Free pre-allocated read requests (which should all have * been returned to the free list at this time -- we don't check) */ if (self->rdreq != NULL) { usbdev_freereq(self->epbulkout, self->rdreq); self->rdreq = NULL; } /* Free the bulk OUT endpoint */ if (self->epbulkout) { DEV_FREEEP(dev, self->epbulkout); self->epbulkout = NULL; } /* Free write requests that are not in use (which should be all * of them) */ if (self->wrreq != NULL) { usbdev_freereq(self->epbulkin, self->wrreq); self->wrreq = NULL; } /* Free the bulk IN endpoint */ if (self->epbulkin) { DEV_FREEEP(dev, self->epbulkin); self->epbulkin = NULL; } /* Clear out all data in the rx_queue */ netpkt_free_queue(&self->rx_queue); } static int cdcncm_setup(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev, FAR const struct usb_ctrlreq_s *ctrl, FAR uint8_t *dataout, size_t outlen) { FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)driver; uint16_t value = GETUINT16(ctrl->value); uint16_t index = GETUINT16(ctrl->index); uint16_t len = GETUINT16(ctrl->len); int ret = -EOPNOTSUPP; if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD) { switch (ctrl->req) { case USB_REQ_GETDESCRIPTOR: { uint8_t descindex = ctrl->value[0]; uint8_t desctype = ctrl->value[1]; self->usbdev.speed = dev->speed; ret = cdcncm_getdescriptor(self, desctype, descindex, self->ctrlreq->buf); } break; case USB_REQ_SETCONFIGURATION: ret = cdcncm_setconfig(self, value); break; case USB_REQ_SETINTERFACE: ret = cdcncm_setinterface(self, index, value); break; default: uerr("Unsupported standard req: 0x%02hhx\n", ctrl->req); break; } } else if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_CLASS) { switch (ctrl->req) { case ECM_SET_PACKET_FILTER: /* SetEthernetPacketFilter is the only required CDCNCM subclass * specific request, but it is still ok to always operate in * promiscuous mode and rely on the host to do the filtering. * This is especially true for our case: * A simulated point-to-point connection. */ uinfo("ECM_SET_PACKET_FILTER wValue: 0x%04hx, wIndex: 0x%04hx\n", GETUINT16(ctrl->value), GETUINT16(ctrl->index)); ret = OK; break; case NCM_GET_NTB_PARAMETERS: if (len >= sizeof(g_ntbparameters)) { memcpy(self->ctrlreq->buf, &g_ntbparameters, sizeof(g_ntbparameters)); ret = sizeof(g_ntbparameters); } break; case NCM_SET_NTB_FORMAT: if (len != 0 || index != self->devinfo.ifnobase) break; switch (value) { case 0x0000: self->parseropts = &g_ndp16_opts; self->ndpsign = self->isncm ? self->parseropts->ndpsign : CDC_MBIM_NDP16_NOCRC_SIGN; uinfo("NCM16 selected\n"); ret = 0; break; case 0x0001: self->parseropts = &g_ndp32_opts; self->ndpsign = self->isncm ? self->parseropts->ndpsign : CDC_MBIM_NDP32_NOCRC_SIGN; uinfo("NCM32 selected\n"); ret = 0; break; default: break; } break; case NCM_GET_NTB_INPUT_SIZE: uinfo("NCM_GET_NTB_INPUT_SIZE len %d\n", len); ret = 0; break; case NCM_SET_NTB_INPUT_SIZE: if (len == 4 && value == 0) { uinfo("NCM_SET_NTB_INPUT_SIZE len %d NTB input size %d\n", len, *(FAR int *)dataout); ret = 0; } break; #ifdef CONFIG_NET_CDCMBIM case MBIM_SEND_COMMAND: { FAR struct cdcmbim_driver_s *mbim = (FAR struct cdcmbim_driver_s *)self; FAR struct iob_s *iob = iob_tryalloc(true); if (iob == NULL) { return -ENOMEM; } ret = iob_copyin(iob, dataout, len, 0, true); if (ret < 0) { iob_free_chain(iob); uerr("CDCMBIM copyin failed: %d\n", ret); return ret; } ret = iob_tryadd_queue(iob, &mbim->rx_queue); if (ret < 0) { iob_free_chain(iob); uerr("CDCMBIM add rx queue failed: %d\n", ret); return ret; } nxsem_post(&mbim->read_sem); poll_notify(mbim->fds, CDC_MBIM_NPOLLWAITERS, POLLIN); } break; case MBIM_GET_RESPONSE: { FAR struct cdcmbim_driver_s *mbim = (FAR struct cdcmbim_driver_s *)self; FAR struct iob_s *iob; ret = -ENOSPC; if ((iob = iob_remove_queue(&mbim->tx_queue)) != NULL) { ret = iob_copyout(self->ctrlreq->buf, iob, len, 0); if (ret >= 0) { iob_free_chain(iob); } } } break; #endif default: uerr("Unsupported class req: 0x%02hhx\n", ctrl->req); break; } } else { uerr("Unsupported type: 0x%02hhx\n", ctrl->type); } if (ret >= 0) { FAR struct usbdev_req_s *ctrlreq = self->ctrlreq; ctrlreq->len = MIN(len, ret); ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT; ret = EP_SUBMIT(dev->ep0, ctrlreq); uinfo("EP_SUBMIT ret: %d\n", ret); if (ret < 0) { ctrlreq->result = OK; cdcncm_ep0incomplete(dev->ep0, ctrlreq); } } return ret; } static void cdcncm_disconnect(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) { uinfo("\n"); } /**************************************************************************** * Name: cdcncm_classobject * * Description: * Register USB CDC/NCM and return the class object. * * Returned Value: * A pointer to the allocated class object (NULL on failure). * ****************************************************************************/ static int cdcncm_classobject(int minor, FAR struct usbdev_devinfo_s *devinfo, FAR struct usbdevclass_driver_s **classdev) { FAR struct cdcncm_driver_s *self; int ret; /* Initialize the driver structure */ self = kmm_zalloc(sizeof(struct cdcncm_driver_s)); if (!self) { nerr("Out of memory!\n"); return -ENOMEM; } self->isncm = true; /* Network device initialization */ self->dev.ops = &g_netops; self->dev.quota[NETPKT_TX] = CONFIG_CDCNCM_QUOTA_TX; self->dev.quota[NETPKT_RX] = CONFIG_CDCNCM_QUOTA_RX; /* USB device initialization */ #if defined(CONFIG_USBDEV_SUPERSPEED) self->usbdev.speed = USB_SPEED_SUPER; #elif defined(CONFIG_USBDEV_DUALSPEED) self->usbdev.speed = USB_SPEED_HIGH; #else self->usbdev.speed = USB_SPEED_FULL; #endif self->usbdev.ops = &g_usbdevops; memcpy(&self->devinfo, devinfo, sizeof(struct usbdev_devinfo_s)); /* Put the interface in the down state. This usually amounts to resetting * the device and/or calling cdcncm_ifdown(). */ cdcncm_ifdown(&self->dev); /* Read the MAC address from the hardware into * priv->dev.netdev.d_mac.ether.ether_addr_octet * Applies only if the Ethernet MAC has its own internal address. */ memcpy(self->dev.netdev.d_mac.ether.ether_addr_octet, "\x00\xe0\xde\xad\xbe\xef", IFHWADDRLEN); /* Register the device with the OS so that socket IOCTLs can be performed */ ret = netdev_lower_register(&self->dev, NET_LL_ETHERNET); if (ret < 0) { nerr("netdev_lower_register failed. ret: %d\n", ret); } *classdev = (FAR struct usbdevclass_driver_s *)self; return ret; } /**************************************************************************** * Name: cdcmbim_classobject * * Description: * Register USB CDC/MBIM and return the class object. * * Returned Value: * A pointer to the allocated class object (NULL on failure). * ****************************************************************************/ #ifdef CONFIG_NET_CDCMBIM static int cdcmbim_classobject(int minor, FAR struct usbdev_devinfo_s *devinfo, FAR struct usbdevclass_driver_s **classdev) { FAR struct cdcmbim_driver_s *self; FAR struct cdcncm_driver_s *ncm; int ret; /* Initialize the driver structure */ self = kmm_zalloc(sizeof(struct cdcmbim_driver_s)); if (!self) { nerr("Out of memory!\n"); return -ENOMEM; } ncm = &self->ncmdriver; ncm->isncm = false; /* Network device initialization */ ncm->dev.ops = &g_netops; ncm->dev.quota[NETPKT_TX] = CONFIG_CDCNCM_QUOTA_TX; ncm->dev.quota[NETPKT_RX] = CONFIG_CDCNCM_QUOTA_RX; /* USB device initialization */ #if defined(CONFIG_USBDEV_SUPERSPEED) ncm->usbdev.speed = USB_SPEED_SUPER; #elif defined(CONFIG_USBDEV_DUALSPEED) ncm->usbdev.speed = USB_SPEED_HIGH; #else ncm->usbdev.speed = USB_SPEED_FULL; #endif ncm->usbdev.ops = &g_usbdevops; memcpy(&ncm->devinfo, devinfo, sizeof(struct usbdev_devinfo_s)); nxmutex_init(&self->lock); nxsem_init(&self->read_sem, 0, 0); uinfo("Register character driver\n"); /* Put the interface in the down state. This usually amounts to resetting * the device and/or calling cdcncm_ifdown(). */ g_netops.ifdown(&self->ncmdriver.dev); /* Register the device with the OS so that socket IOCTLs can be performed */ ret = netdev_lower_register(&self->ncmdriver.dev, NET_LL_MBIM); if (ret < 0) { nerr("netdev_lower_register failed. ret: %d\n", ret); } else { char devname[CDC_MBIM_DEVNAMELEN]; uint8_t index = 0; #ifdef CONFIG_NETDEV_IFINDEX index = self->ncmdriver.dev.netdev.d_ifindex; #endif snprintf(devname, sizeof(devname), CDC_MBIM_DEVFORMAT, index); ret = register_driver(devname, &g_usbdevfops, 0666, self); if (ret < 0) { nerr("register_driver failed. ret: %d\n", ret); } } *classdev = (FAR struct usbdevclass_driver_s *)self; return ret; } #endif /**************************************************************************** * Name: cdcncm_uninitialize * * Description: * Un-initialize the USB CDC/NCM class driver. This function is used * internally by the USB composite driver to uninitialize the CDC/NCM * driver. This same interface is available (with an untyped input * parameter) when the CDC/NCM driver is used standalone. * * Input Parameters: * There is one parameter, it differs in typing depending upon whether the * CDC/NCM driver is an internal part of a composite device, or a * standalone USB driver: * * classdev - The class object returned by cdcncm_classobject() * * Returned Value: * None * ****************************************************************************/ static void cdcncm_uninitialize(FAR struct usbdevclass_driver_s *classdev) { FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)classdev; int ret; /* Un-register the CDC/NCM netdev device */ ret = netdev_lower_unregister(&self->dev); if (ret < 0) { nerr("ERROR: netdev_lower_unregister failed. ret: %d\n", ret); } #ifndef CONFIG_CDCNCM_COMPOSITE usbdev_unregister(&self->usbdev); #endif /* And free the driver structure */ kmm_free(self); } /**************************************************************************** * Name: cdcmbim_uninitialize * * Description: * Un-initialize the USB CDC/MBIM class driver. This function is used * internally by the USB composite driver to uninitialize the CDC/MBIM * driver. This same interface is available (with an untyped input * parameter) when the CDC/MBIM driver is used standalone. * * Input Parameters: * There is one parameter, it differs in typing depending upon whether the * CDC/MBIM driver is an internal part of a composite device, or a * standalone USB driver: * * classdev - The class object returned by cdcmbim_classobject() * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_NET_CDCMBIM static void cdcmbim_uninitialize(FAR struct usbdevclass_driver_s *classdev) { FAR struct cdcmbim_driver_s *self = (FAR struct cdcmbim_driver_s *)classdev; int ret; /* Un-register the CDC/MBIM netdev device */ ret = netdev_lower_unregister(&self->ncmdriver.dev); if (ret < 0) { nerr("ERROR: netdev_lower_unregister failed. ret: %d\n", ret); } # ifndef CONFIG_CDCNCM_COMPOSITE usbdev_unregister(&self->ncmdriver.usbdev); # endif /* And free the driver structure */ nxmutex_destroy(&self->lock); nxsem_destroy(&self->read_sem); kmm_free(self); } #endif #ifndef CONFIG_CDCNCM_COMPOSITE static int cdcnm_initialize(int minor, FAR void **handle, bool isncm) { FAR struct usbdevclass_driver_s *drvr = NULL; struct usbdev_devinfo_s devinfo; int ret; memset(&devinfo, 0, sizeof(struct usbdev_devinfo_s)); devinfo.ninterfaces = CDCECM_NINTERFACES; devinfo.nstrings = CDCECM_NSTRIDS; devinfo.nendpoints = CDCECM_NUM_EPS; devinfo.epno[CDCNCM_EP_INTIN_IDX] = CONFIG_CDCNCM_EPINTIN; devinfo.epno[CDCNCM_EP_BULKIN_IDX] = CONFIG_CDCNCM_EPBULKIN; devinfo.epno[CDCNCM_EP_BULKOUT_IDX] = CONFIG_CDCNCM_EPBULKOUT; ret = isncm ? cdcncm_classobject(minor, &devinfo, &drvr) : cdcmbim_classobject(minor, &devinfo, &drvr); if (ret == OK) { ret = usbdev_register(drvr); if (ret < 0) { uinfo("usbdev_register failed. ret %d\n", ret); } } if (handle) { *handle = drvr; } return ret; } #endif #ifdef CONFIG_CDCNCM_COMPOSITE static void cdcnm_get_composite_devdesc(FAR struct composite_devdesc_s *dev, bool isncm) { memset(dev, 0, sizeof(struct composite_devdesc_s)); /* The callback functions for the CDC/NCM class. * * classobject() and uninitialize() must be provided by board-specific * logic */ dev->mkconfdesc = isncm ? cdcncm_mkcfgdesc : cdcmbim_mkcfgdesc; dev->mkstrdesc = isncm ? cdcncm_mkstrdesc : cdcmbim_mkstrdesc; dev->classobject = isncm ? cdcncm_classobject : cdcmbim_classobject; dev->uninitialize = isncm ? cdcncm_uninitialize : cdcmbim_uninitialize; dev->nconfigs = CDCECM_NCONFIGS; /* Number of configurations supported */ dev->configid = CDCECM_CONFIGID; /* The only supported configuration ID */ /* Let the construction function calculate the size of config descriptor */ dev->cfgdescsize = isncm ? cdcncm_mkcfgdesc(NULL, NULL, USB_SPEED_UNKNOWN, 0) : cdcmbim_mkcfgdesc(NULL, NULL, USB_SPEED_UNKNOWN, 0); /* Board-specific logic must provide the device minor */ /* Interfaces. * * ifnobase must be provided by board-specific logic */ dev->devinfo.ninterfaces = CDCECM_NINTERFACES; /* Number of interfaces in the configuration */ /* Strings. * * strbase must be provided by board-specific logic */ dev->devinfo.nstrings = CDCECM_NSTRIDS + 1; /* Number of Strings */ /* Endpoints. * * Endpoint numbers must be provided by board-specific logic. */ dev->devinfo.nendpoints = CDCECM_NUM_EPS; } #endif /* CONFIG_CDCNCM_COMPOSITE */ /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: cdcncm_initialize / cdcmbim_initialize * * Description: * Register CDC_NCM/MBIM USB device interface. Register the corresponding * network driver to NuttX and bring up the network. * * Input Parameters: * minor - Device minor number. * handle - An optional opaque reference to the CDC_NCM/MBIM class object * that may subsequently be used with cdcncm_uninitialize(). * * Returned Value: * Zero (OK) means that the driver was successfully registered. On any * failure, a negated errno value is returned. * ****************************************************************************/ #ifndef CONFIG_CDCNCM_COMPOSITE int cdcncm_initialize(int minor, FAR void **handle) { return cdcnm_initialize(minor, handle, true); } #ifdef CONFIG_NET_CDCMBIM int cdcmbim_initialize(int minor, FAR void **handle) { return cdcnm_initialize(minor, handle, false); } # endif /* CONFIG_NET_CDCMBIM */ #endif /* CONFIG_CDCNCM_COMPOSITE */ /**************************************************************************** * Name: cdcncm_get_composite_devdesc / cdcmbim_get_composite_devdesc * * Description: * Helper function to fill in some constants into the composite * configuration struct. * * Input Parameters: * dev - Pointer to the configuration struct we should fill * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_CDCNCM_COMPOSITE void cdcncm_get_composite_devdesc(FAR struct composite_devdesc_s *dev) { cdcnm_get_composite_devdesc(dev, true); } # ifdef CONFIG_NET_CDCMBIM void cdcmbim_get_composite_devdesc(FAR struct composite_devdesc_s *dev) { cdcnm_get_composite_devdesc(dev, false); } # endif /* CONFIG_NET_CDCMBIM */ #endif /* CONFIG_CDCNCM_COMPOSITE */ #endif /* CONFIG_NET_CDCNCM */