Lithiumee

Lithiumee

Go wild

Yunxi Box 3.0 Pro Hacks: From Restricted to Rooted

背景#

image
手里有台闲置多年的云犀 Box 3.0 Pro 触屏导播一体机,这台设备支持双路 1080P  HDMI 输入、内置抖音、淘宝、快手直播及 RTMP 推流直播功能。但我们之前的使用场景比较特殊,有些平台以前没法方便地进行 RTMP 推流,更多还是采用电脑连接多张 USB 采集卡和 OBS Studio 虚拟摄像头的方案,因此这个设备闲置多年无人问津。

最近,我正好在开发类似定位的硬件产品,于是开始重新审视这台设备。一方面,希望学习和了解其软硬件方案;另一方面,也期望通过软件改造,让其独特的硬件能在其他场景中发挥作用。

硬件#

拧下藏在背面防滑垫下的螺丝,就能轻松打开后盖。主板的设计相对简洁,元器件只布局在一面,并且采用了核心模组 + 底板的形式。

对于这种 PCB 空间充裕的项目来说,使用核心板或核心模组确实有很多优势,一方面高频的 RF 部分、高速的 DDR 内存、存储等最复杂的设计都集成在核心模组上,大大减少了硬件设计和调试的工作量,有助于产品更快推向市场;另一方面底板可以采用更低的层数和更简单的工艺,将高多层板限制在最小面积,有效控制成本。

从拆解图中可以看到云犀 Box 3.0 Pro 的主要芯片方案:

  • 核心模组:移远 (Quectel) SC60 模组,搭载高通骁龙 625 (MSM8953) SoC,2GB RAM 和 16GB eMMC。
  • 以太网:亚信 (ASIX) AX88179 USB 3.0 to Gigabit Ethernet
  • MIPI-DSI to HDMI Tx 桥接:龙讯 (Lontium) LT8912B
  • HDMI Rx to MIPI-CSI 桥接:东芝 (Toshiba) TC358840XBG *2
  • 电源管理:德州仪器 (TI) BQ25890
  • USB HUB:创惟 (Genesys Logic) GL3523
    IMG_20250330_142827
    IMG_20250330_144149
    起初,我猜测它采用了最常见的瑞芯微 RK3399 方案,但拆机后才发现是高通方案。仔细想想,考虑到直播设备可能需要通过移动网络进行推流,具有基带优势的高通平台就有了很大的吸引力,选择高通方案也就合情合理了。
    image 1
    对于其核心功能 HDMI 采集,云犀 Box 使用了两颗东芝 TC358840XBG 桥接芯片来实现两路信号采集。TC358840XBG 虽然经典,但也有些局限,它需要 Dual Link MIPI-CSI-2 一共 8 Lane 才能实现 4K 30fps 的视频采集(两个 Link 分别采集 4K 画面的左右两半),像云犀 Box 这样使用 Single Link CSI-2 则只能支持到 1080p 采集。相比之下,现在更新的芯片,例如龙讯的 LT6911UXE,其 MIPI 每条 Lane 的最高速率可达 2.5Gbps,仅用一个 4-Lane 的 MIPI 端口就能支持 4K 60fps 采集,若要进行 4K 采集方案会简洁不少。不过 TC358840XBG 有开源驱动可用,对个人开发者友好,而 LT6911UXE 的公开资料目前非常稀少。
    image 2
    在硬件设计上,可以感受到 MSM8953 的外设限制给设计带来了一些挑战。由于 MSM8953 没有内置以太网 MAC,也没有 PCIe 接口,要实现有线网络接入,基本只能依赖 USB 网卡。然而,MSM8953 只有一个 USB 3.0 接口。这就带来了一系列连锁反应:一方面,为了同时支持一个 USB 网卡和一个外部 USB 3.0 Type-A 接口,必须引入 USB HUB 芯片 (GL3523)。另一方面,主要作 Device 用的 Type-C 接口和作 Host 用的 USB HUB 需要使用 USB 开关芯片进行信号物理连接的切换,对应主板上的 PI3USB302 和 TS3USB221A。这意味着一旦系统切换到 Host 模式(连接网卡和 Type-A 口),Type-C 接口就完全被断开了,依赖这个接口的 ADB、Fastboot 等功能也就无法使用了。
    image 3

软件#

从设备界面可以看出云犀 Box 运行的是安卓系统。相比于纯 Linux 方案,采用安卓系统能利用其对硬件的高度抽象简化开发,并且可以借助基于 Java 的成熟应用开发生态大幅提升应用开发效率。

