LINUX I2C设备驱动模型分析之五 I2C 通用字符设备及i2c模块通信接口分析

       前面几章基本上完成了i2c 设备、驱动、总线等几个模块的分析,本章我们主要说下i2c通信接口以及i2c 字符设备(准确的说是注册到i2c总线上的adapter设备对应的字符设备)。

本篇内容主要包括:

  1. i2c通信接口
  2. i2c 字符设备相关内容

 

i2c通信接口

在之前的分析中,我们也知道cpu需要通过i2c adapter的方法与i2c client进行通信。而i2c模块也为此抽象出了cpu与i2c client通信的接口,分别为i2c_transfer、i2c_smbus_xfer接口,这两个接口分别对应了i2c、smbus的通信接口,而这两个接口也就是分别调用i2c_adapter->master_xfer、i2c_adapter->smbus_xfer。

 

针对这两个接口,数据通信的传参是通过类型为i2c_msg的结构体变量进行传递的,下面我们先分析下这个结构体。

struct i2c_msg

该结构体的定义如下,主要包括通信的i2c client的地址、本次通信类型、通信数据以及大小等。

	struct i2c_msg {
	    /*通信的i2c client的地址*/
		__u16 addr;	/* slave address			*/
	    /*通信类型选择(读、写等)*/
		__u16 flags;
	#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
	#define I2C_M_RD		0x0001	/* read data, from slave to master */
	#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
	#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
	    /*消息长度*/
		__u16 len;		/* msg length				*/
	    /*读写的数据存储内存*/
		__u8 *buf;		/* pointer to msg data			*/
};

i2c_transfer

       该接口在对i2c_adapter的变量进行合法性检查以及加锁后,则调用i2c_adapter->master_xfer,由具体的i2c_adapter的通信方法完成通信操作。实现流程相对来说也比较清新,如下为该接口的流程图

i2c_smbus_xfer

该接口的实现与i2c_transfer类似,唯一的区别为若adapter不支持smbus_xfer时,则调用adapter->master_xfer接口,通过i2c模拟smbus进行通信。此处不再给出流程图,由读者自行分析。

 

借助于i2c_transfer、i2c_smbus_xfer这两个接口与具体的i2c_adapter,我们可以在内核层实现与具体的i2c_adapter下所有的i2c client进行通信。

 

i2c 字符设备相关内容

在上面的分析中,我们知道借助i2c_transfer、i2c_smbus_xfer、一个实例化的i2c_adapter类

型的变量,我们可以与该实例化的i2c_adapter类型变量下所有i2c client的通信。若基于这种方式与i2c client进行通信,那就可以不对具体的i2c client开发具体的驱动程序,即可与该i2c client进行通信了。

基于这种思想,i2c模块为每一个注册到i2c 总线的adapter,均创建了对应的字符设备文件,

并为该设备文件提供了读写方法(供系统调用open、read、write、close、ioctl调用)。这样应用程序通过对具体的字符设备文件进行读写操作,即可完成与i2c client的通信(而无需为该i2c client编写内核驱动程序,仅需在应用层为该i2c client开发操作程序即可)。

 

i2c字符设备的创建

针对你i2c 字符设备的创建,主要包括两个部分:

  1. 通过i2c_dev_init接口,注册一个主设备号为I2C_MAJOR,次设备号为0的字符设备,但此时尚未为该字符设备创建设备文件。
  2. 在i2c adapter的实例化对象注册到i2c总线时,借助i2c总线的BUS_NOTIFY_ADD_DEVICE通知事件,通过i2cdev_attach_adapter接口,完成具体i2c adapter对应设备文件节点的创建(通过借助kobject ADD的uevent,以及应用层的udevd/mdev,实现设备节点的创建)。

下面我们从这两方面进行分析。

 

i2c_dev_init

该接口为i2c dev模块的初始化接口,该接口实现的功能为注册主设备号为I2C_MAJOR,次设备号为0的字符设备,并为i2c-dev创建名为“i2c-dev”的类,所有通过i2c-dev创建的字符设备对应的device类型的变量,均属于该类。

