From 295b3291e484bd2ba5064685d806c5ae5d3872d6 Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 23 Apr 2021 19:25:09 -0400 Subject: [PATCH 01/18] Restructure into stdlib like "sys" mod for impl. Change SerialPort to a struct, and move the implementation to a "sys" module which selects between different possible "real" implementations. --- src/lib.rs | 241 +++++++++++++++----------- src/posix/mod.rs | 9 - src/sys/mod.rs | 17 ++ src/{ => sys}/posix/enumerate.rs | 10 +- src/{ => sys}/posix/error.rs | 0 src/{ => sys}/posix/ioctl.rs | 0 src/sys/posix/mod.rs | 9 + src/{ => sys}/posix/poll.rs | 0 src/{ => sys}/posix/termios.rs | 6 +- src/{ => sys}/posix/tty.rs | 269 ++++++++++++++--------------- src/sys/unsupported.rs | 140 +++++++++++++++ src/{ => sys}/windows/com.rs | 246 +++++++++++++------------- src/{ => sys}/windows/dcb.rs | 0 src/{ => sys}/windows/enumerate.rs | 0 src/{ => sys}/windows/error.rs | 0 src/sys/windows/mod.rs | 7 + src/windows/mod.rs | 7 - 17 files changed, 569 insertions(+), 392 deletions(-) delete mode 100644 src/posix/mod.rs create mode 100644 src/sys/mod.rs rename src/{ => sys}/posix/enumerate.rs (98%) rename src/{ => sys}/posix/error.rs (100%) rename src/{ => sys}/posix/ioctl.rs (100%) create mode 100644 src/sys/posix/mod.rs rename src/{ => sys}/posix/poll.rs (100%) rename src/{ => sys}/posix/termios.rs (97%) rename src/{ => sys}/posix/tty.rs (86%) create mode 100644 src/sys/unsupported.rs rename src/{ => sys}/windows/com.rs (80%) rename src/{ => sys}/windows/dcb.rs (100%) rename src/{ => sys}/windows/enumerate.rs (100%) rename src/{ => sys}/windows/error.rs (100%) create mode 100644 src/sys/windows/mod.rs delete mode 100644 src/windows/mod.rs diff --git a/src/lib.rs b/src/lib.rs index c5a4625f..a7af9f97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,21 +26,13 @@ // doc tests. #![doc(test(attr(allow(unused_must_use))))] -use std::convert::From; use std::error::Error as StdError; use std::fmt; use std::io; +use std::path::Path; use std::time::Duration; -#[cfg(unix)] -mod posix; -#[cfg(unix)] -pub use posix::{BreakDuration, TTYPort}; - -#[cfg(windows)] -mod windows; -#[cfg(windows)] -pub use windows::COMPort; +mod sys; /// A type for results generated by interacting with serial ports /// @@ -202,10 +194,8 @@ pub enum ClearBuffer { } /// A struct containing all serial port settings -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct SerialPortBuilder { - /// The port name, usually the device path - path: String, /// The baud rate in symbols-per-second baud_rate: u32, /// Number of bits used to represent a character sent on the line @@ -221,10 +211,16 @@ pub struct SerialPortBuilder { } impl SerialPortBuilder { - /// Set the path to the serial port - pub fn path<'a>(mut self, path: impl Into>) -> Self { - self.path = path.into().as_ref().to_owned(); - self + /// Construct a new `SerialPortBuilder` with the default settings. + pub fn new(baud_rate: u32) -> Self { + SerialPortBuilder { + baud_rate, + data_bits: DataBits::Eight, + flow_control: FlowControl::None, + parity: Parity::None, + stop_bits: StopBits::One, + timeout: Duration::from_millis(0), + } } /// Set the baud rate in symbols-per-second @@ -263,31 +259,11 @@ impl SerialPortBuilder { self } - /// Open a cross-platform interface to the port with the specified settings - pub fn open(self) -> Result> { - #[cfg(unix)] - return posix::TTYPort::open(&self).map(|p| Box::new(p) as Box); - - #[cfg(windows)] - return windows::COMPort::open(&self).map(|p| Box::new(p) as Box); - - #[cfg(not(any(unix, windows)))] - Err(Error::new( - ErrorKind::Unknown, - "open() not implemented for platform", - )) - } - - /// Open a platform-specific interface to the port with the specified settings - #[cfg(unix)] - pub fn open_native(self) -> Result { - posix::TTYPort::open(&self) - } - - /// Open a platform-specific interface to the port with the specified settings - #[cfg(windows)] - pub fn open_native(self) -> Result { - windows::COMPort::open(&self) + /// Open a cross-platform interface to the port with the specified settings. + /// + /// On windows, the path must start with the device prefix, e.g. `\\.\`. + pub fn open(self, path: impl AsRef) -> Result { + Ok(SerialPort(sys::SerialPort::open(self, path)?)) } } @@ -295,21 +271,38 @@ impl SerialPortBuilder { /// /// This trait is all that's necessary to implement a new serial port driver /// for a new platform. -pub trait SerialPort: Send + io::Read + io::Write { +#[derive(Debug)] +pub struct SerialPort(sys::SerialPort); + +impl SerialPort { + /// Open a `SerialPort` at the given path with the given baud rate and default settings. + pub fn open(path: impl AsRef, baud_rate: u32) -> Result { + SerialPortBuilder::new(baud_rate).open(path) + } + + /// Get a builder for a serial port with the given baud_rate. + pub fn builder(baud_rate: u32) -> SerialPortBuilder { + SerialPortBuilder::new(baud_rate) + } + // Port settings getters /// Returns the name of this port if it exists. /// /// This name may not be the canonical device name and instead be shorthand. /// Additionally it may not exist for virtual ports. - fn name(&self) -> Option; + pub fn name(&self) -> Option<&str> { + self.0.name() + } /// Returns the current baud rate. /// /// This may return a value different from the last specified baud rate depending on the /// platform as some will return the actual device baud rate rather than the last specified /// baud rate. - fn baud_rate(&self) -> Result; + pub fn baud_rate(&self) -> Result { + self.0.baud_rate() + } /// Returns the character size. /// @@ -317,7 +310,9 @@ pub trait SerialPort: Send + io::Read + io::Write { /// if the hardware is in an uninitialized state or is using a non-standard character size. /// Setting a baud rate with `set_char_size()` should initialize the character size to a /// supported value. - fn data_bits(&self) -> Result; + pub fn data_bits(&self) -> Result { + self.0.data_bits() + } /// Returns the flow control mode. /// @@ -325,14 +320,18 @@ pub trait SerialPort: Send + io::Read + io::Write { /// occur if the hardware is in an uninitialized state or is using an unsupported flow control /// mode. Setting a flow control mode with `set_flow_control()` should initialize the flow /// control mode to a supported value. - fn flow_control(&self) -> Result; + pub fn flow_control(&self) -> Result { + self.0.flow_control() + } /// Returns the parity-checking mode. /// /// This function returns `None` if the parity mode could not be determined. This may occur if /// the hardware is in an uninitialized state or is using a non-standard parity mode. Setting /// a parity mode with `set_parity()` should initialize the parity mode to a supported value. - fn parity(&self) -> Result; + pub fn parity(&self) -> Result { + self.0.parity() + } /// Returns the number of stop bits. /// @@ -340,10 +339,14 @@ pub trait SerialPort: Send + io::Read + io::Write { /// occur if the hardware is in an uninitialized state or is using an unsupported stop bit /// configuration. Setting the number of stop bits with `set_stop-bits()` should initialize the /// stop bits to a supported value. - fn stop_bits(&self) -> Result; + pub fn stop_bits(&self) -> Result { + self.0.stop_bits() + } /// Returns the current timeout. - fn timeout(&self) -> Duration; + pub fn timeout(&self) -> Duration { + self.0.timeout() + } // Port settings setters @@ -354,22 +357,34 @@ pub trait SerialPort: Send + io::Read + io::Write { /// If the implementation does not support the requested baud rate, this function may return an /// `InvalidInput` error. Even if the baud rate is accepted by `set_baud_rate()`, it may not be /// supported by the underlying hardware. - fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()>; + pub fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { + self.0.set_baud_rate(baud_rate) + } /// Sets the character size. - fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()>; + pub fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> { + self.0.set_data_bits(data_bits) + } /// Sets the flow control mode. - fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()>; + pub fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> { + self.0.set_flow_control(flow_control) + } /// Sets the parity-checking mode. - fn set_parity(&mut self, parity: Parity) -> Result<()>; + pub fn set_parity(&mut self, parity: Parity) -> Result<()> { + self.0.set_parity(parity) + } /// Sets the number of stop bits. - fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()>; + pub fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> { + self.0.set_stop_bits(stop_bits) + } /// Sets the timeout for future I/O operations. - fn set_timeout(&mut self, timeout: Duration) -> Result<()>; + pub fn set_timeout(&mut self, timeout: Duration) -> Result<()> { + self.0.set_timeout(timeout) + } // Functions for setting non-data control signal pins @@ -384,7 +399,9 @@ pub trait SerialPort: Send + io::Read + io::Write { /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. - fn write_request_to_send(&mut self, level: bool) -> Result<()>; + pub fn write_request_to_send(&mut self, level: bool) -> Result<()> { + self.0.write_request_to_send(level) + } /// Writes to the Data Terminal Ready pin /// @@ -397,7 +414,9 @@ pub trait SerialPort: Send + io::Read + io::Write { /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. - fn write_data_terminal_ready(&mut self, level: bool) -> Result<()>; + pub fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> { + self.0.write_data_terminal_ready(level) + } // Functions for reading additional pins @@ -412,7 +431,9 @@ pub trait SerialPort: Send + io::Read + io::Write { /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. - fn read_clear_to_send(&mut self) -> Result; + pub fn read_clear_to_send(&mut self) -> Result { + self.0.read_clear_to_send() + } /// Reads the state of the Data Set Ready control signal. /// @@ -425,7 +446,9 @@ pub trait SerialPort: Send + io::Read + io::Write { /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. - fn read_data_set_ready(&mut self) -> Result; + pub fn read_data_set_ready(&mut self) -> Result { + self.0.read_data_set_ready() + } /// Reads the state of the Ring Indicator control signal. /// @@ -438,7 +461,9 @@ pub trait SerialPort: Send + io::Read + io::Write { /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. - fn read_ring_indicator(&mut self) -> Result; + pub fn read_ring_indicator(&mut self) -> Result { + self.0.read_ring_indicator() + } /// Reads the state of the Carrier Detect control signal. /// @@ -451,7 +476,9 @@ pub trait SerialPort: Send + io::Read + io::Write { /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. - fn read_carrier_detect(&mut self) -> Result; + pub fn read_carrier_detect(&mut self) -> Result { + self.0.read_carrier_detect() + } /// Gets the number of bytes available to be read from the input buffer. /// @@ -461,7 +488,9 @@ pub trait SerialPort: Send + io::Read + io::Write { /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. - fn bytes_to_read(&self) -> Result; + pub fn bytes_to_read(&self) -> Result { + self.0.bytes_to_read() + } /// Get the number of bytes written to the output buffer, awaiting transmission. /// @@ -471,7 +500,9 @@ pub trait SerialPort: Send + io::Read + io::Write { /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. - fn bytes_to_write(&self) -> Result; + pub fn bytes_to_write(&self) -> Result { + self.0.bytes_to_write() + } /// Discards all bytes from the serial driver's input buffer and/or output buffer. /// @@ -481,7 +512,9 @@ pub trait SerialPort: Send + io::Read + io::Write { /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. - fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()>; + pub fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> { + self.0.clear(buffer_to_clear) + } // Misc methods @@ -497,13 +530,51 @@ pub trait SerialPort: Send + io::Read + io::Write { /// # Errors /// /// This function returns an error if the serial port couldn't be cloned. - fn try_clone(&self) -> Result>; + pub fn try_clone(&self) -> Result { + Ok(SerialPort(self.0.try_clone()?)) + } /// Start transmitting a break - fn set_break(&self) -> Result<()>; + pub fn set_break(&self) -> Result<()> { + self.0.set_break() + } /// Stop transmitting a break - fn clear_break(&self) -> Result<()>; + pub fn clear_break(&self) -> Result<()> { + self.0.clear_break() + } +} + +impl io::Read for SerialPort { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + io::Read::read(&mut &self.0, buf) + } +} + +impl io::Read for &SerialPort { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + io::Read::read(&mut &self.0, buf) + } +} + +impl io::Write for SerialPort { + fn write(&mut self, buf: &[u8]) -> io::Result { + io::Write::write(&mut &self.0, buf) + } + + fn flush(&mut self) -> io::Result<()> { + io::Write::flush(&mut &self.0) + } +} + +impl io::Write for &SerialPort { + fn write(&mut self, buf: &[u8]) -> io::Result { + io::Write::write(&mut &self.0, buf) + } + + fn flush(&mut self) -> io::Result<()> { + io::Write::flush(&mut &self.0) + } } impl SerialPort for &mut T { @@ -645,42 +716,10 @@ pub struct SerialPortInfo { pub port_type: SerialPortType, } -/// Construct a builder of `SerialPort` objects -/// -/// `SerialPort` objects are built using the Builder pattern through the `new` function. The -/// resultant `SerialPortBuilder` object can be copied, reconfigured, and saved making working with -/// multiple serial ports a little easier. -/// -/// To open a new serial port: -/// ```no_run -/// serialport::new("/dev/ttyUSB0", 9600).open().expect("Failed to open port"); -/// ``` -pub fn new<'a>(path: impl Into>, baud_rate: u32) -> SerialPortBuilder { - SerialPortBuilder { - path: path.into().into_owned(), - baud_rate, - data_bits: DataBits::Eight, - flow_control: FlowControl::None, - parity: Parity::None, - stop_bits: StopBits::One, - timeout: Duration::from_millis(0), - } -} - /// Returns a list of all serial ports on system /// /// It is not guaranteed that these ports exist or are available even if they're /// returned by this function. pub fn available_ports() -> Result> { - #[cfg(unix)] - return crate::posix::available_ports(); - - #[cfg(windows)] - return crate::windows::available_ports(); - - #[cfg(not(any(unix, windows)))] - Err(Error::new( - ErrorKind::Unknown, - "available_ports() not implemented for platform", - )) + sys::available_ports() } diff --git a/src/posix/mod.rs b/src/posix/mod.rs deleted file mode 100644 index 1daf645a..00000000 --- a/src/posix/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub use self::enumerate::*; -pub use self::tty::*; - -mod enumerate; -mod error; -mod ioctl; -mod poll; -mod termios; -mod tty; diff --git a/src/sys/mod.rs b/src/sys/mod.rs new file mode 100644 index 00000000..9dd1ceb5 --- /dev/null +++ b/src/sys/mod.rs @@ -0,0 +1,17 @@ +#[cfg(unix)] +mod posix; + +#[cfg(windows)] +mod windows; + +#[cfg(not(any(unix, windows)))] +mod unsupported; + +#[cfg(unix)] +pub use posix::*; + +#[cfg(windows)] +pub use windows::*; + +#[cfg(not(any(unix, windows)))] +pub use unsupported::*; \ No newline at end of file diff --git a/src/posix/enumerate.rs b/src/sys/posix/enumerate.rs similarity index 98% rename from src/posix/enumerate.rs rename to src/sys/posix/enumerate.rs index c56b7cbc..482bbb08 100644 --- a/src/posix/enumerate.rs +++ b/src/sys/posix/enumerate.rs @@ -304,17 +304,17 @@ cfg_if! { let type_id = CFGetTypeID(value); if type_id == CFStringGetTypeID() { - let mut buf = Vec::with_capacity(256); + let mut buf = [u8; 256]; CFStringGetCString( value as CFStringRef, - buf.as_mut_ptr(), - 256, + &buf[..].as_mut_ptr(), + buf.len(), kCFStringEncodingUTF8, ); let path = CStr::from_ptr(buf.as_ptr()).to_string_lossy(); vec.push(SerialPortInfo { - port_name: path.to_string(), + port_name: path, port_type: port_type(modem_service), }); } else { @@ -348,7 +348,7 @@ cfg_if! { if let Some(devnode) = d.devnode() { if let Some(path) = devnode.to_str() { if let Some(driver) = p.driver() { - if driver == "serial8250" && crate::new(path, 9600).open().is_err() { + if driver == "serial8250" && crate::SerialPort::open(path, 9600).is_err() { continue; } } diff --git a/src/posix/error.rs b/src/sys/posix/error.rs similarity index 100% rename from src/posix/error.rs rename to src/sys/posix/error.rs diff --git a/src/posix/ioctl.rs b/src/sys/posix/ioctl.rs similarity index 100% rename from src/posix/ioctl.rs rename to src/sys/posix/ioctl.rs diff --git a/src/sys/posix/mod.rs b/src/sys/posix/mod.rs new file mode 100644 index 00000000..ee79b185 --- /dev/null +++ b/src/sys/posix/mod.rs @@ -0,0 +1,9 @@ +pub use enumerate::available_ports; +pub use tty::SerialPort; + +mod enumerate; +mod error; +mod ioctl; +mod poll; +mod termios; +mod tty; \ No newline at end of file diff --git a/src/posix/poll.rs b/src/sys/posix/poll.rs similarity index 100% rename from src/posix/poll.rs rename to src/sys/posix/poll.rs diff --git a/src/posix/termios.rs b/src/sys/posix/termios.rs similarity index 97% rename from src/posix/termios.rs rename to src/sys/posix/termios.rs index 43c357e4..9395f119 100644 --- a/src/posix/termios.rs +++ b/src/sys/posix/termios.rs @@ -93,14 +93,14 @@ pub(crate) fn get_termios(fd: RawFd) -> Result { ) ))] pub(crate) fn get_termios(fd: RawFd) -> Result { - crate::posix::ioctl::tcgets2(fd).into() + crate::sys::posix::ioctl::tcgets2(fd).into() } #[cfg(any(target_os = "ios", target_os = "macos",))] pub(crate) fn set_termios(fd: RawFd, termios: &libc::termios, baud_rate: u32) -> Result<()> { let res = unsafe { libc::tcsetattr(fd, libc::TCSANOW, termios) }; nix::errno::Errno::result(res)?; - crate::posix::ioctl::iossiospeed(fd, &(baud_rate as libc::speed_t))?; + crate::sys::posix::ioctl::iossiospeed(fd, &(baud_rate as libc::speed_t))?; Ok(()) } @@ -136,7 +136,7 @@ pub(crate) fn set_termios(fd: RawFd, termios: &libc::termios) -> Result<()> { ) ))] pub(crate) fn set_termios(fd: RawFd, termios: &Termios) -> Result<()> { - crate::posix::ioctl::tcsets2(fd, &termios) + crate::sys::posix::ioctl::tcsets2(fd, &termios) } pub(crate) fn set_parity(termios: &mut Termios, parity: Parity) { diff --git a/src/posix/tty.rs b/src/sys/posix/tty.rs similarity index 86% rename from src/posix/tty.rs rename to src/sys/posix/tty.rs index 0750d4c3..e0e36181 100644 --- a/src/posix/tty.rs +++ b/src/sys/posix/tty.rs @@ -7,10 +7,10 @@ use std::{io, mem}; use nix::fcntl::{fcntl, OFlag}; use nix::{self, libc, unistd}; -use crate::posix::ioctl::{self, SerialLines}; -use crate::posix::termios; +use crate::sys::posix::ioctl::{self, SerialLines}; +use crate::sys::posix::termios; use crate::{ - ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPort, + ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPortBuilder, StopBits, }; @@ -34,11 +34,11 @@ fn close(fd: RawFd) { /// A serial port implementation for POSIX TTY ports /// /// The port will be closed when the value is dropped. However, this struct -/// should not be instantiated directly by using `TTYPort::open()`, instead use +/// should not be instantiated directly by using `SerialPort::open()`, instead use /// the cross-platform `serialport::open()` or /// `serialport::open_with_settings()`. #[derive(Debug)] -pub struct TTYPort { +pub struct SerialPort { fd: RawFd, timeout: Duration, exclusive: bool, @@ -56,13 +56,13 @@ pub enum BreakDuration { Arbitrary(std::num::NonZeroI32), } -impl TTYPort { +impl SerialPort { /// Opens a TTY device as a serial port. /// /// `path` should be the path to a TTY device, e.g., `/dev/ttyS0`. /// /// Ports are opened in exclusive mode by default. If this is undesireable - /// behavior, use `TTYPort::set_exclusive(false)`. + /// behavior, use `SerialPort::set_exclusive(false)`. /// /// ## Errors /// @@ -70,11 +70,11 @@ impl TTYPort { /// the device is already in use. /// * `InvalidInput` if `path` is not a valid device name. /// * `Io` for any other error while opening or initializing the device. - pub fn open(builder: &SerialPortBuilder) -> Result { + pub fn open(builder: SerialPortBuilder, path: impl AsRef) -> Result { use nix::fcntl::FcntlArg::F_SETFL; use nix::libc::{cfmakeraw, tcflush, tcgetattr, tcsetattr}; - let path = Path::new(&builder.path); + let path = path.as_ref(); let fd = nix::fcntl::open( path, OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK, @@ -90,7 +90,7 @@ impl TTYPort { let mut termios = unsafe { termios.assume_init() }; // If any of these steps fail, then we should abort creation of the - // TTYPort and ensure the file descriptor is closed. + // SerialPort and ensure the file descriptor is closed. // So we wrap these calls in a block and check the result. { // setup TTY for binary serial port access @@ -146,11 +146,11 @@ impl TTYPort { termios::set_termios(fd, &termios)?; // Return the final port object - Ok(TTYPort { + Ok(SerialPort { fd, timeout: builder.timeout, exclusive: false, - port_name: Some(builder.path.clone()), + port_name: Some(path.to_string_lossy().into_owned()), #[cfg(any(target_os = "ios", target_os = "macos"))] baud_rate: builder.baud_rate, }) @@ -204,7 +204,7 @@ impl TTYPort { /// Create a pair of pseudo serial terminals /// /// ## Returns - /// Two connected `TTYPort` objects: `(master, slave)` + /// Two connected `SerialPort` objects: `(master, slave)` /// /// ## Errors /// Attempting any IO or parameter settings on the slave tty after the master @@ -216,9 +216,9 @@ impl TTYPort { /// ## Examples /// /// ``` - /// use serialport::TTYPort; + /// use serialport::SerialPort; /// - /// let (master, slave) = TTYPort::pair().unwrap(); + /// let (master, slave) = SerialPort::pair().unwrap(); /// ``` pub fn pair() -> Result<(Self, Self)> { // Open the next free pty. @@ -258,21 +258,21 @@ impl TTYPort { // Set the port to a raw state. Using these ports will not work without this. let mut termios = MaybeUninit::uninit(); - let res = unsafe { crate::posix::tty::libc::tcgetattr(fd, termios.as_mut_ptr()) }; + let res = unsafe { crate::sys::posix::tty::libc::tcgetattr(fd, termios.as_mut_ptr()) }; if let Err(e) = nix::errno::Errno::result(res) { close(fd); return Err(e.into()); } let mut termios = unsafe { termios.assume_init() }; - unsafe { crate::posix::tty::libc::cfmakeraw(&mut termios) }; - unsafe { crate::posix::tty::libc::tcsetattr(fd, libc::TCSANOW, &termios) }; + unsafe { crate::sys::posix::tty::libc::cfmakeraw(&mut termios) }; + unsafe { crate::sys::posix::tty::libc::tcsetattr(fd, libc::TCSANOW, &termios) }; fcntl( fd, nix::fcntl::FcntlArg::F_SETFL(nix::fcntl::OFlag::empty()), )?; - let slave_tty = TTYPort { + let slave_tty = SerialPort { fd, timeout: Duration::from_millis(100), exclusive: true, @@ -284,7 +284,7 @@ impl TTYPort { // Manually construct the master port here because the // `tcgetattr()` doesn't work on Mac, Solaris, and maybe other // BSDs when used on the master port. - let master_tty = TTYPort { + let master_tty = SerialPort { fd: next_pty_fd.into_raw_fd(), timeout: Duration::from_millis(100), exclusive: true, @@ -314,14 +314,12 @@ impl TTYPort { /// the settings are cached on a per object basis, trying to modify them from two different /// objects can cause some nasty behavior. /// - /// This is the same as `SerialPort::try_clone()` but returns the concrete type instead. - /// /// # Errors /// /// This function returns an error if the serial port couldn't be cloned. - pub fn try_clone_native(&self) -> Result { + pub fn try_clone(&self) -> Result { let fd_cloned: i32 = fcntl(self.fd, nix::fcntl::F_DUPFD(self.fd))?; - Ok(TTYPort { + Ok(SerialPort { fd: fd_cloned, exclusive: self.exclusive, port_name: self.port_name.clone(), @@ -330,88 +328,9 @@ impl TTYPort { baud_rate: self.baud_rate, }) } -} - -impl Drop for TTYPort { - fn drop(&mut self) { - close(self.fd); - } -} - -impl AsRawFd for TTYPort { - fn as_raw_fd(&self) -> RawFd { - self.fd - } -} - -impl IntoRawFd for TTYPort { - fn into_raw_fd(self) -> RawFd { - // Pull just the file descriptor out. We also prevent the destructor - // from being run by calling `mem::forget`. If we didn't do this, the - // port would be closed, which would make `into_raw_fd` unusable. - let TTYPort { fd, .. } = self; - mem::forget(self); - fd - } -} - -/// Get the baud speed for a port from its file descriptor -#[cfg(any(target_os = "ios", target_os = "macos"))] -fn get_termios_speed(fd: RawFd) -> u32 { - let mut termios = MaybeUninit::uninit(); - let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) }; - nix::errno::Errno::result(res).expect("Failed to get termios data"); - let termios = unsafe { termios.assume_init() }; - assert_eq!(termios.c_ospeed, termios.c_ispeed); - termios.c_ospeed as u32 -} - -impl FromRawFd for TTYPort { - unsafe fn from_raw_fd(fd: RawFd) -> Self { - TTYPort { - fd, - timeout: Duration::from_millis(100), - exclusive: ioctl::tiocexcl(fd).is_ok(), - // It is not trivial to get the file path corresponding to a file descriptor. - // We'll punt on it and set it to `None` here. - port_name: None, - // It's not guaranteed that the baud rate in the `termios` struct is correct, as - // setting an arbitrary baud rate via the `iossiospeed` ioctl overrides that value, - // but extract that value anyways as a best-guess of the actual baud rate. - #[cfg(any(target_os = "ios", target_os = "macos"))] - baud_rate: get_termios_speed(fd), - } - } -} - -impl io::Read for TTYPort { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - if let Err(e) = super::poll::wait_read_fd(self.fd, self.timeout) { - return Err(io::Error::from(Error::from(e))); - } - - nix::unistd::read(self.fd, buf).map_err(|e| io::Error::from(Error::from(e))) - } -} - -impl io::Write for TTYPort { - fn write(&mut self, buf: &[u8]) -> io::Result { - if let Err(e) = super::poll::wait_write_fd(self.fd, self.timeout) { - return Err(io::Error::from(Error::from(e))); - } - - nix::unistd::write(self.fd, buf).map_err(|e| io::Error::from(Error::from(e))) - } - - fn flush(&mut self) -> io::Result<()> { - nix::sys::termios::tcdrain(self.fd) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "flush failed")) - } -} -impl SerialPort for TTYPort { - fn name(&self) -> Option { - self.port_name.clone() + pub fn name(&self) -> Option<&str> { + self.port_name.as_deref() } /// Returns the port's baud rate @@ -429,7 +348,7 @@ impl SerialPort for TTYPort { )) ) ))] - fn baud_rate(&self) -> Result { + pub fn baud_rate(&self) -> Result { let termios2 = ioctl::tcgets2(self.fd)?; assert!(termios2.c_ospeed == termios2.c_ispeed); @@ -447,7 +366,7 @@ impl SerialPort for TTYPort { target_os = "netbsd", target_os = "openbsd" ))] - fn baud_rate(&self) -> Result { + pub fn baud_rate(&self) -> Result { let termios = termios::get_termios(self.fd)?; let ospeed = unsafe { libc::cfgetospeed(&termios) }; @@ -463,7 +382,7 @@ impl SerialPort for TTYPort { /// On some platforms this will be the actual device baud rate, which may differ from the /// desired baud rate. #[cfg(any(target_os = "ios", target_os = "macos"))] - fn baud_rate(&self) -> Result { + pub fn baud_rate(&self) -> Result { Ok(self.baud_rate) } @@ -479,7 +398,7 @@ impl SerialPort for TTYPort { target_arch = "powerpc64" ) ))] - fn baud_rate(&self) -> Result { + pub fn baud_rate(&self) -> Result { use self::libc::{ B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000, B460800, B500000, B576000, B921600, @@ -532,7 +451,7 @@ impl SerialPort for TTYPort { Ok(res) } - fn data_bits(&self) -> Result { + pub fn data_bits(&self) -> Result { let termios = termios::get_termios(self.fd)?; match termios.c_cflag & libc::CSIZE { libc::CS8 => Ok(DataBits::Eight), @@ -546,7 +465,7 @@ impl SerialPort for TTYPort { } } - fn flow_control(&self) -> Result { + pub fn flow_control(&self) -> Result { let termios = termios::get_termios(self.fd)?; if termios.c_cflag & libc::CRTSCTS == libc::CRTSCTS { Ok(FlowControl::Hardware) @@ -557,7 +476,7 @@ impl SerialPort for TTYPort { } } - fn parity(&self) -> Result { + pub fn parity(&self) -> Result { let termios = termios::get_termios(self.fd)?; if termios.c_cflag & libc::PARENB == libc::PARENB { if termios.c_cflag & libc::PARODD == libc::PARODD { @@ -570,7 +489,7 @@ impl SerialPort for TTYPort { } } - fn stop_bits(&self) -> Result { + pub fn stop_bits(&self) -> Result { let termios = termios::get_termios(self.fd)?; if termios.c_cflag & libc::CSTOPB == libc::CSTOPB { Ok(StopBits::Two) @@ -579,7 +498,7 @@ impl SerialPort for TTYPort { } } - fn timeout(&self) -> Duration { + pub fn timeout(&self) -> Duration { self.timeout } @@ -591,7 +510,7 @@ impl SerialPort for TTYPort { target_os = "openbsd", target_os = "linux" ))] - fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { + pub fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_baud_rate(&mut termios, baud_rate); termios::set_termios(self.fd, &termios) @@ -599,13 +518,13 @@ impl SerialPort for TTYPort { // Mac OS needs special logic for setting arbitrary baud rates. #[cfg(any(target_os = "ios", target_os = "macos"))] - fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { + pub fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { ioctl::iossiospeed(self.fd, &(baud_rate as libc::speed_t))?; self.baud_rate = baud_rate; Ok(()) } - fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> { + pub fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_flow_control(&mut termios, flow_control); #[cfg(any(target_os = "ios", target_os = "macos"))] @@ -614,7 +533,7 @@ impl SerialPort for TTYPort { return termios::set_termios(self.fd, &termios); } - fn set_parity(&mut self, parity: Parity) -> Result<()> { + pub fn set_parity(&mut self, parity: Parity) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_parity(&mut termios, parity); #[cfg(any(target_os = "ios", target_os = "macos"))] @@ -623,7 +542,7 @@ impl SerialPort for TTYPort { return termios::set_termios(self.fd, &termios); } - fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> { + pub fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_data_bits(&mut termios, data_bits); #[cfg(any(target_os = "ios", target_os = "macos"))] @@ -632,7 +551,7 @@ impl SerialPort for TTYPort { return termios::set_termios(self.fd, &termios); } - fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> { + pub fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_stop_bits(&mut termios, stop_bits); #[cfg(any(target_os = "ios", target_os = "macos"))] @@ -641,44 +560,44 @@ impl SerialPort for TTYPort { return termios::set_termios(self.fd, &termios); } - fn set_timeout(&mut self, timeout: Duration) -> Result<()> { + pub fn set_timeout(&mut self, timeout: Duration) -> Result<()> { self.timeout = timeout; Ok(()) } - fn write_request_to_send(&mut self, level: bool) -> Result<()> { + pub fn write_request_to_send(&mut self, level: bool) -> Result<()> { self.set_pin(SerialLines::REQUEST_TO_SEND, level) } - fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> { + pub fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> { self.set_pin(SerialLines::DATA_TERMINAL_READY, level) } - fn read_clear_to_send(&mut self) -> Result { + pub fn read_clear_to_send(&mut self) -> Result { self.read_pin(SerialLines::CLEAR_TO_SEND) } - fn read_data_set_ready(&mut self) -> Result { + pub fn read_data_set_ready(&mut self) -> Result { self.read_pin(SerialLines::DATA_SET_READY) } - fn read_ring_indicator(&mut self) -> Result { + pub fn read_ring_indicator(&mut self) -> Result { self.read_pin(SerialLines::RING) } - fn read_carrier_detect(&mut self) -> Result { + pub fn read_carrier_detect(&mut self) -> Result { self.read_pin(SerialLines::DATA_CARRIER_DETECT) } - fn bytes_to_read(&self) -> Result { + pub fn bytes_to_read(&self) -> Result { ioctl::fionread(self.fd) } - fn bytes_to_write(&self) -> Result { + pub fn bytes_to_write(&self) -> Result { ioctl::tiocoutq(self.fd) } - fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> { + pub fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> { let buffer_id = match buffer_to_clear { ClearBuffer::Input => libc::TCIFLUSH, ClearBuffer::Output => libc::TCOFLUSH, @@ -692,19 +611,89 @@ impl SerialPort for TTYPort { .map_err(|e| e.into()) } - fn try_clone(&self) -> Result> { - match self.try_clone_native() { - Ok(p) => Ok(Box::new(p)), - Err(e) => Err(e), + pub fn set_break(&self) -> Result<()> { + ioctl::tiocsbrk(self.fd) + } + + pub fn clear_break(&self) -> Result<()> { + ioctl::tioccbrk(self.fd) + } +} + +impl Drop for SerialPort { + fn drop(&mut self) { + close(self.fd); + } +} + +impl AsRawFd for SerialPort { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl IntoRawFd for SerialPort { + fn into_raw_fd(self) -> RawFd { + // Pull just the file descriptor out. We also prevent the destructor + // from being run by calling `mem::forget`. If we didn't do this, the + // port would be closed, which would make `into_raw_fd` unusable. + let SerialPort { fd, .. } = self; + mem::forget(self); + fd + } +} + +/// Get the baud speed for a port from its file descriptor +#[cfg(any(target_os = "ios", target_os = "macos"))] +fn get_termios_speed(fd: RawFd) -> u32 { + let mut termios = MaybeUninit::uninit(); + let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) }; + nix::errno::Errno::result(res).expect("Failed to get termios data"); + let termios = unsafe { termios.assume_init() }; + assert_eq!(termios.c_ospeed, termios.c_ispeed); + termios.c_ospeed as u32 +} + +impl FromRawFd for SerialPort { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + SerialPort { + fd, + timeout: Duration::from_millis(100), + exclusive: ioctl::tiocexcl(fd).is_ok(), + // It is not trivial to get the file path corresponding to a file descriptor. + // We'll punt on it and set it to `None` here. + port_name: None, + // It's not guaranteed that the baud rate in the `termios` struct is correct, as + // setting an arbitrary baud rate via the `iossiospeed` ioctl overrides that value, + // but extract that value anyways as a best-guess of the actual baud rate. + #[cfg(any(target_os = "ios", target_os = "macos"))] + baud_rate: get_termios_speed(fd), } } +} - fn set_break(&self) -> Result<()> { - ioctl::tiocsbrk(self.fd) +impl io::Read for &SerialPort { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if let Err(e) = super::poll::wait_read_fd(self.fd, self.timeout) { + return Err(io::Error::from(Error::from(e))); + } + + nix::unistd::read(self.fd, buf).map_err(|e| io::Error::from(Error::from(e))) } +} - fn clear_break(&self) -> Result<()> { - ioctl::tioccbrk(self.fd) +impl io::Write for &SerialPort { + fn write(&mut self, buf: &[u8]) -> io::Result { + if let Err(e) = super::poll::wait_write_fd(self.fd, self.timeout) { + return Err(io::Error::from(Error::from(e))); + } + + nix::unistd::write(self.fd, buf).map_err(|e| io::Error::from(Error::from(e))) + } + + fn flush(&mut self) -> io::Result<()> { + nix::sys::termios::tcdrain(self.fd) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "flush failed")) } } @@ -713,9 +702,9 @@ fn test_ttyport_into_raw_fd() { // `master` must be used here as Dropping it causes slave to be deleted by the OS. // TODO: Convert this to a statement-level attribute once // https://github.com/rust-lang/rust/issues/15701 is on stable. - // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe + // FIXME: Create a mutex across all tests for using `SerialPort::pair()` as it's not threadsafe #![allow(unused_variables)] - let (master, slave) = TTYPort::pair().expect("Unable to create ptty pair"); + let (master, slave) = SerialPort::pair().expect("Unable to create ptty pair"); // First test with the master let master_fd = master.into_raw_fd(); diff --git a/src/sys/unsupported.rs b/src/sys/unsupported.rs new file mode 100644 index 00000000..b9c53c07 --- /dev/null +++ b/src/sys/unsupported.rs @@ -0,0 +1,140 @@ + +use std::io; + +use crate::{Error, ErrorKind, Result, SerialPortInfo}; + +pub fn available_ports() -> Result> { + Err(Error::new( + ErrorKind::Unknown, + "available_ports() not implemented for platform", + )) +} + +/// Unsupported serial port type can never be constructed. +#[derive(Debug)] +pub enum SerialPort { } + +impl SerialPort { + pub fn open(builder: SerialPortBuilder, path: impl AsRef) -> Result { + Err(Error::new( + ErrorKind::Unknown, + "open() not implemented for platform", + )) + } + + pub fn name(&self) -> Option<&str> { + unimplemented!() + } + + pub fn timeout(&self) -> Duration { + unimplemented!() + } + + pub fn set_timeout(&mut self, timeout: Duration) -> Result<()> { + unimplemented!() + } + + pub fn write_request_to_send(&mut self, level: bool) -> Result<()> { + unimplemented!() + } + + pub fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> { + unimplemented!() + } + + pub fn read_clear_to_send(&mut self) -> Result { + unimplemented!() + } + + pub fn read_data_set_ready(&mut self) -> Result { + unimplemented!() + } + + pub fn read_ring_indicator(&mut self) -> Result { + unimplemented!() + } + + pub fn read_carrier_detect(&mut self) -> Result { + unimplemented!() + } + + pub fn baud_rate(&self) -> Result { + unimplemented!() + } + + pub fn data_bits(&self) -> Result { + unimplemented!() + } + + pub fn parity(&self) -> Result { + unimplemented!() + } + + pub fn stop_bits(&self) -> Result { + unimplemented!() + } + + pub fn flow_control(&self) -> Result { + unimplemented!() + } + + pub fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { + unimplemented!() + } + + pub fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> { + unimplemented!() + } + + pub fn set_parity(&mut self, parity: Parity) -> Result<()> { + unimplemented!() + } + + pub fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> { + unimplemented!() + } + + pub fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> { + unimplemented!() + } + + pub fn bytes_to_read(&self) -> Result { + unimplemented!() + } + + pub fn bytes_to_write(&self) -> Result { + unimplemented!() + } + + pub fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> { + unimplemented!() + } + + pub fn try_clone(&self) -> Result { + unimplemented!() + } + + pub fn set_break(&self) -> Result<()> { + unimplemented!() + } + + pub fn clear_break(&self) -> Result<()> { + unimplemented!() + } +} + +impl io::Read for &SerialPort { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + unimplemented!() + } +} + +impl io::Write for &SerialPort { + fn write(&mut self, buf: &[u8]) -> io::Result { + unimplemented!() + } + + fn flush(&mut self) -> io::Result<()> { + unimplemented!() + } +} \ No newline at end of file diff --git a/src/windows/com.rs b/src/sys/windows/com.rs similarity index 80% rename from src/windows/com.rs rename to src/sys/windows/com.rs index b54bd0ce..0fc02d78 100644 --- a/src/windows/com.rs +++ b/src/sys/windows/com.rs @@ -13,34 +13,34 @@ use winapi::um::winnt::{ DUPLICATE_SAME_ACCESS, FILE_ATTRIBUTE_NORMAL, GENERIC_READ, GENERIC_WRITE, HANDLE, }; -use crate::windows::dcb; +use crate::sys::windows::dcb; use crate::{ - ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPort, + ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result SerialPortBuilder, StopBits, }; /// A serial port implementation for Windows COM ports /// /// The port will be closed when the value is dropped. However, this struct -/// should not be instantiated directly by using `COMPort::open()`, instead use +/// should not be instantiated directly by using `SerialPort::open()`, instead use /// the cross-platform `serialport::open()` or /// `serialport::open_with_settings()`. #[derive(Debug)] -pub struct COMPort { +pub struct SerialPort { handle: HANDLE, timeout: Duration, port_name: Option, } -unsafe impl Send for COMPort {} +unsafe impl Send for SerialPort { -impl COMPort { +impl SerialPort { /// Opens a COM port as a serial device. /// /// `port` should be the name of a COM port, e.g., `COM1`. /// /// If the COM port handle needs to be opened with special flags, use - /// `from_raw_handle` method to create the `COMPort`. Note that you should + /// `from_raw_handle` method to create the `SerialPort`. Note that you should /// set the different settings before using the serial port using `set_all`. /// /// ## Errors @@ -49,12 +49,14 @@ impl COMPort { /// the device is already in use. /// * `InvalidInput` if `port` is not a valid device name. /// * `Io` for any other I/O error while opening or initializing the device. - pub fn open(builder: &SerialPortBuilder) -> Result { - let mut name = Vec::::with_capacity(4 + builder.path.len() + 1); - - name.extend(r"\\.\".encode_utf16()); - name.extend(builder.path.encode_utf16()); - name.push(0); + pub fn open(builder: SerialPortBuilder, path: impl AsRef) -> Result { + let path = path.as_ref(); + + let name: Vec = OsStr::new(r"\\.\") + .encode_wide() + .chain(path.as_os_str().encode_wide()) + .chain(std::iter::once(0)) + .collect(); let handle = unsafe { CreateFileW( @@ -81,9 +83,9 @@ impl COMPort { dcb::set_flow_control(&mut dcb, builder.flow_control); dcb::set_dcb(handle, dcb)?; - let mut com = COMPort::open_from_raw_handle(handle as RawHandle); + let mut com = SerialPort::open_from_raw_handle(handle as RawHandle); com.set_timeout(builder.timeout)?; - com.port_name = Some(builder.path.clone()); + com.port_name = Some(path.to_string_lossy().into_owned()); Ok(com) } @@ -96,12 +98,10 @@ impl COMPort { /// the settings are cached on a per object basis, trying to modify them from two different /// objects can cause some nasty behavior. /// - /// This is the same as `SerialPort::try_clone()` but returns the concrete type instead. - /// /// # Errors /// /// This function returns an error if the serial port couldn't be cloned. - pub fn try_clone_native(&self) -> Result { + pub fn try_clone(&self) -> Result { let process_handle: HANDLE = unsafe { GetCurrentProcess() }; let mut cloned_handle: HANDLE = INVALID_HANDLE_VALUE; unsafe { @@ -115,7 +115,7 @@ impl COMPort { DUPLICATE_SAME_ACCESS, ); if cloned_handle != INVALID_HANDLE_VALUE { - Ok(COMPort { + Ok(SerialPort { handle: cloned_handle, port_name: self.port_name.clone(), timeout: self.timeout, @@ -145,98 +145,22 @@ impl COMPort { fn open_from_raw_handle(handle: RawHandle) -> Self { // It is not trivial to get the file path corresponding to a handle. // We'll punt and set it `None` here. - COMPort { + SerialPort { handle: handle as HANDLE, timeout: Duration::from_millis(100), port_name: None, } } -} - -impl Drop for COMPort { - fn drop(&mut self) { - unsafe { - CloseHandle(self.handle); - } - } -} - -impl AsRawHandle for COMPort { - fn as_raw_handle(&self) -> RawHandle { - self.handle as RawHandle - } -} - -impl FromRawHandle for COMPort { - unsafe fn from_raw_handle(handle: RawHandle) -> Self { - COMPort::open_from_raw_handle(handle) - } -} - -impl io::Read for COMPort { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let mut len: DWORD = 0; - - match unsafe { - ReadFile( - self.handle, - buf.as_mut_ptr() as LPVOID, - buf.len() as DWORD, - &mut len, - ptr::null_mut(), - ) - } { - 0 => Err(io::Error::last_os_error()), - _ => { - if len != 0 { - Ok(len as usize) - } else { - Err(io::Error::new( - io::ErrorKind::TimedOut, - "Operation timed out", - )) - } - } - } - } -} - -impl io::Write for COMPort { - fn write(&mut self, buf: &[u8]) -> io::Result { - let mut len: DWORD = 0; - - match unsafe { - WriteFile( - self.handle, - buf.as_ptr() as LPVOID, - buf.len() as DWORD, - &mut len, - ptr::null_mut(), - ) - } { - 0 => Err(io::Error::last_os_error()), - _ => Ok(len as usize), - } - } - fn flush(&mut self) -> io::Result<()> { - match unsafe { FlushFileBuffers(self.handle) } { - 0 => Err(io::Error::last_os_error()), - _ => Ok(()), - } - } -} - -impl SerialPort for COMPort { - fn name(&self) -> Option { - self.port_name.clone() + pub fn name(&self) -> Option<&str> { + self.port_name.as_deref() } - fn timeout(&self) -> Duration { + pub fn timeout(&self) -> Duration { self.timeout } - fn set_timeout(&mut self, timeout: Duration) -> Result<()> { + pub fn set_timeout(&mut self, timeout: Duration) -> Result<()> { let milliseconds = timeout.as_secs() * 1000 + timeout.subsec_nanos() as u64 / 1_000_000; let mut timeouts = COMMTIMEOUTS { @@ -255,7 +179,7 @@ impl SerialPort for COMPort { Ok(()) } - fn write_request_to_send(&mut self, level: bool) -> Result<()> { + pub fn write_request_to_send(&mut self, level: bool) -> Result<()> { if level { self.escape_comm_function(SETRTS) } else { @@ -263,7 +187,7 @@ impl SerialPort for COMPort { } } - fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> { + pub fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> { if level { self.escape_comm_function(SETDTR) } else { @@ -271,28 +195,28 @@ impl SerialPort for COMPort { } } - fn read_clear_to_send(&mut self) -> Result { + pub fn read_clear_to_send(&mut self) -> Result { self.read_pin(MS_CTS_ON) } - fn read_data_set_ready(&mut self) -> Result { + pub fn read_data_set_ready(&mut self) -> Result { self.read_pin(MS_DSR_ON) } - fn read_ring_indicator(&mut self) -> Result { + pub fn read_ring_indicator(&mut self) -> Result { self.read_pin(MS_RING_ON) } - fn read_carrier_detect(&mut self) -> Result { + pub fn read_carrier_detect(&mut self) -> Result { self.read_pin(MS_RLSD_ON) } - fn baud_rate(&self) -> Result { + pub fn baud_rate(&self) -> Result { let dcb = dcb::get_dcb(self.handle)?; Ok(dcb.BaudRate as u32) } - fn data_bits(&self) -> Result { + pub fn data_bits(&self) -> Result { let dcb = dcb::get_dcb(self.handle)?; match dcb.ByteSize { 5 => Ok(DataBits::Five), @@ -306,7 +230,7 @@ impl SerialPort for COMPort { } } - fn parity(&self) -> Result { + pub fn parity(&self) -> Result { let dcb = dcb::get_dcb(self.handle)?; match dcb.Parity { ODDPARITY => Ok(Parity::Odd), @@ -319,7 +243,7 @@ impl SerialPort for COMPort { } } - fn stop_bits(&self) -> Result { + pub fn stop_bits(&self) -> Result { let dcb = dcb::get_dcb(self.handle)?; match dcb.StopBits { TWOSTOPBITS => Ok(StopBits::Two), @@ -331,7 +255,7 @@ impl SerialPort for COMPort { } } - fn flow_control(&self) -> Result { + pub fn flow_control(&self) -> Result { let dcb = dcb::get_dcb(self.handle)?; if dcb.fOutxCtsFlow() != 0 || dcb.fRtsControl() != 0 { Ok(FlowControl::Hardware) @@ -342,31 +266,31 @@ impl SerialPort for COMPort { } } - fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { + pub fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { let mut dcb = dcb::get_dcb(self.handle)?; dcb::set_baud_rate(&mut dcb, baud_rate); dcb::set_dcb(self.handle, dcb) } - fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> { + pub fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> { let mut dcb = dcb::get_dcb(self.handle)?; dcb::set_data_bits(&mut dcb, data_bits); dcb::set_dcb(self.handle, dcb) } - fn set_parity(&mut self, parity: Parity) -> Result<()> { + pub fn set_parity(&mut self, parity: Parity) -> Result<()> { let mut dcb = dcb::get_dcb(self.handle)?; dcb::set_parity(&mut dcb, parity); dcb::set_dcb(self.handle, dcb) } - fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> { + pub fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> { let mut dcb = dcb::get_dcb(self.handle)?; dcb::set_stop_bits(&mut dcb, stop_bits); dcb::set_dcb(self.handle, dcb) } - fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> { + pub fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> { let mut dcb = dcb::get_dcb(self.handle)?; dcb::set_flow_control(&mut dcb, flow_control); dcb::set_dcb(self.handle, dcb) @@ -383,7 +307,7 @@ impl SerialPort for COMPort { } } - fn bytes_to_write(&self) -> Result { + pub fn bytes_to_write(&self) -> Result { let mut errors: DWORD = 0; let mut comstat = MaybeUninit::uninit(); @@ -394,7 +318,7 @@ impl SerialPort for COMPort { } } - fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> { + pub fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> { let buffer_flags = match buffer_to_clear { ClearBuffer::Input => PURGE_RXABORT | PURGE_RXCLEAR, ClearBuffer::Output => PURGE_TXABORT | PURGE_TXCLEAR, @@ -408,14 +332,7 @@ impl SerialPort for COMPort { } } - fn try_clone(&self) -> Result> { - match self.try_clone_native() { - Ok(p) => Ok(Box::new(p)), - Err(e) => Err(e), - } - } - - fn set_break(&self) -> Result<()> { + pub fn set_break(&self) -> Result<()> { if unsafe { SetCommBreak(self.handle) != 0 } { Ok(()) } else { @@ -423,11 +340,86 @@ impl SerialPort for COMPort { } } - fn clear_break(&self) -> Result<()> { + pub fn clear_break(&self) -> Result<()> { if unsafe { ClearCommBreak(self.handle) != 0 } { Ok(()) } else { Err(super::error::last_os_error()) } } + +} + +impl Drop for SerialPort { + fn drop(&mut self) { + unsafe { + CloseHandle(self.handle); + } + } +} + +impl AsRawHandle for SerialPort { + fn as_raw_handle(&self) -> RawHandle { + self.handle as RawHandle + } +} + +impl FromRawHandle for SerialPort { + unsafe fn from_raw_handle(handle: RawHandle) -> Self { + SerialPort::open_from_raw_handle(handle) + } +} + +impl io::Read for &SerialPort { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut len: DWORD = 0; + + match unsafe { + ReadFile( + self.handle, + buf.as_mut_ptr() as LPVOID, + buf.len() as DWORD, + &mut len, + ptr::null_mut(), + ) + } { + 0 => Err(io::Error::last_os_error()), + _ => { + if len != 0 { + Ok(len as usize) + } else { + Err(io::Error::new( + io::ErrorKind::TimedOut, + "Operation timed out", + )) + } + } + } + } +} + +impl io::Write for &SerialPort { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut len: DWORD = 0; + + match unsafe { + WriteFile( + self.handle, + buf.as_ptr() as LPVOID, + buf.len() as DWORD, + &mut len, + ptr::null_mut(), + ) + } { + 0 => Err(io::Error::last_os_error()), + _ => Ok(len as usize), + } + } + + fn flush(&mut self) -> io::Result<()> { + match unsafe { FlushFileBuffers(self.handle) } { + 0 => Err(io::Error::last_os_error()), + _ => Ok(()), + } + } } diff --git a/src/windows/dcb.rs b/src/sys/windows/dcb.rs similarity index 100% rename from src/windows/dcb.rs rename to src/sys/windows/dcb.rs diff --git a/src/windows/enumerate.rs b/src/sys/windows/enumerate.rs similarity index 100% rename from src/windows/enumerate.rs rename to src/sys/windows/enumerate.rs diff --git a/src/windows/error.rs b/src/sys/windows/error.rs similarity index 100% rename from src/windows/error.rs rename to src/sys/windows/error.rs diff --git a/src/sys/windows/mod.rs b/src/sys/windows/mod.rs new file mode 100644 index 00000000..3379497a --- /dev/null +++ b/src/sys/windows/mod.rs @@ -0,0 +1,7 @@ +pub use enumerate::available_ports; +pub use com::SerialPort; + +mod com; +mod dcb; +mod enumerate; +mod error; diff --git a/src/windows/mod.rs b/src/windows/mod.rs deleted file mode 100644 index 26ec3dee..00000000 --- a/src/windows/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub use self::com::*; -pub use self::enumerate::*; - -mod com; -mod dcb; -mod enumerate; -mod error; From 1c66f322151500540f6bc49eb5afefe2b451d9be Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 23 Apr 2021 19:50:22 -0400 Subject: [PATCH 02/18] Move posix-specific features to an extension. Add a public posix module containing a posix-specific SerialPortExt trait and the BreakDuration type. --- src/lib.rs | 110 +++-------------- src/posix.rs | 57 +++++++++ src/sys/posix/tty.rs | 285 +++++++++++++++++++++---------------------- src/windows.rs | 1 + 4 files changed, 215 insertions(+), 238 deletions(-) create mode 100644 src/posix.rs create mode 100644 src/windows.rs diff --git a/src/lib.rs b/src/lib.rs index a7af9f97..5c7828e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,12 @@ use std::time::Duration; mod sys; +#[cfg(any(unix, doc))] +pub mod posix; + +#[cfg(any(windows, doc))] +pub mod windows; + /// A type for results generated by interacting with serial ports /// /// The `Err` type is hard-wired to [`serialport::Error`](struct.Error.html). @@ -577,105 +583,23 @@ impl io::Write for &SerialPort { } } -impl SerialPort for &mut T { - fn name(&self) -> Option { - (**self).name() - } - - fn baud_rate(&self) -> Result { - (**self).baud_rate() - } - - fn data_bits(&self) -> Result { - (**self).data_bits() - } - - fn flow_control(&self) -> Result { - (**self).flow_control() - } - - fn parity(&self) -> Result { - (**self).parity() - } - - fn stop_bits(&self) -> Result { - (**self).stop_bits() - } - - fn timeout(&self) -> Duration { - (**self).timeout() - } - - fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { - (**self).set_baud_rate(baud_rate) - } - - fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> { - (**self).set_data_bits(data_bits) - } - - fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> { - (**self).set_flow_control(flow_control) - } - - fn set_parity(&mut self, parity: Parity) -> Result<()> { - (**self).set_parity(parity) - } - - fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> { - (**self).set_stop_bits(stop_bits) - } - - fn set_timeout(&mut self, timeout: Duration) -> Result<()> { - (**self).set_timeout(timeout) - } - - fn write_request_to_send(&mut self, level: bool) -> Result<()> { - (**self).write_request_to_send(level) - } - - fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> { - (**self).write_data_terminal_ready(level) - } - - fn read_clear_to_send(&mut self) -> Result { - (**self).read_clear_to_send() - } - - fn read_data_set_ready(&mut self) -> Result { - (**self).read_data_set_ready() - } - - fn read_ring_indicator(&mut self) -> Result { - (**self).read_ring_indicator() - } - - fn read_carrier_detect(&mut self) -> Result { - (**self).read_carrier_detect() - } - - fn bytes_to_read(&self) -> Result { - (**self).bytes_to_read() - } - - fn bytes_to_write(&self) -> Result { - (**self).bytes_to_write() - } - - fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> { - (**self).clear(buffer_to_clear) +#[cfg(unix)] +impl posix::SerialPortExt for SerialPort { + fn pair() -> Result<(Self, Self)> { + let (master, slave) = ::pair()?; + Ok((SerialPort(master), SerialPort(slave))) } - fn try_clone(&self) -> Result> { - (**self).try_clone() + fn exclusive(&self) -> bool { + posix::SerialPortExt::exclusive(&self.0) } - fn set_break(&self) -> Result<()> { - (**self).set_break() + fn set_exclusive(&mut self, exclusive: bool) -> Result<()> { + posix::SerialPortExt::set_exclusive(&mut self.0, exclusive) } - fn clear_break(&self) -> Result<()> { - (**self).clear_break() + fn send_break(&self, duration: posix::BreakDuration) -> Result<()> { + posix::SerialPortExt::send_break(&self.0, duration) } } diff --git a/src/posix.rs b/src/posix.rs new file mode 100644 index 00000000..352b09a7 --- /dev/null +++ b/src/posix.rs @@ -0,0 +1,57 @@ +//! Provides unix-only extensions to the SerialPort type. + +use crate::Result; + +/// Specifies the duration of a transmission break. +#[derive(Clone, Copy, Debug)] +pub enum BreakDuration { + /// 0.25-0.5s + Short, + /// Specifies a break duration that is platform-dependent + Arbitrary(std::num::NonZeroI32), +} + +/// Posix-only extensions to the SerialPort type. +pub trait SerialPortExt { + /// Create a pair of pseudo serial terminals + /// + /// ## Returns + /// Two connected `SerialPort` objects: `(master, slave)` + /// + /// ## Errors + /// Attempting any IO or parameter settings on the slave tty after the master + /// tty is closed will return errors. + /// + /// On some platforms manipulating the master port will fail and only + /// modifying the slave port is possible. + /// + /// ## Examples + /// + /// ``` + /// use serialport::SerialPort; + /// + /// let (master, slave) = SerialPort::pair().unwrap(); + /// ``` + fn pair() -> Result<(Self, Self)> where Self: Sized; + + /// Returns the exclusivity of the port + /// + /// If a port is exclusive, then trying to open the same device path again + /// will fail. + fn exclusive(&self) -> bool; + + /// Sets the exclusivity of the port + /// + /// If a port is exclusive, then trying to open the same device path again + /// will fail. + /// + /// See the man pages for the tiocexcl and tiocnxcl ioctl's for more details. + /// + /// ## Errors + /// + /// * `Io` for any error while setting exclusivity for the port. + fn set_exclusive(&mut self, exclusive: bool) -> Result<()>; + + /// Sends 0-valued bits over the port for a set duration + fn send_break(&self, duration: BreakDuration) -> Result<()>; +} \ No newline at end of file diff --git a/src/sys/posix/tty.rs b/src/sys/posix/tty.rs index e0e36181..53b20b46 100644 --- a/src/sys/posix/tty.rs +++ b/src/sys/posix/tty.rs @@ -13,6 +13,7 @@ use crate::{ ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPortBuilder, StopBits, }; +use crate::posix::{BreakDuration, SerialPortExt}; /// Convenience method for removing exclusive access from /// a fd and closing it. @@ -47,15 +48,6 @@ pub struct SerialPort { baud_rate: u32, } -/// Specifies the duration of a transmission break -#[derive(Clone, Copy, Debug)] -pub enum BreakDuration { - /// 0.25-0.5s - Short, - /// Specifies a break duration that is platform-dependent - Arbitrary(std::num::NonZeroI32), -} - impl SerialPort { /// Opens a TTY device as a serial port. /// @@ -156,38 +148,6 @@ impl SerialPort { }) } - /// Returns the exclusivity of the port - /// - /// If a port is exclusive, then trying to open the same device path again - /// will fail. - pub fn exclusive(&self) -> bool { - self.exclusive - } - - /// Sets the exclusivity of the port - /// - /// If a port is exclusive, then trying to open the same device path again - /// will fail. - /// - /// See the man pages for the tiocexcl and tiocnxcl ioctl's for more details. - /// - /// ## Errors - /// - /// * `Io` for any error while setting exclusivity for the port. - pub fn set_exclusive(&mut self, exclusive: bool) -> Result<()> { - let setting_result = if exclusive { - ioctl::tiocexcl(self.fd) - } else { - ioctl::tiocnxcl(self.fd) - }; - - if let Err(err) = setting_result { - Err(err) - } else { - self.exclusive = exclusive; - Ok(()) - } - } fn set_pin(&mut self, pin: ioctl::SerialLines, level: bool) -> Result<()> { if level { @@ -201,110 +161,6 @@ impl SerialPort { ioctl::tiocmget(self.fd).map(|pins| pins.contains(pin)) } - /// Create a pair of pseudo serial terminals - /// - /// ## Returns - /// Two connected `SerialPort` objects: `(master, slave)` - /// - /// ## Errors - /// Attempting any IO or parameter settings on the slave tty after the master - /// tty is closed will return errors. - /// - /// On some platforms manipulating the master port will fail and only - /// modifying the slave port is possible. - /// - /// ## Examples - /// - /// ``` - /// use serialport::SerialPort; - /// - /// let (master, slave) = SerialPort::pair().unwrap(); - /// ``` - pub fn pair() -> Result<(Self, Self)> { - // Open the next free pty. - let next_pty_fd = nix::pty::posix_openpt(nix::fcntl::OFlag::O_RDWR)?; - - // Grant access to the associated slave pty - nix::pty::grantpt(&next_pty_fd)?; - - // Unlock the slave pty - nix::pty::unlockpt(&next_pty_fd)?; - - // Get the path of the attached slave ptty - #[cfg(not(any( - target_os = "linux", - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia" - )))] - let ptty_name = unsafe { nix::pty::ptsname(&next_pty_fd)? }; - - #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia" - ))] - let ptty_name = nix::pty::ptsname_r(&next_pty_fd)?; - - // Open the slave port - #[cfg(any(target_os = "ios", target_os = "macos"))] - let baud_rate = 9600; - let fd = nix::fcntl::open( - Path::new(&ptty_name), - OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK, - nix::sys::stat::Mode::empty(), - )?; - - // Set the port to a raw state. Using these ports will not work without this. - let mut termios = MaybeUninit::uninit(); - let res = unsafe { crate::sys::posix::tty::libc::tcgetattr(fd, termios.as_mut_ptr()) }; - if let Err(e) = nix::errno::Errno::result(res) { - close(fd); - return Err(e.into()); - } - let mut termios = unsafe { termios.assume_init() }; - unsafe { crate::sys::posix::tty::libc::cfmakeraw(&mut termios) }; - unsafe { crate::sys::posix::tty::libc::tcsetattr(fd, libc::TCSANOW, &termios) }; - - fcntl( - fd, - nix::fcntl::FcntlArg::F_SETFL(nix::fcntl::OFlag::empty()), - )?; - - let slave_tty = SerialPort { - fd, - timeout: Duration::from_millis(100), - exclusive: true, - port_name: Some(ptty_name), - #[cfg(any(target_os = "ios", target_os = "macos"))] - baud_rate, - }; - - // Manually construct the master port here because the - // `tcgetattr()` doesn't work on Mac, Solaris, and maybe other - // BSDs when used on the master port. - let master_tty = SerialPort { - fd: next_pty_fd.into_raw_fd(), - timeout: Duration::from_millis(100), - exclusive: true, - port_name: None, - #[cfg(any(target_os = "ios", target_os = "macos"))] - baud_rate, - }; - - Ok((master_tty, slave_tty)) - } - - /// Sends 0-valued bits over the port for a set duration - pub fn send_break(&self, duration: BreakDuration) -> Result<()> { - match duration { - BreakDuration::Short => nix::sys::termios::tcsendbreak(self.fd, 0), - BreakDuration::Arbitrary(n) => nix::sys::termios::tcsendbreak(self.fd, n.get()), - } - .map_err(|e| e.into()) - } - /// Attempts to clone the `SerialPort`. This allow you to write and read simultaneously from the /// same serial connection. Please note that if you want a real asynchronous serial port you /// should look at [mio-serial](https://crates.io/crates/mio-serial) or @@ -697,6 +553,145 @@ impl io::Write for &SerialPort { } } +impl SerialPortExt for SerialPort { + /// Create a pair of pseudo serial terminals + /// + /// ## Returns + /// Two connected `SerialPort` objects: `(master, slave)` + /// + /// ## Errors + /// Attempting any IO or parameter settings on the slave tty after the master + /// tty is closed will return errors. + /// + /// On some platforms manipulating the master port will fail and only + /// modifying the slave port is possible. + /// + /// ## Examples + /// + /// ``` + /// use serialport::SerialPort; + /// + /// let (master, slave) = SerialPort::pair().unwrap(); + /// ``` + fn pair() -> Result<(Self, Self)> { + // Open the next free pty. + let next_pty_fd = nix::pty::posix_openpt(nix::fcntl::OFlag::O_RDWR)?; + + // Grant access to the associated slave pty + nix::pty::grantpt(&next_pty_fd)?; + + // Unlock the slave pty + nix::pty::unlockpt(&next_pty_fd)?; + + // Get the path of the attached slave ptty + #[cfg(not(any( + target_os = "linux", + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia" + )))] + let ptty_name = unsafe { nix::pty::ptsname(&next_pty_fd)? }; + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia" + ))] + let ptty_name = nix::pty::ptsname_r(&next_pty_fd)?; + + // Open the slave port + #[cfg(any(target_os = "ios", target_os = "macos"))] + let baud_rate = 9600; + let fd = nix::fcntl::open( + Path::new(&ptty_name), + OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK, + nix::sys::stat::Mode::empty(), + )?; + + // Set the port to a raw state. Using these ports will not work without this. + let mut termios = MaybeUninit::uninit(); + let res = unsafe { crate::sys::posix::tty::libc::tcgetattr(fd, termios.as_mut_ptr()) }; + if let Err(e) = nix::errno::Errno::result(res) { + close(fd); + return Err(e.into()); + } + let mut termios = unsafe { termios.assume_init() }; + unsafe { crate::sys::posix::tty::libc::cfmakeraw(&mut termios) }; + unsafe { crate::sys::posix::tty::libc::tcsetattr(fd, libc::TCSANOW, &termios) }; + + fcntl( + fd, + nix::fcntl::FcntlArg::F_SETFL(nix::fcntl::OFlag::empty()), + )?; + + let slave_tty = SerialPort { + fd, + timeout: Duration::from_millis(100), + exclusive: true, + port_name: Some(ptty_name), + #[cfg(any(target_os = "ios", target_os = "macos"))] + baud_rate, + }; + + // Manually construct the master port here because the + // `tcgetattr()` doesn't work on Mac, Solaris, and maybe other + // BSDs when used on the master port. + let master_tty = SerialPort { + fd: next_pty_fd.into_raw_fd(), + timeout: Duration::from_millis(100), + exclusive: true, + port_name: None, + #[cfg(any(target_os = "ios", target_os = "macos"))] + baud_rate, + }; + + Ok((master_tty, slave_tty)) + } + + /// Returns the exclusivity of the port + /// + /// If a port is exclusive, then trying to open the same device path again + /// will fail. + fn exclusive(&self) -> bool { + self.exclusive + } + + /// Sets the exclusivity of the port + /// + /// If a port is exclusive, then trying to open the same device path again + /// will fail. + /// + /// See the man pages for the tiocexcl and tiocnxcl ioctl's for more details. + /// + /// ## Errors + /// + /// * `Io` for any error while setting exclusivity for the port. + fn set_exclusive(&mut self, exclusive: bool) -> Result<()> { + let setting_result = if exclusive { + ioctl::tiocexcl(self.fd) + } else { + ioctl::tiocnxcl(self.fd) + }; + + if let Err(err) = setting_result { + Err(err) + } else { + self.exclusive = exclusive; + Ok(()) + } + } + + /// Sends 0-valued bits over the port for a set duration + fn send_break(&self, duration: BreakDuration) -> Result<()> { + match duration { + BreakDuration::Short => nix::sys::termios::tcsendbreak(self.fd, 0), + BreakDuration::Arbitrary(n) => nix::sys::termios::tcsendbreak(self.fd, n.get()), + } + .map_err(|e| e.into()) + } +} + #[test] fn test_ttyport_into_raw_fd() { // `master` must be used here as Dropping it causes slave to be deleted by the OS. diff --git a/src/windows.rs b/src/windows.rs new file mode 100644 index 00000000..a92e52cb --- /dev/null +++ b/src/windows.rs @@ -0,0 +1 @@ +//! Provides windows-only extensions to the SerialPort type. \ No newline at end of file From 09174fcb44bbeaac007f25bb0e2db1e754cfbc97 Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 23 Apr 2021 20:30:57 -0400 Subject: [PATCH 03/18] Update tests and examples to the new API. --- examples/clear_input_buffer.rs | 9 +++-- examples/clear_output_buffer.rs | 9 +++-- examples/duplex.rs | 7 ++-- examples/hardware_check.rs | 20 +++++----- examples/pseudo_terminal.rs | 6 ++- examples/receive_data.rs | 9 +++-- examples/transmit.rs | 7 ++-- src/lib.rs | 69 +++++++++++++++------------------ src/posix.rs | 2 +- src/sys/posix/enumerate.rs | 2 +- src/sys/posix/tty.rs | 39 ++++++++++++++++++- tests/test_serialport.rs | 23 +++++------ tests/test_try_clone.rs | 7 ++-- tests/test_tty.rs | 21 +++++----- 14 files changed, 134 insertions(+), 96 deletions(-) diff --git a/examples/clear_input_buffer.rs b/examples/clear_input_buffer.rs index 35b0e599..b239755e 100644 --- a/examples/clear_input_buffer.rs +++ b/examples/clear_input_buffer.rs @@ -42,7 +42,7 @@ use std::time::Duration; use clap::{App, AppSettings, Arg}; -use serialport::ClearBuffer; +use serialport::{SerialPort, ClearBuffer}; fn main() { let matches = App::new("Serialport Example - Clear Input Buffer") @@ -76,9 +76,10 @@ fn run(port_name: &str, baud_rate: &str) -> Result<(), Box> { .parse::() .map_err(|_| format!("Invalid baud rate '{}' specified", baud_rate))?; - let port = serialport::new(port_name, rate) + let port = SerialPort::builder() + .baud_rate(rate) .timeout(Duration::from_millis(10)) - .open() + .open(port_name) .map_err(|ref e| format!("Port '{}' not available: {}", &port_name, e))?; let chan_clear_buf = input_service(); @@ -124,7 +125,7 @@ fn input_service() -> mpsc::Receiver<()> { break; } Ok(_) => tx.send(()).unwrap(), // Signal main to clear the buffer - Err(e) => panic!(e), + Err(e) => panic!("{}", e), } } }); diff --git a/examples/clear_output_buffer.rs b/examples/clear_output_buffer.rs index f317f01e..22c26302 100644 --- a/examples/clear_output_buffer.rs +++ b/examples/clear_output_buffer.rs @@ -15,15 +15,15 @@ // use std::error::Error; -use std::io::{self, Read}; use std::panic::panic_any; +use std::io::{self, Read, Write}; use std::sync::mpsc; use std::thread; use std::time::Duration; use clap::{App, AppSettings, Arg, ArgMatches}; -use serialport::ClearBuffer; +use serialport::{SerialPort,ClearBuffer}; const DEFAULT_BLOCK_SIZE: &str = "128"; @@ -68,9 +68,10 @@ fn run(port_name: &str, baud_rate: &str, block_size: usize) -> Result<(), Box() .map_err(|_| format!("Invalid baud rate '{}' specified", baud_rate))?; - let mut port = serialport::new(port_name, rate) + let mut port = SerialPort::builder() + .baud_rate(rate) .timeout(Duration::from_millis(10)) - .open() + .open(port_name) .map_err(|ref e| format!("Port '{}' not available: {}", &port_name, e))?; let chan_clear_buf = input_service(); diff --git a/examples/duplex.rs b/examples/duplex.rs index 73bdb0f4..aed27a9a 100644 --- a/examples/duplex.rs +++ b/examples/duplex.rs @@ -11,15 +11,16 @@ //! To test this, have a physical or virtual loopback device connected as the //! only port in the system. -use std::io::Write; +use std::io::{Write, Read}; use std::time::Duration; use std::{io, thread}; +use serialport::SerialPort; + fn main() { // Open the first serialport available. let port_name = &serialport::available_ports().expect("No serial port")[0].port_name; - let mut port = serialport::new(port_name, 9600) - .open() + let mut port = SerialPort::builder().open(port_name) .expect("Failed to open serial port"); // Clone the port diff --git a/examples/hardware_check.rs b/examples/hardware_check.rs index bbbb2ce6..336b288a 100644 --- a/examples/hardware_check.rs +++ b/examples/hardware_check.rs @@ -15,13 +15,13 @@ //! 3) With two ports physically connected to each other //! `cargo run --example hardware_check /dev/ttyUSB0 /dev/ttyUSB1` -use std::io::Write; +use std::io::{Write, Read}; use std::str; use std::time::Duration; use clap::{App, AppSettings, Arg}; -use serialport::{ClearBuffer, DataBits, FlowControl, Parity, SerialPort, StopBits}; +use serialport::{SerialPort, ClearBuffer, DataBits, FlowControl, Parity, StopBits}; fn main() { let matches = App::new("Serialport Example - Hardware Check") @@ -53,28 +53,28 @@ fn main() { } // Run single-port tests on port1 - let mut port1 = match serialport::new(port1_name, 9600).open() { + let mut port1 = match SerialPort::builder().open(port1_name) { Err(e) => { eprintln!("Failed to open \"{}\". Error: {}", port1_name, e); ::std::process::exit(1); } Ok(p) => p, }; - test_single_port(&mut *port1, port1_loopback); + test_single_port(&mut port1, port1_loopback); if port2_name != "" { // Run single-port tests on port2 - let mut port2 = match serialport::new(port2_name, 9600).open() { + let mut port2 = match SerialPort::builder().open(port2_name) { Err(e) => { eprintln!("Failed to open \"{}\". Error: {}", port2_name, e); ::std::process::exit(1); } Ok(p) => p, }; - test_single_port(&mut *port2, false); + test_single_port(&mut port2, false); // Test loopback pair - test_dual_ports(&mut *port1, &mut *port2); + test_dual_ports(&mut port1, &mut port2); } } @@ -186,7 +186,7 @@ macro_rules! call_query_method_check { }; } -fn test_single_port(port: &mut dyn serialport::SerialPort, loopback: bool) { +fn test_single_port(port: &mut SerialPort, loopback: bool) { println!("Testing '{}':", port.name().unwrap()); // Test setting standard baud rates @@ -262,7 +262,7 @@ fn test_single_port(port: &mut dyn serialport::SerialPort, loopback: bool) { } } -fn test_dual_ports(port1: &mut dyn serialport::SerialPort, port2: &mut dyn serialport::SerialPort) { +fn test_dual_ports(port1: &mut SerialPort, port2: &mut SerialPort) { println!( "Testing paired ports '{}' and '{}':", port1.name().unwrap(), @@ -420,7 +420,7 @@ fn test_dual_ports(port1: &mut dyn serialport::SerialPort, port2: &mut dyn seria } } -fn set_defaults(port: &mut dyn serialport::SerialPort) { +fn set_defaults(port: &mut SerialPort) { port.set_baud_rate(9600).unwrap(); port.set_data_bits(DataBits::Eight).unwrap(); port.set_flow_control(FlowControl::Software).unwrap(); diff --git a/examples/pseudo_terminal.rs b/examples/pseudo_terminal.rs index 7c9c42ac..34621b5f 100644 --- a/examples/pseudo_terminal.rs +++ b/examples/pseudo_terminal.rs @@ -8,9 +8,11 @@ fn main() { use std::thread; use std::time; - use serialport::{SerialPort, TTYPort}; + use serialport::SerialPort; + use serialport::posix::SerialPortExt; - let (mut master, mut slave) = TTYPort::pair().expect("Unable to create pseudo-terminal pair"); + let (mut master, mut slave) = SerialPort::pair() + .expect("Unable to create pseudo-terminal pair"); // Master ptty has no associated path on the filesystem. println!( diff --git a/examples/receive_data.rs b/examples/receive_data.rs index 696b02ba..a91d98ef 100644 --- a/examples/receive_data.rs +++ b/examples/receive_data.rs @@ -1,8 +1,10 @@ -use std::io::{self, Write}; +use std::io::{self, Write, Read}; use std::time::Duration; use clap::{App, AppSettings, Arg}; +use serialport::SerialPort; + fn main() { let matches = App::new("Serialport Example - Receive Data") .about("Reads data from a serial port and echoes it to stdout") @@ -24,9 +26,10 @@ fn main() { let port_name = matches.value_of("port").unwrap(); let baud_rate = matches.value_of("baud").unwrap().parse::().unwrap(); - let port = serialport::new(port_name, baud_rate) + let port = SerialPort::builder() + .baud_rate(baud_rate) .timeout(Duration::from_millis(10)) - .open(); + .open(port_name); match port { Ok(mut port) => { diff --git a/examples/transmit.rs b/examples/transmit.rs index e40c46a0..294e1441 100644 --- a/examples/transmit.rs +++ b/examples/transmit.rs @@ -3,7 +3,7 @@ use std::time::Duration; use clap::{App, AppSettings, Arg}; -use serialport::{DataBits, StopBits}; +use serialport::{SerialPort, DataBits, StopBits}; fn main() { let matches = App::new("Serialport Example - Heartbeat") @@ -67,11 +67,12 @@ fn main() { let rate = matches.value_of("rate").unwrap().parse::().unwrap(); let string = matches.value_of("string").unwrap(); - let builder = serialport::new(port_name, baud_rate) + let builder = SerialPort::builder() + .baud_rate(baud_rate) .stop_bits(stop_bits) .data_bits(data_bits); println!("{:?}", &builder); - let mut port = builder.open().unwrap_or_else(|e| { + let mut port = builder.open(port_name).unwrap_or_else(|e| { eprintln!("Failed to open \"{}\". Error: {}", port_name, e); ::std::process::exit(1); }); diff --git a/src/lib.rs b/src/lib.rs index 5c7828e7..c03273cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -218,48 +218,53 @@ pub struct SerialPortBuilder { impl SerialPortBuilder { /// Construct a new `SerialPortBuilder` with the default settings. - pub fn new(baud_rate: u32) -> Self { - SerialPortBuilder { - baud_rate, - data_bits: DataBits::Eight, - flow_control: FlowControl::None, - parity: Parity::None, - stop_bits: StopBits::One, - timeout: Duration::from_millis(0), - } + pub fn new() -> Self { + Default::default() } - /// Set the baud rate in symbols-per-second + /// Set the baud rate in symbols-per-second. + /// + /// Default: `9600` pub fn baud_rate(mut self, baud_rate: u32) -> Self { self.baud_rate = baud_rate; self } /// Set the number of bits used to represent a character sent on the line + /// + /// Default: `DataBits::Eight` pub fn data_bits(mut self, data_bits: DataBits) -> Self { self.data_bits = data_bits; self } /// Set the type of signalling to use for controlling data transfer + /// + /// Default: `FlowControl::None` pub fn flow_control(mut self, flow_control: FlowControl) -> Self { self.flow_control = flow_control; self } /// Set the type of parity to use for error checking + /// + /// Default: `Parity::None` pub fn parity(mut self, parity: Parity) -> Self { self.parity = parity; self } /// Set the number of bits to use to signal the end of a character + /// + /// Default: `StopBits::One` pub fn stop_bits(mut self, stop_bits: StopBits) -> Self { self.stop_bits = stop_bits; self } /// Set the amount of time to wait to receive data before timing out + /// + /// Default: `Duration::from_millis(0)` pub fn timeout(mut self, timeout: Duration) -> Self { self.timeout = timeout; self @@ -273,6 +278,19 @@ impl SerialPortBuilder { } } +impl Default for SerialPortBuilder { + fn default() -> Self { + SerialPortBuilder { + baud_rate: 9600, + data_bits: DataBits::Eight, + flow_control: FlowControl::None, + parity: Parity::None, + stop_bits: StopBits::One, + timeout: Duration::from_millis(0), + } + } +} + /// A trait for serial port devices /// /// This trait is all that's necessary to implement a new serial port driver @@ -281,14 +299,9 @@ impl SerialPortBuilder { pub struct SerialPort(sys::SerialPort); impl SerialPort { - /// Open a `SerialPort` at the given path with the given baud rate and default settings. - pub fn open(path: impl AsRef, baud_rate: u32) -> Result { - SerialPortBuilder::new(baud_rate).open(path) - } - - /// Get a builder for a serial port with the given baud_rate. - pub fn builder(baud_rate: u32) -> SerialPortBuilder { - SerialPortBuilder::new(baud_rate) + /// Get a builder for a serial port with the default settings. + pub fn builder() -> SerialPortBuilder { + Default::default() } // Port settings getters @@ -583,26 +596,6 @@ impl io::Write for &SerialPort { } } -#[cfg(unix)] -impl posix::SerialPortExt for SerialPort { - fn pair() -> Result<(Self, Self)> { - let (master, slave) = ::pair()?; - Ok((SerialPort(master), SerialPort(slave))) - } - - fn exclusive(&self) -> bool { - posix::SerialPortExt::exclusive(&self.0) - } - - fn set_exclusive(&mut self, exclusive: bool) -> Result<()> { - posix::SerialPortExt::set_exclusive(&mut self.0, exclusive) - } - - fn send_break(&self, duration: posix::BreakDuration) -> Result<()> { - posix::SerialPortExt::send_break(&self.0, duration) - } -} - /// Contains all possible USB information about a `SerialPort` #[derive(Debug, Clone, PartialEq, Eq)] pub struct UsbPortInfo { diff --git a/src/posix.rs b/src/posix.rs index 352b09a7..fa20dfb7 100644 --- a/src/posix.rs +++ b/src/posix.rs @@ -28,7 +28,7 @@ pub trait SerialPortExt { /// ## Examples /// /// ``` - /// use serialport::SerialPort; + /// use serialport::{SerialPort, posix::SerialPortExt}; /// /// let (master, slave) = SerialPort::pair().unwrap(); /// ``` diff --git a/src/sys/posix/enumerate.rs b/src/sys/posix/enumerate.rs index 482bbb08..e0da966d 100644 --- a/src/sys/posix/enumerate.rs +++ b/src/sys/posix/enumerate.rs @@ -348,7 +348,7 @@ cfg_if! { if let Some(devnode) = d.devnode() { if let Some(path) = devnode.to_str() { if let Some(driver) = p.driver() { - if driver == "serial8250" && crate::SerialPort::open(path, 9600).is_err() { + if driver == "serial8250" && crate::SerialPort::builder().open(path).is_err() { continue; } } diff --git a/src/sys/posix/tty.rs b/src/sys/posix/tty.rs index 53b20b46..29dd02a4 100644 --- a/src/sys/posix/tty.rs +++ b/src/sys/posix/tty.rs @@ -488,6 +488,12 @@ impl AsRawFd for SerialPort { } } +impl AsRawFd for crate::SerialPort { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + impl IntoRawFd for SerialPort { fn into_raw_fd(self) -> RawFd { // Pull just the file descriptor out. We also prevent the destructor @@ -499,6 +505,12 @@ impl IntoRawFd for SerialPort { } } +impl IntoRawFd for crate::SerialPort { + fn into_raw_fd(self) -> RawFd { + self.0.into_raw_fd() + } +} + /// Get the baud speed for a port from its file descriptor #[cfg(any(target_os = "ios", target_os = "macos"))] fn get_termios_speed(fd: RawFd) -> u32 { @@ -528,6 +540,12 @@ impl FromRawFd for SerialPort { } } +impl FromRawFd for crate::SerialPort { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + crate::SerialPort(SerialPort::from_raw_fd(fd)) + } +} + impl io::Read for &SerialPort { fn read(&mut self, buf: &mut [u8]) -> io::Result { if let Err(e) = super::poll::wait_read_fd(self.fd, self.timeout) { @@ -569,7 +587,7 @@ impl SerialPortExt for SerialPort { /// ## Examples /// /// ``` - /// use serialport::SerialPort; + /// use serialport::{SerialPort, posix::SerialPortExt}; /// /// let (master, slave) = SerialPort::pair().unwrap(); /// ``` @@ -692,6 +710,25 @@ impl SerialPortExt for SerialPort { } } +impl SerialPortExt for crate::SerialPort { + fn pair() -> Result<(Self, Self)> { + let (master, slave) = SerialPort::pair()?; + Ok((crate::SerialPort(master), crate::SerialPort(slave))) + } + + fn exclusive(&self) -> bool { + self.0.exclusive() + } + + fn set_exclusive(&mut self, exclusive: bool) -> Result<()> { + self.0.set_exclusive(exclusive) + } + + fn send_break(&self, duration: BreakDuration) -> Result<()> { + self.0.send_break(duration) + } +} + #[test] fn test_ttyport_into_raw_fd() { // `master` must be used here as Dropping it causes slave to be deleted by the OS. diff --git a/tests/test_serialport.rs b/tests/test_serialport.rs index 04bad8f1..799bbe15 100644 --- a/tests/test_serialport.rs +++ b/tests/test_serialport.rs @@ -16,42 +16,39 @@ fn test_listing_ports() { fn test_opening_found_ports() { let ports = serialport::available_ports().unwrap(); for p in ports { - let _port = serialport::new(p.port_name, 9600).open(); + let _port = SerialPort::builder().open(p.port_name); } } #[test] fn test_opening_port() { - let _port = serialport::new("/dev/ttyUSB0", 9600).open(); -} - -#[test] -fn test_opening_native_port() { - let _port = serialport::new("/dev/ttyUSB0", 9600).open_native(); + let _port = SerialPort::builder().open("/dev/ttyUSB0"); } #[test] fn test_configuring_ports() { - let _port = serialport::new("/dev/ttyUSB0", 9600) + let _port = SerialPort::builder() + .baud_rate(9600) .data_bits(DataBits::Five) .flow_control(FlowControl::None) .parity(Parity::None) .stop_bits(StopBits::One) .timeout(Duration::from_millis(1)) - .open(); + .open("/dev/ttyUSB0"); } #[test] fn test_duplicating_port_config() { - let port1_config = serialport::new("/dev/ttyUSB0", 9600) + let port1_config = SerialPort::builder() + .baud_rate(9600) .data_bits(DataBits::Five) .flow_control(FlowControl::None) .parity(Parity::None) .stop_bits(StopBits::One) .timeout(Duration::from_millis(1)); - let port2_config = port1_config.clone().path("/dev/ttyUSB1").baud_rate(115_200); + let port2_config = port1_config.clone().baud_rate(115_200); - let _port1 = port1_config.open(); - let _port1 = port2_config.open(); + let _port1 = port1_config.open("/dev/ttyUSB0"); + let _port1 = port2_config.open("/dev/ttyUSB1"); } diff --git a/tests/test_try_clone.rs b/tests/test_try_clone.rs index 31dd2f55..842de0b2 100644 --- a/tests/test_try_clone.rs +++ b/tests/test_try_clone.rs @@ -1,13 +1,14 @@ #![cfg(unix)] extern crate serialport; -use serialport::{SerialPort, TTYPort}; +use serialport::SerialPort; +use serialport::posix::SerialPortExt; use std::io::{Read, Write}; // Test that cloning a port works as expected #[test] fn test_try_clone() { - let (master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair"); + let (master, mut slave) = SerialPort::pair().expect("Unable to create ptty pair"); // Create the clone in an inner scope to test that dropping a clone doesn't close the original // port @@ -36,7 +37,7 @@ fn test_try_clone() { fn test_try_clone_move() { use std::thread; - let (master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair"); + let (master, mut slave) = SerialPort::pair().expect("Unable to create ptty pair"); let mut clone = master.try_clone().expect("Failed to clone the slave"); let loopback = thread::spawn(move || { diff --git a/tests/test_tty.rs b/tests/test_tty.rs index fc76ef05..87893eae 100644 --- a/tests/test_tty.rs +++ b/tests/test_tty.rs @@ -1,4 +1,4 @@ -//! Tests for the `posix::TTYPort` struct. +//! Tests for the `posix::SerialPort` struct. #![cfg(unix)] extern crate serialport; @@ -8,12 +8,13 @@ use std::os::unix::prelude::*; use std::str; use std::time::Duration; -use serialport::{SerialPort, TTYPort}; +use serialport::SerialPort; +use serialport::posix::SerialPortExt; #[test] fn test_ttyport_pair() { - // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe - let (mut master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair"); + // FIXME: Create a mutex across all tests for using `SerialPort::pair()` as it's not threadsafe + let (mut master, mut slave) = SerialPort::pair().expect("Unable to create ptty pair"); master .set_timeout(Duration::from_millis(10)) .expect("Unable to set timeout on the master"); @@ -70,8 +71,8 @@ fn test_ttyport_timeout() { let result_thread = result.clone(); std::thread::spawn(move || { - // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe - let (mut master, _slave) = TTYPort::pair().expect("Unable to create ptty pair"); + // FIXME: Create a mutex across all tests for using `SerialPort::pair()` as it's not threadsafe + let (mut master, _slave) = SerialPort::pair().expect("Unable to create ptty pair"); master.set_timeout(Duration::new(1, 0)).unwrap(); let mut buffer = [0u8]; @@ -98,9 +99,9 @@ fn test_ttyport_set_standard_baud() { // `master` must be used here as Dropping it causes slave to be deleted by the OS. // TODO: Convert this to a statement-level attribute once // https://github.com/rust-lang/rust/issues/15701 is on stable. - // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe + // FIXME: Create a mutex across all tests for using `SerialPort::pair()` as it's not threadsafe #![allow(unused_variables)] - let (master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair"); + let (master, mut slave) = SerialPort::pair().expect("Unable to create ptty pair"); slave.set_baud_rate(9600).unwrap(); assert_eq!(slave.baud_rate().unwrap(), 9600); @@ -124,9 +125,9 @@ fn test_ttyport_set_nonstandard_baud() { // `master` must be used here as Dropping it causes slave to be deleted by the OS. // TODO: Convert this to a statement-level attribute once // https://github.com/rust-lang/rust/issues/15701 is on stable. - // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe + // FIXME: Create a mutex across all tests for using `SerialPort::pair()` as it's not threadsafe #![allow(unused_variables)] - let (master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair"); + let (master, mut slave) = SerialPort::pair().expect("Unable to create ptty pair"); slave.set_baud_rate(10000).unwrap(); assert_eq!(slave.baud_rate().unwrap(), 10000); From 16bcb54361ae4aa5d52e2fc8f57514bfc6e799d6 Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 23 Apr 2021 20:41:28 -0400 Subject: [PATCH 04/18] Add TODO to find out more about threadsafety FIXME --- tests/test_tty.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_tty.rs b/tests/test_tty.rs index 87893eae..b1665d18 100644 --- a/tests/test_tty.rs +++ b/tests/test_tty.rs @@ -14,6 +14,9 @@ use serialport::posix::SerialPortExt; #[test] fn test_ttyport_pair() { // FIXME: Create a mutex across all tests for using `SerialPort::pair()` as it's not threadsafe + // TODO: Find out what's not thread-safe. Looks like the call to ptsname (used on non-linux + // platforms) is considered not-thread-safe, but unclear if anything else is. + // If that function isn't thread safe, perhaps a better fix would be to lock within the pair() function. let (mut master, mut slave) = SerialPort::pair().expect("Unable to create ptty pair"); master .set_timeout(Duration::from_millis(10)) From 7f82827c1500a97d510fb6251f58e987032833ac Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 23 Apr 2021 21:17:32 -0400 Subject: [PATCH 05/18] Cleanup: trailing spaces, unnecessary changes. --- src/lib.rs | 14 +++++++------- src/sys/posix/enumerate.rs | 6 +++--- src/sys/windows/com.rs | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c03273cd..d443dbe6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -223,7 +223,7 @@ impl SerialPortBuilder { } /// Set the baud rate in symbols-per-second. - /// + /// /// Default: `9600` pub fn baud_rate(mut self, baud_rate: u32) -> Self { self.baud_rate = baud_rate; @@ -231,7 +231,7 @@ impl SerialPortBuilder { } /// Set the number of bits used to represent a character sent on the line - /// + /// /// Default: `DataBits::Eight` pub fn data_bits(mut self, data_bits: DataBits) -> Self { self.data_bits = data_bits; @@ -239,7 +239,7 @@ impl SerialPortBuilder { } /// Set the type of signalling to use for controlling data transfer - /// + /// /// Default: `FlowControl::None` pub fn flow_control(mut self, flow_control: FlowControl) -> Self { self.flow_control = flow_control; @@ -247,7 +247,7 @@ impl SerialPortBuilder { } /// Set the type of parity to use for error checking - /// + /// /// Default: `Parity::None` pub fn parity(mut self, parity: Parity) -> Self { self.parity = parity; @@ -255,7 +255,7 @@ impl SerialPortBuilder { } /// Set the number of bits to use to signal the end of a character - /// + /// /// Default: `StopBits::One` pub fn stop_bits(mut self, stop_bits: StopBits) -> Self { self.stop_bits = stop_bits; @@ -263,7 +263,7 @@ impl SerialPortBuilder { } /// Set the amount of time to wait to receive data before timing out - /// + /// /// Default: `Duration::from_millis(0)` pub fn timeout(mut self, timeout: Duration) -> Self { self.timeout = timeout; @@ -271,7 +271,7 @@ impl SerialPortBuilder { } /// Open a cross-platform interface to the port with the specified settings. - /// + /// /// On windows, the path must start with the device prefix, e.g. `\\.\`. pub fn open(self, path: impl AsRef) -> Result { Ok(SerialPort(sys::SerialPort::open(self, path)?)) diff --git a/src/sys/posix/enumerate.rs b/src/sys/posix/enumerate.rs index e0da966d..0dc6574b 100644 --- a/src/sys/posix/enumerate.rs +++ b/src/sys/posix/enumerate.rs @@ -304,12 +304,12 @@ cfg_if! { let type_id = CFGetTypeID(value); if type_id == CFStringGetTypeID() { - let mut buf = [u8; 256]; + let mut buf = Vec::with_capacity(256); CFStringGetCString( value as CFStringRef, - &buf[..].as_mut_ptr(), - buf.len(), + buf.as_mut_ptr(), + 256, kCFStringEncodingUTF8, ); let path = CStr::from_ptr(buf.as_ptr()).to_string_lossy(); diff --git a/src/sys/windows/com.rs b/src/sys/windows/com.rs index 0fc02d78..0520037c 100644 --- a/src/sys/windows/com.rs +++ b/src/sys/windows/com.rs @@ -51,7 +51,7 @@ impl SerialPort { /// * `Io` for any other I/O error while opening or initializing the device. pub fn open(builder: SerialPortBuilder, path: impl AsRef) -> Result { let path = path.as_ref(); - + let name: Vec = OsStr::new(r"\\.\") .encode_wide() .chain(path.as_os_str().encode_wide()) From c2982c1f93e7ab39d94c240bf0bbefda974bf4a8 Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 23 Apr 2021 21:32:06 -0400 Subject: [PATCH 06/18] Fix for windows build. Get the windows build working. Remove the windows-specific extensions module because windows doesn't have any methods not available on posix. --- src/lib.rs | 3 --- src/sys/posix/tty.rs | 2 ++ src/sys/windows/com.rs | 39 +++++++++++++++++++++++++++++++++++---- src/windows.rs | 1 - 4 files changed, 37 insertions(+), 8 deletions(-) delete mode 100644 src/windows.rs diff --git a/src/lib.rs b/src/lib.rs index d443dbe6..845704a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,9 +37,6 @@ mod sys; #[cfg(any(unix, doc))] pub mod posix; -#[cfg(any(windows, doc))] -pub mod windows; - /// A type for results generated by interacting with serial ports /// /// The `Err` type is hard-wired to [`serialport::Error`](struct.Error.html). diff --git a/src/sys/posix/tty.rs b/src/sys/posix/tty.rs index 29dd02a4..7333e98e 100644 --- a/src/sys/posix/tty.rs +++ b/src/sys/posix/tty.rs @@ -507,6 +507,8 @@ impl IntoRawFd for SerialPort { impl IntoRawFd for crate::SerialPort { fn into_raw_fd(self) -> RawFd { + // crate::SerialPort doesn't explicitly implement Drop, so we can just take + // out the inner value. self.0.into_raw_fd() } } diff --git a/src/sys/windows/com.rs b/src/sys/windows/com.rs index 0520037c..867d26f2 100644 --- a/src/sys/windows/com.rs +++ b/src/sys/windows/com.rs @@ -1,7 +1,9 @@ use std::mem::MaybeUninit; use std::os::windows::prelude::*; use std::time::Duration; -use std::{io, ptr}; +use std::path::Path; +use std::ffi::OsStr; +use std::{io, ptr, mem}; use winapi::shared::minwindef::*; use winapi::um::commapi::*; @@ -15,7 +17,7 @@ use winapi::um::winnt::{ use crate::sys::windows::dcb; use crate::{ - ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result + ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPortBuilder, StopBits, }; @@ -32,7 +34,7 @@ pub struct SerialPort { port_name: Option, } -unsafe impl Send for SerialPort { +unsafe impl Send for SerialPort {} impl SerialPort { /// Opens a COM port as a serial device. @@ -296,7 +298,7 @@ impl SerialPort { dcb::set_dcb(self.handle, dcb) } - fn bytes_to_read(&self) -> Result { + pub fn bytes_to_read(&self) -> Result { let mut errors: DWORD = 0; let mut comstat = MaybeUninit::uninit(); @@ -364,12 +366,41 @@ impl AsRawHandle for SerialPort { } } +impl AsRawHandle for crate::SerialPort { + fn as_raw_handle(&self) -> RawHandle { + self.0.as_raw_handle() + } +} + +impl IntoRawHandle for SerialPort { + fn into_raw_handle(self) -> RawHandle { + let handle = self.handle as RawHandle; + // Forget self to avoid running the destructor. + mem::forget(self); + handle + } +} + +impl IntoRawHandle for crate::SerialPort { + fn into_raw_handle(self) -> RawHandle { + // crate::SerialPort doesn't explicitly implement Drop, so we can just take + // out the inner value. + self.0.into_raw_handle() + } +} + impl FromRawHandle for SerialPort { unsafe fn from_raw_handle(handle: RawHandle) -> Self { SerialPort::open_from_raw_handle(handle) } } +impl FromRawHandle for crate::SerialPort { + unsafe fn from_raw_handle(handle: RawHandle) -> Self { + crate::SerialPort(SerialPort::from_raw_handle(handle)) + } +} + impl io::Read for &SerialPort { fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut len: DWORD = 0; diff --git a/src/windows.rs b/src/windows.rs deleted file mode 100644 index a92e52cb..00000000 --- a/src/windows.rs +++ /dev/null @@ -1 +0,0 @@ -//! Provides windows-only extensions to the SerialPort type. \ No newline at end of file From 3b04ade33c192a0cd84fc6c0761745b448adc1a6 Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 23 Apr 2021 21:33:49 -0400 Subject: [PATCH 07/18] Run `cargo fmt` over everything. --- examples/clear_input_buffer.rs | 2 +- examples/clear_output_buffer.rs | 2 +- examples/duplex.rs | 5 +++-- examples/hardware_check.rs | 4 ++-- examples/pseudo_terminal.rs | 6 +++--- examples/receive_data.rs | 2 +- examples/transmit.rs | 2 +- src/posix.rs | 6 ++++-- src/sys/mod.rs | 2 +- src/sys/posix/mod.rs | 2 +- src/sys/posix/tty.rs | 7 +++---- src/sys/unsupported.rs | 7 +++---- src/sys/windows/com.rs | 11 +++++------ src/sys/windows/mod.rs | 2 +- tests/test_try_clone.rs | 2 +- tests/test_tty.rs | 4 ++-- 16 files changed, 33 insertions(+), 33 deletions(-) diff --git a/examples/clear_input_buffer.rs b/examples/clear_input_buffer.rs index b239755e..d6cacc82 100644 --- a/examples/clear_input_buffer.rs +++ b/examples/clear_input_buffer.rs @@ -42,7 +42,7 @@ use std::time::Duration; use clap::{App, AppSettings, Arg}; -use serialport::{SerialPort, ClearBuffer}; +use serialport::{ClearBuffer, SerialPort}; fn main() { let matches = App::new("Serialport Example - Clear Input Buffer") diff --git a/examples/clear_output_buffer.rs b/examples/clear_output_buffer.rs index 22c26302..b4b631d8 100644 --- a/examples/clear_output_buffer.rs +++ b/examples/clear_output_buffer.rs @@ -23,7 +23,7 @@ use std::time::Duration; use clap::{App, AppSettings, Arg, ArgMatches}; -use serialport::{SerialPort,ClearBuffer}; +use serialport::{ClearBuffer, SerialPort}; const DEFAULT_BLOCK_SIZE: &str = "128"; diff --git a/examples/duplex.rs b/examples/duplex.rs index aed27a9a..cc1dc605 100644 --- a/examples/duplex.rs +++ b/examples/duplex.rs @@ -11,7 +11,7 @@ //! To test this, have a physical or virtual loopback device connected as the //! only port in the system. -use std::io::{Write, Read}; +use std::io::{Read, Write}; use std::time::Duration; use std::{io, thread}; @@ -20,7 +20,8 @@ use serialport::SerialPort; fn main() { // Open the first serialport available. let port_name = &serialport::available_ports().expect("No serial port")[0].port_name; - let mut port = SerialPort::builder().open(port_name) + let mut port = SerialPort::builder() + .open(port_name) .expect("Failed to open serial port"); // Clone the port diff --git a/examples/hardware_check.rs b/examples/hardware_check.rs index 336b288a..906dcbd2 100644 --- a/examples/hardware_check.rs +++ b/examples/hardware_check.rs @@ -15,13 +15,13 @@ //! 3) With two ports physically connected to each other //! `cargo run --example hardware_check /dev/ttyUSB0 /dev/ttyUSB1` -use std::io::{Write, Read}; +use std::io::{Read, Write}; use std::str; use std::time::Duration; use clap::{App, AppSettings, Arg}; -use serialport::{SerialPort, ClearBuffer, DataBits, FlowControl, Parity, StopBits}; +use serialport::{ClearBuffer, DataBits, FlowControl, Parity, SerialPort, StopBits}; fn main() { let matches = App::new("Serialport Example - Hardware Check") diff --git a/examples/pseudo_terminal.rs b/examples/pseudo_terminal.rs index 34621b5f..0f1e5362 100644 --- a/examples/pseudo_terminal.rs +++ b/examples/pseudo_terminal.rs @@ -8,11 +8,11 @@ fn main() { use std::thread; use std::time; - use serialport::SerialPort; use serialport::posix::SerialPortExt; + use serialport::SerialPort; - let (mut master, mut slave) = SerialPort::pair() - .expect("Unable to create pseudo-terminal pair"); + let (mut master, mut slave) = + SerialPort::pair().expect("Unable to create pseudo-terminal pair"); // Master ptty has no associated path on the filesystem. println!( diff --git a/examples/receive_data.rs b/examples/receive_data.rs index a91d98ef..9810a2f4 100644 --- a/examples/receive_data.rs +++ b/examples/receive_data.rs @@ -1,4 +1,4 @@ -use std::io::{self, Write, Read}; +use std::io::{self, Read, Write}; use std::time::Duration; use clap::{App, AppSettings, Arg}; diff --git a/examples/transmit.rs b/examples/transmit.rs index 294e1441..9122ca0e 100644 --- a/examples/transmit.rs +++ b/examples/transmit.rs @@ -3,7 +3,7 @@ use std::time::Duration; use clap::{App, AppSettings, Arg}; -use serialport::{SerialPort, DataBits, StopBits}; +use serialport::{DataBits, SerialPort, StopBits}; fn main() { let matches = App::new("Serialport Example - Heartbeat") diff --git a/src/posix.rs b/src/posix.rs index fa20dfb7..9de7f794 100644 --- a/src/posix.rs +++ b/src/posix.rs @@ -32,7 +32,9 @@ pub trait SerialPortExt { /// /// let (master, slave) = SerialPort::pair().unwrap(); /// ``` - fn pair() -> Result<(Self, Self)> where Self: Sized; + fn pair() -> Result<(Self, Self)> + where + Self: Sized; /// Returns the exclusivity of the port /// @@ -54,4 +56,4 @@ pub trait SerialPortExt { /// Sends 0-valued bits over the port for a set duration fn send_break(&self, duration: BreakDuration) -> Result<()>; -} \ No newline at end of file +} diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 9dd1ceb5..7507f40b 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -14,4 +14,4 @@ pub use posix::*; pub use windows::*; #[cfg(not(any(unix, windows)))] -pub use unsupported::*; \ No newline at end of file +pub use unsupported::*; diff --git a/src/sys/posix/mod.rs b/src/sys/posix/mod.rs index ee79b185..522a9ec3 100644 --- a/src/sys/posix/mod.rs +++ b/src/sys/posix/mod.rs @@ -6,4 +6,4 @@ mod error; mod ioctl; mod poll; mod termios; -mod tty; \ No newline at end of file +mod tty; diff --git a/src/sys/posix/tty.rs b/src/sys/posix/tty.rs index 7333e98e..b1c823ff 100644 --- a/src/sys/posix/tty.rs +++ b/src/sys/posix/tty.rs @@ -7,13 +7,13 @@ use std::{io, mem}; use nix::fcntl::{fcntl, OFlag}; use nix::{self, libc, unistd}; +use crate::posix::{BreakDuration, SerialPortExt}; use crate::sys::posix::ioctl::{self, SerialLines}; use crate::sys::posix::termios; use crate::{ - ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, - SerialPortBuilder, StopBits, + ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPortBuilder, + StopBits, }; -use crate::posix::{BreakDuration, SerialPortExt}; /// Convenience method for removing exclusive access from /// a fd and closing it. @@ -148,7 +148,6 @@ impl SerialPort { }) } - fn set_pin(&mut self, pin: ioctl::SerialLines, level: bool) -> Result<()> { if level { ioctl::tiocmbis(self.fd, pin) diff --git a/src/sys/unsupported.rs b/src/sys/unsupported.rs index b9c53c07..57159fa4 100644 --- a/src/sys/unsupported.rs +++ b/src/sys/unsupported.rs @@ -1,4 +1,3 @@ - use std::io; use crate::{Error, ErrorKind, Result, SerialPortInfo}; @@ -12,7 +11,7 @@ pub fn available_ports() -> Result> { /// Unsupported serial port type can never be constructed. #[derive(Debug)] -pub enum SerialPort { } +pub enum SerialPort {} impl SerialPort { pub fn open(builder: SerialPortBuilder, path: impl AsRef) -> Result { @@ -39,7 +38,7 @@ impl SerialPort { } pub fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> { - unimplemented!() + unimplemented!() } pub fn read_clear_to_send(&mut self) -> Result { @@ -137,4 +136,4 @@ impl io::Write for &SerialPort { fn flush(&mut self) -> io::Result<()> { unimplemented!() } -} \ No newline at end of file +} diff --git a/src/sys/windows/com.rs b/src/sys/windows/com.rs index 867d26f2..a5d70f3c 100644 --- a/src/sys/windows/com.rs +++ b/src/sys/windows/com.rs @@ -1,9 +1,9 @@ +use std::ffi::OsStr; use std::mem::MaybeUninit; use std::os::windows::prelude::*; -use std::time::Duration; use std::path::Path; -use std::ffi::OsStr; -use std::{io, ptr, mem}; +use std::time::Duration; +use std::{io, mem, ptr}; use winapi::shared::minwindef::*; use winapi::um::commapi::*; @@ -17,8 +17,8 @@ use winapi::um::winnt::{ use crate::sys::windows::dcb; use crate::{ - ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, - SerialPortBuilder, StopBits, + ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPortBuilder, + StopBits, }; /// A serial port implementation for Windows COM ports @@ -349,7 +349,6 @@ impl SerialPort { Err(super::error::last_os_error()) } } - } impl Drop for SerialPort { diff --git a/src/sys/windows/mod.rs b/src/sys/windows/mod.rs index 3379497a..d171163e 100644 --- a/src/sys/windows/mod.rs +++ b/src/sys/windows/mod.rs @@ -1,5 +1,5 @@ -pub use enumerate::available_ports; pub use com::SerialPort; +pub use enumerate::available_ports; mod com; mod dcb; diff --git a/tests/test_try_clone.rs b/tests/test_try_clone.rs index 842de0b2..78e82caf 100644 --- a/tests/test_try_clone.rs +++ b/tests/test_try_clone.rs @@ -1,8 +1,8 @@ #![cfg(unix)] extern crate serialport; -use serialport::SerialPort; use serialport::posix::SerialPortExt; +use serialport::SerialPort; use std::io::{Read, Write}; // Test that cloning a port works as expected diff --git a/tests/test_tty.rs b/tests/test_tty.rs index b1665d18..b4608f08 100644 --- a/tests/test_tty.rs +++ b/tests/test_tty.rs @@ -8,13 +8,13 @@ use std::os::unix::prelude::*; use std::str; use std::time::Duration; -use serialport::SerialPort; use serialport::posix::SerialPortExt; +use serialport::SerialPort; #[test] fn test_ttyport_pair() { // FIXME: Create a mutex across all tests for using `SerialPort::pair()` as it's not threadsafe - // TODO: Find out what's not thread-safe. Looks like the call to ptsname (used on non-linux + // TODO: Find out what's not thread-safe. Looks like the call to ptsname (used on non-linux // platforms) is considered not-thread-safe, but unclear if anything else is. // If that function isn't thread safe, perhaps a better fix would be to lock within the pair() function. let (mut master, mut slave) = SerialPort::pair().expect("Unable to create ptty pair"); From 5cdd8293afb4947a9ad4f58647e2b060c7a5f6de Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 23 Apr 2021 21:43:08 -0400 Subject: [PATCH 08/18] Use as_ref with manual deref instead of as_deref. One of the CI toolchains uses an older version of rustc, which doesn't include as_deref, so we just do the same thing that as_deref does internally. --- src/sys/posix/tty.rs | 2 +- src/sys/windows/com.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sys/posix/tty.rs b/src/sys/posix/tty.rs index b1c823ff..cb6679e5 100644 --- a/src/sys/posix/tty.rs +++ b/src/sys/posix/tty.rs @@ -185,7 +185,7 @@ impl SerialPort { } pub fn name(&self) -> Option<&str> { - self.port_name.as_deref() + self.port_name.as_ref().map(|s| &**s) } /// Returns the port's baud rate diff --git a/src/sys/windows/com.rs b/src/sys/windows/com.rs index a5d70f3c..876ba186 100644 --- a/src/sys/windows/com.rs +++ b/src/sys/windows/com.rs @@ -155,7 +155,7 @@ impl SerialPort { } pub fn name(&self) -> Option<&str> { - self.port_name.as_deref() + self.port_name.as_ref().map(|s| &**s) } pub fn timeout(&self) -> Duration { From 12c41be89ac553cd10bd4e4e5f61dbef09e6f722 Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 7 May 2021 21:34:40 -0400 Subject: [PATCH 09/18] Move impl of SerialPortExt to the posix ext mod. Implementing the extension in the posix extension mod ensures it will appear in documentation even if documentation is built on windows, since that mod is cfg(doc). --- src/posix.rs | 19 +++++++++++++++++++ src/sys/posix/tty.rs | 19 ------------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/posix.rs b/src/posix.rs index 9de7f794..e1083a5c 100644 --- a/src/posix.rs +++ b/src/posix.rs @@ -57,3 +57,22 @@ pub trait SerialPortExt { /// Sends 0-valued bits over the port for a set duration fn send_break(&self, duration: BreakDuration) -> Result<()>; } + +impl SerialPortExt for crate::SerialPort { + fn pair() -> Result<(Self, Self)> { + let (master, slave) = crate::sys::SerialPort::pair()?; + Ok((crate::SerialPort(master), crate::SerialPort(slave))) + } + + fn exclusive(&self) -> bool { + self.0.exclusive() + } + + fn set_exclusive(&mut self, exclusive: bool) -> Result<()> { + self.0.set_exclusive(exclusive) + } + + fn send_break(&self, duration: BreakDuration) -> Result<()> { + self.0.send_break(duration) + } +} diff --git a/src/sys/posix/tty.rs b/src/sys/posix/tty.rs index cb6679e5..36017366 100644 --- a/src/sys/posix/tty.rs +++ b/src/sys/posix/tty.rs @@ -711,25 +711,6 @@ impl SerialPortExt for SerialPort { } } -impl SerialPortExt for crate::SerialPort { - fn pair() -> Result<(Self, Self)> { - let (master, slave) = SerialPort::pair()?; - Ok((crate::SerialPort(master), crate::SerialPort(slave))) - } - - fn exclusive(&self) -> bool { - self.0.exclusive() - } - - fn set_exclusive(&mut self, exclusive: bool) -> Result<()> { - self.0.set_exclusive(exclusive) - } - - fn send_break(&self, duration: BreakDuration) -> Result<()> { - self.0.send_break(duration) - } -} - #[test] fn test_ttyport_into_raw_fd() { // `master` must be used here as Dropping it causes slave to be deleted by the OS. From 32612b8f997b2e6d8b0f109596cac751072a6efe Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Mon, 3 May 2021 10:14:45 -0400 Subject: [PATCH 10/18] Add timeouts to the new API. Current timeouts are based on [this comment from !78](https://gitlab.com/susurrus/serialport-rs/-/merge_requests/78#note_343695538), copying the windows timeout settings needed to get posix-like timeout behavior. That is, reads should return available data ASAP regardless of timeouts, but stop early when there is a timeout. --- examples/clear_input_buffer.rs | 3 +- examples/clear_output_buffer.rs | 3 +- examples/hardware_check.rs | 4 +- examples/receive_data.rs | 3 +- src/lib.rs | 49 ++++++++++++++++++------- src/sys/posix/poll.rs | 23 +++++++----- src/sys/posix/tty.rs | 39 ++++++++++++++------ src/sys/windows/com.rs | 65 ++++++++++++++++++++++++++------- tests/test_serialport.rs | 6 ++- tests/test_tty.rs | 17 ++++++--- 10 files changed, 152 insertions(+), 60 deletions(-) diff --git a/examples/clear_input_buffer.rs b/examples/clear_input_buffer.rs index d6cacc82..7e0792e7 100644 --- a/examples/clear_input_buffer.rs +++ b/examples/clear_input_buffer.rs @@ -78,7 +78,8 @@ fn run(port_name: &str, baud_rate: &str) -> Result<(), Box> { let port = SerialPort::builder() .baud_rate(rate) - .timeout(Duration::from_millis(10)) + .read_timeout(Some(Duration::from_millis(10))) + .write_timeout(Some(Duration::from_millis(10))) .open(port_name) .map_err(|ref e| format!("Port '{}' not available: {}", &port_name, e))?; diff --git a/examples/clear_output_buffer.rs b/examples/clear_output_buffer.rs index b4b631d8..177d3554 100644 --- a/examples/clear_output_buffer.rs +++ b/examples/clear_output_buffer.rs @@ -70,7 +70,8 @@ fn run(port_name: &str, baud_rate: &str, block_size: usize) -> Result<(), Box, + /// Amount of time to wait to write data before timing out. + write_timeout: Option, } impl SerialPortBuilder { @@ -259,11 +261,21 @@ impl SerialPortBuilder { self } - /// Set the amount of time to wait to receive data before timing out + /// Set the amount of time to wait to receive data before timing out. If set + /// to `None`, hang indefinitely. /// - /// Default: `Duration::from_millis(0)` - pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = timeout; + /// Default: `None` + pub fn read_timeout(mut self, read_timeout: Option) -> Self { + self.read_timeout = read_timeout; + self + } + + /// Set the amount of time to wait to write data before timing out. If set to + /// `None`, hang indefinitely. + /// + /// Default: `None` + pub fn write_timeout(mut self, write_timeout: Option) -> Self { + self.write_timeout = write_timeout; self } @@ -283,7 +295,8 @@ impl Default for SerialPortBuilder { flow_control: FlowControl::None, parity: Parity::None, stop_bits: StopBits::One, - timeout: Duration::from_millis(0), + read_timeout: None, + write_timeout: None, } } } @@ -359,9 +372,14 @@ impl SerialPort { self.0.stop_bits() } - /// Returns the current timeout. - pub fn timeout(&self) -> Duration { - self.0.timeout() + /// Returns the current read timeout. + pub fn read_timeout(&self) -> Option { + self.0.read_timeout() + } + + /// Returns the current write timeout. + pub fn write_timeout(&self) -> Option { + self.0.write_timeout() } // Port settings setters @@ -397,9 +415,14 @@ impl SerialPort { self.0.set_stop_bits(stop_bits) } - /// Sets the timeout for future I/O operations. - pub fn set_timeout(&mut self, timeout: Duration) -> Result<()> { - self.0.set_timeout(timeout) + /// Sets the read timeout for future I/O operations. + pub fn set_read_timeout(&mut self, read_timeout: Option) -> Result<()> { + self.0.set_read_timeout(read_timeout) + } + + /// Sets the write timeout for future I/O operations. + pub fn set_write_timeout(&mut self, write_timeout: Option) -> Result<()> { + self.0.set_write_timeout(write_timeout) } // Functions for setting non-data control signal pins diff --git a/src/sys/posix/poll.rs b/src/sys/posix/poll.rs index f10d5b69..45221637 100644 --- a/src/sys/posix/poll.rs +++ b/src/sys/posix/poll.rs @@ -9,30 +9,35 @@ use nix::poll::{PollFd, PollFlags}; #[cfg(target_os = "linux")] use nix::sys::signal::SigSet; #[cfg(target_os = "linux")] -use nix::sys::time::{TimeSpec, TimeValLike}; +use nix::sys::time::TimeSpec; -pub fn wait_read_fd(fd: RawFd, timeout: Duration) -> io::Result<()> { +pub fn wait_read_fd(fd: RawFd, timeout: Option) -> io::Result<()> { wait_fd(fd, PollFlags::POLLIN, timeout) } -pub fn wait_write_fd(fd: RawFd, timeout: Duration) -> io::Result<()> { +pub fn wait_write_fd(fd: RawFd, timeout: Option) -> io::Result<()> { wait_fd(fd, PollFlags::POLLOUT, timeout) } -fn wait_fd(fd: RawFd, events: PollFlags, timeout: Duration) -> io::Result<()> { +fn wait_fd(fd: RawFd, events: PollFlags, timeout: Option) -> io::Result<()> { use nix::errno::Errno::{EIO, EPIPE}; let mut fd = PollFd::new(fd, events); - let milliseconds = - timeout.as_secs() as i64 * 1000 + i64::from(timeout.subsec_nanos()) / 1_000_000; #[cfg(target_os = "linux")] let wait_res = { - let timespec = TimeSpec::milliseconds(milliseconds); - nix::poll::ppoll(slice::from_mut(&mut fd), Some(timespec), SigSet::empty()) + let timespec = timeout.map(TimeSpec::from); + nix::poll::ppoll(slice::from_mut(&mut fd), timespec, SigSet::empty()) }; #[cfg(not(target_os = "linux"))] - let wait_res = nix::poll::poll(slice::from_mut(&mut fd), milliseconds as nix::libc::c_int); + let wait_res = { + use std::convert::TryFrom; + let milliseconds = match timeout { + Some(duration) => i32::try_from(duration.as_millis()).or(i32::MAX), + None => -1, + }; + nix::poll::poll(slice::from_mut(&mut fd), milliseconds) + }; let wait = match wait_res { Ok(r) => r, diff --git a/src/sys/posix/tty.rs b/src/sys/posix/tty.rs index 36017366..d3f3a7a8 100644 --- a/src/sys/posix/tty.rs +++ b/src/sys/posix/tty.rs @@ -41,7 +41,8 @@ fn close(fd: RawFd) { #[derive(Debug)] pub struct SerialPort { fd: RawFd, - timeout: Duration, + read_timeout: Option, + write_timeout: Option, exclusive: bool, port_name: Option, #[cfg(any(target_os = "ios", target_os = "macos"))] @@ -140,7 +141,8 @@ impl SerialPort { // Return the final port object Ok(SerialPort { fd, - timeout: builder.timeout, + read_timeout: builder.read_timeout, + write_timeout: builder.write_timeout, exclusive: false, port_name: Some(path.to_string_lossy().into_owned()), #[cfg(any(target_os = "ios", target_os = "macos"))] @@ -178,7 +180,8 @@ impl SerialPort { fd: fd_cloned, exclusive: self.exclusive, port_name: self.port_name.clone(), - timeout: self.timeout, + read_timeout: self.read_timeout, + write_timeout: self.write_timeout, #[cfg(any(target_os = "ios", target_os = "macos"))] baud_rate: self.baud_rate, }) @@ -353,8 +356,12 @@ impl SerialPort { } } - pub fn timeout(&self) -> Duration { - self.timeout + pub fn read_timeout(&self) -> Option { + self.read_timeout + } + + pub fn write_timeout(&self) -> Option { + self.write_timeout } #[cfg(any( @@ -415,8 +422,13 @@ impl SerialPort { return termios::set_termios(self.fd, &termios); } - pub fn set_timeout(&mut self, timeout: Duration) -> Result<()> { - self.timeout = timeout; + pub fn set_read_timeout(&mut self, read_timeout: Option) -> Result<()> { + self.read_timeout = read_timeout; + Ok(()) + } + + pub fn set_write_timeout(&mut self, write_timeout: Option) -> Result<()> { + self.write_timeout = write_timeout; Ok(()) } @@ -527,7 +539,8 @@ impl FromRawFd for SerialPort { unsafe fn from_raw_fd(fd: RawFd) -> Self { SerialPort { fd, - timeout: Duration::from_millis(100), + read_timeout: None, + write_timeout: None, exclusive: ioctl::tiocexcl(fd).is_ok(), // It is not trivial to get the file path corresponding to a file descriptor. // We'll punt on it and set it to `None` here. @@ -549,7 +562,7 @@ impl FromRawFd for crate::SerialPort { impl io::Read for &SerialPort { fn read(&mut self, buf: &mut [u8]) -> io::Result { - if let Err(e) = super::poll::wait_read_fd(self.fd, self.timeout) { + if let Err(e) = super::poll::wait_read_fd(self.fd, self.read_timeout) { return Err(io::Error::from(Error::from(e))); } @@ -559,7 +572,7 @@ impl io::Read for &SerialPort { impl io::Write for &SerialPort { fn write(&mut self, buf: &[u8]) -> io::Result { - if let Err(e) = super::poll::wait_write_fd(self.fd, self.timeout) { + if let Err(e) = super::poll::wait_write_fd(self.fd, self.write_timeout) { return Err(io::Error::from(Error::from(e))); } @@ -646,7 +659,8 @@ impl SerialPortExt for SerialPort { let slave_tty = SerialPort { fd, - timeout: Duration::from_millis(100), + read_timeout: None, + write_timeout: None, exclusive: true, port_name: Some(ptty_name), #[cfg(any(target_os = "ios", target_os = "macos"))] @@ -658,7 +672,8 @@ impl SerialPortExt for SerialPort { // BSDs when used on the master port. let master_tty = SerialPort { fd: next_pty_fd.into_raw_fd(), - timeout: Duration::from_millis(100), + read_timeout: None, + write_timeout: None, exclusive: true, port_name: None, #[cfg(any(target_os = "ios", target_os = "macos"))] diff --git a/src/sys/windows/com.rs b/src/sys/windows/com.rs index 876ba186..030a7f82 100644 --- a/src/sys/windows/com.rs +++ b/src/sys/windows/com.rs @@ -1,3 +1,4 @@ +use std::convert::TryFrom; use std::ffi::OsStr; use std::mem::MaybeUninit; use std::os::windows::prelude::*; @@ -30,7 +31,8 @@ use crate::{ #[derive(Debug)] pub struct SerialPort { handle: HANDLE, - timeout: Duration, + read_timeout: Option, + write_timeout: Option, port_name: Option, } @@ -86,7 +88,7 @@ impl SerialPort { dcb::set_dcb(handle, dcb)?; let mut com = SerialPort::open_from_raw_handle(handle as RawHandle); - com.set_timeout(builder.timeout)?; + com.set_timeouts(builder.read_timeout, builder.write_timeout)?; com.port_name = Some(path.to_string_lossy().into_owned()); Ok(com) } @@ -120,7 +122,8 @@ impl SerialPort { Ok(SerialPort { handle: cloned_handle, port_name: self.port_name.clone(), - timeout: self.timeout, + read_timeout: self.read_timeout, + write_timeout: self.write_timeout, }) } else { Err(super::error::last_os_error()) @@ -145,11 +148,15 @@ impl SerialPort { } fn open_from_raw_handle(handle: RawHandle) -> Self { - // It is not trivial to get the file path corresponding to a handle. - // We'll punt and set it `None` here. SerialPort { handle: handle as HANDLE, - timeout: Duration::from_millis(100), + // It's possible to retrieve the COMMTIMEOUTS struct from the handle, + // but mapping that back to simple timeout durations would be difficult. + // Instead we just set `None` and add a warning to `FromRawHandle`. + read_timeout: None, + write_timeout: None, + // It is not trivial to get the file path corresponding to a handle. + // We'll punt and set it `None` here. port_name: None, } } @@ -158,26 +165,49 @@ impl SerialPort { self.port_name.as_ref().map(|s| &**s) } - pub fn timeout(&self) -> Duration { - self.timeout + pub fn read_timeout(&self) -> Option { + self.read_timeout } - pub fn set_timeout(&mut self, timeout: Duration) -> Result<()> { - let milliseconds = timeout.as_secs() * 1000 + timeout.subsec_nanos() as u64 / 1_000_000; + pub fn write_timeout(&self) -> Option { + self.write_timeout + } + + pub fn set_read_timeout(&mut self, read_timeout: Option) -> Result<()> { + self.set_timeouts(read_timeout, self.write_timeout) + } + + pub fn set_write_timeout(&mut self, write_timeout: Option) -> Result<()> { + self.set_timeouts(self.read_timeout, write_timeout) + } + + fn set_timeouts(&mut self, read_timeout: Option, write_timeout: Option) -> Result<()> { + let read_timeout_ms = match read_timeout { + Some(duration) => DWORD::try_from(duration.as_millis()) + .map_or(DWORD::MAX, |timeout| timeout.max(1)), + None => 0, + }; + + let write_timeout_ms = match write_timeout { + Some(duration) => DWORD::try_from(duration.as_millis()) + .map_or(DWORD::MAX, |timeout| timeout.max(1)), + None => 0, + }; let mut timeouts = COMMTIMEOUTS { - ReadIntervalTimeout: 0, + ReadIntervalTimeout: 1, ReadTotalTimeoutMultiplier: 0, - ReadTotalTimeoutConstant: milliseconds as DWORD, + ReadTotalTimeoutConstant: read_timeout_ms, WriteTotalTimeoutMultiplier: 0, - WriteTotalTimeoutConstant: 0, + WriteTotalTimeoutConstant: write_timeout_ms, }; if unsafe { SetCommTimeouts(self.handle, &mut timeouts) } == 0 { return Err(super::error::last_os_error()); } - self.timeout = timeout; + self.read_timeout = read_timeout; + self.write_timeout = write_timeout; Ok(()) } @@ -395,6 +425,13 @@ impl FromRawHandle for SerialPort { } impl FromRawHandle for crate::SerialPort { + /// Create a SerialPort from a raw handle. + /// + /// Warning: the returned `SerialPort` will report timeouts of `None` for + /// `read_timeout` and `write_timeout`, however the actual timeouts set on the + /// underlying handle may be different. You can use `set_read_timeout` and + /// `set_write_timeout` to reset the timeouts on the handle to make them match + /// the values on the `SerialPort`. unsafe fn from_raw_handle(handle: RawHandle) -> Self { crate::SerialPort(SerialPort::from_raw_handle(handle)) } diff --git a/tests/test_serialport.rs b/tests/test_serialport.rs index 799bbe15..89deb5ba 100644 --- a/tests/test_serialport.rs +++ b/tests/test_serialport.rs @@ -33,7 +33,8 @@ fn test_configuring_ports() { .flow_control(FlowControl::None) .parity(Parity::None) .stop_bits(StopBits::One) - .timeout(Duration::from_millis(1)) + .read_timeout(Some(Duration::from_millis(1))) + .write_timeout(Some(Duration::from_millis(1))) .open("/dev/ttyUSB0"); } @@ -45,7 +46,8 @@ fn test_duplicating_port_config() { .flow_control(FlowControl::None) .parity(Parity::None) .stop_bits(StopBits::One) - .timeout(Duration::from_millis(1)); + .read_timeout(Some(Duration::from_millis(1))) + .write_timeout(Some(Duration::from_millis(1))); let port2_config = port1_config.clone().baud_rate(115_200); diff --git a/tests/test_tty.rs b/tests/test_tty.rs index b4608f08..5f4646ac 100644 --- a/tests/test_tty.rs +++ b/tests/test_tty.rs @@ -19,11 +19,17 @@ fn test_ttyport_pair() { // If that function isn't thread safe, perhaps a better fix would be to lock within the pair() function. let (mut master, mut slave) = SerialPort::pair().expect("Unable to create ptty pair"); master - .set_timeout(Duration::from_millis(10)) - .expect("Unable to set timeout on the master"); + .set_read_timeout(Some(Duration::from_millis(10))) + .expect("Unable to set read timeout on the master"); + master + .set_write_timeout(Some(Duration::from_millis(10))) + .expect("Unable to set write timeout on the master"); + slave + .set_read_timeout(Some(Duration::from_millis(10))) + .expect("Unable to set read timeout on the slave"); slave - .set_timeout(Duration::from_millis(10)) - .expect("Unable to set timeout on the slave"); + .set_write_timeout(Some(Duration::from_millis(10))) + .expect("Unable to set write timeout on the slave"); // Test file descriptors. assert!( @@ -76,7 +82,8 @@ fn test_ttyport_timeout() { std::thread::spawn(move || { // FIXME: Create a mutex across all tests for using `SerialPort::pair()` as it's not threadsafe let (mut master, _slave) = SerialPort::pair().expect("Unable to create ptty pair"); - master.set_timeout(Duration::new(1, 0)).unwrap(); + master.set_read_timeout(Some(Duration::new(1, 0))).unwrap(); + master.set_write_timeout(Some(Duration::new(1, 0))).unwrap(); let mut buffer = [0u8]; let read_res = master.read(&mut buffer); From a80f33d12de75436210caba144d659f7037d8383 Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Mon, 3 May 2021 10:40:42 -0400 Subject: [PATCH 11/18] Fix non-linux posix build (Result.or -> unwrap_or). --- src/sys/posix/poll.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sys/posix/poll.rs b/src/sys/posix/poll.rs index 45221637..fb493884 100644 --- a/src/sys/posix/poll.rs +++ b/src/sys/posix/poll.rs @@ -33,7 +33,7 @@ fn wait_fd(fd: RawFd, events: PollFlags, timeout: Option) -> io::Resul let wait_res = { use std::convert::TryFrom; let milliseconds = match timeout { - Some(duration) => i32::try_from(duration.as_millis()).or(i32::MAX), + Some(duration) => i32::try_from(duration.as_millis()).unwrap_or(i32::MAX), None => -1, }; nix::poll::poll(slice::from_mut(&mut fd), milliseconds) From 5923ec004e9b81de27dac5fca66fdbe9da5b1f2a Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Mon, 3 May 2021 11:09:09 -0400 Subject: [PATCH 12/18] Fix formatting (cargo fmt). --- src/sys/windows/com.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/sys/windows/com.rs b/src/sys/windows/com.rs index 030a7f82..29b1a6eb 100644 --- a/src/sys/windows/com.rs +++ b/src/sys/windows/com.rs @@ -181,16 +181,22 @@ impl SerialPort { self.set_timeouts(self.read_timeout, write_timeout) } - fn set_timeouts(&mut self, read_timeout: Option, write_timeout: Option) -> Result<()> { + fn set_timeouts( + &mut self, + read_timeout: Option, + write_timeout: Option, + ) -> Result<()> { let read_timeout_ms = match read_timeout { - Some(duration) => DWORD::try_from(duration.as_millis()) - .map_or(DWORD::MAX, |timeout| timeout.max(1)), + Some(duration) => { + DWORD::try_from(duration.as_millis()).map_or(DWORD::MAX, |timeout| timeout.max(1)) + } None => 0, }; let write_timeout_ms = match write_timeout { - Some(duration) => DWORD::try_from(duration.as_millis()) - .map_or(DWORD::MAX, |timeout| timeout.max(1)), + Some(duration) => { + DWORD::try_from(duration.as_millis()).map_or(DWORD::MAX, |timeout| timeout.max(1)) + } None => 0, }; From 934539e788ff91fb4e1d2409b8927f8b4136eaba Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 7 May 2021 21:31:40 -0400 Subject: [PATCH 13/18] Add windows extensions for setting COMMTIMEOUTS. --- src/lib.rs | 3 ++ src/sys/windows/com.rs | 21 ++++++++- src/windows.rs | 101 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/windows.rs diff --git a/src/lib.rs b/src/lib.rs index d1d987ca..040940f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,9 @@ mod sys; #[cfg(any(unix, doc))] pub mod posix; +#[cfg(any(windows, doc))] +pub mod windows; + /// A type for results generated by interacting with serial ports /// /// The `Err` type is hard-wired to [`serialport::Error`](struct.Error.html). diff --git a/src/sys/windows/com.rs b/src/sys/windows/com.rs index 29b1a6eb..dd9ec053 100644 --- a/src/sys/windows/com.rs +++ b/src/sys/windows/com.rs @@ -17,6 +17,7 @@ use winapi::um::winnt::{ }; use crate::sys::windows::dcb; +use crate::windows::{CommTimeouts, SerialPortExt}; use crate::{ ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPortBuilder, StopBits, @@ -208,7 +209,7 @@ impl SerialPort { WriteTotalTimeoutConstant: write_timeout_ms, }; - if unsafe { SetCommTimeouts(self.handle, &mut timeouts) } == 0 { + if unsafe { SetCommTimeouts(self.handle, &mut timeouts) } == FALSE { return Err(super::error::last_os_error()); } @@ -496,3 +497,21 @@ impl io::Write for &SerialPort { } } } + +impl SerialPortExt for SerialPort { + fn comm_timeouts(&self) -> Result { + let mut timeouts: COMMTIMEOUTS = unsafe { MaybeUninit::zeroed().assume_init() }; + if unsafe { GetCommTimeouts(self.handle, &mut timeouts) } == FALSE { + return Err(super::error::last_os_error()); + } + Ok(timeouts.into()) + } + + fn set_comm_timeouts(&self, timeouts: CommTimeouts) -> Result<()> { + let mut timeouts: COMMTIMEOUTS = timeouts.into(); + if unsafe { SetCommTimeouts(self.handle, &mut timeouts) } == FALSE { + return Err(super::error::last_os_error()); + } + Ok(()) + } +} diff --git a/src/windows.rs b/src/windows.rs new file mode 100644 index 00000000..343647a2 --- /dev/null +++ b/src/windows.rs @@ -0,0 +1,101 @@ +//! Provides windows-only extensions to the SerialPort type. + +// Note: windows-specific things are imported under narrow scope because this +// mod is also compiled on unix when doing a `doc` build. + +use std::convert::TryFrom; +use std::time::Duration; + +#[cfg(windows)] +use winapi::shared::minwindef::DWORD; +#[cfg(windows)] +use winapi::um::winbase::COMMTIMEOUTS; + +use crate::Result; + +/// Represents COM Port Timeouts. Equivalent to +/// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts. +/// +/// See official documentation of the `COMMTIMEOUTS` C struct for details. +/// +/// Timeouts are given as durations instead of integer milliseconds. When applied, +/// they will have at-most millisecond precision. +#[derive(Debug, Copy, Clone)] +pub struct CommTimeouts { + read_interval_timeout: Duration, + read_total_timeout_multiplier: Duration, + read_total_timeout_constant: Duration, + write_total_timeout_multiplier: Duration, + write_total_timeout_constant: Duration, +} + +#[cfg(windows)] +impl From for CommTimeouts { + fn from(timeouts: COMMTIMEOUTS) -> Self { + CommTimeouts { + read_interval_timeout: Duration::from_millis(timeouts.ReadIntervalTimeout as u64), + read_total_timeout_multiplier: Duration::from_millis( + timeouts.ReadTotalTimeoutMultiplier as u64, + ), + read_total_timeout_constant: Duration::from_millis( + timeouts.ReadTotalTimeoutConstant as u64, + ), + write_total_timeout_multiplier: Duration::from_millis( + timeouts.WriteTotalTimeoutMultiplier as u64, + ), + write_total_timeout_constant: Duration::from_millis( + timeouts.WriteTotalTimeoutConstant as u64, + ), + } + } +} + +#[cfg(windows)] +impl From for COMMTIMEOUTS { + fn from(timeouts: CommTimeouts) -> Self { + COMMTIMEOUTS { + ReadIntervalTimeout: DWORD::try_from(timeouts.read_interval_timeout.as_millis()) + .unwrap_or(DWORD::MAX), + ReadTotalTimeoutMultiplier: DWORD::try_from( + timeouts.read_total_timeout_multiplier.as_millis(), + ) + .unwrap_or(DWORD::MAX), + ReadTotalTimeoutConstant: DWORD::try_from( + timeouts.read_total_timeout_constant.as_millis(), + ) + .unwrap_or(DWORD::MAX), + WriteTotalTimeoutMultiplier: DWORD::try_from( + timeouts.write_total_timeout_multiplier.as_millis(), + ) + .unwrap_or(DWORD::MAX), + WriteTotalTimeoutConstant: DWORD::try_from( + timeouts.write_total_timeout_constant.as_millis(), + ) + .unwrap_or(DWORD::MAX), + } + } +} + +/// Windows-only extensions to the SerialPort type. +pub trait SerialPortExt { + /// Gets the current timeouts set on the serial port. While the `read_timeout` + /// and `write_timeout` methods only return the most recent value set through + /// the `SerialPort` API, this will return the values actually seen by Windows. + fn comm_timeouts(&self) -> Result; + + /// Sets the timeouts used by the serialport. Unlike `set_read_timeout` and + /// `set_write_timeout`, which are limited to simple posix-like behavior, this + /// method allows access to the full range of available timeout settings allowed + /// by windows. + fn set_comm_timeouts(&self, timeouts: CommTimeouts) -> Result<()>; +} + +impl SerialPortExt for crate::SerialPort { + fn comm_timeouts(&self) -> Result { + self.0.comm_timeouts() + } + + fn set_comm_timeouts(&self, timeouts: CommTimeouts) -> Result<()> { + self.0.set_comm_timeouts(timeouts) + } +} From bb0becda0db6cf511405755a3904e163ec4c6047 Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Wed, 5 May 2021 11:10:54 -0400 Subject: [PATCH 14/18] Add EventCache for win event HANDLEs. EventCache will hold the HANDLE for a read/write when not in use or will produce a new handle for each thread when multiple threads try to read/write at the same time. The cache holds a single value and will deallocate extra handles when there is already a handle stored. We have one cache for a read_handle and one cache for a write_handle. The expectation is that in the normal case, at most one thread will be reading/writing at a time, and so at most one handle will be created for each of read and write. But in abnormal cases, when multiple threads try to read/write at the same time, we auto-create and auto-close extra handles as needed. Fix the into_raw_handle/into_raw_fd to drop other fields that were previously leaked. --- Cargo.toml | 2 +- src/sys/posix/tty.rs | 16 ++++-- src/sys/windows/com.rs | 96 ++++++++++++++++++++++--------- src/sys/windows/event_cache.rs | 100 +++++++++++++++++++++++++++++++++ src/sys/windows/mod.rs | 1 + 5 files changed, 182 insertions(+), 33 deletions(-) create mode 100644 src/sys/windows/event_cache.rs diff --git a/Cargo.toml b/Cargo.toml index db067631..0def6152 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ regex = "1.5.4" [target."cfg(windows)".dependencies.winapi] version = "0.3.9" features = ["cguid", "commapi", "errhandlingapi", "fileapi", "guiddef", "handleapi", "minwinbase", - "minwindef", "ntdef", "setupapi", "winbase", "winerror", "winnt"] + "minwindef", "ntdef", "setupapi", "winbase", "winerror", "winnt", "ioapiset", "synchapi"] [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] IOKit-sys = "0.1.5" diff --git a/src/sys/posix/tty.rs b/src/sys/posix/tty.rs index d3f3a7a8..72699244 100644 --- a/src/sys/posix/tty.rs +++ b/src/sys/posix/tty.rs @@ -506,11 +506,17 @@ impl AsRawFd for crate::SerialPort { } impl IntoRawFd for SerialPort { - fn into_raw_fd(self) -> RawFd { - // Pull just the file descriptor out. We also prevent the destructor - // from being run by calling `mem::forget`. If we didn't do this, the - // port would be closed, which would make `into_raw_fd` unusable. - let SerialPort { fd, .. } = self; + fn into_raw_fd(mut self) -> RawFd { + // into_raw_fd needs to remove the file descriptor from the `SerialPort` + // to return it, but also needs to prevent Drop from being called, since + // that would close the file descriptor and make `into_raw_fd` unusuable. + // However, we also want to avoid leaking the rest of the contents of the + // struct, so we either need to take it out or be sure it doesn't need to + // be dropped. + let fd = self.fd; + // Currently port_name is the only field that needs to be dropped, and we + // can do that by taking it out of the optional before we forget the struct. + self.port_name.take(); mem::forget(self); fd } diff --git a/src/sys/windows/com.rs b/src/sys/windows/com.rs index dd9ec053..c2db6e9e 100644 --- a/src/sys/windows/com.rs +++ b/src/sys/windows/com.rs @@ -7,9 +7,13 @@ use std::time::Duration; use std::{io, mem, ptr}; use winapi::shared::minwindef::*; +use winapi::shared::winerror::ERROR_IO_PENDING; use winapi::um::commapi::*; +use winapi::um::errhandlingapi::GetLastError; use winapi::um::fileapi::*; use winapi::um::handleapi::*; +use winapi::um::ioapiset::GetOverlappedResult; +use winapi::um::minwinbase::OVERLAPPED; use winapi::um::processthreadsapi::GetCurrentProcess; use winapi::um::winbase::*; use winapi::um::winnt::{ @@ -18,6 +22,7 @@ use winapi::um::winnt::{ use crate::sys::windows::dcb; use crate::windows::{CommTimeouts, SerialPortExt}; +use crate::sys::windows::event_cache::EventCache; use crate::{ ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPortBuilder, StopBits, @@ -32,6 +37,8 @@ use crate::{ #[derive(Debug)] pub struct SerialPort { handle: HANDLE, + read_event: EventCache, + write_event: EventCache, read_timeout: Option, write_timeout: Option, port_name: Option, @@ -70,7 +77,7 @@ impl SerialPort { 0, ptr::null_mut(), OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0 as HANDLE, ) }; @@ -119,16 +126,18 @@ impl SerialPort { TRUE, DUPLICATE_SAME_ACCESS, ); - if cloned_handle != INVALID_HANDLE_VALUE { - Ok(SerialPort { - handle: cloned_handle, - port_name: self.port_name.clone(), - read_timeout: self.read_timeout, - write_timeout: self.write_timeout, - }) - } else { - Err(super::error::last_os_error()) - } + } + if cloned_handle != INVALID_HANDLE_VALUE { + Ok(SerialPort { + handle: cloned_handle, + read_event: EventCache::new(), + write_event: EventCache::new(), + port_name: self.port_name.clone(), + read_timeout: self.read_timeout, + write_timeout: self.write_timeout, + }) + } else { + Err(super::error::last_os_error()) } } @@ -151,6 +160,8 @@ impl SerialPort { fn open_from_raw_handle(handle: RawHandle) -> Self { SerialPort { handle: handle as HANDLE, + read_event: EventCache::new(), + write_event: EventCache::new(), // It's possible to retrieve the COMMTIMEOUTS struct from the handle, // but mapping that back to simple timeout durations would be difficult. // Instead we just set `None` and add a warning to `FromRawHandle`. @@ -409,11 +420,34 @@ impl AsRawHandle for crate::SerialPort { } impl IntoRawHandle for SerialPort { - fn into_raw_handle(self) -> RawHandle { - let handle = self.handle as RawHandle; - // Forget self to avoid running the destructor. + fn into_raw_handle(mut self) -> RawHandle { + // into_raw_handle needs to remove the handle from the `SerialPort` to + // return it, but also needs to prevent Drop from being called, since + // that would close the handle and make `into_raw_handle` unusuable. + // However, we also want to avoid leaking the rest of the contents of the + // struct, so we either need to take it out or be sure it doesn't need to + // be dropped. + let handle = self.handle; + // Take the port_name out of the option to drop it now. + self.port_name.take(); + + // Read out both the read_event and write event into different variables + // before forgetting. This is to prevent a double-free, which could happen + // if either of their destructors panics. For example, suppose we instead + // did ptr::drop_in_place(&self.read_event); If that call panics, we will + // double-free read_event, since we haven't forgotten self yet so the + // destructor for SerialPort will run and try to drop read_event again. + // This is even worse for write_event, since that would double-free both + // read_event and write_event. Therefore we want to pull these both out + // without dropping them, then forget self, then drop them, so that at + // worst a panic causes us to leak a handle rather than double-free. + // + // Unsafe safety: these reads are safe because we are going to forget + // self afterward so won't double-free. + let _read_event = unsafe { ptr::read(&self.read_event) }; + let _write_event = unsafe { ptr::read(&self.write_event) }; mem::forget(self); - handle + handle as RawHandle } } @@ -448,26 +482,34 @@ impl io::Read for &SerialPort { fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut len: DWORD = 0; + let read_event = self.read_event.take_or_create()?; + let mut overlapped: OVERLAPPED = unsafe { MaybeUninit::zeroed().assume_init() }; + overlapped.hEvent = read_event.handle(); + match unsafe { ReadFile( self.handle, buf.as_mut_ptr() as LPVOID, buf.len() as DWORD, &mut len, - ptr::null_mut(), + &mut overlapped, ) } { - 0 => Err(io::Error::last_os_error()), - _ => { - if len != 0 { - Ok(len as usize) - } else { - Err(io::Error::new( - io::ErrorKind::TimedOut, - "Operation timed out", - )) - } - } + 0 if unsafe { GetLastError() } == ERROR_IO_PENDING => {} + 0 => return Err(io::Error::last_os_error()), + _ => return Ok(len as usize), + } + + if unsafe { GetOverlappedResult(self.handle, &mut overlapped, &mut len, TRUE) } == 0 { + return Err(io::Error::last_os_error()); + } + match len { + 0 if buf.len() == 0 => Ok(0), + 0 => Err(io::Error::new( + io::ErrorKind::TimedOut, + "ReadFile() timed out (0 bytes read)", + )), + _ => Ok(len as usize), } } } diff --git a/src/sys/windows/event_cache.rs b/src/sys/windows/event_cache.rs new file mode 100644 index 00000000..5be6dbbf --- /dev/null +++ b/src/sys/windows/event_cache.rs @@ -0,0 +1,100 @@ +//! Provides a cache for event `HANDLE`s so that we can create event handles for +//! read/write lazily while still keeping them around between reads and writes. +use std::io; +use std::ptr; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use winapi::shared::minwindef::*; +use winapi::shared::ntdef::NULL; +use winapi::um::handleapi::*; +use winapi::um::synchapi::CreateEventW; +use winapi::um::winnt::HANDLE; + +/// Cache for a HANDLE to an event. +#[derive(Debug)] +pub struct EventCache { + /// A `HANDLE` to an Event, created with `CreateEventW`, or `NULL`. We use + /// `NULL` to represent a missing handle rather than `INVALID_HANDLE_VALUE` + /// because `CreateEventW` returns `NULL` rather than `INVALID_HANDLE_VALUE` + /// on failure. + handle: AtomicUsize, +} + +impl EventCache { + /// Create a new, empty cache. + pub fn new() -> Self { + EventCache { + handle: AtomicUsize::new(NULL as usize), + } + } + + /// Take out the currently contained `HANDLE` if any, or create a new `HANDLE`. + /// Returns an error only when creating a new event handle fails. + pub fn take_or_create(&self) -> io::Result { + // Fast path: there is a handle, just take it and return it. + let existing = self.handle.swap(NULL as usize, Ordering::Relaxed) as HANDLE; + if existing != NULL { + return Ok(HandleGuard { + cache: self, + handle: existing, + }); + } + + // We can use auto-reset for both read and write because we'll have a different event + // handle for every thread that's trying to read or write. + match unsafe { CreateEventW(ptr::null_mut(), FALSE, FALSE, ptr::null_mut()) } { + NULL => Err(io::Error::last_os_error()), + new_handle => Ok(HandleGuard { + cache: self, + handle: new_handle, + }), + } + } + + /// Return the given `HANDLE` to the cache or silently deallocate it. + fn return_or_deallocate(&self, handle: HANDLE) { + if self + .handle + .compare_exchange_weak( + NULL as usize, + handle as usize, + Ordering::Relaxed, + Ordering::Relaxed, + ) + .is_err() + { + // Already-stored value was not null, so just silently deallocate the returned handle. + unsafe { CloseHandle(handle) }; + } + } +} + +impl Drop for EventCache { + fn drop(&mut self) { + let handle = (*self.handle.get_mut()) as HANDLE; + unsafe { CloseHandle(handle) }; + } +} + +/// Guard for borrowing the event handle from the `EventCache`. It will return +/// the handle to the cache when dropped, or deallocate it if the cache already +/// contains a handle. +pub struct HandleGuard<'a> { + /// Event cache to return the handle to when dropped. + cache: &'a EventCache, + /// Actual handle value. + handle: HANDLE, +} + +impl<'a> HandleGuard<'a> { + /// Get the handle from this guard. + pub fn handle(&self) -> HANDLE { + self.handle + } +} + +impl<'a> Drop for HandleGuard<'a> { + fn drop(&mut self) { + self.cache.return_or_deallocate(self.handle); + } +} diff --git a/src/sys/windows/mod.rs b/src/sys/windows/mod.rs index d171163e..00567a92 100644 --- a/src/sys/windows/mod.rs +++ b/src/sys/windows/mod.rs @@ -5,3 +5,4 @@ mod com; mod dcb; mod enumerate; mod error; +mod event_cache; From 6d83eca6d5cc96dd7a18ff5d94d27191fd141579 Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 7 May 2021 08:43:54 -0400 Subject: [PATCH 15/18] Add the implementation for overlapped I/O Write. --- src/sys/windows/com.rs | 44 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/sys/windows/com.rs b/src/sys/windows/com.rs index c2db6e9e..08f4f896 100644 --- a/src/sys/windows/com.rs +++ b/src/sys/windows/com.rs @@ -480,6 +480,7 @@ impl FromRawHandle for crate::SerialPort { impl io::Read for &SerialPort { fn read(&mut self, buf: &mut [u8]) -> io::Result { + assert!(buf.len() <= DWORD::MAX as usize); let mut len: DWORD = 0; let read_event = self.read_event.take_or_create()?; @@ -495,12 +496,12 @@ impl io::Read for &SerialPort { &mut overlapped, ) } { - 0 if unsafe { GetLastError() } == ERROR_IO_PENDING => {} - 0 => return Err(io::Error::last_os_error()), + FALSE if unsafe { GetLastError() } == ERROR_IO_PENDING => {} + FALSE => return Err(io::Error::last_os_error()), _ => return Ok(len as usize), } - if unsafe { GetOverlappedResult(self.handle, &mut overlapped, &mut len, TRUE) } == 0 { + if unsafe { GetOverlappedResult(self.handle, &mut overlapped, &mut len, TRUE) } == FALSE { return Err(io::Error::last_os_error()); } match len { @@ -516,18 +517,51 @@ impl io::Read for &SerialPort { impl io::Write for &SerialPort { fn write(&mut self, buf: &[u8]) -> io::Result { + assert!(buf.len() <= DWORD::MAX as usize); let mut len: DWORD = 0; + let write_event = self.write_event.take_or_create()?; + let mut overlapped: OVERLAPPED = unsafe { MaybeUninit::zeroed().assume_init() }; + overlapped.hEvent = write_event.handle(); + match unsafe { WriteFile( self.handle, buf.as_ptr() as LPVOID, buf.len() as DWORD, &mut len, - ptr::null_mut(), + &mut overlapped, ) } { - 0 => Err(io::Error::last_os_error()), + FALSE if unsafe { GetLastError() } == ERROR_IO_PENDING => {} + FALSE => return Err(io::Error::last_os_error()), + _ => return Ok(len as usize), + } + + if unsafe { GetOverlappedResult(self.handle, &mut overlapped, &mut len, TRUE) } == FALSE { + return Err(io::Error::last_os_error()); + // // WriteFile() may fail with ERROR_SEM_TIMEOUT, which is not + // // io::ErrorKind::TimedOut prior to Rust 1.46, so create a custom + // // error with kind TimedOut to simplify subsequent error handling. + // // https://github.com/rust-lang/rust/pull/71756 + // let error = io::Error::last_os_error(); + // // TODO: wrap if clause in if_rust_version! { < 1.46 { ... }} + // if error.raw_os_error().unwrap() as DWORD == ERROR_SEM_TIMEOUT + // && error.kind() != io::ErrorKind::TimedOut + // { + // return Err(io::Error::new( + // io::ErrorKind::TimedOut, + // "WriteFile() timed out (ERROR_SEM_TIMEOUT)", + // )); + // } + // return Err(error); + } + match len { + 0 if buf.len() == 0 => Ok(0), + 0 => Err(io::Error::new( + io::ErrorKind::TimedOut, + "WriteFile() timed out (0 bytes written)", + )), _ => Ok(len as usize), } } From cfda6e0804324c3028bbc069b359a7c2c707c4ea Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 7 May 2021 09:24:55 -0400 Subject: [PATCH 16/18] Add Sync for windows SerialPort. --- src/sys/windows/com.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sys/windows/com.rs b/src/sys/windows/com.rs index 08f4f896..6b4bf73e 100644 --- a/src/sys/windows/com.rs +++ b/src/sys/windows/com.rs @@ -45,6 +45,7 @@ pub struct SerialPort { } unsafe impl Send for SerialPort {} +unsafe impl Sync for SerialPort {} impl SerialPort { /// Opens a COM port as a serial device. From ffd958bdd447726635d6f55c686ed77ee9398c33 Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Wed, 19 May 2021 10:09:24 -0400 Subject: [PATCH 17/18] Don't close NULL handles. --- src/sys/windows/event_cache.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sys/windows/event_cache.rs b/src/sys/windows/event_cache.rs index 5be6dbbf..916ad9ab 100644 --- a/src/sys/windows/event_cache.rs +++ b/src/sys/windows/event_cache.rs @@ -72,7 +72,9 @@ impl EventCache { impl Drop for EventCache { fn drop(&mut self) { let handle = (*self.handle.get_mut()) as HANDLE; - unsafe { CloseHandle(handle) }; + if handle != NULL { + unsafe { CloseHandle(handle) }; + } } } From 4d3a17fe79fa6227768860cb84e4a38da40300f9 Mon Sep 17 00:00:00 2001 From: Zachary Stewart Date: Fri, 7 May 2021 23:31:57 -0400 Subject: [PATCH 18/18] Update documentation for v5. --- Cargo.toml | 2 +- README.md | 87 +++++++++++++++++++++++++++++++++++------- src/lib.rs | 59 +++++++++++++++++++--------- src/sys/windows/com.rs | 2 +- src/windows.rs | 25 +++++++----- 5 files changed, 130 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0def6152..5a4492ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serialport" -version = "4.0.2-alpha.0" +version = "5.0.0-alpha.0" authors = ["Bryant Mairs "] categories = ["hardware-support"] edition = "2018" diff --git a/README.md b/README.md index 225cf250..ea51e473 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,11 @@ For async I/O functionality, see the [mio-serial](https://github.com/berkowski/m # Overview -The library exposes cross-platform serial port functionality through the `SerialPort` trait. This -library is structured to make this the simplest API to use to encourate cross-platform development -by default. Working with the resultant `Box` type is therefore recommended. To -expose additional platform-specific functionality use the platform-specific structs directly: -`TTYPort` for POSIX systems and `COMPort` for Windows. +The library exposes cross-platform serial port functionality through the +`SerialPort` struct. Additional platform-dependent features can be enabled by +importing platform-specific `SerialPortExt` traits. `SerialPort` implements the +standard `Read` and `Write` traits. + Serial enumeration is provided on most platforms. The implementation on Linux using `glibc` relies on `libudev`, an external dynamic library that will need to be available on the system the final @@ -41,36 +41,50 @@ let ports = serialport::available_ports().expect("No ports found!"); for p in ports { println!("{}", p.port_name); } - ``` Opening and configuring a port: ```rust -let port = serialport::new("/dev/ttyUSB0", 115_200) - .timeout(Duration::from_millis(10)) - .open().expect("Failed to open port"); +let port = SerialPort::builder() + .baud_rate(115_200) + .read_timeout(Duration::from_millis(10)) + .open("/dev/ttyUSB0") + .expect("Failed to open port"); ``` Writing to a port: ```rust +use std::io::Write; + let output = "This is a test. This is only a test.".as_bytes(); port.write(output).expect("Write failed!"); ``` -Reading from a port (default is blocking with a 0ms timeout): +Reading from a port: ```rust +use std::io::Read; + let mut serial_buf: Vec = vec![0; 32]; -port.read(serial_buf.as_mut_slice()).expect("Found no data!"); +port.read(serial_buf.as_mut_slice()).expect("Read failed"); ``` -Some platforms expose additional functionality, which is opened using the `open_native()` method: +Some platforms expose additional functionality, which is accessed by importing the platform-specific extension trait. ```rust -let port = serialport::new("/dev/ttyUSB0", 115_200) - .open_native().expect("Failed to open port"); +let port = SerialPort::builder() + .baud_rate(115_200) + .read_timeout(Duration::from_millis(10)) + .open("/dev/ttyUSB0") + .expect("Failed to open port"); + +#[cfg(windows)] +use serialport::windows::SerialPortExt; + +#[cfg(unix)] +use serialport::posix::SerialPortExt; ``` Closing a port: @@ -79,6 +93,51 @@ Closing a port: port is done when the `SerialPort` object is `Drop`ed either implicitly or explicitly using `std::mem::drop` (`std::mem::drop(port)`). +# Migrating to Version 5 + +Prior to version 5 of this library, the `SerialPort` type was a trait, and +cross-platform functionality was provided by using `Box`. +Platform-specific functionality required using the platform-specific structs, +`COMPort` and `TTYPort`. + +In version 5, these types have been unified, with a single `SerialPort` struct +as the only serial port type exposed by the library. Platform-specific +functionality is implemented through extension traits, which can be imported +when needed on a particular platform, to allow you to call extra functions on +the `SerialPort` struct. Using a struct instead of a trait means you no longer +need to `Box` `SerialPort` instances, and the extension traits should make it +easier to write cross-platform code that only occasionally needs access to +platform-specific features. + +For example, to send a break on a TTY port, in version 4 and earlier, you would +have to use the `TTYPort` struct instead of the cross-platform `dyn SerialPort`: + +```rust +use serialport::BreakDuration; + +let port = serialport::new("/dev/ttyUSB0", 9600).open_native()?; +port.send_break(BreakDuration::Short)?; +``` + +In version 5, you can now use the common `SerialPort` type everywhere, and to +gain access to the platform-specific `send_break` method, you just have to +import the platform-specific trait. + +```rust +use serialport::posix::{SerialPortExt, BreakDuration}; +use serialport::SerialPort; + +let port = SerialPort::builder().open("/dev/ttyUSB0")?; +port.send_break(BreakDuration::Short)?; +``` + +One other consequence of the switch to a having `SerialPort` as a struct rather +than a trait is that you will now need to import `std::io::Read` and +`std::io::Write` traits explicitly. Previously, the `SerialPort` trait inherited +from `Read` and `Write` so you could call read and write without importing them +whenever the `SerialPort` trait was in scope. With `SerialPort` as a struct, you +now need to explicitly import `Read` and `Write`. + # Examples There are several included examples, which help demonstrate the functionality of this library and diff --git a/src/lib.rs b/src/lib.rs index 040940f8..da4c49ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,15 +6,36 @@ //! //! # Feature Overview //! -//! The library has been organized such that there is a high-level `SerialPort` trait that provides -//! a cross-platform API for accessing serial ports. This is the preferred method of interacting -//! with ports. The `SerialPort::new().open*()` and `available_ports()` functions in the root -//! provide cross-platform functionality. +//! The library provides a single `SerialPort` type which works across the supported platforms. Some +//! platform-specific functionality is available through platform-specific extension traits provided +//! in the [`posix`] and [`windows`] modules, which can be imported when platform-specific functions are +//! needed. //! -//! For platform-specific functionaly, this crate is split into a `posix` and `windows` API with -//! corresponding `TTYPort` and `COMPort` structs (that both implement the `SerialPort` trait). -//! Using the platform-specific `SerialPort::new().open*()` functions will return the -//! platform-specific port object which allows access to platform-specific functionality. +//! To open a [`SerialPort`], create a builder with [`SerialPort::builder()`]. The [`SerialPortBuilder`] +//! can be used to customize settings such as baud rate, number of data bits, flow control, parity +//! and timeouts before opening the port. Note that most of these settings can be changed after opening +//! as well, but they are provided on the builder for convenience. +//! +//! For normal reading and writing, `SerialPort` implements the standard [`Read`][io::Read] and +//! [`Write`][io::Write] traits. +//! +//! ```no_run +//! use std::io::Read; +//! use serialport::SerialPort; +//! # fn main() -> serialport::Result<()> { +//! let mut port = SerialPort::builder().baud_rate(115200).open("/dev/ttyUSB0")?; +//! let mut buf = [0u8; 1024]; +//! let bytes_read = port.read(&mut buf[..])?; +//! println!("Read {} bytes: {:?}", bytes_read, &buf[..bytes_read]); +//! # Ok(()) +//! # } +//! ``` +//! +//! `SerialPort` instances are thread-safe, and both read and write are implemeted for both +//! `SerialPort` and `&SerialPort`. This allows you to share a single `SerialPort` instance +//! between threads without locking. This is primarily intended to allow having 1 thread for +//! reading and 1 thread for writing. It is also possible to get separate SerialPort instances +//! which have the same underlying serial device using [`try_clone`][SerialPort::try_clone()]. #![deny( missing_docs, @@ -219,7 +240,7 @@ pub struct SerialPortBuilder { } impl SerialPortBuilder { - /// Construct a new `SerialPortBuilder` with the default settings. + /// Construct a new [`SerialPortBuilder`] with the default settings. pub fn new() -> Self { Default::default() } @@ -234,7 +255,7 @@ impl SerialPortBuilder { /// Set the number of bits used to represent a character sent on the line /// - /// Default: `DataBits::Eight` + /// Default: [`DataBits::Eight`] pub fn data_bits(mut self, data_bits: DataBits) -> Self { self.data_bits = data_bits; self @@ -242,7 +263,7 @@ impl SerialPortBuilder { /// Set the type of signalling to use for controlling data transfer /// - /// Default: `FlowControl::None` + /// Default: [`FlowControl::None`] pub fn flow_control(mut self, flow_control: FlowControl) -> Self { self.flow_control = flow_control; self @@ -250,7 +271,7 @@ impl SerialPortBuilder { /// Set the type of parity to use for error checking /// - /// Default: `Parity::None` + /// Default: [`Parity::None`] pub fn parity(mut self, parity: Parity) -> Self { self.parity = parity; self @@ -258,7 +279,7 @@ impl SerialPortBuilder { /// Set the number of bits to use to signal the end of a character /// - /// Default: `StopBits::One` + /// Default: [`StopBits::One`] pub fn stop_bits(mut self, stop_bits: StopBits) -> Self { self.stop_bits = stop_bits; self @@ -267,7 +288,7 @@ impl SerialPortBuilder { /// Set the amount of time to wait to receive data before timing out. If set /// to `None`, hang indefinitely. /// - /// Default: `None` + /// Default: [`None`] pub fn read_timeout(mut self, read_timeout: Option) -> Self { self.read_timeout = read_timeout; self @@ -276,7 +297,7 @@ impl SerialPortBuilder { /// Set the amount of time to wait to write data before timing out. If set to /// `None`, hang indefinitely. /// - /// Default: `None` + /// Default: [`None`] pub fn write_timeout(mut self, write_timeout: Option) -> Self { self.write_timeout = write_timeout; self @@ -304,10 +325,9 @@ impl Default for SerialPortBuilder { } } -/// A trait for serial port devices +/// A Serial Port device. /// -/// This trait is all that's necessary to implement a new serial port driver -/// for a new platform. +/// See module-level documentation for an overview. #[derive(Debug)] pub struct SerialPort(sys::SerialPort); @@ -322,7 +342,8 @@ impl SerialPort { /// Returns the name of this port if it exists. /// /// This name may not be the canonical device name and instead be shorthand. - /// Additionally it may not exist for virtual ports. + /// Additionally it may not exist for virtual ports or ports created from a raw + /// handle or file descriptor. pub fn name(&self) -> Option<&str> { self.0.name() } diff --git a/src/sys/windows/com.rs b/src/sys/windows/com.rs index 6b4bf73e..088b439d 100644 --- a/src/sys/windows/com.rs +++ b/src/sys/windows/com.rs @@ -21,8 +21,8 @@ use winapi::um::winnt::{ }; use crate::sys::windows::dcb; -use crate::windows::{CommTimeouts, SerialPortExt}; use crate::sys::windows::event_cache::EventCache; +use crate::windows::{CommTimeouts, SerialPortExt}; use crate::{ ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPortBuilder, StopBits, diff --git a/src/windows.rs b/src/windows.rs index 343647a2..4cde5af7 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -13,20 +13,25 @@ use winapi::um::winbase::COMMTIMEOUTS; use crate::Result; -/// Represents COM Port Timeouts. Equivalent to -/// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts. -/// -/// See official documentation of the `COMMTIMEOUTS` C struct for details. +/// Represents COM Port Timeouts. Equivalent to [`COMMTIMEOUTS`]( +/// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts). +/// See official documentation of the [`COMMTIMEOUTS`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts) +/// struct in the windows API for details of how the fields affect timeout behavior. /// /// Timeouts are given as durations instead of integer milliseconds. When applied, -/// they will have at-most millisecond precision. +/// sub-millisecond times will be truncated. #[derive(Debug, Copy, Clone)] pub struct CommTimeouts { - read_interval_timeout: Duration, - read_total_timeout_multiplier: Duration, - read_total_timeout_constant: Duration, - write_total_timeout_multiplier: Duration, - write_total_timeout_constant: Duration, + #[allow(missing_docs)] + pub read_interval_timeout: Duration, + #[allow(missing_docs)] + pub read_total_timeout_multiplier: Duration, + #[allow(missing_docs)] + pub read_total_timeout_constant: Duration, + #[allow(missing_docs)] + pub write_total_timeout_multiplier: Duration, + #[allow(missing_docs)] + pub write_total_timeout_constant: Duration, } #[cfg(windows)]