您的位置:首页 > 运维架构 > Linux

关于设备模型、设备与驱动关联的过程分析 - linux设备/驱动

2013-01-01 17:11 495 查看
关于设备模型、设备与驱动关联的过程分析-linux设备/驱动
2013-01-01|阅:1转:29|分享



修改
关于设备模型、设备与驱动关联的全过程分析


本文的大多数内容参考了:对于网络上设备与驱动关联的全过程分析(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;

}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: