源码基线:Linux-2.6.24
设备模型概述
-
设备模型主要完成以下工作:
- 1.设备分类,以分层的架构对设备进行描述,隐藏设备内部的连接细节,对外清晰地展示可用的设备。
- 2.创建和管理设备的生命周期。
- 3.通过sysfs虚拟文件系统,向用户空间提供对设备的读写操作,获取设备的信息、改变设备的运行状态。
设备模型的结构组成:
1.总线。
所有的设备都通过总线相连,包括内部的虚拟总线。在内核中,用struct bus_type结构体来表示总线。
struct bus_type {
const char * name;
struct kset drivers;
struct kset devices;
}
name
是总线的名字。kset drivers
与kset devices
,分别代表了总线的驱动程序及插入总线的所有设备集合。
2.设备。
每个设备实例用struct device结构体来表示。
struct device {
struct device *parent;
struct kobject kobj;
char bus_id[BUS_ID_SIZE];
struct bus_type * bus;
struct device_driver *driver;
}
- parent指向设备的所属父设备,通常是某种总线。
- kobj表示本设备对象。
- bus_id是总线上标识设备的ID信息,通常由字符串”<域编号>:<总线编号>:<设备编号>:<功能编号>”定义
bus
标识了设备连接在哪个总线上。driver
管理设备的驱动程序。
3.驱动程序。
设备驱动程序设备模型可以跟踪所有注册的设备,驱动程序为设备提供服务,完成设备的工作。设备的驱动程序由结构体struct device_driver定义。
struct device_driver {
const char * name;
struct bus_type * bus;
struct kobject kobj;
struct klist klist_devices;
}
name
是驱动程序的名字。bus
是指驱动程序所操作的总线类型。kobj
表示驱动程序服务的设备对象。klist_devices
是驱动程序当前能操作的设备链表。
platform虚拟总线
struct kobject与kset结构体
kobject与kset是组成设备模型的基本结构,为来表示设备模型的设备实例与设备层次关系。这两个结构体作为面向对象概念的基类,嵌套在其他结构体里使用。在sysfs中显示的每一个实例,都对应一个kobject。
/* 设备模型的基本结构。
* 在sysfs中显示的每一个对象,都对应一个kobject,它被用来与内核交互并创建它的可见表述。
* 内核用kobject结构将各个对象连接起来组成一个分层的结构体系 */
struct kobject {
const char * k_name;
struct kref kref; /* 对象的引用计数。一个内核对象被创建时,需要知道对象存活的时间,跟踪生命周期的一个方法是使用引用计数,当内核中没有该对象的引用时,表示对象的生命周期结束,可以被删除 */
struct list_head entry; /* 连接到kset建立层次结构 */
struct kobject * parent; /* parent保存了分层结构中上一层节点kobject结构的指针。比如一个kobject结构表示了一个USB设备,它的parent指针可能指向了表示USB集线器的对象,而USB设备是插在USB集线器上的。parent指针最重要的用途是在sysfs分层结构中定位对象 */
struct kset * kset; /* 一个kset是嵌入相同类型结构的kobject集合。每个kset内部,包含了自己的kobject。kset总是在sysfs中出现,一旦设置了kset并把它添加到系统中,将在sysfs中创建一个目录。kobject不必在sysfs中表示,但kset中每一个kobject成员都将在sysfs中得到表述 */
struct kobj_type * ktype; /* 属性结构。kset中也有一个ktype,其使用优先于kobject的此处ktype,因此在典型应用中,kobject中的ktype成员被设置为NULL */
struct sysfs_dirent * sd; /* 指向sysfs下以k_name所命名生成的目录 */
};
struct kset {
struct kobj_type *ktype;
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
struct kset_uevent_ops *uevent_ops;
};
假设有个总线X-bus,以及3个设备驱动A-driver、B-driver、C-driver,且这3个设备都属于同一类X-bus总线。那么kobject与kset在描述设备模型时,通常这样使用。在X-bus的描述结构体中包含kset结构体作为同一类型设备的集合;在描述设备驱动A、B、C的driver结构体中包含kset指针,指向所属的类型集合。每个driver都有一个kobject来表示驱动实例自身,并把所有集合按照加入的先后顺序通过list链表(kset.list与kobject.entry)连接起来。
注册platform总线
函数入口driver_init()
void __init driver_init(void)
{
devices_init();
buses_init();
platform_bus_init();
}
devices_init()
初始化一个kset实例devices_subsys,在sysfs下建立一个“devices”目录。
int __init devices_init(void)
{
/* 由decl_subsys宏定义 */
return subsystem_register(&devices_subsys);
}
int subsystem_register(struct kset *s)
{
return kset_register(s);
}
int kset_register(struct kset * k)
{
kset_init(k);
err = kset_add(k);
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);
}
kset devices_subsys由decl_subsys
宏定义,名字为“devices”,ktype为device_ktype。
#define decl_subsys(_name,_type,_uevent_ops) \
struct kset _name##_subsys = { \
.kobj = { .k_name = __stringify(_name) }, \
.ktype = _type, \
.uevent_ops =_uevent_ops, \
}
decl_subsys(devices, &device_ktype, &device_uevent_ops);
kset_init()
初始化devices_subsys的kobject。
void kset_init(struct kset * k)
{
kobject_init(&k->kobj);
INIT_LIST_HEAD(&k->list);
spin_lock_init(&k->list_lock);
}
void kobject_init(struct kobject * kobj)
{
kref_init(&kobj->kref); /* 初始化kobject的引用计数为1 */
INIT_LIST_HEAD(&kobj->entry); /* 初始化entry链表 */
}
kset_add()
在sysfs下建立以kset->kobject.k_name命名的目录。kobject_add()
的作用主要是在该kobject的kobj->parent指向的目录下,创建以kobj->k_name命名的目录,并把kobj->kset指向对应的kset集合对象,同时把该kset集合对象通过list链表指向kobject的kobj->entry进行关联。
int kset_add(struct kset * k)
{
return kobject_add(&k->kobj);
}
int kobject_add(struct kobject * kobj)
{
int error = 0;
struct kobject * parent;
if (!(kobj = kobject_get(kobj))) /* 将devices_subsys的kobject引用计数增1 */
return -ENOENT;
if (!kobj->k_name) /* decl_subsys宏定义了k_name="devices" */
kobject_set_name(kobj, "NO_NAME"); /* 设置devices_subsys的kobject的名字为"devices" */
parent = kobject_get(kobj->parent); /* 将父设备的引用计数增1,由于devices_subsys初始化时没有指定父结点,所以这里是空操作,parent为NULL */
if (kobj->kset) { /* kobj->kset为NULL,此条件不成立不会执行 */
spin_lock(&kobj->kset->list_lock);
if (!parent)
parent = kobject_get(&kobj->kset->kobj);
list_add_tail(&kobj->entry,&kobj->kset->list);
spin_unlock(&kobj->kset->list_lock);
kobj->parent = parent;
}
error = create_dir(kobj); /* 根据kobject的名字"devices"在sysfs下建立相应的目录 */
return error;
}
static int create_dir(struct kobject * kobj)
{
int error = 0;
if (kobject_name(kobj)) {
error = sysfs_create_dir(kobj);
if (!error) {
if ((error = populate_dir(kobj)))
sysfs_remove_dir(kobj);
}
}
return error;
}
int sysfs_create_dir(struct kobject * kobj)
{
struct sysfs_dirent *parent_sd, *sd;
int error = 0;
/* devices_subsys初始化时没有指定父结点,所以将sysfs_root作为父结点的sysfs目录 */
if (kobj->parent)
parent_sd = kobj->parent->sd;
else
parent_sd = &sysfs_root;
/* 在sysfs目录下,以devices_subsys的kobject的名字"devices"建立一个目录,并将建立的目录结点保存到kobj->sd */
error = create_dir(kobj, parent_sd, kobject_name(kobj), &sd);
if (!error)
kobj->sd = sd;
return error;
}
devices_init()创建的模型:
buses_init()
类似devices_init()
,在sysfs目录下建立一个”bus”目录。
buses_init()创建的模型
platform_bus_init()
完成虚拟总线platform的创建。
int __init platform_bus_init(void)
{
/* 注册到devices目录中 */
device_register(&platform_bus);
/* 注册到bus目录中 */
bus_register(&platform_bus_type);
}
注册platform_bus
struct device platform_bus = {
.bus_id = "platform", /* 目录名 */
};
/* 注册设备:初始化设备的数据结构,将其加入到数据结构的网络中。
完成设备注册后,可以在/sys/devices目录中看到 */
int device_register(struct device *dev)
{
device_initialize(dev); /* 初始化dev结构 */
return device_add(dev); /* 添加dev至目录 */
}
void device_initialize(struct device *dev)
{
/* devices_subsys在之前devices_init()中已经注册。将设备kobject的kset集合指向
devices_subsys */
kobj_set_kset_s(dev, devices_subsys);
/* 初始化设备引用计数为1,同时将其上一层的kset集合对象devices_subsys的引用计
数也加1 */
kobject_init(&dev->kobj);
klist_init(&dev->klist_children, klist_children_get,
klist_children_put);
}
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct class_interface *class_intf;
int error = -EINVAL;
/* 将设备的引用计数加1 */
get_device(dev);
kobject_set_name(&dev->kobj, "%s", dev->bus_id); /* bus_id="platform" */
/* 在device_initialize()中设置了platform_bug.kobject.kset=&devices_subsys,
所以在kobject_add()会设置platform_bus.kobj.parent=&devices_subsys.kobj。
因此这里与devices_subsys、bus_subsys不一样,不是以sysfs_root作为sysfs的
父结点目录,而是以devices_subsys作为sysfs的父结点目录 */
kobject_add(&dev->kobj);
}
注册platform_bus_type
/* 在注册设备及驱动程序之前,需要先有总线。bus_register()函数向系统添加一个新总
线 */
struct bus_type {
const char * name; /* 总线的文本名称,用于在sysfs文件系统中标识总线 */
struct module * owner;
/* 与总线关联的所有设备和驱动程序使用devices和drivers成员,作为集合进行管理。
内核还会创建两个链表(klist_devices和klist_drivers)来保存相同的数据,这些
链表使内核能够快速扫描所有资源(设备和驱动程序),kset保证了与sysfs文件系统
的自动集成。subsys提供与总线子系统的关联,出现在/sys/bus/xxx */
struct kset subsys;
struct kset drivers; /* 总线的驱动程序 */
struct kset devices; /* 插入总线的所有设备 */
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
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);
/* 在有必要将驱动程序关联到设备时,会调用probe。该函数检测设备在系统中是否
真正存在 */
int (*probe)(struct device * dev);
/* 删除驱动程序和设备之间的关联。例如,在将可热挺拔的设备从系统中移除时,会
调用该函数 */
int (*remove)(struct device * dev);
};
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};
/* 注册新的总线 */
int bus_register(struct bus_type * bus)
{
int retval;
kobject_set_name(&bus->subsys.kobj, "%s", bus->name);
bus->subsys.kobj.kset = &bus_subsys;
/* 通过嵌入的kset类型成员subsys,将新总线添加到总线子系统 */
retval = subsystem_register(&bus->subsys);
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent); /* bus_attr_uevent由BUS_ATTR宏定义,在sysfs下创建uevent文件 */
if (retval)
goto bus_uevent_fail;
/* 总线需要了解相关设备及其驱动程序的所有有关信息,因此总线对二者注册了kset。
两个kset分别是drivers和devices,都将总线作为父结点 */
kobject_set_name(&bus->devices.kobj, "devices");
bus->devices.kobj.parent = &bus->subsys.kobj;
kset_register(&bus->devices);
kobject_set_name(&bus->drivers.kobj, "drivers");
bus->drivers.kobj.parent = &bus->subsys.kobj;
bus->drivers.ktype = &driver_ktype;
kset_register(&bus->drivers);
klist_init(&bus->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&bus->klist_drivers, NULL, NULL);
bus->drivers_autoprobe = 1; /* 默认开启总线的自动探测功能 */
add_probe_files(bus); /* 在sysfs下创建drivers_autoprobe与drivers_probe文件 */
}
经过以上的注册过程后,注册生成的sysfs目录结构如下:
/sys
├── bus
│ └── platform
│ ├── devices
│ ├── drivers
│ ├── drivers_autoprobe
│ ├── drivers_probe
│ └── uevent
└── devices
└── platform
└── uevent
注册platform设备
现在我们要向platform注册一个atheros芯片的设备struct platform_devie ath79_wmac_device。注册的方式是通过调用platform_device_register()
函数实现。
static struct resource ath79_wmac_resources[] = {
{
.flags = IORESOURCE_MEM,
}, {
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device ath79_wmac_device = {
.name = "ath9k", /* 默认名 */
.id = -1,
.resource = ath79_wmac_resources,
.num_resources = ARRAY_SIZE(ath79_wmac_resources),
};
static void __init ar933x_wmac_setup(void)
{
ath79_wmac_device.name = "ar933x_wmac"; /* 重命名 */
ath79_wmac_resources[0].start = AR933X_WMAC_BASE;
ath79_wmac_resources[0].end = AR933X_WMAC_BASE + AR933X_WMAC_SIZE - 1;
ath79_wmac_resources[1].start = ATH79_CPU_IRQ_IP2;
ath79_wmac_resources[1].end = ATH79_CPU_IRQ_IP2;
}
void __init ath79_register_wmac(u8 *cal_data, u8 *mac_addr)
{
ar933x_wmac_setup();
platform_device_register(&ath79_wmac_device);
}
int platform_device_register(struct platform_device * pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
int platform_device_add(struct platform_device *pdev)
{
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; /* 指明父设备是platform_bus */
pdev->dev.bus = &platform_bus_type; /* 指明设备归属总线platform_bus_type */
if (pdev->id != -1)
snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,
pdev->id);
else /* ath79_wmac_device.id = -1,所以执行这里,bus_id = "ar933x_wmac" */
strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);
/* 有两个IO资源,MEM与IRQ */
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = pdev->dev.bus_id; /* 赋值"ar933x_wmac" */
p = r->parent;
if (!p) {
if (r->flags & IORESOURCE_MEM)
p = &iomem_resource; /* parent为空,父结点设置为iomem_resource */
else if (r->flags & IORESOURCE_IO)
p = &ioport_resource;
}
if (p && insert_resource(p, r)) { /* 把新IO资源插入IO资源树 */
printk(KERN_ERR
"%s: failed to claim resource %d\n",
pdev->dev.bus_id, i);
ret = -EBUSY;
goto failed;
}
}
device_add(&pdev->dev);
}
到这里ar933x_wmac的目录就创建好了。
/sys
├── bus
│ └── platform
│ ├── devices
│ ├── drivers
│ ├── drivers_autoprobe
│ ├── drivers_probe
│ └── uevent
└── devices
└── platform
├── ar933x_wmac
└── uevent
在device_add()
这个函数里,还有一项重要的工作,通过调用bus_attach_device()
这个函数,把同一总线类型的设备添加总线链表里,方便后续的搜索查找;同理,在注册驱动时,也会把驱动添加到链表里。
添加设备时,会通过bus_for_each_drv()
搜索现有的驱动链表,查找是否有匹配的驱动,如果有,则执行相应的调用__device_attach()
。同理,在添加驱动时,会通过bus_for_each_dev()
搜索现有的设备链表,如果有,则执行相应的调用__driver_attach()
。
我们来看下,注册设备时,添加链表及驱动调用的过程。由于驱动还未注册,所以此时,还不会调用。后面的注册驱动分析过程中,我们再来看是如何调用的。
/* 将设备添加到bus->klist_devices */
void bus_attach_device(struct device * dev)
{
struct bus_type *bus = dev->bus;
int ret = 0;
if (bus) {
dev->is_registered = 1;
if (bus->drivers_autoprobe) /* bus_register()已经默认开启 */
ret = device_attach(dev);
WARN_ON(ret < 0);
if (ret >= 0) /* 这里把device加入到bus的klist_devices链表里 */
klist_add_tail(&dev->knode_bus, &bus->klist_devices);
else
dev->is_registered = 0;
}
}
int device_attach(struct device * dev)
{
int ret = 0;
down(&dev->sem);
if (dev->driver) { /* 驱动还未注册,因此执行else */
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
} else {
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
}
up(&dev->sem);
return ret;
}
int bus_for_each_drv(struct bus_type * bus, struct device_driver * start,
void * data, int (*fn)(struct device_driver *, void *))
{
struct klist_iter i;
struct device_driver * drv;
int error = 0;
/* 由于还未注册驱动,所以klist_drivers为空不会执行直接return error */
klist_iter_init_node(&bus->klist_drivers, &i,
start ? &start->knode_bus : NULL);
while ((drv = next_driver(&i)) && !error)
error = fn(drv, data);
klist_iter_exit(&i);
return error;
}
注册platform驱动
现在我们要向platform注册一个atheros芯片的驱动struct platform ar724x_pci_driver。注册的方式是通过调用platform_driver_register()
函数实现。
static struct platform_driver ar724x_pci_driver = {
.probe = ar724x_pci_probe,
.driver = {
.name = "ar724x-pci",
.owner = THIS_MODULE,
},
};
static int __init ar724x_pci_init(void)
{
return platform_driver_register(&ar724x_pci_driver);
}
postcore_initcall(ar724x_pci_init);
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
ar724x_pci_init()由__define_initcall宏定义,这个宏的作用是把ar724x_pci_init()函数指针安置到.initcall2.init段。在链接脚本vmlinux.lds里,定义了此段。do_initcalls()遍历调用.initcallx.init段里的函数,因此ar724x_pci_init()被调用了。
.initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
__initcall_start = .;
INITCALLS
__initcall_end = .;
}
#define INITCALLS \
*(.initcall0.init) \
*(.initcall0s.init) \
*(.initcall1.init) \
*(.initcall1s.init) \
*(.initcall2.init) \
*(.initcall2s.init) \
*(.initcall3.init) \
*(.initcall3s.init) \
*(.initcall4.init) \
*(.initcall4s.init) \
*(.initcall5.init) \
*(.initcall5s.init) \
*(.initcallrootfs.init) \
*(.initcall6.init) \
*(.initcall6s.init) \
*(.initcall7.init) \
*(.initcall7s.init)
static void __init do_basic_setup(void)
{
driver_init();
do_initcalls();
}
static void __init do_initcalls(void)
{
for (call = __initcall_start; call < __initcall_end; call++) {
(*call)();
}
}
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;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
return driver_register(&drv->driver);
}
int driver_register(struct device_driver * drv)
{
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown)) {
printk(KERN_WARNING
"Driver '%s' needs updating - please use bus_type methods\n",
drv->name);
}
klist_init(&drv->klist_devices, NULL, NULL);
return bus_add_driver(drv);
}
/* 将一个新驱动程序添加到一个总线 */
int bus_add_driver(struct device_driver *drv)
{
struct bus_type * bus = bus_get(drv->bus); /* bus指向&pci_bus_type */
kobject_set_name(&drv->kobj, "%s", drv->name);
drv->kobj.kset = &bus->drivers; /* 指向&pci_bus_type.drivers */
kobject_register(&drv->kobj); /* 在/sys/bus/platform/drivers下
生成ar724x-pci目录 */
if (drv->bus->drivers_autoprobe) {
driver_attach(drv);
}
klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
}
/* 迭代总线上的所有设备,使用驱动程序的match函数进行检测,确定是否有某些设备可
使用该驱动程序管理。最后,将驱动程序添加到总线上注册的所有驱动程序的链表中 */
int driver_attach(struct device_driver * drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
int bus_for_each_dev(struct bus_type * bus, struct device * start,
void * data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device * dev;
/* 这里做的事情简单来说,就是通过搜索注册在总线上的设备链表bus->klist_device
,对各个设备迭代执行__driver_attach(),执行时传递了两个参数,一个是device
,一个是driver。在我们的这个例子里,device是ath79_wmac_device.dev,driver
是ar724x_pci_driver */
klist_iter_init_node(&bus->klist_devices, &i,
(start ? &start->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)
fn(dev, data);
}
![]()
/sys
├── bus
│ └── platform
│ ├── devices
│ ├── drivers
│ │ └──ar724x-pci
│ ├── drivers_autoprobe
│ ├── drivers_probe
│ └── uevent
└── devices
└── platform
├── ar933x_wmac
└── uevent
现在我们来看下__driver_attach(&ath79_wmac_device.dev, &ar724x_pci_driver)做了什么。
static int __driver_attach(struct device * dev, void * data)
{
struct device_driver * drv = data;
down(&dev->sem);
if (!dev->driver) /* 驱动还未挂接,所以driver为空 */
driver_probe_device(drv, dev);
up(&dev->sem);
}
int driver_probe_device(struct device_driver * drv, struct device * dev)
{
/* bus是指向platform_bus_type,其match函数是platform_match() */
if (drv->bus->match && !drv->bus->match(dev, drv))
goto done; /* 设备与驱动不匹配时,直接返回 */
ret = really_probe(dev, drv);
done:
return ret;
}
/* match的作用是进行设备与驱动的匹配 */
static int platform_match(struct device * dev, struct device_driver * drv)
{
/* 通过dev获取其所被包含的platform_device结构体ath79_wmac_device */
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
/* ath79_wmac_device.name = "ar933x_wmac",而ar724x_pci_driver.name =
"ar724x-pci",因此设备与驱动不匹配 */
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}
现在我们再来注册一个驱动struct platform_driver ath_ahb_driver,正是ar933x_wmac的驱动。
static struct platform_driver ath_ahb_driver = {
.probe = ath_ahb_probe,
.remove = ath_ahb_remove,
.driver = {
.name = "ar933x_wmac",
.owner = THIS_MODULE,
},
};
int ath_ahb_init(void)
{
return platform_driver_register(&ath_ahb_driver);
}
其注册过程同之前分析的一样。现在__driver_attach(&ath79_wmac_device.dev, &ath_ahb_driver)时,platform_match()是匹配的了,接下来调用really_probe(&ath79_wmac_device.dev, &ath_ahb_driver)
static int really_probe(struct device *dev, struct device_driver *drv)
{
dev->driver = drv; /* 这里对设备进行了驱动的挂接 */
if (dev->bus->probe) { /* platform_bus_type没有probe() */
dev->bus->probe(dev);
} else if (drv->probe) { /* ath_ahb_probe() */
drv->probe(dev);
}
}
static int ath_ahb_probe(struct platform_device *pdev)
{
void __iomem *mem;
struct ath_softc *sc;
struct ieee80211_hw *hw;
struct resource *res;
const struct platform_device_id *id = platform_get_device_id(pdev);
int irq;
int ret = 0;
struct ath_hw *ah;
char hw_name[64];
/* 获取ath79_wmac_resources[0] */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/* io空间映射 */
mem = devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res));
/* 获取ath79_wmac_resources[1] */
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
irq = res->start;
request_irq(irq, ath_isr, IRQF_SHARED, "ath9k", sc);
}
PCI总线
注册PCI总线
PCI总线的注册同platform总线,都是通过bus_register()
函数进行注册。
struct bus_type pci_bus_type = {
.name = "pci",
.match = pci_bus_match,
.uevent = pci_uevent,
.probe = pci_device_probe,
.remove = pci_device_remove,
.suspend = pci_device_suspend,
.suspend_late = pci_device_suspend_late,
.resume_early = pci_device_resume_early,
.resume = pci_device_resume,
.shutdown = pci_device_shutdown,
.dev_attrs = pci_dev_attrs,
};
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
#define postcore_initcall(fn) __define_initcall("2",fn,2)
postcore_initcall(pci_driver_init);
static int __init pci_driver_init(void)
{
return bus_register(&pci_bus_type);
}
![]()
/sys
└── bus
└── pci
├── devices
├── drivers
├── drivers_autoprobe
├── drivers_probe
└── uevent
注册PCI驱动
现在我们要在PCI总线上注册一个自己的struct pci_driver dkpci_driver的PCI驱动,通过pci_register_driver()
函数进行注册。
struct device_driver {
const char * name; /* 标识驱动程序 */
struct bus_type * bus; /* 指向一个表示总线的对象,并提供特定于总线的
操作 */
struct kobject kobj;
struct klist klist_devices; /* 链表的表头,包括了驱动程序控制的所有
设备的device实例。链表中的各个设备通过
device->knode_driver彼此连接 */
struct klist_node knode_bus; /* 连接一条总线上的所有设备 */
struct module * owner;
const char * mod_name;
struct module_kobject * mkobj;
/* 检测系统中是否存在能够用该设备驱动程序处理的设备 */
int (*probe) (struct device * dev);
/* 删除系统中的设备时会调用remove */
int (*remove) (struct device * dev);
};
/* PCI驱动程序数据结构,表示通用内核代码和设备的底层硬件驱动程序之间的接口 */
struct pci_driver {
struct list_head node;
char *name; /* 设备的文本标识符(驱动程序的模块名称) */
/* 必须为非空指针,以便调用probe */
const struct pci_device_id *id_table;
/* 新设备插入时调用,检测驱动程序是否支持某个PCI设备 */
int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);
/* 设备移除时调用 */
void (*remove) (struct pci_dev *dev);
int (*suspend) (struct pci_dev *dev, pm_message_t state);
int (*suspend_late) (struct pci_dev *dev, pm_message_t state);
int (*resume_early) (struct pci_dev *dev);
int (*resume) (struct pci_dev *dev);
void (*shutdown) (struct pci_dev *dev);
struct pci_error_handlers *err_handler;
/* 用于建立与通用设备模型的关联 */
struct device_driver driver;
struct pci_dynids dynids;
};
static struct pci_driver dkpci_driver = {
name: "dkkernel",
id_table: dk_id_tbl,
probe: dk_pci_probe,
remove: dk_pci_remove,
suspend: dk_pci_suspend,
resume: dk_pci_resume,
};
INT32 bus_module_init(VOID)
{
int status;
status = pci_register_driver(&dkpci_driver);
}
/* 注册PCI驱动程序,使用driver_register()传递到通用设备层 */
static inline int __must_check pci_register_driver(struct pci_driver *driver)
{
return __pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME);
}
int __pci_register_driver(struct pci_driver *drv, struct module *owner,
const char *mod_name)
{
/* initialize common driver fields */
drv->driver.name = drv->name; /* "dkkernel" */
drv->driver.bus = &pci_bus_type;
drv->driver.owner = owner;
drv->driver.mod_name = mod_name;
drv->driver.kobj.ktype = &pci_driver_kobj_type;
spin_lock_init(&drv->dynids.lock);
INIT_LIST_HEAD(&drv->dynids.list);
/* register with core */
driver_register(&drv->driver);
error = pci_create_newid_file(drv);
if (error)
driver_unregister(&drv->driver);
return error;
}
![]()
/sys
└── bus
└── pci
├── devices
├── drivers
│ └──dkkernel
├── drivers_autoprobe
├── drivers_probe
└── uevent
接下来,开始自动探测设备过程driver_attach()
。
int driver_attach(struct device_driver * drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
int bus_for_each_dev(struct bus_type * bus, struct device * start,
void * data, int (*fn)(struct device *, void *))
{
klist_iter_init_node(&bus->klist_devices, &i,
(start ? &start->knode_bus : NULL));
while ((dev = next_device(&i)))
fn(dev, data);
klist_iter_exit(&i);
}
遍历bus->klist_devices链表,然后调用__driver_attach()
开始执行相应的probe()函数。在前面介绍platform总线时,platform设备可以通过platform_device_register()
进行设备的注册,生成bus->klist_devices链表。那么,我们的这个PCI驱动是如何生成bus->klist_devices链表的呢?记住设备模型的三要素:总线、设备、驱动,缺一不可工作。
注册PCI设备
探测PCI设备,加入PCI核
现在我们来看下,klist_devices链表是如何生成的,这又是一个漫长的过程。
上面说到的struct pci_driver dkpci_driver这个PCI驱动,是atheros芯片的驱动,而atheros是基于MIPS的架构,因此接下来我们介绍下MIPS Linux内核的PCI设备探测过程。
PCI总线、PCI设备、PCI驱动每个PCI总线都使用struct pci_bus结构体的一个实例表示,每个PCI设备由struct pci_dev结构体的实例来表示,每个PCI控制器由struct pci_controller结构体的实例来表示。
struct pci_controller {
struct pci_controller *next; /* 指向下一个PCI控制器数据结构的指针。所有控制
器结构体会形成一个链表 */
struct pci_bus *bus; /* PCI控制器归属哪个PCI总线 */
struct pci_ops *pci_ops; /* PCI配置空间的读写操作接口 */
struct resource *mem_resource; /* PCI内存空间范围 */
unsigned long mem_offset; /* 内存空间地址偏移 */
struct resource *io_resource; /* PCI I/O空间范围 */
unsigned long io_offset; /* I/O空间地址偏移 */
unsigned long io_map_base; /* I/O映射的基地址 */
unsigned int index; /* PCI控制器编号 */
unsigned int need_domain_info; /* 域信息 */
int iommu; /* mmu */
/* PCI控制器的总线号读取与设置接口 */
int (*get_busno)(void);
void (*set_busno)(int busno);
};
struct pci_bus {
struct list_head node; /* 总线链表中的结点 */
struct pci_bus *parent; /* (桥接器)总线归属哪个父总线 */
struct list_head children; /* PCI子总线链表 */
struct list_head devices; /* PCI总线上所有设备的链表 */
struct pci_dev *self; /* 总线所归属的桥接器设备 */
struct resource *resource[PCI_BUS_NUM_RESOURCES]; /* 总线的存储资源地址 */
struct pci_ops *ops; /* PCI配置空间的读写操作接口 */
void *sysdata; /* 用于特定于硬件的扩展 */
struct proc_dir_entry *procdir; /* /proc/bus/pci中的目录项 */
unsigned char number; /* 总线号 */
unsigned char primary; /* 主桥接器编号 */
unsigned char secondary; /* 次桥接器编号 */
unsigned char subordinate; /* 下级总线的最大数目 */
char name[48];
};
struct pci_dev {
struct list_head global_list; /* 在所有PCI设备的链表中的结点 */
struct list_head bus_list; /* 在各总线设备链表中的结点 */
struct pci_bus *bus; /* 设备所在的总线 */
struct pci_bus *subordinate; /* 桥接器设备接通的总线 */
void *sysdata; /* 用于特定于硬件的扩展 */
struct proc_dir_entry *procent; /* /proc/bus/pci中的设备目录项 */
unsigned int devfn; /* 编码过的设备和功能索引 */
unsigned short vendor; /* 设备厂商编号 */
unsigned short device; /* 设备编号 */
unsigned short subsystem_vendor;
unsigned short subsystem_device;
unsigned int class; /* 3个字节(base、sub、prog-if) */
u8 revision; /* PCI修订版本号,class的最低字节 */
u8 hdr_type; /* PCI配置空间头部类型 */
u8 pcie_type; /* PCI-E设备/端口类型 */
u8 rom_base_reg; /* 使用哪个配置寄存器来控制ROM */
u8 pin; /* 设备使用的中断针脚 */
struct pci_driver *driver; /* 设备挂接的PCI驱动程序 */
struct device dev; /* 到通用设备模型的接口 */
int cfg_size; /* 配置空间的长度 */
/* 不要直接访问中断线和基本地址寄存器,应该使用这里存储的值 */
unsigned int irq;
struct resource resource[DEVICE_COUNT_RESOURCE];
}
struct pci_ops {
int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);
int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val);
};
通过register_pci_controller()
注册一个PCI总线控制器struct pci_controller loongson2e_pci_controller,把控制器加入到全局链表struct pci_controller *hose_head里。
static struct resource loongson2e_pci_mem_resource = {
.name = "LOONGSON2E PCI MEM",
.start = 0x14000000UL,
.end = 0x1fffffffUL,
.flags = IORESOURCE_MEM,
};
static struct resource loongson2e_pci_io_resource = {
.name = "LOONGSON2E PCI IO MEM",
.start = 0x00004000UL,
.end = IO_SPACE_LIMIT,
.flags = IORESOURCE_IO,
};
static struct pci_controller loongson2e_pci_controller = {
.pci_ops = &bonito64_pci_ops,
.io_resource = &loongson2e_pci_io_resource,
.mem_resource = &loongson2e_pci_mem_resource,
.mem_offset = 0x00000000UL,
.io_offset = 0x00000000UL,
};
struct pci_controller *hose_head, **hose_tail = &hose_head;
register_pci_controller(&loongson2e_pci_controller);
void __devinit register_pci_controller(struct pci_controller *hose)
{
request_resource(&iomem_resource, hose->mem_resource);
request_resource(&ioport_resource, hose->io_resource);
*hose_tail = hose; /* 控制器加入到全局链表hose_head */
hose_tail = &hose->next;
}
pcibios_init()
负责扫描PCI控制器链表,进行PCI设备的查找与添加。
static int __init pcibios_init(void)
{
struct pci_controller *hose;
struct pci_bus *bus;
int next_busno;
int need_domain_info = 0;
/* 扫描PCI控制器链表,进行PCI设备的查找与添加 */
for (next_busno = 0, hose = hose_head; hose; hose = hose->next) {
if (!hose->iommu)
PCI_DMA_BUS_IS_PHYS = 1;
if (hose->get_busno && pci_probe_only)
next_busno = (*hose->get_busno)();
/* next_busno = 0,从0号总线开始扫描 */
bus = pci_scan_bus(next_busno, hose->pci_ops, hose);
hose->bus = bus;
need_domain_info = need_domain_info || hose->index;
hose->need_domain_info = need_domain_info;
if (bus) {
next_busno = bus->subordinate + 1;
/* Don't allow 8-bit bus number overflow inside the hose -
reserve some space for bridges. */
if (next_busno > 224) {
next_busno = 0;
need_domain_info = 1;
}
}
}
}
subsys_initcall(pcibios_init);
static inline struct pci_bus *pci_scan_bus(int bus, struct pci_ops *ops, void *sysdata)
{
struct pci_bus *root_bus;
root_bus = pci_scan_bus_parented(NULL, bus, ops, sysdata);
if (root_bus)
pci_bus_add_devices(root_bus);
return root_bus;
}
struct pci_bus *pci_scan_bus_parented(struct device *parent,
int bus, struct pci_ops *ops, void *sysdata)
{
struct pci_bus *b;
b = pci_create_bus(parent, bus, ops, sysdata);
if (b)
b->subordinate = pci_scan_child_bus(b);
return b;
}
pci_create_bus()
创建PCI总线。根据总线控制器struct pci_controller的index决定总线的域编号,总线号由pci_scan_bus()的第一参数决定。pcibios_init()是从0号总线开始扫描,因此对于loongson2e_pci_controller这个PCI控制器,pci_create_bus()
添加了一个域编号为0,总线编号为0的PCI桥设备"pci0000:00"
。
/sys
└── devices
└── pci0000:00
pci_create_bus()
还完成一些其他工作,把pci_bus.ops指向PCI控制器的pci_ops;把kset集合指向devices_subsys。
unsigned int pci_scan_child_bus(struct pci_bus *bus)
{
unsigned int devfn, pass, max = bus->secondary;
struct pci_dev *dev;
/* 每条PCI总线支持32个插槽,每个插槽支持8个功能设备,所以逻辑上总共是支持
256个设备,所以devfn在256(0x100)内进行扫描 */
for (devfn = 0; devfn < 0x100; devfn += 8)
pci_scan_slot(bus, devfn);
/*
* After performing arch-dependent fixup of the bus, look behind
* all PCI-to-PCI bridges on this bus.
*/
pr_debug("PCI: Fixups for bus %04x:%02x\n", pci_domain_nr(bus), bus->number);
pcibios_fixup_bus(bus);
for (pass=0; pass < 2; pass++)
list_for_each_entry(dev, &bus->devices, bus_list) {
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||
dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)
max = pci_scan_bridge(bus, dev, max, pass);
}
return max;
}
int pci_scan_slot(struct pci_bus *bus, int devfn)
{
int func, nr = 0;
int scan_all_fns;
for (func = 0; func < 8; func++, devfn++) {
struct pci_dev *dev;
dev = pci_scan_single_device(bus, devfn);
if (dev) {
nr++;
/*
* If this is a single function device,
* don't scan past the first function.
*/
if (!dev->multifunction) {
if (func > 0) {
dev->multifunction = 1;
} else {
break;
}
}
} else {
if (func == 0)
break;
}
}
return nr;
}
struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
{
struct pci_dev *dev;
pci_scan_device(bus, devfn); /* 扫描设备 */
pci_device_add(dev, bus); /* 添加设备 */
return dev;
}
这里我们先介绍对PCI设备配置空间的读取操作方式,在内核中是通过一系列的pci_bus_read_config_xxx()
与pci_bus_write_config_xxx()
进行读写,这此函数是由PCI_OP_READ
与PCI_OP_WRITE
宏定义实现的。函数内部是通过调用PCI总线控制器提供的pci_ops来对PCI设备的配置空间进行读写。
#define PCI_OP_READ(size,type,len) \
int pci_bus_read_config_##size \
(struct pci_bus *bus, unsigned int devfn, int pos, type *value) \
{ \
int res; \
unsigned long flags; \
u32 data = 0; \
if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \
spin_lock_irqsave(&pci_lock, flags); \
res = bus->ops->read(bus, devfn, pos, len, &data); \
*value = (type)data; \
spin_unlock_irqrestore(&pci_lock, flags); \
return res; \
}
#define PCI_OP_WRITE(size,type,len) \
int pci_bus_write_config_##size \
(struct pci_bus *bus, unsigned int devfn, int pos, type value) \
{ \
int res; \
unsigned long flags; \
if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \
spin_lock_irqsave(&pci_lock, flags); \
res = bus->ops->write(bus, devfn, pos, len, value); \
spin_unlock_irqrestore(&pci_lock, flags); \
return res; \
}
PCI_OP_READ(byte, u8, 1)
PCI_OP_READ(word, u16, 2)
PCI_OP_READ(dword, u32, 4)
PCI_OP_WRITE(byte, u8, 1)
PCI_OP_WRITE(word, u16, 2)
PCI_OP_WRITE(dword, u32, 4)
PCI配置空间寄存器:
PCI配置空间的地址格式:
接下来我们看下pci_scan_device()
干了什么。
#define PCI_VENDOR_ID 0x00 /* 16 bits */
#define PCI_HEADER_TYPE 0x0e /* 8 bits */
static struct pci_dev * __devinit
pci_scan_device(struct pci_bus *bus, int devfn)
{
struct pci_dev *dev;
u32 l;
u8 hdr_type;
int delay = 1;
if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))
return NULL;
/* some broken boards return 0 or ~0 if a slot is empty: */
if (l == 0xffffffff || l == 0x00000000 ||
l == 0x0000ffff || l == 0xffff0000)
return NULL;
/* Configuration request Retry Status */
while (l == 0xffff0001) {
msleep(delay);
delay *= 2;
if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))
return NULL;
/* Card hasn't responded in 60 seconds? Must be stuck. */
if (delay > 60 * 1000) {
printk(KERN_WARNING "Device %04x:%02x:%02x.%d not "
"responding\n", pci_domain_nr(bus),
bus->number, PCI_SLOT(devfn),
PCI_FUNC(devfn));
return NULL;
}
}
if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type))
return NULL;
dev = alloc_pci_dev();
if (!dev)
return NULL;
dev->bus = bus;
dev->sysdata = bus->sysdata;
dev->dev.parent = bus->bridge;
dev->dev.bus = &pci_bus_type;
dev->devfn = devfn;
dev->hdr_type = hdr_type & 0x7f;
dev->multifunction = !!(hdr_type & 0x80);
dev->vendor = l & 0xffff;
dev->device = (l >> 16) & 0xffff;
dev->cfg_size = pci_cfg_space_size(dev);
dev->error_state = pci_channel_io_normal;
set_pcie_port_type(dev);
/* Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer)
set this higher, assuming the system even supports it. */
dev->dma_mask = 0xffffffff;
if (pci_setup_device(dev) < 0) {
kfree(dev);
return NULL;
}
return dev;
}