5 maneras de conectar y desconectar dispositivos USB con teclado
En multitud de ocasiones, cuando estamos utilizando nuestro ordenador desconectamos un dispositivo USB (de forma segura, como tiene que ser) y, acto seguido, nos damos cuenta de que se nos olvidó copiar un archivo o que teníamos que asegurarnos de que un dato estaba en nuestra unidad. Para estos casos suele ser muy útil desconectar virtualmente los discos USB cuando trabajamos en remoto.
Al haber expulsado el dispositivo, ya no lo vemos en nuestra lista de dispositivos, pero el disco duro o pendrive sigue conectado por el puerto USB, y en muchas distribuciones hoy en día, al haber expulsado la unidad con seguridad, tampoco vemos el dispositivo y no podemos hacer mount desde nuestro terminal. La solución más rápida es desconectar el cable y volverlo a conectar, en algunos casos, ya sea por pereza de tener que levantarnos o porque estamos accediendo a un equipo que no tenemos delante y no hay nadie cerca, no podemos hacerlo.
Información sobre dispositivos USB
Antes de empezar, vamos a ver cómo obtener información acerca de dispositivos USB conectados al sistema. Para ello, podemos utilizar lsusb, que listará los dispositivos conectados ahora mismo. Pongo ejemplos de lo que obtengo en mi equipo ahora mismo, pero tal vez sea muy diferente de lo que obtengas tú:
$ lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 006: ID 8087:0a2a Intel Corp. Bus 001 Device 007: ID 046d:c52b Logitech, Inc. Unifying Receiver Bus 001 Device 005: ID 1a40:0101 Terminus Technology Inc. Hub Bus 001 Device 010: ID 125f:c93a A-DATA Technology Co., Ltd. 4GB Pen Drive Bus 001 Device 003: ID 04f2:b424 Chicony Electronics Co., Ltd Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Si queremos más información, podemos utilizar el modificador -t que nos mostrará una salida en forma de árbol con información sobre los módulos:
$ lsusb -t /: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/8p, 5000M /: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/16p, 480M |__ Port 4: Dev 3, If 0, Class=Video, Driver=uvcvideo, 480M |__ Port 4: Dev 3, If 1, Class=Video, Driver=uvcvideo, 480M |__ Port 5: Dev 10, If 0, Class=Mass Storage, Driver=usb-storage, 480M |__ Port 6: Dev 5, If 0, Class=Hub, Driver=hub/4p, 12M |__ Port 4: Dev 7, If 0, Class=Human Interface Device, Driver=usbhid, 12M |__ Port 4: Dev 7, If 1, Class=Human Interface Device, Driver=usbhid, 12M |__ Port 4: Dev 7, If 2, Class=Human Interface Device, Driver=usbhid, 12M |__ Port 9: Dev 6, If 0, Class=Wireless, Driver=btusb, 12M |__ Port 9: Dev 6, If 1, Class=Wireless, Driver=btusb, 12M
Si queremos mucha más información, podemos utilizar lsusb -v (la salida es muy grande), además podríamos, por ejemplo, saber la máxima potencia que se entrega al dispositivo, de la siguiente forma:
$ lsusb -v 2>/dev/null |egrep "^Bus|MaxPower" Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub MaxPower 0mA Bus 001 Device 006: ID 8087:0a2a Intel Corp. MaxPower 100mA Bus 001 Device 007: ID 046d:c52b Logitech, Inc. Unifying Receiver MaxPower 98mA Bus 001 Device 005: ID 1a40:0101 Terminus Technology Inc. Hub MaxPower 100mA Bus 001 Device 010: ID 125f:c93a A-DATA Technology Co., Ltd. 4GB Pen Drive MaxPower 480mA Bus 001 Device 003: ID 04f2:b424 Chicony Electronics Co., Ltd MaxPower 500mA Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub MaxPower 0mA
Otros comandos muy útiles son usb-devices, hwinfo, o, por ejemplo, si tenemos la ruta de un dispositivo (dentro de /dev/), podemos pedirle al sistema toda la información posible sobre el mismo y los subsistemas por los que tiene que pasar. Por ejemplo, si conectamos un disco duro USB, para nosotros poder ver utilizar el dispositivo, necesitamos un driver SCSI (por ser /dev/sdX), necesitamos también un driver de almacenamiento USB, que funciona a través del puerto USB, que pertenece a un hub, que está enchufado a un puerto PCI, entre otros sistemas intermedios. Todo eso podríamos verlo con
$ udevadm info --query=path --name=/dev/sdX --attribute-walk
o
$ udevadm info -a -n /dev/sdX
Si queremos aventurarnos, también podemos entrar en /sys/bus/usb y mirar todo lo que hay, veremos mucha información, pero afortunadamente los comandos anteriores clasifican toda esta información.
Privilegios y dispositivos
Para llevar a cabo esta tarea necesitaremos conocer qué dispositivo vamos a reconectar. Para ello, podemos ejecutar:
$ dmesg | tail [jue nov 24 19:50:04 2016] sd 7:0:0:0: Attached scsi generic sg3 type 0 [798339.431677] sd 7:0:0:0: [sdc] 15806464 512-byte logical blocks: (8.09 GB/7.54 GiB) [798339.431840] sd 7:0:0:0: [sdc] Write Protect is off [798339.431848] sd 7:0:0:0: [sdc] Mode Sense: 00 00 00 00 [798339.431988] sd 7:0:0:0: [sdc] Asking for cache data failed [798339.431996] sd 7:0:0:0: [sdc] Assuming drive cache: write through [798339.434157] sdc: sdc1 sdc2 [798339.446812] sd 7:0:0:0: [sdc] Attached SCSI removable disk [798360.808588] ISO 9660 Extensions: Microsoft Joliet Level 3 [798360.809353] ISO 9660 Extensions: RRIP_1991A
En esta salida, veremos que el dispositivo con el que trabajamos es sdc (sdc1 y sdc2 serían particiones dentro de ese disco). Para los ejemplos utilizaré este dispositivo, en tu caso tendrás que visualizar con cuál cuentas.
En los ejemplos que se detallan a continuación utilizaré sudo para ejecutar los comandos con privilegios de root. Aunque bastaría con tener un usuario con permiso suficiente. Si queremos ver los privilegios necesarios, basta con hacer ls al dispositivo:
$ ls -latr /dev/sdc brw-rw---- 1 root disk 8, 32 nov 24 19:50 /dev/sdc
Ahí vemos que el dueño es root y el grupo disk. Bastaría con tener un usuario perteneciente al grupo disk.
Método 1. Tratarlo como si fuera un CD/DVD
Es el más sencillo de todos. Seguro que si llevas años en GNU/Linux, cuando trabajabas con CD-ROM o DVD utilizabas el comando eject. Bien, eject servía para abrir el CDROM y eject -t sirve para cerrar la bandeja. Pues bien, si hacemos esto ante el dispositivo USB:
$ sudo eject -t /dev/sdc
El dispositivo debería aparecer como si lo hubiéramos conectado de nuevo.
Método 2. Desenchufado y enchufado virtual
En algunos sistemas (siempre y cuando el hardware lo soporte), cuando retiras el dispositivo USB con seguridad, se deja de alimentar el dispositivo y éste ya no aparece. Es lo mismo que cuando se hace:
udisksctl power-off -b /dev/sdc
En este caso, /dev/sdc es mi dispositivo, y con este comando simuló una desconexión energética virtual.
El problema es que ahora /dev/sdc no existe, es más, si miramos dmesg, obtendremos algo así:
$ dmesg | tail [281954.693298] usb 1-5: USB disconnect, device number 3
Entonces si probamos con el método de eject no funcionará. Nota: He destacado usb 1-5 y en breve veremos por qué.
Si trabajas en remoto, esto puede ser una buena idea. Imagina que tienes discos USB conectados para hacer copias de seguridad. Cuando estás realizando las copias sí que viene bien que el sistema sepa que hay discos conectados pero, cuando no estamos utilizándolos por un lado debemos ahorrar energía y evitar el desgaste de los discos, por lo que es mejor cortar la corriente, por otro lado, no queremos que aplicaciones maliciosas vean que existen estos discos para que no se infecten. (Sí, en GNU/Linux hay virus).
¿Cómo conectamos ahora la corriente?
Debemos hacer una llamada al puerto USB, para ello hay un proyecto llamado hubpower(enlazo a un fork del proyecto original porque aquí se arregla un bug que puede quitarle la corriente a más dispositivos y no sólo al que queremos). Hay más proyectos (como uhubctl), pero este no tiene dependencias para cuando vayamos a compilar, además es sólo un archivo hubpower.c.
Primero, lo compilamos,
$ gcc -o hubpower hubpower.c
Ahora, ¿recordáis los números en negrita del dmesg?, pues los vamos a utilizar, tendremos que desconectar el dispositivo y volver a conectarlo, así:
$ sudo ./hubpower 1:1 power 5 off Port 5 status: 0000 Power-Off $ sudo ./hubpower 1:1 power 5 on Port 5 status: 0100 Power-On
Si no nos detecta el dispositivo, podemos probar hacer:
$ sudo ./hubpower 1:1 bind Bind-driver request sent to the kernel
De esta manera, volveremos a ver nuestro dispositivo USB conectado.
Si no queremos un programa en C… lo tengo en perl
Un programa en C es duro para compilar y probar si lo que vamos a hacer es muy sencillo, así que podemos probar este pequeño port en 10 líneas hecho en perl:
#!/bin/perl require "sys/ioctl.ph"; $device="05"; open(my $usbdev, ">", "/dev/bus/usb/001/001"); $data = pack("H*", "23010800".$device."000000FFFFFF8813"); ioctl($usbdev, 0xC0185500, $data); $data = pack("H*", "23030800".$device."000000FFFFFF8813"); ioctl($usbdev, 0xC0185500, $data); close($usbdev);
Debemos respetar $device, el número de puerto (en mi caso era el 5), es un valor en hexadecimal, por lo tanto el 10 será A, 11 será B, 15 será F, 16 será 10… También tenemos que vigilar el dispositivo y el bus, a los que accedemos desde /dev/bus/usb/001/001, los números deben ir con ceros a la izquierda ya que estamos llamando a ese archivo.
Como vemos, la clave está en ioctl(), es una función que manipula parámetros de un dispositivo a partir de un fichero especial en el sistema de archivos. Entre los valores hexadecimales utilizados, encontramos 0xC0185500, una constante llamada USBDEVFS_CONTROL con la que enviaremos un comando de control al dispositivo USB, Los demás códigos pertenecen a la petición de desconexión y conexión (podéis encontrar más información en el programa hecho en C).
Método 3. Ocultando y mostrando el dispositivo
Otra manera de desconectar el dispositivo podría ser:
echo '1-5' |sudo tee /sys/bus/usb/drivers/usb/unbind
Y podremos recuperarlo haciendo:
echo '1-5' |sudo tee /sys/bus/usb/drivers/usb/bind
Este método, no causa una desconexión completa del dispositivo. Sólo hace que el sistema operativo no hable con él y muchos dispositivos, cuando un ordenador no quiere saber nada de ellos, se ponen en modo bajo consumo, ya que no les vamos a pedir nada.
Método 4. Autorización del dispositivo
Lo malo de este método es que en muchos sistemas se pueden desactivar temporalmente más dispositivos, que no son sólo el que necesitamos, sino que atacamos a todo un hub USB. Por ejemplo:
$ echo 0 | sudo tee /sys/bus/usb/devices/usb1/authorized $ echo 1 | sudo tee /sys/bus/usb/devices/usb1/authorized
Que, por supuesto lo podemos ejecutar todo seguido:
$ echo 0 | sudo tee /sys/bus/usb/devices/usb1/authorized ; echo 1 | sudo tee /sys/bus/usb/devices/usb1/authorized
Tenemos que tener cuidado, si hay más discos conectados al mismo puerto USB (y casi siempre en nuestros ordenadores varios puertos USB de los que vemos están conectados internamente a un hub, por lo que hay grupos de puertos con el mismo USB padre, por decirlo de alguna manera.
Método 5. Reinciar el subsistema USB
Si queremos reiniciar el subsistema USB. Esto es, refrescar todos los dispositivos USB, como desenchufarlos y enchufarlos todos, por un lado podemos descargar y recargar el módulo USB del kernel:
$ sudo modprobe -r ehci_hcd ; sudo modprobe ehci_hcd #Para USB2 $ sudo modprobe -r xhci_hcd ; sudo modprobe xhci_hcd #Para USB3
Aunque algunas distribuciones, las últimas versiones de Ubuntu y derivadas incluidas, tienen los módulos USB integrados y no pueden ser descargados. Por otro lado, puede que el sistema no nos deje descargarlos por estar en uso a causa de otros módulos (impresoras, almacenamiento, dispositivos de interfaz, etc), y si nos ponemos a descargar módulos y romper cosas tal vez nos toque reiniciar el equipo al final. Así que, de otra forma podemos hacer:
$ echo '0000:00:14.5' | sudo tee /sys/bus/pci/drivers/xhci_hcd/unbind $ echo '0000:00:14.5' | sudo tee /sys/bus/pci/drivers/xhci_hcd/bind
Para encontrar nuestro dispositivo, podemos hacer ls dentro de /sys/bus/pci/drivers/xhci_hcd, aparecerán varias cosas, tenemos que buscar una que se parezca a esto aaaa:bb:cc:dd.e. Puede que tu puerto USB no venga como xhci_hcd (USB3), sino que venga como ehci_hcd (USB2)