Linux·platform基于源码分析

linux platform 驱动模型分析

一. 概述 
platform设备和驱动与linux设备模型密切相关。platform在linux设备模型中,其实就是一种虚拟总线没有对应的硬件结构。它的主要作用就是管理系统的外设资源,比如io内存,中断信号线。现在大多数处理器芯片都是soc,如s3c2440,它包括处理器内核(arm920t)和系统的外设(lcd接口,nandflash接口等)。linux在引入了platform机制之后,内核假设所有的这些外设都挂载在platform虚拟总线上,以便进行统一管理。 
二. platform 总线 
1. 在系统中platform对应的文件drivers/base/platform.c,它不是作为一个模块注册到内核的,关键的注册总线的函数由系统初始化部分,对应/init/main.c中的do_basic_setup函数间接调用。这里可以看出platform非常重要,要在系统其他驱动加载之前注册。下面分析platform总线注册函数 

[cpp] view plain copy

  1. int __init platform_bus_init(void)
  2. {
  3. int error;
  4. early_platform_cleanup();
  5. error = device_register(&platform_bus);
  6. //总线也是设备,所以也要进行设备的注册
  7. if (error)
  8. return error;
  9. error = bus_register(&platform_bus_type);
  10. //注册platform_bus_type总线到内核
  11. if (error)
  12. device_unregister(&platform_bus);
  13. return error;
  14. }
 
  1. int __init platform_bus_init(void)

  2. {

  3. int error;

  4. early_platform_cleanup();

  5. error = device_register(&platform_bus);

  6. //总线也是设备,所以也要进行设备的注册

  7. if (error)

  8. return error;

  9. error = bus_register(&platform_bus_type);

  10. //注册platform_bus_type总线到内核

  11. if (error)

  12. device_unregister(&platform_bus);

  13. return error;

  14. }

这个函数向内核注册了一种总线。他首先由/drivers/base/init.c中的driver_init函数调用,driver_init函数由/init/main.c中的do_basic_setup函数调用,do_basic_setup这个函数由kernel_init调用,所以platform总线是在内核初始化的时候就注册进了内核。 
2. platform_bus_type 总线结构与设备结构 
(1) platform总线 设备结构 

[cpp] view plain copy

  1. struct device platform_bus = {
  2. .init_name = "platform",
  3. };
 
  1. struct device platform_bus = {

  2. .init_name = "platform",

  3. };

platform总线也是一种设备,这里初始化一个device结构,设备名称platform,因为没有指定父设备,所以注册后将会在/sys/device/下出现platform目录。 
(2) platform总线 总线结构 

[cpp] view plain copy

  1. struct bus_type platform_bus_type = {
  2. .name = "platform",
  3. .dev_attrs = platform_dev_attrs,
  4. .match = platform_match,
  5. .uevent = platform_uevent,
  6. .pm = &platform_dev_pm_ops,
  7. };
 
  1. struct bus_type platform_bus_type = {

  2. .name = "platform",

  3. .dev_attrs = platform_dev_attrs,

  4. .match = platform_match,

  5. .uevent = platform_uevent,

  6. .pm = &platform_dev_pm_ops,

  7. };

platform_dev_attrs 设备属性 
platform_match match函数,这个函数在当属于platform的设备或者驱动注册到内核时就会调用,完成设备与驱动的匹配工作。 
platform_uevent 热插拔操作函数 
三. platform 设备 
1. platform_device 结构 

[cpp] view plain copy

  1. struct platform_device {
  2. const char * name;
  3. int id;
  4. struct device dev;
  5. u32 num_resources;
  6. struct resource * resource;
  7. struct platform_device_id *id_entry;
  8. /* arch specific additions */
  9. struct pdev_archdata archdata;
  10. };
 
  1. struct platform_device {

  2. const char * name;

  3. int id;

  4. struct device dev;

  5. u32 num_resources;

  6. struct resource * resource;

  7. struct platform_device_id *id_entry;

  8. /* arch specific additions */

  9. struct pdev_archdata archdata;

  10. };

