• I2C(三) linux3.4(内核分析)



    title: I2C(三) linux3.4((内核分析))
    date: 2019/1/28 19:18:42
    toc: true

    I2C(三) linux3.4(内核分析)

    (一)总线流程

    可以看下总线的匹配函数

    struct bus_type i2c_bus_type = {
    	.name		= "i2c",
    	.match		= i2c_device_match,
    	.probe		= i2c_device_probe,
    	.remove		= i2c_device_remove,
    	.shutdown	= i2c_device_shutdown,
    	.pm		= &i2c_device_pm_ops,
    };
    

    bus.probe

    这里插入一下busprobe,简单的搜索probe,可以在driversaseus.c搜索到一些东西,继续查看,可以看到有如下函数,也就是先执行总线的probe,再执行具体驱动的probe

    driver_probe_device
    	> really_probe
    		> dev->bus->probe(dev)
        	> drv->probe(dev)
    

    那么谁来调用这个driver_probe_device,搜索可以看到有以下函数调用,具体的device_attachdriver_attach就不深入了,水平还不够

    static DRIVER_ATTR(bind, S_IWUSR, NULL, driver_bind);
    	>driver_bind
    		>driver_probe_device(drv, dev)
    device_attach
    	>__device_attach
    		>driver_match_device 先匹配
    		>driver_probe_device(drv, dev)
    driver_attach
    	>__driver_attach
    		>driver_match_device 先匹配
    		>driver_probe_device(drv, dev)
    		
    int driver_probe_device(struct device_driver *drv, struct device *dev)
    	>really_probe(dev, drv);
    	{
    		dev->bus->probe(dev)
    		drv->probe(dev)
    	}
    

    match

    这里匹配的是dev 的母体结构client的成员namedriverid_table

    static int i2c_device_match(struct device *dev, struct device_driver *drv)
    {
    	//先找到 dev 的母结构  i2c_client的地址
    	struct i2c_client	*client = i2c_verify_client(dev);
        //通过 driver 找到 i2c_driver
        driver = to_i2c_driver(drv);
    	if (driver->id_table)
    		return i2c_match_id(driver->id_table, client) != NULL;
    					> if (strcmp(client->name, id->name) == 0)
    }
    

    mark

    i2c_device_probe

    匹配之后,会调用这个函数,这个函数会这里会将client 绑定具体的driver,再调用实际驱动的probe,

    client->driver = driver;
    driver->probe(client, i2c_match_id(driver->id_table, client));
    
    static int i2c_device_probe(struct device *dev)
    {
    	struct i2c_client	*client = i2c_verify_client(dev);
    	struct i2c_driver	*driver;
    	int status;
    
    	driver = to_i2c_driver(dev->driver);
    	if (!driver->probe || !driver->id_table)
    		return -ENODEV;
        // 这里会将client 绑定具体的driver
    	client->driver = driver;
    	if (!device_can_wakeup(&client->dev))
    		device_init_wakeup(&client->dev,
    					client->flags & I2C_CLIENT_WAKE);
    	dev_dbg(dev, "probe
    ");
    
    	status = driver->probe(client, i2c_match_id(driver->id_table, client));
    	if (status) {
    		client->driver = NULL;
    		i2c_set_clientdata(client, NULL);
    	}
    	return status;
    }
    
    

    (二)client注册

    这里的client,指的是能够挂接到总线上的设备,或者是说你强制认为总线上就有这个设备.具体的结构如下:

    struct i2c_client {
    	unsigned short flags;		/* div., see below		*/
    	unsigned short addr;		/* chip address - NOTE: 7bit	*/
    					/* addresses are stored in the	*/
    					/* _LOWER_ 7 bits		*/
    	char name[I2C_NAME_SIZE];
    	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
    	struct i2c_driver *driver;	/* and our access routines	*/
    	struct device dev;		/* the device structure		*/
    	int irq;			/* irq issued by device		*/
    	struct list_head detected;
    };
    #define to_i2c_client(d) container_of(d, struct i2c_client, dev)
    
    • struct i2c_adapter *adapter 指的是挂载的具体的哪个总线
    • struct i2c_driver *driver指的是我们用什么驱动,这里指的是自己的字符设备或者块设备等
    • struct device dev这个结构比较关键,这个就是总线设备模型dev---driver部分中左侧dev部分
    • 当我们挂载到bus上的时候,通过bus.probe就会将对应的driver附到client.driver上了

    mark

    方式(一)静态加载

    i2c_register_board_info

    这种方式是编译到内核,注册信息

    i2c_register_board_info
    

    我们一般使用如下方式

    //archarmmach-s3c24xxmach-mini2440.c
    static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
    	{
    		I2C_BOARD_INFO("24c08", 0x50),
    		.platform_data = &at24c08,
    	},
    };
    static void __init mini2440_init(void)
    {
    	i2c_register_board_info(0, mini2440_i2c_devs,
    				ARRAY_SIZE(mini2440_i2c_devs));
    }
    

    具体是怎么注册的?实际上是将具体的这个硬件信息先构造成i2c_devinfo结构,再放入到一个全局的链表中

    mark

    i2c_register_board_info(int busnum,
    	struct i2c_board_info const *info, unsigned len)
    {
    	if (busnum >= __i2c_first_dynamic_bus_num)
    		__i2c_first_dynamic_bus_num = busnum + 1;
    
    	for (status = 0; len; len--, info++) {
    		struct i2c_devinfo        *devinfo;
    		devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
    
    		devinfo->busnum = busnum;
    		devinfo->board_info = *info;
    		//这是一个全局的链表,也就是先分配一个i2c_devinfo.board_info 使用的就是单板传递的信息
    		list_add_tail(&devinfo->list, &__i2c_board_list);   
    	}
    	return status;
    }
    

    这个全局的链表内容是这样的

    struct i2c_devinfo {
    	struct list_head	list;
    	int			busnum;							//链表上的序号
    	struct i2c_board_info	board_info;
    	{
    		char		type[I2C_NAME_SIZE];		//设备名,
    		unsigned short	flags;					//将来传递给client
    		unsigned short	addr;					//设备地址
    		void		*platform_data;				
    		struct dev_archdata	*archdata;		
    		struct device_node *of_node;
    		int		irq;
    	};
    };
    

    i2c_scan_static_board_info

    谁会来调用这个链表呢?也就是根据这个信息添加client到总线上,很显然,是我们在注册总线的时候,来去遍历这个信息,也就是函数i2c_scan_static_board_info

    static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
    {
        struct i2c_devinfo  *devinfo;
        list_for_each_entry(devinfo, &__i2c_board_list, list) {
            if (devinfo->busnum == adapter->nr
                    && !i2c_new_device(adapter,
                            &devinfo->board_info))
                dev_err(&adapter->dev,
                    "Can't create device at 0x%02x
    ",
                    devinfo->board_info.addr);
        }
    }
    

    这个函数根据这个链表,通过i2c_new_device来操作,提前透露一下,这个函数是构造client,加入到总线设备的dev链表中去

    我们从 if (devinfo->busnum == adapter->nr这里可以看出来适配器的选择,也就是

    • devinfo->busnum也指的是总线的编号
    • adapter->nr 应该是适配器的编号,也就是总线的编号

    i2c_new_device

    这里我们根据i2c_board_info >构造链表__i2c_board_list 来创建一个实际的client,然后将这个client的元素dev挂载到总线设备模型的dev上

    struct i2c_client *
    i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
    {
        struct i2c_client   *client;
        int         status;
        client = kzalloc(sizeof *client, GFP_KERNEL); 先分配结构体
    
    	//  指定具体的adapt适配器
        client->adapter = adap;
    	//   指定挂载在总线设备平台上的 platform_data ,挂载在总线平台上的是dev 而不是 client
        client->dev.platform_data = info->platform_data; 
    	//    这个数据属于架构的数据
        if (info->archdata)
            client->dev.archdata = *info->archdata;
    	//具体硬件信息
        client->flags = info->flags;
        client->addr = info->addr;
        client->irq = info->irq;
        strlcpy(client->name, info->type, sizeof(client->name));
    
    	//地址合法性检测
        /* Check for address validity */
        status = i2c_check_client_addr_validity(client);
        if (status) {
            dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx
    ",
                client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
            goto out_err_silent;
        }
        /* Check for address business */  7位地址和10位地址判断
        status = i2c_check_addr_busy(adap, client->addr);
    	
        if (status)
            goto out_err;
    	
    	//设置具体总线平台的类型,节点,设置名字
        client->dev.parent = &client->adapter->dev;
        client->dev.bus = &i2c_bus_type;
        client->dev.type = &i2c_client_type;
        client->dev.of_node = info->of_node;
    	//kobject.name =%d-%04x   适配器.nr- client->addr(10位地址还要|0xa0000)
        /* For 10-bit clients, add an arbitrary offset to avoid collisions */
        dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
                 client->addr | ((client->flags & I2C_CLIENT_TEN)
                         ? 0xa000 : 0));
        //注册client->dev
        status = device_register(&client->dev);
        if (status)
            goto out_err;
        dev_dbg(&adap->dev, "client [%s] registered with bus id %s
    ",
            client->name, dev_name(&client->dev));
        return client;
    }
    
    • client->dev.parent = &client->adapter->dev; 这里将总线设备的这个元素只想来适配器的dev

    i2c_register_adapter

    那么谁来调用这个静态扫描的函数呢?很显然是在注册适配器的时候,也就是注册I2C控制器

    if (adap->nr < __i2c_first_dynamic_bus_num)
    	i2c_scan_static_board_info(adap);
    

    小结

    单板信息最终会加入到这个链表,注册adapt 的时候会扫描这个链表 生成client,将这个client.dev挂载到 总线平台设备的 dev链表上,那么我们如何去找到这个client,在这里有一个宏

    #define to_i2c_client(d) container_of(d, struct i2c_client, dev)
    

    这里的i2c_board_info.type==i2c_client.name 用作总线设备的匹配

    mark

    方式(二)指定设备

    这个方式实际上就是跳过通过单板信息构造链表,直接调用i2c_new_device来构造client,加入到总线设备模型的dev链表中,i2c_new_probed_device则是先判断下设备地址是否存在再来创建,这个函数还能指定自定义的地址检测方式

    i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
    
    
    i2c_new_probed_device(struct i2c_adapter *adap,
    		      struct i2c_board_info *info,
    		      unsigned short const *addr_list,
    		      int (*probe)(struct i2c_adapter *, unsigned short addr))
    {
    	if (!probe)
    		probe = i2c_default_probe;
        
        if (i2c_check_addr_busy(adap, addr_list[i]))
            ...
        probe(adap, addr_list[i])
    	return i2c_new_device(adap, info);
    }
    
    

    具体的实例代码如下

    static struct i2c_board_info at24cxx_info = {	
    	I2C_BOARD_INFO("at24c08", 0x50),   //设置tyep 也就是后来赋值给client的name
        #define I2C_BOARD_INFO(dev_type, dev_addr) 
    	.type = dev_type, .addr = (dev_addr)
    
    };
    static struct i2c_client *at24cxx_client;
    static int at24cxx_dev_init(void)
    {
    	struct i2c_adapter *i2c_adap;
    	i2c_adap = i2c_get_adapter(0);
    	at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);
    	i2c_put_adapter(i2c_adap);
    	
    	return 0;
    }
    
    /// 检测地址
    static struct i2c_client *at24cxx_client;
    static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
    static int at24cxx_dev_init(void)
    {
    	struct i2c_adapter *i2c_adap;
    	struct i2c_board_info at24cxx_info;
    	memset(&at24cxx_info, 0, sizeof(struct i2c_board_info));	
    	strlcpy(at24cxx_info.type, "at24c08", I2C_NAME_SIZE);
    	i2c_adap = i2c_get_adapter(0);
    	at24cxx_client = i2c_new_probed_device(i2c_adap, &at24cxx_info, addr_list, NULL);
    	i2c_put_adapter(i2c_adap);
    	if (at24cxx_client)
    		return 0;
    	else
    		return -ENODEV;
    }
    

    方式(三)用户空间

    直接在shell下操作文件

    # 创建
    echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device
    # 删除
    echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device
    

    搜索下new_device,实际最终调用函数i2c_sysfs_new_device,这个函数内部也是调用i2c_new_device

    static DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);
    static DEVICE_ATTR(delete_device, S_IWUSR, NULL, i2c_sysfs_delete_device);
    

    方式(四)遍历适配器

    前面三个都是指定适配器去挂接设备,那么假设不知道适配器,我们如何去遍历适配器,自动识别出在那个适配器上然后去挂接上client呢?这里使用i2c_add_driver

    注意

    实际上我们的适配器也是挂载到设备平台dev链表中,通过type来区分

    简介

    1. 使用i2c_add_driver注册用户驱动

    2. 匹配已有的挂载client,调用probe

    3. 遍历左侧的dev链表,挑选adapt,来识别驱动自身指定的address_list,如果识别到设备存在

    4. 调用驱动的detect来做进一步检测,设置i2c_board_info.type,赋值client做匹配

    5. 使用i2c_new_device来挂载这个client,这里会和i2c_driverid_table比较,执行驱动的probe

      这里一定会匹配上的,因为client.name就是i2c_board_info.type也就是id_table.name

    代码注释(可以看下后面的设备驱动章节)

    i2c_add_driver
    	i2c_register_driver
    		a. at24cxx_driver放入i2c_bus_type的drv链表
    		   并且从dev链表里取出能匹配的i2c_client并调用probe
    		driver_register
    			
    		
    		b. 对于每一个适配器,调用__process_new_driver
    		   对于每一个适配器,调用它的函数确定address_list里的设备是否存在
    		   如果存在,再调用detect进一步确定、设置,然后i2c_new_device
    		/* Walk the adapters that are already present */
    		i2c_for_each_dev(driver, __process_new_driver);
                //下面这个函数是总线设备的通用函数 ,注释上的意思应该就是
                // 在某类设备总线上,从 start的dev开始遍历,执行以fn为函数的,data为参数的回调i2c_driver
                // 这里也就是对于  i2c_bus_type 上的dev 执行 __process_new_driver  参数是driver也就是 i2c_driver
                //int bus_for_each_dev(struct bus_type *bus, struct device *start,void *data, int (*fn)(struct device *, void *))
                >bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
    
    			//可以先看一下__process_new_driver ,按照之前的注释 data是i2c_driver ,也就是 参数是 __process_new_driver
    			__process_new_driver
    				//这里的dev 是指的适配器,实际上适配器和设备都是挂载在一个链表上的,根据type分辨
    				// 确保遍历的dev 是适配器类型
                    if (dev->type != &i2c_adapter_type)
                        return 0;
    				i2c_do_add_adapter
    					/* Detect supported devices on that bus, and instantiate them */
    					i2c_detect(adap, driver);
    						for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
    							err = i2c_detect_address(temp_client, driver);
    										/* 判断这个设备是否存在:简单的发出S信号确定有ACK */
    										if (!i2c_default_probe(adapter, addr))
    											return 0;
    										
    										memset(&info, 0, sizeof(struct i2c_board_info));
    										info.addr = addr;	
    										
                                			//这里是我们自己的检测函数
    										// 设置info.type
    										err = driver->detect(temp_client, &info);
    					
    										i2c_new_device
    

    代码举例

    static struct i2c_driver at24cxx_driver = {
    	.class  = I2C_CLASS_HWMON, /* 表示去哪些适配器上找设备 */
    	.driver	= {
    		.name	= "100ask",
    		.owner	= THIS_MODULE,
    	},
    	.probe		= at24cxx_probe,
    	.remove		= __devexit_p(at24cxx_remove),
    	.id_table	= at24cxx_id_table,
    	.detect     = at24cxx_detect,  /* 用这个函数来检测设备确实存在 */
    	.address_list	= addr_list,   /* 这些设备的地址 */
    };
    
    //用作 dev 和 driver 的匹配,这里如果能识别,肯定赋值个 client.name 
    static const struct i2c_device_id at24cxx_id_table[] = {
    	{ "at24c08", 0 },
    	{}
    };
    static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
        
    去"class表示的这一类"I2C适配器,用"detect函数"来确定能否找到"address_list里的设备",
    如果能找到就调用i2c_new_device来注册i2c_client, 这会和i2c_driver的id_table比较,
    如果匹配,调用probe
    
    

    (三)适配器

    driversi2cussesi2c-s3c2410.c

    注册适配器最终会调用i2c_driver.detect来设置具体的匹配的参数i2c_client.type,,挂载client到链表

    然后调用驱动i2c_driver.probe注册字符设备驱动等

    引入

    驱动首先从入口开始看起,可以看到是platform框架,也就是驱动入口执行函数是driver.probe

    static int __init i2c_adap_s3c_init(void)
    {
    	return platform_driver_register(&s3c24xx_i2c_driver);
    }
    subsys_initcall(i2c_adap_s3c_init);
    
    static struct platform_driver s3c24xx_i2c_driver = {
    	.probe		= s3c24xx_i2c_probe,
    	.remove		= s3c24xx_i2c_remove,
    	.id_table	= s3c24xx_driver_ids,
    	.driver		= {
    		.owner	= THIS_MODULE,
    		.name	= "s3c-i2c",
    		.pm	= S3C24XX_DEV_PM_OPS,
    		.of_match_table = s3c24xx_i2c_match,
    	},
    };
    
    static struct platform_device_id s3c24xx_driver_ids[] = {
    	{
    		.name		= "s3c2410-i2c",
    		.driver_data	= TYPE_S3C2410,
    	}, {
    		.name		= "s3c2440-i2c",
    		.driver_data	= TYPE_S3C2440,
    	}, { },
    };
    

    s3c24xx_i2c_probe

    1. 设置具体的adapt结构
    2. 设置中断
    3. 使用i2c_add_numbered_adapter或者i2c_add_adapter来注册这个adapt
    4. 使用of_i2c_register_devices会调用i2c_new_device添加client到链表
    static int s3c24xx_i2c_probe(struct platform_device *pdev)
    {
    	struct s3c24xx_i2c *i2c;
    	struct s3c2410_platform_i2c *pdata = NULL;
    	struct resource *res;
    	int ret;
    
    	if (!pdev->dev.of_node) {
    		pdata = pdev->dev.platform_data;
    		if (!pdata) {
    			dev_err(&pdev->dev, "no platform data
    ");
    			return -EINVAL;
    		}
    	}
    
    	i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
    	i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
    
    
    	if (pdata)
    		memcpy(i2c->pdata, pdata, sizeof(*pdata));
    	else
    		s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c);
    	
    	//设置adapt 的参数
    	strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
    	i2c->adap.owner   = THIS_MODULE;
    	// adap.algo 这是具体的硬件传输的函数 
    	i2c->adap.algo    = &s3c24xx_i2c_algorithm;
    	//数据传输重试次数
    	i2c->adap.retries = 2;											
    	i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
    	i2c->tx_setup     = 50;
    
    	spin_lock_init(&i2c->lock);
    	
    	//初始化等待队列,这个在后续的休眠中使用,触发中断后休眠
    	init_waitqueue_head(&i2c->wait);
    
    	/* find the clock and enable it */
    	i2c->dev = &pdev->dev;
    	i2c->clk = clk_get(&pdev->dev, "i2c");
    	clk_enable(i2c->clk);
    
    	/* map the registers */
    	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    	i2c->ioarea = request_mem_region(res->start, resource_size(res),pdev->name);
    	i2c->regs = ioremap(res->start, resource_size(res));
    
    
    
    	/* setup info block for the i2c core */
    	i2c->adap.algo_data = i2c;
    	i2c->adap.dev.parent = &pdev->dev;
    
    	// 初始化I2c 寄存器 gpio复用
    	/* initialise the i2c controller */
    	ret = s3c24xx_i2c_init(i2c);
    
    
    	// 设置中断函数,真正的数据传输
    	i2c->irq = ret = platform_get_irq(pdev, 0);
    	ret = request_irq(i2c->irq, s3c24xx_i2c_irq, 0,dev_name(&pdev->dev), i2c);
    
    	ret = s3c24xx_i2c_register_cpufreq(i2c);
    
    	
    	// 这里使用指定 adapt 编号的方式,也可以使用 i2c_add_adapter 来注册这个adapt
    	
    	/* Note, previous versions of the driver used i2c_add_adapter()
    	 * to add the bus at any number. We now pass the bus number via
    	 * the platform data, so if unset it will now default to always
    	 * being bus 0.
    	 */
    	i2c->adap.nr = i2c->pdata->bus_num;
    	i2c->adap.dev.of_node = pdev->dev.of_node;
    
    	ret = i2c_add_numbered_adapter(&i2c->adap);
    
    	//这个会挂接client 到链表
    	of_i2c_register_devices(&i2c->adap);
    		> i2c_new_device  
    		
    		
    		
    	platform_set_drvdata(pdev, i2c);
    	
    	pm_runtime_enable(&pdev->dev);
    	pm_runtime_enable(&i2c->adap.dev);
    
    	dev_info(&pdev->dev, "%s: S3C I2C adapter
    ", dev_name(&i2c->adap.dev));
    	clk_disable(i2c->clk);
    	return 0;
    }
    

    注册适配器

    这里使用i2c_add_adapter或者i2c_add_numbered_adapter来注册适配器,最终都是调用i2c_register_adapter(adap)来注册适配器的

    i2c_register_adapter

    这里的具体看代码注释,最终会调用__process_new_adapter,应该是要完成实际的驱动注册

    static int i2c_register_adapter(struct i2c_adapter *adap)
    {
    
    	rt_mutex_init(&adap->bus_lock);
    	mutex_init(&adap->userspace_clients_lock);
    	INIT_LIST_HEAD(&adap->userspace_clients);
    
    	//设置 adapt的dev.type,后续能够根据这个和client区别
    	// 注册adap->dev 到总线平台的左侧
    	dev_set_name(&adap->dev, "i2c-%d", adap->nr);
    	adap->dev.bus = &i2c_bus_type;
    	adap->dev.type = &i2c_adapter_type;
    	res = device_register(&adap->dev);
    
    	// 在系统初始化的时候,我们手动添加了一些外设信息,期望构造client加入到dev链表
    	// 如果没有合适的adapt,我们并不能将其构造为client加入,而是保留在链表中
    	// 我们新注册了一个adapt,理所当然需要使用这个新的adapt去尝试构造一个client
    
    	/* create pre-declared device nodes */
    	if (adap->nr < __i2c_first_dynamic_bus_num)
    		i2c_scan_static_board_info(adap);
    
    	// 针对链表上的所有 driver,调用adapt的硬件操作,执行__process_new_adapter
    	// 执行__process_new_adapter 应该是要调用驱动driver 的detect 来创建字符设备等驱动
    	bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
    
    }
    

    __process_new_adapter

    • 这个函数是我们遍历总线的driver,执行这个函数,也就是遍历我们右侧的的i2c_driver

    • 这个函数从名字看就和__process_new_driver很像,从bus_for_each_drv的注释看出来data是作为回调的参数,也就是说最终是i2c_do_add_adapter(to_i2c_driver(d), adapt);,可以看到i2c_do_add_adapter的原型的第二个参数确实是adapt

    /**
     * bus_for_each_drv - driver iterator
     * @bus: bus we're dealing with.
     * @start: driver to start iterating on.
     * @data: data to pass to the callback.
     * @fn: function to call for each driver.
     *
    */
    int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
    		     void *data, int (*fn)(struct device_driver *, void *))
    
        
        
    static int __process_new_adapter(struct device_driver *d, void *data)
    {
    	return i2c_do_add_adapter(to_i2c_driver(d), data);
    }
    
    //上面的data也就是bus_for_each_drv的data 也就是最开始要注册的adapt
    static int i2c_do_add_adapter(struct i2c_driver *driver,
    			      struct i2c_adapter *adap)
    
    

    i2c_do_add_adapter

    这里函数的关键是i2c_detect,3.4内核的driver->attach_adapter可以忽略了

    static int i2c_do_add_adapter(struct i2c_driver *driver,
    			      struct i2c_adapter *adap)
    {
    	/* Detect supported devices on that bus, and instantiate them */
    	i2c_detect(adap, driver);
    
    	// 下面这个driver->attach_adapter 是2.6上的匹配到硬件地址后,
    	// 用来创建字符设备驱动等操作的函数,3.4内核上使用上面的 i2c_detect 来实现这个功能
    	// 一般不需要使用,可以看到结构定义是 __deprecated ,也就是不推荐的了
    	
    	/* Let legacy drivers scan this bus for matching devices */
    	if (driver->attach_adapter) {
    		dev_warn(&adap->dev, "%s: attach_adapter method is deprecated
    ",
    			 driver->driver.name);
    		dev_warn(&adap->dev, "Please use another way to instantiate "
    			 "your i2c_client
    ");
    		/* We ignore the return code; if it fails, too bad */
    		driver->attach_adapter(adap);
    	}
    	return 0;
    }
    

    i2c_detect

    这里先判断下adaptdriver类型,然后构造一个临时的client,地址是driver->address_list中的列表,依次尝试i2c_detect_address来检测

    static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
    {
    	const unsigned short *address_list;
    	struct i2c_client *temp_client;
    	int i, err = 0;
    	int adap_id = i2c_adapter_id(adapter);
    
    	address_list = driver->address_list;
    	if (!driver->detect || !address_list)
    		return 0;
    
    	/* Stop here if the classes do not match */
    	if (!(adapter->class & driver->class))
    		return 0;
    
    	/* Set up a temporary client to help detect callback */
    	temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
    	if (!temp_client)
    		return -ENOMEM;
    	temp_client->adapter = adapter;
    
    	// 创建一个临时的 client,使用 i2c_detect_address来尝试操作
    	for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
    		dev_dbg(&adapter->dev, "found normal entry for adapter %d, "
    			"addr 0x%02x
    ", adap_id, address_list[i]);
    		temp_client->addr = address_list[i];
    		err = i2c_detect_address(temp_client, driver);
    		if (unlikely(err))
    			break;
    	}
    
    	kfree(temp_client);
    	return err;
    }
    
    

    i2c_detect_address

    1. 这里会使用 i2c_smbus_xfer 来实际通信确保硬件存在
    2. 调用最后的 用户驱动 i2c_driver.detect
    3. 成功后使用i2c_new_device 安装clientdev 链表,i2c_new_device 在上述章节回顾
    static int i2c_detect_address(struct i2c_client *temp_client,
    			      struct i2c_driver *driver)
    {
    	struct i2c_board_info info;
    	struct i2c_adapter *adapter = temp_client->adapter;
    	int addr = temp_client->addr;
    	int err;
    
    	/* Make sure the address is valid */
    	err = i2c_check_addr_validity(addr);
    	if (err) {
    		dev_warn(&adapter->dev, "Invalid probe address 0x%02x
    ",
    			 addr);
    		return err;
    	}
    
    	/* Skip if already in use */
    	if (i2c_check_addr_busy(adapter, addr))
    		return 0;
    
    	// 这里会使用 i2c_smbus_xfer 来实际通信确保硬件存在
    	/* Make sure there is something at this address */
    	if (!i2c_default_probe(adapter, addr))
    		return 0;
    
    	// 调用最后的 用户驱动 i2c_driver.detect
    	/* Finally call the custom detection function */
    	memset(&info, 0, sizeof(struct i2c_board_info));
    	info.addr = addr;
    	err = driver->detect(temp_client, &info);
    	if (err) {
    		/* -ENODEV is returned if the detection fails. We catch it
    		   here as this isn't an error. */
    		return err == -ENODEV ? 0 : err;
    	}
    
    	
    	// i2c_new_device  安装client 到dev 链表
    	/* Consistency check */
    	if (info.type[0] == '') {
    		dev_err(&adapter->dev, "%s detection function provided "
    			"no name for 0x%x
    ", driver->driver.name,
    			addr);
    	} else {
    		struct i2c_client *client;
    
    		/* Detection succeeded, instantiate the device */
    		dev_dbg(&adapter->dev, "Creating %s at 0x%02x
    ",
    			info.type, info.addr);
    		client = i2c_new_device(adapter, &info);
    		if (client)
    			list_add_tail(&client->detected, &driver->clients);
    		else
    			dev_err(&adapter->dev, "Failed creating %s at 0x%02x
    ",
    				info.type, info.addr);
    	}
    	return 0;
    }
    
    

    i2c_driver.detect

    接下去就是要分析用户的设备驱动i2c_driver.detect,设置匹配参数

    1. 用来判断那些可能设备地址相同的不同类型的设备.
    2. 设置具体的i2c_board_info.type,这个参数会在i2c_new_device赋值给client,用作i2c总线平台的匹配参数
    3. 这会触发i2c_driverprobe

    接下去执行驱动probe

    这里就是执行字符设备驱动的注册等了

    (四)设备驱动i2c_driver

    driversmisceepromeeprom.c

    driversmisceepromat24.c

    misc 是杂项的意思

    引入

    从入口看起

    static int __init at24_init(void)
    {
    	return i2c_add_driver(&at24_driver);
    }
    module_init(at24_init);
    

    注册设备驱动

    i2c_add_driver

    /* use a define to avoid include chaining to get THIS_MODULE */
    #define i2c_add_driver(driver) 
    	i2c_register_driver(THIS_MODULE, driver)
    
    

    i2c_register_driver

    1. 注册驱动程序,智力应该就会去执行probe 函数了
    2. 遍历这个驱动driver,执行__process_new_driver
    int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
    {
    	int res;
    
    	/* Can't register until after driver model init */
    	if (unlikely(WARN_ON(!i2c_bus_type.p)))
    		return -EAGAIN;
    
    	/* add the driver to the list of i2c drivers in the driver core */
    	driver->driver.owner = owner;
    	driver->driver.bus = &i2c_bus_type;
    
    	
    	// 注册驱动程序,智力应该就会去执行probe 函数了
    	
    	/* When registration returns, the driver core
    	 * will have called probe() for all matching-but-unbound devices.
    	 */
    	res = driver_register(&driver->driver);
    
    	// 遍历这个驱动,执行__process_new_driver
    
    	INIT_LIST_HEAD(&driver->clients);
    	/* Walk the adapters that are already present */
    	i2c_for_each_dev(driver, __process_new_driver);
    
    	return 0;
    }
    

    probe

    综合来看这两个例子,probe里面就是创建字符设备驱动类似的操作,at24里面的比较复杂,但都是一些具体的操作

    __process_new_driver

    这个函数最终调用了i2c_do_add_adapter,也就是和注册适配器后的操作一致,挂接client,绑定client,driver,adapt

    static int __process_new_driver(struct device *dev, void *data)
    {
    	if (dev->type != &i2c_adapter_type)
    		return 0;
    	return i2c_do_add_adapter(data, to_i2c_adapter(dev));
    }
    

    这个函数理论上需要驱动实现detect,但是我在at24.c里面没有找到,在eeprom.c里面是有的,所以这个驱动应该是先有设备驱动和适配器,然后去手动构造client,这样就是另外的路线了

    i2c_do_add_adapter

    i2c_detect

    i2c_detect_address

    i2c_driver.detect

    接下去执行驱动probe

    这几个函数都和注册适配器的是一样的,回去看上面的就可以了

    构造设备驱动

    方式(一) APP>驱动

    参考内核文档 Documentationi2csmbus-protocol

    这里就是在i2c_driverprobe中注册我们真实的驱动程序,比如构造字符设备驱动程序,提供读写的接口即可

    EEPROM的普通读写,可以使用以下简单的函数,参考Documentationi2csmbus-protocol,smbusi2c的一个子集

    //读
    i2c_smbus_read_byte_data(at24cxx_client, addr)
    //写
    i2c_smbus_write_byte_data(at24cxx_client, addr, data)
    

    方式(二)使用i2c-dev

    使用通用的一个驱动程序i2c-dev来控制总线,去读写设备.其实我们可以看到内核源码目录下有个i2c-dev.c,就是这个文件,它类似一个通用的驱动.它实现了一个字符设备驱动,次设备号表示总线编号
    内核配置

    Device Drivers
    	 I2C support
    		<*>   I2C device interface
    
    //i2c_dev->adap->nr 指定的adapt
    static struct i2c_dev *i2c_dev_get_by_minor(unsigned index)
    {
    	struct i2c_dev *i2c_dev;
    	list_for_each_entry(i2c_dev, &i2c_dev_list, list) {
    		if (i2c_dev->adap->nr == index)
    			goto found;
    	}
    	i2c_dev = NULL;
    	return i2c_dev;
    }
    
    
    static const struct file_operations i2cdev_fops = {
    	.owner		= THIS_MODULE,
    	.llseek		= no_llseek,
    	.read		= i2cdev_read,
    	.write		= i2cdev_write,
    	.unlocked_ioctl	= i2cdev_ioctl,
    	.open		= i2cdev_open,
    	.release	= i2cdev_release,
    };
    

    可以看下源码,实际上最终也是调用类似的函数,注意这里面的i2cdev_read/i2cdev_write只是单纯时序上的读写,我们操作EE的读操作实际上是需要先读再写地址的,所以不能用这个接口,应该用i2cdev_ioctl

    i2c_get_functionality
    i2c_smbus_xfer
    

    这里可以参考

    Linux设备驱动开发详解第2版-宋宝华.pdf > 15.4.3 Linux 的 i2c-dev.c 文件分析

    关于设备驱动detect

    我们在分析注册设备驱动以及注册适配器驱动的时候,最终都会调用i2c_driver.detect,这个函数会设置具体的i2c_board_info.type,这个参数会在i2c_new_device赋值给client,用作i2c总线平台的匹配参数这会触发i2c_driverprobe.

    但是我们在刚开始做的设置client的前三种方法,i2c_driver并没有设置detect,也就是说在函数中不会执行到这个过程,我们从公共的入口i2c_detect来看下,加入调试打印看看实际有没有执行

    static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
    {
    	const unsigned short *address_list;
    	struct i2c_client *temp_client;
    	int i, err = 0;
    	int adap_id = i2c_adapter_id(adapter);
    
    	address_list = driver->address_list;
    	if (!driver->detect || !address_list)
        {
            // 在这里加入打印,看看实际驱动的detect 有没有默认值
            printk("no detect
    ");
            return 0;
        }
        else
        {
            //打印实际的detect
            printk("detect is %p 
    ",driver->detect);
        }
    
    
    	/* Stop here if the classes do not match */
    	if (!(adapter->class & driver->class))
    		return 0;
    
    	/* Set up a temporary client to help detect callback */
    	temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
    	if (!temp_client)
    		return -ENOMEM;
    	temp_client->adapter = adapter;
    
    	for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
    		dev_dbg(&adapter->dev, "found normal entry for adapter %d, "
    			"addr 0x%02x
    ", adap_id, address_list[i]);
    		temp_client->addr = address_list[i];
    		err = i2c_detect_address(temp_client, driver);
    		if (unlikely(err))
    			break;
    	}
    
    	kfree(temp_client);
    	return err;
    }
    
    

    测试下代码1th,运行结果如下

    /mnt/iic/1th # insmod at24cxx_drv.ko
    no detect
    /mnt/iic/1th # insmod at24cxx_dev.ko
    /home/book/stu/iic/1th/at24cxx_drv.c at24cxx_probe 13
    
    ------------------------------------------------------------
    /mnt/iic/1th # insmod  at24cxx_dev.ko
    /mnt/iic/1th # insmod at24cxx_drv.ko
    /home/book/stu/iic/1th/at24cxx_drv.c at24cxx_probe 13
    no detect
    

    可以看出来,不论是先加载dev还是先加载driver,都不会有detect,那为啥能正常工作执行probe呢?流程应该是这样的:

    • 我们的i2c_detect实际上只是将client挂接到dev链表上,i2c_client上的driver驱动的依附是在bus.probe=i2c_device_probe中实现的
    • devdriver匹配上就会执行驱动的probe,也就是执行打印

    所以:

    1. 先加载drv时,运行到i2c_detect时直接打印no detect后退出,然后加载dev后匹配执行驱动的probe
    2. 先加载dev时,没有匹配不动作,加载drv时,先匹配上了执行驱动的probe,再去执行i2c_detect,打印no detect

    补充:

    1. 可以看到i2c_detect最终目的是为了挂接client,而我们手动创建client使用了i2c_new_device,跳过了这个步骤,所以也能运行.
    2. 这里要区分两个步骤
      • 挂接clientdev 链表,使用i2c_new_device
      • 匹配clientdriver,使用的是bus.probe
    3. 也就是说如果我们不使用i2c_new_device来手动创建设备挂接到dev链表,就需要detect来辅助内核挂接client挂接

    系统信息查看

    我们可以通过查看文件信息来查看具体的i2c设备

    1. 可以在/sys/bus/i2c/下查看设备文件,这里看出来适配器驱动与client都属于device,这里的50是设备地址.0是总线序号

      /mnt/iic/1th # ls /sys/bus/i2c/
      devices            drivers_autoprobe  uevent
      drivers            drivers_probe
      /mnt/iic/1th # ls /sys/bus/i2c/devices/
      0-0050  i2c-0
      /mnt/iic/1th # ls /sys/bus/i2c/drivers/
      100ask  at24    dummy
      
    2. 查看已有的i2c总线

      #ls  /sys/class/i2c-adapter/
      i2c-0
      
    3. 创建设备节点的new_devicedeleted_device位置

      #ls  /sys/class/i2c-adapter/i2c-0/
      delete_device  name           power          uevent
      device         new_device     subsystem
      
    4. 这个文件夹实际的链接 /sys/devices/platform/s3c2440-i2c/i2c-0

      /sys/class/i2c-adapter/i2c-0   这是一个链接文件  /sys/devices/platform/s3c2440-i2c/i2c-0
      
    5. 查看我们的client

      /sys/devices/platform # ls
      alarmtimer        s3c2440-nand      s3c24xx_led.1     soc-audio
      power             s3c2440-uart.0    s3c24xx_led.2     uevent
      s3c2410-lcd       s3c2440-uart.1    s3c24xx_led.3     wm8976-codec
      s3c2410-ohci      s3c2440-uart.2    s3c24xx_wm8976.0
      s3c2410-wdt       s3c24xx-iis       samsung-audio
      s3c2440-i2c       s3c24xx_led.0     snd-soc-dummy
      
      /sys/devices/platform/s3c2440-i2c # ls
      driver     i2c-0      modalias   power      subsystem  uevent
      
      /sys/devices/platform/s3c2440-i2c/i2c-0 # ls
      0-0051         device         new_device     subsystem
      delete_device  name           power          uevent
      
      
      这里的 0-0051 实际上就是我们挂载的clinet设备了
      

    内核配置

    如果要使用i2c_dev.c这个通用的驱动,可以查看同目录下的Makefile,需要配置obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o

    ────────────────────────────────────────────────────── Search Results ──────────────────────────────────────────────────────┐
      │ Symbol: I2C_CHARDEV [=m]                                                                                                   │
      │ Type  : tristate                                                                                                           │
      │ Prompt: I2C device interface                                                                                               │
      │   Defined at drivers/i2c/Kconfig:39                                                                                        │
      │   Depends on: I2C [=y]                                                                                                     │
      │   Location:                                                                                                                │
      │     -> Device Drivers                                                                                                      │
      │       -> I2C support (I2C [=y])  
    

    关于适配器驱动,看下driversi2cussesMakefile

    obj-$(CONFIG_I2C_S3C2410)	+= i2c-s3c2410.o
    

    也就是找到配置,去除这个

    │ Symbol: I2C_S3C2410 [=y]                                                                                                   │
      │ Type  : tristate                                                                                                           │
      │ Prompt: S3C2410 I2C Driver                                                                                                 │
      │   Defined at drivers/i2c/busses/Kconfig:601                                                                                │
      │   Depends on: I2C [=y] && HAVE_S3C2410_I2C [=y]                                                                            │
      │   Location:                                                                                                                │
      │     -> Device Drivers                                                                                                      │
      │       -> I2C support (I2C [=y])                                                                                            │
      │         -> I2C Hardware Bus support  
    
  • 相关阅读:
    Dynamic attention in tensorflow
    Dynamic seq2seq in tensorflow
    Tensorflow Seq2seq attention decode解析
    zz图像卷积与滤波的一些知识点
    Android SDK更新失败对策
    高维数据降维 国家自然科学基金项目 2009-2013 NSFC Dimensionality Reduction
    近期深度学习论文汇总
    PHP远程连接mysql报错处理办法
    zz 启动Matlab提示Microsoft Visual C++ 2005 Redistributable存在问题问题
    `fw服务端非完整` 工程开发初期的工作
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10334610.html
Copyright © 2020-2023  润新知