Jump to content

Help with udev rules to statically assign device names for USB-UARTS based on USB port ID


Recommended Posts

Posted

I have a bunch of UARTS attached to a USB hub, and I need to map them consistent to /dev/ttyUSB[x]

I've done some mapping of the ports. but I don't quite know what I'm doing.. any help with some example rules would be great..  

here's a sample 

 

/:  Bus 04.Port 1: Dev 1, Class=root_hub, Driver=ehci-platform/1p, 480M
    ID 1d6b:0002 Linux Foundation 2.0 root hub
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M
        ID 05e3:0608 Genesys Logic, Inc. Hub
        |__ Port 3: Dev 3, If 0, Class=Hub, Driver=hub/4p, 480M
            ID 05e3:0608 Genesys Logic, Inc. Hub
        |__ Port 4: Dev 4, If 0, Class=Vendor Specific Class, Driver=ch341, 12M
            ID 1a86:7523 QinHeng Electronics CH340 serial converter

 

Posted

It has been my experience, that enumeration order of USB to serial devices is largely consistent boot-to-boot. At, least if they're all the same device / driver, and you're not moving cables between connectors.

 

If that's not working, there's some udev examples HERE.

 

You can also have udev dump everything that's worth looking at, and grep'ing for the devices you're looking for.

 

udevadm info -e | less

 

If they're all the same device and you need unique names, I'd probably separate them via DEVPATH, which basically works out to the appropriate position in the bus hierarchy. I'd also suggest giving them descriptive symlinks instead of renaming the devices, and address your devices that way.

Posted

Hi,

I made a small detection script using expect. That will spit out the hosts it found per try and baudraute guesstimate. Afterwards it will query the USB port location using udevadm info and build udev rules on a name basis for the hosts it found on those ports.

It's not a generic solution, let me know if you are interested, I'll share when back at my workstation.

Groetjes,

Posted

Hi,

See below udevadm rule:

Spoiler

##
## __usb_serial
##

ACTION=="remove", GOTO="__usb_serial_end"
SUBSYSTEM!="tty", GOTO="__usb_serial_end"

ENV{ID_VENDOR_ID}!="067b",  GOTO="__usb_serial_ftdi"
ENV{ID_MODEL_ID}!="2303",   GOTO="__usb_serial_ftdi"

LABEL="__usb_serial_pl2303"

## these devices do not have a serial number
## assignment is done based on physical location: usb-port, usb-hub
## be sure to plug in all the usb dongles in the same positions, otherwise this assignment is messed up!

#[BEGIN] AUTOMATICALLY GENERATED CONTENT
ENV{ID_PATH}=="platform-3f980000.usb-usb-0:1.5.2:1.0",    SYMLINK+="serial/by-name/sinaspi"
ENV{ID_PATH}=="platform-3f980000.usb-usb-0:1.5.3:1.0",    SYMLINK+="serial/by-name/orangepi"
ENV{ID_PATH}=="platform-3f980000.usb-usb-0:1.5.1.2:1.0",  SYMLINK+="serial/by-name/pi2-01"
ENV{ID_PATH}=="platform-3f980000.usb-usb-0:1.5.1.3:1.0",  SYMLINK+="serial/by-name/pi2-02"
ENV{ID_PATH}=="platform-3f980000.usb-usb-0:1.5.4.1:1.0",  SYMLINK+="serial/by-name/nanopi0"
ENV{ID_PATH}=="platform-3f980000.usb-usb-0:1.5.1.4:1.0",  SYMLINK+="serial/by-name/pi2-03"
ENV{ID_PATH}=="platform-3f980000.usb-usb-0:1.5.4.2:1.0",  SYMLINK+="serial/by-name/nanopi1"
ENV{ID_PATH}=="platform-3f980000.usb-usb-0:1.5.1.1:1.0",  SYMLINK+="serial/by-name/kobol0"
ENV{ID_PATH}=="platform-3f980000.usb-usb-0:1.5.4.4:1.0",  SYMLINK+="serial/by-name/bluebox1"
#[END] AUTOMATICALLY GENERATED CONTENT

