Linux内核驱动模型---设备总线驱动

何为驱动模型

在我看来模型就是对一系列事务,进行抽象、统一、管理。所呈现出的一种层次关系。就像一个公司有部门、小组、个人这样的一个组织关系。那么这样做的好处就是便于管理。

Linux驱动模型

在Linux的世界里将C语言运用到极致,在这里,不能单单的想象成结构体,要体会其数据结构背后的含义,而不能单单的理解成结构体类型。在c++中有个更好的说法叫类。所有的类型都想象成类更好理解。其中结构体的成员变量类比成属性,其中函数指针类比这个类所具有的方法。类比一个人物的固有血条(属性)以及这个人物的攻击技能(方法)。这样的结构在Linux里面很常见。

驱动模型

在Linux中最常见的驱动模型,就是设备(dev)-总线(bus)-驱动(drv),这样的模型结构。在Linux中类一般都是事务的抽象,提取一些通用的共同的内容。设备类型更多的是来描述这个设备具体的一些通用属性。bus是dev和drv的桥梁,纽带。dev和drv的关系就是靠bus来衔接。drv描述更多的方法,这一类的设备他具有哪些技能,一般在drv中进行描述。

dev bus drv的类型定义在kernel\include\linux\device.h文件中

dev类型—struct device

在Linux中设备是一个宽泛的概念,并不是指某一个实体设备。是一个抽象出来的设备,不要当一个实体设备看待。

struct device {
    
    
 struct device  *parent;
 struct device_private *p;
 struct kobject kobj;
 struct bus_type *bus;  /* type of bus device is on */
 struct device_driver *driver; /* which driver has allocated this
        device */
 void  *platform_data; /* Platform specific data, device
        core doesn't touch it */
 void  *driver_data; /* Driver data, set and get with
        dev_set/get_drvdata */
        ....
}
struct device_private {
    
    
 struct klist klist_children;
 struct klist_node knode_parent;
 struct klist_node knode_driver;
 struct klist_node knode_bus;
 struct list_head deferred_probe;
 struct device *device;
};
  1. 其中最重要的几个成员变量是*bus *driver指针,指向和这个dev相关联的总线和驱动。
  2. 同时还有这个*p的成员变量。这个list node届时就会插入到bus管理的那个dev链表中去,被管理起来,还有drv的节点也会插入到drv管理的dev链表中去,被drv管理起来。
  3. 而根据这个结构体,我就能知道自己这个设备是属于哪个bus,以及知道我的驱动是哪个。
  4. dev的name存在于kobj的成员变量name中,他两用同一个name。

bus类型—struct bus_type

这里的bus,同样是宽泛的概念,并不是cpu中实实在在的总线,是一种程序上虚拟出的一种代码结构。

struct bus_type {
    
    
 const char  *name;
 const char  *dev_name;
 struct device  *dev_root;
 struct subsys_private *p;
 int (*match)(struct device *dev, struct device_driver *drv);
 int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
 int (*probe)(struct device *dev);
 }
 
 struct subsys_private {
    
    
 struct klist klist_devices;
 struct klist klist_drivers;
 struct bus_type *bus;
 }
 
  1. match方法为这个bus中dev和drv他们两个匹配的方法,入参刚好一个是dev一个是drv,只有他们俩某个属性对上了(通常我们用dev和drv的名字作为匹配的条件),那么我们称这个dev和drv匹配上了然后就会去执行probe的方法,后面代码中会介绍。
  2. bus类型最重要的是*p这个成员变量。他管理了2个链表,分别是dev链表和drv链表。这样bus就起到一个管理者的角色。他就知道他当前的这个bus拥有哪些设备和驱动。

drv类型—struct device_driver

