Hộp TV 2016, Linux mainline, và năm ngày gom bằng chứng
🌿 buddingupdated 2026-10-14#linux#phan-cung#tv-box#mainline#dich-nguoc#ghi-chep
Ghi chép thực địa, viết khi hộp vẫn còn nằm trên bàn thợ. Mỗi kết luận dưới đây đều có số đo, dòng log hoặc ảnh chụp màn hình đi kèm. Chỗ nào chỉ là suy luận, mình nói rõ.
English: A 2016 TV box, mainline Linux, and five days of evidence.

Cái hộp, và câu hỏi
HiMedia Q10 Pro là hộp Android TV "audiophile" đời 2016: HiSilicon Hi3798CV200 bốn nhân, RAM 2 GB, khay ổ SATA 3.5", đồng hồ VFD 16 đoạn ở mặt trước, ra âm thanh RCA + quang + HDMI. Firmware cuối cùng là Android 7 trên kernel 4.4 của hãng.
Câu hỏi đầu tiên: cố lên Android mới hơn, hay chuyển hẳn sang Linux? Hoá ra câu trả lời nằm ở cấu trúc, chẳng phải chuyện chịu khó:
- Android 7 là trần. Blob video, HDMI và GPU dựng trên BSP 4.4 của hãng, và HAL của Android là thứ duy nhất dùng chúng. Muốn Android mới thì cần BSP mới, mà HiSilicon đã rời mảng set-top box. BSP đó sẽ chẳng bao giờ ra.
- Lai "kernel hãng + Debian" là cái bẫy. Blob nói API MSP riêng của HiSilicon, chẳng phải V4L2 hay DRM. Kodi, mpv, ffmpeg, Chromium đều không nói chuyện được với nó. Giữ driver mà vẫn không có giải mã video phần cứng.
- Mainline thì còn được chăm. Linux 6.18 là bản longterm đang được hỗ trợ; kernel 4.4 rời danh sách của kernel.org từ lâu rồi.
Vậy là chọn Linux mainline, và chấp nhận mọi driver media phải port lại hoặc tự viết.
Khởi động mù: dùng watchdog làm kênh báo tin
Hộp này chẳng có cổng serial, và hai ngày đầu cũng chẳng có hình nào lên TV. Kernel treo và kernel chạy ngon trông y hệt nhau: TV đen, ping không trả lời.
Cái cứu mình là watchdog SP805 có sẵn trong chip. Mỗi mốc trong giai đoạn khởi động sớm lại hẹn giờ watchdog với một thời hạn khác nhau, nên đo thời gian tới lúc hộp tự reset là biết nó đã chạy tới mốc nào. Qua bảy lần nạp và 18 mốc:
| chu kỳ reset | mốc cuối | chứng minh điều gì |
|---|---|---|
| ~19 s | stext | đã vào được kernel chính |
| 134 s | __create_page_tables | xong toàn bộ đoạn trước MMU |
| 194 s | __mmap_switched | MMU bật, chạy bằng địa chỉ ảo |
| 216 s | kernel_init_freeable | mọi initcall đã chạy; sắp exec /init |
Những cú vấp, cú nào cũng để lại bài học dùng được chỗ khác:
- Phần "phụ phí" không cố định. Với stub tí hon nó là 6 s, nhưng đọc kernel 12 MB từ USB thì mất 11–14 s, và càng sâu càng tăng. Đặt các thời hạn cách nhau có 4 s thì một lần đo khớp với ba kết luận. Giờ công cụ từ chối mọi cấu hình có khoảng cách từ 10 s trở xuống.
- Mốc cuối che hết mọi thứ sau nó. Chẳng ai vuốt watchdog, nên kernel lên tới userspace cũng reset y như kernel chết ngay sau mốc cuối. Mốc sâu nhất phải có thời hạn dài nhất.
- Đầu dò không thể kêu thì chẳng phải đầu dò. Mốc "PID 1 đã chạy" của mình mở
/dev/mem, mà initramfs lại không có node đó. "PID 1 đã chạy" và "exec thất bại" cho ra đúng 216 s như nhau. - Thiếu một file là cả script câm.
init.shmở đầu bằngmount -t devtmpfs dev /dev 2>/dev/null. Nhưng/dev/nullnằm trên chính devtmpfs, lúc đó chưa có. Theo POSIX, chuyển hướng hỏng thì lệnh bị bỏ qua, còn script thì cứ chạy tiếp. Mọi dòng2>/dev/nullsau đó hỏng y vậy, cả giai đoạn hai thành chạy suông trên một kernel hoàn toàn khoẻ. Kiểm bằng một dòng:sh -c 'echo X 2>/no/such/dir; echo continues'chỉ in racontinues.
Mạng lên sau khi sửa hai chi tiết trong device tree. Bộ reset của PHY Ethernet bị build thành module (=m), trong khi initramfs chẳng nạp module nào, nên PHY chưa từng được reset. Còn bus MDIO chỉ quét con trực tiếp, nên node PHY phải nằm ngay dưới MAC.
Giờ chạy được gì
Tất cả đo trên hộp, kernel 6.18.55 với chuỗi patch của mình (các bản build k14 tới k25). Ảnh màn hình đọc thẳng từ bộ đệm quét của bộ điều khiển hiển thị trong RAM, tức đúng thứ cổng HDMI đang xuất ra.

