linux 设备树内容和plateform_device

一、gpio 设备树

label: node-name@unit-address {

properties definitions;

child nodes;

};

在Linux内核驱动开发中, 没有直接的API可以获取设备树节点前面的标签(label) 。这是因为标签在设备树源文件(.dts)编译为二进制DTB文件后, 不会作为节点的独立属性保留 ,而是仅用于建立节点间的引用关系。

所以这个 label: 得不到

cpp 复制代码
/* GPIO控制器定义 - 只出现一次 */
gpio1: gpio@10010000 {
compatible = "vendor,gpio-controller";
reg = <0x10010000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
};

/* 多个使用该GPIO控制器的设备 - 引用关系 */
led1 {
compatible = "my-company,led";
gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
};

button1 {
compatible = "my-company,button";
gpios = <&gpio1 6 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio1>;
interrupts = <6 IRQ_TYPE_EDGE_FALLING>;
};
cpp 复制代码
- #gpio-cells = <2>; → gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>; (2个参数)
- #gpio-cells = <3>; → gpios = <&gpio1 5 0 GPIO_ACTIVE_HIGH>; (3个参数)
- #gpio-cells = <1>; → gpios = <&gpio1 5>; (1个参数)

二、解析成私有数据:

cpp 复制代码
/* LED设备的私有数据结构 */
struct led_private_data {
    int gpio;              // GPIO编号
    int active_high;       // 是否高电平有效
    struct timer_list timer; // 用于控制闪烁的定时器
    bool state;            // 当前LED状态
    char name[32];         // 设备名称
};

/* 按键设备的私有数据结构 */
struct button_private_data {
    int gpio;              // GPIO编号
    int active_low;        // 是否低电平有效
    int irq;               // 中断号
    char name[32];         // 设备名称
    struct work_struct work; // 用于中断处理的工作队列
};

/* GPIO控制器的私有数据结构(驱动内部使用) */
struct gpio_controller_data {
    void __iomem *base;    // 寄存器基地址
    int irq;               // 中断号
    spinlock_t lock;       // 自旋锁,保护并发访问
    struct gpio_chip chip; // 用于向内核注册GPIO控制器
};


static int led_probe(struct platform_device *pdev)
{
    struct led_private_data *priv;
    struct device_node *np = pdev->dev.of_node;
    enum of_gpio_flags flags;
    int ret;

    /* 1. 分配私有数据内存 */
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv) {
        dev_err(&pdev->dev, "Failed to allocate private data\n");
        return -ENOMEM;
    }

    /* 2. 解析设备树中的GPIO信息 */
    priv->gpio = of_get_named_gpio_flags(np, "gpios", 0, &flags);
    if (priv->gpio < 0) {
        dev_err(&pdev->dev, "Failed to get GPIO from device tree\n");
        return priv->gpio;
    }
    
    /* 3. 确定GPIO有效电平 */
    priv->active_high = (flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1;
    
    /* 4. 获取设备名称 */
    of_property_read_string(np, "label", &priv->name);
    if (!priv->name)
        snprintf(priv->name, sizeof(priv->name), "led-%d", priv->gpio);

    /* 5. 申请GPIO资源 */
    ret = gpio_request(priv->gpio, priv->name);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request GPIO %d\n", priv->gpio);
        return ret;
    }

    /* 6. 设置GPIO为输出模式 */
    ret = gpio_direction_output(priv->gpio, priv->active_high ? 0 : 1);
    if (ret) {
        dev_err(&pdev->dev, "Failed to set GPIO direction\n");
        goto err_gpio;
    }

    /* 7. 初始化定时器 */
    setup_timer(&priv->timer, led_timer_callback, (unsigned long)priv);
    priv->state = false;

    /* 8. 将私有数据关联到platform_device */
    platform_set_drvdata(pdev, priv);

    dev_info(&pdev->dev, "LED driver probed successfully\n");
    return 0;

err_gpio:
    gpio_free(priv->gpio);
    return ret;
}

