概述:
有些厂商的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中,经常使用的到一个技术就是将一部分的数据保存在特定的段中。