【1】三星移植过的uboot源代码准备
(1)三星对于S5PV210的官方开发板为SMDKV210,对应的移植过的uboot是:android_uboot_smdkv210.tar.bz2
【2】SourceInsight准备
【1】复制到linux的源生目录下,然后解压开。
【2】检查Makefile中的交叉编译工具链
我Ubuntu里的交叉编译工具链的全路径
/home/gec/Documents/mydocuments/arm-2009q3/bin
【3】配置时使用:make smdkv210single_config
,对应include/configs/smdkv210single.h
头文件。
【4】配置完成后直接make编译,编译完成后就进入烧录步骤。
【5】uboot/sd_fusing目录下有sd_fusing.sh脚本,用来烧录。
【1】运行结果是:第一,串口无输出;第二,开发板供电锁存成功。
【2】分析运行结果:uboot中串口最早的输出在"OK",在lowlevel_init.S中初始化串口时打印出来的;串口无输出"O"说明在打印"O"之前代码已经死掉了;开发板供电锁存在lowlevel_init.S中,开发板供电锁存成功说明这个代码之前的部分是没问题的。两个结合起来得到结论:错误在开发板供电锁存代码和串口初始化打印"O"代码之间。
【3】Windows下建立SourceInsight工程,顺藤摸瓜去找可能出问题的地方。整个程序运行是从start.S(uboot/cpu/s5pc11x/start.S)开始的,看代码也从这里开始。实际上只要屏蔽掉(uboot/board/samsung/smdkc110/lowlevel_init.S)的bl PMIC_InitIp
(我们的开发板没用PMIC模块,这里导致了死循环,Uboot就卡死在这)这一行代码
【4】然后重新编译烧录,整个uboot就启动起来了。但是很多配置信息是有问题的,很多功能应该也是不能用的,都要去一一查验。
【1】由上篇博客分析启动代码可知,打印板级信息在第二阶段的display_banner函数,推断得更改include/configs/smdkv210single.h里面的宏定义CONFIG_IDENT_STRING为" for HQS_210"(自定义),然后同步到ubuntu中的一份代码,然后 make distclean; 再make smdkv210single_config,然后再make,然后烧录运行,检查打印出来的banner信息是否如我们改动的那样。
【2】确认时钟部分的配置
时钟部分的运行结果本来就是对的,时钟部分的代码在lowlevel_init.S中的bl system_clock_init调用的这个函数中。函数的代码部分是没任何问题的,根本不需要改动,要改动的是寄存器写入的值,这些值都在配置头文件(smdkv210single.h)中用宏定义定义出来了。如果时钟部分要更改,关键是去更改头文件中的宏定义。
三星移植时已经把210常用的各种时钟配置全都计算好用宏开关来控制了。只要打开相应的宏开关就能将系统配置为各种不同的频率。
【3】DDR配置信息的更改
从运行信息以及bdinfo命令看到的结果,显示DRAM bank0和1的size值都设置错了。
使用md和mw命令测试内存,发现20000000和40000000开头的内存都是可以用的,说明代码中DDR初始化部分是正确的,只是size错了。
内存部分配置成:
#define CONFIG_NR_DRAM_BANKS 2 /* we have 2 bank of DRAM */
//#define SDRAM_BANK_SIZE 0x20000000 /* 512 MB */
#define SDRAM_BANK_SIZE 0x10000000 /* 256 MB */
#define PHYS_SDRAM_1 MEMORY_BASE_ADDRESS /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE SDRAM_BANK_SIZE
//#define PHYS_SDRAM_2 (MEMORY_BASE_ADDRESS + SDRAM_BANK_SIZE) /* SDRAM Bank #2 */
#define PHYS_SDRAM_2 0x40000000
#define PHYS_SDRAM_2_SIZE SDRAM_BANK_SIZE
即:
【4】开发板的名字更改:
在之前的启动代码分析中可知,开发板的名字确认函数在启动的第二阶段,即__start_ramboot函数里面的checkboard中,因此找到该函数的路径,修改名字即可。
【5】实验结果:
【1】目标:将DDR端口0地址配置为30000000开头
更改有2个目的
【2】DDR初始化参数更改
#define DMC0_MEMCONFIG_0 0x20E01323改为:
#define DMC0_MEMCONFIG_0 0x30F01323
【3】smdkv210single.h中相关宏定义修改
#define MEMORY_BASE_ADDRESS 0x20000000改为:
#define MEMORY_BASE_ADDRESS 0x30000000
【4】虚拟地址映射表中相应修改
uboot中开启了MMU对内存进行了段式映射,有一张内存映射表。之前课程中分析过,分析方法是一样的。
经过实际分析,(board/samsung/smdkc110/lowlevel_init.S)发现这个内存映射只是把20000000开始的256MB映射到C0000000开头的256MB。我们更改方法是将2改成3.
【5】查看运行结果:
发现uboot第二阶段运行了,但是整个uboot还是不成功!!
【6】分析问题,寻找解决方案。分析方法有2种:第一种靠经验、靠发现能力、靠直觉去找;第二种就是在整个代码中先基本定位错误地方,然后通过在源代码中添加打印信息来精确定位出错的代码,然后找到精确的出错位置后再去分析错误原因,从而找到解决方案。
【7】修改修改虚拟地址到物理地址的映射函数
【1】先从现象出发定位问题
解决问题的第一步,是定位问题。
定位了问题之后,实际修改程序解决问题不一定改的是这一句代码。但是肯定和这一句代码有关联,我们要通过自己分析来找到这种关联,从而从定位的错误点找到真正需要修改的点,然后去修改他。
实战方法:从打印出来的错误休息中挑选一个关键词,然后去源代码中搜索这个关键字,通过这种搜索的方法定位问题。通过搜索将问题定位在drivers/mmc/mmc.c的818行。
【2】网络搜索解决方案
初步的解决方案是自己先浏览一遍这个问题点周边代码上下文。通过浏览代码上下文,发现这个函数是在读取SD/iNand的ext_csd寄存器的值。通过浏览代码结合出错地方,可以判断出:从卡端读取ext_csd寄存器是成功的,并且从读取结果中拿到了卡的版本号信息。然后代码对版本号进行了判断,并且如果版本号大于5就会报错并且函数错误退出。这就是问题所正。
问题就是:我们使用的iNand卡的版本号大于5,而uboot代码本身不处理版本号大于5的卡,因此出错了。
怎么解决?第一可能,换卡;第二可能,软件修复。
【3】尝试修改代码解决问题
【1】控制台串口更换为串口0
uboot中默认使用串口2来做控制台输入输出的。
SOC中一共有4个串口(串口0、1、2、3),开发板X210上用DB9接口引出了2个串口,分别是串口2和串口0.(靠边的是串口2,靠里那个是串口0)。
三星公司推荐使用串口2来作为调试串口,所以在三星移植的uboot和内核版本中都是以串口2默认为控制台串口的。
有时候项目需要将调试串口修改为另外的串口(譬如串口0),这时候需要修改uboot的代码,做移植让uboot工作在串口0的控制台下。
uboot中真正去硬件初始化串口控制器的代码在lowlevel_init.S中的uart_asm_init中,其中初始化串口的寄存器用ELFIN_UART_CONSOLE_BASE宏作为串口n的寄存器的基地址,结合偏移量对寄存器进行寻址初始化。所以uart_asm_init中到底初始化的是串口几(从0到3)?取决于ELFIN_UART_CONSOLE_BASE宏。这个宏的值又由CONFIG_SERIALn(n是从1到4)来决定。
【2】修改默认网络地址设置
更改完成后如果环境变量还是原来的,正常。因为原来uboot执行过saveenv,因此环境变量已经被保存到iNand中的ENV分区中去了。uboot启动后校验时iNand的ENV分区中的环境变量是正确的,因此会优先加载。我们在uboot源代码中修改的只是默认的环境变量。解决方案是擦除掉iNand中的那一份环境变量,然后迫使uboot启动时使用uboot代码中自带的默认的这一份环境变量,就可以看到了。
可以使用mmc write 0 30000000 11# 32(表示将DDR的0x30000000开头的一段内存中的内容写入iNand中的第17个扇区开始的32个扇区内,写入长度是32个扇区长度(16KB))
【3】修改行提示符
#define CFG_PROMPT "ASTON210 # "
【4】总结
SoC的SROM bank和网卡芯片的CS引脚(SROM就是SRAM/ROM)。SoC的SROMController其实就是SoC提供的对外总线式连接SRAM/ROM的接口。如果SoC要外部外接一些SRAM/ROM类的存储芯片(或者伪装成SROM接口的芯片,譬如网卡芯片)就要通过SROM Controller来连接。网卡接在SROM中好处就是网卡芯片好像一个存储芯片一样被扩展在SoC的一个地址空间中,主机SoC可以直接用一个地址来访问网卡芯片内部寄存器。
网卡芯片内部寄存器使用相对地址访问。网卡芯片内部很多寄存器有一个地址,这个地址是从00开始的,但是实际上我们SoC不能用0地址去访问这个网卡的芯片内部寄存器。SoC访问网卡芯片00寄存器时的地址应该是:起始地址+00这里的起始地址就是网卡芯片对应接在SROM bankn中的bankn对应的基地址。
主机SoC上网,其实就是通过操控网卡芯片内部的寄存器、缓冲区等资源来上网的。也就是说其实SoC是通过网卡芯片来间接上网的。
总结:实际上也是一种总线式连接方式。优势是SoC内部不需要内置网卡控制器,所有的SFR全都在外部网卡芯片中,而且还可以通过地址直接访问(IO与内存统一编址),不用像Nand/SD接口一样使用时序来访问。从逻辑上来看,网卡更像是串口,而不像是SD/Nand。
【2】原理图浏览
210的SROM控制器允许8/16bit的接口,我们实际使用的是16位接口。
网线有8根线,但是实际只有4根有效通信线,另外4根都是GND,用来抗干扰的。4根通信线中管发送的有2根(Tx-和Tx+),管接收的有2根(Rx+和Rx-)。因为网线上传输的是差分信号。
网卡芯片有个CS引脚,(CS就是chip select,片选信号,主机(主芯片)向CS发送有效信号则从机芯片(网卡芯片)工作,主机向CS发送无效信号则从机芯片不工作。),这个引脚要接主机SoC的片选信号引脚,主机S5PV210的每一个SROM bank中有一个片选信号CSn(n=0-5),从原理图可以看出,我们X210上将DM9000的CS引脚接到了CSn1上,对应SROM bank1(推断出DM9000的总线地址基地址是0x88000000)。
DM9000的CMD引脚接到了S5PV210的ADDR2引脚上。DM9000为了减少芯片引脚数,数据线和地址线是复用的(DATA0到DATA15这16根线是有时候做数据线传输数据,有时候做地址线传输地址的。什么时候做什么用就由CMD引脚决定。)通过查询数据手册知道:当CMD为高电平时对应传输是DATA,当CMD为低电平时对应传输为INDEX(offset,寄存器地址)。
注明:这些引脚上的电平变化都是控制器自动的,不需要程序员手工干预。程序员所需要做的就是在配置寄存器值时充分考虑到硬件电路的接法,然后给相应寄存器配置正确的数值即可。
【3】网卡驱动文件介绍
uboot中本来就提供了很多网卡芯片的驱动程序,在uboot/drivers/net/dm9000x.c和dm9000x.h。这个驱动来自于linux kernel源代码。所以我们uboot中是移植而不是编写。
这个驱动是linux内核中做好的,根本不用动可以在uboot中直接使用的。而且因为linux驱动设计的很合理(数据和代码是分开的,这里驱动主要是代码,数据是由硬件开发板中的接法决定的,数据由一定的数据结构来提供。),所以驱动本身具有可移植性。这个就决定了我们移植DM9000驱动时这个驱动文件dm9000x.c和h不用动,要动的是数据。
【4】网卡移植的关键:初始化
uboot在第二阶段init_sequences中进行了一系列的初始化,其中就有网卡芯片的初始化。这个初始化就是关键,在这里的初始化中只要将网卡芯片正确的初始化了,则网卡芯片就能工作(意思是网卡驱动dm9000x.c和dm9000x.h依赖于这里的初始化而工作)。
网卡初始化代码地方在:
lib_arm
board.c
start_armboot
init_sequence
board_init ---->(smdkc110.c)
dm9000_pre_init ---->(smdkc110.c) 这个函数就是移植的关键
dm9000_pre_init函数主要功能就是初始化DM9000网卡。这个初始化过程和我们开发板上DM9000网卡芯片的硬件连接方式有关。必须要结合开发板原理图来分析,然后决定这个函数怎么编程。
原来的代码是三星的工程师根据三星的开发板SMDKV210的硬件接法来写的程序,我们要根据自己的开发板的硬件接法去修改这个程序,让网卡在我们的开发板上能工作。
#define DM9000_16BIT_DATA这个宏用来表示DM9000工作在16位总线模式下。根据上节课的硬件原理图的分析,可以看到我们开发板上DM9000确实工作在16位模式下。
从三星版本的代码中可以看出,它操作的是bit20-bit23,对照数据手册中寄存器定义,可以看出三星的开发板DM9000是接在Bank5上的。而我们接在bank1上的,因此我们需要操作的bit位是bit4-bit7
总结:三个寄存器的修改。主要是三星的开发板DM9000接在bank5,我们接在了bank1上,因此要做一些修改。
【5】基地址的配置等
之前说过,驱动分为2部分:代码和数据。代码不用动,数据要修改。
CONFIG_DM9000_BASE是DM9000网卡通过SROM bank映射到SoC中地址空间中的地址。这个地址的值取决于硬件接到了哪个bank,这个bank的基地址是SoC自己定义好的。譬如我们这里接到了bank1上,bank1的基地址是0x88000000.
DM9000_IO表示访问芯片IO的基地址,直接就是CONFIG_DM9000_BASE;DM9000_DATA表示我们访问数据时的基地址,因为DM9000芯片的CMD引脚接到了ADDR2,因此这里要+4(0b100,对应ADDR2)
本来这样配置就完了,重新编译运行网卡就应该工作了。但是实际测试发现不工作,要怎么样修改呢?修改方式是将CONFIG_DM9000_BASE改成0x88000300就工作了。
问题:这个0x300从哪里来的?我得出的感觉最靠谱的解释是:跟DM9000网卡芯片型号版本有关,我认为这个0x300是DM9000网卡本身的问题,他本身的内部寄存器就有一个0x300的一个偏移量。
2.11.11.1、网卡移植代码实践
(1)经过实践,网卡驱动移植成功。
(2)其实还可以做一些实验。譬如说对网卡驱动初始化部分寄存器的设置,还有网卡CONFIG_DM9000_BASE也可以配成0x88000000再去试一试。
【1】linux系统中网卡驱动的典型工作方式简介
在linux系统中,网卡算是一个设备,这个设备驱动工作后会生成一个设备名叫ethn(n是0、1、2、····)(无线网卡名字一般叫wlan0、wlan1····)。然后linux系统用一些专用命令来操作网卡,譬如ifconfig命令。
linux下的应用程序如何使用网卡驱动来进行网络通信?最通用的方法就是socket接口。linux系统中有一系列的API和库函数,提供了一个socket编程接口,linux下的应用程序都是通过socket来实现上网的,socket内部就是间接调用的网卡驱动实现网络通信的。
linux设计是非常完备的,应用层和驱动层是严格分离的。也就是说写网络编程应用层的人根本不用管驱动,只要会用socket接口即可;写底层驱动的人根本不用管应用层,只要面向linux的网络驱动框架模型即可。
【2】uboot中网卡驱动的工作方式简介
一定要记住:uboot本身是一个裸机程序,是一个整体,没有分层。所以uboot中根本没有驱动和应用的概念。
按照逻辑来说,ping这样的命令实现的代码就是网络应用的应用程序,像dm9000x.c和dm9000x.h这样的代码属于驱动程序。所以在uboot中这些东西是揉在一起的,应用是直接调用驱动实现的。也就是说ping命令内部是直接调用了dm9000的网卡驱动中的函数来实现自己的。
【3】以ping命令为例查找代码验证分析
ping命令是uboot的众多命令之一,ping命令实现的函数叫do_ping
函数的调用关系:
do_ping
NetLoop
PingStart
PingSend
ArpRequest
eth_send(dm9000x.c中)
【1】问题:当前uboot不能启动内核
用同样的方法(使用tftp 0x30008000 zImage-qt; 然后bootm 0x30008000),分别使用我们自己移植的uboot和使用九鼎移植版本的uboot去启动内核,发现九鼎移植版本的可以启动,但是我们自己移植的不可以启动。到此我们就断定我们的uboot有问题,不能启动内核。
做基本检查:首先怀疑是机器码不对。经过和九鼎移植版本的uboot对比发现machid都是2456,说明机器码没错。
想到一个问题,我们之前做实验时将串口改为了串口0,而内核zImage-qt的串口输出在串口2.怀疑可能的问题是uboot使用了串口0而内核使用了串口2所以在uboot后看不到内核的启动信息。
【2】解决:将串口改回串口2
【3】根据现象分析,定位问题并试图解决