linux设备树-uboot对设备树支持

发布时间 2023-04-02 00:19:09作者: 大奥特曼打小怪兽

----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

一、linux内核启动

我们回顾一下uboot引导linux内核启动过程 uboot通过执行bootcmd命令启动内核

bootcmd="nand read 0x30000000 kernel; bootm 0x30000000"

uboot首先将内核镜像从NAND拷贝到内存地址0x30000000,然后执行bootm 0x30000000命令。

1.1  bootm命令

bootm这个命令用于启动一个操作系统映射,实际上调用的是do_bootm_linux函数,还函数位于arch/arm/lib/bootm.c文件:

/* Main Entry point for arm bootm implementation
 *
 * Modeled after the powerpc implementation
 * DIFFERENCE: Instead of calling prep and go at the end
 * they are called if subcommand is equal 0.
 */
int do_bootm_linux(int flag, int argc, char * const argv[],
           bootm_headers_t *images)
{
    /* No need for those on ARM */
    if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)  // 不执行
        return -1;

    if (flag & BOOTM_STATE_OS_PREP) {     // 执行
        boot_prep_linux(images);
        return 0;
    }

    if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {  // 执行
        boot_jump_linux(images, flag);
        return 0;
    }

    boot_prep_linux(images);
    boot_jump_linux(images, flag);
    return 0;
}

boot_prep_linux函数跟内核传递参数有关系,为内核设置启动参数,u-boot向内核传递参数就是在这里做的准备:

boot_jump_linux会将tags的首地址也就是gd->bd->bi_boot_params传给内核,并跳转到内核地址、启动内核,让内核解析这些tag。

1.2 boot_jump_linux

/* Subcommand: GO */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
    unsigned long machid = gd->bd->bi_arch_number;       // 获取机器码
    char *s;
    void (*kernel_entry)(int zero, int arch, uint params);   // 内核入口函数
    unsigned long r2;
    int fake = (flag & BOOTM_STATE_OS_FAKE_GO);

    kernel_entry = (void (*)(int, int, uint))images->ep;   // 指定为内核入口地址

    s = getenv("machid");
    if (s) {
        if (strict_strtoul(s, 16, &machid) < 0) {
            debug("strict_strtoul failed!\n");
            return;
        }
        printf("Using machid 0x%lx from environment\n", machid);
    }

    debug("## Transferring control to Linux (at address %08lx)" \
        "...\n", (ulong) kernel_entry);
    bootstage_mark(BOOTSTAGE_ID_RUN_OS);
    announce_and_cleanup(fake);

    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)  // 如果使能FDT,并且扁平设备树长度大于0
        r2 = (unsigned long)images->ft_addr;
    else
        r2 = gd->bd->bi_boot_params;

    if (!fake) {
         kernel_entry(0, machid, r2);
    }
}
1.2.1 matchid

由于我们没有使用设备树,需要uboo传递一个machid给linux 内核,uboot在不设置machid环境变量时,uboot会使用默认的机器id,我们Mini2440之linux内核移植中将其修改为了168:

#define MACH_TYPE_SMDK2440             168    // 新增的

内核启动的时候会根据machid来比较内核machine_desc中的.nr,machine_desc定义在内核arch/arm/mach-s3c24xx/mach-smdk2440.c文件:

MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
        .atag_offset    = 0x100,

        .init_irq       = s3c2440_init_irq,
        .map_io         = smdk2440_map_io,
        .init_machine   = smdk2440_machine_init,
        .init_time      = smdk2440_init_time,
MACHINE_END

宏MACHINE_START定义在arch/arm/include/asm/mach/arch.h,如下:

/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#define MACHINE_START(_type,_name)                      \
static const struct machine_desc __mach_desc_##_type    \
 __used                                                 \
 __attribute__((__section__(".arch.info.init"))) = {    \
        .nr             = MACH_TYPE_##_type,            \
        .name           = _name,

#define MACHINE_END                             \
};

这里attribute((section(“.arch.info.init”)))就是利用了编译器的特性,把machine_desc放到了.arch.info.init段。

 .init.arch.info : {
  __arch_info_begin = .;
  *(.arch.info.init)
  __arch_info_end = .;
 }

回到前面的MACHINE_START(_type,_name)宏,_type是S3C2440。经过##连接.nr = MACH_TYPE_##_type就变为:.nr = MACH_TYPE_S3C2440。

MACH_TYPE_S3C2440定义在arch/arm/include/generated/asm/mach-types.h:

