最近,某鱼和某宝上开始出现一些基于高通处理器的4g无线网卡,有一些网卡采用的方案也就是我之前折腾过的红米2的主控msm8916。如果能够在这些无线网卡上跑起主线linux,这应该是世界上最便宜的arm64单板电脑了(我入手的时候是20块一个)。但是这些板子基本上就是个黑盒,把所有的功能都驱动起来可能有点麻烦。

First Look

主板是这样的~
A
B

收到货首先就是把主板拆出来,可以看见主板上已经预留好串口和一些调试用的测试点,而且每一个点上都有标注这个点的功能,这让我的工作减轻了不少。在主板的背面还有一个按钮,没有标注任何文字,按着上电发现可以进入edl模式。

把板子上电,电脑可以发现一个adb设备,但是一会就消失了。利用adb关闭之前的时间间隙快速的adb shell进去(这个网卡竟然没直接关掉adbd,usb调试默认是开着的),不难发现这个网卡的emmc大小为4g,内存为512mb。配置还挺高的,超过市面上大多数搭载ipqxxx芯片的路由器了。直接adb reboot bootloader 可以进到fastboot,简单测试了下发现bootloader没有上锁,nice!

导出emmc数据

写出主线内核的设备树最好的参照还是原厂的固件里的boot.img提供的设备树。为了方便分析整个系统的和整成砖之后救砖,需要一个手段把emmc里的各个分区的数据都备份出来,最直观的方法是使用安卓的shell,把emmc的分区一个个dd成镜像,然后在pull下来。

除了这个,其实还有更方便的办法,我在逛github时发现了edl项目,一个python3实现的高通edl模式编程器,只要能够进到edl模式,就可以用这个工具来实现分区擦除、烧写、导出等操作。

配好edl之后直接执行以下指令,emmc分区就被导出来了,而且自动生成了描述分区结构的xml文件,想要恢复时也可以直接刷写这些导出来的内容。

$ edl rl dumps --skip=userdata --genxml
Qualcomm Sahara / Firehose Client V3.53 (c) B.Kerler 2018-2021.
main - Trying with no loader given ...
main - Waiting for the device
main - Device detected :)
main - Mode detected: firehose
firehose_client
firehose_client - [LIB]: No --memory option set, we assume "eMMC" as default ..., if it fails, try using "--memory" with "UFS","NAND" or "spinor" instead !
firehose - TargetName=MSM8916
firehose - MemoryName=eMMC
firehose - Version=1
firehose_client - Supported functions:
-----------------
Wrote partition xml as rawprogram0.xml
firehose_client - Dumping partition modem with sector count 131072 as dumps/modem.bin.
firehose - 
Reading from physical partition 0, sector 131072, sectors 131072                                                                   
firehose_client - Dumped partition modem with sector count 131072 as dumps/modem.bin.
firehose_client - Dumping partition sbl1 with sector count 1024 as dumps/sbl1.bin.
firehose - 
Reading from physical partition 0, sector 262144, sectors 1024                                                                 
firehose_client - Dumped partition sbl1 with sector count 1024 as dumps/sbl1.bin.
firehose_client - Dumping partition sbl1bak with sector count 1024 as dumps/sbl1bak.bin.
firehose - 
Reading from physical partition 0, sector 263168, sectors 1024                                                                  
firehose_client - Dumped partition sbl1bak with sector count 1024 as dumps/sbl1bak.bin.
firehose_client - Dumping partition aboot with sector count 2048 as dumps/aboot.bin.
firehose - 
Reading from physical partition 0, sector 264192, sectors 2048                                                                    
firehose_client - Dumped partition aboot with sector count 2048 as dumps/aboot.bin.
firehose_client - Dumping partition abootbak with sector count 2048 as dumps/abootbak.bin.
firehose - 
....

所有的内容备份会在当前目录下的dump目录生成。

作为能彻底实现开源的前提就是这个设备的secure boot开了没。如果没有开启,就意味着引导程序、gpu、wifi等固件只需要用高通提供的测试密匙签名一遍就可以直接在设备上跑起来。如果开启了secure boot,那必须要厂商的密匙签名cpu才能启动我们自己的引导程序、设备固件。

万能的edl为我们提供了一个功能,在edl模式下直接就能看到secure boot是否开启。

$ edl secureboot
Qualcomm Sahara / Firehose Client V3.53 (c) B.Kerler 2018-2021.
main - Trying with no loader given ...
main - Waiting for the device
main - Device detected :)
main - Mode detected: firehose
firehose_client
firehose_client - [LIB]: No --memory option set, we assume "eMMC" as default ..., if it fails, try using "--memory" with "UFS","NAND" or "spinor" instead !
firehose - TargetName=MSM8916
firehose - MemoryName=eMMC
firehose - Version=1
firehose_client - Supported functions:
-----------------
Sec_Boot0 PKHash-Index:0 OEM_PKHash: False Auth_Enabled: FalseUse_Serial: False
Sec_Boot1 PKHash-Index:0 OEM_PKHash: False Auth_Enabled: FalseUse_Serial: False
Sec_Boot2 PKHash-Index:0 OEM_PKHash: False Auth_Enabled: FalseUse_Serial: False
Sec_Boot3 PKHash-Index:0 OEM_PKHash: False Auth_Enabled: FalseUse_Serial: False
Secure boot disabled.