(1)platform_device结构体中有一个struct resource结构,是设备占用系统的资源,定义在ioport.h中,如下 

[cpp] view plain copy

  1. struct resource {
  2. resource_size_t start;
  3. resource_size_t end;
  4. const char *name;
  5. unsigned long flags;
  6. struct resource *parent, *sibling, *child;
  7. };
 
  1. struct resource {

  2. resource_size_t start;

  3. resource_size_t end;

  4. const char *name;

  5. unsigned long flags;

  6. struct resource *parent, *sibling, *child;

  7. };

(2) num_resources 占用系统资源的数目,一般设备都占用两种资源,io内存和中断信号线。这个为两种资源的总和。 
2. 设备注册函数 platform_device_register 

[cpp] view plain copy

  1. int platform_device_register(struct platform_device *pdev)
  2. {
  3. device_initialize(&pdev->dev);
  4. return platform_device_add(pdev);
  5. }
 
  1. int platform_device_register(struct platform_device *pdev)

  2. {

  3. device_initialize(&pdev->dev);

  4. return platform_device_add(pdev);

  5. }

这个函数首先初始化了platform_device的device结构,然后调用platform_device_add,这个是注册函数的关键,下面分析platform_device_add: 

[cpp] view plain copy

  1. int platform_device_add(struct platform_device *pdev)
  2. {
  3. int i, ret = 0;
  4. if (!pdev)
  5. return -EINVAL;
  6. if (!pdev->dev.parent)
  7. pdev->dev.parent = &platform_bus;
  8. //可以看出,platform设备的父设备一般都是platform_bus,所以注册后的platform设备都出现在/sys/devices/platform_bus下
  9. pdev->dev.bus = &platform_bus_type;
  10. //挂到platform总线上
  11. if (pdev->id != -1)
  12. dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
  13. else
  14. dev_set_name(&pdev->dev, "%s", pdev->name);
  15. //设置设备名字,这个名字与/sys/devices/platform_bus下的名字对应
  16. for (i = 0; i < pdev->num_resources; i++) { //下面操作设备所占用的系统资源
  17. struct resource *p, *r = &pdev->resource[i];
  18. if (r->name == NULL)
  19. r->name = dev_name(&pdev->dev);
  20. p = r->parent;
  21. if (!p) {
  22. if (resource_type(r) == IORESOURCE_MEM)
  23. p = &iomem_resource;
  24. else if (resource_type(r) == IORESOURCE_IO)
  25. p = &ioport_resource;
  26. }
  27. if (p && insert_resource(p, r)) {
  28. printk(KERN_ERR
  29. "%s: failed to claim resource %d\n",
  30. dev_name(&pdev->dev), i);
  31. ret = -EBUSY;
  32. goto failed;
  33. }
  34. }
  35. //上面主要是遍历设备所占用的资源,找到对应的父资源,如果没有定义,那么根据资源的类型,分别赋予iomem_resource和ioport_resource,然后调用insert_resource插入资源。
  36. //这样系统的资源就形成了一个树形的数据结构,便于系统的管理
  37. pr_debug("Registering platform device '%s'. Parent at %s\n",
  38. dev_name(&pdev->dev), dev_name(pdev->dev.parent));
  39. ret = device_add(&pdev->dev);
  40. //注册到设备模型中
  41. if (ret == 0)
  42. return ret;
  43. failed:
  44. while (--i >= 0) {
  45. struct resource *r = &pdev->resource[i];
  46. unsigned long type = resource_type(r);
  47. if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
  48. release_resource(r);
  49. }
  50. return ret;
  51. }
 
  1. int platform_device_add(struct platform_device *pdev)

  2. {

  3. int i, ret = 0;

  4. if (!pdev)

  5. return -EINVAL;

  6. if (!pdev->dev.parent)

  7. pdev->dev.parent = &platform_bus;

  8. //可以看出,platform设备的父设备一般都是platform_bus,所以注册后的platform设备都出现在/sys/devices/platform_bus下

  9. pdev->dev.bus = &platform_bus_type;

  10. //挂到platform总线上

  11. if (pdev->id != -1)

  12. dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);

  13. else

  14. dev_set_name(&pdev->dev, "%s", pdev->name);

  15. //设置设备名字,这个名字与/sys/devices/platform_bus下的名字对应

  16. for (i = 0; i < pdev->num_resources; i++) { //下面操作设备所占用的系统资源

  17. struct resource *p, *r = &pdev->resource[i];

  18. if (r->name == NULL)

  19. r->name = dev_name(&pdev->dev);

  20. p = r->parent;

  21. if (!p) {

  22. if (resource_type(r) == IORESOURCE_MEM)

  23. p = &iomem_resource;

  24. else if (resource_type(r) == IORESOURCE_IO)

  25. p = &ioport_resource;

  26. }

  27. if (p && insert_resource(p, r)) {

  28. printk(KERN_ERR

  29. "%s: failed to claim resource %d\n",

  30. dev_name(&pdev->dev), i);

  31. ret = -EBUSY;

  32. goto failed;

  33. }

  34. }

  35. //上面主要是遍历设备所占用的资源,找到对应的父资源,如果没有定义,那么根据资源的类型,分别赋予iomem_resource和ioport_resource,然后调用insert_resource插入资源。

  36. //这样系统的资源就形成了一个树形的数据结构,便于系统的管理

  37. pr_debug("Registering platform device '%s'. Parent at %s\n",

  38. dev_name(&pdev->dev), dev_name(pdev->dev.parent));

  39. ret = device_add(&pdev->dev);

  40. //注册到设备模型中

  41. if (ret == 0)

  42. return ret;

  43. failed:

  44. while (--i >= 0) {

  45. struct resource *r = &pdev->resource[i];

  46. unsigned long type = resource_type(r);

  47. if (type == IORESOURCE_MEM || type == IORESOURCE_IO)

  48. release_resource(r);

  49. }

  50. return ret;

  51. }

