传统蓝牙HCI连接的流程介绍

一. 声明

本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下:

第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,产生背景,发展轨迹,市面蓝牙介绍,以及蓝牙开发板介绍。

第二篇:Transport层介绍,主要介绍蓝牙协议栈跟蓝牙芯片之前的硬件传输协议,比如基于UART的H4,H5,BCSP,基于USB的H2等

第三篇:传统蓝牙controller介绍,主要介绍传统蓝牙芯片的介绍,包括射频层(RF),基带层(baseband),链路管理层(LMP)等

第四篇:传统蓝牙host介绍,主要介绍传统蓝牙的协议栈,比如HCI,L2CAP,SDP,RFCOMM,HFP,SPP,HID,AVDTP,AVCTP,A2DP,AVRCP,OBEX,PBAP,MAP等等一系列的协议吧。

第五篇:低功耗蓝牙controller介绍,主要介绍低功耗蓝牙芯片,包括物理层(PHY),链路层(LL)

第六篇:低功耗蓝牙host介绍,低功耗蓝牙协议栈的介绍,包括HCI,L2CAP,ATT,GATT,SM等

第七篇:蓝牙芯片介绍,主要介绍一些蓝牙芯片的初始化流程,基于HCI vendor command的扩展

第八篇:附录,主要介绍以上常用名词的介绍以及一些特殊流程的介绍等。

另外,开发板如下所示,对于想学习蓝牙协议栈的最好人手一套。以便更好的学习蓝牙协议栈,相信我,学完这一套视频你将拥有修改任何协议栈的能力(比如Linux下的bluez,Android下的bluedroid)。

------------------------------------------------------------------------------------------------------------------------------------------

CSDN学院链接(进入选择你想要学习的课程):https://edu.csdn.net/lecturer/5352?spm=1002.2001.3001.4144

蓝牙交流扣扣群970324688

Github代码https://github.com/sj15712795029/bluetooth_stack

入手开发板:https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22329603896.18.5aeb41f973iStr&id=622836061708

蓝牙学习目录https://blog.csdn.net/XiaoXiaoPengBo/article/details/107727900

------------------------------------------------------------------------------------------------------------------------------------------

二. hci connection command以及产生的event

此部分指的是hci的连接,不是上层profile的连接,流程如下:

总结步骤:

1)蓝牙协议栈向芯片发送连接的command

2)蓝牙芯片上报蓝牙协议栈command status with create connection opcode

3)蓝牙芯片上报蓝牙协议栈connect complete

下面我们就来一一分析3个步骤

步骤1):蓝牙协议栈像芯片发送连接的command,连接command封包格式如下:

传统蓝牙HCI连接的流程介绍_第1张图片

参数:

BD_ADDR:要连接的remote设备的蓝牙地址

传统蓝牙HCI连接的流程介绍_第2张图片

Packet_Type:支持的数据封包类型。

传统蓝牙HCI连接的流程介绍_第3张图片

Page_Scan_Repetition_Mode:page scan重复模式

传统蓝牙HCI连接的流程介绍_第4张图片

Reserved: 保留

传统蓝牙HCI连接的流程介绍_第5张图片

Clock_Offset:时钟偏移。

传统蓝牙HCI连接的流程介绍_第6张图片

Allow_Role_Switch:是否允许角色转换

传统蓝牙HCI连接的流程介绍_第7张图片

我们直接来看下wireshark转的snoop

传统蓝牙HCI连接的流程介绍_第8张图片

代码如下:





err_t hci_connect_req(struct bd_addr_t *bdaddr, uint8_t allow_role_switch)
{
    uint8_t page_scan_repetition_mode, page_scan_mode;
    uint16_t clock_offset;
    struct bt_pbuf_t *p;
    struct hci_link_t *link = hci_new();
    struct hci_inq_res_t *inqres;

    if(link == NULL)
    {
        BT_HCI_TRACE_ERROR("ERROR:file[%s],function[%s],line[%d]  fail\n",__FILE__,__FUNCTION__,__LINE__);
        return BT_ERR_MEM; /* Could not allocate memory for link */
    }

    link->state = SEND_CREATE_CONNECTION;
    bd_addr_set(&(link->bdaddr), bdaddr);
    HCI_REG(&(hci_active_links), link);


    /* Check if module has been discovered in a recent inquiry */
    for(inqres = pcb->ires; inqres != NULL; inqres = inqres->next)
    {
        if(bd_addr_cmp(&inqres->bdaddr, bdaddr))
        {
            page_scan_repetition_mode = inqres->psrm;
            page_scan_mode = inqres->psm;
            clock_offset = inqres->co;
            break;
        }
    }
    if(inqres == NULL)
    {
        /* No information on parameters from an inquiry. Using default values */
        page_scan_repetition_mode = 0x01; /* Assuming worst case: time between
					 successive page scans starting
					 <= 2.56s */
        page_scan_mode = 0x00; /* Assumes the device uses mandatory scanning, most
			      devices use this. If no conn is established, try
			      again w this parm set to optional page scanning */
        clock_offset = 0x00; /* If the device was not found in a recent inquiry
			    this  information is irrelevant */
    }

    if((p = bt_pbuf_alloc(BT_TRANSPORT_TYPE, HCI_CREATE_CONN_PLEN, BT_PBUF_RAM)) == NULL)
    {
        BT_HCI_TRACE_ERROR("ERROR:file[%s],function[%s],line[%d] bt_pbuf_alloc fail\n",__FILE__,__FUNCTION__,__LINE__);

        return BT_ERR_MEM; /* Could not allocate memory for bt_pbuf_t */
    }

    /* Assembling command packet */
    p = hci_cmd_ass(p, HCI_CREATE_CONNECTION, HCI_LINK_CONTROL, HCI_CREATE_CONN_PLEN);
    /* Assembling cmd prameters */
    memcpy(((uint8_t *)p->payload)+3, bdaddr->addr, 6);
    bt_le_store_16((uint8_t *)p->payload,9,HCI_PACKET_TYPE);
    ((uint8_t *)p->payload)[11] = page_scan_repetition_mode;
    ((uint8_t *)p->payload)[12] = page_scan_mode;
    bt_le_store_16((uint8_t *)p->payload,13,clock_offset);
    ((uint8_t *)p->payload)[15] = allow_role_switch;
    phybusif_output(p, p->tot_len,PHYBUSIF_PACKET_TYPE_CMD);
    bt_pbuf_free(p);
    return BT_ERR_OK;
}