drv同样也是广泛意义上的驱动,并不是某一个具体外设的驱动。这里同样抽象出来了,但是具体的外设驱动肯定是基于这个数据结构的。有点像c++中外设驱动的父类那种感觉。

 struct device_driver {
    
    
 const char  *name;
 struct bus_type  *bus;
 struct driver_private *p;
 int (*probe) (struct device *dev);
 }
 
 struct driver_private {
    
    
 struct kobject kobj;
 struct klist klist_devices;
 struct klist_node knode_bus;
 struct module_kobject *mkobj;
 struct device_driver *driver;
};
  1. 根据*bus 知道我这个驱动是属于哪个bus的。
  2. 同样有个p,p里面有一个bus的节点,届时会插入到bus的那个p的drv链表下,被管理起来
  3. 除此之外还有一个dev的链表,而这个链表是用来管理这个驱动所匹配上所有的设备的,为什么有这样的结构,是应为一个设备只能有一个驱动,而一个驱动可以拥有好几个设备。多个相同或者类似的设备共享一个驱动。这个应该好理解。

关系图

在这里插入图片描述

驱动中涉及的接口

每个注册的接口都有一个register,同样和他相对应的有一个unregister接口,比如int driver_register(struct device_driver *drv) 与他对应有一个void driver_unregister(struct device_driver *drv)接口。主要用于资源的释放等相关操作。

bus相关

因为drv和dev的初始化都要涉及到bus,所以,在linux中bus都要先做初始化才能被dev和drv注册时使用。
接口定义在\drivers\base\bus.c
int bus_register(struct bus_type *bus)

int bus_register(struct bus_type *bus)
{
    
    
 int retval;
 struct subsys_private *priv;
 priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL); //申请bus的p空间
 priv->bus = bus;//p的bus指向这个将要注册的bus
 bus->p = priv;//将要注册的bus的p指向这个刚申请出来的p空间
 /* 初始化bus中p带的两个链表,分别是dev链表和drv链表 */
 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
 klist_init(&priv->klist_drivers, NULL, NULL);
 }

dev相关

接口定义在\drivers\base\core.c,实体大部分工作其实是device_add完成的。
下面介绍的dev注册过程,是指bus上已经注册好drv的情况下,dev注册函数的调用过程,
假如bus上没有drv,那么调用过程没有这么复杂。
假如bus没有匹配的drv说明当前dev的驱动没有或者还没有注册上,没关系,等drv注册的时候还是会来扫一遍设备然后在执行bus的probe方法。
int device_register(struct device *dev)—》int device_add(struct device *dev)

int device_add(struct device *dev)
{
    
    
if (dev->init_name) {
    
    //用init_name 来初始化这个dev的名字
  dev_set_name(dev, "%s", dev->init_name);
  dev->init_name = NULL;
 }
 //一系列检查无误后最后调用bus_add_device->\
 //                klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
 //把这个dev成员p的bus节点插入到-》bus成员p的dev链表上去,被bus管理起来。
 error = bus_add_device(dev);
  bus_probe_device(dev);
}
//调用过程简化
bus_probe_device(dev)-device_initial_probe(dev)-__device_attach(dev, true)\
	-》ret = bus_for_each_drv(dev->bus, NULL, &data,__device_attach_driver)\
	-int __device_attach_driver;
//重要函数--循环取bus上的drv链表上的驱动来挨个和这个注册的dev尝试匹配是否能匹配上。\
//而尝试匹配函数即为fn---也就是-》__device_attach_driver
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
       void *data, int (*fn)(struct device_driver *, void *))
{
    
    
  while ((drv = next_driver(&i)) && !error)
  error = fn(drv, data);
}
__device_attach_driver(struct device_driver *drv, void *_data)
{
    
    
//直接调用bus的match接口,所以是否匹配成功的条件是,bus在注册的时候match方法决定的。\
//-》drv->bus->match(dev, drv)
 ret = driver_match_device(drv, dev);
 if (ret == 0) {
    
    
  /* no match */
  return 0;
 }
 return driver_probe_device(drv, dev);
}

driver_probe_device(drv, dev);-really_probe(dev, drv);

