hi3519av100改用传参设备树方式启动
文章目录
linux
内核启动需求的Setup boot data
时可以通过两种方式:
Setup the kernel tagged list
Setup the device tree
linux-4.9.37/Documentation/arm/Booting
hi3519av100设备原有的启动内核的方式是第一种ATAGS的方式,由于目前hi3519av100和hi3559av100的u-boot和kernel源码都是共用的,且设备树比较方便兼容不同的板子,所有打算将hi3519av100用第二种设备树的方式来启动内核。
问:
- 设备树是什么?
- 设备树如何获得? 存放在哪里?
- u-boot如何获得设备树?
- 如何将设备树传递给内核并启动?
设备树介绍
可以参考 蜗窝科技 的相关博文:(非广告,没收钱)
使用简单的测试设备树进行分析
涉及文件:
- 源设备树文件test.dts
- 编译出来的二进制文件test.dtb
- 反编译出来的设备树文件test-re.dts
- fdtdump打印出来的test.dump
涉及命令:
dtc
: 进行设备树的编译与反编译fdtdump
: 打印解析过程hexdump
: 打印二进制数据
编译与反编译test.dts
:
/dts-v1/;
/ {
node@0 {
a-string-property = "A sttring";
a-string-list-property = "first string", "second string";
a-byte-data-property = [01 23 45 67 89 AB];
child-node@0 {
first-child-property;
second-child-property = <1>;
a-reference-to-something = <&node1>;
};
};
node1: node@1 {
an-empty-property;
a-string-property = "A new sttring";
a-cell-property = <1 2 3 4>;
a-reference-to-something = <&node2>;
child-node@0 {
};
};
node2: node@2 {
};
};
编译成test.dtb
:
dtc -I dts -O dtb -o test.dtb test.dts
反汇编test.dtb
为test-re.dts
文件:
dtc -I dtb -O dts -o test-re.dts test.dtb
test-re.dts
文件内容:
/dts-v1/;
/ {
node@0 {
a-string-property = "A sttring";
a-string-list-property = "first string", "second string";
a-byte-data-property = [01 23 45 67 89 ab];
child-node@0 {
first-child-property;
second-child-property = <0x1>;
a-reference-to-something = <0x1>;
};
};
node@1 {
an-empty-property;
a-string-property = "A new sttring";
a-cell-property = <0x1 0x2 0x3 0x4>;
a-reference-to-something = <0x2>;
linux,phandle = <0x1>;
phandle = <0x1>;
child-node@0 {
};
};
node@2 {
linux,phandle = <0x2>;
phandle = <0x2>;
};
};
可以看到上面源dts
文件与反编译出来的dts
文件还是存在差异的:
同时发现当使用[]括号对4个binary data数据进行属性赋值时,反编译出来是一个u32类型的值,使用其他值的时候还是会反编译成[]的binary data。(看文章后面的图)
使用fdtdump
打印test.dtb
:
fdtdump -ds test.dtb > test.dump
可以参照上面的test.dump
与编译出来的test.dtb
文件进行对比:
fdt_header
头的结构体如下所示:
struct fdt_header {
fdt32_t magic; /* magic word FDT_MAGIC */
fdt32_t totalsize; /* total size of DT block */
fdt32_t off_dt_struct; /* offset to structure */
fdt32_t off_dt_strings; /* offset to strings */
fdt32_t off_mem_rsvmap; /* offset to memory reserve map */
fdt32_t version; /* format version */
fdt32_t last_comp_version; /* last compatible version */
/* version 2 fields below */
fdt32_t boot_cpuid_phys; /* Which physical CPU id we're
booting on */
/* version 3 fields below */
fdt32_t size_dt_strings; /* size of the strings block */
/* version 17 fields below */
fdt32_t size_dt_struct; /* size of the structure block */
};
struct fdt_reserve_entry {
fdt64_t address;
fdt64_t size;
};
struct fdt_node_header {
fdt32_t tag;
char name[];
};
struct fdt_property {
fdt32_t tag;
fdt32_t len;
fdt32_t nameoff;
char data[];
};
#define FDT_MAGIC 0xd00dfeed /* 4: version, 4: total size */
#define FDT_TAGSIZE sizeof(fdt32_t)
#define FDT_BEGIN_NODE 0x1 /* Start node: full name */
#define FDT_END_NODE 0x2 /* End node */
#define FDT_PROP 0x3 /* Property: name off,
size, content */
#define FDT_NOP 0x4 /* nop */
#define FDT_END 0x9
#define FDT_V1_SIZE (7*sizeof(fdt32_t))
#define FDT_V2_SIZE (FDT_V1_SIZE + sizeof(fdt32_t))
#define FDT_V3_SIZE (FDT_V2_SIZE + sizeof(fdt32_t))
#define FDT_V16_SIZE FDT_V3_SIZE
#define FDT_V17_SIZE (FDT_V16_SIZE + sizeof(fdt32_t))
二进制文件中以FDT_BEGIN_NODE (0x1)
表示一个节点的开始,FDT_END_NODE (0x2)
表示一个节点的结束,节点以struct fdt_node_header
描述;以FDT_PROP (0x3)
表示一个节点下的属性,属性以结构体struct fdt_property
描述。
fdtdump
出来的内容:
/dts-v1/;
// magic: 0xd00dfeed
// totalsize: 0x26e (622)
// off_dt_struct: 0x38
// off_dt_strings: 0x1bc
// off_mem_rsvmap: 0x28
// version: 17
// last_comp_version: 16
// boot_cpuid_phys: 0x0
// size_dt_strings: 0xb2
// size_dt_struct: 0x184
与编译出来的dtb
进行对比:
off_dt_struct
表明设备树的结构在dtb
中的偏移位置,这里是从0x38
开始,大小是size_dt_struct
,即0x184
字节。off_dt_strings
是属性名字字符串表在dtb
中开始的位置,大小是size_dt_strings
,这里是0xb2
字节。
那么从0x38
开始,就是根节点/
开始的位置。
根节点/
:以FDT_BEGIN_NODE (0x1)
开始,后面接u32
的0x0
值。
从fdtdump
出来的文件可以看到:
// 0038: tag: 0x00000001 (FDT_BEGIN_NODE)
/ {
/node@0
节点:以FDT_BEGIN_NODE (0x1)
开始,后面接node@0
的ASCII
码,并补全至4字节对齐。
// 0040: tag: 0x00000001 (FDT_BEGIN_NODE)
node@0 {
/node@0
的a-string-property
属性:以FDT_PROP (0x3)
开始,属性值长度为0x9
,属性名字偏移为0x0
,接下来是属性的值”A string”(长度为8)。(长度会是字符串长度+1,即加0x00
的字符串结束符。并且以4字节向上对齐。)(属性的名字存在size_dt_strings
里面,根据off_dt_strings
的地址0x1bc
加上偏移0x0
,就可以得到属性名字”a-string-property”。后面就不再赘述。)
// 004c: tag: 0x00000003 (FDT_PROP)
// 01bc: string: a-string-property
// 0058: value
a-string-property = "A string";
/node@0
的a-string-list-property
属性:属性值长度为0x1B
,属性名字偏移为0x12
,接下来是属性的值”first string second string”(长度为0x1A
)。(dtc编译与反编译过程中是如何区分是普通字符串还是字符串列表的?)
// 0064: tag: 0x00000003 (FDT_PROP)
// 01ce: string: a-string-list-property
// 0070: value
a-string-list-property = "first string", "second string";
/node@0
的a-byte-data-property
属性:属性值长度为0x6
,属性名字偏移为0x29
,接下来是属性的值:[01 23 45 67 89 AB]
,然后4字节向上对齐。
// 008c: tag: 0x00000003 (FDT_PROP)
// 01e5: string: a-byte-data-property
// 0098: value
a-byte-data-property = [01 23 45 67 ffffff89 ffffffab];
实际dts
文件:
a-byte-data-property = [01 23 45 67 89 AB];
反编译出来的dts
文件:
a-byte-data-property = [01 23 45 67 89 ab];
/node@0/child-node@0
下的first-child-property
属性:属性值长度为0x0
,属性名字偏移为0x3E
,即”first-child-property”。
// 00b4: tag: 0x00000003 (FDT_PROP)
// 01fa: string: first-child-property
// 00c0: value
first-child-property;
// 00c0: tag: 0x00000003 (FDT_PROP)
// 020f: string: second-child-property
// 00cc: value
second-child-property = <0x00000001>;
// 00d0: tag: 0x00000003 (FDT_PROP)
// 0225: string: a-reference-to-something
// 00dc: value
a-reference-to-something = <0x00000001>;
实际dts
文件:
a-reference-to-something = <&node1>;
反编译出来的dts
文件:
linux,phandle = <0x1>;
phandle = <0x1>;
这个属性的名字跟/node@0
里面的属性的名字相同,所有属性名字的偏移通用指向了0x41
:(达到了属性名的服用,节省部分空间。)
fdtdump
出来的有错误:
// 011c: tag: 0x00000003 (FDT_PROP)
// 0250: string: a-cell-property
// 0128: value
a-cell-property = <0x00000001 0x00000003 0x00000003 0x00000003>;
实际dts
文件:
a-cell-property = <1 2 3 4>;
反编译出来的dts
文件:
a-cell-property = <0x1 0x2 0x3 0x4>;
// 0138: tag: 0x00000003 (FDT_PROP)
// 0225: string: a-reference-to-something
// 0144: value
a-reference-to-something = <0x00000002>;
实际dts
文件:
a-reference-to-something = <&node2>;
反编译出来的dts
文件:
a-reference-to-something = <0x2>;
被引用的节点会生成linux,phandle
和phandle
,并赋予不同的值:
linux,phandle = <0x1>;
phandle = <0x1>;
linux,phandle = <0x2>;
phandle = <0x2>;
从上面的分析可以知道,整个dtb
文件的结构大概如下所示:
设备树存放位置
查看hi3519av100
的内核编译过程,可以看到dtb
文件实际通过cat
命令是打到了zImage
的后面形成zImage-dtb
:
u-boot获取设备树内容
虽然说dtb
放在zImage
的后面,zImage-dtb
镜像最后生成uImage
,但uImage
的头实际是记录的ih_size
是zImage-dtb
的大小,而不是zImage
的大小,所有要怎么从uImage
里面获得dtb
的内容呢?
typedef struct image_header {
__be32 ih_magic; /* Image Header Magic Number */
__be32 ih_hcrc; /* Image Header CRC Checksum */
__be32 ih_time; /* Image Creation Timestamp */
__be32 ih_size; /* Image Data Size */
__be32 ih_load; /* Data Load Address */
__be32 ih_ep; /* Entry Point Address */
__be32 ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
} image_header_t;
从uImage
的生成过程来看,其实中间那个zImage
是很重要的,查询相关资料发现,zImage
也有一个自己的头,在u-boot
下的arch/arm/lib/zimage.c
里:
#define LINUX_ARM_ZIMAGE_MAGIC 0x016f2818
struct arm_z_header {
uint32_t code[9];
uint32_t zi_magic;
uint32_t zi_start;
uint32_t zi_end;
} __attribute__ ((__packed__));
查看一下生成过程的zImage
镜像:
既然知道了这个arm_z_header
,并且头里面记录了zi_start
和zi_end
,对于zImage
,那么zi_end - zi_start
自然就是zImage
的大小了。
再看一下内核的arch/arm/boot/compressed/head.S
里面有一个奇怪的标记0x04030201
:
start:
.type start,#function
.rept 7
__nop
.endr
ARM( mov r0, r0 )
ARM( b 1f )
THUMB( badr r12, 1f )
THUMB( bx r12 )
.word _magic_sig @ Magic numbers to help the loader
.word _magic_start @ absolute load/run zImage address
.word _magic_end @ zImage end address
.word 0x04030201 @ endianness flag
再对比一下uImage
的前面部分:
首先,u-boot
在启动内核时会将生成uImage
时mkimage
加的64字节头去掉,去掉了之后就剩下普通的zImage-dtb
了。紧接着是zImage
的头,共48字节,接下来是生成zImaeg
时上面那个奇怪的标记endianness flag
,和代码也有对应,镜像位置也对应。基本上就可以获取到在uImage
里面的dtb
文件了。
去掉了uImage
的64字节头后,启动内核的起始内核位置实际是zImage
在内存中的起始位置,假设为addr
,使用地址addr+0x2C获得zi_end,addr+0x28
获得zi_start
,由于zi_start
都是0,那么zi_end
就是zImage
的长度了。addr+0x30
加就是endianness flag
的标记位了。
且知道dtb
就在zImage
后面,那么使用偏移addr+zi_start
就可以获得dtb
的头了。
#define FDT_SIZE_OFFSET (0x1) // 0x4
#define IMAGE_FDT_OFFSET (0xb) // 0x2C
#define IMAGE_FLAG_OFFSET (0xc) // 0x30
#define IMAGE_FLAG (0x04030201)
当使用4字节长度的指针时,IMAGE_FLAG_OFFSET
偏移是endianness flag
标记,然后IMAGE_FDT_OFFSET
偏移是zi_end
(当zi_start
是0时表示zImage
长度,也就是dtb
开始的位置了。)
if (head[IMAGE_FLAG_OFFSET] != IMAGE_FLAG)
...
fdt_head = (ulong *)(image_start+head[IMAGE_FDT_OFFSET]);
好,齐活了。
u-boot传递设备树内容
获取到设备树后,u-boot会在boot_jump_linux
函数中将r2寄存器设置为设备树的地址(relocate之后的地址), 这里就将设备树传递给内核了。内核在setup_arch->setup_machine_fdt
的过程中解析设备树的内容,完成启动过程。
- 分享
- 举报
-
浏览量:2465次2021-12-10 16:36:33
-
浏览量:1705次2024-01-08 17:24:15
-
浏览量:1048次2023-10-12 14:25:01
-
浏览量:1993次2023-10-13 14:34:01
-
2020-08-10 09:21:07
-
浏览量:854次2024-01-08 18:13:05
-
浏览量:2498次2021-12-03 17:42:05
-
浏览量:2656次2020-08-04 20:30:30
-
浏览量:7422次2020-09-06 16:25:23
-
浏览量:2196次2020-08-05 21:02:35
-
浏览量:4383次2020-08-05 20:40:46
-
浏览量:870次2023-10-25 15:43:39
-
浏览量:4278次2019-12-16 13:54:11
-
浏览量:3337次2019-12-28 10:19:54
-
浏览量:4756次2020-08-14 11:29:53
-
浏览量:882次2024-03-12 16:42:47
-
浏览量:4704次2020-08-30 08:25:06
-
浏览量:2743次2019-11-05 20:18:39
-
浏览量:3651次2022-01-04 09:00:18
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
来自远方
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明