步骤2)芯片上报给蓝牙协议栈的command status with create connetion opcode

由于前面我们已经讲解了command status的event格式,所以我们在这就不再贴了,直接上wireshark抓的btsnoop

传统蓝牙HCI连接的流程介绍_第9张图片

Event command status的代码处理也贴过了,我们直接来看步骤3

步骤3)蓝牙芯片向蓝牙协议栈上报connection complete事件,封包格式如下

传统蓝牙HCI连接的流程介绍_第10张图片

参数:

Status:连接的状态

传统蓝牙HCI连接的流程介绍_第11张图片

Connection_Handle:连接的句柄

传统蓝牙HCI连接的流程介绍_第12张图片

BD_ADDR:连接的蓝牙地址

传统蓝牙HCI连接的流程介绍_第13张图片

Link_Type:连接的类型

传统蓝牙HCI连接的流程介绍_第14张图片

Encryption_Enabled: 是否允许加密

传统蓝牙HCI连接的流程介绍_第15张图片

下面我们来直接看下Wireshark抓的btsnoop加深下印象

传统蓝牙HCI连接的流程介绍_第16张图片

最后,我们来看看代码中是怎么处理的

case HCI_CONNECTION_COMPLETE:
        bdaddr = (void *)(((uint8_t *)p->payload)+3); /* Get the Bluetooth address */
        link = hci_get_link(bdaddr);
        switch(((uint8_t *)p->payload)[0])
        {
        case HCI_SUCCESS:
            BT_HCI_TRACE_DEBUG("hci_event_input: Conn successfully completed\n");
            if(link == NULL)
            {
                if((link = hci_new()) == NULL)
                {
                    /* Could not allocate memory for link. Disconnect */
                    BT_HCI_TRACE_DEBUG("hci_event_input: Could not allocate memory for link. Disconnect\n");
                    hci_disconnect_acl(bdaddr, HCI_OTHER_END_TERMINATED_CONN_LOW_RESOURCES);
                    /* Notify L2CAP */
                    lp_disconnect_ind(bdaddr);
                    break;
                }
                bd_addr_set(&(link->bdaddr), bdaddr);
                link->conhdl = *((uint16_t *)(((uint8_t *)p->payload)+1));
                HCI_REG(&(hci_active_links), link);
                HCI_EVENT_CONN_COMPLETE(pcb,bdaddr,ret); /* Allow applicaton to do optional configuration of link */
                BT_HCI_TRACE_DEBUG("hci_event_input: Calling l2cap cb 1\n");
                hci_get_remote_feature(bdaddr);
                if(link->state == SEND_CREATE_CONNECTION)
                {
                    lp_connect_cfm(bdaddr, ((uint8_t *)p->payload)[10], BT_ERR_OK); /* Notify L2CAP */
                }
                else
                    lp_connect_ind(&(link->bdaddr)); /* Notify L2CAP */
                link->state = OPEN;
            }
            else
            {
                link->conhdl = *((uint16_t *)(((uint8_t *)p->payload)+1));
                HCI_EVENT_CONN_COMPLETE(pcb,bdaddr,ret); /* Allow applicaton to do optional configuration of link */
                BT_HCI_TRACE_DEBUG("hci_event_input: Calling l2cap cb 2\n");
                hci_get_remote_feature(bdaddr);
                if(link->state == SEND_CREATE_CONNECTION)
                    lp_connect_cfm(bdaddr, ((uint8_t *)p->payload)[10], BT_ERR_OK); /* Notify L2CAP */
                else
                    lp_connect_ind(&(link->bdaddr)); /* Notify L2CAP */
                link->state = OPEN;
            }
            //TODO: MASTER SLAVE SWITCH??
            break;

三.hci接受连线请求command以及产生的event

老规矩,我们先来看下Wireshark整个封包的交互流程

步骤整理如下:

1)收到芯片上报给协议栈connect request的事件

