Uboot31之start.S第二阶段part3
00531: //******************************//
00532: // Board Specific
00533: // #if defined(CONFIG_SMDKXXXX)
00534: //******************************//
00600:#if defined(CONFIG_X210)
00601: #if defined(CONFIG_GENERIC_MMC)
00602: puts ("SD/MMC: ");
00603: mmc_exist = mmc_initialize(gd->bd);
00604: if (mmc_exist != 0)
00605: {
00606: puts ("0 MB\n");
00607: }
00611: #endif
00632: #endif /* CONFIG_X210 */
1)从536到768行为开发板独有的初始化。意思是三星用一套uboot同时满足了好多个系列型号的开发板,然后在这里把不同开发板自己独有的一些初始化写到了这里。用#if条件编译配合CONFIG_xxx宏来选定特定的开发板。
2)X210相关的配置在599行到632行。
3)mmc_initialize看名字就应该是MMC相关的一些基础的初始化,其实就是用来初始化SoC内部的SD/MMC控制器的。函数在uboot/drivers/mmc/mmc.c里。
4)uboot中对硬件的操作(譬如网卡、SD卡•••)都是借用的linux内核中的驱动来实现的,uboot根目录底下有个drivers文件夹,这里面放的全都是从linux内核中移植过来的各种驱动源文件。
5)mmc_initialize是具体硬件架构无关的一个MMC初始化函数,所有的使用了这套架构的代码都掉用这个函数来完成MMC的初始化。mmc_initialize中再调用board_mmc_init和cpu_mmc_init来完成具体的硬件的MMC控制器初始化工作。
6)cpu_mmc_init在uboot/cpu/s5pc11x/cpu.c中,这里面又间接的调用了drivers/mmc/s3c_mmcxxx.c中的驱动代码来初始化硬件MMC控制器。这里面分层很多,分层的思想一定要有,否则完全就糊涂了。
---------------------------------- mmc_initialize函数解析----------------------------
01177: int mmc_initialize(bd_t *bis)
01178: {
01179: struct mmc *mmc;
我们来看一下mmc的这个结构体。这里定义了一个struct mmc类型的结构体指针;这个struct mmc类型的结构体非常重要,我们说的驱动主要就是构建这个结构体;在这个结构体中构建了一些列变量、函数指针等;这些变量记录了mmc的一些信息,函数指针所指向的函数是用来向sd卡中发送命令、或者发送数据、直接操作最底层的特殊功能寄存器;
01180: int err;
01182: INIT_LIST_HEAD(&mmc_devices);//让链表两个指针都指向自己,其实这个是链表的初始化。
内核链表的初始化函数,内核链表在初始化的时候,链表的next指针和prev指针都是指向其自身。mmc_devices链表全局变量,用来记录系统中所有已经注册的SD/iNand设备。所以向系统中插入一个SD卡/iNand设备,则系统驱动就会向mmc_devices链表中插入一个数据结构表示这个设备。
01183: cur_dev_num = 0;
01184:
01185: if (board_mmc_init(bis) < 0)
01186: cpu_mmc_init(bis);
查看代码可知,board_mmc_init(),其实是__def_mmc_init()函数的别名,而__def_mmc_init()函数的返回值固定是-1,所以if判断成立,执行cpu_mmc_init()函数。
这个函数的作用是把mmc与Soc相关的初始化工作完成了;
mmc的初始化化分两个部分
(1)与SoC有关的部分包括:初始化时钟、初始化相关GPIO、初始化与SoC有关的mmc控制器;
(2)与外部sd卡有关的部分:初始化mmc卡中的芯片控制器等;
第一部分的mmc时钟初始化以及gpio初始化我们放在cpu_s5pc11x文件夹;里面是通过宏定义来选择配置哪些通道的时钟(通过MPLL分频得到)。x210选择的是通道0和通道2(SD卡)。setup_hsmmc_cfg_gpio():MMC控制器IO引脚的设置。在在uboot/cpu/s5pc11x/setup_hsmmc.c中。
与SoC有关的控制器放在drivers/mmc文件夹下;smdk_s3c_hsmmc_init():uboot/drivers/mmc/s3c_hsmmc.c中。该函数里面主要调用s3c_hsmmc_initialize(int channel)函数,参数channel表示的是MMC的通道。
s3c_hsmmc_initialize 这个函数是对SoC中mmc控制器的初始化:在这个函数中主要是把我们最早定义的struct mmc中的变量以及函数指针进行了初始化;
而真正的操作寄存器的函数是
s3c_hsmmc_send_command
s3c_hsmmc_set_ios
s3c_hsmmc_init
发送命令发送数据初始化三个函数,这三个函数是最底层的直接操作GPIO、特殊功能寄存器的函数;
而这三个函数以及一些变量被封装在struct mmc结构体中,我们操作系统对mmc设备进行操作的时候,只到封装以后的这个结构体中进行操作即可;
01197: if (mmc)
{
01198: err = mmc_init(mmc);
01199: if (err)
01200: err = mmc_init(mmc);
01201: if (err)
{
01202: printf("Card init fail!\n");
01203: return err;
01204: }
01205: }
01206: printf("%ldMB\n", (mmc->capacity/(1024*1024/(1<<9))));
(1<<9):这里要说下此变量,这是emmc 里CSD[80]所定义的,指明了此emmc的块大小,对于SD卡分区格式来说,只支持512 bety 块大小。如果 mmc->read_bl_len =1024 的话,对于整个分区体系来说是不支持的,会造成分区不完全。所以后续代码需要修改。
01207: return 0;
01208: } ? end mmc_initialize ?
与mmc内部控制器有关的初始化函数为mmc_init,这个函数也在drivers/mmc文件夹下;
上面几点表面:关于mmc的驱动是分离的,时钟、GPIO一部分;SoC内部控制器一部分;SoC外部控制器一部分;这样做的一个好处就是减轻移植代码的大量工作;比如说mmc没有变,而更换了一个SoC,我们只需要更改SoC相关的那一部分代码即可,同样SoC没有变而mmc卡变了,我们则只需要更改mmc卡相关的那部分初始化代码即可;
最后我们看一下mmc_init这个函数这个代码中是调用了struct mmc 中的函数进行了一些时序操作。mmc_init()函数在drivers/mmc/mmc.c中。这个函数的主要作用是进行mmc卡的初始化。mmc_init函数内部就是依次通过向mmc卡发送命令码(CMD0、CMD2那些)来初始化SD卡/iNand内部的控制器,以达到初始化SD卡的目的。
(4) find_mmc_device(0)函数
find_mmc_device(0)在uboot/drivers/mmc/mmc.c中,这个函数其实就是通过mmc设备编号来在系统中查找对应的mmc设备(struct mmc的对象,根据上面分析系统中有2个,编号分别是0和2。函数工作原理就是通过遍mmc_devices链表,去依次寻找系统中注册的mmc设备,然后对比其设备编号和我们当前要查找的设备编号,如果相同则就找到了要找的设备。找到了后调用mmc_init函数来初始化它。
---------------------------------- mmc_initialize函数解析结束----------------------------
Mmc初始化总结:
(1)整个MMC系统初始化分为2大部分:SoC这一端的MMC控制器的初始化,SD卡这一端卡本身的初始化。前一步主要是在cpu_mmc_init函数中完成,后一部分主要是在mmc_init函数中完成。
(2)整个初始化完成后去使用sd卡/iNand时,操作方法和mmc_init函数中初始化SD卡的操作一样的方式。读写sd卡时也是通过总线向SD卡发送命令、读取/写入数据来完成的。
(3)struct mmc结构体是关键。两部分初始化之间用mmc结构体来链接的,初始化完了后对mmc卡的常规读写操作也是通过mmc结构体来链接的。
00775: /* initialize environment */
00776: env_relocate ();
---------------------------------- env_relocate函数解析----------------------------
00234: void env_relocate (void)
00235: {
/*
* We must allocate a buffer for the environment
*/
env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
由于在配置文件中要定义环境变量区域的大小,即#define CFG_ENV_SIZE 0x10000,这里从heap堆里分配出这么大的空间来,并用env_ptr指向它
00236: if (gd->env_valid == 0)
前面在循环体的 env_init() 中有
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;
{
00238: #if defined(CONFIG_GTH) || defined(CFG_ENV_IS_NOWHERE)
00238: /* Environment not changable */
00239: puts ("Using default environment\n\n");
00240: #else
00241: puts ("*** Warning - bad CRC, using default environment\n\n"
00242: show_boot_progress (-60);
00243: #endif
00244: set_default_env();
--------------------
void set_default_env(void)
00218: {
00219: if (sizeof(default_environment) > ENV_SIZE) {
00220: puts ("*** Error - default environment is too large\n\n");
00221: return;
00222: }
00223:
00224: memset(env_ptr, 0, sizeof(env_t));
00225: memcpy(env_ptr->data, default_environment,sizeof(default_environment));
00227: #ifdef CFG_REDUNDAND_ENVIRONMENT
00228: env_ptr->flags = 0xFF;
00229: #endif
00230: env_crc_update ();
00231: gd->env_valid = 1;
00232: }
下面列出uboot中自己定义的默认环境变量。
uchar default_environment[CFG_ENV_SIZE] = {
00065: #ifdef CONFIG_BOOTARGS
#define CONFIG_BOOTARGS "console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3"
00066: "bootargs=" CONFIG_BOOTARGS "\0"
00067: #endif
00068: #ifdef CONFIG_BOOTCOMMAND
#define CONFIG_BOOTCOMMAND "movi read kernel 30008000; movi read rootfs 30B00000 300000; bootm 30008000 30B00000"
00069: "bootcmd=" CONFIG_BOOTCOMMAND "\0"
00070: #endif
00074: #ifdef CONFIG_MTDPARTITION
#define CONFIG_MTDPARTITION "80000 400000 3000000"
00075: "mtdpart=" CONFIG_MTDPARTITION "\0"
00076: #endif
00083: #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >=0)
#define CONFIG_BOOTDELAY 3
00084: "bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0"
00085: #endif
00086: #if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
#define CONFIG_BAUDRATE 115200
00087: "baudrate=" MK_STR(CONFIG_BAUDRATE) "\0"
00088: #endif
00089: #ifdef CONFIG_ETHADDR
#define CONFIG_ETHADDR 00:40:5c:26:0a:5b
#define CONFIG_NETMASK 255.255.0.0
#define CONFIG_IPADDR 192.168.1.88
#define CONFIG_SERVERIP 192.168.1.102
#define CONFIG_GATEWAYIP 192.168.0.1
00093: "ethaddr=" MK_STR(CONFIG_ETHADDR) "\0"
00094: #endif
00104: #ifdef CONFIG_IPADDR
00105: "ipaddr=" MK_STR(CONFIG_IPADDR) "\0"
00106: #endif
00107: #ifdef CONFIG_SERVERIP
00108: "serverip=" MK_STR(CONFIG_SERVERIP) "\0"
00109: #endif
00119: #ifdef CONFIG_GATEWAYIP
00120: "gatewayip=" MK_STR(CONFIG_GATEWAYIP) "\0"
00121: #endif
00122: #ifdef CONFIG_NETMASK
00123: "netmask=" MK_STR(CONFIG_NETMASK) "\0"
00124: #endif
00143: "\0"
00144: };
-------------------
使用默认的环境变量.env_init()中已经看到default_environment[],这是程序初始时uboot代码中自己定义的一份环境变量默认设置,set_default_env()即将default_environment[]数组中的各环境变量项复制到env_ptr所指向的env_t结构里(确切地说是复制到env_t结构的数据区里)。
00245: }
00246: else {
00247: env_relocate_spec ();
00248: }
00249: gd->env_addr = (ulong)&(env_ptr->data);
00254: } ? end env_relocate ?
---------------------------------- env_relocate函数解析结束----------------------------
环境变量重定位总结:
1)env_relocate是环境变量的重定位,完成从SD卡中将环境变量读取到DDR中的任务。
2)环境变量到底从哪里来?SD卡中有一些(8个)独立的扇区作为环境变量存储区域的。但是我们烧录/部署系统时,我们只是烧录了uboot分区、kernel分区和rootfs分区,根本不曾烧录env分区。所以当我们烧录完系统第一次启动时ENV分区是空的,本次启动uboot尝试去SD卡的ENV分区读取环境变量时失败(读取回来后进行CRC校验时失败),我们uboot选择从uboot内部代码中设置的一套默认的环境变量出发来使用(这就是默认环境变量);这套默认的环境变量在本次运行时会被读取到DDR中的环境变量中,然后被写入(也可能是你saveenv时写入,也可能是uboot设计了第一次读取默认环境变量后就写入)SD卡的ENV分区。然后下次再次开机时uboot就会从SD卡的ENV分区读取环境变量到DDR中,这次读取就不会失败了。
第一次运行uboot时板子会打印如下信息
*** Warning - bad CRC or NAND, using default environment
3)真正的从SD卡到DDR中重定位ENV的代码是在env_relocate_spec内部的movi_read_env完成的。
小结;env_relocate 所做的事情有3件
1. 从heap中分配一段空间,用于env_t结构
2. 找到环境变量(或从内存中找或从nand中找),填充env_t结构
3. 将gd->env_addr指向env_ptr->data, 这个也就是这里的relocate所在吧。
/* IP Address */
00788: gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
将环境变量弄完之后,紧接着就是从环境变量的相应项中获取信息,环境变量是用户与u-boot的一个交互方式,有了它之后,用户即可通过修改环境变量来修改板子的一些信息配置。这里的ip地址和网卡地址即是其中的一个典型例子。来看上面的程序:获取ip地址,注意gd->bd->bi_ip_addr是ungisned long 类型,而ip地址是类似于"192.168.1.111"的字符串。往下跟踪:
1 IPaddr_t getenv_IPaddr (char *var)
2 {
3 return (string_to_ip(getenv(var)));
4 }
getenv("ipaddr") 即在环境变量中找到ipaddr这一项对应的字符串,假设这里为"192.168.1.111"将"192.168.1.111"传入string_to_ip。 IPaddr_t 类型是unsigned long 的一个typedef
5 IPaddr_t string_to_ip(char *s)
6 {
7 IPaddr_t addr;
8 char *e;
9 int i;
10 if (s == NULL)
11 return(0);
12 for (addr=0, i=0; i<4; ++i) {
13 ulong val = s ? simple_strtoul(s, &e, 10) : 0;
14 addr <<= 8;
15 addr |= (val & 0xFF);
16 if (s) {
17 s = (*e) ? e+1 : e;
18 }
19 }
20 return (htonl(addr));
21 }
12~19行 192.168.1.111 分为四个段,也就是要做4次 simple_strtoul()转换成10进制的整型
第次转换后的值赋给val。addr是unsigned long型,32位的,将其分为4段,每8位存储ip地址中的一个段,比如这里,最后addr = (((((192 << 8) | 168) << 8) | 1) << 8 ) | 111 = 0xc0a8016f
第20行,主机字节顺序转换为网络字节顺序返回
若CPU为小端模式时,addr如下存储
31 24 23 16 15 8 7 0
+--------------+---------------+----------------+-----------------+
| 192 = 0xc0 | 168 = 0xa8 | 1 = 0x01 | 111 = 0x6f |
+--------------+---------------+----------------+-----------------+
3 2 1 0
若CPU为大端模式时,addr如下存储
31 24 23 16 15 8 7 0
+--------------+---------------+----------------+-----------------+
| 111 = 0x6f | 1 = 0x01 | 168 = 0xa8 | 192 = 0xc0 |
+--------------+---------------+----------------+-----------------+
3 2 1 0
当与另一台计算机通信时,通常不知道对方存储数据时是先存放最高位字节 (MSB)还是最低位字节 (LSB)恰恰网络字节顺序跟大端模式时相同,htonl函数就是将主机字节顺序转为网络字节顺序,在最高位字节(MSB)-最前 的系统上,这些函数什么都不做。在 最低位字节(LSB)-最前的系统上它们将值转换为正确的顺序。最后将值返回给了gd->bd->bi_ip_addr, 所以其值应该是 0x6f01800a
00790: /* MAC Address */
00791: {
00792: int i;
00793: ulong reg;
00794: char *s, *e;
00795: char tmp[64];
00796:
00797: i = getenv_r ("ethaddr", tmp, sizeof (tmp));
00798: s = (i > 0) ? tmp : NULL;
00799:
00800: for (reg = 0; reg < 6; ++reg) {
00801: gd->bd->bi_enetaddr[reg] = s ?simple_strtoul (s, &e, 16) : 0;
00802: if (s)
00803: s = (*e) ? e + 1 : e;
00804: }
网卡地址以十六进制的形式存于gd->bd->bi_enetaddr[]数组中