该接口的流程图如下,主要实现功能如下:

  1. 调用register_chrdev向字符设备模块注册主设备号为I2C_MAJOR、次设备号为0,且ops为i2cdev_fops的字符设备(此时并没有创建对应的字符设备文件节点)
  2. 调用class_create接口,创建名为“i2c-dev”的类,所有已注册的i2c_adapter类型变量对应的用于创建i2c 字符设备文件inode的device类型的变量,都属于该类。
  3. 向i2c总线的通知链中注册i2c-dev对应的事件处理接口i2c_notifier,该接口主要处理BUS_NOTIFY_ADD_DEVICE/BUS_NOTIFY_DEL_DEVICE类型的事件(LINUX内核的通知链机制,就是一个事件分发与订阅机制,针对某一个通知链,内核子系统可以订阅其关注的事件类型)。
  4. 针对每一个已注册到i2c总线上的i2c_adapter类型的变量,均调用i2cdev_attach_adapter接口,为其创建类型为device的dev变量,该变量的作用即为通过向应用层发送KOBJCT_ADD类型的uevent,由应用层程序udevd/mdev接收后,然后创建字符设备文件节点,而创建的字符设备名称为i2c-adapter_index。i2cdev_attach_adapter接口我们在后面进行介绍。

i2cdev_attach_adapter/i2cdev_detach_adapter接口分析

      这两个接口是被函数i2cdev_notifier_call调用,而i2cdev_notifier_call则为注册到通知链i2c_bus_type->p->bus_notifier的处理接口,当i2c注册及注销时,通过类型为BUS_NOTIFY_ADD_DEVICE、BUS_NOTIFY_DEL_DEVICE的通知事件,确认调用哪一个接口实现i2c通用字符设备的创建或删除。该两个接口分别实现i2c adapter对应的字符设备文件节点的创建与删除(主设备号为I2C_MAJOR,次设备号为adapter->nr)。这两个节点均没有调用register_chrdev接口,向字符设备模块注册对应的字符设备,那它们是如何实现创建或删除字符设备文件节点的呢?这里面主要涉及两个知识点:

  1. 在i2c_dev_init接口中已经注册了一个主设备号为I2C_MAJOR、次设备号为0的字符设备。这样在通过系统调用sys_open打开一个i2c-dev时,字符设备模块通过主设备号I2C_MAJOR,来查找注册到字符设备模块上的字符设备,若查找到就将该字符设备注册时传递的ops变量赋值给文件描述符file的f_op指针,而系统调用open、read、write、close就可以根据该f_op中的函数指针执行后续的操作(open、read、write、close、ioctl等),因此,针对该主设备号为I2C_MAJOR的其他字符设备,可以不用注册到字符设备模块,仅需要完成字符设备文件节点的创建即可,

而在i2c-dev中注册到字符设备模块的ops为i2cdev_ops,在i2cdev_ops中再根据次设备号查找具体的adapter。

  1. 在i2c-dev中,针对每一个注册的adapter,均为其创建一个类型的device的变量,该变量的父设备为该adapter对应的device类型的变量,且该device类型的变量属于i2c_dev_class类。并且借助该device类型变量对应的KOBJECT的KOBJECT_ADD类型的事件,向应用层发布KOBJECT_ADD类型的事件信息,应用程序(udevd、mdev)判断该device类型的变量的成员变量devt不为0,知道其为一个字符设备或块设备,因此调用mknod系统调用,完成设备文件节点的创建。

通过以上两步完成字符设备节点文件的创建以及在字符设备模块完成了一个针对主设备号I2C_MAJOR的注册,并注册的处理接口指针为i2cdev_ops。从而应用层程序即可访问该字符设备文件。

 

而i2c-dev模块为i2c-dev对应的字符设备创建了对应的结构体 i2c_dev,我们看下该结构体的定义:

  1. 该结构体可以理解为类型device结构体的子类(因此可以借助LINUX设备-总线-驱动模型中的device_register接口,并与sysfs文件系统、kobject实现关联,当然此处的i2c-dev并没有绑定到具体的总线时,其仅需要创建字符设备文件节点以及在sysfs中创建对应的设备文件,并归属到i2c_dev_class类);
  2. 指向i2c_adapter类型的实例化对象的指针;
  3. 链表变量用于链接至i2c_dev_list中。
	/*该结构体描述一个i2c adapter对应的字符设备节点变量*/
	struct i2c_dev {
	    /*将所有adapter对应的i2c dev链接至系统链表i2c_dev_list中,以便进行查找
	    (i2cdev_open中会执行这种查找)*/
		struct list_head list;
	    /*该字符设备对应的adapter*/
		struct i2c_adapter *adap;
	    /*该设备对应的device类型的变量*/
		struct device *dev;
};