2)协议栈发送给芯片接受连接请求的command

3)收到command status with accept connection req的opcode

4)收到connect complete的event

步骤1)的connection request的event的封包格式如下

传统蓝牙HCI连接的流程介绍_第17张图片

参数:

BD_ADDR:蓝牙地址

Class_of_Device: 对方的cod

Link_Type:连线类型

传统蓝牙HCI连接的流程介绍_第18张图片

老规矩直接来看下Wireshark抓的btsnoop

传统蓝牙HCI连接的流程介绍_第19张图片

然后看下代码我们是怎么处理的

case HCI_CONNECTION_REQUEST:
        bdaddr = (void *)(((uint8_t *)p->payload)); /* Get the Bluetooth address */
        link_type = ((uint8_t *)p->payload)[9];

        BT_HCI_TRACE_DEBUG("hci_event_input: recv conn req type %d\n",link_type);
        link = hci_get_link(bdaddr);
        if(link == NULL)
        {
            if((link = hci_new()) == NULL)
            {
                link->state = RECEIVED_CONNECTION_REQUEST;
                /* Could not allocate memory for link. Disconnect */

                BT_HCI_TRACE_DEBUG("hci_event_input: Could not allocate memory for link. reject\n");
                hci_reject_connection_request(bdaddr,HCI_HOST_REJECTED_DUE_TO_LIMITED_RESOURCES);
                link->state = REJECTED_CONNECTION_REQUEST;
            }
            else
            {

                bd_addr_set(&(link->bdaddr), bdaddr);
                /* TODO 此部分是否需要反转 */
                memcpy(&link->cod,((uint8_t *)p->payload)+6,3);
                link->role = HCI_ROLE_SLAVE;

                BT_HCI_TRACE_DEBUG("hci_event_input: con req cod 0x%x\n",link->cod);
                HCI_REG(&(hci_active_links), link);
                hci_accept_connection_request(bdaddr,HCI_ROLE_SLAVE);
                link->state = ACCEPTED_CONNECTION_REQUEST;
            }
        }
        else
        {
            if(link_type == HCI_LINK_TYPE_ACL)
            {
                /* Could not allocate memory for link. Disconnect */
                BT_HCI_TRACE_DEBUG("hci_event_input: con exit. reject\n");
                hci_reject_connection_request(bdaddr,HCI_ACL_CONNECTION_EXISTS);
                link->state = REJECTED_CONNECTION_REQUEST;
                hci_close(link);

            }
            else
            {
                HCI_EVENT_SCO_REQ(pcb, bdaddr, ret);
            }
        }
        break;

步骤2)蓝牙协议栈发送给蓝牙芯片接受连接请求的命令封包格式如下:

传统蓝牙HCI连接的流程介绍_第20张图片

参数:

BD_ADDR:蓝牙地址

Role:当前连接的角色

传统蓝牙HCI连接的流程介绍_第21张图片

我们来看下Wireshark的抓的snoop

传统蓝牙HCI连接的流程介绍_第22张图片

代码如下:


err_t hci_accept_connection_request(struct bd_addr_t *bdaddr, uint8_t role)
{
    struct bt_pbuf_t *p;
    if((p = bt_pbuf_alloc(BT_TRANSPORT_TYPE, HCI_ACCEPT_CON_REQ_PLEN, BT_PBUF_RAM)) == NULL)
    {
        BT_HCI_TRACE_ERROR("ERROR:file[%s],function[%s],line[%d] bt_pbuf_alloc fail\n",__FILE__,__FUNCTION__,__LINE__);

        return BT_ERR_MEM;
    }
    /* Assembling command packet */
    p = hci_cmd_ass(p, HCI_ACCEPT_CONNECTION_REQUEST, HCI_LINK_CONTROL, HCI_ACCEPT_CON_REQ_PLEN);
    /* Assembling cmd prameters */
    memcpy(((uint8_t *)p->payload) + 3, bdaddr->addr, 6);
    ((uint8_t *)p->payload)[9] = role;
    phybusif_output(p, p->tot_len,PHYBUSIF_PACKET_TYPE_CMD);
    bt_pbuf_free(p);

    return BT_ERR_OK;
}

步骤3)收到command status with accept connection req的opcode

由于command status的格式以及代码我们已经在前面介绍,所以此部分我们只贴BTSNOOP

传统蓝牙HCI连接的流程介绍_第23张图片

步骤4)收到connect complete的event

跟主动连接一样,就不在这里重复说明

这个小节其实还有很多要讲的,以上我们上面讲的主要是OGF=1的小部分命令以及event

我们在这个小节就不讲其他OGF了,我们在下一个小节,蓝牙初始化的时候来说明其他OGF,初始化主要会牵扯都OGF=3/OGF=4的部分command

你可能感兴趣的:(蓝牙协议剖析,蓝牙协议栈精讲,蓝牙连接流程)