ARM64 DTS 处理-早期处理

  setup_arch() // arch/arm64/kernel/setup.c

  --> setup_machine_fdt(__fdt_pointer); 检查DTB是否正确,以及早期配置系统

  --> unflatten_device_tree();对DTB进行解析,填充设备树结构下内核针对设备树定义的struct device_node类型对象

初始化。

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
    void *dt_virt = fixmap_remap_fdt(dt_phys);// A
    const char *name;

    if (!dt_virt || !early_init_dt_scan(dt_virt)) {// B
        pr_crit("\n"
            "Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n"
            "The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
            "\nPlease check your bootloader.",
            &dt_phys, dt_virt);

        while (true)
            cpu_relax();
    }

    name = of_flat_dt_get_machine_name();// C
    if (!name)
        return;

    pr_info("Machine model: %s\n", name);// D
    dump_stack_set_arch_desc("%s (DT)", name);
}

此函数分为4个点来解析即ABCD,首先需要注意,此函数是ARM64实现的。即位于文件

arch/arm64/kernel/setup.c 中。

函数参数dt_phys 是DTB位于内存中的物理地址。对于此时运行内核的CPU来说,其已经开启MMU,

使用虚拟地址阶段。

此时内核把dt_phys物理地址转换为虚拟地址。并且针对这块物理内存映射一个虚拟地址范围。

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);
    if (!dt_virt)
        return NULL;

    memblock_reserve(dt_phys, size);
    return dt_virt;
}

函数把以dt_phys为起始地址,大小为DTB,页面属性设置为PAGE_KERNEL_RO进行映射。

然后把这块内存给reserve起来。这样在后续的内存分配中就不会使用DTB占用的内存。当然最后

会通过memblock_free()把内存释放。

再来看下这个内核针对DTB占用的内存是如何映射的。

arch/arm64/mm/mmu.c
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);// 给DTB找个虚拟地址,这是虚拟地址base
    int offset;
    void *dt_virt;

    /*
     * Check whether the physical FDT address is set and meets the minimum
     * alignment requirement. Since we are relying on MIN_FDT_ALIGN to be
     * at least 8 bytes so that we can always access the magic and size
     * fields of the FDT header after mapping the first chunk, double check
     * here if that is indeed the case.
     */
    BUILD_BUG_ON(MIN_FDT_ALIGN < 8);
    if (!dt_phys || dt_phys % MIN_FDT_ALIGN) //参数检查,必须MIN_FDT_ALIGN大小对齐
        return NULL;

    /*
     * Make sure that the FDT region can be mapped without the need to
     * allocate additional translation table pages, so that it is safe
     * to call create_mapping_noalloc() this early.
     *
     * On 64k pages, the FDT will be mapped using PTEs, so we need to
     * be in the same PMD as the rest of the fixmap.
     * On 4k pages, we'll use section mappings for the FDT so we only
     * have to be in the same PUD.
     */
    BUILD_BUG_ON(dt_virt_base % SZ_2M);//必须2MB对齐

    BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> SWAPPER_TABLE_SHIFT !=
             __fix_to_virt(FIX_BTMAP_BEGIN) >> SWAPPER_TABLE_SHIFT);

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

需要注意上面这个映射,根据提供的物理地址和虚拟地址进行设置,并且设置页表属性

    if (fdt_magic(dt_virt) != FDT_MAGIC) //映射后,我们再来检查一下指定偏移后是否是指定数据
        return NULL;// 如果上面是DTB的魔幻数字,说明映射没有问题,否则返回NULL

    *size = fdt_totalsize(dt_virt);//这个是获取DTB的大小。这个直接取自DTB头中成员值大小
    if (*size > MAX_FDT_SIZE) //需要注意,当前支持的DTB大小不能超过2MB
        return NULL;

    if (offset + *size > SWAPPER_BLOCK_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;
}

至此,函数__fixmap_remap_fdt完成映射,注意这是个固定映射,即给出虚拟地址范围,然后映射物理地址范围。

在函数fixmap_remap_fdt()运行完后,DTB对应的物理地址被映射了一段虚拟地址,并且这块内存被reserve

起来。

再回到函数 setup_machine_fdt中,在映射完物理内存后,成员dt_virt表示物理内存地址对应的虚拟地址。li

接下来进行B阶段处理,此时针对DTB进行早期的scan。

drivers/of/fdt.c

bool __init early_init_dt_scan(void *params)
{
    bool status;

    status = early_init_dt_verify(params);//主要是对DTB头进行检查。
    if (!status)
        return false;

    early_init_dt_scan_nodes();//针对DTB进行scan。
    return true;
}

函数early_init_dt_verify()

bool __init early_init_dt_verify(void *params)
{
    if (!params)
        return false;

    /* check device tree validity */
    if (fdt_check_header(params)) // 针对提供的DTB,检查其header是否满足要求
        return false;

    /* Setup flat device-tree pointer */
    initial_boot_params = params;//把DTB基地址放入到initial_boot_params
    of_fdt_crc32 = crc32_be(~0, initial_boot_params,
                fdt_totalsize(initial_boot_params)); // 计算DTB的CRC校验值,校验值在of_fdt_raw_init使用
    return true;
}

