Tiny Core Linux

Tiny Core Extensions => TCE Q&A Forum => Topic started by: GNUser on August 26, 2024, 06:24:31 PM

Title: how do firmware extensions work without startup script?
Post by: GNUser on August 26, 2024, 06:24:31 PM
I've noticed that the firmware extensions (e.g., firmware-atheros.tcz, firmware-mediatek.tcz, etc.) do not have a startup script. However, device drivers (e.g., ath9k) are able to find firmware files early in the boot process without any problems.

In TCL, things that need to be available very early in the boot process are usually made available by an additional, custom initramfs. It seems I'm missing the magic sauce in the TCL boot process that ensures firmware extensions are loaded before device drivers need the firmware files.
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on August 26, 2024, 06:41:40 PM
Is the trick simply to make sure that the firmware extension (e.g., firmware-atheros.tcz) appears before the driver extension (e.g., wireless-KERNEL.tcz) in onboot.lst?
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on August 26, 2024, 08:07:54 PM
Hi GNUser
I don't have a precise answer for you, but I do make it a
point to place firmware extensions at the top of onboot.lst.

Two things I noticed:
1. dmesg will often show multiple attempts to load firmware.
2. /lib/udev  contains  firmware.sh.

This makes me suspect  udev  might be involved.
Title: Re: how do firmware extensions work without startup script?
Post by: curaga on August 27, 2024, 03:26:17 AM
The udev trigger for new modules on boot is delayed until after all extensions are loaded. So usually the order in onboot.lst isn't significant.
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on August 27, 2024, 09:59:45 AM
So usually the order in onboot.lst isn't significant.
I can confirm that. Putting firmware-mediatek.tcz (for example) at very bottom of onboot.lst has no ill effect--after boot process is complete, firmware has been loaded and device is operable.

The udev trigger for new modules on boot is delayed until after all extensions are loaded.
curaga, can you please help me see how this is the case? This is the sequence of events as best as I can tell: /init -> /sbin/init (Busybox init) -> /etc/inittab -> /etc/init.d/rcS -> /etc/init.d/tc-config

In tc-config, udev is started around line 107, but extensions don't get loaded until around line 557. Something seems to be happening after extensions are loaded that causes modules to go looking for firmware. What am I missing?
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on August 27, 2024, 10:27:00 AM
Hi GNUser
Check tc-config for:
Code: [Select]
/usr/bin/tce-setup "booting"tce-setup fires off udev if it detects /etc/sysconfig/newmodules.
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on August 27, 2024, 10:32:42 AM
Nice find, Rich. I almost have the complete picture now. Do you know what creates /etc/sysconfig/newmodules?
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on August 27, 2024, 10:36:24 AM
Do you know what creates /etc/sysconfig/newmodules?
I found it: It's /usr/bin/tce-load:

Code: [Select]
...
[ -n "`find /mnt/test/ -type d -name modules`" ] && MODULES=TRUE
...

