linux平台实现USB虚拟总线驱动二(把驱动移植到Android系统)

时间:2024-04-04 19:32:33

by fanxiushu 2019-11-07  转载或引用请注明原始作者。
接上文,把上文中开发的驱动移植到android系统中来。
因为我身边没有Android系统的设备,很疑惑,目前确实没有。
因此只好在模拟器上打主意了, 我想模拟器跟真机其实差不多的,没道理模拟器能移植成功,而真机无法移植成功。
要移植驱动,需要重新编译Android系统的linux kernel源码,重新替换原来的kernel内核。
对真实机器来说,这叫刷机,而模拟器只需替换里边的某个文件,或者启动emulator模拟器指定 -kernel 参数。
因为linux内核是遵循GPL开源协议的,因此任何Android手机厂商,必须提供对应的linux内核源码,
因此不用担心无法获取对应手机的android系统的linux 内核源码来重新编译刷新。

首先,需要获取模拟器对应的 linux 内核源码,
可以使用git到https://android.googlesource.com下载,
但是android.googlesource.com中国境内无法访问,可以替换成 aosp.tuna.tsinghua.edu.cn 。
模拟器内核源码地址是 https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git , 取名叫goldfish,多怪异的名字。
里边有2.6,  3.4, 3.10, 3.18,4.4, 4.9, 4.14所有版本,可是我编译了全部版本,只找到4.4版本 能好用,
首先,2.6和3.4版本太老,在最新的模拟器中基本已经不再支持了,最低都是 3.10的内核版本,
3.10的我编译过ARM模式,可是把新编译的内核放到模拟器中,却是黑屏,3.10编译成ARM64,倒是可以编译成功而且能正常运行。
可是无法设置USB_ARCH_HAS_HCD,这个标志是内核支持USB主机端驱动,
要编译虚拟USB总线驱动,必须要开启 USB_ARCH_HAS_HCD支持。
无论怎么设置,最终编译成ARM64的时候,都会被自动重置 USB_ARCH_HAS_HCD的设置,没办法,只好放弃。
这时只有3.18以上的源码可以选择了,发现3.18也是同样的问题。于是只好从4.x以后版本打主意。
4.x以后的版本只能运行在Android8以上的模拟器中,而且模拟器只支持x86模式,不再支持arm了。
因此就只有编译成x86这一条路了。

以下是下载内核源码和编译器的过程:
准备一台linux系统,我使用的是CentOS7, 当然也可以使用utubun等其他系统,反正linux版本是够多的。
 git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git ;下载内核,
然后使用git branch -a查看版本,使用git checkout 检出对应版本。如果不想下载全部版本,比如只想下载 
android-goldfish-4.4-dev这个分支,使用如下命令:
git clone -b android-goldfish-4.4-dev --single-branch https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git
使用如下命令下载编译器
git clone https://aosp.tuna.tsinghua.edu.cn/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.6 ; (arm编译器)
git clone https://aosp.tuna.tsinghua.edu.cn/platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9 (arm64编译器)
本来是从上面的镜像网站中下载x86编译器,可是发现这个网站提供的x86编译器有问题,
搞的来编译的每个版本都不成功,开始还以为是哪里配置错误了。最后只好去其他网站下载了对应的x86编译器。
下载源码之后,进入对应的源码目录,假设编译成x86平台64位,编译器路径 /home/android/x86_64-linux-android-4.9,
编译器前缀是 x86_64-linux-android-, 如下调用
export PATH="/home/android/x86_64-linux-android-4.9/bin":$PATH
export CROSS_COMPILE=x86_64-linux-android-
export ARCH=x86_64
make x86_64_ranchu_defconfig
make -j8
就这么简单,只要不出问题,编译总是会成功的。
经常折腾嵌入式系统的编译裁剪的对这些流程应该会比较熟悉。

虽然看起来简单,可是一开始的时候,折腾的时间太长了,因为总是编译失败。
而且每次clone和checkout源码太慢了,浪费了太多时间。
网上的资料是很多,但都挺乱,而且基本都是挺老的资料。

 为了把我们的虚拟USB 总线驱动加入到内核中,需要做些配置。
在调用完 make x86_64_ranchu_defconfig,会在内核源码目录下出现  .config,打开这个文件。
确保CONFIG_USB_ARCH_HAS_HCD 已经开启,
同时因为我们这里是需要模拟 USB摄像头,因此需要支持UVC的,因此确保 
CONFIG_MEDIA_SUPPORT 和 CONFIG_MEDIA_USB_SUPPORT 开启,
否则因为没有UVC驱动,模拟的USB摄像头也没法被android系统识别。
 
