のうがき
*nix で使える cu コマンドの話です。
普段、シリアルコンソールとの接続には cu を使っています。screen, minicom, C-Kermit, Tera Term などを使うこともありますが、特に制限がなければ cu 一択です。cu のシンプルなところが好きです。
ずいぶん前から気にはなっていたのですが、screen だとうまく接続できるのに cu だとダメな場合がたびたびありました。接続先からの出力は cu を介して端末に表示されるのですが、どうもこちらからの入力が接続先に送られていないようなのです。コマンドラインは以下のようにしています。どちらも 115200 bps, 8 bit, パリティなし, 1 stop bit, フローコントロールなしを指定しているつもりです。
cu --parity=none --nostop --line /dev/ttyUSB0 --speed 115200 dir
screen /dev/ttyUSB0 115200 cs8 -ixon -ixoff -istrip
時間のある時にググってみました。ずいぶん古い メール ですが似たような現象で困っている方がいらっしゃいました。このメールによると /etc/uucp/port に "type modem" の設定を用意すれば良いようです。さっそく試してみました。
いろいろ試してみましたが、/etc/uucp/port ファイル (*nix の種類によってはディレクトリ名・ファイル名が違うかもしれません。この先 Ubuntu 前提で書きます) に "port dummy" ("dummy" は適当な文字列でいいです) と 1 行書くだけで問題の現象が発生しなくなることを見つけました。空ファイルとか全部コメントのファイルとかも試してみましたが、これらはダメでした。
strace で何か違いがわからないかと思いいろいろ試してみました。strace の引数は以下のようにするのがよかったです。
strace -v -y -e trace=ioctl -o strace-cu-ioctl.log cu --config /tmp/uucp-test/config --parity=none --nostop --line /dev/ttyUSB0 --speed 115200 dir
これで、うまく接続できる場合とダメな場合の strace のログをとって比較するわけです。怪しいと思ったのは以下のログです。上がうまくいった場合、下はダメだった場合です。
ioctl(3</dev/ttyUSB0>, SNDCTL_TMR_START or SNDRV_TIMER_IOCTL_TREAD or TCSETS, {c_iflags=0, c_oflags=0, c_cflags=0x1cb2, c_lflags=0xa00, c_line=0, c_cc[VMIN]=1, c_cc[VTIME]=1, c_cc="\x03\x1c\x7f\x08\x04\x01\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
ioctl(3</dev/ttyUSB0>, SNDCTL_TMR_START or SNDRV_TIMER_IOCTL_TREAD or TCSETS, {c_iflags=0, c_oflags=0, c_cflags=0x800014b2, c_lflags=0xa00, c_line=0, c_cc[VMIN]=1, c_cc[VTIME]=1, c_cc="\x03\x1c\x7f\x08\x04\x01\x01\x00\x11\x13\x1a\x00\x12\x0f\x17\x16\x00\x00\x00"}) = 0
c_cflags の値が違いますね。うまく接続できる場合は 0x1cb2 なのにダメな場合は 0x800014b2 になっています。/usr/include/asm-generic/termbits.h を見たところ "#define CRTSCTS 020000000000" と "#define CLOCAL 0004000" のビット違いではないかと思われます。
ソースファイルを見てみました。cu は UUCP に含まれているコマンドです。今使っている cu のバージョンは 1.07 です。ソースファイル一式は uucp-1.07.tar.gz から取ってきました。
どうも今回の問題は変数の未初期化が原因になっているようです。/etc/uucp/port ファイルがない状態で上記の cu コマンドを実行すると、ハードウェアフロー制御 (RTS/CTS) とキャリア検出 (DCD) の扱いが不定になるようです。該当変数を初期化するには以下のようにソースコードを修正すればいいと思います。これで RTS/CTS は使用しない、DCD は無視の扱いになると思います。
--- uucp-1.07.orig/cu.c
+++ uucp-1.07/cu.c
@@ -274,6 +274,8 @@
struct uuconf_dialer *qdialer;
char bcmd;
+ bzero(&sport, sizeof(sport));
+
zProgram = argv[0];
/* We want to accept -# as a speed. It's easiest to look through
--- uucp-1.07.orig/uuconf/tport.c
+++ uucp-1.07/uuconf/tport.c
@@ -71,6 +71,8 @@
struct uuconf_port sdefault;
int ilineno;
+ bzero(&sdefault, sizeof(sdefault));
+
e = fopen (*pz, "r");
if (e == NULL)
{
さて、cu のバイナリファイル (/usr/bin/cu) を直接書きかえることができる人はソースコードを修正すればいいのですが、やりたくない、面倒だと思う方もいるかと思います。まずは、cu のコマンドオプションで RTS/CTS, DCD を指定できるものがないか調べてみましたが、該当するものはなさそうでした。どうも /etc/uucp/port の設定で逃げるしかなさそうです。
例えば /etc/uucp/port に以下のような記述を用意して、cu のコマンドオプションのポート名かデバイス名でこの記述を指定するようにしてやれば、RTS/CTS, DCD を明示的に指定することが可能です。
port usb-serial
type direct
device /dev/ttyUSB0
speed 115200
hardflow false
carrier false
cu コマンドの引数で指定したポート名やデバイス名が /etc/uucp/port のどれにもマッチしない場合はどうなるのでしょうか? どうも /etc/uucp/port の最後に記述したエントリ (一番最後の port 指定からファイル末尾まで) が使われるようです。cu のマニュアルはひと通り見ていますが、そのような記述はなかったように思います。"port dummy" と 1 行だけ書いて上記の cu コマンドを実行した場合はこのエントリが使用されることになります。type のところは "type direct" もしくは "type modem" と指定できるのですが、type 自体が存在しない場合は modem として扱われるようです (これはどこかに書いてありました)。"type modem" が指定されると RTS/CTS はデフォルトで使用されません。これが "port dummy" と 1 行だけ書いたらうまく接続できるようになった理由と思われます。modem が指定された場合の動きについては深く調べていません。シリアルポートにモデムをつなぐこと自体が今の時代ほとんどないからです。
最後になりましたが、/etc/uucp/port ファイルを作成するにはスーパーユーザーの権限が必要です。権限が得られない場合は cu コマンドの --config オプションを使用するとよいでしょう。この場合には /etc/uucp/config に相当するファイルと /etc/uucp/port に相当するファイルを自分で用意する必要があります。
まとめ
- uucp-1.07 に付属の cu には変数を初期化していないコードがあり、RTS/CTS, DCD が意図せず有効になってしまう場合があります (環境依存)。
- cu は "
cu --parity=none --nostop --line /dev/ttyUSB0 --speed 115200 dir
" のようにオプションを指定することにより、設定ファイルなしでもシリアルポートに接続することができます。しかし、RTS/CTS, DCD の扱いを指定するオプションが用意されていないため、この方法では通信できない場合があります。 - 設定ファイル (/etc/uucp/port) を用意すれば RTS/CTS, DCD の扱いを指定することが出来、この問題を回避することが出来ます。