1extern crate libc;
2extern crate dmx_termios as termios;
3extern crate ioctl_rs as ioctl;
4
5use std::ffi::CString;
6use std::io;
7use std::path::Path;
8use std::time::Duration;
9
10use std::os::unix::prelude::*;
11
12use self::libc::{c_int,c_uint,c_void,size_t};
13
14use ::{SerialDevice,SerialPortSettings};
15
16
17#[cfg(target_os = "linux")]
18const O_NOCTTY: c_int = 0x00000100;
19
20#[cfg(target_os = "macos")]
21const O_NOCTTY: c_int = 0x00020000;
22
23#[cfg(not(any(target_os = "linux", target_os = "macos")))]
24const O_NOCTTY: c_int = 0;
25
26#[cfg(target_os = "linux")]
28const BOTHER: c_uint = 0x1000;
29
30#[cfg(target_os = "linux")]
32const TCSETS2: c_int = 0x402c542b;
33
34
35pub struct TTYPort {
39 fd: RawFd,
40 timeout: Duration
41}
42
43impl TTYPort {
44 pub fn open(path: &Path) -> ::Result<Self> {
61 use self::libc::{O_RDWR,O_NONBLOCK,F_SETFL,EINVAL};
62
63 let cstr = match CString::new(path.as_os_str().as_bytes()) {
64 Ok(s) => s,
65 Err(_) => return Err(super::error::from_raw_os_error(EINVAL))
66 };
67
68 let fd = unsafe { libc::open(cstr.as_ptr(), O_RDWR | O_NOCTTY | O_NONBLOCK, 0) };
69 if fd < 0 {
70 return Err(super::error::last_os_error());
71 }
72
73 let mut port = TTYPort {
74 fd: fd,
75 timeout: Duration::from_millis(100)
76 };
77
78 if let Err(err) = ioctl::tiocexcl(port.fd) {
80 return Err(super::error::from_io_error(err))
81 }
82
83 if unsafe { libc::fcntl(port.fd, F_SETFL, 0) } < 0 {
85 return Err(super::error::last_os_error());
86 }
87
88 let settings = try!(port.read_settings());
90 try!(port.write_settings(&settings));
91
92 Ok(port)
93 }
94
95 fn set_pin(&mut self, pin: c_int, level: bool) -> ::Result<()> {
96 let retval = if level {
97 ioctl::tiocmbis(self.fd, pin)
98 }
99 else {
100 ioctl::tiocmbic(self.fd, pin)
101 };
102
103 match retval {
104 Ok(()) => Ok(()),
105 Err(err) => Err(super::error::from_io_error(err))
106 }
107 }
108
109 fn read_pin(&mut self, pin: c_int) -> ::Result<bool> {
110 match ioctl::tiocmget(self.fd) {
111 Ok(pins) => Ok(pins & pin != 0),
112 Err(err) => Err(super::error::from_io_error(err))
113 }
114 }
115}
116
117impl Drop for TTYPort {
118 fn drop(&mut self) {
119 #![allow(unused_must_use)]
120 ioctl::tiocnxcl(self.fd);
121
122 unsafe {
123 libc::close(self.fd);
124 }
125 }
126}
127
128impl AsRawFd for TTYPort {
129 fn as_raw_fd(&self) -> RawFd {
130 self.fd
131 }
132}
133
134impl io::Read for TTYPort {
135 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
136 try!(super::poll::wait_read_fd(self.fd, self.timeout));
137
138 let len = unsafe { libc::read(self.fd, buf.as_ptr() as *mut c_void, buf.len() as size_t) };
139
140 if len >= 0 {
141 Ok(len as usize)
142 }
143 else {
144 Err(io::Error::last_os_error())
145 }
146 }
147}
148
149impl io::Write for TTYPort {
150 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
151 try!(super::poll::wait_write_fd(self.fd, self.timeout));
152
153 let len = unsafe { libc::write(self.fd, buf.as_ptr() as *mut c_void, buf.len() as size_t) };
154
155 if len >= 0 {
156 Ok(len as usize)
157 }
158 else {
159 Err(io::Error::last_os_error())
160 }
161 }
162
163 fn flush(&mut self) -> io::Result<()> {
164 termios::tcdrain(self.fd)
165 }
166}
167
168impl SerialDevice for TTYPort {
169 type Settings = TTYSettings;
170
171 fn read_settings(&self) -> ::Result<TTYSettings> {
172 use self::termios::{CREAD,CLOCAL}; use self::termios::{ICANON,ECHO,ECHOE,ECHOK,ECHONL,ISIG,IEXTEN}; use self::termios::{OPOST}; use self::termios::{INLCR,IGNCR,ICRNL,IGNBRK}; use self::termios::{VMIN,VTIME}; let mut termios = match termios::Termios::from_fd(self.fd) {
179 Ok(t) => t,
180 Err(e) => return Err(super::error::from_io_error(e))
181 };
182
183 termios.c_cflag |= CREAD | CLOCAL;
185 termios.c_lflag &= !(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ISIG | IEXTEN);
186 termios.c_oflag &= !OPOST;
187 termios.c_iflag &= !(INLCR | IGNCR | ICRNL | IGNBRK);
188
189 termios.c_cc[VMIN] = 0;
190 termios.c_cc[VTIME] = 0;
191
192 Ok(TTYSettings::new(termios))
193 }
194
195 #[cfg(target_os = "linux")]
196 fn write_settings(&mut self, settings: &TTYSettings) -> ::Result<()> {
197 let err = unsafe { ioctl::ioctl(self.fd, TCSETS2, &settings.termios) };
198
199 if err != 0 {
200 return Err(super::error::from_raw_os_error(err));
201 }
202 Ok(())
203 }
204
205 #[cfg(not(target_os = "linux"))]
206 fn write_settings(&mut self, settings: &TTYSettings) -> ::Result<()> {
207 use self::termios::{tcsetattr, tcflush};
208 use self::termios::{TCSANOW, TCIOFLUSH};
209
210
211 if let Err(err) = tcsetattr(self.fd, TCSANOW, &settings.termios) {
213 return Err(super::error::from_io_error(err));
214 }
215
216 if let Err(err) = tcflush(self.fd, TCIOFLUSH) {
217 return Err(super::error::from_io_error(err));
218 }
219
220 Ok(())
221 }
222
223 fn timeout(&self) -> Duration {
224 self.timeout
225 }
226
227 fn set_timeout(&mut self, timeout: Duration) -> ::Result<()> {
228 self.timeout = timeout;
229 Ok(())
230 }
231
232 fn set_rts(&mut self, level: bool) -> ::Result<()> {
233 self.set_pin(ioctl::TIOCM_RTS, level)
234 }
235
236 fn set_dtr(&mut self, level: bool) -> ::Result<()> {
237 self.set_pin(ioctl::TIOCM_DTR, level)
238 }
239
240 fn read_cts(&mut self) -> ::Result<bool> {
241 self.read_pin(ioctl::TIOCM_CTS)
242 }
243
244 fn read_dsr(&mut self) -> ::Result<bool> {
245 self.read_pin(ioctl::TIOCM_DSR)
246 }
247
248 fn read_ri(&mut self) -> ::Result<bool> {
249 self.read_pin(ioctl::TIOCM_RI)
250 }
251
252 fn read_cd(&mut self) -> ::Result<bool> {
253 self.read_pin(ioctl::TIOCM_CD)
254 }
255}
256
257#[derive(Debug,Copy,Clone)]
259pub struct TTYSettings {
260 termios: termios::Termios
261}
262
263impl TTYSettings {
264 fn new(termios: termios::Termios) -> Self {
265 TTYSettings {
266 termios: termios
267 }
268 }
269}
270
271impl SerialPortSettings for TTYSettings {
272 fn baud_rate(&self) -> Option<::BaudRate> {
273 use self::termios::{cfgetospeed,cfgetispeed};
274 use self::termios::{B50,B75,B110,B134,B150,B200,B300,B600,B1200,B1800,B2400,B4800,B9600,B19200,B38400};
275 use self::termios::os::target::{B57600,B115200,B230400};
276
277 #[cfg(target_os = "linux")]
278 use self::termios::os::linux::{B460800,B500000,B576000,B921600,B1000000,B1152000,B1500000,B2000000,B2500000,B3000000,B3500000,B4000000};
279
280 #[cfg(target_os = "macos")]
281 use self::termios::os::macos::{B7200,B14400,B28800,B76800};
282
283 #[cfg(target_os = "freebsd")]
284 use self::termios::os::freebsd::{B7200,B14400,B28800,B76800,B460800,B921600};
285
286 #[cfg(target_os = "openbsd")]
287 use self::termios::os::openbsd::{B7200,B14400,B28800,B76800};
288
289 let ospeed = cfgetospeed(&self.termios);
290 let ispeed = cfgetispeed(&self.termios);
291
292 if ospeed != ispeed {
293 return None
294 }
295
296 match ospeed {
297 B50 => Some(::BaudOther(50)),
298 B75 => Some(::BaudOther(75)),
299 B110 => Some(::Baud110),
300 B134 => Some(::BaudOther(134)),
301 B150 => Some(::BaudOther(150)),
302 B200 => Some(::BaudOther(200)),
303 B300 => Some(::Baud300),
304 B600 => Some(::Baud600),
305 B1200 => Some(::Baud1200),
306 B1800 => Some(::BaudOther(1800)),
307 B2400 => Some(::Baud2400),
308 B4800 => Some(::Baud4800),
309 #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
310 B7200 => Some(::BaudOther(7200)),
311 B9600 => Some(::Baud9600),
312 #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
313 B14400 => Some(::BaudOther(14400)),
314 B19200 => Some(::Baud19200),
315 #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
316 B28800 => Some(::BaudOther(28800)),
317 B38400 => Some(::Baud38400),
318 B57600 => Some(::Baud57600),
319 #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
320 B76800 => Some(::BaudOther(76800)),
321 B115200 => Some(::Baud115200),
322 B230400 => Some(::BaudOther(230400)),
323 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
324 B460800 => Some(::BaudOther(460800)),
325 #[cfg(target_os = "linux")]
326 B500000 => Some(::BaudOther(500000)),
327 #[cfg(target_os = "linux")]
328 B576000 => Some(::BaudOther(576000)),
329 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
330 B921600 => Some(::BaudOther(921600)),
331 #[cfg(target_os = "linux")]
332 B1000000 => Some(::BaudOther(1000000)),
333 #[cfg(target_os = "linux")]
334 B1152000 => Some(::BaudOther(1152000)),
335 #[cfg(target_os = "linux")]
336 B1500000 => Some(::BaudOther(1500000)),
337 #[cfg(target_os = "linux")]
338 B2000000 => Some(::BaudOther(2000000)),
339 #[cfg(target_os = "linux")]
340 B2500000 => Some(::BaudOther(2500000)),
341 #[cfg(target_os = "linux")]
342 B3000000 => Some(::BaudOther(3000000)),
343 #[cfg(target_os = "linux")]
344 B3500000 => Some(::BaudOther(3500000)),
345 #[cfg(target_os = "linux")]
346 B4000000 => Some(::BaudOther(4000000)),
347 #[cfg(target_os = "linux")]
348 BOTHER => {
349
350 if self.termios.c_ospeed != self.termios.c_ispeed {
351 return None;
352 }
353
354 Some(::BaudOther(self.termios.c_ospeed as usize))
355 }
356
357 _ => None
358 }
359 }
360
361 fn char_size(&self) -> Option<::CharSize> {
362 use self::termios::{CSIZE,CS5,CS6,CS7,CS8};
363
364 match self.termios.c_cflag & CSIZE {
365 CS8 => Some(::Bits8),
366 CS7 => Some(::Bits7),
367 CS6 => Some(::Bits6),
368 CS5 => Some(::Bits5),
369
370 _ => None
371 }
372 }
373
374 fn parity(&self) -> Option<::Parity> {
375 use self::termios::{PARENB,PARODD};
376
377 if self.termios.c_cflag & PARENB != 0 {
378 if self.termios.c_cflag & PARODD != 0 {
379 Some(::ParityOdd)
380 }
381 else {
382 Some(::ParityEven)
383 }
384 }
385 else {
386 Some(::ParityNone)
387 }
388 }
389
390 fn stop_bits(&self) -> Option<::StopBits> {
391 use self::termios::{CSTOPB};
392
393 if self.termios.c_cflag & CSTOPB != 0 {
394 Some(::Stop2)
395 }
396 else {
397 Some(::Stop1)
398 }
399 }
400
401 fn flow_control(&self) -> Option<::FlowControl> {
402 use self::termios::{IXON,IXOFF};
403 use self::termios::os::target::{CRTSCTS};
404
405 if self.termios.c_cflag & CRTSCTS != 0 {
406 Some(::FlowHardware)
407 }
408 else if self.termios.c_iflag & (IXON | IXOFF) != 0 {
409 Some(::FlowSoftware)
410 }
411 else {
412 Some(::FlowNone)
413 }
414 }
415
416 fn set_baud_rate(&mut self, baud_rate: ::BaudRate) -> ::Result<()> {
417 use self::termios::cfsetspeed;
418 use self::termios::{B50,B75,B110,B134,B150,B200,B300,B600,B1200,B1800,B2400,B4800,B9600,B19200,B38400};
419 use self::termios::os::target::{B57600,B115200,B230400};
420
421 #[cfg(target_os = "linux")]
422 use self::termios::os::linux::{B460800,B500000,B576000,B921600,B1000000,B1152000,B1500000,B2000000,B2500000,B3000000,B3500000,B4000000};
423
424 #[cfg(target_os = "macos")]
425 use self::termios::os::macos::{B7200,B14400,B28800,B76800};
426
427 #[cfg(target_os = "freebsd")]
428 use self::termios::os::freebsd::{B7200,B14400,B28800,B76800,B460800,B921600};
429
430 #[cfg(target_os = "openbsd")]
431 use self::termios::os::openbsd::{B7200,B14400,B28800,B76800};
432
433 let baud = match baud_rate {
434 ::BaudOther(50) => B50,
435 ::BaudOther(75) => B75,
436 ::Baud110 => B110,
437 ::BaudOther(134) => B134,
438 ::BaudOther(150) => B150,
439 ::BaudOther(200) => B200,
440 ::Baud300 => B300,
441 ::Baud600 => B600,
442 ::Baud1200 => B1200,
443 ::BaudOther(1800) => B1800,
444 ::Baud2400 => B2400,
445 ::Baud4800 => B4800,
446 #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
447 ::BaudOther(7200) => B7200,
448 ::Baud9600 => B9600,
449 #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
450 ::BaudOther(14400) => B14400,
451 ::Baud19200 => B19200,
452 #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
453 ::BaudOther(28800) => B28800,
454 ::Baud38400 => B38400,
455 ::Baud57600 => B57600,
456 #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
457 ::BaudOther(76800) => B76800,
458 ::Baud115200 => B115200,
459 ::BaudOther(230400) => B230400,
460 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
461 ::BaudOther(460800) => B460800,
462 #[cfg(target_os = "linux")]
463 ::BaudOther(500000) => B500000,
464 #[cfg(target_os = "linux")]
465 ::BaudOther(576000) => B576000,
466 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
467 ::BaudOther(921600) => B921600,
468 #[cfg(target_os = "linux")]
469 ::BaudOther(1000000) => B1000000,
470 #[cfg(target_os = "linux")]
471 ::BaudOther(1152000) => B1152000,
472 #[cfg(target_os = "linux")]
473 ::BaudOther(1500000) => B1500000,
474 #[cfg(target_os = "linux")]
475 ::BaudOther(2000000) => B2000000,
476 #[cfg(target_os = "linux")]
477 ::BaudOther(2500000) => B2500000,
478 #[cfg(target_os = "linux")]
479 ::BaudOther(3000000) => B3000000,
480 #[cfg(target_os = "linux")]
481 ::BaudOther(3500000) => B3500000,
482 #[cfg(target_os = "linux")]
483 ::BaudOther(4000000) => B4000000,
484
485 ::BaudOther(rate) => {
486 #[cfg(target_os = "linux")]
490 {
491 use self::termios::os::linux::CBAUD;
492
493 self.termios.c_cflag &= !CBAUD;
494 self.termios.c_cflag |= BOTHER;
495 self.termios.c_ispeed = rate as c_uint;
496 self.termios.c_ospeed = rate as c_uint;
497
498 return Ok(());
499 }
500
501 #[cfg(not(target_os = "linux"))]
502 {
503 use self::libc::EINVAL;
504 return Err(super::error::from_raw_os_error(EINVAL));
505 }
506 }
507 };
508
509 match cfsetspeed(&mut self.termios, baud) {
510 Ok(()) => Ok(()),
511 Err(err) => Err(super::error::from_io_error(err))
512 }
513 }
514
515 fn set_char_size(&mut self, char_size: ::CharSize) {
516 use self::termios::{CSIZE,CS5,CS6,CS7,CS8};
517
518 let size = match char_size {
519 ::Bits5 => CS5,
520 ::Bits6 => CS6,
521 ::Bits7 => CS7,
522 ::Bits8 => CS8
523 };
524
525 self.termios.c_cflag &= !CSIZE;
526 self.termios.c_cflag |= size;
527 }
528
529 fn set_parity(&mut self, parity: ::Parity) {
530 use self::termios::{PARENB,PARODD,INPCK,IGNPAR};
531
532 match parity {
533 ::ParityNone => {
534 self.termios.c_cflag &= !(PARENB | PARODD);
535 self.termios.c_iflag &= !INPCK;
536 self.termios.c_iflag |= IGNPAR;
537 },
538 ::ParityOdd => {
539 self.termios.c_cflag |= PARENB | PARODD;
540 self.termios.c_iflag |= INPCK;
541 self.termios.c_iflag &= !IGNPAR;
542 },
543 ::ParityEven => {
544 self.termios.c_cflag &= !PARODD;
545 self.termios.c_cflag |= PARENB;
546 self.termios.c_iflag |= INPCK;
547 self.termios.c_iflag &= !IGNPAR;
548 }
549 };
550 }
551
552 fn set_stop_bits(&mut self, stop_bits: ::StopBits) {
553 use self::termios::{CSTOPB};
554
555 match stop_bits {
556 ::Stop1 => self.termios.c_cflag &= !CSTOPB,
557 ::Stop2 => self.termios.c_cflag |= CSTOPB
558 };
559 }
560
561 fn set_flow_control(&mut self, flow_control: ::FlowControl) {
562 use self::termios::{IXON,IXOFF};
563 use self::termios::os::target::{CRTSCTS};
564
565 match flow_control {
566 ::FlowNone => {
567 self.termios.c_iflag &= !(IXON | IXOFF);
568 self.termios.c_cflag &= !CRTSCTS;
569 },
570 ::FlowSoftware => {
571 self.termios.c_iflag |= IXON | IXOFF;
572 self.termios.c_cflag &= !CRTSCTS;
573 },
574 ::FlowHardware => {
575 self.termios.c_iflag &= !(IXON | IXOFF);
576 self.termios.c_cflag |= CRTSCTS;
577 }
578 };
579 }
580}
581
582
583#[cfg(test)]
584mod tests {
585 use std::mem;
586
587 use super::TTYSettings;
588 use ::prelude::*;
589
590 fn default_settings() -> TTYSettings {
591 TTYSettings {
592 termios: unsafe { mem::uninitialized() }
593 }
594 }
595
596 #[test]
597 fn tty_settings_sets_baud_rate() {
598 let mut settings = default_settings();
599
600 settings.set_baud_rate(::Baud600).unwrap();
601 assert_eq!(settings.baud_rate(), Some(::Baud600));
602 }
603
604 #[test]
605 fn tty_settings_overwrites_baud_rate() {
606 let mut settings = default_settings();
607
608 settings.set_baud_rate(::Baud600).unwrap();
609 settings.set_baud_rate(::Baud1200).unwrap();
610 assert_eq!(settings.baud_rate(), Some(::Baud1200));
611 }
612
613 #[test]
614 fn tty_settings_sets_nonstandard_baud_rate() {
615 let mut settings = default_settings();
616
617 settings.set_baud_rate(::BaudOther(12345)).unwrap();
618 assert_eq!(settings.baud_rate(), Some(::BaudOther(12345)));
619 }
620
621 #[test]
622 fn tty_settings_sets_char_size() {
623 let mut settings = default_settings();
624
625 settings.set_char_size(::Bits8);
626 assert_eq!(settings.char_size(), Some(::Bits8));
627 }
628
629 #[test]
630 fn tty_settings_overwrites_char_size() {
631 let mut settings = default_settings();
632
633 settings.set_char_size(::Bits8);
634 settings.set_char_size(::Bits7);
635 assert_eq!(settings.char_size(), Some(::Bits7));
636 }
637
638 #[test]
639 fn tty_settings_sets_parity_even() {
640 let mut settings = default_settings();
641
642 settings.set_parity(::ParityEven);
643 assert_eq!(settings.parity(), Some(::ParityEven));
644 }
645
646 #[test]
647 fn tty_settings_sets_parity_odd() {
648 let mut settings = default_settings();
649
650 settings.set_parity(::ParityOdd);
651 assert_eq!(settings.parity(), Some(::ParityOdd));
652 }
653
654 #[test]
655 fn tty_settings_sets_parity_none() {
656 let mut settings = default_settings();
657
658 settings.set_parity(::ParityEven);
659 settings.set_parity(::ParityNone);
660 assert_eq!(settings.parity(), Some(::ParityNone));
661 }
662
663 #[test]
664 fn tty_settings_sets_stop_bits_1() {
665 let mut settings = default_settings();
666
667 settings.set_stop_bits(::Stop2);
668 settings.set_stop_bits(::Stop1);
669 assert_eq!(settings.stop_bits(), Some(::Stop1));
670 }
671
672 #[test]
673 fn tty_settings_sets_stop_bits_2() {
674 let mut settings = default_settings();
675
676 settings.set_stop_bits(::Stop1);
677 settings.set_stop_bits(::Stop2);
678 assert_eq!(settings.stop_bits(), Some(::Stop2));
679 }
680
681 #[test]
682 fn tty_settings_sets_flow_control_software() {
683 let mut settings = default_settings();
684
685 settings.set_flow_control(::FlowSoftware);
686 assert_eq!(settings.flow_control(), Some(::FlowSoftware));
687 }
688
689 #[test]
690 fn tty_settings_sets_flow_control_hardware() {
691 let mut settings = default_settings();
692
693 settings.set_flow_control(::FlowHardware);
694 assert_eq!(settings.flow_control(), Some(::FlowHardware));
695 }
696
697 #[test]
698 fn tty_settings_sets_flow_control_none() {
699 let mut settings = default_settings();
700
701 settings.set_flow_control(::FlowHardware);
702 settings.set_flow_control(::FlowNone);
703 assert_eq!(settings.flow_control(), Some(::FlowNone));
704 }
705}