然后就是把我们的驱动目录(假设目录名是usb_host)加入到 内核源码的 drivers目录的usb子目录中,
在usb_host里边添加Kconfig和Makefile, 在usb_host所在的usb目录中修改 Kconfig,添加 

source "drivers/usb/usb_host/Kconfig"

 修改usb目录中的Makefile ,添加
obj-$(CONFIG_XXX) += usb_host/
CONFIG_XXX对应中usb_host目录内的Makefile配置。
这些配置方式,其实可以借鉴drivers目录中其他驱动的配置参数,所以这里也不再过多介绍。

这些配置完成之后,make -j8, 编译完成,这样就把我们的虚拟USB总线驱动集成到内核中去了。
然后运行Android模拟器,把内核替换成自己编译的,会发现内核版本变化了,我们的驱动也在里边正常运行了。

接下来是应用层程序的编译,因为我们的驱动是把URB请求转发到应用层来处理,然后在应用层模拟USB摄像头数据再返回给驱动。
因此需要实现应用层程序。
首先需要从google官网下载NDK,
当然可以先直接下载 Android Studio开发环境,然后里边的Android模拟器,NDK等等,都可以自动下载。
如果要单独下载,可以如下:
wget https://dl.google.com/android/repository/android-ndk-r20-linux-x86_64.zip
下载的是 NDK r20的版本,
下载之后,配置好环境,就可以直接使用clang++编译代码了。

本来这里编译的代码,
应该以 so 动态库提供,并且提供 JNI 接口给上层java程序调用,
这样android的应用层开发者就可以调用java接口来操作我们的驱动了。
但是这里为了简单,就直接编译成可执行程序,使用adb登录到shell来执行。
本来以为这样就大功告成了,如下图以打印系统日志方式启动模拟器,
系统打印的日志表示已经找到了我们的虚拟USB设备,并且正确识别出了UVC摄像头。
linux平台实现USB虚拟总线驱动二(把驱动移植到Android系统)


但是Android系统却不能识别,Android上面的App程序也不能识别。
一开始的想法是只要内核正确识别出了UVC摄像头,Android应用层就能正确识别摄像头,而且还以为能代替前置或后置摄像头,
能被Android中自带的camera程序识别到。
仔细研究后发现,这基本是两回事,
前置和后置摄像头,与我们的USB 摄像头,内部处理方式都不同。
前置和后置摄像头,是Android提供一套标准的HAL层接口,各个厂商根据接口实现对应摄像头驱动,
然后Android在内部处理一系列数据交互,最后在java应用层提供一套 Camera API给app程序使用。
而USB摄像头,则是标准的在USB通讯上实现的UVC协议,Android并没有在java应用层提供对应的UVC接口。
通常需要我们自己实现,github上有个实现类似功能的开源库:
https://github.com/saki4510t/UVCCamera
 

而且这里还有一个问题,USB分为主机端(host)和设备端(device),Android系统默认是处于usb设备端的状态,
而我们开发的USB虚拟总线驱动是运行在主机端,因此必须让Android系统转换到USB主机端状态,否则还是无法正确识别UVC摄像头。
具体做法就是创建一个 android.hardware.usb.host.xml 文件,内容如下:
<permissions>
<feature name="android.hardware.usb.host"/>
</permissions>
然后把它复制到 /system/etc/permissions 目录下,重启Android系统,
经过这么一通折腾,这个虚拟USB摄像头才能被正常使用。

本来以为能用虚拟USB摄像头这种办法来替换Android系统中的默认摄像头,结果是愿望落空。
看来要替换Android中默认摄像头的视频,使用hook可能是最好的,
一种办法在掌握了摄像头部分HAL接口协议的之后,实现另一个驱动接口来代替它,从而HOOK住里边的视频数据。
或者干脆直接HOOK Android上层的Camera API接口, 因为Android系统的所有java app都是通过app_process启动的。
因此直接改写app_process程序,就能几乎HOOK住所有APP的行为,有个框架xPosed就是实现这样的功能的。
这个比起windows中的HOOK轻松得多,因为就只有一个非常单一的入口,就能HOOK所有APP进程。
而windows中的HOOK是非常零散的,往往要东一块西一块的查漏补缺。

目前并不清楚这个USB虚拟摄像头能在Android系统中有什么用处,既然是实现了,就当做来玩玩。
下图的app是从网上下载的,能识别UVC摄像头的app。图中已经在运行的USB摄像头,就跟上一篇文章摄像头效果一样。
linux平台实现USB虚拟总线驱动二(把驱动移植到Android系统)