use strict;
use warnings;

my $DEVFILENAME = "/proc/bus/usb/devices";

my $showconfig = "yes";
my ($bus, $level, $parent, $port, $count, $devnum, $speed,
    $maxchild, $devclass, $intclass, $driver, $ifnum, $ids,
	  $showclass, $lastif, $HCtype, $nconfig, $HC, $product, $temp);
my @fields;

open(DEVNUM, "<$DEVFILENAME") or
	die "$0: cannot open '$DEVFILENAME'\n";

while (my $line = <DEVNUM>)
  {
	# skip all lines except those we recognize
	next if ($line !~ "^[CDISPT]:");
	chomp $line;

	# First convert '=' signs to spaces.
	$line =~ tr/=/ /;
	# and convert all '(' and ')' to spaces.
	$line =~ tr/(/ /;
	$line =~ tr/)/ /;
	# split the line at spaces.
	@fields = split / +/, $line;

	# T opology:  Bus=01 Lev=01 Prnt=01 Port=03 Cnt=01 Dev#=  3 Spd=1.5 MxCh= 0
	if ($line =~ "^T:")
    {
		# split yields: $bus, $level, $parent, $port, $count, $devnum, $speed, $maxchild.
		$bus    = $fields [2];
		$level  = $fields [4];
		$parent = $fields [6];		  # parent devnum
		$port   = $fields [8] + 1;	# make $port 1-based
		$count  = $fields [10];
		$devnum = $fields [12];
		$speed  = $fields [14];
		$maxchild = $fields [16];
		$devclass = "?";
		$intclass = "?";
		$driver   = "?";
		$ifnum    = "?";
		$showclass = "?";	        # derived from $devclass or $intclass
		$lastif = "?";			        # show only first altsetting
		$HCtype = "?";
		$nconfig = "0";
		$showconfig = "no";
		next;
	  }

	# C onfiguration: * #Ifs= 1 Cfg#= 1 Atr=a0 MxPwr=100mA
	if ($line =~ "^C:")
	  {
	  if ($line =~ "^C:\\*")
	    {	$showconfig = $fields[4]; }
	  else { $showconfig = "no"; }
	  next;
	  }

	# D evice:  Ver= 1.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1
	if ($line =~ "^D:")
	  {
		$devclass = $fields [5];
		$nconfig = $fields [13];
		next;
	  }

	# P roduct ID:  Vendor=0aa7 ProdID=0304 Rev=0.52
	if ($line =~ "^P:")
	  {
		$ids = $line;
		$ids =~ s/P: +//;
		$ids =~ s/ +/ /g;
		$ids =~ s/Vendor /Vendor=/g;
		$ids =~ s/ ProdID /, ProdID=/g;
		$ids =~ s/ Rev /, Rev=/g;
		next;
	  }
	# in case this is a root hub, look at the device strings.
	if ( $line =~ "^S:" )
	  { # for S: line
		if ($level == 00 && $line =~ "hcd")
		  { $HCtype = $fields [4]; }
		elsif ($level == 00 && $line =~ "HCI" && $HCtype eq "?")
		  { $HCtype = $fields [3]; }
		if ($line =~ "Product")
		  {
			my $product = $line;
			$product =~ s/Product //;
			$product =~ s/S: +//;
			$product =~ s/ +/ /g;
			$product =~ s/ $//g;
		  }
		next;
	  }
	# only shows interface descriptors
	# for the active configuration and
	# for the first (prefer: active!) altsetting
	next if (($line !~ "^I:") || ("$showconfig" eq "no"));

	# I nterface:  If#= 0 Alt= 0 #EPs= 1 Cls=03(HID  ) Sub=01 Prot=02 Driver=hid
	$intclass = $fields [9];
	$ifnum    = $fields [2];
	$driver   = $fields [15];

	if (($devclass eq ">ifc") || ($devclass eq "unk."))
	  {	# then use InterfaceClass, not DeviceClass
		$showclass = $intclass;
	  }
	else
	  {	# use DeviceClass
		$showclass = $devclass;
	  }

	if ($level == 0)
	  {
	  if ($HCtype =~ "UHCI-alt")
	    { $HC = "uhci"; }
	  elsif ($HCtype =~ "UHCI")
	    {	$HC = "usb-uhci"; }
	  elsif ($HCtype =~ "OHCI")
	    {	$HC = "usb-ohci"; }
	  else
	    {	$HC = $HCtype; }
		printf("/: Bus %s.Port %s: Dev %s, Class=root_hub, Drv=%s/%sp, %sM\n",
			     $bus, $port, $devnum, $HC, $maxchild, $speed );
    }
	elsif ($lastif ne $ifnum)
	  {
		$temp = $level;
		while ($temp >= 1)
		  {
			print "    ";
			$temp--;
		  }

		if ($nconfig ne "1")
		  { $temp = " Cfg $showconfig/$nconfig"; }
		else
		  { $temp = "";	}
		printf("|_ Port %s: Dev %s%s, If %s, Prod=%s, Class=%s, Drv=s%s, %sM, %s\n",
           $port, $devnum, $temp, $ifnum, $product, $showclass, $driver,
           ($maxchild == 0) ? "" : ("/" . $maxchild . "p"), $speed, $ids);
		$lastif = $ifnum;
	  }
	$product = "";
  }

close (DEVNUM);