From ca76cd692f5edbb6bd084cdba313f134fa065f2a Mon Sep 17 00:00:00 2001 From: Beyley Cardellio Date: Sat, 7 Feb 2026 03:19:08 -0500 Subject: [PATCH] os/serial: Add serial abstraction with Linux implementation Signed-off-by: Beyley Cardellio Part-of: --- src/xrt/auxiliary/os/CMakeLists.txt | 7 + src/xrt/auxiliary/os/os_serial.h | 113 ++++++++++++ src/xrt/auxiliary/os/os_serial_linux.c | 238 +++++++++++++++++++++++++ src/xrt/auxiliary/os/os_serial_stubs.c | 20 +++ 4 files changed, 378 insertions(+) create mode 100644 src/xrt/auxiliary/os/os_serial.h create mode 100644 src/xrt/auxiliary/os/os_serial_linux.c create mode 100644 src/xrt/auxiliary/os/os_serial_stubs.c diff --git a/src/xrt/auxiliary/os/CMakeLists.txt b/src/xrt/auxiliary/os/CMakeLists.txt index 163b623ad..99011b71c 100644 --- a/src/xrt/auxiliary/os/CMakeLists.txt +++ b/src/xrt/auxiliary/os/CMakeLists.txt @@ -14,6 +14,7 @@ add_library( os_documentation.h os_hid.h os_hid_hidraw.c + os_serial.h os_threading.h os_time.cpp ) @@ -24,6 +25,12 @@ if(WIN32) target_link_libraries(aux_os PRIVATE winmm) endif() +if(LINUX) + target_sources(aux_os PRIVATE os_serial_linux.c) +else() + target_sources(aux_os PRIVATE os_serial_stubs.c) +endif() + #### # BLE library # diff --git a/src/xrt/auxiliary/os/os_serial.h b/src/xrt/auxiliary/os/os_serial.h new file mode 100644 index 000000000..6c5fcbf39 --- /dev/null +++ b/src/xrt/auxiliary/os/os_serial.h @@ -0,0 +1,113 @@ +// Copyright 2019, Collabora, Ltd. +// Copyright 2026, Beyley Cardellio +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Wrapper around OS native serial functions. + * @author Jakob Bornecrantz + * @author Rylie Pavlik + * @author Beyley Cardellio + * + * @ingroup aux_os + */ + +#pragma once + +#include "xrt/xrt_config_os.h" +#include "xrt/xrt_compiler.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/*! + * @interface os_serial_device + * + * Representing a single serial interface on a device. + */ +struct os_serial_device +{ + ssize_t (*read)(struct os_serial_device *serial_dev, uint8_t *data, size_t size, int milliseconds); + + ssize_t (*write)(struct os_serial_device *serial_dev, const uint8_t *data, size_t size); + + int (*set_line_control)(struct os_serial_device *serial_dev, bool dtr, bool rts); + + void (*destroy)(struct os_serial_device *serial_dev); +}; + +enum os_serial_parity_mode +{ + OS_SERIAL_PARITY_NONE, + OS_SERIAL_PARITY_EVEN, + OS_SERIAL_PARITY_ODD, +}; + +/*! + * Represents the parameters for opening a serial device, such as baud rate, parity, etc. + */ +struct os_serial_parameters +{ + uint32_t baud_rate; + uint8_t data_bits; // must be 5, 6, 7, or 8 bits + uint8_t stop_bits; // must be 1 or 2 + enum os_serial_parity_mode parity; +}; + +/*! + * Read the next input report, if any, from the given serial device. + * + * If milliseconds are negative, this call blocks indefinitely, 0 polls, + * and positive will block for that amount of milliseconds. + * + * @public @memberof os_serial_device + */ +static inline ssize_t +os_serial_read(struct os_serial_device *serial_dev, uint8_t *data, size_t size, int milliseconds) +{ + return serial_dev->read(serial_dev, data, size, milliseconds); +} + +/*! + * Write an output report to the given device. + * + * @public @memberof os_serial_device + */ +static inline ssize_t +os_serial_write(struct os_serial_device *serial_dev, const uint8_t *data, size_t size) +{ + return serial_dev->write(serial_dev, data, size); +} + +/*! + * Set the line control signals (DTR and RTS) for the given serial device. + * + * @public @memberof os_serial_device + */ +static inline int +os_serial_set_line_control(struct os_serial_device *serial_dev, bool dtr, bool rts) +{ + return serial_dev->set_line_control(serial_dev, dtr, rts); +} + +/*! + * Close and free the given device. + * + * @public @memberof os_serial_device + */ +static inline void +os_serial_destroy(struct os_serial_device *serial_dev) +{ + serial_dev->destroy(serial_dev); +} + +int +os_serial_open(const char *path, const struct os_serial_parameters *parameters, struct os_serial_device **out_serial); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/xrt/auxiliary/os/os_serial_linux.c b/src/xrt/auxiliary/os/os_serial_linux.c new file mode 100644 index 000000000..fa4bee977 --- /dev/null +++ b/src/xrt/auxiliary/os/os_serial_linux.c @@ -0,0 +1,238 @@ +// Copyright 2019, Collabora, Ltd. +// Copyright 2026, Beyley Cardellio +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Serial implementation based on Linux termios + * @author Jakob Bornecrantz + * @author Rylie Pavlik + * @author Beyley Cardellio + * @ingroup aux_os + */ + +#include "xrt/xrt_config_os.h" + +#ifndef XRT_OS_LINUX +#error Linux-specific file. +#endif + +#include +#include + +#include "os_serial.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +struct serial_linux +{ + struct os_serial_device base; + + int fd; +}; + +static struct serial_linux * +serial_linux(struct os_serial_device *serial_dev) +{ + return (struct serial_linux *)serial_dev; +} + +static ssize_t +linux_serial_read(struct os_serial_device *serial_dev, uint8_t *data, size_t size, int milliseconds) +{ + struct serial_linux *serial = serial_linux(serial_dev); + struct pollfd fds; + int ret; + + if (milliseconds >= 0) { + fds.fd = serial->fd; + fds.events = POLLIN; + fds.revents = 0; + ret = poll(&fds, 1, milliseconds); + + if (ret == -1 || ret == 0) { + // Error or timeout. + return ret; + } + + if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) { + // Device disconnect? + return -1; + } + } + + ret = read(serial->fd, data, size); + + if (ret < 0 && (errno == EAGAIN || errno == EINPROGRESS)) { + // Process most likely received a signal. + ret = 0; + } + + return ret; +} + +static ssize_t +linux_serial_write(struct os_serial_device *serial_dev, const uint8_t *data, size_t size) +{ + struct serial_linux *serial = serial_linux(serial_dev); + + return write(serial->fd, data, size); +} + +static int +linux_serial_set_line_control(struct os_serial_device *serial_dev, bool dtr, bool rts) +{ + struct serial_linux *serial = serial_linux(serial_dev); + int status; + + // Get the current modem status bits + if (ioctl(serial->fd, TIOCMGET, &status) == -1) { + return -errno; + } + + if (dtr) { + status |= TIOCM_DTR; + } else { + status &= ~TIOCM_DTR; + } + + if (rts) { + status |= TIOCM_RTS; + } else { + status &= ~TIOCM_RTS; + } + + // Set the updated modem status bits + if (ioctl(serial->fd, TIOCMSET, &status) == -1) { + return -errno; + } + + return 0; +} + +static void +linux_serial_destroy(struct os_serial_device *serial_dev) +{ + struct serial_linux *serial = serial_linux(serial_dev); + + if (serial->fd >= 0) { + close(serial->fd); + } + + free(serial); +} + +int +os_serial_open(const char *path, const struct os_serial_parameters *parameters, struct os_serial_device **out_serial) +{ + int fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC); + if (fd < 0) { + return -errno; + } + + struct termios tty; + if (tcgetattr(fd, &tty) != 0) { + U_LOG_E("Failed to get terminal attributes: %s", strerror(errno)); + close(fd); + return -errno; + } + + // Clear parity bits + tty.c_cflag &= ~(PARENB | PARODD); + + // Set according to PARODD mode + switch (parameters->parity) { + case OS_SERIAL_PARITY_NONE: break; + case OS_SERIAL_PARITY_EVEN: tty.c_cflag |= PARENB; break; + case OS_SERIAL_PARITY_ODD: tty.c_cflag |= PARENB | PARODD; break; + default: + U_LOG_E("Invalid parity mode: %d", parameters->parity); + close(fd); + return -EINVAL; + } + + // Set stop bits + switch (parameters->stop_bits) { + case 1: tty.c_cflag &= ~CSTOPB; break; + case 2: tty.c_cflag |= CSTOPB; break; + default: + U_LOG_E("Invalid stop bits: %d", parameters->stop_bits); + close(fd); + return -EINVAL; + } + + // Clear data size bits + tty.c_cflag &= ~CSIZE; + // Set according to data bits + switch (parameters->data_bits) { + case 5: tty.c_cflag |= CS5; break; + case 6: tty.c_cflag |= CS6; break; + case 7: tty.c_cflag |= CS7; break; + case 8: tty.c_cflag |= CS8; break; + default: + U_LOG_E("Invalid data bits: %d", parameters->data_bits); + close(fd); + return -EINVAL; + } + + tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) + + tty.c_lflag &= ~ICANON; // Disable canonical mode + tty.c_lflag &= ~ECHO; // Disable echo + tty.c_lflag &= ~ECHOE; // Disable erasure + tty.c_lflag &= ~ECHONL; // Disable new-line echo + tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP + + tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl + tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | + ICRNL); // Disable any special handling of received bytes + + tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars) + tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed + + tty.c_cc[VTIME] = 1; // Set timeout to 0.1 seconds (1 decisecond) + tty.c_cc[VMIN] = 0; // Do not wait for minimum number of characters + + const speed_t baud_rate = (speed_t)parameters->baud_rate; + + // Set output speed + if (cfsetospeed(&tty, baud_rate) != 0) { + U_LOG_E("Failed to set output speed: %s", strerror(errno)); + close(fd); + return -errno; + } + + // Set input speed + if (cfsetispeed(&tty, baud_rate) != 0) { + U_LOG_E("Failed to set input speed: %s", strerror(errno)); + close(fd); + return -errno; + } + + // Set final attributes + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + U_LOG_E("Failed to set terminal attributes: %s", strerror(errno)); + close(fd); + return -errno; + } + + struct serial_linux *serial = U_TYPED_CALLOC(struct serial_linux); + + serial->base.read = linux_serial_read; + serial->base.write = linux_serial_write; + serial->base.set_line_control = linux_serial_set_line_control; + serial->base.destroy = linux_serial_destroy; + + serial->fd = fd; + + (*out_serial) = &serial->base; + + return 0; +} diff --git a/src/xrt/auxiliary/os/os_serial_stubs.c b/src/xrt/auxiliary/os/os_serial_stubs.c new file mode 100644 index 000000000..ec9a3cb9b --- /dev/null +++ b/src/xrt/auxiliary/os/os_serial_stubs.c @@ -0,0 +1,20 @@ +// Copyright 2026, Beyley Cardellio +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Serial implementation stub + * @author Beyley Cardellio + * @ingroup aux_os + */ + +#include "util/u_logging.h" + +#include "os_serial.h" + + +int +os_serial_open(const char *path, const struct os_serial_parameters *parameters, struct os_serial_device **out_serial) +{ + U_LOG_E("Serial devices are not implemented on this platform."); + return -1; +}