Disassembler

Artificial intelligence is no match for natural stupidity.
20února2012

Udev pravidla pro USB-to-RS232 převodníky


Mám deb-based linux s jehož pomocí občas hrnu nějaká data do jiných zařízení po COM portu. Jelikož základní deska počítače, na kterém linux běží, pochází již z tohoto století, nemá žádný nativní COM port. Co ale má, je kýbl USB portů, a tak tedy pro krmení používám USB-to-RS232 převodníky. Občas ale těch převodníků potřebuju mít zapojených více zároveň, a protože jsou všechna zařízení stejného typu, rád bych věděl, kam se mi které připojilo, respektive který /dev/ttyUSB končí v kterém portu. V ideálním případě bych ještě přál, aby se mi převodníky vždy připojily stejně.

Manažer z Dejvic


Linux ve vývojovém kernelu 2.5 přišel s poměrně kacířskou myšlenkou, nahradit „systém souborů“ zařízení devfs, hotplug, hotswap a nějaké další nesmysly jedním jediným nástrojem, navíc z větší části běžícím v userspace. Zrodil se device manager zvaný udev. Později, v kernelu 2.6.13 byl vylepšen a dnes ho můžeme bez potíží využít na všelijaké zhůvěřilosti spojené s logickými i fyzickými zařízeními. Jednou z jeho hlavních featur, kterou v tomto článku využiju, jsou pravidla, pomocí kterých je možno zachytávat hardwarové události a upravovat jejich reprezentaci v systému.

Hromadu takových implicitních a generických pravidel najdete v

/lib/udev/rules.d

Vás ale budou zajímat ty naprosto konkrétní, nalézající se v

/etc/udev/rules.d

Jedno takové pravidlo, automaticky vytvořené systémem, se zde již nalézá

root@machine: ~# cat /etc/udev/rules.d/70-persistent-net.rules
# This file was automatically generated by the /lib/udev/write_net_rules
# program, run by the persistent-net-generator.rules rules file.
#
# You can modify it, as long as you keep each rule on a single
# line, and change only the value of the NAME= key.

# PCI device 0x10ec:0x8168 (r8169)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:74:e4:08:1b:3b", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"

Je relativně snadno lidsky čitelné. Všechny kousky obsahující „==“ jsou podmínky, které musí být v uvedeném pořadí splněny, aby systém vykonal kousky zbývající, obsahující různé úpravy a pojmenování. V tomto případě pravidlo hledá síťovou kartu s adresou 00:74:e4:08:1b:3b, a pokud ji najde, pojmenuje ji jako eth0. Velmi podobná pravidla vytvoříme i pro naše USB-to-RS232 převodníky.

Ťuk ťuk. Kdo tam?


Z výše uvedeného příkladu tedy vyplývá, že musíme znát nějakou informaci, která je pro zařízení jedinečná, a na té celé pravidlo postavíme. Udev má samozřejmě nástroje i na zjišťování a dotazování zařízení. Pomocí utility udevadm se tedy udevu zeptáme, co nám o převodníku může povědět.

