* drivers/serial/serial.c
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.
*
****************************************************************************/
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/param.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <poll.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <spawn.h>
#include <libgen.h>
#include <nuttx/irq.h>
#include <nuttx/ascii.h>
#include <nuttx/arch.h>
#include <nuttx/clock.h>
#include <nuttx/sched.h>
#include <nuttx/signal.h>
#include <nuttx/fs/fs.h>
#include <nuttx/cancelpt.h>
#include <nuttx/serial/serial.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/power/pm.h>
#include <nuttx/wqueue.h>
#include <nuttx/kthread.h>
#include <nuttx/spinlock.h>
#include <nuttx/mutex.h>
* Pre-processor Definitions
****************************************************************************/
#if defined(CONFIG_SERIAL_IFLOWCONTROL) && \
defined(CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS)
# if CONFIG_SERIAL_IFLOWCONTROL_LOWER_WATERMARK < 1
# warning CONFIG_SERIAL_IFLOWCONTROL_LOWER_WATERMARK too small
# endif
# if CONFIG_SERIAL_IFLOWCONTROL_UPPER_WATERMARK > 99
# warning CONFIG_SERIAL_IFLOWCONTROL_UPPER_WATERMARK too large
# endif
# if CONFIG_SERIAL_IFLOWCONTROL_LOWER_WATERMARK >= CONFIG_SERIAL_IFLOWCONTROL_UPPER_WATERMARK
# warning CONFIG_SERIAL_IFLOWCONTROL_LOWER_WATERMARK too large
# warning Must be less than CONFIG_SERIAL_IFLOWCONTROL_UPPER_WATERMARK
# endif
#endif
#define POLL_DELAY_USEC 1000
* Private Types
****************************************************************************/
* Private Function Prototypes
****************************************************************************/
static void uart_poll_notify(FAR uart_dev_t *dev, unsigned int min,
unsigned int max, pollevent_t eventset);
static int uart_putxmitchar(FAR uart_dev_t *dev, int ch,
bool oktoblock);
static inline ssize_t uart_irqwrite(FAR uart_dev_t *dev,
FAR const char *buffer,
size_t buflen);
static int uart_tcdrain(FAR uart_dev_t *dev,
bool cancelable, clock_t timeout);
static int uart_tcsendbreak(FAR uart_dev_t *dev,
FAR struct file *filep,
unsigned int ms);
static int uart_open(FAR struct file *filep);
static int uart_close(FAR struct file *filep);
static ssize_t uart_read(FAR struct file *filep,
FAR char *buffer, size_t buflen);
static ssize_t uart_write(FAR struct file *filep,
FAR const char *buffer,
size_t buflen);
static int uart_ioctl(FAR struct file *filep,
int cmd, unsigned long arg);
static int uart_poll(FAR struct file *filep,
FAR struct pollfd *fds, bool setup);
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int uart_unlink(FAR struct inode *inode);
#endif
* Public Function Prototypes
****************************************************************************/
#ifdef CONFIG_TTY_LAUNCH_ENTRY
int CONFIG_TTY_LAUNCH_ENTRYPOINT(int argc, FAR char *argv[]);
#endif
* Private Data
****************************************************************************/
static const struct file_operations g_serialops =
{
uart_open,
uart_close,
uart_read,
uart_write,
NULL,
uart_ioctl,
NULL,
NULL,
uart_poll,
NULL,
NULL
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
, uart_unlink
#endif
};
#ifdef CONFIG_TTY_LAUNCH
static struct work_s g_serial_work;
#endif
* Private Functions
****************************************************************************/
* Name: uart_is_termios_hw_change
*
* Description:
* Return true if the termios hw change
*
****************************************************************************/
static bool uart_is_termios_hw_change(FAR struct file *filep,
FAR const struct termios *new)
{
FAR struct inode *inode = filep->f_inode;
FAR uart_dev_t *dev = inode->i_private;
struct termios old;
int ret;
if (new == NULL)
{
return false;
}
memset(&old, 0, sizeof(old));
ret = dev->ops->ioctl(filep, TCGETS, (unsigned long)(uintptr_t)&old);
if (ret >= 0)
{
if (old.c_speed != new->c_speed)
{
return true;
}
if ((old.c_cflag ^ new->c_cflag) & ~(HUPCL | CREAD | CLOCAL))
{
return true;
}
}
return false;
}
* Name: uart_poll_notify
****************************************************************************/
static void uart_poll_notify(FAR uart_dev_t *dev, unsigned int min,
unsigned int max, pollevent_t eventset)
{
irqstate_t flags;
DEBUGASSERT(max > min && max - min <= CONFIG_SERIAL_NPOLLWAITERS);
flags = uart_spinlock(dev, true);
poll_notify(&dev->fds[min], max - min, eventset);
uart_spinunlock(dev, true, flags);
}
* Name: uart_putxmitchar
****************************************************************************/
static int uart_putxmitchar(FAR uart_dev_t *dev, int ch, bool oktoblock)
{
irqstate_t flags;
int nexthead;
int ret;
for (; ; )
{
* We need to use the "next" head pointer to determine when the
* circular buffer would overrun
*/
nexthead = dev->xmit.head + 1;
if (nexthead >= dev->xmit.size)
{
nexthead = 0;
}
if (nexthead != dev->xmit.tail)
{
dev->xmit.buffer[dev->xmit.head] = ch;
dev->xmit.head = nexthead;
break;
}
* to remove some data from the TX buffer?
*/
else if (oktoblock)
{
* interrupt handling.
*/
flags = uart_spinlock(dev, true);
* have occurred between the test at the top of the loop and
* entering the critical section and the TX buffer may no longer
* be full.
*
* NOTE: On certain devices, such as USB CDC/ACM, the entire TX
* buffer may have been emptied in this race condition. In that
* case, the logic would hang below waiting for space in the TX
* buffer without this test.
*/
if (nexthead != dev->xmit.tail)
{
ret = OK;
}
#ifdef CONFIG_SERIAL_REMOVABLE
* have interrupts off. We do not want the transition to occur
* as a race condition before we begin the wait.
*/
else if (dev->disconnected)
{
ret = -ENOTCONN;
}
#endif
else
{
* the TX interrupt enabled. When the TX interrupt is enabled,
* uart_xmitchars() should execute and remove some of the data
* from the TX buffer.
*
* NOTE that interrupts will be re-enabled while we wait for
* the semaphore.
*/
#ifdef CONFIG_SERIAL_TXDMA
uart_dmatxavail(dev);
#endif
uart_enabletxint(dev);
uart_spinunlock(dev, true, flags);
ret = nxsem_wait(&dev->xmitsem);
flags = uart_spinlock(dev, true);
uart_disabletxint(dev);
}
uart_spinunlock(dev, true, flags);
#ifdef CONFIG_SERIAL_REMOVABLE
* waiting.
*/
if (dev->disconnected)
{
return -ENOTCONN;
}
#endif
if (ret < 0)
{
* become non-full will abort the transfer.
*/
return -EINTR;
}
}
* EAGAIN error to signal this situation.
*/
else
{
return -EAGAIN;
}
}
* unreachable.
*/
return OK;
}
* Name: uart_putc
****************************************************************************/
static inline void uart_putchars(FAR uart_dev_t *dev,
FAR const void *buf, size_t len)
{
FAR const char *pbuf = buf;
while (len > 0)
{
while (!uart_txready(dev))
{
}
if (dev->ops->sendbuf)
{
ssize_t ret = uart_sendbuf(dev, pbuf, len);
if (ret > 0)
{
pbuf += ret;
len -= ret;
}
}
else
{
uart_send(dev, *pbuf++);
len--;
}
}
}
* Name: uart_irqwrite
****************************************************************************/
static inline ssize_t uart_irqwrite(FAR uart_dev_t *dev,
FAR const char *buffer,
size_t buflen)
{
size_t tail = 0;
size_t head = buflen;
if ((dev->tc_oflag & OPOST) != 0)
{
for (head = 0; head < buflen; head++)
{
int ch = buffer[head];
if ((ch == '\r') && (dev->tc_oflag & OCRNL) != 0)
{
uart_putchars(dev, &buffer[tail], head - tail);
uart_putchars(dev, "\n", 1);
tail = head + 1;
}
if ((ch == '\n') && (dev->tc_oflag & (ONLCR | ONLRET)) != 0)
{
uart_putchars(dev, &buffer[tail], head - tail);
uart_putchars(dev, "\r", 1);
tail = head;
}
}
}
uart_putchars(dev, &buffer[tail], head - tail);
return buflen;
}
* Name: uart_tcdrain
*
* Description:
* Block further TX input.
* Wait until all data has been transferred from the TX buffer and
* until the hardware TX FIFOs are empty.
*
****************************************************************************/
static int uart_tcdrain(FAR uart_dev_t *dev,
bool cancelable, clock_t timeout)
{
int ret;
if (cancelable && enter_cancellation_point())
{
#ifdef CONFIG_CANCELLATION_POINTS
* the wait. Exit now with ECANCELED.
*/
leave_cancellation_point();
return -ECANCELED;
#endif
}
* be written while we are trying to flush the old data.
*
* A signal received while waiting for access to the xmit.head will abort
* the operation with EINTR.
*/
ret = nxmutex_lock(&dev->xmit.lock);
if (ret >= 0)
{
irqstate_t flags;
clock_t start;
flags = uart_spinlock(dev, true);
#ifdef CONFIG_SERIAL_REMOVABLE
* interrupts off. We do not want the transition to occur as a race
* condition before we begin the wait.
*/
if (dev->disconnected)
{
dev->xmit.tail = dev->xmit.head;
ret = -ENOTCONN;
}
else
#endif
{
*
* NOTE: There is no timeout on the following loop. In
* situations were this loop could hang (with hardware flow
* control, as an example), the caller should call
* tcflush() first to discard this buffered Tx data.
*/
ret = OK;
while (ret >= 0 && dev->xmit.head != dev->xmit.tail)
{
* the TX interrupt enabled. When the TX interrupt is
* enabled, uart_xmitchars() should execute and remove some
* of the data from the TX buffer. We may have to wait several
* times for the TX buffer to be entirely emptied.
*
* NOTE that interrupts will be re-enabled while we wait for
* the semaphore.
*/
#ifdef CONFIG_SERIAL_TXDMA
uart_dmatxavail(dev);
#endif
uart_enabletxint(dev);
uart_spinunlock(dev, true, flags);
ret = nxsem_wait(&dev->xmitsem);
flags = uart_spinlock(dev, true);
uart_disabletxint(dev);
}
}
uart_spinunlock(dev, true, flags);
* be data in the UART TX FIFO. We get no asynchronous indication of
* this event, so we have to do a busy wait poll.
*/
*
* REVISIT: This is a kludge. The correct fix would be add an
* interface to the lower half driver so that the tcflush() operation
* all also cause the lower half driver to clear and reset the Tx FIFO.
*/
start = clock_systime_ticks();
if (ret >= 0)
{
while (!uart_txempty(dev))
{
clock_t elapsed;
nxsig_usleep(POLL_DELAY_USEC);
elapsed = clock_systime_ticks() - start;
if (elapsed >= timeout)
{
nxmutex_unlock(&dev->xmit.lock);
return -ETIMEDOUT;
}
}
}
nxmutex_unlock(&dev->xmit.lock);
}
if (cancelable)
{
leave_cancellation_point();
}
return ret;
}
* Name: uart_tcsendbreak
*
* Description:
* Request a serial line Break by calling the lower half driver's
* BSD-compatible Break IOCTLs TIOCSBRK and TIOCCBRK, with a sleep of the
* specified duration between them.
*
* Input Parameters:
* dev - Serial device.
* filep - Required for issuing lower half driver IOCTL call.
* ms - If non-zero, duration of the Break in milliseconds; if
* zero, duration is 400 milliseconds.
*
* Returned Value:
* 0 on success or a negated errno value on failure.
*
****************************************************************************/
static int uart_tcsendbreak(FAR uart_dev_t *dev, FAR struct file *filep,
unsigned int ms)
{
int ret;
* beginning the Break to avoid corrupting the transmit data? If so, note
* that just calling uart_tcdrain() here would create a race condition,
* since new transmit data could be written after uart_tcdrain() returns
* but before we re-acquire the dev->xmit.lock here. Therefore, we would
* need to refactor the functional portion of uart_tcdrain() to a separate
* function and call it from both uart_tcdrain() and uart_tcsendbreak()
* in critical section and with xmit lock already held.
*/
if (dev->ops->ioctl)
{
ret = nxmutex_lock(&dev->xmit.lock);
if (ret >= 0)
{
ret = dev->ops->ioctl(filep, TIOCSBRK, 0);
if (ret >= 0)
{
nxsig_usleep((ms == 0) ? 400000 : ms * 1000);
ret = dev->ops->ioctl(filep, TIOCCBRK, 0);
}
}
nxmutex_unlock(&dev->xmit.lock);
}
else
{
ret = -ENOTTY;
}
return ret;
}
* Name: uart_open
*
* Description:
* This routine is called whenever a serial port is opened.
*
****************************************************************************/
static int uart_open(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR uart_dev_t *dev = inode->i_private;
uint16_t tmp;
int ret;
* If a signal is received while we are waiting, then return EINTR.
*/
ret = nxmutex_lock(&dev->closelock);
if (ret < 0)
{
return ret;
}
#ifdef CONFIG_SERIAL_REMOVABLE
* device. We check this after obtaining the close semaphore because
* we might have been waiting when the device was disconnected.
*/
if (dev->disconnected)
{
ret = -ENOTCONN;
goto errout_with_lock;
}
#endif
tmp = dev->open_count + 1;
if (tmp == 0)
{
ret = -EMFILE;
goto errout_with_lock;
}
if (tmp == 1)
{
irqstate_t flags = uart_spinlock(dev, false);
* initialized.
*/
if (!dev->isconsole)
{
ret = uart_setup(dev);
if (ret < 0)
{
uart_spinunlock(dev, false, flags);
goto errout_with_lock;
}
}
* operation. Attach the hardware IRQ(s). Hmm.. should shutdown() the
* the device in the rare case that uart_attach() fails, tmp==1, and
* this is not the console.
*/
ret = uart_attach(dev);
if (ret < 0)
{
if (!dev->isconsole)
{
uart_shutdown(dev);
}
uart_spinunlock(dev, false, flags);
goto errout_with_lock;
}
#ifdef CONFIG_SERIAL_RXDMA
uart_dmarxfree(dev);
#endif
uart_enablerxint(dev);
uart_spinunlock(dev, false, flags);
}
dev->open_count = tmp;
errout_with_lock:
nxmutex_unlock(&dev->closelock);
return ret;
}
* Name: uart_close
*
* Description:
* This routine is called when the serial port gets closed.
* It waits for the last remaining data to be sent.
*
****************************************************************************/
static int uart_close(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR uart_dev_t *dev = inode->i_private;
irqstate_t flags;
* operations.
* NOTE: that we do not let this wait be interrupted by a signal.
* Technically, we should, but almost no one every checks the return value
* from close() so we avoid a potential memory leak by ignoring signals in
* this case.
*/
nxmutex_lock(&dev->closelock);
if (dev->open_count > 1)
{
dev->open_count--;
nxmutex_unlock(&dev->closelock);
return OK;
}
dev->open_count = 0;
uart_disablerxint(dev);
if ((filep->f_oflags & O_NONBLOCK) == 0)
{
uart_tcdrain(dev, false, 4 * TICK_PER_SEC);
}
flags = uart_spinlock(dev, false);
uart_detach(dev);
if (!dev->isconsole)
{
uart_shutdown(dev);
}
uart_spinunlock(dev, false, flags);
uart_datareceived(dev);
* of the device, as the close might be caused by pthread_cancel() of
* a thread currently blocking on any of them.
*/
uart_reset_sem(dev);
if (dev->unlinked)
{
nxmutex_unlock(&dev->closelock);
nxmutex_destroy(&dev->xmit.lock);
nxmutex_destroy(&dev->recv.lock);
nxmutex_destroy(&dev->closelock);
nxsem_destroy(&dev->xmitsem);
nxsem_destroy(&dev->recvsem);
uart_release(dev);
return OK;
}
nxmutex_unlock(&dev->closelock);
return OK;
}
* Name: uart_read
****************************************************************************/
static ssize_t uart_read(FAR struct file *filep,
FAR char *buffer, size_t buflen)
{
FAR struct inode *inode = filep->f_inode;
FAR uart_dev_t *dev = inode->i_private;
FAR struct uart_buffer_s *rxbuf = &dev->recv;
#ifdef CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS
unsigned int nbuffered;
unsigned int watermark;
sbuf_size_t head;
#endif
irqstate_t flags;
ssize_t recvd = 0;
bool echoed = false;
sbuf_size_t tail;
char ch;
int ret;
ret = nxmutex_lock(&dev->recv.lock);
if (ret < 0)
{
* abort the transfer. After the transfer has started, we are
* committed and signals will be ignored.
*/
return ret;
}
* we add data to the head of the buffer; uart_xmitchars takes the
* data from the end of the buffer.
*/
while ((size_t)recvd < buflen)
{
#ifdef CONFIG_SERIAL_REMOVABLE
* further from the device.
*/
if (dev->disconnected)
{
if (recvd == 0)
{
recvd = -ENOTCONN;
}
break;
}
#endif
* NOTE: Rx interrupt handling logic may asynchronously increment
* the head index but must not modify the tail index. The tail
* index is only modified in this function. Therefore, no
* special handshaking is required here.
*
* The head and tail pointers values are sized based
* on the architecture. If the architecture reads 16-bit values
* atomically by nature, they are 16-bit values. On architectures
* where 16-bit access is split into two non-atomic 8-bit accesses,
* the pointers are 8-bit.
*
* The following code is therefore safe even with interrupts enabled.
*/
tail = rxbuf->tail;
if (rxbuf->head != tail)
{
ch = rxbuf->buffer[tail];
* local variable 'tail' so that the final rxbuf->tail update
* is atomic.
*/
if (++tail >= rxbuf->size)
{
tail = 0;
}
rxbuf->tail = tail;
if (dev->tc_iflag & (INLCR | IGNCR | ICRNL))
{
if ((ch == '\n') && (dev->tc_iflag & INLCR))
{
ch = '\r';
}
else if ((ch == '\r') && (dev->tc_iflag & ICRNL))
{
ch = '\n';
}
if ((ch == '\r') && (dev->tc_iflag & IGNCR))
{
continue;
}
}
if ((dev->tc_lflag & ICANON) &&
(ch == ASCII_BS || ch == ASCII_DEL))
{
if (recvd > 0)
{
*buffer-- = '\0';
recvd--;
if (dev->tc_lflag & ECHO)
{
nxmutex_lock(&dev->xmit.lock);
uart_putxmitchar(dev, '\b', true);
uart_putxmitchar(dev, ' ', true);
uart_putxmitchar(dev, '\b', true);
nxmutex_unlock(&dev->xmit.lock);
#ifdef CONFIG_SERIAL_TXDMA
flags = uart_spinlock(dev, true);
uart_dmatxavail(dev);
uart_spinunlock(dev, true, flags);
#endif
uart_enabletxint(dev);
}
}
continue;
}
*
* All of the local modes; echo, line editing, etc.
* Anything to do with break or parity errors.
* ISTRIP - we should be 8-bit clean.
* IUCLC - Not Posix
* IXON/OXOFF - no xon/xoff flow control.
*/
*buffer++ = ch;
recvd++;
if (dev->tc_lflag & ECHO)
{
if (ch == ASCII_ESC)
{
dev->escape = 2;
continue;
}
else if (dev->escape == 2 && ch != ASCII_LBRACKET)
{
dev->escape = 0;
}
else if (dev->escape > 0)
{
dev->escape--;
continue;
}
if (!iscntrl(ch & 0xff) || ch == '\n')
{
nxmutex_lock(&dev->xmit.lock);
if (ch == '\n')
{
uart_putxmitchar(dev, '\r', true);
}
uart_putxmitchar(dev, ch, true);
nxmutex_unlock(&dev->xmit.lock);
* to avoid the tx buffer is empty such as special escape
* sequence received, but enable the tx interrupt.
*/
if (dev->tc_lflag & ICANON)
{
#ifdef CONFIG_SERIAL_TXDMA
flags = uart_spinlock(dev, true);
uart_dmatxavail(dev);
uart_spinunlock(dev, true, flags);
#endif
uart_enabletxint(dev);
}
else
{
echoed = true;
}
}
}
if ((dev->tc_lflag & ICANON) && ch == '\n')
{
break;
}
}
else
{
flags = uart_spinlock(dev, false);
uart_disablerxint(dev);
* between the last time that we checked and when we disabled
* interrupts.
*/
if (rxbuf->head == rxbuf->tail)
{
* additional data to be received.
*/
#ifdef CONFIG_SERIAL_RXDMA
uart_dmarxfree(dev);
#endif
* disabled briefly to assure that the following operations
* are atomic.
*/
uart_enablerxint(dev);
* might have buffered data received between disabling the
* RX interrupt and entering the critical section. Some
* drivers (looking at you, cdcacm...) will push the buffer
* to the receive queue during uart_enablerxint().
* Just continue processing the RX queue if this happens.
*/
if (rxbuf->head != rxbuf->tail)
{
uart_spinunlock(dev, false, flags);
continue;
}
#ifdef CONFIG_DEV_SERIAL_FULLBLOCKS
* If the user has specified the O_NONBLOCK option, then just
* return what we have.
*/
else if ((filep->f_oflags & O_NONBLOCK) != 0)
{
* error (not zero which means end of file).
*/
if (recvd < 1)
{
recvd = -EAGAIN;
}
uart_spinunlock(dev, false, flags);
break;
}
#else
* anything to the caller?
*/
else if (recvd > 0 && !(dev->tc_lflag & ICANON))
{
* of bytes received up to the wait condition.
*/
uart_spinunlock(dev, false, flags);
break;
}
else if (filep->f_inode == 0)
{
* Descriptor is not valid.
*/
recvd = -EBADFD;
uart_spinunlock(dev, false, flags);
break;
}
* If the user has specified the O_NONBLOCK option, then do not
* wait.
*/
else if ((filep->f_oflags & O_NONBLOCK) != 0)
{
recvd = -EAGAIN;
uart_spinunlock(dev, false, flags);
break;
}
#endif
#ifdef CONFIG_SERIAL_REMOVABLE
* while we have interrupts off. We do not want the transition
* to occur as a race condition before we begin the wait.
*/
if (dev->disconnected)
{
ret = -ENOTCONN;
}
else
#endif
{
* automatically re-enable global interrupts when this
* thread goes to sleep.
*/
if (dev->tc_lflag & ICANON)
{
#ifdef CONFIG_SERIAL_TERMIOS
dev->minrecv = 0;
#endif
uart_spinunlock(dev, false, flags);
nxmutex_unlock(&dev->recv.lock);
ret = nxsem_wait(&dev->recvsem);
nxmutex_lock(&dev->recv.lock);
flags = uart_spinlock(dev, false);
}
else
{
#ifdef CONFIG_SERIAL_TERMIOS
dev->minrecv = MIN(buflen - recvd,
dev->minread - recvd);
if (dev->timeout)
{
uart_spinunlock(dev, false, flags);
nxmutex_unlock(&dev->recv.lock);
ret = nxsem_tickwait(&dev->recvsem,
DSEC2TICK(dev->timeout));
}
else
#endif
{
uart_spinunlock(dev, false, flags);
nxmutex_unlock(&dev->recv.lock);
ret = nxsem_wait(&dev->recvsem);
}
nxmutex_lock(&dev->recv.lock);
flags = uart_spinlock(dev, false);
#ifdef CONFIG_SERIAL_TERMIOS
dev->minrecv = dev->minread;
#endif
}
}
uart_spinunlock(dev, false, flags);
* received? Was a removable device disconnected while
* we were waiting?
*/
#ifdef CONFIG_SERIAL_REMOVABLE
if (ret < 0 || dev->disconnected)
#else
if (ret < 0)
#endif
{
* received.
* If some bytes were read, we need to return the
* number of bytes read; if no bytes were read, we
* need to return -1 with the errno set correctly.
*/
if (recvd == 0)
{
* (the VFS layer will set the errno value
* appropriately).
*/
#ifdef CONFIG_SERIAL_REMOVABLE
recvd = dev->disconnected ? -ENOTCONN : ret;
#else
recvd = ret;
#endif
}
break;
}
}
else
{
* interrupts and accept the new data on the next time through
* the loop.
*/
uart_spinunlock(dev, false, flags);
uart_enablerxint(dev);
}
}
}
if (echoed)
{
#ifdef CONFIG_SERIAL_TXDMA
flags = uart_spinlock(dev, true);
uart_dmatxavail(dev);
uart_spinunlock(dev, true, flags);
#endif
uart_enabletxint(dev);
}
#ifdef CONFIG_SERIAL_RXDMA
flags = uart_spinlock(dev, false);
uart_dmarxfree(dev);
uart_spinunlock(dev, false, flags);
#endif
uart_enablerxint(dev);
#ifdef CONFIG_SERIAL_IFLOWCONTROL
#ifdef CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS
* to a non-volatile variable to prevent TOCTOU error in case
* the interrupt handler changes it between comparison and assignment.
* (Copy of tail is not strictly needed but saves us few instructions.)
*/
rxbuf = &dev->recv;
head = rxbuf->head;
tail = rxbuf->tail;
if (head >= tail)
{
nbuffered = head - tail;
}
else
{
nbuffered = rxbuf->size - tail + head;
}
watermark = (CONFIG_SERIAL_IFLOWCONTROL_LOWER_WATERMARK *
rxbuf->size) / 100;
if (nbuffered <= watermark)
{
* crossed. It will probably deactivate RX flow control.
*/
uart_rxflowcontrol(dev, nbuffered, false);
}
#else
if (rxbuf->head == rxbuf->tail)
{
uart_rxflowcontrol(dev, 0, false);
}
#endif
#endif
nxmutex_unlock(&dev->recv.lock);
return recvd;
}
* Name: uart_write
****************************************************************************/
static ssize_t uart_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen)
{
FAR struct inode *inode = filep->f_inode;
FAR uart_dev_t *dev = inode->i_private;
ssize_t nwritten = buflen;
bool oktoblock;
irqstate_t flags;
int ret;
char ch;
* and from debug output in the IDLE task! In these cases, we will need to
* do things a little differently.
*/
if (up_interrupt_context() || sched_idletask())
{
#ifdef CONFIG_SERIAL_REMOVABLE
* the device.
*/
if (dev->disconnected)
{
return -ENOTCONN;
}
#endif
flags = uart_spinlock(dev, false);
ret = uart_irqwrite(dev, buffer, buflen);
uart_spinunlock(dev, false, flags);
return ret;
}
ret = nxmutex_lock(&dev->xmit.lock);
if (ret < 0)
{
* abort the transfer. After the transfer has started, we are
* committed and signals will be ignored.
*/
return ret;
}
#ifdef CONFIG_SERIAL_REMOVABLE
* device. This check occurs after taking the xmit.lock because the
* disconnection event might have occurred while we were waiting for
* access to the transmit buffers.
*/
if (dev->disconnected)
{
nxmutex_unlock(&dev->xmit.lock);
return -ENOTCONN;
}
#endif
* buffer?
*/
oktoblock = ((filep->f_oflags & O_NONBLOCK) == 0);
* we add data to the head of the buffer; uart_xmitchars takes the
* data from the end of the buffer.
*/
uart_disabletxint(dev);
for (; buflen; buflen--)
{
ch = *buffer++;
ret = OK;
if ((dev->tc_oflag & OPOST) != 0)
{
if ((ch == '\r') && (dev->tc_oflag & OCRNL) != 0)
{
ch = '\n';
}
if ((ch == '\n') && (dev->tc_oflag & (ONLCR | ONLRET)) != 0)
{
ret = uart_putxmitchar(dev, '\r', oktoblock);
}
*
* OXTABS - primarily a full-screen terminal optimization
* ONOEOT - Unix interoperability hack
* OLCUC - Not specified by POSIX
* ONOCR - low-speed interactive optimization
*/
}
if (ret >= 0)
{
ret = uart_putxmitchar(dev, ch, oktoblock);
}
* conditions: (1) The wait for buffer space might have been
* interrupted by a signal (ret should be -EINTR), (2) if
* CONFIG_SERIAL_REMOVABLE is defined, then uart_putxmitchar()
* might also return if the serial device was disconnected
* (with -ENOTCONN), or (3) if O_NONBLOCK is specified, then
* then uart_putxmitchar() might return -EAGAIN if the output
* TX buffer is full.
*/
if (ret < 0)
{
* transferred. Otherwise, we return the number of bytes in the
* interrupted transfer.
*/
if (buflen < (size_t)nwritten)
{
* were successfully transferred.
*/
nwritten -= buflen;
}
else
{
* The VFS layer will set the errno value appropriately).
*/
nwritten = ret;
}
break;
}
}
if (dev->xmit.head != dev->xmit.tail)
{
#ifdef CONFIG_SERIAL_TXDMA
flags = uart_spinlock(dev, true);
uart_dmatxavail(dev);
uart_spinunlock(dev, true, flags);
#endif
uart_enabletxint(dev);
}
nxmutex_unlock(&dev->xmit.lock);
return nwritten;
}
* Name: uart_ioctl
****************************************************************************/
static int uart_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct inode *inode = filep->f_inode;
FAR uart_dev_t *dev = inode->i_private;
FAR struct termios *termiosp = (FAR struct termios *)(uintptr_t)arg;
int ret = -ENOTTY;
* but skip TCSETS if no hardware change.
*/
if (dev->ops->ioctl && (cmd != TCSETS ||
uart_is_termios_hw_change(filep, termiosp)))
{
ret = dev->ops->ioctl(filep, cmd, arg);
}
* how to handle the command. Check if we can handle it here.
*/
if (ret == -ENOTTY)
{
switch (cmd)
{
* (without waiting)
*/
case FIONREAD:
{
int count;
irqstate_t flags = uart_spinlock(dev, false);
if (dev->recv.tail <= dev->recv.head)
{
count = dev->recv.head - dev->recv.tail;
}
else
{
count = dev->recv.size - (dev->recv.tail - dev->recv.head);
}
uart_spinunlock(dev, false, flags);
*(FAR int *)((uintptr_t)arg) = count;
ret = 0;
}
break;
* buffer.
*/
case FIONWRITE:
{
int count;
irqstate_t flags = uart_spinlock(dev, false);
if (dev->xmit.tail <= dev->xmit.head)
{
count = dev->xmit.head - dev->xmit.tail;
}
else
{
count = dev->xmit.size - (dev->xmit.tail - dev->xmit.head);
}
uart_spinunlock(dev, false, flags);
*(FAR int *)((uintptr_t)arg) = count;
ret = 0;
}
break;
case FIONSPACE:
{
int count;
irqstate_t flags = uart_spinlock(dev, false);
if (dev->xmit.head < dev->xmit.tail)
{
count = dev->xmit.tail - dev->xmit.head - 1;
}
else
{
count = dev->xmit.size -
(dev->xmit.head - dev->xmit.tail) - 1;
}
uart_spinunlock(dev, false, flags);
*(FAR int *)((uintptr_t)arg) = count;
ret = 0;
}
break;
case TCFLSH:
{
irqstate_t flags = uart_spinlock(dev, true);
if (arg == TCIFLUSH || arg == TCIOFLUSH)
{
dev->recv.tail = dev->recv.head;
#ifdef CONFIG_SERIAL_IFLOWCONTROL
uart_rxflowcontrol(dev, 0, false);
#endif
}
if (arg == TCOFLUSH || arg == TCIOFLUSH)
{
dev->xmit.tail = dev->xmit.head;
uart_datasent(dev);
}
uart_spinunlock(dev, true, flags);
ret = 0;
}
break;
case TCDRN:
{
ret = uart_tcdrain(dev, true, 10 * TICK_PER_SEC);
}
break;
case TCSBRK:
{
ret = uart_tcsendbreak(dev, filep, arg);
}
break;
case TCSBRKP:
{
ret = uart_tcsendbreak(dev, filep, arg * 100);
}
break;
#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP)
case TIOCSCTTY:
{
if ((int)arg < 0 || dev->pid >= 0)
{
ret = -EINVAL;
}
else
{
dev->pid = (pid_t)arg;
ret = 0;
}
}
break;
case TIOCNOTTY:
{
dev->pid = INVALID_PROCESS_ID;
ret = 0;
}
break;
#endif
}
}
if (ret == OK || ret == -ENOTTY)
{
switch (cmd)
{
case TCGETS:
{
if (!termiosp)
{
ret = -EINVAL;
break;
}
termiosp->c_iflag = dev->tc_iflag;
termiosp->c_oflag = dev->tc_oflag;
termiosp->c_lflag = dev->tc_lflag;
#ifdef CONFIG_SERIAL_TERMIOS
termiosp->c_cc[VTIME] = dev->timeout;
termiosp->c_cc[VMIN] = dev->minread;
#endif
ret = 0;
}
break;
case TCSETS:
{
if (!termiosp)
{
ret = -EINVAL;
break;
}
dev->tc_iflag = termiosp->c_iflag;
dev->tc_oflag = termiosp->c_oflag;
dev->tc_lflag = termiosp->c_lflag;
#ifdef CONFIG_SERIAL_TERMIOS
dev->timeout = termiosp->c_cc[VTIME];
dev->minread = termiosp->c_cc[VMIN];
dev->minrecv = dev->minread;
#endif
ret = 0;
}
break;
}
}
return ret;
}
* Name: uart_poll
****************************************************************************/
static int uart_poll(FAR struct file *filep,
FAR struct pollfd *fds, bool setup)
{
FAR struct inode *inode = filep->f_inode;
FAR uart_dev_t *dev = inode->i_private;
pollevent_t eventset;
irqstate_t flags;
int ndx;
int ret = OK;
int i;
#ifdef CONFIG_DEBUG_FEATURES
if (dev == NULL || fds == NULL)
{
return -ENODEV;
}
#endif
flags = uart_spinlock(dev, false);
if (setup)
{
* slot for the poll structure reference
*/
for (i = 0; i < CONFIG_SERIAL_NPOLLWAITERS; i++)
{
if (!dev->fds[i])
{
dev->fds[i] = fds;
fds->priv = &dev->fds[i];
break;
}
}
if (i >= CONFIG_SERIAL_NPOLLWAITERS)
{
fds->priv = NULL;
ret = -EBUSY;
goto errout;
}
uart_spinunlock(dev, false, flags);
* First, check if the xmit buffer is full.
*
* Get exclusive access to the xmit buffer indices.
* NOTE: that we do not let this wait be interrupted by a signal
* (we probably should, but that would be a little awkward).
*/
eventset = 0;
nxmutex_lock(&dev->xmit.lock);
ndx = dev->xmit.head + 1;
if (ndx >= dev->xmit.size)
{
ndx = 0;
}
if (ndx != dev->xmit.tail)
{
eventset |= POLLOUT;
}
nxmutex_unlock(&dev->xmit.lock);
*
* Get exclusive access to the recv buffer indices.
* NOTE: that we do not let this wait be interrupted by a signal
* (we probably should, but that would be a little awkward).
*/
nxmutex_lock(&dev->recv.lock);
if (dev->recv.head != dev->recv.tail)
{
eventset |= POLLIN;
}
nxmutex_unlock(&dev->recv.lock);
#ifdef CONFIG_SERIAL_REMOVABLE
if (dev->disconnected)
{
eventset |= (POLLERR | POLLHUP);
}
#endif
uart_poll_notify(dev, i, i + 1, eventset);
}
else if (fds->priv != NULL)
{
FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;
#ifdef CONFIG_DEBUG_FEATURES
if (!slot)
{
ret = -EIO;
goto errout;
}
#endif
*slot = NULL;
fds->priv = NULL;
uart_spinunlock(dev, false, flags);
}
return ret;
errout:
uart_spinunlock(dev, false, flags);
return ret;
}
* Name: uart_unlink
****************************************************************************/
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int uart_unlink(FAR struct inode *inode)
{
FAR uart_dev_t *dev;
int ret;
DEBUGASSERT(inode->i_private != NULL);
dev = inode->i_private;
ret = nxmutex_lock(&dev->closelock);
if (ret < 0)
{
return ret;
}
if (dev->open_count <= 0)
{
nxmutex_unlock(&dev->closelock);
nxmutex_destroy(&dev->xmit.lock);
nxmutex_destroy(&dev->recv.lock);
nxmutex_destroy(&dev->closelock);
nxsem_destroy(&dev->xmitsem);
nxsem_destroy(&dev->recvsem);
uart_release(dev);
return OK;
}
dev->unlinked = true;
nxmutex_unlock(&dev->closelock);
return OK;
}
#endif
* Name: uart_nxsched_foreach_cb
****************************************************************************/
#ifdef CONFIG_TTY_LAUNCH
static void uart_launch_foreach(FAR struct tcb_s *tcb, FAR void *arg)
{
#ifdef CONFIG_TTY_LAUNCH_ENTRY
if (!strcmp(get_task_name(tcb), CONFIG_TTY_LAUNCH_ENTRYNAME))
#else
if (!strcmp(get_task_name(tcb), CONFIG_TTY_LAUNCH_FILEPATH))
#endif
{
*(FAR int *)arg = 1;
}
}
static void uart_launch_worker(void *arg)
{
#ifdef CONFIG_TTY_LAUNCH_ARGS
FAR char *const argv[] =
{
CONFIG_TTY_LAUNCH_ARGS,
NULL,
};
#else
FAR char *const *argv = NULL;
#endif
int found = 0;
nxsched_foreach(uart_launch_foreach, &found);
if (!found)
{
posix_spawnattr_t attr;
posix_spawnattr_init(&attr);
attr.priority = CONFIG_TTY_LAUNCH_PRIORITY;
attr.stacksize = CONFIG_TTY_LAUNCH_STACKSIZE;
#ifdef CONFIG_TTY_LAUNCH_ENTRY
task_spawn(CONFIG_TTY_LAUNCH_ENTRYNAME,
CONFIG_TTY_LAUNCH_ENTRYPOINT,
NULL, &attr, argv, NULL);
#else
exec_spawn(CONFIG_TTY_LAUNCH_FILEPATH,
argv, NULL, NULL, 0, NULL, &attr);
#endif
posix_spawnattr_destroy(&attr);
}
}
static void uart_launch(void)
{
work_queue(HPWORK, &g_serial_work, uart_launch_worker, NULL, 0);
}
#endif
static void uart_wakeup(FAR sem_t *sem)
{
int sval;
if (nxsem_get_value(sem, &sval) != OK)
{
return;
}
while (sval++ < 1)
{
nxsem_post(sem);
}
}
* Public Functions
****************************************************************************/
* Name: uart_spinlock
*
* Description:
* Enter critical section for this uart.
*
****************************************************************************/
irqstate_t uart_spinlock(FAR uart_dev_t *dev, bool nopreempt)
{
if (nopreempt)
{
return rspin_lock_irqsave_nopreempt(&dev->lock);
}
return rspin_lock_irqsave(&dev->lock);
}
* Name: uart_spinunlock
*
* Description:
* Leave critical section for this uart.
*
****************************************************************************/
void uart_spinunlock(FAR uart_dev_t *dev, bool nopreempt, irqstate_t flags)
{
if (nopreempt)
{
rspin_unlock_irqrestore_nopreempt(&dev->lock, flags);
return;
}
rspin_unlock_irqrestore(&dev->lock, flags);
}
* Name: uart_register
*
* Description:
* Register serial console and serial ports.
*
****************************************************************************/
int uart_register(FAR const char *path, FAR uart_dev_t *dev)
{
#if defined(CONFIG_PM) && defined(CONFIG_SERIAL_CONSOLE)
char buf[NAME_MAX];
#endif
int ret;
#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP)
dev->pid = INVALID_PROCESS_ID;
#endif
#ifdef CONFIG_TTY_FORCE_PANIC
dev->panic_count = 0;
#endif
if (dev->isconsole)
{
dev->tc_lflag |= ISIG | ECHO | ICANON;
dev->tc_oflag = OPOST | ONLCR;
dev->tc_iflag |= ICRNL;
dev->escape = 0;
}
nxmutex_init(&dev->xmit.lock);
nxmutex_init(&dev->recv.lock);
nxmutex_init(&dev->closelock);
nxsem_init(&dev->xmitsem, 0, 0);
nxsem_init(&dev->recvsem, 0, 0);
rspin_lock_init(&dev->lock);
#ifdef CONFIG_SERIAL_TERMIOS
dev->timeout = 0;
dev->minread = 1;
#endif
#if defined(CONFIG_PM) && defined(CONFIG_SERIAL_CONSOLE)
snprintf(buf, sizeof(buf), "console-%s", basename((char *)path));
pm_wakelock_init(&dev->wakelock, buf, PM_IDLE_DOMAIN, PM_NORMAL);
#endif
sinfo("Registering %s\n", path);
ret = register_driver(path, &g_serialops, 0666, dev);
if (ret < 0)
{
return ret;
}
#ifdef CONFIG_SERIAL_GDBSTUB
ret = uart_gdbstub_register(dev, path);
#endif
return ret;
}
* Name: uart_datareceived
*
* Description:
* This function is called from uart_recvchars when new serial data is
* place in the driver's circular buffer. This function will wake-up any
* stalled read() operations that are waiting for incoming data.
*
****************************************************************************/
void uart_datareceived(FAR uart_dev_t *dev)
{
uart_poll_notify(dev, 0, CONFIG_SERIAL_NPOLLWAITERS, POLLIN);
uart_wakeup(&dev->recvsem);
#if defined(CONFIG_PM) && defined(CONFIG_SERIAL_CONSOLE)
* on the console device
*/
if (dev->isconsole)
{
# if CONFIG_SERIAL_PM_ACTIVITY_PRIORITY > 0
pm_wakelock_staytimeout(&dev->wakelock,
CONFIG_SERIAL_PM_ACTIVITY_PRIORITY * MSEC_PER_SEC);
# endif
}
#endif
}
* Name: uart_datasent
*
* Description:
* This function is called from uart_xmitchars after serial data has been
* sent, freeing up some space in the driver's circular buffer. This
* function will wake-up any stalled write() operations that was waiting
* for space to buffer outgoing data.
*
****************************************************************************/
void uart_datasent(FAR uart_dev_t *dev)
{
uart_poll_notify(dev, 0, CONFIG_SERIAL_NPOLLWAITERS, POLLOUT);
uart_wakeup(&dev->xmitsem);
}
* Name: uart_connected
*
* Description:
* Serial devices (like USB serial) can be removed.
* In that case, the "upper half" serial driver must be informed that there
* is no longer a valid serial channel associated with the driver.
*
* In this case, the driver will terminate all pending transfers wint
* ENOTCONN and will refuse all further transactions while the "lower half"
* is disconnected.
* The driver will continue to be registered, but will be in an unusable
* state.
*
* Conversely, the "upper half" serial driver needs to know when the serial
* device is reconnected so that it can resume normal operations.
*
* Assumptions/Limitations:
* This function may be called from an interrupt handler.
*
****************************************************************************/
#ifdef CONFIG_SERIAL_REMOVABLE
void uart_connected(FAR uart_dev_t *dev, bool connected)
{
irqstate_t flags;
* function may be called from interrupt handling logic.
*/
flags = uart_spinlock(dev, true);
dev->disconnected = !connected;
if (!connected)
{
poll_notify(dev->fds, CONFIG_SERIAL_NPOLLWAITERS, POLLERR | POLLHUP);
* disconnection and return the ENOTCONN error.
*/
uart_wakeup(&dev->xmitsem);
uart_wakeup(&dev->recvsem);
}
uart_spinunlock(dev, true, flags);
}
#endif
* Name: uart_reset_sem
*
* Description:
* This function is called when need reset uart semaphore, this may used in
* kill one process, but this process was reading/writing with the
* semaphore.
*
****************************************************************************/
void uart_reset_sem(FAR uart_dev_t *dev)
{
nxsem_reset(&dev->xmitsem, 0);
nxsem_reset(&dev->recvsem, 0);
nxmutex_reset(&dev->xmit.lock);
nxmutex_reset(&dev->recv.lock);
}
* Name: uart_check_special
*
* Description:
* Check if the SIGINT or SIGTSTP character is in the contiguous Rx DMA
* buffer region. The first signal associated with the first such
* character is returned.
*
* If there multiple such characters in the buffer, only the signal
* associated with the first is returned (this a bug!)
*
* Returned Value:
* 0 if a signal-related character does not appear in the. Otherwise,
* SIGKILL or SIGTSTP may be returned to indicate the appropriate signal
* action.
*
****************************************************************************/
#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP) || \
defined(CONFIG_TTY_FORCE_PANIC) || defined(CONFIG_TTY_LAUNCH)
int uart_check_special(FAR uart_dev_t *dev, FAR const char *buf, size_t size)
{
size_t i;
if ((dev->tc_lflag & ISIG) == 0)
{
return 0;
}
for (i = 0; i < size; i++)
{
#ifdef CONFIG_TTY_FORCE_PANIC
if (buf[i] == CONFIG_TTY_FORCE_PANIC_CHAR)
{
if (++dev->panic_count >= CONFIG_TTY_FORCE_PANIC_REPEAT_COUNT)
{
PANIC_WITH_REGS("Force panic by user.", NULL);
}
return 0;
}
else
{
dev->panic_count = 0;
}
#endif
#ifdef CONFIG_TTY_LAUNCH
if (buf[i] == CONFIG_TTY_LAUNCH_CHAR)
{
uart_launch();
return 0;
}
#endif
#ifdef CONFIG_TTY_SIGINT
if (dev->pid > 0 && buf[i] == CONFIG_TTY_SIGINT_CHAR)
{
return SIGINT;
}
#endif
#ifdef CONFIG_TTY_SIGTSTP
if (dev->pid > 0 && buf[i] == CONFIG_TTY_SIGTSTP_CHAR)
{
return SIGTSTP;
}
#endif
}
return 0;
}
#endif