use core;
use libc;
use termios;
use ioctl;
use std::ffi::CString;
use std::io;
use std::path::Path;
use std::time::Duration;
use std::os::unix::prelude::*;
use libc::{c_int, c_void, size_t};
use core::{SerialDevice, SerialPortSettings};
#[cfg(target_os = "linux")]
const O_NOCTTY: c_int = 0x00000100;
#[cfg(target_os = "macos")]
const O_NOCTTY: c_int = 0x00020000;
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
const O_NOCTTY: c_int = 0;
pub struct TTYPort {
fd: RawFd,
timeout: Duration,
}
impl TTYPort {
pub fn open(path: &Path) -> core::Result<Self> {
use libc::{O_RDWR, O_NONBLOCK, F_SETFL, EINVAL};
let cstr = match CString::new(path.as_os_str().as_bytes()) {
Ok(s) => s,
Err(_) => return Err(super::error::from_raw_os_error(EINVAL)),
};
let fd = unsafe { libc::open(cstr.as_ptr(), O_RDWR | O_NOCTTY | O_NONBLOCK, 0) };
if fd < 0 {
return Err(super::error::last_os_error());
}
let mut port = TTYPort {
fd: fd,
timeout: Duration::from_millis(100),
};
if let Err(err) = ioctl::tiocexcl(port.fd) {
return Err(super::error::from_io_error(err));
}
if unsafe { libc::fcntl(port.fd, F_SETFL, 0) } < 0 {
return Err(super::error::last_os_error());
}
let settings = try!(port.read_settings());
try!(port.write_settings(&settings));
Ok(port)
}
fn set_pin(&mut self, pin: c_int, level: bool) -> core::Result<()> {
let retval = if level {
ioctl::tiocmbis(self.fd, pin)
}
else {
ioctl::tiocmbic(self.fd, pin)
};
match retval {
Ok(()) => Ok(()),
Err(err) => Err(super::error::from_io_error(err)),
}
}
fn read_pin(&mut self, pin: c_int) -> core::Result<bool> {
match ioctl::tiocmget(self.fd) {
Ok(pins) => Ok(pins & pin != 0),
Err(err) => Err(super::error::from_io_error(err)),
}
}
}
impl Drop for TTYPort {
fn drop(&mut self) {
#![allow(unused_must_use)]
ioctl::tiocnxcl(self.fd);
unsafe {
libc::close(self.fd);
}
}
}
impl AsRawFd for TTYPort {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl io::Read for TTYPort {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
try!(super::poll::wait_read_fd(self.fd, self.timeout));
let len = unsafe {
libc::read(self.fd, buf.as_ptr() as *mut c_void, buf.len() as size_t)
};
if len >= 0 {
Ok(len as usize)
}
else {
Err(io::Error::last_os_error())
}
}
}
impl io::Write for TTYPort {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
try!(super::poll::wait_write_fd(self.fd, self.timeout));
let len = unsafe {
libc::write(self.fd, buf.as_ptr() as *mut c_void, buf.len() as size_t)
};
if len >= 0 {
Ok(len as usize)
}
else {
Err(io::Error::last_os_error())
}
}
fn flush(&mut self) -> io::Result<()> {
termios::tcdrain(self.fd)
}
}
impl SerialDevice for TTYPort {
type Settings = TTYSettings;
fn read_settings(&self) -> core::Result<TTYSettings> {
use termios::{CREAD, CLOCAL}; use termios::{ICANON, ECHO, ECHOE, ECHOK, ECHONL, ISIG, IEXTEN}; use termios::{OPOST}; use termios::{INLCR, IGNCR, ICRNL, IGNBRK}; use termios::{VMIN, VTIME}; let mut termios = match termios::Termios::from_fd(self.fd) {
Ok(t) => t,
Err(e) => return Err(super::error::from_io_error(e)),
};
termios.c_cflag |= CREAD | CLOCAL;
termios.c_lflag &= !(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ISIG | IEXTEN);
termios.c_oflag &= !OPOST;
termios.c_iflag &= !(INLCR | IGNCR | ICRNL | IGNBRK);
termios.c_cc[VMIN] = 0;
termios.c_cc[VTIME] = 0;
Ok(TTYSettings::new(termios))
}
fn write_settings(&mut self, settings: &TTYSettings) -> core::Result<()> {
use termios::{tcsetattr, tcflush};
use termios::{TCSANOW, TCIOFLUSH};
if let Err(err) = tcsetattr(self.fd, TCSANOW, &settings.termios) {
return Err(super::error::from_io_error(err));
}
if let Err(err) = tcflush(self.fd, TCIOFLUSH) {
return Err(super::error::from_io_error(err));
}
Ok(())
}
fn timeout(&self) -> Duration {
self.timeout
}
fn set_timeout(&mut self, timeout: Duration) -> core::Result<()> {
self.timeout = timeout;
Ok(())
}
fn set_rts(&mut self, level: bool) -> core::Result<()> {
self.set_pin(ioctl::TIOCM_RTS, level)
}
fn set_dtr(&mut self, level: bool) -> core::Result<()> {
self.set_pin(ioctl::TIOCM_DTR, level)
}
fn read_cts(&mut self) -> core::Result<bool> {
self.read_pin(ioctl::TIOCM_CTS)
}
fn read_dsr(&mut self) -> core::Result<bool> {
self.read_pin(ioctl::TIOCM_DSR)
}
fn read_ri(&mut self) -> core::Result<bool> {
self.read_pin(ioctl::TIOCM_RI)
}
fn read_cd(&mut self) -> core::Result<bool> {
self.read_pin(ioctl::TIOCM_CD)
}
}
#[derive(Debug,Copy,Clone)]
pub struct TTYSettings {
termios: termios::Termios,
}
impl TTYSettings {
fn new(termios: termios::Termios) -> Self {
TTYSettings { termios: termios }
}
}
impl SerialPortSettings for TTYSettings {
fn baud_rate(&self) -> Option<core::BaudRate> {
use termios::{cfgetospeed, cfgetispeed};
use termios::{B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200, B38400};
use termios::os::target::{B57600, B115200, B230400};
#[cfg(target_os = "linux")]
use termios::os::linux::{B460800, B500000, B576000, B921600, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000};
#[cfg(target_os = "macos")]
use termios::os::macos::{B7200, B14400, B28800, B76800};
#[cfg(target_os = "freebsd")]
use termios::os::freebsd::{B7200, B14400, B28800, B76800, B460800, B921600};
#[cfg(target_os = "openbsd")]
use termios::os::openbsd::{B7200, B14400, B28800, B76800};
let ospeed = cfgetospeed(&self.termios);
let ispeed = cfgetispeed(&self.termios);
if ospeed != ispeed {
return None;
}
match ospeed {
B50 => Some(core::BaudOther(50)),
B75 => Some(core::BaudOther(75)),
B110 => Some(core::Baud110),
B134 => Some(core::BaudOther(134)),
B150 => Some(core::BaudOther(150)),
B200 => Some(core::BaudOther(200)),
B300 => Some(core::Baud300),
B600 => Some(core::Baud600),
B1200 => Some(core::Baud1200),
B1800 => Some(core::BaudOther(1800)),
B2400 => Some(core::Baud2400),
B4800 => Some(core::Baud4800),
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
B7200 => Some(core::BaudOther(7200)),
B9600 => Some(core::Baud9600),
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
B14400 => Some(core::BaudOther(14400)),
B19200 => Some(core::Baud19200),
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
B28800 => Some(core::BaudOther(28800)),
B38400 => Some(core::Baud38400),
B57600 => Some(core::Baud57600),
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
B76800 => Some(core::BaudOther(76800)),
B115200 => Some(core::Baud115200),
B230400 => Some(core::BaudOther(230400)),
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
B460800 => Some(core::BaudOther(460800)),
#[cfg(target_os = "linux")]
B500000 => Some(core::BaudOther(500000)),
#[cfg(target_os = "linux")]
B576000 => Some(core::BaudOther(576000)),
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
B921600 => Some(core::BaudOther(921600)),
#[cfg(target_os = "linux")]
B1000000 => Some(core::BaudOther(1000000)),
#[cfg(target_os = "linux")]
B1152000 => Some(core::BaudOther(1152000)),
#[cfg(target_os = "linux")]
B1500000 => Some(core::BaudOther(1500000)),
#[cfg(target_os = "linux")]
B2000000 => Some(core::BaudOther(2000000)),
#[cfg(target_os = "linux")]
B2500000 => Some(core::BaudOther(2500000)),
#[cfg(target_os = "linux")]
B3000000 => Some(core::BaudOther(3000000)),
#[cfg(target_os = "linux")]
B3500000 => Some(core::BaudOther(3500000)),
#[cfg(target_os = "linux")]
B4000000 => Some(core::BaudOther(4000000)),
_ => None,
}
}
fn char_size(&self) -> Option<core::CharSize> {
use termios::{CSIZE, CS5, CS6, CS7, CS8};
match self.termios.c_cflag & CSIZE {
CS8 => Some(core::Bits8),
CS7 => Some(core::Bits7),
CS6 => Some(core::Bits6),
CS5 => Some(core::Bits5),
_ => None,
}
}
fn parity(&self) -> Option<core::Parity> {
use termios::{PARENB, PARODD};
if self.termios.c_cflag & PARENB != 0 {
if self.termios.c_cflag & PARODD != 0 {
Some(core::ParityOdd)
}
else {
Some(core::ParityEven)
}
}
else {
Some(core::ParityNone)
}
}
fn stop_bits(&self) -> Option<core::StopBits> {
use termios::CSTOPB;
if self.termios.c_cflag & CSTOPB != 0 {
Some(core::Stop2)
}
else {
Some(core::Stop1)
}
}
fn flow_control(&self) -> Option<core::FlowControl> {
use termios::{IXON, IXOFF};
use termios::os::target::CRTSCTS;
if self.termios.c_cflag & CRTSCTS != 0 {
Some(core::FlowHardware)
}
else if self.termios.c_iflag & (IXON | IXOFF) != 0 {
Some(core::FlowSoftware)
}
else {
Some(core::FlowNone)
}
}
fn set_baud_rate(&mut self, baud_rate: core::BaudRate) -> core::Result<()> {
use libc::EINVAL;
use termios::cfsetspeed;
use termios::{B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200, B38400};
use termios::os::target::{B57600, B115200, B230400};
#[cfg(target_os = "linux")]
use termios::os::linux::{B460800, B500000, B576000, B921600, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000};
#[cfg(target_os = "macos")]
use termios::os::macos::{B7200, B14400, B28800, B76800};
#[cfg(target_os = "freebsd")]
use termios::os::freebsd::{B7200, B14400, B28800, B76800, B460800, B921600};
#[cfg(target_os = "openbsd")]
use termios::os::openbsd::{B7200, B14400, B28800, B76800};
let baud = match baud_rate {
core::BaudOther(50) => B50,
core::BaudOther(75) => B75,
core::Baud110 => B110,
core::BaudOther(134) => B134,
core::BaudOther(150) => B150,
core::BaudOther(200) => B200,
core::Baud300 => B300,
core::Baud600 => B600,
core::Baud1200 => B1200,
core::BaudOther(1800) => B1800,
core::Baud2400 => B2400,
core::Baud4800 => B4800,
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
core::BaudOther(7200) => B7200,
core::Baud9600 => B9600,
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
core::BaudOther(14400) => B14400,
core::Baud19200 => B19200,
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
core::BaudOther(28800) => B28800,
core::Baud38400 => B38400,
core::Baud57600 => B57600,
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "openbsd"))]
core::BaudOther(76800) => B76800,
core::Baud115200 => B115200,
core::BaudOther(230400) => B230400,
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
core::BaudOther(460800) => B460800,
#[cfg(target_os = "linux")]
core::BaudOther(500000) => B500000,
#[cfg(target_os = "linux")]
core::BaudOther(576000) => B576000,
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
core::BaudOther(921600) => B921600,
#[cfg(target_os = "linux")]
core::BaudOther(1000000) => B1000000,
#[cfg(target_os = "linux")]
core::BaudOther(1152000) => B1152000,
#[cfg(target_os = "linux")]
core::BaudOther(1500000) => B1500000,
#[cfg(target_os = "linux")]
core::BaudOther(2000000) => B2000000,
#[cfg(target_os = "linux")]
core::BaudOther(2500000) => B2500000,
#[cfg(target_os = "linux")]
core::BaudOther(3000000) => B3000000,
#[cfg(target_os = "linux")]
core::BaudOther(3500000) => B3500000,
#[cfg(target_os = "linux")]
core::BaudOther(4000000) => B4000000,
core::BaudOther(_) => return Err(super::error::from_raw_os_error(EINVAL)),
};
match cfsetspeed(&mut self.termios, baud) {
Ok(()) => Ok(()),
Err(err) => Err(super::error::from_io_error(err)),
}
}
fn set_char_size(&mut self, char_size: core::CharSize) {
use termios::{CSIZE, CS5, CS6, CS7, CS8};
let size = match char_size {
core::Bits5 => CS5,
core::Bits6 => CS6,
core::Bits7 => CS7,
core::Bits8 => CS8,
};
self.termios.c_cflag &= !CSIZE;
self.termios.c_cflag |= size;
}
fn set_parity(&mut self, parity: core::Parity) {
use termios::{PARENB, PARODD, INPCK, IGNPAR};
match parity {
core::ParityNone => {
self.termios.c_cflag &= !(PARENB | PARODD);
self.termios.c_iflag &= !INPCK;
self.termios.c_iflag |= IGNPAR;
}
core::ParityOdd => {
self.termios.c_cflag |= PARENB | PARODD;
self.termios.c_iflag |= INPCK;
self.termios.c_iflag &= !IGNPAR;
}
core::ParityEven => {
self.termios.c_cflag &= !PARODD;
self.termios.c_cflag |= PARENB;
self.termios.c_iflag |= INPCK;
self.termios.c_iflag &= !IGNPAR;
}
};
}
fn set_stop_bits(&mut self, stop_bits: core::StopBits) {
use termios::CSTOPB;
match stop_bits {
core::Stop1 => self.termios.c_cflag &= !CSTOPB,
core::Stop2 => self.termios.c_cflag |= CSTOPB,
};
}
fn set_flow_control(&mut self, flow_control: core::FlowControl) {
use termios::{IXON, IXOFF};
use termios::os::target::CRTSCTS;
match flow_control {
core::FlowNone => {
self.termios.c_iflag &= !(IXON | IXOFF);
self.termios.c_cflag &= !CRTSCTS;
}
core::FlowSoftware => {
self.termios.c_iflag |= IXON | IXOFF;
self.termios.c_cflag &= !CRTSCTS;
}
core::FlowHardware => {
self.termios.c_iflag &= !(IXON | IXOFF);
self.termios.c_cflag |= CRTSCTS;
}
};
}
}
#[cfg(test)]
mod tests {
use core;
use std::mem;
use super::TTYSettings;
use core::prelude::*;
fn default_settings() -> TTYSettings {
TTYSettings { termios: unsafe { mem::uninitialized() } }
}
#[test]
fn tty_settings_sets_baud_rate() {
let mut settings = default_settings();
settings.set_baud_rate(core::Baud600).unwrap();
assert_eq!(settings.baud_rate(), Some(core::Baud600));
}
#[test]
fn tty_settings_overwrites_baud_rate() {
let mut settings = default_settings();
settings.set_baud_rate(core::Baud600).unwrap();
settings.set_baud_rate(core::Baud1200).unwrap();
assert_eq!(settings.baud_rate(), Some(core::Baud1200));
}
#[test]
fn tty_settings_sets_char_size() {
let mut settings = default_settings();
settings.set_char_size(core::Bits8);
assert_eq!(settings.char_size(), Some(core::Bits8));
}
#[test]
fn tty_settings_overwrites_char_size() {
let mut settings = default_settings();
settings.set_char_size(core::Bits8);
settings.set_char_size(core::Bits7);
assert_eq!(settings.char_size(), Some(core::Bits7));
}
#[test]
fn tty_settings_sets_parity_even() {
let mut settings = default_settings();
settings.set_parity(core::ParityEven);
assert_eq!(settings.parity(), Some(core::ParityEven));
}
#[test]
fn tty_settings_sets_parity_odd() {
let mut settings = default_settings();
settings.set_parity(core::ParityOdd);
assert_eq!(settings.parity(), Some(core::ParityOdd));
}
#[test]
fn tty_settings_sets_parity_none() {
let mut settings = default_settings();
settings.set_parity(core::ParityEven);
settings.set_parity(core::ParityNone);
assert_eq!(settings.parity(), Some(core::ParityNone));
}
#[test]
fn tty_settings_sets_stop_bits_1() {
let mut settings = default_settings();
settings.set_stop_bits(core::Stop2);
settings.set_stop_bits(core::Stop1);
assert_eq!(settings.stop_bits(), Some(core::Stop1));
}
#[test]
fn tty_settings_sets_stop_bits_2() {
let mut settings = default_settings();
settings.set_stop_bits(core::Stop1);
settings.set_stop_bits(core::Stop2);
assert_eq!(settings.stop_bits(), Some(core::Stop2));
}
#[test]
fn tty_settings_sets_flow_control_software() {
let mut settings = default_settings();
settings.set_flow_control(core::FlowSoftware);
assert_eq!(settings.flow_control(), Some(core::FlowSoftware));
}
#[test]
fn tty_settings_sets_flow_control_hardware() {
let mut settings = default_settings();
settings.set_flow_control(core::FlowHardware);
assert_eq!(settings.flow_control(), Some(core::FlowHardware));
}
#[test]
fn tty_settings_sets_flow_control_none() {
let mut settings = default_settings();
settings.set_flow_control(core::FlowHardware);
settings.set_flow_control(core::FlowNone);
assert_eq!(settings.flow_control(), Some(core::FlowNone));
}
}