设备-驱动-字符设备驱动的问题与理解
笔记学习整理基于野火鲁班猫教程并且添加自己学习后理解的内容然后还有ai的一些总结。如果有说的不好或者不对的地方希望大家指正!!!
我在看野火的pinctrl和gpio子系统代码和以前他们讲完设备模型后的代码我发现都有一个共通点,就是他们很喜欢设备写一个代码,驱动+字符驱动写成一个代码。我就有点疑惑:
- 为啥是驱动和字符驱动老是写一块?不能分开写然后extrn需要的变量吗?
- 字符设备驱动和设备驱动的差别到底是什么?
先上他们的代码(这个代码有点风险的,我后续再讲)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h> /* 平台设备*/
#include <linux/gpio/consumer.h> /* GPIO描述符*/
#include <linux/of.h> /* DT*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
/*------------------字符设备内容----------------------*/
#define DEV_NAME "led_test"
#define DEV_CNT (1)
/*定义字符设备的设备号*/
static dev_t led_devno;
/*定义字符设备结构体chr_dev */
static struct cdev led_chr_dev;
struct class *class_led; //保存创建的类
struct device *device; // 保存创建的设备
static struct gpio_desc *led;
static const struct of_device_id dt_ids[] =
{
{ .compatible = "fire,led_test", },
{ }
};
/*字符设备操作函数集,open函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
pr_info("device probed!\n");
return 0;
}
/*字符设备操作函数集,write函数*/
static ssize_t led_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
unsigned char write_data; //用于保存接收到的数据
int error = copy_from_user(&write_data, buf, cnt);
if(error < 0) {
printk("led_test write failed \n");
return -1;
}
/*设置GPIO 输出电平 具体引脚根据板卡原理图*/
if(write_data)
{
gpiod_set_value(led,1); // 引脚输出高电平,红灯灭
}
else
{
gpiod_set_value(led, 0); // 引脚输出底电平,红灯亮
}
return 0;
}
/*字符设备操作函数集*/
static struct file_operations led_chr_dev_fops =
{
.owner = THIS_MODULE,
.open = led_chr_dev_open,
.write = led_chr_dev_write,
};
static int my_pdrv_probe (struct platform_device *pdev)
{
int ret;
struct device *dev = &pdev->dev;
/*gpio申请,指定输出*/
led = gpiod_get(dev, "led", GPIOD_OUT_HIGH);
/*---------------------注册 字符设备部分-----------------*/
//第一步
//采用动态分配的方式,获取设备编号,次设备号为0,
//设备名称为led_test,可通过命令cat /proc/devices查看
//DEV_CNT为1,当前只申请一个设备编号
ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
if(ret < 0){
printk("fail to alloc led_devno\n");
goto alloc_err;
}
//第二步
//关联字符设备结构体cdev与文件操作结构体file_operations
led_chr_dev.owner = THIS_MODULE;
cdev_init(&led_chr_dev, &led_chr_dev_fops);
//第三步
//添加设备至cdev_map散列表中
ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT);
if(ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
//第四步
/*创建类 */
class_led = class_create(THIS_MODULE, DEV_NAME);
/*创建设备*/
device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);
pr_info("device probed!\n");
return 0;
add_err:
//添加设备失败时,需要注销设备号
unregister_chrdev_region(led_devno, DEV_CNT);
printk("error! \n");
alloc_err:
return -1;
}
static struct platform_driver mypdrv = {
.probe = my_pdrv_probe,
.driver = {
.name = "pdrv_led_test",
.of_match_table = dt_ids,
.owner = THIS_MODULE,
},
};
/*驱动初始化函数*/
static int __init led_platform_driver_init(void)
{
platform_driver_register(&mypdrv);
return 0;
}
/*驱动注销函数*/
static void __exit led_platform_driver_exit(void)
{
pr_info("led_test exit!\n");
gpiod_put(led);
/*删除设备*/
device_destroy(class_led, led_devno); //清除设备
class_destroy(class_led); //清除类
cdev_del(&led_chr_dev); //清除设备号
unregister_chrdev_region(led_devno, DEV_CNT); //取消注册字符设备
platform_driver_unregister(&mypdrv);
}
module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);
MODULE_LICENSE("GPL");
这里是驱动代码,他是弄了一个设备树匹配的驱动。
解答问题:
- 首先字符驱动是提供用户与内核态之间的操作接口,例如/dev/设备的文件,我们open这个文件就可以操作对应的设备。但是设备驱动是用来获取设备信息的。我们看到probe函数中led是通过gpiod_get函数获取的led设备的信息(这个led在设备树定义好了)。然后提供给字符设备用。所以一个是负责操作,一个是获取信息
- 驱动和字符驱动放在一起的好处的可以很好的管理驱动的生命周期同步,保证资源安全。你可以看到代码中是通过led结构体获取设备信息。然后字符设备调用。用户的read啊write啊都会在字符设备驱动中调用对应函数。Ok假如我们用extern led结构体的方式,字符设备驱动和设备驱动分开写,这样就会导致假设我的设备驱动卸载了,你字符驱动还没卸载,但是你继续操作/dev/设备,那就会出问题。所以两个写一块的话,设备驱动卸载可以在exit函数或者remove函数对字符设备驱动也进行卸载处理,保证了驱动的生命周期同步。
接下来讲一下这个代码的问题:
1、假如probe没有成功执行:probe 执行失败时,remove 函数不会执行(只有驱动与设备成功绑定,也就是probe成功执行且返回0,之后卸载驱动的时候remove才会执行),但 exit 函数一定会执行(驱动的init和exit函数是加载和卸载无条件必须执行)。所以既然是exit必定执行,他exit函数那里就没有检查资源是否分配了。有可能probe失败了啥也没分配。

119

被折叠的 条评论
为什么被折叠?



