本文原是 «特斯拉也要CarPlay!» 中的一节,后又增加了 Z3TC 的部分,篇幅过长,遂修改后独立成文。

鼠标、U盘等设备本身并没有电池,它们被插到电脑上后,是电脑通过USB口给它们提供电力。同样的,手机插到电脑上,也是电脑给手机充电。那么如果把鼠标、U盘(通过转接线)插到手机上呢?这时手机就需要转“受”(外接设备,peripheral)为“攻”(主机,host),对外提供电力。实现这一转换的协议就是 USB On-The-Go (简称:OTG),转接线也必须得是支持OTG的,具体实现细节«USB charging, part 1: requirements»这篇文章介绍得很清楚,建议作为延伸阅读。

你可能注意到了问题,因为手机通常只有一个USB口,临时用一下OTG没啥问题,但如果要长时间使用OTG,那么USB被占用了没法充电,就不是个可持续方案。在处理自制车载屏幕时我就遇到了这个问题:如果连接了 CarKit 就没法充电,要充电就没法连接 CarKit,两者无法同时发生。对于手机而言,如果是“攻”模式就对外供电,如果是“受”模式就接受供电,而我们的需求是让它又“攻”又“受”————作为主机控制外接设备 + 同时接受外部充电。这时就需要 USB Accessory charging adaptors (ACA) 出场了,它解决的就是怎么同时给主机和外接设备供电的问题。ACA的实现细节在上述«USB charging»一文中也介绍清楚了。因为还要额外的供电,所以需要支持 ACA的 Y型 OTG线。我买的是下图这款,介绍页面上还列出了已知支持的设备名单,并写明并不是所有设备都支持。

Xperia Z5

搞定线材只是第一步,接下来需要让 Xperia Z5 支持 ACA 。

搜到了 XDA 上已经有大神 @nlra 搞定了这个事情:《USB OTG (host mode) + simultaneous charging!》。大致内容是:

因为骁龙换了USB 3芯片,驱动也变成了完全不同 DWC3。新的驱动没有实现 USB ACAs (就是我们需要的功能:OTG同时充电)。有人已经给 一加One 内核里的DWC3加上了 ACA,作者基于此(的重写版本)移植到了 Z5 内核并做了一些其它的改动:

  1. 开关做成了选项,可以在系统启动时或内核命令行里指定,也可以在启动后通过 sysfs 修改
  2. 支持ACA功能,可以OTG同时充电
  3. 绕过索尼对驱动的“特别”修改:把供电OTG线当成非OTG线。(不知道索尼为啥干这个,可能是为了降低进水风险?)
  4. 其它修改包括:禁用检测不到设备时的主机模式超时,非OTG供电Y型线上的USB设备也可以触发OTG,正常OTG情况下不需要手动触发“检查USB设备”

好消息是我的 5.1.1 固件版本 就是文章中提及的 32.0.A.6.20x,应该可以直接使用;而坏消息是大神只给出了修改后的 patch 代码,需要自己编译。

编译内核

对于如何编译 Android 内核我也是一窍不通,只能硬着头皮慢慢摸索。先看了下Xperia官方开发者网站上的编译指南,只了解了个大概流程,没有实质性帮助,因为:1. 写得比较简单,省略了很多步骤,应该是给有经验的开发者看的;2. 不确定是信息过旧还是过新,反正就是跟实际代码和工具链对不上,毕竟 2021年了再去搞 Android 5 的编译确实不合时宜。另外找到一篇博客《编译 Android 内核与修改 ramdisk》 介绍得就比较详细。

要编译内核就得先搞到源码,好在Xperia把代码都开源在 GitHub 上,找到对应代码库后翻一下分支列表就找到了对应 32.0.A.6.xxx 的代码,拉取下来。代码库中 README_Xperia 文档也介绍了比较详细的编译流程,对照之前的博客看,大致有了方向。下面介绍具体步骤,完整命令见文末附录。

准备交叉编译工具,文档中推荐使用 aarch64-linux-android-4.9 来避免问题。博客中也给出了这个版本的 git clone 地址,照着拉取下来并配置PATHCROSS_COMPILE环境变量。但我在后面编译过程中 make报错“multiple target patterns”,也没有打印出更详细错误信息,搜来搜去没有个结论,一时毫无头绪。StackOverflow 上的一个回答给出了debug方向,通过在报错行之前加上 $(info VAR="$(vmlinux-deps)") 打印出具体错误,发现是没有 /usr/bin/aarch64-linux-gnu-gcc。可我的 aarch64-linux-android-4.9 就是从 android.googlesource.com 上拉取的,怎么可能会没有呢?慢着,现在都 Android 11 了,当年 Android 5 的编译工具链可能早就变了,如果拉取的是 master 应该是对不上的。找到编译工具对应 Android 5.1.1 的tag android-5.1.1_r37,拉取下来检查下 bin 里面的文件,果然就有了 aarch64-linux-gnu-gcc。这个结论也记录在 StackOverflow 上,希望能给人有所启发。

