Hi GNUser
OK, I'm back.
Here's the new cut:
#!/bin/sh
# Script to reset a USB device written by Richard A. Rost September 4,2024
# This script attempts to replicate the behavior of unplugging/replugging
# a USB device by unbinding/binding the devices driver.
# *************************************************************************** #
#
# Default behavior for Debug and Reset can be changed here.
# -d command line switch turns on debugging messages.
Debug="test"
# Uncomment the next line to turn on debugging messages.
#Debug="echo"
# -r command line switch requests resetting the device.
Reset="No"
# Uncomment the next line to request resetting the device.
#Reset="Yes"
# *************************************************************************** #
# --------------------------------- Aliases --------------------------------- #
alias cp='busybox cp'
alias cut='busybox cut'
alias mkdir='busybox mkdir -p'
alias rm='busybox rm -rf'
alias tr='busybox tr'
# ------------------------------- End Aliases ------------------------------- #
# -------------------------------- Constants -------------------------------- #
# Current version.
VERSION="Version 0.1"
# Maximum number of command line parameters we accept.
MAXPARAMS=2
# Number of command line parameters passed in.
PARAMCOUNT=$#
# List of parameters passed in.
PARAMLIST=$@
# Needed for embedding a newline character in some commands.
NL="
"
# These are the properties we'll be searching for in WalkTheSysPath().
SEARCHTERMS="SUBSYSTEM= DRIVER="
# ------------------------------ End Constants ------------------------------ #
# -------------------------------- Variables -------------------------------- #
# Set if user passes in /dev/, such as /dev/sdg , /dev/sdg1 , or /dev/ttyUSB0.
DEV=""
# Set if user passes in VID:PID. VID and PID must each be 4 digits separated by
# a colon. VID and PID are hex and may contain letters. Both uppercase and
# lowercase are acceptable.
VIDPID=""
# VID and PID separated with colon removed.
VID=""
PID=""
# Variable used for temporary lists.
TMPLIST=""
# The path we traverse from end to beginning in search for its driver.
DEVPATH=""
# Working copy of DEVPATH used by WalkTheSysPath().
SEARCHPATH=""
# The text after the last slash in DEVPATH. Echoed into unbind/bind to
# identify which USB device to effect.
ENDPATH=""
# These are the properties we are scanning for. We want to find:
# [ SUBSYSTEM == "usb" ] && [ DRIVER != "" ]
SUBSYSTEM=""
DRIVER=""
# Driver directory.
BINDPATH=""
# That directory should contain the unbind and bind files.
BINDFILE=""
UNBINDFILE=""
# ------------------------------ End Variables ------------------------------ #
# -------------------------------- Functions -------------------------------- #
# --------------------------------------------------------------------------- #
ParseCommand()
{
# [ $PARAMCOUNT -gt $MAXPARAMS ] && Usage "To many parameters entered."
for ITEM in $PARAMLIST
do
case $ITEM in
-h) Usage ;;
-help) Usage ;;
--help) Usage ;;
-d) # Throw away -d to avoid Invalid option error. It's handled in Main.
;;
-r) # Request device reset.
Reset="Yes"
;;
"/dev/"?*)
# Skip if DEV already defined.
[ -n "$DEV" ] && continue
# Skip if VIDPID already defined.
[ -n "$VIDPID" ] && continue
DEV="$ITEM"
# The results are saved along with string lengths for sorting later.
for DEVPATH in $(udevadm trigger -v -n -t devices -p DEVNAME="$DEV")
do
TMPLIST=$TMPLIST$(printf "%04d %s\n" ${#DEVPATH} "$DEVPATH")$NL
done
"$Debug" "TMPLIST=$TMPLIST"
;;
[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])
# Skip if DEV already defined.
[ -n "$DEV" ] && continue
# Skip if VIDPID already defined.
[ -n "$VIDPID" ] && continue
# Covert uppercase characters to lowercase.
VIDPID="$(echo "$ITEM" | busybox tr '[A-Z]' '[a-z]')"
# Separate VID from PID and remove colon.
VID="$(echo "$VIDPID" | cut -c 1-4)"
PID="$(echo "$VIDPID" | cut -c 6-9)"
"$Debug" "VID=$VID PID=$PID"
# This won't work because it searches for "ID_VENDOR_ID=0644 OR ID_MODEL_ID=0200"
# udevadm trigger -v -n -t devices -p ID_VENDOR_ID=0644 -p ID_MODEL_ID=0200
udevadm trigger -v -n -t devices -p ID_VENDOR_ID="$VID" | sort > VIDs.txt
udevadm trigger -v -n -t devices -p ID_MODEL_ID="$PID" | sort > PIDs.txt
# Since we had to search for VID and PID separately, we check which
# results are common to both lists for an exact match. The results
# are saved along with string lengths for sorting later.
for DEVPATH in $(comm -12 VIDs.txt PIDs.txt)
do
TMPLIST=$TMPLIST$(printf "%04d %s\n" ${#DEVPATH} "$DEVPATH")$NL
done
# Clean up the files we created.
rm VIDs.txt PIDs.txt
"$Debug" "TMPLIST=$TMPLIST"
;;
*)
echo "Invalid option."
exit 1
;;
esac
done
}
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
ParseList()
{
# Parse list of DEVPATHs returned by udevadm and save the longest line.
# Sort our list, longest lines first, remove string length, and
# save the first entry (longest string).
DEVPATH="$(echo "$TMPLIST" | sort -r | cut -c 6- | head -n 1)"
"$Debug" "DEVPATH=$DEVPATH"
TMPLIST=""
# Make sure we got result.
[ -z "$DEVPATH" ] && echo "No DEVPATH found" && exit 1
# Make sure it's USB device.
[ ! $(echo "$DEVPATH" | grep -E '/usb[0-9]') ] && echo "Not a USB device" && exit 1
}
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
SetupBindPaths()
{
# Find the drivers directory.
BINDPATH="$(find /sys/bus/usb/drivers/ -type d -name "$DRIVER")"
[ -z "$BINDPATH" ] && echo "No BINDPATH found" && exit 1
# That directory should contain the unbind and bind files.
BINDFILE="$BINDPATH""/bind"
UNBINDFILE="$BINDPATH""/unbind"
"$Debug" "BINDPATH=$BINDPATH"
"$Debug" "BINDFILE=$BINDFILE"
"$Debug" "UNBINDFILE=$UNBINDFILE"
# Make sure BINDPATH contains correct ENDPATH link.
[ "$(realpath "$BINDPATH/$ENDPATH")" != "$SEARCHPATH" ] && echo "Bad BINDPATH" && exit 1
}
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
Usage()
{
[ "$1" != "" ] && echo "$1" && echo
echo "
$0 $VERSION
This script attempts to reset a USB device without an unplug/replug
sequence using a supplied /dev/device OR VID:PID. The device will
only be reset if the -r switch is included.
Usage:
$0 /dev/device | VID:PID [ -d -r ]
/dev/device Must point to a connected USB device.
VID:PID Two 4 digit IDs separated by a colon.
Since IDs are hex, they may contain
letters. Upper and/or lower case are OK.
-d Turn on debugging messages.
-r Request device reset.
Command line switches must be given separately, ie -d -r, not -dr.
"
exit
}
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
WalkTheSysPath()
{
# This walks up the DEVPATH by shortening it one entry at a time in an
# effort to located its driver.
"$Debug" "$NL""WalkTheSysPath(): Searching for SUBSYSTEM and DRIVER."
# SEARCHPATH is our working copy of DEVPATH.
SEARCHPATH="$DEVPATH"
# Copy the text after the last slash in SEARCHPATH.
ENDPATH="${SEARCHPATH##*/}"
# When ENDPATH begins with usbN* where N is a digit, we've hit the USB border.
while [ ! $(echo "$ENDPATH" | grep -E '^usb[0-9]') ]
do
"$Debug" "$NL""SEARCHPATH=$SEARCHPATH"
INFO="$(udevadm info -q property -p "$SEARCHPATH" 2> /dev/null)"
for ST in $SEARCHTERMS
do
# Search for our search term (i.e. SUBSYSTEM=).
T="$(echo "$INFO" | grep -E "^$ST")"
# Remove our search term (i.e. SUBSYSTEM=) from the result.
T="${T/$ST/}"
case $ST in
"SUBSYSTEM=")
# If the result matched our search term, save it.
SUBSYSTEM="$T"
"$Debug" "$ST$T"
;;
"DRIVER=")
DRIVER="$T"
"$Debug" "$ST$T"
;;
esac
done
if [ "$SUBSYSTEM" == "usb" ] && [ "$DRIVER" != "" ]
then
# We found what we were searching for.
"$Debug" "$NL""WalkTheSysPath(): Search complete."
"$Debug" "SEARCHPATH=$SEARCHPATH"
"$Debug" "ENDPATH=$ENDPATH SUBSYSTEM=$SUBSYSTEM DRIVER=$DRIVER"
return
fi
# Clear the properties for the next pass.
SUBSYSTEM=""
DRIVER=""
# Shorten the path and update ENDPATH for the next pass
SEARCHPATH="${SEARCHPATH%/*}"
ENDPATH="${SEARCHPATH##*/}"
done
"$Debug" "$NL""WalkTheSysPath(): Search failed."
}
# --------------------------------------------------------------------------- #
# ------------------------------ End Functions ------------------------------ #
# ---------------------------------- Main ----------------------------------- #
# Program starts here.
[ $PARAMCOUNT -eq 0 ] && Usage
for ITEM in $PARAMLIST
do
# We want this to happen before any debug messages are encountered.
# Turn on debugging messages if option -d is specified.
[ "$ITEM" == "-d" ] && Debug="echo" && break
done
ParseCommand
ParseList
WalkTheSysPath
SetupBindPaths
# See if device reset (-r) was requested.
if [ "$Reset" == "Yes" ]
then
if [ -e "$UNBINDFILE" ]
then
# Remove device.
echo "$ENDPATH" | sudo tee "$UNBINDFILE" 1> /dev/null
fi
"$Debug" "Sleep ..."
sleep 3
"$Debug" "... Wake up"
if [ -e "$BINDFILE" ]
then
# Add device back.
echo "$ENDPATH" | sudo tee "$BINDFILE" 1> /dev/null
fi
fi
exit
Help is included:
tc@E310:~$ ./Scripting/USB_Reset/USBreset.sh -h
./Scripting/USB_Reset/USBreset.sh Version 0.1
This script attempts to reset a USB device without an unplug/replug
sequence using a supplied /dev/device OR VID:PID. The device will
only be reset if the -r switch is included.
Usage:
./Scripting/USB_Reset/USBreset.sh /dev/device | VID:PID [ -d -r ]
/dev/device Must point to a connected USB device.
VID:PID Two 4 digit IDs separated by a colon.
Since IDs are hex, they may contain
letters. Upper and/or lower case are OK.
-d Turn on debugging messages.
-r Request device reset.
Command line switches must be given separately, ie -d -r, not -dr.
The default behavior is debugging messages and device reset are off.
Use -d and -r to turn them on. There are settings at the beginning of the
script if you want to have them default to on. There are no - switches to
turn them off.
This is what it looks like resetting a thumb drive with debugging messages on:
tc@E310:~$ ./Scripting/USB_Reset/USBreset.sh 8644:8003 -d -r > USBtest.txt
VID=8644 PID=8003
TMPLIST=0045 /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5
0089 /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block/sdg
0094 /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block/sdg/sdg1
DEVPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block/sdg/sdg1
WalkTheSysPath(): Searching for SUBSYSTEM and DRIVER.
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block/sdg/sdg1
SUBSYSTEM=block
DRIVER=
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block/sdg
SUBSYSTEM=block
DRIVER=
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0/block
SUBSYSTEM=
DRIVER=
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0/7:0:0:0
SUBSYSTEM=scsi
DRIVER=sd
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7/target7:0:0
SUBSYSTEM=scsi
DRIVER=
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host7
SUBSYSTEM=scsi
DRIVER=
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0
SUBSYSTEM=usb
DRIVER=usb-storage
WalkTheSysPath(): Search complete.
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0
ENDPATH=1-5:1.0 SUBSYSTEM=usb DRIVER=usb-storage
BINDPATH=/sys/bus/usb/drivers/usb-storage
BINDFILE=/sys/bus/usb/drivers/usb-storage/bind
UNBINDFILE=/sys/bus/usb/drivers/usb-storage/unbind
Sleep ...
... Wake up
TMPLIST shows the results udevadm returned for the VID:PID which I
prefixed with their string lengths so I could grab the longest one.
DEVPATH contains the chosen string.
WalkTheSysPath() asks udevadm for the properties of SEARCHPATH.
It's looking for a match on SUBSYSTEM == "usb" && DRIVER != ""
If it doesn't match, go up one level on SEARCHPATH and try again
This continues until a match is found, or fails if it reaches .../usb[0-9]
Once found, the bind path, bind files, and what to echo into them
are determined.
The new (and hopefully working) version is attached.