摘要 本文以Linux 2.6.25 内核为例,分析了基于platform总线的驱动模型。首先介绍了Platform总线的基本概念,接着介绍了platform device和platform driver的定义和加载过程,分析了其与基类device 和driver的派生关系及在此过程中面向对象的设计思想。最后以ARM S3C2440中I2C控制器为例介绍了基于platform总线的驱动开发流程。
【关键字】platform_bus, platform_device, resource , platform_driver, file_operations
目录
1 何谓platform bus? 2
2 device和platform_device 3
3 device_register和platform_device_register 5
4 device_driver和platform driver 8
5 driver_register 和platform_driver_register 10
6 bus、device及driver三者之间的关系 17
7 哪些适用于plarform驱动? 18
8 基于platform总线的驱动开发流程 18
8.1 初始化platform_bus 19
8.2 定义platform_device 22
8.3 注册platform_device 22
8.4 定义platform_driver 28
8.5 注册platform_driver 29
8.6 操作设备 32
1 何谓platform bus?
Linux系统中许多部分对设备是如何链接的并不感兴趣,但是他们需要知道哪些类型的设备是可以使用的。设备模型提供了一种机制来对设备进行分类,在更高的功能层面上描述这些设备,并使得这些设备对用户空间可见。因此从2.6内核开始引入了设备模型。
总线是处理器和一个或多个设备之间的通道,在设备模型中, 所有的设备都通过总线相连。总线可以相互插入。设备模型展示了总线和它们所控制的设备之间的实际连接。
Platform总线是2.6 kernel中最近引入的一种虚拟总线,主要用来管理CPU的片上资源,具有更好的移植性,因此在2.6 kernel中,很多驱动都用platform改写了。
platform_bus_type的定义如下:
http://lxr.linux.no/#linux+v2.6.25/drivers/base/platform.c#L609
609struct 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};
619EXPORT_SYMBOL_GPL(platform_bus_type);
http://lxr.linux.no/#linux+v2.6.25/include/linux/device.h#L55
55struct bus_type {
56 const char *name;
57 struct bus_attribute *bus_attrs;
58 struct device_attribute *dev_attrs;
59 struct driver_attribute *drv_attrs;
60
61 int (*match)(struct device *dev, struct device_driver *drv);
62 int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
63 int (*probe)(struct device *dev);
64 int (*remove)(struct device *dev);
65 void (*shutdown)(struct device *dev);
66
67 int (*suspend)(struct device *dev, pm_message_t state);
68 int (*suspend_late)(struct device *dev, pm_message_t state);
69 int (*resume_early)(struct device *dev);
70 int (*resume)(struct device *dev);
71
72 struct bus_type_private *p;
73};
总线名称是"platform",其只是bus_type的一种,定义了总线的属性,同时platform_bus_type还有相关操作方法,如挂起、中止、匹配及hotplug事件等。
总线bus是联系driver和device的中间枢纽。Device通过所属的bus找到driver,由match操作方法进行匹配。
Bus、driver及devices的连接关系
2 device和platform_device
Plarform device会有一个名字用于driver binding(在注册driver的时候会查找driver的目标设备的bus位置,这个过程称为driver binding),另外IRQ以及地址空间等资源也要给出 。
platform_device结构体用来描述设备的名称、资源信息等。该结构被定义在http://lxr.linux.no/#linux+v2.6.25/include/linux/platform_device.h#L16中,定义原型如下:
16struct platform_device {
17 const char * name; //定义平台设备的名称,此处设备的命名应和相应驱动程序命名一致
18 int id;
19 struct device dev;
20 u32 num_resources;
21 struct resource * resource; //定义平台设备的资源
22};
在这个结构里封装了struct device及struct resource。可知:platform_device由device派生而来,是一种特殊的device。
下面来看一下platform_device结构体中最重要的一个成员struct resource * resource。struct resource被定义在http://lxr.linux.no/#linux+v2.6.25/include/linux/ioport.h#L18中,定义原型如下:
14
18struct resource {
19 resource_size_t start; //定义资源的起始地址
20 resource_size_t end; //定义资源的结束地址
21 const char *name; //定义资源的名称
22 unsigned long flags; 定义资源的类型,比如MEM,IO,IRQ,DMA类型
23 struct resource *parent, *sibling, *child;
24};
这个结构表示设备所拥有的资源,即I/O端口、I/O映射内存、中断及DMA等。这里的地址指的是物理地址。
另外还需要注意platform_device中的device结构,它详细描述了设备的情况,其为所有设备的基类,定义如下:
http://lxr.linux.no/#linux+v2.6.25/include/linux/device.h#L422
422struct device {
423 struct klist klist_children;
424 struct klist_node knode_parent;
425 struct klist_node knode_driver;
426 struct klist_node knode_bus;
427 struct device *parent;
428
429 struct kobject kobj;
430 char bus_id[BUS_ID_SIZE];
431 struct device_type *type;
432 unsigned is_registered:1;
433 unsigned uevent_suppress:1;
434
435 struct semaphore sem;
438
439 struct bus_type *bus;
440 struct device_driver *driver;
442 void *driver_data;
443 void *platform_data;
445 struct dev_pm_info power;
446
447#ifdef CONFIG_NUMA
448 int numa_node;
449#endif
450 u64 *dma_mask;
451 u64 coherent_dma_mask;
456
457 struct device_dma_parameters *dma_parms;
458
459 struct list_head dma_pools;
460
461 struct dma_coherent_mem *dma_mem;
463
464 struct dev_archdata archdata;
465
466 spinlock_t devres_lock;
467 struct list_head devres_head;
468
469
470 struct list_head node;
471 struct class *class;
472 dev_t devt;
473 struct attribute_group **groups;
474
475 void (*release)(struct device *dev);
476};
477
3 device_register和platform_device_register
http://lxr.linux.no/#linux+v2.6.25/drivers/base/core.c#L881
870
881int device_register(struct device *dev)
882{
883 device_initialize(dev);
884 return device_add(dev);
885}
初始化一个设备,然后加入到系统中。
http://lxr.linux.no/#linux+v2.6.25/drivers/base/platform.c#L325
316
320int platform_device_register(struct platform_device *pdev)
321{
322 device_initialize(&pdev->dev);
323 return platform_device_add(pdev);
324}
325EXPORT_SYMBOL_GPL(platform_device_register);
我们看到注册一个platform device分为了两部分,初始化这个platform_device,然后将此platform_device添加到platform总线中。输入参数platform_device可以是静态的全局设备。
另外一种机制就是动态申请platform_device_alloc一个platform_device设备,然后通过platform_device_add_resources及platform_device_add_data等添加相关资源和属性。
无论哪一种platform_device,最终都将通过platform_device_add这册到platform总线上。
229
236int platform_device_add(struct platform_device *pdev)
237{
238 int i, ret = 0;
239
240 if (!pdev)
241 return -EINVAL;
242
初始化设备的parent为platform_bus,初始化驱备的总线为platform_bus_type。
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
设置设备struct device 的bus_id成员,留心这个地方,在以后还需要用到这个的。
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 }
//resources分为两种IORESOURCE_MEM和IORESOURCE_IO
//CPU对外设IO端口物理地址的编址方式有两种:I/O映射方式和内存映射方式
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;
283
284 failed:
285 while (--i >= 0)
286 if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))
287 release_resource(&pdev->resource[i]);
288 return ret;
289}
290EXPORT_SYMBOL_GPL(platform_device_add);
由platform_device_register和platform_device_add的实现可知,device_register()和platform_device_register()都会首先初始化设备
区别在于第二步:其实platform_device_add()包括device_add(),不过要先注册resources,然后将设备挂接到特定的platform总线。
4 device_driver和platform driver
Platform device是一种device自己是不会做事情的,要有人为它做事情,那就是platform driver。platform driver遵循linux系统的driver model。对于device的discovery/enumerate都不是driver自己完成的而是由由系统的driver注册机制完成。driver编写人员只要将注册必须的数据结构初始化并调用注册driver的kernel API就可以了。
接下来来看platform_driver结构体的原型定义,在http://lxr.linux.no/#linux+v2.6.25/include/linux/platform_device.h#L48中,代码如下:
48 struct platform_driver {
49 int (*probe)(struct platform_device *);
50 int (*remove)(struct platform_device *);
51 void (*shutdown)(struct platform_device *);
52 int (*suspend)(struct platform_device *, pm_message_t state);
53 int (*suspend_late)(struct platform_device *, pm_message_t state);
54 int (*resume_early)(struct platform_device *);
55 int (*resume)(struct platform_device *);
56 struct device_driver driver;
57};
可见,它包含了设备操作的几个功能函数,同时包含了一个device_driver结构,说明device_driver是platform_driver的基类。驱动程序中需要初始化这个变量。下面看一下这个变量的定义,位于http://lxr.linux.no/#linux+v2.6.25/include/linux/device.h#L121中:
121struct device_driver {
122 const char *name;
123 struct bus_type *bus;
124
125 struct module *owner;
126 const char *mod_name;
127
128 int (*probe) (struct device *dev);
129 int (*remove) (struct device *dev);
130 void (*shutdown) (struct device *dev);
131 int (*suspend) (struct device *dev, pm_message_t state);
132 int (*resume) (struct device *dev);
133 struct attribute_group **groups;
134
135 struct driver_private *p;
136};
device_driver提供了一些操作接口,但其并没有实现,相当于一些虚函数,由派生类platform_driver进行重载,无论何种类型的driver都是基于device_driver派生而来的,具体的各种操作都是基于统一的基类接口的,这样就实现了面向对象的设计。
需要注意这两个变量:name和owner。其作用主要是为了和相关的platform_device关联起来,owner的作用是说明模块的所有者,驱动程序中一般初始化为THIS_MODULE。
device_driver结构中也有一个name变量。platform_driver从字面上来看就知道是设备驱动。设备驱动是为谁服务的呢?当然是设备了。内核正是通过这个一致性来为驱动程序找到资源,即 platform_device中的resource。
5 driver_register 和platform_driver_register
内核提供的platform_driver结构体的注册函数为platform_driver_register(),其原型定义在http://lxr.linux.no/#linux+v2.6.25/drivers/base/platform.c#L458文件中,具体实现代码如下:
439
443int 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;
//在really_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}
458EXPORT_SYMBOL_GPL(platform_driver_register);
不要被上面的platform_drv_XXX吓倒了,它们其实很简单,就是将struct device转换为struct platform_device和struct platform_driver,然后调用platform_driver中的相应接口函数。那为什么不直接调用platform_drv_XXX等接口呢?这就是Linux内核中面向对象的设计思想。
device_driver提供了一些操作接口,但其并没有实现,相当于一些虚函数,由派生类platform_driver进行重载,无论何种类型的driver都是基于device_driver派生而来的,device_driver中具体的各种操作都是基于统一的基类接口的,这样就实现了面向对象的设计。
在文件http://lxr.linux.no/#linux+v2.6.25/drivers/base/driver.c#L234中,实现了driver_register()函数。
209
217int driver_register(struct device_driver *drv)
218{
219 int ret;
220
//如果总线的方法和设备自己的方法同时存在,将打印告警信息,对于platform bus,其没有probe等接口
221 if ((drv->bus->probe && drv->probe) ||
222 (drv->bus->remove && drv->remove) ||
223 (drv->bus->shutdown && drv->shutdown))
224 printk(KERN_WARNING "Driver '%s' needs updating - please use "
225 "bus_type methods\n", drv->name);
226 ret = bus_add_driver(drv);
227 if (ret)
228 return ret;
229 ret = driver_add_groups(drv, drv->groups);
230 if (ret)
231 bus_remove_driver(drv);
232 return ret;
233}
234EXPORT_SYMBOL_GPL(driver_register);
226 其主要将驱动挂接到总线上,通过总线来驱动设备。
644
648int bus_add_driver(struct device_driver *drv)
649{
650 struct bus_type *bus;
651 struct driver_private *priv;
652 int error = 0;
653
654 bus = bus_get(drv->bus);
655 if (!bus)
656 return -EINVAL;
657
658 pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
659
660 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
661 if (!priv) {
662 error = -ENOMEM;
663 goto out_put_bus;
664 }
665 klist_init(&priv->klist_devices, NULL, NULL);
666 priv->driver = drv;
667 drv->p = priv;
668 priv->kobj.kset = bus->p->drivers_kset;
669 error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
670 "%s", drv->name);
671 if (error)
672 goto out_unregister;
673
674 if (drv->bus->p->drivers_autoprobe) {
675 error = driver_attach(drv);
676 if (error)
677 goto out_unregister;
678 }
679 klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
680 module_add_driver(drv->owner, drv);
681
682 error = driver_create_file(drv, &driver_attr_uevent);
683 if (error) {
684 printk(KERN_ERR "%s: uevent attr (%s) failed\n",
685 __FUNCTION__, drv->name);
686 }
687 error = driver_add_attrs(bus, drv);
688 if (error) {
689
690 printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
691 __FUNCTION__, drv->name);
692 }
693 error = add_bind_files(drv);
694 if (error) {
695
696 printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
697 __FUNCTION__, drv->name);
698 }
699
700 kobject_uevent(&priv->kobj, KOBJ_ADD);
701 return error;
702out_unregister:
703 kobject_put(&priv->kobj);
704out_put_bus:
705 bus_put(bus);
706 return error;
707}
如果总线上的driver是自动probe的话,则将该总线上的driver和device绑定起来。
http://lxr.linux.no/#linux+v2.6.25/drivers/base/dd.c#L285
272
281int driver_attach(struct device_driver *drv)
282{
283 return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
284}
285EXPORT_SYMBOL_GPL(driver_attach);
扫描该总线上的每一个设备,将当前driver和总线上的设备进行match,如果匹配成功,则将设备和driver绑定起来。
246static int __driver_attach(struct device *dev, void *data)
247{
248 struct device_driver *drv = data;
249
250
259
260 if (dev->parent)
261 down(&dev->parent->sem);
262 down(&dev->sem);
263 if (!dev->driver)
264 driver_probe_device(drv, dev);
265 up(&dev->sem);
266 if (dev->parent)
267 up(&dev->parent->sem);
268
269 return 0;
270}
263,如果该设备尚没有匹配的driver,则尝试匹配。
http://lxr.linux.no/#linux+v2.6.25/drivers/base/dd.c#L187
170
187int driver_probe_device(struct device_driver *drv, struct device *dev)
188{
189 int ret = 0;
190
191 if (!device_is_registered(dev))
192 return -ENODEV;
193 if (drv->bus->match && !drv->bus->match(dev, drv))
194 goto done;
195
196 pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
197 drv->bus->name, __FUNCTION__, dev->bus_id, drv->name);
198
199 ret = really_probe(dev, drv);
200
201done:
202 return ret;
203}
193,如果该总线上的设备需要进行匹配,则验证是否匹配。对于platform总线,其匹配过程如下:
http://lxr.linux.no/#linux+v2.6.25/drivers/base/platform.c#L555
542
555static 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}
560,简单的进行字符串匹配,这也是我们强调platform_device和platform_driver中的name属性需要一致的原因。
匹配成功后,则调用probe接口。
http://lxr.linux.no/#linux+v2.6.25/drivers/base/dd.c#L101
98static atomic_t probe_count = ATOMIC_INIT(0);
99static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);
100
101static int really_probe(struct device *dev, struct device_driver *drv)
102{
103 int ret = 0;
104
105 atomic_inc(&probe_count);
106 pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
107 drv->bus->name, __FUNCTION__, drv->name, dev->bus_id);
108 WARN_ON(!list_empty(&dev->devres_head));
109
110 dev->driver = drv;
111 if (driver_sysfs_add(dev)) {
112 printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
113 __FUNCTION__, dev->bus_id);
114 goto probe_failed;
115 }
116
117 if (dev->bus->probe) {
118 ret = dev->bus->probe(dev);
119 if (ret)
120 goto probe_failed;
121 } else if (drv->probe) {
122 ret = drv->probe(dev);
123 if (ret)
124 goto probe_failed;
125 }
126
127 driver_bound(dev);
128 ret = 1;
129 pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
130 drv->bus->name, __FUNCTION__, dev->bus_id, drv->name);
131 goto done;
132
133probe_failed:
134 devres_release_all(dev);
135 driver_sysfs_remove(dev);
136 dev->driver = NULL;
137
138 if (ret != -ENODEV && ret != -ENXIO) {
139
140 printk(KERN_WARNING
141 "%s: probe of %s failed with error %d\n",
142 drv->name, dev->bus_id, ret);
143 }
144
148 ret = 0;
149done:
150 atomic_dec(&probe_count);
151 wake_up(&probe_waitqueue);
152 return ret;
153}
154
如果bus和driver同时具备probe方法,则优先调用总线的probe函数。否则调用device_driver的probe函数,此probe函数是经过各种类型的driver重载的函数,这就实现了利用基类的统一方法来实现不同的功能。对于platform_driver来说,其就是:
http://lxr.linux.no/#linux+v2.6.25/drivers/base/platform.c#L394
394static 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所定义的操作方法,这个是在定义某个platform_driver时静态指定的操作接口。
至此,platform_driver成功挂接到platform bus上了,并与特定的设备实现了绑定,并对设备进行了probe处理。
6 bus、device及driver三者之间的关系
在数据结构设计上,总线、设备及驱动三者相互关联。
platform device包含device,根据device可以获得相应的bus及driver。
设备添加到总线上后形成一个双向循环链表,根据总线可以获得其上挂接的所有device,进而获得了 platform device。根据device也可以获得驱动该总线上所有设备的相关driver。
platform driver包含driver,根据driver可以获得相应的bus,进而获得bus上所有的device,进一步获得platform device,根据name对driver与platform device进行匹配,匹配成功后将device与相应的driver关联起来,即实现了platform device和platform driver的关联。
匹配成功后调用driver的probe进而调用platform driver的probe,在probe里实现驱动特定的功能。
7 哪些适用于plarform驱动?
platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过platform device提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,这样拥有更好的可移植性。platform机制的本身使用并不复杂,由两部分组成:platform_device和platfrom_driver。Platform driver通过platform bus获取platform_device。
通常情况下只要和内核本身运行依赖性不大的外围设备,相对独立的,拥有各自独立的资源(地址总线和IRQs),都可以用 platform_driver来管理,而timer,irq等小系统之内的设备则最好不用platfrom_driver机制。
platform_device最大的特定是CPU直接寻址设备的寄存器空间,即使对于其他总线设备,设备本身的寄存器无法通过CPU总线访问,但总线的controller仍然需要通过platform bus来管理。
总之,platfrom_driver的根本目的是为了统一管理系统的外设资源,为驱动程序提供统一的接口来访问系统资源,将驱动和资源分离,提高程序的可移植性。
8 基于platform总线的驱动开发流程
基于Platform总线的驱动开发流程如下:
? 定义初始化platform bus
? 定义各种platform devices
? 注册各种platform devices
? 定义相关platform driver
? 注册相关platform driver
? 操作相关设备
图 platform机制开发驱动流程
以S3C24xx平台为例,来简单讲述下platform驱动的实现流程。