s3c6410 uboot代码分析

来源:互联网 发布:plc编程软件通用版 编辑:程序博客网 时间:2024/04/29 16:07

以下用以记录uboot代码的分析过程,目标是s3c6410,如有错误,欢迎指正。

强调,内容与三星原厂提供的uboot-1.1.6有更改的地方,因为外接外设的区别,特别是nand_flash、外接网卡芯片和LCD芯片。以下纯代码情景分析,请结合uboot的功能结构图和内存分布图查看代码,这样会更加容易理解。

s3c-u-boot-1.1.6源代码可以在三星下面的网站获得,但前提是你有官方的email。

还是百度google搜一下吧,当然我这也是有的哦。

http://www.samsung.com/global/business/semiconductor/productInfo.do?fmly_id=835&partnum=S3C6410

 

                   功能结构图(上图)                                                                   uboot内存分布图(上图)

1.start.s代码分析(第一阶段)

 

--------------------

.globl _start
_start: b reset
 ldr pc,_undefined_instruction
 ldr pc,_software_interrupt
 ldr pc, _prefetch_abort
 ldr pc, _data_abort
 ldr pc, _not_used
 ldr pc, _irq
 ldr pc, _fiq

_undefined_instruction:
 .word undefined_instruction
_software_interrupt:
 .word software_interrupt
_prefetch_abort:
 .word prefetch_abort
_data_abort:
 .word data_abort
_not_used:
 .word not_used
_irq:
 .word irq
_fiq:
 .word fiq
_pad:
 .word 0x12345678
.global _end_vect
_end_vect:

 .balignl16,0xdeadbeef

--------------------

 

 

--------------------

_undefined_instruction:
 .word undefined_instruction
_software_interrupt:
 .word software_interrupt
_prefetch_abort:
 .word prefetch_abort
_data_abort:
 .word data_abort
_not_used:
 .word not_used
_irq:
 .word irq
_fiq:
 .word fiq
_pad:
 .word 0x12345678
.global _end_vect
_end_vect:

 .balignl16,0xdeadbeef

--------------------

 

 

--------------------

_TEXT_BASE:
 .word TEXT_BASE

--------------------

 

 

--------------------

_TEXT_PHY_BASE:
 .word CFG_PHY_UBOOT_BASE

--------------------

 

 

--------------------

.globl _armboot_start
_armboot_start:
 .word _start

--------------------

 

 

--------------------

.globl _bss_start
_bss_start:
 .word __bss_start

.globl _bss_end
_bss_end:
 .word _end

--------------------

 

 

--------------------

reset:

 mrs r0,cpsr
 bic r0,r0,#0x1f
 orr r0,r0,#0xd3
 msr cpsr,r0

--------------------

 

 

--------------------

cpu_init_crit:

--------------------

 

--------------------
 mov r0, #0
 mcr p15, 0, r0, c7, c7,0 
 mcr p15, 0, r0, c8, c7,0 

--------------------

 

--------------------
 mrc p15, 0, r0, c1, c0, 0
 bic r0, r0,#0x00002300 @ clear bits 13, 9:8 (--V- --RS)
 bic r0, r0,#0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
 orr r0, r0,#0x00000002 @ set bit 2 (A) Align
 orr r0, r0,#0x00001000 @ set bit 12 (I) I-Cache
 mcr p15, 0, r0, c1, c0,0

--------------------

 


--------------------

 
 ldr r0, =0x70000000
 orr r0, r0, #0x13
 mcr p15,0,r0,c15,c2,4      @ 256M(0x70000000-0x7fffffff)

--------------------

 

 

--------------------

bl lowlevel_init

--------------------

 

 

--------------------

 ldr r0,=0xff000fff
 bic r1, pc,r0  
 ldr r2,_TEXT_PHY_BASE  
 bic r2, r2,r0  
 cmp    r1,r2                 
 beq    after_copy  

--------------------

 

 

--------------------

#ifdef CONFIG_BOOT_NAND
 mov r0, #0x1000
 bl copy_from_nand
#endif

--------------------

 

 

--------------------

#ifdef CONFIG_BOOT_MOVINAND
 ldr sp, _TEXT_PHY_BASE
 bl movi_bl2_copy
 after_copy
