本文主要记录了在友善之臂使用的基于瑞芯微github上uboot的rkdevelop分支,以及瑞星微官方的stable-4.4-rk3399-linux分支中,uboot如何给linux内核所需要的kernel-dtb的探究过程。

目录

0 - uboot代码准备

1 - 友善之臂版uboot如何获取要加载的设备树

1.1 - get_fdt_name 获取设备树dtb文件名

1.2 - get_content() 获取所需dtb的地址

2 - 瑞芯微github上stable-4.4版uboot如何获取设备树

2.1 - uboot-dtb

2.2 - kernel-dtb

2.2.1 - get_file_info() 直接在 resource.img 中查找 rk-kernel.dtb

2.2.2 - EARLY_DISTRO中寻找指定dtb

2.2.3 - HWID判断使用dtb


需要知道的一点是,在arm64架构下,linux已经弃用mach_xx等文件夹来描述板级信息供linux内核启动时去匹配根节点下的compatible属性来找到对应设备树。

取而代之是单纯的接收bootloader传递过来的单一设备树文件dtb所在的内存地址。所以当多个dtb文件存在镜像中或者内存中时,如何找到需要的dtb的这个任务,就落在了bootloader肩上。

0 - uboot代码准备

本文中主要涉及到了两个版本的uboot

  • 友善之臂基于瑞芯微 rkdevelop 分支进行修改过的uboot
  • 瑞芯微官方github上stable-4.4-rk3399-linux 分支的uboot

分别可以通过以下方式获取。

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;
}

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