在u-boot移植过程中,由于u-boot烧录在SD卡中,因此老是加载内核失败,是什么原因呢?在加载内核的打印信息中有这样类似的信息:
reading kernel.. 1120, 10240
MMC read: dev # 1, block # 1120, count 10240 ...10240 blocks read: OK
completed
reading RFS.. 11360, 2048
MMC read: dev # 1, block # 11360, count 2048 ...2048 blocks read: OK
信息描述的意思是从MMC 设备1(这里是指SD卡)的1120块中去读取内核,从11360块中读取ramdisk,但由于此时的SD卡中相应的位置并没有写入kernel 和ramdisk,因此在后续的加载时会失败。那么可以怎么解决呢?这里使用两种方式,一种是让u-boot从eMMC中去读取kernel 和 ramdisk ,另一种是烧录kernel 和 ramdisk 到SD卡中,这里主要记录的是第二种方式的修改记录,对于第一种方式,相较之下比较简单,只要修改默认从eMMC中读取就可以了。
1、u-boot 使用sdfuse 命令烧录的实现过程
u-boot中烧录的镜像的命令格式是:
sdfuse flash [bootloader|kernel|ramdisk|system] xx.bin/img
当使用sdfuse进行烧录时,会调用 common/cmd_fastboot.c –> do_sdfuse()函数进行命令参数解析和处理;do_sdfuse() 函数会调用set_partition_table_sdmmc() 建立一个分区表,下面是do_sdfuse()部分代码:
int do_sdfuse (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
……
if (set_partition_table_sdmmc())
return 1;
……
/* 根据参数解析,判断进入哪种处理方式 */
if ((argc == 2) && !strcmp(argv[1], "info")){……
}else if ((argc == 2) && !strcmp(argv[1], "flashall")){……
}else if ((argc == 4) && !strcmp(argv[1], "flash"))
{
if (update_from_sd(argv[2], argv[3]))
goto err_sdfuse;
ret = 0;
}
else if ((argc == 3) && !strcmp(argv[1], "erase"))
{
……
}
……
}
do_sdfuse 函数通过调用update_from_sd()函数从SD卡中读取镜像进行烧录,update_from_sd()的部分代码:
static int update_from_sd (char *part, char *file)
{
int ret = 1;
/* 读取镜像 */
if (file != NULL){
……
sprintf(filename, "%s%s", CFG_FASTBOOT_SDFUSE_DIR, file);
offset = CFG_FASTBOOT_TRANSFER_BUFFER;
count = 0;
size = file_fat_read (filename, (unsigned char *) offset, count);
if (size == -1) {
printf("Failed to read %s\n", filename);
return 1;
}
download_size = 0; // should be 0
download_bytes = size;
}
else{
download_size = 0; // should be 0
download_bytes = 0;
}
{
char command[32];
if (download_bytes == 0)
sprintf(command, "%s:%s", "erase", part);
else
sprintf(command, "%s:%s", "flash", part);
ret = rx_handler(command, sizeof(command));
}
return ret;
}
rx_handler()函数中实现操作方式的判断:根据 “rx_handler(command, sizeof(command));”的一个参数进行分类判断,这里解析到传递的第一个参数是“flash”:
static int rx_handler (const unsigned char *buffer, unsigned int buffer_size)
{
……
if (download_size){……}
else{
……
if (memcmp(cmdbuf, "flash:", 6) == 0){
……
/* 根据启动方式:oneNand、MMC*/
/* 如果是 oneNAND */
调用函数:write_to_ptn()
/* 如果是 MMC */
调用函数:write_to_ptn_sdmmc()
……
}
……
}
使用的板子是外接eMMC的,因此是调用write_to_ptn_sdmmc()函数;write_to_ptn_sdmmc()函数中实现的功能是判断通过哪种命令方式来将镜像写到MMC中:mmc命令 或 movi命令,如何判断使用哪种方式来写镜像呢?在set_partition_table_sdmmc()函数中为每一分区都定义了一个flag,通过这个flag来判断写入的方式。
static int write_to_ptn_sdmmc(struct fastboot_ptentry *ptn, unsigned int addr, unsigned int size)
{
……
if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_USE_MMC_CMD)
{
argv[2] = device;
argv[3] = buffer;
argv[4] = start;
argv[5] = length;
……
if (check_compress_ext4((char*)addr,ptn->length) != 0) {
ret = do_mmcops(NULL, 0, 6, argv);
}
else {
ret = write_compressed_ext4((char*)addr,
ptn->start / CFG_FASTBOOT_SDMMC_BLOCKSIZE);
}
}
else if (ptn->flags & FASTBOOT_PTENTRY_FLAGS_USE_MOVI_CMD){
……
/* 将烧录镜像描述名修改为分区名,例如 ramdisk -> rootfs*/
if(!strcmp(ptn->name, "ramdisk ")){
strncpy(part, "rootfs", 7);
argv[4] = length;
……
}
……
ret = do_movi(NULL, 0, argc, argv);
}
……
}
system镜像是通过 mmc命令来烧写的,这里会通过在函数write_compressed_ext4()中调用write_raw_chunk函数来执行mmc命令:
int write_raw_chunk(char* data, unsigned int sector, unsigned int sector_size)
{
char run_cmd[64];
int err ;
ext4_printf("write raw data in %d size %d \n", sector, sector_size);
sprintf(run_cmd,"mmc write %s 0x%x 0x%x 0x%x", dev_number_write ? "1" : "0",
(int)data, sector, sector_size);
err = run_command(run_cmd, 0);
return (1-err); //mj
}
kernel 、ramdisk、u-boot都是使用movi 命令烧录的,也就是调用“do_movi(NULL, 0, argc, argv);”,运行movi命令就是通过do_movi函数( common/cmd_movi.c)来解析的,函数主要实现使用movi命令时是读/写 到eMMC 还是 SD卡:
int do_movi(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
{
……
/* 判断是哪种方式启动、将该启动方式的设备设置为当前设备 */
if(OmPin == BOOT_EMMC441){
boot_dev = 0;
}
else if(OmPin == BOOT_MMCSD){
//emmc exist..
if(strcmp(mmc->name, "S5P_MSHC4") == 0){
boot_dev = 1;
}
else{
boot_dev = 0;
}
}
else{
boot_dev = 0;
printf("[ERROR]Undefined booting mode\n");
}
/* 根据attribute 来确定从哪个分区读/写; attribute在分区表中定义 */
if (attribute == 0x2) {
……
printf("%s bootloader.. %ld, %ld ", rw ? "writing":"reading",
start_blk, blkcnt);
sprintf(run_cmd,"mmc %s 1 0x%lx 0x%lx 0x%lx", //0-->1
rw ? "write":"read",
addr, start_blk, blkcnt);
run_command(run_cmd, 0);
return 1;
}
/* u-boot r/w for emmc*/
if (attribute == 0x20) {
……
printf("%s bootloader.. %ld, %ld \n", rw ? "writing":"reading",
start_blk, blkcnt);
……
sprintf(run_cmd,"mmc %s 0 0x%lx 0x%lx 0x%lx",
rw ? "write":"read",
addr, start_blk, blkcnt);
run_command(run_cmd, 0);
……
return 1;
}
if (attribute == 0x4) {
……
printf("%s kernel.. %ld, %ld ", rw ? "writing" : "reading",
start_blk, blkcnt);
if (1 == fuse_by_fastboot)
sprintf(run_cmd, "mmc %s %s 0x%lx 0x%lx 0x%lx",
rw ? "write" : "read", dev_number_write ? "1" : "0",
addr, start_blk, blkcnt);
else {
sprintf(run_cmd,"mmc %s %d 0x%lx 0x%lx 0x%lx",rw ? "write":"read",boot_dev,
addr, start_blk, blkcnt);
}
run_command(run_cmd, 0);
printf("completed\n");
return 1;
}
……
}
do_movi函数调用run_command(run_cmd, 0)函数,运行命令解析函数,执行“mmc”命令,mmc的执行函数是:do_mmcops(),do_mmcops函数实现从MMC中读数据,或者写数据到MMC中。
2、从SD卡中读写镜像
通过上面的分析,已经大概知道了u-boot读写镜像的过程,因此接下要实现从SD卡中读写镜像功能就有思路了。只需要在实现烧录的时候进行判断烧录到哪个分区就可以,这里修改后实现了将kernel、ramdisk、system烧录到启动的那个MMC设备上,将u-boot始终烧录到eMMC中。在do_movi函数中读写u-boot、kernel、ramdisk镜像执行的mmc命令可以分析出,u-boot的读写设备是写死的,而kernel、ramdisk却是可以通过判断来决定操作哪个MMC设备。
/* bootloader 第二个参数写死的:0 或者 1*/
sprintf(run_cmd,"mmc %s 0 0x%lx 0x%lx 0x%lx",
rw ? "write":"read",
addr, start_blk, blkcnt);
/* kernel、ramdisk 类似*/
if (1 == fuse_by_fastboot)
sprintf(run_cmd, "mmc %s %s 0x%lx 0x%lx 0x%lx",
rw ? "write" : "read", dev_number_write ? "1" : "0",
addr, start_blk, blkcnt);
因此只需要根据当前的启动方式判断启动设备,并将fuse_by_fastboot设置为1即可实现将kernel、ramdisk烧录到SD卡中,do_movi()如下修改(+表示添加):
+ fuse_by_fastboot = 1;
if(OmPin == BOOT_EMMC441){
boot_dev = 0;
+ dev_number_write = 0;
}
else if(OmPin == BOOT_MMCSD){
//emmc exist..
if(strcmp(mmc->name, "S5P_MSHC4") == 0){
boot_dev = 1;
}
else{
boot_dev = 0;
}
+ dev_number_write = 1;
}
else{
+ dev_number_write = 0;
boot_dev = 0;
printf("[ERROR]Undefined booting mode\n");
}
将system烧录到SD卡,需要在执行movi命令之前根据启动方式进行判断当前的启动设备,write_raw_chunk函数做如下改动(+表示添加)
int write_raw_chunk(char* data, unsigned int sector, unsigned int sector_size)
{
char run_cmd[64];
int err ;
+ if(OmPin == BOOT_EMMC441){
+ dev_number_write = 0;
+ }
+ else if(OmPin == BOOT_MMCSD){
+ dev_number_write = 1;
+ }
+ else{
+ dev_number_write = 0;
+ printf("[ERROR]Undefined booting mode\n");
+ }
}
至此修改完成,可以实现将kernel、ramdisk、system烧写到SD卡中,或者从SD卡中读取。实现这样的目的是通过PC查看SD卡,可以更加直观的看到分区的分布和镜像烧录的哪去了的问题。解决了明明格式化了分区,为什么烧录的镜像除了system镜像,其他的镜像还是没有被消灭掉,还正常的在eMMC中躺着,这是因为只是格式化了设置的分区,并没有对u-boot中的虚拟分区(暂时这样理解的)进行格式化,下图就中说明了,使用fdisk -c 1 300 300 300 对SD卡进行分区后内存分布,还剩两个部分的内存空间,而我们的u-boot 的虚拟分区就位于头部的块中,这是因为在烧录时是从第一个块开始的。那么如果想要消灭MMC中的kernel、u-boot、ramdisk,就应该将正SD卡格式化,包括黑色显示未使用部分,如果只是格式建立的分区是消灭不了的。(备注:水平欠佳,如有欠缺欢迎纠正)