Intro
Tal e como comentei nunha publicación anterior, teño un servidor Proxmox extraendo datos de 3 sensores de temperatura e humidade Xiaomi Mijia a través de BLE. Despois duns días funcionando correctamente, o sistema conxelouse xa que non recibía ningún dos paquetes enviados polos sensores.
Ao conectarme ao servidor, esperaba atopar a interfaz BT crasheados, quizais lanzando erros, pero ao executar hciconfig -a non apareceu absolutamente nada. O adaptador non estaba “DOWN” nin daba ningún erro, desaparecera por completo do SO como se se tivese sido desconectado fisicamente.
````
O bloqueo do firmware de Intel
Para descubrir que pasou, revisei os logs do sistema con dmesg | grep -i -E "bluetooth|btusb|hci0" | tail -30. Devolveu o seguinte:
[13208393.586823] Bluetooth: hci0: command 0xfc1e tx timeout
[13208398.336401] Bluetooth: hci0: Failed usb_autopm_get_interface: -16
O meu adaptador BT é un Intel AX210 (8087:0032). Cambieino porque a versión predeterminada que viña no meu mini PC era un Mediatek sen controladores adecuados para Linux. Así que cambiei ao de Intel, que se supuña que funcionaba e era barato (uns 10€). Simplemente pensei que era un erro aleatorio e, non sen certo medo, reiniciei o servidor. Ao final, foi unha boa proba para ver se todos os servizos estaban ben despregados. Todo foi ben, todos os servizos volveron levantarse e, o que é máis importante, os sensores volveron ler.
Bloqueándose, outra vez
A segunda vez que pasou isto tiven que investigar o problema, xa que estaba claro que non era un suceso aleatorio. Resulta que estes Intel AX210 teñen un problema coñecido no que a execución de escaneos BLE pasivos e continuos pode facer que o firmware pete. Ese erro -16 tradúcese como EBUSY, o que significa que a interface USB estaba completamente bloqueada. rmmod btusb non fixo nada para recuperalo.
Como reiniciar constantemente non é o ideal (a idea é que o mini-pc sexa un servidor, un reinicio cada poucos meses pode valer, pero non cada poucos días), atopei que se pode forzar un ciclo de enerxía do USB por software usando comandos de unbind/bind de PCI. Escribín un pequeno script en bash para comprobar se o adaptador estaba caído e, de estalo, reiniciar o controlador USB. Púxeno nun cron de 5 minutos e esperei a ver se funcionaba. Porén, esta aproximación non fixo nada para deter os bloqueos. Con todo, desta volta dmesg mostrou un novo patrón. O firmware estaba a inicializarse dende cero exactamente cada 199 segundos. Non estaba a fallar aleatoriamente, algo estaba poñéndoo a durmir. Linux ten unha función chamada Autosuspend de USB para aforrar enerxía. Debido a que un escaneo pasivo de BLE non mantén unha conexión activa cos sensores, o kernel asumiu que o adaptador Intel estaba inactivo e púxoo en suspensión. Cando TheengsGateway intentou lelo, o firmware espertou, entrou en pánico e petou de todo.
Solucionando o problema
Buscando información sobre o problema, atopei este fío en Github e, en particular, este comentario do usuario sashoalm que me apuntou na dirección correcta (ou máis ben me deu a solución directamente).
sashoalm descubriu que, unha vez que o dispositivo desaparece, para recuperalo necesitas volver a escanealo. Tamén indica que isto pode desconectar temporalmente algúns periféricos, pero para o meu servidor isto non importa xa que non hai USBs en uso. Basicamente executou:
for dev in /sys/bus/pci/drivers/[uoex]hci_hcd/*:*; do [ -e "$dev" ] && echo -n "${dev##*/}" | sudo tee "${dev%/*}/unbind" > /dev/null && echo -n "${dev##*/}" | sudo tee "${dev%/*}/bind" > /dev/null; done
e o adaptador volveu á vida. Despois de probar este enfoque e comprobar que funcionaba no host de Proxmox, decidín tentar evitar o problema.
Para isto, só tiña que dicirlle ao kernel que deixase de poñer o adaptador a durmir.
Primeiro, busquei o ID do fabricante e do produto usando lsusb (8087:0032). Despois, creei unha regra udev para desactivar a xestión de enerxía para ese dispositivo en particular:
cat > /etc/udev/rules.d/99-bluetooth-power.rules << 'EOF'
ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="8087", ATTRS{idProduct}=="0032", TEST=="power/control", ATTR{power/control}="on"
EOF
Tamén editei /etc/default/grub e engadín usbcore.autosuspend=-1 á liña GRUB_CMDLINE_LINUX_DEFAULT, seguido dun update-grub, por se acaso. Despois, un reinicio e listo.
No momento de actualizar este artigo, pasaron 33 días sen caídas do BT, así que parece que isto arreglou o problema!