第6章 platform总线
从input设备和字符设备的分析中,我们初步理解了总线发现设备、管理设备、配置设备的功能。一般来说,总线都是物理存在的,但是Linux系统提供了一种简单的总线——platform总线。
platform并不是一种物理存在的总线,而是个逻辑概念。现代的PC机系统通常提供了一条根总线(PCI总线)管理设备,但是有些设备并没有挂载在PCI总线上(比如键盘、鼠标的控制器),所以不能由PCI总线管理,于是Linux内核虚拟了platform总线来统一管理这种设备。
6.1 从驱动发现设备的过程
platform总线虽然是很简单的总线,但是一样具有总线的通用功能。通过分析这种简单的总线,可以帮助我们理解总线的概念和总线对设备和驱动的管理,方便后面对复杂总线的分析,比如说PCI总线。
我们选的例子是q40kbd,在drivers/input/serio目录里。这是一个键盘控制器驱动,它使用了platform总线。
6.1.1 驱动的初始化
设备驱动一般从它的初始化函数开始分析,q40kbd驱动的初始化函数是q40kbd_init,它的作用是把驱动程序注册到系统,代码如代码清单6-1所示。
代码清单6-1 q40kbd_init函数
static int __init q40kbd_init(void)
{
int error;
if (!MACH_IS_Q40)
return -EIO;
/*驱动作为platform总线驱动注册*/
error = platform_driver_register(&q40kbd_driver);
if (error)
return error;
/*分配一个platform设备*/
q40kbd_device = platform_device_alloc("q40kbd", -1);
if (!q40kbd_device)
goto err_unregister_driver;
/*platform设备注册*/
error = platform_device_add(q40kbd_device);
if (error)
goto err_free_device;
return 0;这段代码很简单,首先注册一个platform驱动,然后注册一个platform设备。这个过程显示了platform总线的用法。第3章介绍的PCI总线可以自动扫描设备,而platform总线是虚拟的总线,物理上并不存在,没有扫描设备的功能,所以platform总线需要直接注册设备。本节先从驱动的注册开始分析。
6.1.2 注册驱动
驱动注册调用的函数是platform_driver_register,它的代码如代码清单6-2所示。
代码清单6-2 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;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
return driver_register(&drv->driver);
}platform_driver_register函数把驱动的总线设置为platform总线,然后依次设置驱动的各个函数指针,最后调用driver_register函数注册驱动。driver_register函数很简单,初始化之后就调用bus_add_driver函数。
6.1.3 为总线增加一个驱动
bus_add_driver函数的名字显示,它的作用是为总线增加一个驱动。bus_add_driver的代码如代码清单6-3所示。
代码清单6-3 bus_add_driver函数
int bus_add_driver(struct device_driver * drv)
{
struct bus_type * bus = get_bus(drv->bus);
int error = 0;
if (bus) {
pr_debug("bus %s: add driver %s\n", bus->name, drv->name);
/*为kobject结构设置名字*/
error = kobject_set_name(&drv->kobj, "%s", drv->name);
if (error) {
put_bus(bus);
return error;
}
drv->kobj.kset = &bus->drivers;
/*为sysfs文件系统创建设备的相关文件*/
if ((error = kobject_register(&drv->kobj))) {
put_bus(bus);
return error;
}
driver_attach(drv);
/*驱动加入总线的驱动列表*/
klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
/*这行和下面一行也是为了在sysfs创建驱动的属性文件和模块*/
module_add_driver(drv->owner, drv);
driver_add_attrs(bus, drv);
add_bind_files(drv);
}
return error;
}bus_add_driver函数使用了kobject_register和driver_add_attrs等函数为sysfs文件系统创建设备驱动相关的目录和文件。第4章sysfs文件系统已经介绍过这种用法,就不再一一分析了。
6.1.4 驱动加载
真正执行驱动加载的是driver_attach函数,它的代码如代码清单6-4所示。
代码清单6-4 driver_attach函数
void driver_attach(struct device_driver * drv)
{
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;
int error = 0;
if (!bus)
return -EINVAL;
/*初始化一个klist,从设备start开始*/
klist_iter_init_node(&bus->klist_devices, &i,
(start ? &start->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);
klist_iter_exit(&i);
return error;
}bus_for_each_dev函数首先初始化一个klist_iter结构,目的是在双向链表中定位一个成员。klist结构和使用在lib目录下的klist.c文件中定义,代码和功能都非常简单,读者可以自行阅读理解。
6.1.5 遍历总线上已经挂载的设备
遍历总线上已经挂载的设备,起始位置是初始化klist_iter结构时设置的start设备,只遍历这个设备之后挂载的设备。当前场景设置的start设备为空,所以要遍历所有platform总线的设备。对每个设备调用fn函数指针。fn就是传入的函数指针__driver_attach,它的代码如代码清单6-5所示。
代码清单6-5 __driver_attach函数
static int __driver_attach(struct device * dev, void * data)
{
struct device_driver * drv = data;
if (dev->parent) /* Needed for USB */
down(&dev->parent->sem);
down(&dev->sem);
if (!dev->driver)
driver_probe_device(drv, dev);__driver_attach获取设备的锁之后,调用driver_probe_device函数,它的代码如代码清单6-6所示。
代码清单6-6 driver_probe_device函数
int driver_probe_device(struct device_driver * drv, struct device * dev)
{
int ret = 0;
/*先调用总线配置的match函数*/
if (drv->bus->match && !drv->bus->match(dev, drv))
goto Done;
pr_debug("%s: Matched Device %s with Driver %s\n",
drv->bus->name, dev->bus_id, drv->name);
dev->driver = drv;
/*总线的match函数通过后,继续调用总线的probe函数*/
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret) {
dev->driver = NULL;
goto ProbeFailed;
}
} else if (drv->probe) {
/*如果驱动提供了probe函数,则调用驱动的probe函数*/
ret = drv->probe(dev);
if (ret) {
dev->driver = NULL;
goto ProbeFailed;
}
}
/*设备发现了驱动,通过sysfs创建一些文件.和设备做符号链接*/
device_bind_driver(dev);driver_probe_device函数可以分为两个步骤。
- 第一步:调用总线提供的
match函数。如果检查通过,说明该设备和驱动是匹配的,设备所指向的驱动指针要赋值为当前驱动。 - 第二步:探测(probe)。首先调用总线提供的
probe函数,如果驱动有自己的probe函数,还要调用驱动的probe函数。
probe的目的是总线或者设备进一步的探测。比如硬盘控制器,它本身是个PCI设备,同时又提供硬盘接入的功能。那么它驱动的probe函数就要扫描SCSI总线,把所有接入的硬盘都扫描出来。
1. match函数
platform总线的match函数就是platform_match,它的代码如代码清单6-7所示。
代码清单6-7 platform_match函数
static int platform_match(struct device * dev, struct device_driver * drv)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}platform_match函数很简单,就是比较驱动的名字和设备的名字是否相同,相同就可以匹配。而注册的q40kbd驱动的名字是"q40kbd",也就是说如果找到这个名字的设备,两者就匹配了。
2. probe函数
platform总线的probe函数是platform_drv_probe,这个函数是个封装函数,它只是简单调用了驱动的probe函数。驱动的probe函数就是q40kbd_probe,它的代码如代码清单6-8所示。
代码清单6-8 q40kbd_probe函数
static int __devinit q40kbd_probe(struct platform_device *dev)
{
q40kbd_port = kzalloc(sizeof(struct serio), GFP_KERNEL);
if (!q40kbd_port)
return -ENOMEM;
q40kbd_port->id.type = SERIO_8042;
q40kbd_port->open = q40kbd_open;
q40kbd_port->close = q40kbd_close;
q40kbd_port->dev.parent = &dev->dev;
strlcpy(q40kbd_port->name, "Q40 Kbd Port", sizeof(q40kbd_port->name));
strlcpy(q40kbd_port->phys, "Q40", sizeof(q40kbd_port->phys));
serio_register_port(q40kbd_port);
printk(KERN_INFO "serio: Q40 kbd registered\n");
return 0;
}q40kbd_probe函数设置了一个serio结构变量,然后注册到系统。调用serio_register_port这关键的一步是实现什么?在platform总线里面又注册serio,又是为什么?这些问题放到下一章分析。
6.2 从设备找到驱动的过程
总结platform总线驱动的注册过程,我们发现和input设备驱动注册的过程很相像,都是逐个遍历设备,检查是否和驱动匹配。由此可以联想,platform总线加载设备的过程,应该也是遍历驱动,看是否和设备匹配。事实是否如此?我们从设备注册的过程开始分析。
6.2.1 注册设备和总线类型
注册设备使用的是platform_device_add函数,它的代码如代码清单6-9所示。
代码清单6-9 platform_device_add函数
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
if (!pdev)
return -EINVAL;
/*如果没父设备,就设置platform_bus为父设备*/
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;
/*设置设备的bus为platform_bus_type */
pdev->dev.bus = &platform_bus_type;
if (pdev->id != -1)
snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%u", pdev->name, pdev->id);
else
strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);platform_device_add函数第一部分是设置设备的父设备和总线类型。
6.2.2 注册设备的资源
platform_device_add函数第二部分注册设备的资源。
/*把设备I/O端口和I/O内存资源注册到系统*/
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;
p = r->parent;
if (!p) {
if (r->flags & IORESOURCE_MEM)
p = &iomem_resource;
else if (r->flags & IORESOURCE_IO)
p = &ioport_resource;
}
if (p && insert_resource(p, r)) {
printk(KERN_ERR
"%s: failed to claim resource %d\n",
pdev->dev.bus_id, i);
ret = -EBUSY;
goto failed;
}
}注意I/O端口(设备控制寄存器)和I/O内存注册到系统的部分代码。回顾设备基本概念一章,PCI总线通过扫描设备的配置文件,可以配置设备的I/O端口和I/O内存,而platform总线在物理上并不存在,自然不能自动扫描设备,那么如何配置设备的I/O端口和I/O内存?从内核驱动中,可以发现platform设备很多使用了探测的方式。一般是先往一个I/O端口写数据,看是否回应判断设备的I/O端口是否存在。至于本章分析的q40kbd设备,它甚至没有注册自己的I/O端口和内存,而是直接使用了缺省值。说明这是一种很古老的设备了。
pr_debug("Registering platform device '%s'. Parent at %s\n",
pdev->dev.bus_id, pdev->dev.parent->bus_id);
ret = device_add(&pdev->dev);6.2.3 增加一个设备对象
platform_device_add函数第三部分调用device_add增加一个设备对象,它的代码如代码清单6-10所示。
代码清单6-10 device_add函数
int device_add(struct device *dev)
{
struct device *parent = NULL;
char *class_name = NULL;
int error = -EINVAL;
dev = get_device(dev);
if (!dev || !strlen(dev->bus_id))
goto Error;
/*获得父设备*/
parent = get_device(dev->parent);
pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id);
/* first, register with generic layer. */
kobject_set_name(&dev->kobj, "%s", dev->bus_id);
if (parent)
dev->kobj.parent = &parent->kobj;
/*在sys目录生成设备目录*/
if ((error = kobject_add(&dev->kobj)))
goto Error;
/*设置uevent属性*/
dev->uevent_attr.attr.name = "uevent";
dev->uevent_attr.attr.mode = S_IWUSR;
if (dev->driver)
dev->uevent_attr.attr.owner = dev->driver->owner;
dev->uevent_attr.store = store_uevent;
device_create_file(dev, &dev->uevent_attr);函数device_add第一部分要为设备在sys目录创建目录和uevent属性文件。这些内容已经多次出现了,就不一一分析了。
/*设备的属性文件*/
if (MAJOR(dev->devt)) {
struct device_attribute *attr;
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
if (!attr) {
error = -ENOMEM;
goto PMError;
}
attr->attr.name = "dev";
attr->attr.mode = S_IRUGO;
if (dev->driver)
attr->attr.owner = dev->driver->owner;
attr->show = show_dev;
error = device_create_file(dev, attr);
if```c
if (error) {
kfree(attr);
goto attrError;
}
dev->devt_attr = attr;
}
/*创建设备类的符号链接*/
if (dev->class) {
sysfs_create_link(&dev->kobj, &dev->class->subsys.kset.kobj,
"subsystem");
sysfs_create_link(&dev->class->subsys.kset.kobj, &dev->kobj,
dev->bus_id);
sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device");
class_name = make_class_name(dev->class->name, &dev->kobj);
sysfs_create_link(&dev->parent->kobj, &dev->kobj, class_name);
}
/*设备的能源管理*/
if ((error = device_pm_add(dev)))
goto PMError;
if ((error = bus_add_device(dev)))
goto BusError;
kobject_uevent(&dev->kobj, KOBJ_ADD);函数device_add第二部分要为设备创建一堆的符号链接和设备属性文件等.这仍是已经熟悉的内容,略过.
bus_attach_device(dev);
/*设备加入父设备的链表*/
if (parent)
klist_add_tail(&dev->knode_parent, &parent->klist_children);函数device_add第三部分调用bus_attach_device把设备注册到总线,它的代码如代码清单6-11所示.
代码清单6-11 bus_attach_device
void bus_attach_device(struct device * dev)
{
struct bus_type * bus = dev->bus;
if (bus) {
device_attach(dev);
klist_add_tail(&dev->knode_bus, &bus->klist_devices);
}
}
int device_attach(struct device * dev)
{
int ret = 0;
down(&dev->sem);
if (dev->driver) {
/*设备已经有了驱动,执行sysfs的链接和链表操作*/
device_bind_driver(dev);
ret = 1;
} else
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
up(&dev->sem);
return ret;
}回顾上一节添加驱动到总线的处理函数是bus_for_each_dev,而本节添加设备到总线的处理函数是bus_for_each_drv.前者的作用是遍历设备,为驱动寻找合适的设备,后者的作用是遍历驱动,为设备寻找合适的驱动.这两个函数的实现几乎一样,前者已经分析过,读者可以自行分析.
6.3 本章小结
本章分析了platform总线,我们发现和input架构的管理方式类似,总线下面也有两个列表,一个是设备列表,另一个是驱动列表.无论注册设备还是驱动都要遍历总线,寻找匹配的驱动或者设备.
platform总线虽然简单但是很典型,基本说明了总线架构的概念.在内核驱动中,总线的使用很广泛,驱动目录里的scsi、usb、ieee1394、pcmcia等都是总线类型,它们也都使用了通用的设备和驱动管理.读者可以分析这些总线是怎么管理设备、怎么发现设备和管理驱动的,这样可以增强对设备管理架构的理解.
第7章 serio总线
从第5章input驱动的分析中,我们了解到驱动可以分为几个层次,驱动之间可以嵌套.和这种架构类似,总线也可以分为几个层次,一种类型的总线可以架构在另一种类型的总线之上.
第6章platform总线驱动提供的probe函数中,调用了serio_register_port函数.这引出了总线嵌套的概念以及在内核中极重要的总线适配的概念.
7.1 什么是总线适配器
我们知道计算机的体系架构中,PCI总线占有重要的地位,是连接CPU和外部设备的标准总线.而网卡、声卡、显卡、SCSI卡等设备很多都是以PCI卡的形式出现,并插入计算机的PCI插槽.这些设备中,声卡显卡加载驱动后,就可以直接读写操作.而像SCSI卡这种设备就比较麻烦了,因为SCSI卡本身又可以连接SCSI硬盘,因此加载SCSI卡的PCI驱动后,必须进行SCSI总线扫描,发现SCSI硬盘设备,才能正确地读写硬盘.
这里,SCSI卡就担任了总线桥的任务,它提供了总线之间的协议转换和互操作.像SCSI卡这样的设备,称为主机总线适配器(HBA,Host Bus Adapter),它一方面是PCI设备,另一方面它又管理SCSI总线的设备.
7.2 向serio总线注册设备
第6章的例子中提到,注册到platform总线的设备和驱动匹配之后,驱动本身会探测端口并注册到serio总线.serio_register_port函数就执行这个注册操作.serio总线建筑在platform总线之上,它们分工合作,共同提供了完整的驱动功能.
从架构角度来看,serio总线这种总线嵌套使用模式类似于总线适配器的模式,虽然从物理上来说,物理上存在的总线适配器和serio还是存在不同之处.
7.2.1 注册端口登记事件
我们接续第6章的分析,serio_register_port函数的作用是注册serio总线,如代码清单7-1所示.
// 代码清单7-1 serio_register_port函数(serio.c)
static inline void serio_register_port(struct serio *serio)
{
__serio_register_port(serio, THIS_MODULE);
}
void __serio_register_port(struct serio *serio, struct module *owner)
{
serio_init_port(serio);
/*注册一个SERIO_REGISTER_PORT事件*/
serio_queue_event(serio, owner, SERIO_REGISTER_PORT);
}serio_register_port函数的输入参数serio设置了端口类型是SERIO_8042,说明是8042兼容型的(I8042是Intel开发的键盘控制芯片).
serio_register_port函数封装了__serio_register_port函数,后者首先初始化一个serio结构,设置总线类型为serio总线,然后调用serio_queue_event函数向系统注册一个端口登记事件.
serio_queue_event函数作用是登记端口,如代码清单7-2所示.
// 代码清单7-2 serio_queue_event(serio.c)
static void serio_queue_event(void *object, struct module *owner,
enum serio_event_type event_type)
{
unsigned long flags;
struct serio_event *event;
spin_lock_irqsave(&serio_event_lock, flags);
list_for_each_entry_reverse(event, &serio_event_list, node) {
/*如果发现相同的event,退出*/
if (event->object == object) {
if (event->type == event_type)
goto out;
break;
}
}
if ((event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC)) == NULL) {
/*...省略部分代码...*/
}
event->type = event_type;
event->object = object;
event->owner = owner;
/*event加到链表尾,并唤醒线程*/
list_add_tail(&event->node, &serio_event_list);
wake_up(&serio_wait);
}serio_queue_event函数首先遍历内核的serio_event_list链表,检查所有注册的事件,如果发现有相同类型的事件,直接退出.这说明同一个端口只能注册一次,如果重复登记,把它们合并为一次.然后创建一个serio_event结构,设置这个serio_event结构的类型为端口注册,唤醒处理这个事件的任务.
这个注册事件由谁来处理?实际是serio_thread内核线程处理的,如代码清单7-3所示.
// 代码清单7-3 serio_thread(serio.c)
static int serio_thread(void *nothing)
{
do {
serio_handle_event();
/*内核线程进入睡眠状态。如果被唤醒,且事件链表非空则处理事件*/
wait_event_interruptible(serio_wait,
kthread_should_stop() || !list_empty(&serio_event_list));
try_to_freeze();
} while (!kthread_should_stop());
printk(KERN_DEBUG "serio: kseriod exiting\n");
return 0;
}serio_thread线程实际的处理由serio_handle_event函数执行,如代码清单7-4所示.
// 代码清单7-4 serio_handle_event函数
static void serio_handle_event(void)
{
struct serio_event *event;
mutex_lock(&serio_mutex);
if ((event = serio_get_event())) {
switch (event->type) {
case SERIO_REGISTER_PORT:
serio_add_port(event->object);
break;
default:
break;
}
}
...
}serio_handle_event函数处理各种事件,比如端口的注册和撤销、重新扫描端口等.对于SERIO_REGISTER_PORT事件,实际通过serio_add_port函数来处理,如代码清单7-5所示.
// 代码清单7-5 serio_add_port(serio.c)
static void serio_add_port(struct serio *serio)
{
int error;
if (serio->parent) {
/*修改端口父设备的参数*/
serio_pause_rx(serio->parent);
serio->parent->child = serio;
serio_continue_rx(serio->parent);
}
/*把串口设备加入全局链表*/
list_add_tail(&serio->node, &serio_list);
if (serio->start)
serio->start(serio);
error = device_add(&serio->dev);
if (error) {
/*...省略部分代码...*/
} else {
serio->registered = 1;
error = sysfs_create_group(&serio->dev.kobj, &serio_id_group);
}
}serio_add_port函数要调用serio结构的start函数,因为q40kbd注册端口的时候,并没有设置start函数,所以此处不会执行.
7.2.2 遍历总线的驱动
serio_add_port函数的关键部分是device_add函数.在第6章platform总线的分析中,我们已经分析过这个函数,它的作用就是遍历总线的驱动,通过总线提供的match函数找到一个合适的驱动,然后调用总线的probe函数.
我们首先分析serio总线的match函数,然后再分析probe函数.
1. match函数
serio总线的match函数定义在serio.c文件,它的真实名字是serio_bus_match,如代码清单7-6所示.
// 代码清单7-6 serio_bus_match(serio.c)
static int serio_bus_match(struct device *dev, struct device_driver *drv)
{
struct serio *serio = to_serio_port(dev);
struct serio_driver *serio_drv = to_serio_driver(drv);
/*如果指定了手工绑定,则匹配不成功*/
if (serio->manual_bind || serio_drv->manual_bind)
return 0;
return serio_match_port(serio_drv->id_table, serio);
}serio_bus_match函数在设备注册时多次调用,它的输入参数是serio总线上注册的每一个驱动,需要逐个检查端口设备serio和驱动的匹配情况.如果设备或者驱动设置了手工绑定,直接返回,否则调用serio_match_port函数检查设备和驱动的ID表是否匹配,如代码清单7-7所示.
// 代码清单7-7 serio_match_port(serio.c)
static int serio_match_port(const struct serio_device_id *ids,
struct serio *serio)
{
while (ids->type || ids->proto) {
if ((ids->type == SERIO_ANY || ids->type == serio->id.type) &&
(ids->proto == SERIO_ANY || ids->proto == serio->id.proto) &&
(ids->extra == SERIO_ANY || ids->extra == serio->id.extra) &&
(ids->id == SERIO_ANY || ids->id == serio->id.id))
return 1;
ids++;
}
return 0;
}serio_match_port函数很简单,就是检查设备和驱动ID表的type、proto等参数是否相同.登记设备的时候,赋予的type是SERIO_8042,搜索内核代码,和它匹配的驱动就是目录drivers/input/keyboard下的atkbd.c文件.
2. probe函数
现在返回device_add函数,设备和驱动匹配之后,首先调用serio总线提供的probe函数,也就是serio_driver_probe函数,如代码清单7-8所示.
// 代码清单7-8 serio_driver_probe(serio.c)
static int serio_driver_probe(struct device *dev)
{
struct serio *serio = to_serio_port(dev);
struct serio_driver *drv = to_serio_driver(dev->driver);
return serio_connect_driver(serio, drv);
}
static int serio_connect_driver(struct serio *serio, struct serio_driver *drv)
{
int retval;
mutex_lock(&serio->drv_mutex);
retval = drv->connect(serio, drv);
mutex_unlock(&serio->drv_mutex);
return retval;
}serio_driver_probe函数直接调用了serio_connect_driver函数,最终调用驱动提供的connect函数,就是atkbd_connect,如代码清单7-9所示.
// 代码清单7-9 atkbd_connect(atkbd.c)
static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
{
struct atkbd *atkbd;
struct input_dev *dev;
int err = -ENOMEM;
atkbd = kzalloc(sizeof(struct atkbd), GFP_KERNEL);
dev = input_allocate_device();
if (!atkbd || !dev)
goto fail;
/*atkbd的dev赋值为创建的input设备。input要为这个设备加载对应的input handler*/
atkbd->dev = dev;
/*创建一个工作队列,这个队列做什么?就是处理input_event不处理的事件*/
ps2_init(&atkbd->ps2dev, serio);
INIT_WORK(&atkbd->event_work, atkbd_event_work, atkbd);
mutex_init(&atkbd->event_mutex);
switch (serio->id.type) {
/*对8042_XL芯片,设置translated成员为1*/
case SERIO_8042_XL:
atkbd->translated = 1;
case SERIO_8042:
/*如果赋值write函数,设置write为1*/
if (serio->write)
atkbd->write = 1;
break;
}
atkbd->softraw = atkbd_softraw;
atkbd->softrepeat = atkbd_softrepeat;
atkbd->scroll = atkbd_scroll;
if (atkbd->softrepeat)
atkbd->softraw = 1;
serio_set_drvdata(serio, atkbd);
...
}atkbd_connect函数第一部分创建一个input设备和一个atkbd结构.atkbd是input设备的控制结构,它封装了input设备的重要信息.
/*打开serio登记的open函数。serio在q40kbd里面创建的*/
err = serio_open(serio, drv);
if (err)
goto fail;
if (atkbd->write) {
if (atkbd_probe(atkbd)) {
serio_close(serio);
err = -ENODEV;
goto fail;
}
atkbd->set = atkbd_select_set(atkbd, atkbd_set, atkbd_extra);
atkbd_activate(atkbd);
} else {
atkbd->set = 2;
atkbd->id = 0xab00;
}
/* 根据set值选择码表*/
atkbd_set_keycode_table(atkbd);
/*设置input设备的属性*/
atkbd_set_device_attrs(atkbd);
device_create_file(&serio->dev, &atkbd_attr_extra);
device_create_file(&serio->dev, &atkbd_attr_scroll);
device_create_file(&serio->dev, &atkbd_attr_set);
device_create_file(&serio->dev, &atkbd_attr_softrepeat);
device_create_file(&serio->dev, &atkbd_attr_softraw);
atkbd_enable(atkbd);
input_register_device(atkbd->dev);
return 0;
...
}atkbd_connect函数第二部分设置atkbd结构的属性和input设备的属性.因为q40kbd没有设置write函数,所以本场景设置set值为2,然后要根据set值设置atkbd结构使用的码表.码表作用是把键盘输入设备的原始数据转换为统一的键值,供上层应用使用.
7.2.3 注册input设备
调用input_register_device注册input设备.这个函数在第5章分析过,它最终要为设备找到匹配的驱动.这个驱动就是键盘驱动,根据我们前面对input的分析,这个驱动通过input_register_handler函数加入到input的驱动列表.
atkbd_connect函数开始部分调用了serio注册的open函数,这个函数就是q40kbd_open,它的作用是打开键盘设备,以及设置设备的中断信息,如代码清单7-10所示.
// 代码清单7-10 q40kbd_open
static int q40kbd_open(struct serio *port)
{
q40kbd_flush();
if (request_irq(Q40_IRQ_KEYBOARD, q40kbd_interrupt, 0, "q40kbd", port)) {
printk(KERN_ERR "q40kbd.c: Can't get irq %d.\n", Q40_IRQ_KEYBOARD);
return -EBUSY;
}
/* off we go */
/*写芯片的控制端口*/
master_outb(-1, KEYBOARD_UNLOCK_REG);
master_outb(1, KEY_IRQ_ENABLE_REG);
return 0;
}q40kbd_open函数要申请中断并且设置设备端口.这个芯片是管理键盘的,设置芯片的I/O端口启动了键盘.而这里的中断是物理上存在的,它的中断处理函数需要调用serio设备、input设备等一系列虚拟设备的处理函数,把来自物理底层的事件一步步报上去.
7.3 虚拟键盘驱动
上一节注册了一个input键盘设备,注册设备的同时需要找到它的驱动.
7.3.1 键盘驱动的初始化
input作为一个设备框架,它提供的键盘驱动位于目录drivers/char下的keyboard.c文件.
键盘驱动的初始化函数是kbd_init,如代码清单7-11所示.
// 代码清单7-11 kbd_init函数(keyboard.c)
int __init kbd_init(void)
{
int i;
for (i = 0; i < MAX_NR_CONSOLES; i++) {
kbd_table[i].ledflagstate = KBD_DEFLEDS;
kbd_table[i].default_ledflagstate = KBD_DEFLEDS;
kbd_table[i].ledmode = LED_SHOW_FLAGS;
kbd_table[i].lockstate = KBD_DEFLOCK;
kbd_table[i].slockstate = 0;
kbd_table[i].modeflags = KBD_DEFMODE;
kbd_table[i].kbdmode = VC_XLATE;
}
input_register_handler(&kbd_handler);
/*启动键盘的tasklet*/
tasklet_enable(&keyboard_tasklet);
tasklet_schedule(&keyboard_tasklet);
return 0;
}kbd_init函数设置ID表后,调用input_register_handler函数注册一个input驱动.
此时需要回顾input设备的注册过程,在设备匹配到驱动以后,还要调用驱动提供的connect函数和start函数.
7.3.2 与设备建立连接
首先我们从connect函数开始分析,这个函数的全名是kbd_connect,作用是和具体的设备建立连接,执行打开设备的过程,如代码清单7-12所示.
// 代码清单7-12 kbd_connect(keyboard.c)
```c
// 代码清单7-12 kbd_connect(keyboard.c)
static struct input_handle *kbd_connect(struct input_handler *handler,
struct input_dev *dev,
struct input_device_id *id)
{
struct input_handle *handle;
int i;
/*从保留键开始测试*/
for (i = KEY_RESERVED; i < BTN_MISC; i++)
if (test_bit(i, dev->keybit))
break;
if (i == BTN_MISC && !test_bit(EV_SND, dev->evbit))
return NULL;
handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
if (!handle)
return NULL;
handle->dev = dev;
handle->handler = handler;
handle->name = "kbd";
input_open_device(handle);
return handle;
}atkbd_connect函数创建一个input_handle结构,然后调用input_open_device函数执行input设备的open函数。对这个例子来说,因为设备没有注册open函数,实际上不会执行open。
7.3.3 启动键盘设备
返回input设备的注册过程,驱动提供的start函数是kbd_start,它的作用是点亮键盘的LED灯,启动键盘设备。如代码清单7-13所示。
// 代码清单7-13 kbd_start(keyboard.c)
static void kbd_start(struct input_handle *handle)
{
unsigned char leds = ledstate;
tasklet_disable(&keyboard_tasklet);
if (leds != 0xff) {
input_inject_event(handle, EV_LED, LED_SCROLLL, !!(leds & 0x01));
input_inject_event(handle, EV_LED, LED_NUML, !!(leds & 0x02));
input_inject_event(handle, EV_LED, LED_CAPSL, !!(leds & 0x04));
input_inject_event(handle, EV_SYN, SYN_REPORT, 0);
}
tasklet_enable(&keyboard_tasklet);
}函数kbd_start开始控制真正的键盘设备,它的作用是刷新键盘的LED灯,这就是我们在系统启动时候看到的键盘闪烁。函数input_inject_event的作用是发送LED事件,让LED灯闪烁,这个功能必须由芯片来触发。根据目前学到的知识可以推测,这个过程一定要通过input调用serio总线上的设备驱动,写事件到相应的I/O端口。读者可以分析一下这个流程。
明白了原理和框架,其实内核的很多代码已经可以整理出大致的过程,只是具体实现的方法不同。因为我们选的是一个旧的键盘设备,这个芯片实际是不能驱动键盘闪烁的。
7.3.4 输入设备和主机系统之间的事件
输入设备和主机系统之间的事件有多种,这些事件类型如表7-1所示。
表7-1 输入设备和主机系统之间的事件
| 事件类型 | 说明 |
|---|---|
| EV_KEY | 按键事件 |
| EV_REL | 相对坐标事件(如鼠标移动) |
| EV_ABS | 绝对坐标事件(如触摸屏) |
| EV_MSC | 其他杂项事件 |
| EV_LED | LED指示灯事件 |
| EV_SND | 声音事件 |
| EV_REP | 重复按键事件 |
| EV_FF | 力反馈事件 |
| EV_PWR | 电源管理事件 |
| EV_FF_STATUS | 力反馈状态事件 |
| EV_SW | 开关事件 |
| … | … |
这些事件类型很多,每种事件里面又定义了很多子事件。针对具体事件的处理,需要相关驱动的开发人员仔细设计。
7.4 键盘中断
现在总结设备和驱动处理的整个路径。最底层的设备是Q40kbd,它是一个platform总线设备,和platform总线的驱动匹配后,注册了一个serio端口设备。serio设备也需要匹配serio总线的驱动atkbd,然后调用驱动的connect函数创建并注册一个input设备。input设备再次匹配input之上登记的驱动,最终找到input驱动kbd。
通过一层层的总线、设备和驱动的搜索和匹配,内核最终建立了设备的驱动架构。一旦设备和驱动都就位了,那么就可以从键盘接收用户的输入了。
7.4.1 q40kbd设备的中断处理
最底层的设备是q40kbd,当用户按键的时候,触发它的中断,然后系统将一层层往上报按键事件,所以需要从q40kbd设备的中断处理函数开始分析。中断处理函数q40kbd_interrupt如代码清单7-14所示。
// 代码清单7-14 q40kbd_interrupt(q40kbd.c)
static irqreturn_t q40kbd_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned long flags;
spin_lock_irqsave(&q40kbd_lock, flags);
if (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG))
serio_interrupt(q40kbd_port, master_inb(KEYCODE_REG), 0, regs);
/*向I/O端口KEYBOARD_UNLOCK_REG写数据*/
master_outb(-1, KEYBOARD_UNLOCK_REG);
spin_unlock_irqrestore(&q40kbd_lock, flags);
return IRQ_HANDLED;
}q40kbd_interrupt读I/O端口KEYCODE_REG的数据,作为输入参数调用serio_interrupt函数来处理。
7.4.2 serio总线的中断处理
serio_interrupt是serio总线提供的中断处理函数,它要进一步调用驱动提供的中断处理函数,如代码清单7-15所示。
代码清单7-15 serio_interrupt(serio.c)
irqreturn_t serio_interrupt(struct serio *serio, unsigned char data, unsigned int dfl, struct pt_regs *regs) { /* ......省略部分代码 */ if (likely(serio->drv)) { ret = serio->drv->interrupt(serio, data, dfl, regs); } else if (!dfl && serio->registered) { /* serio重新扫描,和前面的设备注册差不多的流程 */ serio_rescan(serio); ret = IRQ_HANDLED; }
函数serio_interrupt检查是否已经匹配驱动,如果是,调用驱动的中断处理函数。
7.4.3 驱动提供的中断处理
serio驱动是什么?就是前一节分析的atkbd,所以中断处理函数就是atkbd_interrupt,如代码清单7-16所示。
代码清单7-16 atkbd_interrupt(atkbd.c)
static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data, unsigned int flags, struct pt_regs *regs) { /* ......省略变量定义代码 */ /* 如果需要,回一个ACK给键盘 */ if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_ACK)) if (ps2_handle_ack(&atkbd->ps2dev, data)) goto out; /* 如果有进程在等键盘,唤醒等待的进程 */ if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_CMD)) if (ps2_handle_response(&atkbd->ps2dev, data)) goto out; if (!atkbd->enabled) goto out; /* 上报原始的扫描码 */ input_event(dev, EV_MSC, MSC_RAW, code); /* ......省略部分代码 */ /* 转换扫描码为通用的字符码,码表就是atkbd_connect时注册进去的码表 */ keycode = atkbd->keycode[code]; if (keycode != ATKBD_KEY_NULL) input_event(dev, EV_MSC, MSC_SCAN, code);
函数atkbd_interrupt要三次调用input_event上报输入的数据:第一次上报原始的扫描码;第二次也是上报原始的扫描码,只是为了兼容性考虑,对扫描码做了处理;第三次上报用户真正需要的按键值。
input_regs(dev, regs);
input_event(dev, EV_KEY, keycode, value);
/* 同步事件,上报结束 */
input_sync(dev);
if (value && add_release_event) {
input_report_key(dev, keycode, 0);
input_sync(dev);
}
}
/* ......省略部分代码 */函数atkbd_interrupt第二部分上报真正的按键值。按键值根据设备的转换码表将原始的扫描码转换后得到,对上层应用来说,按键值是统一的。
上报输入事件通过函数input_event完成,它要处理同步事件、按键事件、绝对坐标、LED灯、声音等各种事件,而我们最关心的是按键事件,所以省略了其他事件的处理代码,如代码清单7-17所示。
代码清单7-17 input_event(input.c)
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct input_handle *handle; if (type > EV_MAX || !test_bit(type, dev->evbit)) return; /* 产生随机数,这里利用按键产生随机数 */ add_input_randomness(type, code, value); switch (type) { /* EV_KEY是按键事件 */ case EV_KEY: if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value) return; if (value == 2) break; change_bit(code, dev->key); if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) { dev->repeat_key = code; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY])); } break;
input_event函数的第一部分是对各种输入事件进行处理。对按键事件而言,如果设备要求周期重复上报按键,要启动input设备的定时器,启动时间为当前时间加上设备的延时时间。当启动时间到达后,重复上报按键值。
if (dev->grab)
dev->grab->handler->event(dev->grab, type, code, value);
else
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value);input_event函数的第二部分调用input驱动对输入事件进行处理。如果设备已经有了指定的input_handler,也就是有了指定的驱动,则调用驱动的event函数,否则遍历设备的input_handler链表,逐个调用驱动的event函数。用户的按键经过层层的驱动到最后,终于汇入了input字符设备定义的架构,此时原始的按键数据已经变成了标准的输入键值。
input键盘设备驱动的event函数其实就是kbd_event,它的代码如代码清单7-18所示。
代码清单7-18 kbd_event(keyboard.c)
static void kbd_event(struct input_handle *handle, unsigned int event_type, unsigned int event_code, int value) { /* 原始码由kbd_rawcode处理 */ if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev)) kbd_rawcode(value); /* 标准的字符码,由kbd_keycode处理 */ if (event_type == EV_KEY) kbd_keycode(event_code, value, HW_RAW(handle->dev), handle->dev); tasklet_schedule(&keyboard_tasklet); do_poke_blanked_console = 1; schedule_console_callback();
kbd_event根据上报输入事件的类型分别处理,如果上报原始扫描码,调用kbd_rawcode函数处理,如果是经过转换的键值,则调用kbd_keycode函数处理。这两个函数最终都要调用put_queue函数把按键数据送到控制台的输入缓冲区里。
控制台是Linux输入/输出系统的一个重要概念。控制台简单地把用户的输入发送到计算机处理,然后再把处理结果返回给用户。从软件角度看,控制台提供给用户一个使用命令行的字符界面,用于接收用户输入和反馈输出结果。由于现代计算机功能强大,可以利用硬件模拟出来很多控制台界面,Linux系统支持多个控制台,而用户的输入由当前活跃的控制台接收。kbd_event把用户的按键数据送到当前控制台的输入缓冲区,应用程序调用标准的库函数scanf读控制台的输入缓冲区,就可以获得用户的按键值。
这里启用了一个tasklet_schedule,处理led事件等耗时间的操作,用软中断继续处理。回顾基础知识一节,软中断的好处是软中断里面可以打开中断。所以一些和硬件相关的操作放在中断里面,而和硬件无关的逻辑等放在软中断,可以减少长时间关中断的代价(中断的处理代码也可以打开中断,软中断处理也可能需要关闭中断,是否开闭中断,需要仔细设计)。
7.5 本章小结
platform总线和serio总线构成了总线的嵌套关系。这个例子比较简单,但是却很典型。对于一个真正的计算机系统来说,最重要的总线是PCI总线,大多数设备都是PCI设备,而总线适配器(HBA)一般都是挂载到PCI总线上。作为最重要的总线,下章将分析PCI总线。
第8章 PCI总线
前面的章节先后介绍了input字符设备以及platform总线。input设备是个逻辑概念,它建立在真正的物理设备之上,是对设备功能的抽象表达。本章要重点分析真实的物理设备和PCI总线的应用。
8.1 深入理解PCI总线
PCI总线是现代计算机系统中最重要的总线,当PCI总线扫描到PCI设备后,已经为设备设置了DMA信息、中断信息和I/O端口、I/O内存信息,这些信息是实现PCI设备驱动的基础,也是深入理解PCI设备驱动的重点。
8.1.1 PCI设备工作原理
PCI设备具有自己的设备配置信息,也具备I/O端口和I/O内存,这些端口和内存构成一个独立的地址空间,就是PCI总线地址空间。这个空间和主存的空间是隔离的。彼此互相独立,CPU要通过主桥(host bridge)才能访问PCI地址空间,而PCI设备也要通过主桥才能访问主存。
主桥可以直接产生一条PCI总线,这条总线也是主桥管理的第一条总线,也是0号PCI总线。在内核代码中,会直接使用这条0号总线。从该总线还可以扩展出一系列的PCI总线,称为PCI桥,以主桥为根节点,这些桥和设备形成了一颗PCI树。这些扩展出来的PCI总线都可以连接PCI设备,但是在一条PCI总线上,最多只能挂载256个PCI设备(PCI桥本身也是一个PCI设备)。
如图8-1所示,PCI 0号总线下面有四个PCI设备,其中一个是桥设备,这个桥设备引出了PCI总线1,它的下面又可以挂载256个PCI设备。
图8-1 PCI设备和总线图
(图中:PCI 0号总线连接设备0、设备1、设备2、桥设备;桥设备引出PCI 1号总线,连接设备3、设备4等)
8.1.2 PCI总线域
PCI设备具有一个8 bit的总线号,一个5 bit的设备编号以及一个3 bit的功能编号。
因此一个主桥下最多拥有256个总线,这个对大型系统上而言是不够的,为此Linux引入PCI域的概念。每个PCI域可拥有256个总线,而每个总线可有32个设备。如果设备是多功能设备,还可以支持8个子设备。图8-2给出了一个简单系统的PCI设备图。
图8-2 PCI设备图
(图中:域0,总线00,设备01,功能0)
以图8-2显示的PCI设备00:01:0为例,分隔符将设备地址分为三个区间,首区间00是总线号,中间区间01是设备编号,末尾区间0是功能编号,这是一个多功能PCI设备。
8.1.3 PCI资源管理
为了管理PCI设备的I/O端口和I/O内存,内核定义了一个resource结构。
首先分析代表I/O端口的resource,如下所示:
struct resource ioport_resource = {
.name = "PCI IO",
.start = 0,
.end = IO_SPACE_LIMIT,
.flags = IORESOURCE_IO,
};ioport_resource起始地址为0,结束地址为0xffff,这个变量定义了全部的I/O端口地址空间,每个PCI设备新加入系统,都要检查它配置空间的I/O端口的结束地址是否大于0xffff,和现存设备是否有冲突,是否可以插入I/O端口地址空间。
I/O内存则是另一个resource结构,如下所示:
struct resource iomem_resource = {
.name = "PCI mem",
.start = 0,
.end = -1,
.flags = IORESOURCE_MEM,
};I/O内存需要映射到主机内存地址空间,所以它的结束地址为–1。当PCI设备加入系统的时候,同样要检查它配置空间的I/O内存和其他设备是否有冲突,是否可以插入I/O内存地址空间。
8.1.4 PCI配置空间读取和设置
第3章已经介绍了,对PCI设备的读取和设置要通过I/O指令读一个特殊的I/O端口空间来完成。内核中提供了一个pci_raw_ops结构来控制配置空间的读写。这个结构的读写函数通常被设置为pci_conf1_read函数和pci_conf1_write函数,通过它们执行对PCI配置空间的读写。
有必要分析pci_conf1_read函数对PCI配置空间的读取过程,如代码清单8-1所示。
代码清单8-1 pci_conf1_read(direct.c)
int pci_conf1_read(unsigned int seg, unsigned int bus, unsigned int devfn, int reg, int len, u32 *value) { /* ......省略部分代码 */ outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8); switch (len) { case 1: *value = inb(0xCFC + (reg & 3)); break; case 2: *value = inw(0xCFC + (reg & 2)); break; case 4: *value = inl(0xCFC); break; }
pci_conf1_read函数首先往I/O端口0xCF8写入一个PCI地址,然后从I/O端口0xCFC读该地址的配置信息。pci_conf1_write函数和pci_conf1_read函数很类似,此处不再分析。
8.2 PCI设备扫描过程
Linux内核具备多种PCI的扫描方式,它们彼此之间大同小异。作为例子,本节选择典型的传统扫描模式,这种扫描模式定义在legacy.c文件。
传统扫描模式的执行函数是pci_legacy_init,如代码清单8-2所示。
代码清单8-2 pci_legacy_init函数(legacy.c)
static int __init pci_legacy_init(void) { if (!raw_pci_ops) { printk("PCI: System does not support PCI\n"); return 0; } /* 如果曾经扫描过,则不再扫描 */ if (pcibios_scanned++) return 0; printk("PCI: Probing PCI hardware\n"); /* 扫描0号总线 */ pci_root_bus = pcibios_scan_root(0); if (pci_root_bus) /* 扫描出来的设备加入pci总线 */ pci_bus_add_devices(pci_root_bus); /* 对bios提供的PCI总线进行扫描 */ pcibios_fixup_peer_bridges(); return 0; }
pci_legacy_init函数首先扫描0号总线,如果扫描成功,则把0号总线作为系统的根总线。然后,要把0号总线上扫描出来的所有设备都加入一个全局的PCI设备链表。最后,调用pcibios_fixup_peer_bridges对BIOS提供的PCI总线做进一步的扫描。
8.2.1 扫描0号总线
扫描0号总线调用的是pcibios_scan_root函数,代码如代码清单8-3所示。
代码清单8-3 pcibios_scan_root函数
struct pci_bus * __devinit pcibios_scan_root(int busnum) { struct pci_bus *bus = NULL; dmi_check_system(pciprobe_dmi_table); while ((bus = pci_find_next_bus(bus)) != NULL) { if (bus->number == busnum) { /* Already scanned */ return bus; } } printk(KERN_DEBUG "PCI: Probing PCI hardware (bus %02x)\n", busnum); return pci_scan_bus_parented(NULL, busnum, &pci_root_ops, NULL); }
pcibios_scan_root函数首先逐个遍历所有的PCI总线,检查指定的总线是否已经扫描过,如果已经扫描,则直接返回。如果(此处无更多内容可延续,原始文本在此结束。)
第6-8章 总线机制
8.2.2 扫描总线上的PCI设备
pci_scan_bus_parented函数的功能是扫描总线上可能接入的256个PCI设备,如果扫描到的PCI设备是个桥设备,还要递归扫描桥设备,把桥设备可能接入的PCI设备扫描出来,这个函数的代码如代码清单8-4所示。
/* 代码清单8-4 pci_scan_bus_parented函数 */
struct pci_bus * __devinit 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_scan_bus_parented函数可分成两个步骤:第一步是创建一个总线对象,第二步是调用pci_scan_child_bus对创建的总线对象进行递归扫描。
1. 创建一个总线对象
首先分析创建总线对象的pci_create_bus函数,它的parent参数为空,说明这条总线没有父设备,是一条根总线。
1)pci_create_bus第一部分是创建一个总线对象和一个设备对象。
它的代码如代码清单8-5所示。
/* 代码清单8-5 pci_create_bus函数 */
struct pci_bus * __devinit pci_create_bus(struct device *parent,
int bus, struct pci_ops *ops, void *sysdata)
{
int error;
struct pci_bus *b;
struct device *dev;
/*申请一个PCI总线结构*/
b = pci_alloc_bus();
if (!b)
return NULL;
/*申请一个dev结构*/
dev = kmalloc(sizeof(*dev), GFP_KERNEL);
....../*省略部分代码*/
b->sysdata = sysdata;
b->ops = ops;
/*检查是否被创建*/
if (pci_find_bus(pci_domain_nr(b), bus)) {
/* If we already got to this bus through a different bridge,
pr_debug("PCI: Bus %04x:%02x already known\n", pci_domain_nr(b), bus);
goto err_out;
}
/*创建的总线加入PCI总线链表*/
down_write(&pci_bus_sem);
list_add_tail(&b->node, &pci_root_buses);
up_write(&pci_bus_sem);NOTE
PCI总线本身就是一个设备,所以除了总线对象外,还要为它创建一个设备对象。总线对象要链接到一个全局的链表头
pci_root_buses,这样通过这条链表,就可以遍历所有的PCI总线。
2)pci_create_bus函数第二部分执行结构和对象的注册。
/*设置dev结构并登记到系统*/
memset(dev, 0, sizeof(*dev));
dev->parent = parent;
dev->release = pci_release_bus_bridge_dev;
sprintf(dev->bus_id, "pci%04x:%02x", pci_domain_nr(b), bus);
error = device_register(dev);
if (error)
goto dev_reg_err;
b->bridge = get_device(dev);
b->class_dev.class = &pcibus_class;
sprintf(b->class_dev.class_id, "%04x:%02x", pci_domain_nr(b), bus);
error = class_device_register(&b->class_dev);
if (error)
goto class_dev_reg_err;
error = class_device_create_file(&b->class_dev, &class_device_attr_cpuaffinity);
if (error)
goto class_dev_create_file_err;
/* Create legacy_io and legacy_mem files for this bus */
pci_create_legacy_files(b);
error = sysfs_create_link(&b->class_dev.kobj, &b->bridge->kobj, "bridge");
if (error)
goto sys_create_link_err;
b->number = b->secondary = bus;首先把设备对象注册到系统,这个过程在第6章已经分析过了。其次是注册PCI总线类和为sysfs文件系统创建符号连接。
3)pci_create_bus函数最后设置PCI总线的资源。
/*设置总线的资源*/
b->resource[0] = &ioport_resource;
b->resource[1] = &iomem_resource;
return b;TIP
PCI总线的资源有两类,一类是I/O端口,另一类是I/O内存。总线上所有设备的端口和内存组成了一个空间,为了避免冲突,内核设置了全局的数据结构
ioport_resource和iomem_resource,分别保存所有的I/O端口资源和所有的I/O内存资源。
2. 扫描总线
现在返回pci_scan_bus_parented函数,当成功创建总线对象后,开始扫描这条总线。扫描总线调用pci_scan_child_bus函数,如代码清单8-6所示。
/* 代码清单8-6 pci_scan_child_bus(probe.c) */
unsigned int __devinit pci_scan_child_bus(struct pci_bus *bus)
{
unsigned int devfn, pass, max = bus->secondary;
struct pci_dev *dev;
pr_debug("PCI: Scanning bus %04x:%02x\n", pci_domain_nr(bus), bus->number);
/* Go find them, Rover! *//*扫描总线下面的256个设备*/
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);
/*扫描子总线,分两次扫描,第一次是扫描bios发现的总线*/
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);
}NOTE
扫描PCI总线是个递归的过程。每条PCI总线可以配置32个多功能设备,每个多功能设备又可以安装8个子设备,总共就是256个设备。这256个设备中,有的设备可能是PCI桥,每个PCI桥下面又可以接入256个设备。通过函数
pci_scan_slot扫描每个多功能设备的8个子设备,通过pci_scan_bridge函数扫描PCI桥设备。对于桥设备,还要递归调用pci_scan_child_bus函数扫描本桥设备下面可能接入的PCI设备。
8.2.3 扫描多功能设备
扫描PCI多功能设备和扫描桥设备有重复的地方,因此本节以扫描多功能设备的函数pci_scan_slot为例进行分析,它的代码如代码清单8-7所示。
/* 代码清单8-7 pci_scan_slot函数(probe.c) */
int __devinit pci_scan_slot(struct pci_bus *bus, int devfn)
{
int func, nr = 0;
int scan_all_fns;
scan_all_fns = pcibios_scan_all_fns(bus, devfn);
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 && !scan_all_fns)
break;
}
}
return nr;
}pci_scan_slot函数从0号设备开始进行扫描,如果扫描发现是单功能设备,不再继续扫描,如果发现是多功能设备,则进行8次扫描。
8.2.4 扫描单个设备
扫描单个设备调用pci_scan_single_device函数,输入参数是总线结构和设备功能号,如代码清单8-8所示。
/* 代码清单8-8 pci_scan_single_device函数(probe.c) */
pci_scan_single_device(struct pci_bus *bus, int devfn)
{
struct pci_dev *dev;
dev = pci_scan_device(bus, devfn);
if (!dev)
return NULL;
pci_device_add(dev, bus);
pci_scan_msi_device(dev);
return dev;
} pci_scan_single_device函数调用pci_scan_device扫描设备,扫描成功后把设备加入总线的设备链表。最后的pci_scan_msi_device函数是检查设备的MSI能力,MSI和设备的中断有关,当前可以不关心。
8.2.5 扫描设备信息
扫描PCI设备通过读取PCI设备的配置空间完成,这部分原理在第3章已经介绍过。扫描设备的代码在pci_scan_device函数中,如代码清单8-9所示。
/* 代码清单8-9 pci_scan_device(probe.c) */
pci_scan_device(struct pci_bus *bus, int devfn)
{
struct pci_dev *dev;
u32 l;
u8 hdr_type;
int delay = 1;
/*读PCI设备制造商的ID*/
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;
/*处理需要重复读配置信息的情况*/
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;
}
}pci_scan_device函数第一部分读PCI设备制造商的ID,所有的制造商都要分配厂商ID号,从ID就可以获得设备厂商信息。这部分代码要处理异常情况,某些设备可能返回重试状态,这种情况要延迟一段时间,再次尝试读制造商的ID,如果延迟时间超过60秒,还没有读到ID,则返回失败。
pci_scan_device函数第二部分为设备分配一个PCI设备结构,然后根据设备配置空间读取的信息对设备进行赋值。
/*读PCI设备的类型*/
if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type))
return NULL;
/*申请一个PCI设备结构*/
dev = kzalloc(sizeof(struct pci_dev), GFP_KERNEL);
if (!dev)
return NULL;
/*设置PCI设备的参数,包括类型、制造商和是否多功能等*/
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;
/* Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer)
set this higher, assuming the system even supports it. */
/*设置设备的dma地址掩码*/
dev->dma_mask = 0xffffffff;
if (pci_setup_device(dev) < 0) {
kfree(dev);
return NULL;
}
return dev;
}此时,只读取了配置空间的制造商ID和头部信息(HEADER_TYPE),信息的进一步读取在函数pci_setup_device中完成,这个函数同时要设置PCI设备的信息,如代码清单8-10所示。
/* 代码清单8-10 pci_setup_device */
static int pci_setup_device(struct pci_dev * dev)
{
u32 class;
sprintf(pci_name(dev), "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus),
dev->bus->number, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn));
/*读类别*/
pci_read_config_dword(dev, PCI_CLASS_REVISION, &class);
class >>= 8; /* upper 3 bytes */
dev->class = class;
class >>= 8;
/* "Unknown power state" */
dev->current_state = PCI_UNKNOWN;
/* Early fixups, before probing the BARs */
pci_fixup_device(pci_fixup_early, dev);
class = dev->class >> 8;pci_setup_device函数第一部分是读设备类的信息。前面的高24位是class信息,后面的低8位是revision信息,并根据读取的信息设置PCI设备。
pci_setup_device函数第二部分是根据设备的类型读需要的信息。
switch (dev->hdr_type) { /* header type */
case PCI_HEADER_TYPE_NORMAL: /* standard header */
if (class == PCI_CLASS_BRIDGE_PCI)
goto bad;
/*读中断信息*/
pci_read_irq(dev);
/*读配置空间的资源信息,总共可以有6条信息*/
pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
/*读子系统厂商ID*/
pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
/*读子系统ID*/
pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device);
break;
case PCI_HEADER_TYPE_BRIDGE: /* bridge header */
if (class != PCI_CLASS_BRIDGE_PCI)
goto bad;
/* The PCI-to-PCI bridge spec requires that subtractive
decoding (i.e. transparent) bridge must have programming
interface code of 0x01. */
pci_read_irq(dev);
dev->transparent = ((dev->class & 0xff) == 1);
pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);
break;
....../*省略card bus类型的处理代码*/NOTE
设备有三种类型:通常的PCI设备、PCI桥设备和CARDBUS设备。每种设备都要读中断信息和资源信息。PCI设备的配置空间提供两种资源,一种是I/O端口,另一种是I/O内存。普通设备可以提供六个资源信息,而桥设备只有两个资源信息。
操作系统扫描PCI总线,目的就是获得PCI设备的信息,然后为每个设备分配一个PCI设备结构。PCI总线扫描到设备之后,需要为设备加载正确的驱动。这部分内容在第6章和第7章已经介绍,此处不再分析。
8.3 本章小结
PCI总线可说是现代计算机系统中最重要的总线,当PCI总线扫描到PCI设备后,已经为设备设置了DMA信息、中断信息和I/O端口、I/O内存信息,这些信息是实现PCI设备驱动的基础,也是深入理解PCI设备驱动的重要点。内核中实现了大量PCI设备的驱动,读者可以挑选熟悉的驱动,分析一下驱动如何处理这些信息。
(图像参考:第314页图663,第329页图694,第330页图697)