imx6ULL从应用程序到驱动程序
文章目录
- 应用程序
- 驱动程序
- 注册主函数
- 注册platfrom_device
- 注册platfrom_drive(platfrom_device 和 platfrom_drive没有注册先后之分,谁先注册都可以)
- platfrom_device 和 platfrom_drive 匹配成功
应用程序
应用程序比较简单,就是通过open来打开一个字符设备,再通过write来改变led状态
int main(int argc, char** argv)
{int fd;char status = 0;if (argc != 3){printf("Usage: %s <dev> <on|off>\n", argv[0]);printf(" eg: %s /dev/myled on\n", argv[0]);printf(" eg: %s /dev/myled off\n", argv[0]);return -1;}//openfd = open(argv[1], O_RDWR);if (fd == -1){printf("can not open %s\n", argv[1]);printf("errno=%d\n",perror);return -1;}// write//strcmp()用于比较两个字符串是否相同,一样则返回0if (strcmp(argv[2], "on") == 0){status = 1;write(fd, &status, 1);}else{status = 0;write(fd, &status, 1);}close(fd);return 0;}
驱动程序
注册主函数
当用户程序调用 open(“/dev/mydevice”, O_RDWR) 会经过这几个步骤
- 触发系统调用:应用程序通过 glibc 库调用 open(),进入内核态。
- VFS 处理请求:内核的虚拟文件系统(VFS)解析路径 /dev/mydevice,发现这是一个设备文件。
- 查找设备驱动:VFS 根据设备文件的主设备号(Major Number)找到对应的驱动程序。
- 调用驱动函数:VFS 从驱动注册的 file_operations 结构体中获取 .open 函数指针, 执行驱动实现的 mydevice_open() 函数。
所以为了让VFS能够从驱动注册的 file_operations 结构体中获取 .open 函数指针
要先
static ssize_t led_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{struct inode *inode = file_inode(filp);//得到节int minor = iminor(inode);//从节点里取出次设备号char status;//在驱动程序里定义,属于内核空间int ret;//这里加个ret是因为copy_from_user函数有返回值//如果不使用返回值会有警告ret = copy_from_user(&status, buf, 1);//拷贝到val,buf:用户空间,拷贝一个字节p_ledopr -> ctrl(minor,status);//根据次设备号控制ledreturn 1;
}static int led_open(struct inode *inode, struct file *filp)
{int minor = iminor(inode);//得到次设备号printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);p_ledopr -> init(minor);//根据次设备号初始化ledreturn 0;}static struct file_operations led_fop = {.owner = THIS_MODULE,.open = led_open,.write = led_write,};
系统怎么知道在哪里找这个file_operations,所以要通过register_chrdev把这个注册到内核
/*入口函数*/
//动态加载:模块可随时通过 insmod 或 modprobe 加载到运行中的内核。
static int __init led_init(void)
{int err;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);//调用 register_chrdev 向内核注册设备,获取主设备号并绑定 file_operations。/*仅调用 register_chrdev:设备号已注册,但 /dev 下没有设备节点,应用程序无法通过 open("/dev/...") 访问设备。所以还要调用 device_create,自动生成设备节点(如 /dev/my_device0)*/major = register_chrdev(0,"jollyled",&led_fop);//内核设备号注册与操作函数绑定的入口/*内核同时提供了class_create()函数,可以用它来创建一个类,这个类存放于sys下面,创建了这个类,再调用device_create()函数来在/dev目录下创建相应的设备节点。*///class_create :/sys/class/<class_name>//device_create:/sys/class/<class_name>/<device>led_class = class_create(THIS_MODULE,"jollyled_class");err = PTR_ERR(led_class);if (IS_ERR(led_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "jollyled");return -1;}return 0;
}
有入口函数就有出口函数
/*出口函数*/
//模块可通过 rmmod 卸载,释放资源。
static void __exit led_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);class_destroy(led_class);unregister_chrdev(major, "jollyled");//第二个参数代表设备名,但在函数的实现源码中没有用到,没有什么意义。}
出入口函数必要性:
- 确保内核资源管理的正确性和安全性。
- 支持模块的动态加载与卸载,提升系统灵活性。
- 避免资源泄漏,保障内核稳定性。
注册platfrom_device
platfrom_device用于设备硬件的描述
static struct resource resources[] = {{ //引脚第几组第几个.start = GROUP_PIN(3,1),//中断类型.flags = IORESOURCE_IRQ,//设备名.name = "Jolly_led_pin",},{.start = GROUP_PIN(5,8),.flags = IORESOURCE_IRQ,.name = "Jolly_led_pin",},};static struct platform_device board_A_led_dev = {.name = "Jolly_led",//用于和platfrom_drive匹配的是这个名字.num_resources = ARRAY_SIZE(resources),.resource = resources,.dev = {.release = led_dev_release,},
};
注册进内核
//用于向内核注册一个平台设备(struct platform_device),描述设备的硬件资源(如内存地址、中断号、设备名称等)。
static int __init led_dev_init(void)
{int err;err = platform_device_register(&board_A_led_dev); return 0;
}static void __exit led_dev_exit(void)
{platform_device_unregister(&board_A_led_dev);
}
注册platfrom_drive(platfrom_device 和 platfrom_drive没有注册先后之分,谁先注册都可以)
//函数原型const struct of_device_id *of_match_device()
static const struct of_device_id Zane_leds[] = {//of_device_id 用于device和driver的match{ .compatible = "Jolly,leddrv" },//前面的Jolly是厂商名,这个Jolly要和设备树节点zane(我自己命的名)里的compatible一致才能匹配上//后面的leddrv是模块名,注意:和driver去match的不是模块名{ },
};static struct platform_driver chip_demo_gpio_driver = {.probe = chip_demo_gpio_probe,.remove = chip_demo_gpio_remove,.driver = {.name = "Jolly_led",//没有设备树的时候通过这个name去跟platform_device的name匹配//of_device_id是一个表,把这个表的指针赋给这个of_match_table就完成了匹配表的设定:.of_match_table = Zane_leds;},
};
我的设备树节点
向内核注册平台驱动(struct platform_driver),提供操作设备的函数(如初始化、探测、移除等)。
static int __init chip_demo_gpio_drv_init(void)
{int err;err = platform_driver_register(&chip_demo_gpio_driver); register_led_operations(&jolly_led_demo_opr);return 0;
}static void __exit lchip_demo_gpio_drv_exit(void)
{platform_driver_unregister(&chip_demo_gpio_driver);
}
当注册了platfrom_device 或 platfrom_drive 都会去按照这个规则来匹配对方
- platform_device的.driver_override 与 platform_driver.driver中的.name;
- platform_device的.of_node数组的某个.name与 与 platform_driver.driver.of_match_table数组的某个.name(来源于设备树);
- platform_device的.name 与 platform_driver.id_table数组中的某个.name;(常用)
- platform_deivce的.name 与 platform_driver.driver中的.name;(常用)
platfrom_device 和 platfrom_drive 匹配成功
当设备的device 和其对应的driver 在总线上完成配对之后,系统就调用 platform设备的probe函数完成驱动注册最后工作。
static int chip_demo_gpio_probe(struct platform_device *pdev)
{/*platform_get_resource 跟设备树没什么关系,但设备树的节点被转换为platform_device后,设备树中的reg属性、interrupts属性也会被转换为“resource”。*///这一段不是从设备树加载设备节点/*struct resource *res;int i = 0;while (1){//通过platform_get_resource在设备树一个个匹配platform_device *pdevres = platform_get_resource(pdev, IORESOURCE_IRQ, i);if (!res)//如果是空则说明没有设备了,也就没有必要进行下面的create_device了break;g_ledpins[g_ledcnt] = res->start;//取出资源led_class_create_device(g_ledcnt);//在probe里得到一个led设备再创建一个节点i++;g_ledcnt++;}return 0;*///二进制格式dtb设备树文件需要先转化成设备节点device_node结构,然后再将device_node转换成平台设备platform_device。struct device_node *np;int err = 0;int led_pin;np = pdev->dev.of_node;//把设备树转化成设备节点device_node结构if (!np)return -1;//np:要读取的设备树节点,“pin”:要读取的属性的名字,读取到以后放到led_pinerr = of_property_read_u32(np, "pin", &led_pin);//用于读取只有一个整形值的属性g_ledpins[g_ledcnt] = led_pin;led_class_create_device(g_ledcnt);g_ledcnt++;return 0;}