直接显示Secure boot disabled. ,secure boot没有开启,嗯~可玩性更高了,我们最终可以把原厂除了wifi和modem校准数据之外所有分区的内容都换掉。甚至可以实现硬件虚拟化(KVM),想想都挺离谱的,在一个无线网卡上跑libvirt虚拟机。。。

反编译设备树

安卓的boot.img 包含了内核、设备树、ramdisk、内核参数等linux内核起来必须要的组件。要重头实现一个主线内核的设备树,最好的参照还是安卓提供的设备树。

解包boot.img我使用的是unpackbootimg

$ unpackbootimg -i boot.bin -o ./  
BOARD_KERNEL_CMDLINE console=ttyHSL0,115200,n8 androidboot.console=ttyHSL0 androidboot.hardware=qcom user_debug=31 msm_rtb.filter=0x237 ehci-hcd.park=3 androidboot.bootdevice=7824900.sdhci earlyprintk
BOARD_KERNEL_BASE 0x80000000
BOARD_NAME 
BOARD_PAGE_SIZE 2048
BOARD_HASH_TYPE sha1
BOARD_KERNEL_OFFSET 0x00008000
BOARD_RAMDISK_OFFSET 0x01000000
BOARD_SECOND_OFFSET 0x00f00000
BOARD_TAGS_OFFSET 0x00000100
BOARD_DT_SIZE 5240832

接着使用dtc反编译设备树,但是报了个文件头的错误。

$ dtc -I dtb -O dts dtb -o dts
FATAL ERROR: Blob has incorrect magic number

使用bless 16进制编辑器查看文件头,发现是dt.img格式,这意味着这个boot.img设备树不止一个。
使用dtimgextract解包后吓到我了,足足有200多个设备树,看傻了。
人直接傻了
也只能写个脚本一个个反编译后自己找了。批量反编译设备树的脚本如下。反编译的产物会放在output下。

#!/bin/bash

        for i in $( ls *.dtb ); do
            echo decompile $i
            dtc -I dtb -O dts $i -o output/$i.dts
        done

光靠一个个看是看不出什么的,这些设备树都挺相似的,而且名字也不能提供什么有用的信息。所以得去安卓的shell里得到关于当前设备树里更有用的信息。
高通的设备树里比较特别的属性是qcom,board-id和qcom,msm-id,这两个属性是bootloader用来决定使用什么设备树的关键条件,应该每一个设备树之间的id都是不一样的。通过adb pull下来,用16进制查看器分析一波。

$ adb pull /proc/device-tree/qcom,board-id
/proc/device-tree/qcom,board-id: 1 fil... skipped. 0.0 MB/s (8 bytes in 0.002s)
$ adb pull /proc/device-tree/qcom,msm-id
/proc/device-tree/qcom,msm-id: 1 file ...skipped. 0.0 MB/s (32 bytes in 0.002s)

model属性也是不同设备树之间相互区别的属性之一,这个属性是字符串直接能cat出来。

shell@msm8916_32_512:/proc/device-tree $ cat model                             
Qualcomm Technologies, Inc. MSM 8916 512MB MTP

接下来就是漫长的寻找阶段了,懒得写脚本了,技术太菜了~
最终锁定了一个符合条件的设备树,model,board-id,msm-id
都与我获取的完全一致。

/dts-v1/;