#endif

--------------------

 

 

after_copy:

 

 

--------------------

#ifdef CONFIG_ENABLE_MMU
enable_mmu:
 
 ldr r5, =0x0000ffff
 mcr p15, 0, r5, c3, c0,0  @ load domain accessregister

 
 ldr r0, _mmu_table_base
 ldr r1,=CFG_PHY_UBOOT_BASE
 ldr r2, =0xfff00000
 bic r0, r0, r2
 orr r1, r0, r1
 mcr p15, 0, r1, c2, c0,0

 
mmu_on:
 mrc p15, 0, r0, c1, c0, 0
 orr r0, r0,#1   
 mcr p15, 0, r0, c1, c0, 0
 nop
 nop
 nop
 nop
#endif

--------------------

 

 

--------------------

skip_hw_init:
 
stack_setup:
#ifdef CONFIG_MEMORY_UPPER_CODE
 ldr sp, =(CFG_UBOOT_BASE +CFG_UBOOT_SIZE - 0xc)
#else
 ldr r0,_TEXT_BASE  
 sub r0, r0,#CFG_MALLOC_LEN 
 sub r0, r0,#CFG_GBL_DATA_SIZE
#ifdef CONFIG_USE_IRQ
 sub r0, r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
 sub sp, r0,#12  

#endif

--------------------

 

 

--------------------

clear_bss:
 ldr r0,_bss_start  
 ldr r1,_bss_end  
 mov  r2,#0x00000000  

clbss_l:
 str r2,[r0]  
 add r0, r0, #4
 cmp r0, r1
 ble clbss_l

--------------------

 

 

--------------------

 ldr pc,_start_armboot

_start_armboot:
 .word start_armboot

--------------------

 

2.第二阶段代码分析(代码在lib_arm目录下的board.c里面,start_armboot函数)

 

1)初始化CPU及外围硬件


 init_fnc_t**init_fnc_ptr;
 char *s;
#ifndef CFG_NO_FLASH
 ulong size;
#endif

#if defined(CONFIG_VFD) ||defined(CONFIG_LCD)
 unsigned long addr;
#endif

#ifdefined(CONFIG_BOOT_MOVINAND)
 uint *magic = (uint *) (PHYS_SDRAM_1);
#endif

 
#ifdef CONFIG_MEMORY_UPPER_CODE
 ulong gd_base;

 gd_base =CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE -sizeof(gd_t);
#ifdef CONFIG_USE_IRQ
 gd_base -=(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
#endif
 gd = (gd_t*)gd_base;
#else
 gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN -sizeof(gd_t));
#endif

 
 __asm__ __volatile__("": : :"memory");

 memset ((void*)gd,0, sizeof (gd_t));
 gd->bd = (bd_t*)((char*)gd -sizeof(bd_t));
 memset (gd->bd, 0, sizeof(bd_t));

 monitor_flash_len =_bss_start - _armboot_start;

 for (init_fnc_ptr =init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
  if ((*init_fnc_ptr)() != 0){
   hang();
  }
 }

解释:定义二级指针init_fnc_ptr指向一个存放函数指针的数组,init_fnc_ptr是typedef int(init_fnc_t)(void)类型,即函数类型,init_fnc_ptr可以指向一个没有参数,返回值为int型的函数指针的地址(很绕哦,呵呵),我们看上面代码最后的for循环init_fnc_ptr =init_sequence,if中会使用(*init_fnc_ptr)()方式调用init_sequence中的函数(函数名可以看为一个地址),如果返回值不是0,则执行hang报错。

       因为我们定义了CONFIG_MEMORY_UPPER_CODE宏,所以gd=(gd_t*)gd_base,由gd_base的值我们知道,malloc区域、stack区域、bdinfo数据在内存的位置是放在upperof uboot。

      __asm__ __volatile__("": ::"memory");这条是内嵌汇编,请查看另一篇介绍内嵌汇编的博文。

      gd->bd指针指向数据类型为bd_t的结构体,bd_t结构体记录开发板的参数,例如串口波特率、ip地址、机器类型、启动参数、环境变量位置等。

      下面分析for循环执行的函数:

       cpu_init:因为我们没有定义CONFIG_USE_IRQ,所以这个函数直接返回0

       board_init:


