本文来分析fixmap_remap_fdt函数的代码
输入:dtb的物理地址
输出:dtb映射后的虚拟地址
功能:为dtb所在的物理内存建立映射
void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
{
void *dt_virt;
int size;
dt_virt = __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);
memblock_reserve(dt_phys, size);
return dt_virt;
}
映射使用的flag是PAGE_KERNEL_RO;
物理地址就是dtb做2MB对齐后的地址,在当前项目中dtb在0x13408000,对齐后就是0x13400000
虚拟地址就是FIX_FDT对应的fixmap地址:0xffffffbefe800000
映射区域大小不确定,需要从dtb中读取出来。这就有问题了,我们做这个映射就是为了访问dtb,而现在为了完成映射却需要先访问dtb。解决方法是:先对dtb进行2MB大小的映射,映射完成后就可以读取dtb的前面几个字节,其中有dtb的size,最后再根据这个size来确定最后的映射大小。因为对dtb大小有要求,必须小于2M,我们这个项目中第一次进行2M映射后就已经满足要求了。
[9950] booting linux @ 0x10080000, ramdisk @ 0x13608000 (2083882), tags/device tree @ 0x13408000
[ 0.000000] fixed : 0xffffffbefe7fb000 - 0xffffffbefec00000 ( 4116 KB)
FIXADDR_TOP = 0xffffffbefec00000
FIX_FDT = 1024
#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT))
再看__fixmap_remap_fdt的实现:
由于section map需要保证物理地址和虚拟地址都是2M对齐,而我们的物理地址是0x13408000,因此会有个offset=0x8000,所以返回的dt_virt需要在dt_virt_base基础上再加上offset,因此最终的结果是0xFFFFFFBEFE808000,它会被保存在变量initial_boot_params中,后面会使用到。
第一次映射是在VA:dt_virt_base,PA:0x13400000,size=2M进行的,注意调用的是create_mapping_noalloc,也就是不允许申请page来完成映射。对于section map,需要两个页表,分别是pgd和pmd,pgd自然使用nit_mm.pgd,其实就是swapper_pg_dir,pmd则使用的是bm_pmd,因为fixmap的地址空间非常小,也就几MB大小(0xffffffbefe800000和0xffffffbefe7fb000完全在同一个pgd范围内,一个pgd可以表示的范围是1G),而之前early_fixmap_init已经完成了swapper_pg_dir[251]=bm_pmd,这里只需要bm_pmd[500]=0x13400000即可
void *__init __fixmap_remap_fdt(phys_addr_t dt_phys, int *size, pgprot_t prot)
{
const u64 dt_virt_base = __fix_to_virt(FIX_FDT);
int offset;
void *dt_virt;
offset = dt_phys % SWAPPER_BLOCK_SIZE;
dt_virt = (void *)dt_virt_base + offset;
/* map the first chunk so we can read the size from the header */
create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE),
dt_virt_base, SWAPPER_BLOCK_SIZE, prot);
*size = fdt_totalsize(dt_virt);
if (*size > MAX_FDT_SIZE)
return NULL;
if (offset + *size > SWAPPER_BLOCK_SIZE)
create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE), dt_virt_base,
round_up(offset + *size, SWAPPER_BLOCK_SIZE), prot);
return dt_virt;
}
ffffff8009cfd000 b bm_pmd
可以看到,0xffffff8009cfd000+500*8=0xffffff8009cfdfa0,对应的值是0x13400f91,其实就是0x13400000,因为后面12bit是flag,其中最后是1不是3正好表面这个是block map,不是page table。
最后再说下dtb的大小限制。
可以看到,dtb的大小限制在2MB内,我们项目实际只有600KB。
既然由此限制,为何还存在第二次map的可能?原因是dtb虽然整体小于2M,但是起始物理地址可能不是2MB对齐,如果它正好包含了一个2M的边界,那即便很小,也会占用4MB,但是因为整体小于2M,所以绝对不会占用3个2M,这也就是第二次map的原因了。
#define MAX_FDT_SIZE SZ_2M
此函数执行完成后,内核就可以访问dtb了。
还有点要提下,这里最后会把这块空间添加到memblock的reserve区域中:
memblock_reserve(dt_phys, size);
此时此刻,memblock还不清楚物理内存地址范围,那么现在可以调用memblock_reserve函数吗?可以的。
以前我的误解:memblock_reserve标记保留的区域,一定是在memblock中的memory区域范围内的。
当然这个说法也不错,那时从memblock_reserve的代码看,它不关注Memory的状态,也就是内存整体的物理地址范围它不在乎。所以在还没有调用memblock_add前是可以memblock_reserve的。
至于之前的想法,那是因为一般地调用顺序总是先从memblock申请内存,在memblock_reserve,而申请到的空间一定是在memory区域内的,所以之前的说法也对。