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的过程中解析设备树的内容,完成启动过程。
- 分享
- 举报
暂无数据-
浏览量:2993次2021-12-10 16:36:33
-
浏览量:2887次2024-01-08 17:24:15
-
浏览量:2282次2023-10-12 14:25:01
-
浏览量:3467次2023-10-13 14:34:01
-
2020-08-10 09:21:07
-
浏览量:1567次2024-01-08 18:13:05
-
浏览量:3172次2020-08-04 20:30:30
-
浏览量:3075次2021-12-03 17:42:05
-
浏览量:8328次2020-09-06 16:25:23
-
浏览量:3742次2020-08-05 21:02:35
-
浏览量:5099次2020-08-05 20:40:46
-
浏览量:1676次2023-10-25 15:43:39
-
浏览量:5426次2020-08-14 11:29:53
-
浏览量:5034次2019-12-16 13:54:11
-
浏览量:3778次2019-12-28 10:19:54
-
浏览量:1818次2024-03-12 16:42:47
-
浏览量:11560次2022-08-12 15:15:09
-
浏览量:5794次2020-08-30 08:25:06
-
浏览量:3177次2019-11-05 20:18:39
- Hi3519DV500移植Yolov8
- 海思3559A上编译libyuv源码操作步骤
- 超高清 | 中国高动态范围视频标准HDR Vivid开始全面商用
- [HarmonyOS之旅] Chapter4 - HarmonyOS启动流程
- HI3516EV300音频输入串电阻方案、音频输出串电阻方案及影响
- Hi3531D SDK安装及升级步骤分享
- 基于易百纳SS928或者欧拉派移植ubuntu20.04和ROS环境
- Hi3516EV200设置手动曝光时间
- 海思 AI 芯片 (Hi3559A V100) 算法开发(三) 在 PC 仿真库使用 YOLOv3 进行图片目标检测以及 NMS、YOLO 讲解
- Hi3559AV100 sample_vdec 视频解码
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
来自远方
微信支付举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明

微信扫码分享
QQ好友