3. mini2440内核注册platform设备过程 
因为一种soc确定之后,其外设模块就已经确定了,所以注册platform设备就由板级初始化代码来完成,在mini2440中是mach-mini2440.c的mini2440_machine_init函数中调用platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices))来完成注册。这个函数完成mini2440的所有platform设备的注册: 
(1) platform_add_devices函数是platform_device_register的简单封装,它向内核注册一组platform设备 
(2) mini2440_devices是一个platform_device指针数组,定义如下: 

[cpp] view plain copy

  1. static struct platform_device *mini2440_devices[] __initdata = {
  2. &s3c_device_usb,
  3. &s3c_device_rtc,
  4. &s3c_device_lcd,
  5. &s3c_device_wdt,
  6. &s3c_device_i2c0,
  7. &s3c_device_iis,
  8. &mini2440_device_eth,
  9. &s3c24xx_uda134x,
  10. &s3c_device_nand,
  11. &s3c_device_sdi,
  12. &s3c_device_usbgadget,
  13. };
 
  1. static struct platform_device *mini2440_devices[] __initdata = {

  2. &s3c_device_usb,

  3. &s3c_device_rtc,

  4. &s3c_device_lcd,

  5. &s3c_device_wdt,

  6. &s3c_device_i2c0,

  7. &s3c_device_iis,

  8. &mini2440_device_eth,

  9. &s3c24xx_uda134x,

  10. &s3c_device_nand,

  11. &s3c_device_sdi,

  12. &s3c_device_usbgadget,

  13. };

这个就是mini2440的所有外设资源了,每个外设的具体定义在/arch/arm/plat-s3c24xx/devs.c,下面以s3c_device_lcd为例说明,其他的类似。s3c_device_lcd在devs.c中它定义为: 