| bộ phận | trạng thái | bằng chứng |
|---|---|---|
| 4 nhân CPU | chạy | nhân phụ được thả qua TEE (SC_GEN1); đường SRAM của hãng không ghi được từ vùng non-secure |
| Khởi động lại | chạy | reset bằng SP805 với LOAD 0x100: bản sao của HiSilicon không bao giờ kích từ 0. Lên lại sau 65 s |
| DVFS CPU + devfreq GPU | chạy | điện áp CPU 840–1150 mV, GPU 200–400 MHz |
| Ethernet, WiFi (AP6255) | chạy | WiFi cần GPIO4_6 (WL_REG_ON) và chân SDIO 1.8 V |
| USB 3 bên hông | chạy | đo được 91.7 MB/s SuperSpeed |
| Remote IR + phím cảm ứng | chạy | bo cảm ứng gửi mã NEC vào chính đầu thu IR; kernel gốc chưa từng quét phím |
| Đồng hồ VFD (TM1629A) | chạy | font 6 ô 16 đoạn canh theo ảnh chụp; đọc đúng chữ "HIMEDI" |
| Hiển thị (driver DRM cho VDP) | chạy | nhận lại chế độ 1080p60 của bootloader; plane đồ hoạ G0 + plane video NV12 V0 |
| GPU (Mali-T720, Panfrost) | chạy | kmscube 59.9 fps, glmark2 90 điểm trên KMS, 0 lỗi GPU |
| Giải mã video phần cứng | chạy | H.264, HEVC (8/10-bit, 4K), VP9 khớp từng bit; MPEG-2 trong khoảng 66–68 dB |
| Âm thanh (tự viết từ đầu) | chạy | RCA, quang, HDMI và DAC USB; nghe tận tai cả |
| HDMI-CEC | phát chạy | chiều nhận chưa thử: ở đây chẳng có TV hỗ trợ CEC |
| SATA | chập chờn | xem phần "Còn dở" bên dưới |
Giải mã video phần cứng: từ một màu xanh lè tới khớp từng bit
Bộ giải mã (VDH) và bộ hậu xử lý (VPSS) lấy từ bản port 6.12 của cộng đồng, và phải sửa thật sự mới chạy được trên 6.18 bản 32-bit. Lần giải mã đầu "thành công": 300 khung, không lỗi, khung nào cũng xanh lè một màu.

Hai lỗi chồng lên nhau. VDH báo lỗi đúng ở 40 khung có byte chống giả mã khởi đầu (emulation-prevention), sửa bằng cách cho driver tự gỡ chúng. Sau đó khung nào cũng hỏng ở VPSS với post-processing failed, state=0x8d, sửa bằng ghi non-secure và một bộ đệm write-back thật thay cho địa chỉ vật lý 0.

