1. U-Boot获取布局内存
在DDR控制器初始化完成之后,就要确定系统中有多少可用内存,这通常是由dram_init_banksize来实现的,而不同的板级文件会实现不同的dram_init_banksize,这也就决定了不同的板级可以定义不同的获取内存布局的方法。这里介绍一种从dtb中获取内存布局的实现,这可以通过简单的调用fdtdec_setup_memory_banksize来实现。而在使能了CONFIG_OF_LIBFDT的情况下,U-Boot又会通过fdt_fixup_memory_banks来为内核dtb添加或修改memory节点,这样就可以只在U-Boot中定义memory信息,内核dtb省略memory定义,从而可以使其更加简洁通用。相信这也是后续U-Boot推荐使用的方式。
这里涉及的函数逻辑都比较简单,下面简单介绍一下。
1.1. fdtdec_setup_memory_banksize:从dtb获取内存布局
fdtdec_setup_memory_banksize的逻辑很简单,就是从dtb中解析memory节点,然后将内存的start和size填充到gd->bd->bi_dram数组。
1.2. arch_fixup_fdt:转换gd->bd->bi_dram
arch_fixup_fdt是一个处理器或板级相关的函数,默认实现为一个空的weak函数。这里以ARM的实现举例。
bi_dram本质是一个结构体数组,而fdt_fixup_memory_banks的参数是start数组和size数组,arch_fixup_fdt完成一个简单的转换。
struct { /* RAM configuration */
phys_addr_t start;
phys_size_t size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
1.3. fdt_fixup_memory_banks:修改内核dtb
fdt_fixup_memory_banks的主要逻辑如下:
- 找到内核dtb的memory节点,如果没有则创建一个memory节点
- 设置节点的属性为device_type
- 将所有的memory的start和size信息写到内核dtb
这里需要注意的是,如果内核dtb中已有memory节点,fdt_fixup_memory_banks只修改第一个memory节点。
2. 函数源码
U-Boot版本:v2023.04
2.1. dram_init_banksize
如下只是dram_init_banksize的一个示例,不同的板级会不同的实现。
int dram_init_banksize(void)
{
return fdtdec_setup_memory_banksize();
}
2.2. fdtdec_setup_memory_banksize
/**
* fdtdec_setup_memory_banksize() - decode and populate gd->bd->bi_dram
*
* Decode the /memory 'reg' property to determine the address and size of the
* memory banks. Use this data to populate the global data board info with the
* phys address and size of memory banks.
*
* This function should be called from a boards dram_init_banksize(). This
* helper function allows for boards to query the device tree for memory bank
* information instead of hard coding the information in cases where it cannot
* be detected automatically.
*
* Return: 0 if OK, -EINVAL if the /memory node or reg property is missing or
* invalid
*/
int fdtdec_setup_memory_banksize(void)
{
int bank, ret, reg = 0;
struct resource res;
ofnode mem = ofnode_null();
mem = get_next_memory_node(mem);
if (!ofnode_valid(mem)) {
debug("%s: Missing /memory node\n", __func__);
return -EINVAL;
}
for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) {
ret = ofnode_read_resource(mem, reg++, &res);
if (ret < 0) {
reg = 0;
mem = get_next_memory_node(mem);
if (!ofnode_valid(mem))
break;
ret = ofnode_read_resource(mem, reg++, &res);
if (ret < 0)
break;
}
if (ret != 0)
return -EINVAL;
gd->bd->bi_dram[bank].start = (phys_addr_t)res.start;
gd->bd->bi_dram[bank].size =
(phys_size_t)(res.end - res.start + 1);
debug("%s: DRAM Bank #%d: start = 0x%llx, size = 0x%llx\n",
__func__, bank,
(unsigned long long)gd->bd->bi_dram[bank].start,
(unsigned long long)gd->bd->bi_dram[bank].size);
}
return 0;
}
2.3. arch_fixup_fdt
源码文件:arch/arm/lib/bootm-fdt.c
int arch_fixup_fdt(void *blob)
{
__maybe_unused int ret = 0;
#if defined(CONFIG_ARMV7_NONSEC) || defined(CONFIG_OF_LIBFDT)
struct bd_info *bd = gd->bd;
int bank;
u64 start[CONFIG_NR_DRAM_BANKS];
u64 size[CONFIG_NR_DRAM_BANKS];
for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) {
start[bank] = bd->bi_dram[bank].start;
size[bank] = bd->bi_dram[bank].size;
#ifdef CONFIG_ARMV7_NONSEC
ret = armv7_apply_memory_carveout(&start[bank], &size[bank]);
if (ret)
return ret;
#endif
}
#ifdef CONFIG_OF_LIBFDT
ret = fdt_fixup_memory_banks(blob, start, size, CONFIG_NR_DRAM_BANKS);
if (ret)
return ret;
#endif
#ifdef CONFIG_ARMV8_SPIN_TABLE
ret = spin_table_update_dt(blob);
if (ret)
return ret;
#endif
#if defined(CONFIG_ARMV7_NONSEC) || defined(CONFIG_ARMV8_PSCI) || \
CONFIG_IS_ENABLED(SEC_FIRMWARE_ARMV8_PSCI)
ret = psci_update_dt(blob);
if (ret)
return ret;
#endif
#endif
#ifdef CONFIG_FMAN_ENET
ret = fdt_update_ethernet_dt(blob);
if (ret)
return ret;
#endif
return 0;
}
2.4. fdt_fixup_memory_banks
/**
* fdt_fixup_memory_banks - Update DT memory node
* @blob: Pointer to DT blob
* @start: Pointer to memory start addresses array
* @size: Pointer to memory sizes array
* @banks: Number of memory banks
*
* Return: 0 on success, negative value on failure
*
* Based on the passed number of banks and arrays, the function is able to
* update existing DT memory nodes to match run time detected/changed memory
* configuration. Implementation is handling one specific case with only one
* memory node where multiple tuples could be added/updated.
* The case where multiple memory nodes with a single tuple (base, size) are
* used, this function is only updating the first memory node without removing
* others.
*/
int fdt_fixup_memory_banks(void *blob, u64 start[], u64 size[], int banks)
{
int err, nodeoffset;
int len, i;
u8 tmp[MEMORY_BANKS_MAX * 16]; /* Up to 64-bit address + 64-bit size */
if (banks > MEMORY_BANKS_MAX) {
printf("%s: num banks %d exceeds hardcoded limit %d."
" Recompile with higher MEMORY_BANKS_MAX?\n",
__FUNCTION__, banks, MEMORY_BANKS_MAX);
return -1;
}
err = fdt_check_header(blob);
if (err < 0) {
printf("%s: %s\n", __FUNCTION__, fdt_strerror(err));
return err;
}
/* find or create "/memory" node. */
nodeoffset = fdt_find_or_add_subnode(blob, 0, "memory");
if (nodeoffset < 0)
return nodeoffset;
err = fdt_setprop(blob, nodeoffset, "device_type", "memory",
sizeof("memory"));
if (err < 0) {
printf("WARNING: could not set %s %s.\n", "device_type",
fdt_strerror(err));
return err;
}
for (i = 0; i < banks; i++) {
if (start[i] == 0 && size[i] == 0)
break;
}
banks = i;
if (!banks)
return 0;
len = fdt_pack_reg(blob, tmp, start, size, banks);
err = fdt_setprop(blob, nodeoffset, "reg", tmp, len);
if (err < 0) {
printf("WARNING: could not set %s %s.\n",
"reg", fdt_strerror(err));
return err;
}
return 0;
}