i2cdev_attach_adapter接口分析

而i2cdev_attach_adapter即实现上述b中所述的内容,我们下面看下这个接口的实现流程图,其实现的功能在上面已经说明,即:

  1. 创建i2c-dev类型的变量,并完成设置操作,同时将该i2c-dev变量加入到链表i2c_dev_list中。
  2. 调用device_create,创建device类型的变量,同时将其注册到设备驱动模型中的dev_kset子系统下(因未设置其需要注册的bus,不需要注册到具体总线上,也不需要进行设备与驱动的绑定操作),并为该设备文件在sysfs文件系统下创建对应的文件,同时向应用层发送KOBJECT_ADD的事件,由应用层程序接收并完成字符设备文件节点的创建工作。

i2cdev_detach_adapter接口分析

该接口实现的功能与i2cdev_attach_adapter刚好相反,具体实现代码如下,此处不再细说。

	static int i2cdev_detach_adapter(struct device *dev, void *dummy)
	{
		struct i2c_adapter *adap;
		struct i2c_dev *i2c_dev;
	
		if (dev->type != &i2c_adapter_type)
			return 0;
		adap = to_i2c_adapter(dev);
	
		i2c_dev = i2c_dev_get_by_minor(adap->nr);
		if (!i2c_dev) /* attach_adapter must have failed */
			return 0;
	
		device_remove_file(i2c_dev->dev, &dev_attr_name);
		return_i2c_dev(i2c_dev);
		device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
	
		pr_debug("i2c-dev: adapter [%s] unregistered\n", adap->name);
		return 0;
}

      这两个函数什么时候调用呢,在前面我们说了i2c-dev向i2c总线的通知链中注册了事件处理接口i2cdev_notifier_call,该接口即实现了对上述两个函数的调用。

        当调用i2c_register_adapter、i2c_del_adapter(内部调用LINUX设备-总线-驱动模型的device_register、device_unregister)时,i2总线的通知链即会发生BUS_NOTIFY_ADD_DEVICE、BUS_NOTIFY_DEL_DEVICE的事件,从而i2cdev_notifier_call即会进行i2cdev_attach_adapter、i2cdev_detach_adapter的调用,完成adapter对应字符设备文件节点的创建与删除操作。

 

	static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
				 void *data)
	{
		struct device *dev = data;
	
		switch (action) {
		case BUS_NOTIFY_ADD_DEVICE:
			return i2cdev_attach_adapter(dev, NULL);
		case BUS_NOTIFY_DEL_DEVICE:
			return i2cdev_detach_adapter(dev, NULL);
		}
	
		return 0;
	}

i2c-dev字符设备文件操作接口

主要实现了open、read、write、close、ioctl等系统调用接口的调用,针对sys_open操作最终调用到i2cdev_open接口的流程图如下所示(向了解do_sys_open具体实现的,请参考我之前写的LINUX VFS的内容,此处不再展开这部分的内容)。

 

 

i2c-dev模块的字符设备文件操作接口的定义如下,此处我们仅分析i2cdev_open接口,而针对i2cdev_read、i2cdev_write、i2cdev_ioctl接口而言,也就是通过调用i2c_transfer、i2c_smbus_xfer接口,完成与具体i2c client的通信。

 

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_open接口

该接口的作用是根据打开的字符设备文件节点,查找对应的adapter,并创建i2c_client类型的变量,并完成与adapter的关联,最后将i2c_client变量赋值给文件描述符的私有变量,供read/write/ioctl接口使用,read/write/ioctl接口通过i2c_client、i2c_adapter、i2c_transfer、i2c_smbus_xfer完成与具体的i2c 设备的通信操作。

 

 

       以上就是本章的内容,我认为i2c模块的提供的基于adapter的字符设备文件的作用还是很大的,对于那些仅需要获取状态的简单功能的i2c设备,可以直接使用该字符设备进行访问,无需为这类设备写内核驱动。

发布了140 篇原创文章 · 获赞 30 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/lickylin/article/details/103226154
今日推荐