Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement custom baud rates on Apple with the IOSSIOSPEED ioctl. #39

Merged
merged 1 commit into from
Jun 21, 2024
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unlreased
- [add][minor] Support more custom baud rates on iOS and macOS.

# Version 0.2.25 - 2024-06-13
- [fix][patch] Fix documentation build failre on http://docs.rs.

Expand Down
58 changes: 58 additions & 0 deletions src/sys/unix/apple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::path::PathBuf;
use std::os::unix::io::RawFd;

/// A ioctl to set the baud rate of a serial port.
///
/// Value taken from random forum because there is no public documentation.
/// * https://fpc-pascal.freepascal.narkive.com/oI4b0CM2/non-standard-baud-rates-in-os-x-iossiospeed-ioctl
/// * https://github.com/dcuddeback/serial-rs/issues/37
const IOCTL_IOSSIOSPEED: u64 = 0x80045402;

/// Set the baud rate of a serial port using the IOSSIOSPEED ioctl.
///
/// The speed set this way applied to the input and the output speed.
///
/// According to some source, the speed set this way is *not* reported back in the `termios` struct by `tcgetattr`,
/// but according to other sources it *is*.
/// Even the two examples from Apple below contradict each-other on this point.
///
/// Testing seems to suggest that the value *is* reported correctly by `tcgetattr`.
/// So to avoid synchronization problems with other FDs for the same serial port we trust `tcgetattr`.
/// https://github.com/de-vri-es/serial2-rs/issues/38#issuecomment-2182531900
///
/// This is Apple, so there is no public documentation (why would you?).
/// This is the best I could find:
/// * https://opensource.apple.com/source/IOSerialFamily/IOSerialFamily-91/tests/IOSerialTestLib.c.auto.html
/// * https://developer.apple.com/library/archive/samplecode/SerialPortSample/Listings/SerialPortSample_SerialPortSample_c.html
pub fn ioctl_iossiospeed(fd: RawFd, baud_rate: libc::speed_t) -> Result<(), std::io::Error> {
unsafe {
super::check(libc::ioctl(fd, IOCTL_IOSSIOSPEED, &baud_rate))?;
Ok(())
}
}

pub fn enumerate() -> std::io::Result<Vec<PathBuf>> {
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::FileTypeExt;

let serial_ports = std::fs::read_dir("/dev")?
.filter_map(|entry| {
let entry = entry.ok()?;
let kind = entry.metadata().ok()?.file_type();
if kind.is_char_device() && is_tty_name(entry.file_name().as_bytes()) {
Some(entry.path())
} else {
None
}
})
.collect();
Ok(serial_ports)
}

fn is_tty_name(name: &[u8]) -> bool {
// Sigh, closed source doesn't have to mean undocumented.
// Anyway:
// https://stackoverflow.com/questions/14074413/serial-port-names-on-mac-os-x
// https://learn.adafruit.com/ftdi-friend/com-slash-serial-port-name
name.starts_with(b"tty.") || name.starts_with(b"cu.")
}
72 changes: 30 additions & 42 deletions src/sys/unix/bsd.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use cfg_if::cfg_if;
use std::path::PathBuf;

