首页 > 代码库 > Linux Bluetooth内核分析之HCI部分
Linux Bluetooth内核分析之HCI部分
关于HCI规范相关内容,请看<Bluetooth HCI介绍>
首先我们要了解,在Linux上实现的是HCI的Host部分
1. 相关数据结构
1.1 hci_dev
在Linux中hci_dev用来表示一个HCI Host(对应于一个Control)
成员 | 作用 |
char name[8] | 蓝牙名称 |
__u8 bus | HCI总线类型,有HCI_USB,HCI_PCCARD,HCI_UART,HCI_PCI等 |
__u8 dev_type | HCI Controller类型,有HCI_BREDR,HCI_AMP两种 |
bdaddr_t bdaddr | Bluetooth device address,48 bits |
__u8 dev_name[248] | 蓝牙本地名称,可在用户空间设置 |
unsigned int flags | 进程的标记, 如PF_STARTING |
__u16 pkt_type | HCI packet types, 有HCI_DM1,HCI_DM3,HCI_DH1,HCI_HV1等 |
__u16 link_policy | Link policies, 有HCI_LP_RSWITCH,HCI_LP_HOLD,HCI_LP_SNIFF,HCI_LP_PARK |
__u16 link_mode | Link modes, 有HCI_LM_ACCEPT,HCI_LM_MASTER,HCI_LM_AUTH等 |
struct workqueue_struct* workqueue | 工作队列, 在hci_register_dev中初始化,用于控制下面的(延迟)工作队列任务 |
struct work_struct power_on rx_work cmd_work tx_work | 工作队列任务, 对应的内核线程为 hci_power_on hci_rx_work hci_cmd_work hci_tx_work |
struct delayed_work power_off discov_off service_cache | 延迟工作队列任务, 对应的内核线程为 hci_power_off hci_discov_off service_cache_off |
struct sk_buff_head | 数据包链表 raw_q/cmd_q用于存放待发送的数据包 rx_q用于存放待接收的数据包 |
2. 初始化
HCI部分的初始化是由函数hci_sock_init()来完成
hci_sock_init() -> proto_register() -> bt_sock_register() -> bt_procfs_init()
proto_register将HCI协议加到一个prot_list中(用于/proc/net/protocols)
bt_sock_register向PF_BLUETOOTH注册协议,即将BTPROTO_HCI添加到bt_proto数组中
同时注册HCI套接字创建函数hci_sock_create()
bt_procfs_init向Proc文件系统添加HCI部分
3. HCI套接字
在初始化中注册了HCI套接字创建函数hci_sock_create()
注意: BTPROTO_HCI只支持SOCK_RAW类型的套接字
hci_sock_create()主要是在用户在创建BTPROTO_HCI类型的SOCK_RAW套接字时调用
包括一些常规的初始化如分配内存,初始化HCI套接字相关数据
最重要的是定义了HCI套接字操作集hci_sock_ops
当绑定一个HCI套接字时,有三种通道RAW/MONITOR/CONTROL(RAW为默认的通道)
MONITOR: 用于通知用户设备事件,如HCI_DEV_UP/HCI_DEV_SUSPEND,用hci_notify()来完成
4. 发送数据
<style type="text/css">.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }</style>在Linux中,定义了五种HCI数据包类型
COMMAND/ACLDATA/SCODATA/EVENT/VENDOR
在hci_sock_ops定义了数据发送函数hci_sock_sendmsg()
依据HCI套接字所绑定的通道的不同,有不同的处理
1) MONITOR: 不做处理
2) CONTROL: 交由mgmt_control()来处理
3) RAW: 根据HCI数据包类型的不同放到raw_q/cmd_q,然后调度工作队列
调用hci_tx_work/hci_cmd_work进行处理
hci_tx_work函数内容如下
static void hci_tx_work(struct work_struct *work){ struct hci_dev *hdev = container_of(work, struct hci_dev, tx_work); struct sk_buff *skb; BT_DBG("%s acl %d sco %d le %d", hdev->name, hdev->acl_cnt, hdev->sco_cnt, hdev->le_cnt); /* Schedule queues and send stuff to HCI driver */ hci_sched_acl(hdev); hci_sched_sco(hdev); hci_sched_esco(hdev); hci_sched_le(hdev); /* Send next queued raw (unknown type) packet */ while ((skb = skb_dequeue(&hdev->raw_q))) hci_send_frame(skb);}
将不同类型的数据包发送至驱动部分(hci_send_frame函数会调用hci_dev->send)
5. 接收数据
驱动空间接收到来自Controller的数据后,调用hci_recv_frame()
hci_recv_frame()将数据包放入到队列rx_q中,然后进行工作队列rx_work调度
从而触发rx_work对应的处理函数hci_rx_work()
-------------------------------------------------------------------------
若socket处于混杂模式(promisc,即RAW类型的HCI套接字)
则调用hci_send_to_sock(),然后调用sock_queue_rcv_skb()
sock_queue_rcv_skb()会把数据包添加到队列sk_receive_queue的尾部
#### 然后中间不知发送了什么,就接着调用下面的过程…… ####
hci_sock_recvmsg() –> hci_sock_cmsg() –> put_cmsg()发送到原始套接字
-------------------------------------------------------------------------
hci_rx_work()会根据帧的类型进行不同的处理
1) HCI_EVENT_PKT: hci_event_packet() 处理来自Controller的事件
2) HCI_ACLDATA_PKT: hci_acldata_packet() 处理ACL类型的数据包
3) HCI_SCODATA_PKT: hci_scodata_packet() 处理SCO类型的数据包
hci_acldata_packet和hci_scodata_packet会把数据包往上层的协议发送
6. IOCTL
hci_sock_ioctl函数负责处理ioctl事件,主要包括三类
1) 设置设备状态 如HCIDEVUP/HCIDEVDOWN/HCIDEVRESET/HCIDEVRESTAT
2) 获取设备信息 如HCIGETDEVLIST/HCIGETDEVINFO/HCIGETCONNLIST/HCIGETCONNINFO/HCIGETAUTHINFO
3) 设置Controler 如HCISETSCAN/HCISETAUTH/HCISETENCRYPT,向控制器发出命令并等待回应
hci_dev_cmd()->hci_request()->hci_xxx_req()
值得注意的就是HCIDEVUP, 通过调用hci_dev_open来打开蓝牙设备(会调用hci_dev->open)
7. 驱动部分
驱动部分的代码位于drivers/bluetooth
一般的蓝牙驱动初始化过程是
xxx_init() –> xxx_probe() –> hci_alloc_dev() –> 初始化hci_dev结构体 –> hci_register_dev()
其中,初始化hci_dev结构体部分主要包括:类型(bus),操作集(open,close,flush,send,ioctl)
通常驱动会自定义一个设备相关的结构体,进行硬件相关的数据初始化
但该结构体都有一个成员变量是hci_dev结构体
数据的接收函数通常是由中断来完成,在蓝牙中HCI是传输在UART/USB等之上,故中断的方式也是依靠其他模块
通常会在xxx_probe或open函数中显式或隐式的进行接收函数的注册
xxx_receive() –> hci_recv_frame() –> 将数据帧放到hci_dev->rx_q链表尾部 –> 调度工作队列中的rx_work
除了hci_recv_frame外,还有两个bt协议栈数据接收函数hci_recv_fragment和hci_recv_stream_fragment
7. API
7.1 为底层提供的API
hci_alloc_dev/hci_free_dev
hci_register_dev/hci_unregister_dev
hci_suspend_dev/hci_resume_dev
hci_recv_frame/hci_recv_fragment/hci_recv_stream_fragment
7.2 为上层提供的API
bt_sock_register/bt_sock_unregister
hci_register_cb/hci_unregister_cb
参考:
<Linux workqueue工作原理>
<Bluetooth UART接口driver初始化分析>