|
| 1 | +use mio_serial::{Error, ErrorKind, Result}; |
| 2 | +use nix::fcntl::FcntlArg::F_SETFL; |
| 3 | +use nix::fcntl::{fcntl, OFlag}; |
| 4 | +use nix::libc::{cfmakeraw, tcgetattr, tcsetattr}; |
| 5 | +use std::mem::MaybeUninit; |
| 6 | +use std::os::unix::prelude::*; |
| 7 | +use std::path::Path; |
| 8 | +use std::time::Duration; |
| 9 | + |
| 10 | +/// fork from serialport-rs |
| 11 | +mod ioctl { |
| 12 | + use nix::ioctl_none_bad; |
| 13 | + use nix::libc; // exclude ioctl_read_bad |
| 14 | + |
| 15 | + ioctl_none_bad!(tiocexcl, libc::TIOCEXCL); |
| 16 | + // ioctl_none_bad!(tiocnxcl, libc::TIOCNXCL); |
| 17 | + // ioctl_read_bad!(tiocmget, libc::TIOCMGET, libc::c_int); |
| 18 | + // ioctl_none_bad!(tiocsbrk, libc::TIOCSBRK); |
| 19 | + // ioctl_none_bad!(tioccbrk, libc::TIOCCBRK); |
| 20 | +} |
| 21 | + |
| 22 | +#[allow(dead_code)] |
| 23 | +#[derive(Debug)] |
| 24 | +struct DummyTtyPort { |
| 25 | + fd: RawFd, |
| 26 | + timeout: Duration, |
| 27 | + exclusive: bool, |
| 28 | + port_name: Option<String>, |
| 29 | + #[cfg(any(target_os = "ios", target_os = "macos"))] |
| 30 | + baud_rate: u32, |
| 31 | +} |
| 32 | + |
| 33 | +#[allow(path_statements)] |
| 34 | +const _: () = { |
| 35 | + // DummyTtyPort should be same size on SerialStream. |
| 36 | + assert!( |
| 37 | + core::mem::size_of::<mio_serial::SerialStream>() |
| 38 | + == core::mem::size_of::<DummyTtyPort>() |
| 39 | + ); |
| 40 | +}; |
| 41 | + |
| 42 | +/// fork from serialport-rs |
| 43 | +mod termios { |
| 44 | + use mio_serial::{DataBits, FlowControl, Parity, Result, StopBits}; |
| 45 | + use nix::libc; |
| 46 | + use std::os::fd::RawFd; |
| 47 | + |
| 48 | + pub(crate) type Termios = libc::termios; |
| 49 | + |
| 50 | + // #[cfg(any(target_os = "ios", target_os = "macos",))] |
| 51 | + pub(crate) fn get_termios(fd: RawFd) -> Result<Termios> { |
| 52 | + use std::mem::MaybeUninit; |
| 53 | + |
| 54 | + let mut termios = MaybeUninit::uninit(); |
| 55 | + let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) }; |
| 56 | + nix::errno::Errno::result(res)?; |
| 57 | + let mut termios = unsafe { termios.assume_init() }; |
| 58 | + termios.c_ispeed = self::libc::B9600; |
| 59 | + termios.c_ospeed = self::libc::B9600; |
| 60 | + Ok(termios) |
| 61 | + } |
| 62 | + |
| 63 | + #[cfg(any(target_os = "ios", target_os = "macos",))] |
| 64 | + #[rustfmt::skip] |
| 65 | + /// https://github.com/serialport/serialport-rs/pull/194 |
| 66 | + /// Issued for prolific + apple platform |
| 67 | + pub(crate) fn set_termios(fd: RawFd, termios: &mut libc::termios, baud_rate: u32) -> Result<()> { |
| 68 | + |
| 69 | + let mut ispeed_res = 0; |
| 70 | + let mut ospeed_res = 0; |
| 71 | + if baud_rate > 0 { |
| 72 | + unsafe { |
| 73 | + ispeed_res = libc::cfsetispeed(&mut *termios, baud_rate as libc::speed_t); |
| 74 | + ospeed_res = libc::cfsetospeed(&mut *termios, baud_rate as libc::speed_t); |
| 75 | + } |
| 76 | + } |
| 77 | + nix::errno::Errno::result(ispeed_res)?; |
| 78 | + nix::errno::Errno::result(ospeed_res)?; |
| 79 | + |
| 80 | + let res = unsafe { libc::tcsetattr(fd, libc::TCSANOW, termios) }; |
| 81 | + nix::errno::Errno::result(res)?; |
| 82 | + |
| 83 | + Ok(()) |
| 84 | + } |
| 85 | + |
| 86 | + pub(crate) fn set_parity(termios: &mut Termios, parity: Parity) { |
| 87 | + match parity { |
| 88 | + Parity::None => { |
| 89 | + termios.c_cflag &= !(libc::PARENB | libc::PARODD); |
| 90 | + termios.c_iflag &= !libc::INPCK; |
| 91 | + termios.c_iflag |= libc::IGNPAR; |
| 92 | + }, |
| 93 | + Parity::Odd => { |
| 94 | + termios.c_cflag |= libc::PARENB | libc::PARODD; |
| 95 | + termios.c_iflag |= libc::INPCK; |
| 96 | + termios.c_iflag &= !libc::IGNPAR; |
| 97 | + }, |
| 98 | + Parity::Even => { |
| 99 | + termios.c_cflag &= !libc::PARODD; |
| 100 | + termios.c_cflag |= libc::PARENB; |
| 101 | + termios.c_iflag |= libc::INPCK; |
| 102 | + termios.c_iflag &= !libc::IGNPAR; |
| 103 | + }, |
| 104 | + }; |
| 105 | + } |
| 106 | + |
| 107 | + pub(crate) fn set_flow_control( |
| 108 | + termios: &mut Termios, |
| 109 | + flow_control: FlowControl, |
| 110 | + ) { |
| 111 | + match flow_control { |
| 112 | + FlowControl::None => { |
| 113 | + termios.c_iflag &= !(libc::IXON | libc::IXOFF); |
| 114 | + termios.c_cflag &= !libc::CRTSCTS; |
| 115 | + }, |
| 116 | + FlowControl::Software => { |
| 117 | + termios.c_iflag |= libc::IXON | libc::IXOFF; |
| 118 | + termios.c_cflag &= !libc::CRTSCTS; |
| 119 | + }, |
| 120 | + FlowControl::Hardware => { |
| 121 | + termios.c_iflag &= !(libc::IXON | libc::IXOFF); |
| 122 | + termios.c_cflag |= libc::CRTSCTS; |
| 123 | + }, |
| 124 | + }; |
| 125 | + } |
| 126 | + |
| 127 | + pub(crate) fn set_data_bits(termios: &mut Termios, data_bits: DataBits) { |
| 128 | + let size = match data_bits { |
| 129 | + DataBits::Five => libc::CS5, |
| 130 | + DataBits::Six => libc::CS6, |
| 131 | + DataBits::Seven => libc::CS7, |
| 132 | + DataBits::Eight => libc::CS8, |
| 133 | + }; |
| 134 | + |
| 135 | + termios.c_cflag &= !libc::CSIZE; |
| 136 | + termios.c_cflag |= size; |
| 137 | + } |
| 138 | + |
| 139 | + pub(crate) fn set_stop_bits(termios: &mut Termios, stop_bits: StopBits) { |
| 140 | + match stop_bits { |
| 141 | + StopBits::One => termios.c_cflag &= !libc::CSTOPB, |
| 142 | + StopBits::Two => termios.c_cflag |= libc::CSTOPB, |
| 143 | + }; |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +/// fork from serialport-rs |
| 148 | +/// hotfix type open for prolific CDC device on apple platform. |
| 149 | +pub fn open( |
| 150 | + config: &crate::SerialTtyOptions, |
| 151 | +) -> Result<mio_serial::SerialStream> { |
| 152 | + let path = Path::new(&config.name); |
| 153 | + let fd = nix::fcntl::open( |
| 154 | + path, |
| 155 | + OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC, |
| 156 | + nix::sys::stat::Mode::empty(), |
| 157 | + ) |
| 158 | + .unwrap(); |
| 159 | + |
| 160 | + // Try to claim exclusive access to the port. This is performed even |
| 161 | + // if the port will later be set as non-exclusive, in order to respect |
| 162 | + // other applications that may have an exclusive port lock. |
| 163 | + unsafe { |
| 164 | + ioctl::tiocexcl(fd)?; |
| 165 | + } |
| 166 | + |
| 167 | + let mut termios = MaybeUninit::uninit(); |
| 168 | + nix::errno::Errno::result(unsafe { tcgetattr(fd, termios.as_mut_ptr()) }) |
| 169 | + .unwrap(); |
| 170 | + let mut termios = unsafe { termios.assume_init() }; |
| 171 | + |
| 172 | + // setup TTY for binary serial port access |
| 173 | + // Enable reading from the port and ignore all modem control lines |
| 174 | + termios.c_cflag |= libc::CREAD | libc::CLOCAL; |
| 175 | + // Enable raw mode which disables any implicit processing of the input or output data streams |
| 176 | + // This also sets no timeout period and a read will block until at least one character is |
| 177 | + // available. |
| 178 | + unsafe { cfmakeraw(&mut termios) }; |
| 179 | + |
| 180 | + // write settings to TTY |
| 181 | + unsafe { tcsetattr(fd, libc::TCSANOW, &termios) }; |
| 182 | + |
| 183 | + // Read back settings from port and confirm they were applied correctly |
| 184 | + let mut actual_termios = MaybeUninit::uninit(); |
| 185 | + unsafe { tcgetattr(fd, actual_termios.as_mut_ptr()) }; |
| 186 | + let actual_termios = unsafe { actual_termios.assume_init() }; |
| 187 | + |
| 188 | + if actual_termios.c_iflag != termios.c_iflag |
| 189 | + || actual_termios.c_oflag != termios.c_oflag |
| 190 | + || actual_termios.c_lflag != termios.c_lflag |
| 191 | + || actual_termios.c_cflag != termios.c_cflag |
| 192 | + { |
| 193 | + return Err(Error::new( |
| 194 | + ErrorKind::Unknown, |
| 195 | + "Settings did not apply correctly", |
| 196 | + )); |
| 197 | + }; |
| 198 | + |
| 199 | + #[cfg(any(target_os = "ios", target_os = "macos"))] |
| 200 | + if config.baud_rate > 0 { |
| 201 | + unsafe { libc::tcflush(fd, libc::TCIOFLUSH) }; |
| 202 | + } |
| 203 | + |
| 204 | + // clear O_NONBLOCK flag |
| 205 | + fcntl(fd, F_SETFL(nix::fcntl::OFlag::empty()))?; |
| 206 | + |
| 207 | + let mut termios = termios::get_termios(fd)?; |
| 208 | + termios::set_parity(&mut termios, config.parity); |
| 209 | + termios::set_flow_control(&mut termios, config.flow_control); |
| 210 | + termios::set_data_bits(&mut termios, config.data_bits); |
| 211 | + termios::set_stop_bits(&mut termios, config.stop_bits); |
| 212 | + |
| 213 | + termios::set_termios(fd, &mut termios, config.baud_rate)?; // Acutal patched area |
| 214 | + |
| 215 | + // rust don't allow access private member as force |
| 216 | + let dummy_struct = Box::new(DummyTtyPort { |
| 217 | + fd, |
| 218 | + timeout: config.timeout, |
| 219 | + exclusive: true, |
| 220 | + port_name: Some(config.name.clone()), |
| 221 | + #[cfg(any(target_os = "ios", target_os = "macos"))] |
| 222 | + baud_rate: config.baud_rate, |
| 223 | + }); |
| 224 | + |
| 225 | + let dummy_ref: &DummyTtyPort = Box::leak(dummy_struct); |
| 226 | + |
| 227 | + Ok(unsafe { |
| 228 | + let ret: mio_serial::SerialStream = std::mem::transmute_copy(dummy_ref); |
| 229 | + ret |
| 230 | + }) |
| 231 | +} |
0 commit comments