【linux iic子系统】i2c设备与驱动匹配过程(三)

9 篇文章 5 订阅

本文讲解基于linux4.18内核。

重要

根据前面内容我们可以知道i2c子系统有几个重要的东西:

I2C总线:i2c总线主要就是维护着两个链表——驱动、设备。i2c驱动注册时候,通过i2c_driver结构体内部driver成员挂载到总线上。i2c设备注册时候,通过i2c_client结构体内部device成员挂载到总线上。所以总线作用就是i2c设备和I2C驱动的匹配和删除等。值得注意的是,i2c适配器也是挂在总线上(它的作用会在《i2c设备添加方法》章节里说明)!

 

I2C驱动:使用i2c_driver结构体描述,是挂载在i2c总线上的i2c设备的驱动程序,它可以一对多(包含不同i2c适配器上设备)。

 

I2C设备:使用i2c_client结构体描述,是具体硬件i2c设备的一个软件层面抽象,一个i2c设备对应一个i2c_client结构体,并且一个i2c设备只能绑定一个i2c驱动!

 

I2C适配器:使用i2c_adapter结构体描述,是SOC上i2c控制器的一个软件层面抽象,硬件层面是i2c设备硬件连接器件,软件层面是i2c驱动和i2c设备间通信所需要使用的一个结构体,通过内部i2c_algorithm成员包含的通信方法来实现通信。

 

匹配过程分析

将会分两部分,分别是i2c设备注册匹配分析、i2c驱动注册匹配分析

注意本文基于4.18.0内核分析匹配过程!

i2c设备注册匹配分析

拿tmp75设备注册来说,下面是一个简单的tmp75设备注册代码

……
static struct i2c_board_info my_tmp75_info = {
    I2C_BOARD_INFO("my_tmp75", 0x48),
};        //设备信息描述结构体,用于填充 i2c_client结构体

static struct i2c_client *my_tmp75_client;        //tmp75 i2c设备描述结构体
static int my_tmp75_init(void)
{
    struct i2c_adapter *i2c_adapt;
    i2c_adapt = i2c_get_adapter(6);       //获取总线6(i2c控制器6)描述结构体,获取哪个总线取决i2c设备挂在哪个i2c控制器上

    if (i2c_adapt == NULL)
    {
        printk("get adapter fail!\n");
        goto get_adapter_fail;
    }

    my_tmp75_client = i2c_new_device(i2c_adapt, &my_tmp75_info);        //分配并注册i2c设备
    if (my_tmp75_client == NULL)
    {
        printk("i2c new fail!\n");
        goto i2c_new_fail;
    }

    i2c_put_adapter(i2c_adapt);

    return 0;

i2c_new_fail:

    i2c_put_adapter(i2c_adapt);

get_adapter_fail:

    return -1;
}
……

可以看到,其中最主要的函数是 i2c_new_device ,将其代码简化后如下:

struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    client->adapter = adap;
    client->dev.platform_data = info->platform_data;
    client->flags = info->flags;
    client->addr = info->addr;
    client->irq = info->irq;
    client->dev.parent = &client->adapter->dev;

    client->dev.bus = &i2c_bus_type;//表示这个设备归哪个总线管理
    client->dev.type = &i2c_client_type;
    client->dev.of_node = of_node_get(info->of_node);
    client->dev.fwnode = info->fwnode;

    status = device_register(&client->dev);
}

这个函数将info结构体中的信息填充到  i2c_client  结构体,并且设置了 device 成员内部成员属性。值得注意的是,它设置了 divece.type i2c_client_type,表明这个设备是iic设备,在前面提到iic设备和iic控制设备都会通过device挂载到iic总线,它们之间区分便通过这个属性。

       函数最后调用了 device_register 将设备注册到系统,注意看,这里只传入了 i2c_client 结构体中的 device 成员,将代码如下(未简化):

int device_register(struct device *dev)
{
    device_initialize(dev);
    return device_add(dev);
}

       内部有两个函数, device_initialize用于初始化 dev 这里不关心它,将 device_add 函数代码简化后如下:

