总线设备驱动模型

整体框架

../../_images/image-20200602092253534.png

无论先注册平台设备还是平台驱动,程序都会去bus里面找对应的链表,根据匹配规则进行设备和驱动的匹配,当成功后就会调用驱动的probe函数,在probe函数中自己可以注册设备分配自己的file_operotions结构体,并完成硬件的初始化工作。

匹配规则

  • 比较platform_device.driver_overrideplatform_driver.driver.name 可以设置platform_device .driver_override ,强制选择某个 platform_driver

  • 然后比较: platform_ device.nameplatform_driver.id_table[i].name platform_driver.id_tablestruct platform_device_id类型的指针,表示该 drv 支持若干个 device ,它里面 列出了各个 device 的

    {
    	.name;           //表示该 dv 支持的设备的名字
    	.driver_data;    //提供给该 d evice 的私有数据
    }
    
  • 最后比较: platform_device.nameplatform_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】文件,如下:

#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】文件,如下:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#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");