一、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平台驱动模型中,私有数据的管理主要通过以下方式实现:
-
platform_device的数据 :这部分通常来自设备树解析或平台特定代码设置
-
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设备共用同一个驱动模块时,互斥锁的使用需要遵循以下原则:
每个设备实例应有独立的锁
需要加锁,但每个设备实例应该有自己独立的互斥锁 ,而不是所有设备共用一个全局锁。具体来说:
-
锁应该在私有数据结构中 :
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