linux platform 驱动模型分析

来源:互联网 发布:windows桌面更新了很久 编辑:程序博客网 时间:2024/04/27 23:10

linux platform驱动模型分析

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

1.   int __initplatform_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.    }

int __init platform_bus_init(void)

{

  int error;

  early_platform_cleanup();

  error = device_register(&platform_bus);

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

  if (error)

   return error;

  error =  bus_register(&platform_bus_type);

        //注册platform_bus_type总线到内核

  if (error)

   device_unregister(&platform_bus);

  return error;

}

这个函数向内核注册了一种总线。他首先由/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总线设备结构

1.   struct device platform_bus = {

2.   .init_name = "platform",

3.   };

struct device platform_bus = {

.init_name = "platform",

};

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

struct bus_type platform_bus_type = {

1.   .name = "platform",

2.   .dev_attrs = platform_dev_attrs,

3.   .match = platform_match,

4.   .uevent = platform_uevent,

5.   .pm = &platform_dev_pm_ops,

6.   };

struct bus_type platform_bus_type = {

.name  = "platform",

.dev_attrs = platform_dev_attrs,

.match  = platform_match,

.uevent  = platform_uevent,

.pm  = &platform_dev_pm_ops,

};

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

1.   struct platform_device {

2.   constchar * 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.    };

struct platform_device {

  const char  * name;

  int   id;

  struct device  dev;

  u32   num_resources;

  struct resource  * resource;

  struct platform_device_id  *id_entry;

  /* arch specific additions */

  struct pdev_archdata  archdata;

};

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

1.   struct resource {

2.   resource_size_t start;

3.   resource_size_t end;

4.   constchar *name;

5.   unsigned long flags;

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

7.   };

struct resource {

  resource_size_t start;

  resource_size_t end;

  const char *name;

  unsigned long flags;

  struct resource *parent, *sibling, *child;

};

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

1.  int platform_device_register(struct platform_device *pdev)

2.   {

3.   device_initialize(&pdev->dev);

4.   return platform_device_add(pdev);

5.   }

int platform_device_register(struct platform_device *pdev)

{

  device_initialize(&pdev->dev);

  return platform_device_add(pdev);

}

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

1.  intplatform_device_add(struct platform_device *pdev)

2.   {

3.   int i, ret = 0;

4.    

5.   if (!pdev)

6.   return -EINVAL;

7.    

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

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

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

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

12.    //挂到platform总线上

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

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

15.    else

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

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

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

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

20.     

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

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

23.     

24.    p = r->parent;

25.    if (!p) {

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

27.    p = &iomem_resource;

28.    elseif (resource_type(r) == IORESOURCE_IO)

29.    p = &ioport_resource;

30.    }

31.     

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

33.    printk(KERN_ERR

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

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

36.    ret = -EBUSY;

37.    goto failed;

38.    }

39.    }

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

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

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

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

44.     

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

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

47.    if (ret == 0)

48.    return ret;

49.    failed:

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

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

52.    unsigned long type = resource_type(r);

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

54.    release_resource(r);

55.    }

56.    return ret;

57.    }

int platform_device_add(struct platform_device *pdev)

{

int i, ret = 0;

 

if (!pdev)

  return -EINVAL;

 

if (!pdev->dev.parent)

  pdev->dev.parent = &platform_bus;

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

pdev->dev.bus = &platform_bus_type;

        //挂到platform总线上

if (pdev->id != -1)

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

else

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

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

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

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

 

  if (r->name == NULL)

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

 

  p = r->parent;

  if (!p) {

   if (resource_type(r) == IORESOURCE_MEM)

    p = &iomem_resource;

   else if (resource_type(r) == IORESOURCE_IO)

    p = &ioport_resource;

  }

 

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

   printk(KERN_ERR

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

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

   ret = -EBUSY;

   goto failed;

  }

}

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

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

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

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

 

ret = device_add(&pdev->dev);

        //注册到设备模型中

if (ret == 0)

  return ret;

 failed:

while (--i >= 0) {

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

  unsigned long type = resource_type(r);

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

   release_resource(r);

}

return ret;

}

3. mini2440内核注册platform设备过程
因为一种soc确定之后,其外设模块就已经确定了,所以注册platform设备就由板级初始化代码来完成,在mini2440中是mach-mini2440.cmini2440_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指针数组,定义如下:

1.   staticstruct 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.    };

static struct platform_device *mini2440_devices[] __initdata = {

  &s3c_device_usb,

  &s3c_device_rtc,

  &s3c_device_lcd,

  &s3c_device_wdt,

  &s3c_device_i2c0,

  &s3c_device_iis,

  &mini2440_device_eth,

  &s3c24xx_uda134x,

  &s3c_device_nand,

  &s3c_device_sdi,

  &s3c_device_usbgadget,

};

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

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.    };

struct platform_device s3c_device_lcd = {

  .name     = "s3c2410-lcd",

  .id     = -1,

  .num_resources    = ARRAY_SIZE(s3c_lcd_resource),

  .resource    = s3c_lcd_resource,

  .dev              = {

   .dma_mask   = &s3c_device_lcd_dmamask,

   .coherent_dma_mask  = 0xffffffffUL

  }

};

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

1.   staticstruct 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.    };

static struct resource s3c_lcd_resource[] = {

  [0] = {

   .start = S3C24XX_PA_LCD,

   .end   = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,

   .flags = IORESOURCE_MEM,

  },

  [1] = {

   .start = IRQ_LCD,

   .end   = IRQ_LCD,

   .flags = IORESOURCE_IRQ,

  }

};

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

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.   };

struct platform_driver {

  int (*probe)(struct platform_device *);

  int (*remove)(struct platform_device *);

  void (*shutdown)(struct platform_device *);

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

  int (*resume)(struct platform_device *);

  struct device_driver driver;

  struct platform_device_id *id_table;

};

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

1.   staticstruct 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.    };

static struct platform_driver s3c2412fb_driver = {

  .probe   = s3c2412fb_probe,

  .remove   = s3c2410fb_remove,

  .suspend  = s3c2410fb_suspend,

  .resume   = s3c2410fb_resume,

  .driver   = {

   .name  = "s3c2412-lcd",

   .owner  = THIS_MODULE,

  },

};

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

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.    }

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;

  return driver_register(&drv->driver);

}

这个函数首先使驱动属于platform_bus_type总线,将platform_driver结构中的定义的proberemove,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函数。这个函数实现如下:

1.   staticint 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.    

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

7.   if (pdrv->id_table)

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

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

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

11.    }

static int platform_match(struct device *dev, struct device_driver *drv)

{

  struct platform_device *pdev = to_platform_device(dev);

  struct platform_driver *pdrv = to_platform_driver(drv);

 

  /* match against the id table first */

  if (pdrv->id_table)

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

  /* fall-back to driver name match */

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

}

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

 

 

总结(个人总结):

        

原创粉丝点击