函数内首先执行dm9000_pre_init()函数,因为我们把DM9000AEP网卡映射到内存的Xm0CSn[1]上,所以我们要设置访问CSn[1]的方式,SROM_BW_REG &= ~(0xf<< 4);SROM_BW_REG |=(1<<7) |(1<<6) |(1<<4);两条代码的含义为设置0x70000000控制器的CSn[1]访问方式为nBEenable、wait enable、16位数据总线访问模式,SROM_BC1_REG =((DM9000_Tacs<<28)+(DM9000_Tcos<<24)+(DM9000_Tacc<<16)+(DM9000_Tcoh<<12)+(DM9000_Tah<<8)+(DM9000_Tacp<<4)+(DM9000_PMC));这句话的意思是设置访问的时序,s3c6410datasheet中已经给出了时序代码,欢迎查看哦。因为我的zc6410开发箱接了4.3的TFTLCD,所以在代码中增加了对LCD的配置工作,代码如下:

 writel(readl(MIFPCON)& (~(1 << 3)),MIFPCON);

 writel(readl(MIFPCON)& (~(3 << 0)) | 0x1,SPCON);

 writel(0xaaaaaaaa,GPICON);
 writel(0xaaaaaa, GPJCON);

以上四行代码分别对MIFPCON、SPCON、GPICON、GPJCON四个寄存器赋值。为什么赋值?查看s3c6410datasheet手册14.5.1节,给出了DisplayController的引脚配置,MIFPCON的[3]位设置为0(normalmode)instead of “1”(by-pass mode),SPCON[1:0]位的值设置为“01”(useRGB I/FStyle)or “00” to use Host I/FStyle,我们设置的是01。GPICON、GPJCON赋值的原因请看下面图:



第一张图是LCD控制器接口连接原理图,后面的是图是芯片手册,通过两个图我们就知道为什么要写后面两行代码了吧。


 

 好了,73-74行一个是记录机器类型,一个是指定向内核传参的地址。

      interrupt_init:

                           

 184行:值0x0101的含义是设置Prescaler0、1、2、3的值,请看下图真相(datasheet)


env_init:如下图,因为没有定义ENV_IS_EMBEDDED,所有只是执行了142-143,把环境变量的首地址赋值给gd->env_addr。


init_baudrate:

                 

139行使用getenv_r函数在default_environment里找baudrate关键字,找到后把“=”号后面的值赋值给gd->baudrate,然后

       再放到gd->bd->bi_baudrate里面。simple_strtoul是uboot实现的字符串转UL类型。

      serial_init:什么都没做,保持默认的8位数据、无奇偶校验、1 停止位、无开始位。

      console_init_f:gd->have_console = 1就这一句话

      display_banner:串口打印uboot信息,就是uboot启动的时候我们看到的信息,这里使用的是printf,但是我们追进去后,关注的函数

      应该是serial_putc,它是真实向串口输出一个字符的函数,这个函数会递归调用,应该说自己调用自己,遇到\n结束。

      print_cpuinfo:打印CPU信息,CPU型号和速度 CPU:...

      checkboard:打印开发板信息 BOARD:...


dram_init:

 记录dram的起始地址,0x50000000,size为256M

display_dram_config:因为没有定义DEBUG,所以打印DRAM:256M

 

2)配置malloc空间

     (因为CONFIG_LCD、CONFIG_VFD没有定义,所以跳过这一部分)


我们定义了UPPER_CODE,所以执行第一个mem_malloc_init。这个函数的作用是记录堆栈空间的起始地址、结束地址、当前地址。

 

3)启动设备初始化(SD、NAND、ONENAND)

      系统一开始尝试从SD卡启动,因为本篇介绍的是NANDFLASH启动方式,所以SD卡部分暂不分析,会单独开辟章节介绍s3c6410

      的SD卡启动方式(包括windows下SDflasher应用程序的编写、SD卡硬件电路分析、SD寄存器操作和启动流程)。

      下面分析nand启动方式,我们在自己的头文件里定义了CONFIG_COMMANDS和CFG_CMD_NAND,所以会执行nand_init函数

