详解Linux2.6内核中基于platform机制的驱动模型(续)

来源:互联网 发布:近视眼矫正手术 知乎 编辑:程序博客网 时间:2024/06/15 08:35
8.1    初始化platform_bus
Platform总线的初始化是在platform_bus_init()完成的,代码如下:
http://lxr.linux.no/#linux+v2.6.25/drivers/base/platform.c#L621
  26struct device platform_bus = {
  27        .bus_id         = "platform",
  28};
  29EXPORT_SYMBOL_GPL(platform_bus);

621int __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}

该函数创建了一个名为 “platform”的设备,后续platform的设备都会以此为parent。在sysfs中表示为:所有platform类型的设备都会添加在 platform_bus所代表的目录下,即 /sys/devices/platform下面。
-sh-3.1# ls /sys/devices/platform/  
Fixed MDIO bus.0     fsl-i2c.0            serial8250
fsl-ehci.0           fsl-i2c.1            serial8250.0
fsl-gianfar.0        mpc83xx_spi.0        uevent
fsl-gianfar.1        mpc83xx_wdt.0
fsl-gianfar_mdio.-5  power

-sh-3.1# ls /sys/
block/    class/    firmware/ kernel/   power/   
bus/      devices/  fs/       module/  
-sh-3.1# ls /sys/bus/
i2c/         of_platform/ pci_express/ scsi/        usb/        
mdio_bus/    pci/         platform/    spi/        
-sh-3.1# ls /sys/bus/i2c/
devices/           drivers_autoprobe  uevent            
drivers/           drivers_probe   

-sh-3.1# ls /sys/bus/platform/devices/
Fixed MDIO bus.0/    fsl-gianfar_mdio.-5/ mpc83xx_wdt.0/
fsl-ehci.0/          fsl-i2c.0/           serial8250/
fsl-gianfar.0/       fsl-i2c.1/           serial8250.0/
fsl-gianfar.1/       mpc83xx_spi.0/      
-sh-3.1# ls /sys/bus/platform/drivers
drivers/           drivers_autoprobe  drivers_probe     
-sh-3.1# ls /sys/bus/platform/drivers/
fsl-ehci/         fsl-gianfar_mdio/ mpc83xx_spi/      serial8250/
fsl-gianfar/      fsl-i2c/          mpc83xx_wdt/    

platform_bus必须在系统注册任何platform driver和platform device之前初始化,那么这是如何实现的呢?

http://lxr.linux.no/#linux+v2.6.25/drivers/base/init.c

  14
  20void __init driver_init(void)
  21{
  22       
  23        devices_init();
  24        buses_init();
  25        classes_init();
  26        firmware_init();
  27        hypervisor_init();
  28
  29       
  32        platform_bus_init();
  33        system_bus_init();
  34        cpu_dev_init();
  35        memory_dev_init();
  36}

init/main.c
start_kernel  》 rest_init  》 kernel_init  》 do_basic_setup》driver_init 》platform_bus_init

http://lxr.linux.no/#linux+v2.6.25/drivers/base/init.c#L32
724
 731static void __init do_basic_setup(void)
 732{
 733       
 734        init_workqueues();
 735        usermodehelper_init();
 736        driver_init();
 737        init_irq_proc();
 738        do_initcalls();
 739}

platform driver和platform device的初始化是在do_initcalls中进行的。

8.2    定义platform_device
http://lxr.linux.no/#linux+v2.6.25/arch/arm/plat-s3c24xx/devs.c#L276中定义了系统的资源,是一个高度可移植的文件,大部分板级资源都在这里集中定义。

274
 275
 276static struct resource s3c_i2c_resource[] = {
 277        [0] = {
 278                .start = S3C24XX_PA_IIC,
 279                .end   = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,
 280                .flags = IORESOURCE_MEM,
 281        },
 282        [1] = {
 283                .start = IRQ_IIC,
 284                .end   = IRQ_IIC,
 285                .flags = IORESOURCE_IRQ,
 286        }
 287
 288};
 289
 290struct platform_device s3c_device_i2c = {
 291        .name             = "s3c2410-i2c",
 292        .id               = -1,
 293        .num_resources    = ARRAY_SIZE(s3c_i2c_resource),
 294        .resource         = s3c_i2c_resource,
 295};
 296
 297EXPORT_SYMBOL(s3c_device_i2c);

