Linux驱动之Platform Driver

来源:岁月联盟 编辑:exp 时间:2011-09-16

# Platform Driver 平台驱动
    Linux中的所有设备驱动都需要注册到系统平台下,这此操作由platform_device.h中定义的一组函数完成.我们先来看看struct platform_driver这个结构体:
    view plaincopy to clipboardprint?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;   
       const struct platform_device_id *id_table; 
}; 
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; 
       const struct platform_device_id *id_table;
};
    该结构中包含了一组操作函数和一个struct device_driver的对像. 在我们自己的驱动中首先要做的就是定义platform_driver中的函数,并创建这个结构的一个对象实例, 然后在init()函数中调用platform_driver_register()向系统注册我们的驱动.
    如kernel-3.0的leds-s3c24xx.c 中的platform_driver的定义为:
view plaincopy to clipboardprint?static struct platform_driver s3c24xx_led_driver = { 
    .probe        = s3c24xx_led_probe, 
    .remove        = s3c24xx_led_remove, 
    .driver        = { 
        .name        = "s3c24xx_led", 
        .owner        = THIS_MODULE, 
    }, 
}; 
static struct platform_driver s3c24xx_led_driver = {
    .probe        = s3c24xx_led_probe,
    .remove        = s3c24xx_led_remove,
    .driver        = {
        .name        = "s3c24xx_led",
        .owner        = THIS_MODULE,
    },
};
    其它中指定了.driver的名字和所有者. 在调用platform_driver_register()注册驱动时,platform首先指定该设备是在系统中platform_bus主总线下,这只是一个虚拟总线驱动.然后platform驱动在调用driver_register(), driver_register在调用bus_add_driver()向系统总线管理器中注册驱动.如果成功会进行设备扫描,在dd.c中的driver_attach()中进行.实际会调用__driver_attach(),该函数又调用driver_probe_device(), driver_probe_device()分别会调用驱动注册时指定的总线probe和驱动自己的probe函数, 即我们上面注册的s3c24xx_led_probe()函数, 总线probe函数由platform_driver_register()中设置, 还设置了默认的Driver的操作函数.
    在我们的probe函数中主要是进行设备的探测和初始化,这里2410的led是连接到gpio上的,所以总是存在,但是如果是一些像usb那样的外设,我们就需要先探测对外的外设是否存在.在sc324xx_led_probe中主要做了以下3件事.
    1.创建一个私有数据对象, 并调用platform_set_drvdata()来附着到设备对象上.传给probe函数的参数是一个struct platform_device的平台设备对像(在那创建的呢?),
       struct platform_device在定义如下:
view plaincopy to clipboardprint?struct platform_device { 
    const char  * name; 
    int     id; 
    struct device   dev; 
    u32     num_resources; 
    struct resource * resource; 
 
    const struct platform_device_id *id_entry; 
 
    /* MFD cell pointer */ 
    struct mfd_cell *mfd_cell; 
 
    /* arch specific additions */ 
    struct pdev_archdata    archdata; 
}; 
struct platform_device {
 const char * name;
 int  id;
 struct device dev;
 u32  num_resources;
 struct resource * resource;

 const struct platform_device_id *id_entry;

 /* MFD cell pointer */
 struct mfd_cell *mfd_cell;

 /* arch specific additions */
 struct pdev_archdata archdata;
};

    其中有一个struct device对象,这个对象保存了系统中一个具体的设备的相关信息. 其中driver_data一般用于保存和设备具体相关的私有信息,如这里用于保存struct s3c24xx_gpio_led实例,但是我们不要直接操作它,而应该使用platform_set_drvdata()函数.
    2. 初始化具体的gpio
    3. 调用led_classdev_register向内核注册一个led类驱动
  小节: 这这个过程我们可以看到,在一个驱动程序中需要做那些事情,及基本的流程.
       1.需要定义一个platform_driver的对象来描述驱动相关信息.
       2.实现platform_driver中定义的必要的函数
       3.向平台驱动管理器注册驱动.
       4.驱动管理器会调用.probe函数来探测设备,需要在这里做设备初始化.
       5.内核根据操作请求调用驱动的相应函数,如open, close等.
      
# Platform Device 平台设备
    在platform driver中内核回调用我们注册的.probe函数,参数为一个platform_device的对象指针,但是这个从那来的呢, 以s3c2410 smdk板为例,对应的mach为mach-s3c2410,具体用的那个要看我们在配置内核的时候选择的什么平台和mach了,具体怎么配置或增加mach,不在本文的范围. 现在打开arch/arm/mach-s3c2410/mach-smdk2410.c 在文件的最后有个MACHINE_START的结构体:
view plaincopy to clipboardprint?MACHINE_START(SMDK2410, "SMDK2410") 
    .phys_io    = S3C2410_PA_UART, 
    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, 
    .boot_params    = S3C2410_SDRAM_PA + 0x100, 
    .map_io        = smdk2410_map_io, 
    .init_irq    = s3c24xx_init_irq, 
    .init_machine    = smdk_machine_init, 
    .timer        = &s3c24xx_timer, 
MACHINE_END  
MACHINE_START(SMDK2410, "SMDK2410")
    .phys_io    = S3C2410_PA_UART,
    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    .boot_params    = S3C2410_SDRAM_PA + 0x100,
    .map_io        = smdk2410_map_io,
    .init_irq    = s3c24xx_init_irq,
    .init_machine    = smdk_machine_init,
    .timer        = &s3c24xx_timer,
MACHINE_END
   该结构体描述了该mach相关的系统资源,内核在初始化时,根据该结构体的内容,进行初始化操作.找到其中指定的smdk_machine_init函数, 该函数的内容不多,现在我们只关心:
   platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs));
这个调用, 进入platform_add_devices可以看到在该函数中有一个for循环来遍历devs参数中的platform_device,然后调用platform_device_register注册设备.从而生成前面我们的s3c24xx_led_probe收到的platform_device.
   那主个devs又是那来的呢,回到上一层,可以看到传入的是一个静态定义的platform_device的数组,这里面就是根据我们的mach定义那的一些设置的配置信息,有点好pc的bios.所以要修改硬件配置就需要从这里开始.

作者“欢迎来到牛棚”