/ {
	#address-cells = <0x02>;
	#size-cells = <0x02>;
	model = "Qualcomm Technologies, Inc. MSM 8916 512MB MTP";
	compatible = "qcom,msm8916-mtp\0qcom,msm8916\0qcom,mtp";
	qcom,msm-id = <0xce 0x00 0xf8 0x00 0xf9 0x00 0xfa 0x00>;
	interrupt-parent = <0x01>;
	qcom,board-id = <0x08 0x100>;
........

看到led的定义,更加确定bootloader载入的就是这个设备树。led的命名大概也能猜出来功能。10进制的数据编译后都变成了16进制的,转换一下就能得到正确的gpio号了。


		gpio-leds {
			compatible = "gpio-leds";
			pinctrl-names = "default";
			pinctrl-0 = <0xec>;

			WIFI_S {
				gpios = <0x4f 0x14 0x00>;
				label = "WIFI_S";
				linux,default-trigger = "none";
				default-state = "off";
				retain-state-suspended;
			};

			4G_L2 {
				gpios = <0x4f 0x15 0x00>;
				label = "4G_L2";
				linux,default-trigger = "none";
				default-state = "off";
				retain-state-suspended;
			};

			4G_L3 {
				gpios = <0x4f 0x44 0x00>;
				label = "4G_L3";
				linux,default-trigger = "none";
				default-state = "off";
				retain-state-suspended;
			};

			4G_S {
				gpios = <0x4f 0x16 0x00>;
				label = "4G_S";
				linux,default-trigger = "none";
				default-state = "off";
				retain-state-suspended;
			};

			SIM_SEL {
				gpios = <0x4f 0x02 0x00>;
				label = "SIM_SEL";
				linux,default-trigger = "none";
				default-state = "on";
				retain-state-suspended;
			};

			sim_en {
				gpios = <0x4f 0x01 0x00>;
				label = "SIM_EN";
				linux,default-trigger = "none";
				default-state = "off";
				retain-state-suspended;
			};

			FTM_ST {
				gpios = <0x4f 0x24 0x00>;
				label = "FTM_ST";
				linux,default-trigger = "none";
				default-state = "keep";
				retain-state-suspended;
			};
		};
	};

查看红米2的维修电路图,发现gpio37是进edl模式的引脚,一旦接地就会进入edl模式。与这个网卡上的唯一的按钮行为是一致的,通过按钮的定义基本上能够确定就是它了。

		gpio_keys {
			compatible = "gpio-keys";
			input-name = "gpio-keys";
			pinctrl-names = "tlmm_gpio_key_active\0tlmm_gpio_key_suspend";
			pinctrl-0 = <0xe9>;
			pinctrl-1 = <0xea>;
			vio-supply = <0x4a>;

			key_freset {
				label = "key_freset";
				gpios = <0x4f 0x25 0x00>;
				linux,input-type = <0x01>;
				linux,code = <0xd8>;
				debounce-interval = <0x3c>;
			};
		};

0x25 的十进制正是37
得到了设备树,基本上就是稳了,还需要一个好用的bootloader,可玩性就可以再上一个台阶。

移植lk2nd

为了能够启动主线内核,首先需要lk2nd能够跑起来,lk2nd是msm8916-mainline项目对于littlekernel的一个fork,将lk作为以boot.img的形式作为原来的bootloader的第二层引导。

那帮大佬最近加入了lk1st的支持,也就是说可以在关掉secure boot的设备上完全丢掉原来的bootloader,这也是我的最终目的,换掉原来的bootloader,实现真正的全部开源。(当然不包括高通完全没开放的firmware)

反正都是一套代码上的两个不同target,先把lk2nd跑起来好了,毕竟是2nd,原厂的bootloader把很多初始化的工作都做好了,移植其实简单的很,只需要一个假的设备树,让第一层bootloader认为这个boot.img是兼容这个设备的就行,根据前面逆向出的qcom,board-id和qcom,msm-id,lk2nd设备树如下所示,放在dts/msm8916目录下,修改下rules.mk就基本上能编译出镜像了。

// SPDX-License-Identifier: GPL-2.0-only

/dts-v1/;

#include <skeleton.dtsi>

/ {
	model = "Handsome OpenStick";
	compatible = "handsome,openstick", "qcom,msm8916", "lk2nd,device";
	qcom,board-id = <0x08 0x100>;
	qcom,msm-id = <0xce 0x00 0xf8 0x00 0xf9 0x00 0xfa 0x00>;
};

进到fastboot模式将img文件直接fastboot boot。
okay,lk2nd顺利跑通!能够正确执行lk2nd特有的oem指令。

$ fastboot oem lk_log    
OKAY [  0.002s]
Finished. Total time: 0.002s
$ fastboot get_staged /dev/stdout
Uploading '/dev/stdout'                            [0] smem ram ptable found: ver: 1 len: 4
[0] Minor socinfo format detected: 0.8
[0] smem ram ptable found: ver: 1 len: 4
[0] SCM call: 0x2000601 failed with :fffffffc
[0] Failed to initialize SCM
[0] welcome to lk

[0] calling constructors
[0] initializing heap
.....

但是一个好用的bootloader不能仅仅止步于此,还需要对lk做一点小小修改来实现按下edl键进入fastboot和显示设备当前的启动状态。

What’s Next?

其实目标已经实现了很大一部分了,但是过程有点复杂,实现的过程还在整理之中。

  • 实现一个好用的bootloader。 (已实现)
  • 实现一个能基本驱动所有硬件的主线内核设备树。 (已实现)
  • 在这块板子上跑起基于alpine linux的postmarket os。 (已实现)
  • 实现usb的模式切换的硬件mod,可以让这个网卡的usb工作在主机模式下,从而可以使用usb摄像头、u盘等设备。 (已实现)
  • 完成openwrt的移植,让这个网卡彻底飞起来。(悄悄地说我实现了~~)
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