背景#
手裡有台閒置多年的雲犀 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
起初,我猜測它採用了最常見的瑞芯微 RK3399 方案,但拆機後才發現是高通方案。仔細想想,考慮到直播設備可能需要通過移動網路進行推流,具有基帶優勢的高通平台就有了很大的吸引力,選擇高通方案也就合情合理了。
對於其核心功能 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 的公開資料目前非常稀少。
在硬體設計上,可以感受到 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 等功能也就無法使用了。
軟體#
從設備界面可以看出雲犀 Box 運行的是安卓系統。相比於純 Linux 方案,採用安卓系統能利用其對硬體的高度抽象簡化開發,並且可以借助基於 Java 的成熟應用開發生態大幅提升應用開發效率。
不過這個安卓系統被深度限制了,只能運行官方的雲犀 APP,無法直接啟動任意應用,沒有地方可以打開 ADB,下拉通知欄被鎖定,系統設置等標準安卓界面也都被隱藏了。看來想通過簡單的系統界面操作來解除限制並不可行。
於是制定了下一步計劃:利用高通平台的 9008 緊急下載模式 (EDL) 來提取設備 eMMC 存儲中的 system
分區鏡像,然後掛載鏡像進行修改,替換內置 App,最後重新燒錄修改後的鏡像。
嘗試後發現,主板上預留的按鍵 S2 正好連接到 MSM8953 的 force_boot_from_usb
引腳 (GPIO37)。在上電時按住此鍵,設備就能進入 9008 模式。利用 Qualcomm Premium Tool 工具導出 system
分區鏡像,之後就可以在 PC 上自由地掛載和修改這個鏡像了。
由於雲犀 APP 可以啟動抖音、快手、淘寶直播這幾個特定的第三方應用,我便將淘寶直播對應的 APK 文件替換成了修改了包名的 QuickShortcutMaker APK,進行偷梁換柱。將修改後的 system.img
通過 9008 模式刷回設備,重啟後看到桌面上原 “淘寶直播” 的圖標都變成了 QuickShortcutMaker 的圖標。打開 QuickShortcutMaker,就可以自由地啟動系統內安裝的任何應用和系統組件了,包括被隱藏的安卓原生設置。
為了獲得完整的系統控制權,Root 必不可少。使用 Magisk 對設備的 boot
分區鏡像進行修補,為系統加入 su 命令,刷入後這個設備就順利獲得了 Root 權限。
之前擔心的 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 調試就意外地出現了,應該是廠商預留的調試手段。這下省去了我們自己進行複雜軟硬體修改的麻煩。
後來為了梳理切換 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。
其他#
啟動流程#
由於在分區備份寫入過程中出過一些差錯,我在排查故障時對 MSM8953 平台的啟動流程進行了大致的了解:
- 上電與 Boot ROM (PBL):
- 按下電源鍵後,處理器從內部 ROM (Boot ROM) 的預定義地址開始執行,運行主引導加載程序 (PBL - Primary Boot Loader)。
- PBL 讀取 eMMC 等啟動介質上的分區表 (GPT),找到
sbl1
(Secondary Boot Loader 1) 或備份sbl1bak
分區,將其加載到片上內存 (IMEM) 並跳轉執行。
- SBL1:
- SBL1 首先初始化 DDR 記憶體,然後加載並驗證後續啟動階段所需的組件:
tz.mbn
: TrustZone 相關組件 (QSEE, HYP, TEE 等)。devcfg.mbn
: 設備配置數據。rpm.mbn
: 資源功耗管理器 (RPM) 固件,加載到 RPM 核心的內部內存 (TCM)。aboot.mbn
: 應用引導加載程序 (APPSBL),通常是 Little Kernel (LK)。
- 向 RPM 發送啟動信號並解除其復位狀態。
- 通過寫
RMR_EL3
請求 Warm Reset,CPU 以 AArch64 EL3 特權級重新啟動,進入 TrustZone 執行環境。
- SBL1 首先初始化 DDR 記憶體,然後加載並驗證後續啟動階段所需的組件:
- QSEE/ TrustZone:
- 在 EL3 環境下進行安全相關的初始化。
- 將 CPU 切換回 AArch32 EL1,跳轉到
aboot.mbn
(LK) 的入口點。
- Aboot (LK):
- LK (little kernel),提供 Fastboot 刷機模式
- 初始化必要的硬體,如 MIPI-DSI 控制器和顯示面板,從
splash
分區讀取 Logo 文件並顯示開機畫面。 - 加載
boot.img
(正常啟動) 或recovery.img
(恢復模式),並對其進行驗證。 - 準備好內核運行環境,切換到 AArch64 EL1,跳轉到內核的入口地址。
- Kernel:
- 識別掛載
system
、vendor
、userdata
、persist
等分區 - 加載並啟動其他協處理器固件,如 Modem (基帶)、WCNSS (Wi-Fi/Bluetooth) 。
- 進入 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 博客
Android 設備開機日誌分析_掛載安卓開機 log-CSDN 博客
轉載 - Qualcomm MSM8953 啟動流程:PBL-SBL1-(bootloader) LK-Android - liangliangge - 博客園