设备名称为s3c2410-i2c,“-1”只有一个i2c设备,两个资源s3c_i2c_resource,分别为i2c控制器的寄存器空间和中断信息。

8.3    注册platform_device

定义了platform_device后,需要添加到系统中,就可以调用函数platform_add_devices。
http://lxr.linux.no/#linux+v2.6.25/arch/arm/mach-s3c2440/mach-smdk2440.c

smdk2440_devices将系统资源组织起来,统一注册进内核。

151static struct platform_device *smdk2440_devices[] __initdata = {
 152        &s3c_device_usb,
 153        &s3c_device_lcd,
 154        &s3c_device_wdt,
 155        &s3c_device_i2c,
 156        &s3c_device_iis,
 157};

166static void __init smdk2440_machine_init(void)
 167{
 168        s3c24xx_fb_set_platdata(&smdk2440_fb_info);
 169
 170        platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
 171        smdk_machine_init();
 172}
 173
 174MACHINE_START(S3C2440, "SMDK2440")
 175       
 176        .phys_io        = S3C2410_PA_UART,
 177        .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
 178        .boot_params    = S3C2410_SDRAM_PA + 0x100,
 179
 180        .init_irq       = s3c24xx_init_irq,
 181        .map_io         = smdk2440_map_io,
 182        .init_machine   = smdk2440_machine_init,
 183        .timer          = &s3c24xx_timer,
 184MACHINE_END

170        platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
将系统所有资源注册进系统,在此之前platform bus需要初始化成功,否则无法将platform devices挂接到platform bus上。为了保证platform drive初始化时,相关platform资源已经注册进系统,smdk2440_machine_init需要很早执行,而其作为平台初始化init_machine 时,将优先于系统所有驱动的初始化。

其调用顺序如下:
start_kernel》setup_arch》init_machine》arch_initcall(customize_machine)
http://lxr.linux.no/#linux+v2.6.25/arch/arm/kernel/setup.c#L788
786arch_initcall(customize_machine);
 787
 788void __init setup_arch(char **cmdline_p)
 789{
 790        struct tag *tags = (struct tag *)&init_tags;
 791        struct machine_desc *mdesc;
 792        char *from = default_command_line;
 793
 794        setup_processor();
 795        mdesc = setup_machine(machine_arch_type);
//根据machine id获得移植时定义的machine desc结构
 796        machine_name = mdesc->name;
 797
 798        if (mdesc->soft_reboot)
 799                reboot_setup("s");
 800
 801        if (__atags_pointer)
 802                tags = phys_to_virt(__atags_pointer);
 803        else if (mdesc->boot_params)
 804                tags = phys_to_virt(mdesc->boot_params);
 805
 806       
 810        if (tags->hdr.tag != ATAG_CORE)
 811                convert_to_tag_list(tags);
 812        if (tags->hdr.tag != ATAG_CORE)
 813                tags = (struct tag *)&init_tags;
 814
 815        if (mdesc->fixup)
 816                mdesc->fixup(mdesc, tags, &from, &meminfo);
 817
 818        if (tags->hdr.tag == ATAG_CORE) {
 819                if (meminfo.nr_banks != 0)
 820                        squash_mem_tags(tags);
 821                save_atags(tags);
 822                parse_tags(tags);
 823        }
 824
 825        init_mm.start_code = (unsigned long) &_text;
 826        init_mm.end_code   = (unsigned long) &_etext;
 827        init_mm.end_data   = (unsigned long) &_edata;
 828        init_mm.brk        = (unsigned long) &_end;
 829
 830        memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
 831        boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
 832        parse_cmdline(cmdline_p, from);
 833        paging_init(&meminfo, mdesc);
 834        request_standard_resources(&meminfo, mdesc);
 835
 836#ifdef CONFIG_SMP
 837        smp_init_cpus();
 838#endif
 839
 840        cpu_init();
 841
 842       
 845        init_arch_irq = mdesc->init_irq;
 846        system_timer = mdesc->timer;
 847        init_machine = mdesc->init_machine;
//对init_machine指针赋值
 848
 849#ifdef CONFIG_VT
 850#if defined(CONFIG_VGA_CONSOLE)
 851        conswitchp = &vga_con;
 852#elif defined(CONFIG_DUMMY_CONSOLE)
 853        conswitchp = &dummy_con;
 854#endif
 855#endif
 856}