不过这个安卓系统被深度限制了,只能运行官方的云犀 APP,无法直接启动任意应用,没有地方可以打开 ADB,下拉通知栏被锁定,系统设置等标准安卓界面也都被隐藏了。看来想通过简单的系统界面操作来解除限制并不可行。
IMG_20250406_150942_1
于是制定了下一步计划:利用高通平台的 9008 紧急下载模式 (EDL) 来提取设备 eMMC 存储中的 system 分区镜像,然后挂载镜像进行修改,替换内置 App,最后重新烧录修改后的镜像。

尝试后发现,主板上预留的按键 S2 正好连接到 MSM8953 的 force_boot_from_usb 引脚 (GPIO37)。在上电时按住此键,设备就能进入 9008 模式。利用 Qualcomm Premium Tool 工具导出 system 分区镜像,之后就可以在 PC 上自由地挂载和修改这个镜像了。
image 4
由于云犀 APP 可以启动抖音、快手、淘宝直播这几个特定的第三方应用,我便将淘宝直播对应的 APK 文件替换成了修改了包名的 QuickShortcutMaker APK,进行偷梁换柱。将修改后的 system.img 通过 9008 模式刷回设备,重启后看到桌面上原 “淘宝直播” 的图标都变成了 QuickShortcutMaker 的图标。打开 QuickShortcutMaker,就可以自由地启动系统内安装的任何应用和系统组件了,包括被隐藏的安卓原生设置。
为了获得完整的系统控制权,Root 必不可少。使用 Magisk 对设备的 boot 分区镜像进行修补,为系统加入 su 命令,刷入后这个设备就顺利获得了 Root 权限。
IMG_20250406_163334
之前担心的 USB 问题还是出现了。虽然在设置中成功开启了 USB 调试选项,但将 Type-C 口连接到电脑后仍然不能看到 ADB 设备。说明此时 USB 控制器正处于 Host 模式,用于内置的 USB 网卡和 Type-A 口。

但是想要自己强制切换模式就比较棘手了,一条可能可行但未经验证的路径是一方面在硬件上,割断 USB 切换芯片的切换控制信号线,使得 USB 信号物理上始终通向 Type-C 接口;另一方面在软件上通过将 /sys/kernel/debug/msm_otg/mode 设置为 peripheral ,使 USB 控制器工作在 Device 模式。

不过偶然发现按下主板上另一个预留按键 S3 后,USB 调试就意外地出现了,应该是厂商预留的调试手段。这下省去了我们自己进行复杂软硬件修改的麻烦。
image 5
后来为了梳理切换 USB 模式的过程,从 boot.img 中提取并反编译了设备树。在设备树的 usb_detect 节点中,发现 qcom,gpio-usbdetect 相比 Example ,还额外定义了一个 key-gpio,其 GPIO 编号与 vol_up 键对应,板上的 S3 按键正是对应此 vol_up 。因此厂商应该是修改了内核中的 gpio-usbdetect 驱动,使得按下 S3 按键也能触发中断,并执行 USB 开关切换以及 USB Role (Host/Peripheral) 的切换逻辑。

vol_up {
	label = "volume_up";
	gpios = <0xbe 0x55 0x01>;
	linux,input-type = <0x01>;
	linux,code = <0x73>;
	debounce-interval = <0x0f>;
};

usb_detect {
	compatible = "qcom,gpio-usbdetect";
	interrupt-parent = <0x11e>;
	interrupts = <0x00 0xc6 0x00>;
	interrupt-names = "vbus_det_irq";
	key-gpio = <0xbe 0x55 0x01>;
	usb-switch-gpio = <0xbe 0x1c 0x00>;
};

通过观察内核日志 (dmesg),可以看到按下 S3 前后 usb_role_switch 的相关 log:

[ 1089.480345] [yunxi_bq25890]: vbus_detect_delay_work: key_level: 0
[ 1089.480374] [yunxi_bq25890]: usb_role_switch , is_host: 1

[ 1329.977896] --== bq25890_irq_handler
[ 1339.940379] [yunxi_bq25890]: vbus_detect_delay_work: key_level: 0
[ 1339.940410] [yunxi_bq25890]: usb_role_switch , is_host: 0

HDMI 采集#

HDMI 采集是这个设备最核心且独特的功能了。我的目标是能够绕过限制重重的官方云犀 APP,直接利用这一硬件能力。

TC358843XBG 芯片通过 MIPI CSI-2 接口输出视频数据,这与一般 Sensor 接入 SoC 的方式一致。不同之处在于,TC358843XBG 直接输出 YUV 或 RGB 格式的数据,而非摄像头常见的 Bayer RAW 格式。

