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


前言

本文讲解基于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内核分析匹配过程!

2.1 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 成员内部成员属性。值得注意的是,它设置了 device.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驱动注册时匹配过程!

2.2 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集等等,这里就不再多讲解了。

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

在这里插入图片描述


参考链接

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值