6

I have an Arduino Uno attached over USB, using the cdc_acm driver. It is available at /dev/ttyACM0.

The convention for the Arduino's serial interface is for the DTR signal to be used for a reset signal—when using the integrated serial-to-USB adapter, the DTR/RTS/DSR/CTS signal; or, when using an RS-232 cable, pins 4 or 5 (and possibly 6 or 8) are wired to the RESET pin.

This reset avenue has the important advantage of being, if not truly out-of-band, at least very near-failsafe (due to being implemented via the always-out-of-band serial controller in conjunction with the not-normally-user-controllable watchdog circuit), and while it can be physically disabled (via wiring either a capacitor or a resistor, depending on the model, to the RESET pin), to do so completely ruins this important killswitch and all associated utility.

Unfortunately, it seems that, currently, Linux absolutely always sends this signal when any program attaches to an ACM device for any reason, and (unlike Windows,) provides no even-vaguely-known-reliable way to prevent this.

(Currently both -hupcl, "send a hangup signal when the last process closes the tty" and -clocal, "disable modem control signals" do not prevent this signal from being sent every time the device is opened.)


tl;dr: What do I need to do to access /dev/ttyACM0 without sending it a DTR/RTS/DSR/CTS signal (short of blocking the signal on the hardware level)?

  • and I guess this question applies to ALL serial drivers, not just cdc_acm. But first things first, and the once-in-a-blue-moon that onboard RS-232 chips are used, even then probably nobody cares about being unable to control this. But with Arduinos, there's a clear and pressing concern. (In BOTH CASES, though, it's highly concerning that Windows has us so handily beat; I'd have expected this situation to be the other way around) – JamesTheAwesomeDude Sep 25 '18 at 22:12
  • 1
    If you can recompile the cdc-acm.ko kernel module, you can try commenting out this line from drivers/usb/class/cdc-acm.c. – mosvy Sep 25 '18 at 23:50
  • 1
    @mosvy I did try your hack. It does indeed prevent DTR from being trigger on open/close. Triggering it manually is still possible too. – 1N4001 Jun 4 '19 at 23:43
  • 1
    @mosvy This is the only software solution I have found for Linux - can you post your hack as an answer? – Mtl Dev Sep 24 '19 at 19:58
  • 1
    @MtlDev I want to submit a patch for a sysctl which allow to disable this globally, but I did not come yet round to it; so in the meanwhile, I'll make the hack more visible. – mosvy Sep 24 '19 at 20:31
4

When a userland process is opening a serial device like /dev/ttyS0 or /dev/ttyACM0, linux will raise the DTR/RTS lines by default, and will drop them when closing it.

It does that by calling a dtr_rts callback defined by the driver.

Unfortunately, there isn't yet any sysctl or similar which allows to disable this annoying behavior (of very little use nowadays), so the only thing that works is to remove that callback from the driver's tty_port_operations structure, and recompile the driver module.

You can do that for the cdc-acm driver by commenting out this line:

--- drivers/usb/class/cdc-acm.c~
+++ drivers/usb/class/cdc-acm.c
@@ -1063,7 +1063,7 @@
 }

 static const struct tty_port_operations acm_port_ops = {
-       .dtr_rts = acm_port_dtr_rts,
+       /* .dtr_rts = acm_port_dtr_rts, */
        .shutdown = acm_port_shutdown,
        .activate = acm_port_activate,
        .destruct = acm_port_destruct,

This will not prevent you from using the DTR/RTS lines via serial ioctls like TIOCMSET, TIOCMBIC, TIOCMBIS, which will be handled by the acm_tty_tiocmset(), etc callbacks from the acm_ops structure, as usual.

Similar hacks could be used with other drivers; I personally have used this with the PL2303 usb -> serial driver.

[The diff is informative; it will not apply directly because this site mangles tabs and whitespaces]

| improve this answer | |
  • so, to be clear: this is a straight-up bug in the upstream mainline Linux driver code, which will require compiling a custom kernel with patches to even test the solution to, and won't be functional OOTB until we've done all of: a) found a way to ensure this solution doesn't break anything, b) convinced the upstream maintainer it doesn't break anything, and then c) waited for the changes to get rolled into an official kernel release and picked up by the distros? – JamesTheAwesomeDude Oct 14 '19 at 18:09
  • 1. it's not a bug, it's very much intended behavior, leftover from an era where serial ports were mainly used with modems. 2. there's no need to compile a custom kernel -- just a custom module (and even that is not necessary -- you could just clear 8 or 4 bytes in the cdc-acm.ko binary, if you give the details about your system, I may be able to tell you where ;-)) 3. the changes needed to implement the sysctl I was talking about are trivial, there's no way they could break anything. Convincing the maintainer that this is useful may not be that simple, though. – mosvy Oct 14 '19 at 22:10
  • 1. the "bug" being that stty -F $i -clocal && cat $i still sends a modem control signal, despite such being ostensibly disabled; 2. I'm currently limping along a dying Ubuntu 16.04 installation lol; I'll add this to the to-do list if I ever spin up a Gentoo though; 3. I think the key will be selling this as a bugfix ("-clocal is BROKEN wrt open(2) due to the current state of the code") rather than as a feature request ("pander to my peripherals by changing current behavior that other code may be built on by now") – JamesTheAwesomeDude Oct 18 '19 at 19:07
  • I'm accepting this answer based on @1N4001's and @MtlDev's testimonies that it works. I'd love it if you could edit in some exposition emphasizing the fact that requires running a freaking custom kernel (all distros tested contained cdc_acm as a builtin module), so it's not reliable/viable for most users not willing to make such deep, "warranty-breaking" modifications to their OS that may or may not require intervention to preserve thru future OS+kernel updates; but, it's apparently the best "solution" that exists currently. Thanks for your investigation and testing. – JamesTheAwesomeDude May 6 at 18:06
  • Does anyone closer to The Internet Elders than I know how to get the ball rolling on getting this bugfix — see my previous comment @2019-08-18 — merged into upstream? While this patch is nice for the Sufficiently Determined (who would have just either killed the RST pin by now or made their AVR code expect hard-resets on connection, if they had any actual/external engineering requirements to comply with,) I think it's telling that the answer has fewer upvotes than the question has stars — this code, as a syadmin-level patch, is just a workaround on par with other workarounds. – JamesTheAwesomeDude May 6 at 18:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.