安装开源的 Open Camera 应用后,就可以直接识别并预览到两路 HDMI 输入的画面,两路 HDMI 输入即为两个摄像头。这样一来通过 Open Camera 便直接可以进行 HDMI 输入的实时监看和视频录制。再次让人感叹还得是安卓,良好的硬件抽象带来了强大的兼容性,让很多功能无需为每个特定平台重复开发,并且在庞大生态加持下,可以直接利用现成的甚至是开源的海量应用。

通过查看 Open Camera 的调试信息,并结合对云犀官方 APP 的反编译分析,还可以确认云犀 Box 使用的是较旧的 android.hardware.Camera API (Camera1 API),并不支持功能更强大、更灵活的 Camera2 API。

image 6

view

其他#

启动流程#

由于在分区备份写入过程中出过一些差错,我在排查故障时对 MSM8953 平台的启动流程进行了大致的了解:

  1. 上电与 Boot ROM (PBL):
    1. 按下电源键后,处理器从内部 ROM (Boot ROM) 的预定义地址开始执行,运行主引导加载程序 (PBL - Primary Boot Loader)。
    2. PBL 读取 eMMC 等启动介质上的分区表 (GPT),找到 sbl1 (Secondary Boot Loader 1) 或备份 sbl1bak 分区,将其加载到片上内存 (IMEM) 并跳转执行。
  2. SBL1:
    1. SBL1 首先初始化 DDR 内存,然后加载并验证后续启动阶段所需的组件:
      • tz.mbn: TrustZone 相关组件 (QSEE, HYP, TEE 等)。
      • devcfg.mbn: 设备配置数据。
      • rpm.mbn: 资源功耗管理器 (RPM) 固件,加载到 RPM 核心的内部内存 (TCM)。
      • aboot.mbn: 应用引导加载程序 (APPSBL),通常是 Little Kernel (LK)。
    2. 向 RPM 发送启动信号并解除其复位状态。
    3. 通过写 RMR_EL3 请求 Warm Reset,CPU 以 AArch64 EL3 特权级重新启动,进入 TrustZone 执行环境。
  3. QSEE/ TrustZone:
    1. 在 EL3 环境下进行安全相关的初始化。
    2. 将 CPU 切换回 AArch32 EL1,跳转到 aboot.mbn (LK) 的入口点。
  4. Aboot (LK):
    1. LK (little kernel),提供 Fastboot 刷机模式
    2. 初始化必要的硬件,如 MIPI-DSI 控制器和显示面板,从 splash 分区读取 Logo 文件并显示开机画面。
    3. 加载 boot.img (正常启动) 或 recovery.img (恢复模式),并对其进行验证。
    4. 准备好内核运行环境,切换到 AArch64 EL1,跳转到内核的入口地址。
  5. Kernel:
    1. 识别挂载 systemvendoruserdatapersist 等分区
    2. 加载并启动其他协处理器固件,如 Modem (基带)、WCNSS (Wi-Fi/Bluetooth) 。
    3. 进入 userspace,启动用户空间的第一个进程 init,安卓启动。

设备树提取#

设备树描述了硬件信息,内核启动时会读取它来配置相应的驱动程序。在高通平台上,DTB (Device Tree Blob) 通常附加在内核镜像 (zImage) 的末尾。

使用 unpackbootimg 工具从 boot.img 中分离出 zImage,然后使用从moetayuko/split-appended-dtb获取的 split-appended-dtb 工具从 boot.img 中提取出所有附加的 DTB 文件,一共提取出 69 个 DTB 文件。

$ file boot.img 
boot.img: Android bootimg, kernel, ramdisk, page size: 2048, cmdline (console=ttyHSL0,115200,n8 androidboot.console=ttyHSL0 androidboot.hardware=qcom msm_rtb.filter=0x237 ehci-hcd.park=3 lpm_levels)
$ ./unpackbootimg -i boot.img -o ./
BOARD_KERNEL_CMDLINE console=ttyHSL0,115200,n8 androidboot.console=ttyHSL0 androidboot.hardware=qcom msm_rtb.filter=0x237 ehci-hcd.park=3 lpm_levels.sleep_disabled=1 androidboot.bootdevice=7824900.sdhci earlycon=msm_hsl_uart,0x78af000 buildvariant=user
BOARD_KERNEL_BASE 80000000
BOARD_PAGE_SIZE 2048
$ ls
boot.img  boot.img-base  boot.img-cmdline  boot.img-pagesize  boot.img-ramdisk.gz  boot.img-zImage
$ ./split-appended-dtb boot.img-zImage 
Found 69 appended dtbs, please check the output.

