平台简介
开发板:tiny4412ADK + S700 + 4GB Flash
要移植的内核版本:Linux-4.4.0 (支持device tree)
u-boot版本:友善之臂自带的 U-Boot 2010.12 (为支持uImage启动,做了少许改动)
busybox版本:busybox 1.25
交叉编译工具链: arm-none-linux-gnueabi-gcc
(gcc version 4.8.3 20140320 (prerelease) (Sourcery CodeBench Lite 2014.05-29))
摘要
在Linux引入设备树之后,将原来写在代码中的大量的硬件信息全部移到了设备树中,然后在Linux启动的时候会解析设备树,利用解析到的硬件信息构造device,然后注册到相应的bus上,如果有对应的driver,则会调用driver的probe函数。那么这个过程是怎么进行的?Linux系统有各种device,如对于platform子系统来说有platform_device、对于I2C子系统有i2c_client、对于SPI子系统来说有spi_device,那么这些device是怎么跟设备树关联起来的呢?
在分析的过程中参考了下面的几篇博文:
http://www.wowotech.net/device_model/dt-code-analysis.html
http://www.wowotech.net/comm/i2c_overview.html
http://www.wowotech.net/comm/i2c_provider.html
这几篇博文讲的非常好,下面一篇是之前总结的:
http://www.cnblogs.com/pengdonglin137/p/4495056.html
内核文档:
Documentation/devicetree/booting-without-of.txt
Documentation/devicetree/usage-model.txt
官方文档:
Power_ePAPR_APPROVED_v1.1.pdf
正文
设备树的populate过程大致有如下几个阶段(下文中“节点”与“device node”可以理解为一个意思):
一、根据设备树创建device node链表
start_kernel
---> setup_arch
---> unflatten_device_tree
在u-boot引导内核的时候,会将设备树在物理内存中的物理起始地址(存放在寄存器r2中)传递给Linux内核,然后Linux内核在函数unflatten_device_tree中会解析设备树镜像,并利用扫描到的信息创建由device node构成的链表,全局变量of_root指向链表的根节点,设备树的每个节点都会有一个struct device_node与之对应。
二、遍历device node链表,创建并注册platform_device
start_kernel
---> rest_init
---> kernel_init
---> kernel_init_freeable
---> do_basic_setup
---> do_initcalls
在do_initcalls函数中,kernel会依次执行各个initcall函数,在这个过程中,会调用 customize_machine,具体如下:
static int __init customize_machine(void)
{
/*
* customizes platform devices, or adds new ones
* On DT based machines, we fall back to populating the
* machine from the device tree, if no callback is provided,
* otherwise we would always need an init_machine callback.
*/
of_iommu_init();
if (machine_desc->init_machine)
machine_desc->init_machine();
#ifdef CONFIG_OF
else
of_platform_populate(NULL, of_default_bus_match_table,
NULL, NULL);
#endif
return 0;
}
arch_initcall(customize_machine);
这样就可调用到exynos_dt_machine_init:
static void __init exynos_dt_machine_init(void)
{
......
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
}
在of_platform_populate中会调用of_platform_bus_create ---> of_platform_device_create_pdata,完成platform_device的创建和注册。那么Linux系统是怎么知道哪些device node要注册为platform_device,哪些是用于i2c_client,哪些是用于spi_device?不知道你有没有注意到调用of_platform_populate的时候给它传递了一个参数of_default_bus_match_table,它的定义如下:
const struct of_device_id of_default_bus_match_table[] = {
{ .compatible = "simple-bus", },
{ .compatible = "simple-mfd", },
#ifdef CONFIG_ARM_AMBA
{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
{} /* Empty terminated list */
};
是这个意思:如果某个device node的compatible属性的值与数组of_default_bus_match_table中的任意一个元素的compatible的值match(但是对于compatible属性的值是arm,primecell的节点有些特殊,它是单独处理的),那么这个device node的child device node(device_node的child成员变量指向的是这个device node的子节点,也是一个链表)仍旧会被注册为platform_device。
of_platform_populate:
1: int of_platform_populate(struct device_node *root,
2: const struct of_device_id *matches,
3: const struct of_dev_auxdata *lookup,
4: struct device *parent)
5: {
6: struct device_node *child;
7: int rc = 0;
8:
9: root = root ? of_node_get(root) : of_find_node_by_path("/"); // 找到root device node
10: if (!root)
11: return -EINVAL;
12:
13: for_each_child_of_node(root, child) { // 遍历root device node的child device node
14: rc = of_platform_bus_create(child, matches, lookup, parent, true);
15: if (rc) {
16: of_node_put(child);
17: break;
18: }
19: }
20: of_node_set_flag(root, OF_POPULATED_BUS);
21:
22: of_node_put(root);
23: return rc;
24: }
of_platform_bus_create :
1: static int of_platform_bus_create(struct device_node *bus,
2: const struct of_device_id *matches,
3: const struct of_dev_auxdata *lookup,
4: struct device *parent, bool strict)
5: {
6: const struct of_dev_auxdata *auxdata;
7: struct device_node *child;
8: struct platform_device *dev;
9: const char *bus_id = NULL;
10: void *platform_data = NULL;
11: int rc = 0;
12:
13: /* Make sure it has a compatible property */
14: if (strict && (!of_get_property(bus, "compatible", NULL))) { // 这样可以把chosen、aliases、memory等没有compatible属性的节点排除在外
15: pr_debug("%s() - skipping %s, no compatible propn",
16: __func__, bus->full_name);
17: return 0;
18: }
19:
20: auxdata = of_dev_lookup(lookup, bus); // tiny4412给lookup传递的是NULL
21: if (auxdata) {
22: bus_id = auxdata->name;
23: platform_data = auxdata->platform_data;
24: }
25:
26: if (of_device_is_compatible(bus, "arm,primecell")) {
27: /*
28: * Don't return an error here to keep compatibility with older
29: * device tree files.
30: */
31: of_amba_device_create(bus, bus_id, platform_data, parent);
32: return 0;
33: }
34:
35: dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); // 根据device node创建 platform_device并注册
36: if (!dev || !of_match_node(matches, bus)) // 判断是不是需要继续遍历这个device node下的child device node
37: return 0;
38:
39: for_each_child_of_node(bus, child) { // 遍历这个device node下的child device node,将child device node也注册为platform_device
40: pr_debug(" create child: %sn", child->full_name);
41: rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
42: if (rc) {
43: of_node_put(child);
44: break;
45: }
46: }
47: of_node_set_flag(bus, OF_POPULATED_BUS);
48: return rc;
49: }
三、注册其他设备
I2C设备的注册
下面说一下i2c_client是如何注册的。先看下面一张图(来自蜗窝科技):
下面是从http://www.wowotech.net/comm/i2c_overview.html摘抄的一段话:
1)platform bus(/sys/bus/platform)是驱动工程师常见的bus,用于挂载和CPU通过系统总线连接的各类外设。在I2C framework中,I2C控制器直接从属于platform bus,我们在linux kernel中常说的I2C driver,都是指I2C controller driver,都是以platform driver的形式存在,当然,对应的控制器是platform device。
2)与此同时,kernel抽象出I2C bus(/sys/bus/i2c),用于挂载和I2C controller通过I2C总线连接的各个I2C slave device。
3)比较特殊的地方是,I2C core使用一个虚拟实体----I2C adapter,抽象I2C controller有关的功能(主要是数据的收发),I2C adapter也挂载在I2C bus上。
4)I2C adapter和I2C slave device都挂载在I2C bus上,就可以方便的进行Master(I2C adapter)和Slave之间的匹配操作,并通过I2C core提供的统一接口,访问I2C salve device,进行数据的收发。
我们知道,i2c控制器在i2c驱动模型中被抽象为i2c_adapter,但是i2c控制器驱动实际上是在platform_bus上,所以i2c控制器对应的是platform_device,因此会在上面调用of_platform_populate时注册,然后i2c控制器驱动的probe函数会被调用。以tiny4412开发板为例,在drivers/i2c/busses/i2c-s3c2410.c的probe函数中调用注册adapter的函数接口:i2c_add_numbered_adapter ---> i2c_add_adapter ---> i2c_register_adapter ---> of_i2c_register_devices,在函数of_i2c_register_devices中会遍历这个adapter对应的device node的child device node,这些child device node对应的就是挂载i2c bus上的板级外设的硬件信息(这些板级外设使用I2C接口跟SOC通信),如 MMA7660。然后调用of_i2c_register_device,这个函数根据每个child device node的信息构造i2c_board_info,并调用i2c_new_device,在i2c_new_device中会创建并注册i2c_client,注册i2c_client的时候如果找到了对应的设备驱动程序(如 MMA7660的驱动程序),设备驱动程序的probe函数就会被调动。