update_system() {
if [ "$BOOTING" ]; then
[ "$MODULES" ] && sudo /bin/touch /etc/sysconfig/newmodules
So if tce-load loads an extension that contains modules, it creates the newmodules flag. The new modules flag causes tce-setup to fire off udev after all extensions have been loaded. Mystery solved. Thank you both very much.
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on August 27, 2024, 10:45:22 AM
Hi GNUser
Nice find, Rich. I almost have the complete picture now. ...
Yeah, there's a lot going on there. It can be tricky trying to follow it all.
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on August 30, 2024, 07:51:51 AM
Hi Rich. I did some simple experiments and it seems I'm missing a small piece of the puzzle.

I connected a wifi usb adapter that uses the Atheros AR9271 chipset.

First experiment (firmware extension loaded during boot):
    I add firmware-atheros.tcz to my onboot.lst. Then I reboot. When machine is done booting, I do ifconfig -a I can see wlan1 listed, so the usb adapter is working.

Second experiment (firmware extension loaded manually after boot):
    I remove firmware-atheros.tcz from onboot.lst. Then I reboot. After boot is complete, I open a terminal and run these commands:
Code: [Select]
$ dmesg | grep firmware # the failed firmware load here is not a surprise
[    0.071368] Spectre V2 : Enabling Restricted Speculation for firmware calls
[    2.880738] psmouse serio2: trackpoint: IBM TrackPoint firmware: 0x0e, buttons: 3/3
[   13.148438] platform regulatory.0: Direct firmware load for regulatory.db failed with error -2
[   13.233707] usb 2-1.2: Direct firmware load for ath9k_htc/htc_9271-1.4.0.fw failed with error -2
[   13.285943] usb 2-1.2: Direct firmware load for htc_9271.fw failed with error -2
[   13.295216] usb 2-1.2: no suitable firmware found!
[   13.295221] usb 2-1.2: ath9k_htc: Failed to get firmware htc_9271.fw
$ tce-load -i firmware-atheros
$ sudo depmod -a
$ sudo udevadm trigger
$ dmesg | grep firmware # why no new attempt at loading firmware?
[    0.071368] Spectre V2 : Enabling Restricted Speculation for firmware calls
[    2.880738] psmouse serio2: trackpoint: IBM TrackPoint firmware: 0x0e, buttons: 3/3
[   13.148438] platform regulatory.0: Direct firmware load for regulatory.db failed with error -2
[   13.233707] usb 2-1.2: Direct firmware load for ath9k_htc/htc_9271-1.4.0.fw failed with error -2
[   13.285943] usb 2-1.2: Direct firmware load for htc_9271.fw failed with error -2
[   13.295216] usb 2-1.2: no suitable firmware found!
[   13.295221] usb 2-1.2: ath9k_htc: Failed to get firmware htc_9271.fw
Then I run ifconfig -a there is no wlan1 in the output (obviously, since firmware has not been loaded onto the device). I must manually unplug the adapter and plug it back in for it to work.

Any idea what's the secret sauce in the boot process that re-detects usb devices after driver and firmware extensions have been loaded? (It's not udevadm trigger.)  I've looked in /etc/init.d/ as well as in /usr/bin/tce-setup and have not found an obvious answer.
Title: Re: how do firmware extensions work without startup script?
Post by: Paul_123 on August 30, 2024, 08:22:10 AM
That doesn't work, udev will find the usb device and tell the kernel to load the driver.  The driver is what loads the firmware.   The firmware must be loaded before (or during boot) the kernel device driver is loaded.

Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on August 30, 2024, 08:32:36 AM
The driver is what loads the firmware.   The firmware must be loaded before (or during boot) the kernel device driver is loaded.
I figured it's the driver that loads the firmware. But that doesn't explain everything either, because the following doesn't work :-\
Code: [Select]
$ tce-load -i firmware-atheros
firmware-atheros.tcz: OK
$ sudo modprobe -r ath9k_htc
$ sudo modprobe ath9k_htc
Just to clarify that all this is just an exercise in understanding. All my gear is working fine.
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on August 30, 2024, 08:38:43 AM
So there's no way for a firmware extension loaded after boot to help the user short of manually disconnecting and reconnecting the relevant device?
Title: Re: how do firmware extensions work without startup script?
Post by: Paul_123 on August 30, 2024, 09:20:54 AM
Don’t load the device drivers at boot either (in this case wireless-KERNEL.tcz).
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on August 30, 2024, 09:38:29 AM
Hi GNUser
Despite the firmware failure, does  lsmod  show that the
driver is still loaded? If it does, see if this works:
Code: [Select]
sudo modprobe -r ath9k_htc
tce-load -i firmware-atheros
sudo udevadm trigger
Title: Re: how do firmware extensions work without startup script?
Post by: curaga on August 30, 2024, 09:41:41 AM
modprobe -r sometimes fails if the driver is in use. Your chain of modprobe -r and modprobe should normally work. I suppose it could also be another driver that loads the firmware, IIRC ath9k had multiple component drivers.
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on August 30, 2024, 02:54:43 PM
Hi GNUser
Despite the firmware failure, does  lsmod  show that the
driver is still loaded? If it does, see if this works:
Code: [Select]
sudo modprobe -r ath9k_htc
tce-load -i firmware-atheros
sudo udevadm trigger
Hi Rich. Yes  lsmod  shows the driver still loaded. The above commands do not cause the firmware to get loaded.

Hi curaga. I tried modprobe -r followed by modprobe using all of the ath component drivers, no luck.

I'm guessing that in the particular case of usb devices it's the usb system that's requesting the driver to load the firmware?
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on August 30, 2024, 04:16:12 PM
cuaraga, I can confirm that modprobe -r is removing the modules as expected (according to lsmod).
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on August 30, 2024, 11:56:26 PM
For pci devices, the modprobe -r <driver>; modprobe <driver> strategy works just fine for firmware loaded after boot. It seems the modprobe strategy only fails in the specific case of usb devices.

In the special case of usb devices, user needs to either a) load firmware during boot (via onboot.lst) or b) disconnect+reconnect the device after loading the necessary firmware.

While it would be nice to simulate usb device disconnect+reconnect at software level, it seems non-trivial and more trouble than it's worth.

