Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ nix = { version = "0.26", default-features = false, features = ["fs", "ioctl", "
libudev = { version = "0.3.0", optional = true }
unescaper = "0.1.3"

[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
[target.'cfg(target_vendor = "apple")'.dependencies]
# TODO: Remove pinning this dependency when we are bumping our MSRV.
core-foundation = "=0.10.0"
core-foundation-sys = "0.8.4"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ demand.
- `i686-unknown-linux-musl`
- `x86_64-unknown-linux-gnu`
- `x86_64-unknown-linux-musl`
- macOS/iOS
- macOS/iOS/tvOS/watchOS/visionOS
- `aarch64-apple-darwin`
- `aarch64-apple-ios`
- `x86_64-apple-darwin`
Expand Down
4 changes: 2 additions & 2 deletions doc/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ The BSDs basically **only** have the Termios2 API, but they call it Termios. It

* https://man.openbsd.org/tty.4

## macOS and iOS
## Darwin

While macOS and iOS have the heritage of a BSD, their support is slightly different. In theory, they support arbitrary baud rates in their Termios API much like the BSDs, but in practice this doesn't work with many hardware devices, as it's dependent on driver support. Instead, Apple added the `IOSSIOSPEED` ioctl in Mac OS X 10.4, which can set the baud rate to an arbitrary value. As the oldest macOS version supported by Rust is 10.7, it's available on all Mac platforms.
While macOS, iOS, and similar Apple platforms have the heritage of a BSD, their support is slightly different. In theory, they support arbitrary baud rates in their Termios API much like the BSDs, but in practice this doesn't work with many hardware devices, as it's dependent on driver support. Instead, Apple added the `IOSSIOSPEED` ioctl in Mac OS X 10.4, which can set the baud rate to an arbitrary value. As the oldest macOS version supported by Rust is 10.7, it's available on all Mac platforms.

This API requires the port to be set into raw mode with `cfmakeraw`, and must be done after every call to `tcsetattr`, as that will reset the baud rate. Additionally, there is no way to retrieve the actual baud rate from the OS. This is therefore the clunkiest API of any platform.
24 changes: 9 additions & 15 deletions src/posix/enumerate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ cfg_if! {
}

cfg_if! {
if #[cfg(any(target_os = "ios", target_os = "macos"))] {
if #[cfg(target_vendor = "apple")] {
use core_foundation::base::CFType;
use core_foundation::base::TCFType;
use core_foundation::dictionary::CFDictionary;
Expand All @@ -26,22 +26,16 @@ cfg_if! {
}
}

#[cfg(any(
target_os = "freebsd",
target_os = "ios",
target_os = "linux",
target_os = "macos"
))]
#[cfg(any(target_os = "freebsd", target_os = "linux", target_vendor = "apple"))]
use crate::SerialPortType;
#[cfg(any(target_os = "ios", target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
use crate::UsbPortInfo;
#[cfg(any(
target_os = "android",
target_os = "ios",
all(target_os = "linux", not(target_env = "musl"), feature = "libudev"),
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_vendor = "apple",
))]
use crate::{Error, ErrorKind};
use crate::{Result, SerialPortInfo};
Expand Down Expand Up @@ -265,7 +259,7 @@ fn parse_modalias(moda: &str) -> Option<UsbPortInfo> {
})
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
fn get_parent_device_by_type(
device: io_object_t,
parent_type: *const c_char,
Expand All @@ -292,7 +286,7 @@ fn get_parent_device_by_type(
}
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
#[allow(non_upper_case_globals)]
/// Returns a specific property of the given device as an integer.
fn get_int_property(device_type: io_registry_entry_t, property: &str) -> Result<u32> {
Expand Down Expand Up @@ -321,7 +315,7 @@ fn get_int_property(device_type: io_registry_entry_t, property: &str) -> Result<
))
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
/// Returns a specific property of the given device as a string.
fn get_string_property(device_type: io_registry_entry_t, property: &str) -> Result<String> {
let cf_property = CFString::new(property);
Expand All @@ -345,7 +339,7 @@ fn get_string_property(device_type: io_registry_entry_t, property: &str) -> Resu
.ok_or(Error::new(ErrorKind::Unknown, "Failed to get string value"))
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
/// Determine the serial port type based on the service object (like that returned by
/// `IOIteratorNext`). Specific properties are extracted for USB devices.
fn port_type(service: io_object_t) -> SerialPortType {
Expand Down Expand Up @@ -381,7 +375,7 @@ fn port_type(service: io_object_t) -> SerialPortType {
}

cfg_if! {
if #[cfg(any(target_os = "ios", target_os = "macos"))] {
if #[cfg(target_vendor = "apple")] {
/// Scans the system for serial ports and returns a list of them.
/// The `SerialPortInfo` struct contains the name of the port which can be used for opening it.
pub fn available_ports() -> Result<Vec<SerialPortInfo>> {
Expand Down
16 changes: 7 additions & 9 deletions src/posix/ioctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ mod raw {
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"
target_os = "openbsd",
target_vendor = "apple",
))]
ioctl_read!(fionread, b'f', 127, libc::c_int);

Expand All @@ -37,10 +36,9 @@ mod raw {
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"
target_os = "openbsd",
target_vendor = "apple"
))]
ioctl_read!(tiocoutq, b't', 115, libc::c_int);

