#!/bin/sh # SPDX-License-Identifier: GPL-2.0-or-later # # Copyright (c) 2009 Greg Kroah-Hartman # Copyright (c) 2009 Randy Dunlap # Copyright (c) 2009 Frans Pop # # When checking with shellcheck ignore # "In POSIX sh, local is undefined." # every modern shell supports it. # shellcheck disable=SC3043 print_string() { file=$1 name=$2 if [ -f "$file" ]; then echo "S: $name=$(cat "$file")" fi } class_decode() { class=$1 # v4: in hex case $class in "00") echo ">ifc " ;; "01") echo "audio" ;; "02") echo "commc" ;; "03") echo "HID " ;; "05") echo "PID " ;; "06") echo "still" ;; "07") echo "print" ;; "08") echo "stor." ;; "09") echo "hub " ;; "0a") echo "data " ;; "0b") echo "scard" ;; "0d") echo "c-sec" ;; "0e") echo "video" ;; "0f") echo "perhc" ;; "10") echo "av " ;; "11") echo "blbrd" ;; "12") echo "bridg" ;; "dc") echo "diagd" ;; "e0") echo "wlcon" ;; "ef") echo "misc " ;; "fe") echo "app. " ;; "ff") echo "vend." ;; "*") echo "unk. " ;; esac } print_endpoint() { eppath=$1 read -r addr < "$eppath/bEndpointAddress" read -r attr < "$eppath/bmAttributes" read -r dir < "$eppath/direction" if [ "$dir" = "in" ]; then dir="I" elif [ "$dir" = "out" ]; then dir="O" elif [ "$dir" = "both" ]; then dir="B" fi read -r eptype < "$eppath/type" if [ "$eptype" = Control ]; then eptype="Ctrl" elif [ "$eptype" = Interrupt ]; then eptype="Int." fi read -r maxps_hex < "$eppath/wMaxPacketSize" maxps_hex="0x$maxps_hex" # Extract MaxPS size (bits 0-10) and multiplicity values (bits 11-12) maxps=$(($(printf "%4i*%s\n" $((maxps_hex & 0x7ff)) \ $((1 + ((maxps_hex >> 11) & 0x3)))))) read -r interval < "$eppath/interval" printf "E: Ad=%s(%s) Atr=%s(%s) MxPS=%4s Ivl=%s\n" \ "$addr" "$dir" "$attr" "$eptype" "$maxps" "$interval" } print_interface() { ifpath=$1 read -r ifnum < "$ifpath/bInterfaceNumber" read -r altset < "$ifpath/bAlternateSetting" read -r numeps < "$ifpath/bNumEndpoints" read -r class < "$ifpath/bInterfaceClass" read -r subclass < "$ifpath/bInterfaceSubClass" read -r protocol < "$ifpath/bInterfaceProtocol" if [ -L "$ifpath/driver" ]; then # v4: allow for no driver driver=$(readlink "$ifpath/driver") driver=${driver##*/} else driver="(none)" fi classname=$(class_decode "$class") printf "I: If#=%2i Alt=%2i #EPs=%2i Cls=%s(%s) Sub=%s Prot=%s Driver=%s\n" \ "0x${ifnum#0}" "${altset#0}" "0x${numeps#0}" "$class" "$classname" "$subclass" \ "$protocol" "$driver" for endpoint in "$ifpath"/ep_?? do if [ -L "$endpoint" ] || [ -d "$endpoint" ]; then # v4: verify endpoint exists print_endpoint "$endpoint" fi done } print_device() { local devpath=$1 local parent=$2 local level=$3 local count=$4 [ -d "$devpath" ] || return cd "$devpath" || return read -r busnum < "busnum" read -r devnum < "devnum" if [ "$level" -gt 0 ]; then port=$((${devpath##*[-.]} - 1)) else port=0 fi read -r speed < "speed" read -r maxchild < "maxchild" printf "\nT: Bus=%02i Lev=%02i Prnt=%02i Port=%02i Cnt=%02i Dev#=%3i Spd=%-4s MxCh=%2i\n" \ "$busnum" "$level" "$parent" "$port" "$count" "$devnum" "$speed" "$maxchild" read -r ver < "version" read -r devclass < "bDeviceClass" read -r devsubclass < "bDeviceSubClass" read -r devprotocol < "bDeviceProtocol" read -r maxps0 < "bMaxPacketSize0" read -r numconfigs < "bNumConfigurations" classname="$(class_decode "$devclass")" printf "D: Ver=%5s Cls=%s(%s) Sub=%s Prot=%s MxPS=%2i #Cfgs=%3i\n" \ "$ver" "$devclass" "$classname" "$devsubclass" "$devprotocol" \ "$maxps0" "$numconfigs" read -r vendid < "idVendor" read -r prodid < "idProduct" read -r bcddevice < "bcdDevice" revmajor="${bcddevice%%??}" revminor="${bcddevice##??}" printf "P: Vendor=%s ProdID=%s Rev=%s.%s\n" \ "$vendid" "$prodid" "$revmajor" "$revminor" print_string manufacturer "Manufacturer" print_string product Product print_string serial SerialNumber read -r numifs < "bNumInterfaces" read -r cfgnum < "bConfigurationValue" read -r attr < "bmAttributes" read -r maxpower < "bMaxPower" printf "C: #Ifs=%2i Cfg#=%2i Atr=%s MxPwr=%s\n" \ "$numifs" "$cfgnum" "$attr" "$maxpower" # There's not really any useful info in endpoint 00 #print_endpoint $devpath/ep_00 for interface in "$busnum"-*:?.* do print_interface "$devpath/$interface" done devcount=0 for subdev in "$busnum"-* do echo "$subdev" | grep -Eq "^$busnum-[0-9]+(\.[0-9]+)*$" \ || continue devcount=$((devcount + 1)) if [ -d "$devpath/$subdev" ]; then print_device "$devpath/$subdev" \ "$devnum" "$((level +1))" "$devcount" fi done } if [ ! -d /sys/bus ]; then echo "Error: directory /sys/bus does not exist; is sysfs mounted?" >&2 exit 1 fi for device in $(find /sys/bus/usb/devices -name 'usb*' -printf '%f\n' | sort -V) do print_device "/sys/bus/usb/devices/$device" 0 0 0 done