777static void (*init_machine)(void) __initdata;
 778
 779static int __init customize_machine(void)
 780{
 781       
 782        if (init_machine)
 783                init_machine();
 784        return 0;
 785}
 786arch_initcall(customize_machine);
arch_initcall将customize_machine放在特定的段中,系统将在某个地方运行所有的arch_initcall修饰的函数。

http://lxr.linux.no/#linux+v2.6.25/include/linux/init.h#L182
152#ifndef MODULE  //非可加载模块,即编译链接进内核的代码
 153
 154#ifndef __ASSEMBLY__
 155
 156
 165
 166#define __define_initcall(level,fn,id) \
 167        static initcall_t __initcall_##fn##id __used \
 168        __attribute__((__section__(".initcall" level ".init"))) = fn
 169
 170
 176#define pure_initcall(fn)               __define_initcall("0",fn,0)
 177
 178#define core_initcall(fn)               __define_initcall("1",fn,1)
 179#define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)
 180#define postcore_initcall(fn)           __define_initcall("2",fn,2)
 181#define postcore_initcall_sync(fn)      __define_initcall("2s",fn,2s)
 182#define arch_initcall(fn)               __define_initcall("3",fn,3)
 183#define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)
 184#define subsys_initcall(fn)             __define_initcall("4",fn,4)
 185#define subsys_initcall_sync(fn)        __define_initcall("4s",fn,4s)
 186#define fs_initcall(fn)                 __define_initcall("5",fn,5)
 187#define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)
 188#define rootfs_initcall(fn)             __define_initcall("rootfs",fn,rootfs)
 189#define device_initcall(fn)             __define_initcall("6",fn,6)
 190#define device_initcall_sync(fn)        __define_initcall("6s",fn,6s)
 191#define late_initcall(fn)               __define_initcall("7",fn,7)
 192#define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)
 193
 194#define __initcall(fn) device_initcall(fn)
 195
 196#define __exitcall(fn) \
 197        static exitcall_t __exitcall_##fn __exit_call = fn
 198
。。。。。。。。。
 239#endif
 240
 241
 249#define module_init(x)  __initcall(x);
 250
 251
 261#define module_exit(x)  __exitcall(x);
 262
 263#else

各种xx_core_initcall被定义到了不同的分级的段中
所以arch_initcall == __initcall_fn3 它将被链接器放于section  .initcall3.init. 中

module_init()==__initcall(fn)==device_initcall(fn)== __initcall_fn6

各个段的优先级由链接脚本定义
http://lxr.linux.no/#linux+v2.6.25/include/asm-generic/vmlinux.lds.h#L328
#define INITCALLS       \
   *(.initcall0.init)      \
   *(.initcall0s.init)      \
   *(.initcall1.init)      \
   *(.initcall1s.init)      \
   *(.initcall2.init)      \
   *(.initcall2s.init)      \
   *(.initcall3.init)      \
   *(.initcall3s.init)      \
   *(.initcall4.init)      \
   *(.initcall4s.init)      \
   *(.initcall5.init)      \
   *(.initcall5s.init)      \
 *(.initcallrootfs.init)      \
   *(.initcall6.init)      \
   *(.initcall6s.init)      \
   *(.initcall7.init)      \
   *(.initcall7s.init)

这个__initcall_start是在文件arch/xxx/kernel/vmlinux.lds.S定义的:
__initcall_start = .;
   INITCALLS
  __initcall_end = .;