分析代码中的368行nand_init函数,我们知道在uboot启动起来之后,会显示NAND: 512M(你nandflash的大小),所以不难想象,

      nand_init会向终端打印NAND大小的信息,以下是nand_init的实现:


 

     nand_init函数的实现体在drivers/nand/nand.c中,nand_init函数不仅会打印出nandflash的大小,还会初始化描述nand的结构体

      nand_info以及代表“nand”的设备结构体nand_chip,这两个结构体前者是mtd层对设备的抽象和对块设备的接口统一,后者是

      设备的实体,所有对设备的读写控制操作都最终通过这个结构体完成,下面我们开始分析nand_init函数:

       64~69行:从外表看,最后会执行size+= nand_info[i].size,由此最起码可以猜测到这个函数会计算出nand的大小。那么是怎样计算出

      来的呢,我们需要看nand_init_chip函数,注意,在进入这个函数之前,先看一下传入的三个参数,前两个参数我们已经介绍过,第三

      个参数是nand的数据寄存器,访问地址为0x70200010。nand_init_chip函数会根据我们传入的参数,去查找对应的nand设备,并初

      始化一些功能接口为以后对nand操作做准备,下面看图:

     47行:mtd->priv实现了mtd中间层对底层nand设备的接口,我们以后在访问nand硬件时,通过mtd的priv成员可以快速找到我们的

      nand设备。

      49行:nand成员中保存了读写nand数据的数据寄存器基地址,我们通过读写base_addr中的数据,实现对nand中数据的读和写,

      后面的__iomem是个宏定义,这样定义的#define__iomem,只定义并没有给值,所以没有任何功能意义,但是对于我们在看代码

      的时候,很容易能判断出后面的变量是IO地址空间的寄存器地址。

      50行:是对nand设备的初始化操作,我们进入函数体

820~823行:判断是否是从nand_flash启动。看s3c6410寄存器就会明白:

 

     820行:NFCONF定义的宏,其实是取0x70200000地址里面的内容,那么如果我们把OM跳线设置为nand启动,这个[31]

      位的值就会为1,这样的话NFCONF &0x80000000的值就是1了,因而boot_nand的值为1;

      825行:清除0x70200004的[16]位,关闭软件锁存,如果此位设置为1,则NFSBLK(0x70200020)到NFEBLK

     (0x70200024)-1被开启,除了这部分区域,写或擦除命令是无效的,只有读命令是有效的(NFSBLK和NFEBLK)为可编程

      可编程开始和结束块地址寄存器;

      826~827行:我们在进入这个函数的时候就做过了;

      828行:nand->cmd_ctrl =s3c_nand_hwcontrol,这个函数是用于向nand硬件发送命令的,比如发送00h,代表的是读命令。

      829行:nand->dev_ready =s3c_nand_device_ready,是用于判断nand芯片处于忙/可读状态的。s3c_nand_device_ready

      用一个循环去判断NFSTAT(0x70000028)的最低位(RnB输入引脚状态),如果是1表示现在nand可读,0代表正在忙不可读

      830行:bbt(bad blocktable)坏块表,因为我们没用到,所以s3c_nand_scan_bbt函数会直接返回;

      我们没有定义宏CFG_NAND_FLASH_BBT,所以nand->options  |=NAND_SKIP_BBTSCAN,后面宏的含义代表在初始化期间

      将跳过坏块扫描;

      839行:CFG_NAND_HWECC需要我们自己定义,含义代表使用错误纠错码;

      840行:代表使用NAND_FLASH模块内部的ECC模块产生纠错码;

      841行:nand->ecc.hwctl  =s3c_nand_enable_hwecc,设置对ECC的控制,这个函数应该在产生ECC编码前被调用。这个函数的

      功能为确认SLC FLASH或是MLC FLASH,SLC代表single layer cell,MCL为multi-levelcell,有关SLC和MLC的区别在容量、可

      读写总次数、读写速度上,SLC的读写速度要快于MLC,但MCL的容量要比SLC大很多,因为1cell可以容纳4bits,有兴趣可以查阅

      相关手册。此函数还有一个功能为:1.初始化主区ECC解码器/编码器(向0x70200004的[5]位写1)2.开启主区ECC

    (向0x70200004的[7]位写0);

      842行:nand->ecc.calculate =s3c_nand_calculate_ecc,用于存储产生的校验纠错码;

      843行:nand->ecc.correct =s3c_nand_correct_data,用生成的ECC码检测是否有错误,没有则返回,具体内容看函数说明就好;

      845~849行:向nand发送4条命令,849行为等待设备准备好;

      851~852行:将读取到nand芯片的厂商信息和芯片ID编号;

      854~859行:nand_flash_ids结构体保存了很多公司生产的nand芯片信息和编号,for循环将通过ID找到和我们板子匹配的NAND芯片;

      再往下虽然代码挺多的,但不用担心,只会执行877行的nand->ecc.layout =&s3c_nand_oob_16,这是在定义oob信息;

      以下是nand芯片可以处理的命令以及命令的含义(下面是三星K9F4G08U0A-PCB0芯片的命令集)


