1 use std::mem::MaybeUninit;
2 use std::os::unix::prelude::*;
3 use std::path::Path;
4 use std::time::{Duration, Instant};
5 use std::{io, mem};
6
7 use nix::fcntl::{fcntl, OFlag};
8 use nix::{libc, unistd};
9
10 use crate::posix::ioctl::{self, SerialLines};
11 use crate::posix::termios;
12 use crate::{
13 ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPort,
14 SerialPortBuilder, StopBits,
15 };
16
17 /// Convenience method for removing exclusive access from
18 /// a fd and closing it.
close(fd: RawFd)19 fn close(fd: RawFd) {
20 // remove exclusive access
21 let _ = ioctl::tiocnxcl(fd);
22
23 // On Linux and BSD, we don't need to worry about return
24 // type as EBADF means the fd was never open or is already closed
25 //
26 // Linux and BSD guarantee that for any other error code the
27 // fd is already closed, though MacOSX does not.
28 //
29 // close() also should never be retried, and the error code
30 // in most cases in purely informative
31 let _ = unistd::close(fd);
32 }
33
34 /// A serial port implementation for POSIX TTY ports
35 ///
36 /// The port will be closed when the value is dropped. This struct
37 /// should not be instantiated directly by using `TTYPort::open()`.
38 /// Instead, use the cross-platform `serialport::new()`. Example:
39 ///
40 /// ```no_run
41 /// let mut port = serialport::new("/dev/ttyS0", 115200).open().expect("Unable to open");
42 /// # let _ = &mut port;
43 /// ```
44 ///
45 /// Note: on macOS, when connecting to a pseudo-terminal (`pty` opened via
46 /// `posix_openpt`), the `baud_rate` should be set to 0; this will be used to
47 /// explicitly _skip_ an attempt to set the baud rate of the file descriptor
48 /// that would otherwise happen via an `ioctl` command.
49 ///
50 /// ```
51 /// use serialport::{TTYPort, SerialPort};
52 ///
53 /// let (mut master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair");
54 /// # let _ = &mut master;
55 /// # let _ = &mut slave;
56 /// // ... elsewhere
57 /// let mut port = TTYPort::open(&serialport::new(slave.name().unwrap(), 0)).expect("Unable to open");
58 /// # let _ = &mut port;
59 /// ```
60 #[derive(Debug)]
61 pub struct TTYPort {
62 fd: OwnedFd,
63 timeout: Duration,
64 exclusive: bool,
65 port_name: Option<String>,
66 #[cfg(any(target_os = "ios", target_os = "macos"))]
67 baud_rate: u32,
68 }
69
70 /// Specifies the duration of a transmission break
71 #[derive(Clone, Copy, Debug)]
72 pub enum BreakDuration {
73 /// 0.25-0.5s
74 Short,
75 /// Specifies a break duration that is platform-dependent
76 Arbitrary(std::num::NonZeroI32),
77 }
78
79 impl TTYPort {
80 /// Opens a TTY device as a serial port.
81 ///
82 /// `path` should be the path to a TTY device, e.g., `/dev/ttyS0`.
83 ///
84 /// Ports are opened in exclusive mode by default. If this is undesirable
85 /// behavior, use `TTYPort::set_exclusive(false)`.
86 ///
87 /// If the port settings differ from the default settings, characters received
88 /// before the new settings become active may be garbled. To remove those
89 /// from the receive buffer, call `TTYPort::clear(ClearBuffer::Input)`.
90 ///
91 /// ## Errors
92 ///
93 /// * `NoDevice` if the device could not be opened. This could indicate that
94 /// the device is already in use.
95 /// * `InvalidInput` if `path` is not a valid device name.
96 /// * `Io` for any other error while opening or initializing the device.
open(builder: &SerialPortBuilder) -> Result<TTYPort>97 pub fn open(builder: &SerialPortBuilder) -> Result<TTYPort> {
98 use nix::fcntl::FcntlArg::F_SETFL;
99 use nix::libc::{cfmakeraw, tcgetattr, tcsetattr};
100
101 let path = Path::new(&builder.path);
102 let raw_fd = nix::fcntl::open(
103 path,
104 OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC,
105 nix::sys::stat::Mode::empty(),
106 )?;
107 let fd = unsafe { OwnedFd::from_raw_fd(raw_fd) };
108
109 // Try to claim exclusive access to the port. This is performed even
110 // if the port will later be set as non-exclusive, in order to respect
111 // other applications that may have an exclusive port lock.
112 ioctl::tiocexcl(fd.as_raw_fd())?;
113
114 let mut termios = MaybeUninit::uninit();
115 nix::errno::Errno::result(unsafe { tcgetattr(fd.as_raw_fd(), termios.as_mut_ptr()) })?;
116 let mut termios = unsafe { termios.assume_init() };
117
118 // setup TTY for binary serial port access
119 // Enable reading from the port and ignore all modem control lines
120 termios.c_cflag |= libc::CREAD | libc::CLOCAL;
121 // Enable raw mode which disables any implicit processing of the input or output data streams
122 // This also sets no timeout period and a read will block until at least one character is
123 // available.
124 unsafe { cfmakeraw(&mut termios) };
125
126 // write settings to TTY
127 unsafe { tcsetattr(fd.as_raw_fd(), libc::TCSANOW, &termios) };
128
129 // Read back settings from port and confirm they were applied correctly
130 let mut actual_termios = MaybeUninit::uninit();
131 unsafe { tcgetattr(fd.as_raw_fd(), actual_termios.as_mut_ptr()) };
132 let actual_termios = unsafe { actual_termios.assume_init() };
133
134 if actual_termios.c_iflag != termios.c_iflag
135 || actual_termios.c_oflag != termios.c_oflag
136 || actual_termios.c_lflag != termios.c_lflag
137 || actual_termios.c_cflag != termios.c_cflag
138 {
139 return Err(Error::new(
140 ErrorKind::Unknown,
141 "Settings did not apply correctly",
142 ));
143 };
144
145 #[cfg(any(target_os = "ios", target_os = "macos"))]
146 if builder.baud_rate > 0 {
147 unsafe { libc::tcflush(fd.as_raw_fd(), libc::TCIOFLUSH) };
148 }
149
150 // clear O_NONBLOCK flag
151 fcntl(fd.as_raw_fd(), F_SETFL(nix::fcntl::OFlag::empty()))?;
152
153 // Configure the low-level port settings
154 let mut termios = termios::get_termios(fd.as_raw_fd())?;
155 termios::set_parity(&mut termios, builder.parity);
156 termios::set_flow_control(&mut termios, builder.flow_control);
157 termios::set_data_bits(&mut termios, builder.data_bits);
158 termios::set_stop_bits(&mut termios, builder.stop_bits);
159 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
160 termios::set_baud_rate(&mut termios, builder.baud_rate)?;
161 #[cfg(any(target_os = "ios", target_os = "macos"))]
162 termios::set_termios(fd.as_raw_fd(), &termios, builder.baud_rate)?;
163 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
164 termios::set_termios(fd.as_raw_fd(), &termios)?;
165
166 // Return the final port object
167 let mut port = TTYPort {
168 fd: fd,
169 timeout: builder.timeout,
170 exclusive: true,
171 port_name: Some(builder.path.clone()),
172 #[cfg(any(target_os = "ios", target_os = "macos"))]
173 baud_rate: builder.baud_rate,
174 };
175
176 // Ignore setting DTR for pseudo terminals (indicated by baud_rate == 0).
177 if builder.baud_rate > 0 {
178 if let Some(dtr) = builder.dtr_on_open {
179 port.write_data_terminal_ready(dtr)?;
180 }
181 }
182
183 Ok(port)
184 }
185
186 /// Returns the exclusivity of the port
187 ///
188 /// If a port is exclusive, then trying to open the same device path again
189 /// will fail.
exclusive(&self) -> bool190 pub fn exclusive(&self) -> bool {
191 self.exclusive
192 }
193
194 /// Sets the exclusivity of the port
195 ///
196 /// If a port is exclusive, then trying to open the same device path again
197 /// will fail.
198 ///
199 /// See the man pages for the tiocexcl and tiocnxcl ioctl's for more details.
200 ///
201 /// ## Errors
202 ///
203 /// * `Io` for any error while setting exclusivity for the port.
set_exclusive(&mut self, exclusive: bool) -> Result<()>204 pub fn set_exclusive(&mut self, exclusive: bool) -> Result<()> {
205 let setting_result = if exclusive {
206 ioctl::tiocexcl(self.fd.as_raw_fd())
207 } else {
208 ioctl::tiocnxcl(self.fd.as_raw_fd())
209 };
210
211 setting_result?;
212 self.exclusive = exclusive;
213 Ok(())
214 }
215
set_pin(&mut self, pin: ioctl::SerialLines, level: bool) -> Result<()>216 fn set_pin(&mut self, pin: ioctl::SerialLines, level: bool) -> Result<()> {
217 if level {
218 ioctl::tiocmbis(self.fd.as_raw_fd(), pin)
219 } else {
220 ioctl::tiocmbic(self.fd.as_raw_fd(), pin)
221 }
222 }
223
read_pin(&mut self, pin: ioctl::SerialLines) -> Result<bool>224 fn read_pin(&mut self, pin: ioctl::SerialLines) -> Result<bool> {
225 ioctl::tiocmget(self.fd.as_raw_fd()).map(|pins| pins.contains(pin))
226 }
227
228 /// Create a pair of pseudo serial terminals
229 ///
230 /// ## Returns
231 /// Two connected `TTYPort` objects: `(master, slave)`
232 ///
233 /// ## Errors
234 /// Attempting any IO or parameter settings on the slave tty after the master
235 /// tty is closed will return errors.
236 ///
237 /// On some platforms manipulating the master port will fail and only
238 /// modifying the slave port is possible.
239 ///
240 /// ## Examples
241 ///
242 /// ```
243 /// use serialport::TTYPort;
244 ///
245 /// let (mut master, mut slave) = TTYPort::pair().unwrap();
246 ///
247 /// # let _ = &mut master;
248 /// # let _ = &mut slave;
249 /// ```
pair() -> Result<(Self, Self)>250 pub fn pair() -> Result<(Self, Self)> {
251 // Open the next free pty.
252 let next_pty_fd = nix::pty::posix_openpt(nix::fcntl::OFlag::O_RDWR)?;
253
254 // Grant access to the associated slave pty
255 nix::pty::grantpt(&next_pty_fd)?;
256
257 // Unlock the slave pty
258 nix::pty::unlockpt(&next_pty_fd)?;
259
260 // Get the path of the attached slave ptty
261 #[cfg(not(any(
262 target_os = "linux",
263 target_os = "android",
264 target_os = "emscripten",
265 target_os = "fuchsia"
266 )))]
267 let ptty_name = unsafe { nix::pty::ptsname(&next_pty_fd)? };
268
269 #[cfg(any(
270 target_os = "linux",
271 target_os = "android",
272 target_os = "emscripten",
273 target_os = "fuchsia"
274 ))]
275 let ptty_name = nix::pty::ptsname_r(&next_pty_fd)?;
276
277 // Open the slave port
278 #[cfg(any(target_os = "ios", target_os = "macos"))]
279 let baud_rate = 9600;
280 let fd = nix::fcntl::open(
281 Path::new(&ptty_name),
282 OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
283 nix::sys::stat::Mode::empty(),
284 )?;
285
286 // Set the port to a raw state. Using these ports will not work without this.
287 let mut termios = MaybeUninit::uninit();
288 let res = unsafe { crate::posix::tty::libc::tcgetattr(fd, termios.as_mut_ptr()) };
289 if let Err(e) = nix::errno::Errno::result(res) {
290 close(fd);
291 return Err(e.into());
292 }
293 let mut termios = unsafe { termios.assume_init() };
294 unsafe { crate::posix::tty::libc::cfmakeraw(&mut termios) };
295 unsafe { crate::posix::tty::libc::tcsetattr(fd, libc::TCSANOW, &termios) };
296
297 fcntl(
298 fd,
299 nix::fcntl::FcntlArg::F_SETFL(nix::fcntl::OFlag::empty()),
300 )?;
301 let fd = unsafe { OwnedFd::from_raw_fd(fd) };
302
303 let slave_tty = TTYPort {
304 fd,
305 timeout: Duration::from_millis(100),
306 exclusive: true,
307 port_name: Some(ptty_name),
308 #[cfg(any(target_os = "ios", target_os = "macos"))]
309 baud_rate,
310 };
311
312 // Manually construct the master port here because the
313 // `tcgetattr()` doesn't work on Mac, Solaris, and maybe other
314 // BSDs when used on the master port.
315 let master_tty = TTYPort {
316 fd: unsafe { OwnedFd::from_raw_fd(next_pty_fd.into_raw_fd()) },
317 timeout: Duration::from_millis(100),
318 exclusive: true,
319 port_name: None,
320 #[cfg(any(target_os = "ios", target_os = "macos"))]
321 baud_rate,
322 };
323
324 Ok((master_tty, slave_tty))
325 }
326
327 /// Sends 0-valued bits over the port for a set duration
send_break(&self, duration: BreakDuration) -> Result<()>328 pub fn send_break(&self, duration: BreakDuration) -> Result<()> {
329 match duration {
330 BreakDuration::Short => nix::sys::termios::tcsendbreak(self.fd.as_fd(), 0),
331 BreakDuration::Arbitrary(n) => nix::sys::termios::tcsendbreak(self.fd.as_fd(), n.get()),
332 }
333 .map_err(|e| e.into())
334 }
335
336 /// Attempts to clone the `SerialPort`. This allow you to write and read simultaneously from the
337 /// same serial connection. Please note that if you want a real asynchronous serial port you
338 /// should look at [mio-serial](https://crates.io/crates/mio-serial) or
339 /// [tokio-serial](https://crates.io/crates/tokio-serial).
340 ///
341 /// Also, you must be very careful when changing the settings of a cloned `SerialPort` : since
342 /// the settings are cached on a per object basis, trying to modify them from two different
343 /// objects can cause some nasty behavior.
344 ///
345 /// This is the same as `SerialPort::try_clone()` but returns the concrete type instead.
346 ///
347 /// # Errors
348 ///
349 /// This function returns an error if the serial port couldn't be cloned.
try_clone_native(&self) -> Result<TTYPort>350 pub fn try_clone_native(&self) -> Result<TTYPort> {
351 let fd_cloned: i32 = fcntl(
352 self.fd.as_raw_fd(),
353 nix::fcntl::F_DUPFD_CLOEXEC(self.fd.as_raw_fd()),
354 )?;
355 Ok(TTYPort {
356 fd: unsafe { OwnedFd::from_raw_fd(fd_cloned) },
357 exclusive: self.exclusive,
358 port_name: self.port_name.clone(),
359 timeout: self.timeout,
360 #[cfg(any(target_os = "ios", target_os = "macos"))]
361 baud_rate: self.baud_rate,
362 })
363 }
364 }
365
366 impl AsRawFd for TTYPort {
as_raw_fd(&self) -> RawFd367 fn as_raw_fd(&self) -> RawFd {
368 self.fd.as_raw_fd()
369 }
370 }
371
372 impl IntoRawFd for TTYPort {
into_raw_fd(self) -> RawFd373 fn into_raw_fd(self) -> RawFd {
374 // Pull just the file descriptor out. We also prevent the destructor
375 // from being run by calling `mem::forget`. If we didn't do this, the
376 // port would be closed, which would make `into_raw_fd` unusable.
377 let fd = self.fd.as_raw_fd();
378 mem::forget(self);
379 fd
380 }
381 }
382
383 /// Get the baud speed for a port from its file descriptor
384 #[cfg(any(target_os = "ios", target_os = "macos"))]
get_termios_speed(fd: RawFd) -> u32385 fn get_termios_speed(fd: RawFd) -> u32 {
386 let mut termios = MaybeUninit::uninit();
387 let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) };
388 nix::errno::Errno::result(res).expect("Failed to get termios data");
389 let termios = unsafe { termios.assume_init() };
390 assert_eq!(termios.c_ospeed, termios.c_ispeed);
391 termios.c_ospeed as u32
392 }
393
394 impl FromRawFd for TTYPort {
from_raw_fd(fd: RawFd) -> Self395 unsafe fn from_raw_fd(fd: RawFd) -> Self {
396 TTYPort {
397 fd: unsafe { OwnedFd::from_raw_fd(fd) },
398 timeout: Duration::from_millis(100),
399 exclusive: ioctl::tiocexcl(fd).is_ok(),
400 // It is not trivial to get the file path corresponding to a file descriptor.
401 // We'll punt on it and set it to `None` here.
402 port_name: None,
403 // It's not guaranteed that the baud rate in the `termios` struct is correct, as
404 // setting an arbitrary baud rate via the `iossiospeed` ioctl overrides that value,
405 // but extract that value anyways as a best-guess of the actual baud rate.
406 #[cfg(any(target_os = "ios", target_os = "macos"))]
407 baud_rate: get_termios_speed(fd),
408 }
409 }
410 }
411
412 impl io::Read for TTYPort {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>413 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
414 if let Err(e) = super::poll::wait_read_fd(self.fd.as_fd(), self.timeout) {
415 return Err(io::Error::from(Error::from(e)));
416 }
417
418 nix::unistd::read(self.fd.as_raw_fd(), buf).map_err(|e| io::Error::from(Error::from(e)))
419 }
420 }
421
422 impl io::Write for TTYPort {
write(&mut self, buf: &[u8]) -> io::Result<usize>423 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
424 if let Err(e) = super::poll::wait_write_fd(self.fd.as_fd(), self.timeout) {
425 return Err(io::Error::from(Error::from(e)));
426 }
427
428 nix::unistd::write(self.fd.as_fd(), buf).map_err(|e| io::Error::from(Error::from(e)))
429 }
430
flush(&mut self) -> io::Result<()>431 fn flush(&mut self) -> io::Result<()> {
432 let timeout = Instant::now() + self.timeout;
433 loop {
434 return match nix::sys::termios::tcdrain(self.fd.as_fd()) {
435 Ok(_) => Ok(()),
436 Err(nix::errno::Errno::EINTR) => {
437 // Retry flushing. But only up to the ports timeout for not retrying
438 // indefinitely in case that it gets interrupted again.
439 if Instant::now() < timeout {
440 continue;
441 } else {
442 Err(io::Error::new(
443 io::ErrorKind::TimedOut,
444 "timeout for retrying flush reached",
445 ))
446 }
447 }
448 Err(_) => Err(io::Error::new(io::ErrorKind::Other, "flush failed")),
449 };
450 }
451 }
452 }
453
454 impl SerialPort for TTYPort {
name(&self) -> Option<String>455 fn name(&self) -> Option<String> {
456 self.port_name.clone()
457 }
458
459 /// Returns the port's baud rate
460 ///
461 /// On some platforms this will be the actual device baud rate, which may differ from the
462 /// desired baud rate.
463 #[cfg(any(
464 target_os = "android",
465 all(
466 target_os = "linux",
467 not(any(
468 target_env = "musl",
469 target_arch = "powerpc",
470 target_arch = "powerpc64"
471 ))
472 )
473 ))]
baud_rate(&self) -> Result<u32>474 fn baud_rate(&self) -> Result<u32> {
475 let termios2 = ioctl::tcgets2(self.fd.as_raw_fd())?;
476
477 assert!(termios2.c_ospeed == termios2.c_ispeed);
478
479 Ok(termios2.c_ospeed)
480 }
481
482 /// Returns the port's baud rate
483 ///
484 /// On some platforms this will be the actual device baud rate, which may differ from the
485 /// desired baud rate.
486 #[cfg(any(
487 target_os = "dragonfly",
488 target_os = "freebsd",
489 target_os = "netbsd",
490 target_os = "openbsd"
491 ))]
baud_rate(&self) -> Result<u32>492 fn baud_rate(&self) -> Result<u32> {
493 let termios = termios::get_termios(self.fd.as_raw_fd())?;
494
495 let ospeed = unsafe { libc::cfgetospeed(&termios) };
496 let ispeed = unsafe { libc::cfgetispeed(&termios) };
497
498 assert!(ospeed == ispeed);
499
500 Ok(ospeed as u32)
501 }
502
503 /// Returns the port's baud rate
504 ///
505 /// On some platforms this will be the actual device baud rate, which may differ from the
506 /// desired baud rate.
507 #[cfg(any(target_os = "ios", target_os = "macos"))]
baud_rate(&self) -> Result<u32>508 fn baud_rate(&self) -> Result<u32> {
509 Ok(self.baud_rate)
510 }
511
512 /// Returns the port's baud rate
513 ///
514 /// On some platforms this will be the actual device baud rate, which may differ from the
515 /// desired baud rate.
516 #[cfg(all(
517 target_os = "linux",
518 any(
519 target_env = "musl",
520 target_arch = "powerpc",
521 target_arch = "powerpc64"
522 )
523 ))]
baud_rate(&self) -> Result<u32>524 fn baud_rate(&self) -> Result<u32> {
525 use self::libc::{
526 B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000,
527 B460800, B500000, B576000, B921600,
528 };
529 use self::libc::{
530 B110, B115200, B1200, B134, B150, B1800, B19200, B200, B230400, B2400, B300, B38400,
531 B4800, B50, B57600, B600, B75, B9600,
532 };
533
534 let termios = termios::get_termios(self.fd.as_raw_fd())?;
535 let ospeed = unsafe { libc::cfgetospeed(&termios) };
536 let ispeed = unsafe { libc::cfgetispeed(&termios) };
537
538 assert!(ospeed == ispeed);
539
540 let res: u32 = match ospeed {
541 B50 => 50,
542 B75 => 75,
543 B110 => 110,
544 B134 => 134,
545 B150 => 150,
546 B200 => 200,
547 B300 => 300,
548 B600 => 600,
549 B1200 => 1200,
550 B1800 => 1800,
551 B2400 => 2400,
552 B4800 => 4800,
553 B9600 => 9600,
554 B19200 => 19_200,
555 B38400 => 38_400,
556 B57600 => 57_600,
557 B115200 => 115_200,
558 B230400 => 230_400,
559 B460800 => 460_800,
560 B500000 => 500_000,
561 B576000 => 576_000,
562 B921600 => 921_600,
563 B1000000 => 1_000_000,
564 B1152000 => 1_152_000,
565 B1500000 => 1_500_000,
566 B2000000 => 2_000_000,
567 B2500000 => 2_500_000,
568 B3000000 => 3_000_000,
569 B3500000 => 3_500_000,
570 B4000000 => 4_000_000,
571 _ => unreachable!(),
572 };
573
574 Ok(res)
575 }
576
data_bits(&self) -> Result<DataBits>577 fn data_bits(&self) -> Result<DataBits> {
578 let termios = termios::get_termios(self.fd.as_raw_fd())?;
579 match termios.c_cflag & libc::CSIZE {
580 libc::CS8 => Ok(DataBits::Eight),
581 libc::CS7 => Ok(DataBits::Seven),
582 libc::CS6 => Ok(DataBits::Six),
583 libc::CS5 => Ok(DataBits::Five),
584 _ => Err(Error::new(
585 ErrorKind::Unknown,
586 "Invalid data bits setting encountered",
587 )),
588 }
589 }
590
flow_control(&self) -> Result<FlowControl>591 fn flow_control(&self) -> Result<FlowControl> {
592 let termios = termios::get_termios(self.fd.as_raw_fd())?;
593 if termios.c_cflag & libc::CRTSCTS == libc::CRTSCTS {
594 Ok(FlowControl::Hardware)
595 } else if termios.c_iflag & (libc::IXON | libc::IXOFF) == (libc::IXON | libc::IXOFF) {
596 Ok(FlowControl::Software)
597 } else {
598 Ok(FlowControl::None)
599 }
600 }
601
parity(&self) -> Result<Parity>602 fn parity(&self) -> Result<Parity> {
603 let termios = termios::get_termios(self.fd.as_raw_fd())?;
604 if termios.c_cflag & libc::PARENB == libc::PARENB {
605 if termios.c_cflag & libc::PARODD == libc::PARODD {
606 Ok(Parity::Odd)
607 } else {
608 Ok(Parity::Even)
609 }
610 } else {
611 Ok(Parity::None)
612 }
613 }
614
stop_bits(&self) -> Result<StopBits>615 fn stop_bits(&self) -> Result<StopBits> {
616 let termios = termios::get_termios(self.fd.as_raw_fd())?;
617 if termios.c_cflag & libc::CSTOPB == libc::CSTOPB {
618 Ok(StopBits::Two)
619 } else {
620 Ok(StopBits::One)
621 }
622 }
623
timeout(&self) -> Duration624 fn timeout(&self) -> Duration {
625 self.timeout
626 }
627
628 #[cfg(any(
629 target_os = "android",
630 target_os = "dragonfly",
631 target_os = "freebsd",
632 target_os = "netbsd",
633 target_os = "openbsd",
634 target_os = "linux"
635 ))]
set_baud_rate(&mut self, baud_rate: u32) -> Result<()>636 fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> {
637 let mut termios = termios::get_termios(self.fd.as_raw_fd())?;
638 termios::set_baud_rate(&mut termios, baud_rate)?;
639 termios::set_termios(self.fd.as_raw_fd(), &termios)
640 }
641
642 // Mac OS needs special logic for setting arbitrary baud rates.
643 #[cfg(any(target_os = "ios", target_os = "macos"))]
set_baud_rate(&mut self, baud_rate: u32) -> Result<()>644 fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> {
645 ioctl::iossiospeed(self.fd.as_raw_fd(), &(baud_rate as libc::speed_t))?;
646 self.baud_rate = baud_rate;
647 Ok(())
648 }
649
set_flow_control(&mut self, flow_control: FlowControl) -> Result<()>650 fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> {
651 let mut termios = termios::get_termios(self.fd.as_raw_fd())?;
652 termios::set_flow_control(&mut termios, flow_control);
653 #[cfg(any(target_os = "ios", target_os = "macos"))]
654 return termios::set_termios(self.fd.as_raw_fd(), &termios, self.baud_rate);
655 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
656 return termios::set_termios(self.fd.as_raw_fd(), &termios);
657 }
658
set_parity(&mut self, parity: Parity) -> Result<()>659 fn set_parity(&mut self, parity: Parity) -> Result<()> {
660 let mut termios = termios::get_termios(self.fd.as_raw_fd())?;
661 termios::set_parity(&mut termios, parity);
662 #[cfg(any(target_os = "ios", target_os = "macos"))]
663 return termios::set_termios(self.fd.as_raw_fd(), &termios, self.baud_rate);
664 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
665 return termios::set_termios(self.fd.as_raw_fd(), &termios);
666 }
667
set_data_bits(&mut self, data_bits: DataBits) -> Result<()>668 fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> {
669 let mut termios = termios::get_termios(self.fd.as_raw_fd())?;
670 termios::set_data_bits(&mut termios, data_bits);
671 #[cfg(any(target_os = "ios", target_os = "macos"))]
672 return termios::set_termios(self.fd.as_raw_fd(), &termios, self.baud_rate);
673 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
674 return termios::set_termios(self.fd.as_raw_fd(), &termios);
675 }
676
set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()>677 fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> {
678 let mut termios = termios::get_termios(self.fd.as_raw_fd())?;
679 termios::set_stop_bits(&mut termios, stop_bits);
680 #[cfg(any(target_os = "ios", target_os = "macos"))]
681 return termios::set_termios(self.fd.as_raw_fd(), &termios, self.baud_rate);
682 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
683 return termios::set_termios(self.fd.as_raw_fd(), &termios);
684 }
685
set_timeout(&mut self, timeout: Duration) -> Result<()>686 fn set_timeout(&mut self, timeout: Duration) -> Result<()> {
687 self.timeout = timeout;
688 Ok(())
689 }
690
write_request_to_send(&mut self, level: bool) -> Result<()>691 fn write_request_to_send(&mut self, level: bool) -> Result<()> {
692 self.set_pin(SerialLines::REQUEST_TO_SEND, level)
693 }
694
write_data_terminal_ready(&mut self, level: bool) -> Result<()>695 fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> {
696 self.set_pin(SerialLines::DATA_TERMINAL_READY, level)
697 }
698
read_clear_to_send(&mut self) -> Result<bool>699 fn read_clear_to_send(&mut self) -> Result<bool> {
700 self.read_pin(SerialLines::CLEAR_TO_SEND)
701 }
702
read_data_set_ready(&mut self) -> Result<bool>703 fn read_data_set_ready(&mut self) -> Result<bool> {
704 self.read_pin(SerialLines::DATA_SET_READY)
705 }
706
read_ring_indicator(&mut self) -> Result<bool>707 fn read_ring_indicator(&mut self) -> Result<bool> {
708 self.read_pin(SerialLines::RING)
709 }
710
read_carrier_detect(&mut self) -> Result<bool>711 fn read_carrier_detect(&mut self) -> Result<bool> {
712 self.read_pin(SerialLines::DATA_CARRIER_DETECT)
713 }
714
bytes_to_read(&self) -> Result<u32>715 fn bytes_to_read(&self) -> Result<u32> {
716 ioctl::fionread(self.fd.as_raw_fd())
717 }
718
bytes_to_write(&self) -> Result<u32>719 fn bytes_to_write(&self) -> Result<u32> {
720 ioctl::tiocoutq(self.fd.as_raw_fd())
721 }
722
clear(&self, buffer_to_clear: ClearBuffer) -> Result<()>723 fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> {
724 let buffer_id = match buffer_to_clear {
725 ClearBuffer::Input => libc::TCIFLUSH,
726 ClearBuffer::Output => libc::TCOFLUSH,
727 ClearBuffer::All => libc::TCIOFLUSH,
728 };
729
730 let res = unsafe { nix::libc::tcflush(self.fd.as_raw_fd(), buffer_id) };
731
732 nix::errno::Errno::result(res)
733 .map(|_| ())
734 .map_err(|e| e.into())
735 }
736
try_clone(&self) -> Result<Box<dyn SerialPort>>737 fn try_clone(&self) -> Result<Box<dyn SerialPort>> {
738 match self.try_clone_native() {
739 Ok(p) => Ok(Box::new(p)),
740 Err(e) => Err(e),
741 }
742 }
743
set_break(&self) -> Result<()>744 fn set_break(&self) -> Result<()> {
745 ioctl::tiocsbrk(self.fd.as_raw_fd())
746 }
747
clear_break(&self) -> Result<()>748 fn clear_break(&self) -> Result<()> {
749 ioctl::tioccbrk(self.fd.as_raw_fd())
750 }
751 }
752
753 #[test]
test_ttyport_into_raw_fd()754 fn test_ttyport_into_raw_fd() {
755 // `master` must be used here as Dropping it causes slave to be deleted by the OS.
756 // TODO: Convert this to a statement-level attribute once
757 // https://github.com/rust-lang/rust/issues/15701 is on stable.
758 // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe
759 #![allow(unused_variables)]
760 let (master, slave) = TTYPort::pair().expect("Unable to create ptty pair");
761
762 // First test with the master
763 let master_fd = master.into_raw_fd();
764 let mut termios = MaybeUninit::uninit();
765 let res = unsafe { nix::libc::tcgetattr(master_fd, termios.as_mut_ptr()) };
766 if res != 0 {
767 close(master_fd);
768 panic!("tcgetattr on the master port failed");
769 }
770
771 // And then the slave
772 let slave_fd = slave.into_raw_fd();
773 let res = unsafe { nix::libc::tcgetattr(slave_fd, termios.as_mut_ptr()) };
774 if res != 0 {
775 close(slave_fd);
776 panic!("tcgetattr on the master port failed");
777 }
778 close(master_fd);
779 close(slave_fd);
780 }
781