准备 mkbootimg 用于生成 boot.img。我这里使用的是 mkbootimg_tools,集成了解包和打包工具,输入和输出都通过文件(夹)管理,使用起来比 mkbootimg 更方便。

准备 ramdisk.img 文件。不需要自己编译,下载个现成的 boot.img(版本需要一致),使用 mkbootimg_tools 工具解包得到一个包含 ramdisk.img、dt.img、config 等文件的文件夹,先放着,后面组装时可以直接使用。

下载前文 XDA 帖子中给出的 patch 文件。进入内核源码目录。应用 patch,得到修改后的DWC3驱动。配置 ARCHKBUILD_DIFFCONFIG 环境变量,跑 make kitakami_defconfig。然后跑 make。编译好的内核在 arch/arm64/boot/Image 目录下。至此,编译步骤跟一般的软件编译基本一致。

组装 boot.img。dt.img 已经在前面解包时得到了,不需要编译。把前一步编译得到的 kernel 复制到 mkbootimg_tools 工具之前解包得到文件夹里,替换掉解包出来的 kernel。编辑 config 文件把 dwc3.aca_enable=Y 等配置加到 cmdline 上。最后跑 mkbootimg_tools 打包得到 boot.img。

以上过程我是在 Google Cloud 上开了个实例来搞的,全新的 Ubuntu 环境,超快的下载速度,CPU搞多点编译嗖嗖的,硬盘20G就够了,搞完就把实例一关,用不了几个钱,非常推荐。

最后使用 fastboot 刷入千辛万苦得到的 boot.img。开机进入设置 - 关于本机 检查下,果然是刚刚编译出的这个。连上后试用,完全OK👌👍。

OTG和充电一起 OTG和充电一起

Xperia Z3 Tablet Compact

本来写到了这里就该结束了,然而我还是按耐不住折腾的心继续搞事。因为 Z5 的屏幕小看着不爽,又二手收了个 Xperia Z3TC,8寸 16:9屏 大小正好。同样是骁龙 800 系列 CPU,上面的这些我又得再来一遍。Z3TC官方支持的最后版本是 Android 6.0.1,固件版本 23.5.A.1.291,先用线刷更新上去。然后准备源码,奇怪的是 sonyxperiadev/kernel-copyleft 上并没有,从索尼开发者网站可以下载到存档