函数 early_init_dt_scan_nodes()主要是进行早期的初始化,根据DTB配置,进行早期的初始化。

void __init early_init_dt_scan_nodes(void)
{
    int rc = 0;

    /* Retrieve various information from the /chosen node */
    rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
    if (!rc)
        pr_warn("No chosen node found, continuing without\n");

    /* Initialize {size,address}-cells info */
    of_scan_flat_dt(early_init_dt_scan_root, NULL);

    /* Setup memory, calling early_init_dt_add_memory_arch */
    of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

函数of_scan_flat_dt针对每个DTB里面的节点进行扫描,然后调用提供的函数。需要注意回调函数返回0继续扫描

返回非0结束扫描。当然,扫描到最后一个也是必须返回了。此时返回值也是0.

第一个扫描是处理chosen 节点。并且把bootargs属性值拷贝到boot_command_line中。如果config文件定义了

CONFIG_CMDLINE这个宏,即也通过config文件配置命令行,则把配置的命令行参数内容拷贝到

boot_command_line。

函数early_init_dt_scan_chosen最后通过pr_debug打印命令行参数。

值得注意的是,函数early_init_dt_scan_chosen还针对ramdisk进行处理。此时需要内核配置

CONFIG_BLK_DEV_INITRD.

这里主要是查找DTS里面 linux,initrd-start和"linux,initrd-end",需要注意这里配置的是ramdisk的物理地址。

最后把ramdisk的起始地址和大小存放到 phys_initrd_start和phys_initrd_size中。我们将在linux 根文件系统中详细介绍

根文件系统相关内容。

继续看第二个函数early_init_dt_scan_root。

这里获取#size-cells 和 #address-cells 然后设置到 dt_root_size_cells和dt_root_addr_cells中去。

#address-cells表示用几个cell表示地址,#size-cells表示用几个cell表示地址长度,就是表示地址和长度

时需要几个32位数。所以上面给出的注释是初始化 {size,address}-cells信息。

最后扫描函数  提供的回调函数 early_init_dt_scan_memory是针对内存配置进行初始化。

这里主要是调用 memblock_add()函数把整个内存块加入内核中。

函数device_type属性的类型为memory,然后获取linux,usable-memory或者reg属性的值。如下形式:

    /*
     * Reserve below regions from memory node:
     *
     *  0x05e0,0000 - 0x05ef,ffff: MCU firmware runtime using
     *  0x05f0,1000 - 0x05f0,1fff: Reboot reason
     *  0x06df,f000 - 0x06df,ffff: Mailbox message data
     *  0x0740,f000 - 0x0740,ffff: MCU firmware section
     *  0x21f0,0000 - 0x21ff,ffff: pstore/ramoops buffer
     *  0x3e00,0000 - 0x3fff,ffff: OP-TEE
     */  

  memory@0 {
        device_type = "memory";
        reg = <0x00000000 0x00000000 0x00000000 0x05e00000>,
              <0x00000000 0x05f00000 0x00000000 0x00001000>,
              <0x00000000 0x05f02000 0x00000000 0x00efd000>,
              <0x00000000 0x06e00000 0x00000000 0x0060f000>,
              <0x00000000 0x07410000 0x00000000 0x1aaf0000>,
              <0x00000000 0x22000000 0x00000000 0x1c000000>;
    };

当然如果配置了hotpluggable属性,也会设置memory的hotplug性质。

再次回到函数early_init_dt_scan()中,在进行完上面处理后,早期的基本设置初始化完毕。

 

函数of_flat_dt_get_machine_name()获取root节点的model或者compatible字段。

const char * __init of_flat_dt_get_machine_name(void)
{
    const char *name;
    unsigned long dt_root = of_get_flat_dt_root();

    name = of_get_flat_dt_prop(dt_root, "model", NULL);
    if (!name)
        name = of_get_flat_dt_prop(dt_root, "compatible", NULL);
    return name;
}

在model没有设置情况下,则了解compatible属性内容。一般如下形式:

/ {
    model = "HiKey Development Board";
    compatible = "hisilicon,hi6220-hikey", "hisilicon,hi6220";

最后函数打印平台相关信息。

    pr_info("Machine model: %s\n", name);

 

通过上面处理可以看到,在对DTB进行处理时,早期需要针对DTB扫描几次,然后每次针对不同情况处理。

那么如果每个情况都这样进行,每次都扫描,这样性能会非常低,所以,函数unflatten_device_tree()会统一展开

设备树,然后针对每个节点初始化,即初始化一个struct device_node对象关联节点。后面我们需要关注

struct device_node类型对象和struct device以及struct device_driver关系。

 

你可能感兴趣的:(Linux,Kernel,ARM64,体系架构,Linux,Kernel,设备驱动)