http://lxr.linux.no/#linux+v2.6.25/init/main.c#L664
664static void __init do_initcalls(void)
 665{
 666        initcall_t *call;
 667        int count = preempt_count();
 668
 669        for (call = __initcall_start; call < __initcall_end; call++) {
.。。。。
 682
 683                result = (*call)();
 684
。。。 }              
 720       
 721        flush_scheduled_work();
 722}

因此__initcall_fnx,数字越小,越先被调用,故arch_initcall优先于module_init所修饰的函数。

arch_initcall修饰的函数的调用顺序如下:
start_kernel  》 rest_init(在setup_arch之后)  》 kernel_init  》 do_basic_setup》do_initcalls(在driver_init()之后),因为platform_bus_init在此之前已经初始化完毕了,便可将设备挂接到总线上了。

8.4    定义platform_driver
Platform bus和设备都定义好了后,需要定义一个platform driver用来驱动此设备。

对于设备来说:
290struct platform_device s3c_device_i2c = {
 291        .name             = "s3c2410-i2c",
 292        .id               = -1,
 293        .num_resources    = ARRAY_SIZE(s3c_i2c_resource),
 294        .resource         = s3c_i2c_resource,
 295};
 296
 297EXPORT_SYMBOL(s3c_device_i2c);

根据platform总线上device和driver的匹配规则可知,I2C 的platform driver的名字是s3c2410-i2c。

http://lxr.linux.no/#linux+v2.6.25/drivers/i2c/busses/i2c-s3c2410.c#L1
903
 904
 905static struct platform_driver s3c2410_i2c_driver = {
 906        .probe          = s3c24xx_i2c_probe,
 907        .remove         = s3c24xx_i2c_remove,
 908        .resume         = s3c24xx_i2c_resume,
 909        .driver         = {
 910                .owner  = THIS_MODULE,
 911                .name   = "s3c2410-i2c",
 912        },
 913};

8.5    注册platform_driver
http://lxr.linux.no/#linux+v2.6.25/drivers/i2c/busses/i2c-s3c2410.c#L1

925static int __init i2c_adap_s3c_init(void)
 926{
 927        int ret;
 928
 929        ret = platform_driver_register(&s3c2410_i2c_driver);
 930        if (ret == 0) {
 931                ret = platform_driver_register(&s3c2440_i2c_driver);
 932                if (ret)
 933                        platform_driver_unregister(&s3c2410_i2c_driver);
 934        }
 935
 936        return ret;
 937}
 938

945module_init(i2c_adap_s3c_init);
 946module_exit(i2c_adap_s3c_exit);

在i2c_adap_s3c_init中注册s3c2410_i2c_driver,那么i2c_adap_s3c_init何时执行的呢?module_init(i2c_adap_s3c_init)表明其存放在initcall段,调用顺序如下:
init/main.c
start_kernel  》 rest_init  》 kernel_init  》 do_basic_setup》do_initcalls,因为platform_bus_init在此之前已经初始化完毕了,且设备已经注册到内核中了,驱动将和内核绑定,并最终调用s3c24xx_i2c_probe。