really_probe(struct device *dev, struct device_driver *drv)
{
    
    
 dev->driver = drv;
 if (dev->bus->probe) {
    
    
  ret = dev->bus->probe(dev);
 }
 else if (drv->probe) {
    
    
  ret = drv->probe(dev);
 }
 //绑定驱动,dev的p拥有的drv节点插入到drv的成员变量p的dev链表中,被drv管理起来。
 //最后实现klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
 driver_bound(dev);
}
  1. 正常情况下__device_attach_driver返回0,目的bus_for_each_drv里面循环不退出取每个drv进行匹配,假如一个返回不等于的值,取drv跳出循环,后面的drv也就不匹配了。
  2. bus_for_each_drv的返回值还和driver_probe_device的返回值有关而driver_probe_device 最底层和probe相关一般正常调用执行返回也是0,所以当这个设备与之相对应的驱动匹配成功并执行probe后正常是不会退出的。继续取drv与之匹配判断。

drv相关

同样下面drv的注册也是基于bus上面dev已有的情况下进行分析。drv注册函数大部分工作在bus_add_driver完成。
接口定义在\drivers\base\driver.c
int driver_register(struct device_driver *drv)-》bus_add_driver(drv);

int bus_add_driver(struct device_driver *drv)
{
    
    
 struct bus_type *bus;
 struct driver_private *priv;
 priv = kzalloc(sizeof(*priv), GFP_KERNEL);//申请drv结构的成员变量p
 //初始化p的dev链表,届时dev注册的时候,会吧dev挂载这个链表下面,被drv所管理起来。
 klist_init(&priv->klist_devices, NULL, NULL);
 priv->driver = drv;//p的drv指针指向这个drv
 drv->p = priv;//drv的p指向这块申请的内存,
 //上面这两个操作意思就是随便拿到p还是drv都能拿到对方的结构。
//把drv的p成员的bus节点,插到bus成员p的drv链表上,被bus管理起来,操作和dev的注册类似。
 klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
 error = driver_attach(drv);
}

driver_attach-bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
int __driver_attach(struct device *dev, void *data)
{
    
    
//这个函数在dev注册的时候也出现过,调用bus的match接口用来判断这个dev和drv是否匹配。
 ret = driver_match_device(drv, dev);
 if (ret == 0) {
    
    
  /* no match */
  return 0;
 }  else if (ret < 0) {
    
    
		dev_dbg(dev, "Bus failed to match device: %d", ret);
		return ret;
  } /* ret > 0 means positive match */
 if (!dev->driver)
 //这个函数同样在dev注册的时候也出现过,其作用,判断bus的probe假如有则调用,、
 //否则在指向drv的probe,同时要把这个dev绑定到这个驱动上去,被这个驱动管理起来。
  driver_probe_device(drv, dev);
  return 0;
}
  1. bus_for_each_dev和drv注册的时候类似,取bus上的每一个dev和你这个驱动进行匹配。匹配的方法为 __driver_attach
  2. 正常情况下__driver_attach返回总是0目的为了bus_for_each_dev中的循环取dev节点。假如一个驱动注册时,bus的设备链表上一个匹配出错,后面的也就不匹配了。以前的内核版本的这个接口没有这个错误分支,总是返回0.

总结

总的来说Linux中的代码确实很多,全部看懂实在不大可能,包括这篇文章中我也是删减了很多和主题不大相关的代码和内容。比如代码中有很多和调试相关和文件系统相关的内容我都没要贴,因为这些内容主要是在/sys/class/目录下的一些层次关系的管理,和本文的主题没什么太大的关联,所以看代码也一样,不是太相关的就直接略过,毕竟经历有限。而如何判断哪些是相关的,哪些是次要的,那就要好好的积累经验了,万事都有一个过程,你慢慢的熟悉了,接触的多了,就会有意识。

后续

当初学习的时候也会去网上查资料,先看别人的理解,摸摸大体主干,然后自己再去看代码,验证是否理解正确。看了很多资料后,理论架构说的很清楚了,但心里还是有一个疑问,这套东西,用在什么地方,有什么好。说白了就是缺少理论和实际的结合。所以我后面还会在写一篇,介绍这个驱动模型的实例平台总线的运用。理论和实例的结合分析会让你更好的理解这个知识点。

猜你喜欢

转载自blog.csdn.net/weixin_41884251/article/details/113818865