瑞芯微RK3399设备树传递分析
本文主要记录了在友善之臂使用的基于瑞芯微github上uboot的rkdevelop分支,以及瑞星微官方的stable-4.4-rk3399-linux分支中,uboot如何给linux内核所需要的kernel-dtb的探究过程。
目录
1.1 - get_fdt_name 获取设备树dtb文件名
1.2 - get_content() 获取所需dtb的地址
2 - 瑞芯微github上stable-4.4版uboot如何获取设备树
2.2.1 - get_file_info() 直接在 resource.img 中查找 rk-kernel.dtb
需要知道的一点是,在arm64架构下,linux已经弃用mach_xx等文件夹来描述板级信息供linux内核启动时去匹配根节点下的compatible属性来找到对应设备树。
取而代之是单纯的接收bootloader传递过来的单一设备树文件dtb所在的内存地址。所以当多个dtb文件存在镜像中或者内存中时,如何找到需要的dtb的这个任务,就落在了bootloader肩上。
0 - uboot代码准备
本文中主要涉及到了两个版本的uboot
- 友善之臂基于瑞芯微 rkdevelop 分支进行修改过的uboot
- 瑞芯微官方github上stable-4.4-rk3399-linux 分支的uboot
分别可以通过以下方式获取。
- https://gitlab.com/friendlyelec/rk3399-android-8.1/-/tree/master/u-boot
- https://github.com/rockchip-linux/u-boot/tree/stable-4.4-rk3399-linux
1 - 友善之臂版uboot如何获取要加载的设备树
友善之臂用的版本是2014.10版的uboot
首先,引用源自 common/cmd_bootrk.c @ rk_load_image_from_storage()
/* loader fdt from resource if content.load_addr == NULL */
#ifdef CONFIG_OF_LIBFDT
if (!content.load_addr) {
#ifdef CONFIG_OF_FROM_RESOURCE
puts("load fdt from resouce.\n");
content = rkimage_load_fdt(get_disk_partition(RESOURCE_NAME));
#endif
}
跟踪看到,board/rockchip/common/rkloader/rkimage.c @ rkimage_load_fdt()
resource_content rkimage_load_fdt(const disk_partition_t* ptn)
{
resource_content content;
snprintf(content.path, sizeof(content.path), "%s", get_fdt_name());
content.load_addr = 0;
#ifndef CONFIG_RESOURCE_PARTITION
return content;
#else
if (!ptn)
return content;
if (!strcmp((char*)ptn->name, BOOT_NAME)
|| !strcmp((char*)ptn->name, RECOVERY_NAME)) {
//load from bootimg's second data area.
unsigned long blksz = ptn->blksz;
int offset = 0;
rk_boot_img_hdr *hdr = NULL;
#ifdef CONFIG_RK_NVME_BOOT_EN
hdr = memalign(SZ_4K, blksz << 2);
#else
hdr = memalign(ARCH_DMA_MINALIGN, blksz << 2);
#endif
if (StorageReadLba(ptn->start, (void *) hdr, 1 << 2) != 0) {
return content;
}
if (!memcmp(hdr->magic, BOOT_MAGIC,
BOOT_MAGIC_SIZE) && hdr->second_size) {
//compute second data area's offset.
offset = ptn->start + (hdr->page_size / blksz);
offset += ALIGN(hdr->kernel_size, hdr->page_size) / blksz;
offset += ALIGN(hdr->ramdisk_size, hdr->page_size) / blksz;
if (get_content(offset, &content))
load_content(&content);
}
return content;
}
//load from spec partition.
if (get_content(ptn->start, &content))
load_content(&content);
return content;
#endif
}
对于上面两个红框处,我们分别讨论
1.1 - get_fdt_name 获取设备树dtb文件名
其代码本质是调用 getenv 从环境变量中取得 "dtb_name"变量上的字符串
static const char* get_fdt_name(void)
{
char *name = getenv("dtb_name");
if (name)
return name;
if (!gBootInfo.fdt_name[0]) {
return FDT_PATH;
}
return gBootInfo.fdt_name;
}
而这个“dtb_name“变量中包含的字符串就是我们需要的dtb文件名。它是在 board/rockchip/rk33xx/rk33xx.c @ rk33xx.c文件中被写入的
/*
* Board revision list: <GPIO4_D1 | GPIO4_D0>
* 0b00 - NanoPC-T4
* 0b01 - NanoPi M4
*
* Extended by ADC_IN4
* Group A:
* 0x04 - NanoPi NEO4
* 0x06 - SOC-RK3399
*
* Group B:
* 0x21 - NanoPi M4 Ver2.0
*/
static int pcb_rev = -1;
static void bd_hwrev_init(void)
{
gpio_direction_input(GPIO_BANK4 | GPIO_D0);
gpio_direction_input(GPIO_BANK4 | GPIO_D1);
pcb_rev = gpio_get_value(GPIO_BANK4 | GPIO_D0);
pcb_rev |= (gpio_get_value(GPIO_BANK4 | GPIO_D1) << 1);
if (pcb_rev == 0x3) {
/* Revision group A: 0x04 ~ 0x13 */
pcb_rev = 0x4 + get_adc_index(4);
} else if (pcb_rev == 0x1) {
int idx = get_adc_index(4);
/* Revision group B: 0x21 ~ 0x2f */
if (idx > 0) {
pcb_rev = 0x20 + idx;
}
}
}
/* To override __weak symbols */
u32 get_board_rev(void)
{
return pcb_rev;
}
static void set_dtb_name(void)
{
char info[64] = {0, };
snprintf(info, ARRAY_SIZE(info),
"rk3399-nanopi4-rev%02x.dtb", get_board_rev());
setenv("dtb_name", info);
}
我们因此知道了,友善之臂的nanopi系列板子,是通过板上 GPIO4_D1、GPIO4_D0 的输入电平,以及 ADC_IN4 脚的输入电压来判断载入什么dtb的
1.2 - get_content() 获取所需dtb的地址
rkimage_load_fdt() 函数中,将 “dev_name” 中的设备树名写入到content.path变量中。在 common/resource.c @ get_content() 函数中
bool get_content(int base_offset, resource_content* content) {
bool ret = false;
index_tbl_entry entry;
debug("get_content: base_offset = 0x%x\n", base_offset);
if (!base_offset) {
base_offset = get_base_offset();
}
if (!base_offset) {
FBTERR("base offset is NULL!\n");
goto end;
}
if (!get_entry(base_offset, content->path, &entry))
goto end;
content->content_offset = entry.content_offset + base_offset;
content->content_size = entry.content_size;
ret = true;
end:
return ret;
}
将 content.path 传入 get_entry() 函数,其函数修改传入的 entry 参数变量。get_entry() 函数也在common/resource.c
static bool get_entry(int base_offset, const char* file_path,
index_tbl_entry* entry) {
。。。
ret = get_entry_ram(header, table, header.tbl_entry_num
* header.tbl_entry_size * BLOCK_SIZE,
file_path, entry);
end:
if (table) {
free(table);
}
return ret;
}
可知,其调用 get_entry_ram 函数,将指定dtb文件地址解析并传送到 entry 中。在 common/resource.c @ get_entry_ram()函数中
。。。
for (i = 0; i < header.tbl_entry_num; i++) {
//TODO: support tbl_entry_size
memcpy(entry, table + i * header.tbl_entry_size * BLOCK_SIZE,
sizeof(*entry));
if (memcmp(entry->tag, INDEX_TBL_ENTR_TAG,
sizeof(entry->tag))) {
FBTERR("Something wrong with index entry:%d!\n", i);
goto end;
}
FBTDBG("Lookup entry(%d):\n\tpath:%s\n\toffset:%d\tsize:%d\n",
i, entry->path, entry->content_offset,
entry->content_size);
if (!strncmp(entry->path, file_path, sizeof(entry->path)))
break;
}
。。。
从resource镜像的头部中寻找对应的设备树名,一条一条比对,当比对当前在entry->path中的设备树名与需要的设备树名比对相同时退出。
resource.img 镜像大概长这样
2 - 瑞芯微github上stable-4.4版uboot如何获取设备树
stable-4.4版本的uboot(2017.09)使用类似现在linux的架构目录。uboot类似kernel也有了自己的设备树,方便自身加载初始化。因此这里分两个部分讨论,一个是uboot自己的设备树 uboot-dtb ,一个是linux需要的设备树 kernel-dtb。
2.1 - uboot-dtb
在 uboot/configs 目录下存放各种defconfig,其中包含有一项配置,例如firefly的firefly-rk3399
直接指定了使用什么默认设备树
在 uboot\arch\arm\dts\rk3399-firefly.dts 中我们可以看到
/*
* Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd.
*
* SPDX-License-Identifier: GPL-2.0+
*/
/dts-v1/;
#include <dt-bindings/pwm/pwm.h>
#include <dt-bindings/pinctrl/rockchip.h>
#include "rk3399.dtsi"
#include "rk3399-sdram-ddr3-1600.dtsi"
#include "rk3399-u-boot.dtsi"
/ {
model = "Firefly-RK3399 Board";
compatible = "firefly,firefly-rk3399", "rockchip,rk3399";
依据这个来进行uboot中设备树的匹配,但是注意,这个是uboot自用的设备树,并不是要传递给kernel的
2.2 - kernel-dtb
在 arch\arm\mach-rockchip\resource_img.c @ rockchip_read_dtb_file()函数中,我们可以看到
。。。
int rockchip_read_dtb_file(void *fdt_addr)
{
struct resource_file *file;
char *def_dtb = DTB_FILE;
int ret;
/* search order: "rk-kernel.dtb" -> distro -> hwid */
file = get_file_info(NULL, def_dtb);
if (!file) {
#ifdef CONFIG_ROCKCHIP_EARLY_DISTRO_DTB
ret = rockchip_read_distro_dtb(fdt_addr);
if (ret > 0)
return ret; /* found & load done */
#endif
#ifdef CONFIG_ROCKCHIP_HWID_DTB
file = rockchip_read_hwid_dtb();
#endif
if (!file)
return -ENODEV;
}
。。。
从resource.img中获取kernel-dtb有三个阶段(图中的红、绿、蓝三框)。
2.2.1 - get_file_info() 直接在 resource.img 中查找 rk-kernel.dtb
首先是去resource.img中寻找有没有叫“rk-kernel.dtb”的这个设备树文件(宏定义DTB_FILE),在 arch\arm\mach-rockchip\resource_img.c 中:
static struct resource_file *get_file_info(struct resource_img_hdr *hdr,
const char *name)
{
struct resource_file *file;
struct list_head *node;
if (list_empty(&entrys_head)) {
if (init_resource_list(hdr))
return NULL;
}
list_for_each(node, &entrys_head) {
file = list_entry(node, struct resource_file, link);
if (!strcmp(file->name, name))
return file;
}
return NULL;
}
2.2.2 - EARLY_DISTRO中寻找指定dtb
如果 2.2.1 搜索失败了,没有rk-kernel.dtb,且定义了 CONFIG_ROCKCHIP_EARLY_DISTRO_DTB 则调用 rockchip_read_distro_dtb(fdt_addr)函数去寻找,在arch\arm\mach-rockchip\resource_img.c 中:
#ifdef CONFIG_ROCKCHIP_EARLY_DISTRO_DTB
static int rockchip_read_distro_dtb(void *fdt_addr)
{
const char *cmd = "part list ${devtype} ${devnum} -bootable devplist";
char *devnum, *devtype, *devplist;
char devnum_part[12];
char fdt_hex_str[19];
char *fs_argv[5];
int size;
int ret;
if (!rockchip_get_bootdev() || !fdt_addr)
return -ENODEV;
ret = run_command_list(cmd, -1, 0);
if (ret)
return ret;
。。。
在 arch\arm\mach-rockchip\Kconfig中我们可以看到,如果定义了 ROCKCHIP_EARLY_DISTRO_DTB,则我们还可以自己定义在镜像文件中dtb的名称
2.2.3 - HWID判断使用dtb
如果2.2.1失败了,没有rk-kernel.dtb,且定义了CONFIG_ROCKCHIP_HWID_DTB 则调用rockchip_read_hwid_dtb()函数,读取硬件引脚来判断使用什么dtb,在arch\arm\mach-rockchip\resource_img.c 中:
/* Get according to hardware id(GPIO/ADC) */
static struct resource_file *rockchip_read_hwid_dtb(void)
{
struct resource_file *file;
struct list_head *node;
/* Find dtb file according to hardware id(GPIO/ADC) */
list_for_each(node, &entrys_head) {
file = list_entry(node, struct resource_file, link);
if (!strstr(file->name, ".dtb"))
continue;
if (strstr(file->name, KEY_WORDS_ADC_CTRL) &&
strstr(file->name, KEY_WORDS_ADC_CH) &&
!rockchip_read_dtb_by_adc(file->name)) {
return file;
} else if (strstr(file->name, KEY_WORDS_GPIO) &&
!rockchip_read_dtb_by_gpio(file->name)) {
return file;
}
}
return NULL;
}
更多推荐
所有评论(0)