static int button_probe(struct platform_device *pdev)
{
    struct button_private_data *priv;
    struct device_node *np = pdev->dev.of_node;
    enum of_gpio_flags flags;
    int ret;

    /* 1. 分配私有数据内存 */
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv) {
        dev_err(&pdev->dev, "Failed to allocate private data\n");
        return -ENOMEM;
    }

    /* 2. 解析设备树中的GPIO信息 */
    priv->gpio = of_get_named_gpio_flags(np, "gpios", 0, &flags);
    if (priv->gpio < 0) {
        dev_err(&pdev->dev, "Failed to get GPIO from device tree\n");
        return priv->gpio;
    }
    
    /* 3. 确定有效电平 */
    priv->active_low = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0;
    
    /* 4. 获取设备名称 */
    of_property_read_string(np, "label", &priv->name);
    if (!priv->name)
        snprintf(priv->name, sizeof(priv->name), "button-%d", priv->gpio);

    /* 5. 获取中断信息 */
    priv->irq = gpio_to_irq(priv->gpio);
    if (priv->irq < 0) {
        dev_err(&pdev->dev, "Failed to get IRQ number\n");
        return priv->irq;
    }

    /* 6. 初始化工作队列 */
    INIT_WORK(&priv->work, button_work_handler);

    /* 7. 申请GPIO资源 */
    ret = gpio_request(priv->gpio, priv->name);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request GPIO %d\n", priv->gpio);
        return ret;
    }

    /* 8. 设置GPIO为输入模式 */
    ret = gpio_direction_input(priv->gpio);
    if (ret) {
        dev_err(&pdev->dev, "Failed to set GPIO direction\n");
        goto err_gpio;
    }

    /* 9. 注册中断处理函数 */
    ret = request_irq(priv->irq, button_irq_handler, 
                     IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                     priv->name, priv);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ %d\n", priv->irq);
        goto err_gpio;
    }

    /* 10. 将私有数据关联到platform_device */
    platform_set_drvdata(pdev, priv);

    dev_info(&pdev->dev, "Button driver probed successfully\n");
    return 0;

err_gpio:
    gpio_free(priv->gpio);
    return ret;
}

static int gpio_controller_probe(struct platform_device *pdev)
{
    struct gpio_controller_data *gc_data;
    struct device_node *np = pdev->dev.of_node;
    int ret;

    /* 1. 分配私有数据内存 */
    gc_data = devm_kzalloc(&pdev->dev, sizeof(*gc_data), GFP_KERNEL);
    if (!gc_data) {
        dev_err(&pdev->dev, "Failed to allocate private data\n");
        return -ENOMEM;
    }

    /* 2. 解析设备树中的寄存器信息 */
    ret = of_address_to_resource(np, 0, &gc_data->res);
    if (ret) {
        dev_err(&pdev->dev, "Failed to get register address\n");
        return ret;
    }

    /* 3. 映射寄存器地址 */
    gc_data->base = devm_ioremap_resource(&pdev->dev, &gc_data->res);
    if (IS_ERR(gc_data->base)) {
        dev_err(&pdev->dev, "Failed to map registers\n");
        return PTR_ERR(gc_data->base);
    }

    /* 4. 解析中断信息 */
    gc_data->irq = irq_of_parse_and_map(np, 0);
    if (!gc_data->irq) {
        dev_err(&pdev->dev, "Failed to get IRQ\n");
        return -EINVAL;
    }

    /* 5. 初始化自旋锁 */
    spin_lock_init(&gc_data->lock);

    /* 6. 设置GPIO芯片结构体 */
    gc_data->chip.label = np->name;
    gc_data->chip.dev = &pdev->dev;
    gc_data->chip.owner = THIS_MODULE;
    gc_data->chip.base = -1;  /* 动态分配GPIO基址 */
    gc_data->chip.ngpio = 32; /* 假设有32个GPIO */
    gc_data->chip.direction_input = gc_gpio_dir_in;
    gc_data->chip.direction_output = gc_gpio_dir_out;
    gc_data->chip.get = gc_gpio_get;
    gc_data->chip.set = gc_gpio_set;

    /* 7. 注册GPIO控制器到内核 */
    ret = gpiochip_add(&gc_data->chip);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register GPIO chip\n");
        return ret;
    }

    /* 8. 将私有数据关联到platform_device */
    platform_set_drvdata(pdev, gc_data);

    dev_info(&pdev->dev, "GPIO controller probed successfully\n");
    return 0;
}

const char *node_name = of_node_get_name(np); // 这里会得到 "led1"

