前言 Linux平台下的蓝牙驱动可以按照外设类型分为hci_uart, hci_usb, hci_sdio等,取决于Controller芯片所提供的接口。所以通常所说的蓝牙驱动实际上是HCI设备的驱动,因为不管是什么接口,对于内核蓝牙协议(net/bluetooth)来说都是在操作hdev(hci device)设备。本文基于linux kernel 6.7-rc1版本。
本文主要介绍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
从上图左侧的流程图可以知道,整个蓝牙驱动的入口为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 ) { ... 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。
上面是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 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] AdvertisementMonitor path registered [bluetooth] 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] [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] 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社区弃用。