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 #include #include 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 // 即使代码相同,物理内存地址完全不同 } ``` ![](https://i-blog.csdnimg.cn/direct/39a5d2f257d443c6832b308b172391dd.png) ```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

相关推荐
python百炼成钢40 分钟前
40.linux自带LED驱动
linux·运维·服务器
乌萨奇也要立志学C++41 分钟前
【Linux】线程概念 线程与进程深度剖析:虚实内存转换、实现机制与优缺点详解
linux·c++
福尔摩斯张43 分钟前
使用Linux命名管道实现无血缘关系进程间通信
linux·服务器·网络
会飞的土拨鼠呀43 分钟前
linux 重新运行NetworkManager
linux·运维·服务器
浪漫血液&44 分钟前
索引为什么用B+树而不是B树
数据结构·数据库·b树·b+树
Overt0p44 分钟前
抽奖系统 (1)
java·spring boot·spring·java-ee·java-rabbitmq
shawnyz1 小时前
RHCSE--SHELL02--变量
linux·运维·服务器
c***69301 小时前
SpringCloudGateWay
java
子豪-中国机器人1 小时前
C++自定义结构体学习方法:
java·开发语言