Other opening usb device by serial number

Bill Evans at Mariposa

Active Member

Reaction score: 16
Messages: 114

Suppose I have two Kindles of the same model (but differing serial numbers, natch). I wish to open the particular Kindle that has a given serial number. The Kindles have been connected to the host in an arbitrary order. How do I open the right Kindle?

Looking at /dev/ugen* (or /dev/usb/*), I can march through them and use usbconfig to find the serial number. But I can't then mount that; I get the message
Code:
Block device required
So I'd like to figure out which /dev/d* corresponds to that file in /dev/usb/*. How do I do this?

One way might be to modify /etc/devd.conf so that I run a program (let's call it fred) every time a USB device is connected or disconnected. The idea would be, on connection, to look at /dev/d* to see what's been added, and then add a symbolic link somewhere under /dev/ which points from the serial number to that device. The problem is that when fred is run upon connection, the new device /dev/d* hasn't been added yet. Grr.

As a final desperate move, I can have fred run barney in the background and exit; barney would wait a second, look for a modified /dev/d*, and then generate the appropriate symbolic link. But I'd rather not do that.

Obviously I can't rely on /dev/msdosfs/Kindle, because who knows which Kindle that points to?

Is there a straightforward way to map from /dev/usb/* to /dev/d*?
 

wblock@

Administrator
Staff member
Administrator
Moderator
Developer

Reaction score: 3,630
Messages: 13,850

By d*, do you mean da*? diskinfo -v da0 shows the disk ID number. If those numbers are unique between the two devices, they can be used instead of the USB serial number.
 
OP
OP
Bill Evans at Mariposa

Bill Evans at Mariposa

Active Member

Reaction score: 16
Messages: 114

Yes, /dev/da* is where the new device name will be. But that alone won't help my program distinguish between the one Kindle and the other, because they can be connected to the host in arbitrary order (in time). I really, really need to map between /dev/da* and the serial number, and this will probably mean needing to map between /dev/da* and either /dev/ugen* or /dev/usb/*.
 
OP
OP
Bill Evans at Mariposa

Bill Evans at Mariposa

Active Member

Reaction score: 16
Messages: 114

Oh, wait. I read that too fast. Ignore my previous post until I can take a deep breath and go at it again. Sorry.
 
OP
OP
Bill Evans at Mariposa

Bill Evans at Mariposa

Active Member

Reaction score: 16
Messages: 114

Ok, here we go, for a Kindle at /dev/da0 ...
Code:
Script started on Sun May 22 09:27:07 2016
command: diskinfo -v /dev/da0
/dev/da0
        512             # sectorsize
        3240329216      # mediasize in bytes,(3.0G)
        6328768         # mediasize in sectors
        0               # stripesize
        0               # stripeoffset
        393             # Cylinders according to firmware.
        255             # Heads according to firmware.
        63              # Sectors according to firmware.
                        # Disk ident.


Script done on Sun May 22 09:27:07 2016
... and at /dev/da0s1:
Code:
Script started on Sun May 22 09:37:12 2016
command: diskinfo -v /dev/da0s1
/dev/da0s1
        512             # sectorsize
        3240321024      # mediasize in bytes (3.0G)
        6328752         # mediasize in sectors
        0               # stripesize
        8192            # stripeoffset
        393             # Cylinders according to firmware.
        255             # Heads according to firmware.
        63              # Sectors according to firmware.
                        # Disk ident.


Script done on Sun May 22 09:37:12 2016
The disk ID is evidently blank. The solution probably needs to be USB-aware.
 

wblock@

Administrator
Staff member
Administrator
Moderator
Developer

Reaction score: 3,630
Messages: 13,850

In that case, the devd(8) script that looks at USB serial numbers and creates a link based on them is one way to do it. I'd create a link with the serial number in the name to help identify it.
Code:
match "cdev" "da[0-9]+";
match "sernum" "123456";
action "ln -sf /dev/$device-name /dev/kindle-$sernum";
Untested. Might need another action to change the permissions on the new link.
 
OP
OP
Bill Evans at Mariposa

Bill Evans at Mariposa

Active Member

Reaction score: 16
Messages: 114

I'm really interested in a general way to access a file system via the USB serial number, and ran some tests using a Kindle, a couple of different brands of thumb drives, and an ancient Nikon camera whose USB storage looks like a SCSIoid drive (sweet).

I placed an attach statement and a detach statement at the end of /etc/devd.conf:

Code:
attach 0 {
        action "/usr/local/bin/tmp.wje attach $device-name $sernum";
};

detach 0 {
        action "/usr/local/bin/tmp.wje detach $device-name";
};
We can expect $sernum to be defined while executing an attach statement (but not a notify or detach statement) for a USB device. I tried using
Code:
match "sernum" ".+"
in the attach statement to weed out non-USB devices, but this caused the X mouse cursor to freeze on boot. Weird. Instead, the script checks: if the first command line parameter is "attach" and there are not exactly three command line parameters, the script simply exits. (Fewer parameters would mean that $sernum is empty.)

In an attach statement for a USB device, $cdev is undefined, and $device-name is "umass0" or similar, not "dasomething". There is no actual /dev/umass0; it is nonetheless useful for keeping track of $device-name, because when the device is unplugged, a similar detach statement (if present in the config file) will be executed with the same $device-name, which is useful because $sernum will not be defined (as it was with the attach statement).

To get the corresponding device name (like /dev/da0s1), simply go through the /dev directory, looking at all entries beginning with da[0-9] whose modification time is very recent. Expect two such entries, where the shorter entry is equal to the left-hand part of the longer entry. Use the longer entry.

Surreally, these two entries will not appear immediately, so wait a second or two before looking at the /dev directory. I didn't want to wait before returning control to devd, so the script starts with a fork(), and the parent returns immediately.

Write robust code. With the Kindle, I observed more events than necessary. If you get an attach for (for example) umass0, and umass0 is already attached, detach the old links and then reprocess. If you get a detach for (for example) umass0, and umass0 is not currently attached, drop the event on the floor.

Using these observations (or perhaps running tests yourself), you can write code that creates symbolic links from USB serial numbers to devices such as /dev/da0s1. With Linux we got this feature for free, and it's a minor pain to have to re-implement it. But that's a small price to pay to avoid Wayland and what's-it's-d.
 
OP
OP
Bill Evans at Mariposa

Bill Evans at Mariposa

Active Member

Reaction score: 16
Messages: 114

My previous post presents an idea that can work, pretty much. But it's ugly, because timing is involved, and if two USB devices are attached during the same second (which happens sometimes, but not always, at boot), problems arise. I've found a much more straightforward way to show the connection between a device and a USB serial number. Here's an example:
Code:
camcontrol inquiry da0 -S
This will send the device's serial number to standard output. The remaining work to create the desired links is just standard hacking, with no timing issues. devd can still be used as a trigger to update the links.
 
OP
OP
Bill Evans at Mariposa

Bill Evans at Mariposa

Active Member

Reaction score: 16
Messages: 114

The "standard hacking" to which I refer in the previous post reveals a thing or two.

First, don't use an attach or detach statement; use a notify statement.

Second, when you use a notify statement, wblock's idea of looking at $cdev is exactly right. Look at only those events where $type is "CREATE" or "DESTROY", and:
Code:
$cdev=~/^da[0-9]+./    # <--- perlspeak
Third, you'll actually get two such "CREATE" events when you plug in the USB device, and two such "DESTROY" events when you unplug it. That's fine. Just make sure your code handles this.

Proof of concept is in this Perl script. I'll be using it as my production code. It uses the camcontrol command I mentioned previously. Its comments show how to change /etc/devd.conf.
 
OP
OP
Bill Evans at Mariposa

Bill Evans at Mariposa

Active Member

Reaction score: 16
Messages: 114

I have updated the Perl script referenced in my previous post. As part of my testing, I inserted the USB stick containing the image for FreeBSD 10.3-RELEASE, amd64, and that worked fine; it contains only one partition. But then I inserted the USB stick for i386, which has three partitions, of which only the second one is mountable. But the Perl script effectively recognized only the final partition, which isn't useful.

The revised Perl script, when encountering a CREATE event for a partition, runs fstyp on that partition. If this returns non-zero, the event is dropped on the floor. Problem fixed.
 

aragats

Daemon

Reaction score: 433
Messages: 1,055

I created a devd config file with many entries for various USB-to-RS232 adapters, e.g.:
Code:
....
attach 100 {
        match "vendor"          "0x0403";
        match "product"         "0x6001";
        match "sernum"          "A9028Q17";
        action "ln -sf /dev/tty$ttyname /dev/ttyu6";
};
....
Everything works fine for unique serial numbers. Some cheap devices may share the same serial number.
Is there an "official" way to "increment" a symlink name in such cases to avoid duplicates/conflicts or I'll have to run a shell script which will take care of them?
Thanks for advises!
 

ralphbsz

Daemon

Reaction score: 865
Messages: 1,394

I don't know an official way. You'll have to do that yourself.

One option would be to write a "program" that can be used instead of ln, and which automatically changes/increase/modifies the link name if there is a conflict. That program doesn't need to be written in assembly or C; it could be a simple script in sh (shell), python, perl, your favorite scripting language).

But I wanted to give one warning: All the stuff being discussed here is dangerous, because of race conditions. If you are in a situation where programs happen to run slowly (maybe the system is dangerously overloaded), or devd reacts too slow, or USB devices are being attached and detached at insane speed (perhaps virtual ones on virtualized systems, or by automated testing switches), it is possible that confusion happens: By the time the external program called by the notify or action line actually runs, the USB devices could have been detached again, reattached elsewhere, and perhaps a different device attached at this device name! To really make this correct and atomic, it would be necessary to lock or hold open the device, to prevent the kernel from closing and destroying it, and pass control of the device to a program that continues running. Doing this correctly in the presence of rapid configuration changes requires a carefully designed hand-over-hand mechanism.
 

aragats

Daemon

Reaction score: 433
Messages: 1,055

Thanks, ralphbsz !
Regarding the race conditions: I don't worry much at the moment since this stuff is just for convenience: I have to plug/unplug several USB-to-Serial adapters many times every day, so just want to have persistent names for them. If something goes wrong I can always look in /dev and run usbconfig.
Here is a working script (still can be improved):
Code:
#/bin/sh

IDX=0
RES=1
ACTION=$1
TTYNAME=$2

test_idx() {
if [ ! -e /dev/ttyuA$IDX ] ; then
    ln -s /dev/tty$TTYNAME /dev/ttyuA$IDX
    RES=0
else
    RES=1
fi
}

if [ "x$ACTION" = "xattach" ] ; then
    while [ $RES != 0 ] ; do
        test_idx
        IDX=$((IDX+1))
    done
else
    for LINK in /dev/ttyuA* ; do
        ORG=`readlink $LINK`
        if [ ! -e $ORG ] ; then
            rm $LINK
        fi
    done
fi
 
Top