Rồi mình kiểm cho kỹ. md5 của mọi byte giải mã ra trùng khít giữa FFmpeg-phần cứng, GStreamer-phần cứng và FFmpeg-phần mềm (720p 461d0f24e55e…, 1080p 69914be4d1c3…). Phần cứng chẳng phải "gần đúng"; nó chính là đáp án chuẩn.
| clip (FFmpeg v4l2request) | khung | so với phần mềm | fps |
|---|---|---|---|
| H.264 1080p60 | 2400/2400 | md5 trùng | 75 |
| HEVC 1080p 8-bit | 300/300 | md5 trùng | 51 |
| HEVC Main10 1080p | 300/300 | md5 trùng (P010) | 31 |
| HEVC 2160p (4K) | 150/150 | md5 trùng | 15 |
| VP9 1080p | 300/300 | md5 trùng | 52 |
| MPEG-2 1080p / 576i | 300 / 250 | PSNR khung thấp nhất 68.4 / 66.1 dB | 41 / 68 |
Hai phát hiện về hiệu năng, đáng biết nếu bạn mày mò chip tương tự:
- Bộ đệm không cache là cái bẫy. 1080p thật ra mã hoá cao 1088 dòng, nên GStreamer phải cắt-chép từng khung. Chép từ bộ nhớ không cache tốn 28.7 s CPU cho 300 khung và ghìm phát lại ở 15 fps. Cho bộ đệm được cache thì lên 94 fps, chỉ 3.6 s: nhanh 6.1 lần, đỡ 7.9 lần CPU, md5 vẫn trùng.
- Đường hiển thị quan trọng hơn bộ giải mã. Qua framebuffer chép bằng CPU (simpledrm), 1080p chỉ được 11.7 fps và ngốn 34 s CPU. Đưa thẳng vào plane video của VDP, không chép, thì 48.5 fps với 3.8 s: gấp 4 lần khung hình, chỉ tốn 1/9 CPU.

Còn một chuyện lạ chưa giải: khung giải mã bằng phần mềm đi qua bộ vẽ GL của mpv làm Panfrost báo lỗi job INSTR_INVALID_PC (356 lỗi mỗi clip 720p, lặp lại được). Một lần chạy như vậy đi trước một cú reset không rõ nguyên do. Khung giải mã phần cứng đi qua cùng bộ vẽ thì 0 lỗi. Phía Mesa mình chưa đào.
Âm thanh: driver viết từ công thức thanh ghi
Mainline chẳng có driver cho khối âm thanh (AIAO). Mình rút công thức cấp thanh ghi từ SDK của hãng rồi viết một driver ALSA nhỏ: một vòng đệm cho cả ba cổng cùng đọc, con trỏ đọc được hrtimer hỏi định kỳ, nên chẳng cần ngắt, cũng chẳng cần node device tree. Bài thử âm đo được 48.592 và 48.593 khung/giây trên hai cổng (mục tiêu 48.000, cửa sổ đo 1 s cộng phụ phí tiến trình). Rồi tới phần quan trọng: tai nghe thấy tiếng ở RCA, rồi ở HDMI (N=6144, CTS đo được 148.351), và cổng quang sáng đỏ.
Bên trên là một "chế độ nhạc" biến hộp thành máy phát nhạc qua mạng: MPD, AirPlay 2, Spotify Connect, UPnP/OpenHome, xuất bit-perfect ra DAC USB (đo passthrough 16/48, 0 lần hụt đệm). Kèm thêm chọn ngõ ra, bật tắt từng cổng và chỉnh lệch trái/phải, từ remote, dòng lệnh hay trình duyệt trên điện thoại.


Một hộp, mấy cuộc đời
Kernel trên USB khởi động giữ nguyên làm đường lui. Nó kexec sang kernel mới nhất trong lúc watchdog đã hẹn giờ, kèm bộ đếm lỗi, nên một kernel hỏng chỉ tốn một lần reset, chẳng bao giờ làm chết hộp. Rồi menu trên TV cho chọn phiên:



