分类: LINUX
2012-04-23 20:32:38
设备模型之二 总线设备驱动
2012-04-19
一、概要分析
Linux设备模型中三个很重要的概念就是总线、设备和驱动,即bus,device和driver。它们分别对应的数据结构分别为struct bus_type,struct device和struct device_driver。
总线是处理器与一个或多个设备之间的通道,在设备模型中,所有的设备都通过总线相连。在最底层,Linux系统中的每一个设备都用device结构的一个实例来表示。而驱动则是使总线上的设备能够完成它应该完成的功能。
在系统中有多种总线,如PCI总线、SCSI总线等。系统中的多个设备和驱动是通过总线让它们联系起来的。在bus_type中两个很重要的成员就是 struct kset drivers和struct kset devices【 在2.6.32.2中已被 struct bus_type_private 结构包含 】。它分别代表了连接在这个总线上的两个链,一个是设备链表,另一个则是设备驱动链表。也就是说,通过一个总线描述符,就可以找到挂载到这条总线上的设备,以及支持该总线的不同的设备驱动程序。
1.1 bus_type 相当于一个容器,是device 和device_driver 的管理机构,它
包含了一个device 集合(kset)和一个device_driver 集合(kset),分别表
示挂在这个总线下的所有设备和所有设备驱动程序。
1.2 device_driver 挂在某个bus_type 下面,包含了一个device 集合(kset),
表示这个驱动程序操作(或控制)的所有设备。device_driver 还包含一个
bus_type 指针,表示驱动程序所在的总线。
1.3 device 挂在某个bus_type 下面,包含了一个device_driver 指针,表示
这个设备对应的设备驱动程序。device 还包含一个bus_type 指针,表示
设备所在的总线。需要说明的是,一个实际的总线在设备驱动模型中是用两
个结构表示的:bus_type 和device。bus_type 代表总线类型,出现在
/sys/bus/目录下;device 代表总线设备,出现在/sys/devices/录下,这
表明实际的总线本质上是一种设备。
1.2 总线、设备与驱动的绑定
在系统启动时,它会对每种类型的总线创建一个描述符,并将使用该总线的设备链接到该总线描述符的devices链上来。也即是说在系统初始化时,它会扫描连接了哪些设备,并且为每个设备建立一个struce device变量,然后将该变量链接到这个设备所连接的总线的描述符上去。另一方面,每当加载了一个设备驱动,则系统也会准备一个struct device_driver结构的变量,然后再将这个变量也链接到它所在总线的描述符的drivers链上去。
对于设备来说,在结构体struct device中有两个重要的成员,一个是struct bus_type *bus,另一个是struct device_driver *driver。bus成员就表示该设备是链接到哪一个总线上的,而driver成员就表示当前设备是由哪个驱动程序所驱动的。对于驱动程序来说,在结构体struct device_driver中也有两个成员,struct bus_type *bus和struct list_head devices,这里的bus成员也是指向这个驱动是链接到哪个总线上的,而devices这个链表则是表示当前这个驱动程序可以去进行驱动的那些设备。一个驱动程序可以支持一个或多个设备,而一个设备则只会绑定给一个驱动程序。
对于device与device_driver之间建立联系的方式,主要有两种方式。第一种,在计算机启动的时候,总线开始扫描连接在其上的设备,为每个设备建立一个struct device变量并链接到该总线的devices链上,然后开始初始化不同的驱动程序,驱动程序到它所在的总线的devices链上去遍历每一个还没有被绑定给某个驱动的设备,然后再查看是否能够支持这种设备,如果它能够支持这种设备,则将这个设备与这个驱动联系起来。即,将这个设备的device变量加到驱动的devices链上,同时让struct device中的device_driver指向当前这个驱动。第二种则是热插拔。也即是在系统运行时插入了设备,此时内核会去查找在该bus链上注册了的device_driver,然后再将设备与驱动联系起来。
1.3 Question:
a)设备与驱动根据什么规则联系起来?
b)驱动程序/设备如何找到总线设备的?
以后内容采用 2.6.32.2 内核,有一些跟以前不一样。以后采用使用方法形式来说明:(例子在device_module文件夹中)总线设备驱动操作方法非常的类似,我主要以bus进行说明而已。设备和驱动主要按照bus方法进行操作即可。
二、总线bus
第一步:定义数据结构
【NOTE】很少 bus_type 成员要求初始化; 大部分由设备模型核心处理. 但是, 我们确实不得不指定总线的名子, 以及任何伴随它的方法。
头文件:
#include
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 (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct bus_type_private *p; /*总线类型的私有数据*/
};
struct bus_type_private {
struct kset subsys; /*与该总线相关的子系统*/
struct kset *drivers_kset; /*总线驱动程序的KSET*/
struct kset *devices_kset; /*挂载在总线上的设备KSET*/
struct klist klist_devices; /*挂载在总线上的设备链表*/
struct klist klist_drivers; /*驱动程序链表*/
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
};
Q1:后来注册的设备\驱动如何添加到 struct kset *devices_kset 、struct kset *drivers_kset?
Q2:后来注册的设备\驱动如何寻找到总线?
第二步:实现总线方法
int (*match)(struct device *device, struct device_driver *driver);
这个方法被调用, 大概多次, 无论何时一个新设备或者驱动被添加给这个总线. 它应当返回一个非零值如果给定的设备可被给定的驱动处理. (我们马上进入设备和 device_driver 结构的细节). 这个函数必须在总线级别处理, 因为那是合适的逻辑存在的地方; 核心内核不能知道如何匹配每个可能总线类型的设备和驱动.
int (*uevent) (struct device *device, char **envp, int num_env p, char *buffer, int buffer_size);
这个模块允许总线添加变量到环境中, 在产生一个热插拔事件在用户空间之前。
第三步:注册总线
int bus_register(struct bus_type *bus);
注册总线一定要检查其返回值,可能会注册失败。
/*退出时注销总线*/
void bus_unregister(struct bus_type *bus);
第四步:创建总线属性文件==>用于与空间进行交互
Q:如何将传送进去的 buf 跟内核交互==>读出的buf 哪里来,写入的 buf 要到哪里去?
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *bus, char *buf);
ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};
#define BUS_ATTR(_name, _mode, _show, _store) \
struct bus_attribute bus_attr_##_name = __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);
三、设备device
#include
struct device {
struct device *parent; /*父设备*/
struct device_private *p; /*设备结构的私有数据*/
struct kobject kobj; /*kobject对象*/
const char *init_name; /*设备初始化名字替换了 bus_id */
struct device_type *type; /*设备方法==>类似于 kobject_ktype*/
struct semaphore sem; /*信号量*/
struct bus_type *bus; /*设备所在的总线*/
struct device_driver *driver;/*该设备的驱动,怎样跟驱动匹配呢?*/
void *platform_data; /*平台总线私有数据*/
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem;
struct dev_archdata archdata;
dev_t devt; /* dev_t, creates the sysfs "dev" */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups;
void (*release)(struct device *dev); /*为何必须有呢?没有行吗*/
};
struct device_private {
struct klist klist_children;
struct klist_node knode_parent;
struct klist_node knode_driver;
struct klist_node knode_bus;
void *driver_data; /*我们所关心的数据*/
struct device *device;
};
Q1:利用结构中的kobject 跟 kset 进行关联。
Q2:在进行设备注册时,就将设备添加到总线中去,利用bus_add_device(dev) 实现的。
大家可以去 drivers/base/core.c 看源代码
int device_register(struct device *dev)
{
device_initialize(dev); //初始化设备
return device_add(dev); //添加设备
}
void device_initialize(struct device *dev)
{
/*
*设置设备的kobject所属集合,利用了kobject 挂载到devices_kset其
*实在第一层,sys/devices/... 而在sys/bus/xxx 目录下是个链接文件
*/
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype); //初始化设备的kobject
INIT_LIST_HEAD(&dev->dma_pools);//初始化设备的DMA池,用于传大数
mutex_init(&dev->mutex); //初始化互斥锁
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock); //初始化自旋锁,用于同步子设备链表
INIT_LIST_HEAD(&dev->devres_head); //初始化子设备链表头
device_pm_init(dev);
set_dev_node(dev, -1);
}
int device_add(struct device *dev)
{
...
error = device_private_init(dev); //初始化设备的私有成员
...
parent = get_device(dev->parent); //增加父设备kobject的引用
setup_parent(dev, parent); //设置该设备kobject父对象
...
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); //将设备kobject添加进父对象设备模型
...
error = bus_add_device(dev); //将设备添加进总线中
...
bus_probe_device(dev); //现在该为设备在总线上寻找合适的驱动了
...
}
到了这里就将上面的几个问题给解决了,这对于驱动也是类似的在这里就总结一下一个设备添加和删除过程:当在总线上添加(或删除)设备时,会依次遍历总线上的所有驱动程序,调用总线定义的match()方法检查要加入(或删除)的设备是否和驱动程序“匹配”,如果匹配成功,则对设备调用总线或驱动程序中定义的probe(或remove)方法。添加设备时产生KOBJ_ADD uevent,删除设备时产生KOBJ_REMOVE uevent。
设备添加流程:
device_register(dev)->device_add(dev)-> kobject_uevent(KOBJ_ADD)
device_add(dev)->bus_attach_device(dev)->device_attach(dev)
->__device_attach(drv,dev)->bus_type():match(dev,drv)
then ->driver_probe_device()->really_probe()
->bus_type:probe()或device_driver:probe()
设备删除
device_unregister(dev)->device_del(dev)->bus_remove_device(dev)
->device_release_driver(dev) ->__ device_release_driver(dev)
->bus_type:remove()或device_driver:remove()
device_del(dev)->kobject_uevent(KOBJ_REMOVE)
四、驱动driver
(1)添加驱动程序
driver_register(drv)-> bus_add_driver (drv)-> driver_attach(drv)
->__driver_attach(dev,drv) ->bus_type():match(dev,drv) then
->driver_probe_device()->really_probe()->bus_type:probe() device_driver:probe()
driver_register(drv)-> kobject_uevent(KOBJ_ADD)
(2) 驱动程序删除
driver_unregister(dev)->bus_remove_driver(drv)-> driver_detach(drv)
->__ device_release_driver(dev) [dev-drv 管理的所有设备]
->bus_type:remove() 或device_driver:remove()
五、内嵌的设备和驱动 【linux设备驱动程序第三版】
设备结构包含设备模型核心需要的来模型化系统的信息. 大部分子系统, 但是, 跟踪关于它们驻留的设备的额外信息. 结果, 对设备很少由空设备结构所代表; 相反, 这个结构, 如同 kobject 结构, 常常是嵌入一个更高级的设备表示中. 如果你查看 struct pci_dev 的定义或者 struct usb_device 的定义, 你会发现一个 struct device 埋在其中. 常常地, 低层驱动甚至不知道 struct device, 但是有例外. lddbus 驱动创建它自己的设备类型( struct ldd_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);
这个结构允许驱动提供一个实际的名子给设备( 这可以清楚地不同于它的总线 ID, 存储于设备结构) 以及一个这些驱动信息的指针. 给真实设备的结构常常还包含关于供应者信息, 设备型号, 设备配置, 使用的资源, 等等. 可以在 struct pci_dev (
lddbus 输出的注册接口看来如此:
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);
return device_register(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device);
这里, 我们简单地填充一些嵌入的设备结构成员( 单个驱动不应当需要知道这个 ), 并且注册这个设备到驱动核心. 如果我们想添加总线特定的属性到设备, 我们可在这里做。如同大部分驱动核心结构的情形, device_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 输出这个版本字串为它知道的每个驱动. 总线特定的驱动注册函数是:
int register_ldd_driver(struct ldd_driver *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);
}
这个函数的第一部分只注册低级的 device_driver 结构到核心; 剩下的建立版本属性. 因为这个属性在运行时被创建, 我们不能使用 DRIVER_ATTR 宏; 反之, driver_attribute 结构必须手工填充. 注意我们设定属性的拥有者为驱动模块, 不是 lddbus 模块; 这样做的理由是可以在为这个属性的 show 函数的实现中见到:
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);
}
有些来源于各位大侠博客:
%EC%C5%B6%AF%B0%AE%C9%F1/blog/item/173b12648e84adcd8db10d2a.html