随后通过 dtc (Device Tree Compiler) 工具将二进制的 DTB 文件反编译为人类可读的文本格式 DTS (Device Tree Source)。

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

设备树数量众多但只会使用一个,LK 会根据 SBL 传入的硬件 ID (如 qcom,msm-id, qcom,board-id) 来选择最匹配的一个加载。通过查看 LK 在启动过程中通过串口输出的日志信息可以发现匹配到了 ID 为 <293 8 0 0x0> 的设备树

[2110] Found an appended flattened device tree (Qualcomm Technologies, Inc. MSM8953 + PMI8950 MTP - 293 8 0 0x0)
[2120] Add DTB entry 293/00000008/0x00000000/0/10016/0/0/0/a11c281f/3e740
[2120] Device tree exact match the board: <293 8 0 0x0> == <293 8 0 0x10001>

据此找到对应的设备树 DTS 文件即为:

/ {
	model = "Qualcomm Technologies, Inc. MSM8953 + PMI8950 MTP";
	compatible = "qcom,msm8953-mtp\0qcom,msm8953\0qcom,mtp";
	qcom,msm-id = <0x125 0x00>;
	interrupt-parent = <0x01>;
	qcom,board-id = <0x08 0x00>;
	qcom,pmic-id = <0x10016 0x00 0x00 0x00>;
	...
};

从 DTS 文件中可以获取非常丰富的信息,包括特殊的 GPIO 分配、屏幕初始化序列等。对于进行系统移植非常有价值,比如有了下面的 MIPI DSI 屏幕初始化信息,点亮这块屏幕就变得容易了。

