Cubietruck开发板SPL阶段加载uboot到SDRAM并启动

接着前几篇博客继续分析,在lowlevel_init结束后也就是s_init返回后,跳转到_main,那么就从_main开始吧!

ENTRY(_main)/* * Set up initial C runtime environment and call board_init_f(0). */#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) ldr sp, =(CONFIG_SPL_STACK) #else ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) #endif bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ sub sp, sp, #GD_SIZE /* allocate one GD above SP */ bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ mov r9, sp/* GD is above SP */ mov r0, #0 bl board_init_f#if ! defined(CONFIG_SPL_BUILD)/* * Set up intermediate environment (new sp and gd) and call * relocate_code(addr_moni). Trick here is that we'll return * 'here' but relocated. */ ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */ bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ ldr r9, [r9, #GD_BD]/* r9 = gd->bd */ sub r9, r9, #GD_SIZE/* new GD is below bd */ adr lr, here ldr r0, [r9, #GD_RELOC_OFF]/* r0 = gd->reloc_off */ add lr, lr, r0 ldr r0, [r9, #GD_RELOCADDR]/* r0 = gd->relocaddr */ b relocate_code here:/* Set up final (full) environment */ bl c_runtime_cpu_setup /* we still call old routine here */ ldr r0, =__bss_start /* this is auto-relocated! */ ldr r1, =__bss_end/* this is auto-relocated! */ mov r2, #0x00000000/* prepare zero to clear BSS */clbss_l:cmp r0, r1/* while not at end of BSS */ strlo r2, [r0]/* clear 32-bit BSS word */ addlo r0, r0, #4/* move to next */ blo clbss_l bl coloured_LED_init bl red_led_on /* call board_init_r(gd_t *id, ulong dest_addr) */ movr0, r9/* gd_t */ ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */ /* call board_init_r */ ldr pc, =board_init_r /* this is auto-relocated! */ /* we should not return here. */#endifENDPROC(_main)

前面已经分析过了,SPL阶段只会编译到bl board_init_f,而且这个函数是中的

void __weak board_init_f(ulong dummy) { /* Clear the BSS. */ memset(__bss_start, 0, __bss_end - __bss_start); /* Set global data pointer. */ gd = &gdata; board_init_r(NULL, 0); }

这里的board_init_r是中的

