欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

蓝牙开发那些事(4)——关于流控

程序员文章站 2024-03-17 08:00:57
...

上一章,留了个尾巴。

蓝牙开发那些事(4)——关于流控

先来个数据包格式的图,上图中,packet header和payload header中间都出现了流控(FLOW)位。

我们之间讲HCI命令的时候,提到了HCI transport层的流控。

其实HCI上层的L2CAP层,也提供了流控服务的。

是不是概念很搞?

接下来我们具体讲一下流控。

其实只要有传输就会有流控,在我们蓝牙的体系里,数据的流动有两个关键位置,一是介于controller和host之间的HCI,这个通过hci transport层的流控实现,我们前面讲过hci 命令,本章将继续讲到hci data的流控。

二是空中包,这个流控有可以分为LC层的流控和l2cap层的流控,LC层的流控是主要方式,通过数据包中packet header的流控位实现,l2cap层的流控通过一种特殊的s-frame(rr、rej)来实现,不过一般l2cap层的流控是不打开的,下文将展开讲。

先说packet header中的流控,packet header都是controller中的LC层自动填充的,那么这里的流控位是从哪里来的呢?

既然是LC层的东西,答案自然是controller层。

Controller接收也有一个buffer的概念,自然不可能无限制接收空中包,当controller中的buffer塞满的时候,packet header中的flow位便会置位为stop。

看上去是controller内部的行为,host改变不了对不对?

实际上我们可以做一个简单的实验的,因为数据的流动方向是,空中—>controlleràhost,假如host一直不去controller取数据的时候,空中包自然会把controller塞满。

我们可以利用hci层的流控机制,之前我们在第一章中已经讲述了hci command的流控,这里就要涉及到hci data的流控。

我们知道hci acl data是双向的,由controller流向host的是remote端发过来的数据,由host流向controller的是本地发给peer端的数据。

当打开controller到host方向上的流控的时候,首先host向controller发送host buffer size command通知到controller,大概host可以存放多少包数据,之后,controller向host上传acl data的时候,controller内部会计数,把host的buffer数减去1,当host处理掉一包数据之后,会向controller下发host number of complete packets command,来通知到controller,host释放掉一包数据的空间了,由controller内部计数,把host的buffer数加上1。

假如我们故意不下发host number of complete packets command的时候,controller的内部计数变成0了以后,就无法向host上传acl data数据了。

再过一段时间,controller的接收buffer就会满,此时我们如果看空中包的话,controller回复给远端的数据包,就会看到packet header中的flow位变成stop了。

笔者以前工作中就遇到过这样的情况,host的buffer空间捉襟见肘,只能通过流控的方式来减缓对端发送的速度。

一般来讲,HOST的buffer相对controller来说要大一些,需要使用流控的场合较少。

我们抓包的数据中,就没有controller向host方向的流控,但是host向controller方向的流控是有的。

Host向controller方向的流控的方式有packet based和data block based两种,也大同小异,只是计量方式不同而已,前者是包的个数,后者是包的大小。

我们这次抓的数据包是选择的packet based的方式:

蓝牙开发那些事(4)——关于流控

首先host在连接阶段会向controller发送read buffer size的命令

蓝牙开发那些事(4)——关于流控

随后controller回复了最多支持的acl data packets的数量是8个。

然后我们看看当host向controller下发acl data的时候会发生什么:

蓝牙开发那些事(4)——关于流控

图中标出的两条数据,第一条是向远端发送的一条acl data数据,(protocol栏下面是l2cap的都是这种数据),这个时候协议栈内部是有个计数的,controller的buffer数量从8会减为7。

然后跨越千山万水,在第二个标志处,controller向host返回了number of completed packets event,其中number of completed packets的数目是1,这表示controller处理掉了一包数据,可以释放buffer了,于是controller的buffer数量会从7变为8。

好了,packet header的flow我们讲完了,payload header中也有一个flow位,这是什么鬼?

Lc层的流控相当于一个总阀,l2cap层以上,通过cid区分,建立了多个逻辑链路,每个逻辑链路也可能带有flow功能的,这个flow功能就是l2cap层提供的服务了,我们下一章再讲吧。

接下来,我们看看流控在btstack的体现。

对于controller向host发送的方向来说, 在收到acl data的时候:

static void acl_handler(uint8_t *packet, int size){



    // log_info("acl_handler: size %u", size);



    // get info

    hci_con_handle_t con_handle = READ_ACL_CONNECTION_HANDLE(packet);

    hci_connection_t *conn      = hci_connection_for_handle(con_handle);

    uint8_t  acl_flags          = READ_ACL_FLAGS(packet);

    uint16_t acl_length         = READ_ACL_LENGTH(packet);



    // ignore non-registered handle

    if (!conn){

        log_error( "hci.c: acl_handler called with non-registered handle %u!" , con_handle);

        return;

    }



    // assert packet is complete   

    if ((acl_length + 4) != size){

        log_error("hci.c: acl_handler called with ACL packet of wrong size %d, expected %u => dropping packet", size, acl_length + 4);

        return;

    }



#ifdef ENABLE_CLASSIC

    // update idle timestamp

    hci_connection_timestamp(conn);

#endif



#ifdef ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL

    hci_stack->host_completed_packets = 1;

    conn->num_packets_completed++;

#endif

对于host_completed_packets置1,然后会回复hci_host_num_completed_packets给controller,再把host_completed_packet置0。

对于host向controller的方向来说,当发送acl data给对端的时候,调用hci_send_acl_packet_fragments函数,其中会执行

   connection->num_packets_sent++;

将hci_connection_t 结构体的num_packets_sent加1,这个值很重要,因为在判断是否可以向controller发送数据的时候,hci_can_send_prepared_acl_packet_now中是调用hci_number_free_acl_slots_for_handle函数去判断的 

   for (it = (btstack_linked_item_t *) hci_stack->connections; it != NULL; it = it->next){

        hci_connection_t * connection = (hci_connection_t *) it;

        if (hci_is_le_connection(connection)){

            num_packets_sent_le += connection->num_packets_sent;

        }

        if (connection->address_type == BD_ADDR_TYPE_ACL){

            num_packets_sent_classic += connection->num_packets_sent;

        }

    }

    log_debug("ACL classic buffers: %u used of %u", num_packets_sent_classic, hci_stack->acl_packets_total_num);

    int free_slots_classic = hci_stack->acl_packets_total_num - num_packets_sent_classic;

根据controller报告给host的buffer大小(通过hci read buffer size command取得),减去这个num_packets_sent就是还剩下的buffer,如果buffer 为0自然就不能再发了。

当收到number of completed packets event的时候,这个num_packets_sent就可以释放。

case HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS:{

            if (size < 3) return;

            uint16_t num_handles = packet[2];

            if (size != (3 + num_handles * 4)) return;

            uint16_t offset = 3;

            for (i=0; i<num_handles;i++){

                handle = little_endian_read_16(packet, offset) & 0x0fff;

                offset += 2;

                uint16_t num_packets = little_endian_read_16(packet, offset);

                offset += 2;

               

                conn = hci_connection_for_handle(handle);

                if (!conn){

                    log_error("hci_number_completed_packet lists unused con handle %u", handle);

                    continue;

                }

               

                if (conn->num_packets_sent >= num_packets){

                    conn->num_packets_sent -= num_packets;

                } else {

                    log_error("hci_number_completed_packets, more packet slots freed then sent.");

                    conn->num_packets_sent = 0;

                }