分析完board_nand_init函数后,我们继续看nand_init_chip第52行,nand_scan函数:

 

这个函数的主要功能就是2768行的nand_scan_ident函数,功能是填充mtd结构体,配置对nand的接口。这样下次在访问设备时,可

      通过mtd层找到对应的底层设备,我们看下nand_scan_ident函数:


 

到此,我们不再往下追函数了,

      2501行在设置mtd设备层的接口函数,

      2504行nand_get_flash_type函数代码比较多,主要的功能还是在获得nand芯片的厂商信息和ID,并判断是否支持,如果支持

      为这个nand设备和mtd填充一些功能接口函数,

      我们再来看nand_scan_ident函数的后面代码,2512行是for循环,maxchips的值是nand_scan函数传递进来的1,所以我们最后

      看到的i值为1,在2526行chip->numchips=1,mtd->size=512(我的nandflash型号是K9F4G08U0A-PCB0,大小512M,

      我们在board_nand_init函数中已经在结构体nand_flash_ids中找到我们的nand型号,chipsize的值为512)。

      nand初始化分析完毕。。。


 

4)环境变量初始化

      环境变量初始化,即start_armboot函数第379行的env_relocate()函数,这个函数实现体在env_common.c中,我们看真相:

 

这个函数的功能其实就是让env_ptr指向存放环境变量的首地址,并且填充env_ptr->data成员变量。

      208和212的两个宏我们没有定义,所以直接看223行,223行malloc一个CFG_ENV_SIZE的空间用于存放环境变量

      230行是在环境变量被迁移到内存后,我们可以使用内存中的环境变量

      由于gd->env_valid我们前面没有赋值,所以232行if会执行,打印236行的内容

      240~244行是判断默认的环境变量是否大于我们限定的环境变量大小

      247行是把uboot代码中已经存在的default_environment指针所指向的环境变量拷贝到env_ptr->data中

      253行是crc32的校验,会对环境变量的内容使用CRC32校验表做每8字节的DO1校验,校验表定义在crc32.c的77行

      259行是把内存中可操作的环境变量记录在全局变量中


 

5)网络初始化

      网络初始化分为IP地址初始化和MAC地址初始化,看下图:


387行:IP地址初始化,getenv_IPaddr会返回IPaddr_t(unsignedlong)类型的IP地址赋值给gd->bd->bi_ip_addr全局变量。

进入getenv_IPaddr函数会执行return(string_to_ip(getenv(var))),我们先看getenv(var):

 

497行:是给软件狗准备的,我的ZC6410板子上有硬件狗,所以这个WATCHDOG_RESET其实就是一个空的macro。

      499~510行:会查找环境变量中的name,name是传递进来的ipaddr,进入查找旅程env_get_char函数,如果我们没有忘记的话

      我们在env_relocate ()函数的230行执行过env_get_char =env_get_char_memory,所以会执行env_get_char_memory函数,

     这个函数在env_common.c中实现:


 