void board_init_r(gd_t *dummy1, ulong dummy2) { u32 boot_device; debug(">>spl:board_init_r()\n"); #ifdef CONFIG_SYS_SPL_MALLOC_START mem_malloc_init(CONFIG_SYS_SPL_MALLOC_START, CONFIG_SYS_SPL_MALLOC_SIZE); #endif#ifndef CONFIG_PPC /* * timer_init() does not exist on PPC systems. The timer is initialized * and enabled (decrementer) in interrupt_init() here. */ timer_init(); #endif#ifdef CONFIG_SPL_BOARD_INIT spl_board_init(); #endif boot_device = spl_boot_device(); debug("boot device - %d\n", boot_device); switch (boot_device) { #ifdef CONFIG_SPL_RAM_DEVICE case BOOT_DEVICE_RAM: spl_ram_load_image(); break; #endif #ifdef CONFIG_SPL_MMC_SUPPORT case BOOT_DEVICE_MMC1: case BOOT_DEVICE_MMC2: case BOOT_DEVICE_MMC2_2: spl_mmc_load_image(); break; #endif #ifdef CONFIG_SPL_NAND_SUPPORT case BOOT_DEVICE_NAND: spl_nand_load_image(); break; #endif #ifdef CONFIG_SPL_ONENAND_SUPPORT case BOOT_DEVICE_ONENAND: spl_onenand_load_image(); break; #endif #ifdef CONFIG_SPL_NOR_SUPPORT case BOOT_DEVICE_NOR: spl_nor_load_image(); break; #endif #ifdef CONFIG_SPL_YMODEM_SUPPORT case BOOT_DEVICE_UART: spl_ymodem_load_image(); break; #endif #ifdef CONFIG_SPL_SPI_SUPPORT case BOOT_DEVICE_SPI: spl_spi_load_image(); break; #endif #ifdef CONFIG_SPL_ETH_SUPPORT case BOOT_DEVICE_CPGMAC: #ifdef CONFIG_SPL_ETH_DEVICE spl_net_load_image(CONFIG_SPL_ETH_DEVICE); #else spl_net_load_image(NULL); #endif break; #endif #ifdef CONFIG_SPL_USBETH_SUPPORT case BOOT_DEVICE_USBETH: spl_net_load_image("usb_ether"); break; #endif #ifdef CONFIG_SPL_USB_SUPPORT case BOOT_DEVICE_USB: spl_usb_load_image(); break; #endif #ifdef CONFIG_SPL_SATA_SUPPORT case BOOT_DEVICE_SATA: spl_sata_load_image(); break; #endif default: debug("SPL: Un-supported Boot Device\n"); hang(); } switch (spl_image.os) { case IH_OS_U_BOOT: debug("Jumping to U-Boot\n"); break; #ifdef CONFIG_SPL_OS_BOOT case IH_OS_LINUX: debug("Jumping to Linux\n"); spl_board_prepare_for_linux(); jump_to_image_linux((void *)CONFIG_SYS_SPL_ARGS_ADDR); #endif default: debug("Unsupported OS image.. Jumping nevertheless..\n"); } jump_to_image_no_args(&spl_image); }

其实这些前几篇大概已经分析过了,这里详细分析一下spl_mmc_load_image(); 系统从SD卡启动
的就是运行这段代码的,想学习一下SD卡的驱动

void spl_mmc_load_image(void) { struct mmc *mmc; int err; u32 boot_mode; mmc_initialize(gd->bd); /* We register only one device. So, the dev id is always 0 */ mmc = find_mmc_device(0); if (!mmc) { #ifdef CONFIG_SPL_LIBCOMMON_SUPPORT puts("spl: mmc device not found!!\n"); #endif hang(); } err = mmc_init(mmc); if (err) { #ifdef CONFIG_SPL_LIBCOMMON_SUPPORT printf("spl: mmc init failed: err - %d\n", err); #endif hang(); } boot_mode = spl_boot_mode(); if (boot_mode == MMCSD_MODE_RAW) { debug("boot mode - RAW\n"); #ifdef CONFIG_SPL_OS_BOOT if (spl_start_uboot() || mmc_load_image_raw_os(mmc)) #endif err = mmc_load_image_raw(mmc, CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR); #ifdef CONFIG_SPL_FAT_SUPPORT } else if (boot_mode == MMCSD_MODE_FAT) { debug("boot mode - FAT\n"); #ifdef CONFIG_SPL_OS_BOOT if (spl_start_uboot() || spl_load_image_fat_os(&mmc->block_dev, CONFIG_SYS_MMC_SD_FAT_BOOT_PARTITION)) #endif err = spl_load_image_fat(&mmc->block_dev, CONFIG_SYS_MMC_SD_FAT_BOOT_PARTITION, CONFIG_SPL_FAT_LOAD_PAYLOAD_NAME); #endif #ifdef CONFIG_SUPPORT_EMMC_BOOT } else if (boot_mode == 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). */ int part = (mmc->part_config >> 3) & PART_ACCESS_MASK; if (part == 7) part = 0; if (mmc_switch_part(0, part)) { #ifdef CONFIG_SPL_LIBCOMMON_SUPPORT puts("MMC partition switch failed\n"); #endif hang(); } #ifdef CONFIG_SPL_OS_BOOT if (spl_start_uboot() || mmc_load_image_raw_os(mmc)) #endif err = mmc_load_image_raw(mmc, CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR); #endif } else { #ifdef CONFIG_SPL_LIBCOMMON_SUPPORT puts("spl: wrong MMC boot mode\n"); #endif hang(); } if (err) hang(); }

首先mmc_initialize(gd->bd);

int mmc_initialize(bd_t *bis) { INIT_LIST_HEAD (&mmc_devices); cur_dev_num = 0; if (board_mmc_init(bis) < 0) cpu_mmc_init(bis); #ifndef CONFIG_SPL_BUILD print_mmc_devices(','); #endif do_preinit(); return 0; }

其中的board_mmc_init(bis)
#ifdef CONFIG_GENERIC_MMC int board_mmc_init(bd_t *bis) { sunxi_mmc_init(CONFIG_MMC_SUNXI_SLOT); #if !defined (CONFIG_SPL_BUILD) && defined (CONFIG_MMC_SUNXI_SLOT_EXTRA) sunxi_mmc_init(CONFIG_MMC_SUNXI_SLOT_EXTRA); #endif return 0; } #endif

很明显实际的工作交给了sunxi_mmc_init(CONFIG_MMC_SUNXI_SLOT);

int sunxi_mmc_init(int sdc_no) { struct mmc *mmc; memset(&mmc_dev[sdc_no], 0, sizeof(struct mmc)); memset(&mmc_host[sdc_no], 0, sizeof(struct sunxi_mmc_host)); mmc = &mmc_dev[sdc_no]; sprintf(mmc->name, "SUNXI SD/MMC"); mmc->priv = &mmc_host[sdc_no]; mmc->send_cmd = mmc_send_cmd; mmc->set_ios = mmc_set_ios; mmc->init = mmc_core_init; mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; mmc->host_caps = MMC_MODE_4BIT; mmc->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; mmc->f_min = 400000; mmc->f_max = 52000000; mmc_resource_init(sdc_no); mmc_clk_io_on(sdc_no); mmc_register(mmc); return 0; }

上面就是mmc_initialize(gd->bd); 的实际工作了,满眼都是结构体,还是从结构体说起吧!
首先抛开linux下SD卡的驱动模型,比如什么kobject、kset、块设备的缓冲、总线等概念,
但是有些概念还是需要的,比如HOST控制器,所以这里主要介绍4个struct

struct mmc { struct list_head link; char name[32]; void *priv; uint voltages; uint version; uint has_init; uint f_min; uint f_max; int high_capacity; uint bus_width; uint clock; uint card_caps; uint host_caps; uint ocr; uint dsr; uint dsr_imp; uint scr[2]; uint csd[4]; uint cid[4]; ushort rca; char part_config; char part_num; uint tran_speed; uint read_bl_len; uint write_bl_len; uint erase_grp_size; u64 capacity; u64 capacity_user; u64 capacity_boot; u64 capacity_rpmb; u64 capacity_gp[4]; block_dev_desc_t block_dev; int (*send_cmd)(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data); void (*set_ios)(struct mmc *mmc); int (*init)(struct mmc *mmc); int (*getcd)(struct mmc *mmc); int (*getwp)(struct mmc *mmc); uint b_max; char op_cond_pending; /* 1 if we are waiting on an op_cond command */ char init_in_progress; /* 1 if we have done mmc_start_init() */ char preinit; /* start init as early as possible */ uint op_cond_response; /* the response byte from the last op_cond */ };

不用说了,MMC的主要结构体。其中要注意的是block_dev_desc_t block_dev; void *priv;
说一下个人的理解,其中block_dev_desc_t block_dev; 相当于MMC留个上层的接口,相对于
linux驱动中的嵌入到实际驱动的字符设备结构体、块设备结构体 ,毕竟MMC对于上层而言是
块设备,那么这个block_dev_desc_t block_dev; 中重要的接口就是block_read、block_write、
block_erase,由于对linux块设备驱动研究的不是很深,好像linux的块设备驱动没有这种直接的

接口,这里的接口更像是无OS的裸机驱动,还有和linux块设备驱动的最大区别莫过于没有缓冲,
或者说没有linux的缓冲结构复杂,这个跟SD卡是否存在文件系统有关系吗??
那么void *priv; 是什么呢?在sunxi_mmc_init中对priv 的操作就是mmc->priv = &mmc_host[sdc_no];
所以猜测用于指向mmc控制器,和linux的MMC驱动中的host有点像。
那么小结一下:
mmc驱动的模型:
block_dev_desc_t block_dev; 是留给上层的接口,使用方法是
mmc->block_dev.block_read(0, sector, 1, header); 读取块设备的一个例子。



void *priv; 是具体的MMC控制器,为什么要有这个呢?还拿读取块设备作为例子吧。

mmc->block_dev.block_read(0, sector, 1, header); 的实际操作按理说是直接操作MMC控制器的,

但是系统往往不只有一个MMC控制器,所以加入这个void *priv可以指向某个MMC控制器,而不至于
把驱动写死了,也就是说mmc->block_dev.block_read(0, sector, 1, header); 的实际执行动作是可以
从几个MMC控制器中选择的。
小结完毕
看看block_dev_desc(也就是block_dev_desc_t)和sunxi_mmc_host的定义吧
typedef struct block_dev_desc { intif_type; /* type of the interface */ intdev; /* device number */ unsigned char part_type; /* partition type */ unsigned char target; /* target SCSI ID */ unsigned char lun; /* target LUN */ unsigned char type; /* device type */ unsigned char removable; /* removable device */ #ifdef CONFIG_LBA48 unsigned char lba48; /* device can use 48bit addr (ATA/ATAPI v7) */ #endif lbaint_t lba; /* number of blocks */ unsigned long blksz; /* block size */ intlog2blksz; /* for convenience: log2(blksz) */ charvendor [40+1]; /* IDE model, SCSI Vendor */ charproduct[20+1]; /* IDE Serial no, SCSI product */ charrevision[8+1]; /* firmware revision */ unsigned long (*block_read)(int dev, lbaint_t start, lbaint_t blkcnt, void *buffer); unsigned long (*block_write)(int dev, lbaint_t start, lbaint_t blkcnt, const void *buffer); unsigned long(*block_erase)(int dev, lbaint_t start, lbaint_t blkcnt); void*priv; /* driver private struct pointer */ }block_dev_desc_t;

struct sunxi_mmc_host { unsigned mmc_no; uint32_t *mclkreg; unsigned database; unsigned fatal_err; unsigned mod_clk; struct sunxi_mmc *reg; };

虽然不能理解每个成员的意义,但是主要的成员还是看的懂的。
那么回过头来看看int sunxi_mmc_init(int sdc_no)
memset(&mmc_dev[sdc_no], 0, sizeof(struct mmc));
memset(&mmc_host[sdc_no], 0, sizeof(struct sunxi_mmc_host));

意思很明白,但是问题是为什么会有struct mmc、struct sunxi_mmc_host数组呢?
其实也不难理解,a20有4个MMC控制器吧,搜索参考手册中的SDC,结果存在SDC0~3。
OK继续,mmc->priv = &mmc_host[sdc_no]不用解释了,参考前面对MMC控制器的解释。
mmc->send_cmd = mmc_send_cmd;
mmc->set_ios = mmc_set_ios;
mmc->init = mmc_core_init;

mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
mmc->host_caps = MMC_MODE_4BIT;
mmc->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS;

mmc->f_min = 400000;
mmc->f_max = 52000000;

这些都不用说了吧,每个意义不能很深的理解,但是大概意思还是懂的,
mmc_resource_init(sdc_no);
static int mmc_resource_init(int sdc_no) { struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no]; struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; debug("init mmc %d resource\n", sdc_no); switch (sdc_no) { case 0: mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC0_BASE; mmchost->mclkreg = &ccm->sd0_clk_cfg; break; case 1: mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC1_BASE; mmchost->mclkreg = &ccm->sd1_clk_cfg; break; case 2: mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC2_BASE; mmchost->mclkreg = &ccm->sd2_clk_cfg; break; case 3: mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC3_BASE; mmchost->mclkreg = &ccm->sd3_clk_cfg; break; default: printf("Wrong mmc number %d\n", sdc_no); return -1; } mmchost->database = (unsigned int)mmchost->reg + 0x100; mmchost->mmc_no = sdc_no; return 0; }

设置mmhost为具体的控制器。由于在这里是SUNXI_MMC0_BASE
mmc_clk_io_on(sdc_no); 代码就不粘了,主要是对MMC要用到的管脚初始化、还有用刚才

设置好的mmhost设置MMC时钟等等。
mmc_register(mmc); 不用说了,注册MMC,在这里分析一下


int mmc_register(struct mmc *mmc) { /* Setup dsr related values */ mmc->dsr_imp = 0; mmc->dsr = 0xffffffff; /* Setup the universal parts of the block interface just once */ mmc->block_dev.if_type = IF_TYPE_MMC; mmc->block_dev.dev = cur_dev_num++; mmc->block_dev.removable = 1; mmc->block_dev.block_read = mmc_bread; mmc->block_dev.block_write = mmc_bwrite; mmc->block_dev.block_erase = mmc_berase; if (!mmc->b_max) mmc->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; INIT_LIST_HEAD (&mmc->link); list_add_tail (&mmc->link, &mmc_devices); return 0; }

很明显,设置对上层留出的接口。
mmc->block_dev.block_read = mmc_bread;
mmc->block_dev.block_write = mmc_bwrite;
mmc->block_dev.block_erase = mmc_berase;
等赋值语句,其实就是通过给block_dev.block_read、block_dev.block_write、block_dev.block_erase赋值。
从而给出了读、写、擦除块的统一访问方式,要不然必须直接(其实也可以)调用mmc_bread、mmc_bwrite、
mmc_berase。注意这里的 mmc_bread、mmc_bwrite、mmc_berase并不属于struct mmc。

同样
mmc->send_cmd = mmc_send_cmd;
mmc->set_ios = mmc_set_ios;
mmc->init = mmc_core_init;

中的mmc_send_cmd、mmc_set_ios、mmc_core_init; 并不属于struct mmc。可以看到struct mmc的作用
只是起到具体的连接几个函数的作用。比如把block_read连接到mmc_bread; 而mmc_bread又调用
mmc_read_blocks,而mmc_read_blocks又调用mmc_send_cmd,需要注意的是mmc_send_cmd是操作
具体的MMC控制器的,刚才说了block_read是可以选择具体的MMC控制器的,那么怎么选择呢?这当中就是
通过mmc这个结构体,可以看到整个流程的每个函数都需要参数struct mmc,而这个mmc可以通过设备号
查找得到,从而就选择了某个mmc,由于mmc的mmc_initialize(gd->bd)过程已经把mmc设备号绑定到具体的
MMC控制器上,但是可以重新绑定的,用户需要注意的就是在选在实际SD卡0,的时候有可能程序中是find设备
好为3的mmc(是由mmc_initialize(gd->bd)造成的)。
继续分析INIT_LIST_HEAD (&mmc->link); list_add_tail (&mmc->link, &mmc_devices);
不用说了,系统是通过链表把各个设备的驱动联系起来的,所以要有链表的操作。
这里需要注意的是mmc->block_dev.dev = cur_dev_num++; 给了mmc->block_dev.dev 一个设备号。
所以在寻找设备时,可以通过设备号。比如spl_mmc_load_image函数中
/* We register only one device. So, the dev id is always 0 */
mmc = find_mmc_device(0);

意思就很明白了。


告一段落,返回spl_mmc_load_image中,接下来是mmc = find_mmc_device(0); 不用说了吧。
err = mmc_init(mmc); 刚开始看到的时候好郁闷,怎么又是init?那么分析一下吧

int mmc_init(struct mmc *mmc) { int err = IN_PROGRESS; unsigned start = get_timer(0); if (mmc->has_init) return 0; if (!mmc->init_in_progress) err = mmc_start_init(mmc); if (!err || err == IN_PROGRESS) err = mmc_complete_init(mmc); debug("%s: %d, time %lu\n", __func__, err, get_timer(start)); return err; }

主要的是mmc_start_init(mmc)、mmc_complete_init(mmc)。

int mmc_start_init(struct mmc *mmc) { int err; if (mmc_getcd(mmc) == 0) { mmc->has_init = 0; #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) printf("MMC: no card present\n"); #endif return NO_CARD_ERR; } if (mmc->has_init) return 0; err = mmc->init(mmc); if (err) return err; mmc_set_bus_width(mmc, 1); mmc_set_clock(mmc, 1); /* Reset the Card */ err = mmc_go_idle(mmc); if (err) return err; /* The internal partition reset to user partition(0) at every CMD0*/ mmc->part_num = 0; /* Test for SD version 2 */ err = mmc_send_if_cond(mmc); /* Now try to get the SD card's operating condition */ err = sd_send_op_cond(mmc); /* If the command timed out, we check for an MMC card */ if (err == TIMEOUT) { err = mmc_send_op_cond(mmc); if (err && err != IN_PROGRESS) { #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) printf("Card did not respond to voltage select!\n"); #endif return UNUSABLE_ERR; } } if (err == IN_PROGRESS) mmc->init_in_progress = 1; return err; }

看到这里大概就明白了,这其实是涉及到SD卡的协议的,回想一下,其实刚才的mmc_initialize(gd->bd)
做的工作其实都只是为访问SD卡而做的数据结构的初始化和a20的mmc控制器、管脚的初始化,这里的
mmc_init(mmc); 其实是按照SD协议去初始化SD卡,恍然大迷瞪。

mmc_complete_init()就不说了。

返回到void spl_mmc_load_image(void)中,接下来boot_mode = spl_boot_mode();
前面已经分析过了,会返回boot_mode == MMCSD_MODE_RAW。
所以mmc_load_image_raw(mmc,CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR);
我去,这才是真正的开始加载uboot代码。
【Cubietruck开发板SPL阶段加载uboot到SDRAM并启动】下一篇再分析吧,有点累

    推荐阅读