LABEL="__usb_serial_ftdi"

ENV{ID_VENDOR_ID}!="0403",  GOTO="__usb_serial_end"
ENV{ID_MODEL_ID}!="6015",   GOTO="__usb_serial_end"

## these devices have a serial number

ENV{ID_SERIAL_SHORT}=="DT03O6AD", SYMLINK+="serial/by-name/kobol0"
ENV{ID_SERIAL_SHORT}=="DJ00JULE", SYMLINK+="serial/by-name/bluebox1"
ENV{ID_SERIAL_SHORT}=="DJ00JDCX", SYMLINK+="serial/by-name/bluebox0"

LABEL="__usb_serial_end"

## EOF

 

And below bash snippet of how to generate it:

generate_udev_rule() { # <dev> <name>
    typeset _DEV="${1:?}"
    typeset _NAME="${2:?}"
    typeset _ID_PATH
    
    _ID_PATH="$( udevadm info --query property "${_DEV}" | sed -n '/^ID_PATH/ { s/^ID_PATH=//p; q; }' )"

    printf "ENV{ID_PATH}==\"%s\",      SYMLINK+=\"serial/by-name/%s\"\n" \
        "${_ID_PATH:?}" \
        "${_NAME:?}"
}

 

Hope that helps!

Groetjes,

configure-serial-tty

Posted

Hi, sorry, I forgot to mention that the configure-serial-tty script does require some target modification: it depends on the baudrate being fixed on the target, as sending <BRK> both triggers Magic SysRq and optional baudrate switch by *getty. To get this to work on my setup with highish successrate, I reconfigured the target's serial-getty configurations to only work on one baudrate.

 

See parts of my ansible task that sets that puppy up:

Spoiler