Thread is solved. Thank you all.
Title: Re: how do firmware extensions work without startup script?
Post by: CentralWare on August 30, 2024, 11:57:18 PM
@GNUser:

   Take a peek at /usr/local/tce.installed/firmware-atheros and see if there are additional "instructions" which may not being launched when the extension is loaded (-i) manually

   The firmware package is crammed with a good amount of compiler/source debris (looking at 14.x64 file listing (http://repo.tinycorelinux.net/14.x/x86_64/tcz/firmware-atheros.tcz.list)) however, you may need to simply probe the device chipset (sudo modprobe ar9271) if it's not already showing in lsmod (just a guess with limited intel) before you launch udevadm.  It's my guess that when you bring ar9271 online...  it likely sees ath#k as a dependency and brings that online as well.  See what happens?!  Again...  it's a total guess...  but looks like /usr/local/lib/firmware/carl9170fw contains most everything you'd need to trace it out yourself! :D
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on August 31, 2024, 11:01:16 AM
Hi GNUser
I did some playing around and came up with this:
Code: [Select]
#!/bin/sh

Debug="test"
# Uncomment the next line to turn on debugging messages.
Debug="echo"

# Vendor ID and product ID (8644:8003).
VID="8644"
PID="8003"

# Find the  modalias  file that contains our VID and PID.
MODALIS="$(find /sys -name modalias | xargs  grep "v""$VID""p""$PID" 2> /dev/null)"

if [ -n "$MODALIS" ]
then
"$Debug" "$MODALIS"
# Grab the path to modalias.
UEVENT="${MODALIS%/*}"
# Grab the device designation from the end of the path.
DEVICE="${UEVENT##*/}"
# Complete path to uevent file.
UEVENT="$UEVENT""/uevent"
"$Debug" "DEVICE=$DEVICE"
"$Debug" "UEVENT=$UEVENT"
fi

if [ -f "$UEVENT" ]
then
# Grab the driver name from uevent file.
DRIVER="$(grep "DRIVER" $UEVENT | cut -d '=' -f2)"
"$Debug" "DRIVER=$DRIVER"
fi

if [ -n "$DRIVER" ]
then
# Find the directory named for the driver.
BINDPATH="$(find /sys -type d -name "$DRIVER")"
# That directory should contain the unbind and bind files.
BINDFILE="$BINDPATH""/bind"
UNBINDFILE="$BINDPATH""/unbind"
"$Debug" "BINDPATH=$BINDPATH"
"$Debug" "BINDFILE=$BINDFILE"
"$Debug" "UNBINDFILE=$UNBINDFILE"
fi

if [ -e "$UNBINDFILE" ]
then
# Remove device.
echo "$DEVICE" | sudo tee "$UNBINDFILE" 1> /dev/null
fi

"$Debug" "Sleep ..."
sleep 3
"$Debug" "... Wake up"


if [ -e "$BINDFILE" ]
then
# Add device back.
echo "$DEVICE" | sudo tee "$BINDFILE" 1> /dev/null
fi
I don't have any USB Wifi devices, but I tested it with a thumb drive.
I did observe remove and add events using  udevadm monitor.
Set the   VID  and  PID  variables to match your wifi device.
A copy of the script is attached. Ask questions if something is not clear.
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 01, 2024, 03:55:46 PM
Hi, Rich. Thank you very much for the script.

Here is  lsusb  output for context:

Code: [Select]
bruno@x230:~$ lsusb
...
Bus 002 Device 003: ID 040d:3801 VIA Technologies, Inc.
...

Unfortunately, the script fails here:

Code: [Select]
MODALIS="$(find /sys -name modalias | xargs  grep "v""$VID""p""$PID" 2> /dev/null)"
Because the two relevant files for my usb device are these (notice no "modalias" in the name)...

Code: [Select]
/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2/uevent
/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2/idProduct

...and they do not contain anything like v$VIDp$PID:

Code: [Select]
$ sudo cat /sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2/uevent
MAJOR=189
MINOR=130
DEVNAME=bus/usb/002/003
DEVTYPE=usb_device
DRIVER=usb
PRODUCT=40d/3801/108
TYPE=255/255/255
BUSNUM=002
DEVNUM=003

$ sudo cat /sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2/idProduct
3801
Also, notice how VID is 040d but the uevent file contains 40d (missing leading zero). Why, oh why?

Anyway, inspired by your script, I created this variation...

Code: [Select]
#!/bin/sh
productId=3801 # get this from lsusb
productFile=$(find /sys -name 'idProduct' | xargs grep -l "$productId" 2>/dev/null)
device=$(basename $(dirname $productFile)) # e.g., device=2-1.2
sudo sh -c "echo $device >/sys/bus/usb/drivers/usb/unbind 2>/dev/null"
sudo sh -c "echo $device >/sys/bus/usb/drivers/usb/bind"
...and it's working great:

Code: [Select]
$ ifconfig -a wlan1
ifconfig: wlan1: error fetching interface information: Device not found
$ tce-load -i firmware-atheros
firmware-atheros.tcz: OK
$ ./USBReset.sh # instead of manually detaching+reattaching the device
$ ifconfig -a wlan1
wlan1     Link encap:Ethernet  HWaddr 00:12:7B:20:6B:A0 
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
Thanks for pointing me in the right direction with this!
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on September 02, 2024, 12:06:28 AM
Hi GNUser
Thanks for testing and the feedback.

... (notice no "modalias" in the name)...
I guess not all devices have aliases assign to them.

Quote
... Also, notice how VID is 040d but the uevent file contains 40d (missing leading zero). Why, oh why? ...
Now that you mention it, I recall running into that a couple of years ago.

Quote
... Anyway, inspired by your script, I created this variation...
A couple of notes:
1. idProduct is not unique if not coupled with idVendor.
2. Your search of the /sys dir may not be deep enough, possibly
   resetting the hub instead of the port.

Quote
... and it's working great: ...
Hey, no fair. That's what I was shooting for. ;D

Anyway, I reworked my script:
Code: [Select]
#!/bin/sh

Debug="test"
# Uncomment the next line to turn on debugging messages.
Debug="echo"

# Vendor ID and product ID (8644:8003).
VID="8644"
PID="8003"

# Strips leading zeros from variable. If variable is all
# zeros, variable will be set to a single zero.
while [ ${#VID} -gt 1 ] # ${#VID} returns string length of variable.
do
# If first character of variable is not "0", end loop.
[ ${VID:0:1} != "0" ] && break
# Remove first character from variable.
VID="${VID:1}"
done

# Strips leading zeros from variable. If variable is all
# zeros, variable will be set to a single zero.
while [ ${#PID} -gt 1 ] # ${#PID} returns string length of variable.
do
# If first character of variable is not "0", end loop.
[ ${PID:0:1} != "0" ] && break
# Remove first character from variable.
PID="${PID:1}"
done

# Find the  modalias  file that contains our VID and PID.
UEVENTS="$(find /sys -name uevent | xargs  grep "$VID""/""$PID" 2> /dev/null)"

if [ -n "$UEVENTS" ]
then
# Grab the last entry from $UEVENTS.
for UEVENT in $UEVENTS
do
UEVENT="$UEVENT"
done

"$Debug" "UEVENT=$UEVENT"
# Complete path to uevent file after stripping from :PRODUCT to end of line.
UEVENT="${UEVENT%:PRODUCT*}"
# Strip uevent file name from path.
DEVICE="${UEVENT%/*}"
"$Debug" "DEVICE=$DEVICE"
# Grab the device designation from the end of the path.
DEVICE="${DEVICE##*/}"
"$Debug" "UEVENT=$UEVENT"
"$Debug" "DEVICE=$DEVICE"
fi

if [ -f "$UEVENT" ]
then
# Grab the driver name from uevent file.
DRIVER="$(grep "DRIVER" $UEVENT | cut -d '=' -f2)"
"$Debug" "DRIVER=$DRIVER"
fi

if [ -n "$DRIVER" ]
then
# Find the directory named for the driver.
BINDPATH="$(find /sys -type d -name "$DRIVER")"
# That directory should contain the unbind and bind files.
BINDFILE="$BINDPATH""/bind"
UNBINDFILE="$BINDPATH""/unbind"
"$Debug" "BINDPATH=$BINDPATH"
"$Debug" "BINDFILE=$BINDFILE"
"$Debug" "UNBINDFILE=$UNBINDFILE"
fi

if [ -e "$UNBINDFILE" ]
then
# Remove device.
echo "$DEVICE" | sudo tee "$UNBINDFILE" 1> /dev/null
fi

"$Debug" "Sleep ..."
sleep 3
"$Debug" "... Wake up"


if [ -e "$BINDFILE" ]
then
# Add device back.
echo "$DEVICE" | sudo tee "$BINDFILE" 1> /dev/null
fi

I'd appreciate if you would take this version for a spin to
see if it works any better. I've attached a copy to this post.

Quote
... Thanks for pointing me in the right direction with this!
Thanks for providing an interesting puzzle. :)
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 02, 2024, 08:17:28 AM
Thanks for providing an interesting puzzle. :)
Only a hacker would say such a thing :)

Your revised script gets farther but fails to set DRIVER, without which it cannot figure out the crucial UNBINDFILE and BINDFILE:

Code: [Select]
$ ifconfig -a wlan1
ifconfig: wlan1: error fetching interface information: Device not found
$ tce-load -i firmware-atheros
firmware-atheros.tcz: OK
$ ./USBreset.sh
UEVENT=/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/uevent:PRODUCT=40d/3801/108
DEVICE=/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2
UEVENT=/sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/uevent
DEVICE=1-1.2
DRIVER=
Sleep ...
... Wake up
$ ifconfig -a wlan1
ifconfig: wlan1: error fetching interface information: Device not found
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 02, 2024, 01:00:53 PM
Hi Rich. Why not unbind+bind using the generic  /sys/bus/usb/drivers/usb/unbind  and   /sys/bus/usb/drivers/usb/bind?

That simplifies the script because once we have DEVICE we're basically done. Here is what the script could look like with this logic plus passing VID and PID as arguments:

Code: [Select]
#!/bin/sh

# Syntax: $ USBReset.sh <vendorId> <productId>
# Usage example: $ USBReset.sh

Debug="test"
# Uncomment the next line to turn on debugging messages.
Debug="echo"

# Vendor ID and product ID (e.g., 8644 8003).
VID="$1"
PID="$2"

# Strips leading zeros from variable. If variable is all
# zeros, variable will be set to a single zero.
while [ ${#VID} -gt 1 ] # ${#VID} returns string length of variable.
do
# If first character of variable is not "0", end loop.
[ ${VID:0:1} != "0" ] && break
# Remove first character from variable.
VID="${VID:1}"
done

# Strips leading zeros from variable. If variable is all
# zeros, variable will be set to a single zero.
while [ ${#PID} -gt 1 ] # ${#PID} returns string length of variable.
do
# If first character of variable is not "0", end loop.
[ ${PID:0:1} != "0" ] && break
# Remove first character from variable.
PID="${PID:1}"
done

# Find the  modalias  file that contains our VID and PID.
UEVENTS="$(find /sys -name uevent | xargs  grep "$VID""/""$PID" 2> /dev/null)"

if [ -n "$UEVENTS" ]
then
# Grab the last entry from $UEVENTS.
for UEVENT in $UEVENTS
do
UEVENT="$UEVENT"
done

"$Debug" "UEVENT=$UEVENT"
# Complete path to uevent file after stripping from :PRODUCT to end of line.
UEVENT="${UEVENT%:PRODUCT*}"
# Strip uevent file name from path.
DEVICE="${UEVENT%/*}"
"$Debug" "DEVICE=$DEVICE"
# Grab the device designation from the end of the path.
DEVICE="${DEVICE##*/}"
"$Debug" "UEVENT=$UEVENT"
"$Debug" "DEVICE=$DEVICE"
fi

# Remove device.
echo "$DEVICE" | sudo tee /sys/bus/usb/drivers/usb/unbind >/dev/null 2>&1

"$Debug" "Sleep ..."
sleep 3
"$Debug" "... Wake up"

# Add device back.
echo "$DEVICE" | sudo tee /sys/bus/usb/drivers/usb/bind >/dev/null

It works well:
Code: [Select]
$ ifconfig -a wlan1
ifconfig: wlan1: error fetching interface information: Device not found
$ tce-load -i firmware-atheros
firmware-atheros.tcz: OK
$ ./USBreset.sh 040d 3801
UEVENT=/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2/uevent:PRODUCT=40d/3801/108
DEVICE=/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2
UEVENT=/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2/uevent
DEVICE=2-1.2
Sleep ...
... Wake up
$ ifconfig -a wlan1
wlan1     Link encap:Ethernet  HWaddr 00:12:7B:20:6B:A0 
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on September 02, 2024, 10:24:54 PM
Hi GNUser
Hi Rich. Why not unbind+bind using the generic  /sys/bus/usb/drivers/usb/unbind  and   /sys/bus/usb/drivers/usb/bind? ...
There are a bunch of directories under  /sys/bus/usb/drivers/usb/.
I don't want to guess and reset something too high in the chain of
devices, like a hub, and resetting everything connected to it.

I changed my tactics and rewrote the script:
1. I'm using udevadm to find device path(s) for VID and PID.
2. Then use udevadm to find the driver name.
3. Find the driver name in /sys/.
      That provides  BINDPATH (plus BINDFILE and UNBINDFILE).
4. Sort the links in  BINDPATH  from longest to shortest.
5. Check the links to see which one points back to our device.
6. Strip the leading path information from the link (USBPORT).
7. Use  USBPORT , BINDFILE , and  UNBINDFILE  to reset the port.

Code: [Select]
#!/bin/sh

Debug="test"
# Uncomment the next line to turn on debugging messages.
Debug="echo"

# Vendor ID and product ID (8644:8003).
VID="8644"
PID="8003"

# Notes
# This is the driver property we are looking for.
# ID_USB_DRIVER=usb-storage
#
# 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
#
# Returns properties for the device listed in the path.
# udevadm info -q property -p /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-7

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.
DEVICES="$(comm -12 VIDs.txt PIDs.txt)"

[ -z "$DEVICES" ] && echo "No DEVICES found" && exit 1

for DEVICE in $DEVICES
do
ID_USB_DRIVER="$(udevadm info -q property -p "$DEVICE" | grep "ID_USB_DRIVER=" | cut -d = -f2)"
"$Debug" "DEVICE=$DEVICE    ID_USB_DRIVER=$ID_USB_DRIVER"
[ -n "$ID_USB_DRIVER" ] && break
done

[ -z "$ID_USB_DRIVER" ] && echo "No ID_USB_DRIVER found" && exit 1

BINDPATH="$(find /sys/bus/usb/drivers/ -type d -name "$ID_USB_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"

# Needed for embedding a newline character in "for ITEM in ..." loop.
# Without it, sort and cut won't be applied to all ITEMs in LIST.
NL="
"

# BINDPATH directory contains links to devices using that driver.
# ${#ITEM} returns string length for sorting later on.
# "/sys/${ITEM##*../}" converts ../../../../devices/... to /sys/devices/...
for ITEM in $(readlink "$BINDPATH"/*)
do
LIST=$LIST$(printf "%04d %s\n" ${#ITEM} "/sys/${ITEM##*../}")$NL
done

"$Debug" "LIST=$LIST"

# We want to sort longest to shortest and remove the string lengths.
CHOICES="$(echo "$LIST" | sort -r | cut -c 6-)"

"$Debug" "CHOICES=$CHOICES"

# Yhis tries to find and remove needle (CHOICE) from haystack (DEVICE).
# If it succeeds, we found the link to our device.
for CHOICE in $CHOICES
do
if [ "$DEVICE" != "${DEVICE/$CHOICE/}" ]
then
# Remove leading path information.
USBPORT="${CHOICE##*/}"
"$Debug" "USBPORT=$USBPORT"
break
fi
done


if [ -e "$UNBINDFILE" ]
then
# Remove device.
echo "$USBPORT" | sudo tee "$UNBINDFILE" 1> /dev/null
fi

"$Debug" "Sleep ..."
sleep 3
"$Debug" "... Wake up"


if [ -e "$BINDFILE" ]
then
# Add device back.
echo "$USBPORT" | sudo tee "$BINDFILE" 1> /dev/null
fi

# Clean up.
rm -rf VIDs.txt
rm -rf PIDs.txt

Hopefully, third times the charm.
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 03, 2024, 08:25:13 AM
Hi Rich. I tested your rewrite of USBReset.sh:
Code: [Select]
$ ./USBreset.sh
DEVICE=/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2    ID_USB_DRIVER=
No ID_USB_DRIVER found
...it would be nice to simulate usb device disconnect+reconnect at software level, it seems non-trivial...
I was on to something ;)
Title: Re: how do firmware extensions work without startup script?
Post by: mocore on September 03, 2024, 08:55:17 AM
While it would be nice to simulate usb device disconnect+reconnect at software level, it seems non-trivial and more trouble than it's worth.

 a bit of a tangent , idk how the lines are drawn (tbh) between "software level" and hw , or if you are all ready aware of this option 

any way this *  https://github.com/mvp/uhubctl -  uhubctl - USB hub per-port power control
 seams relevant (as a workaround with hw power off ctrl-ed via sw)

*(all though iv not had much luck finding hw(versions :() supporting this feature )
... perhaps if your lucky ur usb chip supports the feature !?!

or the issues provide some insight into relevant parts of linux usb system structure?! (longshoot)
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 03, 2024, 10:40:44 AM
a bit of a tangent , idk how the lines are drawn (tbh) between "software level" and hw
By "software level" I mean anything other than manually pulling out the usb device and pushing it back into the usb port.
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on September 03, 2024, 11:01:38 AM
Hi GNUser
Could you please post the result of this command:
Code: [Select]
udevadm info -q property -p /sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 03, 2024, 12:30:58 PM
Sure thing:

Code: [Select]
$ udevadm info -q property -p /sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2
BUSNUM=002
DEVNAME=/dev/bus/usb/002/003
DEVNUM=003
DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2
DEVTYPE=usb_device
ID_BUS=usb
ID_MODEL=VIA_USB2.0_WLAN
ID_MODEL_ENC=VIA\x20USB2.0\x20WLAN
ID_MODEL_ID=3801
ID_REVISION=0108
ID_SERIAL=VIA_TECH_VIA_USB2.0_WLAN_12345
ID_SERIAL_SHORT=12345
ID_USB_INTERFACES=:ff0000:
ID_VENDOR=VIA_TECH
ID_VENDOR_ENC=VIA\x20TECH
ID_VENDOR_ID=040d
MAJOR=189
MINOR=130
PRODUCT=40d/3801/108
SUBSYSTEM=usb
TYPE=255/255/255
USEC_INITIALIZED=2151963
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on September 03, 2024, 02:31:39 PM
Hi GNUser
I think I need a deeper look. How about this please:
Code: [Select]
udevadm info -a -p /sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 03, 2024, 02:43:50 PM
Code: [Select]
$ udevadm info -a -p /sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2':
    KERNEL=="2-1.2"
    SUBSYSTEM=="usb"
    DRIVER==""
    ATTR{configuration}==""
    ATTR{bMaxPacketSize0}=="64"
    ATTR{bDeviceClass}=="ff"
    ATTR{bcdDevice}=="0108"
    ATTR{bNumInterfaces}==""
    ATTR{bConfigurationValue}==""
    ATTR{manufacturer}=="VIA TECH"
    ATTR{bNumConfigurations}=="1"
    ATTR{authorized}=="1"
    ATTR{speed}=="480"
    ATTR{idProduct}=="3801"
    ATTR{urbnum}=="11"
    ATTR{devnum}=="6"
    ATTR{product}=="VIA USB2.0 WLAN"
    ATTR{maxchild}=="0"
    ATTR{bmAttributes}==""
    ATTR{bDeviceSubClass}=="ff"
    ATTR{bMaxPower}==""
    ATTR{rx_lanes}=="1"
    ATTR{removable}=="unknown"
    ATTR{idVendor}=="040d"
    ATTR{version}==" 2.00"
    ATTR{avoid_reset_quirk}=="0"
    ATTR{serial}=="12345"
    ATTR{bDeviceProtocol}=="ff"
    ATTR{tx_lanes}=="1"
    ATTR{ltm_capable}=="no"
    ATTR{devpath}=="1.2"
    ATTR{busnum}=="2"
    ATTR{quirks}=="0x0"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb2/2-1':
    KERNELS=="2-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{bDeviceClass}=="09"
    ATTRS{bcdDevice}=="0000"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{authorized}=="1"
    ATTRS{speed}=="480"
    ATTRS{idProduct}=="0024"
    ATTRS{urbnum}=="61"
    ATTRS{devnum}=="2"
    ATTRS{maxchild}=="6"
    ATTRS{bmAttributes}=="e0"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bMaxPower}=="0mA"
    ATTRS{rx_lanes}=="1"
    ATTRS{removable}=="unknown"
    ATTRS{idVendor}=="8087"
    ATTRS{version}==" 2.00"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{bDeviceProtocol}=="01"
    ATTRS{tx_lanes}=="1"
    ATTRS{ltm_capable}=="no"
    ATTRS{devpath}=="1"
    ATTRS{busnum}=="2"
    ATTRS{quirks}=="0x0"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb2':
    KERNELS=="usb2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{bDeviceClass}=="09"
    ATTRS{bcdDevice}=="0606"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{manufacturer}=="Linux 6.6.8-tinycore64 ehci_hcd"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{authorized}=="1"
    ATTRS{speed}=="480"
    ATTRS{idProduct}=="0002"
    ATTRS{urbnum}=="26"
    ATTRS{devnum}=="1"
    ATTRS{product}=="EHCI Host Controller"
    ATTRS{maxchild}=="3"
    ATTRS{bmAttributes}=="e0"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bMaxPower}=="0mA"
    ATTRS{rx_lanes}=="1"
    ATTRS{removable}=="unknown"
    ATTRS{idVendor}=="1d6b"
    ATTRS{interface_authorized_default}=="1"
    ATTRS{authorized_default}=="1"
    ATTRS{version}==" 2.00"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{serial}=="0000:00:1a.0"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{tx_lanes}=="1"
    ATTRS{ltm_capable}=="no"
    ATTRS{devpath}=="0"
    ATTRS{busnum}=="2"
    ATTRS{quirks}=="0x0"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0':
    KERNELS=="0000:00:1a.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="ehci-pci"
    ATTRS{power_state}=="D0"
    ATTRS{broken_parity_status}=="0"
    ATTRS{subsystem_device}=="0x1e2d"
    ATTRS{dma_mask_bits}=="32"
    ATTRS{vendor}=="0x8086"
    ATTRS{local_cpus}=="f"
    ATTRS{companion}==""
    ATTRS{class}=="0x0c0320"
    ATTRS{msi_bus}=="1"
    ATTRS{device}=="0x1e2d"
    ATTRS{local_cpulist}=="0-3"
    ATTRS{driver_override}=="(null)"
    ATTRS{d3cold_allowed}=="1"
    ATTRS{irq}=="19"
    ATTRS{revision}=="0x04"
    ATTRS{reset_method}=="af_flr pm"
    ATTRS{consistent_dma_mask_bits}=="32"
    ATTRS{ari_enabled}=="0"
    ATTRS{uframe_periodic_max}=="100"
    ATTRS{enable}=="1"
    ATTRS{subsystem_vendor}=="0x8086"

  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""
    ATTRS{waiting_for_supplier}=="0"
Title: Re: how do firmware extensions work without startup script?
Post by: mocore on September 04, 2024, 03:32:50 AM
By "software level" I mean anything other than manually pulling out the usb device and pushing it back into the usb port.

posted in case it of any use (i might be barking ... up the wrong tree)
 this builds on the previously linked uhubctl  https://github.com/octoprobe/usbhubctl and mentions navigating usb topology
so seamingly similar (atleast to me) domain 
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 04, 2024, 07:39:44 AM
Thanks, mocore. I'll check out usbhubctl as it sounds useful. That being said, I think this particular little problem is solvable without external utilities (i.e., using only what's available in TCL base).
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on September 05, 2024, 04:07:28 PM
Hi GNUser
OK, I'm back.

Here's the new cut:
Code: [Select]
#!/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:
Code: [Select]
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:
Code: [Select]
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.
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 05, 2024, 04:22:25 PM
You're back and you mean business!

It works like a charm now:

Code: [Select]
$ ifconfig -a wlan1
ifconfig: wlan1: error fetching interface information: Device not found

$ tce-load -i firmware-atheros
firmware-atheros.tcz: OK

$ ./USBReset.sh 040d:3801 -d -r
VID=040d    PID=3801
TMPLIST=0051 /sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2

DEVPATH=/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2

WalkTheSysPath(): Searching for SUBSYSTEM and DRIVER.

SEARCHPATH=/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2
SUBSYSTEM=usb
DRIVER=

SEARCHPATH=/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1
SUBSYSTEM=usb
DRIVER=usb

WalkTheSysPath(): Search complete.
SEARCHPATH=/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1
ENDPATH=2-1    SUBSYSTEM=usb    DRIVER=usb
BINDPATH=/sys/bus/usb/drivers/usb
BINDFILE=/sys/bus/usb/drivers/usb/bind
UNBINDFILE=/sys/bus/usb/drivers/usb/unbind
Sleep ...
... Wake up

$ ifconfig -a wlan1
wlan1     Link encap:Ethernet  HWaddr 00:12:7B:20:6B:A0 
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

Wow, you are definitely not one to settle for superficial/accidental solutions. This is impressively thorough.

I manage several machines from a distance, so I expect that over time I will find several applications for this script.

Many thanks, Rich.
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on September 05, 2024, 05:13:32 PM
Hi GNUser
You're back and you mean business! ...
Some of us are stubborn and too dumb to know when to quit. :)

Quote
... It works like a charm now: ...
How about that, sometimes being persistent does pay off.

Quote
... Wow, you are definitely not one to settle for superficial/accidental solutions. This is impressively thorough. ...
I did my best to cover all the bases. I added the option of using /dev/device for a
system with multiple USB devices with the same VID:PID on the off chance they
show up under /dev/.

Quote
... Many thanks, Rich.
You are quite welcome. Thank you for your patience. If you have
any problems or questions, let me know.
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on September 05, 2024, 05:29:19 PM
Hi GNUser
I had another idea I'd like to investigate.

Could you please post the result of this command using your MAC address:
Code: [Select]
tc@E310:~$ udevadm trigger -v -n -t devices -a address=00:13:20:c4:4a:20
/sys/devices/pci0000:00/0000:00:1e.0/0000:03:08.0/net/eth0
tc@E310:~$
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 06, 2024, 09:32:44 AM
Some of us are stubborn and too dumb to know when to quit. :)
Ha! I know how that goes. Sometimes I just want a quick fix to get a problem out of the way. But sometimes I want to throw an atom bomb on the problem so that I never have to rehash a quick fix again.

Could you please post the result of this command using your MAC address:
Code: [Select]
$ sudo udevadm trigger -v -n -t devices -a address=00:12:7b:20:6b:a0
/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2/2-1.2:1.0/net/wlan1
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on September 06, 2024, 09:48:30 AM
Hi GNUser
...
Code: [Select]
$ sudo udevadm trigger -v -n -t devices -a address=00:12:7b:20:6b:a0
/sys/devices/pci0000:00/0000:00:1a.0/usb2/2-1/2-1.2/2-1.2:1.0/net/wlan1
Excellent. When I have a chance later tonight or tomorrow, I will
add MAC address as another choice. That will guarantee a way
to distinguish between multiple devices with identical VID:PID
identifiers.
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 06, 2024, 10:08:37 AM
Note that when I first tried this command, I got no output. I had copied and pasted the MAC address that  ifconfig  outputted, which uses capital letters. It seems the  address=  option to  udevadm  requires letters in the MAC address to be lowercase.
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on September 06, 2024, 11:55:43 AM
Hi GNUser
Thanks for the heads up, but I'm afraid I already dealt
with that. If you look at where VIDPID gets processed
in  ParseCommand() , you'll find:
Code: [Select]
# Covert uppercase characters to lowercase.
VIDPID="$(echo "$ITEM" | busybox tr '[A-Z]' '[a-z]')"

In fact, it's even mentioned in the usage message:
Quote
/home/tc/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:
        /home/tc/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.

I planned on giving the MAC address the same treatment.

Now that you got me thinking about it, I'm going to make
a  ToLower()  function since I'm using it twice. :)
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on September 06, 2024, 01:44:35 PM
Hi GNUser
Version 0.2 is ready for testing:
Code: [Select]
#!/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.
#
# Special thanks to GNUser for his patience and help in testing and debugging.
#

# ------------------------------- Change Log -------------------------------- #
# Version 0.1 Sep 5,2024
# First working release.
#
# Version 0.2 Sep 6,2024
# In addition to /dev/device Or VID:PID, A third choice of MAC address is now
# available.
# Upper to lower case conversion was turned into function  ToLower().
#
# ----------------------------- End Change Log ------------------------------ #


# *************************************************************************** #
#
# 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.2"
# 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=""
# Set if user passes in a MAC address.
MACADDR=""
# 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()
{
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
# Skip if MACADDR already defined.
[ -n "$MACADDR" ] && 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
# Skip if MACADDR already defined.
[ -n "$MACADDR" ] && continue

# Covert uppercase characters to lowercase.
VIDPID="$(ToLower "$ITEM")"
# 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"
;;

[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]:[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
# Skip if MACADDR already defined.
[ -n "$MACADDR" ] && continue

# Covert uppercase characters to lowercase.
MACADDR="$(ToLower "$ITEM")"
"$Debug" "MACADDR=$MACADDR"
# The results are saved along with string lengths for sorting later.
for DEVPATH in $(udevadm trigger -v -n -t devices -a address="$MACADDR")
do
TMPLIST=$TMPLIST$(printf "%04d %s\n" ${#DEVPATH} "$DEVPATH")$NL
done
"$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

}
# --------------------------------------------------------------------------- #


# --------------------------------------------------------------------------- #
ToLower()
{
# Converts any uppercase characters in a string to lowercase.
# Usage:
# Dest="$(ToLower Src)"

echo "$(echo "$1" | tr '[A-Z]' '[a-z]')"
}
# --------------------------------------------------------------------------- #


# --------------------------------------------------------------------------- #
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  OR  MAC address.
The device will only be reset if the  -r  switch is included.

Usage:
${0##*/} /dev/device | VID:PID | XX:XX:XX:XX:XX:XX [ -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.

XX:XX:XX:XX:XX:XX Six 2 digit numbers separated by colons
  that make up the MAC address of a network
  interface device. Since these numbers 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

MAC address has been added as choice for specifying a device.
The help message has been updated to reflect that addition.

While the script found my network device from the (uppercase) MAC
address, it's not USB based so it quits after identifying  DEVPATH :
Code: [Select]
tc@E310:~/ReplaceMe$ ~/Scripting/USB_Reset/USBreset.sh -d 00:13:20:C4:4A:20
MACADDR=00:13:20:c4:4a:20
TMPLIST=0058 /sys/devices/pci0000:00/0000:00:1e.0/0000:03:08.0/net/eth0

DEVPATH=/sys/devices/pci0000:00/0000:00:1e.0/0000:03:08.0/net/eth0
Not a USB device
tc@E310:~/ReplaceMe$

Tag, you're it. ;D
Title: Re: how do firmware extensions work without startup script?
Post by: GNUser on September 07, 2024, 04:22:34 PM
Hi Rich. I wanted to get back to you sooner, but sometimes real life interferes with my linux addiction ;D

Version 0.2 does exactly what it says on the tin. At this point I can't think of any way of making this script better.

You remain a distinguished member of the wizards' guild.
Title: Re: how do firmware extensions work without startup script?
Post by: Rich on September 07, 2024, 09:21:06 PM
Hi GNUser
I wanted to get back to you sooner, but sometimes real life interferes with my linux addiction ;D ...
No worries, there was no rush. Besides, patience is one of my specialties.
Thanks for confirming it worked as expected.

Quote
... At this point I can't think of any way of making this script better. ...
But if you do, let me know. :)

(https://forum.tinycorelinux.net/index.php?action=dlattach;topic=27237.0;attach=6879)