关于of_register_platform_driver()

本文深入解析Linux中platform_driver_register与of_register_platform_driver的区别,重点介绍它们如何帮助驱动程序注册及获取硬件信息,尤其是在PowerPC和ARM架构上的实现差异。
一般认为驱动需要完成两部分:
1,  对上层的接口注册;
2,  对硬件的读写控制;

对与上层的接口注册很好理解, 只需要按照固定的模块初始化方法,就可以生成设备节点.

而对于硬件的控制部分,  一部分人喜欢直接算出寄存器物理地址,然后使用ioremap获得控制地址(至少我原来是这样做的).

实际上, linux在初始化时已经将关于soc的操作抽象成一个总线设备类型 对于不同soc的操作可以理解为总线上不同的设备,  每个设备都有自己的所有信息(包括地址, 中断等),  驱动在初始化时只需要使用of_register_platform_driver()函数就可以根据驱动提供的条件找到该设备. 并传给你需要的probe函数,  驱动就可以获得详细的硬件信息.

而对于这种方式的实现主要集中在arch/powerpc/kernel/of_platform.c 以及 /driver/of/目录下的源码, linux在启动过程中会初始化完所有soc的设备资源,  该资源由dts文件给出.采用这总方式可以使相同cpu相同接口设备实现更好的兼容.
 
 
platform通常是一种虚拟的总线驱动,如i2c总线,他可以按照i2c总线规范封装一层虚拟驱动,让i2c下挂设备可以不用关心i2c协议就顺利进行相关操作。

of_register_platform_driver主要涉及soc,ebc等总线。
platform_driver_register则更广泛应用,比如i2c总线多用该函数注册。

当然并不是说of_register_platform_driver就做不了i2c总线,其实注意到这两种platform的结构体其实基本是一样的,只能说platform_driver_register应用得更广泛,of_register_platform_driver则更有针对性一点。

在ARM体系结构中常见的是platform_driver_register
在powerpc中是of_register_platform_driver
下面通过具体的代码来分析其区别
platform_driver_register在driver/base/platform.c中定义
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);
}
EXPORT_SYMBOL_GPL(platform_driver_register);
——————————————————————————————————
而of_register_platform_driver在of_platform.c中定义
static inline int of_register_platform_driver(struct of_platform_driver *drv)
{
    return of_register_driver(drv, &of_platform_bus_type);
}
int of_register_driver()在driver/of/plateform.c中定义
int of_register_driver(struct of_platform_driver *drv, struct bus_type *bus)
{
    drv->driver.bus = bus;

    /* register with core */
    return driver_register(&drv->driver);
}
而着最终都是调用的driver_register();
_____________________________________
这是由于二者结构获取硬件信息 的方式不同造成 的,在powerpc体系是通过dts
对比platform_driver和of_platform_driver
在include/linux/platform_device.h
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;
};
在include/linux/of_platform_device.h
struct of_platform_driver
{
    int    (*probe)(struct of_device* dev,
             const struct of_device_id *match);
    int    (*remove)(struct of_device* dev);

    int    (*suspend)(struct of_device* dev, pm_message_t state);
    int    (*resume)(struct of_device* dev);
    int    (*shutdown)(struct of_device* dev);

    struct device_driver    driver;
};
#define    to_of_platform_driver(drv) \
    container_of(drv,struct of_platform_driver, driver)
of_device_id 和platform_device_id
可以看出 主要所区别在 of_device 和platform_device
***********************************************************
在arch/powerpc/include/asm/of_device.h定义了
/*
 * The of_device is a kind of "base class" that is a superset of
 * struct device for use by devices attached to an OF node and
 * probed using OF properties.
 */
struct of_device
{
    struct device        dev;        /* Generic device interface */
    struct pdev_archdata    archdata;
};

/*
 * Struct used for matching a device
 */
struct of_device_id
{
    char    name[32];
    char    type[32];
    char    compatible[128];  //dts中看到这个东东了哈。通过它的匹配获取硬件资源
#ifdef __KERNEL__
    void    *data;
#else
    kernel_ulong_t data;
#endif
};
——————————————————————————————————
在在include/linux/of_platform_device.h定义
struct platform_device {
    const char    * name;
    int        id;
    struct device    dev;//这个是通用的
    u32        num_resources;
    struct resource    * resource; //获取硬件资源

    const struct platform_device_id    *id_entry;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};从上述定义的文件也可以发现powerpc 并不是一个通用的,和自己体系相关

struct platform_device_id {
    char name[PLATFORM_NAME_SIZE];
    kernel_ulong_t driver_data
            __attribute__((aligned(sizeof(kernel_ulong_t))));
};

