android-usb-serial 0.1.0

Pure Rust USB serial drivers for Android (and Linux), built on nusb
Documentation
//! USB serial driver implementations.
//!
//! Each chip family implements the [`Driver`](crate::drivers::Driver) trait. Prefer
//! [`crate::open_port`], which calls [`create_driver`](crate::drivers::create_driver)
//! after [`crate::ProbeTable`] selection.

mod cdc_acm;
mod ch34x;
mod chrome_ccd;
mod cp21xx;
mod ftdi;
mod gsm_modem;
mod prolific;

pub use cdc_acm::CdcAcmDriver;
pub use ch34x::Ch34xDriver;
pub use chrome_ccd::ChromeCcdDriver;
pub use cp21xx::Cp21xxDriver;
pub use ftdi::ftdi_baud_encoding;
pub use ftdi::FtdiDriver;
pub use gsm_modem::GsmModemDriver;
pub use prolific::ProlificDriver;

use crate::config::{FlowControl, LineConfig, PurgeKind};
use crate::error::Result;
use crate::probe::DriverType;
use crate::reader::SerialReader;
use crate::rx_filter::RxFilter;
use crate::transport::{BulkIn, BulkOut, SharedTransport};

/// Default bulk OUT timeout used by drivers.
pub const WRITE_TIMEOUT_MS: u32 = 5000;

/// Modem status lines reported by chips that support them.
#[derive(Debug, Clone, Copy, Default)]
pub struct ModemStatus {
    pub cts: bool,
    pub dsr: bool,
    pub ri: bool,
    pub cd: bool,
}

/// Chip-specific USB serial protocol (control transfers + bulk endpoints).
pub trait Driver: Send {
    fn open(&mut self, transport: &SharedTransport) -> Result<()>;
    fn close(&mut self) -> Result<()>;
    fn write(&mut self, data: &[u8]) -> Result<usize>;
    fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
    fn set_line_config(&mut self, cfg: LineConfig) -> Result<()>;
    fn set_flow_control(&mut self, flow: FlowControl) -> Result<()>;
    fn set_dtr(&mut self, value: bool) -> Result<()>;
    fn set_rts(&mut self, value: bool) -> Result<()>;
    fn set_break(&mut self, enabled: bool) -> Result<()>;
    fn purge(&mut self, kind: PurgeKind) -> Result<()>;
    fn modem_status(&mut self) -> Result<ModemStatus>;
    fn bulk_in_mps(&self) -> u16;
    fn take_bulk_in(&mut self) -> Option<Box<dyn BulkIn>>;
    fn rx_filters(&self) -> Vec<Box<dyn RxFilter>> {
        Vec::new()
    }
    fn start_reader(&mut self) -> Result<SerialReader> {
        let bulk = self
            .take_bulk_in()
            .ok_or_else(|| crate::error::UsbSerialError::Io("no bulk in".into()))?;
        Ok(SerialReader::start(
            bulk,
            self.bulk_in_mps(),
            200,
            self.rx_filters(),
        ))
    }
}

/// Construct an uninitialized driver for `driver_type` / `port_index`.
pub fn create_driver(driver_type: DriverType, port_index: usize) -> Box<dyn Driver> {
    match driver_type {
        DriverType::CdcAcm => Box::new(CdcAcmDriver::new(port_index)),
        DriverType::Ftdi => Box::new(FtdiDriver::new(port_index)),
        DriverType::Cp21xx => Box::new(Cp21xxDriver::new(port_index)),
        DriverType::Ch34x => Box::new(Ch34xDriver::new(port_index)),
        DriverType::Prolific => Box::new(ProlificDriver::new(port_index)),
        DriverType::GsmModem => Box::new(GsmModemDriver::new(port_index)),
        DriverType::ChromeCcd => Box::new(ChromeCcdDriver::new(port_index)),
    }
}

pub fn line_coding_bytes(cfg: &LineConfig) -> [u8; 7] {
    let stop = match cfg.stop_bits {
        crate::config::StopBits::One => 0u8,
        crate::config::StopBits::OnePointFive => 1,
        crate::config::StopBits::Two => 2,
    };
    let parity = match cfg.parity {
        crate::config::Parity::None => 0,
        crate::config::Parity::Odd => 1,
        crate::config::Parity::Even => 2,
        crate::config::Parity::Mark => 3,
        crate::config::Parity::Space => 4,
    };
    let data = match cfg.data_bits {
        crate::config::DataBits::Five => 5,
        crate::config::DataBits::Six => 6,
        crate::config::DataBits::Seven => 7,
        crate::config::DataBits::Eight => 8,
    };
    [
        (cfg.baud_rate & 0xff) as u8,
        ((cfg.baud_rate >> 8) & 0xff) as u8,
        ((cfg.baud_rate >> 16) & 0xff) as u8,
        ((cfg.baud_rate >> 24) & 0xff) as u8,
        stop,
        parity,
        data,
    ]
}

struct EndpointPair {
    bulk_in: Option<Box<dyn BulkIn>>,
    bulk_out: Option<Box<dyn BulkOut>>,
    in_ep: u8,
    out_ep: u8,
    mps: u16,
}

impl EndpointPair {
    pub(crate) fn from_addresses(in_ep: u8, out_ep: u8, mps: u16) -> Self {
        Self {
            bulk_in: None,
            bulk_out: None,
            in_ep,
            out_ep,
            mps,
        }
    }

    fn open(transport: &SharedTransport, iface: u8) -> Result<Self> {
        let eps = transport.endpoints(iface);
        let in_ep = eps
            .iter()
            .find(|e| e.is_bulk_in())
            .ok_or_else(|| crate::error::UsbSerialError::ProbeFailed("no bulk in".into()))?;
        let out_ep = eps
            .iter()
            .find(|e| e.is_bulk_out())
            .ok_or_else(|| crate::error::UsbSerialError::ProbeFailed("no bulk out".into()))?;
        Ok(Self {
            bulk_in: None,
            bulk_out: None,
            in_ep: in_ep.address,
            out_ep: out_ep.address,
            mps: in_ep.max_packet_size,
        })
    }

    fn ensure_out(&mut self, transport: &SharedTransport) -> Result<()> {
        if self.bulk_out.is_none() {
            self.bulk_out = Some(transport.open_bulk_out(self.out_ep, self.mps)?);
        }
        Ok(())
    }

    fn ensure_in(&mut self, transport: &SharedTransport) -> Result<()> {
        if self.bulk_in.is_none() {
            self.bulk_in = Some(transport.open_bulk_in(self.in_ep, self.mps)?);
        }
        Ok(())
    }

    fn write(&mut self, transport: &SharedTransport, data: &[u8]) -> Result<usize> {
        // Only claim OUT — IN may already be owned by SerialReader after take_in().
        // Opening IN again hits nusb "endpoint already in use".
        self.ensure_out(transport)?;
        self.bulk_out
            .as_mut()
            .unwrap()
            .write(data, WRITE_TIMEOUT_MS)
    }

    fn take_in(&mut self, transport: &SharedTransport) -> Option<Box<dyn BulkIn>> {
        self.ensure_in(transport).ok()?;
        self.bulk_in.take()
    }
}