summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/r3/posix/serialport-posix.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Runtime/r3/posix/serialport-posix.cpp
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Runtime/r3/posix/serialport-posix.cpp')
-rw-r--r--src/VBox/Runtime/r3/posix/serialport-posix.cpp1269
1 files changed, 1269 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r3/posix/serialport-posix.cpp b/src/VBox/Runtime/r3/posix/serialport-posix.cpp
new file mode 100644
index 00000000..0afbb676
--- /dev/null
+++ b/src/VBox/Runtime/r3/posix/serialport-posix.cpp
@@ -0,0 +1,1269 @@
+/* $Id: serialport-posix.cpp $ */
+/** @file
+ * IPRT - Serial Port API, POSIX Implementation.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/serialport.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/cdefs.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include "internal/magics.h"
+
+#include <errno.h>
+#ifdef RT_OS_SOLARIS
+# include <sys/termios.h>
+#else
+# include <termios.h>
+#endif
+#include <sys/types.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef RT_OS_DARWIN
+# include <sys/poll.h>
+#else
+# include <sys/poll.h>
+#endif
+#include <sys/ioctl.h>
+#include <pthread.h>
+
+#ifdef RT_OS_LINUX
+/*
+ * TIOCM_LOOP is not defined in the above header files for some reason but in asm/termios.h.
+ * But inclusion of this file however leads to compilation errors because of redefinition of some
+ * structs. That's why it is defined here until a better solution is found.
+ */
+# ifndef TIOCM_LOOP
+# define TIOCM_LOOP 0x8000
+# endif
+/* For linux custom baudrate code we also need serial_struct */
+# include <linux/serial.h>
+#endif /* linux */
+
+/** Define fallback if not supported. */
+#if !defined(CMSPAR)
+# define CMSPAR 0
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Internal serial port state.
+ */
+typedef struct RTSERIALPORTINTERNAL
+{
+ /** Magic value (RTSERIALPORT_MAGIC). */
+ uint32_t u32Magic;
+ /** Flags given while opening the serial port. */
+ uint32_t fOpenFlags;
+ /** The file descriptor of the serial port. */
+ int iFd;
+ /** The status line monitor thread if enabled. */
+ RTTHREAD hMonThrd;
+ /** Flag whether the monitoring thread should shutdown. */
+ volatile bool fMonThrdShutdown;
+ /** Reading end of wakeup pipe. */
+ int iFdPipeR;
+ /** Writing end of wakeup pipe. */
+ int iFdPipeW;
+ /** Event pending mask. */
+ volatile uint32_t fEvtsPending;
+ /** Flag whether we are in blocking or non blocking mode. */
+ bool fBlocking;
+ /** The current active config (we assume no one changes this behind our back). */
+ struct termios PortCfg;
+ /** Flag whether a custom baud rate was chosen (for hosts supporting this.). */
+ bool fBaudrateCust;
+ /** The custom baud rate. */
+ uint32_t uBaudRateCust;
+} RTSERIALPORTINTERNAL;
+/** Pointer to the internal serial port state. */
+typedef RTSERIALPORTINTERNAL *PRTSERIALPORTINTERNAL;
+
+
+/**
+ * Baud rate conversion table descriptor.
+ */
+typedef struct RTSERIALPORTBRATECONVDESC
+{
+ /** The platform independent baud rate used by the RTSerialPort* API. */
+ uint32_t uBaudRateCfg;
+ /** The speed identifier used in the termios structure. */
+ speed_t iSpeedTermios;
+} RTSERIALPORTBRATECONVDESC;
+/** Pointer to a baud rate converions table descriptor. */
+typedef RTSERIALPORTBRATECONVDESC *PRTSERIALPORTBRATECONVDESC;
+/** Pointer to a const baud rate conversion table descriptor. */
+typedef const RTSERIALPORTBRATECONVDESC *PCRTSERIALPORTBRATECONVDESC;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** The event poller was woken up due to an external interrupt. */
+#define RTSERIALPORT_WAKEUP_PIPE_REASON_INTERRUPT 0x0
+/** The event poller was woken up due to a change in the monitored status lines. */
+#define RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_CHANGED 0x1
+/** The monitor thread encoutnered repeating errors querying the status lines and terminated. */
+#define RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_MONITOR_FAILED 0x2
+
+
+/*********************************************************************************************************************************
+* Global variables *
+*********************************************************************************************************************************/
+
+/** The baud rate conversion table. */
+static const RTSERIALPORTBRATECONVDESC s_rtSerialPortBaudrateConv[] =
+{
+ { 50, B50 },
+ { 75, B75 },
+ { 110, B110 },
+ { 134, B134 },
+ { 150, B150 },
+ { 200, B200 },
+ { 300, B300 },
+ { 600, B600 },
+ { 1200, B1200 },
+ { 1800, B1800 },
+ { 2400, B2400 },
+ { 4800, B4800 },
+ { 9600, B9600 },
+ { 19200, B19200 },
+ { 38400, B38400 },
+ { 57600, B57600 },
+ { 115200, B115200 }
+};
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Converts the given termios speed identifier to the baud rate used in the API.
+ *
+ * @returns Baud rate or 0 if not a standard baud rate
+ */
+DECLINLINE(uint32_t) rtSerialPortGetBaudrateFromTermiosSpeed(speed_t enmSpeed)
+{
+ for (unsigned i = 0; i < RT_ELEMENTS(s_rtSerialPortBaudrateConv); i++)
+ {
+ if (s_rtSerialPortBaudrateConv[i].iSpeedTermios == enmSpeed)
+ return s_rtSerialPortBaudrateConv[i].uBaudRateCfg;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Converts the given baud rate to proper termios speed identifier.
+ *
+ * @returns Speed identifier if available or B0 if no matching speed for the baud rate
+ * could be found.
+ * @param uBaudRate The baud rate to convert.
+ * @param pfBaudrateCust Where to store the flag whether a custom baudrate was selected.
+ */
+DECLINLINE(speed_t) rtSerialPortGetTermiosSpeedFromBaudrate(uint32_t uBaudRate, bool *pfBaudrateCust)
+{
+ *pfBaudrateCust = false;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(s_rtSerialPortBaudrateConv); i++)
+ {
+ if (s_rtSerialPortBaudrateConv[i].uBaudRateCfg == uBaudRate)
+ return s_rtSerialPortBaudrateConv[i].iSpeedTermios;
+ }
+
+#ifdef RT_OS_LINUX
+ *pfBaudrateCust = true;
+ return B38400;
+#else
+ return B0;
+#endif
+}
+
+
+/**
+ * Tries to set the default config on the given serial port.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal serial port instance data.
+ */
+static int rtSerialPortSetDefaultCfg(PRTSERIALPORTINTERNAL pThis)
+{
+ pThis->fBaudrateCust = false;
+ pThis->uBaudRateCust = 0;
+ pThis->PortCfg.c_iflag = INPCK; /* Input parity checking. */
+ cfsetispeed(&pThis->PortCfg, B9600);
+ cfsetospeed(&pThis->PortCfg, B9600);
+ pThis->PortCfg.c_cflag |= CS8 | CLOCAL; /* 8 data bits, ignore modem control lines. */
+ if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_READ)
+ pThis->PortCfg.c_cflag |= CREAD; /* Enable receiver. */
+
+ /* Set to raw input mode. */
+ pThis->PortCfg.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ECHOK | ISIG | IEXTEN);
+ pThis->PortCfg.c_cc[VMIN] = 0; /* Achieve non-blocking behavior. */
+ pThis->PortCfg.c_cc[VTIME] = 0;
+
+ int rc = VINF_SUCCESS;
+ int rcPsx = tcflush(pThis->iFd, TCIOFLUSH);
+ if (!rcPsx)
+ {
+ rcPsx = tcsetattr(pThis->iFd, TCSANOW, &pThis->PortCfg);
+ if (rcPsx == -1)
+ rc = RTErrConvertFromErrno(errno);
+
+ if (RT_SUCCESS(rc))
+ {
+#ifdef RT_OS_LINUX
+ if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_ENABLE_LOOPBACK)
+ {
+ int fTiocmSet = TIOCM_LOOP;
+ rcPsx = ioctl(pThis->iFd, TIOCMBIS, &fTiocmSet);
+ if (rcPsx == -1)
+ rc = RTErrConvertFromErrno(errno);
+ }
+ else
+ {
+ /* Make sure it is clear. */
+ int fTiocmClear = TIOCM_LOOP;
+ rcPsx = ioctl(pThis->iFd, TIOCMBIC, &fTiocmClear);
+ if (rcPsx == -1 && errno != EINVAL) /* Pseudo terminals don't support loopback mode so ignore an error here. */
+ rc = RTErrConvertFromErrno(errno);
+ }
+#else
+ if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_ENABLE_LOOPBACK)
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+
+ return rc;
+}
+
+
+/**
+ * Converts the given serial port config to the appropriate termios counterpart.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal serial port instance data.
+ * @param pCfg Pointer to the serial port config descriptor.
+ * @param pTermios Pointer to the termios structure to fill.
+ * @param pfBaudrateCust Where to store the flag whether a custom baudrate was selected.
+ * @param pErrInfo Additional error to be set when the conversion fails.
+ */
+static int rtSerialPortCfg2Termios(PRTSERIALPORTINTERNAL pThis, PCRTSERIALPORTCFG pCfg, struct termios *pTermios,
+ bool *pfBaudrateCust, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pErrInfo); /** @todo Make use of the error info. */
+ speed_t enmSpeed = rtSerialPortGetTermiosSpeedFromBaudrate(pCfg->uBaudRate, pfBaudrateCust);
+ if (enmSpeed != B0)
+ {
+ tcflag_t const fCFlagMask = (CS5 | CS6 | CS7 | CS8 | CSTOPB | PARENB | PARODD | CMSPAR);
+ tcflag_t fCFlagNew = CLOCAL;
+
+ switch (pCfg->enmDataBitCount)
+ {
+ case RTSERIALPORTDATABITS_5BITS:
+ fCFlagNew |= CS5;
+ break;
+ case RTSERIALPORTDATABITS_6BITS:
+ fCFlagNew |= CS6;
+ break;
+ case RTSERIALPORTDATABITS_7BITS:
+ fCFlagNew |= CS7;
+ break;
+ case RTSERIALPORTDATABITS_8BITS:
+ fCFlagNew |= CS8;
+ break;
+ default:
+ AssertFailed();
+ return VERR_INVALID_PARAMETER;
+ }
+
+ switch (pCfg->enmParity)
+ {
+ case RTSERIALPORTPARITY_NONE:
+ break;
+ case RTSERIALPORTPARITY_EVEN:
+ fCFlagNew |= PARENB;
+ break;
+ case RTSERIALPORTPARITY_ODD:
+ fCFlagNew |= PARENB | PARODD;
+ break;
+#if CMSPAR != 0
+ case RTSERIALPORTPARITY_MARK:
+ fCFlagNew |= PARENB | CMSPAR | PARODD;
+ break;
+ case RTSERIALPORTPARITY_SPACE:
+ fCFlagNew |= PARENB | CMSPAR;
+ break;
+#else
+ case RTSERIALPORTPARITY_MARK:
+ case RTSERIALPORTPARITY_SPACE:
+ return VERR_NOT_SUPPORTED;
+#endif
+ default:
+ AssertFailed();
+ return VERR_INVALID_PARAMETER;
+ }
+
+ switch (pCfg->enmStopBitCount)
+ {
+ case RTSERIALPORTSTOPBITS_ONE:
+ break;
+ case RTSERIALPORTSTOPBITS_ONEPOINTFIVE:
+ if (pCfg->enmDataBitCount == RTSERIALPORTDATABITS_5BITS)
+ fCFlagNew |= CSTOPB;
+ else
+ return VERR_NOT_SUPPORTED;
+ break;
+ case RTSERIALPORTSTOPBITS_TWO:
+ if (pCfg->enmDataBitCount != RTSERIALPORTDATABITS_5BITS)
+ fCFlagNew |= CSTOPB;
+ else
+ return VERR_NOT_SUPPORTED;
+ break;
+ default:
+ AssertFailed();
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Assign new flags. */
+ if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_READ)
+ pTermios->c_cflag |= CREAD; /* Enable receiver. */
+ pTermios->c_cflag = (pTermios->c_cflag & ~fCFlagMask) | fCFlagNew;
+ pTermios->c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ECHOK | ISIG | IEXTEN);
+ pTermios->c_iflag = INPCK; /* Input parity checking. */
+ pTermios->c_cc[VMIN] = 0; /* Achieve non-blocking behavior. */
+ pTermios->c_cc[VTIME] = 0;
+ cfsetispeed(pTermios, enmSpeed);
+ cfsetospeed(pTermios, enmSpeed);
+ }
+ else
+ return VERR_SERIALPORT_INVALID_BAUDRATE;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Converts the given termios structure to an appropriate serial port config.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal serial port instance data.
+ * @param pTermios The termios structure to convert.
+ * @param pCfg The serial port config to fill in.
+ */
+static int rtSerialPortTermios2Cfg(PRTSERIALPORTINTERNAL pThis, struct termios *pTermios, PRTSERIALPORTCFG pCfg)
+{
+ int rc = VINF_SUCCESS;
+ bool f5DataBits = false;
+ speed_t enmSpeedIn = cfgetispeed(pTermios);
+ Assert(enmSpeedIn == cfgetospeed(pTermios)); /* Should always be the same. */
+
+ if (!pThis->fBaudrateCust)
+ {
+ pCfg->uBaudRate = rtSerialPortGetBaudrateFromTermiosSpeed(enmSpeedIn);
+ if (!pCfg->uBaudRate)
+ rc = VERR_SERIALPORT_INVALID_BAUDRATE;
+ }
+ else
+ pCfg->uBaudRate = pThis->uBaudRateCust;
+
+ switch (pTermios->c_cflag & CSIZE)
+ {
+ case CS5:
+ pCfg->enmDataBitCount = RTSERIALPORTDATABITS_5BITS;
+ f5DataBits = true;
+ break;
+ case CS6:
+ pCfg->enmDataBitCount = RTSERIALPORTDATABITS_6BITS;
+ break;
+ case CS7:
+ pCfg->enmDataBitCount = RTSERIALPORTDATABITS_7BITS;
+ break;
+ case CS8:
+ pCfg->enmDataBitCount = RTSERIALPORTDATABITS_8BITS;
+ break;
+ default:
+ AssertFailed(); /* Should not happen. */
+ pCfg->enmDataBitCount = RTSERIALPORTDATABITS_INVALID;
+ rc = RT_FAILURE(rc) ? rc : VERR_INVALID_PARAMETER;
+ }
+
+ /* Convert parity. */
+ if (pTermios->c_cflag & PARENB)
+ {
+ /*
+ * CMSPAR is not supported on all systems, especially OS X. As configuring
+ * mark/space parity there is not supported and we start from a known config
+ * when opening the serial port it is not required to check for this here.
+ */
+#if CMSPAR == 0
+ bool fCmsParSet = RT_BOOL(pTermios->c_cflag & CMSPAR);
+#else
+ bool fCmsParSet = false;
+#endif
+ if (pTermios->c_cflag & PARODD)
+ pCfg->enmParity = fCmsParSet ? RTSERIALPORTPARITY_MARK : RTSERIALPORTPARITY_ODD;
+ else
+ pCfg->enmParity = fCmsParSet ? RTSERIALPORTPARITY_SPACE: RTSERIALPORTPARITY_EVEN;
+ }
+ else
+ pCfg->enmParity = RTSERIALPORTPARITY_NONE;
+
+ /*
+ * 1.5 stop bits are used with a data count of 5 bits when a UART derived from the 8250
+ * is used.
+ */
+ if (pTermios->c_cflag & CSTOPB)
+ pCfg->enmStopBitCount = f5DataBits ? RTSERIALPORTSTOPBITS_ONEPOINTFIVE : RTSERIALPORTSTOPBITS_TWO;
+ else
+ pCfg->enmStopBitCount = RTSERIALPORTSTOPBITS_ONE;
+
+ return rc;
+}
+
+
+/**
+ * Wakes up any thread polling for a serial port event with the given reason.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal serial port instance data.
+ * @param bWakeupReason The wakeup reason to pass to the event poller.
+ */
+DECLINLINE(int) rtSerialPortWakeupEvtPoller(PRTSERIALPORTINTERNAL pThis, uint8_t bWakeupReason)
+{
+ int rcPsx = write(pThis->iFdPipeW, &bWakeupReason, 1);
+ if (rcPsx != 1)
+ return RTErrConvertFromErrno(errno);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The status line monitor thread worker.
+ *
+ * @returns IPRT status code.
+ * @param ThreadSelf Thread handle to this thread.
+ * @param pvUser User argument.
+ */
+static DECLCALLBACK(int) rtSerialPortStsLineMonitorThrd(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+ PRTSERIALPORTINTERNAL pThis = (PRTSERIALPORTINTERNAL)pvUser;
+ unsigned long const fStsLinesChk = TIOCM_CAR | TIOCM_RNG | TIOCM_DSR | TIOCM_CTS;
+ int rc = VINF_SUCCESS;
+ uint32_t fStsLinesOld = 0;
+ uint32_t cStsLineGetErrors = 0;
+#ifdef RT_OS_LINUX
+ bool fPoll = false;
+#endif
+
+ RTThreadUserSignal(hThreadSelf);
+
+ int rcPsx = ioctl(pThis->iFd, TIOCMGET, &fStsLinesOld);
+ if (rcPsx == -1)
+ {
+ ASMAtomicXchgBool(&pThis->fMonThrdShutdown, true);
+ return RTErrConvertFromErrno(errno);
+ }
+
+ while ( !pThis->fMonThrdShutdown
+ && RT_SUCCESS(rc))
+ {
+# ifdef RT_OS_LINUX
+ /*
+ * Wait for status line change.
+ *
+ * XXX In Linux, if a thread calls tcsetattr while the monitor thread is
+ * waiting in ioctl for a modem status change then 8250.c wrongly disables
+ * modem irqs and so the monitor thread never gets released. The workaround
+ * is to send a signal after each tcsetattr.
+ *
+ * TIOCMIWAIT doesn't work for the DSR line with TIOCM_DSR set
+ * (see http://lxr.linux.no/#linux+v4.7/drivers/usb/class/cdc-acm.c#L949)
+ * However as it is possible to query the line state we will not just clear
+ * the TIOCM_DSR bit from the lines to check but resort to the polling
+ * approach just like on other hosts.
+ */
+ if (!fPoll)
+ {
+ rcPsx = ioctl(pThis->iFd, TIOCMIWAIT, fStsLinesChk);
+ if (!rcPsx)
+ {
+ rc = rtSerialPortWakeupEvtPoller(pThis, RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_CHANGED);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (rcPsx == -1 && errno != EINTR)
+ fPoll = true;
+ }
+ else
+#endif
+ {
+ uint32_t fStsLines = 0;
+ rcPsx = ioctl(pThis->iFd, TIOCMGET, &fStsLines);
+ if (!rcPsx)
+ {
+ cStsLineGetErrors = 0; /* Reset the error counter once we had one successful query. */
+
+ if (((fStsLines ^ fStsLinesOld) & fStsLinesChk))
+ {
+ rc = rtSerialPortWakeupEvtPoller(pThis, RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_CHANGED);
+ if (RT_FAILURE(rc))
+ break;
+
+ fStsLinesOld = fStsLines;
+ }
+ else /* No change, sleep for a bit. */
+ RTThreadSleep(100 /*ms*/);
+ }
+ else if (rcPsx == -1 && errno != EINTR)
+ {
+ /*
+ * If querying the status line fails too often we have to shut down the
+ * thread and notify the user of the serial port.
+ */
+ if (cStsLineGetErrors++ >= 10)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ rtSerialPortWakeupEvtPoller(pThis, RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_MONITOR_FAILED);
+ break;
+ }
+
+ RTThreadSleep(100 /*ms*/);
+ }
+ }
+ }
+
+ ASMAtomicXchgBool(&pThis->fMonThrdShutdown, true);
+ return rc;
+}
+
+
+/**
+ * Creates the status line monitoring thread.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal serial port instance data.
+ */
+static int rtSerialPortMonitorThreadCreate(PRTSERIALPORTINTERNAL pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Check whether querying the status lines is supported at all, pseudo terminals
+ * don't support it so an error returned in that case.
+ */
+ uint32_t fStsLines = 0;
+ int rcPsx = ioctl(pThis->iFd, TIOCMGET, &fStsLines);
+ if (!rcPsx)
+ {
+ pThis->fMonThrdShutdown = false;
+ rc = RTThreadCreate(&pThis->hMonThrd, rtSerialPortStsLineMonitorThrd, pThis, 0 /*cbStack*/,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IPRT-SerPortMon");
+ if (RT_SUCCESS(rc))
+ {
+ /* Wait for the thread to start up. */
+ rc = RTThreadUserWait(pThis->hMonThrd, 20*RT_MS_1SEC);
+ if ( rc == VERR_TIMEOUT
+ || pThis->fMonThrdShutdown)
+ {
+ /* Startup failed, try to reap the thread. */
+ int rcThrd;
+ rc = RTThreadWait(pThis->hMonThrd, 20*RT_MS_1SEC, &rcThrd);
+ if (RT_SUCCESS(rc))
+ rc = rcThrd;
+ else
+ rc = VERR_INTERNAL_ERROR;
+ /* The thread is lost otherwise. */
+ }
+ }
+ }
+ else if (errno == ENOTTY || errno == EINVAL)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = RTErrConvertFromErrno(errno);
+
+ return rc;
+}
+
+
+/**
+ * Shuts down the status line monitor thread.
+ *
+ * @param pThis The internal serial port instance data.
+ */
+static void rtSerialPortMonitorThreadShutdown(PRTSERIALPORTINTERNAL pThis)
+{
+ bool fShutDown = ASMAtomicXchgBool(&pThis->fMonThrdShutdown, true);
+ if (!fShutDown)
+ {
+ int rc = RTThreadPoke(pThis->hMonThrd);
+ AssertRC(rc);
+ }
+
+ int rcThrd = VINF_SUCCESS;
+ int rc = RTThreadWait(pThis->hMonThrd, 20*RT_MS_1SEC, &rcThrd);
+ AssertRC(rc);
+ AssertRC(rcThrd);
+}
+
+
+/**
+ * The slow path of rtSerialPortSwitchBlockingMode that does the actual switching.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal serial port instance data.
+ * @param fBlocking The desired mode of operation.
+ * @remarks Do not call directly.
+ */
+static int rtSerialPortSwitchBlockingModeSlow(PRTSERIALPORTINTERNAL pThis, bool fBlocking)
+{
+ int fFlags = fcntl(pThis->iFd, F_GETFL, 0);
+ if (fFlags == -1)
+ return RTErrConvertFromErrno(errno);
+
+ if (fBlocking)
+ fFlags &= ~O_NONBLOCK;
+ else
+ fFlags |= O_NONBLOCK;
+ if (fcntl(pThis->iFd, F_SETFL, fFlags) == -1)
+ return RTErrConvertFromErrno(errno);
+
+ pThis->fBlocking = fBlocking;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Switches the serial port to the desired blocking mode if necessary.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal serial port instance data.
+ * @param fBlocking The desired mode of operation.
+ */
+DECLINLINE(int) rtSerialPortSwitchBlockingMode(PRTSERIALPORTINTERNAL pThis, bool fBlocking)
+{
+ if (pThis->fBlocking != fBlocking)
+ return rtSerialPortSwitchBlockingModeSlow(pThis, fBlocking);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTSerialPortOpen(PRTSERIALPORT phSerialPort, const char *pszPortAddress, uint32_t fFlags)
+{
+ AssertPtrReturn(phSerialPort, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPortAddress, VERR_INVALID_POINTER);
+ AssertReturn(*pszPortAddress != '\0', VERR_INVALID_PARAMETER);
+ AssertReturn(!(fFlags & ~RTSERIALPORT_OPEN_F_VALID_MASK), VERR_INVALID_PARAMETER);
+ AssertReturn((fFlags & RTSERIALPORT_OPEN_F_READ) || (fFlags & RTSERIALPORT_OPEN_F_WRITE),
+ VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ PRTSERIALPORTINTERNAL pThis = (PRTSERIALPORTINTERNAL)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ int fPsxFlags = O_NOCTTY | O_NONBLOCK;
+
+ if ((fFlags & RTSERIALPORT_OPEN_F_READ) && !(fFlags & RTSERIALPORT_OPEN_F_WRITE))
+ fPsxFlags |= O_RDONLY;
+ else if (!(fFlags & RTSERIALPORT_OPEN_F_READ) && (fFlags & RTSERIALPORT_OPEN_F_WRITE))
+ fPsxFlags |= O_WRONLY;
+ else
+ fPsxFlags |= O_RDWR;
+
+ pThis->u32Magic = RTSERIALPORT_MAGIC;
+ pThis->fOpenFlags = fFlags;
+ pThis->fEvtsPending = 0;
+ pThis->iFd = open(pszPortAddress, fPsxFlags);
+ pThis->fBlocking = false;
+ if (pThis->iFd != -1)
+ {
+ /* Create wakeup pipe for the event API. */
+ int aPipeFds[2];
+ int rcPsx = pipe(&aPipeFds[0]);
+ if (!rcPsx)
+ {
+ /* Make the pipes close on exec. */
+ pThis->iFdPipeR = aPipeFds[0];
+ pThis->iFdPipeW = aPipeFds[1];
+
+ if (fcntl(pThis->iFdPipeR, F_SETFD, FD_CLOEXEC))
+ rc = RTErrConvertFromErrno(errno);
+
+ if ( RT_SUCCESS(rc)
+ && fcntl(pThis->iFdPipeW, F_SETFD, FD_CLOEXEC))
+ rc = RTErrConvertFromErrno(errno);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtSerialPortSetDefaultCfg(pThis);
+ if ( RT_SUCCESS(rc)
+ && (fFlags & RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING))
+ rc = rtSerialPortMonitorThreadCreate(pThis);
+
+ if (RT_SUCCESS(rc))
+ {
+ *phSerialPort = pThis;
+ return VINF_SUCCESS;
+ }
+ }
+
+ close(pThis->iFdPipeR);
+ close(pThis->iFdPipeW);
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+
+ close(pThis->iFd);
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(int) RTSerialPortClose(RTSERIALPORT hSerialPort)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ if (pThis == NIL_RTSERIALPORT)
+ return VINF_SUCCESS;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Do the cleanup.
+ */
+ AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTSERIALPORT_MAGIC_DEAD, RTSERIALPORT_MAGIC), VERR_INVALID_HANDLE);
+
+ if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING)
+ rtSerialPortMonitorThreadShutdown(pThis);
+
+ close(pThis->iFd);
+ close(pThis->iFdPipeR);
+ close(pThis->iFdPipeW);
+ RTMemFree(pThis);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(RTHCINTPTR) RTSerialPortToNative(RTSERIALPORT hSerialPort)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, -1);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, -1);
+
+ return pThis->iFd;
+}
+
+
+RTDECL(int) RTSerialPortRead(RTSERIALPORT hSerialPort, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbToRead > 0, VERR_INVALID_PARAMETER);
+
+ int rc = rtSerialPortSwitchBlockingMode(pThis, true);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Attempt read.
+ */
+ ssize_t cbRead = read(pThis->iFd, pvBuf, cbToRead);
+ if (cbRead > 0)
+ {
+ if (pcbRead)
+ /* caller can handle partial read. */
+ *pcbRead = cbRead;
+ else
+ {
+ /* Caller expects all to be read. */
+ while ((ssize_t)cbToRead > cbRead)
+ {
+ ssize_t cbReadPart = read(pThis->iFd, (uint8_t *)pvBuf + cbRead, cbToRead - cbRead);
+ if (cbReadPart < 0)
+ return RTErrConvertFromErrno(errno);
+ else if (cbReadPart == 0)
+ return VERR_DEV_IO_ERROR;
+
+ cbRead += cbReadPart;
+ }
+ }
+ }
+ else if (cbRead == 0)
+ rc = VERR_DEV_IO_ERROR;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTSerialPortReadNB(RTSERIALPORT hSerialPort, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbToRead > 0, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
+
+ *pcbRead = 0;
+
+ int rc = rtSerialPortSwitchBlockingMode(pThis, false);
+ if (RT_SUCCESS(rc))
+ {
+ ssize_t cbThisRead = read(pThis->iFd, pvBuf, cbToRead);
+ if (cbThisRead > 0)
+ {
+ /*
+ * The read data needs to be scanned for the BREAK condition marker encoded in the data stream,
+ * if break detection was enabled during open.
+ */
+ if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_DETECT_BREAK_CONDITION)
+ { /** @todo */ }
+
+ *pcbRead = cbThisRead;
+ }
+ else if (cbThisRead == 0)
+ rc = VERR_DEV_IO_ERROR;
+ else if ( errno == EAGAIN
+# ifdef EWOULDBLOCK
+# if EWOULDBLOCK != EAGAIN
+ || errno == EWOULDBLOCK
+# endif
+# endif
+ )
+ rc = VINF_TRY_AGAIN;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTSerialPortWrite(RTSERIALPORT hSerialPort, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbToWrite > 0, VERR_INVALID_PARAMETER);
+
+ int rc = rtSerialPortSwitchBlockingMode(pThis, true);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Attempt write.
+ */
+ ssize_t cbWritten = write(pThis->iFd, pvBuf, cbToWrite);
+ if (cbWritten > 0)
+ {
+ if (pcbWritten)
+ /* caller can handle partial write. */
+ *pcbWritten = cbWritten;
+ else
+ {
+ /* Caller expects all to be written. */
+ while ((ssize_t)cbToWrite > cbWritten)
+ {
+ ssize_t cbWrittenPart = write(pThis->iFd, (const uint8_t *)pvBuf + cbWritten, cbToWrite - cbWritten);
+ if (cbWrittenPart < 0)
+ return RTErrConvertFromErrno(errno);
+ else if (cbWrittenPart == 0)
+ return VERR_DEV_IO_ERROR;
+ cbWritten += cbWrittenPart;
+ }
+ }
+ }
+ else if (cbWritten == 0)
+ rc = VERR_DEV_IO_ERROR;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTSerialPortWriteNB(RTSERIALPORT hSerialPort, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbToWrite > 0, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
+
+ *pcbWritten = 0;
+
+ int rc = rtSerialPortSwitchBlockingMode(pThis, false);
+ if (RT_SUCCESS(rc))
+ {
+ ssize_t cbThisWrite = write(pThis->iFd, pvBuf, cbToWrite);
+ if (cbThisWrite > 0)
+ *pcbWritten = cbThisWrite;
+ else if (cbThisWrite == 0)
+ rc = VERR_DEV_IO_ERROR;
+ else if ( errno == EAGAIN
+# ifdef EWOULDBLOCK
+# if EWOULDBLOCK != EAGAIN
+ || errno == EWOULDBLOCK
+# endif
+# endif
+ )
+ rc = VINF_TRY_AGAIN;
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTSerialPortCfgQueryCurrent(RTSERIALPORT hSerialPort, PRTSERIALPORTCFG pCfg)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+
+ return rtSerialPortTermios2Cfg(pThis, &pThis->PortCfg, pCfg);
+}
+
+
+RTDECL(int) RTSerialPortCfgSet(RTSERIALPORT hSerialPort, PCRTSERIALPORTCFG pCfg, PRTERRINFO pErrInfo)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+
+ struct termios PortCfgNew; RT_ZERO(PortCfgNew);
+ bool fBaudrateCust = false;
+ int rc = rtSerialPortCfg2Termios(pThis, pCfg, &PortCfgNew, &fBaudrateCust, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ int rcPsx = tcflush(pThis->iFd, TCIOFLUSH);
+ if (!rcPsx)
+ {
+#ifdef RT_OS_LINUX
+ if (fBaudrateCust)
+ {
+ struct serial_struct SerLnx;
+ rcPsx = ioctl(pThis->iFd, TIOCGSERIAL, &SerLnx);
+ if (!rcPsx)
+ {
+ SerLnx.custom_divisor = SerLnx.baud_base / pCfg->uBaudRate;
+ if (!SerLnx.custom_divisor)
+ SerLnx.custom_divisor = 1;
+ SerLnx.flags &= ~ASYNC_SPD_MASK;
+ SerLnx.flags |= ASYNC_SPD_CUST;
+ rcPsx = ioctl(pThis->iFd, TIOCSSERIAL, &SerLnx);
+ }
+ }
+#else /* !RT_OS_LINUX */
+ /* Hosts not supporting custom baud rates should already fail in rtSerialPortCfg2Termios(). */
+ AssertMsgFailed(("Should not get here!\n"));
+#endif /* !RT_OS_LINUX */
+ pThis->fBaudrateCust = fBaudrateCust;
+ pThis->uBaudRateCust = pCfg->uBaudRate;
+
+ if (!rcPsx)
+ rcPsx = tcsetattr(pThis->iFd, TCSANOW, &PortCfgNew);
+ if (rcPsx == -1)
+ rc = RTErrConvertFromErrno(errno);
+ else
+ memcpy(&pThis->PortCfg, &PortCfgNew, sizeof(struct termios));
+
+#ifdef RT_OS_LINUX
+ /*
+ * XXX In Linux, if a thread calls tcsetattr while the monitor thread is
+ * waiting in ioctl for a modem status change then 8250.c wrongly disables
+ * modem irqs and so the monitor thread never gets released. The workaround
+ * is to send a signal after each tcsetattr.
+ */
+ if (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING)
+ RTThreadPoke(pThis->hMonThrd);
+#endif
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTSerialPortEvtPoll(RTSERIALPORT hSerialPort, uint32_t fEvtMask, uint32_t *pfEvtsRecv,
+ RTMSINTERVAL msTimeout)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!(fEvtMask & ~RTSERIALPORT_EVT_F_VALID_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pfEvtsRecv, VERR_INVALID_POINTER);
+
+ *pfEvtsRecv = 0;
+
+ fEvtMask |= RTSERIALPORT_EVT_F_STATUS_LINE_MONITOR_FAILED; /* This will be reported always, no matter what the caller wants. */
+
+ /* Return early if there are events pending from previous calls which weren't fetched yet. */
+ for (;;)
+ {
+ uint32_t fEvtsPending = ASMAtomicReadU32(&pThis->fEvtsPending);
+ if (fEvtsPending & fEvtMask)
+ {
+ *pfEvtsRecv = fEvtsPending & fEvtMask;
+ /* Write back, repeat the whole procedure if someone else raced us. */
+ if (ASMAtomicCmpXchgU32(&pThis->fEvtsPending, fEvtsPending & ~fEvtMask, fEvtsPending))
+ return VINF_SUCCESS;
+ }
+ else
+ break;
+ }
+
+ int rc = rtSerialPortSwitchBlockingMode(pThis, false);
+ if (RT_SUCCESS(rc))
+ {
+ struct pollfd aPollFds[2]; RT_ZERO(aPollFds);
+ aPollFds[0].fd = pThis->iFd;
+ aPollFds[0].events = POLLERR | POLLHUP;
+ aPollFds[0].revents = 0;
+ if ( (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_READ)
+ && (fEvtMask & RTSERIALPORT_EVT_F_DATA_RX))
+ aPollFds[0].events |= POLLIN;
+ if ( (pThis->fOpenFlags & RTSERIALPORT_OPEN_F_WRITE)
+ && (fEvtMask & RTSERIALPORT_EVT_F_DATA_TX))
+ aPollFds[0].events |= POLLOUT;
+
+ aPollFds[1].fd = pThis->iFdPipeR;
+ aPollFds[1].events = POLLIN | POLLERR | POLLHUP;
+ aPollFds[1].revents = 0;
+
+ int rcPsx = 0;
+ int msTimeoutLeft = msTimeout == RT_INDEFINITE_WAIT ? -1 : (int)msTimeout;
+ while (msTimeoutLeft != 0)
+ {
+ uint64_t tsPollStart = RTTimeMilliTS();
+
+ rcPsx = poll(&aPollFds[0], RT_ELEMENTS(aPollFds), msTimeoutLeft);
+ if (rcPsx != -1 || errno != EINTR)
+ break;
+ /* Restart when getting interrupted. */
+ if (msTimeoutLeft > -1)
+ {
+ uint64_t tsPollEnd = RTTimeMilliTS();
+ uint64_t tsPollSpan = tsPollEnd - tsPollStart;
+ msTimeoutLeft -= RT_MIN(tsPollSpan, (uint32_t)msTimeoutLeft);
+ }
+ }
+
+ uint32_t fEvtsPending = 0;
+ if (rcPsx < 0 && errno != EINTR)
+ rc = RTErrConvertFromErrno(errno);
+ else if (rcPsx > 0)
+ {
+ if (aPollFds[0].revents != 0)
+ {
+ if (aPollFds[0].revents & POLLERR)
+ rc = VERR_DEV_IO_ERROR;
+ else
+ {
+ fEvtsPending |= (aPollFds[0].revents & POLLIN) ? RTSERIALPORT_EVT_F_DATA_RX : 0;
+ fEvtsPending |= (aPollFds[0].revents & POLLOUT) ? RTSERIALPORT_EVT_F_DATA_TX : 0;
+ /** @todo BREAK condition detection. */
+ }
+ }
+
+ if (aPollFds[1].revents != 0)
+ {
+ AssertReturn(!(aPollFds[1].revents & (POLLHUP | POLLERR | POLLNVAL)), VERR_INTERNAL_ERROR);
+ Assert(aPollFds[1].revents & POLLIN);
+
+ uint8_t bWakeupReason = 0;
+ ssize_t cbRead = read(pThis->iFdPipeR, &bWakeupReason, 1);
+ if (cbRead == 1)
+ {
+ switch (bWakeupReason)
+ {
+ case RTSERIALPORT_WAKEUP_PIPE_REASON_INTERRUPT:
+ rc = VERR_INTERRUPTED;
+ break;
+ case RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_CHANGED:
+ fEvtsPending |= RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED;
+ break;
+ case RTSERIALPORT_WAKEUP_PIPE_REASON_STS_LINE_MONITOR_FAILED:
+ fEvtsPending |= RTSERIALPORT_EVT_F_STATUS_LINE_MONITOR_FAILED;
+ break;
+ default:
+ AssertFailed();
+ rc = VERR_INTERNAL_ERROR;
+ }
+ }
+ else
+ rc = VERR_INTERNAL_ERROR;
+ }
+ }
+ else
+ rc = VERR_TIMEOUT;
+
+ *pfEvtsRecv = fEvtsPending & fEvtMask;
+ fEvtsPending &= ~fEvtMask;
+ ASMAtomicOrU32(&pThis->fEvtsPending, fEvtsPending);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTSerialPortEvtPollInterrupt(RTSERIALPORT hSerialPort)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+
+ return rtSerialPortWakeupEvtPoller(pThis, RTSERIALPORT_WAKEUP_PIPE_REASON_INTERRUPT);
+}
+
+
+RTDECL(int) RTSerialPortChgBreakCondition(RTSERIALPORT hSerialPort, bool fSet)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+
+ int rc = VINF_SUCCESS;
+ int rcPsx = ioctl(pThis->iFd, fSet ? TIOCSBRK : TIOCCBRK);
+ if (rcPsx == -1)
+ rc = RTErrConvertFromErrno(errno);
+
+ return rc;
+}
+
+
+RTDECL(int) RTSerialPortChgStatusLines(RTSERIALPORT hSerialPort, uint32_t fClear, uint32_t fSet)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+
+ int rc = VINF_SUCCESS;
+ int fTiocmSet = 0;
+ int fTiocmClear = 0;
+
+ if (fClear & RTSERIALPORT_CHG_STS_LINES_F_RTS)
+ fTiocmClear |= TIOCM_RTS;
+ if (fClear & RTSERIALPORT_CHG_STS_LINES_F_DTR)
+ fTiocmClear |= TIOCM_DTR;
+
+ if (fSet & RTSERIALPORT_CHG_STS_LINES_F_RTS)
+ fTiocmSet |= TIOCM_RTS;
+ if (fSet & RTSERIALPORT_CHG_STS_LINES_F_DTR)
+ fTiocmSet |= TIOCM_DTR;
+
+ int rcPsx = ioctl(pThis->iFd, TIOCMBIS, &fTiocmSet);
+ if (!rcPsx)
+ {
+ rcPsx = ioctl(pThis->iFd, TIOCMBIC, &fTiocmClear);
+ if (rcPsx == -1)
+ rc = RTErrConvertFromErrno(errno);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTSerialPortQueryStatusLines(RTSERIALPORT hSerialPort, uint32_t *pfStsLines)
+{
+ PRTSERIALPORTINTERNAL pThis = hSerialPort;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTSERIALPORT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pfStsLines, VERR_INVALID_POINTER);
+
+ *pfStsLines = 0;
+
+ int rc = VINF_SUCCESS;
+ int fStsLines = 0;
+ int rcPsx = ioctl(pThis->iFd, TIOCMGET, &fStsLines);
+ if (!rcPsx)
+ {
+ /* This resets the status line event pending flag. */
+ for (;;)
+ {
+ uint32_t fEvtsPending = ASMAtomicReadU32(&pThis->fEvtsPending);
+ if (ASMAtomicCmpXchgU32(&pThis->fEvtsPending, fEvtsPending & ~RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED, fEvtsPending))
+ break;
+ }
+
+ *pfStsLines |= (fStsLines & TIOCM_CAR) ? RTSERIALPORT_STS_LINE_DCD : 0;
+ *pfStsLines |= (fStsLines & TIOCM_RNG) ? RTSERIALPORT_STS_LINE_RI : 0;
+ *pfStsLines |= (fStsLines & TIOCM_DSR) ? RTSERIALPORT_STS_LINE_DSR : 0;
+ *pfStsLines |= (fStsLines & TIOCM_CTS) ? RTSERIALPORT_STS_LINE_CTS : 0;
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+
+ return rc;
+}
+