root@machine:~# udevadm info -a -n /dev/ttyUSB0

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:1d.0/usb2/2-1/2-1:1.0/ttyUSB0/tty/ttyUSB0':
    KERNEL=="ttyUSB0"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1:1.0/ttyUSB0':
    KERNELS=="ttyUSB0"
    SUBSYSTEMS=="usb-serial"
    DRIVERS=="ftdi_sio"
    ATTRS{latency_timer}=="1"
    ATTRS{port_number}=="0"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1:1.0':
    KERNELS=="2-1:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="ftdi_sio"
    ATTRS{bInterfaceNumber}=="00"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bNumEndpoints}=="02"
    ATTRS{bInterfaceClass}=="ff"
    ATTRS{bInterfaceSubClass}=="ff"
    ATTRS{bInterfaceProtocol}=="ff"
    ATTRS{supports_autosuspend}=="1"
    ATTRS{interface}=="FT232R USB UART"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1':
    KERNELS=="2-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="a0"
    ATTRS{bMaxPower}==" 90mA"
    ATTRS{urbnum}=="2912998"
    ATTRS{idVendor}=="0403"
    ATTRS{idProduct}=="6001"
    ATTRS{bcdDevice}=="0600"
    ATTRS{bDeviceClass}=="00"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="8"
    ATTRS{speed}=="12"
    ATTRS{busnum}=="2"
    ATTRS{devnum}=="2"
    ATTRS{devpath}=="1"
    ATTRS{version}==" 2.00"
    ATTRS{maxchild}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="FTDI"
    ATTRS{product}=="FT232R USB UART"
    ATTRS{serial}=="A400gJxW"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2':
    KERNELS=="usb2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="e0"
    ATTRS{bMaxPower}=="  0mA"
    ATTRS{urbnum}=="50"
    ATTRS{idVendor}=="1d6b"
    ATTRS{idProduct}=="0001"
    ATTRS{bcdDevice}=="0300"
    ATTRS{bDeviceClass}=="09"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{speed}=="12"
    ATTRS{busnum}=="2"
    ATTRS{devnum}=="1"
    ATTRS{devpath}=="0"
    ATTRS{version}==" 1.10"
    ATTRS{maxchild}=="2"
    ATTRS{quirks}=="0x0"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="Linux 3.0.0-16-server uhci_hcd"
    ATTRS{product}=="UHCI Host Controller"
    ATTRS{serial}=="0000:00:1d.0"
    ATTRS{authorized_default}=="1"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0':
    KERNELS=="0000:00:1d.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="uhci_hcd"
    ATTRS{vendor}=="0x8086"
    ATTRS{device}=="0x27c8"
    ATTRS{subsystem_vendor}=="0x8086"
    ATTRS{subsystem_device}=="0x4f4d"
    ATTRS{class}=="0x0c0300"
    ATTRS{irq}=="23"
    ATTRS{local_cpus}=="00000000,00000000,00000000,00000000,00000000,00000000,00000000,0000000f"
    ATTRS{local_cpulist}=="0-3"
    ATTRS{numa_node}=="-1"
    ATTRS{dma_mask_bits}=="32"
    ATTRS{consistent_dma_mask_bits}=="32"
    ATTRS{enable}=="1"
    ATTRS{broken_parity_status}=="0"
    ATTRS{msi_bus}==""

  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""

Uf. To je hodně informací. Na jednu stranu je fajn, že nám udevadm řekne i kolik je na převodníku šroubků a jak jsou dotaženy, ale kdo se má vyznat v tom, jaká informace je důležitá? Zapojme tedy další, zdánlivě úplně stejné zařízení, nechme si udevadmem vypsat informace i u něj a porovnejme rozdíly.

root@machine:~# udevadm info -a -n /dev/ttyUSB0 > usb0
root@machine:~# udevadm info -a -n /dev/ttyUSB1 > usb1
root@machine:~# diff usb0 usb1
8,9c8,9
<   looking at device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1:1.0/ttyUSB0/tty/ttyUSB0':
<     KERNEL=="ttyUSB0"
---
>   looking at device '/devices/pci0000:00/0000:00:1d.0/usb2/2-2/2-2:1.0/ttyUSB1/tty/ttyUSB1':
>     KERNEL=="ttyUSB1"
13,14c13,14
<   looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1:1.0/ttyUSB0':
<     KERNELS=="ttyUSB0"
---
>   looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-2/2-2:1.0/ttyUSB1':
>     KERNELS=="ttyUSB1"
20,21c20,21
<   looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1:1.0':
<     KERNELS=="2-1:1.0"
---
>   looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-2/2-2:1.0':
>     KERNELS=="2-2:1.0"
33,34c33,34
<   looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1':
<     KERNELS=="2-1"
---
>   looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-2':
>     KERNELS=="2-2"
42c42
<     ATTRS{urbnum}=="3213928"
---
>     ATTRS{urbnum}=="3277658"
53,54c53,54
<     ATTRS{devnum}=="2"
<     ATTRS{devpath}=="1"
---
>     ATTRS{devnum}=="3"
>     ATTRS{devpath}=="2"
62c62
<     ATTRS{serial}=="A400gJxW"
---
>     ATTRS{serial}=="A400ghHs"

