尽管
LDD3中说对多数程序员掌握设备驱动模型不是必要的,但对于嵌入式
Linux的底层程序员而言,对设备驱动模型的学习非常重要。
Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述。换句话说,
Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要新添加设备或驱动提供一般性的统一接口,这使得驱动程序的开发变得更简单了,而程序员只需要去学习接口就行了。
在正式进入设备驱动模型的学习之前,有必要把
documentation/filesystems/sysfs.txt读一遍(不能偷懒)。
sysfs.txt主要描述
/sys目录的创建及其属性,
sys目录描述了设备驱动模型的层次关系,我们可以简略看一下
/sys目录,
block:所有块设备
devices:系统所有设备(块设备特殊),对应
struct device的层次结构
bus:系统中所有总线类型(指总线类型而不是总线设备,总线设备在
devices下),
bus的每个子目录都包含
--devices:包含到
devices目录中设备的软链接
--drivers:与
bus类型匹配的驱动程序
class:系统中设备类型(如声卡、网卡、显卡等)
fs:一些文件系统,具体可参考
filesystems /fuse.txt中例子
dev:包含
2个子目录
--char:字符设备链接,链接到
devices目录,以
:命名
--block:块设备链接
Linux设备模型学习分为:
Linux设备底层模型,描述设备的底层层次实现(
kobject);
Linux上层容器,包括总线类型(
bus_type)、设备(
device)和驱动(
device_driver)。
====Linux设备底层模型 ====
谨记:像上面看到的一样,设备模型是层次的结构,层次的每一个节点都是通过
kobject实现的。在文件上则体现在
sysfs文件系统。
kobject结构
内核中存在
struct kobject数据结构,每个加载到系统中的
kobject都唯一对应
/sys或者子目录中的一个文件夹。可以这样说,许多
kobject结构就构成设备模型的层次结构。每个
kobject对应一个或多个
struct
attribute描述属性的结构。
点击(
此处)折叠或打开
-
struct kobject{
-
constchar*name;/*对应sysfs的目录名*/
-
struct list_head entry;/*kobjetct双向链表*/
-
struct kobject*parent;/*指向kset中的kobject,相当于指向父目录*/
-
struct kset*kset;/*指向所属的kset*/
-
struct kobj_type*ktype;/*负责对kobject结构跟踪*/
-
struct sysfs_dirent*sd;
-
struct kref kref;/*kobject引用计数*/
-
unsignedintstate_initialized:1;
-
unsignedintstate_in_sysfs:1;
-
unsignedintstate_add_uevent_sent:1;
-
unsignedintstate_remove_uevent_sent:1;
-
unsignedintuevent_suppress:1;
-
};
kobject结构是组成设备模型的基本结构,最初
kobject设计只用来跟踪模块引用计数,现已增加支持,
——
sysfs表述:在
sysfs中的每个对象都有对应的
kobject
—— 数据结构关联:通过链接将不同的层次数据关联
—— 热插拔事件处理:
kobject子系统将产生的热插拔事件通知用户空间
kobject一般不单独使用,而是嵌入到上层结构(比如
struct device,
struct device_driver)当中使用。
kobject的创建者需要直接或间接设置的成员有:
ktype、
kset和
parent。
kset我们后面再说,
parent设置为
NULL时,
kobject默认创建到
/sys顶层目录下,否则创建到对应的
kobject目录中。重点来分析
ktype成员的类型,
点击(
此处)折叠或打开
-
#include<kobject.h>
-
struct kobj_type{
-
void(*release)(struct
kobject*kobj);/*释放*/
-
conststruct sysfs_ops*sysfs_ops;/*默认属性实现*/
-
struct attribute**default_attrs;/*默认属性*/
-
conststruct kobj_ns_type_operations*(*child_ns_type)(struct
kobject*kobj);
-
constvoid*(*namespace)(struct
kobject*kobj);
-
};
ktype包含了释放设备、默认属性以及属性的实现方法几个重要成员。每个
kobject必须有一个
release方法,并且
kobject在该方法被调用之前必须保持不变(处于稳定状态)。默认属性的结构如下,
点击(
此处)折叠或打开
-
#include<linux/sysfs.h>
-
struct attribute{
-
constchar*name;/*属性名称*/
-
mode_t mode;/*属性保护:只读设为S_IRUGO,可写设为S_IWUSR*/
-
}
kobj_type中的
default_attrs为二级结构指针,可以对每个
kobject使用多个默认属性,最后一个属性使用
NULL填充。
struct
sysfs_ops结构则如下,
点击(
此处)折叠或打开
-
struct sysfs_ops{
-
ssize_t(*show)(struct
kobject*,struct attribute*,char*);
-
ssize_t(*store)(struct
kobject*,struct attribute*,constchar*,size_t);
-
};
show方法用于将传入的指定属性编码后放到
char *类型的
buffer中,
store则执行相反功能:将
buffer中的编码信息解码后传递给
struct
attribute类型变量。两者都是返回实际的属性长度。
一个使用
kobject的简单例子如下,
点击(
此处)折叠或打开
-
#include<linux/module.h>
-
#include<linux/init.h>
-
#include<linux/device.h>
-
#include<linux/string.h>
-
#include<linux/sysfs.h>
-
#include<linux/kernel.h>
-
-
MODULE_AUTHOR("xhzuoxin");
-
MODULE_LICENSE("Dual BSD/GPL");
-
-
void my_obj_release(struct kobject*kobj)
-
{
-
printk("release ok.n");
-
}
-
-
ssize_t my_sysfs_show(struct kobject*kobj,struct attribute*attr,char*buf)
-
{
-
printk("my_sysfs_show.n");
-
printk("attrname:%s.n",attr->name);
-
sprintf(buf,"%s",attr->name);
-
return strlen(attr->name)+1;
-
}
-
-
ssize_t my_sysfs_store(struct kobject*kobj,struct attribute*attr,constchar*buf,
-
size_t count)
-
{
-
printk("my_sysfs_store.n");
-
printk("write:%sn",buf);
-
-
return count;
-
}
-
-
struct sysfs_ops my_sysfs_ops={
-
.show=my_sysfs_show,
-
.store=my_sysfs_store,
-
};
-
-
struct attribute my_attrs={
-
.name="zx_kobj",
-
.mode=S_IRWXUGO,
-
};
-
-
struct attribute*my_attrs_def[]={
-
&my_attrs,
-
NULL,
-
};
-
struct kobj_type my_ktype={
-
.release=my_obj_release,
-
.sysfs_ops=&my_sysfs_ops,
-
.default_attrs=my_attrs_def,
-
};
-
-
struct kobject my_kobj;
-
-
int__init kobj_test_init(void)
-
{
-
printk("kobj_test init.n");
-
kobject_init_and_add(&my_kobj,&my_ktype,NULL,"zx");
-
-
return 0;
-
}
-
-
void __exit kobj_test_exit(void)
-
{
-
printk("kobj_test exit.n");
-
kobject_del(&my_kobj);
-
}
-
-
module_init(kobj_test_init);
-
module_exit(kobj_test_exit);
例子中有两个函数,用于初始化添加和删除kobject结构,
点击(
此处)折叠或打开
-
intkobject_init_and_add(struct kobject*kobj,struct
kobj_type*ktype,
-
struct kobject*parent,constchar*fmt,...);/*fmt指定kobject名称*/
-
void kobject_del(struct kobject*kobj);
加载模块后,在
/sys目录下增加了一个叫
zx达到目录,
zx目录下创建了一个属性文件
zx_kobj,使用
tree
/sys/zx查看。
内核提供了许多与
kobject结构相关的函数,如下:
点击(
此处)折叠或打开
-
//kobject初始化函数
-
void kobject_init(struct kobject*kobj);
-
//设置指定kobject的名称
-
intkobject_set_name(struct kobject*kobj,constchar*format,...);
-
//将kobj 对象的引用计数加,同时返回该对象的指针
-
struct kobject*kobject_get(struct kobject*kobj);
-
//将kobj对象的引用计数减,如果引用计数降为,则调用kobject release()释放该kobject对象
-
void kobject_put(struct kobject*kobj);
-
//将kobj对象加入Linux设备层次。挂接该kobject对象到kset的list链中,增加父目录各级kobject的引//用计数,在其parent指向的目录下创建文件节点,并启动该类型内核对象的hotplug函数
-
intkobject_add(struct kobject*kobj);
-
//kobject注册函数,调用kobject init()初始化kobj,再调用kobject_add()完成该内核对象的注册
-
intkobject_register(struct kobject*kobj);
-
//从Linux设备层次(hierarchy)中删除kobj对象
-
void kobject_del(struct kobject*kobj);
-
//kobject注销函数.与kobject register()相反,它首先调用kobject
del从设备层次中删除该对象,再调//用kobject put()减少该对象的引用计数,如果引用计数降为,则释放kobject对象
-
void kobject_unregister(struct kobject*kobj);
kset结构
我们先看上图,
kobject通过
kset组织成层次化的结构,
kset将一系列相同类型的
kobject使用(双向)链表连接起来,可以这样 认为,
kset充当链表头作用,
kset内部内嵌了一个
kobject结构。内核中用
kset数据结构表示为:
点击(
此处)折叠或打开
-
#include<linux/kobject.h>
-
struct kset{
-
struct list_head list;/*用于连接kset中所有kobject的链表头*/
-
spinlock_t list_lock;/*扫描kobject组成的链表时使用的锁*/
-
struct kobject kobj;/*嵌入的kobject*/
-
conststruct kset_uevent_ops*uevent_ops;/*kset的uevent操作*/
-
};
与
kobject相似,
kset_init()完成指定
kset的初始化,
kset_get()和
kset_put()分别增加和减少
kset对象的引用计数。
Kset_add()和
kset_del()函数分别实现将指定
keset对象加入设备层次和从其中删除;
kset_register()函数完成
kset的注册而
kset_unregister()函数则完成
kset的注销。
==== 设备模型上层容器 ====
这里要描述的上层容器包括总线类型(
bus_type)、设备(
device)和驱动(
device_driver),这
3个模型环环相扣,参考图
9-2。为何称为容器?因为
bus_type/device/device_driver结构都内嵌了
Linux设备的底层模型(
kobject结构)。为什么称为上层而不是顶层?因为实际的驱动设备结构往往内嵌
bus_type/device/device_driver这些结构,比如
pci,
usb等。
总线类型、设备、驱动
3者之间关系:
在继续之前,自我感觉需要区分
2个概念:总线设备与总线类型。总线设备本质上是一种设备,也需要像设备一样进行初始化,但位于设备的最顶层,总线类型是一种在设备和驱动数据结构中都包含的的抽象的描述(如图
9-2),总线类型在
/sys/bus目录下对应实体,总线设备在
/devices目录下对应实体。
总线类型bus_type
内核对总线类型的描述如下:
点击(
此处)折叠或打开
-
struct bus_type{
-
constchar*name;/*总线类型名*/
-
struct bus_attribute*bus_attrs;/*总线的属性*/
-
struct device_attribute*dev_attrs;/*设备属性,为每个加入总线的设备建立属性链表*/
-
struct driver_attribute*drv_attrs;/*驱动属性,为每个加入总线的驱动建立属性链表*/
-
-
/*驱动与设备匹配函数:当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次,若指定的驱动程序能够处理指定的设备,则返回非零值。必须在总线层使用这个函数,因为那里存在正确的逻辑,核心内核不知道如何为每个总线类型匹配设备和驱动程序*/
-
int(*match)(struct
device*dev,struct device_driver*drv);
-
/*在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)*/
-
int(*uevent)(struct
device*dev,struct kobj_uevent_env*env);
-
int(*probe)(struct
device*dev);/**/
-
int(*remove)(struct
device*dev);/*设备移除调用操作*/
-
void(*shutdown)(struct
device*dev);
-
-
int(*suspend)(struct
device*dev,pm_message_t state);
-
int(*resume)(struct
device*dev);
-
-
conststruct dev_pm_ops*pm;
-
-
struct subsys_private*p;/*一个很重要的域,包含了device链表和drivers链表*/
-
};
接着对
bus_type中比较关注的几个成员进行简述,
[1] struct bus_attribute结构,
device_attribute与
driver_attribute将分别在设备和驱动分析过程中看到,
点击(
此处)折叠或打开
-
struct bus_attribute{
-
struct attribute attr;
-
ssize_t(*show)(struct
bus_type*bus,char*buf);
-
ssize_t(*store)(struct
bus_type*bus,constchar*buf,size_t
count);
-
};
[2] subsys_private中包含了对加入总线的设备的链表描述和驱动程序的链表描述,省略的部分结构如下
点击(
此处)折叠或打开
-
struct subsys_private{
-
struct kset subsys;
-
struct kset*devices_kset;/*使用kset构建关联的devices链表头*/
-
struct kset*drivers_kset;/*使用kset构建关联的drivers链表头*/
-
struct klist klist_devices;/*通过循环可访问devices_kset的链表*/
-
struct klist klist_drivers;/*通过循环可访问drivers_kset的链表*/
-
struct bus_type*bus;/*反指向关联的bus_type结构*/
-
......
-
};
bus_type通过扫描设备链表和驱动链表,使用
mach方法查找匹配的设备和驱动,然后将
struct device中的
*driver设置为匹配的驱动,将
struct
device_driver中的
device设置为匹配的设备,这就完成了将总线、设备和驱动
3者之间的关联。
bus_type只有很少的成员必须提供初始化,大部分由设备模型核心控制。内核提供许多函数实现
bus_type的注册注销等操作,新注册的总线可以再
/sys/bus目录下看到。
点击(
此处)折叠或打开
-
struct bus_type ldd_bus_type={/*bus_type初始化*/
-
.name="ldd",
-
.match=ldd_match,/*方法实现参见实例*/
-
.uevent=ldd_uevent,/*方法实现参见实例*/
-
};
-
ret=bus_register(&ldd_bus_type);/*注册,成功返回0*/
-
if(ret)
-
return ret;
-
void bus_unregister(struct bus_type*bus);/*注销*/
设备device
设备通过
device结构描述,
点击(
此处)折叠或打开
-
struct device{
-
struct device*parent;/*父设备,总线设备指定为NULL*/
-
struct device_private*p;/*包含设备链表,driver_data(驱动程序要使用数据)等信息*/
-
struct kobject kobj;
-
constchar*init_name;/*初始默认的设备名,但@device_add调用之后又重新设为NULL*/
-
struct device_type*type;
-
struct mutex mutex;/*mutextosynchronize
callstoits driver*/
-
struct bus_type*bus;/*type
of bus deviceison*/
-
struct device_driver*driver;/*which
driver has allocated this device*/
-
void*platform_data;/*Platform
specific data,device core doesn't touch it*/
-
struct dev_pm_info power;
-
-
#ifdef CONFIG_NUMA
-
intnuma_node;/*NUMA
node this deviceiscloseto*/
-
#endif
-
u64*dma_mask;/*dma
mask(ifdma'able device)*/
-
u64 coherent_dma_mask;/*Like dma_mask,butfor
-
alloc_coherent mappings as
-
notall hardware supports
-
64 bit addressesforconsistent
-
allocations such descriptors.*/
-
struct device_dma_parameters*dma_parms;
-
struct list_head dma_pools;/*dma pools(ifdma'ble)*/
-
struct dma_coherent_mem*dma_mem;/*internalforcoherent
mem override*/
-
/*arch specific additions*/
-
struct dev_archdata archdata;
-
#ifdef CONFIG_OF
-
struct device_node*of_node;
-
#endif
-
-
dev_t devt;/*dev_t,creates
the sysfs"dev"设备号*/
-
spinlock_t devres_lock;
-
struct list_head devres_head;
-
struct klist_node knode_class;
-
structclass*class;
-
conststruct attribute_group**groups;/*optional
groups*/
-
-
void(*release)(struct
device*dev);
-
};
设备在
sysfs文件系统中的入口可以有属性,这通过
struct device_attribute单独描述,提供
device_create_file类型函数添加属性。
点击(
此处)折叠或打开
-
/*interfaceforexporting
device attributes*/
-
struct device_attribute{
-
struct attribute attr;
-
ssize_t(*show)(struct
device*dev,struct device_attribute*attr,
-
char*buf);
-
ssize_t(*store)(struct
device*dev,struct device_attribute*attr,
-
constchar*buf,size_t count);
-
};
使用宏
DEVICE_ATTR宏可以方便地再编译时构建设备属性,构建好属性之后就必须将属性添加到设备。
点击(
此处)折叠或打开
-
/*最终生成变量dev_attr_##_name描述属性,
-
*比如DEVICE_ATTR(zx,S_IRUGO,show_method,NULL);
-
*则create_file中entry传入实参为dev_attr_zx*/
-
DEVICE_ATTR(_name,_mode,_show,_store);
-
/*属性文件的添加与删除使用以下函数*/
-
intdevice_create_file(struct device*device,struct
device_attribute*entry);
-
void device_remove_file(struct device*dev,struct device_attribute*attr);
总线设备的注册:总线设备与一般设备一样,需要单独注册,与一般设备不同,总线设备的
parent与
bus域设为
NULL。一般设备注册注销函数为
点击(
此处)折叠或打开
-
intdevice_register(struct device*dev);/*成功返回0,需要检查返回值*/
-
void device_unregister(struct device*dev);
实际创建新设备时,不是直接使用
device结构,而是将
device结构嵌入到具体的设备结构当中,比如
点击(
此处)折叠或打开
-
struct ldd_device{
-
char*name;/*设备名称*/
-
struct ldd_driver*driver;/*ldd设备关联的驱动*/
-
struct device dev;/*嵌入的device结构*/
-
};
-
/*同时提供根据device结构