#define MACH_TYPE_S3C2440              168

现在使用设备树的话,这个参数就不需要设置了。

1.2.2 r2

如果我们使用了设备树,r2将会被设备为了:

r2 = (unsigned long)images->ft_addr;

二、支持FDT(扁平设备树)

2.1 支持FDT

我们如何修改uboot使其支持FDT呢,我们需要在include/configs/smdk2440.h中配置:

#define CONFIG_OF_LIBFDT  

配置CONFIG_OF_LIBFDT之后,uboot编译时将会编译lib/libfdt文件夹:

lib/Makefile:54:obj-$(CONFIG_OF_LIBFDT) += libfdt/
2.1.1 编译uboot
2.1.2 下载uboot

2.2 启动命令 

uboot支持FDT后,我们便可以将dtb下载到内存地址0x30001000中:

tftp 0x30001000 smdk2440.dtb

然后我们将内核镜像加载到内存0x30008000地址:

nand write 300008000 kernel

然后可以使用如下命令启动内核:

 bootm 0x30008000 - 0x30000000   // 无设备树时,直接bootm 0x30008000
//bootm  uImage地址  ramdisk地址  设备树镜像地址

其中:

  • 第一个参数为内核映像在内存的地址;
  • 第二个参数为initrd的地址,若不存在initrd,可以用“-”符号代替;
  • 第三个参数dtb_address为.dtb文件在内存的地址;

此外对于dtb的地址0x30000000,我们需要遵循以下原则:

  • 不要破坏uboot本身;
  • 不要破坏内核本身: 内核本身的空间不能占用, 内核要用到的内存区域也不能占用;

由于Mini2440的内存区间为0x30000000~0x34000000,并且:

  • 高地位为存储uboot代码,以及uboot使用的栈空间,大概250kb;
  • 这里我们将内核加载地址修改为0x30008000地址,内核启动时一般会在它所处位置的下边放置页表,这块空间(一般是0x4000即16K字节)不能被占用;
2.2.1 smdk2440.dtb生成

2.3 FTD命令

uboot提供了fdt的相关命令:

"addr [-c]  <addr> [<length>]   - Set the [control] fdt location to <addr>\n"
    "fdt move   <fdt> <newaddr> <length> - Copy the fdt to <addr> and make it active\n"
    "fdt resize [<extrasize>]            - Resize fdt to size + padding to 4k addr + some optional <extrasize> if needed\n"
    "fdt print  <path> [<prop>]          - Recursive print starting at <path>\n"
    "fdt list   <path> [<prop>]          - Print one level starting at <path>\n"
    "fdt get value <var> <path> <prop>   - Get <property> and store in <var>\n"
    "fdt get name <var> <path> <index>   - Get name of node <index> and store in <var>\n"
    "fdt get addr <var> <path> <prop>    - Get start address of <property> and store in <var>\n"
    "fdt get size <var> <path> [<prop>]  - Get size of [<property>] or num nodes and store in <var>\n"
    "fdt set    <path> <prop> [<val>]    - Set <property> [to <val>]\n"
    "fdt mknode <path> <node>            - Create a new node after <path>\n"
    "fdt rm     <path> [<prop>]          - Delete the node or <property>\n"
    "fdt header                          - Display header info\n"
    "fdt bootcpu <id>                    - Set boot cpuid\n"
    "fdt memory <addr> <size>            - Add/Update memory node\n"
    "fdt rsvmem print                    - Show current mem reserves\n"
    "fdt rsvmem add <addr> <size>        - Add a mem reserve\n"
    "fdt rsvmem delete <index>           - Delete a mem reserves\n"
    "fdt chosen [<start> <end>]          - Add/update the /chosen branch in the tree\n"
    "                                        <start>/<end> - initrd start/end addr\n"

2.4 修改dtb

如果修改设备树中的设备节点信息,有两种办法;

  • 修改dts文件,重新编译得到dtb并上传烧写
  • 使用uboot提供的一些命令来修改dtb文件,修改后再把它保存到板子上,以后就使用这个修改后的dtb文件;

参考文章

[1]linux设备驱动(19)设备树详解3-u-boot传输dts

[2]linux设备驱动(20)设备树详解4-kernel解析dts

[3]linux设备驱动(21)设备树详解5-dts的应用

[4]第四课:u-boot对设备树的支持

[5][uboot] (番外篇)uboot之fdt介绍

[6]内核解析U-boot传入的machid