/**************************************************************************** * drivers/usbdev/pl2303.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. * * This logic emulates the Prolific PL2303 serial/USB converter * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_BOARD_USBDEV_SERIALSTR #include #endif /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Configuration ************************************************************/ /* Number of requests in the write queue */ #ifndef CONFIG_PL2303_NWRREQS # define CONFIG_PL2303_NWRREQS 4 #endif /* Number of requests in the read queue */ #ifndef CONFIG_PL2303_NRDREQS # define CONFIG_PL2303_NRDREQS 4 #endif /* Logical endpoint numbers / max packet sizes */ #ifndef CONFIG_PL2303_EPINTIN # warning "EPINTIN not defined in the configuration" # define CONFIG_PL2303_EPINTIN 1 #endif #ifndef CONFIG_PL2303_EPBULKOUT # warning "EPBULKOUT not defined in the configuration" # define CONFIG_PL2303_EPBULKOUT 2 #endif #ifndef CONFIG_PL2303_EPBULKIN # warning "EPBULKIN not defined in the configuration" # define CONFIG_PL2303_EPBULKIN 3 #endif /* Packet and request buffer sizes */ #ifndef CONFIG_PL2303_EP0MAXPACKET # define CONFIG_PL2303_EP0MAXPACKET 64 #endif /* Ideally, the BULKOUT request size should *not* be the same size as the * maxpacket size. That is because IN transfers of exactly the maxpacket * size will be followed by a NULL packet. The BULKOUT request buffer * size, on the other hand, is always the same as the maxpacket size. */ #ifndef CONFIG_PL2303_BULKIN_REQLEN # define CONFIG_PL2303_BULKIN_REQLEN 96 #endif /* Vendor and product IDs and strings */ #ifndef CONFIG_PL2303_VENDORID # define CONFIG_PL2303_VENDORID 0x067b #endif #ifndef CONFIG_PL2303_PRODUCTID # define CONFIG_PL2303_PRODUCTID 0x2303 #endif #ifndef CONFIG_PL2303_VENDORSTR # warning "No Vendor string specified" # define CONFIG_PL2303_VENDORSTR "NuttX" #endif #ifndef CONFIG_PL2303_PRODUCTSTR # warning "No Product string specified" # define CONFIG_PL2303_PRODUCTSTR "USBdev Serial" #endif #undef CONFIG_PL2303_SERIALSTR #define CONFIG_PL2303_SERIALSTR "0" #undef CONFIG_PL2303_CONFIGSTR #define CONFIG_PL2303_CONFIGSTR "Bulk" /* USB Controller */ #ifdef CONFIG_USBDEV_SELFPOWERED # define PL2303_SELFPOWERED USB_CONFIG_ATTR_SELFPOWER #else # define PL2303_SELFPOWERED (0) #endif #ifdef CONFIG_USBDEV_REMOTEWAKEUP # define PL2303_REMOTEWAKEUP USB_CONFIG_ATTR_WAKEUP #else # define PL2303_REMOTEWAKEUP (0) #endif #ifndef CONFIG_USBDEV_MAXPOWER # define CONFIG_USBDEV_MAXPOWER 100 #endif /* Descriptors **************************************************************/ /* These settings are not modifiable via the NuttX configuration */ #define PL2303_VERSIONNO (0x0202) /* Device version number */ #define PL2303_CONFIGIDNONE (0) /* Config ID means to return to address mode */ #define PL2303_CONFIGID (1) /* The only supported configuration ID */ #define PL2303_NCONFIGS (1) /* Number of configurations supported */ #define PL2303_INTERFACEID (0) #define PL2303_ALTINTERFACEID (0) #define PL2303_NINTERFACES (1) /* Number of interfaces in the configuration */ #define PL2303_NENDPOINTS (3) /* Number of endpoints in the interface */ /* Endpoint configuration */ #define PL2303_EPINTIN_ADDR (USB_DIR_IN|CONFIG_PL2303_EPINTIN) #define PL2303_EPINTIN_ATTR (USB_EP_ATTR_XFER_INT) #define PL2303_EPINTIN_MXPACKET (10) #define PL2303_EPINTIN_MXBURST (0) #define PL2303_EPOUTBULK_ADDR (CONFIG_PL2303_EPBULKOUT) #define PL2303_EPOUTBULK_ATTR (USB_EP_ATTR_XFER_BULK) #define PL2303_EPINBULK_ADDR (USB_DIR_IN|CONFIG_PL2303_EPBULKIN) #define PL2303_EPINBULK_ATTR (USB_EP_ATTR_XFER_BULK) /* String language */ #define PL2303_STR_LANGUAGE (0x0409) /* en-us */ /* Descriptor strings */ #define PL2303_MANUFACTURERSTRID (1) #define PL2303_PRODUCTSTRID (2) #define PL2303_SERIALSTRID (3) #define PL2303_CONFIGSTRID (4) /* Buffer big enough for any of our descriptors */ #define PL2303_MXDESCLEN (64) #define PL2303_MAXSTRLEN (PL2303_MXDESCLEN-2) /* Vendor specific control requests *****************************************/ #define PL2303_CONTROL_TYPE (0x20) #define PL2303_SETLINEREQUEST (0x20) /* OUT, Recipient interface */ #define PL2303_GETLINEREQUEST (0x21) /* IN, Recipient interface */ #define PL2303_SETCONTROLREQUEST (0x22) /* OUT, Recipient interface */ #define PL2303_BREAKREQUEST (0x23) /* OUT, Recipient interface */ /* Vendor read/write */ #define PL2303_RWREQUEST_TYPE (0x40) #define PL2303_RWREQUEST (0x01) /* IN/OUT, Recipient device */ /* Trace values *************************************************************/ #define PL2303_CLASSAPI_SETUP TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SETUP) #define PL2303_CLASSAPI_SHUTDOWN TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SHUTDOWN) #define PL2303_CLASSAPI_ATTACH TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_ATTACH) #define PL2303_CLASSAPI_DETACH TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_DETACH) #define PL2303_CLASSAPI_IOCTL TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_IOCTL) #define PL2303_CLASSAPI_RECEIVE TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RECEIVE) #define PL2303_CLASSAPI_RXINT TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RXINT) #define PL2303_CLASSAPI_RXAVAILABLE TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RXAVAILABLE) #define PL2303_CLASSAPI_SEND TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SEND) #define PL2303_CLASSAPI_TXINT TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXINT) #define PL2303_CLASSAPI_TXREADY TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXREADY) #define PL2303_CLASSAPI_TXEMPTY TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXEMPTY) /**************************************************************************** * Private Types ****************************************************************************/ /* Container to support a list of requests */ struct pl2303_req_s { FAR struct pl2303_req_s *flink; /* Implements a singly linked list */ FAR struct usbdev_req_s *req; /* The contained request */ }; /* This structure describes the internal state of the driver */ struct pl2303_dev_s { FAR struct uart_dev_s serdev; /* Serial device structure */ FAR struct usbdev_s *usbdev; /* usbdev driver pointer */ uint8_t config; /* Configuration number */ uint8_t nwrq; /* Number of queue write requests (in reqlist) */ uint8_t nrdq; /* Number of queue read requests (in epbulkout) */ bool rxenabled; /* true: UART RX "interrupts" enabled */ uint8_t linest[7]; /* Fake line status */ int16_t rxhead; /* Working head; used when rx int disabled */ FAR struct usbdev_ep_s *epintin; /* Interrupt IN endpoint structure */ FAR struct usbdev_ep_s *epbulkin; /* Bulk IN endpoint structure */ FAR struct usbdev_ep_s *epbulkout; /* Bulk OUT endpoint structure */ FAR struct usbdev_req_s *ctrlreq; /* Control request */ struct sq_queue_s reqlist; /* List of write request containers */ /* Pre-allocated write request containers. The write requests will * be linked in a free list (reqlist), and used to send requests to * EPBULKIN; Read requests will be queued in the EBULKOUT. */ struct pl2303_req_s wrreqs[CONFIG_PL2303_NWRREQS]; struct pl2303_req_s rdreqs[CONFIG_PL2303_NWRREQS]; /* Serial I/O buffers */ char rxbuffer[CONFIG_PL2303_RXBUFSIZE]; char txbuffer[CONFIG_PL2303_TXBUFSIZE]; }; /* The internal version of the class driver */ struct pl2303_driver_s { struct usbdevclass_driver_s drvr; FAR struct pl2303_dev_s *dev; }; /* This is what is allocated */ struct pl2303_alloc_s { struct pl2303_dev_s dev; struct pl2303_driver_s drvr; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Transfer helpers *********************************************************/ static uint16_t usbclass_fillrequest(FAR struct pl2303_dev_s *priv, uint8_t *reqbuf, uint16_t reqlen); static int usbclass_sndpacket(FAR struct pl2303_dev_s *priv); static inline int usbclass_recvpacket(FAR struct pl2303_dev_s *priv, uint8_t *reqbuf, uint16_t reqlen); /* Configuration ************************************************************/ static int usbclass_mkstrdesc(uint8_t id, struct usb_strdesc_s *strdesc); static int usbclass_mkepbulkdesc( FAR const struct usb_epdesc_s *indesc, #ifdef CONFIG_USBDEV_SUPERSPEED FAR const struct usb_ss_epcompdesc_s *incompdesc, #endif uint8_t speed, FAR struct usb_epdesc_s *outdesc); static int16_t usbclass_mkcfgdesc(uint8_t *buf, uint8_t speed, uint8_t type); static void usbclass_resetconfig(FAR struct pl2303_dev_s *priv); static int usbclass_setconfig(FAR struct pl2303_dev_s *priv, uint8_t config); /* Completion event handlers ************************************************/ static void usbclass_ep0incomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req); static void usbclass_rdcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req); static void usbclass_wrcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req); /* USB class device *********************************************************/ static int usbclass_bind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); static void usbclass_unbind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); static int usbclass_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 usbclass_disconnect(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); #ifdef CONFIG_SERIAL_REMOVABLE static void usbclass_suspend(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); static void usbclass_resume(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); #endif /* Serial port **************************************************************/ static int usbser_setup(FAR struct uart_dev_s *dev); static void usbser_shutdown(FAR struct uart_dev_s *dev); static int usbser_attach(FAR struct uart_dev_s *dev); static void usbser_detach(FAR struct uart_dev_s *dev); static void usbser_rxint(FAR struct uart_dev_s *dev, bool enable); static void usbser_txint(FAR struct uart_dev_s *dev, bool enable); static bool usbser_txempty(FAR struct uart_dev_s *dev); /**************************************************************************** * Private Data ****************************************************************************/ /* USB class device *********************************************************/ static const struct usbdevclass_driverops_s g_driverops = { usbclass_bind, /* bind */ usbclass_unbind, /* unbind */ usbclass_setup, /* setup */ usbclass_disconnect, /* disconnect */ #ifdef CONFIG_SERIAL_REMOVABLE usbclass_suspend, /* suspend */ usbclass_resume, /* resume */ #else NULL, /* suspend */ NULL, /* resume */ #endif }; /* Serial port **************************************************************/ static const struct uart_ops_s g_uartops = { usbser_setup, /* setup */ usbser_shutdown, /* shutdown */ usbser_attach, /* attach */ usbser_detach, /* detach */ NULL, /* ioctl */ NULL, /* receive */ usbser_rxint, /* rxinit */ NULL, /* rxavailable */ #ifdef CONFIG_SERIAL_IFLOWCONTROL NULL, /* rxflowcontrol */ #endif NULL, /* send */ usbser_txint, /* txinit */ NULL, /* txready */ usbser_txempty /* txempty */ }; /* USB descriptor templates these will be copied and modified ***************/ static const struct usb_devdesc_s g_devdesc = { USB_SIZEOF_DEVDESC, /* len */ USB_DESC_TYPE_DEVICE, /* type */ {LSBYTE(0x0200), MSBYTE(0x0200)}, /* usb */ USB_CLASS_PER_INTERFACE, /* classid */ 0, /* subclass */ 0, /* protocol */ CONFIG_PL2303_EP0MAXPACKET, /* maxpacketsize */ { LSBYTE(CONFIG_PL2303_VENDORID), /* vendor */ MSBYTE(CONFIG_PL2303_VENDORID) }, { LSBYTE(CONFIG_PL2303_PRODUCTID), /* product */ MSBYTE(CONFIG_PL2303_PRODUCTID) }, { LSBYTE(PL2303_VERSIONNO), /* device */ MSBYTE(PL2303_VERSIONNO) }, PL2303_MANUFACTURERSTRID, /* imfgr */ PL2303_PRODUCTSTRID, /* iproduct */ PL2303_SERIALSTRID, /* serno */ PL2303_NCONFIGS /* nconfigs */ }; static const struct usb_cfgdesc_s g_cfgdesc = { USB_SIZEOF_CFGDESC, /* len */ USB_DESC_TYPE_CONFIG, /* type */ {0, 0}, /* totallen -- to be provided */ PL2303_NINTERFACES, /* ninterfaces */ PL2303_CONFIGID, /* cfgvalue */ PL2303_CONFIGSTRID, /* icfg */ USB_CONFIG_ATTR_ONE | /* attr */ PL2303_SELFPOWERED | PL2303_REMOTEWAKEUP, (CONFIG_USBDEV_MAXPOWER + 1) / 2 /* mxpower */ }; static const struct usb_ifdesc_s g_ifdesc = { USB_SIZEOF_IFDESC, /* len */ USB_DESC_TYPE_INTERFACE, /* type */ 0, /* ifno */ 0, /* alt */ PL2303_NENDPOINTS, /* neps */ USB_CLASS_VENDOR_SPEC, /* classid */ 0, /* subclass */ 0, /* protocol */ PL2303_CONFIGSTRID /* iif */ }; static const struct usb_epdesc_s g_epintindesc = { USB_SIZEOF_EPDESC, /* len */ USB_DESC_TYPE_ENDPOINT, /* type */ PL2303_EPINTIN_ADDR, /* addr */ PL2303_EPINTIN_ATTR, /* attr */ { LSBYTE(PL2303_EPINTIN_MXPACKET), /* maxpacket */ MSBYTE(PL2303_EPINTIN_MXPACKET) }, 1 /* interval */ }; #ifdef CONFIG_USBDEV_SUPERSPEED static const struct usb_ss_epcompdesc_s g_epintincompdesc = { USB_SIZEOF_SS_EPCOMPDESC, USB_DESC_TYPE_ENDPOINT_COMPANION, PL2303_EPINTIN_MXBURST, 0, { LSBYTE((PL2303_EPINTIN_MXBURST + 1) * PL2303_EPINTIN_MXPACKET), MSBYTE((PL2303_EPINTIN_MXBURST + 1) * PL2303_EPINTIN_MXPACKET) }, }; #endif static const struct usb_epdesc_s g_epbulkoutdesc = { USB_SIZEOF_EPDESC, /* len */ USB_DESC_TYPE_ENDPOINT, /* type */ PL2303_EPOUTBULK_ADDR, /* addr */ PL2303_EPOUTBULK_ATTR, /* attr */ { LSBYTE(64), MSBYTE(64) }, /* maxpacket -- might change to 512 */ 0 /* interval */ }; #ifdef CONFIG_USBDEV_SUPERSPEED static const struct usb_ss_epcompdesc_s g_epbulkoutcompdesc = { USB_SIZEOF_SS_EPCOMPDESC, USB_DESC_TYPE_ENDPOINT_COMPANION, CONFIG_PL2303_EPBULK_MAXBURST, CONFIG_PL2303_EPBULK_MAXSTREAM, { 0, 0 }, }; #endif static const struct usb_epdesc_s g_epbulkindesc = { USB_SIZEOF_EPDESC, /* len */ USB_DESC_TYPE_ENDPOINT, /* type */ PL2303_EPINBULK_ADDR, /* addr */ PL2303_EPINBULK_ATTR, /* attr */ { LSBYTE(64), MSBYTE(64) }, /* maxpacket -- might change to 512 */ 0 /* interval */ }; #ifdef CONFIG_USBDEV_SUPERSPEED static const struct usb_ss_epcompdesc_s g_epbulkincompdesc = { USB_SIZEOF_SS_EPCOMPDESC, USB_DESC_TYPE_ENDPOINT_COMPANION, CONFIG_PL2303_EPBULK_MAXBURST, CONFIG_PL2303_EPBULK_MAXSTREAM, { 0, 0 }, }; #endif #ifdef CONFIG_USBDEV_DUALSPEED static const struct usb_qualdesc_s g_qualdesc = { USB_SIZEOF_QUALDESC, /* len */ USB_DESC_TYPE_DEVICEQUALIFIER, /* type */ {LSBYTE(0x0200), MSBYTE(0x0200) }, /* USB */ USB_CLASS_VENDOR_SPEC, /* classid */ 0, /* subclass */ 0, /* protocol */ CONFIG_PL2303_EP0MAXPACKET, /* mxpacketsize */ PL2303_NCONFIGS, /* nconfigs */ 0, /* reserved */ }; #endif /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: usbclass_fillrequest * * Description: * If there is data to send it is copied to the given buffer. Called either * to initiate the first write operation, or from the completion interrupt * handler service consecutive write operations. * * NOTE: * The USB serial driver does not use the serial drivers uart_xmitchars() * API. That logic is essentially duplicated here because unlike UART * hardware, we need to be able to handle writes not byte-by-byte, but * packet-by-packet. * Unfortunately, that decision also exposes some internals of the serial * driver in the following. * ****************************************************************************/ static uint16_t usbclass_fillrequest(FAR struct pl2303_dev_s *priv, uint8_t *reqbuf, uint16_t reqlen) { FAR uart_dev_t *serdev = &priv->serdev; FAR struct uart_buffer_s *xmit = &serdev->xmit; irqstate_t flags; uint16_t nbytes = 0; /* Disable interrupts */ flags = enter_critical_section(); /* Transfer bytes while we have bytes available and there is room in the * request */ while (xmit->head != xmit->tail && nbytes < reqlen) { *reqbuf++ = xmit->buffer[xmit->tail]; nbytes++; /* Increment the tail pointer */ if (++(xmit->tail) >= xmit->size) { xmit->tail = 0; } } /* When all of the characters have been sent from the buffer * disable the "TX interrupt". */ if (xmit->head == xmit->tail) { uart_disabletxint(serdev); } /* If any bytes were removed from the buffer, inform any waiters * there there is space available. */ if (nbytes) { uart_datasent(serdev); } leave_critical_section(flags); return nbytes; } /**************************************************************************** * Name: usbclass_sndpacket * * Description: * This function obtains write requests, transfers the TX data into the * request, and submits the requests to the USB controller. This continues * until either (1) there are no further packets available, or (2) there is * not further data to send. * ****************************************************************************/ static int usbclass_sndpacket(FAR struct pl2303_dev_s *priv) { FAR struct usbdev_ep_s *ep; FAR struct usbdev_req_s *req; FAR struct pl2303_req_s *reqcontainer; uint16_t reqlen; irqstate_t flags; int len; int ret = OK; #ifdef CONFIG_DEBUG_FEATURES if (priv == NULL) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return -ENODEV; } #endif flags = enter_critical_section(); /* Use our IN endpoint for the transfer */ ep = priv->epbulkin; /* Loop until either (1) we run out or write requests, or (2) * usbclass_fillrequest() is unable to fill the request with data * (i.e., until there is no more data to be sent). */ uinfo("head=%d tail=%d nwrq=%d empty=%d\n", priv->serdev.xmit.head, priv->serdev.xmit.tail, priv->nwrq, sq_empty(&priv->reqlist)); /* Get the maximum number of bytes that will fit into one bulk IN request */ reqlen = MAX(CONFIG_PL2303_BULKIN_REQLEN, ep->maxpacket); while (!sq_empty(&priv->reqlist)) { /* Peek at the request in the container at the head of the list */ reqcontainer = (struct pl2303_req_s *)sq_peek(&priv->reqlist); req = reqcontainer->req; /* Fill the request with serial TX data */ len = usbclass_fillrequest(priv, req->buf, reqlen); if (len > 0) { /* Remove the empty container from the request list */ sq_remfirst(&priv->reqlist); priv->nwrq--; /* Then submit the request to the endpoint */ req->len = len; req->priv = reqcontainer; req->flags = USBDEV_REQFLAGS_NULLPKT; ret = EP_SUBMIT(ep, req); if (ret != OK) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL), (uint16_t) - ret); break; } } else { break; } } leave_critical_section(flags); return ret; } /**************************************************************************** * Name: usbclass_recvpacket * * Description: * A normal completion event was received by the read completion handler at * the interrupt level (with interrupts disabled). This function handles * the USB packet and provides the received data to the uart RX buffer. * * Assumptions: * Called from the USB interrupt handler with interrupts disabled. * ****************************************************************************/ static inline int usbclass_recvpacket(FAR struct pl2303_dev_s *priv, uint8_t *reqbuf, uint16_t reqlen) { FAR uart_dev_t *serdev = &priv->serdev; FAR struct uart_buffer_s *recv = &serdev->recv; uint16_t currhead; uint16_t nexthead; uint16_t nbytes = 0; /* Get the next head index. During the time that RX interrupts are * disabled, the the serial driver will be extracting data from the * circular buffer and modifying recv.tail. During this time, we should * avoid modifying recv.head; Instead we will use a shadow copy of the * index. When interrupts are restored, the real recv.head will be updated * with this index. */ if (priv->rxenabled) { currhead = recv->head; } else { currhead = priv->rxhead; } /* Pre-calculate the head index and check for wrap around. We need to do * this so that we can determine if the circular buffer will overrun BEFORE * we overrun the buffer! */ nexthead = currhead + 1; if (nexthead >= recv->size) { nexthead = 0; } /* Then copy data into the RX buffer until either: (1) all of the data has * been copied, or (2) the RX buffer is full. * NOTE: If the RX buffer becomes full, then we have overrun the serial * driver and data will be lost. */ while (nexthead != recv->tail && nbytes < reqlen) { /* Copy one byte to the head of the circular RX buffer */ recv->buffer[currhead] = *reqbuf++; /* Update counts and indices */ currhead = nexthead; nbytes++; /* Increment the head index and check for wrap around */ nexthead = currhead + 1; if (nexthead >= recv->size) { nexthead = 0; } } /* Write back the head pointer using the shadow index if RX "interrupts" * are disabled. */ if (priv->rxenabled) { recv->head = currhead; } else { priv->rxhead = currhead; } /* If data was added to the incoming serial buffer, then wake up any * threads is waiting for incoming data. If we are running in an interrupt * handler, then the serial driver will not run until the interrupt handler * returns. */ if (priv->rxenabled && nbytes > 0) { uart_datareceived(serdev); } /* Return an error if the entire packet could not be transferred */ if (nbytes < reqlen) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RXOVERRUN), 0); return -ENOSPC; } return OK; } /**************************************************************************** * Name: usbclass_mkstrdesc * * Description: * Construct a string descriptor * ****************************************************************************/ static int usbclass_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc) { FAR uint8_t *data = (FAR uint8_t *)(strdesc + 1); FAR const char *str; int len; int ndata; int i; switch (id) { case 0: { /* Descriptor 0 is the language id */ strdesc->len = 4; strdesc->type = USB_DESC_TYPE_STRING; data[0] = LSBYTE(PL2303_STR_LANGUAGE); data[1] = MSBYTE(PL2303_STR_LANGUAGE); return 4; } case PL2303_MANUFACTURERSTRID: str = CONFIG_PL2303_VENDORSTR; break; case PL2303_PRODUCTSTRID: str = CONFIG_PL2303_PRODUCTSTR; break; case PL2303_SERIALSTRID: #ifdef CONFIG_BOARD_USBDEV_SERIALSTR str = board_usbdev_serialstr(); #else str = CONFIG_PL2303_SERIALSTR; #endif break; case PL2303_CONFIGSTRID: str = CONFIG_PL2303_CONFIGSTR; break; default: 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 > (PL2303_MAXSTRLEN / 2)) { len = (PL2303_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: usbclass_mkepintdesc * * Description: * Construct the interrupt endpoint descriptor * ****************************************************************************/ static int usbclass_mkepintdesc( FAR const struct usb_epdesc_s *indesc, #ifdef CONFIG_USBDEV_SUPERSPEED FAR const struct usb_ss_epcompdesc_s *incompdesc, #endif uint8_t speed, FAR struct usb_epdesc_s *outdesc) { int len = sizeof(struct usb_epdesc_s); memcpy(outdesc, indesc, USB_SIZEOF_EPDESC); #ifdef CONFIG_USBDEV_SUPERSPEED if (speed >= USB_SPEED_SUPER) { FAR struct usb_ss_epcompdesc_s *outcompdesc = (FAR struct usb_ss_epcompdesc_s *)(outdesc++); memcpy(outcompdesc, incompdesc, USB_SIZEOF_SS_EPCOMPDESC); len += sizeof(struct usb_ss_epcompdesc_s); } #else UNUSED(speed); #endif return len; } /**************************************************************************** * Name: usbclass_mkepbulkdesc * * Description: * Construct the endpoint descriptor * ****************************************************************************/ static int usbclass_mkepbulkdesc( FAR const struct usb_epdesc_s *indesc, #ifdef CONFIG_USBDEV_SUPERSPEED FAR const struct usb_ss_epcompdesc_s *incompdesc, #endif uint8_t speed, FAR struct usb_epdesc_s *outdesc) { int len = sizeof(struct usb_epdesc_s); uint16_t mxpacket = CONFIG_PL2303_EPBULK_FSSIZE; #ifdef CONFIG_USBDEV_SUPERSPEED if (speed >= USB_SPEED_SUPER) { mxpacket = CONFIG_PL2303_EPBULK_SSSIZE; } else #endif #ifdef CONFIG_USBDEV_DUALSPEED if (speed == USB_SPEED_HIGH) { mxpacket = CONFIG_PL2303_EPBULK_HSSIZE; } #else UNUSED(speed); #endif /* Copy the canned descriptor */ memcpy(outdesc, indesc, USB_SIZEOF_EPDESC); /* Then add the correct max packet size */ outdesc->mxpacketsize[0] = LSBYTE(mxpacket); outdesc->mxpacketsize[1] = MSBYTE(mxpacket); #ifdef CONFIG_USBDEV_SUPERSPEED if (speed >= USB_SPEED_SUPER) { /* Copy the descriptor */ FAR struct usb_ss_epcompdesc_s *outcompdesc = (FAR struct usb_ss_epcompdesc_s *)(outdesc++); memcpy(outcompdesc, incompdesc, USB_SIZEOF_SS_EPCOMPDESC); if (outcompdesc->mxburst >= USB_SS_BULK_EP_MAXBURST) { outcompdesc->mxburst = USB_SS_BULK_EP_MAXBURST - 1; } if (outcompdesc->attr > USB_SS_BULK_EP_MAXSTREAM) { outcompdesc->attr = USB_SS_BULK_EP_MAXSTREAM; } outcompdesc->wbytes[0] = 0; outcompdesc->wbytes[1] = 0; len += sizeof(struct usb_ss_epcompdesc_s); } #endif return len; } /**************************************************************************** * Name: usbclass_mkcfgdesc * * Description: * Construct the configuration descriptor * ****************************************************************************/ static int16_t usbclass_mkcfgdesc(uint8_t *buf, uint8_t speed, uint8_t type) { FAR struct usb_cfgdesc_s *cfgdesc = (FAR struct usb_cfgdesc_s *)buf; uint16_t totallen = 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; } /* Configuration descriptor -- Copy the canned descriptor and fill in the * type (we'll also need to update the size below */ memcpy(cfgdesc, &g_cfgdesc, USB_SIZEOF_CFGDESC); cfgdesc->type = type; buf += USB_SIZEOF_CFGDESC; totallen += USB_SIZEOF_CFGDESC; /* Copy the canned interface descriptor */ memcpy(buf, &g_ifdesc, USB_SIZEOF_IFDESC); buf += USB_SIZEOF_IFDESC; totallen += USB_SIZEOF_IFDESC; ret = usbclass_mkepintdesc(&g_epintindesc, #ifdef CONFIG_USBDEV_SUPERSPEED &g_epintincompdesc, #endif speed, (FAR struct usb_epdesc_s *)buf); buf += ret; totallen += ret; ret = usbclass_mkepbulkdesc(&g_epbulkoutdesc, #ifdef CONFIG_USBDEV_SUPERSPEED &g_epbulkoutcompdesc, #endif speed, (FAR struct usb_epdesc_s *)buf); buf += ret; totallen += ret; ret = usbclass_mkepbulkdesc(&g_epbulkindesc, #ifdef CONFIG_USBDEV_SUPERSPEED &g_epbulkincompdesc, #endif speed, (FAR struct usb_epdesc_s *)buf); buf += ret; totallen += ret; /* Finally, fill in the total size of the configuration descriptor */ cfgdesc->totallen[0] = LSBYTE(totallen); cfgdesc->totallen[1] = MSBYTE(totallen); return totallen; } /**************************************************************************** * Name: usbclass_resetconfig * * Description: * Mark the device as not configured and disable all endpoints. * ****************************************************************************/ static void usbclass_resetconfig(FAR struct pl2303_dev_s *priv) { /* Are we configured? */ if (priv->config != PL2303_CONFIGIDNONE) { /* Yes.. but not anymore */ priv->config = PL2303_CONFIGIDNONE; /* Inform the "upper half" driver that there is no (functional) USB * connection. */ #ifdef CONFIG_SERIAL_REMOVABLE uart_connected(&priv->serdev, false); #endif /* Disable endpoints. This should force completion of all pending * transfers. */ EP_DISABLE(priv->epintin); EP_DISABLE(priv->epbulkin); EP_DISABLE(priv->epbulkout); } } /**************************************************************************** * Name: usbclass_setconfig * * Description: * Set the device configuration by allocating and configuring endpoints and * by allocating and queue read and write requests. * ****************************************************************************/ static int usbclass_setconfig(FAR struct pl2303_dev_s *priv, uint8_t config) { FAR struct usbdev_req_s *req; struct usb_ss_epdesc_s epdesc; int i; int ret = 0; #ifdef CONFIG_DEBUG_FEATURES if (priv == NULL) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return -EIO; } #endif if (config == priv->config) { /* Already configured -- Do nothing */ usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALREADYCONFIGURED), 0); return 0; } /* Discard the previous configuration data */ usbclass_resetconfig(priv); /* Was this a request to simply discard the current configuration? */ if (config == PL2303_CONFIGIDNONE) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGNONE), 0); return 0; } /* We only accept one configuration */ if (config != PL2303_CONFIGID) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGIDBAD), 0); return -EINVAL; } /* Configure the IN interrupt endpoint */ usbclass_mkepintdesc(&g_epintindesc, #ifdef CONFIG_USBDEV_SUPERSPEED &g_epintincompdesc, #endif priv->usbdev->speed, &epdesc.epdesc); ret = EP_CONFIGURE(priv->epintin, &epdesc.epdesc, false); if (ret < 0) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINCONFIGFAIL), 0); goto errout; } priv->epintin->priv = priv; /* Configure the IN bulk endpoint */ usbclass_mkepbulkdesc(&g_epbulkindesc, #ifdef CONFIG_USBDEV_SUPERSPEED &g_epbulkincompdesc, #endif priv->usbdev->speed, &epdesc.epdesc); ret = EP_CONFIGURE(priv->epbulkin, &epdesc.epdesc, false); if (ret < 0) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINCONFIGFAIL), 0); goto errout; } priv->epbulkin->priv = priv; /* Configure the OUT bulk endpoint */ usbclass_mkepbulkdesc(&g_epbulkoutdesc, #ifdef CONFIG_USBDEV_SUPERSPEED &g_epbulkoutcompdesc, #endif priv->usbdev->speed, &epdesc.epdesc); ret = EP_CONFIGURE(priv->epbulkout, &epdesc.epdesc, true); if (ret < 0) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTCONFIGFAIL), 0); goto errout; } priv->epbulkout->priv = priv; /* Queue read requests in the bulk OUT endpoint */ DEBUGASSERT(priv->nrdq == 0); for (i = 0; i < CONFIG_PL2303_NRDREQS; i++) { req = priv->rdreqs[i].req; req->callback = usbclass_rdcomplete; ret = EP_SUBMIT(priv->epbulkout, req); if (ret != OK) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT), (uint16_t)-ret); goto errout; } priv->nrdq++; } /* We are successfully configured */ priv->config = config; /* Inform the "upper half" driver that we are "open for business" */ #ifdef CONFIG_SERIAL_REMOVABLE uart_connected(&priv->serdev, true); #endif return OK; errout: usbclass_resetconfig(priv); return ret; } /**************************************************************************** * Name: usbclass_ep0incomplete * * Description: * Handle completion of EP0 control operations * ****************************************************************************/ static void usbclass_ep0incomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req) { if (req->result || req->xfrd != req->len) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_REQRESULT), (uint16_t)-req->result); } } /**************************************************************************** * Name: usbclass_rdcomplete * * Description: * Handle completion of read request on the bulk OUT endpoint. This * is handled like the receipt of serial data on the "UART" * ****************************************************************************/ static void usbclass_rdcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req) { FAR struct pl2303_dev_s *priv; irqstate_t flags; int ret; /* Sanity check */ #ifdef CONFIG_DEBUG_FEATURES if (!ep || !ep->priv || !req) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return; } #endif /* Extract references to private data */ priv = (FAR struct pl2303_dev_s *)ep->priv; /* Process the received data unless this is some unusual condition */ flags = enter_critical_section(); switch (req->result) { case 0: /* Normal completion */ usbtrace(TRACE_CLASSRDCOMPLETE, priv->nrdq); usbclass_recvpacket(priv, req->buf, req->xfrd); break; case -ESHUTDOWN: /* Disconnection */ usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSHUTDOWN), 0); priv->nrdq--; leave_critical_section(flags); return; default: /* Some other error occurred */ usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDUNEXPECTED), (uint16_t)-req->result); break; }; /* Requeue the read request */ req->len = ep->maxpacket; ret = EP_SUBMIT(ep, req); if (ret != OK) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT), (uint16_t)-req->result); } leave_critical_section(flags); } /**************************************************************************** * Name: usbclass_wrcomplete * * Description: * Handle completion of write request. This function probably executes * in the context of an interrupt handler. * ****************************************************************************/ static void usbclass_wrcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req) { FAR struct pl2303_dev_s *priv; FAR struct pl2303_req_s *reqcontainer; irqstate_t flags; /* Sanity check */ #ifdef CONFIG_DEBUG_FEATURES if (!ep || !ep->priv || !req || !req->priv) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return; } #endif /* Extract references to our private data */ priv = (FAR struct pl2303_dev_s *)ep->priv; reqcontainer = (FAR struct pl2303_req_s *)req->priv; /* Return the write request to the free list */ flags = enter_critical_section(); sq_addlast((FAR sq_entry_t *)reqcontainer, &priv->reqlist); priv->nwrq++; leave_critical_section(flags); /* Send the next packet unless this was some unusual termination * condition */ switch (req->result) { case OK: /* Normal completion */ usbtrace(TRACE_CLASSWRCOMPLETE, priv->nwrq); usbclass_sndpacket(priv); break; case -ESHUTDOWN: /* Disconnection */ usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRSHUTDOWN), priv->nwrq); break; default: /* Some other error occurred */ usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRUNEXPECTED), (uint16_t)-req->result); break; } } /**************************************************************************** * USB Class Driver Methods ****************************************************************************/ /**************************************************************************** * Name: usbclass_bind * * Description: * Invoked when the driver is bound to a USB device driver * ****************************************************************************/ static int usbclass_bind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) { FAR struct pl2303_dev_s *priv = ((FAR struct pl2303_driver_s *)driver)->dev; size_t reqlen = CONFIG_PL2303_EPBULK_FSSIZE; FAR struct pl2303_req_s *reqcontainer; irqstate_t flags; int ret; int i; usbtrace(TRACE_CLASSBIND, 0); /* Bind the structures */ priv->usbdev = dev; /* Save the reference to our private data structure in EP0 so that it * can be recovered in ep0 completion events (Unless we are part of * a composite device and, in that case, the composite device owns * EP0). */ dev->ep0->priv = priv; /* Preallocate control request */ priv->ctrlreq = usbdev_allocreq(dev->ep0, PL2303_MXDESCLEN); if (priv->ctrlreq == NULL) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCCTRLREQ), 0); ret = -ENOMEM; goto errout; } priv->ctrlreq->callback = usbclass_ep0incomplete; /* Pre-allocate all endpoints... the endpoints will not be functional * until the SET CONFIGURATION request is processed in usbclass_setconfig. * This is done here because there may be calls to kmm_malloc and the SET * CONFIGURATION processing probably occurs within interrupt handling * logic where kmm_malloc calls will fail. */ /* Pre-allocate the IN interrupt endpoint */ priv->epintin = DEV_ALLOCEP(dev, PL2303_EPINTIN_ADDR, true, USB_EP_ATTR_XFER_INT); if (!priv->epintin) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINALLOCFAIL), 0); ret = -ENODEV; goto errout; } priv->epintin->priv = priv; /* Pre-allocate the IN bulk endpoint */ priv->epbulkin = DEV_ALLOCEP(dev, PL2303_EPINBULK_ADDR, true, USB_EP_ATTR_XFER_BULK); if (!priv->epbulkin) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINALLOCFAIL), 0); ret = -ENODEV; goto errout; } priv->epbulkin->priv = priv; /* Pre-allocate the OUT bulk endpoint */ priv->epbulkout = DEV_ALLOCEP(dev, PL2303_EPOUTBULK_ADDR, false, USB_EP_ATTR_XFER_BULK); if (!priv->epbulkout) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTALLOCFAIL), 0); ret = -ENODEV; goto errout; } priv->epbulkout->priv = priv; /* Pre-allocate read requests. The buffer size is one full packet. */ #if defined(CONFIG_USBDEV_SUPERSPEED) if (dev->speed == USB_SPEED_SUPER || dev->speed == USB_SPEED_SUPER_PLUS) { if (CONFIG_PL2303_EPBULK_MAXBURST < USB_SS_BULK_EP_MAXBURST) { reqlen = CONFIG_PL2303_EPBULK_SSSIZE * (CONFIG_PL2303_EPBULK_MAXBURST + 1); } else { reqlen = CONFIG_PL2303_EPBULK_SSSIZE * USB_SS_BULK_EP_MAXBURST; } } else #endif #if defined(CONFIG_USBDEV_DUALSPEED) if (dev->speed == USB_SPEED_HIGH) { reqlen = CONFIG_PL2303_EPBULK_HSSIZE; } else #endif { reqlen = CONFIG_PL2303_EPBULK_FSSIZE; } for (i = 0; i < CONFIG_PL2303_NRDREQS; i++) { reqcontainer = &priv->rdreqs[i]; reqcontainer->req = usbdev_allocreq(priv->epbulkout, reqlen); if (reqcontainer->req == NULL) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDALLOCREQ), -ENOMEM); ret = -ENOMEM; goto errout; } reqcontainer->req->priv = reqcontainer; reqcontainer->req->callback = usbclass_rdcomplete; } /* Pre-allocate write request containers and put in a free list. * The buffer size should be larger than a full packet. Otherwise, * we will send a bogus null packet at the end of each packet. * * Pick the larger of the max packet size and the configured request * size. */ if (CONFIG_PL2303_BULKIN_REQLEN > reqlen) { reqlen = CONFIG_CDCACM_BULKIN_REQLEN; } for (i = 0; i < CONFIG_PL2303_NWRREQS; i++) { reqcontainer = &priv->wrreqs[i]; reqcontainer->req = usbdev_allocreq(priv->epbulkin, reqlen); if (reqcontainer->req == NULL) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRALLOCREQ), -ENOMEM); ret = -ENOMEM; goto errout; } reqcontainer->req->priv = reqcontainer; reqcontainer->req->callback = usbclass_wrcomplete; flags = enter_critical_section(); sq_addlast((FAR sq_entry_t *)reqcontainer, &priv->reqlist); priv->nwrq++; /* Count of write requests available */ leave_critical_section(flags); } /* Report if we are selfpowered */ #ifdef CONFIG_USBDEV_SELFPOWERED DEV_SETSELFPOWERED(dev); #endif /* And pull-up the data line for the soft connect function */ DEV_CONNECT(dev); return OK; errout: usbclass_unbind(driver, dev); return ret; } /**************************************************************************** * Name: usbclass_unbind * * Description: * Invoked when the driver is unbound from a USB device driver * ****************************************************************************/ static void usbclass_unbind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) { FAR struct pl2303_dev_s *priv; FAR struct pl2303_req_s *reqcontainer; irqstate_t flags; int i; usbtrace(TRACE_CLASSUNBIND, 0); #ifdef CONFIG_DEBUG_FEATURES if (!driver || !dev || !dev->ep0) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return; } #endif /* Extract reference to private data */ priv = ((FAR struct pl2303_driver_s *)driver)->dev; #ifdef CONFIG_DEBUG_FEATURES if (!priv) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); return; } #endif /* Make sure that we are not already unbound */ if (priv != NULL) { /* 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 usbclass_resetconfig * should cause the endpoints to immediately terminate all * transfers and return the requests to us (with result == -ESHUTDOWN) */ usbclass_resetconfig(priv); up_mdelay(50); /* Free the pre-allocated control request */ if (priv->ctrlreq != NULL) { usbdev_freereq(dev->ep0, priv->ctrlreq); priv->ctrlreq = NULL; } /* Free pre-allocated read requests (which should all have * been returned to the free list at this time -- we don't check) */ DEBUGASSERT(priv->nrdq == 0); for (i = 0; i < CONFIG_PL2303_NRDREQS; i++) { reqcontainer = &priv->rdreqs[i]; if (reqcontainer->req) { usbdev_freereq(priv->epbulkout, reqcontainer->req); reqcontainer->req = NULL; } } /* Free the bulk OUT endpoint */ if (priv->epbulkout) { DEV_FREEEP(dev, priv->epbulkout); priv->epbulkout = NULL; } /* Free write requests that are not in use (which should be all * of them */ flags = enter_critical_section(); DEBUGASSERT(priv->nwrq == CONFIG_PL2303_NWRREQS); while (!sq_empty(&priv->reqlist)) { reqcontainer = (struct pl2303_req_s *)sq_remfirst(&priv->reqlist); if (reqcontainer->req != NULL) { usbdev_freereq(priv->epbulkin, reqcontainer->req); priv->nwrq--; /* Number of write requests queued */ } } DEBUGASSERT(priv->nwrq == 0); leave_critical_section(flags); /* Free the interrupt IN endpoint */ if (priv->epintin) { DEV_FREEEP(dev, priv->epintin); priv->epintin = NULL; } /* Free the bulk IN endpoint */ if (priv->epbulkin) { DEV_FREEEP(dev, priv->epbulkin); priv->epbulkin = NULL; } } /* Clear out all data in the circular buffer */ priv->serdev.xmit.head = 0; priv->serdev.xmit.tail = 0; } /**************************************************************************** * Name: usbclass_setup * * Description: * Invoked for ep0 control requests. This function probably executes * in the context of an interrupt handler. * ****************************************************************************/ static int usbclass_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 pl2303_dev_s *priv; FAR struct usbdev_req_s *ctrlreq; uint16_t value; uint16_t index; uint16_t len; int ret = -EOPNOTSUPP; #ifdef CONFIG_DEBUG_FEATURES if (!driver || !dev || !dev->ep0 || !ctrl) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return -EIO; } #endif /* Extract reference to private data */ usbtrace(TRACE_CLASSSETUP, ctrl->req); priv = ((FAR struct pl2303_driver_s *)driver)->dev; #ifdef CONFIG_DEBUG_FEATURES if (!priv || !priv->ctrlreq) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); return -ENODEV; } #endif ctrlreq = priv->ctrlreq; /* Extract the little-endian 16-bit values to host order */ value = GETUINT16(ctrl->value); index = GETUINT16(ctrl->index); len = GETUINT16(ctrl->len); uinfo("type=%02x req=%02x value=%04x index=%04x len=%04x\n", ctrl->type, ctrl->req, value, index, len); switch (ctrl->type & USB_REQ_TYPE_MASK) { /* Standard Requests */ case USB_REQ_TYPE_STANDARD: { switch (ctrl->req) { case USB_REQ_GETDESCRIPTOR: { /* The value field specifies the descriptor type in the MS byte * and the descriptor index in the LS byte * (order is little endian) */ switch (ctrl->value[1]) { case USB_DESC_TYPE_DEVICE: { ret = usbdev_copy_devdesc(ctrlreq->buf, &g_devdesc, dev->speed); } break; #ifdef CONFIG_USBDEV_DUALSPEED case USB_DESC_TYPE_DEVICEQUALIFIER: { ret = USB_SIZEOF_QUALDESC; memcpy(ctrlreq->buf, &g_qualdesc, ret); } break; case USB_DESC_TYPE_OTHERSPEEDCONFIG: #endif /* CONFIG_USBDEV_DUALSPEED */ case USB_DESC_TYPE_CONFIG: { ret = usbclass_mkcfgdesc(ctrlreq->buf, dev->speed, ctrl->value[1]); } break; case USB_DESC_TYPE_STRING: { /* index == language code. */ ret = usbclass_mkstrdesc(ctrl->value[0], (struct usb_strdesc_s *)ctrlreq->buf); } break; default: { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_GETUNKNOWNDESC), value); } break; } } break; case USB_REQ_SETCONFIGURATION: { if (ctrl->type == 0) { ret = usbclass_setconfig(priv, value); } } break; case USB_REQ_GETCONFIGURATION: { if (ctrl->type == USB_DIR_IN) { *(FAR uint8_t *)ctrlreq->buf = priv->config; ret = 1; } } break; case USB_REQ_SETINTERFACE: { if (ctrl->type == USB_REQ_RECIPIENT_INTERFACE) { if (priv->config == PL2303_CONFIGID && index == PL2303_INTERFACEID && value == PL2303_ALTINTERFACEID) { usbclass_resetconfig(priv); usbclass_setconfig(priv, priv->config); ret = 0; } } } break; case USB_REQ_GETINTERFACE: { if (ctrl->type == (USB_DIR_IN | USB_REQ_RECIPIENT_INTERFACE) && priv->config == PL2303_CONFIGIDNONE) { if (index != PL2303_INTERFACEID) { ret = -EDOM; } else { *(FAR uint8_t *) ctrlreq->buf = PL2303_ALTINTERFACEID; ret = 1; } } } break; default: usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDSTDREQ), ctrl->req); break; } } break; /* PL2303 Vendor-Specific Requests */ case PL2303_CONTROL_TYPE: { if ((ctrl->type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_INTERFACE) { switch (ctrl->req) { case PL2303_SETLINEREQUEST: { memcpy(priv->linest, ctrlreq->buf, MIN(len, 7)); ret = 0; } break; case PL2303_GETLINEREQUEST: { memcpy(ctrlreq->buf, priv->linest, 7); ret = 7; } break; case PL2303_SETCONTROLREQUEST: case PL2303_BREAKREQUEST: { ret = 0; } break; default: usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), ctrl->type); break; } } } break; case PL2303_RWREQUEST_TYPE: { if ((ctrl->type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE) { if (ctrl->req == PL2303_RWREQUEST) { if ((ctrl->type & USB_DIR_IN) != 0) { if (ctrl->value[0] == 0x84) { *ctrlreq->buf = 0x02; ret = 1; } else if (ctrl->value[0] == 0x94) { *ctrlreq->buf = 0x00; ret = 1; } else if (ctrl->value[0] == 0x83) { *ctrlreq->buf = 0xff; ret = 1; } else if (ctrl->value[0] == 0x86) { *ctrlreq->buf = 0xaa; ret = 1; } else { *(FAR uint32_t *)ctrlreq->buf = 0xdeadbe02; ret = 4; } } else { ret = 0; } } else { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), ctrl->type); } } } break; default: usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDTYPE), ctrl->type); break; } /* Respond to the setup command if data was returned. On an error return * value (ret < 0), the USB driver will stall. */ if (ret >= 0) { ctrlreq->len = MIN(len, ret); ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT; ret = EP_SUBMIT(dev->ep0, ctrlreq); if (ret < 0) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPRESPQ), (uint16_t)-ret); ctrlreq->result = OK; usbclass_ep0incomplete(dev->ep0, ctrlreq); } } return ret; } /**************************************************************************** * Name: usbclass_disconnect * * Description: * Invoked after all transfers have been stopped, when the host is * disconnected. This function is probably called from the context of an * interrupt handler. * ****************************************************************************/ static void usbclass_disconnect(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) { FAR struct pl2303_dev_s *priv; irqstate_t flags; usbtrace(TRACE_CLASSDISCONNECT, 0); #ifdef CONFIG_DEBUG_FEATURES if (!driver || !dev || !dev->ep0) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return; } #endif /* Extract reference to private data */ priv = ((FAR struct pl2303_driver_s *)driver)->dev; #ifdef CONFIG_DEBUG_FEATURES if (!priv) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); return; } #endif /* Inform the "upper half serial driver that we have lost the USB serial * connection. */ flags = enter_critical_section(); #ifdef CONFIG_SERIAL_REMOVABLE uart_connected(&priv->serdev, false); #endif /* Reset the configuration */ usbclass_resetconfig(priv); /* Clear out all outgoing data in the circular buffer */ priv->serdev.xmit.head = 0; priv->serdev.xmit.tail = 0; priv->rxhead = 0; leave_critical_section(flags); /* Perform the soft connect function so that we will we can be * re-enumerated. */ DEV_CONNECT(dev); } /**************************************************************************** * Name: usbclass_suspend * * Description: * Handle the USB suspend event. * ****************************************************************************/ #ifdef CONFIG_SERIAL_REMOVABLE static void usbclass_suspend(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) { FAR struct pl2303_dev_s *priv; usbtrace(TRACE_CLASSSUSPEND, 0); #ifdef CONFIG_DEBUG_FEATURES if (!driver || !dev) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return; } #endif /* Extract reference to private data */ priv = ((FAR struct pl2303_driver_s *)driver)->dev; /* And let the "upper half" driver now that we are suspended */ uart_connected(&priv->serdev, false); } #endif /**************************************************************************** * Name: usbclass_resume * * Description: * Handle the USB resume event. * ****************************************************************************/ #ifdef CONFIG_SERIAL_REMOVABLE static void usbclass_resume(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) { FAR struct pl2303_dev_s *priv; usbtrace(TRACE_CLASSRESUME, 0); #ifdef CONFIG_DEBUG_FEATURES if (!driver || !dev) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return; } #endif /* Extract reference to private data */ priv = ((FAR struct pl2303_driver_s *)driver)->dev; /* Are we still configured? */ if (priv->config != PL2303_CONFIGIDNONE) { /* Yes.. let the "upper half" know that have resumed */ uart_connected(&priv->serdev, true); } } #endif /**************************************************************************** * Serial Device Methods ****************************************************************************/ /**************************************************************************** * Name: usbser_setup * * Description: * This method is called the first time that the serial port is opened. * ****************************************************************************/ static int usbser_setup(FAR struct uart_dev_s *dev) { FAR struct pl2303_dev_s *priv; usbtrace(PL2303_CLASSAPI_SETUP, 0); /* Sanity check */ #ifdef CONFIG_DEBUG_FEATURES if (!dev || !dev->priv) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return -EIO; } #endif /* Extract reference to private data */ priv = (FAR struct pl2303_dev_s *)dev->priv; /* Check if we have been configured */ if (priv->config == PL2303_CONFIGIDNONE) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SETUPNOTCONNECTED), 0); return -ENOTCONN; } return OK; } /**************************************************************************** * Name: usbser_shutdown * * Description: * This method is called when the serial port is closed. This operation * is very simple for the USB serial backend because the serial driver * has already assured that the TX data has full drained -- it calls * usbser_txempty() until that function returns true before calling this * function. * ****************************************************************************/ static void usbser_shutdown(FAR struct uart_dev_s *dev) { usbtrace(PL2303_CLASSAPI_SHUTDOWN, 0); /* Sanity check */ #ifdef CONFIG_DEBUG_FEATURES if (!dev || !dev->priv) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); } #endif } /**************************************************************************** * Name: usbser_attach * * Description: * Does not apply to the USB serial class device * ****************************************************************************/ static int usbser_attach(FAR struct uart_dev_s *dev) { usbtrace(PL2303_CLASSAPI_ATTACH, 0); return OK; } /**************************************************************************** * Name: usbser_detach * * Description: * Does not apply to the USB serial class device * ****************************************************************************/ static void usbser_detach(FAR struct uart_dev_s *dev) { usbtrace(PL2303_CLASSAPI_DETACH, 0); } /**************************************************************************** * Name: usbser_rxint * * Description: * Called by the serial driver to enable or disable RX interrupts. We, of * course, have no RX interrupts but must behave consistently. This method * is called under the conditions: * * 1. With enable==true when the port is opened (just after usbser_setup * and usbser_attach are called called) * 2. With enable==false while transferring data from the RX buffer * 2. With enable==true while waiting for more incoming data * 3. With enable==false when the port is closed (just before usbser_detach * and usbser_shutdown are called). * ****************************************************************************/ static void usbser_rxint(FAR struct uart_dev_s *dev, bool enable) { FAR struct pl2303_dev_s *priv; FAR uart_dev_t *serdev; irqstate_t flags; usbtrace(PL2303_CLASSAPI_RXINT, (uint16_t)enable); /* Sanity check */ #ifdef CONFIG_DEBUG_FEATURES if (!dev || !dev->priv) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return; } #endif /* Extract reference to private data */ priv = (FAR struct pl2303_dev_s *)dev->priv; serdev = &priv->serdev; /* We need exclusive access to the RX buffer and private structure * in the following. */ flags = enter_critical_section(); if (enable) { /* RX "interrupts" are enabled. Is this a transition from disabled * to enabled state? */ if (!priv->rxenabled) { /* Yes. During the time that RX interrupts are disabled, the * the serial driver will be extracting data from the circular * buffer and modifying recv.tail. During this time, we * should avoid modifying recv.head; When interrupts are restored, * we can update the head pointer for all of the data that we * put into circular buffer while "interrupts" were disabled. */ if (priv->rxhead != serdev->recv.head) { serdev->recv.head = priv->rxhead; /* Yes... signal the availability of new data */ uart_datareceived(serdev); } /* RX "interrupts are no longer disabled */ priv->rxenabled = true; } } /* RX "interrupts" are disabled. Is this a transition from enabled * to disabled state? */ else if (priv->rxenabled) { /* Yes. During the time that RX interrupts are disabled, the * the serial driver will be extracting data from the circular * buffer and modifying recv.tail. During this time, we * should avoid modifying recv.head; When interrupts are disabled, * we use a shadow index and continue adding data to the circular * buffer. */ priv->rxhead = serdev->recv.head; priv->rxenabled = false; } leave_critical_section(flags); } /**************************************************************************** * Name: usbser_txint * * Description: * Called by the serial driver to enable or disable TX interrupts. We, of * course, have no TX interrupts but must behave consistently. Initially, * TX interrupts are disabled. This method is called under the conditions: * * 1. With enable==false while transferring data into the TX buffer * 2. With enable==true when data may be taken from the buffer. * 3. With enable==false when the TX buffer is empty * ****************************************************************************/ static void usbser_txint(FAR struct uart_dev_s *dev, bool enable) { FAR struct pl2303_dev_s *priv; usbtrace(PL2303_CLASSAPI_TXINT, (uint16_t)enable); /* Sanity checks */ #ifdef CONFIG_DEBUG_FEATURES if (!dev || !dev->priv) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return; } #endif /* Extract references to private data */ priv = (FAR struct pl2303_dev_s *)dev->priv; /* If the new state is enabled and if there is data in the XMIT buffer, * send the next packet now. */ uinfo("enable=%d head=%d tail=%d\n", enable, priv->serdev.xmit.head, priv->serdev.xmit.tail); if (enable && priv->serdev.xmit.head != priv->serdev.xmit.tail) { usbclass_sndpacket(priv); } } /**************************************************************************** * Name: usbser_txempty * * Description: * Return true when all data has been sent. This is called from the * serial driver when the driver is closed. It will call this API * periodically until it reports true. NOTE that the serial driver takes * all responsibility for flushing TX data through the hardware so we can * be a bit sloppy about that. * ****************************************************************************/ static bool usbser_txempty(FAR struct uart_dev_s *dev) { FAR struct pl2303_dev_s *priv = (FAR struct pl2303_dev_s *)dev->priv; usbtrace(PL2303_CLASSAPI_TXEMPTY, 0); #ifdef CONFIG_DEBUG_FEATURES if (!priv) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); return true; } #endif /* When all of the allocated write requests have been returned to the * reqlist, then there is no longer any TX data in flight. */ return priv->nwrq >= CONFIG_PL2303_NWRREQS; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: usbdev_serialinitialize * * Description: * Register USB serial port (and USB serial console if so configured). * ****************************************************************************/ int usbdev_serialinitialize(int minor) { FAR struct pl2303_alloc_s *alloc; FAR struct pl2303_dev_s *priv; FAR struct pl2303_driver_s *drvr; char devname[16]; int ret; /* Allocate the structures needed */ alloc = (FAR struct pl2303_alloc_s *) kmm_malloc(sizeof(struct pl2303_alloc_s)); if (!alloc) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCDEVSTRUCT), 0); return -ENOMEM; } /* Convenience pointers into the allocated blob */ priv = &alloc->dev; drvr = &alloc->drvr; /* Initialize the USB serial driver structure */ memset(priv, 0, sizeof(struct pl2303_dev_s)); sq_init(&priv->reqlist); /* Fake line status */ priv->linest[0] = (115200) & 0xff; /* Baud=115200 */ priv->linest[1] = (115200 >> 8) & 0xff; priv->linest[2] = (115200 >> 16) & 0xff; priv->linest[3] = (115200 >> 24) & 0xff; priv->linest[4] = 0; /* One stop bit */ priv->linest[5] = 0; /* No parity */ priv->linest[6] = 8; /* 8 data bits */ /* Initialize the serial driver sub-structure */ #ifdef CONFIG_SERIAL_REMOVABLE priv->serdev.disconnected = true; #endif priv->serdev.recv.size = CONFIG_PL2303_RXBUFSIZE; priv->serdev.recv.buffer = priv->rxbuffer; priv->serdev.xmit.size = CONFIG_PL2303_TXBUFSIZE; priv->serdev.xmit.buffer = priv->txbuffer; priv->serdev.ops = &g_uartops; priv->serdev.priv = priv; /* Initialize the USB class driver structure */ #if defined(CONFIG_USBDEV_SUPERSPEED) drvr->drvr.speed = USB_SPEED_SUPER; #elif defined(CONFIG_USBDEV_DUALSPEED) drvr->drvr.speed = USB_SPEED_HIGH; #else drvr->drvr.speed = USB_SPEED_FULL; #endif drvr->drvr.ops = &g_driverops; drvr->dev = priv; /* Register the USB serial class driver */ ret = usbdev_register(&drvr->drvr); if (ret) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_DEVREGISTER), (uint16_t)-ret); goto errout_with_alloc; } /* Register the USB serial console */ #ifdef CONFIG_PL2303_CONSOLE priv->serdev.isconsole = true; ret = uart_register("/dev/console", &priv->serdev); if (ret < 0) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONSOLEREGISTER), (uint16_t)-ret); goto errout_with_class; } #endif /* Register the single port supported by this implementation */ snprintf(devname, sizeof(devname), "/dev/ttyUSB%d", minor); ret = uart_register(devname, &priv->serdev); if (ret) { usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UARTREGISTER), (uint16_t)-ret); goto errout_with_class; } return OK; errout_with_class: usbdev_unregister(&drvr->drvr); errout_with_alloc: kmm_free(alloc); return ret; }