aboutsummaryrefslogtreecommitdiff
path: root/nuttx/drivers/serial/serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'nuttx/drivers/serial/serial.c')
-rw-r--r--nuttx/drivers/serial/serial.c221
1 files changed, 206 insertions, 15 deletions
diff --git a/nuttx/drivers/serial/serial.c b/nuttx/drivers/serial/serial.c
index 8987f01b8..0fed1d6c5 100644
--- a/nuttx/drivers/serial/serial.c
+++ b/nuttx/drivers/serial/serial.c
@@ -1,7 +1,7 @@
/************************************************************************************
* drivers/serial/serial.c
*
- * Copyright (C) 2007-2009, 2011-2012 Gregory Nutt. All rights reserved.
+ * Copyright (C) 2007-2009, 2011-2013 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
@@ -158,7 +158,11 @@ static void uart_pollnotify(FAR uart_dev_t *dev, pollevent_t eventset)
struct pollfd *fds = dev->fds[i];
if (fds)
{
+#ifdef CONFIG_SERIAL_REMOVABLE
+ fds->revents |= ((fds->events | (POLLERR|POLLHUP)) & eventset);
+#else
fds->revents |= (fds->events & eventset);
+#endif
if (fds->revents != 0)
{
fvdbg("Report events: %02x\n", fds->revents);
@@ -208,18 +212,40 @@ static int uart_putxmitchar(FAR uart_dev_t *dev, int ch)
*/
flags = irqsave();
- dev->xmitwaiting = true;
+#ifdef CONFIG_SERIAL_REMOVABLE
+ /* Check if the removable device is no longer connected 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)
+ {
+ irqrestore(flags);
+ return -ENOTCONN;
+ }
+#endif
/* Wait for some characters to be sent from the buffer with the TX
* interrupt enabled. When the TX interrupt is enabled, uart_xmitchars
* should execute and remove some of the data from the TX buffer.
*/
+ dev->xmitwaiting = true;
uart_enabletxint(dev);
ret = uart_takesem(&dev->xmitsem, true);
uart_disabletxint(dev);
irqrestore(flags);
+#ifdef CONFIG_SERIAL_REMOVABLE
+ /* Check if the removable device was disconnected while we were
+ * waiting.
+ */
+
+ if (dev->disconnected)
+ {
+ return -ENOTCONN;
+ }
+#endif
/* Check if we were awakened by signal. */
if (ret < 0)
@@ -288,6 +314,17 @@ static ssize_t uart_write(FAR struct file *filep, FAR const char *buffer, size_t
if (up_interrupt_context() || getpid() == 0)
{
+#ifdef CONFIG_SERIAL_REMOVABLE
+ /* If the removable device is no longer connected, refuse to write to
+ * the device.
+ */
+
+ if (dev->disconnected)
+ {
+ return -ENOTCONN;
+ }
+#endif
+
/* up_putc() will be used to generate the output in a busy-wait loop.
* up_putc() is only available for the console device.
*/
@@ -317,6 +354,20 @@ static ssize_t uart_write(FAR struct file *filep, FAR const char *buffer, size_t
return ret;
}
+#ifdef CONFIG_SERIAL_REMOVABLE
+ /* If the removable device is no longer connected, refuse to write to the
+ * device. This check occurs after taking the xmit.sem because the
+ * disconnection event might have occurred while we were waiting for
+ * access to the transmit buffers.
+ */
+
+ if (dev->disconnected)
+ {
+ uart_givesem(&dev->xmit.sem);
+ return -ENOTCONN;
+ }
+#endif
+
/* Loop while we still have data to copy to the transmit buffer.
* we add data to the head of the buffer; uart_xmitchars takes the
* data from the end of the buffer.
@@ -392,9 +443,13 @@ static ssize_t uart_write(FAR struct file *filep, FAR const char *buffer, size_t
uart_givesem(&dev->xmit.sem);
- /* Were we interrupted by a signal? That should be the only condition that
- * uart_putxmitchar() should return an error.
- */
+ /* uart_putxmitchar() might return an error under one of two
+ * conditions: (1) The wait for buffer space might have been
+ * interrupted by a signal (ret should be -EINTR), or (2) if
+ * CONFIG_SERIAL_REMOVABLE is defined, then uart_putxmitchar()
+ * might also return if the serial device was disconnected
+ * (wtih -ENOTCONN).
+ */
if (ret < 0)
{
@@ -413,11 +468,11 @@ static ssize_t uart_write(FAR struct file *filep, FAR const char *buffer, size_t
}
else
{
- /* No data was transferred. Return -EINTR. The VFS layer will
- * set the errno value appropriately).
+ /* No data was transferred. Return the negated error. The VFS layer
+ * will set the errno value appropriately).
*/
- nread = -EINTR;
+ nread = -ret;
}
}
@@ -458,6 +513,22 @@ static ssize_t uart_read(FAR struct file *filep, FAR char *buffer, size_t buflen
while (recvd < buflen)
{
+#ifdef CONFIG_SERIAL_REMOVABLE
+ /* If the removable device is no longer connected, refuse to read any
+ * further from the device.
+ */
+
+ if (dev->disconnected)
+ {
+ if (recvd == 0)
+ {
+ recvd = -ENOTCONN;
+ }
+
+ break;
+ }
+#endif
+
/* Check if there is more data to return in the circular buffer.
* NOTE: Rx interrupt handling logic may aynchronously increment
* the head index but must not modify the tail index. The tail
@@ -548,6 +619,7 @@ static ssize_t uart_read(FAR struct file *filep, FAR char *buffer, size_t buflen
{
recvd = -EAGAIN;
}
+
break;
}
#else
@@ -599,20 +671,41 @@ static ssize_t uart_read(FAR struct file *filep, FAR char *buffer, size_t buflen
*/
flags = irqsave();
- dev->recvwaiting = true;
- uart_enablerxint(dev);
+#ifdef CONFIG_SERIAL_REMOVABLE
+ /* Check if the removable device is no longer connected 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)
+ {
+ uart_enablerxint(dev);
+ irqrestore(flags);
+ ret = -ENOTCONN;
+ break;
+ }
+#endif
/* Now wait with the Rx interrupt re-enabled. NuttX will
* automatically re-enable global interrupts when this thread
* goes to sleep.
*/
+ dev->recvwaiting = true;
+ uart_enablerxint(dev);
ret = uart_takesem(&dev->recvsem, true);
irqrestore(flags);
- /* Was a signal received while waiting for data to be received? */
+ /* Was a signal received while waiting for data to be
+ * received? Was a removable device disconnected while
+ * we were waiting?
+ */
+#ifdef CONFIG_SERIAL_REMOVABLE
+ if (ret < 0 || dev->disconnected)
+#else
if (ret < 0)
+#endif
{
/* POSIX requires that we return after a signal is received.
* If some bytes were read, we need to return the number of bytes
@@ -626,7 +719,11 @@ static ssize_t uart_read(FAR struct file *filep, FAR char *buffer, size_t buflen
* set the errno value appropriately.
*/
+#ifdef CONFIG_SERIAL_REMOVABLE
+ recvd = dev->disconnected ? -ENOTCONN : -EINTR;
+#else
recvd = -EINTR;
+#endif
}
break;
@@ -852,12 +949,12 @@ int uart_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup)
if (ndx != dev->xmit.tail)
{
- eventset |= POLLOUT;
+ eventset |= (fds->events & POLLOUT);
}
uart_givesem(&dev->xmit.sem);
- /* Check if the receive buffer is empty
+ /* Check if the receive buffer is empty.
*
* 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
@@ -867,11 +964,20 @@ int uart_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup)
(void)uart_takesem(&dev->recv.sem, false);
if (dev->recv.head != dev->recv.tail)
{
- eventset |= POLLIN;
+ eventset |= (fds->events & POLLIN);
}
uart_givesem(&dev->recv.sem);
+#ifdef CONFIG_SERIAL_REMOVABLE
+ /* Check if a removable device has been disconnected. */
+
+ if (dev->disconnected)
+ {
+ eventset |= (POLLERR|POLLHUP);
+ }
+#endif
+
if (eventset)
{
uart_pollnotify(dev, eventset);
@@ -971,6 +1077,7 @@ static int uart_close(FAR struct file *filep)
{
uart_shutdown(dev); /* Disable the UART */
}
+
irqrestore(flags);
uart_givesem(&dev->closesem);
@@ -1004,6 +1111,19 @@ static int uart_open(FAR struct file *filep)
return ret;
}
+#ifdef CONFIG_SERIAL_REMOVABLE
+ /* If the removable device is no longer connected, refuse to open the
+ * 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_sem;
+ }
+#endif
+
/* Start up serial port */
/* Increment the count of references to the device. */
@@ -1145,10 +1265,12 @@ int uart_register(FAR const char *path, FAR uart_dev_t *dev)
void uart_datareceived(FAR uart_dev_t *dev)
{
- /* Awaken any awaiting read() operations */
+ /* Is there a thread waiting for read data? */
if (dev->recvwaiting)
{
+ /* Yes... wake it up */
+
dev->recvwaiting = false;
(void)sem_post(&dev->recvsem);
}
@@ -1172,8 +1294,12 @@ void uart_datareceived(FAR uart_dev_t *dev)
void uart_datasent(FAR uart_dev_t *dev)
{
+ /* Is there a thread waiting for space in xmit.buffer? */
+
if (dev->xmitwaiting)
{
+ /* Yes... wake it up */
+
dev->xmitwaiting = false;
(void)sem_post(&dev->xmitsem);
}
@@ -1183,4 +1309,69 @@ void uart_datasent(FAR uart_dev_t *dev)
uart_pollnotify(dev, POLLOUT);
}
+/************************************************************************************
+ * 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;
+
+ /* Is the device disconnected? */
+
+ flags = irqsave();
+ dev->disconnected = !connected;
+ if (!connected)
+ {
+ /* Yes.. wake up all waiting threads. Each thread should detect the
+ * disconnection and return the ENOTCONN error.
+ */
+
+ /* Is there a thread waiting for space in xmit.buffer? */
+
+ if (dev->xmitwaiting)
+ {
+ /* Yes... wake it up */
+
+ dev->xmitwaiting = false;
+ (void)sem_post(&dev->xmitsem);
+ }
+
+ /* Is there a thread waiting for read data? */
+
+ if (dev->recvwaiting)
+ {
+ /* Yes... wake it up */
+
+ dev->recvwaiting = false;
+ (void)sem_post(&dev->recvsem);
+ }
+
+ /* Notify all poll/select waiters that and hangup occurred */
+
+ uart_pollnotify(dev, (POLLERR|POLLHUP));
+ }
+
+ irqrestore(flags);
+}
+#endif
+
+