前言

Linux平台下的蓝牙驱动可以按照外设类型分为hci_uart, hci_usb, hci_sdio等,取决于Controller芯片所提供的接口。所以通常所说的蓝牙驱动实际上是HCI设备的驱动,因为不管是什么接口,对于内核蓝牙协议(net/bluetooth)来说都是在操作hdev(hci device)设备。本文基于linux kernel 6.7-rc1版本。

Linux Bluetooth Diagram

本文主要介绍Amlogic公司的W2 蓝牙驱动的框架和流程,该芯片蓝牙接口采用UART,所以下面主要介绍hci_uart驱动,其他公司的蓝牙驱动也都大同小异。

蓝牙驱动框图

自2017年Linux 4.12内核引入serdev驱动框架后,很多公司便基于serdev框架实现了bluetooth,ethernet等驱动,例如drivers/bluetooth目录下的hci_bcm.c, hci_norkia.c文件,以及drivers/net/ethernet/qualcomm/qca_uart.c文件,若想了解serdev详细介绍,可以参考连接:https://elinux.org/images/0/0e/Serdev-elce-2017-2.pdf

Bluetooth driver Diagram

从上图左侧的流程图可以知道,整个蓝牙驱动的入口为hci_uart_init,在该函数中首先注册了tty线路规程tty_ldisc,该步骤主要是将hci_uart的操作挂载到tty设备中;另外还调用各个公司蓝牙驱动的init函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static int __init hci_uart_init(void)
{
...
/* Register the tty discipline */
err = tty_register_ldisc(&hci_uart_ldisc);

#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
....

#ifdef CONFIG_BT_HCIUART_QCA
qca_init();
#endif
#ifdef CONFIG_BT_HCIUART_AML
aml_init();
#endif
}

在函数aml_init()函数中主要做了四个事情:

  • 注册platform设备驱动,platform_driver_register()
  • 注册serdev设备驱动,serdev_device_driver_register()
  • 注册hci_uart操作接口,hci_uart_register_proto()
  • 注册hci uart device,hci_uart_register_device()

在右边的类图主要介绍了各个类之间的关系,以及结构体定义的内容,方便代码走读;从类图上可以知道platform设备和serdev设备最后都是通过driver_register()函数注册系统device中,只是使用了不同的bus_type。另外注册serdev和platform设备会通过解析dts文件来获取设备信息,serdev的dts信息如下,硬件原理图中W2的Uart接口是连接到SoC的uart_E管脚上,另外指定了firmware的路径以及其他默认配置,这在init W2时会用到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uart_E: serial@fe080000 {
compatible = "amlogic,meson-sc2-uart";
reg = <0x0 0xfe080000 0x0 0x18>;
interrupts = <0 172 1>;
clocks = <&xtal>, <&xtal>, <&xtal>;
clock-names = "xtal", "pclk", "baud";
pinctrl-names = "default";
pinctrl-0 = <&e_uart_pins>;
bluetooth {
compatible = "amlogic, aml-bt-cfg";
status = "okay";
firmware = "/amlogic/bluetooth/w2_bt_fw_uart.bin";
ant_number = <1>;
a2dp_sink_en = <1>;
};
};

platform设备的dts配置如下,platform设备需要管理W2的enable管脚以及32KHz的时钟,dts文件中需要指定bt_en-gpio和pwm config信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
aml_bt_pwr: aml_bt_pwr {
compatible = "amlogic, aml-bt-pwr";
bt_en-gpios = <&gpio GPIOX_17 GPIO_ACTIVE_HIGH>;
pwm_config = <&wcn_pwm_conf>;
status = "okay";
};

wcn_pwm_conf:wcn_pwm_conf{
pwm_channel1_conf {
pwms = <&pwm_ef 0 30550 0>;
duty-cycle = <15270>;
times = <8>;
};
pwm_channel2_conf {
pwms = <&pwm_ef 2 30500 0>;
duty-cycle = <15250>;
times = <12>;
};
};

蓝牙hdev初始化流程

上面platform设备probe后已准备好W2 bluetooth启动的硬件条件(en_gpio&32KHz clk), 接下来就是download W2 firmware流程以及一些RF配置等,这些操作主要挂在结构体aml_hci_proto上,包括函数aml_open和aml_setup。

hdev init flowchat

上面是aml_setup函数的流程图,到这里hdev就已经准备号了,从user space来看就可以通过BlueZ协议栈中的bluetoothctl命令来查看和power on HCI设备了。
关于linux-firmware又是另一个话题了,linux内核在管理各个设备的固件统一放到文件系统/lib/firmware目录中,各个厂商在发布固件时统一往linux-firmware仓库中提交,该仓库会间隔大概一个月发布一次,基于linux发布平台的系统集成商或者消费者就可以通过更新linux-firmware包达到升级设备驱动的目的了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# bluetoothctl
hci0 new_settings: bondable br/edr le secure-conn cis-central cis-peripheral
Agent registered
[CHG] Controller 22:22:F5:43:3B:AF Pairable: yes
[bluetooth]# bluetoothd[112]: Path / reserved for Adv Monitor app :1.5
AdvertisementMonitor path registered
[bluetooth]# show
Controller 22:22:F5:43:3B:AF (public)
Name: BlueZ 5.70
Alias: BlueZ 5.70
Class: 0x00000000 (0)
Powered: no
PowerState: off
Discoverable: no
DiscoverableTimeout: 0x000000b4 (180)
Pairable: yes
UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb)
......
[bluetooth]# power on
[CHG] Controller 22:22:F5:43:3B:AF PowerState: off-enabling
hci0 new_settings: powered bondable br/edr le secure-conn cis-central cis-peripheral
Changing power on succeeded
[CHG] Controller 22:22:F5:43:3B:AF PowerState: on
[CHG] Controller 22:22:F5:43:3B:AF Powered: yes
[bluetooth]# show
Controller 22:22:F5:43:3B:AF (public)
Name: BlueZ 5.70
Alias: BlueZ 5.70
Class: 0x00000000 (0)
Powered: yes
PowerState: on
Discoverable: no
DiscoverableTimeout: 0x000000b4 (180)
Pairable: yes
UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
.....

多说一句

在serdev框架之前,kernel只能提供tty_ldisc设备,然后在BlueZ协议栈中通过hciattach和btattach命令来加载设备并download firmware,但是这样会导致蓝牙驱动对power管脚以及其他控制方法造成混乱(kernel需要暴露更多的设备信息到User Space),所以hciattach和btattach方法逐渐被BlueZ社区弃用。