Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3043949
  • 博文数量: 396
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4209
  • 用 户 组: 普通用户
  • 注册时间: 2016-07-04 13:04
文章分类

全部博文(396)

文章存档

2022年(1)

2021年(2)

2020年(8)

2019年(24)

2018年(135)

2017年(158)

2016年(68)

我的朋友

分类: 嵌入式

2018-10-25 22:48:46

前言


现在调试的Ubuntu、debian系统,蓝牙上层的协议使用bluez,蓝牙的移植与bluedroid略有不同。本文主要介绍Ubuntu(蓝牙移植上debian与Ubuntu是一样的)系统下蓝牙移植的相关知识,并给出移植指导。涉及的知识点有bluez下蓝牙的驱动、hciattach的作用、蓝牙电源的控制、蓝牙移植修改点。


1 Bluez下内核蓝牙框架简介


使用Bluez时,需要内核提供一系列的socket接口来操作蓝牙,内核中蓝牙的框架如图1所示。蓝牙框架分成两部分:蓝牙socket部分及蓝牙驱动部分。蓝牙socket部分负责管理提供给bluez的socket,并包含L2cap层的功能;蓝牙驱动包含hci层协议及蓝牙硬件接口的管理。蓝牙socket部分与蓝牙驱动通过hci_core来连接。从Bluez下移植蓝牙方面看,只关心两个地方,一个是蓝牙驱动的移植,另一个是bluez的工具集中的hciattach工具(使用uart接口的蓝牙才需要这部分), 




图1 内核中蓝牙框图 
对比bluedroid与bluez在蓝牙移植方面的差异,最大的不同就是hci和L2cap层所处的位置,在bluedroid中,hci和L2cap层放在bluedroid中,是在内核之上。而bluez中,hci和L2cap层不属于bluez中的代码,而是放到内核里。


2 内核中的蓝牙


在Ubuntu系统下,Bluez的蓝牙驱动负责hci协议的处理、与蓝牙硬件交互数据、注册hci接口供蓝牙socket部分使用。


2.1 注册hci_core接口


不管是uart接口还是usb接口的蓝牙,都是通过hci_register_dev函数向hci_core层注册接口,下面为uart接口及usb接口蓝牙向hci_core层注册例子:


kernel\drivers\bluetooth\hci_ldisc.c
    hdev->bus = HCI_UART;
    hci_set_drvdata(hdev, hu);


    hdev->open  = hci_uart_open;
    hdev->close = hci_uart_close;
    hdev->flush = hci_uart_flush;
    hdev->send  = hci_uart_send_frame;
    SET_HCIDEV_DEV(hdev, hu->tty->dev);


    if (test_bit(HCI_UART_RAW_DEVICE, &hu->hdev_flags))
        set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks);


    if (!test_bit(HCI_UART_RESET_ON_INIT, &hu->hdev_flags))
        set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks);


    if (test_bit(HCI_UART_CREATE_AMP, &hu->hdev_flags))
        hdev->dev_type = HCI_AMP;
    else
        hdev->dev_type = HCI_BREDR;


    if (test_bit(HCI_UART_INIT_PENDING, &hu->hdev_flags))
        return 0;


    if (hci_register_dev(hdev) < 0)       //uart接口蓝牙注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
kernel\drivers\bluetooth\rtk_btusb_8723bu.c
    HDEV_BUS = HCI_USB;


    data->hdev = hdev;


    SET_HCIDEV_DEV(hdev, &intf->dev);


    hdev->open     = btusb_open;
    hdev->close    = btusb_close;
    hdev->flush    = btusb_flush;
    hdev->send     = btusb_send_frame;
    hdev->notify   = btusb_notify;


#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 4, 0)
    hci_set_drvdata(hdev, data);
#else
    hdev->driver_data = data;
    hdev->destruct = btusb_destruct;
    hdev->owner = THIS_MODULE;
#endif


……


    err = hci_register_dev(hdev);        // usb接口蓝牙注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Hci_core向蓝牙驱动传递数据通过hdev->send接口,而蓝牙驱动接收到数据后直接调用hci_core Export的hci_recv_frame、hci_recv_fragment接口提交数据。 
在使用hci_register_dev注册接口的时候,hci_core会在rfkill下注册一个RFKILL_TYPE_BLUETOOTH类型的设备,名称为hciX。在Ubuntu系统中,只要发现rfkill下有注册RFKILL_TYPE_BLUETOOTH类型设备,就认为存在蓝牙设备,桌面上就会显示蓝牙图标,后面对蓝牙的开关操作就是通过rfkill下的接口。


2.2 蓝牙驱动的移植


现在调试过的蓝牙有uart接口和usb接口的,这两种接口的驱动是完全不同的,下面分别介绍。


2.2.1 USB接口蓝牙


对于usb接口的蓝牙,只要给蓝牙上电,usb枚举到蓝牙设备后,就会调用蓝牙驱动的probe函数,在该函数中就会向hci_core注册接口。


kernel\drivers\bluetooth\rtk_btusb_8723bu.c
    static int btusb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{


……


    hdev = hci_alloc_dev();
    if (!hdev) {
        rtk_free(data);
        data = NULL;
        return -ENOMEM;
    }


    HDEV_BUS = HCI_USB;


    data->hdev = hdev;


    SET_HCIDEV_DEV(hdev, &intf->dev);


    hdev->open     = btusb_open;
    hdev->close    = btusb_close;
    hdev->flush    = btusb_flush;
    hdev->send     = btusb_send_frame;
    hdev->notify   = btusb_notify;


……


    err = hci_register_dev(hdev);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
然后在hdev->open 的时候,在btusb_open中进行fw下载和参数配置的工作,这时蓝牙就可以正常工作了。下载的fw和配置文件通过内核的int request_firmware(const struct firmware **fw, const char *name, struct device *device)函数获取,对于需要获取的文件,只需要提供文件名,该函数会自动搜索系统部分路径,其中就包含“/lib/firmware/”,所以只要把fw及配置文件放到“/lib/firmware/”目录下即可。同时usb保证了传输的可靠性,所以也不需要什么h4、h5协议了。 
从上面可以看出,对于usb接口蓝牙的移植,只需要保证两步工作就可以了: 
1、 蓝牙usb功能驱动的移植; 
如rtl8723bu的蓝牙,bluez与bluedroid下使用的驱动是一样的,但有一个定义是区分用于bluez还是bluedroid的。在rtk_btusb_8723bu.h文件如下代码中:


#ifndef CONFIG_PLATFORM_UBUNTU
#define CONFIG_BLUEDROID        1 /* bleuz 0, bluedroid 1 */
#else
#define CONFIG_BLUEDROID        0
#endif
1
2
3
4
5
只要定义了CONFIG_PLATFORM_UBUNTU即可。 
该定义在kernel\arch\arm\configs\下config文件中配置, 
CONFIG_PLATFORM_UBUNTU=y


2、 把fw及配置文件打包到“/lib/firmware/”目录下; 
如gb5_wxga板子的rtl8723bu模组,只需要把rtl8723b_fw、rtl8723bu_config文件放到“\ rootfs\lib\firmware\”目录下即可。


2.2.2 UART接口蓝牙


不像usb接口蓝牙,可以直接向usb驱动注册蓝牙的功能驱动,后面就等着probe被调用就可以了。Uart接口蓝牙,使用那个uart口依赖硬件,同时也没办法像usb一样向串口驱动注册一个功能驱动等待probe,uart是没有枚举的过程的。所以uart接口蓝牙就需要应用层把使用的串口通知hci_core层,同时uart接口蓝牙的fw download及config配置工作也放到了应用层,这些工作都是由hciattach来实现。Hciattach的流程下一节介绍,这里介绍uart接口蓝牙驱动移植需要做的工作。相比图1,图2描述的uart驱动更接近代码结构。


 
图2 uart接口蓝牙驱动框图 
从图2可以看到,串口的使用有一个切换的过程,在初始化的时候,由hciattach使用串口,初始化完成后,把串口切换给hci使用,hci负责与串**互蓝牙数据,中间还经过了h4/h5协议层,驱动层跟移植相关只有h4/h5协议,若h4/h5使用的是内核自带的协议,那驱动层就不需要做任何的工作。 
以rtl8723bs为例,需要使用rtk修改过的h5协议,就需要在kernel\drivers\bluetooth\目录下增加hci_rtk_h5.c文件,hci_ldisc.c增加对hci_rtk_h5.c的init及deinit,由于内核中注册hci协议会使用一个id号,相同id的协议不能再注册,内核中已经有的hci_h5.c与 hci_rtk_h5.c使用的是相同的id号,所以内核中需要屏蔽hci_h5.c的注册。


    kernel\drivers\bluetooth\hci_ldisc.c
static int __init hci_uart_init(void)


……


#ifdef CONFIG_BT_HCIUART_H4
    h4_init();
#endif
#ifdef CONFIG_BT_HCIUART_BCSP
    bcsp_init();
#endif
#ifdef CONFIG_BT_HCIUART_LL
    ll_init();
#endif
#ifdef CONFIG_BT_HCIUART_ATH3K
    ath_init();
#endif
#ifdef CONFIG_BT_HCIUART_3WIRE
    h5_init();
#endif
//Realtek_add_start 
//add realtek h5 support    
#ifdef CONFIG_BT_HCIUART_RTKH5
    rtk_h5_init();
#endif
//Realtek_add_end   




……


static void __exit hci_uart_exit(void)


……


    #ifdef CONFIG_BT_HCIUART_RTKH5
    rtk_h5_deinit();
#endif
kernel\drivers\bluetooth\ hci_uart.h
//Realtek_add_start
#ifdef CONFIG_BT_HCIUART_RTKH5
int rtk_h5_init(void);
int rtk_h5_deinit(void);
#endif
kernel\drivers\bluetooth\ hci_rtk_h5.c
static struct hci_uart_proto h5 = {
    .id     = HCI_UART_3WIRE,  // 与h5_init注册是相同的id
    .open       = h5_open,
    .close      = h5_close,
    .enqueue    = h5_enqueue,
    .dequeue    = h5_dequeue,
    .recv       = h5_recv,
    .flush      = h5_flush
};


int rtk_h5_init(void)
{
    int err = hci_uart_register_proto(&h5);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
屏蔽内核中现有的hci_h5.c修改方式为:
kernel\arch\arm\configs\目录下config文件修改下面两行。
# CONFIG_BT_HCIUART_ATH3K is not set   //屏蔽BT_HCIUART_ATH3K
1
2
3
CONFIG_BT_HCIUART_RTKH5=y // 打开BT_HCIUART_RTKH5


Hci_ldisc通过tty_register_ldisc(N_HCI, &hci_uart_ldisc)向串口注册HCI line discipline,当hciattach通过ioctl把串口切换到HCI line discipline时,hci_ldisc就可以与串口通信了。
1
kernel\drivers\bluetooth\ hci_ldisc.c
static int __init hci_uart_init(void)
{
    static struct tty_ldisc_ops hci_uart_ldisc;
    int err;


    BT_INFO("HCI UART driver ver %s", VERSION);


    /* Register the tty discipline */


    memset(&hci_uart_ldisc, 0, sizeof (hci_uart_ldisc));
    hci_uart_ldisc.magic        = TTY_LDISC_MAGIC;
    hci_uart_ldisc.name     = "n_hci";
    hci_uart_ldisc.open     = hci_uart_tty_open;
    hci_uart_ldisc.close        = hci_uart_tty_close;
    hci_uart_ldisc.read     = hci_uart_tty_read;
    hci_uart_ldisc.write        = hci_uart_tty_write;
    hci_uart_ldisc.ioctl        = hci_uart_tty_ioctl;
    hci_uart_ldisc.poll     = hci_uart_tty_poll;
    hci_uart_ldisc.receive_buf  = hci_uart_tty_receive;
    hci_uart_ldisc.write_wakeup = hci_uart_tty_wakeup;
    hci_uart_ldisc.owner        = THIS_MODULE;


    if ((err = tty_register_ldisc(N_HCI, &hci_uart_ldisc))) {
        BT_ERR("HCI line discipline registration failed. (%d)", err);
        return err;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
3 Hciattach的处理流程


只有uart接口的蓝牙才需要hciattach工具,hciattach的作用为配置串口,下载fw及config文件,把串口切换给hci_ldisc使用。 
Bluez带有hciattach的源码,框架也比较清晰,对很多厂家都有支持,但实际调试realtek及boardcom的模组时,Bluez自带的hciattach都是不能使用的,realtek及boardcom对hciattach有特殊的修改,主要是针对fw和config的下载部分。但hciattach的作用及流程与Bluez自带的hciattach是一样的。Hciattach的流程如图3所示。


 
图3 hciattach初始化流程 
Hciattach的流程比较简单,从现在Ubuntu及debian系统的设计看,hciattach都是开机时就运行,一直到关机时才结束。 
Hciattach的移植涉及下面几个地方: 
1、 把hciattach可执行文件放到bin目录下; 
以lemaker板子为例: 
把hciattach_rtk放到\rootfs\usr\sbin\目录下; 
2、 把fw及config文件打包进系统,放置的路径由hciattach open fw确定 
以使用rtl8723bs模组: 
把rtl8723b_fw、rtk8723_bt_config放到 
\rootfs\lib\firmware\rtl8723bs\目录下; 
3、 加入hciattach的启动与退出控制: 
把 bluetooth.conf文件放到\rootfs\etc\init\目录下。


bluetooth.conf文件内容
description     "bluetooth daemon"


start on started dbus
stop on stopping dbus


env UART_CONF=/etc/bluetooth/uart
env RFCOMM_CONF=/etc/bluetooth/rfcomm.conf


expect fork
respawn


exec /usr/sbin/bluetoothd


post-start script
    #[ "$VERBOSE" = no ] && redirect='>/dev/null 2>&1' || redirect=


    # start_uarts()
    #if [ -x /usr/sbin/hciattach ] && [ -f $UART_CONF ];
    #then
    #   grep -v '^#' $UART_CONF | while read i; do
    #     eval "/usr/sbin/hciattach $i $redirect" || :
    #   done
    #fi
    exec hciattach_rtk -n -s 115200 /dev/ttyS2 rtk_h5 &


    # start_rfcomm()
    if [ -x /usr/bin/rfcomm ] && [ -f $RFCOMM_CONF ] ;
    then
        # rfcomm must always succeed for now: users
        # may not yet have an rfcomm-enabled kernel
        eval "/usr/bin/rfcomm -f $RFCOMM_CONF bind all $redirect" || :
    fi
end script


post-stop script
    # stop_uarts()
    logger -t bluez "Stopping uarts"
    kill
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
bluetooth.conf中有脚本,在开机、关机时运行,这里: 
开机运行:exec hciattach_rtk -n -s 115200 /dev/ttyS2 rtk_h5 & 
关机运行:killall hciattach_rtk >/dev/null 2>&1 || :


4 蓝牙电源的管理


前面的文档中一直没有提到蓝牙的电源是怎么控制的。在蓝牙的电源控制方面,Ubuntu及debian系统都没有做很好的处理,从现在的蓝牙图形界面应用看,这两个系统中,默认蓝牙是一直有电的并且开机时就打开,并没有考虑关闭蓝牙的时候把蓝牙断电。 
现在我们使用usb接口蓝牙,对于插拨的usb蓝牙,不需要考虑电源,只要插上就有电了,对于焊在板子上的蓝牙,由于没有增加对蓝牙上电的操作,所以要在wifi打开的情况下(wifi上电了,蓝牙也就上电了)才能使用蓝牙。 
对于sdio接口的蓝牙,以rtl8723bs为例,修改了kernel\net\rfkill\目录下的rfkill-actions_8723bs.c文件,在这个文件里不再注册rfkill接口,而是修改为在加载驱动是给蓝牙上电,卸载驱动时断开蓝牙电源。 
至于为什么不保留rfkill-actions_8723bs.c在rfkill中的接口,通过rfkill来控制电源,是由于rfkill-actions_8723bs.c注册进rfkill的类型也是RFKILL_TYPE_BLUETOOTH,与hci注册的类型是一样的,这并没有冲突,但由于Ubuntu的蓝牙图形界面操作蓝牙打开、关闭时,同时都会把RFKILL_TYPE_BLUETOOTH类型的节点打开、关闭,这里上电的延时就没法保证,而且并没有调用hciattach进行蓝牙的初始化,对于串口蓝牙就没法使用了。


若重写蓝牙图形操作界面时,可以采用下面的方案进行电源的管理。 
1、 usb接口蓝牙: 
在rfkill中增加一个节点用于控制蓝牙上、掉电,类型为 
RFKILL_TYPE_BLUETOOTH,但名称修改为bt_power; 
蓝牙打开的操作: 
A) 找到rfkill下类型为RFKILL_TYPE_BLUETOOTH名称为bt_power的节点,给蓝牙上电; 
B) 延时(根据实际调整); 
C) 找到rfkill下类型为RFKILL_TYPE_BLUETOOTH名称为hciX的节点,打开蓝牙; 
蓝牙关闭的操作: 
A) 找到rfkill下类型为RFKILL_TYPE_BLUETOOTH名称为hciX的节点,关闭蓝牙; 
B) 延时(根据实际调整); 
C) 找到rfkill下类型为RFKILL_TYPE_BLUETOOTH名称为bt_power的节点,给蓝牙断电;


2、 Uart接口蓝牙 
在rfkill中增加一个节点用于控制蓝牙上、掉电,类型为 
RFKILL_TYPE_BLUETOOTH,但名称修改为bt_power; 
蓝牙打开的操作: 
A) 找到rfkill下类型为RFKILL_TYPE_BLUETOOTH名称为bt_power的节点,给蓝牙上电; 
B) 延时(根据实际调整); 
C) 启动hciattach完成蓝牙的初始化并切换串口给hci使用; 
D) 找到rfkill下类型为RFKILL_TYPE_BLUETOOTH名称为hciX的节点,打开蓝牙; 
蓝牙关闭的操作: 
A) 找到rfkill下类型为RFKILL_TYPE_BLUETOOTH名称为hciX的节点,关闭蓝牙; 
B) 关闭hciattach; 
C) 延时(根据实际调整); 
D) 找到rfkill下类型为RFKILL_TYPE_BLUETOOTH名称为bt_power的节点,给蓝牙断电;


上面方案要求图形界面对不同接口蓝牙进行不同的操作,统一性不是很好,可以把操作部分放到脚本中实现,脚本根据实际硬件修改,而图形界面只需要调用脚本,不需要关心脚本的操作内容,这样实现会更好一些。
--------------------- 
作者:简单的过客 
来源:CSDN 
原文:https://blog.csdn.net/zjli321/article/details/52122447 
版权声明:本文为博主原创文章,转载请附上博文链接!
阅读(1643) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~