• linux 内核驱动--Platform Device和Platform_driver注册过程


    linux 内核驱动--Platform Device和Platform_driver注册过程

    从 Linux 2.6 起引入了一套新的驱动管理和注册机制 :Platform_device 和 Platform_driver 。

    Linux 中大部分的设备驱动,都可以使用这套机制 ,  设备用 Platform_device 表示,驱动用 Platform_driver 进行注册。


    Linux platform driver 机制和传统的 device driver  机制 ( 通过 driver_register 函数进行注册 ) 相比,一个十分明显的优势在于 platform 机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过 platform device 提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性 ( 这些标准接口是安全的 ) 。


    Platform 机制的本身使用并不复杂,由两部分组成: platform_device 和 platfrom_driver 。

    通过 Platform 机制开发发底层驱动的大致流程为: 定义platform_add_devices --> 注册platform_device --> 定义platform_add_driver --> 注册platform_driver 。

    1、platform_device注册过程:


    首先要确认的就是设备的资源信息,例如设备的地址,中断号等。

    在 2.6 内核中 platform 设备用结构体 platform_device 来描述,该结构体定义在 kernelincludelinuxplatform_device.h 中,

    struct platform_device {
        const char    * name;
        int        id;
        struct device    dev;
        u32        num_resources;
        struct resource    *  resource ;
        struct platform_device_id    *id_entry;
    };

    该结构一个重要的元素是 resource ,该元素存入了最为重要的设备资源信息,定义在 kernelincludelinuxioport.h 中,

    struct resource {
        resource_size_t start;
        resource_size_t end;
        const char *name;
        unsigned long flags;
        struct resource *parent, *sibling, *child;
    };
     

    下面举 s3c6410 平台的 i2c 驱动作为例子来说明:

    static struct platform_device *smdk6410_devices [] __initdata = {
    #ifdef CONFIG_SMDK6410_SD_CH0
        &s3c_device_hsmmc0,
    #endif
    #ifdef CONFIG_SMDK6410_SD_CH1
        &s3c_device_hsmmc1,
    #endif
        &s3c_device_i2c0 ,
        &s3c_device_i2c1,
        &s3c_device_fb,
        &s3c_device_usb,
        &s3c_device_usb_hsotg,
        &smdk6410_lcd_powerdev,

        &smdk6410_smsc911x,
    };
    把一个或几个设备资源放在一起,便于集中管理,其中IIC设备 platform_device如下:
    struct platform_device  s3c_device_i2c0  = {
        .name          = "s3c2410-i2c",
    #ifdef CONFIG_S3C_DEV_I2C1
        .id          = 0,
    #else
        .id          = -1,
    #endif
        .num_resources      = ARRAY_SIZE(s3c_i2c_resource ),
        .resource      = s3c_i2c_resource,
    };


    具体resource如下:
    static struct resource  s3c_i2c_resource [] = {
        [0] = {
            .start = S3C_PA_IIC,
            .end   = S3C_PA_IIC + SZ_4K - 1,
            .flags = IORESOURCE_MEM,
        },
        [1] = {
            .start = IRQ_IIC,
            .end   = IRQ_IIC,
            .flags = IORESOURCE_IRQ,
        },
    };

    这里定义了两组 resource ,它描述了一个 I2C 设备的资源,第 1 组描述了这个 I2C 设备所占用的总线地址范围, IORESOURCE_MEM 表示第 1 组描述的是内存类型的资源信息,第 2 组描述了这个 I2C 设备的中断号, IORESOURCE_IRQ 表示第 2 组描述的是中断资源信息。设备驱动会根据 flags 来获取相应的资源信息。


    定义好了 platform_device 结构体后就可以调用函数 platform_add_devices 向系统中添加该设备了,之后可以调用 platform_driver_register() 进行设备注册。

    s3c6410-i2c的platform_device是在系统启动时,在mach-smdk6410.c里的smdk6410_machine_init()函数里进行注册的,这个函数申明为arch_initcall的函数调用,arch_initcall的优先级高于module_init。所以会在Platform驱动注册之前调用。(详细参考imach-smdk6410.c)

    static void __init smdk6410_machine_init(void)
    {
        s3c_i2c0_set_platdata(NULL);
        s3c_i2c1_set_platdata(NULL);
        s3c_fb_set_platdata(&smdk6410_lcd_pdata);

        gpio_request(S3C64XX_GPN(5), "LCD power");
        gpio_request(S3C64XX_GPF(13), "LCD power");
        gpio_request(S3C64XX_GPF(15), "LCD power");

        i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
        i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));

        platform_add_devices(smdk6410_devices, ARRAY_SIZE(smdk6410_devices));
        //添加多设备

    }


    int platform_add_devices(struct platform_device **devs, int num)
    {
        int i, ret = 0;

        for (i = 0; i < num; i++) {
             ret = platform_device_register(devs[i]);
            if (ret) {
                while (--i >= 0)
                    platform_device_unregister(devs[i]);
                break;
            }
        }

        return ret;
    }


    int platform_device_register(struct platform_device *pdev)
    {
        device_initialize(&pdev->dev);
        return platform_device_add(pdev);
    }


    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;

        pdev->dev.bus = &platform_bus_type;

        if (pdev->id != -1)
            dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);//如果有id 表示有多个同类设备用 pdev->name和 pdev->id标识该设备
        else
            dev_set_name(&pdev->dev, "%s", pdev->name);
    //否则,只用 pdev->name标识该设备

        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; //   作为 IOMEM 资源分配  
                else if (resource_type(r) == IORESOURCE_IO)
                     p = &ioport_resource; //   作为 IO PORT资源分配  
            }

             if (p && insert_resource(p, r)) { // 将新的 resource 插入内核 resource tree
                printk(KERN_ERR
                       "%s: failed to claim resource %d ",
                       dev_name(&pdev->dev), i);
                ret = -EBUSY;
                goto failed;
            }
        }

        pr_debug("Registering platform device '%s'. Parent at %s ",
             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;
    }

    2、platform_driver注册过程:


    在platform_device注册完成后,就可以进行platform_driver注册了,在驱动初始化函数中调用函数platform_driver_register() 注册 platform_driver ,需要注意的是 s3c_device_i2c 结构中 name 元素和 s3c6410_i2c_driver 结构中 driver.name 必须是相同的,这样在 platform_driver_register() 注册时会对所有已注册的所有 platform_device 中的 name 和当前注册的 platform_driver 的 driver.name 进行比较,只有找到相同的名称的 platfomr_device 才能注册成功。

    static struct platform_driver s3c24xx_i2c_driver = {
        .probe        = s3c24xx_i2c_probe,
        .remove        = s3c24xx_i2c_remove,
        .suspend_late    = s3c24xx_i2c_suspend_late,
        .resume        = s3c24xx_i2c_resume,
        .id_table    = s3c24xx_driver_ids,
        .driver        = {
            .owner    = THIS_MODULE,
            .name    = "s3c-i2c",
        },
    };

    static int __init i2c_adap_s3c_init(void)
    {
         return platform_driver_register(&s3c24xx_i2c_driver);//注册IIC驱动
    }

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


    int driver_register(struct device_driver *drv)
    {
        int ret;
        struct device_driver *other;

        BUG_ON(!drv->bus->p);

        if ((drv->bus->probe && drv->probe) ||
            (drv->bus->remove && drv->remove) ||
            (drv->bus->shutdown && drv->shutdown))
            printk(KERN_WARNING "Driver '%s' needs updating - please use "
                "bus_type methods ", drv->name);

        other = driver_find(drv->name, drv->bus);//检查Driver是否已经存在
        if (other) {
            put_driver(other);
            printk(KERN_ERR "Error: Driver '%s' is already registered, "
                "aborting... ", drv->name);
            return -EBUSY;
        }
        //若不存在,则添加驱动到驱动树。
        ret = bus_add_driver(drv);
        if (ret)
            return ret;
        ret = driver_add_groups(drv, drv->groups);
        if (ret)
            bus_remove_driver(drv);
        return ret;
    }


    int  bus_add_driver (struct device_driver *drv)
    {
        struct bus_type *bus;
        struct driver_private *priv;
        int error = 0;

        bus = bus_get(drv->bus);
        if (!bus)
            return -EINVAL;

        pr_debug("bus: '%s': add driver %s ", bus->name, drv->name);

        priv = kzalloc(sizeof(*priv), GFP_KERNEL);
        if (!priv) {
            error = -ENOMEM;
            goto out_put_bus;
        }
        klist_init(&priv->klist_devices, NULL, NULL);
        priv->driver = drv;
        drv->p = priv;
        priv->kobj.kset = bus->p->drivers_kset;
        error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
                         "%s", drv->name);
        if (error)
            goto out_unregister;

        if (drv->bus->p->drivers_autoprobe) {
            error = driver_attach(drv);
            if (error)
                goto out_unregister;
        }
        klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
        module_add_driver(drv->owner, drv);

        error = driver_create_file(drv, &driver_attr_uevent);
        if (error) {
            printk(KERN_ERR "%s: uevent attr (%s) failed ",
                __func__, drv->name);
        }
        error = driver_add_attrs(bus, drv);
        if (error) {
           
            printk(KERN_ERR "%s: driver_add_attrs(%s) failed ",
                __func__, drv->name);
        }
        error = add_bind_files(drv);
        if (error) {
           
            printk(KERN_ERR "%s: add_bind_files(%s) failed ",
                __func__, drv->name);
        }

        kobject_uevent(&priv->kobj, KOBJ_ADD);
        return 0;
    out_unregister:
        kfree(drv->p);
        drv->p = NULL;
        kobject_put(&priv->kobj);
    out_put_bus:
        bus_put(bus);
        return error;
    }

  • 相关阅读:
    循序渐进Java Socket网络编程(多客户端、信息共享、文件传输)
    android开发设置dialog的高宽
    重温WCF之消息契约(MessageContract)(六)
    重温WCF之数单向通讯、双向通讯、回调操作(五)
    重温WCF之数据契约和序列化(四)
    重温WCF之发送和接收SOAP头(三)
    重温WCF之一个服务实现多个契约(二)
    重温WCF之构建一个简单的WCF(一)(1)通过控制台和IIS寄宿服务
    sql server 用户'sa'登录失败(错误18456)(转载)
    jQuery ajax同步的替换方法,使用 $.Deferred()对象
  • 原文地址:https://www.cnblogs.com/haimeng2010/p/3582403.html
Copyright © 2020-2023  润新知