# 总线设备驱动模型 ## 整体框架 ![](media/image-20200602092253534.png) 无论先注册平台设备还是平台驱动,程序都会去bus里面找对应的链表,根据匹配规则进行设备和驱动的匹配,当成功后就会调用驱动的probe函数,在probe函数中自己可以注册设备分配自己的file_operotions结构体,并完成硬件的初始化工作。 ### 匹配规则 - 比较`platform_device.driver_override` 和`platform_driver.driver.name` 可以设置`platform_device .driver_override` ,强制选择某个 `platform_driver` 。 - 然后比较: `platform_ device.name` 和 `platform_driver.id_table[i].name` `platform_driver.id_table`是`struct platform_device_id`类型的指针,表示该 drv 支持若干个 device ,它里面 列出了各个 device 的 ```c { .name; //表示该 dv 支持的设备的名字 .driver_data; //提供给该 d evice 的私有数据 } ``` - 最后比较: `platform_device.name` 和 `platform_driver.driver.name` `platform_driver.id_table`可能为空,这时可以根据`platform_driver.driver.name` 来寻找同名的 `platform_device` 。 ### 平台设备注册函数调用关系 ``` platform_device_register platform_device_add device_add bus_add_device // 放入链表 bus_probe_device // probe 枚举设备, 即找到匹配的 dev, drv device_initial_probe __device_attach bus_for_each_drv (...,(...,__device_attach_driver __device_attach_driver driver_match_device(drv, dev) // 是否匹配 driver_probe_device // 调用 d rv 的 p robe ``` ### 平台驱动注册函数调用关系 ``` platform_driver_register __platform_driver_register driver_register bus_add_driver // 放入链表 driver_attach(drv) bus_for_each_dev(drv bus, NULL, drv, __driver_attach); __driver_attach driver_match_device(drv, dev) // 是否匹配 driver_probe_device // 调用 d rv 的 p robe ``` ### 常用函数 ``` drivers/base/platform.c 设备相关 platform_device_register\platform_device_unregister platform_add_devices// 注册多个 device 驱动相关 platform_driver_register\platform_driver_unregister 获取资源相关 返回该dev 某种类型(type)的资源中的第几个(num)资源 struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num) eg:获取中断内存资源 platform_get_resource(dev, IORESOURCE_MEM, i) 返回该dev所用的第几个(num) 中断: int platform_get_irq(struct platform_device *dev, unsigned int num) 通过名字返回该dev的某类型的资源 struct resource *platform_get_resource_byname(struct platform_device *dev, unsigned int type, const char *name) 通过名字返回该dev的中断号 int platform_get_irq_byname(struct platform_device *dev, const char *name) ``` ## 总线设备驱动模型步骤 ### 平台设备 1. 根据硬件,构建自己的设备资源信息resource,也就是LED0所使用的所有寄存器 2. 构建自己的platform_device设备,把自己的设备资源信息resource,填充进平台设备 3. 入口函数中,注册自己的平台设备platform_device到总线上的平台设备链表中注册的过程中会根据匹配规则 到【总线平台驱动链表】里面找,如果匹配成功会调用驱动里面的platform_driver下的probe函数 。 4. release函数:当卸载对端即【平台驱动】时候调用该函数, 5. 出口函数:如果用户卸载该驱动的时候会,会到这里,把平台设备从总线中去掉 参见代码【03.led_driver_bus_dev_drv】工程下的【led_dev.c】文件,如下: ```c #define CCM_CCGR1_BASE (0x20C406C) #define IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 (0x2290014) #define GPIO5_DR_BASE (0x020AC000) #define GPIO5_GDIR_BASE (0x020AC000+0x4) #define REGISTER_LENGTH 4 static void led_release(struct device *dev) { /* * 4:当卸载对端即【平台驱动】时候调用该函数, */ printk("led device released!\r\n"); } /* * 1:根据硬件,构建自己的设备资源信息resource,也就是LED0所使用的所有寄存器 */ static struct resource led_resources[] = { [0] = { .start = CCM_CCGR1_BASE, .end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1), .flags = IORESOURCE_MEM, }, [1] = { .start = IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3, .end = (IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 + REGISTER_LENGTH - 1), .flags = IORESOURCE_MEM, }, [2] = { .start = GPIO5_DR_BASE, .end = (GPIO5_DR_BASE + REGISTER_LENGTH - 1), .flags = IORESOURCE_MEM, }, [3] = { .start = GPIO5_GDIR_BASE, .end = (GPIO5_GDIR_BASE + REGISTER_LENGTH - 1), .flags = IORESOURCE_MEM, }, }; /* * 2:构建自己的platform_device设备,把自己的设备资源信息resource,填充进平台设备 */ static struct platform_device leddevice = { .name = "xym-led", .id = -1, .dev = { .release = &led_release, }, .num_resources = ARRAY_SIZE(led_resources), .resource = led_resources, }; static int __init leddevice_init(void) { /* * 3:入口函数中,注册自己的平台设备platform_device到总线上的平台设备链表中 * 注册的过程中会根据匹配规则 到【总线平台驱动链表】里面找, * 如果匹配成功会调用驱动里面的platform_driver下的probe函数 */ return platform_device_register(&leddevice); } static void __exit leddevice_exit(void) { /* * 5:如果用户卸载该驱动的时候会,会到这里,把平台设备从总线中去掉 */ platform_device_unregister(&leddevice); } module_init(leddevice_init); module_exit(leddevice_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("xyan_m@163.com"); ``` ### 平台驱动 1. 构建自己的platform驱动结构体 2. 在入口函数,注册自己的 platform_driver 到平台的总线驱动链表里面注册的过程中会根据匹配规则 到【总线平台设备链表】里面找,如果匹配成功会调用驱动里面的platform_driver下的probe函数 3. 匹配成功后会调用probe,在probe首先获取平台设备驱动里面的platform_device资源 4. 在probe函数里面,剩下的又回到新字符设备那一套了 1. 自己创建struct file_operations结构体fp 2. 在probe函数中,申请设备号,把fp占着该设备号对应的槽 3. 初始化cdev 并添加到内核 4. 创建类 5. 在类下创建设备节点 5. 当卸载对端即【平台设备】时候调用led_remove函数,倒着来,删除一遍 6. 如果用户卸载该驱动的时候,会调用出口函数,把平台驱动从总线中去掉 参见代码【03.led_driver_bus_dev_drv】工程下的【led_drv.c】文件,如下: ```c #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "board_fire_imx6ull_pro.h" struct led_char_dev { dev_t devid; /* 字符ID */ struct cdev cdev; /* 字符设备 */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ struct hw_led_res res[4]; /* led的平台资源 */ }; struct led_char_dev g_led_dev; /* 定义led设备 */ static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; } /* write(fd, &val, 1); */ static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset) { unsigned long err; uint8_t status = 0; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); err = copy_from_user(&status, buf, 1); /* 根据次设备号和status控制LED */ hw_led_write(0, status); return 1; } static int led_drv_open (struct inode *node, struct file *file) { hw_led_init(g_led_dev.res); file->private_data = &g_led_dev; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static int led_drv_close (struct inode *node, struct file *file) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); hw_led_reinit(); return 0; } /* * 4.1:自己创建struct file_operations结构体fp */ static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_drv_open, .read = led_drv_read, .write = led_drv_write, .release = led_drv_close, }; static int led_probe(struct platform_device *dev) { printk("led driver and device has matched,get res!\r\n"); int i; /* * 3:匹配成功后会调用,首先获取平台设备驱动里面的platform_device资源 */ for (i = 0; i < 4; i++) { g_led_dev.res[i].p_res = platform_get_resource(dev, IORESOURCE_MEM, i); if (!g_led_dev.res[i].p_res) { dev_err(&dev->dev, "No MEM resource for always on\n"); return -ENXIO; } g_led_dev.res[i].size = resource_size(g_led_dev.res[i].p_res); } /* * 4:剩下的又回到新字符设备那一套了 */ /* * 4.2:在probe函数中,申请设备号,把fp占着该设备号对应的槽 */ if(g_led_dev.major){ g_led_dev.devid = MKDEV(g_led_dev.major, 0); register_chrdev_region(g_led_dev.devid, 1, "xym_led"); }else{ alloc_chrdev_region(&g_led_dev.devid, 0, 1, "xym_led"); /* 申请设备号 */ g_led_dev.major = MAJOR(g_led_dev.devid); /* 获取主设备号 */ g_led_dev.minor = MINOR(g_led_dev.devid); /* 获取次设备号 */ } /* * 4.3:初始化cdev 并添加到内核 */ g_led_dev.cdev.owner = THIS_MODULE; cdev_init(&g_led_dev.cdev, &led_fops); cdev_add(&g_led_dev.cdev, g_led_dev.devid, 1); /* * 4.4:创建类 */ g_led_dev.class = class_create(THIS_MODULE, "xym_led_class"); if (IS_ERR(g_led_dev.class)) { return PTR_ERR(g_led_dev.class); } /* * 4.5:在类下创建设备节点 */ g_led_dev.device = device_create(g_led_dev.class, NULL, g_led_dev.devid, NULL, "xym_led"); return PTR_ERR(g_led_dev.device); } printk("%s %s line %d:led_probe !\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static int led_remove(struct platform_device *dev) { /* * 5:当卸载对端即【平台设备】时候调用该函数,倒着来,删除一遍 */ hw_led_reinit(); cdev_del(&g_led_dev.cdev); /* 删除字符设备 */ unregister_chrdev_region(g_led_dev.devid, 1); /* 取消注册的字符设备释放槽 */ device_destroy(g_led_dev.class, g_led_dev.devid); /* 删除设备节点 */ class_destroy(g_led_dev.class); /* 删除类 */ return 0; } /* * 1:构建自己的platform驱动结构体 */ static struct platform_driver platform_driver_led = { .driver = { .name = "xym-led", /* 驱动名字,用于和设备匹配 */ }, .probe = led_probe, .remove = led_remove, }; static int __init led_init(void) { /* * 2:注册自己的 platform_driver 到平台的总线驱动链表里面 * 注册的过程中会根据匹配规则 到【总线平台设备链表】里面找, * 如果匹配成功会调用驱动里面的platform_driver下的probe函数 */ printk("%s %s line %d:insmod !\n", __FILE__, __FUNCTION__, __LINE__); return platform_driver_register(&platform_driver_led); } static void __exit led_exit(void) { /* * 6:如果用户卸载该驱动的时候,会到这里,把平台驱动从总线中去掉 */ printk("%s %s line %d: rmmod ! \n", __FILE__, __FUNCTION__, __LINE__); platform_driver_unregister(&platform_driver_led); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("xym_@163.com"); ```