以前的文章已经分析过了linux总线的注册过程,今天就拿一个具体总线例子来分析一下,platform总线是一个虚拟总线(在硬件上并非存在这个总线),在linux 2.6的设备驱动中platform总线用的比较多,所以分析platform总线是非常有必要的。
先看看在我的机子上platform总线上都注册了哪些设备和驱动:
[root@FLYING platform]# pwd
/sys/bus/platform
[root@FLYING platform]# tree
.
|-- devices
| |-- bluetooth -> ../../../devices/platform/bluetooth
| |-- floppy.0 -> ../../../devices/platform/floppy.0
| |-- i8042 -> ../../../devices/platform/i8042
| |-- microcode -> ../../../devices/platform/microcode
| |-- pcspkr -> ../../../devices/platform/pcspkr
| |-- serial8250 -> ../../../devices/platform/serial8250
| `-- vesafb.0 -> ../../../devices/platform/vesafb.0
|-- drivers
| |-- i8042
| | |-- bind
| | |-- i8042 -> ../../../../devices/platform/i8042
| | |-- uevent
| | `-- unbind
| |-- parport_pc
| | |-- bind
| | |-- module -> ../../../../module/parport_pc
| | |-- uevent
| | `-- unbind
| |-- pcspkr
| | |-- bind
| | |-- module -> ../../../../module/pcspkr
| | |-- pcspkr -> ../../../../devices/platform/pcspkr
| | |-- uevent
| | `-- unbind
| |-- serial8250
| | |-- bind
| | |-- module -> ../../../../module/8250
| | |-- serial8250 -> ../../../../devices/platform/serial8250
| | |-- uevent
| | `-- unbind
| `-- vesafb
| |-- bind
| |-- uevent
| |-- unbind
| `-- vesafb.0 -> ../../../../devices/platform/vesafb.0
|-- drivers_autoprobe
|-- drivers_probe
`-- uevent
在这个目录树结构图中清晰的展示了注册在platform总线上的设备和驱动。
platform初始化
在系统初始化的时候会调用函数platform_bus_init();始化platform总线:
609 struct bus_type platform_bus_type = {
610 .name = "platform",
611 .dev_attrs = platform_dev_attrs,
612 .match = platform_match,
613 .uevent = platform_uevent,
614 .suspend = platform_suspend,
615 .suspend_late = platform_suspend_late,
616 .resume_early = platform_resume_early,
617 .resume = platform_resume,
618 };
621 int __init platform_bus_init(void)
622 {
623 int error;
624
625 error = device_register(&platform_bus);
626 if (error)
627 return error;
628 error = bus_register(&platform_bus_type);
629 if (error)
630 device_unregister(&platform_bus);
631 return error;
632 }
第625行,首先注册一个设备platform_bus ,它是platform总线下所有设备的父设备.
第628行,注册platform总线,在以前文章已经详细分析过总线注册过程。
注册platform设备
接下来就分析如何在platform总线上注册设备。在platform.c文件中很容易找到这个函数platform_device_register(),就是用来注册一个设备到platform总线上的。
跟踪一下这个函数的流程:
platform_device_register()->device_initialize()->platform_device_add()-insert_resource ()->device_add();
最终还是调用一个基础函数device_add()注册设备。
详细分析一下platform_device_add()这个函数吧!
236 int platform_device_add(struct platform_device *pdev)
237 {
238 int i, ret = 0;
239
240 if (!pdev)
241 return -EINVAL;
242
243 if (!pdev->dev.parent)
244 pdev->dev.parent = &platform_bus;
245
246 pdev->dev.bus = &platform_bus_type;
247
248 if (pdev->id != -1)
249 snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,
250 pdev->id);
251 else
252 strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);
253
254 for (i = 0; i < pdev->num_resources; i++) {
255 struct resource *p, *r = &pdev->resource[i];
256
257 if (r->name == NULL)
258 r->name = pdev->dev.bus_id;
259
260 p = r->parent;
261 if (!p) {
262 if (r->flags & IORESOURCE_MEM)
263 p = &iomem_resource;
264 else if (r->flags & IORESOURCE_IO)
265 p = &ioport_resource;
266 }
267
268 if (p && insert_resource(p, r)) {
269 printk(KERN_ERR
270 "%s: failed to claim resource %d\n",
271 pdev->dev.bus_id, i);
272 ret = -EBUSY;
273 goto failed;
274 }
275 }
276
277 pr_debug("Registering platform device '%s'. Parent at %s\n",
278 pdev->dev.bus_id, pdev->dev.parent->bus_id);
279
280 ret = device_add(&pdev->dev);
281 if (ret == 0)
282 return ret;
……………….
}
第243-246行,指定父设备对象和总线类型,也就是在初始化platform总线时注册的设备和总线。
第254-266行,将pdev设备使用的内存或IO资源注册到以iomem_resource或ioport_resource为根的资源树上,这样做的目的是为了防止资源使用冲突。使用insert_resource() 把使用的资源信息注册到资源树上,而在后面要使用这些资源时必须要调用函数request_region()申请使用资源。这也是platform总线上设备的一个特点。
第280行,最后调用函数device_add()注册设备,在以前文章中已经分析过了,这里就不多说了。
注册platform驱动
注册一个platform 设备驱动也是比较简单的.
443 int platform_driver_register(struct platform_driver *drv)
444 {
445 drv->driver.bus = &platform_bus_type;
446 if (drv->probe)
447 drv->driver.probe = platform_drv_probe;
448 if (drv->remove)
449 drv->driver.remove = platform_drv_remove;
450 if (drv->shutdown)
451 drv->driver.shutdown = platform_drv_shutdown;
452 if (drv->suspend)
453 drv->driver.suspend = platform_drv_suspend;
454 if (drv->resume)
455 drv->driver.resume = platform_drv_resume;
456 return driver_register(&drv->driver);
457 }
第445行,初始化驱动关联的总线,既然我们注册的是platform驱动,那么总线自然是platform_bus_type。
第446-455,初始化驱动的probe, remove, shutdown, suspend, resume回调函数.
第456行,注册一个驱动。不多说,以前就分析过了。
Platform总线上设备与驱动匹配
在以前文中已经分析过设备和驱动的匹配过程,在匹配的时候会调用三个重要的回调函数一是总线的match方法,二是总线的prob方法,最后一个是驱动的prob方法.
在上面platform总线的定义中我们可以看到platform总线定义的match回调函数是platform_match();我们来跟踪一下代码:
555 static int platform_match(struct device *dev, struct device_driver *drv)
556 {
557 struct platform_device *pdev;
558
559 pdev = container_of(dev, struct platform_device, dev);
560 return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
561 }
可见platform总线上设备和驱动的匹配规则只是比较名字,只要是名字一样就匹配成功.
在platform总线并未定义prob方法, 所有我们直接看驱动的prob方法 。
在上面分析的注册platform驱动有这么一段代码:
446 if (drv->probe)
447 drv->driver.probe = platform_drv_probe;
所以最后调用的是函数platform_drv_probe(),看看它的实现:
394 static int platform_drv_probe(struct device *_dev)
395 {
396 struct platform_driver *drv = to_platform_driver(_dev->driver);
397 struct platform_device *dev = to_platform_device(_dev);
398
399 return drv->probe(dev);
400 }
这个函数很简单, 先是找到platform_driver,然后调用drv->probe() ,这个回调函数一般都是在我们定义platform_driver时定义的.
驱动的其它回调方法remove, shutdown, suspend, resume的实现都是和prob一样的,这里就不具体看了。
好了,到这里这个总线模型就分析完了, 这个总线还是比较简单的,但是它是非常有用的,在内核驱动中大量使用, 有了这个知识后就可以自己跟踪内核中的大部分驱动代码了.