【android bluetooth 协议分析 02】【bluetooth hal 层详解 5】【高通蓝牙hal主要流程介绍-下】

1. 背景

本节主要讨论 高通 蓝牙 hal 中,的一些流程。 看看你是否都清楚如下问题:

  1. 高通芯片电如何控制?
  2. 串口是在哪里控制的?
  3. 固件如何下载?
  4. 初始化流程是怎么样的?

如果你已经对上述讨论的问题,已经很清楚了,那你无需阅读该文章,请自行忽略。当然,也可以给笨叔挑挑错。 欢迎评论,一起探讨,毕竟都是笨叔自己的理解,难免有点出入,我也想进步!!!

阅读 本篇内容前, 请先阅读
【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()

...
}

下载固件其实也很简单, 这里主要分为 两步:

  1. 创建 PatchDLManager 对象
  2. 执行 该对象的 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_ParamsHCI_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 函数总结为如下 几个步骤:

  1. 获取芯片 版本信息,并找到对应的 PatchPathManager
  2. 向芯片设置 波特率
  3. 下载 tlv 文件
  4. 关闭内部 LDO, 改为使用外部 LDO
  5. 获取当前固件(Firmware, FW)支持的附加功能(Add-on Features)列表
  6. 发送 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_EXTVDD_LDO_IN 引脚)

这通常在 芯片数据手册参考设计 中有说明。 这里可以对比 高通的参考理解。


5. 实际应用举例

举例:某蓝牙芯片的数据手册中写道:

If an external LDO is used to supply the RF domain, set LDO_RF_EN = 0 and connect external 1.2V to VDD_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);
  

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值