Để Android 13 lên được là cả một chuỗi lỗi câm, sửa lần lượt từng cái:
- Tuỳ chọn kernel, trong đó có
EXT4_FS_POSIX_ACL. Thiếu nó, vold không đặt được ACL mặc định và system_server cứ lặp "rebooting into recovery". - GPU chỉ có GLES 2.0. SurfaceFlinger sập, trừ khi đặt biến ghi đè phiên bản của Mesa trong môi trường (một file init
.rctrong lớp vendor; đặt property thì vô ích) và tắt phần mồi shader lúc khởi động. - Mạng của Waydroid mặc định dùng iptables đời cũ, mà 6.18 cất nó sau một tuỳ chọn riêng; đường nftables của nó thì chạy.
Còn dở
Nói thật cả những chỗ hổng:
- SATA chập chờn về mặt vật lý. Cắm lại xong, link chỉ lên được 1.5 Gbps. Chạy tải thì thấy 20 lần reset cứng, tụt xuống UDMA/33, và bộ đếm lỗi CRC leo từ 17 lên 26, tốc độ còn ~0.2 MB/s. SMART thì báo ổ khoẻ. Phép thử quyết định là chạy Android gốc của hãng: nó cũng chẳng thấy ổ, với giá trị thanh ghi trùng y những gì mình đã ghi. Vậy lỗi ở dây hoặc đầu cắm, chẳng phải phần mềm. Dây mới đang về, từ giờ tới lúc đó hộp chạy từ USB.
- Android chưa có internet. Kernel của mình thiếu policy routing (
CONFIG_IP_ADVANCED_ROUTER), mà netd của Android cần nó để đặt route mặc định. Phía hộp (bridge, NAT) vẫn ổn. Chỉ là một dòng tuỳ chọn kernel, tạm gác tới khi ổ SATA về lại. - Android trên USB 2 thì chậm. Lần đầu mất ~50 phút (nó biên dịch mọi app), khởi động lại mất 7 phút. Chạy từ ổ SATA là 100 s.
- Không có DRM cỡ Netflix. Đo trên firmware gốc: Widevine L3 (OEMCrypto v11). L1 chẳng bao giờ khởi tạo được. Bản firmware gốc là bản build
engđặt theo tên chip, chẳng phải thiết bị được chứng nhận, nên theo mình hiểu thì Netflix HD vốn chưa từng có cửa, kể cả hồi 2016. - Chỉ 32-bit. Firmware khởi động và TEE là 32-bit, nên kernel chạy AArch32, app chỉ có bản arm64 sẽ không cài được.
- Android 7 gốc qua kexec có khởi động, nhưng TV toàn sọc nhiễu. Chỉ bật khi muốn; rút USB ra là nó chạy bình thường, vì eMMC chưa từng bị ghi.
Những lần mình sai, giữ lại có chủ ý
Mấy cái này tốn thời gian thật, nên mình giữ trong ghi chép:
- Mình kết luận trước khi kiểm. "Kernel lên rồi, Ethernet chạy rồi" hoá ra là một chiếc camera IP đang trả lời ở đúng địa chỉ mình chọn. Năm vòng build-và-boot dựng trên nó. Chỉ một lệnh
arplà lộ ra ngay. Từ đó mỗi kết luận đều gắn nhãn đã đọc, suy luận hay chưa biết. rebootcủa mainline treo trên chip này. Nó chẳng có trình xử lý khởi động lại, mà unit khởi động của mình đã tắt watchdog trước đó, nên hộp đứng mãi ở "Restarting system" tới khi có người rút điện. Chuyện này còn làm nhiễu một kết luận trước đó ("image ghi từ hộp không boot được"), và mình phải rút lại.- Thời hạn watchdog tràn thanh ghi 32-bit (300 s × 24 MHz) thì nó hẹn một khoảng khác hẳn. Giờ các script bảo vệ từ chối mọi giá trị ngoài 1–178 s.
- Bộ kiểm chỉ biết một kiểu mã hoá lệnh báo đoạn code có thật là thiếu: GCC dựng hằng số bằng immediate xoay, còn PID 1 hoá ra là Thumb-2 trong khi kernel là ARM.
Giờ thì sao
Năm ngày, giờ nó là một hộp Linux hiền lành. Lên menu TV trong ~90 s, giải mã video phần cứng khớp từng bit, phát nhạc ra DAC, chạy desktop, Kodi, Chromium và Android 13, và vẫn hiện giờ trên chiếc VFD cũ. Lỗi vật lý cuối cùng đang chờ một sợi dây mới.
Chăm trồng bởi Ba rồng & AI nữa: hạt giống (cái hộp, những câu hỏi, những lần "chắc chưa?") là của Ba rồng; những đêm chăm dài thì chia nhau.