int device_add(struct device *dev)
{
    device_add_class_symlinks(dev);//创建类符号链接,相互创建dev和class之间的连接文件
    device_add_attrs(dev);//创建sys目录下设备其他属性文件  ,添加设备属性文件
    bus_add_device(dev);//将设备添加到对应总线,重要
    dpm_sysfs_add(dev);//电源管理相关
    device_pm_add(dev);//添加设备到激活设备列表中,用于电源管理

    if (dev->bus)//呼唤通知链表,通知注册监听该总线的设备,有新设备加入
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_ADD_DEVICE, dev);

//产生一个内核uevent事件,该事件可以被内核以及应用层捕获,属于linux设备模型中热插拔机制
    kobject_uevent(&dev->kobj, KOBJ_ADD);

    bus_probe_device(dev);//在总线上寻找对应的driver
}

我们比较关心 bus_add_device 函数和 bus_probe_device 函数,将代码简化如下:

int bus_add_device(struct device *dev)
{
    klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}

将 device 添加到总线的设备管理链表上!

void bus_probe_device(struct device *dev)
{
    if (bus->p->drivers_autoprobe)
    device_initial_probe(dev);
}

执行了 device_initial_probe 函数。将 device_initial_probe 函数代码如下(未简化):

void device_initial_probe(struct device *dev)
{
    __device_attach(dev, true);
}

__device_attach 函数代码简化如下:

static int __device_attach(struct device *dev, bool allow_async)
{
    bus_for_each_drv(dev->bus, NULL, &data,__device_attach_driver);
}

int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
     void *data, int (*fn)(struct device_driver *, void *))
{
    while ((drv = next_driver(&i)) && !error)
        error = fn(drv, data);
}

结合上面两个函数代码可以知道,将设备与总线上所有driver进行匹配,匹配函数是 __device_attach_driver ,其代码简化如下:

static int __device_attach_driver(struct device_driver *drv, void *_data)
{
    if (dev->driver)//如果设备已经匹配,则返回
    return -EBUSY;

    ret = driver_match_device(drv, dev);//检测设备与驱动是否匹配,返回大于0表示匹配,其其它则进行下一个driver匹配!
    if (ret == 0) {
        /* no match */
        return 0;
    } else if (ret == -EPROBE_DEFER) {
        dev_dbg(dev, "Device match requests probe deferral\n");
        driver_deferred_probe_add(dev);
    } else if (ret < 0) {
        dev_dbg(dev, "Bus failed to match device: %d", ret);
        return ret;
    } /* ret > 0 means positive match */

    return driver_probe_device(drv, dev);//匹配成功后,执行总线的probe函数,然后再调用i2c驱动的probe函数(自己写的probe)
}

存档1

其中比较关心的是 driver_match_device 函数与 driver_probe_device 函数,这两个函数内部东西较多,这里记录一下存档,后面方面回来看,在这个位置记为 存档1 ,先看 driver_match_device 函数,代码如下(未简化):

static inline int driver_match_device(struct device_driver *drv, struct device *dev)
{
    return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

这是一个内嵌函数,内容很简单,判断总线的match指针是否为空,不为空则执行match指针指向的函数。I2c总线结构体在内核启动时候已经注册,具体结构如下:

struct bus_type i2c_bus_type = {
    .name                = "i2c",
    .match                = i2c_device_match,
    .probe                = i2c_device_probe,
    .remove        = i2c_device_remove,
    .shutdown        = i2c_device_shutdown,
};

所以将会执行 i2c_device_match 函数,代码如下(未简化):

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    struct i2c_client        *client = i2c_verify_client(dev);//判断当前device是不是iic设备,是的话返回i2c_client结构体
    struct i2c_driver        *driver;

    /* Attempt an OF style match */     //使用drv内部的of_match_table列表来匹配,一般使用设备树注册设备时才会使用这种匹配
    if (i2c_of_match_device(drv->of_match_table, client))用设备树中的 compatible 属性和i2c驱动driver成员中的 of_match_table 来匹配
        return 1;

    /* Then ACPI style match */    //ACPI专用匹配,较少用不关心
    if (acpi_driver_match_device(dev, drv))
        return 1;

    driver = to_i2c_driver(drv);

    /* Finally an I2C match */
    if (i2c_match_id(driver->id_table, client))    //最常用的匹配方式
        return 1;

return 0;
}

struct i2c_client *i2c_verify_client(struct device *dev)
{
    return (dev->type == &i2c_client_type)? to_i2c_client(dev): NULL;
}//这个函数很有意思,它会先判断这个device是不是iic设备,对应之前提到的iic适配器(iic控制器)也会作为设备挂在总线上!所以当iic适配器注册到总线时候,虽然也会调用总线匹配函数,但直接就结束了!