186行的值同样在env_relocate正确执行过后,被赋值为1,返回gd->env_addr+index,gd->env_addr指向default_environment

       因为default_environment是个指针数组,index代表数组的索引,即环境变量名得索引,看default_environment数组就能够明白

      如果你已经自己看过了这两段for循环,你可能知道外层for循环在遍历default_environment数组,内层判断是否超出环境变量的

      大小。后面外层循环会执行507行的envmatch函数,这个函数是真正匹配getenv传递进去的ipaddr参数的,里面使用while循环

      去找环境变量中的‘=’号前面的是否有ipaddr字符串,有则返回ipaddr在环境变量中的索引,比如ipaddr=“。。。”\0这个字符串

      被放在default_environment[1]中,则返回这个索引值1。

      getenv的509行返回这个索引所在的环境环境变量的地址,即return ( ((uchar*)(gd->env_addr + index)) )。

      我们返回getenv_IPaddr函数,执行return(string_to_ip(getenv(var))),把上面返回的地址所在的字符串(IP)转化为int类型的数据

      保存在gd->bd->bi_ip_addr中。

 

      390~403:MAC地址初始化

      和上面的IP初始化一样,做着换汤不换药的事情

 

6)设备列表初始化

      start_armboot函数的417行:devices_init()函数。函数完成的主要功能是把设备放入list列表。这种方法是被推荐的,因为下次在查找

      设备的时候会通过这个列表来查询设备,方便统一管理。这个设备列表的接口的个二级指针变量devlist(list_t类型->二级指针)。

      下面不贴代码了,因为不难理解。这个函数的169~171行for循环在把标准输入、标准输出、标准错误的名字赋值到stdio_names全局

      数组变量中。176行比较关键,是为uboot中的设备建立了list列表,以后增加设备时,可以插入这个list中通过devlist指针。

      再往下面是很多宏定义,但是我们都没有定义,只有一个函数会执行,就是drv_system_init()。这个函数注册了一个设备,设备名是

      serial,填充设备的device_t结构体,指明设备的输入和输出设备等,最后使用device_register(&dev),把自己注册到devlist列表中。

 

7)配置功能函数表

      start_armboot函数的423行执行jumptable_init(),为gd-jt全局变量定义了一些独立的函数接口,方便调用。

 

8)控制台初始化

       start_armboot函数的437行执行console_init_r(),设定一个控制台。我们知道,我们的uboot也是有printf之类输出、有命令可以

      让我们输入的,所以我们需要定义出一个控制台设备,让我们的输入和输出都指向它,下面看函数:

 

491行:从devlist设备列表中获得当前devlist中所列举的最大设备个数,我们前面已经对这个接口做过赋值,并配置到devlist中,且

      当初设置的值为0,代表第一个设备,那么这里items的值就应该是0了;

      493和500行的宏我没有定义,不执行;

      507~519行:查找输入输出设备,

      511行:定义一个能够代表控制台设备的结构体,并且从devlist中为自己获取一个列表位置,ListGetPtrToItem函数如下图所示:


 

上面if和elseif都没有得到执行,直接执行最后的return

return的值由最下面的define决定,我们看到list->itemList数组其实就是devlist的设备挂载点

我们将会把我们的设备列表都列举到这个数组中

但是需要注意一个问题,list->itemList定义是list->itemlist[1],只是分配了一个字段,如何能存下多个设备列表呢

gnu编译器对标准C做了扩展,支持动态大小的数组定义,所以我想你应该使用gnu编译器来编译uboot

      513和516行把这个获得的列表地址赋值给了inputdev和outputdev两个设备指针;

       522和528行是真正的给我们的标准输入和标准输入设备填充结构的函数,会把dev地址再赋值给全局结构体stdio_devices

      这个结构体是最终记录标准输入输出结构体的;

      534行:CFG_CONSOLE_INFO_QUIET宏没有定义,所以会执行下面的代码,我们在启动uboot之后,会打印

In:serial

Out:serial

Error:serial

      就是这段代码起的效果;

      559行:使用setenv把标准输入输出和标准错误设备变量设置到环境变量中。

 

9)开中断异常向量表

      start_armboot函数的433行执行enable_interrupts,内容如下:


 

以上代码是内嵌汇编,这个我不解释,因为我前面有文章专门介绍__asm____volatile__用法的,作用就是清除cpsr中的

      [7]位。这位的含义是中断使能/禁止位,拥有中断最高优先级,这里代码用于开启中断。

 

