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卡中发送命令、或者发送数据、直接操作最底层的特殊功能寄存器;

Uboot31之start.S第二阶段part3
01180:     int err;
01182:     INIT_LIST_HEAD(&mmc_devices);//
让链表
两个指针都指向自己,其实这个是链表的初始化。

内核链表的初始化函数,内核链表在初始化的时候,链表的next指针和prev指针都是指向其自身。mmc_devices链表全局变量,用来记录系统中所有已经注册的SD/iNand设备。所以向系统中插入一个SD/iNand设备,则系统驱动就会向mmc_devices链表中插入一个数据结构表示这个设备。

Uboot31之start.S第二阶段part3
01183:     cur_dev_num = 0;
01184:
01185:     if (board_mmc_init(bis) < 0)
01186:     cpu_mmc_init(bis);

Uboot31之start.S第二阶段part3

查看代码可知,board_mmc_init(),其实是__def_mmc_init()函数的别名,而__def_mmc_init()函数的返回值固定是-1,所以if判断成立,执行cpu_mmc_init()函数。

Uboot31之start.S第二阶段part3

这个函数的作用是把mmcSoc相关的初始化工作完成了;

mmc的初始化化分两个部分

  (1)与SoC有关的部分包括:初始化时钟、初始化相关GPIO、初始化与SoC有关的mmc控制器;

  (2)与外部sd卡有关的部分:初始化mmc卡中的芯片控制器等;

第一部分的mmc时钟初始化以及gpio初始化我们放在cpu_s5pc11x文件夹;里面是通过宏定义来选择配置哪些通道的时钟(通过MPLL分频得到)。x210选择的是通道0和通道2SD卡)。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 这个函数是对SoCmmc控制器的初始化:在这个函数中主要是把我们最早定义的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卡发送命令码(CMD0CMD2那些)来初始化SD/iNand内部的控制器,以达到初始化SD卡的目的。

4 find_mmc_device(0)函数

find_mmc_device(0)uboot/drivers/mmc/mmc.c中,这个函数其实就是通过mmc设备编号来在系统中查找对应的mmc设备(struct mmc的对象,根据上面分析系统中有2个,编号分别是02。函数工作原理就是通过遍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_addrungisned 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 (== 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+: 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[]数组中