Android USB 框架 —— UsbHostManager

知其所以然

我们知道 USB 是主从模式(Host/Slave)的,有趣的是,Android 手机既可以被当做从设备连接到我们的 PC 被识别成为一个 USB 设备,也可以当做主设备识别外部的 USB 设备。这篇东东主要着眼于后者。

我之前一直有一个疑惑,那就是 Android 上层与 Linux 内核底层究竟是如何通讯的。之前我知道了可以通过 sysfs ,或者是通过 uevent 来打通内核与上层。现在,我学习了 Android USB Host 之后,发现了还有一种新的方法 —— Android JNI 。

首先声明,我对 Android 这一块知道的确实不多,所以下面的描述肯定是会有这样那样的错误,还待日后更加深入学习。

Android JNI

关于 Java JNI 的我写了另外的一篇东东,就不再赘述了。下面着重说明 Android JNI 与 Java JNI 的区别。

Android 中使用了一种不同于传统 Java JNI 的方法来定义其 native 函数。其中很重要的区别就是 Android 使用了一种 Java 和 C 函数的映射表数组,并在其中表述了函数的参数和返回值。这个数组的类型就是 JNINativeMethod ,定义如下:

typedef struct {
    const char *name;
    const char *signature;
    void *fnPtr;
} JNINativeMethod;

其中,第一个变量是 Java 中的函数名,第二个变量是一个描述返回值和参数的字符串,第三个是指向 native 实现的函数指针。

比较难以理解的第二个参数,具体可以参照这篇博客

发现 USB 设备流程

首先,我先使用自顶向下来说明一下整个流程:

在 UsbHostManager 中创建一个线程来监控(注意,这里用“监控”可是很有讲究的,因为当 Android 设备处于 Host 模式时,当有 USB 设备插拔时,首先相应的是 Linux 内核,然后内核再将设备变动的信息传递给上层,而 Android 根据这些信息在进行相应的动作,所以这里使用了“监控”二字。这跟 Android 设备处于 Slave 模式下的 UsbDeviceManager 是完全不同的)设备的连接状态。该线程执行的是 JNI 层的函数,而 JNI 层则是通过调用 libusb 库的函数,利用内核提供的 inotify 机制来监控 /dev/bus/usb 下文件的变动来判断是否有新的设备的插拔。下面的图示很好地展现了这种自顶向下的路线:

Alt text
(Source:Unboxing Android USB)

接下来,我再以自底向上的方法来详细阐述整个流程:

首先,USB 插入设备之后,内核会在 /dev/bus/usb 这个目录下建立代表该文件的设备文件入口(device file entry)(当然,个中细节还需要花时间深入理解,这里先假设其成立),然后 Android 上层使用 libusb 这个库来监控这个目录下的文件变动,其中用到的技术就是 inotify 。关于 inotify 我也写了一篇东东来简要地说明它,可以先去看看。下面将通过分析 libusb 的源代码(具体位置在 system/core/libusb/usbhost.c)来详细说明:

首先,先初始化 inotify ,获得文件描述符,此后的所有事件都是通过读取该文件描述符中的数据来判断:

struct usb_host_context *usb_host_init()
{
    struct usb_host_context *context = calloc(1, sizeof(struct usb_host_context));
    if (!context) {
        fprintf(stderr, "out of memory in usb_host_context\n");
        return NULL;
    }
    // 初始化,获取文件描述符
    context->fd = inotify_init();
    if (context->fd < 0) {
        fprintf(stderr, "inotify_init failed\n");
        free(context);
        return NULL;
    }
    return context;
}

这里要说明一下 struct usb_host_context 这个数据结构:

struct usb_host_context {
    int                         fd; // inotify 返回的文件描述符,通过读取其中的数据来判断事件
    usb_device_added_cb         cb_added; // 当有 USB 设备插入是调用的回调函数,在 JNI 层赋值,后面会提到
    usb_device_removed_cb       cb_removed; // 同上,当有 USB 设备拔出是的回调函数
    void                        *data; // 调用者给上面两个回调函数的参数
    int                         wds[MAX_USBFS_WD_COUNT]; // /dev/bus/usb 下各个子目录对应的 watch descriptor
    int                         wdd; // /dev 对应的 watch descriptor
    int                         wddbus; // /dev/bus 对应的 watch descriptor
};

然后,就是要开始添加要监控的目录了(即我们这里的 /dev/bus/usb):

