Ubuntu系统(bluez)蓝牙调试

espressif 2018-08-11

前言
现在调试的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接口的蓝牙才需要这部分),

Ubuntu系统(bluez)蓝牙调试

图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驱动更接近代码结构。

Ubuntu系统(bluez)蓝牙调试 

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

屏蔽内核中现有的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.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;
    }

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所示。

Ubuntu系统(bluez)蓝牙调试

图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

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

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

相关推荐

chinaycheng / 0评论 2019-10-22