cpp 复制代码
led1 {
    compatible = "gpio-leds";
    label = "user_led";
    gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
};

- 使用 of_node_get_name(np) 会得到 "led1" (节点名称)
- 使用 of_property_read_string(np, "label", &name) 会得到 "user_led" (label属性值)

是的,在platform_driver驱动开发中,私有数据完全可以包含自定义的内容。这是驱动开发中非常常见且灵活的做法。

三、 platform_driver与私有数据

在Linux平台驱动模型中,私有数据的管理主要通过以下方式实现:

  1. platform_device的数据 :这部分通常来自设备树解析或平台特定代码设置

  2. platform_driver自定义的私有数据 :这部分是驱动开发者根据需要添加的自定义内容

三、多个设备共用一个led驱动的方法

cpp 复制代码
## 方法一:修改驱动代码
在驱动程序中扩展 of_match_table ,使其支持多个不同的 compatible 字符串:

```
static const struct of_device_id led_of_match[] = {
    { .compatible = "my-company,led" },
    { .compatible = "my-company,led2" },  // 添加新的compatible值
    { /* 结束标记 */ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
```
这样,无论设备树中的设备使用哪个compatible字符串,只要在这个列表中定义了,就能匹配到同一个驱动。

# 驱动共用实现方法
是的,您可以通过两种方式实现让不同的设备共用同一驱动程序:

## 方法一:修改驱动代码
在驱动程序中扩展 of_match_table ,使其支持多个不同的 compatible 字符串:

```
static const struct of_device_id led_of_match[] = {
    { .compatible = "my-company,led" },
    { .compatible = "my-company,led2" },  // 添加新的compatible值
    { /* 结束标记 */ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
```
这样,无论设备树中的设备使用哪个compatible字符串,只要在这个列表中定义了,就能匹配到同一个驱动。

## 方法二:修改设备树
将所有设备节点的 compatible 属性修改为相同的值:

```
led1 {
    compatible = "my-company,led";
    gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
};
led2 {
    compatible = "my-company,led";  // 修改为与led1相同的值
    gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;
};
```

四、多个LED设备共用同一个驱动模块

当多个LED设备共用同一个驱动模块时,互斥锁的使用需要遵循以下原则:

每个设备实例应有独立的锁

需要加锁,但每个设备实例应该有自己独立的互斥锁 ,而不是所有设备共用一个全局锁。具体来说:

  1. 锁应该在私有数据结构中 :

    struct led_drv_priv {
    struct gpio_desc gpiod;
    struct timer_list timer;
    bool state;
    int blink_interval;
    struct mutex lock; /
    每个设备实例独立的锁 */
    struct cdev cdev;
    };

仿止竞争

cpp 复制代码
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
main()
{
     /* 锁机制和原子操作 */
    struct mutex mutex;       /* 互斥锁,用于保护读写操作 */
    spinlock_t lock;          /* 自旋锁,用于保护快速操作 */
    atomic_t open_count;      /* 设备打开计数 */
    atomic_t ref_count;       /* 引用计数 */

    /* 增加打开计数和引用计数 */
    atomic_inc(&drv_data->open_count);
    atomic_inc(&drv_data->ref_count);

    /* 减少打开计数和引用计数 */
    atomic_dec(&drv_data->open_count);
    atomic_dec(&drv_data->ref_count);

    /* 加锁保护设备关闭逻辑 */

    if (mutex_lock_interruptible(&drv_data->mutex)) {
        return -ERESTARTSYS;
    }
    /* TODO: 添加设备硬件清理操作 */
    mutex_unlock(&drv_data->mutex);

    /* 使用自旋锁保护快速读取操作 */
    spin_lock_irqsave(&drv_data->lock, flags);
    spin_unlock_irqrestore(&drv_data->lock, flags);
}

互斥锁(mutex)和自旋锁(spin lock)是Linux内核中两种重要的并发控制机制,它们有以下主要区别:

