如何防止 cdc_acm 打开 DTR?

如何防止 cdc_acm 打开 DTR?

我使用驱动程序通过 USB 连接了 Arduino Uno cdc_acm。它可以在/dev/ttyACM0.

Arduino 串行接口的惯例是DTR用于复位信号——当使用集成串行转 USB 适配器时,为 DTR/RTS/DSR/CTS 信号;或者,当使用 RS-232 电缆时,将引脚 4 或 5(也可能是 6 或 8)连接至该RESET引脚。

这种重置途径的一个重要优点是,如果不是的话确实至少在带外很近-failsafe(由于是通过始终带外串行控制器与用户通常无法控制的看门狗电路一起实现的),并且被物理禁用(通过将电容器或电阻器连接到引脚,具体取决于型号RESET),这样做会完全破坏这个重要的终止开关和所有相关的实用程序。

很遗憾,看起来,目前,当任何程序出于任何原因连接到 ACM 设备时,Linux 绝对总是发送此信号,并且(与 Windows 不同,) 没有提供任何甚至模糊已知的可靠方法来防止这种情况发生。

(目前-hupcl“当最后一个进程关闭 tty 时发送挂断信号”-clocal“禁用调制解调器控制信号”不是阻止发送此信号每次该设备是打开.)


tl;dr:我需要做什么才能/dev/ttyACM0在不向其发送 DTR/RTS/DSR/CTS 信号的情况下进行访问(除了在硬件级别上阻止信号)?

答案1

当用户态进程打开串行设备(如/dev/ttyS0或 )时,linux默认/dev/ttyACM0会升高这些行,并在关闭它时丢弃它们。DTR/RTS

dtr_rts它通过调用驱动程序定义的回调来实现这一点。

不幸的是,还没有任何 sysctl 或类似的东西允许禁用这种烦人的行为(现在很少用),所以唯一有效的方法是从驱动程序tty_port_operations结构中删除该回调,并重新编译驱动程序模块。

您可以cdc-acm通过注释掉为驱动程序执行此操作这条线:

--- 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,

这会不是阻止您DTR/RTS通过串行 ioctl 使用线路,例如TIOCMSET, TIOCMBIC, TIOCMBIS,这将由acm_tty_tiocmset()acm_ops结构中的回调,像往常一样。

类似的黑客攻击也可用于其他驱动程序;我个人已经将其与PL2303USB -> 串行驱动程序一起使用。

[差异信息丰富;它不会直接应用,因为该网站会破坏制表符和空格]

答案2

我认为有一个很好的解决方法可以解决这个问题。可以从命名管道(例如 /tmp/arduino)读取数据,而不是从设备 /dev/ttyUSB0 或 /dev/ttyACM0 读取数据,简单的程序(如下)将把数据从设备复制到管道并保存设备打开(从而避免将 DTR 设置为高电平)。这也避免了处理从设备读取的所有困难。使用命名管道,可以使用 cat、less -f 等工具或任何具有标准 open + read 的程序,无需发出 ioctl 命令来控制 tty。用于将设备复制到管道的程序将作为新贵服务运行,并将数据从设备复制到管道(并且可能生成一些日志)。该程序必须处理 SIGPIPE 信号,以避免在关闭任何管道读取器进程时被关闭。由于通过 fcntl 有意设置输入阻塞,因此服务器上的负载可以忽略不计。我已经测试过它,看起来效果很好。我实际上正在尝试通过连接 Arduino 来解决同样的问题。好的副作用是,只需在需要时重新启动 upstart 服务即可重新启动 Arduino,因为它将 DTR 设置为高电平。

#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>

/* for simplicity, most error handling is ommited, make sure you add it before using in production code */
void ignore_signal(int sig)
{
        static struct sigaction _sigact;
        memset(&_sigact, 0, sizeof(_sigact));
        _sigact.sa_handler = SIG_IGN;
        sigaction(sig, &_sigact, NULL);
}

int main()
{
        ignore_signal(SIGPIPE);

        int flags;
        flags = fcntl(0, F_GETFL, 0) & ~O_NONBLOCK;
        fcntl(0, F_SETFL, flags);


        char c;
        int n;
        while(1)
        {
                n = read(0,&c,1);
                if(n!=1)
                {
                        sleep(1);
                }
                else
                {
                        write(1,&c,1); /* ignoring the case that return code = -1 and errno = EPIPE means that data from Arduino are lost whenever pipe is not read */
                        write(2,&c,1);
                }
        }
        return 0;
}

启动它的 shell 脚本是(需要作为 upstart 服务重新实现):

#!/bin/bash

DEV=/dev/ttyUSB0
PIPE=/tmp/arduino
LOG=/var/log/arduino.log

if test ! -p $PIPE
then
    rm -f $PIPE
    mkfifo $PIPE
fi

./my_dd <$DEV >$PIPE 2>>$LOG &
dd if=$PIPE of=/dev/null count=0 bs=1

我认为 logrotate 文件需要使用 copytruncate,因为该文件仍然打开(我没有机会测试这一点):

/var/log/arduino.log {
  rotate 5
  daily
  compress
  missingok
  notifempty
  create 640 root root
  copytruncate
}

附加说明:同时我意识到,为了避免管道损坏,可以通过使用 cat 或 dd 结合带有参数 PIPE 的 trap 命令来实现,这也会过滤 SIGPIPE 信号。上面的解决方案适用于我,因此我没有尝试使用 trap 命令来获得可比较的结果。

相关内容