首页 > 代码库 > 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
        rx_q
        raw_q
        cmd_q

数据包链表
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初始化分析>