在Linux设备模型中,Bus(总线)是一类特殊的设备,它是连接处理器和其它设备之间的通道。为了方便设备模型的实现,内核规定,系统中的每个设备都要连接在一个Bus上,这个Bus可以是一个system bus、virtual bus或platform bus等。内核通过struct bus_type类型来抽象Bus从而定义各种不同的总线,例如platform bus这个抽象总线以及I2C 总线这种具体存在的总线等,4.4.3版本的内核中它们的定义如下所示:
struct bus_type platform_bus_type = { .name = "platform", .dev_groups = platform_dev_groups, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops, }; 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总线比较好理解,platform这种虚拟总线有什么用呢?我们知道一个现实的Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI 等的设备而言,这自然不是问题,但是在嵌入式系统里面,SoC 系统中集成独立的外设控制器,挂接在SoC 内存空间的外设等不依附于此类总线。基于这一背景,Linux 发明了一种虚拟的总线,称为platform总线,我们在写这一类外设的驱动时便可以使用platform总线来挂接这些设备和驱动。我们注意到上面的platform_bus_type并未定义probe和remove这两个成员,而i2c_bus_type里却定义了,这就要说到总线是如何匹配挂接在上面的设备和驱动的了。当一个新的设备(驱动)挂接到该总线上时,总线便会从该总线上寻找能与之匹配的驱动(设备)。这通过调用总线的probe函数来实现,以I2C总线的probe函数为例,里面有如下一行代码:
status = driver->probe(client, i2c_match_id(driver->id_table, client));
再进入i2c_match_id函数内部:
1 static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, 2 const struct i2c_client *client) 3 { 4 while (id->name[0]) { 5 if (strcmp(client->name, id->name) == 0) 6 return id; 7 id++; 8 } 9 return NULL; 10 }
我们便得知总线通过比较i2c_client里的name和i2c_driver里的id_table是否匹配来判断i2c设备和驱动是否匹配,如果匹配,就调用该驱动的probe函数,完成一些检测和初始化的工作,以完成两者的连接。
上面是是对i2c总线的分析,大部分总线基本都是如此匹配设备和驱动的,然而并不是所有的总线都需要probe和remove接口的,因为对有些总线来说(例如platform bus),它本身就是一个虚拟的总线,无所谓检测和初始化,直接就能使用,因此这些总线就可以将这两个回调函数留空。