转载自 http://wenku.baidu.com/link?url=KpOdULJu1CxP1swqRs_Szoyg5r_8rje4N08o4QtB5L9QlPjWesTYlrTPgkxPOriFtsmsqCyg-gWMVOkLjgYN640zsk7vGlN24tO5tkJcj8K
有修改
ARM Linux 内核在Linux-3.x 内核有了很大的变化,对一些新的平台的支持取消了传统的设备文件而用设备树取代,这里以飞思卡尔的imx6sx-sdb设备树识别为例说明Linux 是如何识别设备树的。这里采用的linux内核版本为3.14.28。
1、 设备树文件
参考文件 arch/arm/boot/dts/imx6sx-sdb.dts,在线可以看 http://lxr.oss.org.cn/source/arch/arm/boot/dts/imx6sx-sdb.dts?v=3.17
编译设备树文件,内核顶层目录下执行如下命令可以编译设备树文件:
$ make dtbs ARCH=arm
(可能因为我使用yocto进行编译的,所以我执行时候报错)
编译后生成文件为 imx6sx-sdb.dtb, 文件是使用大端字节序存储,可用下面命令看其二进制内容:
$ hexdump imx6sx-sdb.dtb -C
在uboot 引导内核之前,会将设备树文件加载到内存中,以备Linux 内核使用,这里就不详细说明了
2、 Linux 内核启动
Linux 内核启动分几个阶段:
1) Linux 内核自解压
2) Linux 内核初始化----汇编
3) Linux 内核初始化----C
这里从第三阶段开始说明,分析这个阶段,主要是查看函数start_kernel,在start_kernel 中有几个函数这里重点分析:
2.1 setup_arch
arch/arm/kernel/setup.c
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
…
mdesc = setup_machine_fdt(__atags_pointer);
if (!mdesc)
mdesc = setup_machine_tags(__atags__pointer, __machine_arch_type);
…
}
arch/arm/kernel/devtree.c
/**
* setup_machine_fdt - Machine setup when an dtb was passed to the kernel
* @dt_phys: physical address of dt blob
*
* If a dtb was passed to the kernel in r2, then use it to choose the
* correct machine_desc and to setup the system.
*/
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
const struct machine_desc *mdesc, *mdesc_best = NULL;
#ifdef CONFIG_ARCH_MULTIPLATFORM
DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
MACHINE_END
mdesc_best = &__mach_desc_GENERIC_DT;
#endif
if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys)))
return NULL;
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
if (!mdesc) {
const char *prop;
long size;
unsigned long dt_root;
early_print("\nError: unrecognized/unsupported "
"device tree compatible list:\n[ ");
dt_root = of_get_flat_dt_root();
prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
while (size > 0) {
early_print("'%s' ", prop);
size -= strlen(prop) + 1;
prop += strlen(prop) + 1;
}
early_print("]\n\n");
dump_machine_table(); /* does not return */
}
/* Change machine number to match the mdesc we're using */
__machine_arch_type = mdesc->nr;
return mdesc;
}
setup_machine_fdt 函数是用来识别设备树,看注释,bootloader 如果将一个设备树文件加载到内存中,其通过r2 寄存器将设备树的物理地址传递到Linux 内核中,Linux 内核来选择正确的机器且对其进行设置
函数中需要重点分析的函数有: of_flat_dt_match_machine 和 of_get_flat_dt_root
drivers/of/fdt.c
const void * __init of_flat_dt_match_machine(const void *default_match
const void * (*get_next_compat)(const char * const **))
{
const void *data = NULL;
const void *best_data = default_match;
const char *const *compat;
unsigned long dt_root;
unsigned int best_score = ~1, score = 0;
//找到设备树中的根节点(开始结点)
dt_root = of_get_flat_dt_root();
/*
* get_next_compat = arch_get_next_mach
* 内核中可以同时支持多个平台,这个函数用来获得下一个平台的
* dt_compat 属性,结合上边函数克制data 内容
*/
while((data = get_next_compat(&compat))) {
/*
* of_flat_dt_match 用来匹配设备树中内容和内核所支持平台是否匹配
*/
score = of_flat_dt_match(dt_root, compat);
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;
}
}
if (!best_data) {
const char *prop;
long size;
/*
* 设备树中内容和内核所支持平台没有匹配则打印出错提示
*/
pr_err("\n unrecognized device tree list: \n");
prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
if (prop) {
while (size > 0) {
printk("'%s'", prop);
size -= strlen(prop) + 1;
prop += strlen(prop) + 1;
}
printk("]\n\n");
return NULL;
}
}
}
函数中会调用函数 of_get_flat_dt_root 和 get_next_compat , of_get_flat_dt_root 用来从设备树文件中找到根节点, get_next_compat 按照上下文这个函数等于 arch_get_next_mach , 下边会重点分析这两个函数。
补充:设备树相关宏和结构体:
结点相关宏:
include/linux/of_fdt.h
OF_DT_HEADER 0xd00dfeed 标记
OF_DT_BEGIN_NODE 0x1 开始结点
OF_DT_END_NODE 0x2 结束结点
OF_DT_PROP 0x3 Property: name off, size, *content资源
OF_DT_NOP 0x4 NOP
OF_DT_END 0x9 结束
OF_DT_VERSION 0x10 版本
include/linux/of_fdt.h
struct boot_param_header {
__be32 magic; // OF_DT_HEADER 幻数
__be32 totalsize; // 设备树总体大小
__be32 off_dt_struct; // structure 偏移
__be32 off_dt_strings; // strings 偏移
__be32 off_mem_rsvmap; // 内存预留映射表偏移
__be32 version; // 格式版本
__be32 last_comp_version; // 最后兼容版本
__be32 boot_cpuid_phys; // 我们要启动的CPU 的ID
__be32 dt_strings_size; // 设备树strings 块大小
__be32 dt_struct_size; // 设备树structure 块大小
};
drivers/of/fdt.c 看3.17的内核实现又不一样了
unsigned long __inti of_get_flat_dt_root(void)
{
unsigned long p = ((unsigned long)initial_boot_params) +
be32_to_cpu(initial_boot_params->off_dt_struct);
/*
* 结合上述内容p = 设备树起始地址 + 0x0038
* 设备树文件按16 进制显示:
* 0000030 0000 0000 0000 0000 0000 0100 0000 0000
* 0x38 = 0x00000001
*/
/*
* 跳过所有无效数据
*/
while(be32_to_cpup((__be32 *)p) == OF_DT_NOP)
p += 4;
/*
* p = OF_DT_BEGIN_NODE 表示找到开始节点,也就是设备树有效数据的开始
*/
BUG_ON(be32_to_cpup((__be32 *)p) != OF_DT_BEGIN_NODE);
p += 4;
/*
* 数据对齐
*/
return ALIGN(p + strlen(char *p) + 1, 4);
}
arch/arm/kernel/devtree.c
static const void * __init arch_get_next_mach(const char *const **match)
{
static const struct machine_desc *mdesc = __arch_info_begin;
if (m > __arch_info_end)
return NULL;
mdesc++;
*match = m->dt_compat;
return m;
}
内核中又一个段叫.arch.info.init,内核在编译的时候会将所有支持的平台的信息链接到这个段中即machine_desc,其中 __arch_info_begin 表示这个段的开始 __arch_info_end 表示这个段的结束。
连接脚本参考arch/arm/kernel/vmlinux.lds 中有如下内容:
.init.arch.info: {
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
}
machine_desc 注册参考arch/arm/mach-imx/mach-imx6sx.c 中
static void __init imx6sx_init_machine(void)
{
struct device *parent;
mxc_arch_reset_init_dt();
parent = imx_soc_device_init();
if (parent == NULL)
pr_warn("failed to initialize soc device\n");
of_platform_populate(NULL, of_default_bus_match_table,
imx6sx_auxdata_lookup, parent);
imx6sx_enet_init();
imx_anatop_init();
imx6sx_pm_init();
imx6sx_qos_init();
}
static const char *imx6sx_dt_compat[] __initdata = {
"fsl,imx6sx",
NULL,
};
DT_MACHINE_START(IMX6SX, "Freescale i.MX6 SoloX (Device Tree)")
.map_io = imx6sx_map_io,
.init_irq = imx6sx_init_irq,
.init_machine = imx6sx_init_machine,
.init_late = imx6sx_init_late,
.dt_compat = imx6sx_dt_compat,
.restart = mxc_restart,
MACHINE_END
DT_MACHINE_START 的含义就是定义结构体 machine_desc 并初始化且标识链接到.arch.info.init 段中,定义如下:
arch/arm/include/asm/mach/arch.h
#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,
上述内容主要是通过读取设备树信息,检测当前使用内核是否支持本平台,如果支持则做相应的初始化,这里主要分析平台识别过程。
2.2 rest_init
reset_init 函数中创建了几个内核线程,其中kernel_init 使我们要重点分析的。其中 kernel_init ----> kernel_init_freeable -----> do_basic_setup ----> do_initcalls
init\main.c
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
init\main.c
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
在Linux 内核连接文件arch/arm/kernel/vmlinux.lds 文件中有如下内容:
__initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
链接文件定义了若干个段,在内核编译时不同的函数会连接到不容的段中,比如Linux内核会创建一个函数指针指向初始化函数且链接到initcall 段中,initcall 段中的0~7 数字表示优先级, 也就是将来这些初始化函数执行的先后。inicall 段中的函数在Linux 内核启动过程中也就是 do_initcalls 执行的时候被调用。do_initcalls 函数中 for循环的作用就是按照先后执行所有连接到initcall 段中的函数。那么这和设备树有什么关系呢,接下来我们再分析一个函数。
arch/arm/kernel/setup.c
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.
*/
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);
arch_initcall 的作用是定义函数指针变量指向customize_machine 并将此函数指针连接到initcall 段。
customize_machine 会在系统启动被调用
查看 customize_machine 中实际上是调用了 machine_desc->init_machine 函数也就是前文注册的 machine_desc 中的:
.init_machine = imx6sx_init_machine,
设备树的转换主要通过 of_platform_populate ;
drivers/of/platform.c
/**
* of_platform_populate() - Populate platform_devices from device tree data
* @root: parent of the first level to probe or NULL for the root of the tree
* @matches: match table, NULL to use the default
* @lookup: auxdata table for matching id and platform_data with device nodes
* @parent: parent to hook devices from, NULL for toplevel
*
* Similar to of_platform_bus_probe(), this function walks the device tree
* and creates devices from nodes. It differs in that it follows the modern
* convention of requiring all device nodes to have a 'compatible' property,
* and it is suitable for creating devices which are children of the root
* node (of_platform_bus_probe will only create children of the root which
* are selected by the @matches argument).
*
* New board support should be using this function instead of
* of_platform_bus_probe().
*
* Returns 0 on success, < 0 on failure.
*/
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child;
int rc = 0;
//判断是否是根节点
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;
//遍历结点
for_each_child_of_node(root, child) {
//转换为platform_devie 并遍历本节点的子节点
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc)
break;
}
of_node_put(root);
return rc;
}
EXPORT_SYMBOL_GPL(of_platform_populate);
看注释, of_platform_populate 会将相应结点转换为 platform_deivce ,而且下文代码中的 of_platform_bus_create 是一个递归调用逐次遍历各级子节点完成相应的转换,这里就不一一分析,先要了解整个转换过程可以跟进去看看。