748
 752
 753static int s3c24xx_i2c_probe(struct platform_device *pdev)
 754{
 755        struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
 756        struct resource *res;
 757        int ret;
 758
 759       
 760
 761        i2c->dev = &pdev->dev;
 762        i2c->clk = clk_get(&pdev->dev, "i2c");
 763        if (IS_ERR(i2c->clk)) {
 764                dev_err(&pdev->dev, "cannot get clock\n");
 765                ret = -ENOENT;
 766                goto err_noclk;
 767        }
 768
 769        dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
 770
 771        clk_enable(i2c->clk);
 772
 773       
 774
 775        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 776        if (res == NULL) {
 777                dev_err(&pdev->dev, "cannot find IO resource\n");
 778                ret = -ENOENT;
 779                goto err_clk;
 780        }
 781
 782        i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1,
 783                                         pdev->name);
 784
 785        if (i2c->ioarea == NULL) {
 786                dev_err(&pdev->dev, "cannot request IO\n");
 787                ret = -ENXIO;
 788                goto err_clk;
 789        }
 790
 791        i2c->regs = ioremap(res->start, (res->end-res->start)+1);
 792
 793        if (i2c->regs == NULL) {
 794                dev_err(&pdev->dev, "cannot map IO\n");
 795                ret = -ENXIO;
 796                goto err_ioarea;
 797        }
 798
 799        dev_dbg(&pdev->dev, "registers %p (%p, %p)\n", i2c->regs, i2c->ioarea, res);
 800
 801       
 802
 803        i2c->adap.algo_data = i2c;
 804        i2c->adap.dev.parent = &pdev->dev;
 805
 806       
 807
 808        ret = s3c24xx_i2c_init(i2c);
 809        if (ret != 0)
 810                goto err_iomap;
 811
 812       
 815
 816        res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
 817        if (res == NULL) {
 818                dev_err(&pdev->dev, "cannot find IRQ\n");
 819                ret = -ENOENT;
 820                goto err_iomap;
 821        }
 822
 823        ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,
 824                          pdev->name, i2c);
 825
 826        if (ret != 0) {
 827                dev_err(&pdev->dev, "cannot claim IRQ\n");
 828                goto err_iomap;
 829        }
 830
 831        i2c->irq = res;
 832               
 833        dev_dbg(&pdev->dev, "irq resource %p (%lu)\n", res,
 834                (unsigned long)res->start);
 835
 836        ret = i2c_add_adapter(&i2c->adap);
 837        if (ret < 0) {
 838                dev_err(&pdev->dev, "failed to add bus to i2c core\n");
 839                goto err_irq;
 840        }
 841
 842        platform_set_drvdata(pdev, i2c);
 843
 844        dev_info(&pdev->dev, "%s: S3C I2C adapter\n", i2c->adap.dev.bus_id);
 845        return 0;
 846
 847 err_irq:
 848        free_irq(i2c->irq->start, i2c);
 849
 850 err_iomap:
 851        iounmap(i2c->regs);
 852
 853 err_ioarea:
 854        release_resource(i2c->ioarea);
 855        kfree(i2c->ioarea);
 856
 857 err_clk:
 858        clk_disable(i2c->clk);
 859        clk_put(i2c->clk);
 860
 861 err_noclk:
 862        return ret;
 863}

当进入probe函数后,需要获取设备的资源信息,常用获取资源的函数主要是:
struct resource * platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
根据参数type所指定类型,例如IORESOURCE_MEM,来获取指定的资源。
struct int platform_get_irq(struct platform_device *dev, unsigned int num);
获取资源中的中断号。
struct resource * platform_get_resource_byname(struct platform_device *dev, unsigned int type, char *name);
根据参数name所指定的名称,来获取指定的资源。
int platform_get_irq_byname(struct platform_device *dev, char *name);
根据参数name所指定的名称,来获取资源中的中断号。

此probe函数获取物理IO空间,通过request_mem_region和ioremap等操作物理地址转换成内核中的虚拟地址,初始化I2C控制器,通过platform_get_irq或platform_get_resource得到设备的中断号以后,就可以调用request_irq函数来向系统注册中断,并将此I2C控制器添加到系统中。

8.6    操作设备
进行了platform_device_register 和platform_driver_register后,驱动的相应信息就出现在sys目录的相应文件夹下,然后,我们该如何调用设备呢??怎么对设备进行打开读写等操作呢???

Platform总线只是为了方便管理挂接在CPU总线上的设备,与用户空间的交互,如读写还是需要利用file_operations。当然如果此platform设备无需和用户空间交互,则无需file_operations实例。

对于I2C总线来说,其file_operations如下:
http://lxr.linux.no/#linux+v2.6.25/drivers/i2c/i2c-core.c#L461
 478static const struct file_operations i2cdev_fops = {
 479        .owner          = THIS_MODULE,
 480        .llseek         = no_llseek,
 481        .read           = i2cdev_read,
 482        .write          = i2cdev_write,
 483        .ioctl          = i2cdev_ioctl,
 484        .open           = i2cdev_open,
 485        .release        = i2cdev_release,
 486};

其和platform bus的区别在于,platform bus提供机制访问I2C 控制器本身的资源,而I2C总线提供访问I2C 控制器上挂接的I2C设备的机制

阅读全文
0 0
原创粉丝点击