关于设备模型、设备与驱动关联的全过程分析
本文的大多数内容参考了:对于网络上设备与驱动关联的全过程分析(I2C方式)一文。在此特别感谢这位作者前辈的无私奉献。本人只是添加了一些个人理解和补充。
在Linux操作系统中,驱动程序的加载分为两种:内核启动时自动加载和用户手动加载;硬件设备也可以采用两种方式添加到系统中:在系统启动前及系统运行时的热插拨。下面,我们以arm体系结构下的at91处理器中的I2C控制器为例,介绍一下硬件设备及相关的驱动程序是如何绑定及松绑的。
1.平台驱动注册过程
具体的目录如下:
关于设备模型、设备与驱动关联的全过程分析。
1.1at91_i2c_init()函数
1.2platform_driver_register()函数
1.3driver_register()函数
1.4bus_add_driver()函数
1.5dd.c文件driver_attach()函数
1.1at91_i2c_init()函数
在文件drivers/i2c/busses/i2c-at91.c中,定义了结构体structplatform_driver并进行了初始化,通过使用module_init()宏进行声明,当模块被加载到内核时会调用at91_i2c_init()函数。在此函数中,调用了platform_driver_register()函数来完成注册。
staticstructplatform_driverat91_i2c_driver={
.probe=at91_i2c_probe,
.remove=__devexit_p(at91_i2c_remove),
.suspend=at91_i2c_suspend,
.resume=at91_i2c_resume,
.driver={
.name
="at91_i2c",
.owner
=THIS_MODULE,
},
};
staticint__initat91_i2c_init(void)
{
returnplatform_driver_register(&at91_i2c_driver);
}
1.2platform_driver_register()函数
在文件drivers/base/platform.c中,实现并导出了platform_driver_register()函数,以便使其他模块中的函数可以调用此函数。它在完成简单的包装后,调用了driver_register()函数,完成了从平台实现到Linux内核实现的过渡。
intplatform_driver_register(structplatform_driver*drv)
{
/*设置成platform_bus_type这个很重要,因为driver和device是通过bus联系在一起的,具体在本例中是通过 platform_bus_type中注册的回调例程和属性来是实现的,driver与device的匹配就是通过platform_bus_type注册的回到例程platform_match ()来完成的。*/ drv->driver.bus=&platform_bus_type;
//在really_probe函数中,回调了platform_drv_probe函数
if(drv->probe)
drv->driver.probe=platform_drv_probe;
if(drv->remove)
drv->driver.remove=platform_drv_remove;
if(drv->shutdown)
drv->driver.shutdown=platform_drv_shutdown;
if(drv->suspend)
drv->driver.suspend=platform_drv_suspend;
if(drv->resume)
drv->driver.resume=platform_drv_resume;
returndriver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_register);
在platform_driver_register()函数中,对总线附值使用了如下语句,给总线类型和相关操作函数赋值。drv->driver.bus=&platform_bus_type;
structbus_typeplatform_bus_type={
.name="platform",
.dev_attrs=platform_dev_attrs,
.match=platform_match,
.uevent=platform_uevent,
.suspend=platform_suspend,
.suspend_late=platform_suspend_late,
.resume_early=platform_resume_early,
.resume=platform_resume,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
总线bus是联系driver和device的中间枢纽。Device通过所属的bus找到driver.
structbus_type{
constchar*name;
structsubsystemsubsys;
structksetdrivers;//drivers-〉list链表包含了所有注册到该bus的driver.
structksetdevices;//devices->list链表包含了所有注册到该bus的devices.
structklistklist_devices;//
structklistklist_drivers;
structblocking_notifier_headbus_notifier;
structbus_attribute*bus_attrs;
structdevice_attribute*dev_attrs;
structdriver_attribute*drv_attrs;
int(*match)(structdevice*dev,structdevice_driver*drv);
int(*uevent)(structdevice*dev,char**envp,
intnum_envp,char*buffer,intbuffer_size);
int(*probe)(structdevice*dev);
int(*remove)(structdevice*dev);
void(*shutdown)(structdevice*dev);
int(*suspend)(structdevice*dev,pm_message_tstate);
int(*suspend_late)(structdevice*dev,pm_message_tstate);
int(*resume_early)(structdevice*dev);
int(*resume)(structdevice*dev);
};
在此,我们需要关注一下platform_match()和platform_drv_probe()函数。platform_match()函数确定驱动与设备的关联,而platform_drv_probe()函数会在随后介绍的函数中被调用。
//比较驱动信息中的name与设备信息中的name两者是否一致
staticintplatform_match(structdevice*dev,structdevice_driver*drv)
{
structplatform_device*pdev=container_of(dev,structplatform_device,dev);
//通过device找到他所属的platform_device
return(strncmp(pdev->name,drv->name,BUS_ID_SIZE)==0);
}
linuxkernel源码中有一个神奇的container_of宏,可以根据一个结构体中成员的地址计算出结构体自身的地址。
完全可以这样理解container_of宏:
#definecontainer_of(ptr,type,member)(type*)((char*)ptr-offset_of(type,member))
对于实现匹配关系的设备和驱动应有如下关系:
platform_device-〉device:该device通过链表连接到BUS上.
platform_driver-〉driver(device_driver类型的结构体):该device通过链表连接到BUS上.
platform_device-〉device->device_driver(指针),该指针指向了platform_driver-〉driver。
这样platform_device、platform_driver就通过platform_device-〉device联系在一起了。
因此我们可以通过platform_device-〉device找到对应的platform_device和platform_driver。
这样就很容易理解下面的platform_drv_probe函数了:
staticintplatform_drv_probe(structdevice*_dev)
{
structplatform_driver*drv=to_platform_driver(_dev->driver);
structplatform_device*dev=to_platform_device(_dev);
returndrv->probe(dev);//转去执行platform_driver中定义的probe函数。
}。
对于挂到总线上的设备、驱动是通过:kset、kobject建立各个层次之间的联系。对于kset、kobject。可以参考DDR3设备管理的章节。
至此对设备(device)、总线(bus)、驱动(device_driver)的关系有了一个大概的了解。
对于2.6内核中更上面一层的封装:platform_device、platform_driver也有了大概的了解。
1.3driver_register()函数
在文件drivers/base/driver.c中,实现了driver_register()函数。在此函数中,初始化结构体structdevice_driver中的klist_device和unloaded字段,通过klist_device字段,可以保存此驱动支持的设备链表,通过“完成”接口机制,完成线程间的同步。链表和“完成”接口的详细信息可以参考文献[1]。返回bus_add_driver()函数的运行结果。
/***driver_register-registerdriverwithbus*
@drv:drivertoregister*
Wepassoffmostoftheworktothebus_add_driver()call,*
sincemostofthethingswehavetododealwiththebusstructures.
*Theoneinterestingaspectisthatwesetupdrv->unloaded*
asacompletionthatgetscompletewhenthedriverreferencecountreaches0.*/
intdriver_register(structdevice_driver*drv)
{
//如果总线的方法和设备自己的方法同时存在,将打印告警信息
if((drv->bus->probe&&drv->probe)||(drv->bus->remove&&drv->remove)||(drv->bus->shutdown&&drv->shutdown))
{
printk(KERN_WARNING"Driver'%s'needsupdating-pleaseusebus_typemethods\n",drv->name);
}
klist_init(&drv->klist_devices,NULL,NULL);//将driver驱动上的设备链表清空
init_completion(&drv->unloaded);
returnbus_add_driver(drv);// 将本driver驱动注册登记到drv->bus所在的总线上。
在内核中以driver成员变量kobject,表示driver.
所谓的注册即是:driver中的内核成员对象kobject的链表 (kobj->entry),加入到bus总线的 bus_type ->kset->list链表中。这样就可以通过总线,找到所有定义在该总线下的kobject(driver).
}
1.4bus_add_driver()函数
在文件drivers/base/bus.c中实现了bus_add_driver()函数,它通过语句klist_add_tail(&drv->knode_bus,&bus->klist_drivers);将驱动信息保存到总线结构中,在设备注册过程中,我们就可以明白此语句的作用了。在此语句之前,调用了driver_attach()函数。
/***bus_add_driver-Addadrivertothebus.*@drv:driver.**/
intbus_add_driver(structdevice_driver*drv)
{
structbus_type*bus=get_bus(drv->bus);//获取总线内容,即前面定义的
platform_bus_type
interror=0;
if(!bus)
return0;
pr_debug("bus%s:adddriver%s\n",bus->name,drv->name);
/ *kobject_set_name:设置kboj->name[KOBJ_NAME_LEN]数组内容,如果KOBJ_NAME_LEN长度不够,会调用kmalloc申请之后kobj->k_name指针或者指向kboj->name或者指向kmalloc返回地址*/
error=kobject_set_name(&drv->kobj,"%s",drv->name);
//设置kboj->name 成员= drv->name
if(error)
gotoout_put_bus;//释放总线
drv->kobj.kset=&bus->drivers;//设置device_driver结构体的kobj.kset成员变量(是一个kset指针变量)。很重要,它指向总线(bus)的kset成员,bus->drivers成员drivers为kset类型。在platform_driver_register()函数中有如下总线赋值语句:drv->driver.bus =&platform_bus_type;
platform_bus_type为内核定义的一类总线(bus)。此处的drv->kobj.kset指针指向了总线(bus)的kset成员。总线的kset成员是kobject的顶层容器,包含了定义在该总线下面所有的kobject。
/******************************************************************/
/*kobject_register() 理解 :把drv的kobj登记到管理它的bus->kset集合上去。同时再根据层级关系创建相应的目录文件。
注册登记该kobj,如果该kobj属于某个kset,那么将自己的entry节点(list_head)挂接到该kset的list链表上,以示自己需要该kset的滋润,同时kobj->parent=&kset->kobj,parent指向kset用来管理自己的kobj
如果该kobj的parent已经定义,那么简单的将parent的引用计数加1(如果该kobj不属于kset,而属于parent,那么简单的将parent的引用计数加1.)
对于kobj属于某个kset的情况,可以实现kset向下查找kobj,也可以实现kobj向上查找kset。
对于kobj属于某个parent的情况,查找只能是单向的,只能kobj找到parent,parent不能查找该parent挂接的kobj们。parent是用来明显建立亲子关系图的标志性变量,当然在kset也能若隐若现的显露出这种关系,但总不如parent正宗和高效。之后调用create_dir()创建该kobj在sysfs中的目录文件
最后调用kobject_uevent()将KOBJ_ADD事件通知到用户空间的守护进程*/
if((error=kobject_register(&drv->kobj)))//将driver挂到bus总线上。
gotoout_put_bus;
error=driver_attach(drv);//查找所有注册在该bus上的device,当有device的name和driver->name一样时。即找到了该driver对应的设备。在里面调用的really_probe()函数中,实现了设备与驱动的绑定。语句如下:dev->driver =drv;和ret=drv->probe(dev)
if(error)
gotoout_unregister;
klist_add_tail(&drv->knode_bus,&bus->klist_drivers);
module_add_driver(drv->owner,drv);
/* 所以一个驱动需要维持住1个 klist 链条和一个kobj层次结构--驱动drv->kobj对象,内核一方面使用该kobj在sysfs中建立统一的与该kobj对应的目录对象供用户空间访问,另一方面使用该kobj的引用计数来获悉该kobj设备的繁忙与空闲情况,
// 当本kobj对象的引用计数到达0时,只要其他条件允许,那么说明集成本kobj的结构体对象不再使用,内核得知这个情况很重要,因为这对内核进行进一步的决策提供了详细的证据资料,进而对物理设备进行细致的电源管理成了可能,//如:当hub1上的所有端口设备都被拔掉之后,hub1就可以安全的进入省电模式了,而这个功能在2.4内核中是找不到的.
error=driver_add_attrs(bus,drv);
if(error){
/*Howthehelldowegetoutofthispickle?Giveup*/
printk(KERN_ERR"%s:driver_add_attrs(%s)failed\n",
__FUNCTION__,drv->name);
}
error=add_bind_files(drv);
if(error){
/*Ditto*/
printk(KERN_ERR"%s:add_bind_files(%s)failed\n",
__FUNCTION__,drv->name);
}
returnerror;
out_unregister:
kobject_unregister(&drv->kobj);
out_put_bus:
put_bus(bus);
returnerror;
}
下面说明设备驱动是如何知道总线对应设备的
1.5dd.c文件driver_attach()函数
在文件drivers/base/dd.c中,实现了设备与驱动交互的核心函数。
1.5.1driver_attach()函数
函数driver_attach()返回bus_for_each_dev()函数的运行结果。bus_for_each_dev()函数的原型如下:
intbus_for_each_dev(structbus_type*bus,structdevice*start,void*data,
int(*fn)(structdevice*,void*));
该函数迭代了在总线上的每个设备,将相关的device结构传递给fn,同时传递data值。如果start是NULL,将从总线上的第一个设备开始迭代;否则将从start后的第一个设备开始迭代。如果fn返回一个非零值,将停止迭代,而这个值也会从该函数返回(摘自<<Linux设备驱动程序>>第三版)。
该函数是如何知道总线上的每个设备的呢?在设备注册过程中,我会详细介绍。
/**drivers/base/dd.c-Thecoredevice/driverinteractions.**Thisfilecontainsthe(sometimestricky)codethatcontrolsthe*interactionsbetweendevicesanddrivers,whichprimarilyincludes*driverbindingandunbinding.*//***driver_attach-tryto binddrivertodevices.*@drv:driver.**Walkthelistofdevicesthatthebushasonitandtryto*matchthedriverwitheachone.Ifdriver_probe_device()*
returns0andthe@dev->driverisset,we'vefounda*compatiblepair.*/
intdriver_attach(structdevice_driver*drv)
{
returnbus_for_each_dev(drv->bus,NULL,drv,__driver_attach);
}
1.5.2__driver_attach()函数
函数__driver_attach()在调用driver_probe_device()函数前,需要进行线程间的互斥处理。
staticint__driver_attach(structdevice*dev,void*data)
{
structdevice_driver*drv=data;
/**Lockdeviceandtrytobindtoit.Wedroptheerror*hereandalwaysreturn0,becauseweneedtokeeptrying*tobindtodevicesandsomedriverswillreturnanerror*simplyifitdidn'tsupportthedevice.**driver_probe_device()willspitawarning ifthere*isanerror.*/
if(dev->parent)
down(&dev->parent->sem);
down(&dev->sem);
if(!dev->driver)
driver_probe_device(drv,dev);
up(&dev->sem);
if(dev->parent)
up(&dev->parent->sem);
return0;
}
1.5.3driver_probe_device()函数
在driver_probe_device()函数中,调用了match函数platform_match(),如果它返回0,表示驱动与设备不一致,函数返回;否则,调用really_probe()函数。
/***driver_probe_device-attempttobinddevice&drivertogether
*@drv:drivertobindadeviceto*@dev:devicetotrytobindtothedriver**First,wecallthebus'smatchfunction,ifonepresent,whichshould*comparethedeviceIDsthedriversupportswiththedeviceIDsofthe*device.Notewedon'tdothis ourselvesbecausewedon'tknowthe*formatoftheIDstructures,norwhatistobeconsideredamatchand*whatisnot.**Thisfunctionreturns1ifamatchisfound,anerrorifoneoccurs*(thatisnot-ENODEVor-ENXIO),and0otherwise.**Thisfunction mustbecalledwith@dev->semheld.Whencalledfora*USBinterface,@dev->parent->semmustbeheldaswell.*/
intdriver_probe_device(structdevice_driver*drv,structdevice*dev)
{
structstupid_thread_structure*data;
structtask_struct*probe_task;
intret=0;
if(!device_is_registered(dev))
return-ENODEV;
if(drv->bus->match&&!drv->bus->match(dev,drv))
gotodone;
pr_debug("%s:MatchedDevice%swithDriver%s\n",
drv->bus->name,dev->bus_id,drv->name);
data=kmalloc(sizeof(*data),GFP_KERNEL);
if(!data)
return-ENOMEM;
data->drv=drv;
data->dev=dev;
if(drv->multithread_probe){
probe_task=kthread_run(really_probe,data,
"probe-%s",dev->bus_id);
if(IS_ERR(probe_task))
ret=really_probe(data);
}else
ret=really_probe(data);
done:
returnret;
}
structstupid_thread_structure{
structdevice_driver*drv;
structdevice*dev;
};
1.5.4really_probe()函数
在really_probe()函数中,实现了设备与驱动的绑定。语句如下:dev->driver=drv;和
ret=drv->probe(dev);probe()函数的实现如下:
include/linux/platform_device.h
#defineto_platform_device(x)container_of((x),structplatform_device,dev)
drivers/base/platform.c
#defineto_platform_driver(drv)(container_of((drv),structplatform_driver,driver))
staticintplatform_drv_probe(structdevice*_dev)
{
structplatform_driver*drv=to_platform_driver(_dev->driver);
structplatform_device*dev=to_platform_device(_dev);
returndrv->probe(dev);
}
在此函数中,回调了我们在i2c-at91.c文件中实现的探测函数at91_i2c_probe(),至此,平台驱动的注册过程结束。
staticatomic_tprobe_count=ATOMIC_INIT(0);
staticDECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);
staticintreally_probe(void*void_data)
{
structstupid_thread_structure*data=void_data;
structdevice_driver*drv=data->drv;
structdevice*dev=data->dev;
intret=0;
atomic_inc(&probe_count);
pr_debug("%s:Probingdriver%swithdevice%s\n",
drv->bus->name,drv->name,dev->bus_id);
WARN_ON(!list_empty(&dev->devres_head));
dev->driver=drv;
if(driver_sysfs_add(dev)){
printk(KERN_ERR"%s:driver_sysfs_add(%s)failed\n",
__FUNCTION__,dev->bus_id);
gotoprobe_failed;
}
if(dev->bus->probe){
ret=dev->bus->probe(dev);
if(ret)
gotoprobe_failed;
}elseif(drv->probe){
ret=drv->probe(dev);
if(ret)
gotoprobe_failed;
}
//设备与驱动绑定后,对系统中已注册的组件进行事件通知。
driver_bound(dev);
ret=1;
pr_debug("%s:BoundDevice%stoDriver%s\n",
drv->bus->name,dev->bus_id,drv->name);
gotodone;
probe_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver=NULL;
if(ret!=-ENODEV&&ret!=-ENXIO){
/*drivermatchedbuttheprobefailed*/
printk(KERN_WARNING
"%s:probeof%sfailedwitherror%d\n",
drv->name,dev->bus_id,ret);
}
/**Ignoreerrorsreturnedby->probesothatthenextdrivercantry*itsluck.*/
ret=0;
done:
kfree(data);
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
returnret;
}
|
|
|