Expand Down Expand Up @@ -80,10 +78,10 @@ mod raw {
0x2B,
libc::termios2
);
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
const IOSSIOSPEED: libc::c_ulong = 0x80045402;
ioctl_write_ptr_bad!(
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
iossiospeed,
IOSSIOSPEED,
libc::speed_t
Expand Down Expand Up @@ -199,7 +197,7 @@ pub fn tcsets2(fd: RawFd, options: &libc::termios2) -> Result<()> {
.map_err(|e| e.into())
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
pub fn iossiospeed(fd: RawFd, baud_rate: &libc::speed_t) -> Result<()> {
unsafe { raw::iossiospeed(fd, baud_rate) }
.map(|_| ())
Expand Down
9 changes: 4 additions & 5 deletions src/posix/termios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ cfg_if! {
if #[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
all(
Expand All @@ -21,7 +19,8 @@ cfg_if! {
target_arch = "powerpc",
target_arch = "powerpc64"
)
)
),
target_vendor = "apple",
))] {
pub(crate) type Termios = libc::termios;
} else if #[cfg(any(
Expand All @@ -45,7 +44,7 @@ cfg_if! {
// calls in this lib to the IOSSIOSPEED ioctl. So whenever we get this struct, make sure to
// reset the input & output baud rates to a safe default. This is accounted for by the
// corresponding set_termios that is mac-specific and always calls IOSSIOSPEED.
#[cfg(any(target_os = "ios", target_os = "macos",))]
#[cfg(target_vendor = "apple")]
pub(crate) fn get_termios(fd: RawFd) -> Result<Termios> {
use std::mem::MaybeUninit;

Expand Down Expand Up @@ -96,7 +95,7 @@ pub(crate) fn get_termios(fd: RawFd) -> Result<Termios> {
crate::posix::ioctl::tcgets2(fd)
}

#[cfg(any(target_os = "ios", target_os = "macos",))]
#[cfg(target_vendor = "apple")]
pub(crate) fn set_termios(fd: RawFd, termios: &libc::termios, baud_rate: u32) -> Result<()> {
let res = unsafe { libc::tcsetattr(fd, libc::TCSANOW, termios) };
nix::errno::Errno::result(res)?;
Expand Down
44 changes: 22 additions & 22 deletions src/posix/tty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub struct TTYPort {
timeout: Duration,
exclusive: bool,
port_name: Option<String>,
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
baud_rate: u32,
}

Expand Down Expand Up @@ -169,7 +169,7 @@ impl TTYPort {
));
};

#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
if builder.baud_rate > 0 {
unsafe { libc::tcflush(fd.0, libc::TCIOFLUSH) };
}
Expand All @@ -183,11 +183,11 @@ impl TTYPort {
termios::set_flow_control(&mut termios, builder.flow_control);
termios::set_data_bits(&mut termios, builder.data_bits);
termios::set_stop_bits(&mut termios, builder.stop_bits);
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[cfg(not(target_vendor = "apple"))]
termios::set_baud_rate(&mut termios, builder.baud_rate)?;
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
termios::set_termios(fd.0, &termios, builder.baud_rate)?;
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[cfg(not(target_vendor = "apple"))]
termios::set_termios(fd.0, &termios)?;

// Return the final port object
Expand All @@ -196,7 +196,7 @@ impl TTYPort {
timeout: builder.timeout,
exclusive: true,
port_name: Some(builder.path.clone()),
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
baud_rate: builder.baud_rate,
};

Expand Down Expand Up @@ -318,7 +318,7 @@ impl TTYPort {
let ptty_name = nix::pty::ptsname_r(&next_pty_fd)?;

// Open the slave port
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
let baud_rate = 9600;
let fd = nix::fcntl::open(
Path::new(&ptty_name),
Expand Down Expand Up @@ -347,7 +347,7 @@ impl TTYPort {
timeout: Duration::from_millis(100),
exclusive: true,
port_name: Some(ptty_name),
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
baud_rate,
};

Expand All @@ -359,7 +359,7 @@ impl TTYPort {
timeout: Duration::from_millis(100),
exclusive: true,
port_name: None,
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
baud_rate,
};

Expand Down Expand Up @@ -396,7 +396,7 @@ impl TTYPort {
exclusive: self.exclusive,
port_name: self.port_name.clone(),
timeout: self.timeout,
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
baud_rate: self.baud_rate,
})
}
Expand Down Expand Up @@ -426,7 +426,7 @@ impl IntoRawFd for TTYPort {
}

/// Get the baud speed for a port from its file descriptor
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
fn get_termios_speed(fd: RawFd) -> u32 {
let mut termios = MaybeUninit::uninit();
let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) };
Expand Down Expand Up @@ -458,7 +458,7 @@ impl FromRawFd for TTYPort {
// It's not guaranteed that the baud rate in the `termios` struct is correct, as
// setting an arbitrary baud rate via the `iossiospeed` ioctl overrides that value,
// but extract that value anyways as a best-guess of the actual baud rate.
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
baud_rate: get_termios_speed(fd),
}
}
Expand Down Expand Up @@ -559,7 +559,7 @@ impl SerialPort for TTYPort {
///
/// On some platforms this will be the actual device baud rate, which may differ from the
/// desired baud rate.
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
fn baud_rate(&self) -> Result<u32> {
Ok(self.baud_rate)
}
Expand Down Expand Up @@ -695,7 +695,7 @@ impl SerialPort for TTYPort {
}

// Mac OS needs special logic for setting arbitrary baud rates.
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> {
ioctl::iossiospeed(self.fd, &(baud_rate as libc::speed_t))?;
self.baud_rate = baud_rate;
Expand All @@ -705,36 +705,36 @@ impl SerialPort for TTYPort {
fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> {
let mut termios = termios::get_termios(self.fd)?;
termios::set_flow_control(&mut termios, flow_control);
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
return termios::set_termios(self.fd, &termios, self.baud_rate);
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[cfg(not(target_vendor = "apple"))]
return termios::set_termios(self.fd, &termios);
}

fn set_parity(&mut self, parity: Parity) -> Result<()> {
let mut termios = termios::get_termios(self.fd)?;
termios::set_parity(&mut termios, parity);
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
return termios::set_termios(self.fd, &termios, self.baud_rate);
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[cfg(not(target_vendor = "apple"))]
return termios::set_termios(self.fd, &termios);
}

fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> {
let mut termios = termios::get_termios(self.fd)?;
termios::set_data_bits(&mut termios, data_bits);
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
return termios::set_termios(self.fd, &termios, self.baud_rate);
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[cfg(not(target_vendor = "apple"))]
return termios::set_termios(self.fd, &termios);
}

fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> {
let mut termios = termios::get_termios(self.fd)?;
termios::set_stop_bits(&mut termios, stop_bits);
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
return termios::set_termios(self.fd, &termios, self.baud_rate);
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[cfg(not(target_vendor = "apple"))]
return termios::set_termios(self.fd, &termios);
}

Expand Down
13 changes: 4 additions & 9 deletions tests/test_tty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ fn test_ttyport_timeout() {
}

#[test]
#[cfg(any(target_os = "ios", target_os = "macos"))]
#[cfg(target_vendor = "apple")]
fn test_osx_pty_pair() {
#![allow(unused_variables)]
let (mut master, slave) = TTYPort::pair().expect("Unable to create ptty pair");
Expand All @@ -116,7 +116,7 @@ fn test_osx_pty_pair() {
// On Mac this should work (in fact used to in b77768a) but now fails. It's not functionality that
// should be required, and the ptys work otherwise. So going to just disable this test instead.
#[test]
#[cfg_attr(any(target_os = "ios", target_os = "macos"), ignore)]
#[cfg_attr(target_vendor = "apple", ignore)]
fn test_ttyport_set_standard_baud() {
// `master` must be used here as Dropping it causes slave to be deleted by the OS.
// TODO: Convert this to a statement-level attribute once
Expand All @@ -133,15 +133,10 @@ fn test_ttyport_set_standard_baud() {
assert_eq!(slave.baud_rate().unwrap(), 115_200);
}

// On mac this fails because you can't set nonstandard baud rates for these virtual ports
#[test]
#[cfg_attr(
any(
target_os = "ios",
all(target_os = "linux", target_env = "musl"),
target_os = "macos"
),
ignore
any(all(target_os = "linux", target_env = "musl"), target_vendor = "apple"),
ignore = "fails on Mac because you can't set nonstandard baud rates for these virtual ports"
)]
fn test_ttyport_set_nonstandard_baud() {
// `master` must be used here as Dropping it causes slave to be deleted by the OS.
Expand Down
Loading