#!/usr/bin/perl # tlp-usblist - list usb device info with autosuspend attributes # # Copyright (c) 2025 Thomas Koch <linrunner at gmx.net> and others. # SPDX-License-Identifier: GPL-2.0-or-later package tlp_usblist; use strict; use warnings; # --- Constants use constant USBD => "/sys/bus/usb/devices"; # --- Modules use Getopt::Long; # --- Global vars my %usbdevices; my $verbose = 0; # --- Subroutines # Read content from a sysfile # $_[0]: input file # return: content / empty string if nonexistent or not readable sub catsysf { my $fname = "$_[0]"; my $sysval = ""; if ( open (my $sysf, "<", $fname) ) { chomp ($sysval = <$sysf>); close ($sysf); } return $sysval; } # Read device driver from DEVICE/uevent # $_[0]: (sub)device base path # return: driver / empty string if uevent nonexistent or not readable sub getdriver { my $dpath = "$_[0]"; my $driver = ""; if ( open (my $sysf, "<", $dpath . "/uevent") ) { # read file line by line while (<$sysf>) { # match line content and return DRIVER= value if ( s/^DRIVER=(.*)/$1/ ) { chomp ($driver = $_); last; # break loop } } close ($sysf); } return $driver } # Get drivers associated with USB device by iterating subdevices # $_[0]: device base path # return: driver list / "no driver" if none found sub usbdriverlist { my $dpath = "$_[0]"; my $driverlist = ""; # iterate subdevices foreach my $subdev (glob $dpath . "/*:*") { # get subdevice driver my $driver = getdriver ("$subdev"); if ( $driver ) { if (index ($driverlist, $driver) == -1) { if ($driverlist) { $driverlist = $driverlist . ", " . $driver; } else { $driverlist = $driver; } } # if index } # if $driver } # foreach $subdev if (! $driverlist) { $driverlist = "no driver"; } return $driverlist } # --- MAIN # parse arguments GetOptions ('verbose' => \$verbose); # Read USB device tree attributes as arrays into %usbdevices hash, indexed by Bus_Device foreach my $udev (grep { ! /:/ } glob USBD . "/*") { my $usbv = "(autosuspend not available)"; # get device id my $usbk = sprintf ("%03d_%03d", catsysf ("$udev/busnum"), catsysf ("$udev/devnum") ); # get device mode and timeout if ( length (my $ptimeout = catsysf ("$udev/power/autosuspend_delay_ms")) && length (my $pmode = catsysf ("$udev/power/control")) ) { if ( $verbose ) { # get device status my $pstatus = catsysf ("$udev/power/runtime_status"); # format: device mode, timeout, status $usbv = sprintf ("control = %-5s autosuspend_delay_ms = %4d, runtime_status = %-9s", $pmode . ",", $ptimeout, $pstatus); } else { # format: device mode, timeout $usbv = sprintf ("control = %-5s autosuspend_delay_ms = %4d", $pmode . ",", $ptimeout); } } # store formatted result in hash @{$usbdevices{$usbk}} = ($udev, $usbv); } # Output device list with attributes and drivers foreach (`lsusb 2> /dev/null`) { my ($bus, $dev, $usbid, $desc) = /Bus (\S+) Device (\S+): ID (\S+)[ ]+(.*)/; if (length ($bus) and length ($dev) and length ($usbid) ) { my $usbk = $bus . "_" . $dev; $desc =~ s/\s+$//; $desc ||= "<unknown>"; print "Bus $bus Device $dev ID $usbid $usbdevices{$usbk}[1] -- $desc (" . usbdriverlist($usbdevices{$usbk}[0]) . ")\n"; } } exit 0;