[cpp] view plain copy

  1. struct platform_device s3c_device_lcd = {
  2. .name = "s3c2410-lcd",
  3. .id = -1,
  4. .num_resources = ARRAY_SIZE(s3c_lcd_resource),
  5. .resource = s3c_lcd_resource,
  6. .dev = {
  7. .dma_mask = &s3c_device_lcd_dmamask,
  8. .coherent_dma_mask = 0xffffffffUL
  9. }
  10. };
 
  1. struct platform_device s3c_device_lcd = {

  2. .name = "s3c2410-lcd",

  3. .id = -1,

  4. .num_resources = ARRAY_SIZE(s3c_lcd_resource),

  5. .resource = s3c_lcd_resource,

  6. .dev = {

  7. .dma_mask = &s3c_device_lcd_dmamask,

  8. .coherent_dma_mask = 0xffffffffUL

  9. }

  10. };

可以看出,它占用的资源s3c_lcd_resource,定义如下: 

[cpp] view plain copy

  1. static struct resource s3c_lcd_resource[] = {
  2. [0] = {
  3. .start = S3C24XX_PA_LCD,
  4. .end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
  5. .flags = IORESOURCE_MEM,
  6. },
  7. [1] = {
  8. .start = IRQ_LCD,
  9. .end = IRQ_LCD,
  10. .flags = IORESOURCE_IRQ,
  11. }
  12. };
 
  1. static struct resource s3c_lcd_resource[] = {

  2. [0] = {

  3. .start = S3C24XX_PA_LCD,

  4. .end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,

  5. .flags = IORESOURCE_MEM,

  6. },

  7. [1] = {

  8. .start = IRQ_LCD,

  9. .end = IRQ_LCD,

  10. .flags = IORESOURCE_IRQ,

  11. }

  12. };

这是一个数组,有两个元素,说明lcd占用了系统两个资源,一个资源类型是IORESOURCE_MEM代表io内存,起使地址S3C24XX_PA_LCD,这个是LCDCON1寄存器的地址。另外一个资源是中断信号线。 
四. platform设备驱动 
如果要将所写的驱动程序注册成platform驱动,那么所做的工作就是初始化一个platform_driver,然后调用platform_driver_register进行注册。 
1. 基本数据机构platform_driver 

[cpp] view plain copy

  1. struct platform_driver {
  2. int (*probe)(struct platform_device *);
  3. int (*remove)(struct platform_device *);
  4. void (*shutdown)(struct platform_device *);
  5. int (*suspend)(struct platform_device *, pm_message_t state);
  6. int (*resume)(struct platform_device *);
  7. struct device_driver driver;
  8. struct platform_device_id *id_table;
  9. };
 
  1. struct platform_driver {

  2. int (*probe)(struct platform_device *);

  3. int (*remove)(struct platform_device *);

  4. void (*shutdown)(struct platform_device *);

  5. int (*suspend)(struct platform_device *, pm_message_t state);

  6. int (*resume)(struct platform_device *);

  7. struct device_driver driver;

  8. struct platform_device_id *id_table;

  9. };

这是platform驱动基本的数据结构,在驱动程序中我们要做的就是声明一个这样的结构并初始化。下面是lcd驱动程序对它的初始化: 

[cpp] view plain copy

  1. static struct platform_driver s3c2412fb_driver = {
  2. .probe = s3c2412fb_probe,
  3. .remove = s3c2410fb_remove,
  4. .suspend = s3c2410fb_suspend,
  5. .resume = s3c2410fb_resume,
  6. .driver = {
  7. .name = "s3c2412-lcd",
  8. .owner = THIS_MODULE,
  9. },
  10. };
 
  1. static struct platform_driver s3c2412fb_driver = {

  2. .probe = s3c2412fb_probe,

  3. .remove = s3c2410fb_remove,

  4. .suspend = s3c2410fb_suspend,

  5. .resume = s3c2410fb_resume,

  6. .driver = {

  7. .name = "s3c2412-lcd",

  8. .owner = THIS_MODULE,

  9. },

  10. };

