277 lines
7.4 KiB
Perl
Executable File
277 lines
7.4 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
use strict;
|
|
# $Id: motelist-linux,v 1.1 2007/05/29 19:34:30 adam Exp $
|
|
# @author Cory Sharp <cory@moteiv.com>
|
|
# @author Joe Polastre
|
|
|
|
my $help = <<'EOF';
|
|
usage: motelist [options]
|
|
|
|
$Revision: 1.1 $
|
|
|
|
options:
|
|
-h display this help
|
|
-c compact format, not pretty but easier for parsing
|
|
-f specify the usb-serial file (for smote.cs)
|
|
-k kernel version: 2.4, 2.6, auto (default)
|
|
-m method to scan usb: procfs, sysfs, auto (default)
|
|
-dev_prefix force the device prefix for the serial device
|
|
-usb display extra usb information
|
|
EOF
|
|
|
|
my %Opt = (
|
|
compact => 0,
|
|
usb => 0,
|
|
method => "auto",
|
|
kernel => "auto",
|
|
dev_prefix => [ "/dev/usb/tts/", "/dev/ttyUSB", "/dev/tts/USB" ],
|
|
usbserial => "sudo cat /proc/tty/driver/usbserial |",
|
|
);
|
|
|
|
while (@ARGV) {
|
|
last unless $ARGV[0] =~ /^-/;
|
|
my $opt = shift @ARGV;
|
|
if( $opt eq "-h" ) { print "$help\n"; exit 0; }
|
|
elsif( $opt eq "-c" ) { $Opt{compact} = 1; }
|
|
elsif( $opt eq "-f" ) { $Opt{usbserial} = shift @ARGV; }
|
|
elsif( $opt eq "-k" ) { $Opt{kernel} = shift @ARGV; }
|
|
elsif( $opt eq "-m" ) { $Opt{method} = shift @ARGV; }
|
|
elsif( $opt eq "-dev_prefix" ) { $Opt{dev_prefix} = shift @ARGV; }
|
|
elsif( $opt eq "-usb" ) { $Opt{usb} = 1; }
|
|
else { print STDERR "$help\nerror, unknown command line option $opt\n"; exit 1; }
|
|
}
|
|
|
|
if( $Opt{kernel} eq "auto" ) {
|
|
$Opt{kernel} = "unknown";
|
|
$Opt{kernel} = $1 if snarf("/proc/version") =~ /\bLinux version (\d+\.\d+)/;
|
|
}
|
|
|
|
if( $Opt{method} eq "auto" ) {
|
|
$Opt{method} = ($Opt{kernel} eq "2.4") ? "procfs" : "sysfs";
|
|
}
|
|
|
|
my @devs = $Opt{method} eq "procfs" ? scan_procfs() : scan_sysfs();
|
|
print_motelist( sort { cmp_usbdev($a,$b) } @devs );
|
|
|
|
|
|
#
|
|
# SysFS
|
|
#
|
|
sub scan_sysfs {
|
|
|
|
# Scan /sys/bus/usb/drivers/usb for FTDI devices
|
|
my @ftdidevs =
|
|
grep { ($_->{UsbVendor}||"") eq "0403" && ($_->{UsbProduct}||"") eq "6001" }
|
|
map { {
|
|
SysPath => $_,
|
|
UsbVendor => snarf("$_/idVendor",1),
|
|
UsbProduct => snarf("$_/idProduct",1),
|
|
} }
|
|
glob("/sys/bus/usb/drivers/usb/*");
|
|
|
|
# Gather information about each FTDI device
|
|
for my $f (@ftdidevs) {
|
|
my $syspath = $f->{SysPath};
|
|
|
|
$f->{InfoManufacturer} = snarf("$syspath/manufacturer",1);
|
|
$f->{InfoProduct} = snarf("$syspath/product",1);
|
|
$f->{InfoSerial} = snarf("$syspath/serial",1);
|
|
$f->{UsbDevNum} = snarf("$syspath/devnum",1);
|
|
|
|
my $devstr = readlink($syspath);
|
|
if( $devstr =~ m{([^/]+)/usb(\d+)/.*-([^/]+)$} ) {
|
|
$f->{UsbPath} = "usb-$1-$3";
|
|
$f->{UsbBusNum} = $2;
|
|
}
|
|
($f->{SysDev} = $syspath) =~ s{^.*/}{};
|
|
|
|
my $port = "$syspath/$f->{SysDev}:1.0";
|
|
($f->{DriverName} = readlink("$port/driver")) =~ s{^.*/}{} if -l "$port/driver";
|
|
($f->{SerialDevName} = (glob("$port/tty*"),undef)[0]) =~ s{^.*/}{};
|
|
$f->{SerialDevNum} = $1 if $f->{SerialDevName} =~ /(\d+)/;
|
|
$f->{SerialDevName} = getSerialDevName( $f->{SerialDevNum} ) || " (none)";
|
|
}
|
|
|
|
return @ftdidevs;
|
|
}
|
|
|
|
|
|
#
|
|
# Scan Procfs
|
|
#
|
|
sub scan_procfs {
|
|
|
|
my $text_devs = snarf("< /proc/bus/usb/devices");
|
|
my $text_serial = snarf($Opt{usbserial});
|
|
|
|
my @usbdevs = map { {parse_usb_devices_text($_)} }
|
|
grep { !/^\s*$/ } split /\n+(?=T:)/, $text_devs;
|
|
my %usbtree = build_usb_tree( @usbdevs );
|
|
my %usbserialtree = build_usbserial_tree( $text_serial );
|
|
for my $tts ( values %usbserialtree ) {
|
|
$usbtree{usbkey($tts->{path})}{usbserial} = $tts if defined $tts->{path};
|
|
}
|
|
|
|
my @ftdidevs = map { {
|
|
UsbVendor => $_->{Vendor},
|
|
UsbProduct => $_->{ProdID},
|
|
InfoManufacturer => $_->{Manufacturer},
|
|
InfoProduct => $_->{Product},
|
|
InfoSerial => $_->{SerialNumber},
|
|
UsbBusNum => $_->{nbus},
|
|
UsbDevNum => $_->{ndev},
|
|
UsbPath => (($Opt{kernel} eq "2.4") ? $_->{usbserial}{path} : $_->{usbpath}),
|
|
DriverName => $_->{driver},
|
|
SerialDevNum => $_->{usbserial}{tts},
|
|
SerialDevName => getSerialDevName($_->{usbserial}{tts}) || " (none)",
|
|
} }
|
|
grep { ($_->{Vendor}||"") eq "0403" && ($_->{ProdID}||"") eq "6001" }
|
|
values %usbtree;
|
|
|
|
return @ftdidevs;
|
|
}
|
|
|
|
sub build_usb_tree {
|
|
my @devs = @_;
|
|
my %tree = ();
|
|
for my $dev (sort { $a->{Lev} <=> $b->{Lev} } @devs) {
|
|
my ($bus,$lev,$prnt) = ( $dev->{Bus}+0, $dev->{Lev}+0, $dev->{Prnt}+0 );
|
|
my $devnum = $dev->{"Dev#"}+0;
|
|
$dev->{nbus} = $bus;
|
|
$dev->{ndev} = $devnum;
|
|
$tree{"bus$bus"} = {} unless exists $tree{"bus$bus"};
|
|
$tree{"bus$bus"}{"dev$devnum"} = $dev;
|
|
if( $lev == 0 ) {
|
|
$dev->{usbpath} = "usb-$dev->{SerialNumber}";
|
|
} else {
|
|
my $sep = ($lev==1) ? "-" : ".";
|
|
$dev->{parent} = $tree{"bus$bus"}{"dev$prnt"};
|
|
$dev->{usbpath} = $dev->{parent}{usbpath} . $sep . ($dev->{Port}+1);
|
|
}
|
|
$tree{usbkey($dev->{usbpath})} = $dev;
|
|
}
|
|
return %tree;
|
|
}
|
|
|
|
sub parse_usb_devices_text {
|
|
my $text = shift;
|
|
$text =~ s/^\S+\s*//gm;
|
|
$text =~ s/\s*(\S+=)/\001$1/g;
|
|
return map { m/(.*?)=(.*)/ } split /\001/, $text;
|
|
}
|
|
|
|
sub build_usbserial_tree {
|
|
my $text = shift;
|
|
my %tree = ();
|
|
while( $text =~ /^([^:]+):(.*)/mg ) {
|
|
my ($tts,$params) = ($1,$2);
|
|
$tree{$tts} = { tts => $tts };
|
|
while ($params =~ m/\s+([^:]+):(?:"([^"]*)"|(\S+))/g) {
|
|
$tree{$tts}{$1} = $2||$3;
|
|
}
|
|
}
|
|
return %tree;
|
|
}
|
|
|
|
sub usbkey {
|
|
if( $Opt{kernel} eq "2.4" ) {
|
|
(my $key = $_[0]) =~ s/^.*-//;
|
|
return $key;
|
|
}
|
|
return $_[0];
|
|
}
|
|
|
|
|
|
#
|
|
# getSerialDevName
|
|
#
|
|
# For each device, force to use dev_prefix if it's not an array. Otherwise,
|
|
# assume it's a list of candidate prefixes. Check them and commit to the
|
|
# first one that actually exists.
|
|
#
|
|
sub getSerialDevName {
|
|
my $devnum = shift;
|
|
my $devname = undef;
|
|
if( defined $devnum ) {
|
|
if( ref($Opt{dev_prefix}) eq "ARRAY" ) {
|
|
$devname = $devnum;
|
|
for my $prefix (@{$Opt{dev_prefix}}) {
|
|
my $file = $prefix . $devnum;
|
|
if( -e $file ) { $devname = $file; last; }
|
|
}
|
|
} else {
|
|
$devname = $Opt{dev_prefix} . $devnum;
|
|
}
|
|
}
|
|
return $devname;
|
|
}
|
|
|
|
|
|
#
|
|
# Print motelist
|
|
#
|
|
sub print_motelist {
|
|
my @devs = @_;
|
|
|
|
# If none were found, quit
|
|
if( @devs == 0 ) {
|
|
print "No devices found.\n";
|
|
return;
|
|
}
|
|
|
|
# Print a header
|
|
if( !$Opt{compact} ) {
|
|
if( $Opt{usb} ) {
|
|
print << "EOF" unless $Opt{compact};
|
|
Bus Dev USB Path Reference Device Description
|
|
--- --- ------------------------ ---------- ---------------- -------------------------------------
|
|
EOF
|
|
} else {
|
|
print << "EOF" unless $Opt{compact};
|
|
Reference Device Description
|
|
---------- ---------------- ---------------------------------------------
|
|
EOF
|
|
}
|
|
}
|
|
|
|
# Print the usb information
|
|
for my $dev (sort { cmp_usbdev($a,$b) } @devs) {
|
|
my $desc = join( " ", $dev->{InfoManufacturer}||"", $dev->{InfoProduct}||"" ) || " (none)";
|
|
my @output = ( $dev->{InfoSerial}||" (none)", $dev->{SerialDevName}, $desc );
|
|
@output = ( $dev->{UsbBusNum}, $dev->{UsbDevNum}, $dev->{UsbPath}, @output ) if $Opt{usb};
|
|
if( $Opt{compact} ) {
|
|
print join(",",@output) . "\n";
|
|
} else {
|
|
printf( ($Opt{usb}?"%3d %3d %-24s ":"")."%-10s %-16s %s\n", @output );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#
|
|
# Cmp Usbdev's
|
|
#
|
|
sub cmp_usbdev {
|
|
my ($a,$b) = @_;
|
|
if( defined $a->{SerialDevNum} ) {
|
|
if( defined $b->{SerialDevNum} ) {
|
|
return $a->{SerialDevNum} <=> $b->{SerialDevNum};
|
|
}
|
|
return -1;
|
|
}
|
|
return 1 if defined $b->{SerialDevNum};
|
|
return ($a->{InfoSerial}||"") cmp ($b->{InfoSerial}||"");
|
|
}
|
|
|
|
#
|
|
# Read a file in
|
|
#
|
|
sub snarf {
|
|
open my $fh, $_[0] or return undef;
|
|
my $text = do{local $/;<$fh>};
|
|
close $fh;
|
|
$text =~ s/\s+$// if $_[1];
|
|
return $text;
|
|
}
|
|
|