pub fn enumerate() -> std::io::Result<Vec<PathBuf>> {
Expand All @@ -20,48 +19,37 @@ pub fn enumerate() -> std::io::Result<Vec<PathBuf>> {
}

fn is_tty_name(name: &[u8]) -> bool {
cfg_if! {
if #[cfg(any(target_os = "ios", target_os = "macos"))] {
// Sigh, closed source doesn't have to mean undocumented.
// Anyway:
// https://stackoverflow.com/questions/14074413/serial-port-names-on-mac-os-x
// https://learn.adafruit.com/ftdi-friend/com-slash-serial-port-name
name.starts_with(b"tty.") || name.starts_with(b"cu.")

} else {
// For BSD variants, we simply report all entries in /dev that look like a TTY.
// This may contain a lot of false positives for pseudo-terminals or other fake terminals.
// If anyone can improve this for a specific BSD they love, by all means send a PR.

// https://man.dragonflybsd.org/?command=sio&section=4
// https://leaf.dragonflybsd.org/cgi/web-man?command=ucom&section=ANY
#[cfg(target_os = "dragonfly")]
const PREFIXES: [&[u8]; 4] = [b"ttyd", b"cuaa", b"ttyU", b"cuaU"];

// https://www.freebsd.org/cgi/man.cgi?query=uart&sektion=4&apropos=0&manpath=FreeBSD+13.0-RELEASE+and+Ports
// https://www.freebsd.org/cgi/man.cgi?query=ucom&sektion=4&apropos=0&manpath=FreeBSD+13.0-RELEASE+and+Ports
#[cfg(target_os = "freebsd")]
const PREFIXES: [&[u8]; 5] = [b"ttyu", b"cuau", b"cuad", b"ttyU", b"cuaU"];

// https://man.netbsd.org/com.4
// https://man.netbsd.org/ucom.4
#[cfg(target_os = "netbsd")]
const PREFIXES: [&[u8]; 4] = [b"tty", b"dty", b"ttyU", b"dtyU"];

// https://man.openbsd.org/com
// https://man.openbsd.org/ucom
#[cfg(target_os = "openbsd")]
const PREFIXES: [&[u8]; 4] = [b"tty", b"cua", b"ttyU", b"cuaU"];

for prefix in PREFIXES {
if let Some(suffix) = name.strip_prefix(prefix) {
if !suffix.is_empty() && suffix.iter().all(|c| c.is_ascii_digit()) {
return true;
}
// For BSD variants, we simply report all entries in /dev that look like a TTY.
// This may contain a lot of false positives for pseudo-terminals or other fake terminals.
// If anyone can improve this for a specific BSD they love, by all means send a PR.

// https://man.dragonflybsd.org/?command=sio&section=4
// https://leaf.dragonflybsd.org/cgi/web-man?command=ucom&section=ANY
#[cfg(target_os = "dragonfly")]
const PREFIXES: [&[u8]; 4] = [b"ttyd", b"cuaa", b"ttyU", b"cuaU"];

// https://www.freebsd.org/cgi/man.cgi?query=uart&sektion=4&apropos=0&manpath=FreeBSD+13.0-RELEASE+and+Ports
// https://www.freebsd.org/cgi/man.cgi?query=ucom&sektion=4&apropos=0&manpath=FreeBSD+13.0-RELEASE+and+Ports
#[cfg(target_os = "freebsd")]
const PREFIXES: [&[u8]; 5] = [b"ttyu", b"cuau", b"cuad", b"ttyU", b"cuaU"];

// https://man.netbsd.org/com.4
// https://man.netbsd.org/ucom.4
#[cfg(target_os = "netbsd")]
const PREFIXES: [&[u8]; 4] = [b"tty", b"dty", b"ttyU", b"dtyU"];

// https://man.openbsd.org/com
// https://man.openbsd.org/ucom
#[cfg(target_os = "openbsd")]
const PREFIXES: [&[u8]; 4] = [b"tty", b"cua", b"ttyU", b"cuaU"];

for prefix in PREFIXES {
if let Some(suffix) = name.strip_prefix(prefix) {
if !suffix.is_empty() && suffix.iter().all(|c| c.is_ascii_digit()) {
return true;
}
}

false
}
}

false
}
35 changes: 29 additions & 6 deletions src/sys/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@ pub struct SerialPort {

cfg_if! {
if #[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "ios",
target_os = "macos",
))] {
mod apple;
pub use apple::*;

} else if #[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
))] {
mod bsd;
pub use bsd::*;
Expand Down Expand Up @@ -148,8 +153,26 @@ impl SerialPort {
}

pub fn set_configuration(&mut self, settings: &Settings) -> std::io::Result<()> {
// On iOS and macOS we set the baud rate with the IOSSIOSPEED ioctl.
// But we also need to ensure the `set_on_file()` doesn't fail.
// So fill in a safe speed in the termios struct which we will override shortly after.
#[cfg(any(target_os = "ios", target_os = "macos"))]
let (settings, baud_rate) = {
let baud_rate = settings.termios.c_ospeed;
let mut settings = settings.clone();
settings.termios.c_ispeed = 9600;
settings.termios.c_ospeed = 9600;
(settings, baud_rate)
};
#[cfg(any(target_os = "ios", target_os = "macos"))]
let settings = &settings;

settings.set_on_file(&mut self.file)?;

// On iOS and macOS, override the speed with the IOSSIOSPEED ioctl.
#[cfg(any(target_os = "ios", target_os = "macos"))]
ioctl_iossiospeed(self.file.as_raw_fd(), baud_rate)?;

let applied_settings = self.get_configuration()?;
if applied_settings != *settings {
Err(other_error("failed to apply some or all settings"))
Expand Down
Loading