10)网卡芯片初始化

      我的板子上使用的是DM9000AEP网卡,不是CS8900,所以代码有所改动,增加了对DM9000网卡的初始化支持,这里我调用了

      DM9000里的一个函数eth_set_mac,用于初始化网卡芯片,代码如下:


 

注释写的很好,最起码我们知道这段代码在干什么,首先打印出全局变量gd里面存放的网卡设备的MAC地址,这个值是我们在

      板子头文件里面定义的CONFIG_ETHADDR获取的;

      293行:在填充MAC地址的值到网卡芯片专门用于保存MAC地址的寄存器当中,所以这里我们定义了DM9000_PAR,它的值是0x10

      用于保存MAC地址,为什么是0x10这个地址保存MAC地址,我们当然不知道,得去找DM9000网卡芯片厂商的datasheet,如下:


 

看倒数第二行,告诉你DM9000的物理网卡地址寄存器叫PAR,地址是10h-15h6个字节用于保存MAC地址

       那么我们下面就把我们的物理网卡地址写到这个寄存器里面来吧,使用294行的函数(注意我们已经开启MMU了,不能

      不能直接访问内存和IO内存了)。DM9000_iow的函数实现体如下图所示:


 

DM9000_outb是个宏,向IO内存中写入值,例如DM9000_outb(reg,DM9000_IO)的含义是把reg的值写入DM9000_IO这个寄存器

那么DM9000_IO和DM9000_DATA的值是多少呢?即你现在需要具备以下的分析过程:

我们想让DM9000网卡能用,就必须接到板子上

不是随便接的,因为你需要能够控制DM9000的寄存器才能通过软件控制它

那么你又要想如何才能操作到DM9000网卡的寄存器?

操作DM9000网卡寄存器就有对它的读和写,必须通过总线访问到DM9000网卡寄存器的数据地址

访问数据地址必须通过CPU的内存映射(CPU访问外设的地址空间)

所以,最终我们得让CPU和DM9000连接,把DM9000接到CPU能访问的地址空间上

我们如何知道连接上了呢?得看DM9000网卡的连接原理图,如下:


 

看上面的图,有一个叫NET_ncs的引脚,它就是访问片选引脚,它接到了CPU的CS1上,这张图写的是CS#

CS#代表什么意思?幸好我是知道接到CS1上的,这个‘#’应该是硬件工程师的失误了吧

不管怎么样,你要知道接到s3c6410cpu的CS1引脚上是什么意思,代表cpu可以通过这个地址来访问DM9000哦

接下来我们看一下,CS1的值是多少,即什么地址可以访问到DM9000,查看s3c6410datasheet:


 

看图中红线部分,原来接NET_nCS接到了s3c6410的SRAM1上,即CS1上,地址是0x18000000

好了,回到DM9000_iow函数继续分析,我们定义DM9000_IO和DM9000_DATA的值,如下图所示:


 

不难看出,DM9000_IO的值是0x18000300

?,为什么不是0x18000000呢?因为DM9000网卡采用pakeage机制,访问DM9000的内部寄存器被映射到了

CS1+0x300偏移地址处,DM9000_DATA寄存器的地址是DM9000_IO的值+4(都是32位)

DM9000_outb(reg, DM9000_IO);
 DM9000_outb(value, DM9000_DATA);

上面两行代码应该知道什么意思了吧,第一行是指定往DM9000网卡的哪一个地址写,reg是0x10,即是用于存储
MAC地址的DM9000内部PAR寄存器,那么DM9000_outb(reg, DM9000_IO)相当于说要向

0x18000310地址写入一个字节,DM9000_outb(value,DM9000_DATA)相当于说0x18000310被写入的内容是value

好了,通过eth_set_mac函数的293~294行,我们把MAC地址写入了DM9000的PAR寄存器中

      295~296行:向广播地址寄存器中赋值全1;

      299~301行:是用于调试的,看能不能从刚才的寄存器中读出我们写入的MAC地址。

       网卡分析比较复杂,关键是你要找到IO访问基地址,还要知道DM9000的中断线,因为我们读网卡数据,系统是通过中断的方式

      来获得的(linux系统,其他的不能太确定),还有DM9000网卡是数据和地址复用的,通过NET_CMD引脚来控制。我的DM9000

      中断引脚接的是EINT7,看上面的DM9000网卡的连接原理图就知道了。

 

