4. 蓝牙驱动基础:字符设备驱动模型、platform_driver、设备树匹配、probe/remove回调函数实现
在嵌入式Linux系统中,蓝牙控制器(如UART或USB接口的蓝牙模块)通常被抽象为一个字符设备,并通过platform_driver框架与设备树(Device Tree)进行匹配。本章将深入剖析蓝牙驱动的底层骨架:从字符设备驱动模型出发,到platform_driver的注册与卸载,再到设备树匹配规则,最后聚焦于probe/remove回调函数的完整实现。掌握这些基础,是后续中断处理与兼容性调试的基石。
4.1 字符设备驱动模型
字符设备是Linux驱动中最基础的设备类型之一,蓝牙HCI(Host Controller Interface)驱动通常以字符设备的形式暴露给用户空间。核心数据结构为struct cdev和struct file_operations。
关键概念: 字符设备通过主设备号(major)和次设备号(minor)标识,用户空间通过
open()、read()、write()、ioctl()等系统调用与驱动交互。
4.1.1 核心数据结构
| 结构体 | 作用 | 关键字段 |
|---|---|---|
struct cdev |
内核中表示字符设备的对象 | ops(指向file_operations)、dev(设备号) |
struct file_operations |
定义设备支持的系统调用回调 | .open、.release、.read、.write、.unlocked_ioctl |
dev_t |
封装主次设备号的类型 | MAJOR(dev)、MINOR(dev) |
4.1.2 字符设备注册流程
- 分配设备号:使用
alloc_chrdev_region()动态分配,或register_chrdev_region()静态指定。 - 初始化cdev:调用
cdev_init()绑定file_operations。 - 添加cdev:调用
cdev_add()将设备注册到内核。 - 创建设备节点:通过
class_create()和device_create()在/dev/下生成节点。
实践要点: 蓝牙驱动中,
unlocked_ioctl常用于发送HCI命令(如重置、扫描),而read/write用于传输HCI数据包。
4.2 platform_driver 框架
在嵌入式系统中,蓝牙控制器常挂接在平台总线(platform bus)上,尤其是通过UART或SDIO连接的蓝牙芯片。platform_driver是Linux驱动模型中的标准抽象,用于管理那些不依赖传统PCI/USB等热插拔总线的设备。
4.2.1 platform_driver 结构
static struct platform_driver bt_platform_driver = {
.probe = bt_probe,
.remove = bt_remove,
.driver = {
.name = "bt_hci_uart",
.of_match_table = bt_of_match,
.pm = &bt_pm_ops, // 电源管理回调
},
};
| 字段 | 说明 | 蓝牙驱动典型值 |
|---|---|---|
.probe |
设备匹配成功时调用,完成初始化 | 分配私有数据、注册字符设备、配置中断 |
.remove |
设备移除或驱动卸载时调用 | 释放中断、注销设备、释放内存 |
.driver.name |
驱动名称,用于非设备树匹配 | "bt_hci_uart" |
.of_match_table |
设备树匹配表 | 指向of_device_id数组 |
4.2.2 注册与卸载
// 模块入口
static int __init bt_driver_init(void)
{
return platform_driver_register(&bt_platform_driver);
}
// 模块出口
static void __exit bt_driver_exit(void)
{
platform_driver_unregister(&bt_platform_driver);
}
module_init(bt_driver_init);
module_exit(bt_driver_exit);
兼容性陷阱: 如果设备树中
compatible属性与of_match_table不匹配,probe将不会被调用。调试时务必检查/sys/bus/platform/drivers/bt_hci_uart/目录下是否有对应设备。
4.3 设备树匹配
设备树(Device Tree)是描述硬件拓扑的扁平数据结构,蓝牙驱动通过of_match_table与设备树节点进行匹配。匹配成功后,内核自动调用驱动的probe函数。
4.3.1 设备树节点示例
&uart1 {
status = "okay";
pinctrl-0 = <&uart1_pins>;
bluetooth {
compatible = "brcm,bcm43438-bt";
reg = <0>;
max-speed = <3000000>;
shutdown-gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&gpio0>;
interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
};
};
4.3.2 驱动端匹配表
static const struct of_device_id bt_of_match[] = {
{ .compatible = "brcm,bcm43438-bt", .data = &bcm43438_data },
{ .compatible = "ti,wl1271-bt", .data = &wl1271_data },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, bt_of_match);
| 设备树属性 | 驱动解析方式 | 蓝牙驱动用途 |
|---|---|---|
compatible |
of_match_device() |
识别蓝牙芯片型号 |
interrupts |
platform_get_irq() |
获取中断号,注册中断处理 |
shutdown-gpios |
devm_gpiod_get() |
控制蓝牙芯片电源/复位 |
max-speed |
of_property_read_u32() |
配置UART波特率 |
调试技巧: 使用
dtc -I dtb -O dts反编译设备树,确认蓝牙节点是否存在;检查/proc/device-tree/下的属性值。
4.4 probe/remove 回调函数实现
probe和remove是蓝牙驱动的生命线。probe负责初始化硬件、分配资源、注册中断;remove负责反向清理。下面给出一个典型的蓝牙UART驱动probe/remove实现框架。
4.4.1 probe 回调实现
static int bt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct bt_private *priv;
struct resource *res;
int irq, ret;
// 1. 分配私有数据结构
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
// 2. 获取中断号
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 3. 获取GPIO资源(如复位引脚)
priv->shutdown_gpio = devm_gpiod_get(dev, "shutdown", GPIOD_OUT_LOW);
if (IS_ERR(priv->shutdown_gpio))
return PTR_ERR(priv->shutdown_gpio);
// 4. 注册字符设备
ret = alloc_chrdev_region(&priv->dev_num, 0, 1, "bt_hci");
if (ret < 0)
return ret;
cdev_init(&priv->cdev, &bt_fops);
priv->cdev.owner = THIS_MODULE;
ret = cdev_add(&priv->cdev, priv->dev_num, 1);
if (ret)
goto err_cdev;
// 5. 创建设备节点
priv->class = class_create(THIS_MODULE, "bt_class");
if (IS_ERR(priv->class)) {
ret = PTR_ERR(priv->class);
goto err_class;
}
priv->device = device_create(priv->class, NULL, priv->dev_num,
NULL, "bt_hci%d", 0);
if (IS_ERR(priv->device)) {
ret = PTR_ERR(priv->device);
goto err_device;
}
// 6. 注册中断处理函数
ret = devm_request_irq(dev, irq, bt_irq_handler,
IRQF_TRIGGER_FALLING, "bt_hci", priv);
if (ret)
goto err_irq;
// 7. 初始化蓝牙控制器(如发送HCI复位命令)
ret = bt_hardware_init(priv);
if (ret)
goto err_hw;
dev_info(dev, "Bluetooth HCI driver initialized\n");
return 0;
err_hw:
// 硬件初始化失败,回滚
err_irq:
device_destroy(priv->class, priv->dev_num);
err_device:
class_destroy(priv->class);
err_class:
cdev_del(&priv->cdev);
err_cdev:
unregister_chrdev_region(priv->dev_num, 1);
return ret;
}
4.4.2 remove 回调实现
static int bt_remove(struct platform_device *pdev)
{
struct bt_private *priv = platform_get_drvdata(pdev);
// 1. 停止蓝牙控制器(如发送关机命令)
bt_hardware_shutdown(priv);
// 2. 释放中断(devm_ 自动处理,但显式调用更安全)
// devm_free_irq(&pdev->dev, priv->irq, priv);
// 3. 销毁设备节点
device_destroy(priv->class, priv->dev_num);
class_destroy(priv->class);
// 4. 删除字符设备
cdev_del(&priv->cdev);
unregister_chrdev_region(priv->dev_num, 1);
// 5. 释放GPIO(devm_ 自动处理)
// devm_gpiod_put(&pdev->dev, priv->shutdown_gpio);
dev_info(&pdev->dev, "Bluetooth HCI driver removed\n");
return 0;
}
兼容性调试要点:
- probe失败时,必须确保所有已分配资源被正确回滚,否则后续probe尝试可能因资源泄漏而失败。
- remove中应等待所有未完成的中断处理完成(可使用
synchronize_irq())。 - 如果蓝牙控制器支持固件加载,probe中应包含固件请求逻辑,并处理超时情况。
4.5 本章小结
- 字符设备模型是蓝牙驱动与用户空间交互的桥梁,核心是
cdev和file_operations。 - platform_driver将蓝牙驱动挂接到平台总线,通过
probe/remove管理设备生命周期。 - 设备树匹配通过
compatible属性实现硬件描述与驱动的解耦,是嵌入式蓝牙驱动的标准做法。 - probe/remove实现必须严格遵循资源分配与释放的对称性,任何遗漏都会导致内核不稳定或内存泄漏。
下一阶段预告: 在掌握了驱动基础框架后,第5章将深入蓝牙中断处理机制,包括中断上半部/下半部、 threaded IRQ 以及中断共享与兼容性调试。