1. 程式人生 > >Ubuntu系統(bluez)藍芽除錯

Ubuntu系統(bluez)藍芽除錯

前言

現在除錯的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.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所示。

這裡寫圖片描述
圖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的節點,給藍芽斷電;

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