Skip to content

Instantly share code, notes, and snippets.

@tai
Created May 14, 2021 20:43
Show Gist options
  • Select an option

  • Save tai/1f382c44a0b4e7791301de1366be5917 to your computer and use it in GitHub Desktop.

Select an option

Save tai/1f382c44a0b4e7791301de1366be5917 to your computer and use it in GitHub Desktop.
#!/usr/bin/perl
use English;
use Getopt::Long;
use YAML;
use JSON;
use strict;
sub help {
my $p = $0; $p =~ s|^.*/||;
print <<EOF;
$p - List associated device files for each USB hub/device
Usage: $p [-ra] <hub-or-dev-id> [<hub-or-dev-id>...]
Options:
-r : recursively track USB hub chain
-a : show all USB hubs, including the one that has no device plugged in
--cutoff <n=20> : hide device files longer than this length
Example:
\$ $p # list active USB hubs (that have device plugged in)
\$ $p -a # list all USB hubs
\$ $p 3-4.4 # list devices plugged to hub at bus3-port4-port4
3-4.4.4 0557:2008 Prolific_Technology_Inc._USB-Serial_Controller
- /dev/ttyUSB0
\$ $p -r # list all plugged devices recursively
1-4.4 0b95:1790 ASIX_Elec._Corp._AX88179_00000000001025
- enx58278cbf9025
1-9 8087:0aa7 8087_0aa7
- hci0
...
EOF
exit(0);
}
sub identify {
my @devpath = split('/', shift);
while ($_ = pop @devpath) {
# root hub
return ($1, undef) if /^usb(\d+)/;
# device under a root hub
return ($1, $2) if /^((\d+)-\d+)$/;
# device under a hub
return ($1, $2) if /^((\d+\-[\d\.]+)\.\d+)$/;
}
}
sub vidpid {
my @product = split('/', shift);
return sprintf("%04s:%04s", $product[0], $product[1]);
}
sub scan {
open(my $fh, "udevadm info -e |");
my $db = { hub => {}, dev => {} };
my %item;
while (<$fh>) {
chomp($_);
undef %item if /^P:/;
$item{$1} = $POSTMATCH if /^E: (DEVNAME|DEVPATH|DEVLINKS|DEVTYPE)=/;
$item{$1} = $POSTMATCH if /^E: (ID_SERIAL|ID_MODEL_FROM_DATABASE|PRODUCT)=/;
$item{$1} = $POSTMATCH if /^E: (ID_NET_NAME|RFKILL_NAME)=/;
$item{$1} = $2 if m|^E: (TYPE)=(\d+)/\d+/\d+|;
if (/^$/ && $item{DEVPATH} =~ m|/usb\d+/|o) {
my($dev_id, $hub_id) = identify($item{DEVPATH});
# register USB device
$db->{dev}{$dev_id} ||= {
id => $item{ID_SERIAL} || $item{ID_MODEL_FROM_DATABASE},
vp => vidpid($item{PRODUCT})
};
# register USB hub
$db->{hub}{$dev_id} ||= {} if $item{TYPE} == 9;
# skip root hub
next unless $hub_id;
# register upstream USB hub of the device
$db->{hub}{$hub_id} ||= {};
$db->{hub}{$hub_id}{$dev_id} ||= $db->{dev}{$dev_id};
# register device files
if ($item{DEVTYPE} ne 'usb_device') {
my @devs;
push(@devs, $item{DEVNAME}) if $item{DEVNAME};
push(@devs, split(/\s+/, $item{DEVLINKS})) if $item{DEVLINKS};
# handle network interfaces
unless (@devs) {
my $ifname = $item{RFKILL_NAME} || $item{ID_NET_NAME};
push(@devs, $ifname) if $ifname;
}
if (@devs) {
$db->{dev}{$dev_id}{devpath} ||= [];
push(@{$db->{dev}{$dev_id}{devpath}}, @devs);
}
}
}
}
return $db;
}
sub list_hub {
my($opt, $db) = @_;
my @hubs = sort keys %{$db->{hub}};
my $len = (sort(map(length, @hubs)))[-1];
foreach my $hub (@hubs) {
# hide inactive (no plugged in device) hubs by default
my $count = 0;
scan_dev($opt, $db, $hub, sub { $count++ });
next unless $count > 0 || $opt->{all};
my $id = $db->{dev}{$hub}{id};
printf("%-*s: %s\n", $len, $hub, $id);
}
}
sub scan_dev {
my($opt, $db, $id, $hook) = @_;
if ($db->{hub}{$id}) {
while (my($key, $val) = each %{$db->{hub}{$id}}) {
$hook->($key) unless $db->{hub}{$key};
scan_dev($opt, $db, $key, $hook) if $db->{hub}{$key} && $opt->{recursive};
}
}
else {
$hook->($id) if $db->{dev}{$id};
}
}
sub list_dev {
my($opt, $db, @ids) = @_;
foreach my $id (@ids) {
scan_dev($opt, $db, $id, sub {
my $key = shift;
print "$key $db->{dev}{$key}{vp} $db->{dev}{$key}{id}\n";
foreach my $dev (@{$db->{dev}{$key}{devpath}}) {
next if $opt->{cutoff} > 0 && length($dev) > $opt->{cutoff};
print "- $dev\n";
}
});
}
}
sub main {
my $opt = {};
GetOptions($opt, qw(--help --all --recursive --cutoff=i));
$opt->{cutoff} ||= 20;
help() if $opt->{help};
my $db = scan();
my @ids = @ARGV;
if (@ids || $opt->{recursive}) {
@ids = sort grep {/^\d+$/} keys %{$db->{hub}} unless @ids;
list_dev($opt, $db, @ids);
}
else {
list_hub($opt, $db);
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment