Ubuntu系统(bluez)蓝牙调试

来源:互联网 发布:淘宝怎么出售禁售商品 编辑:程序博客网 时间:2024/06/03 18:14

前言

现在调试的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接口蓝牙注册
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接口蓝牙注册

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);

然后在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

只要定义了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.cstatic 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();#endifkernel\drivers\bluetooth\ hci_uart.h//Realtek_add_start#ifdef CONFIG_BT_HCIUART_RTKH5int rtk_h5_init(void);int rtk_h5_deinit(void);#endifkernel\drivers\bluetooth\ hci_rtk_h5.cstatic 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);
屏蔽内核中现有的hci_h5.c修改方式为:kernel\arch\arm\configs\目录下config文件修改下面两行。# CONFIG_BT_HCIUART_ATH3K is not set   //屏蔽BT_HCIUART_ATH3K

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就可以与串口通信了。
kernel\drivers\bluetooth\ hci_ldisc.cstatic 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;    }

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 dbusstop on stopping dbusenv UART_CONF=/etc/bluetooth/uartenv RFCOMM_CONF=/etc/bluetooth/rfcomm.confexpect forkrespawnexec /usr/sbin/bluetoothdpost-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" || :    fiend scriptpost-stop script    # stop_uarts()    logger -t bluez "Stopping uarts"    kill

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的节点,给蓝牙断电;

上面方案要求图形界面对不同接口蓝牙进行不同的操作,统一性不是很好,可以把操作部分放到脚本中实现,脚本根据实际硬件修改,而图形界面只需要调用脚本,不需要关心脚本的操作内容,这样实现会更好一些。

0 0
原创粉丝点击