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

linux usb gadget serialport, get Not a typewriter #243

Open
cppcoffee opened this issue Jan 22, 2025 · 12 comments
Open

linux usb gadget serialport, get Not a typewriter #243

cppcoffee opened this issue Jan 22, 2025 · 12 comments

Comments

@cppcoffee
Copy link

cppcoffee commented Jan 22, 2025

Open the usb gadget serialport, get an error occurs.

The source code is as follows:

use serialport::{DataBits, Parity, StopBits};
use std::io::Read;
use std::io::{self, Write};
use std::time::Duration;

fn main() {
    let mut port = serialport::new("/dev/ttyGS0", 9600)
        .parity(Parity::None)
        .stop_bits(StopBits::One)
        .data_bits(DataBits::Eight)
        .timeout(Duration::from_secs(120))
        .open_native()
        .unwrap();       // Not a typewriter
...

Error message:

thread 'main' panicked at src/main.rs:13:10:
called `Result::unwrap()` on an `Err` value: Error { kind: Unknown, description: "Not a typewriter" }
@cppcoffee cppcoffee changed the title Not a typewriter linux usb gadget serialport, get Not a typewriter Jan 22, 2025
@sirhcel
Copy link
Contributor

sirhcel commented Jan 22, 2025

Sorry to hear that @cppcoffee! Let's dig into what's causing this. How do you set up the USB gadget device in question? Could you run your example with RUST_BACKTRACE set to 1? For example by setting in beforehand like:

$ export RUST_BACKTRACE=1

@asmforce
Copy link

asmforce commented Jan 22, 2025

It's not the same issue but seems to be related: 4.7.0 fails to work with virtual serial port created by socat utility while 4.6.1 works perfectly fine:

$ cargo build --release
   Compiling proc-macro2 v1.0.93
   Compiling unicode-ident v1.0.14
   Compiling libc v0.2.169
   Compiling pkg-config v0.3.31
   Compiling thiserror v1.0.69
   Compiling bitflags v1.3.2
   Compiling cfg-if v1.0.0
   Compiling bitflags v2.8.0
   Compiling scopeguard v1.2.0
   Compiling libudev-sys v0.1.4
   Compiling quote v1.0.38
   Compiling syn v2.0.96
   Compiling nix v0.26.4
   Compiling libudev v0.3.0
   Compiling thiserror-impl v1.0.69
   Compiling unescaper v0.1.5
   Compiling serialport v4.7.0
   Compiling experiments-rs v1.0.0 (/home/asmforce/work/experiments-rs)
    Finished `release` profile [optimized] target(s) in 2.52s
$ ./target/release/experiments-rs 
Error: Custom { kind: Other, error: "Not a typewriter" }
$ cargo update serialport
    Updating crates.io index
     Locking 1 package to latest compatible version
 Downgrading serialport v4.7.0 -> v4.6.1 (available: v4.7.0)
$ cargo build --release
   Compiling serialport v4.6.1
   Compiling experiments-rs v1.0.0 (/home/asmforce/work/experiments-rs)
    Finished `release` profile [optimized] target(s) in 0.52s
$ ./target/release/experiments-rs 
Written 4 bytes
Success

Here's the app's source code:

use std::io::{Error, Write};
use std::time::Duration;
use serialport::{FlowControl, DataBits, Parity, StopBits};

fn main() -> Result<(), Error> {
    let path = "/dev/ttyTEST";
    let builder = serialport::new(path, 9600)
        .flow_control(FlowControl::None)
        .data_bits(DataBits::Eight)
        .parity(Parity::None)
        .stop_bits(StopBits::One)
        .timeout(Duration::from_millis(1000));

    let mut port = builder.open()?;

    let data = [12u8, 23u8, 34u8, 45u8];
    match port.write(&data) {
        Ok(count) => println!("Written {} bytes", count),
        Err(e) => println!("{}", e)
    }

    match port.flush() {
        Ok(()) => println!("Success"),
        Err(e) => println!("{}", e)
    }

    Ok(())
}

And here is the command to create a virtual serial port:

sudo socat -d -d pty,link=/tmp/cvsimulator_sp,echo=0,perm=0777,b9600 pty,link=/dev/ttyTEST,echo=0,perm=0777,b9600

@sirhcel
Copy link
Contributor

sirhcel commented Jan 22, 2025

Thank you for bringing up the issue with Linux pseudo terminals @asmforce!

At a first glance, I have no idea how to reliably detect whether the actual device is supposed to support setting control lines. But trying on best-effort looks like a way to go. See sirhcel@9b56161.

@cppcoffee
Copy link
Author

cppcoffee commented Jan 23, 2025

Hi @sirhcel , testing revealed issues with serialport version 4.7.0, while version 4.6.1 works normally.

The test code is as follows:

fn test_serial() {
    let mut buf = [0; 4096];

    let mut port = serialport::new("/dev/ttyGS0", 1_500_000)
        .parity(Parity::Even)
        .stop_bits(StopBits::One)
        .data_bits(DataBits::Eight)
        .timeout(Duration::from_secs(120))
        .open_native()
        .unwrap();

    loop {
        let _n = port.read(&mut buf).unwrap();
    }
}

@bachrc
Copy link

bachrc commented Jan 28, 2025

Bumping this, having the same kind of issue with version 4.7.0, that 4.6.1 fixes.

Launched socat with this command

$ socat -d -d pty,raw,echo=0 pty,raw,echo=0
2025/01/28 10:26:13 socat[16319] N PTY is /dev/pts/1
2025/01/28 10:26:13 socat[16319] N PTY is /dev/pts/2
2025/01/28 10:26:13 socat[16319] N starting data transfer loop with FDs [5,5] and [7,7]

And my code is:

let mut port = serialport::new("/dev/pts/2", 115200)
        .open_native()
        .expect("Ouverture du port série");

Previously, with version 4.7.0, the error was:

thread 'main' panicked at apps/serial_tests/src/main.rs:20:10:
Ouverture du port série: Error { kind: Unknown, description: "Not a typewriter" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I have rust in version 1.75

@sirhcel
Copy link
Contributor

sirhcel commented Jan 28, 2025

Hello @cppcoffee and @bachrc, does sirhcel@9b56161 solve the issue with 4.7.0 in your case?

@cppcoffee
Copy link
Author

cppcoffee commented Jan 31, 2025

Hello @cppcoffee and @bachrc, does sirhcel@9b56161 solve the issue with 4.7.0 in your case?

In my case, the issue occurs in version 4.7.0, but version 4.6.1 does not have this problem.

@sirhcel
Copy link
Contributor

sirhcel commented Jan 31, 2025

In my case, the issue occurs in version 4.7.0, but version 4.6.1 does not have this problem.

Yes, i read #243 (comment) this way. Could you please give sirhcel@9b56161 a spin to check whether this also solves the issue with the USB gadget device as well?

@dcallahan-qti
Copy link

I have the same issue as @cppcoffee when trying to open /dev/ttyGS0 using version 4.7.0 while 4.6.1 works.

serialport::new("/dev/ttyGS0", 115200)
        .data_bits(serialport::DataBits::Eight)
        .parity(serialport::Parity::None)
        .stop_bits(serialport::StopBits::One)
        .flow_control(serialport::FlowControl::None)
        .timeout(Duration::from_secs(30))
        .open()?

I tried sirhcel/serialport-rs@9b56161 but I still get the same error:

Serial(Error { kind: Unknown, description: "Invalid argument" })

@sirhcel
Copy link
Contributor

sirhcel commented Mar 3, 2025

Thank you for trying out sirhcel@9b56161 @dcallahan-qti! Then the fix covers socat devices but still fails for USB serial gadgets and we need to dig deeper:

  • Is /dev/ttyG0 the gadget side of this setup? Or is this the device on the host the gadget is connected to?
  • Could you please run the test binary with strace for a quick shot whether there is a system call which fails?
  • Are you able to open /dev/ttyG0 with another serial library like pySerial?
  • Could you instrument or debug TTYPort::open to pinpoint the cause (the dbg or println will likely suffice for instumenting)

Please feel free to ping me for help if steps are not clear to you. By the way: How does you USB gadget setup look like?

Skimming though gadget_serial.txt and reading

The gadget serial driver only provides simple unreliable data communication. It does not yet handle flow control or many other features of normal serial devices.

makes me wonder if we are failing at a simple setup step like flow control.

@dcallahan-qti
Copy link

dcallahan-qti commented Mar 4, 2025

@sirhcel Yes, /dev/ttyGS0 is the gadget side of the setup. The MPU USB port is configured as a Device.

I found if I comment out the following code in TTYPort::open, the USB Gadget "/dev/ttyGS0" opens correctly.

if builder.baud_rate > 0 {
    if let Some(dtr) = builder.dtr_on_open {
        port.write_data_terminal_ready(dtr)?;
    }
}

It looks like this was added in 82226e1.

Removing this code allows the USB Gadget to open because it's the USB device side of the connection, which is intended to monitor the DTR signal from the Host and not set it. Typically, the Host sets the DTR signal to indicate to the connected device that it is ready to communicate. I'm assuming that's why the code was added in the first place, to signal connected devices that are designed to wait for the DTR before communicating.

If I update the serialport::new call with preserve_dtr_on_open() everything works.

serialport::new("/dev/ttyGS0", 115200)
        .data_bits(serialport::DataBits::Eight)
        .parity(serialport::Parity::None)
        .stop_bits(serialport::StopBits::One)
        .flow_control(serialport::FlowControl::None)
        .timeout(Duration::from_secs(30))
        .preserve_dtr_on_open()
        .open()?

@gswebspace
Copy link

Ran into this issue and adding preserve_dtr_on_open() in the builder works for me for a virtual serial port created via socat.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants