Uboot 2017.01 SPL中的image_loader

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kl1125290220/article/details/75013028

概述:

有些厂商的MCU的启动过程是:ROM code > SPL > uboot。也就是在Uboot启动前还需要一个SPL来进行一系列的初始化工作,而SPL和UBoot有什么区别呢?我认为最大的区别是:SPL对镜像大小是否敏感,不能超过指定大小,并且其运行环境是在片内RAM中
于是,我们在ROM code阶段后,就是SPL阶段了。而SPL阶段初始化完外部SDRAM后,此时就有环境来去运行我们Uboot了。这个时候需要加载Uboot镜像,在SPL中就是使用image_loader 来实现的。


原型:

struct spl_image_loader {
#ifdef CONFIG_SPL_LIBCOMMON_SUPPORT
    const char *name;
#endif
    uint boot_device;
    /**
     * load_image() - Load an SPL image
     *
     * @spl_image: place to put image information
     * @bootdev: describes the boot device to load from
     */
    int (*load_image)(struct spl_image_info *spl_image,
              struct spl_boot_device *bootdev);
};

所有的image_loader都是一个spl_image_loader结构体。其中有一个关键函数 int load_image函数。该函数不同的设备有不同的实现,而这个函数就是用来加载在该设备下的image。

那么,我们是怎么定义一个image_loader的呢?
首先我们要看,在Uboot中,是怎么找到并使用这个image_loader的。
先用,再来去分析它的机制


使用:

在SPL代码的最后阶段就是去加载内核镜像或者加载Uboot镜像。
首先是在spl.c中的board_init_r函数中调用boot_from_devices()函数来进行加载镜像。

void board_init_r(){
    .....   
    if (boot_from_devices(&spl_image, spl_boot_list,
                  ARRAY_SIZE(spl_boot_list))) {
        puts("SPL: failed to boot from all boot devices\n");
        hang();
    }
    ......
}

进入boot_from_devices查看:

static int boot_from_devices(struct spl_image_info *spl_image,
                 u32 spl_boot_list[], int count)
{
    int i;

    for (i = 0; i < count && spl_boot_list[i] != BOOT_DEVICE_NONE; i++) {
        struct spl_image_loader *loader;

        loader = spl_ll_find_loader(spl_boot_list[i]);
#if defined(CONFIG_SPL_SERIAL_SUPPORT) && defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
        if (loader)
            printf("Trying to boot from %s", loader->name);
        else
            puts("SPL: Unsupported Boot Device!\n");
#endif
        if (loader && !spl_load_image(spl_image, loader))
            return 0;
    }

    return -ENODEV;
}

根据函数的名称,我们就可以猜测到,spl_ll_find_loader 函数就是用来寻找相应的image_loader的。

那么让我们继续进入spl_ll_find_loader函数,看看这里面是什么鬼东西。

static struct spl_image_loader *spl_ll_find_loader(uint boot_device)
{
    struct spl_image_loader *drv =
        ll_entry_start(struct spl_image_loader, spl_image_loader);
    const int n_ents =
        ll_entry_count(struct spl_image_loader, spl_image_loader);
    struct spl_image_loader *entry;

    for (entry = drv; entry != drv + n_ents; entry++) {
        if (boot_device == entry->boot_device)
            return entry;
    }

    /* Not found */
    return NULL;
}

首先我们来分析下第一行。
drv的值是通过ll_entry_start宏进行获取的。现在我们先不管这个宏是干嘛的。
让我们来接着看下去:
n_ents是通过ll_entry_count宏,进行判断的。我们可以通过这个函数名初步判断,这个是获取image_loader的数量。

最后,就到了我们一个for循环,我们先进行猜测,这个函数是干嘛的。
这个函数的目标就是获取一个对应设备的的image_loader。那么肯定会涉及到对每个image_loader进行遍历比较。而比较的一个关键点就是,image_loader的一个成员变量boot_device。
那么根据我们的猜测来去分析下面的for循环可能就能有着事半功倍的效果。

首先,我们的for循环开始,
entry 等于我们一开始通过ll_entry_start宏获取的一个image_loader。我们可以判断,这是drv是第一个image_loader。
然后判断为true条件就是entry 不等于 第一个image_loader加上所有的image_loader的总数。
并且,在for循环里面,进行对比的正是通过boot_device进行对比。

这都验证了我们的猜测。现在基本上这个函数的功能我们已经摸清了。但是,我们仍然有一个十分关键的地方没有摸清楚。

那就是,我们是在哪里定义各个设备对应的image_loader的呢?只有找到这个,我们才能够知道,SPL是来加载镜像的。

定义

要判断各个设备对应的的Image_loader是怎么工作,那么久必须要找到他们定义它的地方。那么我们应该怎么找到呢?
这个时候之前没有分析的宏就能够派上用场了ll_entry_start。让我们来看看,Uboot它自己是怎么找到各个设备的image_loader的呢。

首先,让我们来看看ll_entry_start的原型。

#define ll_entry_start(_type, _list)                    \
({                                  \
    static char start[0] __aligned(4) __attribute__((unused,    \
        section(".u_boot_list_2_"#_list"_1")));         \
    (_type *)&start;                        \
})

这个宏,它定义了一个只有0个char的数组start。而这个start数组,指向的是一个section的地址:”.u_boot_list_2_”#_list”_1”。把这个展开就是.u_boot_list_2_spl_image_loader_1。也就是说,我们的image_loader是保存在uboot镜像中的u_boot_list_2_spl_image_loader_1段中。

于是struct spl_image_loader *drv就是等于,struct spl_image_loader *drv = u_boot_list_2_spl_image_loader_1段的起始地址。

其实看到这里,后面的代码细节就没有必要看了。我们可以通过这个section来去寻找在哪里定义了各个设备的image_loader。

通过进一步的分析,我们可以知道,实际上定义这个section并进行添加的是SPL_LOAD_IMAGE_METHOD这个宏。我们在uboot的源码中进行查找,不出所料我们找到了,

SPL_LOAD_IMAGE_METHOD("MMC1", 0, BOOT_DEVICE_MMC1, spl_mmc_load_image);
SPL_LOAD_IMAGE_METHOD("MMC2", 0, BOOT_DEVICE_MMC2, spl_mmc_load_image);
SPL_LOAD_IMAGE_METHOD("MMC2_2", 0, BOOT_DEVICE_MMC2_2, spl_mmc_load_image);

现在,我们就得到了,在mmc设备上,SPL是如何加载镜像的。是通过spl_mmc_load_image函数进行加载。
函数原型如下:

int spl_mmc_load_image(struct spl_image_info *spl_image,
               struct spl_boot_device *bootdev)
{
    struct mmc *mmc = NULL;
    u32 boot_mode;
    int err = 0;
    __maybe_unused int part;

    err = spl_mmc_find_device(&mmc, bootdev->boot_device);
    if (err)
        return err;

    err = mmc_init(mmc);
    if (err) {
#ifdef CONFIG_SPL_LIBCOMMON_SUPPORT
        printf("spl: mmc init failed with error: %d\n", err);
#endif
        return err;
    }

    boot_mode = spl_boot_mode(bootdev->boot_device);
    err = -EINVAL;
    switch (boot_mode) {
    case MMCSD_MODE_EMMCBOOT:
            /*
             * We need to check what the partition is configured to.
             * 1 and 2 match up to boot0 / boot1 and 7 is user data
             * which is the first physical partition (0).
             */
            part = (mmc->part_config >> 3) & PART_ACCESS_MASK;

            if (part == 7)
                part = 0;

            if (CONFIG_IS_ENABLED(MMC_TINY))
                err = mmc_switch_part(mmc, part);
            else
                err = blk_dselect_hwpart(mmc_get_blk_desc(mmc), part);

            if (err) {
#ifdef CONFIG_SPL_LIBCOMMON_SUPPORT
                puts("spl: mmc partition switch failed\n");
#endif
                return err;
            }
            /* Fall through */
    case MMCSD_MODE_RAW:
        debug("spl: mmc boot mode: raw\n");

        if (!spl_start_uboot()) {
            err = mmc_load_image_raw_os(spl_image, mmc);
            if (!err)
                return err;
        }

        err = mmc_load_image_raw_partition(spl_image, mmc,
            CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION);
        if (!err)
            return err;
#ifdef CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTOR
        err = mmc_load_image_raw_sector(spl_image, mmc,
            CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR);
        if (!err)
            return err;
#endif
        /* If RAW mode fails, try FS mode. */
    case MMCSD_MODE_FS:
        debug("spl: mmc boot mode: fs\n");

        err = spl_mmc_do_fs_boot(spl_image, mmc);
        if (!err)
            return err;

        break;
#ifdef CONFIG_SPL_LIBCOMMON_SUPPORT
    default:
        puts("spl: mmc: wrong boot mode\n");
#endif
    }

    return err;
}

总结

在Uboot中,经常使用的到一个技术就是将一部分的数据保存在特定的段中。

猜你喜欢

转载自blog.csdn.net/kl1125290220/article/details/75013028