上面几个函数是我们要实现的,它将赋值给device_driver中的相关成员,probe函数是用来查询特定设备是够真正存在的函数。当设备从系统删除的时候调用remove函数。 
2. 注册函数platform_driver_register 

[cpp] view plain copy

  1. int platform_driver_register(struct platform_driver *drv)
  2. {
  3. drv->driver.bus = &platform_bus_type;
  4. if (drv->probe)
  5. drv->driver.probe = platform_drv_probe;
  6. if (drv->remove)
  7. drv->driver.remove = platform_drv_remove;
  8. if (drv->shutdown)
  9. drv->driver.shutdown = platform_drv_shutdown;
  10. return driver_register(&drv->driver);
  11. }
 
  1. int platform_driver_register(struct platform_driver *drv)

  2. {

  3. drv->driver.bus = &platform_bus_type;

  4. if (drv->probe)

  5. drv->driver.probe = platform_drv_probe;

  6. if (drv->remove)

  7. drv->driver.remove = platform_drv_remove;

  8. if (drv->shutdown)

  9. drv->driver.shutdown = platform_drv_shutdown;

  10. return driver_register(&drv->driver);

  11. }

这个函数首先使驱动属于platform_bus_type总线,将platform_driver结构中的定义的probe,remove,shutdown赋值给device_driver结构中的相应成员,以供linux设备模型核心调用,然后调用driver_regster将设备驱动注册到linux设备模型核心中。 
五. 各环节的整合 
前面提到mini2440板级初始化程序将它所有的platform设备注册到了linux设备模型核心中,在/sys/devices/platform目录中都有相应的目录表示。platform驱动则是由各个驱动程序模块分别注册到系统中的。但是他们是如何联系起来的呢,这就跟linux设备模型核心有关系了。在ldd3中的linux设备模型的各环节的整合中有详细的论述。这里简要说明一下platform实现的方法。每当注册一个platform驱动的时候就会调用driver_register,这个函数的调用会遍历设备驱动所属总线上的所有设备,并对每个设备调用总线的match函数。platform驱动是属于platform_bus_type总线,所以调用platform_match函数。这个函数实现如下: 

[cpp] view plain copy

  1. static int platform_match(struct device *dev, struct device_driver *drv)
  2. {
  3. struct platform_device *pdev = to_platform_device(dev);
  4. struct platform_driver *pdrv = to_platform_driver(drv);
  5. /* match against the id table first */
  6. if (pdrv->id_table)
  7. return platform_match_id(pdrv->id_table, pdev) != NULL;
  8. /* fall-back to driver name match */
  9. return (strcmp(pdev->name, drv->name) == 0);
  10. }
 
  1. static int platform_match(struct device *dev, struct device_driver *drv)

  2. {

  3. struct platform_device *pdev = to_platform_device(dev);

  4. struct platform_driver *pdrv = to_platform_driver(drv);

  5. /* match against the id table first */

  6. if (pdrv->id_table)

  7. return platform_match_id(pdrv->id_table, pdev) != NULL;

  8. /* fall-back to driver name match */

  9. return (strcmp(pdev->name, drv->name) == 0);

  10. }

这个函数将device结构转换为platform_devcie结构,将device_driver结构转换为platform_driver结构,并调用platform_match_id对设备与驱动相关信息进行比较。如果没有比较成功会返回0,以便进行下一个设备的比较,如果比较成功就会返回1,并且将device结构中的driver指针指向这个驱动。然后调用device_driver中的probe函数,在lcd驱动中就是s3c2412fb_probe。这个函数是我们要编写的函数。这个函数检测驱动的状态,并且测试能否真正驱动设备,并且做一些初始化工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值