2021-05-12 14:32:11
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層註冊例子:
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
相關文章