#...
    -   name: Determine serial-getty services
        shell: |
            systemctl list-units --type=service --state=active,inactive --full --no-pager --no-legend --plain "{{item}}@*" |
                while read _UNIT _DONTCARE
                do
                    echo "{{item}} ${_UNIT:-N/A}"
                done
        with_items:
        -   'serial-getty'
        -   'armbianmonitor'
        register: service_units
            
    -   name: Modify serial-getty console baudrate
        shell: |
            set -x
            _TIMESTAMP=`date -Isec`
            _TMP=`mktemp`
            _ITEM_STDOUT="{{item.stdout}}"
            echo "${_ITEM_STDOUT:-}" |
                while read _SERVICE _UNIT
                do
                    _UNITFILE=`systemctl show "${_UNIT:?}" | sed -n '/FragmentPath/ { s/^FragmentPath=//p; q; }'`
                    sed -n 's/^ExecStart=//p;' "${_UNITFILE:?}" > "${_TMP}"
                    read -r _CMDLINE < "${_TMP}"
                    eval set -- ${_CMDLINE:-}
                    OPTIND=2 # skip ARGV[0]
                    while getopts ':o:-:' OPT 
                    do
                        :
                    done
                    shift $(( ${OPTIND:-} - 1 ))
                    _BAUDRATES="${1:?}"
                    _BAUDRATE_NEW="${_BAUDRATES%%,*}"
                    if [ "${_BAUDRATE_NEW:-}" != "${_BAUDRATES:-}" ]
                    then
                        _BACKUP="${_UNITFILE:?}.${_TIMESTAMP:?}~"
                        echo "Making backup of '${_UNITFILE:-N/A}' to '${_BACKUP:-N/A}'."
                        cp -ab "${_UNITFILE:?}" "${_BACKUP:?}"
                        echo "Modifying '${_UNITFILE:-N/A}'."
                        ## Example:
                        ##   ExecStart=-/sbin/agetty -o '-p -- \\u' --keep-baud 1500000 %I $TERM          
                        sed -i -E "
                            /^ExecStart=/ {
                                h;
                                s/^[ \t]*[^#].*/#[DISABLED by Ansible] &/p;
                                g;
                                i #[BEGIN] Ansible managed
                                s/ *\<-s\>//;
                                s/--keep-baud[[:blank:]]//;
                                s/--extract-baud[[:blank:]]//;
                                s/${_BAUDRATES}/${_BAUDRATE_NEW}/;
                                a #[END] Ansible managed
                            }" "${_UNITFILE:?}"
                        echo "# diff -y -W\${COLUMNS:-} '${_UNITFILE:-}' '${_BACKUP:-}'"
                        echo "Changed!"
                        systemctl daemon-reload
                    fi
                done
            rm "${_TMP:?}"
        register: serial_getty_config
        changed_when: "'Changed!' in serial_getty_config.stdout"
        vars:
        -   __item: 'serial-getty'
        loop: "{{ service_units.results }}"
        when: 
        -   __item == item.item
#...

 

The ansible task shell script will try to parse the commandline to get the currently configured baudrates. It assumes the baudrates are sorted from high to low, i.e. 1500000,115200,38400,9600 for my NanoPi R2S and Helios64 boxen. It will replace the baudrates with the most-left it had parsed (which should be the highest baudrate). It also will disable the options to *getty that skip setting the baudrate explicitly (i.e. keep-baud and extract-baud).

 

Let me know if this worked out for you!

Groetjes,

Posted
11 hours ago, lanefu said:

There's consistency on the port ids of the hub im plugging into.  Ill add some more samples

 

lane@Ippon-LJ-M1:~$ ssh tritium-h5 lsusb --tree
lane@tritium-h5's password:
Permission denied, please try again.
lane@tritium-h5's password:
/:  Bus 09.Port 1: Dev 1, Class=root_hub, Driver=musb-hdrc/1p, 480M
/:  Bus 08.Port 1: Dev 1, Class=root_hub, Driver=ohci-platform/1p, 12M
/:  Bus 07.Port 1: Dev 1, Class=root_hub, Driver=ohci-platform/1p, 12M
/:  Bus 06.Port 1: Dev 1, Class=root_hub, Driver=ohci-platform/1p, 12M
/:  Bus 05.Port 1: Dev 1, Class=root_hub, Driver=ohci-platform/1p, 12M
/:  Bus 04.Port 1: Dev 1, Class=root_hub, Driver=ehci-platform/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M
        |__ Port 3: Dev 3, If 0, Class=Hub, Driver=hub/4p, 480M
            |__ Port 3: Dev 6, If 0, Class=Vendor Specific Class, Driver=ch341, 12M
            |__ Port 4: Dev 5, If 0, Class=Vendor Specific Class, Driver=ch341, 12M
        |__ Port 4: Dev 4, If 0, Class=Vendor Specific Class, Driver=ch341, 12M
/:  Bus 03.Port 1: Dev 1, Class=root_hub, Driver=ehci-platform/1p, 480M
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=ehci-platform/1p, 480M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=ehci-platform/1p, 480M

 

Posted

Hi @lanefu,

For your usecase, best to do the following:

for A in /dev/ttyUSB*
do
	(
		echo "${A:?}"
		udevadm info --query property "${A:?}" | egrep -- '^ID_(PATH|SERIAL_SHORT)=' 
	) | xargs
done

 

That will give you either the USB port location in the USB tree (ID_PATH) or any serial number (ID_SERIAL_SHORT) as provided by udev, ready to make rules with.

 

As I bought a lot of low-end USB-serial dongles, they do not have any serial - hence the use of the USB port location. I run the script (attached in earlier post) every time I change something in the hub, or when I plug the hub into another device.

 

Let me know if this helped you!

Groetjes,

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...

Important Information

Terms of Use - Privacy Policy - Guidelines