| /**************************************************************************** |
| * 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 <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <sys/param.h> |
| |
| #include <nuttx/irq.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/queue.h> |
| #include <nuttx/serial/serial.h> |
| #include <nuttx/usb/usb.h> |
| #include <nuttx/usb/usbdev.h> |
| #include <nuttx/usb/usbdev_trace.h> |
| |
| #ifdef CONFIG_BOARD_USBDEV_SERIALSTR |
| #include <nuttx/board.h> |
| #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_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); |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| static void usbclass_mkepbulkdesc(const struct usb_epdesc_s *indesc, |
| uint16_t mxpacket, struct usb_epdesc_s *outdesc); |
| static int16_t usbclass_mkcfgdesc(uint8_t *buf, uint8_t speed, uint8_t type); |
| #else |
| static int16_t usbclass_mkcfgdesc(uint8_t *buf); |
| #endif |
| 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 */ |
| }; |
| |
| 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 */ |
| }; |
| |
| 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_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_mkepbulkdesc |
| * |
| * Description: |
| * Construct the endpoint descriptor |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| static inline void usbclass_mkepbulkdesc( |
| FAR const struct usb_epdesc_s *indesc, |
| uint16_t mxpacket, |
| FAR struct usb_epdesc_s *outdesc) |
| { |
| /* 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); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: usbclass_mkcfgdesc |
| * |
| * Description: |
| * Construct the configuration descriptor |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| static int16_t usbclass_mkcfgdesc(uint8_t *buf, uint8_t speed, uint8_t type) |
| #else |
| static int16_t usbclass_mkcfgdesc(uint8_t *buf) |
| #endif |
| { |
| FAR struct usb_cfgdesc_s *cfgdesc = (FAR struct usb_cfgdesc_s *)buf; |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| bool hispeed = (speed == USB_SPEED_HIGH); |
| uint16_t bulkmxpacket; |
| #endif |
| uint16_t totallen; |
| |
| /* This is the total length of the configuration (not necessarily the |
| * size that we will be sending now. |
| */ |
| |
| totallen = USB_SIZEOF_CFGDESC + USB_SIZEOF_IFDESC + |
| PL2303_NENDPOINTS * USB_SIZEOF_EPDESC; |
| |
| /* 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); |
| buf += USB_SIZEOF_CFGDESC; |
| |
| /* Copy the canned interface descriptor */ |
| |
| memcpy(buf, &g_ifdesc, USB_SIZEOF_IFDESC); |
| buf += USB_SIZEOF_IFDESC; |
| |
| /* Make the three endpoint configurations. First, check for switches |
| * between high and full speed |
| */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| if (type == USB_DESC_TYPE_OTHERSPEEDCONFIG) |
| { |
| hispeed = !hispeed; |
| } |
| #endif |
| |
| memcpy(buf, &g_epintindesc, USB_SIZEOF_EPDESC); |
| buf += USB_SIZEOF_EPDESC; |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| if (hispeed) |
| { |
| bulkmxpacket = 512; |
| } |
| else |
| { |
| bulkmxpacket = 64; |
| } |
| |
| usbclass_mkepbulkdesc(&g_epbulkoutdesc, bulkmxpacket, |
| (FAR struct usb_epdesc_s *)buf); |
| buf += USB_SIZEOF_EPDESC; |
| usbclass_mkepbulkdesc(&g_epbulkindesc, bulkmxpacket, |
| (FAR struct usb_epdesc_s *)buf); |
| #else |
| memcpy(buf, &g_epbulkoutdesc, USB_SIZEOF_EPDESC); |
| buf += USB_SIZEOF_EPDESC; |
| memcpy(buf, &g_epbulkindesc, USB_SIZEOF_EPDESC); |
| #endif |
| |
| /* 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; |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| struct usb_epdesc_s epdesc; |
| uint16_t bulkmxpacket; |
| #endif |
| 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 */ |
| |
| ret = EP_CONFIGURE(priv->epintin, &g_epintindesc, false); |
| if (ret < 0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINCONFIGFAIL), 0); |
| goto errout; |
| } |
| |
| priv->epintin->priv = priv; |
| |
| /* Configure the IN bulk endpoint */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| if (priv->usbdev->speed == USB_SPEED_HIGH) |
| { |
| bulkmxpacket = 512; |
| } |
| else |
| { |
| bulkmxpacket = 64; |
| } |
| |
| usbclass_mkepbulkdesc(&g_epbulkindesc, bulkmxpacket, &epdesc); |
| ret = EP_CONFIGURE(priv->epbulkin, &epdesc, false); |
| #else |
| ret = EP_CONFIGURE(priv->epbulkin, &g_epbulkindesc, false); |
| #endif |
| if (ret < 0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINCONFIGFAIL), 0); |
| goto errout; |
| } |
| |
| priv->epbulkin->priv = priv; |
| |
| /* Configure the OUT bulk endpoint */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| usbclass_mkepbulkdesc(&g_epbulkoutdesc, bulkmxpacket, &epdesc); |
| ret = EP_CONFIGURE(priv->epbulkout, &epdesc, true); |
| #else |
| ret = EP_CONFIGURE(priv->epbulkout, &g_epbulkoutdesc, true); |
| #endif |
| 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; |
| FAR struct pl2303_req_s *reqcontainer; |
| irqstate_t flags; |
| uint16_t reqlen; |
| 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. */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| reqlen = 512; |
| #else |
| reqlen = 64; |
| #endif |
| |
| 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. |
| */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| reqlen = 512; |
| #else |
| reqlen = 64; |
| #endif |
| |
| 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 = USB_SIZEOF_DEVDESC; |
| memcpy(ctrlreq->buf, &g_devdesc, ret); |
| } |
| 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: |
| { |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| ret = usbclass_mkcfgdesc(ctrlreq->buf, |
| dev->speed, ctrl->req); |
| #else |
| ret = usbclass_mkcfgdesc(ctrlreq->buf); |
| #endif |
| } |
| 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 */ |
| |
| #ifdef 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; |
| } |