linux总线、设备和设备驱动的关系
之一:bus_type
总线是处理器和一个或多个设备之间的通道,在设备模型中,所有的设备都通过总线相连,甚至是内部的虚拟"platform"总线。可以通过ls -l /sys/bus看到系统加载的所有总线。
drwxr-xr-x
root
root
1970-01-01 00:02 platform
drwxr-xr-x
root
root
1970-01-01 00:02 spi
drwxr-xr-x
root
root
1970-01-01 00:02 scsi
drwxr-xr-x
root
root
1970-01-01 00:02 usb
drwxr-xr-x
root
root
1970-01-01 00:02 serio
drwxr-xr-x
root
root
1970-01-01 00:02 i2c
drwxr-xr-x
root
root
1970-01-01 00:02 mmc
drwxr-xr-x
root
root
1970-01-01 00:02 sdio
drwxr-xr-x
root
root
1970-01-01 00:02 ac97
总线可以相互插入。设备模型展示了总线和它们所控制的设备之间的实际连接。在Linux 设备模型中,总线由bus_type
结构表示,定义在 :
struct bus_type {
const
char
*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);
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
(*suspend_late)(struct device *dev, pm_message_t state);
int
(*resume_early)(struct device *dev);
int
(*resume)(struct device *dev);
struct
pm_ext_ops *pm;
struct
bus_type_private *p;
};
1,总线的注册和删除,总线的主要注册步骤:
(1)申明和初始化bus_type 结构体。只有很少的bus_type
成员需要初始化,大部分都由设备模型核心控制。但必须为总线指定名字及一些必要的方法。例如:
struct bus_type ldd_bus_type = {
.name =
"ldd",
.match =
ldd_match,
.uevent =
ldd_uevent,
};
(2)调用bus_register函数注册总线。int bus_register(struct bus_type
*bus),该调用可能失败,所以必须始终检查返回值。
ret = bus_register(&ldd_bus_type);
if (ret)
return ret;
若成功,新的总线子系统将被添加进系统,之后可以向总线添加设备。当必须从系统中删除一个总线时,调用:
void bus_unregister(struct bus_type *bus);
2,总线方法
在 bus_type 结构中定义了许多方法,它们允许总线核心作为设备核心与单独的驱动程序之间提供服务的中介,主要介绍以下两个方法:
int (*match)(struct device * dev, struct device_driver *
drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env
*env);
对设备和驱动的迭代:若要编写总线层代码,可能不得不对所有已经注册到总线的设备或驱动进行一些迭代操作,这可能需要仔细研究嵌入到
bus_type 结构中的其他数据结构,但最好使用内核提供的辅助函数:
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *));
int bus_for_each_drv(struct bus_type *bus, struct device_driver
*start, void *data, int (*fn)(struct device_driver *, void
*));
3,总线属性
几乎Linux 设备模型中的每一层都提供添加属性的函数,总线层也不例外。bus_attribute 类型定义在 如下:
struct bus_attribute {
struct
attribute
attr;
ssize_t
(*show)(struct bus_type *, char * buf);
ssize_t
(*store)(struct bus_type *, const char * buf, size_t count);
};
内核提供了一个宏在编译时创建和初始化bus_attribute 结构:
BUS_ATTR(_name,_mode,_show,_store)
int bus_create_file(struct bus_type *bus, struct bus_attribute
*attr);
void bus_remove_file(struct bus_type *bus, struct bus_attribute
*attr);
例如创建一个包含源码版本号简单属性方法如下:
static ssize_t show_bus_version(struct bus_type *bus, char
*buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n",
Version);
}
static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);
//得到bus_attr_version
if (bus_create_file(&ldd_bus_type,
&bus_attr_version))
printk(KERN_NOTICE "Unable to create version attribute\n");
之二:device
在最底层,Linux 系统中的每个设备由一个struct device 代表:
struct device
{
struct
klist klist_children;
struct
klist_node knode_parent;
struct
klist_node knode_driver;
struct
klist_node knode_bus;
struct
device *parent; * 设备的 "父"
设备,该设备所属的设备,通常一个父设备是某种总线或者主控制器。如果 parent 是 NULL, 则该设备是顶层设备,较少见
*/
struct
kobject kobj;
char bus_id[BUS_ID_SIZE];
const
char *init_name;
struct
device_type *type;
unsigned uevent_suppress:1;
struct
semaphore sem;
struct
bus_type *bus;
struct
device_driver *driver;
void *driver_data;
void *platform_data;
struct
dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node;
#endif
u64 *dma_mask;
u64 coherent_dma_mask;
struct
device_dma_parameters *dma_parms;
struct
list_head dma_pools;
struct
dma_coherent_mem *dma_mem;
struct
dev_archdata archdata;
spinlock_t devres_lock;
struct
list_head devres_head;
struct
list_head node;
struct
class *class;
dev_t devt;
struct
attribute_group **groups;
void (*release)(struct device
*dev);
};
(1)设备注册
在注册struct device 前,最少要设置parent, bus_id, bus, 和 release
成员,设备的注册和注销函数为:
int device_register(struct device *dev);
void device_unregister(struct device *dev);
一个实际的总线也是一个设备,所以必须单独注册,以下为lddbus注册它的虚拟总线设备:
static void ldd_bus_release(struct device *dev)
{
printk(KERN_DEBUG "lddbus release\n");
}
struct device ldd_bus = {
.bus_id = "ldd0",
.release = ldd_bus_release
};
ret = device_register(&ldd_bus);
if (ret)
printk(KERN_NOTICE "Unable to
register ldd0\n");
(2)设备属性
sysfs 中的设备入口可有属性,相关的结构是:
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, const
char *buf, size_t count);
};
DEVICE_ATTR(_name,_mode,_show,_store);
int device_create_file(struct device
*device, struct device_attribute * entry);
void device_remove_file(struct device *
dev, struct device_attribute * attr);
一个实例是:终端执行:cd /sys/class/leds/lcd-backlight,
ls回显:
uevent
subsystem
device
power
brightness
这些属性可能都是通过device_create_file添加上去(至少brightness是这样)。进入device目录,再输入pwd,
回显:/sys/devices/platform/smdk-backlight。变换到devices目录下了,可见设备模型的不同构成是指向同一个设备的。
2.6下字符设备开始用struct cdev结构体表示,但是我想调用device_create_file(dev,
&dev_attr_debug);函数在/sys中导出信息,device_create_file()的第一个入口参数类型为struct
device结构体。问题是struct cdev与struct
device这两个结构体没有任何联系的地方?答案是可以采用共同拥有的Kobjcet这个成员作为纽带,所以从子类cdev--->父类
kobject--->子类device,推导得
到:container_of(kobj)-->list_entry(entry)->(struct
device*)
。因为containerof是从结构体指针成员找到结构体地址,所以从cdev的kobj可以找到父类kobject的地址,而所有的kobject的entery都是在一个链表里面,遍历这个链表,找到结构体成员为特定device结构的那一项。
(3)设备结构的嵌入
device 结构包含设备模型核心用来模拟系统的信息。但大部分子系统记录了关于它们拥有的设备的额外信息,所以很少单纯用device
结构代表设备,而是通常将其嵌入一个设备的高层结构体表示中。
lddbus 驱动创建了它自己的 device 类型(也即每类设备会建立自己的设备结构体,其中至少一个成员是struct
device类型,比如video_device),并期望每个设备驱动使用这个类型来注册它们的设备:
struct ldd_device {
char *name;
struct ldd_driver *driver;
struct device dev;
};
#define to_ldd_device(dev) container_of(dev, struct ldd_device,
dev);
lddbus 导出的注册和注销接口如下:
static void ldd_dev_release(struct device *dev)
{ }
int register_ldd_device(struct ldd_device *ldddev)
{
ldddev->dev.bus =
&ldd_bus_type;
//依赖的总线
ldddev->dev.parent =
&ldd_bus;
//父设备
ldddev->dev.release = ldd_dev_release;
strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE);
//设备名拷贝入device结构体中
return
device_register(&ldddev->dev);
//仍然用device_register注册,只不过上层打包了
}
EXPORT_SYMBOL(register_ldd_device);
void unregister_ldd_device(struct ldd_device *ldddev)
{
device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(unregister_ldd_device);
之三:device_driver
设备模型跟踪所有系统已知的驱动,主要目的是使驱动程序核心能协调驱动和新设备之间的关系。一旦驱动在系统中是已知的对象就可能完成大量的工作。驱动程序的结构体device_driver
定义如下:
struct device_driver {
const
char *name;
struct
bus_type *bus;
struct
module *owner;
const char
*mod_name;
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);
struct attribute_group **groups;
struct pm_ops *pm;
struct driver_private *p;
};
(1)驱动程序的注册和注销
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);
(2)驱动程序的属性
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *drv, char
*buf);
ssize_t (*store)(struct device_driver *drv, const
char *buf, size_t count);
};
DRIVER_ATTR(_name,_mode,_show,_store)
*属性文件创建的方法:*/
int driver_create_file(struct device_driver * drv, struct
driver_attribute * attr);
void driver_remove_file(struct device_driver * drv, struct
driver_attribute * attr);
(3)驱动程序结构的嵌入
对大多数驱动程序核心结构,device_driver
结构通常被嵌入到一个更高层的、总线相关的结构中。当然也有直接注册驱动的,不用嵌入到高层结构体。如driver_register(&wm97xx_driver)。
以lddbus 子系统为例,它定义了ldd_driver 结构:
struct ldd_driver {
char *version;
struct module *module;
struct device_driver driver;
struct driver_attribute version_attr;
};
#define to_ldd_driver(drv) container_of(drv, struct ldd_driver,
driver);
lddbus总线中相关的驱动注册和注销函数是:
static ssize_t show_version(struct device_driver *driver, char
*buf)
{
struct
ldd_driver *ldriver = to_ldd_driver(driver);
sprintf(buf,
"%s\n", ldriver->version);
return
strlen(buf);
}
int register_ldd_driver(struct ldd_driver
*driver) //device_driver被嵌入到更高层结构体
{
int ret;
driver->driver.bus = &ldd_bus_type;
ret =
driver_register(&driver->driver);
if (ret)
return ret;
driver->version_attr.attr.name =
"version";
driver->version_attr.attr.owner =
driver->module;
driver->version_attr.attr.mode = S_IRUGO;
driver->version_attr.show =
show_version;
driver->version_attr.store = NULL;
return driver_create_file(&driver->driver,
&driver->version_attr);
}
void unregister_ldd_driver(struct ldd_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(register_ldd_driver);
EXPORT_SYMBOL(unregister_ldd_driver);
在sculld 中创建的 ldd_driver 结构如下:
static struct ldd_driver sculld_driver = {
.version =
"$Revision: 1.21 $",
.module =
THIS_MODULE,
.driver =
{
.name = "sculld",
},
};
之四:class_register
类是一个设备的高层视图,它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能,而不用关心设备是如何连接和工作的。类成员通常由上层代码所控制,而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。
几乎所有的类都显示在/sys/class目录中,可以通过ls -l
/sys/class来显示。出于历史的原因,有一个例外:块设备显示在/sys/block目录中。在许多情况,类子系统是向用户空间导出信息的最好方
法。当类子系统创建一个类时,它将完全拥有这个类,根本不用担心哪个模块拥有那些属性,而且信息的表示也比较友好。为了管理类,驱动程序核心导出了一些接
口,其目的之一是提供包含设备号的属性以便自动创建设备节点,所以udev的使用离不开类。类函数和结构与设备模型的其他部分遵循相同的模式,可与前三篇文章类比。
(1)管理类的接口和注册注销函数
类由 struct class 的结构体来定义:
struct class {
const
char *name; *每个类需要一个唯一的名字,
它将显示在 /sys/class 中*/
struct module *owner;
struct
class_attribute *class_attrs;
struct
device_attribute *dev_attrs;
struct
kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env
*env);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
struct pm_ops *pm;
struct class_private *p;
};
int class_register(struct class *cls);
void class_unregister(struct class *cls);
struct class_attribute {
struct attribute attr;
ssize_t
(*show)(struct class *cls, char *buf);
ssize_t (*store)(struct class *cls, const char *buf, size_t
count);
};
CLASS_ATTR(_name,_mode,_show,_store);
int class_create_file(struct class *cls, const struct
class_attribute *attr);
void class_remove_file(struct class *cls, const struct
class_attribute *attr);、
(2)类设备,类存在的真正目的是给作为类成员的各个设备提供一个容器,成员由struct class_device
来表示,暂没用到。
(3)类接口
类子系统有一个 Linux
设备模型的其他部分找不到的附加概念,称为“接口”,可将它理解为一种设备加入或离开类时获得信息的触发机制,结构体如下:
struct class_interface {
struct list_head node;
struct class *class;
int
(*add_dev) (struct device *,
struct class_interface *);
void (*remove_dev) (struct device *, struct
class_interface *);
};
int class_interface_register(struct class_interface
*class_intf);
void class_interface_unregister(struct class_interface
*class_intf);
设定class的好处:设备驱动一般在注册的时候都会调用此类class的一些函数,主要作用就是在sys目录里面创建一些节点,比如cd到/sys
/class下面可以看到这一类的设备,与这个相关的就是一些kobjects。当然对于一个新设备,可以注册进一个class也可以不注册进去,如果存
在对应class的话注册进去更好。
linux总线、设备和设备驱动的关系的更多相关文章
- linux设备驱动归纳总结(八):2.总线、设备和驱动的关系【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-110295.html linux设备驱动归纳总结(八):2.总线.设备和驱动的关系 xxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(八):2.总线、设备和驱动的关系
linux设备驱动归纳总结(八):2.总线.设备和驱动的关系 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- Linux总线设备驱动模型
1. Linux2.6内核引入总线.设备.驱动模型来描述各种总线(PCI.USB.I2C.SPI)与外围设备及其驱动之间的关系. 2. 在Linux内核中,总线用bus_type结构来描述,定义于文件 ...
- Linux 总线、设备、驱动模型 与 设备树
1.总线.设备.驱动模型 本着高内聚.低耦合的原则,Linux 把设备驱动模型分为了总线.设备和驱动三个实体,这三个实体在内核里的职责分别如下: 设备和驱动向总线进行注册,总线负责把设备和对应的驱动绑 ...
- Linux platform平台总线、平台设备、平台驱动
平台总线(platform_bus)的需求来源? 随着soc的升级,S3C2440->S3C6410->S5PV210->4412,以前的程序就得重新写一遍,做着大量的重复工作, 人 ...
- 《Linux总线、设备与驱动》USB设备发现机制
说明:本分析基于mstar801平台Linux2.6.35.11内核,其他内核版本仅供参考. 一.程序在内核中的位置 1.usb host做为pci总线下的一个设备存在(嵌入式系统中有可能也会直接挂在 ...
- Linux Platform devices 平台设备驱动
设备总线驱动模型:http://blog.csdn.net/lizuobin2/article/details/51570196 本文主要参考:http://www.wowotech.net/devi ...
- 《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动
USB 的全称是 Universal Serial Bus,顾名思义:通用串行总线. 提到总线,联想一下,在你心目中总线总是用来干嘛的?还记得 I2C 总线? I2C 总线上挂有二条信号线,一条是 S ...
- linux设备驱动程序--串行通信驱动框架分析
linux 串行通信接口驱动框架 在学习linux内核驱动时,不论是看linux相关的书籍,又或者是直接看linux的源码,总是能在linux中看到各种各样的框架,linux内核极其庞杂,linux各 ...
随机推荐
- 社区APP “钱途”漫漫
花样年曾宣称:2013年“彩生活”物业品牌收入1.85亿,毛利率超过40%:万科万客会APP.龙湖物业APP……大量房地产企业依托物业企业,纷纷瞄准移动互联网.云计算.物联网等高新科技为基础的物业服务 ...
- CSS3写常用的形状
正方形: 1 .square{ width: 100px;height: 100px; background: #E57779;} 长方形: .rectangle{ width: 200px;he ...
- C#实现在CAD图纸中插入另一个DWG图块的代码
C#实现在CAD图纸中插入另一个DWG图块的代码 PromptPointResult ppr = ed.GetPoint("请选择插入点:"); Point3d pt = ppr. ...
- 根据不同的分辨率选择不同的css文件
<SCRIPT language=javascript> <!-- Begin if (screen.width == 640) { document.write('<link ...
- iOS 简单block的使用
1.第一种方法 声明block: - (void)test:(int) param_1 completion:(void(^)(int)) completion; 实现block: -(void)te ...
- Python 网页爬虫
解决问题:获取网页上的内容.特别是加载主框架后,再用AJAX获取数据生成内容的网页. PyQuery:可以像jQuery的py实现.你给他一个PyQuery一个HTML,他给你一个类似jQuery的操 ...
- .NET打印功能实现 PrintDocument
//打印按钮 private void btnPrint_Click(object sender, EventArgs e) { if (this.printDialog1.ShowDialog() ...
- 遇到的 autoresizingMask 相关的问题
1.前言 当一个控件设置好 frame,然后出现会 frame 显示不准或是跟随父控件的变化而变化了,你就要考虑是否是 autoresizing 的问题了 当在 xib 中布局时,报 NSAutore ...
- php验证字符串是否以逗号隔开包括中文字符串
if(preg_match('/^[\x{4e00}-\x{9fa5}\w]+(,[\x{4e00}-\x{9fa5}\w]+)*$/u','体育,娱乐')){ echo 'ok';}
- 同一个tomcat多个web应用共享session
tomcat版本:apache-tomcat-6.0.29(次方tomcat6和tomcat7支持) 1.修改D:\apache-tomcat-6.0.29\conf\server.xml文件 ...