void usb_host_run(struct usb_host_context *context,
                  usb_device_added_cb added_cb,
                  usb_device_removed_cb removed_cb,
                  usb_discovery_done_cb discovery_done_cb,
                  void *client_data)
{
    int done;

    done = usb_host_load(context, added_cb, removed_cb, discovery_done_cb, client_data);

    while (!done) {

        done = usb_host_read_event(context);
    }
} /* usb_host_run() */

主要的工作是在 usb_host_load() 中完成的:

int usb_host_load(struct usb_host_context *context,
                  usb_device_added_cb added_cb,
                  usb_device_removed_cb removed_cb,
                  usb_discovery_done_cb discovery_done_cb,
                  void *client_data)
{
    int done = 0;
    int i;

    context->cb_added = added_cb;
    context->cb_removed = removed_cb;
    context->data = client_data;

    /* watch for files added and deleted within USB_FS_DIR */
    context->wddbus = -1;
    for (i = 0; i < MAX_USBFS_WD_COUNT; i++)
        context->wds[i] = -1;

    /* watch the root for new subdirectories */
    // 如上面说说,wdd 是用来发现 /dev 下是否有子目录创建或者删除的
    context->wdd = inotify_add_watch(context->fd, DEV_DIR, IN_CREATE | IN_DELETE);

    watch_existing_subdirs(context, context->wds, MAX_USBFS_WD_COUNT);

    /* check for existing devices first, after we have inotify set up */
    done = find_existing_devices(added_cb, client_data);
    if (discovery_done_cb)
        done |= discovery_done_cb(client_data);

    return done;
} /* usb_host_load() */

其中的 watch_existing_subdirs() 就是添加 /dev/bus/usb 下的文件变动的监控的。函数不难理解,主要说明一下,就是 context->wds 这个数组:wds[0] 代表的是整个 /dev/bus/usb 下子目录的变动,而 wds[] 数组中其他的代表了真正设备的变动,因为内核会在每个新设备插拔之后在 /dev/bus/bus 这个目录下增加设备对应的文件如:/dev/bus/usb/001/001 等。

在完成了文件监控之后,接下来就可以来读数据了,调用了上面的 usb_host_read_event() ,这个函数重复的地方很多,下面我只选取了关键的 USB 设备的部分来说明:

for (i = 1; (i < MAX_USBFS_WD_COUNT) && !done; i++) {
    if (wd == context->wds[i]) {
        snprintf(path, sizeof(path), USB_FS_DIR "/%03d/%s", i, event->name);
        if (event->mask == IN_CREATE) {
            done = context->cb_added(path, context->data);
        } else if (event->mask == IN_DELETE) {
            done = context->cb_removed(path, context->data);
        }
    }
}

如果传来的数据是跟真正 USB 设备相关的(通过 watch descriptor 来判断,而且结合上面对 context->wds[] 数组的说明),那么就通过调用相应的回调函数来通知上层相应的设备变动。

好,底层的这部分说明完了,接下来就要 JNI 层了。JNI 层负责给调用上面提到的函数,指定合适的回调函数,具体位置在:frameworks/base/services/core/jni/com_android_server_UsbHostManager.cpp:

static void android_server_UsbHostManager_monitorUsbHostBus(JNIEnv* /* env */, jobject thiz)
{
    struct usb_host_context* context = usb_host_init();
    if (!context) {
        ALOGE("usb_host_init failed");
        return;
    }
    // this will never return so it is safe to pass thiz directly
    usb_host_run(context, usb_device_added, usb_device_removed, NULL, (void *)thiz);
}

在联系我们在 libusb 上提到的 usb_host_init()usb_host_run() ,有没有一种 connected 的感觉呢?

我们前面说过,JNI 层的函数是被 UsbHostManager 调用的,我们再来看看 UsbHostManager 中具体的监控进程,位置在 frameworks/base/services/usb/java/com/android/server/usb/UsbHostManager.java

public void systemReady() {
    synchronized (mLock) {
        // Create a thread to call into native code to wait for USB host events.
        // This thread will call us back on usbDeviceAdded and usbDeviceRemoved.
        Runnable runnable = new Runnable() {
            public void run() {
                monitorUsbHostBus();
            }
        };
        new Thread(null, runnable, "UsbService host thread").start();
    }
}

那么如何打通这两者呢?哈,我们上面提到的 Android JNI 就扮演着连接者的角色。看看下面的代码你就明白了:

static JNINativeMethod method_table[] = {
    { "monitorUsbHostBus", "()V", (void*)android_server_UsbHostManager_monitorUsbHostBus },
	......
};

至此,Android 发现外部 USB 设备的整个流程我们就说明完了。

EOF

Author: simowce

Permalink: https://blog.simowce.com/android-usb-framework--usbhostmanager/

知识共享许可协议
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。