Entender como se cargan en el núcleo de linux los módulos (drivers) de los dispostivos hardware del sistema no es demasiado complicado, a la vez es muy interesante para poder resolver alguno de los problemas relacionados con ellos, como por ejemplo, que el módulo que se usa para comunicarse con un dispositivo no sea el óptimo. Normalmente hay varios módulos que pueden gestionar un mismo dispositivo, y a veces el que se carga de forma automática no es la mejor opción.
La información de los dispositivos de entrada del sistema se puede consultar en /proc/bus/input/devices:
$sudo less /proc/bus/input/devices
La información de los dispositivos USB del sistema, incluidos drivers cargados para su control se se puede consultar con la utilidad usb-devices:
$usb-devices
Los dispositivos hardware conectados al sistema tienen un directorio sysfs en /sys en el que se exporta la información de los atributos declarados por el dispositivo como un árbol de atributo-valor, cada nodo del árbol se corresponde con un fichero de texto plano (con el mismo nombre del atributo que contiene el valor del atributo). Posteriormente udev usará esta información para crear el nodo del dispositivo (device node) en /dev.
El sysfs es un sistema de archivos virtual gestionado por el kernel que tiene una estructura en árbol que replica la estructura de los buses del sistema.
Para el caso de un dispositivos USB conectado al bus x, puerto y, el sysfs del dispositivo estará en el nodo:
/sys/bus/usb/devices/x-y
El bus y el puerto del dispositivo USB lo podemos encontrar de muchas maneras:
- Con el comando lsusb.
- Mirando los logs del sistema
a) $dmesg -wH mientras conectamos y desconectamos el dispositivo
b) $sudo journalctl -b | grep usb, si el dispositivo está permanentemente conectado (ej: una pantalla táctil) y se detecta al arrancar el sistema
c) $sudo less /proc/bus/input/devices (atributo sysfs)
Dentro de este directorio tenemos tantos fichero de texto plano como propiedades, por ejemplo el archivo idVendor contiene el valor del código hex del fabricante del dispositivo.
Dado que todos los dispositivos tienen que tener una propiedad llamada idVendor, si conocemos este valor, podemos usar el siguiente comando para encontrar el sysfs del dispositivo. Como ejemplo para la pantalla táctil de mi portátil con idVendor = 03EB
$sudo grep 03eb /sys/bus/usb/devices/*/idVendor
Una vez que sabemos cual es el sysfs de un dispositivo podernos ver todas sus propiedades en un solo pantallazo, sin necesidad de ir al directorio e ir mirando los ficheros de propiedades uno por uno.
$sudo udevadm info --path=device_sysfs_path
ejemplo
$sudo udevadm info --path=/sys/bus/usb/devices/1-7
Puede suceder que el dispositivo tenga varias interfaces, en este caso el sysfs tendrá a su vez tantos subnodos como interfaces (1-7:1.0 y 1-7:1.1) en el caso de las dos interfaces. Además cada interfaz puede requerir un driver distinto, por lo que debe declarar un modalias distinto, así que dentro de los directorios de interfaz 1-7:1.0 y 1.1 habrá tantos subdirectorios como drivers se pueden usar para gestionar el dispositivo.
Al igual que antes, las distintas propiedades se pueden ver de una sola vez con:
$sudo udevadm info --path=<path to the interface or driver directory>
o, en el caso de dispositivos USB
$sudo lsusb -v | more
Una vez que sabemos el sysfs de un dispositivo (ex: /sys/bus/usb/devices/1-7) podemos ir allí y comprobar que seguramente tenga varias interfaces (un dispositivo puede hacer varias cosas normalmente).
Cada una de las interfaces tiene asociado un modalias, que servirá después para encontrar el módulo que va a encargarse de controlar el dispositivo
$cd /sys/bus/usb/devices/1-7
$find . -name modalias
./1-7:1.0/0003:03EB:8B03.0008/modalias
./1-7:1.0/modalias
./1-7:1.1/0003:03EB:8B03.0009/input/input30/modalias
./1-7:1.1/0003:03EB:8B03.0009/modalias
./1-7:1.1/modalias
Si volcamos el valor de un modalias podemos luego preguntar a modinfo por el módulo que le será asignado
$cat ./1-7:1.0/modalias
usb:v03EBp8B03d1032dc00dsc00dp00ic03isc00ip00in00
$modinfo usb:v03EBp8B03d1032dc00dsc00dp00ic03isc00ip00in00
La información de modinfo es muy completa y lo primero que aparece es el módulo (nombre y ubicación del fichero .ko)
Cuando se ejecuta depmod, el programa lee los MODULE_DEVICE_TABLE() de todos los módulos (cada driver publica la identificación del hardware que puede controlar y el tipo de hardware usb, hid etc) con toda esta información se crean los modalias (una especie de claves con comodines) para cada driver y se almacenan en el fichero modules.alias.
Usando la información del dispositivo en /sys , una serie de reglas internas del sistema ( modalias en /lib/modules/<uname -r>/modules.alias) y otras definidas por el usuario (udev rules), udev encuentra el módulo o driver para gestionar el dispositivo en cuestión, crea los nodos para el dispositivo en /dev y carga el módulo adecuado.
Los nodos (device nodes) del dispositivo son como nombres de ficheros que los programas de usuario del sistema usan para acceder al dispositivo físico a través del sistema operativo y el módulo del dispositivo. Udev se ejecuta automáticamente después de que el dispositivo se ha conectado y el sysfs del dispositivo se ha creado en /sys, pero depmod hay que ejecutarlo cada vez que se actualizan los módulos del sistema o se instala uno nuevo para regenerar las claves modalias.
Ejemplos de device nodes son:
/dev/ttyACM0 para un puerto serie RS232
/dev/lpt0 para una impresora
/dev/sda para una unidad de almacenamiento (disco duro o ssd)
/dev/input/mouse2 para un ratón
1- Un programador programa el módulo de un dispostivo , por ejemplo module_foo y lo construye (make)
2- El módulo se copia en el directorio: /lib/modules/<uname -r>/module_foo.ko
3- Después de copiarlo hay que ejecutar depmod para regenerar los modalias del fichero /lib/modules/<uname -r>/modules.alias a partir de las estructuras MODULE_DEVICE_TABLE() exportadas por cada módulo. Básicamente, en estas estructuras cada módulo declara la identificación del hardware que puede controlar y el tipo de hardware usb, hid etc. Ejemplo de entrada modalias en modules.alias para el módulo usbhid: usb:v*p*d*dc*dsc*dp*ic03isc*ip*in*
En este punto el kernel está listo para reconocer el nuevo dispositivo cuando se conecte a alguno de los buses del sistema.
4- El usuario conecta el dispositivo a uno de los buses del sistema (p.ej:el PCI-USB)
5- El sistema realiza el handshake con el dispositivo, le asigna un puerto libre en el bus y recibe del dispositivo el valor de una serie de atributos básicos como: tipo de hardaware, vendorId, ProductId etc)
6- El sistema crea un nodo sysfs para el dispositivo en /sys (ejemplo en: /bus/<bus_name>/devices/bus_id-port_id). Con la información obtenida del dispositivo en el handshake en el sysfs del dispositivo se crean subnodos modalias, ejemplo de nodo modalias en
/sys/bus/usb/devices/1-7/1-7:1.0/modalias: usb:v03EBp8B03d1032dc00dsc00dp00ic03isc00ip00in00
7- El sistema genera un evento que activa a udev. Udev accede al modalias del sysfs dispositivo y busca en modules.alias el módulo adecuado para gestionarlo y lo carga
ENV{MODALIAS}=="?*", RUN{builtin}+="kmod load $env{MODALIAS}"
además se crean los device nodes en /dev para que los programas del espacio de usuario puedan acceder al dispositivo a través de un
descriptor de archivo (ejemplos: /dev/ttyACM0, /dev/input/mouse1, /dev/input/event16)
Se pueden escribir udev rules a partir de la información del dispositivo para gestionar la carga del módulo y la creación del devnode.
Para escribir udevrules es útil poder acceder a las claves con los atributos del dispositivo, ejemplo:
sudo udevadm info --attribute-walk --name=/dev/input/event16
Nota final
Todo este proceso se puede seguir consultando los logs del sistema, principalmente:
sudo journalctl -b | grep <nombre_del_modulo> (en caso de que se cargue en el arranque)
cat /var/logs/Xorg.0.log | grep <nombre_del_modulo>
tail -f /var/logs/Xorg.0.log | grep <nombre_del_modulo> si lo que queremos es ver los registros en vivo al conectar y desconectar el dispositivo.
demesg -wH (en el caso de un módulo plug and play)
Forcing the system to load a specific module for a given device: enlace
Blacklisting modules in Linux: enlace
How to blacklist linux modules to keep them from loading: enlace
How to use udev for device detection and management in Linux: enlace
Como obtener y entender el modalias de un dispositivo (sysfs): enlace
Overview of device and module handling (linux from scratch): enlace
En este enlace se explica qué es un módulo y como se carga en el núcleo del sistema operativo: enlace
En este enlace se muestra como crear y cargar un módulo muy simple: enlace
Modalias a practical way to map stuff to hardware: enlace
https://wiki.archlinux.org/index.php/Modalias
https://wiki.archlinux.org/index.php/Kernel_module
http://www.reactivated.net/writing_udev_rules.html#example-printer
https://unix.stackexchange.com/questions/242546/how-to-get-bus-id-of-an-usb-device
http://www.reactivated.net/writing_udev_rules.html#terminology
https://wiki.archlinux.org/index.php/Multitouch_displays#Drivers
https://unix.stackexchange.com/questions/440263/how-to-load-my-custom-driver-module-on-usb-device-connected
https://askubuntu.com/questions/1301815/eturbotouch-does-not-work-on-xubuntu-focal-fossa
Encontar el sysfs de un dispositivo conociendo el vendor id: (ejemplo 03eb)
~sudo grep 03eb /sys/bus/usb/devices/*/idVendor
Encontrar la información del dispositivo para escribir una regla udev
$udevadm info --path={sysfs}