### 1. 等待方式
- 互斥锁 :当锁被占用时,请求锁的进程会进入睡眠状态,让出CPU资源,等待锁可用时被唤醒
- 自旋锁 :当锁被占用时,请求锁的进程会一直循环检测锁是否可用,不放弃CPU资源
### 2. 适用场景
- 互斥锁 :适合保护 较长时间 的临界区,例如I/O操作、复制数据等耗时操作
- 自旋锁 :适合保护 非常短 的临界区,例如更新计数器、修改指针等简单操作
### 3. CPU资源消耗
- 互斥锁 :CPU利用率较高,因为进程在等待时会让出CPU
- 自旋锁 :CPU利用率较低,因为进程在等待时会持续占用CPU进行自旋
### 4. 上下文切换
- 互斥锁 :会引起上下文切换,有一定开销
- 自旋锁 :不会引起上下文切换,避免了切换开销
### 5. 死锁风险
- 互斥锁 :可能发生优先级反转和死锁
- 自旋锁 :如果在自旋期间睡眠或阻塞,会导致严重的系统问题
### 6. 中断处理中的使用
- 互斥锁 :不能在中断上下文(如中断处理函数)中使用
- 自旋锁 :可以在中断上下文和进程上下文中使用,但需要在使用时禁用中断(spin_lock_irqsave/spin_unlock_irqrestore)
cpp 复制代码
void write(int value) {
    int temp;
    pthread_rwlock_wrlock(&rwlock);  // 写锁
    data = value;
    temp=322;
    pthread_rwlock_unlock(&rwlock);
} 
这里面的temp 为什么不加锁
答案:temp 是线程私有变量,不需要锁
根本原因:锁保护的是共享数据,不是代码。temp 是每个线程独立的栈变量,不是共享资源

每个线程有自己的栈:线程A和线程B同时调用 write() 时,它们操作的是不同内存地址的 temp。

你的理解完全正确!所有函数内定义的局部变量(栈变量)都不需要加锁。这是并发编程的基本准则之一。
1. 根本原因:线程拥有独立的栈空间
每个线程在创建时都会分配独立的栈内存(通常 1-8MB),局部变量就存储在这里:
void func() {
    int a = 10;      // 线程A的栈地址: 0x7f3c...1000
    int b = 20;      // 线程B的栈地址: 0x7f4d...2000
    // 即使代码相同,物理内存地址完全不同
}
cpp 复制代码
// 1. 初始化 cdev
cdev_init(&my_cdev, &fops);

// 2. 注册到内核(此时驱动已生效,但 /dev 下无文件)
cdev_add(&my_cdev, devno, 1);

// 3. 创建设备文件(用户才能看到 /dev/mydev)
device_create(my_class, NULL, devno, NULL, "mydev");

如果 devt = MKDEV(0,0) 或直接传 0:不会创建 /dev 下的设备节点文件
如果 devt 是有效设备号:会在 /sys/class/xxx/yyy/dev 中显示设备号,并触发 udev/mdev 自动创建 /dev/yyy 设备文件

4. 什么时候可以没有设备号?

纯 sysfs 驱动示例

复制代码
// 只创建 sysfs 接口,不生成 /dev 文件
device_create(sensor_class, NULL, 0, NULL, "sensor_config");

// 用户空间访问方式:
// echo 100 > /sys/class/sensor_class/sensor_config/threshold
// cat /sys/class/sensor_class/sensor_config/temperature
相关推荐
researcher-Jiang几秒前
高性能计算之MPI:第一次MPI并行程序设计练习
linux·运维·服务器
Wireless_wifi67 分钟前
Why Choose IPQ9574 for Your WiFi 7 Solution
linux·人工智能·5g
Tim_1011 分钟前
【C++】009、extern关键字
java·开发语言
ShiXZ21314 分钟前
PDF-OCR文件识别篇(七):数据入库
java·pdf·json·ocr·springboot
尽兴-24 分钟前
Redis 为什么快?
数据库·redis·内存
林澈在路上26 分钟前
最新版权清晰 AI音乐写歌工具软件App推荐 商用全场景实测指南
数据库·人工智能·ai·aigc·音频
rebibabo1 小时前
Java基础(番外) | Kafka 入门:分区、副本与消费者组原理
java·分布式·kafka·学习笔记·副本·分区·异步日志
Flittly1 小时前
【AgentScope Java新手村系列】(17)长期记忆系统
java·spring boot·spring
MYMOTOE61 小时前
国内对标腾讯 WorkBuddy 的桌面 AI 智能体软件大全
linux
wei1986211 小时前
.net添加web引用和添加服务引用有什么区别?
java·前端·.net