11)一些后续初始化

      看下面的真相:


好吧,这个后续初始化工作我们好像都没有定义,等等,除了BOARD_LATE_INIT。不过board_late_init函数里面我什么都没写,

 

2)main_loop详解

      这个挺重要的,因为它是uboot和我们用户交互的接口。让我们一起进去分析吧(代码有点小长哦)


 

297行:CFG_HUSH_PARSER没有定义,所以会定义全局变量lastcommand[CFG_CBSIZE],CFG_CBSIZE的值为256,用于记录

      console buffer size;

      304行:我们定义了CONFIG_BOOTDELAY在头文件中,默认是3,我改成了1。后面会定义指针s和int型bootdelay。用于uboot判断

      无任何按键按下,就执行CONFIG_BOOTCOMMAND宏所对应的命令,这个命令通常用于加载启动操作系统;

      308行:CONFIG_PREBOOT宏没有定义,不管;

      311行:CONFIG_BOOTCOUNT_LIMIT没有定义,不管;

      318行:没有定义CONFIG_VFD,所以也不管,但我后面会加上这个宏,因为我想在启动的时候在我的LCD上显示我的LOGO;

      329行:CONFIG_BOOTCOUNT_LIMIT没有定义,不管;

      339行:CONFIG_MODEM_SUPPORT没有定义,不管;

      350行:CONFIG_VERSION_VARIABLE没有定义,不管;

      。。。再没有定义的就不贴了,直接找我们定义的看;

      362行:其实CONFIG_AUTO_COMPLETE宏我也没有定义,但是这个东西挺好用的,可以帮助我们自动补齐命令

      390行:我们定义了CONFIG_BOOTDELAY,所以391行s的值将保存bootdelay环境变量的值得地址,392行把地址的值给bootdelay

      408行:s保存bootcmd等于号后面字符串的地址;

      412行:if判断为真,执行418行的run_command函数,这个函数很重要,是查找解析命令并执行的,所以我们还是看图吧:

 

run_command函数的1265行,设置ctrlc_was_pressed =0,,这个变量标记ctrl+c没有被按下

1267行判断没有输入命令则返回-1

1271行判断我们输入的命令字符串是否大于我们定义的控制台buffer

1276行把命令拷贝到cmdbuf数组中

1285行是一个while循环,str指针指向cmdbuf

1291~1312用于查找“;”号,因为我们的命令可以使用“;”号来执行多条命令,就好像

在shell环境里你输入ls;cd,会先执行ls,然后又会执行cd

1324行process_macros是判断如果一条命令里面有两条命令,把第二条命令分离出来放到finaltoken数组中

1327行:parse_line函数解析字符串,并把解析出来的命令和参数放到argv中,parse_line是字符串解析,不贴图

1333行:是到.u_boot_cmd(u-boot.lds中定义)代码段中找匹配的命令

__u_boot_cmd_start和__u_boot_cmd_end两个全局变量是这个段的起始地址和结束地址

1340行:判断参数,如果所给的参数与实现命令的结构体中所指定的参数大小不一样,将调用命令中提供的usage函数

1346行如果定义了CFG_CMD_BOOTD,则在1348行判断执行的是do_bootd命令则执行1356行的flag

1363行,是真正去执行我们命令行所输入的命令

1370行,判断是否有ctrl+c按下,如果有run_command执行结束

      再次退回main_loop函数,

      449行:开始执行等待用户输入的操作,CFG_HUSH_PARSER没有定义,463行的代码会被执行,readline函数会答应命令行提示符

      比如我设置的CFG_PROMPT值为ZC6410 #,将会在命令行打印ZC6410 #,然后等待用户输入;

      467行:把console_buffer中的数据拷贝到lastcommand数组中,484行判断len是否为0,如果按下ctrl+c按键,这个值将会是-1,

      因为前面代码serial驱动代码中是有这部分代码判断的,如果判断ctrl+c按下,则返回-1,这里的len值自然就是-1,如果ctrl+c按键

      没有被按下,将执行run_command,执行用户输入的命令。


转至:http://blog.sina.com.cn/s/blog_54f82cc20101226t.html