nuttx/drivers/usbdev/pl2303.c
SPRESENSE 021a58d71a usbdev: Add board unique serial string support
iSerialNumber field in the device descriptor can be used to determining the
board when multiple boards connected to the same host. So add feature to change
serial string by board unique ID dynamically.
To use this feature, user must be implement the board_usbdev_serialstr() logic.

refs #13909
2022-01-19 09:25:48 +01:00

2436 lines
69 KiB
C

/****************************************************************************
* 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 <queue.h>
#include <debug.h>
#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/arch.h>
#include <nuttx/serial/serial.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/usbdev_trace.h>
#ifdef CONFIG_PL2303_BOARD_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 */
/* Misc Macros **************************************************************/
/* min/max macros */
#ifndef min
# define min(a,b) ((a)<(b)?(a):(b))
#endif
#ifndef max
# define max(a,b) ((a)>(b)?(a):(b))
#endif
/* 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);
/* Request helpers **********************************************************/
static struct usbdev_req_s *usbclass_allocreq(FAR struct usbdev_ep_s *ep,
uint16_t len);
static void usbclass_freereq(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
/* 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_allocreq
*
* Description:
* Allocate a request instance along with its buffer
*
****************************************************************************/
static struct usbdev_req_s *usbclass_allocreq(FAR struct usbdev_ep_s *ep,
uint16_t len)
{
FAR struct usbdev_req_s *req;
req = EP_ALLOCREQ(ep);
if (req != NULL)
{
req->len = len;
req->buf = EP_ALLOCBUFFER(ep, len);
if (!req->buf)
{
EP_FREEREQ(ep, req);
req = NULL;
}
}
return req;
}
/****************************************************************************
* Name: usbclass_freereq
*
* Description:
* Free a request instance along with its buffer
*
****************************************************************************/
static void usbclass_freereq(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
if (ep != NULL && req != NULL)
{
if (req->buf != NULL)
{
EP_FREEBUFFER(ep, req->buf);
}
EP_FREEREQ(ep, req);
}
}
/****************************************************************************
* Name: usbclass_mkstrdesc
*
* Description:
* Construct a string descriptor
*
****************************************************************************/
static int usbclass_mkstrdesc(uint8_t id, struct usb_strdesc_s *strdesc)
{
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;
strdesc->data[0] = LSBYTE(PL2303_STR_LANGUAGE);
strdesc->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_PL2303_BOARD_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)
{
strdesc->data[ndata] = str[i];
strdesc->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(
const FAR 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 = usbclass_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 = usbclass_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 = usbclass_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 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;
}
/* Free the pre-allocated control request */
if (priv->ctrlreq != NULL)
{
usbclass_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)
{
usbclass_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)
{
usbclass_freereq(priv->epbulkin, reqcontainer->req);
priv->nwrq--; /* Number of write requests queued */
}
}
DEBUGASSERT(priv->nwrq == 0);
leave_critical_section(flags);
}
/* 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 */
sprintf(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;
}