欢迎光临
我们一直在努力

07_蓝牙(二)

上一篇把 MUSEPaper2 蓝牙从设置页面到 RTL8852BS 的完整链路梳理了一遍,这一篇开始真正修改。刚开始的现象很简单:系统里有 BluetoothHost,设置页也有蓝牙开关,但是蓝牙不能稳定完成初始化和扫描。这种问题如果只看最终现象,很容易直接怀疑内核串口驱动。但按照上一篇的分层思路,应该先找“最后一个已经成功的点”,再检查它后面的第一处失败。

第一阶段:确认真实 UART,不要相信遗留配置

Realtek vendor 配置文件位于 vendor/spacemit/musepaper2/bluetooth/rtkbt.conf,原来的配置是:

BtDeviceNode=/dev/ttyS1

但是根据 MUSEPaper2 的 DTS、pinctrl 和设备节点,实际蓝牙连接的是 UART2:

/dev/ttyS2

所以这里修改为:
ttyS2

这个问题看起来很基础,但是 AI 告诉我说板级移植中非常常见。配置可能来自另一块板、另一个 SoC 版本或旧硬件设计,文件名看起来都对,里面却藏着一个错误的设备节点。
如果 vendor 库一直打开 /dev/ttyS1,上层看到的通常只会是“Controller 无响应”,并不会友好地提示“这份配置是从别的板子复制来的”。

第二阶段:让 blue_host 真的有权限访问硬件

串口节点存在,不代表 blue_host 能打开。
OpenHarmony 的 HDF 服务会运行在自己的用户和进程中,不能拿 root shell 的结果直接代替服务进程结果。

板级 init 配置位于 device/board/spacemit/musepaper2/cfg/init.musepaper2.cfg 蓝牙需要:

"chmod 775 /sys/class/rfkill/rfkill0/state",
"chown blue_host blue_host /sys/class/rfkill/rfkill0/state",
"chown blue_host blue_host /dev/ttyS2",
"chown bluetooth bluetooth /dev/uhid"

这里分别解决:

rfkill state -> blue_host 可以控制蓝牙上电/断电
/dev/ttyS2 -> blue_host 可以打开实际 UART
/dev/uhid -> 蓝牙 HID 相关能力可以访问 uhid

可以在设备上检查:

ls -l /dev/ttyS2
ls -l /sys/class/rfkill/rfkill0/state

终端输出

这里依然是适配完成后的输出。

第三阶段:修正固件查找路径

这一点错误对于我来说太难发现了,所以会运用一些 AI 来帮我发现。嘿嘿。

RTL8852BS 的蓝牙 Controller 在初始化过程中需要加载 firmware patch 和 config。

vendor/spacemit/musepaper2/bluetooth/BUILD.gn 中的固件目标使用:
配置
最终文件进入 vendor 的 /vendor/etc/firmware/,但是 Realtek 头文件原来定义的是:

#define FW_PATCHFILE_LOCATION "/vendor/firmware/"

也就是构建系统把固件放在一个位置,运行时却去另一个位置寻找。

不要在源码里寻找这个路径,因为它是设备运行时路径。RTL8852BS 的原始固件保存在 vendor/spacemit/musepaper2/bluetooth/ 中。BUILD.gn 使用 ohos_prebuilt_etc,通过 install_images = [ chipset_base_dir ] 指定安装进 vendor 镜像,再通过 relative_install_dir = "firmware" 指定安装到 etc/firmware。因此全量构建后,文件出现在 out/musepaper2/packages/phone/vendor/etc/firmware/,烧录并挂载 vendor 分区后,设备看到的路径才是 /vendor/etc/firmware/。

最终在 vendor/spacemit/musepaper2/bluetooth/include/bt_vendor_rtk.h 修改为:
修改
这让我再次感受到,源码里“有固件文件”完全不等于设备能加载固件。至少要同时确认:

源码文件存在
GN target 包含它
安装目录正确
设备上最终文件存在
运行时代码查找路径一致

少一个都不算闭环。

第四阶段:Realtek vendor 库到底做了什么

OpenHarmony 的 VendorInterface 会动态加载:
在这里插入图片描述
在这里插入图片描述
然后通过 dlsym 查找 Realtek vendor 库导出的 BLUETOOTH_VENDOR_LIB_INTERFACE。
在这里插入图片描述
在 vendor/spacemit/musepaper2/bluetooth/src/bt_vendor_rtk.c 中:
在这里插入图片描述
初始化时会先读取 rtkbt.conf,保存蓝牙地址并初始化 H5 相关状态。
HDI 随后调用 BT_OP_POWER_ON。

Realtek vendor 库中的处理大致是:

upio_set_bluetooth_power(UPIO_BT_POWER_OFF);
usleep(20000L);
upio_set_bluetooth_power(UPIO_BT_POWER_ON);

也就是先通过 rfkill 断电,再短暂延时,然后重新上电。

先关闭、等待 20 ms、再开启,是为了确保 RTL8852BS 的蓝牙 Controller 真正经历一次有效复位,清除上一轮固件、H5、UART 波特率和内部状态,从双方都能预期的初始状态重新建立通信。

