From 60e91edf0a77cb4a7c8ef5f669975dc614ee9e2c Mon Sep 17 00:00:00 2001 From: Daniel Stuart Date: Fri, 24 May 2024 11:08:11 -0300 Subject: [PATCH] Fix hang on TTY read In some cases, when a underlying error happens to a TTY port between poll::wait_read_fd and unistd::read, the read function would hang waiting for some data that is never received. This commit sets the port to non-canonical mode, with VMIN = VTIME = 0. With this change, it has the effect of making reads non-blocking, returning right away. The timeout behaviour is maintained, as prior to reading we call unix::poll through poll::wait_read_fd. Fixes: https://github.com/serialport/serialport-rs/issues/7 --- src/posix/termios.rs | 7 +++++++ src/posix/tty.rs | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/posix/termios.rs b/src/posix/termios.rs index f53af807..606ff62f 100644 --- a/src/posix/termios.rs +++ b/src/posix/termios.rs @@ -4,6 +4,7 @@ use cfg_if::cfg_if; use crate::{DataBits, FlowControl, Parity, Result, StopBits}; use nix::libc; +use core::time; use std::os::unix::prelude::*; cfg_if! { @@ -183,6 +184,12 @@ pub(crate) fn set_flow_control(termios: &mut Termios, flow_control: FlowControl) }; } +pub(crate) fn set_timeout(termios: &mut Termios, timeout: time::Duration) { + let timeout = u32::min((timeout.as_millis() / 100) as u32, u8::MAX as u32) as u8; + termios.c_cc[libc::VMIN as usize] = 0; + termios.c_cc[libc::VTIME as usize] = timeout; +} + pub(crate) fn set_data_bits(termios: &mut Termios, data_bits: DataBits) { let size = match data_bits { DataBits::Five => libc::CS5, diff --git a/src/posix/tty.rs b/src/posix/tty.rs index b1d68cff..ba3e3f43 100644 --- a/src/posix/tty.rs +++ b/src/posix/tty.rs @@ -134,7 +134,9 @@ impl TTYPort { nix::errno::Errno::result(unsafe { tcgetattr(fd.0, termios.as_mut_ptr()) })?; let mut termios = unsafe { termios.assume_init() }; - // setup TTY for binary serial port access + // Setup TTY for binary serial port access + // Enable non-canonical mode + termios.c_lflag &= !libc::ICANON; // Enable reading from the port and ignore all modem control lines termios.c_cflag |= libc::CREAD | libc::CLOCAL; // Enable raw mode which disables any implicit processing of the input or output data streams @@ -175,6 +177,8 @@ impl TTYPort { termios::set_flow_control(&mut termios, builder.flow_control); termios::set_data_bits(&mut termios, builder.data_bits); termios::set_stop_bits(&mut termios, builder.stop_bits); + // Set termios read to non-blocking, as we handle blocking ourselves (with unix::poll) + termios::set_timeout(&mut termios, Duration::from_millis(0)); #[cfg(not(any(target_os = "ios", target_os = "macos")))] termios::set_baud_rate(&mut termios, builder.baud_rate); #[cfg(any(target_os = "ios", target_os = "macos"))]