首頁 > 軟體

Ubuntu系統(bluez)藍牙偵錯

2020-06-16 16:49:21

前言
現在偵錯的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層註冊例子:

kerneldriversbluetoothhci_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介面藍牙註冊

kerneldriversbluetoothrtk_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註冊介面。

kerneldriversbluetoothrtk_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即可。
該定義在kernelarcharmconfigs下config檔案中設定,
CONFIG_PLATFORM_UBUNTU=y

2、 把fw及組態檔打包到“/lib/firmware/”目錄下;
如gb5_wxga板子的rtl8723bu模組,只需要把rtl8723b_fw、rtl8723bu_config檔案放到“ rootfslibfirmware”目錄下即可。

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協定,就需要在kerneldriversbluetooth目錄下增加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的註冊。

    kerneldriversbluetoothhci_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
kerneldriversbluetooth hci_uart.h
//Realtek_add_start
#ifdef CONFIG_BT_HCIUART_RTKH5
int rtk_h5_init(void);
int rtk_h5_deinit(void);
#endif
kerneldriversbluetooth 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修改方式為:
kernelarcharmconfigs目錄下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就可以與串列埠通訊了。

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

圖3 hciattach初始化流程
Hciattach的流程比較簡單,從現在Ubuntu及debian系統的設計看,hciattach都是開機時就執行,一直到關機時才結束。
Hciattach的移植涉及下面幾個地方:
1、 把hciattach可執行檔案放到bin目錄下;
以lemaker板子為例:
把hciattach_rtk放到rootfsusrsbin目錄下;
2、 把fw及config檔案打包進系統,放置的路徑由hciattach open fw確定
以使用rtl8723bs模組:
把rtl8723b_fw、rtk8723_bt_config放到
rootfslibfirmwarertl8723bs目錄下;
3、 加入hciattach的啟動與退出控制:
把 bluetooth.conf檔案放到rootfsetcinit目錄下。

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為例,修改了kernelnetrfkill目錄下的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的節點,給藍牙斷電;

上面方案要求圖形介面對不同介面藍牙進行不同的操作,統一性不是很好,可以把操作部分放到指令碼中實現,指令碼根據實際硬體修改,而圖形介面只需要呼叫指令碼,不需要關心指令碼的操作內容,這樣實現會更好一些。

Linux公社的RSS地址:https://www.linuxidc.com/rssFeed.aspx

本文永久更新連結地址https://www.linuxidc.com/Linux/2018-08/153458.htm


IT145.com E-mail:sddin#qq.com