0. linux设备模型简介
linux内核在2.5时期引入了设备模型。这是个简洁高效的模型,它使所有的设备层次结构清晰,尤其使电源管理、热插拔以及设备驱动和用户空间的交互变得容易。
设备模型中的核心概念是设备(device)和驱动(driver),以面对对象的思维来看,这是两个重要的基类。各个驱动模块均在此基础上,构筑起自己的设备和驱动类型。
1. device
struct device是设备模型中表示设备的数据结构。当我们创建一个device结构后,调用device_register()将其加入设备的拓扑结构中。下面先简单看看device数据结构中的一些成员。
-
struct device {
-
struct device *parent;
-
struct kobject kobj;
-
...
-
};
struct kobject是内核中用于引用计数的一个数据结构。什么是引用计数呢?可以这样比喻,它就像一条线从某个数据结构中引出来,这根线攥在设备模型核心手中。这样设备模型核心就可以知道一个数据结构是否
还被使用,当引用计数为零,就可以释放它。因此,kobject总是内嵌在内核需要关注的数据结构上,如这里的struct device。
不仅如此,构筑于kobject之上的sysfs提供了一个和用户空间交互的窗口。sysfs将在相应的文章里细讲,这里只要知道,当一个kobject添加进内核中时,/sys目录下就会出现相应的目录。可以看到device数据结构中有一个parent成员的指针,它指向另一个device数据结构,而那个数据结构中同样内嵌了kobject。这样在/sys目录下,设备的拓扑关系就和目录树一样。可以不指定parent,这样就会直接在/sys目录下出现,但这样的设备只可能是虚拟的。
当然,在注册设备前必须要给它一个名字,不然sysfs不知道该显示什么。
-
int dev_set_name(struct device *dev, const char *fmt, ...);
只要稍微看一眼这个函数的实现就知道,其实是给kobject设置名字,device数据结构中根本没有char *name之类的成员。ok,有了名字就可以注册了,下面放一段代码片段:
-
...
-
-
struct device *dev;
-
-
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
-
dev_set_name(dev, "test");
-
device_register(dev);
-
-
...
最终的效果,是在/sys/device/下创建了一个test目录。为什么不是/sys下呢?其实是device_register()函数做了一些小处理。
2. driver
驱动和设备是一对多的关系,一个驱动服务于一系列的设备是常有的事。struct device_driver是设备模型中表示驱动的数据结构,其某些关键成员如下:
-
struct device_driver {
-
const char *name;
-
struct bus_type *bus;
-
...
-
}
同样,名字是必须要制定的。这里还出现一个新的数据结构bus_type,它也是必须的。在调用driver_register()函数前,必须指定bus_type,因此我们先讲一下bus_type。
3. 设备和驱动的纽带——总线(bus)
在linux设备模型中,所有的设备驱动都是挂在总线上的。这个总线可以是现实存在的,比如usb,也可以是虚拟的,比如platform bus。而且总线之间也呈现类似目录的拓扑结构。比如所有的物理总线本身都是一个挂在platform总线下
的一个设备,目录/sys/devices/platform就呈现了这样一种关系。下面看看struct bus_type数据结构的关键成员。
-
struct bus_type {
-
const char *name;
-
int (*match)(struct device *dev, struct device_driver *drv);
-
...
-
};
match函数是一个关键的成员,设备和驱动添加到驱动模型中时都需要探测有没有和自己匹配的驱动或设备。匹配的原则是自定义的,简单的情形是比较名字是否相同,这是platform bus的做法。下面就以platform bus为例子,简单
分析下总线这个概念。
前面讲到device和driver相当于基类,那现实的驱动模块就在其基础上扩展的。比如platform设备,是这样定义的:
-
struct platform_device {
-
const char *name;
-
struct device dev;
-
...
-
};
我们来看看platform_device_add()这个函数:
-
int platform_device_add(struct platform_device *pdev)
-
{
-
...
-
-
if (!pdev->dev.parent)
-
pdev->dev.parent = &platform_bus;
-
-
pdev->dev.bus = &platform_bus_type;
-
-
if (pdev->id != -1)
-
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
-
else
-
dev_set_name(&pdev->dev, "%s", pdev->name);
-
-
...
-
-
ret = device_add(&pdev->dev);
-
-
...
-
}
首先判断是否指定了父设备。这里的platform_bus是platform总线把自己注册成了一个设备,因此我们可以在/sys/devices中看到platform目录,这是个没有父设备的设备,和之前我们写的test设备一样。
不同的地方是指定了platform_bus_type,因此这个设备也就是挂在platform总线上的,如果这个设备名字是test,那么在/sys/bus/platform/devices目录下就会添加一个test的软连接。为什么是链接而不是目录?从上面代码的最开头
可以看到,test设备的父设备可能是platform_bus,因此这些链接大多指向/sys/devices/platform。从这里可以看到,一个platform设备,它的父设备可能不是platform_bus,那platform总线是不是就类似于个大容器?
再看看platform_driver的定义:
-
struct platform_driver {
-
int (*probe)(struct platform_device *);
-
...
-
struct device_driver driver;
-
...
-
}
再看看platform_driver_register()这个函数:
-
int platform_driver_register(struct platform_driver *drv)
-
{
-
drv->driver.bus = &platform_bus_type;
-
if (drv->probe)
-
drv->driver.probe = platform_drv_probe;
-
...
-
-
return driver_register(&drv->driver);
-
}
这里依然指定了platform_bus_type,并且也把device_driver的探测函数指向platform_drv_probe。这个platform_drv_probe函数有什么用呢,讲了这么多的platform_bus_type又是怎么用呢?我们来看driver_register()函数:
-
int driver_register(struct device_driver *drv)
-
{
-
...
-
ret = bus_add_driver(drv);
-
...
-
}
-
int bus_add_driver(struct device_driver *drv)
-
{
-
...
-
if (drv->bus->p->drivers_autoprobe) {
-
error = driver_attach(drv);
-
if (error)
-
goto out_unregister;
-
}
-
...
-
}
留意这几行代码就可以了,关键的地方在driver_attach()函数。我们可以想到,惯常的做法总线数据结构里肯定有链表之类的来管理其下的设备和驱动。bus_add_driver()从名字可以知道,是将驱动加入到bus的管理中。同时还有更重要的任务便是遍历设备链表来寻求匹配的设备,以便使其工作起来。
至于匹配的规则,则调用bus_type数据结构中的match函数去判断。如果匹配成功,那么就调用platform_drv_probe()函数。将对应的device数据结构作为参数传递给它。同时,device中有device_driver的指针成员,这个在前面是没提出的,这里就会把相匹配的device_driver赋值给它,设备和驱动就耦合起来了。可以去仔细的看看driver_attach()的实现,验证以上说法。
我们再看看platform_drv_probe()函数:
-
static int platform_drv_probe(struct device *_dev)
-
{
-
struct platform_driver *drv = to_platform_driver(_dev->driver);
-
struct platform_device *dev = to_platform_device(_dev);
-
-
return drv->probe(dev);
-
}
我们经常写的platform设备驱动的时候,有没有想过probe函数里面的参数是怎么传进来的呢?看了以上代码就应该明了吧。通常新手会犯的错误是定义的platform设备名字和platform驱动里的名字不一致,然后驱动死活跑不起来。
但是这里还有一个问题,就是以上都假设现有设备再有驱动,如果反过来呢?不用担心,驱动和设备都在bus的链表里,在device_add()函数里,也同样的进行了匹配,感兴趣可以看下代码。
4. class
class是比总线更高的视图。为什么会有class呢?linux的驱动近年趋向以功能划分,而不再是从硬件的角度去看了。一个典型的例子就是input子系统的出现。能否像写C++那样写一个类,把输入设备囊括其中?这个想法很狂野,但最终还是实现了。现在,近乎所有的输入设备都写成input设备,不管它们实际上是i2c的触屏,还是GPIO矩阵的按键,甚至数量繁多的传感器。input子系统太繁杂,不打算拿它做例子。
我们看一个超简单的class,led驱动。这是个简单有趣的驱动模块,足够理解class的概念。
通过class_create()创建一个class数据结构,调用成功后在/sys/class目录下就会创建相应的目录。还是同样的套路,led驱动模块定义一个led_classdev,内嵌一个device指针:
-
struct led_classdev {
-
const char *name;
-
struct device *dev;
-
...
-
};
然后看看设备注册函数led_classdev_register():
-
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
-
{
-
led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
-
"%s", led_cdev->name);
-
...
-
}
可以看到device_create()中的第一个参数leds_class,这是预先创建的class。此函数是device_register()的封装,在调用device_register()之前为device的class成员赋值。一旦调用成功,就会在/sys/leds/下创建和设备名字一致的目录。
这样不管led驱动实际是platform设备,或其它,总能在/sys/class/leds下找到它。
阅读(377) | 评论(0) | 转发(0) |