*********************************
返回到我们的
int of_register_driver(struct of_platform_driver *drv, struct bus_type *bus)
{
    drv->driver.bus = bus;

    /* register with core */
    return driver_register(&drv->driver);
}
bus 是从of_register_platform_driver中传入 of_platform_bus_type,是个全局变量在
arch/powerpc/kernel/of_platform.c中定义
struct bus_type of_platform_bus_type = {
       .uevent    = of_device_uevent,
};

of_device_uevent,在同上目录of_device.c定义
int of_device_uevent(struct device *dev, struct kobj_uevent_env *env)
{
    struct of_device *ofdev;
    const char *compat;
    int seen = 0, cplen, sl;

    if (!dev)
        return -ENODEV;

    ofdev = to_of_device(dev);

    if (add_uevent_var(env, "OF_NAME=%s", ofdev->dev.of_node->name))
        return -ENOMEM;

    if (add_uevent_var(env, "OF_TYPE=%s", ofdev->dev.of_node->type))
        return -ENOMEM;

        /* Since the compatible field can contain pretty much anything
         * it's not really legal to split it out with commas. We split it
         * up using a number of environment variables instead. */

    compat = of_get_property(ofdev->dev.of_node, "compatible", &cplen);//根据compatible获取资源 具体看dts文件,在i2c学习中提过
    while (compat && *compat && cplen > 0) {
        if (add_uevent_var(env, "OF_COMPATIBLE_%d=%s", seen, compat))
            return -ENOMEM;

        sl = strlen (compat) + 1;
        compat += sl;
        cplen -= sl;
        seen++;
    }

    if (add_uevent_var(env, "OF_COMPATIBLE_N=%d", seen))
        return -ENOMEM;

    /* modalias is trickier, we add it in 2 steps */
    if (add_uevent_var(env, "MODALIAS="))
        return -ENOMEM;
    sl = of_device_get_modalias(ofdev, &env->buf[env->buflen-1],
                    sizeof(env->buf) - env->buflen);
    if (sl >= (sizeof(env->buf) - env->buflen))
        return -ENOMEM;
    env->buflen += sl;

    return 0;
}

/**
 * platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 */
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);
}
EXPORT_SYMBOL_GPL(platform_driver_register);
——————————————————————————————————
struct bus_type {
    const char        *name;
    struct bus_attribute    *bus_attrs;
    struct device_attribute    *dev_attrs;
    struct driver_attribute    *drv_attrs;

    int (*match)(struct device *dev, struct device_driver *drv);
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);

    const struct dev_pm_ops *pm;

    struct bus_type_private *p;
};

struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_attrs    = platform_dev_attrs,
    .match        = platform_match,
    .uevent        = platform_uevent,
    .pm        = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

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

    const struct platform_device_id    *id_entry;

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

/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);//获取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);
}
platform_match_id()代码如下 id_table中匹配名字,成功返回
static const struct platform_device_id *platform_match_id(
            const struct platform_device_id *id,
            struct platform_device *pdev)
{
    while (id->name[0]) {
        if (strcmp(pdev->name, id->name) == 0) {
            pdev->id_entry = id;
            return id;
        }
        id++;
    }
    return NULL;
}
********************
static int platform_uevent(struct device *dev, struct kobj_uevent_env *env)
{
    struct platform_device    *pdev = to_platform_device(dev);

    add_uevent_var(env, "MODALIAS=%s%s", PLATFORM_MODULE_PREFIX,
        (pdev->id_entry) ? pdev->id_entry->name : pdev->name);
    return 0;
}
和powerpc类似,都调用了add_uevent_var
/**
 * add_uevent_var - add key value string to the environment buffer
 * @env: environment buffer structure
 * @format: printf format for the key=value pair
 *
 * Returns 0 if environment variable was added successfully or -ENOMEM
 * if no space was available.
 */
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...)
{
    va_list args;
    int len;

    if (env->envp_idx >= ARRAY_SIZE(env->envp)) {
        WARN(1, KERN_ERR "add_uevent_var: too many keys\n");
        return -ENOMEM;
    }

    va_start(args, format);
    len = vsnprintf(&env->buf[env->buflen],
            sizeof(env->buf) - env->buflen,
            format, args);
    va_end(args);

    if (len >= (sizeof(env->buf) - env->buflen)) {
        WARN(1, KERN_ERR "add_uevent_var: buffer size too small\n");
        return -ENOMEM;
    }

    env->envp[env->envp_idx++] = &env->buf[env->buflen];
    env->buflen += len + 1;
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值