经过之前文章介绍,读者可能还未对i2c子系统有整体的认识,没关系,这篇文章可以帮助你。
我们知道,注册一个字符设备驱动可以向上提供字符设备节点,比如/dev/xxx节点,我们对这个节点进行write/read操作,最终就会调用到字符驱动提供的write/read函数,以完成我们想要实现的功能。同样i2c设备与驱动匹配之后,也是注册设备节点供用户操作,注意的是,这里说的设备节点包含字符设备和块设备节点,本文以字符设备为例进行讲解。
如果对字符设备驱动没有概念的读者,请先查阅相关文章学习。对于接触过字符设备驱动的读者,也可参阅以下文章:
https://blog.csdn.net/zz2633105/article/details/116898695?spm=1001.2014.3001.5501
Linux内核启动过程中会注册一条虚拟的i2c总线,以便i2c设备与i2c驱动挂靠,注意,这里说的总线是总线设备模型中的总线,并不对应实物上的总线,为了与后面实际总线进行区分,后续将这条虚拟的i2c总线称为i2c_bus_type。
我们所说的i2c设备对应的软件结构i2c_client与i2c驱动对应的软件结构i2c_driver均挂在i2c_bus_type上,除此之外,i2c控制器(i2c_adapter)也是作为设备挂在i2c_bus_type上的,但它并不是i2c设备类型!i2c设备与i2c驱动挂接简图如下图所示:
根据前面《i2c设备与驱动匹配过程》文章我们知道,设备与驱动匹配成功后最终会执行i2c驱动的probe函数,那么,我们在probe函数中注册字符设备节点,不就提供了与字符设备驱动一样的接口了吗?事实上,部分i2c设备的驱动就是这样做的,比如温度传感器tmp75,其驱动内部实现tmp75温度读取函数,再由标准字符设备驱动接口ioctl调用读函数向上层返回温度数据,后续文章将会提供示例代码。
话不多说直接上图(内核版本linux4.18.0)。
简要讲解一下:
使用单片机读写过I2c设备的人都知道,通过GPIO引脚模拟i2c时序即可读写i2c设备,既然i2c_adapter已经提供了通信方法,那直接操作i2c_adapter不就可以读写i2c设备了吗?的确是的,i2c子系统也为每个i2c适配器注册了字符设备节点,如上图右上方所示,我们可以操作i2c-0节点来完成读写i2c设备0,如通过i2c-0提供的ioctl接口+I2C_SLAVE命令字即可设置i2c设备地址,再通过ioctl接口+I2C_RDWR命令字即可读数据,关于直接使用i2c-0来操作i2c设备的具体方法不在这详细讲解,读者可自行搜索关键字:linux i2c 用户态读写。
在i2c核心有一个文件叫i2c-dev.c,文件中向系统注册了一个模块。
代码图1
723行占用了字符驱动设备号(89),但并未实际注册字符驱动设备。735行向系统中注册了通知链,去监测i2c总线上设备注册!当i2c总线上设备注册/删除并呼唤通知链时,则会调用i2cdev_notifier结构体中的.notifier_call指向的函数!
代码图2
如果总线上是添加设备,则调用i2cdev_attach_adapter函数,如果是删除设备则调用i2cdev_detach_adapter函数。
代码图3
如果总线上是添加设备是i2c适配器(638行),则使用之前占用的设备号向系统中注册字符设备驱动,并提供fops操作方法集。653行创建了名为i2c-x的设备节点。
通过上面所述可以知道,当i2c适配器注册到系统中并呼唤通知链后会触发i2cdev_attach_adapter函数执行,进而为i2c适配器注册一个字符驱动,那么这里就有一个疑问了,如果i2c适配器在i2c-dev.c模块注册之前提前注册到了系统怎么办?
回到代码图1截图740行,模块注册时会调用i2c_for_each_dev!这个函数会去遍历i2c总线上所有设备,执行i2cdev_attach_adapter函数为提前注册到i2c总线上的i2c适配器注册字符驱动!
其中fn函数就是i2cdev_attach_adapter函数。
上面代码图1的解释中提到“735行向系统中注册了通知链,去监测i2c总线上设备注册”,所以我们再看看i2c适配器注册会发生什么?
i2c适配器注册有两个函数,i2c_add_numbered_adapter与i2c_add_adapter,它们最终都会调用i2c_register_adapter函数,其代码简化后如下:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
mutex_lock(&core_lock);
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
mutex_unlock(&core_lock);
}
首先设置设备为i2c_adapter_type类型,然后调用device_register函数将i2c适配注册到i2c总线上并呼唤通知链,接着如果是静态注册的话,调用i2c_scan_static_board_info函数将__i2c_board_list链表上对应总线号的设备实例化注册进i2c总线中,最后调用bus_for_each_drv函数遍历i2c总线上所有i2c驱动,使用它们的i2c设备地址组中地址探测该i2c适配器上是否有响应设备,有的话便实例化后注册进i2c总线。我们先看看device_register(直接调用device_add)函数,其代码简化后如下:
int device_add(struct device *dev)
{
error = device_add_attrs(dev);//创建sys目录下设备其他属性文件
error = bus_add_device(dev);//将设备添加到对应总线,重要
device_pm_add(dev);//添加设备到激活设备列表中,用于电源管理
if (dev->bus)//呼唤通知链表,通知注册监听该总线的设备,有新设备加入
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
bus_probe_device(dev);//在总线上寻找对应的driver
}
首先将i2c适配注册到I2c总线上,然后呼唤通知链,事件类型为BUS_NOTIFY_ADD_DEVICE(对应左侧i2c-dev模块监测类型),最后调用bus_probe_device设备匹配驱动函数,这个函数是用于i2c设备匹配i2c总线上驱动的,所以虽然i2c适配器也会调用这个函数,但在函数内部调用到i2c总线上的match函数后,因为其类型i2c_adapter_type是而不是i2c_client_type,因此直接结束。i2c_scan_static_board_info函数,用途在设备添加方法中简要介绍了一下,具体情况可参阅《i2c设备的添加方法》文章。接下来看看bus_for_each_drv函数做了什么:
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
void *data, int (*fn)(struct device_driver *, void *))
{
klist_iter_init_node(&bus->p->klist_drivers, &i,
start ? &start->p->knode_bus : NULL);
while ((drv = next_driver(&i)) && !error)
error = fn(drv, data);
klist_iter_exit(&i);
}
static int __process_new_adapter(struct device_driver *d, void *data)
{
return i2c_do_add_adapter(to_i2c_driver(d), data);
}
static int i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap)
{
i2c_detect(adap, driver);
}
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
if (!driver->detect || !address_list)//驱动链表不为空
return 0;
if (!(adapter->class & driver->class))//i2c驱动支持该i2c适配器类型
return 0;
err = i2c_detect_address(temp_client, driver);
}
static int i2c_detect_address(struct i2c_client *temp_client, struct i2c_driver *driver)
{
err = i2c_check_7bit_addr_validity_strict(addr);//检查是否为7位有效地址
if (i2c_check_addr_busy(adapter, addr))//跳过已经使用的i2c设备
if (!i2c_default_probe(adapter, addr))//检查这个地址是否有回应
info.addr = addr;//执行到这,则i2c设备存在,调用i2c驱动的detect函数
err = driver->detect(temp_client, &info);//函数中最少填充i2c设备名
client = i2c_new_device(adapter, &info);//实例化i2c设备并注册待i2c总线
if (client)//不为空的话,将i2c设备挂载到i2c驱动链表上
list_add_tail(&client->detected, &driver->clients);
}
首先检查有效性、是否有设备回应、是否被使用,之后初始化了i2c_board_info结构,注意只初始化了地址(实例化设备必须还要名字),然后调用了i2c驱动中的detect函数,如果成功则调用 i2c_new_device函数真正实例化i2c设备,并且将i2c设备挂在i2c驱动的链表上!i2c驱动注册时也会调用函数i2c_do_add_adapter函数,具体情况可参阅《i2c设备的添加方法》文章。
读者到这有没有什么疑问?是谁向i2c总线驱动模型中注册了i2c_adatper结构,并提供通信方法(algorithm)的呢?
看下图,简要解释一下你就明白了。
platform_bus_type是虚拟的平台总线,pci_bus_type是虚拟的pci总线,注意了,pci_bus_type是总线驱动模型中的概念,与实际的pci总线是两回事。
i2c_adapter的一般通过两种方法注册(当然,你也可以写一个模块暴力注册i2c_adapter,只要你开心就好)。
这里简单提了一下i2c_adapter注册方法,想要详细了解的可以看下一篇文章《i2c-designware框架分析》。