1. 背景
本节主要讨论 高通 蓝牙 hal 中,的一些流程。 看看你是否都清楚如下问题:
- 高通芯片电如何控制?
- 串口是在哪里控制的?
- 固件如何下载?
- 初始化流程是怎么样的?
如果你已经对上述讨论的问题,已经很清楚了,那你无需阅读该文章,请自行忽略。当然,也可以给笨叔挑挑错。 欢迎评论,一起探讨,毕竟都是笨叔自己的理解,难免有点出入,我也想进步!!!
阅读 本篇内容前, 请先阅读
【android bluetooth 协议分析 02】【bluetooth hal 层详解 3】【高通蓝牙hal主要流程介绍-上】
【android bluetooth 协议分析 02】【bluetooth hal 层详解 4】【高通蓝牙hal主要流程介绍-中】
我们继续接着 中篇 第4.小节,讲解。
本文讨论的重点是,如何打 patch
// hidl_hci/1.0/default/uart_controller.cpp
bool UartController::Init(PacketReadCallback pkt_read_cb)
{
...
patch_dl_manager = new (std::nothrow)PatchDLManager(soc_type_, uart_transport, &power_manager_);
patch_dl_manager->PerformChipInit()
...
}
下载固件其实也很简单, 这里主要分为 两步:
- 创建 PatchDLManager 对象
- 执行 该对象的 PerformChipInit 方法
2. 创建PatchDLManager
// hidl_hci/1.0/default/patch_dl_manager.cpp
PatchDLManager::PatchDLManager(BluetoothSocType soc_type, HciUartTransport* transport, PowerManager* power_manager) :
soc_type_(soc_type), uart_transport_(transport), power_manager_(power_manager), dnld_fd_in_progress_(-1)
{
ALOGI("%s", __func__);
bt_logger_ = Logger::Get();
fd_transport_ = uart_transport_->GetCtrlFd(),
wait_vsc_evt_ = true;
patch_dnld_pending_ = false;
secure_bridge_enabled = false;
is_mode_change_needed = false;
elf_config_read_ = false;
unified_hci = false; // 这里将 unified_hci 设置为 false
memset(&add_on_features, 0, sizeof(add_on_features));
LoadPatchMaptable(); // 主要关注这里
}
在 PatchDLManager 构造函数里面,最重要的是 LoadPatchMaptable
// hidl_hci/1.0/default/patch_dl_manager.cpp
void PatchDLManager::LoadPatchMaptable() {
...
PatchPathInfoMap_.insert(std::make_pair<uint64_t, PatchPathManager*>(HASTINGS_VER_2_0,
new XmemPatchPathManager("HST2_0", "htbtfw20.tlv", "htnv20.bin",
"htbtxm.tlv", "htnv20xm.bin")));
...
}
- 在 LoadPatchMaptable 中,会有很多 PatchPathInfoMap_.insert。 我们只关注我们用到的 HASTINGS_VER_2_0
其次 我们发现在构造函数中将 unified_hci 设置为 false. 表面我们默认使用 标准的 HCI 命令格式
1. unified hci 和 hci 之间的区别
蓝牙中的 Unified HCI(统一 HCI) 和传统 HCI(标准 HCI) 的区别对比表格
对比项 | 标准 HCI(HCI) | 统一 HCI(Unified HCI) |
---|---|---|
定义 | Bluetooth 规范中定义的标准 Host Controller Interface | 厂商扩展的一种统一命令格式接口(通常基于 Vendor Command) |
命令结构 | 多种标准命令组,如 Controller & Baseband、LE Controller、Link Control 等 | 将所有厂商扩展命令统一到一个格式中,如 HCI_VS_UNIFIED_CMD |
适配性 | 不同厂商命令格式差异大,适配复杂 | 命令格式标准化,适配多个芯片更容易 |
命令数量 | 由蓝牙核心规范定义,命令数量固定 | 支持厂商自定义子命令(Sub-opcode),命令功能更丰富 |
使用方式 | 主机通过标准 HCI 命令与控制器通信 | 主机通过统一入口发送子命令(统一接口 + 参数区) |
扩展能力 | 扩展性有限,复杂功能需额外定义 Vendor Command | 扩展性强,便于支持 LE Audio、功耗调节、天线切换等新特性 |
兼容性 | 所有蓝牙控制器都支持 | 仅部分 SoC 支持,需查看芯片手册或 FW 支持情况 |
典型命令 | HCI_Read_Local_Version , HCI_LE_Set_Adv_Params |
HCI_VS_UNIFIED_CMD + sub-opcode = READ_FEATURES |
开发成本 | 跨平台适配较复杂 | 同一套命令可适配多个型号,开发效率更高 |
应用场景 | 标准蓝牙功能(连接、配对、广播等) | 高级特性配置(LE Audio, TWS 配对、板载 LDO 切换等) |
总结 :
标准 HCI 是蓝牙协议规定的基础接口,而 Unified HCI 是厂商为简化命令扩展和多型号芯片适配而提供的统一入口方式,便于驱动层统一管理高级特性。
3. 调用 PerformChipInit
int PatchDLManager::PerformChipInit()
{
// 这里会向芯片 获取芯片内部 mac 地址
BluetoothAddress::GetLocalAddress(vnd_local_bd_addr_);
// 确保 rts 使能, 告诉蓝牙芯片,主机已经准备好接受数据,你可以发送数据给我
/* Workaround UART issue: Make sure RTS is flowed ON in case it was not flowed on during cleanup due to UART issue */
err = uart_transport_->Ioctl(USERIAL_OP_FLOW_ON, &flags);
ret = SocInit(vnd_local_bd_addr_, is_emb_wp_mode);
return ret;
}
int PatchDLManager::SocInit(uint8_t *bdaddr, bool is_emb_wp_mode)
{
// 1.1 获取芯片 版本信息
if ((err = PatchVerReq()) < 0) {
}
ALOGI("%s: Chipset Version (0x%16llx)", __func__,
(unsigned long long)chipset_ver_); // Chipset Version (0x400a020000100200)
// 1.2. 根据 chipset version 从 PatchPathInfoMap_ 找到对应 的patch 路径信息, 这里找到的就是 HASTINGS_VER_2_0
auto itr = PatchPathInfoMap_.find(chipset_ver_);
if (itr != PatchPathInfoMap_.end())
info = itr->second;
// 2. 向芯片设置 波特率为 3M
err = SetBaudRateReq();
ALOGI("%s: Baud rate changed successfully ", __func__);
// 3. 下载 tlv 文件
err = DownloadTlvFile();
ALOGI("%s: Download TLV file successfully ", __func__);
/* 4. 关闭内部 LDO(一种线性稳压器,用于将较高的输入电压稳压成较低的输出电压),改为使用外部 LDO*/
err = DisableInternalLdo();
/* get chipset supported feature request */
ALOGI("%s: chipset_ver_: 0x%16llx Calling get addon feature",__func__,
(unsigned long long)chipset_ver_);
// 5. 获取当前固件(Firmware, FW)支持的附加功能(Add-on Features)列表
GetAddOnFeatureList();
// 7. 发送 reset 命令
err = HciReset();
if ( err < 0 ) {
} else {
ALOGI("HCI Reset is done\n");
}
dnld_fd_in_progress_ = -1;
return err;
}
我们可以将 PatchDLManager::SocInit 函数总结为如下 几个步骤:
- 获取芯片 版本信息,并找到对应的 PatchPathManager
- 向芯片设置 波特率
- 下载 tlv 文件
- 关闭内部 LDO, 改为使用外部 LDO
- 获取当前固件(Firmware, FW)支持的附加功能(Add-on Features)列表
- 发送 reset 命令
1. 获取芯片 版本信息,并找到对应的 PatchPathManager
// 1.1 获取芯片 版本信息
if ((err = PatchVerReq()) < 0) {
}
ALOGI("%s: Chipset Version (0x%16llx)", __func__,
(unsigned long long)chipset_ver_); // Chipset Version (0x400a020000100200)
// 1.2. 根据 chipset version 从 PatchPathInfoMap_ 找到对应 的patch 路径信息, 这里找到的就是 HASTINGS_VER_2_0
auto itr = PatchPathInfoMap_.find(chipset_ver_);
if (itr != PatchPathInfoMap_.end())
info = itr->second;
#define EDL_PATCH_VER_REQ_CMD (0x19)
int PatchDLManager::PatchVerReq()
{
int size, err = 0;
unsigned char cmd[HCI_MAX_CMD_SIZE];
unsigned char rsp[HCI_MAX_EVENT_SIZE];
char dst_buff[MAX_BUFF_SIZE] = {'\0'};
char res_buff[MAX_BUFF_SIZE] = {'\0'};
struct timeval tv;
ALOGI("%s: Sending Get Version CMD to SOC", __func__);
// 组合要给 controller 发送的数据格式
FrameHciPkt(cmd, EDL_PATCH_VER_REQ_CMD/*0x19*/, 0, -1, EDL_PATCH_CMD_LEN/*1*/);
/* Total length of the packet to be sent to the Controller */
size = (HCI_CMD_IND/*1*/ + HCI_COMMAND_HDR_SIZE/*3*/ + EDL_PATCH_CMD_LEN/*1*/); // 5
/* 将上述 cmd 命令 通过串口发送给 控制器 */
err = HciSendVsCmd((unsigned char*)cmd, rsp, size);
...
return err;
}
1. PatchDLManager::FrameHciPkt
/* HCI Packet types */
#define HCI_COMMAND_PKT 0x01
#define HCI_VENDOR_CMD_OGF 0x3F
#define HCI_PATCH_CMD_OCF (0)
#define cmd_opcode_pack(ogf, ocf) (uint16_t)((ocf & 0x03ff)|(ogf << 10))
void PatchDLManager::FrameHciPkt(
unsigned char *cmd,
int edl_cmd, unsigned int p_base_addr,
int segtNo, int size
)
{
int offset = 0;
hci_command_hdr *cmd_hdr;
memset(cmd, 0x0, HCI_MAX_CMD_SIZE);
cmd_hdr = (hci_command_hdr*)(cmd + 1);
cmd[0] = HCI_COMMAND_PKT; // 0x01
cmd_hdr->opcode = cmd_opcode_pack(HCI_VENDOR_CMD_OGF/*0x3f*/, HCI_PATCH_CMD_OCF/*0*/); // 0xfc00
cmd_hdr->plen = size; // 0x01
cmd[4] = edl_cmd; // 0x19
switch (edl_cmd) {
case EDL_PATCH_VER_REQ_CMD:
// 将 cmd 打印出来方便调试:
// HCI-CMD -1: 0x1 0x0 0xfc 0x1 0x19
ALOGI("%s: Sending EDL_PATCH_VER_REQ_CMD", __func__);
ALOGI("HCI-CMD %d:\t0x%x \t0x%x \t0x%x \t0x%x \t0x%x",
segtNo, cmd[0], cmd[1], cmd[2], cmd[3], cmd[4]);
break;
default:
ALOGE("%s: Unknown EDL CMD !!!", __func__);
}
}
2. PatchDLManager::HciSendVsCmd
int PatchDLManager::HciSendVsCmd(unsigned char *cmd, unsigned char *rsp, int size)
{
int ret = 0;
char dst_buff[MAX_BUFF_SIZE] = {'\0'};
struct timeval tv;
// 将 cmd 通过 串口发送给 controller
ret = uart_transport_->UartWrite(cmd, size);
if (wait_vsc_evt_) {
/* Check for response from the Controller */
if (!unified_hci) {
// 读取该命令所对应的 控制器 返回的事件
if (ReadVsHciEvent(rsp, HCI_MAX_EVENT_SIZE) < 0) {
}
ALOGI("%s: Received HCI-Vendor Specific Event from SOC", __func__);
}
else
{
}
}
failed:
return ret;
}
1. HciUartTransport::UartWrite
int HciUartTransport::UartWrite(const uint8_t *buf, int len)
{
std::unique_lock<std::mutex> guard(internal_mutex_);
return WriteSafely(buf, len);
}
int HciUartTransport::WriteSafely(const uint8_t *data, int length)
{
int write_len = 0;
while (length > 0) {
ssize_t ret = write(ctrl_fd_, data + write_len, length);
write_len += ret;
length -= ret;
}
return write_len;
}
- 最终 通过 ctrl_fd_ 节点写入
- 那这里的 ctrl_fd_ 是谁呢? /dev/ttyHS0
回顾一下之前的流程:
bool HciUartTransport::InitTransport(tUSERIAL_CFG *p_cfg)
{
uint32_t baud;
uint8_t data_bits;
uint16_t parity;
uint8_t stop_bits;
struct termios termios;
ctrl_fd_ = -1;
// 当前 我们的 soc_type_ = BT_SOC_HASTINGS
if (soc_type_ == BT_SOC_CHEROKEE) {
} else {
ctrl_fd_ = OpenUart(uart_device_, p_cfg);
ALOGD("%s: soc_type(%d), opening '%s' return fd=%d", __func__, soc_type_,
uart_device_, ctrl_fd_);
if (ctrl_fd_ < 0)
return false;
}
return true;
}
HciUartTransport(HealthInfoLog* theHealthInfo) {
ctrl_fd_ = -1;
data_fd_ = -1;
health_info = theHealthInfo;
Util::getUartDevice(uart_device_); // 这里就已经获取到 我们的 /dev/ttyHS0 设备了
};
2. PatchDLManager::ReadVsHciEvent
int PatchDLManager::ReadVsHciEvent(unsigned char* buf, int size)
{
int tot_len;
bool collecting_ram_dump = false;
unsigned short int opcode;
do {
tot_len = ReadNewHciEventWithTimer(buf, size);
if (buf[1] == LOG_BT_EVT_VENDOR_SPECIFIC) {
...
} else if (buf[1] == EVT_CMD_COMPLETE) {
ALOGI("%s: Expected CC", __func__);
if (tot_len > UNIFIED_HCI_CC_MIN_LENGTH) {
opcode = (buf[4] | (buf[5] << 8));
if (((HCI_VS_WIPOWER_CMD_OPCODE == opcode) && (UNIFIED_HCI_CODE == buf[6])) ||
((HCI_VS_GET_VER_CMD_OPCODE == opcode) && (buf[7] == EDL_PATCH_VER_REQ_CMD) /*这里会满足这个条件*/)) {
unified_hci = true; // 将当前命令的事件 标识为 一个 unified hci cmd
ALOGI("HCI Unified command interface supported");
}
}
} else {
}
} while (collecting_ram_dump);
/* Check if the set patch command is successful or not */
if (GetVsHciEvent(buf) != HCI_CMD_SUCCESS)
return -1;
return tot_len;
}
1. PatchDLManager::ReadNewHciEventWithTimer
int PatchDLManager::ReadNewHciEventWithTimer(unsigned char* buf, int size)
{
int retval;
unsigned char protocol_byte;
unsigned char hdr[BT_EVT_HDR_SIZE];
unsigned char packet_len;
unsigned short tot_len;
fd_set set;
struct timeval timeout;
ALOGI("%s: >", __func__);
FD_ZERO(&set);
FD_SET(fd_transport_, &set);
/* timer value */
timeout.tv_sec = 0; /* in second */
timeout.tv_usec = HCI_EVENT_READ_TIMEOUT_IN_US; /* in usecond */
do {
// 通过 select 监听 串口, 如果有数据上来就返回
retval = select(fd_transport_ + 1, &set, NULL, NULL, &timeout);
// 从串口读一个字节
retval = uart_transport_->Read(&protocol_byte, 1);
// 如果是一个 hci event ,就退出 循环。否则继续 循环读
if (protocol_byte == LOG_BT_EVT_PACKET_TYPE) {
break;
} else {
ALOGI("%s: Got an invalid proto byte: %d", __func__, protocol_byte);
}
} while (1);
// 继续 select 监听串口
retval = select(fd_transport_ + 1, &set, NULL, NULL, &timeout);
// 读两个字节 表示头
retval = uart_transport_->Read(hdr, BT_EVT_HDR_SIZE/*2*/);
ALOGI("read scucesssssfully HDR");
packet_len = hdr[BT_EVT_HDR_LEN_OFFSET/*1*/]; // 读取包的长度
ALOGI("packet_len: %d\n", packet_len);
buf[0] = protocol_byte; // 将 0 , 1, 2字节拷贝到 rsp 中。
memcpy(buf + 1, hdr, BT_EVT_HDR_SIZE);
// 继续 select 监听串口
retval = select(fd_transport_ + 1, &set, NULL, NULL, &timeout);
// 读取 包中所指定的消息的长度
retval = uart_transport_->Read(buf + BT_EVT_HDR_SIZE + 1, packet_len);
tot_len = packet_len + BT_EVT_HDR_SIZE + 1; // 总的包长度
ALOGI("read scucesssssfully payload: tot_len: %d", tot_len);
return tot_len;
}
controller 返回给我们的事件内容,全部保存在 rsp 数组中了。
2. PatchDLManager::GetVsHciEvent
01-14 17:15:30.214035 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: HCI Unified command interface supported
01-14 17:15:30.214042 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: GetVsHciEvent: Received HCI-Vendor Specific event
01-14 17:15:30.214049 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: GetVsHciEvent: Opcode: 0xfc00
01-14 17:15:30.214056 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: GetVsHciEvent: ocf: 0x0
01-14 17:15:30.214062 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: GetVsHciEvent: ogf: 0x3f
01-14 17:15:30.214068 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: GetVsHciEvent: Status: 0x0
01-14 17:15:30.214074 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: GetVsHciEvent: Sub-Opcode: 0x19
01-14 17:15:30.214081 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: GetVsHciEvent: Parameter Length: 0x12
01-14 17:15:30.214087 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: GetVsHciEvent: Command Request Response
01-14 17:15:30.214094 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: unified Current Product ID : 0x00000010
01-14 17:15:30.214100 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: unified Current Patch Version : 0x0d2b
01-14 17:15:30.214106 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: unified Current ROM Build Version : 0x0200
01-14 17:15:30.214113 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: unified Current SOC Version : 0x400a0200
int PatchDLManager::GetVsHciEvent(unsigned char *rsp)
{
int err = 0;
unsigned char paramlen = 0;
unsigned char EMBEDDED_MODE_CHECK = 0x02;
unsigned int opcode = 0;
unsigned char subOpcode = 0;
unsigned int ocf = 0;
unsigned int ogf = 0;
unsigned char status = 0;
uint8_t baudrate_rsp_status_offset = 0;
uint8_t addon_features_bitmask_offset = 0;
if ( (rsp[EVENTCODE_OFFSET] == VSEVENT_CODE) || (rsp[EVENTCODE_OFFSET] == EVT_CMD_COMPLETE))
ALOGI("%s: Received HCI-Vendor Specific event", __func__);
else {
...
}
if (!unified_hci) {
...
} else {
paramlen = rsp[EVT_PLEN];
opcode = rsp[5]<<8 | rsp[4];
ocf = opcode & 0x03ff;
ogf = opcode >> 10;
status = rsp[6];
subOpcode = rsp[7];
ALOGI("%s: Opcode: 0x%x", __func__, opcode);
ALOGI("%s: ocf: 0x%x", __func__, ocf);
ALOGI("%s: ogf: 0x%x", __func__, ogf);
ALOGI("%s: Status: 0x%x", __func__, status);
ALOGI("%s: Sub-Opcode: 0x%x", __func__, subOpcode);
ALOGI("%s: Parameter Length: 0x%x", __func__, paramlen);
}
switch ( ocf ) {
case EDL_CMD_REQ_RES_EVT:
ALOGI("%s: Command Request Response", __func__);
HandleEdlCmdResEvt(subOpcode, paramlen, rsp); // 这个函数就是来具体解析 我们收到的 rsp 数组里面的数据。从中提取出 我们的版本信息。
break;
...
default:
ALOGE("%s: Not a valid status!!!", __func__);
err = -1;
break;
}
failed:
return err;
}
void PatchDLManager::HandleEdlCmdResEvt(unsigned char subOpcode, unsigned char paramlen,
unsigned char* rsp)
{
...
switch (subOpcode) {
case EDL_PATCH_VER_RES_EVT: // 0x19
case EDL_APP_VER_RES_EVT:
if (!unified_hci) {
} else {
productid = (unsigned int)(rsp[PATCH_PROD_ID_OFFSET_UNIFIED + 3] << 24 |
rsp[PATCH_PROD_ID_OFFSET_UNIFIED + 2] << 16 |
rsp[PATCH_PROD_ID_OFFSET_UNIFIED + 1] << 8 |
rsp[PATCH_PROD_ID_OFFSET_UNIFIED] );
ALOGI("\t unified Current Product ID\t\t: 0x%08x", productid);
/* Patch Version indicates FW patch version */
patchversion = (unsigned short)(rsp[PATCH_PATCH_VER_OFFSET_UNIFIED + 1] << 8 |
rsp[PATCH_PATCH_VER_OFFSET_UNIFIED] );
ALOGI("\t unified Current Patch Version\t\t: 0x%04x", patchversion);
/* ROM Build Version indicates ROM build version like 1.0/1.1/2.0 */
buildversion =
(int)(rsp[PATCH_ROM_BUILD_VER_OFFSET_UNIFIED + 1] << 8 |
rsp[PATCH_ROM_BUILD_VER_OFFSET_UNIFIED] );
ALOGI("\t unified Current ROM Build Version\t: 0x%04x", buildversion);
if (paramlen - 10) {
soc_id =
(unsigned int)(rsp[PATCH_SOC_VER_OFFSET_UNIFIED + 3] << 24 |
rsp[PATCH_SOC_VER_OFFSET_UNIFIED + 2] << 16 |
rsp[PATCH_SOC_VER_OFFSET_UNIFIED + 1] << 8 |
rsp[PATCH_SOC_VER_OFFSET_UNIFIED] );
ALOGI("\t unified Current SOC Version\t\t: 0x%08x", soc_id);
}
}
chipset_ver_ = QCA_BT_VER(soc_id, productid, buildversion);
break;
}
}
上面我完整的分析了 高通的hal 如何 发送数据给 controller 并,解析对应的 hci event 事件。
2. 向芯片设置 波特率
// 2. 向芯片设置 波特率为 3M
/* Change baud rate 115.2 kbps to 3Mbps*/
err = SetBaudRateReq();
ALOGI("%s: Baud rate changed successfully ", __func__);
int PatchDLManager::SetBaudRateReq()
{
int size, err = 0;
unsigned char cmd[HCI_MAX_CMD_SIZE];
unsigned char rsp[HCI_MAX_EVENT_SIZE];
hci_command_hdr *cmd_hdr;
int flags;
uint8_t bt_baud_rate = uart_transport_->GetMaxBaudrate(); // 获取最大 波特率 3M
struct timeval tv;
memset(cmd, 0x0, HCI_MAX_CMD_SIZE);
cmd_hdr = (hci_command_hdr*)(cmd + 1);
cmd[0] = HCI_COMMAND_PKT; /*0x01*/
cmd_hdr->opcode = cmd_opcode_pack(HCI_VENDOR_CMD_OGF/*0x3f*/, EDL_SET_BAUDRATE_CMD_OCF/*0x48*/);
cmd_hdr->plen = VSC_SET_BAUDRATE_REQ_LEN; /*1*/
cmd[4] = bt_baud_rate;
/* Total length of the packet to be sent to the Controller */
size = (HCI_CMD_IND + HCI_COMMAND_HDR_SIZE + VSC_SET_BAUDRATE_REQ_LEN);
// 设置波特率前,先关闭流控
/* Flow off during baudrate change */
if ((err = uart_transport_->Ioctl(USERIAL_OP_FLOW_OFF, &flags)) < 0) {
}
// 通过串口将命令发送给 controller
/* Send the HCI command packet to UART for transmission */
err = uart_transport_->UartWrite(cmd, size);
// 设置最大波特率
/* Change Local UART baudrate to high speed UART */
uart_transport_->SetBaudRate(bt_baud_rate);
// 查看是否设置成功
/* Check current Baudrate */
uart_transport_->GetBaudRate();
// 打开流控
/* Flow on after changing local uart baudrate */
if ((err = uart_transport_->Ioctl(USERIAL_OP_FLOW_ON, &flags)) < 0) {
ALOGE("%s: HW Flow-on error: 0x%x \n", __func__, err);
goto error;
}
// 接收 controller 返回的事件
/* Wait for command complete event */
err = ReadHciEvent(rsp, HCI_MAX_EVENT_SIZE);
return err;
}
- uart_transport_->UartWrite 向 controller 发送cmd 和 ReadHciEvent 在上面介绍过,这里不再介绍。都类似。
- 我们这里关注一下,波特率 和流程相关的内容
1. HciUartTransport::GetMaxBaudrate
uint8_t HciUartTransport::GetMaxBaudrate()
{
switch (soc_type_) {
case BT_SOC_CHEROKEE:
#ifdef UART_BAUDRATE_3_0_MBPS
return (uint8_t) USERIAL_BAUD_3M;
#else
return (uint8_t) USERIAL_BAUD_3_2M;
#endif
break;
case BT_SOC_MOSELLE:
case BT_SOC_HAMILTON:
return (uint8_t) USERIAL_BAUD_3_2M;
break;
case BT_SOC_HASTINGS: // 这个类型
case BT_SOC_ROME:
case BT_SOC_GENOA:
/* fall through */
default:
return (uint8_t) USERIAL_BAUD_3M;
}
}
- 这里的最大波特率,是根据, 芯片 支持的最大波特率。 固定死的。 和在任何平台 例如,是高通的平台,还是 mtk 的平台,没有关系。
2. 开关流控
int HciUartTransport::Ioctl(userial_vendor_ioctl_op_t op, int *p_data)
{
int err = -1;
struct timeval tv;
char dst_buff[MAX_BUFF_SIZE];
switch (op) {
case USERIAL_OP_FLOW_ON:
ALOGI("## userial_vendor_ioctl: UART Flow On ");
ioctl(ctrl_fd_, TIOCMGET/*0x5415*/, p_data);
*p_data |= TIOCM_RTS/*0x004*/;
err = ioctl(ctrl_fd_, TIOCMSET/*0x5418*/, p_data);
break;
case USERIAL_OP_FLOW_OFF:
ALOGI("## userial_vendor_ioctl: UART Flow Off ");
ioctl(ctrl_fd_, TIOCMGET/*0x5415*/, p_data);
*p_data &= ~TIOCM_RTS/*0x004*/;
err = ioctl(ctrl_fd_, TIOCMSET/*0x5418*/, p_data);
break;
case USERIAL_GET_ERR_CODE:
err = ioctl(ctrl_fd_, MSM_GENI_SERIAL_TIOCFAULT/*0x54EC*/, NULL);
break;
default:
break;
}
return err;
}
开关流控在高通平台 也是很容易做的:
- 开流程 : 将 TIOCM_RTS 置位
- 关流程: 清除 TIOCM_RTS 位
同时也可以通过 MSM_GENI_SERIAL_TIOCFAULT 命令 ,读取高通串口的异常错误。
3. 设置串口波特率
void HciUartTransport::SetBaudRate(uint8_t userial_baud)
{
uint32_t tcio_baud;
struct termios termios;
struct timeval tv;
ALOGI("## userial_vendor_set_baud: %d", userial_baud);
userial_to_tcio_baud(userial_baud, &tcio_baud);
{
tcgetattr(ctrl_fd_, &termios);
cfsetospeed(&termios, tcio_baud);
cfsetispeed(&termios, tcio_baud);
tcsetattr(ctrl_fd_, TCSADRAIN, &termios); /* don't change speed until last write done */
}
}
3. 下载 tlv 文件
// 3. 下载 tlv 文件
err = DownloadTlvFile();
ALOGI("%s: Download TLV file successfully ", __func__);
int PatchDLManager::DownloadTlvFile()
{
int tlv_size = -1;
int err = -1;
char nvm_file_path_bid[256] = { 0, };
char nvm_alt_file_path_bid[256] = { 0, };
int nvm_file_path_len = strlen(nvm_file_path);
int nvm_alt_file_path_len = 0;
int xmem_nvm_file_path_len = 0;
int board_id_cmd_status = -1;
char dst_buff[MAX_BUFF_SIZE];
struct timeval tv;
// 打开我们 tlv 文件,并将 tlv 文件的内容读到缓存中。
if ((tlv_size = GetTlvFile(rampatch_file_path, rampatch_alt_file_path)) > 0) {
...
err = TlvDnldReq(tlv_size); // 这里开始下载 tlv 文件
}
// 获取 board id, 也是发送高通自定义的一些命令 Board Id 88 65
if ((board_id_cmd_status = GetBoardIdReq()) < 0) {
ALOGE("%s: failed to get board id(0x%x)", __func__, err);
}
tlv_size = -1;
err = -1;
if (IsXmemDownload(&tlv_size)) {
...
} else {
/* NVM TLV file Downloading */
tlv_size = -1;
err = -1;
nvm_alt_file_path_len = strlen(nvm_alt_file_path);
nvm_file_path_len = strlen(nvm_file_path);
if (board_id_cmd_status != -1) {
if (nvm_file_path_len != 0) {
memcpy(nvm_file_path_bid, nvm_file_path, nvm_file_path_len - 2);
strlcat(nvm_file_path_bid, (char*)board_id_, sizeof(nvm_file_path_bid));
}
if (nvm_alt_file_path_len != 0){
memcpy(nvm_alt_file_path_bid, nvm_alt_file_path, nvm_alt_file_path_len - 2);
strlcat(nvm_alt_file_path_bid, (char*)board_id_, sizeof(nvm_alt_file_path_bid));
}
// 这里最终会 打开并加载 /vendor/bt_firmware/image/htnv20.bin 文件
if ((tlv_size = GetTlvFile(nvm_file_path_bid/* /bt_firmware/image/htnv20.bin */,nvm_alt_file_path_bid /* /vendor/bt_firmware/image/htnv20.bin */)) < 0);
}
err = TlvDnldReq(tlv_size); // 走下载流程
}
return err;
}
1. PatchDLManager::GetTlvFile
int PatchDLManager::GetTlvFile(const char *file_path/* /bt_firmware/image/htbtfw20.tlv */, const char* alt_file_path/* /vendor/bt_firmware/image/htbtfw20.tlv */)
{
FILE * pFile = NULL;
int fileSize;
int readSize;
if (pFile == NULL) {
// 打开 tlv 文件
pFile = OpenPatchFile(file_path, alt_file_path);
}
if( pFile == NULL) {
return -1;
}
/* Get File Size */
fseek(pFile, 0, SEEK_END);
fileSize = ftell(pFile); // 获取文件大小
rewind(pFile);
// 在栈上 分配内存, 这里感觉高通 做的不是很好。 如果 tlv 文件过大, 会导致 栈溢出。 可以使用malloc 分配在 堆上。
pdata_buffer_ = (unsigned char*)new char[fileSize];
if (pdata_buffer_ == NULL) {
ALOGE("Allocated Memory failed");
fclose(pFile);
return -1;
}
/* Copy file into allocated buffer */
readSize = fread(pdata_buffer_, 1, fileSize, pFile); // 将 tlv 文件内容全部读出来
/* File Close */
fclose(pFile); // 关闭文件
if (readSize != fileSize) {
ALOGE("Read file size(%d) not matched with actual file size (%d bytes)", readSize, fileSize);
delete []pdata_buffer_;
return -1;
}
if (ReadTlvInfo()) // 读 tlv 文件信息
return readSize;
else
return -1;
}
1. PatchDLManager::OpenPatchFile
01-14 17:15:30.262455 714 2620 E vendor.running.bluetooth@1.0-patch_dl_manager: /bt_firmware/image/htbtfw20.tlv File Open Fail No such file or directory (2)
01-14 17:15:30.262504 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: File open /vendor/bt_firmware/image/htbtfw20.tlv succeeded
FILE* PatchDLManager::OpenPatchFile(const char *file_path, const char* alt_file_path) {
FILE *pFile = NULL;
if (!(file_path && (pFile = fopen( file_path, "r" )))) {
ALOGE("%s File Open Fail %s (%d)", file_path, strerror(errno), errno); // 打开 /bt_firmware/image/htbtfw20.tlv 失败
//Try opening from alternate path
if (!(alt_file_path && (pFile = fopen(alt_file_path, "r")))) {
ALOGE("%s File Opening from alternate path: Fail %s (%d)", alt_file_path,
strerror(errno), errno);
return NULL;
} else {
ALOGI("File open %s succeeded", alt_file_path); // 最终 成功打开 /vendor/bt_firmware/image/htbtfw20.tlv
return pFile;
}
} else {
ALOGI("File open %s succeeded", file_path);
return pFile;
}
}
2. PatchDLManager::ReadTlvInfo
01-14 17:15:30.263698 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: ====================================================
01-14 17:15:30.263754 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: TLV Type : 0x1
01-14 17:15:30.263764 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Length : 204988 bytes
01-14 17:15:30.263772 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Total Length : 204956 bytes
01-14 17:15:30.263779 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Patch Data Length : 204920 bytes
01-14 17:15:30.263785 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Signing Format Version : 0x1
01-14 17:15:30.263795 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Signature Algorithm : 0x0
01-14 17:15:30.263800 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Event Handling : 0x3
01-14 17:15:30.263805 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Reserved : 0x0
01-14 17:15:30.263810 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Product ID : 0x0010
01-14 17:15:30.263815 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Rom Build Version : 0x0200
01-14 17:15:30.263820 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Patch Version : 0x6bb7
01-14 17:15:30.263825 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Reserved : 0x0
01-14 17:15:30.263831 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: Patch Entry Address : 0x0
01-14 17:15:30.263836 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: ====================================================
bool PatchDLManager::ReadTlvInfo() {
int nvm_length, nvm_tot_len, nvm_index, i;
bool status = false;
unsigned short nvm_tag_len;
tlv_patch_info *ptlv_header;
tlv_nvm_hdr *nvm_ptr;
unsigned char data_buf[PRINT_BUF_SIZE] = { 0, };
unsigned char *nvm_byte_ptr;
ptlv_header = (tlv_patch_info*)pdata_buffer_; // 将 tlv 文件 内容,强制格式转换
/* checking for type of patch file */
if (pdata_buffer_[0] == ELF_FLAG && !memcmp(&pdata_buffer_[1], "ELF", 3)) {
} else {
/* To handle different event between rampatch and NVM */
tlv_type_ = ptlv_header->tlv_type;
tlv_dwn_cfg_ = ptlv_header->tlv.patch.dwnd_cfg;
}
if (tlv_type_ == ELF_TYPE_PATCH) {
} else if (ptlv_header->tlv_type == TLV_TYPE_PATCH ||
ptlv_header->tlv_type == TLV_TYPE_PATCH_XMEM) {
ALOGI("====================================================");
ALOGI("TLV Type\t\t\t : 0x%x", ptlv_header->tlv_type);
ALOGI("Length\t\t\t : %d bytes", (ptlv_header->tlv_length1) |
(ptlv_header->tlv_length2 << 8) |
(ptlv_header->tlv_length3 << 16));
ALOGI("Total Length\t\t\t : %d bytes", ptlv_header->tlv.patch.tlv_data_len);
ALOGI("Patch Data Length\t\t\t : %d bytes", ptlv_header->tlv.patch.tlv_patch_data_len);
ALOGI("Signing Format Version\t : 0x%x", ptlv_header->tlv.patch.sign_ver);
ALOGI("Signature Algorithm\t\t : 0x%x", ptlv_header->tlv.patch.sign_algorithm);
ALOGI("Event Handling\t\t\t : 0x%x", ptlv_header->tlv.patch.dwnd_cfg);
ALOGI("Reserved\t\t\t : 0x%x", ptlv_header->tlv.patch.reserved1);
ALOGI("Product ID\t\t\t : 0x%04x\n", ptlv_header->tlv.patch.prod_id);
ALOGI("Rom Build Version\t\t : 0x%04x\n", ptlv_header->tlv.patch.build_ver);
ALOGI("Patch Version\t\t : 0x%04x\n", ptlv_header->tlv.patch.patch_ver);
ALOGI("Reserved\t\t\t : 0x%x\n", ptlv_header->tlv.patch.reserved2);
ALOGI("Patch Entry Address\t\t : 0x%x\n", (ptlv_header->tlv.patch.patch_entry_addr));
ALOGI("====================================================");
status = true;
} else if ( (ptlv_header->tlv_type >= TLV_TYPE_BT_NVM) &&
((ptlv_header->tlv_type <= TLV_TYPE_BT_FM_NVM)) ) {
} else {
ALOGE("TLV Header type is unknown (%d) ", ptlv_header->tlv_type);
}
return status;
}
2.PatchDLManager::TlvDnldReq
01-14 17:15:30.263854 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: TlvDnldReq: TLV size: 204992, Total Seg num: 843, remain size: 143
int PatchDLManager::TlvDnldReq(int tlv_size)
{
int total_segment, remain_size, i, err = -1;
unsigned char wait_cc_evt = true;
bool is_last_seg = false;
int segment_download_len = MAX_SIZE_PER_TLV_SEGMENT; // 每段 的大小 243 字节
total_segment = tlv_size / MAX_SIZE_PER_TLV_SEGMENT; // 计算当前 tlv 文件有多少个段
remain_size = (tlv_size < MAX_SIZE_PER_TLV_SEGMENT) ? \
tlv_size : (tlv_size % MAX_SIZE_PER_TLV_SEGMENT); // 不够 一个段 的字节个数
ALOGI("%s: TLV size: %d, Total Seg num: %d, remain size: %d",
__func__, tlv_size, total_segment, remain_size);
if (tlv_type_ == TLV_TYPE_PATCH || tlv_type_ == ELF_TYPE_PATCH
|| tlv_type_ == TLV_TYPE_PATCH_XMEM) {
/* Prior to Rome version 3.2(including inital few rampatch release of Rome 3.2), the event
* handling mechanism is SKIP_EVT_NONE. After few release of rampatch for Rome 3.2, the
* mechamism is changed to SKIP_EVT_VSE_CC. Rest of the mechanism is not used for now
*/
switch (tlv_dwn_cfg_) {
case SKIP_EVT_VSE_CC:
wait_vsc_evt_ = false;
wait_cc_evt = false;
ALOGI("Event handling type: SKIP_EVT_VSE_CC");
break;
}
}
// 开始逐个段 开始下载
for (i = 0; i <= total_segment && !is_last_seg; i++) {
/* check for last segment based on remaining size
* and total number of segments.
*/
if ((remain_size && i == total_segment) ||
(!remain_size && (i + 1) == total_segment)) {
// 如果检测到当前 已经下载到最后一个段。
is_last_seg = true;
// Update segment download len if last segment is being downloaded
if (remain_size)
segment_download_len = remain_size;
ALOGI("%s: Updating seg len to %d as last segment",
__func__, segment_download_len);
}
if ((tlv_type_ == TLV_TYPE_PATCH || tlv_type_ == TLV_TYPE_PATCH_XMEM
|| tlv_type_ == ELF_TYPE_PATCH) && is_last_seg) {
/*
* 1. None of the command segments receive CCE
* 2. No command segments receive VSE except the last one
* 3. If tlv_dwn_cfg_ is SKIP_EVT_NONE then wait for VSE and CCE
* ( except CCE is not received for last segment)
*/
wait_cc_evt = false;
wait_vsc_evt_ = true;
}
patch_dnld_pending_ = true;
// 调用 TlvDnldSegment 来下载 对应的段
if ((err = TlvDnldSegment(i, segment_download_len, wait_cc_evt )) < 0) {
}
patch_dnld_pending_ = false;
}
error:
if (patch_dnld_pending_)
patch_dnld_pending_ = false;
return err;
}
1. PatchDLManager::TlvDnldSegment
01-14 17:15:30.263872 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: TlvDnldSegment: Downloading TLV Patch segment no.0, size:243
01-14 17:15:30.263877 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: FrameHciPkt: Sending EDL_PATCH_TLV_REQ_CMD
01-14 17:15:30.263884 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: HCI-CMD 0: 0x1 0x0 0xfc 0xf5 0x1e 0xf3
01-14 17:15:30.263915 714 2620 I vendor.running.bluetooth@1.0-patch_dl_manager: TlvDnldSegment: Successfully downloaded patch segment: 0
int PatchDLManager::TlvDnldSegment(int index, int seg_size, unsigned char wait_cc_evt)
{
int size = 0, err = -1;
unsigned char cmd[HCI_MAX_CMD_SIZE];
unsigned char rsp[HCI_MAX_EVENT_SIZE];
// 将当前 下载段的序号 和 段的 大小 打印出来
ALOGI("%s: Downloading TLV Patch segment no.%d, size:%d", __func__, index, seg_size);
// 前面介绍过,这里会将 我们要发送的 cmd 组合好后, 打印出来
/* Frame the HCI CMD PKT to be sent to Controller*/
FrameHciPkt(cmd, EDL_PATCH_TLV_REQ_CMD/*0x1E*/, 0, index, seg_size);
/* Total length of the packet to be sent to the Controller */
size = (HCI_CMD_IND + HCI_COMMAND_HDR_SIZE + cmd[PLEN]);
/* Initialize the RSP packet everytime to 0 */
memset(rsp, 0x0, HCI_MAX_EVENT_SIZE);
/* Send HCI Command packet to Controller */
err = HciSendVsCmd((unsigned char*)cmd, rsp, size); // 这个前面也介绍过, 通过串口发送出去
if ( err != size) {
ALOGE("Failed to send the patch payload to the Controller! 0x%x", err);
return err;
}
// 在下载 /vendor/bt_firmware/image/htbtfw20.tlv 文件时: 不用等待 命令返回事件。 所以不会执行到这里
if (!unified_hci) {
if (wait_cc_evt) { // 在下载 /vendor/bt_firmware/image/htnv20.bin 时,需要确保每个命令,都有事件响应。所以会执行到这里
err = ReadHciEvent(rsp, HCI_MAX_EVENT_SIZE);
if ( err < 0) {
ALOGE("%s: Failed to downlaod patch segment: %d!", __func__, index);
return err;
}
}
}
ALOGI("%s: Successfully downloaded patch segment: %d", __func__, index);
return err;
}
4. 关闭内部 LDO, 改为使用外部 LDO
/* 4. 关闭内部 LDO(一种线性稳压器,用于将较高的输入电压稳压成较低的输出电压),改为使用外部 LDO*/
err = DisableInternalLdo();
int PatchDLManager::DisableInternalLdo()
{
int ret = 0;
if (IsExtldoEnabled()) {
unsigned char cmd[5] = { 0x01, 0x0C, 0xFC, 0x01, 0x32 }; // 也是向 controller 发送 厂商自定义命令
unsigned char rsp[HCI_MAX_EVENT_SIZE];
ALOGI(" %s ", __func__);
ret = HciSendVsCmd(cmd, rsp, 5);
if (ret != 5) {
ALOGE("%s: Send failed with ret value: %d", __func__, ret);
ret = -1;
} else {
/* Wait for command complete event */
ret = ReadHciEvent(rsp, HCI_MAX_EVENT_SIZE);
if ( ret < 0) {
ALOGE("%s: Failed to get response from controller", __func__);
}
}
}
return ret;
}
代码很简单。不再 表述。 但是我们搞软件的 很少有人知道 LDO. 我在这里简单 介绍一下。
1. LDO 相关介绍
1. LDO(Low Dropout Regulator)
LDO 是一种线性稳压器,用于将较高的输入电压稳压成较低的输出电压。特点是压差小、噪声低,适合对电压稳定性和噪声敏感的模拟/射频电路。
蓝牙芯片内部通常集成了多个 LDO,用于给不同模块(射频、基带、存储等)提供稳定电源。
2.内部 LDO vs 外部 LDO
项目 | 内部 LDO(Internal) | 外部 LDO(External) |
---|---|---|
集成度 | 高(芯片内自带) | 低(需要外部器件) |
成本 | 较低 | 成本略高 |
灵活性 | 低(固定参数) | 高(可选型号、调节性能) |
效率 | 一般 | 更高(可选更高效率型号) |
发热 | 芯片本身发热 | 外部分担发热 |
3. 为什么要“Disable internal LDO”?
有以下几个典型场景:
1. 外部 LDO 性能更好
- 某些应用(如车规、音频设备)对电源噪声、稳定性要求高,外部 LDO 可以提供更低噪声、更稳定的供电。
- 有些外部 LDO 支持更高电流、或热性能更好,适用于高负载场景。
2. 降低芯片内部发热
- 内部 LDO 是线性稳压器,不如 DC-DC 效率高。长时间使用容易导致芯片局部发热。
- 使用外部 LDO 可以将发热“搬”到芯片外部,有助于系统热管理。
3. 系统中已有 LDO 共用
- 某些主板(如智能手机、汽车主机)已经集成高性能电源管理芯片(PMIC),其中包含多个 LDO,可以统一管理多个模块电压,避免重复供电。
4. 如何实现?
在蓝牙芯片硬件设计或固件初始化过程中,通常会有一个配置寄存器或引脚选择项来:
禁用内部 LDO(比如设置一个 LDO_EN 寄存器为 0)
启用芯片对外部电源引脚的使用(通常芯片会提供
VDD_EXT
或VDD_LDO_IN
引脚)
这通常在 芯片数据手册 或 参考设计 中有说明。 这里可以对比 高通的参考理解。
5. 实际应用举例
举例:某蓝牙芯片的数据手册中写道:
If an external LDO is used to supply the RF domain, set
LDO_RF_EN = 0
and connect external 1.2V toVDD_RF
.
意思是:如果打算使用外部 1.2V LDO 来给 RF 区域供电,必须通过寄存器禁用内部 LDO,然后把外部 LDO 输出接到指定引脚上。
6.总结
“Disable internal LDO to use external LDO instead” 的含义是:
- 关闭蓝牙芯片内部的电压稳压器(LDO);
- 改为使用板级电路中外接的 LDO 提供稳定电压;
- 目的在于获得更好的电源性能、更低的噪声或更佳的热设计。
这是一种常见的硬件设计策略,尤其是在高端或对电源敏感的应用中,比如车载蓝牙、音频设备、工业通信模块等。
5. 获取当前固件(Firmware, FW)支持的附加功能(Add-on Features)列表
// 5. 获取当前固件(Firmware, FW)支持的附加功能(Add-on Features)列表
GetAddOnFeatureList(); // 同样也是 发送高通自定义的命令
6. 发送 reset 命令
// 7. 发送 reset 命令
err = HciReset(); // 这个是标准的 hci 命令
int PatchDLManager::HciReset()
{
int size, err = 0;
unsigned char cmd[HCI_MAX_CMD_SIZE];
unsigned char rsp[HCI_MAX_EVENT_SIZE];
hci_command_hdr *cmd_hdr;
ALOGI("%s: HCI RESET ", __func__);
memset(cmd, 0x0, HCI_MAX_CMD_SIZE);
cmd_hdr = (hci_command_hdr*)(cmd + 1);
cmd[0] = HCI_COMMAND_PKT;
cmd_hdr->opcode = HCI_RESET;
cmd_hdr->plen = 0;
size = (HCI_CMD_IND + HCI_COMMAND_HDR_SIZE);
err = uart_transport_->UartWrite(cmd, size);
err = ReadCmdCmplEvent(rsp, HCI_MAX_EVENT_SIZE);
}