原标题:devicetree: kernel internals and practical troubleshooting
。
原文档地址:devicetree: Kernel Internals and Practical Troubleshooting。
目前已经有了很多关于设备树是什么样子以及如何创建设备树的示例,而本文将探讨Linux内核如何使用设备树。本文的主题包括内核设备树框架、设备创建、资源分配、驱动程序绑定和连接对象等内容。故障排除将考虑初始化、分配和绑定排序、内核配置和驱动程序问题。
注意:本文所讲述内容指定的内核版本为 3.15rc-1
到 3.16-rc7
。涉及到CPU架构的代码将查看 arch/arm/
。
设备树是具有描述系统中设备的节点的树数据结构。每个节点都有描述所表示设备特征的属性/值对。每个节点只有一个父节点,只有根节点没有父节点。 —— ePAPR v1.1
设备树描述了无法通过探测定位的硬件,即描述了不可被主动发现的设备。
设备树的生命周期如下图所示:
其生命周期为:
dts,device tree source file,设备树源文件,具体可见Device Tree Reference学习笔记 > 2 设备树的使用,示例如下:
/ { /* incomplete .dts example */ // <--- root node
model = "Qualcomm APQ8074 Dragonboard"; // <--- property
compatible = "qcom,apq8074-dragonboard"; // <--- property
interrupt-parent = <&intc>; // <--- property, phandle
soc: soc { // <--- node
ranges; // <--- property
compatible = "simple-bus"; // <--- property
intc: interrupt-controller@f9000000 { // <--- node, phandle
compatible = "qcom,msm-qgic2"; // <--- property
interrupt-controller; // <--- property
reg = <0xf9000000 0x1000>, // <--- property
<0xf9002000 0x1000>;
console: serial@f991e000 { // <--- node
compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
reg = <0xf991e000 0x1000>; // <--- property
interrupts = <0 108 0x0>; // <--- property
};
// ...
}
Binary Blob format,二进制块格式。是一个扁平结构,可以通过顺序扫描和偏移进行访问。其基本数据结构如下图所示:
FDT是平铺的结构,可以使用 fdt_*
族函数进行顺序扫描或偏移量进行访问。
Expanded DT有如下特性:
parent
、 child
、 sibling
、next
、 allnext
等节点)。具体如下:
of_find_node_by_path()
等方式进行树数据结构方式的访问。allnext
链表在被修改过之前使用深度优先顺序,但是修改后无法保证该特性,因此在开发时应视为随机顺序。of_*
族函数)。struct device_node {
const char *name;
const char *type;
phandle phandle;
const char *full_name;
struct property *properties;
struct property *deadprops;
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
struct device_node *next;
struct device_node *allnext;
struct kobject kobj;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
...
#endif
};
暂时不做内核开发,仅列出可用API:
of_find_node_by_name (*from, ...)
of_find_node_by_type (*from, ...)
// handle is unique, so *from not needed.
of_find_node_by_phandle (handle)
// traverse allnext and properties.
of_find_node_with_property (*from, ...)
machine_desc结构体用于为一个特定的设备树描述启动选项,该结构体可以用于多个不同的设备树。
machine_desc结构体定义如下(Linux 6.10.0-rc):
struct machine_desc {
unsigned int nr; /* architecture number */
const char *name; /* architecture name */
unsigned long atag_offset; /* tagged list (relative) */
const char *const *dt_compat; /* array of device tree
* 'compatible' strings */
unsigned int nr_irqs; /* number of IRQs */
#ifdef CONFIG_ZONE_DMA
phys_addr_t dma_zone_size; /* size of DMA-able area */
#endif
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned char reserve_lp0 :1; /* never has lp0 */
unsigned char reserve_lp1 :1; /* never has lp1 */
unsigned char reserve_lp2 :1; /* never has lp2 */
enum reboot_mode reboot_mode; /* default restart mode */
unsigned l2c_aux_val; /* L2 cache aux value */
unsigned l2c_aux_mask; /* L2 cache aux mask */
void (*l2c_write_sec)(unsigned long, unsigned);
const struct smp_operations *smp; /* SMP operations */
bool (*smp_init)(void);
void (*fixup)(struct tag *, char **);
void (*dt_fixup)(void);
long long (*pv_fixup)(void);
void (*reserve)(void); /* reserve mem blocks */
void (*map_io)(void); /* IO mapping function */
void (*init_early)(void);
void (*init_irq)(void);
void (*init_time)(void);
void (*init_machine)(void);
void (*init_late)(void);
void (*restart)(enum reboot_mode, const char *);
};
其中:
.nr
:机器编号,通常对应于具体的硬件平台。
.name
:平台名称,用于内核启动时输出日志。
L2 Cache
l2c_aux_val
l2c_aux_mask
boot hooks:
smp_init
fixup
dt_fixup
pv_fixup
reserve
map_io
init_early
:非常早的回调函数,会在 setup_arch()
中调用init_irq
init_time
init_machine
init_late
runtime hooks:
l2c_write_sec
restart
该结构体在代码中使用静态创建,在支持多平台的Linux系统镜像上有多个该结构体。
选择是否支持多平台会在编译Linux内核时交互时配置参数即可:
make ARCH=arm menuconfig
进入Arm-Linux的menuconfig配置Require kernel to be portable to multiple machines (ARCH_MULTIPLATFORM) [Y/n/?] (NEW)
CONFIG_ARCH_MULTIPLATFORM
,并影响编译。关于该项,kernelconfig.io的解释如下:
In general, all Arm machines can be supported in a single kernel image, covering either Armv4/v5 or Armv6/v7.
However, some configuration options require hardcoding machine specific physical addresses or enable errata workarounds that may break other machines.
Selecting N here allows using those options, including DEBUG_UNCOMPRESS, XIP_KERNEL and ZBOOT_ROM. If unsure, say Y.
该结构体可以按照如下的方式定义:
static const char * const qcom_dt_match[] __initconst = {
"qcom,apq8074-dragonboard",
"qcom,apq8084",
"qcom,msm8660-surf",
NULL
};
DT_MACHINE_START(QCOM_DT, "Qualcomm (Flattened Device Tree)")
.dt_compat = qcom_dt_match,
MACHINE_END
上述的使用了两个宏,其定义如下:
/*
* Set of macros to define architecture features. This is built into
* a table by the linker.
*/
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__section(".arch.info.init") = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
该宏会:
__mach_desc_${type}_type
的结构体。machine_desc
结构体存放到 vmlinux.lds
中的 .arch.info.init
段。__used
告诉编译器这段代码有用,组织对该变量的优化和警告。简化的内核启动过程的伪代码如下:
start_kernel()
pr_notice("%s", linux_banner)
setup_arch()
mdesc = setup_machine_fdt(__atags_pointer)
// 迭代机器匹配表,找到FDT中与机器兼容的字符串的最佳匹配
mdesc = of_flat_dt_match_machine()
/* sometimes firmware provides buggy data */
// 某些版本的uboot在内存节点中可能会传递垃圾条目
mdesc->dt_fixup()
early_paging_init()
mdesc->init_meminfo()
arm_memblock_init()
mdesc->reserve()
paging_init()
devicemaps_init()
mdesc->map_io()
...
arm_pm_restart = mdesc->restart
unflatten_device_tree() <===============
if (mdesc->smp_init())
...
handle_arch_irq = mdesc->handle_irq
...
mdesc->init_early()
pr_notice("Kernel command line: %s\n", ...)
init_IRQ()
machine_desc->init_irq()
outer_cache.write_sec = machine_desc->l2c_write_sec
time_init()
machine_desc->init_time()
rest_init()
kernel_thread(kernel_init, ...)
kernel_init()
do_initcalls()
customize_machine()
machine_desc->init_machine()
// device probing, driver binding
init_machine_late()
machine_desc->init_late()
在上述代码中:
if
、 while
等控制语句匹配原则:
例如:
/ {
model = "TI Zoom3";
compatible = "ti,omap3-zoom3", "ti,omap36xx", "ti,omap3";
};
machine_desc
结构体:DT_MACHINE_START(OMAP3_DT, "Generic OMAP3 (Flattened Device Tree)")
.init_early = omap3430_init_early,
.dt_compat = omap3_boards_compat,
MACHINE_END
DT_MACHINE_START(OMAP36XX_DT, "Generic OMAP36xx (Flattened Device Tree)")
.init_early = omap3630_init_early,
.dt_compat = omap36xx_boards_compat,
MACHINE_END
static const char *omap3_boards_compat[] __initconst = {
"ti,omap3430",
"ti,omap3",
NULL,
};
static const char *omap36xx_boards_compat[] __initconst = {
"ti,omap36xx",
NULL,
};
"ti,omap3-zoom3"
对应的结构体"ti,omap36xx"
和 "ti,omap3"
:
在 machine_desc
结构体中,L2 Cache有如下两个配置项。
unsigned l2c_aux_val; /* L2 cache aux value */
unsigned l2c_aux_val; /* L2 cache aux mask */
其中, l2c_aux_mask
用于配置是否需要开启L2兼容性检查:
(l2c_aux_mask || l2c_aux_val) != 0
时,TODO
machine_desc
结构体。machine_desc
结构体中的值会改变启动过程。