记录一下这次把向日葵控控 A2 改成 OpenKVM-A2 的过程。
设备就是那个 OrayKVM A2,本来硬件能力是够的:HDMI 输入采集画面,USB HID 模拟键鼠,网口/Wi-Fi 访问管理端。但是原厂方案绕不开向日葵客户端和云绑定。我想要的是很简单的东西:局域网里直接打开网页就能看屏幕、打键盘、动鼠标,不依赖向日葵客户端,不走云。
这次比较特殊,整个过程基本都是 Codex 做的。我给了目标、设备 SSH、现场拔插线配合和一些判断,剩下从备份、分析、写代码、刷机、测试、开源、发 GitHub Release,到最后写这篇文章,都是 Codex 操作完成的。
仓库在这里:
https://github.com/glzjin/openkvm-a2
固件 Release 在这里:
https://github.com/glzjin/openkvm-a2/releases/tag/v0.1.0
先说结论
最后做出来的是一个只支持 OrayKVM A2 的本地 KVM 固件。功能包括:
- 网页看 HDMI 输入画面;
- 网页控制键盘、绝对鼠标、相对鼠标、滚轮;
- 支持 Ctrl+Alt+Delete、Alt+Tab、Win+L 等快捷键;
- HTTP Basic Auth 登录;
- Web 里可以改管理密码;
- SSH 下可以 openkvm –set-password 改密码;
- 可以 openkvm-mode official/openkvm/toggle 在官方和 OpenKVM 之间切换;
- 只刷 mtd2,不动 bootloader、kernel、配置分区和序列号/校准分区;
- GitHub 上放了源码和实验固件;
- 写了一个一键刷机脚本。
一键刷机大概就是这样:
sh scripts/flash-a2.sh --yes admin@<device-ip> 44022 ./openkvm-a2-rootfs.squashfs
脚本会先上传镜像到 /tmp,在设备端校验大小和 SHA-256,然后只执行:
flashcp -v /tmp/openkvm-a2-rootfs.squashfs /dev/mtd2
sync
reboot
重点:只写 /dev/mtd2。不要写 mtd0、mtd1、mtd3、mtd4。
为什么没直接移植 PiKVM
一开始确实想过,既然目标是 KVM,那能不能直接把 PiKVM 搬过来。
后来 Codex 看了一圈,结论是不值得。
PiKVM 的 kvmd 依赖比较现代的环境,Python、systemd、nginx、Janus 这些都不是 A2 这种设备喜欢的东西。A2 只有 128MB 内存,Linux 3.4,而且视频链路不是标准 V4L2 摄像头,而是 HiSilicon 自己那套 MPP。
强行移植完整 PiKVM,大概率会变成:
- 系统依赖塞不下;
- 运行内存不够;
- 视频链路对不上;
- 调起来很痛苦;
- 以后维护也麻烦。
所以最后方案改成:不移植完整 PiKVM,只参考 PiKVM 的体验。底层用 A2 原生能力重写。
工程底座
底座选了 OpenIPC Divinus。
原因很简单,它本来就是一个轻量 C 写的视频服务,已经有嵌入式 SoC 的 HAL 和 MPP 相关结构。相比从零开始写一个视频服务器,改 Divinus 更现实。
最后工程变成这样:
- 保留 Divinus Git 历史;
- 保留 Divinus MIT 声明;
- 整个 OpenKVM-A2 因为参考 PiKVM GPLv3 体验和前端思路,按 GPL-3.0-only 发布;
- HiSilicon MPP 闭源库不进 Git;
- 原厂 rootfs/kernel/mtd 备份不进 Git;
- 设备密码、SSH 密钥、抓包文件不进 Git。
仓库里只放源码、脚本、文档、测试和最小打包逻辑。
备份
这个是第一步,也是最重要的一步。
Codex 先通过 SSH 把设备上的 mtd0 到 mtd4 全部备份下来,并做 SHA-256。后面所有操作都以这个备份作为恢复基线。
分区边界大概按这个原则处理:
- mtd0:不动;
- mtd1:kernel,不动;
- mtd2:rootfs,最终只写这个;
- mtd3:配置分区,只通过正常文件操作写 /config/openkvm/;
- mtd4:序列号/校准之类,不动。
这也是后面所有脚本的原则:能不写 Flash 就不写,必须写也只写 mtd2。
视频
视频这块踩坑比较多。
A2 的 HDMI 输入不是走普通摄像头接口。链路大概是:
ADV7611 -> BT.1120 -> VI -> VPSS -> VENC
也就是 ADV7611 收 HDMI,BT.1120 进 HiSilicon VI,VPSS 处理,VENC 编 H.264。
Divinus 原来更像 IPC 摄像头那套,会初始化 sensor、ISP、AE、AWB。A2 这里不能这么搞,因为输入源不是 sensor,是 HDMI receiver。所以 Codex 加了一个 A2 专用模式,跳过 sensor/ISP,直接起 BT.1120 VI。
一开始探针能跑,但整机重启后视频没帧。ADV7611 显示有信号,VI 的 IntCnt 是 0。
最后发现是 /dev/hi_mipi 的 combo input mode 没设。原厂启动流程里会设 BT.1120 input mode,OpenKVM 自己独立启动时漏了。补上 input mode 6 后,VI 开始进帧。
这个坑比较典型:不是 ADV7611 没信号,也不是 VENC 不工作,是 VI 前面的输入模式没被硬件切到 BT.1120。
现在提供三个视频接口:
- /video.mp4:主路径,分片 MP4;
- /video.264:裸 H.264;
- /mjpeg:降级路径。
网页默认走 MP4。浏览器不支持或者播放失败,就降级到 MJPEG。
这里也踩了一个前端坑。MediaSource 要创建 blob: URL,但 CSP 里一开始没放 media-src blob:,Chrome 直接把自己的视频 URL 拦了。修完之后,Chrome 长时间播放 MP4 正常。
HID
HID 是另一条线。
A2 不是 Linux gadget HID,而是一个 STM32 MCU 负责 USB HID。主控这边通过 /dev/ttyAMA1 给 STM32 发串口包,然后 STM32 再把键鼠事件发给目标主机。
这个协议没有直接复制原厂代码。Codex 用几种方式还原:
- 看社区资料和按键表;
- 看真机串口行为;
- 观察 STM32 ACK;
- 写黄金向量测试;
- 再用真实 Windows 画面验证键鼠效果。
实现后支持:
- 普通键盘按键;
- 左右 Ctrl/Alt/Shift/Meta;
- F1-F12;
- 方向键和编辑键;
- 绝对鼠标坐标;
- 相对鼠标移动;
- 左键、中键、右键;
- 滚轮;
- 断线释放;
- 快捷键面板。
之前遇到过“刚开始能用,过一会儿不动”的情况。Codex 加了 ACK 统计和回包排空,连续发了很多正反抵消鼠标报告,确认不是 UART 回包堆积。后面有一次 HID MCU status=1,最终判断更像是目标主机 USB HID 枚举侧没准备好,重插 A2 到目标主机的 USB 后恢复。
所以文档里也写了:UART 在线不等于目标主机已经接受 HID。status=1 先检查 USB 线、目标主机枚举和官方模式行为。
Web 控制台
Web UI 没做成完整 PiKVM。
保留的功能:
- 视频画面;
- 键盘捕获;
- 绝对/相对鼠标模式;
- 滚轮;
- 全屏;
- 连接状态;
- HID 状态;
- 快捷键;
- 管理密码修改。
删掉或者没做的功能:
- ATX;
- 虚拟介质;
- GPIO;
- OCR;
- 录像;
- 宏;
- 终端;
- 完整 kvmd API;
- 云中继。
理由也简单:首版目标是可用,不是把 PiKVM 全家桶搬进 128MB 的老设备。
认证和配置
认证是 HTTP Basic Auth。
密码文件不存明文,只存:
sha256:salt:hash
盐随机生成,hash 是 SHA-256(salt || password)。
密码可以两种方式改:
openkvm --set-password
或者在 Web 管理弹窗里改。
持久配置放在:
/config/openkvm/
里面主要是 openkvm.yaml、openkvm.passwd 和 mode。重启、刷 rootfs、切换官方/OpenKVM 都不会覆盖已有密码。
去云化
OpenKVM 模式下关闭这些东西:
- sunlogin_client;
- kvm_lan_ctrl;
- 原厂 uhttpd 30080 页面;
- oray_daemon.sh 守护;
- 原厂 OTA/文件传输相关脚本;
- LED 网络探测脚本。
这里也有一个小坑:一开始以为停掉 sunlogin 就没有外连了,后来查网络发现 LED 脚本还在周期性访问公网网站做连通性探测。Codex 把它也纳入了模式守卫:OpenKVM 模式停掉,official 模式恢复。
双模式
这个功能我觉得很有必要。
如果直接把官方东西全删了,调试时很难对照,也不方便回滚。所以最终 rootfs 里保留官方栈,但不默认启动。通过 openkvm-mode 切换:
openkvm-mode status
openkvm-mode official
openkvm-mode openkvm
openkvm-mode toggle
官方模式下,原厂 30080 页面和客户端起来;OpenKVM 模式下,只保留 80 Web 和 44022 SSH。
从 44022 chroot 会话里切换时,用:
/bin/busybox chroot /proc/1/root /usr/sbin/openkvm-mode toggle
这条后面也写进文档了。
SSH
SSH 也顺手加固了一下。
原厂有 44022 和 44033。OpenKVM 最终保留 44022,关掉 44033。Dropbear 参数也改了,禁用不需要的转发,设置 idle timeout,去掉 DSA host key。
另外重新设置了更随机的 SSH 密码,并且只保存在本地 dist 里的 ignored 文件,不进 Git。
测试
测试分几类。
主机端测试:
make -C tests clean test sanitize
覆盖 HID 帧、鼠标布局、按键映射、Basic Auth、WebSocket 控制权、非法帧、断线释放、配置持久化、模式切换等。
真机测试:
- MPP ABI 探针;
- SYS/VB 初始化;
- BT.1120 VI;
- VPSS;
- H.264 VENC;
- /video.mp4;
- /video.264;
- /mjpeg;
- Chrome/Edge 真实播放;
- 键盘输入;
- 绝对鼠标;
- 相对鼠标;
- 左右键;
- Ctrl+Alt+Delete;
- 断线释放;
- 单控制端;
- 多观看端;
- 官方/OpenKVM 往返;
- 端口检查;
- 10 分钟 soak。
原计划是跑 2 小时,我嫌太久,改成了 10 分钟。600 秒测试通过,RSS 很低,没看到 panic/Oops/segfault。
HDMI out
HDMI out 也折腾了一下。
现象是物理显示器接 A2 的 HDMI out,会隔一会儿黑一下。开始怀疑 OpenKVM 改坏了什么,后来切回官方固件也闪。
这就说明不是 OpenKVM 独有问题。
最后做了一个保守处理:把 EDID/输出目标降到 1080p30。这个不是完美解决所有显示器兼容性,只是当前设备上更稳的策略。
rootfs 制作
rootfs 构建脚本做的事大概是:
- 从用户自己的原厂 mtd2 解包;
- 保留官方客户端和旧 Web,作为 official 模式;
- 加入 OpenKVM 程序、配置和私有运行库;
- 加入 S84openkvm 和模式守卫脚本;
- 移除 OTA/文件传输相关组件;
- 删掉没用的多 ABI C++ 运行库副本,省空间;
- 重建 SquashFS;
- 检查大小,保留至少 512KiB 余量;
- 生成 rootfs 镜像、恢复 U 盘镜像、SHA-256 清单和初始密码包。
这里注意,GitHub 仓库不提交 dist、不提交 .vendor、不提交 toolchain、不提交设备密码、不提交原厂备份。
一键刷机脚本
后来又加了 scripts/flash-a2.sh。
这个脚本故意写得很简单,不做自动发现,不做复杂菜单,不自动选择设备。必须显式 –yes,必须传 admin@device-ip。
sh scripts/flash-a2.sh --yes admin@<device-ip> 44022 ./openkvm-a2-rootfs.squashfs
流程就是:
- 检查本地镜像存在;
- 算本地大小和 SHA-256;
- SSH 检查设备有 /dev/mtd2 和 flashcp;
- SCP 上传到 /tmp;
- 设备端重新算大小和 SHA-256;
- 一致才 flashcp -v 写 /dev/mtd2;
- sync;
- reboot。
没有自动写其它分区,也没有自动回滚。嵌入式设备上自动写 Flash 这件事,越少越好。
开源发布
Codex 最后把仓库整理了一遍。
- README 改成中英双语;
- 写了 doc/FLASHING.md;
- 写了架构、HID、rootfs、设备闸门、安全策略文档;
- 补了 LICENSE、NOTICE、Divinus MIT 声明;
- GitHub Actions 改成只跑 host tests 和 public guard;
- public guard 检查真实 IP、私钥、设备密码、固件镜像是否误提交;
- 创建 GitHub 仓库;
- 推送源码;
- 创建 v0.1.0 Release;
- 上传 rootfs、recovery USB 镜像、SHA-256 文件和初始密码包。
GitHub 仓库:
https://github.com/glzjin/openkvm-a2
Release:
https://github.com/glzjin/openkvm-a2/releases/tag/v0.1.0
怎么刷
如果直接用 Release 里的二进制,流程是:
curl -LO https://github.com/glzjin/openkvm-a2/releases/download/v0.1.0/openkvm-a2-rootfs.squashfs
curl -LO https://github.com/glzjin/openkvm-a2/releases/download/v0.1.0/openkvm-a2-rootfs.squashfs.sha256
sha256sum -c openkvm-a2-rootfs.squashfs.sha256
sh scripts/flash-a2.sh --yes admin@<device-ip> 44022 ./openkvm-a2-rootfs.squashfs
如果设备里之前没有 /config/openkvm/,初始 Web 密码在 Release 的 rootfs-credentials.tar.gz 里。第一次登录后应该马上改密码。
当前限制
- 只支持 OrayKVM A2;
- 不支持 A2-300/Pro;
- 没有音频;
- 没有虚拟介质;
- 没有 ATX 电源控制;
- 没有完整 kvmd API;
- 没有 VNC/IPMI;
- 没有云中继;
- 公网不要直接映射设备端口,建议 VPN 或 HTTPS 反代。
总结
这次最有意思的不是某一段代码,而是整个链路都跑通了。
从一台只能按原厂逻辑绑定云客户端的 A2,到一个能本地打开网页控制、能切回官方、能刷回原厂、能开源构建、能 GitHub Release 下载固件的 OpenKVM-A2,中间所有实际操作都是 Codex 完成的。
Codex 做的事情包括:方案判断、固件备份、MPP ABI 验证、视频链路、HID 协议、Web 控制台、认证、SSH 加固、模式切换、rootfs 重制、真机刷写、测试、GitHub 发布、Release 上传和博客发布。
我这边主要负责给目标、给设备、现场拔插线,以及在关键地方确认现象。
这个项目算是一个比较典型的例子:AI 不只是补全代码,也可以在有权限、有工具、有反馈的情况下,把一个真实硬件项目从想法一路推到可发布。
