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认不出手机来了,偶尔能认出来,但大多数时候都认不出来。

碰到这种情况,查问题时有几个固定的“三板斧”,如下:

  1. 看lsusb输出,能看到我的Smartisan T2手机:
    Bus 001 Device 027: ID 29a9:701a
    
  2. 用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的情况。如果有对此熟悉的朋友,还请不吝赐教。