Skip to content

Commit 7c0dca8

Browse files
committed
Prolific device such as PL2303 does not working with original serialport-rs and mio-serial.
The problem comes with their baudrate fd mechanism is different compare to all others in macOS. Related issue : serialport/serialport-rs#194
1 parent 92aed9d commit 7c0dca8

File tree

5 files changed

+254
-10
lines changed

5 files changed

+254
-10
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,7 @@ mio = { version = "*"}
2323

2424
[target.'cfg(unix)'.dependencies]
2525
rustix-openpty = "0.1.1"
26-
signal-hook = "0.3.10"
26+
signal-hook = "0.3.10"
27+
28+
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
29+
nix = { version = "0.26", default-features = false, features = ["fs", "ioctl", "poll", "signal", "term"] }

examples/serial_monitor/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/serial_tty/mod.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ const DEFAULT_TTY_PATH: &str = "COM3";
2929

3030
const DEFAULT_BAUDRATE: u32 = 115200;
3131

32+
#[cfg(any(target_os = "macos", all(test, target_os = "macos")))]
33+
mod prolific_apple_patch;
34+
3235
pub mod event_loop;
3336

3437
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
@@ -159,22 +162,27 @@ pub fn new(
159162
_window_id: u64,
160163
) -> Result<SerialTty> {
161164
if let Ok(ports) = mio_serial::available_ports() {
162-
if let Some(matched) = ports.iter().find(|x| x.port_name == config.name)
165+
let stream = if let Some(matched) =
166+
ports.iter().find(|x| x.port_name == config.name)
163167
{
164168
match &matched.port_type {
165169
mio_serial::SerialPortType::UsbPort(u) => {
166-
println!(
167-
"mfn : {}",
168-
u.manufacturer.clone().unwrap_or("".to_owned())
169-
);
170+
let mfn = u.manufacturer.clone().unwrap_or("".to_owned());
171+
println!("mfn : {}", mfn);
172+
#[cfg(any(target_os = "macos", target_os = "ios",))]
173+
if mfn.contains("Prolific") {
174+
println!("Prolific Device on macOS should be handle with different ways.");
175+
prolific_apple_patch::open(config)?
176+
} else {
177+
mio_serial::SerialStream::open(&config.in_to_builder())?
178+
}
170179
},
171-
_ => {},
180+
_ => mio_serial::SerialStream::open(&config.in_to_builder())?,
172181
}
173182
} else {
174183
println!("Not Found");
175-
}
176-
177-
let stream = mio_serial::SerialStream::open(&config.in_to_builder())?;
184+
Err(Error::new(ErrorKind::InvalidData, "Unknown SerialTty Call"))?
185+
};
178186

179187
Ok(SerialTty { stream })
180188
} else {
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
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

Comments
 (0)