From c24f68c1028b30364acb4ed085de15364fddbcb9 Mon Sep 17 00:00:00 2001 From: "H. Peter Anvin" Date: Wed, 10 Sep 2025 17:58:01 -0700 Subject: [PATCH 1/3] linux_termios: clear CIBAUD flags, too In case the serial port was already set up with split speed, using one of the standard speed constants (i.e. not BOTHER) it is necessary to clear CIBAUD to revert back to single speed. Signed-off-by: H. Peter Anvin --- linux_termios.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux_termios.c b/linux_termios.c index 3630e57..0dd0b10 100644 --- a/linux_termios.c +++ b/linux_termios.c @@ -86,7 +86,7 @@ SP_PRIV void set_termios_speed(void *data, int speed) #else struct termios *term = (struct termios *) data; #endif - term->c_cflag &= ~CBAUD; + term->c_cflag &= ~(CBAUD | CIBAUD); term->c_cflag |= BOTHER; term->c_ispeed = term->c_ospeed = speed; } From 02caf0c979fee9bfdac47dccbfb8147f8b8128eb Mon Sep 17 00:00:00 2001 From: "H. Peter Anvin" Date: Wed, 10 Sep 2025 17:59:54 -0700 Subject: [PATCH 2/3] termios: read output speed, not input speed POSIX is pretty clear that if only one speed is supported, it is the output speed setting that counts. On some platforms, the input speed can even end up being reported as 0 for single speed configuration. Thus, look at the output speed setting, not the input speed setting. Signed-off-by: H. Peter Anvin --- serialport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/serialport.c b/serialport.c index b3b9249..392ec61 100644 --- a/serialport.c +++ b/serialport.c @@ -1812,7 +1812,7 @@ static enum sp_return get_config(struct sp_port *port, struct port_data *data, #endif for (i = 0; i < NUM_STD_BAUDRATES; i++) { - if (cfgetispeed(&data->term) == std_baudrates[i].index) { + if (cfgetospeed(&data->term) == std_baudrates[i].index) { config->baudrate = std_baudrates[i].value; break; } @@ -1820,7 +1820,7 @@ static enum sp_return get_config(struct sp_port *port, struct port_data *data, if (i == NUM_STD_BAUDRATES) { #ifdef __APPLE__ - config->baudrate = (int)data->term.c_ispeed; + config->baudrate = (int)data->term.c_ospeed; #elif defined(USE_TERMIOS_SPEED) TRY(get_baudrate(port->fd, &config->baudrate)); #else From f051e30dff057a7304a6c94a942da9a09740851d Mon Sep 17 00:00:00 2001 From: "H. Peter Anvin" Date: Wed, 10 Sep 2025 18:01:48 -0700 Subject: [PATCH 3/3] termios: check to see if termios speed_t is a direct map to baud If termios speed_t is a direct mapping to bauds, it is presumably safe to assume that it can be used as a generic interface. This applies to Linux with glibc 2.42+, GNU Hurd, and at least some BSDs. Try to detect this case and if so, do the simple thing. Signed-off-by: H. Peter Anvin --- configure.ac | 56 +++++++++++++++++++++++++++++++++++++++- libserialport_internal.h | 7 +++-- linux_termios.c | 12 ++++++--- serialport.c | 19 +++++++++++++- 4 files changed, 86 insertions(+), 8 deletions(-) diff --git a/configure.ac b/configure.ac index d71833f..ae6406e 100644 --- a/configure.ac +++ b/configure.ac @@ -111,6 +111,60 @@ AC_SYS_LARGEFILE # Define size_t if not defined as standard. AC_TYPE_SIZE_T +# Check to see if the baud rates in termios.h seem sane. +AC_CACHE_CHECK([if is sane], [sp_cv_termios_sane], [ + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +#include +#if (!defined(B0) || B0 == 0) \ + && (!defined(B50) || B50 == 50) \ + && (!defined(B75) || B75 == 75) \ + && (!defined(B110) || B110 == 110) \ + && (!defined(B134) || B134 == 134) \ + && (!defined(B150) || B150 == 150) \ + && (!defined(B200) || B200 == 200) \ + && (!defined(B300) || B300 == 300) \ + && (!defined(B600) || B600 == 600) \ + && (!defined(B1200) || B1200 == 1200) \ + && (!defined(B1800) || B1800 == 1800) \ + && (!defined(B2400) || B2400 == 2400) \ + && (!defined(B4800) || B4800 == 4800) \ + && (!defined(B7200) || B7200 == 7200) \ + && (!defined(B9600) || B9600 == 9600) \ + && (!defined(B14400) || B14400 == 14400) \ + && (!defined(B19200) || B19200 == 19200) \ + && (!defined(B28800) || B28800 == 28800) \ + && (!defined(B33600) || B33600 == 33600) \ + && (!defined(B38400) || B38400 == 38400) \ + && (!defined(B57600) || B57600 == 57600) \ + && (!defined(B76800) || B76800 == 76800) \ + && (!defined(B115200) || B115200 == 115200) \ + && (!defined(B153600) || B153600 == 153600) \ + && (!defined(B230400) || B230400 == 230400) \ + && (!defined(B307200) || B307200 == 307200) \ + && (!defined(B460800) || B460800 == 460800) \ + && (!defined(B500000) || B500000 == 500000) \ + && (!defined(B576000) || B576000 == 576000) \ + && (!defined(B614400) || B614400 == 614400) \ + && (!defined(B921600) || B921600 == 921600) \ + && (!defined(B1000000) || B1000000 == 1000000) \ + && (!defined(B1152000) || B1152000 == 1152000) \ + && (!defined(B1500000) || B1500000 == 1500000) \ + && (!defined(B2000000) || B2000000 == 2000000) \ + && (!defined(B2500000) || B2500000 == 2500000) \ + && (!defined(B3000000) || B3000000 == 3000000) \ + && (!defined(B3500000) || B3500000 == 3500000) \ + && (!defined(B4000000) || B4000000 == 4000000) \ + && (!defined(B5000000) || B5000000 == 5000000) \ + && (!defined(B10000000) || B10000000 == 10000000) +# define TERMIOS_SPEED_T_SANE 1 +#else +# error " uses stupid constants" +#endif +]])], [sp_cv_termios_sane=yes], [sp_cv_termios_sane=no])]) + +AS_IF([test x$sp_cv_termios_sane = xyes], +[AC_DEFINE(HAVE_SANE_TERMIOS, 1, [ speeds are sane])], +[ # Check for specific termios structures. AC_CHECK_TYPES([struct termios2],,, [[#include ]]) @@ -121,7 +175,7 @@ AC_CHECK_MEMBERS([struct termios.c_ispeed, struct termios.c_ospeed, # Check for the BOTHER definition, needed for setting arbitrary baud rates. # We can't just #ifdef BOTHER in the code, because of the separation between # code using libc headers and code using kernel termios.h headers. -AC_CHECK_DECLS([BOTHER],,, [[#include ]]) +AC_CHECK_DECLS([BOTHER],,, [[#include ]])]) # Check for serial_struct. AC_CHECK_TYPES([struct serial_struct],,, [[#include ]]) diff --git a/libserialport_internal.h b/libserialport_internal.h index 57346d6..88bb9f6 100644 --- a/libserialport_internal.h +++ b/libserialport_internal.h @@ -130,8 +130,11 @@ #endif /* Non-standard baudrates are not available everywhere. */ -#if (defined(HAVE_TERMIOS_SPEED) || defined(HAVE_TERMIOS2_SPEED)) && HAVE_DECL_BOTHER -#define USE_TERMIOS_SPEED +#ifdef HAVE_SANE_TERMIOS +/* Directly supported by termios */ +# undef USE_TERMIOS_SPEED +#elif (defined(HAVE_TERMIOS_SPEED) || defined(HAVE_TERMIOS2_SPEED)) && HAVE_DECL_BOTHER +# define USE_TERMIOS_SPEED #endif struct sp_port { diff --git a/linux_termios.c b/linux_termios.c index 0dd0b10..dad0e9c 100644 --- a/linux_termios.c +++ b/linux_termios.c @@ -18,10 +18,10 @@ */ /* - * At the time of writing, glibc does not support the Linux kernel interfaces - * for setting non-standard baud rates and flow control. We therefore have to - * prepare the correct ioctls ourselves, for which we need the declarations in - * linux/termios.h. + * glibc before version 2.42 does not support the Linux kernel + * interfaces for setting non-standard baud rates and flow control. We + * therefore have to prepare the correct ioctls ourselves, for which + * we need the declarations in linux/termios.h. * * We can't include linux/termios.h in serialport.c however, because its * contents conflict with the termios.h provided by glibc. So this file exists @@ -38,6 +38,8 @@ #include #include "linux_termios.h" +#ifndef HAVE_SANE_TERMIOS + SP_PRIV unsigned long get_termios_get_ioctl(void) { #ifdef HAVE_STRUCT_TERMIOS2 @@ -127,3 +129,5 @@ SP_PRIV void set_termiox_flow(void *data, int rts, int cts, int dtr, int dsr) termx->x_cflag |= DSRXON; } #endif + +#endif diff --git a/serialport.c b/serialport.c index 392ec61..f1279cf 100644 --- a/serialport.c +++ b/serialport.c @@ -23,6 +23,7 @@ #include "libserialport_internal.h" +#ifndef HAVE_SANE_TERMIOS static const struct std_baudrate std_baudrates[] = { #ifdef _WIN32 /* @@ -42,8 +43,8 @@ static const struct std_baudrate std_baudrates[] = { #endif #endif }; - #define NUM_STD_BAUDRATES ARRAY_SIZE(std_baudrates) +#endif void (*sp_debug_handler)(const char *format, ...) = sp_default_debug_handler; @@ -1692,7 +1693,9 @@ static enum sp_return set_flow(int fd, struct port_data *data) static enum sp_return get_config(struct sp_port *port, struct port_data *data, struct sp_port_config *config) { +#ifndef HAVE_SANE_TERMIOS unsigned int i; +#endif TRACE("%p, %p, %p", port, data, config); @@ -1811,6 +1814,9 @@ static enum sp_return get_config(struct sp_port *port, struct port_data *data, data->termiox_supported = 0; #endif +#ifdef HAVE_SANE_TERMIOS + config->baudrate = cfgetospeed(&data->term); +#else for (i = 0; i < NUM_STD_BAUDRATES; i++) { if (cfgetospeed(&data->term) == std_baudrates[i].index) { config->baudrate = std_baudrates[i].value; @@ -1827,6 +1833,7 @@ static enum sp_return get_config(struct sp_port *port, struct port_data *data, config->baudrate = -1; #endif } +#endif switch (data->term.c_cflag & CSIZE) { case CS8: @@ -1898,7 +1905,10 @@ static enum sp_return get_config(struct sp_port *port, struct port_data *data, static enum sp_return set_config(struct sp_port *port, struct port_data *data, const struct sp_port_config *config) { +#ifndef HAVE_SANE_TERMIOS unsigned int i; +#endif + #ifdef __APPLE__ BAUD_TYPE baud_nonstd; @@ -2064,6 +2074,12 @@ static enum sp_return set_config(struct sp_port *port, struct port_data *data, int controlbits; if (config->baudrate >= 0) { +#ifdef HAVE_SANE_TERMIOS + if (cfsetospeed(&data->term, config->baudrate) < 0) + RETURN_FAIL("cfsetospeed() failed"); + if (cfsetispeed(&data->term, config->baudrate) < 0) + RETURN_FAIL("cfsetispeed() failed"); +#else for (i = 0; i < NUM_STD_BAUDRATES; i++) { if (config->baudrate == std_baudrates[i].value) { if (cfsetospeed(&data->term, std_baudrates[i].index) < 0) @@ -2088,6 +2104,7 @@ static enum sp_return set_config(struct sp_port *port, struct port_data *data, RETURN_ERROR(SP_ERR_SUPP, "Non-standard baudrate not supported"); #endif } +#endif } if (config->bits >= 0) {