Cannot connect to adb under linux because of option module
怎么解决linux下找不到adb的问题
哥们在Linux底下工作,从来没有发生过adb设备找不到的问题,手机一插上Linux机器,如果找不到adb,几乎可以肯定这个手机是坏的,或者usb线是坏的。
不像在Windows下,用adb连个手机有很多槽点。比如要装驱动,比如作为小厂商,我们只能自己改改谷歌给的官方驱动——这个是没过微软驱动认证的,在Windows7上以的Windows版本几乎装不上——反正我是不知道怎么装。
但最近突然很背的发现,我的Linux机器不认我的手机adb了,于是费了很大的劲查这个事情,刚刚终于“完美”解决了(加引号是因为好像是完美解决了😅)。
首先扯点跟这个问题无关的,关于Linux下用adb一般都会碰到的问题。那就是adb usb一插上来,默认这个设备是root用户的,我们作为普通用户登录,是无法操作这个设备的,除非你的系统里设置过了相关的规则,这个设备插上来的时候,系统允许你操作它你才能看到adb devices。
取决于你对设备有没有读权限和写权限,如果没有读权限,那你的adb devices里根本看不到这个设备相关的信息;如果有读权限,但是没有写权限,那么你会看到类似这样的输出:
d064b0b3 no permissions
如果发现这种情况,解决的办法是通过udev规则,让设备一插上来就改一下权限/所有者,让你可以操作它。我封装了一个fix-usb-permission的命令,用起来非常方便,你不妨一试。
另外有一个问题,是在锤子科技的早期,因为我们的usb设备厂商id没有加入到谷歌的代码库,导致必须在 ~/.android/adb_usb.ini
里写一个 0x29a9
,也就是我们的usb厂商id。
回到这回碰到的问题,不知从什么时候起,可能因为我经常升级我的debian系统,突然有一天就发现adb认不出手机来了,偶尔能认出来,但大多数时候都认不出来。
碰到这种情况,查问题时有几个固定的“三板斧”,如下:
- 看lsusb输出,能看到我的Smartisan T2手机:
Bus 001 Device 027: ID 29a9:701a
- 用dmesg看kernel的log:
[188316.830836] option 1-2:1.0: GSM modem (1-port) converter detected [188316.831869] usb 1-2: GSM modem (1-port) converter now attached to ttyUSB0
一下就发现问题了,原来把我的手机认成一个modem了。
解决问题的时候,试了好几个方案。注意到上面kernel log里第一行提到了 “option”,这是一个kernel usb的.ko模块,于是在google上搜,好像有人说可以通过 /etc/modprobe.d/
下的blacklist机制把它给禁用掉。我试了一下,把option禁用掉之后,我的usb键盘和鼠标都不能用了。
于是试了一下另一个方法,在udev规则里直接 rmmod option
,发现情况有好转,adb认出来的概率高了很多,但还是经常不成功,老是提示 option 模块被占用,不能卸载。
后来不小心试了一下 lsusb -t
命令,加上 -t
参数打印更多信息,结果发现当adb能用的时候, lsusb -t
的输出里显示是这样的:
|__ Port 2: Dev 28, If 0, Class=Vendor Specific Class, Driver=, 480M
当adb不能用被认成GSM modem的时候,输出是这样的:
|__ Port 2: Dev 27, If 0, Class=Vendor Specific Class, Driver=option, 480M
于是开始猛搜在linux下怎么强制改变usb的driver,最后没找到这个,但倒的确找到一个怎么强制“unbind”一个usb设备的驱动的方法(google “usb driver unbind”,第一条就是)。
然后写了如下的udev规则:
SUBSYSTEM=="usb", ATTR{idVendor}=="29a9", RUN+="/home/bhj/system-config/bin/rmmod-option" OWNER="bhj"
最后, rmmod-option
这个脚本是这样的:
#!/bin/bash me=$(readlink -f $0) if test ! -e "$me"; then me=$(readlink -f "$(which $0)") if test ! -e "$me"; then die "Can't find out about me" exit 1 fi fi if test "$RUNNING_WITH_MY_OWN_SESSION" != true; then echo "export RUNNING_WITH_MY_OWN_SESSION=true; $me" | at now fi ( echo '****************************************************************' set echo ) >> /tmp/$(basename $0).$UID if ! is-tty-io; then exec > /tmp/$(basename $0).txt 2>&1 fi set -x cd /sys/bus/usb/drivers/option # DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-2 devpath=$(basename $DEVPATH) ( for n in $(seq 1 20); do done_unbind=false for x in *😗; do if test ! -e "$x"; then break fi if test "${x%😗}" = "$devpath" -o "$(cat $x/interface)" = "ADB Interface"; then echo "$x" | tee unbind || true done_unbind=true fi done echo time: $n sleep .2 || true done bhj_cmd=$(cat <<EOF exec > /tmp/rmmod-option.\$UID 2>&1 set -x if my-adb devices?; then if my-adb devices? | grep "offline\$"; then ps-killall adb.fork-server fi fi if test "$ID_SERIAL_SHORT" = "\$(get-about-me adb-serial)"; then ask-to-sync-org fi EOF ) su - bhj -c "echo \"$bhj_cmd\"|at now" )&
你看的时候,估计会觉得非常奇怪,里面为什么不直接干活,而要用到at now把活扔到那个里面去干呢?这是因为udev启动的进程会被系统监控,如果不能在几秒钟内快速结束的话,系统就会强行将其停止。所以,在网上查了一番之后,发现了这个解决方案,把活扔到at daemon里去。后面还用到一次at,那个则是因为我如果发现自己的daily use手机插上来的话,会跟它同步一下我的org-mode日程,但过程中要用到sawfish的一些界面提示,然而如果在at daemon里,root身份导致即使已经su成bhj,也不可以连接我个人bhj用户的sawfish,因为每次一连就会去连root自己的sawfish,而那个是不存在的。为了解决这个问题,又在su bhj之后不直接干活,而是再次扔给at daemon,但这次的at daemon干活的时候已经不是root,而是bhj了。
最后,这个方法还是不很保险,偶尔还会发生认不出adb devices或adb devices显示offline的情况。如果有对此熟悉的朋友,还请不吝赐教。