你可以自由地随意修改本文档的任何文字内容及图表.docx
《你可以自由地随意修改本文档的任何文字内容及图表.docx》由会员分享,可在线阅读,更多相关《你可以自由地随意修改本文档的任何文字内容及图表.docx(72页珍藏版)》请在冰豆网上搜索。
你可以自由地随意修改本文档的任何文字内容及图表
声明
你可以自由地随意修改本文档的任何文字内容及图表,但是如果你在自己的文档中以任何形式直接引用了本文档的任何原有文字或图表并希望发布你的文档,那么你也得保证让所有得到你的文档的人同时享有你曾经享有过的权利。
i2c源代码情景分析(Beta2)
作者在上的ID为shrek2
欢迎补充,欢迎批评指正!
(注意:
本文档中的源代码以i2c-2.9.1包及www.arm.linux.org.uk上下载的pxa的i2c适配器的补丁2360-2为准!
)
第1章i2c核心数据结构之间的关系
i2c总线适配器(adapter)就是一条i2c总线的控制器,在物理连接上若干i2c设备并联于该i2c总线的SCL和SDA线上,如下图所示:
那么相应软件数据结构的设计、数据结构之间的关系就至少应该描述硬件物理连接的这种组织关系。
Linux的i2c框架中各个部分的关系如下图所示:
内核中i2c相关代码可以分为三个层次:
1.i2c框架:
i2c.h和i2c-core.c为i2c框架的主体,提供了核心数据结构的定义、i2c适配器驱动和设备驱动的注册、注销管理,i2c通信方法上层的、与具体适配器无关的代码、检测设备地址的上层代码等;i2c-dev.c用于创建i2c适配器的/dev/i2c/%d设备节点,提供i2c设备访问方法等。
2.i2c总线适配器驱动:
定义描述具体i2c总线适配器的i2c_adapter数据结构、实现在具体i2c适配器上的i2c总线通信方法,并由i2c_algorithm数据结构进行描述。
3.i2c设备驱动:
定义描述具体设备的i2c_client和可能的私有数据结构、借助i2c框架的i2c_probe函数实现注册设备的attach_adapter方法、提供设备可能使用的地址范围、以及设备地址检测成功后创建i2c_client数据结构的回调函数。
下面介绍i2c各核心数据结构的定义和它们之间的连接关系。
1.一个i2c设备的驱动程序由i2c_driver数据结构描述,定义于include/linux/i2c.h:
structi2c_driver{
charname[32];
intid;
unsignedintflags;
int(*attach_adapter)(structi2c_adapter*);
int(*detach_client)(structi2c_client*);
int(*command)(structi2c_client*client,unsignedintcmd,void*arg);
void(*inc_use)(structi2c_client*client);
void(*dec_use)(structi2c_client*client);
};
其中name为最大长度为32字节的字符串,id可选0xf000到0xffff中的任一数值,flags域可以直接设置为I2C_DF_NOTIFY。
attach_adapter回调函数在安装i2c设备驱动程序模块时、或者在安装i2c适配器驱动程序模块时被调用,用于检测、认领设备并为设备分配i2c_client数据结构。
detach_client方法在卸载适配器或设备驱动程序模块时被调用,用于从总线上注销设备、并释放i2c_client及相应的私有数据结构。
inc_use和dec_use所指向的函数用于改变i2c设备驱动程序模块的引用计数。
注意不要直接调用i2c_driver数据结构中的这两个方法,而要通过如下函数调用路径:
i2c_use_client>i2c_inc_use_client>inc_use
i2c_release_client>i2c_dec_use_client>dec_use
通过最顶层的i2c_use/release_client函数来同时改变i2c设备和i2c适配器驱动程序模块的引用计数。
另外,不能在attach_adapter函数检测到一个i2c设备时就增加驱动程序模块的引用计数,而应该在用户进程访问一个/dev/i2c/%d设备节点时增加模块的引用计数,则关闭设备节点时减少引用计数(但在当前的应用中,适配器和设备的驱动程序都是静态地链接入内核映像的,所以在pxa255的i2c补丁中并没有使用控制引用计数的函数)。
2.一个i2c设备由i2c_client数据结构进行描述:
structi2c_client{
charname[32];
intid;
unsignedintflags;/*div.,seebelow*/
unsignedintaddr;/*chipaddress-NOTE:
7bitaddressesarestoredinthe*/
/*_LOWER_7bitsofthischar*/
structi2c_adapter*adapter;/*theadapterwesiton*/
structi2c_driver*driver;/*andouraccessroutines*/
void*data;/*fortheclients*/
intusage_count;/*Howmanyaccessescurrentlytotheclient*/
};
在安装适配器或者设备的驱动程序时通过设备驱动程序i2c_driver中的attach_adapter函数检测设备地址。
如果检测成功则调用设备驱动程序提供的回调函数创建描述设备的i2c_client数据结构,并将其中的driver指针指向设备驱动程序的i2c_driver数据结构。
这样将来就可以使用i2c_driver中的注销设备和控制引用计数的方法了。
由下文可见在描述i2c适配器的i2c_adapter数据结构中设计了指向该总线上所有i2c设备的i2c_client数据结构的指针数组clients,而每个i2c_client又通过adapter指针回指i2c_adapter。
数据结构之间类似的组织关系在Linux内核中屡见不鲜,比如父子进程的PCB之间、父目录及子目录和子文件的dentry之间,等等。
每个i2c设备都有唯一的7位地址addr。
由于设备可能支持多个地址,所以在设备驱动程序模块中要指出需要检测的地址范围(由i2c_client_address_data二维数组指定),而设备实际使用的地址在检测成功并为之分配i2c_client数据结构时填入。
以i2c设备ltc3445为例,硬件支持的地址为1001011或者0101011,即7位地址的高2位由具体的布线方法决定(可以分别接到VCC或者GND)。
如果ltc3445驱动程序的开发者知道具体的布线方法,那么在驱动程序中就可以直接指定。
否则可以指定地址检测范围为这两个地址,而在加载驱动程序模块时由软件进行地址检测。
需要说明的是,i2c设备的7位地址是就当前i2c总线而言的,是“相对地址”。
不同的i2c总线上的设备可以使用相同的7位地址,但是它们所在的i2c总线不同。
所以在系统中一个i2c设备的“绝对地址”由二元组(i2c适配器的ID和设备在该总线上的7位地址)表示。
i2c_client数据结构为描述i2c设备的“模板”,而具体的i2c设备可能需要描述个性的私有数据。
私有数据结构由i2c_client数据结构中的data域指向。
设备驱动程序开发者可以设计合适的私有数据结构来描述硬件的特性。
值得一提的是,目前在Linux内核中常用的表示与具体设备、对象等相关的私有数据结构的方法有两种,一种就是采用void类型的指针data来指向具体的私有数据结构,又比如file结构中的private_data域在设备驱动程序中往往被设置为指向具体的设备数据结构;第二种方法就是采用union域,比如VFS的super_block、inode数据结构。
super_block和inode数据结构本身集中描述了各种文件系统的共性,而具体文件系统的个性则放到union中进行描述,在挂载具体的文件系统时实例化为具体的union对象,比如ext2_inode_union或者jffs2_inode_info。
有关设计私有数据的的讨论可参见本文末尾的讨论部分。
当不同进程访问同一i2c总线时,对i2c总线的互斥访问由i2c_adapter的lock信号量实现,系统调用执行流只有在获得该信号量期间才能调用master_xfer,并且在阻塞期间不释放信号量(类似在读写正规文件期间必须持有inode.i_sem,参见本文末尾的讨论部分)。
而usage_count域为设备的使用引用计数,在i2c_use_client和i2c_release_client函数中控制usage_count域的值(但是当前pxa255的i2c补丁中并没有使用这两个函数,usage_count的值自初始化后就一直为0)。
3.一个i2c适配器由i2c_adapter数据结构描述:
structi2c_adapter{
charname[32];
unsignedintid;/*==isalgo->id|hwdep.struct->id,forregisteredvaluesseebelow*/
structi2c_algorithm*algo;/*thealgorithmtoaccessthebus*/
void*algo_data;
void(*inc_use)(structi2c_adapter*);
void(*dec_use)(structi2c_adapter*);
int(*client_register)(structi2c_client*);
int(*client_unregister)(structi2c_client*);
void*data;/*privatedatafortheadapter*/
structsemaphorelock;
unsignedintflags;/*flagsspecifyingdiv.data*/
structi2c_client*clients[I2C_CLIENT_MAX];
intclient_count;
inttimeout;
intretries;
#ifdefCONFIG_PROC_FS
/*Noneedtosetthiswhenyouinitializetheadapter*/
intinode;
#ifLINUX_VERSION_CODEstructproc_dir_entry*proc_entry;
#endif
#endif/*defCONFIG_PROC_FS*/
};
在i2c_adapter数据结构中设计了clients指针数组,指向该总线上每个设备的i2c_client数据结构。
由于一条i2c总线上最多只有I2C_CLENT_MAX个设备,所以可以使用静态数组(题外话,如果相关数据结构的个数是未知的,链表显然是更好的选择)。
lock信号量用于实现对i2c总线的互斥访问:
在访问i2c总线上的任一设备期间当前进程必须首先获得该信号量,并且在阻塞等待i2c操作完成期间不释放。
一个i2c适配器上的i2c总线通信方法由其驱动程序提供的i2c_algorithm数据结构描述,由algo指针指向。
i2c_algorithm数据结构即为i2c_adapter数据结构与具体i2c适配器的总线通信方法的中间层,由下文可见正是这个中间层使得上层的i2c框架代码与与具体i2c适配器的总线通信方法无关,从而实现了i2c框架的可移植性和重用性。
当安装具体i2c适配器的驱动程序时由相应驱动程序实现具体的i2c_algorithm数据结构,其中的函数指针指向操作具体i2c适配器的代码(换用面向对象的语言,就是当创建子类对象时将基类中定义的函数调用接口实例化为与具体子类相关的代码。
值得说明的是,在Linux内核层次中数据结构的设计大量地采用了面向对象的概念来实现框架的可移植性和重用性)。
inc_use和dec_use方法可以用来控制适配器驱动程序的引用计数;client_register和client_unregister函数可以用来完成适配器端的、额外的设备注册和注销工作。
这些函数在当前pxa255的i2c补丁中都没有实现。
最后timeout和retries用于超时重传机制。
4.具体i2c适配器的通信方法由i2c_algorithm数据结构进行描述:
structi2c_algorithm{
charname[32];/*textualdescription*/
unsignedintid;
int(*master_xfer)(structi2c_adapter*adap,structi2c_msgmsgs[],intnum);
int(*smbus_xfer)(structi2c_adapter*adap,u16addr,
unsignedshortflags,charread_write,
u8command,intsize,unioni2c_smbus_data*data);
int(*slave_send)(structi2c_adapter*,char*,int);
int(*slave_recv)(structi2c_adapter*,char*,int);
int(*algo_control)(structi2c_adapter*,unsignedint,unsignedlong);
u32(*functionality)(structi2c_adapter*);
};
master_xfer/smbus_xfer指针指向i2c适配器驱动程序模块实现的i2c通信协议或者smbus通信协议。
由下文分析可见在用户进程通过i2c-dev提供的/dev/i2c/%d设备节点访问i2c设备时,最终是通过调用master_xfer或者smbus_xfer指向的方法完成的。
slave_send/recv函数用于实现当i2c适配器扮演slave角色时的传输方法。
由于在pxa255的现有应用中其i2c适配器始终为主导i2c通信的master,故补丁中这两个函数都没有实现。
i2c_algorithm提供了i2c适配器的驱动,而i2c设备的驱动为i2c_driver。
内核中静态指针数组adapters和drivers分别记录所有已经注册的i2c总线设备和i2c设备驱动。
从下文源代码分析可以看到,安装i2c总线驱动和i2c设备驱动的顺序不确定,因此在安装i2c设备驱动时必须遍历所有已注册的适配器上的i2c设备,以“认领”相应的设备;同理,在安装i2c适配器驱动时必须遍历所有已注册的i2c设备的驱动程序,让已有驱动程序“认领”新注册的适配器上的所有设备。
5.假设一条i2c总线上有两个使用相同驱动程序的i2c设备,在打开该i2c总线的设备结点后相关数据结构之间的逻辑组织关系如下图所示。
在阅读下文时请经常参照下图。
上层的i2c框架实现了控制策略,具体i2c适配器和设备的驱动实现了使具体设备可用的机制,上层策略和底层机制通过中间的函数调用接口联系。
正是中间的函数调用接口使得上层策略与底层机制无关,从而使得上层策略具有良好的可移植性和重用性。
阅读完全文后可以回过头来总结一下各数据结构的作用、创建时机、由谁创建等,品味这一点是如何通过这些数据结构实现的,进一步在自己的研发过程中积极实践这种思想并享受学以致用的乐趣:
)
第2章i2c-core.c的初始化
i2c-core.c提供了i2c框架的主体代码,包括i2c适配器驱动和设备驱动的注册、注销管理的框架代码、i2c通信方法的上层、与具体适配器无关的代码和检测设备地址的上层代码等。
i2c_init函数
i2c-core模块的初始化函数如下:
staticint__initi2c_init(void)
{
printk(KERN_DEBUG"i2c-core.o:
i2ccoremodule\n");
memset(adapters,0,sizeof(adapters));
memset(drivers,0,sizeof(drivers));
adap_count=0;
driver_count=0;
init_MUTEX(&adap_lock);
init_MUTEX(&driver_lock);
在i2c-core.c中定义了内核静态指针数组adapters和drivers,用于注册描述i2c适配器及其驱动程序的i2c_adapter数据结构和描述设备及其驱动程序的i2c_driver数据结构。
同时也定义了保护这两个全局指针数组的信号量:
staticstructi2c_adapter*adapters[I2C_ADAP_MAX];
staticstructi2c_driver*drivers[I2C_DRIVER_MAX];
structsemaphoreadap_lock;
structsemaphoredriver_lock;
其中表示适配器和驱动程序最大数量的宏在linux/i2c.h中均被定义为16。
由于安装i2c适配器驱动程序模块和i2c设备驱动程序模块的先后顺序不确定,所以必须通过全局数据结构(比如这里的指针数组或者链表等)来保存所有已安装的数据结构的地址,这样在后安装i2c适配器驱动程序时就可将drivers指针数组中记录的地址传递给i2c_add_adapter函数;在后安装i2c设备驱动程序时就可将adapters指针数组中记录的地址传递给i2c_add_driver函数(而这两个函数最终都是通过i2c_driver.的attach_adapter函数“认领”设备的)。
这里首先初始化adapters和drivers指针数组及相关信号量adap_lock和driver_lock,并将计数器清0。
i2cproc_init();
return0;
}
然后通过i2cproc_init函数创建相应的/proc/bus/i2c文件,使得用户进程可以通过该文件得到当前系统上所有已注册的i2c总线信息。
i2cproc_init函数
该函数用于创建描述系统中所有i2c总线的/proc/bus/i2c文件。
inti2cproc_init(void)
{
structproc_dir_entry*proc_bus_i2c;
i2cproc_initialized=0;
if(!
proc_bus){
printk("i2c-core.o:
/proc/bus/doesnotexist");
i2cproc_cleanup();
return-ENOENT;
}
在i2c-core.c文件中定义了表示proc接口初始化程度的静态变量i2cproc_initialized,其初始值为0。
内核全局变量proc_bus定义于fs/proc/root.c,声明于include/linux/proc_fs.h,在fs/proc/root.c的proc_root_init函数中初始化:
structproc_dir_entry*proc_net,*proc_bus,……
void__initproc_root_init(void)
{
interr=register_filesystem(&proc_fs_type);
if(err)
return;
proc_mnt=kern_mount(&proc_fs_type);
err=PTR_ERR(proc_mnt);
if(IS_ERR(proc_mnt)){
unregister_filesystem(&proc_fs_type);
return;
}
……
proc_bus=proc_mkdir("bus",0);
}
可见在proc_root_init函数中注册、挂载proc文件系统,而proc_bus指向代表“/proc/bus”目录的proc_dir_entry类型的变量。
在确保proc_bus不为NULL后,接下来通过create_proc_entry函数在/proc/bus下创建文件/proc/bus/i2c,相应的读回调函数为read_bus_i2c。
该函数很简单,就是遍历内核静态数组adapters,向用户空间返回所有已注册的i2c适配器的信息。
在此不再赘述,可参见内核文档Documentation/i2c/proc-interface。
函数的最后再增加i2cproc_initialized计数值。
proc_bus_i2c=create_proc_entry("i2c",/*name*/
0,/*mode*/
proc_bus);/*parent*/
if(!
proc_bus_i2c){
printk("i2c-core.o:
Couldnotcreate/proc/bus/i2c");
i2cproc_cleanup();
return-ENOENT;
}
proc_bus_i2c->read_proc=&read_bus_i2c;
#if(LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,27))
proc_bus_i2c->owner=THIS_MODULE;
#else
proc_bus_i2c->fill_inode=&monitor_bus_i2c;
#endif/*(LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,27))*/
i2cproc_initialized+=2;
return0;
}
第3章安装、卸载pxa255的i2c适配器驱动程序
pxa_i2c数据结构
linux/i2c.h中定义的i2c_adapter数据结构为描述各种i2c适配器的通用“模板”,它定义了注册总线上所有设备的clients指针数组、指向具体i2c适配器的总线通信方法i2c_algorithm的algo指针、实现i2c总线操作原子性的lock信号量等设施。
但在适配器的驱动程序中可以根据具体适配器的需要“扩充”该数据结构。
在pxa255的i2c适配器驱动程序补丁中通过pxa_i2c数据结构来描述pxa255的i2c适配器:
structpxa_i2c{
spinlock_tlock;
wait_queue_head_twait;
structi2c_msg*msg;
u