可以看到只有三种匹配方法,相比于平台总线匹配,少了一种通过设备名字与驱动名字匹配的方式,所以iic驱动中的名字不重要!

我们用的最多的匹配函数是 i2c_match_id,其代码如下:

const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,const struct i2c_client *client)
{
    if (!(id && client))
        return NULL;

    while (id->name[0]) {
        if (strcmp(client->name, id->name) == 0)
            return id;

            id++;
    }

    return NULL;
}

struct i2c_device_id {
    char name[I2C_NAME_SIZE];
    kernel_ulong_t driver_data;        /* Data private to the driver */
};

使用iic设备的名字与iic driver->id_table中的name进行比较,即strcmp(i2c_client->name, i2c_driver->id_table->name);

匹配成功则返回对应的id位置!

 

回到我们的存档1,即如下:

static int __device_attach_driver(struct device_driver *drv, void *_data)
{
    if (dev->driver)//如果设备已经匹配,则返回
        return -EBUSY;

    ret = driver_match_device(drv, dev);//检测设备与驱动是否匹配,返回大于0表示匹配,其其它则进行下一个driver匹配!
    if (ret == 0) {
    /* no match */
        return 0;
    } else if (ret == -EPROBE_DEFER) {
        dev_dbg(dev, "Device match requests probe deferral\n");
        driver_deferred_probe_add(dev);
    } else if (ret < 0) {
        dev_dbg(dev, "Bus failed to match device: %d", ret);
        return ret;
} /* ret > 0 means positive match */

    return driver_probe_device(drv, dev);//匹配成功后,执行总线的probe函数,然后再调用i2c驱动的probe函数(自己写的probe)
}

driver_match_device 函数匹配成功后将会执行 driver_probe_device 函数,其代码简化后如下:

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
    int ret = 0;

    if (!device_is_registered(dev))//判断设备是否已经注册
        return -ENODEV;

    ret = really_probe(dev, drv);
    return ret;
}

其中比较重要的是 really_probe 函数,其代码简化后如下:

static int really_probe(struct device *dev, struct device_driver *drv)
{
    dev->driver = drv;//将设备中驱动指针指向匹配到的驱动!

    if (dev->bus->probe) {//执行总线的probe函数!
        ret = dev->bus->probe(dev);
        if (ret)
            goto probe_failed;
    } else if (drv->probe) {
        ret = drv->probe(dev);
        if (ret)
            goto probe_failed;
    }

    driver_bound(dev);//驱动绑定设备
}

回到刚才的总线描述结构体:

struct bus_type i2c_bus_type = {
    .name                = "i2c",
    .match                = i2c_device_match,
    .probe                = i2c_device_probe,
    .remove        = i2c_device_remove,
    .shutdown        = i2c_device_shutdown,
};

最终调用 i2c_device_probe 函数,其代码简化后如下:

static int i2c_device_probe(struct device *dev)
{
    if (driver->probe_new)
        status = driver->probe_new(client);
    else if (driver->probe)
        status = driver->probe(client, i2c_match_id(driver->id_table, client));
}

先判断iic驱动中是否使用 probe_new ,如果没有则执行当前的 probe 函数,也就是iic驱动中自己写的probe函数!不容易,终于执行了probe函数,下面分析iic驱动注册时匹配过程!

 

i2c驱动注册匹配分析

同样使用tmp75代码来讲解,代码简化后如下:

#define DEV_NAME        "my_tmp75"
#define TMP75_ADDR        0x48

struct my_tmp75_device
{
    struct i2c_client client;
    struct miscdevice misc;
    char name[8];
    unsigned char addr;
};

static ssize_t my_tmp75_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
    struct my_tmp75_device *my_tmp75_dev;
    my_tmp75_dev = container_of(filp->private_data, struct my_tmp75_device, misc);
    return ret;
}

static ssize_t my_tmp75_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
    struct my_tmp75_device *my_tmp75_dev;
    my_tmp75_dev = container_of(filp->private_data, struct my_tmp75_device, misc);
    return data;
}

static const struct file_operations my_tmp75_i2c_fops= {
    .owner = THIS_MODULE,
    .read = my_tmp75_read,
    .write = my_tmp75_write,
};