Krom očekávaných rozdílů ve fyzickém umístění převodníku, máme na posledních třech řádcích přesně to, co potřebujeme. Sériové číslo zařízení, které je neměnné a je zapsáno přímo v zařízení. Právě to použijeme k jednoznačnému rozlišení v udev pravidlech.

Pravidla


K sestavení pravidla tedy použijeme onen dlouhý výpis informací, poskytnutý udevadmem. Informace a atributy jsou mezi jednotlivými zařízeními předávány a „probublávají“ vzhůru. Zařízení však některé údaje mohou přepsat, přidat nebo upravit a předat dál upravené, což můžete vidět třeba u údajů KERNELS nebo SUBSYSTEMS. Čím je tedy údaj ve výpisu výše, tím je pro naše pravidlo důležitější.

looking at device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1:1.0/ttyUSB0/tty/ttyUSB0':
    KERNEL=="ttyUSB0"
    SUBSYSTEM=="tty"
    DRIVER==""

Odsud si ukradneme SUBSYSTEM, čímž zajistíme, že se další podmínky nebudou vyhodnocovat, pokud linuxu podstrčíme zařízení jiného typu, než TTY. V „rodičovském zařízení“ nic moc zajímavého nemáme. Z dalšího rodiče bychom mohli použít ATTRS{interface}=="FT232R USB UART". Já ale žádná zařízení, která by se hlásila jinak nemám, takže název interface přeskočím a přejdu rovnou do dalšího rodiče, kde mám ATTRS{serial}=="A400gJxW". Žádný silnější rozlišovací znak už nemám, takže sériové číslo bude druhou podmínkou pro mé pravidlo. Po pravidle budu chtít, aby mi při připojení vytvořilo symlink, takže třetí podmínkou, vyčtenou z manuálu bude ACTION==“add“ a akcí SYMLINK+="nazev_prevodniku". Když obdobnou operaci provedu pro všechny mnou vlastněné převodníky, skončím s něčím takovým:

root@machine:~# cat /etc/udev/rules.d/71-persistent-tty.rules
SUBSYSTEM=="tty", ACTION=="add", ATTRS{serial}=="A400gHC1", SYMLINK+="rs232/zelena"
SUBSYSTEM=="tty", ACTION=="add", ATTRS{serial}=="A400gJF9", SYMLINK+="rs232/modra"
SUBSYSTEM=="tty", ACTION=="add", ATTRS{serial}=="A400ghHs", SYMLINK+="rs232/cervena"
SUBSYSTEM=="tty", ACTION=="add", ATTRS{serial}=="A400gJxW", SYMLINK+="rs232/zluta"
SUBSYSTEM=="tty", ACTION=="add", ATTRS{serial}=="A400gJPH", SYMLINK+="rs232/cerna"

Pak stačí jen přikázat udevu, aby si znovu načetl pravidla

service udev reload

a po připojení převodníků už nemusím koukat do dmesg a řešit, do kterého ttyUSB jsem se zrovna trefil, protože udev vše udělá za mě.

root@machine:~# ls -l /dev/rs232/
total 0
lrwxrwxrwx  1 root root   10 2012-02-20 11:50 cerna -> ../ttyUSB0
lrwxrwxrwx  1 root root   10 2012-02-20 10:33 cervena -> ../ttyUSB1
lrwxrwxrwx  1 root root   10 2012-02-20 10:33 modra -> ../ttyUSB4
lrwxrwxrwx  1 root root   10 2012-02-20 10:33 zelena -> ../ttyUSB2
lrwxrwxrwx  1 root root   10 2012-02-20 10:33 zluta -> ../ttyUSB3

Potřebujete-li další nápovědu, jiná pravidla či akce, RTFM. V tomto případě to důrazně doporučuji.