Linux蓝牙框架(二) - 内核协议层
前言
自BT Core 5.3在2021年发布以来,当前linux内核蓝牙协议与BlueZ协议栈最多的提交主要集中在LE Audio上,如果需要使用最新的LE Audio相关的功能,BlueZ协议栈与内核需要同时升级到最新版本。本篇文章主要讨论的主题是内核的蓝牙协议的框架,以及如何与蓝牙驱动和BlueZ协议栈交互,还是先看一下整体的框图,下面我们会介绍Bluetooth Protocol部分的内容。
蓝牙启动
内核蓝牙模块依赖CONFIG_BT宏开关,其入口函数bt_init()通过subsys_initcall()符号声明到.initcall的section中,这样在kernel_init的流程中通过执行do_initcalls函数,会把.initcall段中的函数按顺序全部执行一遍。
bt_init()函数定义在net/bluetooth/af_bluetooth.c文件中,该函数主要完成下面几个功能:
- bt_sysfs_init,创建蓝牙协议层的文件系统,用于用户空间配置/调试/查看等操作;
- sock_register,注册PF_BLUETOOTH协议族,类似PF_INET,并将bt_sock_create()函数挂载到系统sock框架中,为后续初始化PF_BLUETOOTH簇下的各种类型的socket做好准备;
- hci_sock_init <net/bluetooth/hci_sock.c>,挂载BTPROTO_HCI类型socket的操作函数实体hci_sock_family_ops,并创建HCI proc file;
- l2cap_init <net/bluetooth/l2cap_core.c>, 挂载BTPROTO_L2CAP类型socket的操作函数实体l2cap_sock_family_ops,并注册l2cap event的回调函数l2cap_cb;
- sco_init <net/bluetooth/sco.c>, 挂载BTPROTO_SCO类型socket的操作函数实体sco_sock_family_ops,并注册hci event的回调函数sco_cb;
- mgmt_init <net/bluetooth/mgmt.c>, 将mgmt(管理和配置)的处理handle(mgmt_handlers)注册到hci sockeck中,这样当hci socket接收到关于mgmt的操作命令后,就可以直接调用mgmt_handlers中对应的处理函数;
Socket接口
在include/net/bluetooth.h文件中定义了PF_BLUETOOTH协议簇下socket支持的proto协议,如下代码:
1 |
用户空间比较常用的是L2CAP 和HCI,若涉及到HFP或者A2DP等音频应用,则需要用到SCO/RFCOMM/AVDTP等proto,下面简单介绍一下L2CAP和HCI的接口和功能。
HCI socket
hci socket主要用于处理hci command和hci event; 前面已经提到kernel启动后会注册hci sock proto到PF_BLUETOOTH协议簇中,在应用层创建socket时,内核空间会调用hci_sock_create函数,挂载hci socket 操作函数实体hci_sock_ops,定义如下:
1 | /** User Space**/ |
从上面定义的hci_sock_ops接口来看,主要挂载了socket操作的接口函数,例如bind/listen/connect/accept/shutdown等函数,这里有2个函数是比较重要的.
- hci_sock_sendmsg(), 该函数主要是处理HCI_CHANNEL_CONTROL channel的数据 **hci_mgmt_cmd()**,也就是mgmt注册的操作命令,详细参考mgmt_handlers[]定义;用户空间通过该接口来控制内核的蓝牙流程和读取及配置host/controller参数等;
- hci_hci_sock_ioctl(), 该函数提供了关于hci设备的操作命令,例如获取设备列表HCIGETDEVLIST,获取设备信息HCIGETDEVINFO,设置acl_mtu HCISETACLMTU等命令,可以方便用户空间读写HCI设备;
L2CAP socket
L2CAP socket主要用于处理基于链路的数据,包括命令和事件,其注册方式和流程跟HCI socket相似,在create BTPROTO_L2CAP类型的socket时挂载l2cap_sock_ops操作接口,如下代码:
1 | static const struct proto_ops l2cap_sock_ops = { |
从上面的定义中可以看到也是常规的socket操作函数,这里介绍几个比较重要的接口:
- l2cap_sock_connect(), 该函数用于创建BREDR或者LE的L2CAP链路,并且阻塞等待链接的结果;
- l2cap_sock_bind(), 将对应cid和psm绑定到当前socket,例如ATT_CID用于创建LE的链路;
- l2cap_sock_sendmsg(), 基于L2CAP链接的数据发送,直接调用l2cap_chan_send()函数将数据发送出去;
- l2cap_sock_recvmsg(), 使用bt_sock_stream_recvmsg和bt_sock_recvmsg函数接收缓存中的数据流或event,并封装为skb返回给用户空间;
iso socket
iso socket用于处理LE Audio的相关命令和数据,主要协议实现在net/bluetooth/iso.c中,并且iso也提供了BTPROTO_ISO scoket类型的接口,但是初始化是放在mgmt的set_exp_feature handle中,在用户空间需要使能iso socket,需要指定iso的uuid,发送MGMT_OP_SET_EXP_FEATURE操作码,如下代码:
1 | /** User Space**/ |
使能iso socket后,用户空间就可以创建BTPROTO_ISO类型的socket了,上面定义了socket的操作接口iso_sock_ops,其中比较重要的有以下几个函数:
- iso_sock_bind(), 绑定地址类型以及检查广播地址;
- iso_sock_connect(), 根据目标地址的类型,创建BIS和CIS连接,广播的地址类型为全零;
- iso_sock_sendmsg(), 发送iso的数据流,会调用hci_queue_iso()函数将skb添加到队列中;
- iso_sock_recvmsg(), 接收缓存中的iso数据流,并返回给用户空间;
MGMT组件
从上面的的框图中可以看到处在了sock接口和HCI接口的中间,实际也是如此,mgmt的主要功能定义在mgmt_handlers数组中,这些接口可以让用户空间的代码非常方便的通过socket进行调用,该数组的index与mgmt_commands数组对应,mgmt_commands数组中定义每个元素的具体值<include/net/bluetooth/mgmt.h>作为mgmt_handlers数组的index,这样内核向用户空间提供的接口就可以屏蔽详细的HCI命令,用户也可以不用关心各个命令的参数格式。
HCI接口
HCI接口是内核协议的最底层,主要负责封装HCI命令并发送到蓝牙驱动,以及接收hdev设备的数据并处理,包括event和各个链路的数据(L2CAP/SCO/ISO等);HCI命令以及event的定义可以参见文件<include/net/bluetooth/hci.h>,这些命令/事件以及数据结构的定义可以根蓝牙spec core完全对应,所以当需要跟踪某一个命令或事件在内核中的处理流程可以从这里入手。
另外需要说明的是数据的首发不是同步进行的,而是内核维护了一个链表和队列,当协议层需要发送时会先将数据或命令挂到队列的后面,同时会挂上该命令对应的event回调函数。
其他
- 文件系统hci_sysfs.c 用于记录内核协议在运行过程中的一些配置/状态/缓存等信息;
- 调试系统hci_debugfs.c 用于记录查看和调试运行时的数据/缓存,例如加密信息/地址管理/dut测试模式等信息;