qcom,mdss_dsi_yunxi_1200p_video {
				qcom,mdss-dsi-panel-name = "yunxi 1200p video mode dsi panel";
				qcom,mdss-dsi-panel-controller = <0x1a4>;
				qcom,mdss-dsi-panel-type = "dsi_video_mode";
				qcom,mdss-dsi-panel-destination = "display_1";
				qcom,mdss-dsi-panel-framerate = <0x3c>;
				qcom,mdss-dsi-virtual-channel-id = <0x00>;
				qcom,mdss-dsi-stream = <0x00>;
				qcom,mdss-dsi-panel-width = <0x4b0>;
				qcom,mdss-dsi-panel-height = <0x780>;
				qcom,mdss-dsi-h-front-porch = <0x32>;
				qcom,mdss-dsi-h-back-porch = <0x3e>;
				qcom,mdss-dsi-h-pulse-width = <0x08>;
				qcom,mdss-dsi-h-sync-skew = <0x00>;
				qcom,mdss-dsi-v-back-porch = <0x08>;
				qcom,mdss-dsi-v-front-porch = <0x0a>;
				qcom,mdss-dsi-v-pulse-width = <0x06>;
				qcom,mdss-dsi-h-left-border = <0x00>;
				qcom,mdss-dsi-h-right-border = <0x00>;
				qcom,mdss-dsi-v-top-border = <0x00>;
				qcom,mdss-dsi-v-bottom-border = <0x00>;
				qcom,mdss-dsi-bpp = <0x18>;
				qcom,mdss-dsi-color-order = "rgb_swap_rgb";
				qcom,mdss-dsi-underflow-color = <0xff>;
				qcom,mdss-dsi-border-color = <0x00>;
				qcom,mdss-dsi-on-command = [39 01 00 00 01 00 02 b0 00 39 01 00 00 01 00 06 b3 14 08 00 22 00 39 01 00 00 01 00 02 b4 0c 39 01 00 00 01 00 03 b6 3a d3 39 01 00 00 01 00 02 b7 00 39 01 00 00 01 00 07 b8 07 90 1e 00 1e 32 39 01 00 00 01 00 07 b9 07 82 3c 00 3c 87 39 01 00 00 01 00 07 ba 07 9e 20 00 20 8f 39 01 00 00 01 00 19 ce 7d 40 43 49 55 62 71 82 94 a8 b9 cb db e9 f5 fc ff 01 38 02 02 44 24 01 39 01 00 00 01 00 02 c0 ff 39 01 00 00 01 00 25 c1 04 61 00 20 8c a4 d6 ff ff ff ff 7f 73 ef b9 f6 ff ff ff ff bf 54 22 02 00 00 00 00 00 00 62 03 00 22 00 01 39 01 00 00 01 00 0a c2 31 f7 80 00 09 00 04 00 00 39 01 00 00 01 00 04 c3 00 00 00 39 01 00 00 01 00 0d c4 70 00 00 00 00 00 00 00 00 05 04 00 39 01 00 00 01 00 15 c6 6f 73 75 0a 14 0b 16 00 00 00 00 00 00 00 00 00 00 1c 17 09 39 01 00 00 01 00 1f c7 08 0e 19 22 31 3f 49 58 3c 44 4e 5b 64 6c 76 04 0c 13 1e 2d 3d 47 58 3c 44 50 5d 66 6e 76 39 01 00 00 01 00 14 c8 01 00 00 00 00 fc 00 00 00 00 00 fc 00 00 00 00 00 fc 00 39 01 00 00 01 00 0d cb 3f c0 0f f0 03 00 03 00 03 00 00 c0 39 01 00 00 01 00 02 cc 11 39 01 00 00 01 00 06 d0 44 81 bb 15 94 39 01 00 00 01 00 1a d3 0b 33 bf bb b3 33 33 37 00 01 00 a0 98 a0 00 39 39 33 3b 37 72 07 3d bf 99 39 01 00 00 01 00 03 44 07 7f 39 01 00 00 01 00 06 2a 00 00 04 af 00 39 01 00 00 01 00 06 2b 00 00 07 7f 00 39 01 00 00 01 00 02 d6 01 39 01 00 00 01 00 02 3a 77 39 01 00 00 01 00 02 2c 00 39 01 00 00 01 00 02 51 e6 39 01 00 00 01 00 02 53 24 39 01 00 00 01 00 02 55 01 05 01 00 00 32 00 02 29 00 05 01 00 00 78 00 02 11 00 39 01 00 00 32 00 02 35 00];
				qcom,mdss-dsi-off-command = [05 01 00 00 32 00 02 28 00 05 01 00 00 78 00 02 10 00];
				qcom,mdss-dsi-on-command-state = "dsi_lp_mode";
				qcom,mdss-dsi-off-command-state = "dsi_hs_mode";
				qcom,mdss-dsi-h-sync-pulse = <0x01>;
				qcom,mdss-dsi-traffic-mode = "burst_mode";
				qcom,mdss-dsi-lane-map = "lane_map_0123";
				qcom,mdss-dsi-bllp-eof-power-mode;
				qcom,mdss-dsi-bllp-power-mode;
				qcom,mdss-dsi-lane-0-state;
				qcom,mdss-dsi-lane-1-state;
				qcom,mdss-dsi-lane-2-state;
				qcom,mdss-dsi-lane-3-state;
				qcom,mdss-dsi-panel-timings = <0xf23a2800 0x6c6e2c3e 0x2e030400>;
				qcom,mdss-dsi-t-clk-post = <0x02>;
				qcom,mdss-dsi-t-clk-pre = <0x2d>;
				qcom,mdss-dsi-bl-min-level = <0x01>;
				qcom,mdss-dsi-bl-max-level = <0xfff>;
				qcom,mdss-dsi-dma-trigger = "trigger_sw";
				qcom,mdss-dsi-mdp-trigger = "none";
				qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_pwm";
				qcom,mdss-dsi-reset-sequence = <0x01 0x14 0x00 0x02 0x01 0x14>;
				qcom,mdss-dsi-panel-timings-phy-v2 = <0x241f0809 0x50304a0 0x241f0809 0x50304a0 0x241f0809 0x50304a0 0x241f0809 0x50304a0 0x241c0809 0x50304a0>;
				qcom,mdss-dsi-bl-pmic-pwm-frequency = <0x64>;
				qcom,mdss-dsi-bl-pmic-bank-select = <0x00>;
				qcom,mdss-dsi-pwm-gpio = <0x1a5 0x04 0x00>;
				qcom,panel-supply-entries = <0x1a3>;
				linux,phandle = <0x1a9>;
				phandle = <0x1a9>;
			};
		};

参考#

云犀 BOX - 视界,精彩一触即播,国内领先触屏直播硬件,让直播导播更简单 - 云犀直播

高通平台 dtb 文件的加载过程_高通 dump dts 文件 - CSDN 博客

我的 4g 网卡运行着 GNU/Linux -- 某 4g 无线网卡的逆向工程与主线 Linux 移植 (一)_fatal error: blob has incorrect magic number-CSDN 博客

利用 9008 刷写 / 备份单分区 - 哔哩哔哩

高通芯片启动流程 - asges 林 - 博客园

设备刷机需要的软件 | 宁宁's Blog

Android 设备开机日志分析_挂载安卓开机 log-CSDN 博客

转载 - Qualcomm MSM8953 启动流程:PBL-SBL1-(bootloader) LK-Android - liangliangge - 博客园

Android 启动加载器分析 —— Aboot - 知乎

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。