2012年4月12日木曜日

USBポートの電源制御

ワンセグ野郎を始めて2ヶ月位経ちますが、3回ほどdvbstreamからデータを取れなくなって停止していることがありました。
dvbstream起動時に、次のようなメッセージを出力しているので、信号を受信できない状態になるようです。

Bit error rate: 0
Signal strength: 0
SNR: 0
FE_STATUS:
Setting filter for PID 8192
FILTER 8192: DMX SET PES FILTER: Connection timed out

こうなってしまうとUSBデバイスを抜き差ししないと回復しません。
USBデバイスへの電源On/Offでよさそうなのですが、リブートしてもUSBデバイスへの給電は切れないようなので、USBポートの電源制御をする必要があるようです。

USBコントローラーが電源制御をサポートしていれば、ソフト的に制御できることが分かったのですが、残念ながら使用しているマザーボードでは未サポートでした。

さらに調べるとポート毎に電源制御ができるUSB HUBがあり、Linux向けのソフト(節電USB・HUB(U2H-SW4)の制御ソフト)を公開している方を見つけました。
作者の方は、sianoチップ利用のワンセグ野郎ユーザーのようで、同じ症状改善のために書かれたようです。すばらしい。

早速、コンパイルして使用してみましたが、私の環境ではそのままでは上手く動きませんでした。
HUBの仕様変更を疑ってSnoopyProでデータを確認したり、あれこれ調べたところ、/dev/hidrawXとの送受信データの先頭に1byte付けてあげる(レポートID用?)と上手く動くことが分かりました。

で、ts.plに組み込みたかったので、perlで書いてみました。

set_status('/dev/hidraw0', 1, 0);  # hidraw0のポート1の電源OFF
usleep((1000000*0.3));
set_status('/dev/hidraw0', 1, 1);  # hidraw0のポート1の電源ON

sub set_status()
{
        my ($file, $port_num, $power) = @_;

        sysopen my $fh, $file, O_RDWR|O_NONBLOCK or do {
                print STDERR "cannot open $file: $!\n";
                return 0;
        };

        my @set_status = (0,0x03,0x5d,0,0,0,0,0,0);

        if ($port_num == 1) {
                $set_status[4] = 0x05;
        }
        elsif (1 < $port_num and $port_num < 5) {
                $set_status[4] = int($port_num);
        }
        else {
                return 0;
        }

        $set_status[5] = 0x01 if ($power);

        my $dat = pack("C9", @set_status);
        my $r = syswrite $fh, $dat, 9;

        if ($r != 9) {
                print STDERR "syswrite failed: f=$file, p=$port_num: $!\n";
                return 0;
        }
        close $fh;
}

U2H-SW4は、lsusbで確認するとUSB portを5つ持っていて、USB port1がHIDになっているようです。
USB port5が機器に印刷されている1番ポートに対応しています。

ポートの状態確認は、次のようにして取得できます。
4byte目の該当するビットが立っている場合は、電源Onを表しています。

my $port = get_status("/dev/hidraw0");
print "$file\n";
foreach my $i (0..3) {
        my $on = $port & (1<< $i);
        printf " port%d: %s\n", $i+1, $on ? "On" : "Off";
}

sub get_status()
{
        my ($file) = shift @_;

        sysopen my $fh, $file, O_RDWR|O_NONBLOCK or do {
                print STDERR "cannot open $file: $!\n";
                return undef;
        }

        my @get_status = (0,0x03,0x5d,0x02,0,0,0,0,0);

        my $dat = pack("C9", @get_status);
        my $r = syswrite $fh, $dat, 9;

        my ($rout, $info) = (undef, undef);
        my $rin = '';
        vec($rin, fileno($fh), 1) = 1;
        my ($nfound, $timeleft) = select($rout=$rin, undef, undef, 3);

        $r = read $fh, $info, 9;
        close $fh;

        return undef if ($r != 9);

        my @st = unpack("C9", $info);
        if (($st[3] & 0xc3) != 0x03 and $st[5] == 0x75) {
                printf STDERR "unknown data received: %s\n", unpack("H*", $info);
                return undef;
        }

        my ($port, $i) = (0, 0);
        foreach my $flg (0x20, 0x04, 0x08, 0x10) {
                $port |= (1 << $i) if ($st[3] & $flg);
                $i++;
        }
        return $port;
}
ts.plに組み込むにあたり、dvbstreamで使用しているDVBデバイスが接続しているU2H-SW4を見つける必要があります。
もっとスマートな方法があると思いますが、udevadmコマンドでdevpathを調べることで接続しているU2H-SW4とHUB portを特定できました。

U2H-SW4のHIDは、次のように/dev/hidraw*からベンダー/プロダクトコードが一致するものを調べ、udevadmコマンドでdevpathを取得しておきます。
説明用のコードでは、見やすさのためにエラー処理はdie()にしました。
sub find_u2hsw4()
{
        my $HIDIOCGRAWINFO = 0x80084803;

        opendir my $dh, '/dev' or die "cannot opendir /dev: $!";
        my @files = sort grep /^hidraw\d+$/, readdir($dh);
        closedir $dh;

        my @target = ();
        foreach my $f (@files) {
                my $file = "/dev/$f";
                my $info = undef;

                open my $fd, '+<', $file or die "cannot open $file: $!";
                ioctl($fd, $HIDIOCGRAWINFO, $info) or die "ioctl failed: $file: $!";
                close $fd;

                my ($busy, $vendor, $product) = unpack("Iss",$info);

                # find ActionStar USB HID
                if ($vendor == 0x2101 and ($product & 0xffff) == 0x8501) {
                        my $path = `/sbin/udevadm info --query=path --name=$file`;
                        chomp($path);
                        $path =~ s!/hidraw/${f}$!!;
                        push @target, [$file, $path];
                }
        }

        return @target;
}
上記で見つけたHIDのdevpathと、dvbstreamで使用している/dev/dvb/adapterX/frontendXのdevpathを調べることにより、接続しているHUBとHUB portを見つけることが出来ます。

ts.plでdvbstreamにSIGTERMを送った後、dvbstreamのログから直近でSNR: 0を見つけた場合に、電源をOn/Offするようにして使ってます。

この記事からSyntaxHighlighter を設定してみました。

0 件のコメント:

コメントを投稿