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 字符设备注册流程

  1. 分配设备号:使用alloc_chrdev_region()动态分配,或register_chrdev_region()静态指定。
  2. 初始化cdev:调用cdev_init()绑定file_operations。
  3. 添加cdev:调用cdev_add()将设备注册到内核。
  4. 创建设备节点:通过class_create()和device_create()在/dev/下生成节点。
实践要点: 蓝牙驱动中,unlocked_ioctl常用于发送HCI命令(如重置、扫描),而read/write用于传输HCI数据包。
字符设备注册流程 alloc_chrdev_region cdev_init cdev_add device_create 说明:蓝牙驱动通常在 probe 中完成字符设备注册, 在 remove 中调用 cdev_del 和 unregister_chrdev_region 清理。 用户空间 → /dev/bt_hci → file_operations → 蓝牙硬件

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/目录下是否有对应设备。
platform_driver 匹配与生命周期 设备树节点 compatible = "vendor,bt-chip" 平台总线 (platform bus) 匹配 .of_match_table platform_driver probe / remove probe 回调:分配资源 → 注册字符设备 → 配置中断 → 初始化蓝牙控制器 remove 回调:停止控制器 → 释放中断 → 注销字符设备 → 释放内存 提示:probe 失败时驱动应回滚所有已分配资源,避免内存泄漏。

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/下的属性值。
设备树匹配流程 内核启动 解析设备树 创建platform_device 匹配? 调用 probe 回调 设备忽略/延迟绑定 匹配依据:compatible 属性与 of_match_table

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中应包含固件请求逻辑,并处理超时情况。
probe/remove 资源生命周期 probe 阶段 分配私有数据 获取中断号 注册字符设备 注册中断处理 初始化硬件 创建设备节点 remove 阶段 停止硬件 释放中断 销毁设备节点 删除字符设备 释放GPIO 释放私有数据 关键原则:probe 分配的顺序与 remove 释放的顺序相反(LIFO)

4.5 本章小结

  • 字符设备模型是蓝牙驱动与用户空间交互的桥梁,核心是cdev和file_operations。
  • platform_driver将蓝牙驱动挂接到平台总线,通过probe/remove管理设备生命周期。
  • 设备树匹配通过compatible属性实现硬件描述与驱动的解耦,是嵌入式蓝牙驱动的标准做法。
  • probe/remove实现必须严格遵循资源分配与释放的对称性,任何遗漏都会导致内核不稳定或内存泄漏。
下一阶段预告: 在掌握了驱动基础框架后,第5章将深入蓝牙中断处理机制,包括中断上半部/下半部、 threaded IRQ 以及中断共享与兼容性调试。