use cfg_if::cfg_if;
use std::io::{IoSlice, IoSliceMut};
use std::os::raw::c_int;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::time::Duration;
pub struct SerialPort {
pub file: std::fs::File,
pub read_timeout_ms: u32,
pub write_timeout_ms: u32,
}
impl std::fmt::Debug for SerialPort {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SerialPort")
.field("fd", &self.file.as_raw_fd())
.finish()
}
}
cfg_if! {
if #[cfg(any(
target_os = "ios",
target_os = "macos",
))] {
mod apple;
pub use apple::*;
} else if #[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
))] {
mod bsd;
pub use bsd::*;
} else if #[cfg(any(
target_os = "linux",
target_os = "android",
))] {
mod linux;
pub use linux::*;
} else if #[cfg(any(
target_os = "illumos",
target_os = "solaris",
))] {
mod solarish;
pub use solarish::*;
} else {
mod other;
pub use other::*;
}
}
cfg_if! {
if #[cfg(all(
any(target_os = "android", target_os = "linux"),
not(any(target_arch = "powerpc", target_arch = "powerpc64"))
))]
{
pub type RawTermios = libc::termios2;
#[derive(Clone)]
pub struct Settings {
pub(crate) termios: RawTermios,
}
impl Settings {
fn get_from_file(file: &std::fs::File) -> std::io::Result<Self> {
unsafe {
let mut termios = std::mem::zeroed();
check(libc::ioctl(file.as_raw_fd(), libc::TCGETS2 as _, &mut termios))?;
Ok(Settings { termios })
}
}
fn set_on_file(&self, file: &mut std::fs::File) -> std::io::Result<()> {
unsafe {
check(libc::ioctl(file.as_raw_fd(), libc::TCSETSW2 as _, &self.termios))?;
}
Ok(())
}
}
} else {
pub type RawTermios = libc::termios;
#[derive(Clone)]
pub struct Settings {
pub termios: RawTermios,
#[cfg(target_os = "aix")]
pub rts_cts: bool,
}
impl Settings {
fn get_from_file(file: &std::fs::File) -> std::io::Result<Self> {
unsafe {
let mut termios = std::mem::zeroed();
check(libc::tcgetattr(file.as_raw_fd(), &mut termios))?;
Ok(Settings {
termios,
#[cfg(target_os = "aix")]
rts_cts: false,
})
}
}
fn set_on_file(&self, file: &mut std::fs::File) -> std::io::Result<()> {
#[cfg(target_os = "aix")]
if self.rts_cts {
return Err(std::io::Error::other("RTS/CTS flow control is not supported on AIX"));
}
unsafe {
check(libc::tcsetattr(file.as_raw_fd(), libc::TCSADRAIN, &self.termios))?;
Ok(())
}
}
}
}
}
impl SerialPort {
pub fn open(path: &Path) -> std::io::Result<Self> {
use std::os::unix::fs::OpenOptionsExt;
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(false)
.custom_flags(libc::O_NONBLOCK | libc::O_NOCTTY)
.open(path)?;
Ok(Self::from_file(file))
}
#[cfg(any(feature = "doc", all(unix, feature = "unix")))]
pub fn pair() -> std::io::Result<(Self, Self)> {
use std::os::unix::io::FromRawFd;
unsafe {
cfg_if! {
if #[cfg(any(
target_os = "openbsd",
target_os = "dragonfly",
))] {
let pty_a = check(libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY))?;
check(libc::fcntl(pty_a, libc::F_SETFD, libc::FD_CLOEXEC))?;
} else {
let pty_a = check(libc::posix_openpt(libc::O_RDWR | libc::O_CLOEXEC | libc::O_NOCTTY))?;
}
}
check(libc::fcntl(pty_a, libc::F_SETFL, libc::O_NONBLOCK))?;
let pty_a = std::fs::File::from_raw_fd(pty_a);
let pty_a = Self::from_file(pty_a);
check(libc::grantpt(pty_a.file.as_raw_fd()))?;
check(libc::unlockpt(pty_a.file.as_raw_fd()))?;
let pty_b_name = pts_name(&pty_a)?;
let pty_b = Self::open(&pty_b_name)?;
Ok((pty_a, pty_b))
}
}
pub fn from_file(file: std::fs::File) -> Self {
Self {
file,
read_timeout_ms: super::DEFAULT_TIMEOUT_MS,
write_timeout_ms: super::DEFAULT_TIMEOUT_MS,
}
}
pub fn try_clone(&self) -> std::io::Result<Self> {
Ok(Self {
file: self.file.try_clone()?,
read_timeout_ms: self.read_timeout_ms,
write_timeout_ms: self.write_timeout_ms,
})
}
pub fn get_configuration(&self) -> std::io::Result<Settings> {
Settings::get_from_file(&self.file)
}
pub fn set_configuration(&mut self, settings: &Settings) -> std::io::Result<()> {
cfg_if! {
if #[cfg(any(target_os = "ios", target_os = "macos"))] {
let mut apply_settings = settings.clone();
apply_settings.termios.c_ispeed = 9600;
apply_settings.termios.c_ospeed = 9600;
let apply_settings = &apply_settings;
} else {
let apply_settings = settings;
}
}
apply_settings.set_on_file(&mut self.file)?;
#[cfg(any(target_os = "ios", target_os = "macos"))]
ioctl_iossiospeed(self.file.as_raw_fd(), settings.termios.c_ospeed)?;
let applied_settings = self.get_configuration()?;
if !applied_settings.matches_requested(settings) {
Err(other_error("failed to apply some or all settings"))
} else {
Ok(())
}
}
pub fn set_read_timeout(&mut self, timeout: Duration) -> std::io::Result<()> {
self.read_timeout_ms = timeout.as_millis().try_into().unwrap_or(u32::MAX);
Ok(())
}
pub fn get_read_timeout(&self) -> std::io::Result<Duration> {
Ok(Duration::from_millis(self.read_timeout_ms.into()))
}
pub fn set_write_timeout(&mut self, timeout: Duration) -> std::io::Result<()> {
self.write_timeout_ms = timeout.as_millis().try_into().unwrap_or(u32::MAX);
Ok(())
}
pub fn get_write_timeout(&self) -> std::io::Result<Duration> {
Ok(Duration::from_millis(self.write_timeout_ms.into()))
}
pub fn read(&self, buf: &mut [u8]) -> std::io::Result<usize> {
if !poll(&self.file, libc::POLLIN, self.read_timeout_ms)? {
return Err(std::io::ErrorKind::TimedOut.into());
}
unsafe {
loop {
let result = check_isize(libc::read(
self.file.as_raw_fd(),
buf.as_mut_ptr().cast(),
buf.len() as _,
));
match result {
Err(ref e) if e.raw_os_error() == Some(libc::EINTR) => continue,
x => return x,
}
}
}
}
pub fn read_vectored(&self, buf: &mut [IoSliceMut<'_>]) -> std::io::Result<usize> {
if !poll(&self.file, libc::POLLIN, self.read_timeout_ms)? {
return Err(std::io::ErrorKind::TimedOut.into());
}
unsafe {
loop {
let result = check_isize(libc::readv(
self.file.as_raw_fd(),
buf.as_mut_ptr().cast(),
buf.len() as _,
));
match result {
Err(ref e) if e.raw_os_error() == Some(libc::EINTR) => continue,
x => return x,
}
}
}
}
pub fn is_read_vectored(&self) -> bool {
true
}
pub fn write(&self, buf: &[u8]) -> std::io::Result<usize> {
if !poll(&self.file, libc::POLLOUT, self.write_timeout_ms)? {
return Err(std::io::ErrorKind::TimedOut.into());
}
unsafe {
loop {
let result = check_isize(libc::write(self.file.as_raw_fd(), buf.as_ptr().cast(), buf.len() as _));
match result {
Err(ref e) if e.raw_os_error() == Some(libc::EINTR) => continue,
x => return x,
}
}
}
}
pub fn write_vectored(&self, buf: &[IoSlice<'_>]) -> std::io::Result<usize> {
if !poll(&self.file, libc::POLLOUT, self.write_timeout_ms)? {
return Err(std::io::ErrorKind::TimedOut.into());
}
unsafe {
loop {
let result = check_isize(libc::writev(self.file.as_raw_fd(), buf.as_ptr().cast(), buf.len() as _));
match result {
Err(ref e) if e.raw_os_error() == Some(libc::EINTR) => continue,
x => return x,
}
}
}
}
pub fn is_write_vectored(&self) -> bool {
true
}
pub fn flush_output(&self) -> std::io::Result<()> {
unsafe {
check(libc::tcdrain(self.file.as_raw_fd()))?;
Ok(())
}
}
pub fn discard_buffers(&self, discard_input: bool, discard_output: bool) -> std::io::Result<()> {
unsafe {
let mut flags = 0;
if discard_input && discard_output {
flags = libc::TCIOFLUSH;
} else if discard_input {
flags = libc::TCIFLUSH;
} else if discard_output {
flags = libc::TCOFLUSH;
}
check(libc::tcflush(self.file.as_raw_fd(), flags))?;
Ok(())
}
}
pub fn set_rts(&self, state: bool) -> std::io::Result<()> {
set_pin(&self.file, libc::TIOCM_RTS, state)
}
pub fn read_cts(&self) -> std::io::Result<bool> {
read_pin(&self.file, libc::TIOCM_CTS)
}
pub fn set_dtr(&self, state: bool) -> std::io::Result<()> {
set_pin(&self.file, libc::TIOCM_DTR, state)
}
pub fn read_dsr(&self) -> std::io::Result<bool> {
read_pin(&self.file, libc::TIOCM_DSR)
}
pub fn read_ri(&self) -> std::io::Result<bool> {
read_pin(&self.file, libc::TIOCM_RI)
}
pub fn read_cd(&self) -> std::io::Result<bool> {
read_pin(&self.file, libc::TIOCM_CD)
}
pub fn set_break(&self, enable: bool) -> std::io::Result<()> {
unsafe {
if enable {
check(libc::ioctl(self.file.as_raw_fd(), libc::TIOCSBRK as _))?;
} else {
check(libc::ioctl(self.file.as_raw_fd(), libc::TIOCCBRK as _))?;
}
Ok(())
}
}
}
fn poll(file: &std::fs::File, events: std::os::raw::c_short, timeout_ms: u32) -> std::io::Result<bool> {
unsafe {
let mut poll_fd = libc::pollfd {
fd: file.as_raw_fd(),
events,
revents: 0,
};
check(libc::poll(&mut poll_fd, 1, timeout_ms as i32))?;
Ok(poll_fd.revents != 0)
}
}
fn set_pin(file: &std::fs::File, pin: c_int, state: bool) -> std::io::Result<()> {
unsafe {
if state {
check(libc::ioctl(file.as_raw_fd(), libc::TIOCMBIS as _, &pin))?;
} else {
check(libc::ioctl(file.as_raw_fd(), libc::TIOCMBIC as _, &pin))?;
}
Ok(())
}
}
fn read_pin(file: &std::fs::File, pin: c_int) -> std::io::Result<bool> {
unsafe {
let mut bits: c_int = 0;
check(libc::ioctl(file.as_raw_fd(), libc::TIOCMGET as _, &mut bits))?;
Ok(bits & pin != 0)
}
}
fn check(ret: i32) -> std::io::Result<i32> {
if ret == -1 {
Err(std::io::Error::last_os_error())
} else {
Ok(ret)
}
}
fn check_isize(ret: isize) -> std::io::Result<usize> {
if ret == -1 {
Err(std::io::Error::last_os_error())
} else {
Ok(ret as usize)
}
}
fn other_error<E>(msg: E) -> std::io::Error
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
std::io::Error::other(msg)
}
#[cfg(any(doc, feature = "doc", all(unix, feature = "unix")))]
fn pts_name(master: &SerialPort) -> std::io::Result<std::path::PathBuf> {
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
cfg_if! {
if #[cfg(any(
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
target_os = "illumos",
target_os = "solaris",
target_os = "aix",
target_os = "haiku",
))] {
static PTSNAME: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _lock = PTSNAME.lock();
unsafe {
let name = libc::ptsname(master.file.as_raw_fd());
let name = std::ffi::CStr::from_ptr(name).to_bytes().to_vec();
Ok(OsString::from_vec(name).into())
}
} else {
let mut name = Vec::with_capacity(256);
let ptr = name.spare_capacity_mut().as_mut_ptr().cast();
loop {
unsafe {
let ret = libc::ptsname_r(master.file.as_raw_fd(), ptr, name.capacity());
match ret {
0 => {
let len = std::ffi::CStr::from_ptr(ptr).to_bytes().len();
name.set_len(len);
return Ok(OsString::from_vec(name).into())
},
libc::ERANGE if name.capacity() < 2048 => {
name.reserve(name.capacity() * 2);
},
error => return Err(std::io::Error::from_raw_os_error(error)),
}
}
}
}
}
}
impl Settings {
pub fn set_raw(&mut self) {
unsafe {
#[allow(clippy::unnecessary_cast)] libc::cfmakeraw(&mut self.termios as *mut _ as *mut libc::termios);
self.termios.c_iflag |= libc::IGNBRK | libc::IGNPAR;
self.termios.c_cc[libc::VMIN] = 1;
self.termios.c_cc[libc::VTIME] = 0;
}
self.set_char_size(crate::CharSize::Bits8);
self.set_stop_bits(crate::StopBits::One);
self.set_parity(crate::Parity::None);
self.set_flow_control(crate::FlowControl::None);
}
pub fn set_baud_rate(&mut self, baud_rate: u32) -> std::io::Result<()> {
cfg_if! {
if #[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
))] {
unsafe {
check(libc::cfsetospeed(&mut self.termios, baud_rate as _))?;
check(libc::cfsetispeed(&mut self.termios, baud_rate as _))?;
Ok(())
}
} else if #[cfg(all(
any(target_os = "android", target_os = "linux"),
not(any(target_arch = "powerpc", target_arch = "powerpc64"))
))]
{
self.termios.c_cflag &= !(libc::CBAUD | libc::CIBAUD);
self.termios.c_cflag |= libc::BOTHER;
self.termios.c_cflag |= libc::BOTHER << libc::IBSHIFT;
self.termios.c_ospeed = baud_rate;
self.termios.c_ispeed = baud_rate;
Ok(())
} else {
unsafe {
let &(constant, _) = BAUD_RATES.iter()
.find(|&&(_, bits_per_second)| bits_per_second == baud_rate)
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidInput, "unsupported baud rate"))?;
check(libc::cfsetospeed(&mut self.termios, constant))?;
check(libc::cfsetispeed(&mut self.termios, constant))?;
Ok(())
}
}
}
}
pub fn get_baud_rate(&self) -> std::io::Result<u32> {
cfg_if! {
if #[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
))]
{
unsafe {
let baud_rate = libc::cfgetospeed(&self.termios);
#[allow(clippy::useless_conversion)] baud_rate.try_into()
.map_err(|_| std::io::Error::other(format!("baud rate out of range: {} > {}", baud_rate, u32::MAX)))
}
} else if #[cfg(all(
any(target_os = "android", target_os = "linux"),
not(any(target_arch = "powerpc", target_arch = "powerpc64"))
))]
{
Ok(self.termios.c_ospeed)
} else {
unsafe {
let termios_speed = libc::cfgetospeed(&self.termios as *const _ as *const _ );
let &(_, bits_per_second) = BAUD_RATES.iter()
.find(|&&(constant, _)| constant == termios_speed)
.ok_or_else(|| other_error("unrecognized baud rate"))?;
Ok(bits_per_second)
}
}
}
}
pub fn set_char_size(&mut self, char_size: crate::CharSize) {
let bits = match char_size {
crate::CharSize::Bits5 => libc::CS5,
crate::CharSize::Bits6 => libc::CS6,
crate::CharSize::Bits7 => libc::CS7,
crate::CharSize::Bits8 => libc::CS8,
};
self.termios.c_cflag = (self.termios.c_cflag & !libc::CSIZE) | bits;
}
pub fn get_char_size(&self) -> std::io::Result<crate::CharSize> {
let bits = self.termios.c_cflag & libc::CSIZE;
match bits {
libc::CS5 => Ok(crate::CharSize::Bits5),
libc::CS6 => Ok(crate::CharSize::Bits6),
libc::CS7 => Ok(crate::CharSize::Bits7),
libc::CS8 => Ok(crate::CharSize::Bits8),
_ => Err(other_error("unrecognized char size")),
}
}
pub fn set_stop_bits(&mut self, stop_bits: crate::StopBits) {
match stop_bits {
crate::StopBits::One => self.termios.c_cflag &= !libc::CSTOPB,
crate::StopBits::Two => self.termios.c_cflag |= libc::CSTOPB,
};
}
pub fn get_stop_bits(&self) -> std::io::Result<crate::StopBits> {
if self.termios.c_cflag & libc::CSTOPB == 0 {
Ok(crate::StopBits::One)
} else {
Ok(crate::StopBits::Two)
}
}
pub fn set_parity(&mut self, parity: crate::Parity) {
match parity {
crate::Parity::None => self.termios.c_cflag = self.termios.c_cflag & !libc::PARODD & !libc::PARENB,
crate::Parity::Even => self.termios.c_cflag = self.termios.c_cflag & !libc::PARODD | libc::PARENB,
crate::Parity::Odd => self.termios.c_cflag = self.termios.c_cflag | libc::PARODD | libc::PARENB,
};
}
pub fn get_parity(&self) -> std::io::Result<crate::Parity> {
if self.termios.c_cflag & libc::PARENB == 0 {
Ok(crate::Parity::None)
} else if self.termios.c_cflag & libc::PARODD != 0 {
Ok(crate::Parity::Odd)
} else {
Ok(crate::Parity::Even)
}
}
pub fn set_flow_control(&mut self, flow_control: crate::FlowControl) {
match flow_control {
crate::FlowControl::None => {
self.termios.c_iflag &= !(libc::IXON | libc::IXOFF);
#[cfg(not(target_os = "aix"))]
{
self.termios.c_cflag &= !libc::CRTSCTS;
}
},
crate::FlowControl::XonXoff => {
self.termios.c_iflag |= libc::IXON | libc::IXOFF;
#[cfg(not(target_os = "aix"))]
{
self.termios.c_cflag &= !libc::CRTSCTS;
}
},
crate::FlowControl::RtsCts => {
#[cfg(target_os = "aix")]
{
self.rts_cts = true;
}
#[cfg(not(target_os = "aix"))]
{
self.termios.c_iflag &= !(libc::IXON | libc::IXOFF);
self.termios.c_cflag |= libc::CRTSCTS;
}
},
};
}
pub fn get_flow_control(&self) -> std::io::Result<crate::FlowControl> {
let ixon = self.termios.c_iflag & libc::IXON != 0;
let ixoff = self.termios.c_iflag & libc::IXOFF != 0;
#[cfg(target_os = "aix")]
let crtscts = false;
#[cfg(not(target_os = "aix"))]
let crtscts = self.termios.c_cflag & libc::CRTSCTS != 0;
if !crtscts && !ixon && !ixoff {
Ok(crate::FlowControl::None)
} else if crtscts && !ixon && !ixoff {
Ok(crate::FlowControl::RtsCts)
} else if !crtscts && ixon && ixoff {
Ok(crate::FlowControl::XonXoff)
} else {
Err(other_error("unknown flow control configuration"))
}
}
}
impl Settings {
fn matches_requested(&self, requested: &Self) -> bool {
let a = &self.termios;
let b = &requested.termios;
cfg_if::cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
use libc::{CBAUD, CBAUDEX, IBSHIFT};
let no_baud = !(CBAUD | CBAUDEX | ((CBAUD | CBAUDEX) << IBSHIFT));
let same = true;
let same = same && (a.c_cflag & no_baud == b.c_cflag & no_baud);
let same = same && (a.c_iflag == b.c_iflag);
let same = same && (a.c_oflag == b.c_oflag);
let same = same && (a.c_lflag == b.c_lflag);
let same = same && (a.c_cc == b.c_cc);
let same = same && (a.c_line == b.c_line);
if !same {
return false;
}
} else {
let same = true;
let same = same && a.c_cflag == b.c_cflag;
let same = same && a.c_iflag == b.c_iflag;
let same = same && a.c_oflag == b.c_oflag;
let same = same && a.c_lflag == b.c_lflag;
let same = same && a.c_cc == b.c_cc;
if !same {
return false;
}
}
}
match (self.get_baud_rate(), requested.get_baud_rate()) {
(Ok(speed_actual), Ok(speed_requested)) => {
speed_actual.abs_diff(speed_requested) <= speed_requested / 40
},
(Err(_), Err(_)) => true,
_ => false,
}
}
}