本文转载自微信公众号《人人都是Geek》,作者Peter布道。转载本文请联系大家是极客公众号。一个开发板本节结合设备信息采集的详细讲解,了解设备和驱动是如何绑定的。所谓设备信息收集,就是根据不同的外设,找到各自的外设信息。我们知道一个完整的开发板??有CPU和各种控制器(如I2C控制器、SPI控制器、DMA控制器等),CPU和Controllers可以统称为SOC。此外还有各种外围IP,如LCD、HDMI、SD、CAMERA等,如下图所示:我们看到一块开发板上有很多设备。这些设备是如何层层叠叠的?层数是如何展开的?设备和驱动程序是如何绑定的?我们带着这些问题进入本节的主题。层层展开设备内核启动时,逐层展开寻找设备。设备树之所以称为设备树,是因为设备在内核中的结构就像一棵树,从根部开始逐层展开,我们看一张图更直观的理解:大圈就是我们常说的soc,里面包括CPU和各种控制器A,B,I2C,SPI,外设E和F都接在soc上。IP外设都有特定的总线,比如I2C总线和SPI总线,对应的I2C设备和SPI设备都挂在各自的总线上,但是soc内部只有系统总线,并没有特定的总线。第一节讨论了总线、设备和驱动模型的原理,即任何驱动都是通过相应的总线与设备连接的,所以虽然soc内部没有具体的总线,但是内核使用的是platformvirtualbustocontrol都是一个一个找的,这也是遵循了内核高内聚低耦合的设计理念。接下来,我们按照平台设备、i2c设备、spi设备的顺序逐层探索设备是如何部署的。1.扩展平台设备。在上图中,您可以看到红色字体标记的简单总线。这些是连接各种控制器的总线。在内核中就是平台总线,挂载的设备就是平台设备。让我们看看平台设备是如何部署的。还记得上一节在内核初始化的时候有一个叫init_machine()的回调函数吗?如果你在board文件中注册了这个函数,那么系统启动的时候就会调用这个函数。如果没有定义,会通过调用of_platform_populate()展开挂在“simple-bus”下的设备,如图(位于kernel/arch/arm/kernel/setup.c,kernel/drivers/of/platform.crespectively):将simple-bus下面的节点一一展开为平台设备。2.扩展i2c设备有经验的朋友都知道,在写i2c控制器的时候,肯定会调用i2c_register_adapter()函数。该函数的实现如下(kernel/drivers/i2c/i2c-core.c):在注册函数的最后,有一个函数of_i2c_register_devices(adap),实现如下:of_i2c_register_devices()函数会遍历控制器下的节点,然后通过of_i2c_register_device()函数注册i2c控制器下的设备。3.展开spi设备spi设备的注册和i2c设备一样,遍历spi控制器下spi节点下的设备,然后通过对应的注册函数进行注册,只是和api不同i2c注册的接口,我们看一下具体代码(kernel/drivers/spi/spi.c):通过spi_register_master注册spi控制器时,会通过of_register_spi_devices遍历spi总线下的设备进行注册。这样就完成了spi设备的注册。各级设备的开发学习到这里,相信大家应该明白了,设备的硬件信息是从设备树中获取的,比如寄存器地址、中断号、时钟等等。接下来我们看看设备树中是如何记录信息的,为下一节自定义开发板做准备。1、reg寄存器我们先来看设备树中的soc描述信息。红色标记表示用来表示寄存器地址的数据量个数,绿色标记表示用来表示寄存器空间大小的数据量个数。图中的意思是中断控制器的基地址为0xfec00000,空间大小为0x1000。如果address-cells的值为2,表示需要两个数量级来表示基地址。例如,如果寄存器是64位,则需要两个数量级,每个数量级代表一个32位数字。2.ranges的取值范围表示从本地地址到父地址的转换。如果ranges为空,表示与CPU是1:1的映射关系。如果没有范围,则说明它不是一个内存区域。