static const struct i2c_device_id my_tmp75_ids[] = {
    {.name = DEV_NAME, },
    { },
};

static const struct of_device_id my_tmp75_dtids[] = {
    {.compatible = DEV_NAME, },
    { },
};

MODULE_DEVICE_TABLE(i2c, my_tmp75_ids);
MODULE_DEVICE_TABLE(of, my_tmp75_dtids);

static int my_tmp75_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret;
    struct my_tmp75_device *i2c_dev;

    i2c_dev = devm_kzalloc(&client->dev, sizeof(struct my_tmp75_device), GFP_KERNEL);
    if (i2c_dev == NULL)
    {
        printk("devm_kzalloc fail!\n");
        return -1;
    }

    i2c_set_clientdata(client, i2c_dev);
    i2c_dev->client = *client;
    i2c_dev->misc.name = DEV_NAME;
    i2c_dev->misc.minor = MISC_DYNAMIC_MINOR;
    i2c_dev->misc.fops = &my_tmp75_i2c_fops;

    ret = misc_register(&i2c_dev->misc);
    if (ret)
    {
        printk("failed register misc device!\n");
        return ret;
    }

    return 0;
}

static int my_tmp75_remove(struct i2c_client *client)
{
    struct my_tmp75_device *dev;

    dev = i2c_get_clientdata(client);
    misc_deregister(&dev->misc);
    return 0;
}

struct i2c_driver my_tmp75_driver = {
        .driver = {
        .name = "my_i2c",
        .owner = THIS_MODULE,
        .of_match_table = my_tmp75_dtids,
    },

    .probe = my_tmp75_probe,
    .remove = my_tmp75_remove,
    .id_table = my_tmp75_ids,
};

static int __init my_tmp75_driver_init(void)
{
    return i2c_add_driver(&my_tmp75_driver);
}

static void __exit my_tmp75_driver_exit(void)
{
    i2c_del_driver(&my_tmp75_driver);
}

module_init(my_tmp75_driver_init);
module_exit(my_tmp75_driver_exit);

//module_i2c_driver(my_tmp75_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("caodongwang");
MODULE_DESCRIPTION("This i2c driver for my tmp75!");

my_tmp75_ids 便是上面所说的匹配列表,设备或驱动注册时候,会使用这个列表匹配设备!即设备注册时候,让设备去匹配总线上所有驱动的id_table列表,而驱动注册的时候,会使用这个列表去匹配总线上所有设备!

驱动注册使用的是 i2c_add_driver 宏,它的定义是:

#define i2c_add_driver(driver)  i2c_register_driver(THIS_MODULE, driver)

实际上是调用了 i2c_register_driver 函数,代码简化后如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    driver->driver.bus = &i2c_bus_type;//添加总线

    res = driver_register(&driver->driver);//驱动注册核心函数,注意只传入了driver成员

    /* 遍历所有挂在总线上的iic适配器,用它们去探测driver中指定的iic设备地址列表 */
    i2c_for_each_dev(driver, __process_new_driver);
}

i2c_for_each_dev 函数是用于探测未注册的设备,并将其注册后匹配,我们一般很少用到它这里不展开讲解,将会在《i2c设备添加方法》中介绍!

我们一般使用的是 driver_register 函数,其代码简化后如下:

int driver_register(struct device_driver *drv)
{
    other = driver_find(drv->name, drv->bus);//判断驱动是否已经注册过,如果注册过则不再继续
    if (other) {
        printk(KERN_ERR "Error: Driver '%s' is already registered, aborting...\n", drv->name);
        return -EBUSY;
}

    ret = bus_add_driver(drv);
}

将驱动添加至总线使用 bus_add_driver 函数,其代码简化后如下:

int bus_add_driver(struct device_driver *drv)
{
    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);//将驱动添加到总线
    if (drv->bus->p->drivers_autoprobe) {
        if (driver_allows_async_probing(drv)) {
            pr_debug("bus: '%s': probing driver %s asynchronously\n",drv->bus->name, drv->name);
            async_schedule(driver_attach_async, drv);
        } else {
            error = driver_attach(drv);
            if (error)
                goto out_unregister;
        }
    }
}

将驱动添加到总线后执行 driver_attach 函数,其代码(未简化)如下:

int driver_attach(struct device_driver *drv)
{
    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *))
{
    klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL));

    while (!error && (dev = next_device(&i)))    //遍历总线上所有设备,分别执行__driver_attach函数
        error = fn(dev, data);
}

从上面可以看出,驱动加载后会去遍历总线上所有设备,分别执行 __driver_attach 函数,其代码简化后如下:

static int __driver_attach(struct device *dev, void *data)
{
    ret = driver_match_device(drv, dev);//调用总线的match函数,检测设备与驱动是否匹配,返回大于0表示匹配,其它则进行下一个device匹配!
    if (ret == 0) {
        /* no match */
        return 0;
    } else if (ret == -EPROBE_DEFER) {
        dev_dbg(dev, "Device match requests probe deferral\n");
    driver_deferred_probe_add(dev);
    } else if (ret < 0) {
        dev_dbg(dev, "Bus failed to match device: %d", ret);
        return ret;
    } /* ret > 0 means positive match */

    if (!dev->driver)//如果匹配成功,则要判断这个设备是否已经绑定了驱动,举个例子,之前有个设备已经和别的驱动绑定了,但同时也支持这个驱动!
    driver_probe_device(drv, dev);//匹配成功后,执行总线的probe函数,然后再调用i2c驱动的probe函数(自己写的probe)
}

上面的代码看起来是不是很熟悉?没错,基本和 存档1 一样,来看看存档1的代码:

static int __device_attach_driver(struct device_driver *drv, void *_data)
{
    if (dev->driver)//如果设备已经匹配,则返回
        return -EBUSY;

    ret = driver_match_device(drv, dev);//检测设备与驱动是否匹配,返回大于0表示匹配,其其它则进行下一个driver匹配!
    if (ret == 0) {
        /* no match */
        return 0;
    } else if (ret == -EPROBE_DEFER) {
        dev_dbg(dev, "Device match requests probe deferral\n");
        driver_deferred_probe_add(dev);
    } else if (ret < 0) {
        dev_dbg(dev, "Bus failed to match device: %d", ret);
        return ret;
    } /* ret > 0 means positive match */

    return driver_probe_device(drv, dev);//匹配成功后,执行总线的probe函数,然后再调用i2c驱动的probe函数(自己写的probe)
}

发现了吧,两个核心函数是一样的,都是先去调用i2c总线的match函数看看设备与驱动是否匹配,如果匹配就调用i2c总线的probe函数,然后在总线的probe函数中调用i2c驱动的probe函数,在我们自己写的probe函数中注册字符设备,创建设备节点,实现fops集等等,这里就不再多讲解了。

总结一下,简要匹配过程如下图所示。

知道了匹配过程还需要知道怎么写,下一章节将讲解如何添加iic设备!

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
LinuxI2C子系统是用于在Linux内核中管理和操作I2C总线子系统。它提供了一组API和驱动程序,允许用户空间应用程序与连接到I2C总线上的设备进行通信。 在Linux中,I2C子系统由以下几个主要组件组成: 1. I2C核心驱动程序:这是I2C子系统的核心部分,负责管理I2C总线设备的注册、协议处理等功能。它提供了一组API供其他驱动程序或用户空间应用程序使用。 2. I2C适配器驱动程序:这些驱动程序用于支持特定的硬件I2C适配器,如FPGA、SOC等。它们与I2C核心驱动程序紧密配合,负责将硬件特定的操作转换为通用的I2C操作。 3. I2C设备驱动程序:这些驱动程序用于支持连接到I2C总线上的具体设备。每个I2C设备都有一个对应的设备驱动程序,负责处理设备的初始化、通信协议等。在Linux中,这些设备驱动程序通常作为内核模块存在。 4. I2C工具和库:除了内核驱动程序外,Linux还提供了一些用户空间工具和库,用于与I2C设备进行交互。例如,`i2cdetect`工具用于检测I2C总线上的设备地址,`i2cget`和`i2cset`工具用于读取和写入I2C设备的寄存器值。 用户空间应用程序可以使用I2C子系统提供的API和工具来访问和控制连接到I2C总线上的设备。通过打开适当的设备节点文件,并使用相应的读写操作,可以向设备发送命令和数据,以及从设备读取响应和数据。 总而言之,LinuxI2C子系统提供了一套完整的解决方案,使用户能够方便地在Linux环境中操作和管理I2C设备
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值