这回可没有大神给我移植好的 dwc3_otg 驱动直接用,得自己动手了。因为 Z5 已经是后面两代产品了,驱动也已经修改了很多,很难再进行比对,所以我选择对比 一加One 的 dwc3_otg( @PhoenixWright 的修改版本: https://forum.xda-developers.com/t/kernel-power-over-otg-host-mod.3201100/#post-64461279 ),凭着我粗浅的C语言知识把修改一点点应用过来。编译内核跟 Z5 有点不同,交叉编译环境需要换成 32 位,其它流程不变(完整命令见文末附录)。

dwc3_otg.c.patch

接下来是组装 boot.img,按照 Z5 的经验,下载个 stock kernel 来用就行了,可 Z3TC 的 stock kernel 愣是没找到(或者下载地址挂了)。那就自己从 FTF 提取吧,跟着某图片都挂了的教程操作:把 FTF 解压得到 kernel.sin,再用 flashTool sinEditor 提取出 kernel.elf。按照教程的说法,此时 kernel.elf 就是 boot.img (原话:“rename your 'kernel.elf' to 'boot.img'”),可当我刷入 kernel.elf 时 fastboot 报错说这不是个 boot image (remote: image is not a boot image)。一开始怀疑是提取的问题,换了 unSIN 工具尝试,甚至检查了 sin 文件结构是否正确,但都没有问题。我就纳闷了,检查 kernel.elf 文件的二进制内容,确实不对。boot image 开头应该是 ANDROID magic,而 kernel.elf 的开头是 ELF 。既然是真的 ELF,那就再用 flashTool extract elf 提取一次吧,得到 kernel.elf.Image, kernel.elf.qcdt, kernel.elf.ramdisk.gz。这跟某古早mkelf教程的描述也不完全一致,看文件大小和格式猜测这仨应该是 kernel, dt.img 和 ramdisk.img,姑且一试手动用 mkbootimg 再组成 boot.img 刷入,成功开机。至此费了九牛二虎之力成功得到 stock boot.img。

elf magic boot image magic

最后替换 kernel 重新打包得到 boot.img。记得把 dwc3.aca_enable=Y 加到 cmdline 里面。

sgp611-patched-kernel-version

相关文件

Z5

Z3TC

完整命令

环境:Ubuntu 16.04

Z5 boot.img 制作

cd ~ # /home/work: 32.0.A.6.200-boot.img, otg-aca-xperia_z5.diff

# 安装编译依赖和工具
sudo apt-get install -y build-essential kernel-package libncurses5-dev bzip2 cpio git

# 安装交叉编译工具
git clone --depth 1 https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9
git fetch android-5.1.1_r37
git checkout -b android-5.1.1_r37 FETCH_HEAD
export PATH=/home/work/aarch64-linux-android-4.9/bin:$PATH
export CROSS_COMPILE=aarch64-linux-android-

# 安装 mkbootimg_tools
git clone --depth 1 https://github.com/xiaolu/mkbootimg_tools.git # commitid: 2d8905e
export PATH=/home/work/mkbootimg_tools:$PATH

# 准备 ramdisk.img
mkboot 32.0.A.6.200-boot.img boot_unpacked # 解包

# 准备源码
wget https://github.com/sonyxperiadev/kernel-copyleft/archive/32.0.A.6.xxx.zip
unzip 32.0.A.6.xxx.zip
cd kernel-copyleft-32.0.A.6.xxx
git init
git apply ../otg-aca-xperia_z5.diff

# 开始编译
export ARCH=arm64
export KBUILD_DIFFCONFIG=sumire_dsds_diffconfig # 我的是 E6683 双卡,具体看 README_Xperia
make kitakami_defconfig
make -j4 # 4核CPU

# 组装
cd ..
cp kernel-copyleft-32.0.A.6.xxx/arch/arm64/boot/Image/kernel.img boot_unpacked/kernel # 替换 kernel
vim boot_unpacked/img_info # cmdline 增加 dwc3.aca_enable=Y 等配置
mkboot boot_unpacked my-aca-boot.img # 打包出 boot.img

# 刷入
fastboot flash boot my-aca-boot.img

Z3TC boot.img 制作

省略部分重复步骤

cd ~ # /home/work
ls
# > sgp611-aca-dwc3_otg.c                                     # 修改后的 dwc3_otg.c
# > android_kernel_sony_23.5.A.1.291                          # 源码文件夹
# > kernel.elf.Image, kernel.elf.qcdt, kernel.elf.ramdisk.gz  # 从 kernel.elf <- kernel.sin 解包得到

# 得到 stock kernel
mkbootimg \
    --kernel kernel.elf.Image \
    --ramdisk kernel.elf.ramdisk.gz \
    --cmdline "console=ttyHSL0,115200,n8 androidboot.hardware=qcom user_debug=23 msm_rtb.filter=0x3b7 ehci-hcd.park=3 androidboot.bootdevice=msm_sdcc.1 vmalloc=300M dwc3.maximum_speed=high dwc3_msm.prop_chg_detect=Y" \
    --base 0x00000000 \
    --pagesize 2048 \
    --dt kernel.elf.qcdt \
    --ramdisk_offset 0x02000000 --tags_offset 0x01E00000 \
    --output boot.img

# 准备 ramdisk.img
mkboot boot.img boot_unpacked # 解包

# 安装交叉编译工具
git clone --depth 1 https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9
git fetch android-6.0.1_r81
git checkout -b android-6.0.1_r81 FETCH_HEAD
export PATH=/home/work/arm-linux-androideabi-4.9/bin:$PATH
export CROSS_COMPILE=arm-linux-androideabi-


# 准备源码
cd android_kernel_sony_23.5.A.1.291
cp ../sgp611-aca-dwc3_otg.c drivers/usb/dwc3/dwc3_otg.c

# 开始编译
export ARCH=arm
export KBUILD_DIFFCONFIG=shinano_scorpion_windy_defconfig # 我的是 SGP611,具体看 README_Xperia
make shinano_sirius_defconfig
make -j4 # 4核CPU

# 组装
cd ..
cp android_kernel_sony_23.5.A.1.291/arch/arm/boot/zImage-dtb boot_unpacked/kernel # 替换 kernel
vim boot_unpacked/img_info # cmdline 增加 dwc3.aca_enable=Y 配置
mkboot boot_unpacked my-aca-boot.img # 打包出 boot.img