diff options
Diffstat (limited to 'nuttx/drivers/serial/serial.c')
-rw-r--r-- | nuttx/drivers/serial/serial.c | 221 |
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 + + |