移植U-Boot时遇到的问题
1. 在lowlevel_init过程中,本来想实现一个串口直接打印字符串的过程,编译的时候出现了问题,说是发现代码执行段没有对齐:“unaligned opcodes detected in executable segment”。开始的时候随便找了几个地方加上 .align 4, 并没有用,后来发现原因是字符串的长度不是字长的整数倍,比如:
uart_init_ok_log: .string "hello world!\r\n",由于该字符串会自动加上结束符,因此该字符串的长度是15,那么紧跟其后的指令当然就不对齐了。
解决方法:
第一,通过添加空格调整字符串的长度。
第二,在字符串声明之后添加对齐声明,比如:
uart_init_ok_log: .string "hello world!\r\n"
.align 4
第三,调整该字符串定义的位置,比如放到文件末尾。
2. 官方针对S5PC110的代码并没有在lowlevel_init中真正初始化串口,它只是初始化了一些GPIO,这里我为了方便调试,自己用ARM GCC 汇编实现了串口的初始化,本想进一步实现通过传递字符串首地址来打印字符串的功能,结果反复尝试都失败,程序执行到此处时就像进入了死循环一样。
原因:通过使用arm-linux-objdump命令分析编译后生成的elf格式文件发现,当传递参数,即ldr r0, =uart_init_ok_log 时,r0取出的数值是uart_init_ok_log标号地址处的内容,是0x34800000之后的地址值,其实这已经是内存区域的地址了,要知道我现在的uboot是从SD Card启动的,其实是在iRAM运行的,这两块地址相去甚去,因此访问内存地址会跑飞!
目前我对于编译地址、运行地址等概念还很模糊,需要继续思考,继续实践学习。
3. 使用汇编语言初始化DDR2内存时,出现了类似死循环的现象,根本不能正常工作。
原因:通过前面实现的串口输出功能调试,发现在等待DLL锁定的循环中出现了如下死循环:
find_lock_value:
ldr r1, [r0, DMC_PHYSTATUS_OFFSET]
and r1, r1, #7
bne find_lock_value
在分支语句之前的语句中并没有影响CPSR的语句,使用的是前面程序对CPSR状态的影响情况,而前面是判断处理器是S5PC100还是S5PC110,因此肯定不相等,也就陷入了死循环。
解决方案:
find_lock_value:
ldr r1, [r0, DMC_PHYSTATUS_OFFSET]
and r1, r1, #7
cmp r1, #7
bne find_lock_value
感悟:1. 这个错误很低级,说明自己ARM GCC汇编底子不行。
2. 循环结构一定注意小心!
解决上述问题之后,发现DDR还是不能正常读写,猜测是芯片的timing参数不合适,因此我试了试demo程序的参数,果然正常工作了... ...Demo代码是针对时钟频率为200M的情况,而当前初始化DDR2的时候,时钟频率是133M,所以时序参数会有不同,其中一些值计算过后不到一个周期,我就直接配成了1,而Demo中则在此基础上加上了裕度值,没有出现某个时序参数是1的情况,所以配置芯片时序参数的时候,尤其是针对不熟悉的芯片,要留出裕度,至于优化的事情可以后面再做。
4. 代码搬移问题。
board_init_f是第一个调用的C函数,它一开始就调用了memset函数,但分析来看memset函数相对起始地址已经远远超过16KB,因此需要搬移代码,搬移的过程其实是通过各个设备的驱动程序实现数据的复制,其实芯片内ROM内有已经烧写好的复制代码了,我们只需要根据其接口调用即可。
在这里会遇到一个问题:我们在执行board_init_f函数之前已经将整个u-boot.bin文件写入了DDR2 SDRAM中,并且起始地址就是我们的编译地址。那么我们如何去执行SDRAM中的board_init_f函数呢?如果写bl board_init_f肯定不行了,因为这是相对跳转,只会在SRAM中运行。解决方案如下:
ldr r0, =_start //r0中存放的是_start标号的编译地址,即0x34800000
ldr r1, _board_init_f_offset //开始我写成了ldr r1, =_board_init_f_offset,这是数据传送的伪指令,应该使用访存指令。
add lr, r0, r1
mov pc, lr
_board_init_f_offset:
.word board_init_f - _start
5. 由于当前的U-Boot没有支持NAND Flash,本想先调试以下裸板驱动,然后添加相关的用户命令支持,没想到一个致命的失误浪费了我好几天的时间,这次应该好好总结一下。
该工程会生成两个镜像文件,bl1.bin、bl2.bin,前者烧写在SD Card的sector1开始的存储空间,主要负责芯片的启动,如关闭看门狗,初始化堆栈,还负责初始化DDR2 SDRAM,将SD Card上的bl2.bin通过iROM固件搬移至内存中,然后跳转到bl2.bin开始的地址去,代码执行权交给bl2.bin。bl2.bin的主要功能是实现GPIO、UART、NAND Flash驱动,然后测试NAND Flash的读写功能。
第一步,需要确定代码已经搬移成功,所以bl2.bin通过GPIO驱动让LED闪烁,表示代码已经顺利执行到该镜像,当我要测试UART驱动时,问题出现了:初始化相关寄存器时程序便跑飞了,但是这部分代码显然没有死循环,所以感觉匪夷所思。然后,我考虑putc、puts、getc等函数与编译器的built-in函数重复,然后被覆盖,但是从反汇编文件来看,程序是正常的,不过将以上几个函数修改了名字,并且添加了编译选项-fno-builtin -ffreestanding ,表示该工程不是普通应用程序,也不使用任何库,后来反复修改,偶尔成功输出。
原因:BL1只搬移了16KB,而bl2.bin大小将近21KB,反汇编文件显示uart_init函数代码在镜像中的偏移是17KB左右,因此会出错。之前偶尔成功的原因是,由于只是调试UART,注释掉了NAND Flash相关的代码,镜像只不到1KB,镜像完整。
教训:当程序出现这类匪夷所思的错误时,可以考虑跳出此处的逻辑,比如怀疑一下镜像文件的完整性、内存的工作情况等等。
调试过程中多次阅读镜像文件的内容,特总结如下。
镜像文件是链接脚本控制的,链接脚本可以指定程序入口ENTRY、镜像的格式FORMAT、输入的各个目标文件INPUT、输出文件OUTPUT、链接地址等等。链接脚本的另一个重要功能是描述镜像的结构:镜像分成几个段,每个段的起始地址、结束地址是多少等等。代码段在镜像中的偏移取决于链接的顺序,如arm-linux-ld -Tbl2.lds start.o clock.o main.o ./lib/built-in.o ./drivers/built-in.o -o bl2.elf,执行过该操作后代码段的相对顺序和上面目标文件出现的顺序是一致的。
6. 测试裸板NAND flash程序时,又卡了好几天,十分郁闷。
现象:按照芯片读写流程实现的驱动程序不能正常工作,编程时会失败,读到的数据不是自己写入的。
出现的问题:
1. 宏定义PAGE_SIZE_BYTE应该是2048,但是为了移植方便,将它定义为#define PAGE_SIZE_BYTE ( 1 << COLUMN_ADDR_BITS ),而COLUMN_ADDR_BITS 的值为12, 显然错了,将PAGE_SIZE_BYTE变成4096了,这样涉及到Page读写循环时就会出错。
2.其实驱动能够正常工作,但是测试程序出现了问题:NAND Flash在编程写入时需要先擦除相应的块,这一点我在读芯片手册的时候没看到,因此每次尝试编程写入都会失败,更不用说读数据了。
7. goni开发板默认不支持Nand Flash选项,这里尝试移植。
在board_init_r()函数中有处理Nand Flash的代码,可以看到它是由宏CONFIG_CMD_NAND控制的,定义这个宏之后,就通过nand_init()初始化Nand Flash设备。
nand_init()函数位于drivers/mtd/nand/nand.c文件里,说明这个函数和板卡、芯片无关。它主要调用board_nand_init(),搜索该函数发现它是与板卡相关的,所以移植的接口就是它。通过观察board_nand_init()函数所在文件的目录,发现与板卡相关的Nand Flash驱动在drivers/mtd/nand/目录下,下面有s3c64xx.c、tegra_nand.c等文件,因此我们新建文件s5pv210_nand.c。
第一步,为了方便,参考s3c64xx.c, cp drivers/mtd/nand/s3c64xx.c drivers/mtd/nand/s5pv210_nand.c。
第二步,可以看到包含的头文件中,有一句#include <asm/arch/s3c6400.h>,因此我们要在当前include/asm/arch/目录下新建头文件s5p_nand.h。这个头文件的内容同样参考6400,查看之后发现该头文件主要定义NFC各寄存器的地址,各寄存器中的控制位序号,以及page size等等。所以,cp arch/arm/include/asm/arch-s3c64xx/s3c6400.h include/asm/arch/s5p_nand.h,然后根据S5PV210芯片手册修改就可以了。
第三步,为了支持Nand,所以要在include/configs/s5p_goni.h中添加宏CONFIG_CMD_NAND,接着在drivers/mtd/nand目录下的Makefile中修改,添加s5pv210_nand.o编译对象。
第四步,其实移植的过程心里很没底,为了不花太多时间阅读源码,只是根据s3c6400的驱动稍作修改,而且对于ECC也并不熟悉。不过走到这一步可以尝试编译,发现出错之处都是宏为定义,比如我在include/configs/s5p_goni.h添加了CONFIG_SYS_NAND_BASE CONFIG_SYS_NAND_ECCBYTES两个宏。最后编译通过,烧写之后发现可以输入nand相关的操作命令了。
第五步,测试。当前板子的U-Boot编译地址为0x34800000,且U-Boot经过relocate后会将自己搬移到DDR2 SDRAM顶端的位置,即0x3FFFFFFF下面的一块区域,又因为S5PV210的DDR2 SDRAM的有效地址是从0x20000000开始的,所以内存起始地址和U-Boot运行地址之间有大块的空闲内存可以用来使用。
测试步骤:
1. nand erase 0xc000000 0x20000 //擦除192M为起始地址的128KB(Block Size)
2. nand write 0x34800000 0xc000000 0x800 //将U-Boot镜像的前2KB(Page Size)数据写入Nand Flash的192M为首地址的空间
3. nand read 0x20000000 0xc000000 0x800 //将刚刚写入的内容读到RAM以0x20000000为起始地址的空间
4. md.b 0x20000000 64; md.b 0x34800000 64 //对比读写的数据
实践证明,读写测试正确,移植初步成功......虽然源码细节并不十分清楚。
8. 官方针对goni板卡的源码中并没有支持网络功能,因此考虑移植DM9000AEP驱动,以实现板卡和PC之间的通信,最终板卡将以TFTP客户端的身份从TFTP服务器,即PC上下载镜像文件,便于调试。
第一步,搜集资料:S5PV210 datasheet、Tiny210 核心板和扩展板原理图、DM9000 datasheet、DM9000A datasheet,DM9000和DM9000A是同一芯片的不同封装版本,DM9000是100pin的,而DM9000A系列是48pin的,引脚数量相差悬殊,因此功能必定有差别,移植过程就证实了这一废话。
第二步,阅读Tiny210底板原理图。DM9000AEP连接到了S5PV210的SROM Controller接口上,即S5PV210通过访存的形式读写DM9000AEP寄存器,所以我们要根据电路原理图的连接情况仔细分析SROM Controller的配置。
DM9000AEP Pins SROM Controller Pins
SD[15:0] Xm0DATA[15:0] 可见SROM Controller的databus width必须是16bit
IOR Xm0OEn DM9000AEP读控制信号
IOW Xm0WEn DM9000AEP写控制信号
CS Xm0CS1 说明连接到了SROM Controller的Bank1,这一点极其重要,一开始我竟然愚蠢地将bank1认为是逻辑上的第一块,初始化了bank0,!
INT EINT7 DM9000AEP是可以产生中断的,不过这里并没有使用
CMD Xm0ADDR2 CMD为高电平时,会更新DM9000AEP内部的INDEX,即写入将要操作的寄存器索引值(编号),无论是读寄存器还是写寄存器,都需要先更新一下INDEX
CMD为低电平时,DM9000AEP认为当前数据总线上的是数据,这根线连接到了Xm0ADDR2,说明S5PV210访问DM9000AEP时,要根据命令还是数据,在
地址总线上发出相应的地址,这一点极其重要!
其余的信号不再列出。
第三步,既然我们用到了SROM Controller,那么就来初始化相应的寄存器吧。要知道DM9000AEP是板级设备,它通过GPIO连接到SROM Controller,因此必须保证相关的GPIO要初始化为相应的功能复用模式,逐一查看各引脚,发现DM9000AEP用到的所有引脚复位后的状态就是我们需要的,这一点S5PV210做得非常好,毕竟那么多GPIO,所以索性把一些引脚复位后直接初始化成相关的功能得了。接着,初始化SROM Controller,这就需要查看S5PV210的数据手册了。SROM Controller可以支持NOR Flash、PROM、SRAM等类型的存储器,一共分成了6个bank,每个bank最大寻址空间是16M,这一点也应该注意,如果我们发出的地址超出最大寻址范围,估计会出问题。
SROM Controller有7个寄存器:BW、BC0、BC1、BC2、BC3、BC4、BC5,第一个是配置数据位宽的寄存器,后面几个是配置时序参数的寄存器。
这里只记录一下我们用到的比特:BW[4]:bank1的数据位宽选择信号,我们选择16bits,BW[5]:既然是16bit mode,那么SROM address base必须是half-word,这意味着我们要考虑地址对齐的问题!!具体来说,AHB总线上的地址是按字节编址的,即地址00b对应一个字节的数据,地址01b对应一个字节的数据,地址10b对应一个字节的数据,地址11b对应一个字节的数据,而SROM的databus width为16,也就是说SROM中的地址00b对应一个16bit的数据,也就是两个字节,地址01b也对应一个16bit的数据,因此,AHB地址总线发出00b与01b其实都是在访问SROM的01b地址上的数据,同样地,AHB地址总线上发出10b与11b都是在访问SROM的10b地址上的数据,而这就等价于AHB地址总线上的地址数据逻辑右移一位,送到SROM的地址总线上,即SROM_ADDR[22:0] <= AHB_ADDR[23:1],明白了这一点就可以解决一个后面遇到的问题。接着说BW寄存器的位,BW[6]:总线访问周期中不插入等待周期,BW[7]:我们每次都是操作16bit数据,不使用高低位字节使能控制。
BC1寄存器中需要对比DM9000AEP的读写时序和SROM基本读写时序图,将一些时间参数对应上,然后填写到BC1对应的寄存器中,这需要对时序非常理解,但我并不打算深究,粗略看了下,参考别人的代码,适当增加一下各个时序参数的裕度即可。
第三步,前面分析了最基本的初始化步骤,那么初始化代码应该在哪里写呢?这就需要了解U-Boot的启动流程了,在board_init_r函数中有如下语句:
#if defined(CONFIG_CMD_NET)
puts( "NET: ");
eth_initialize( gd->bd );
#endif
我们需要进一步分析eth_initialize(),该函数在net/eth.c中,这个文件在eth层面上实现了很多操作网卡的框架函数,如eth_initialize、eth_register、eth_unregister、eth_init、eth_halt、eth_send、eth_rx等等。此函数首先将两个全局变量eth_devices和eth_current置为NULL,eth_devices相当于所有网卡数据结构的链表头,eth_current是当前使用的网卡。接着,查看环境变量中是否有bootfile,如果有就将相关的名字复制到BootFile全局变量中,然后就到了非常关键的一步调用board_eth_init(),该函数在eth.c中的原型如下所示:
int board_eth_init( bd_t *bis ) __attribute__( ( weak, alias( "__def_eth_init" ) ) );
那么__def_eth_init()是什么呢?
static int __def_eth_init( bd_t *bis )
{
return -1;
}
可见,若用户没有自己实现board_eth_init(),那么此函数没有任何作用,不过这显然是提醒我们板卡上网卡的初始化代码应该写到以board_eth_init为名字的函数中,到此为止,我们知道了初始化代码应该书写的位置,但是这个函数应该放在哪个文件中呢?这就涉及到U-Boot的文件组织情况了,跟具体板卡相关的文件均放在源码根目录的board目录下,比如我们这种情况就应该选择board/samsung/goni/goni.c,samsung是SoC制造商的名字,goni是板卡的名字,三星命名自己提供的设计参考样板为goni。在goni.c中我们实现board_eth_init(),实现的伪代码如下:
/* board/samsung/goni/goni.c */
int board_eth_init( bd_t *bis )
{
GPIO_Config();
SROM_Controller_Config();
return dm9000_initialize( bis );
}
最后一行调用了实际的网卡初始化函数,U-Boot drivers/net/dm9000x.c中已经提供了驱动源码,这里我们只需要调用此接口即可。顺便说一下,dm9000_initialize中会调用eth_register函数,将dm9000x.c中表示dm9000AEP设备的数据挂接到eth_devices链表中,这样一来,board_eth_init函数就会正确返回,正常完成初始化。
第四步,修改配置选项,将DM9000驱动加入工程,并且驱动网卡。首先,通过board_init_r可知,我们需要在配置头文件中加入CONFIG_CMD_NET才能支持网络功能 ,那么在include/configs/s5p_goni.h中加入:
#define CONFIG_CMD_NET,
接着我们要将dm9000x.c加入到工程中,显然看一下相关的Makefile就行了,关于dm9000x.c的Makefile选项:
COBJS-$(CONFIG_DRIVER_DM9000) += dm9000x.o
所以我们应该在s5p_goni.h中加入
#define CONFIG_DRIVER_DM9000
接下来的部分就需要分析以下dm9000x.c的驱动源码了,从初始化部分开始,dm9000_initialize()调用的第一个函数是dm9000_get_enetaddr(),看名字就知道这是获取MAC地址的函数,展开看一下:
1 static void dm9000_get_enetaddr(struct eth_device *dev) 2 { 3 #if !defined(CONFIG_DM9000_NO_SROM) 4 int i; 5 for (i = 0; i < 3; i++) 6 dm9000_read_srom_word(i, dev->enetaddr + (2 * i)); 7 #endif 8 }
代码中出现的宏CONFIG_DM9000_NO_SROM就是我们需要考虑的地方,看dm9000的数据手册可以知道,它支持网卡外接一个EEPROM芯片,用来存储MAC地址、PID、VID等等,但从电路原理图来看,网卡上没有接EEPROM,这就需要在配置文件s5p_goni.h中添加第三个宏:
#define CONFIG_DM9000_NO_SROM 1
添加了这个宏之后,当前这个函数就没有任何功能了,所以我们需要自己写代码实现获取MAC地址的功能,前面说到eth.c中实现了不少操作网卡的框架函数,其实也有一些是能用的,比如int eth_getenv_enetaddr( char *name, uchar *enetaddr ),这个函数的功能是通过获取环境变量name中的值来设置enetaddr,具体点说,U-Boot中有一个叫作etheraddr的环境变量,通过获取它里面的值来设置网卡的MAC地址,这个函数的实现如下:
1 void eth_parse_enetaddr(const char *addr, uchar *enetaddr) 2 { 3 char *end; 4 int i; 5 6 for (i = 0; i < 6; ++i) { 7 enetaddr[i] = addr ? simple_strtoul(addr, &end, 16) : 0; 8 if (addr) 9 addr = (*end) ? end + 1 : end; 10 } 11 } 12 13 int eth_getenv_enetaddr(char *name, uchar *enetaddr) 14 { 15 eth_parse_enetaddr(getenv(name), enetaddr); 16 return is_valid_ether_addr(enetaddr); 17 }
说到这里,思路就很清晰了,直接贴出要添加的代码:
if( !( eth_get_enetaddr( "ethaddr", dev->enetaddr ) ) ) { printf( "Ethernet Address is NOT valid, please set MAC address mannuly!" ); }
显然,我们需要在环境变量中添加ethaddr,如何在U-Boot中添加环境变量呢?前面我们选择了CONFIG_ENV_IS_NOWHERE,且默认的环境变量放在default_enviroment字符数组中:
1 /************************************************************************ 2 * Default settings to be used when no valid environment is found 3 */ 4 #define XMK_STR(x) #x 5 #define MK_STR(x) XMK_STR(x) 6 7 const uchar default_environment[] = { 8 #ifdef CONFIG_BOOTARGS 9 "bootargs=" CONFIG_BOOTARGS "\0" 10 #endif 11 #ifdef CONFIG_BOOTCOMMAND 12 "bootcmd=" CONFIG_BOOTCOMMAND "\0" 13 #endif 14 #ifdef CONFIG_RAMBOOTCOMMAND 15 "ramboot=" CONFIG_RAMBOOTCOMMAND "\0" 16 #endif 17 #ifdef CONFIG_NFSBOOTCOMMAND 18 "nfsboot=" CONFIG_NFSBOOTCOMMAND "\0" 19 #endif 20 #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) 21 "bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0" 22 #endif 23 #if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0) 24 "baudrate=" MK_STR(CONFIG_BAUDRATE) "\0" 25 #endif 26 #ifdef CONFIG_LOADS_ECHO 27 "loads_echo=" MK_STR(CONFIG_LOADS_ECHO) "\0" 28 #endif 29 #ifdef CONFIG_ETHADDR 30 "ethaddr=" MK_STR(CONFIG_ETHADDR) "\0" 31 #endif 32 #ifdef CONFIG_ETH1ADDR 33 "eth1addr=" MK_STR(CONFIG_ETH1ADDR) "\0" 34 #endif 35 #ifdef CONFIG_ETH2ADDR 36 "eth2addr=" MK_STR(CONFIG_ETH2ADDR) "\0" 37 #endif 38 #ifdef CONFIG_ETH3ADDR 39 "eth3addr=" MK_STR(CONFIG_ETH3ADDR) "\0" 40 #endif 41 #ifdef CONFIG_ETH4ADDR 42 "eth4addr=" MK_STR(CONFIG_ETH4ADDR) "\0" 43 #endif 44 #ifdef CONFIG_ETH5ADDR 45 "eth5addr=" MK_STR(CONFIG_ETH5ADDR) "\0" 46 #endif 47 #ifdef CONFIG_ETHPRIME 48 "ethprime=" CONFIG_ETHPRIME "\0" 49 #endif 50 #ifdef CONFIG_IPADDR 51 "ipaddr=" MK_STR(CONFIG_IPADDR) "\0" 52 #endif 53 #ifdef CONFIG_SERVERIP 54 "serverip=" MK_STR(CONFIG_SERVERIP) "\0" 55 #endif 56 #ifdef CONFIG_SYS_AUTOLOAD 57 "autoload=" CONFIG_SYS_AUTOLOAD "\0" 58 #endif 59 #ifdef CONFIG_PREBOOT 60 "preboot=" CONFIG_PREBOOT "\0" 61 #endif 62 #ifdef CONFIG_ROOTPATH 63 "rootpath=" CONFIG_ROOTPATH "\0" 64 #endif 65 #ifdef CONFIG_GATEWAYIP 66 "gatewayip=" MK_STR(CONFIG_GATEWAYIP) "\0" 67 #endif 68 #ifdef CONFIG_NETMASK 69 "netmask=" MK_STR(CONFIG_NETMASK) "\0" 70 #endif 71 #ifdef CONFIG_HOSTNAME 72 "hostname=" MK_STR(CONFIG_HOSTNAME) "\0" 73 #endif 74 #ifdef CONFIG_BOOTFILE 75 "bootfile=" CONFIG_BOOTFILE "\0" 76 #endif 77 #ifdef CONFIG_LOADADDR 78 "loadaddr=" MK_STR(CONFIG_LOADADDR) "\0" 79 #endif 80 #ifdef CONFIG_CLOCKS_IN_MHZ 81 "clocks_in_mhz=1\0" 82 #endif 83 #if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0) 84 "pcidelay=" MK_STR(CONFIG_PCI_BOOTDELAY) "\0" 85 #endif 86 #ifdef CONFIG_ENV_VARS_UBOOT_CONFIG 87 "arch=" CONFIG_SYS_ARCH "\0" 88 "cpu=" CONFIG_SYS_CPU "\0" 89 "board=" CONFIG_SYS_BOARD "\0" 90 #ifdef CONFIG_SYS_VENDOR 91 "vendor=" CONFIG_SYS_VENDOR "\0" 92 #endif 93 #ifdef CONFIG_SYS_SOC 94 "soc=" CONFIG_SYS_SOC "\0" 95 #endif 96 #endif 97 #ifdef CONFIG_EXTRA_ENV_SETTINGS 98 CONFIG_EXTRA_ENV_SETTINGS 99 #endif 100 "\0" 101 };
可见,default_enviroment是一个字符数组,里面存放了一个很长很长的字符串,每个环境变量的格式都是name=value,不同环境变量之间以一个‘\0’分隔,如果要添加一个环境变量,只需要在这里增加相应的宏定义即可,比如我们要添加ethaddr的话,只需要在配置头文件中添加宏:
#define CONFIG_ETHADDR 00:40:5C:26:0A:5B
我们回到dm9000_initialzie
int dm9000_initialize(bd_t *bis) { struct eth_device *dev = &(dm9000_info.netdev); /* Load MAC address from EEPROM */ dm9000_get_enetaddr(dev); dev->init = dm9000_init; dev->halt = dm9000_halt; dev->send = dm9000_send; dev->recv = dm9000_rx; sprintf(dev->name, "dm9000"); eth_register(dev); return 0; }
这里将一个eth_device的函数指针指向了dm9000具体的驱动函数,给网卡取了名字dm9000,然后调用eth.c中的函数eth_register,向系统注册自己,浏览一下代码就可以看到很多地方都在使用DM9000_ior和DM9000_iow,这是两个小函数:
1 /* 2 Write a byte to I/O port 3 */ 4 static void 5 DM9000_iow(int reg, u8 value) 6 { 7 DM9000_outb(reg, DM9000_IO); 8 DM9000_outb(value, DM9000_DATA); 9 } 10 11 /* 12 Read a byte from I/O port 13 */ 14 static u8 15 DM9000_ior(int reg) 16 { 17 DM9000_outb(reg, DM9000_IO); 18 return DM9000_inb(DM9000_DATA); 19 }
#define DM9000_outb( d, r ) writeb( d, ( valatile u8* ) ( r ) )
#define DM9000_ib=nb( r ) readb( ( volatile u8 * )( r ) )
也就是说上面两个函数最终都会变为访存操作,而访存的地址就是DM9000_IO和DM9000_DATA,所以这两个宏的定义至关重要!可以看到,我们写寄存器的时候就向DM9000_IO这个地址写寄存器的INDEX,而写数据或者读数据的时候就访问DM9000_DATA对应的地址,根据前面对电路图的分析,说明访问DM9000_IO这个地址的时候,CMD引脚,即Xm0ADDR2是低电平,访问DM9000_DATA这个地址的时候,Xm0ADDR2是高电平,而且,这两个地址必须在SROM bank1所在的地址空间内,也就是必须保证DM9000AEP的CS引脚有效,且不能超过bank1 16MB的寻址范围!这意味着在SROM Bank1的前16MB寻址范围内,任意能使Xm0ADDR2为高电平的地址都能作为DM9000_DATA,而任意能使Xm0ADDR2为低电平的地址都能作为DM9000_IO。比如,S5PV210的SROM Bank1起始地址是0x8800_0000,这个地址就可以定义为DM9000_IO,但是不是DM9000_DATA就能定义为0x8800_0004呢?不能!这一点的确让我疑惑了一会儿,看了网上好多资料,都没有让我信服的说法,感觉大家都是抄来抄去,并没有把问题搞清楚,其实原因是我前面初始化SROM Controller BW寄存器的时候说到的,AHB总线按字节编址,而我们将SROM Controller的bank1配置成了16bit模式,所以要考虑地址线对齐的问题,简而言之,我们是通过操作AHB address的bit3来设置SROM Controller的Xm0ADDR2的。而为什么网上很多人都在说0x300的偏移呢?这就是我们要DM9000和DM9000AEP两份芯片数据手册的原因了,DM9000数据手册中提到的芯片是100pin封装的,它有一些SA地址引脚和MII的TXD、RXD引脚,TXD引脚是strap pin,意思是,在芯片上电复位的时候会采样strap pin上的电平,根据采样结果,实现一些配置,如CS片选信号是高电平有效或者低电平有效,如INT引脚在中断来到后是高电平还是低电平,这里的效果是确定DM9000的片选情况:只有当SA9 SA8都是高电平,SA7是低电平,而SA[6:4]跟复位时采样到TXD[2:0]状态相同时,DM9000才会被选中,而我们知道DM9000是内置PHY的,所以一般不会外接PHY,所以TXD引脚一般不用,直接接地,这样一算,用二进制表示就是11_0000_0000,即0x300。而我们使用的是DM9000 AEP,48pin,没有SA引脚,所以我们除了要考虑地址总线对齐情况之外,不用考虑SA的问题,因此也就没有那个偏移!
结合以上分析,我们需要在s5p_goni.h中添加如下内容:
#define CONFIG_DM9000_BASE ( 0x88000000 )
#define DM9000_IO CONFIG_DM9000_BASE
#define DM9000_DATA ( CONFIG_DM9000_BASE + 0x8 )
最后一步,网卡已经能正常初始化、发送和接收数据了,但是怎么验证呢?我们就需要使用一些网络功能,实际验证一下,这里即选择ping和tftp。查看net下的Makefile可以看到,只要定义了CONFIG_CMD_NET,tftp服务就被编译到系统中了,而要用ICMP协议的话,需在s5p_goni.h中添加
#define CONFIG_CMD_PING
阅读源码发现U-Boot启动之后,并没有初始化网卡,而是每次使用网络服务的时候才会执行,然后开始相应的网络功能,执行时会进入NetLoop,简而言之就是检测网卡接收到数据与否,检查是否按下了Ctril-C,如果有数据就开始处理,功能完成后会退出NetLoop,执行eth_halt,即将网卡隔离掉,所以如果我们只是使用一些客户端角色的网络功能时,只能主动发起请求,其他网络节点是不能检测到板卡这个网络节点存在的,因为不发起网络服务的时候,根本就没有检测网卡的接收数据情况。
最后就是测试一下ICMP和TFTP服务的问题了,ICMP测试多数时候是成功的,但是失败的几率也不小,个人猜测是网络性能不好,设置的超时时间有些短,不过我测试TFTP服务时,都是成功的。还有一个问题,就是初始化时网口的LED并没亮,程序打印信息也证实没有建立连接,但在这样的情况发起PING和TFTP多数情况下也是成功的,应该是后面的程序不断尝试连接的原因,最终连接建立。
测试TFTP的过程:
最后led.bin程序正确执行,至此DM9000AEP的驱动移植工作告一段落。