upio_set_bluetooth_power() 会扫描 /sys/class/rfkill/rfkill*/type,找到类型为 bluetooth 的实例,再向它的 state 写入 0 或 1。
之后调用 BT_OP_HCI_CHANNEL_OPEN,Realtek vendor 库打开 /dev/ttyS2,初始化 UART/H5,并向 HDI 返回一个逻辑 HCI 通道 FD。
最后的 BT_OP_INIT 会开始真正的 Controller 初始化,包括:

读取 HCI/LMP 版本
选择匹配的 firmware 和 config
读取芯片参数
必要时修改 Controller UART 波特率
同步修改 Host UART 波特率
分片下载 firmware patch
等待每一步 Command Complete Event
通知上层初始化完成

到这里,一个很重要的要求就出现了:初始化命令发出去之前,接收 HCI Event 的通道必须已经准备好。
否则命令虽然通过 UART 发给了 Controller,Controller 的响应却没人读取,初始化还是无法完成。

第五阶段:串口已经打开,blue_host 却崩溃

完成前面的调整后,已经能够证明:

rfkill 可以拉高
blue_host 可以打开 /dev/ttyS2
Realtek vendor 库已经进入 HCI channel open

但 blue_host 仍然会在后续阶段崩溃。这时候问题的性质已经变了。
如果 /dev/ttyS2 已经成功打开,说明至少下面这些部分基本成立:

UART 驱动 probe
设备节点创建
init 权限
vendor 配置节点
基本 open 路径

因此继续修改串口核心的证据并不充分,应该沿着用户态调用栈向上看。

OpenHarmony HCI HDI 的初始化入口在 drivers/peripheral/bluetooth/hci/hdi_service/hci_interface_impl.cpp,HciInterfaceImpl::Init() 会调用 VendorInterface::GetInstance()。
在这里插入图片描述
接着进入 drivers/peripheral/bluetooth/hci/hdi_service/implement/vendor_interface.cpp,其中的顺序是:

dlopen vendor 库

dlsym vendor interface

vendor init

BT_OP_POWER_ON

WatchHciChannel

watcher Start

BT_OP_INIT

问题就藏在 WatchHciChannel() 中。

最终根因:HciWatcher 没有创建

WatchHciChannel() 打开 HCI 通道以后,会根据通道数量创建协议对象。
单通道时:
单通道
多通道时则会创建 MctProtocol,分别注册 ACL 和 Event FD。
多通道
但是当时的 VendorInterface::Initialize() 中,watcher_ 并没有被构造。
也就是说,代码实际上在做:

// watcher_ 还是 nullptr
watcher_->AddFdToWatcher(...);

watcher_ 是一个 std::shared_ptr<HciWatcher>。默认构造的 shared_ptr 为空,对它使用 -> 就是空指针解引用,结果是 blue_host 直接崩溃。

这个问题的现象很像“蓝牙串口不工作”,实际却只是一个 C++ 对象生命周期问题。

最终在 BT_OP_POWER_ON 成功后、调用 WatchHciChannel() 之前恢复:
在这里插入图片描述

HciWatcher 为什么这么重要

HciWatcher 位于 drivers/peripheral/bluetooth/hci/hdi_service/implement/hci_watcher.cpp,它内部使用 select() 等待多个 FD:

HCI channel fd
wakeup pipe
可选低功耗 timeout

FD 是 Linux 为进程已经打开的文件、设备、Socket 或 Pipe 分配的数字编号。程序通过这个数字进行读取、写入、监听和关闭;本项目中 blue_host 的某个 FD 指向 /dev/ttyS2,而 HciWatcher 负责监听 vendor 层返回的 HCI 通道 FD。

工作流程可以简化成:

把 HCI FD 加入 read fd_set

select 阻塞等待

FD 可读

调用 H4Protocol/MctProtocol 的读取函数

解析 HCI Event / ACL / SCO / ISO

通过 HDI callback 送回 bluetooth_service

它还创建了一个 wakeup pipe。当新增 FD、删除 FD、修改 timeout 或停止线程时,向 pipe 写一个字节,就能让正在 select() 中阻塞的线程立即醒来。
蓝牙 Controller 初始化依赖大量“发送命令—等待事件”的交互。如果没有 watcher,命令可能发出去了,但是响应无法回到上层,整个初始化自然无法继续。

修复后的扫描结果

修复并部署 libhci_interface_service_1.0.z.so 后,再次开启蓝牙:

hidumper -s BluetoothHost -a "-br"

可以看到:
成功
到这里,蓝牙才从“串口能打开”变成了“整个 HCI 链路真的工作”。

这个问题看起来很大:设置页打不开、扫描不到设备、服务反复失败。
最后的直接修复却只有一行。

这就很令人感慨,——等等,这是技术博客,不是人生哲理分享,还是算了吧。。

下一篇记录最后的修复与测试过程,也会认真复盘中间那段不太愉快的经历:因为错误地继续修改 UART/serdev 核心,设备一度卡在 SpacemiT Logo。相比成功的那一行代码,那次失败反而让我更清楚应该怎样安全地调试启动链。

赞(0)
未经允许不得转载:171主机测评 » 07_蓝牙(二)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址