diff --git a/drivers/serial/serial.c b/drivers/serial/serial.c index 4d053060d7..0fda561818 100644 --- a/drivers/serial/serial.c +++ b/drivers/serial/serial.c @@ -90,6 +90,10 @@ static inline ssize_t uart_irqwrite(FAR uart_dev_t *dev, 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); + /* Character driver methods */ static int uart_open(FAR struct file *filep); @@ -485,6 +489,87 @@ static int uart_tcdrain(FAR uart_dev_t *dev, 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; + + /* tcsendbreak is a cancellation point */ + + if (enter_cancellation_point()) + { +#ifdef CONFIG_CANCELLATION_POINTS + /* If there is a pending cancellation, then do not perform + * the wait. Exit now with ECANCELED. + */ + + leave_cancellation_point(); + return -ECANCELED; +#endif + } + + /* REVISIT: Do we need to perform the equivalent of tcdrain() before + * 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) + { + /* Request lower half driver to start the Break */ + + ret = dev->ops->ioctl(filep, TIOCSBRK, 0); + if (ret >= 0) + { + /* Wait 400 ms or the requested Break duration */ + + nxsig_usleep((ms == 0) ? 400000 : ms * 1000); + + /* Request lower half driver to end the Break */ + + ret = dev->ops->ioctl(filep, TIOCCBRK, 0); + } + } + + nxmutex_unlock(&dev->xmit.lock); + } + else + { + /* With no lower half IOCTL, we cannot request Break at all. */ + + ret = -ENOTTY; + } + + leave_cancellation_point(); + return ret; +} + /**************************************************************************** * Name: uart_open * @@ -1357,6 +1442,22 @@ static int uart_ioctl(FAR struct file *filep, int cmd, unsigned long arg) } break; + case TCSBRK: + { + /* Non-standard Break specifies duration in milliseconds */ + + ret = uart_tcsendbreak(dev, filep, arg); + } + break; + + case TCSBRKP: + { + /* POSIX Break specifies duration in units of 100ms */ + + ret = uart_tcsendbreak(dev, filep, arg * 100); + } + break; + #if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP) /* Make the controlling terminal of the calling process */ diff --git a/libs/libc/termios/Make.defs b/libs/libc/termios/Make.defs index ec44a79391..7e1217c664 100644 --- a/libs/libc/termios/Make.defs +++ b/libs/libc/termios/Make.defs @@ -25,7 +25,7 @@ CSRCS += lib_cfspeed.c lib_cfmakeraw.c lib_isatty.c lib_tcflush.c CSRCS += lib_tcdrain.c lib_tcflow.c lib_tcgetattr.c lib_tcsetattr.c -CSRCS += lib_ttyname.c lib_ttynamer.c +CSRCS += lib_tcsendbreak.c lib_ttyname.c lib_ttynamer.c # Add the termios directory to the build diff --git a/libs/libc/termios/lib_tcsendbreak.c b/libs/libc/termios/lib_tcsendbreak.c new file mode 100644 index 0000000000..088d177353 --- /dev/null +++ b/libs/libc/termios/lib_tcsendbreak.c @@ -0,0 +1,112 @@ +/**************************************************************************** + * libs/libc/termios/lib_tcsendbreak.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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#include +#include + +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tcsendbreak + * + * Description: + * Transmit a serial line Break, which is a continuous stream of 0 bits. + * + * A proper Break needs to last longer than a NUL ('\0') character. + * Effectively this causes a Framing Error at the receiver's UART. Most + * UARTs have the ability to distinguish between a Break and other types + * of Framing Errors and may treat it specially. Some devices utilize + * Breaks for things like synchronization. + * + * Regarding the duration parameter and its portability between Unix-like + * systems: It seems that a duration parameter of 0 specifies a Break of + * 0.25 to 0.5 seconds with consistency between various Unix flavors, but + * the interpretation of a nonzero duration parameter varies wildly from + * one operating system to another: + * + * - Linux, AIX, Tru64: duration in milliseconds + * - BSDs, macOS, HP-UX: ignore duration + * - Solaris, UnixWare: behaves like tcdrain() + * + * On FreeBSD and OpenBSD, a duration of 0 gives a Break of 0.4 seconds. + * + * Sources of information about the duration parameter include: the man + * page for termios(3) as found in release 5.13 of the Linux man-pages + * project, the man page for tcsendbreak(3) as found in FreeBSD 13.1 and + * OpenBSD 7.2, and some StackOverflow search results. + * + * In NuttX, we combine the above treatments of duration: Zero or negative + * values will give a Break of 0.4 seconds; positive values will be + * treated as a duration in milliseconds. We hope this combination will + * work well for programs that are ported from any of the above families. + * + * NuttX-specific implementation details: tcsendbreak() calls IOCTL + * TCSBRK. This is handled in the upper half serial driver (see + * drivers/serial/serial.c), which treats its argument like 'duration' + * described here, except unsigned, supporting only 0 or positive values; + * 0 maps to 400 and all other values are used as-is, in milliseconds. + * This, in turn, calls IOCTLs TIOCSBRK and TIOCCBRK to start and end a + * BSD-compatible Break, respectively, with a sleep of the given duration + * between them. For tcsendbreak() to work, the arch-specific lower half + * serial driver must implement TIOCSBRK and TIOCCBRK. Some architectures + * do not implement these at all. Others implement them but rely upon one + * or more Kconfig options being set, such as CONFIG_*_U[S]ART_BREAKS and, + * on some architectures, a separate CONFIG_*_SERIALBRK_BSDCOMPAT, to + * provide the required Break behavior. Furthermore, the sleep time will + * be at least the given duration, but may be substantially longer + * depending on the system's tick resolution (see CONFIG_USEC_PER_TICK) + * and other factors. + * + * This function blocks for the duration of the Break. + * + * Input Parameters: + * fd - The 'fd' argument is an open file descriptor associated + * with a terminal. + * duration - If 0 or negative, request a serial line Break lasting 0.4 + * seconds. If non-zero, request a serial line Break lasting + * that duration in milliseconds. + * + * Returned Value: + * Upon successful completion, 0 is returned. Otherwise, -1 is returned + * and errno is set to indicate the error. + * + ****************************************************************************/ + +int tcsendbreak(int fd, int duration) +{ + if (duration <= 0) + { + duration = 400; + } + + return ioctl(fd, TCSBRK, (unsigned long)duration); +}