1、移植前的准备工作
1.1、三星移植过的uboot源代码准备
(1)三星对于S5PV210的官方开发板为SMDKV210,对应移植的_uboot_smdkv210.tar.bz2
1.2、SourceInsight准备
(1)移植的时候最重要的工作就是看代码、改代码然后编译运行测试。
(2)编译代码必须在linux中(不能在windows共享文件夹下配置uboot,因为windows没有符号链接,而配置过程中需要生成符号链接),那么看代码和改代码可以在linux中(vim、gedit)也可以在windows中(Sourceinsight)。
(4)那么如果看代码和改代码在windows中,而编译代码又必须在linux中,怎么实现windows中的代码和linux中的代码同步?
1)通过共享文件夹在linux中进行复制(cp /mnt/hgfs/winshare/xxx.c .);
2)通过一些专用工具,譬如sshsecureshell、filezilla。
1.3、便捷的文件传输工具sshsecureshell
(1)windows中安装sshsecureshell客户端。
(2)虚拟机上网。不同的ubuntu版本中网卡重启的命令不同,
在ubuntu10.04中网卡重启使用:/etc/init.d/networking restart 或者 service network restart;
在ubuntu14.04中以上两个都不行了,要重启网卡需要使用:ifdown eth0 然后ifup eth0。
2、ubuntu14.04上网及安装openssh
2.1、ubuntu14.04上网问题
(1)虚拟机上网有2种模式,NAT和桥接。
(2)虚拟机上网配置要注意这几个地方:
第一个要注意选择NAT还是桥接模式;
第二个如果是桥接模式要注意桥接到哪个网卡上(一般笔记本都有2个网卡,一个是有线网卡一个是无线网卡),如果是NAT模式则不用考虑这一点;
第三个要注意ubuntu中网络配置文件/etc/network/interfaces,这个文件中是配置网卡信息的(譬如静态ip还是dhcp(动态ip),静态ip地址是多少等);
(3)ubuntu14.04中重启网卡的命令变了。
(4)两种方式上网的配置过程:
NAT方式下上网配置过程:
前提是主机windows通过无线wifi上网了(或者windows通过有线网卡上网也可以)。
然后在虚拟机中配置2点:
第一选择NAT方式;
第二配置/etc/network/inerfaces文件中使用dhcp方式;
然后重启网卡,确认ip地址得到后即可上网。
桥接方式上网配置过程:
前提是主机windows通过无线wifi上网了(主机通过有线网卡上网的配置方式一点不同)。
然后在虚拟机中配置3点:
第一选择桥接方式;
第二配置桥接到无线网卡(如果主机windows是通过有线上网的,则桥接到有线网卡);
无线网卡关键词:wireless
有线网卡关键词:ethernet
第三配置/etc/network/inerfaces文件中使用dhcp方式;
然后重启网卡,确认ip地址得到后即可上网。
(5)虚拟机要ping通开发板的配置过程。
前提是开发板和电脑之间通过网线连接好。
然后在虚拟机中配置3点:
第一选择桥接方式;
第二配置桥接到有线网卡(如果配置为自动或者配置桥接到无线网卡则肯定无法ping通开发板);
第三配置/etc/network/inerfaces文件中使用static方式,ip地址配置保证和主机windows、开发板三者处于同一网段;
然后重启网卡,确认ip地址得到后即可ping通开发板。
(6)老版的windows系统中有一个bug,如果windows没有检测到有线网卡连接了外部网络则windows中本地连接是不工作的,即网卡不工作。解决方案是用网线随便连接一个有联网能力的东西即可,譬如网线连接你的电脑到旁边的电脑、开发板(开发板中运行了linux系统)、路由器端口等。
2.2、搭建openssh环境
(1)安装ssh-server。(sudo apt-get install openssh-server)如果报错提示依赖错误,可以参考:http://www.cnblogs.com/mliudong/p/4094519.html
(2)securecrt登录
(3)sshsecureshell登录
sshsecureshell登录不上,要修改/etc/ssh/sshd_config,参考:http://blog.sina.com.cn/s/blog_5f435c130102v6pv.html。
修改完重启时如果/etc/init.d/ssh restart不起作用,可以使用:ps -e | grep ssh,看sshd的进程号,然后kill -9 进程号杀死ssh进程以达到重启的目的,或者直接重启ubuntu系统
3、移植初体验
3.1、直接编译三星移植版的uboot尝试运行
(1)复制到linux的源生目录下,然后解压开。
cp /mnt/hgfs/share/s5pv210/android_uboot_smdkv210.tar.bz2 .
tar -jxvf android_uboot_smdkv210.tar.bz2
(2)检查Makefile中的交叉编译工具链(和自己安装的是否一致)
(3)三星版本的Makefile中没有x210_sd_config,因此不能用make x210_sd_config进行配置,x210_sd_config是九鼎移植版uboot中才有的;这款开发板来自三星官方出版的smdkv210,在Makefile中搜索smdkv210,找到如下:
发现smdkv210还有不同的版本,任选其中一个使用,这里使用smdkv210single_config,
因此配置时使用:make smdkv210single_config,对应include/configs/smdkv210single.h头文件。
(4)配置完成后直接make编译,编译完成后就进入烧录步骤。
(5)uboot/sd_fusing目录下有sd_fusing.sh脚本,用来烧录。
但由于三星官方是通过64位的Linux系统编译生成了mkbl1和sd_fdisk(使用file命令查看),因此不能使用,而我的ubuntu系统yes64位的,所以我自己移植了一份32位的,在~/porting_x210/uboot_samsung/u-boot-samsung-dev/uboot_sd_fusing目录
3.2、代码分析&问题查找
运行结果是:第一,串口无输出;第二,开发板供电锁存成功。
分析运行结果:uboot中串口最早的输出在"OK",在lowlevel_init.S中初始化串口时打印出来的;串口无输出"O"说明在打印"O"之前代码已经死掉了;开发板供电锁也存在lowlevel_init.S中,开发板供电锁存成功说明这个代码之前的部分是没问题的。两个结合起来得到结论:错误发生在开发板供电锁存代码和串口初始化打印"O"代码之间。
(1)Windows下建立SourceInsight工程,将所有文件加进工程
(2)顺藤摸瓜去找可能出问题的地方
整个程序运行是从start.S开始的,看代码也从这里开始。
在lowlevel_init.S中发现只要屏蔽掉bl PMIC_InitIp 这一行代码,然后重新编译,整个uboot就启动起来了。
但是很多配置信息是有问题的,并且很多功能应该也是不能用的,都要去一一查验。
4、时钟和DDR的配置移植
4.1、更改 smdkv210single.h中的宏
宏CONFIG_IDENT_STRING改为" for CHM210",然后同步到ubuntu,
然后 make distclean; make smdkv210single_config,然后make,然后烧录运行,
检查打印出来的banner信息是否如我们改动的那样。
4.2、确认时钟部分的配置
(1)前面可以看到,时钟部分的运行结果本来就是对的,时钟部分的代码在lowlevel_init.S中的bl system_clock_init调用的这个函数中。函数的代码部分是没任何问题的,根本不需要改动,如果需要更改时钟设置,那么就要改动寄存器写入的值,而这些值都在配置头文件(smdkv210single.h)中用宏定义定义出来了。如果时钟部分要更改,关键是去更改头文件中的宏定义。
(2)三星移植时已经把210常用的各种时钟配置全都计算好用宏开关来控制了。只要打开相应的宏开关就能将系统配置为相应的时钟频率。
//#define CONFIG_CLK_667_166_166_133
//#define CONFIG_CLK_533_133_100_100
//#define CONFIG_CLK_800_200_166_133
#define CONFIG_CLK_800_100_166_133
//修改值
//#define CONFIG_CLK_1000_200_166_133 //原始值
//#define CONFIG_CLK_400_200_166_133
//#define CONFIG_CLK_400_100_166_133
但是这里还是出现了一些问题,应该是uboot中一些跟时钟密切相关的设备的配置并没有采用宏开关的方式(只要我们更改这个宏,那么就会进行不同的时钟配置,但是那里并没有更改),而是采用硬编码的方式,
譬如说串口的配置被写死成了跟#define CONFIG_CLK_1000_200_166_133这个宏相关。
4.3、DDR配置信息的更改
(1)从运行信息看到的结果,显示DRAM bank0和1的size长度值都设置错了。
(2)DRAM bank0和1的长度的软件配置在uboot的第二阶段的dram_init中
这些宏实际是smdkv210single.h中设置的配置值
修改配置值:
#define CONFIG_NR_DRAM_BANKS 2 /* we have 2 bank of DRAM */
//#define SDRAM_BANK_SIZE 0x20000000 /* 512 MB */
#define SDRAM_BANK_SIZE 0x10000000 /* 512 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/* SDRAM Bank #2 *
然后同步到linux,重新编译烧录运行
5、DDR地址的更改
5.1、目标:将DDR端口0地址配置为30000000开头
(1)更改的目的:将DRAM0的地址设置为30000000开头,这样就和DRAM1连接起来了,构成一片连续的内存,
原先的内存设置 DRAM0的地址范围:0200000000x2FFFFFFF,DRAM1的地址范围:0x400000000x4FFFFFFF
这样的内存是断续的。
5.2、DDR初始化参数更改
(1)DDR的初始化代码在lowlevel_init.S中。代码部分就是对相应寄存器进行初始化;使用宏进行初始化,因此代码部分是不用修改的,要修改的是宏定义的值,而uboot为了具有可移植性把这些宏定义都在include/configs/xxx.h中了。因此我们只需要去这个配置文件中修改配置值即可。
(2)更改内容是:
#define DMC0_MEMCONFIG_0 0x20E01323 改为:
#define DMC0_MEMCONFIG_0 0x30E01323 注意20改为30了。
5.3、smdkv210single.h中相关宏定义修改
(1)上面将寄存器的值改了后相当于是对硬件配置部分做了更改。但是uboot中DDR相关的一些软件配置值还没更改,还在原来位置,所以要去更改。
(2)
#define MEMORY_BASE_ADDRESS 0x20000000改为:
#define MEMORY_BASE_ADDRESS 0x30000000
5.4、虚拟地址映射表中相应修改
(1)由于uboot中开启了MMU对内存进行了虚拟地址映射,因此还要考虑这个地址映射后的物理地址是否是我们想要的,
比如说Makefile中指定链接地址是0xc3e00000,这个虚拟地址最终是将我们的uboot放在了哪里,
uboot在开启MMU前构建了一张TLB(页表),里面是内存映射关系。
.globl mmu_table
mmu_table:
.set __base,0
// Access for iRAM
.rept 0x100
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
// Not Allowed
.rept 0x200 - 0x100
.word 0x00000000
.endr
.set __base,0x200
// should be accessed
.rept 0x600 - 0x200
FL_SECTION_ENTRY __base,3,0,1,1
.set __base,__base+1
.endr
.rept 0x800 - 0x600
.word 0x00000000
.endr
.set __base,0x800
// should be accessed
.rept 0xb00 - 0x800
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
/* .rept 0xc00 - 0xb00
.word 0x00000000
.endr */
.set __base,0xB00
.rept 0xc00 - 0xb00
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
//下面这一段是将c000 0000~d000 0000的虚拟地址映射到2000 0000~3000 0000的地址范围
//.set __base,0x200
.set __base,0x300
// 256MB for SDRAM with cacheable
.rept 0xD00 - 0xC00
FL_SECTION_ENTRY __base,3,0,1,1
.set __base,__base+1
.endr
// access is not allowed.
@.rept 0xD00 - 0xC80
@.word 0x00000000
@.endr
.set __base,0xD00
// 1:1 mapping for debugging with non-cacheable
.rept 0x1000 - 0xD00
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
(2)经过实际分析,发现这个内存映射只是把20000000开始的256MB映射到C0000000开头的256MB。其它地方都是原样映射,我们更改方法是将2改成3.
.set __base,0x200 改为
.set __base,0x300
(3)重新配置编译,烧录运行查看结果。
发现只打印了OK,说明我们第一阶段内存初始化出问题了,导致不能重定位到内存,所以没有进入uboot的第二阶段
6、DDR初始化参数更改2
6.1、修改DMC0的配置参数
(1)修改DDR中DMC0的memconfig_0寄存器的配置值,将
#define DMC0_MEMCONFIG_0 0x30E01323 改为:
#define DMC0_MEMCONFIG_0 0x30F01323
(2)然后重新同步、编译烧写运行,发现uboot第二阶段运行了,即内存能工作了,但是SD卡的初始化出问题了,内存能工作,说明前面DDR的硬件寄存器配置没问题,出问题的只可能是修改DDR地址的过程中某个步骤影响到了SD卡的初始化。
(3)分析问题,寻找解决方案。分析方法有2种:
第一种:靠自身经验积累;
第二种:就是在整个代码中先基本定位错误地方,然后通过在源代码中添加打印信息来精确定位出错的代码,然后找到精确的出错位置后再去分析错误原因,从而找到解决方案。
6.2、修改虚拟地址到物理地址的映射函数
(1)修改uboot/board/samsung/smdkc110/smdkc110.c中的virt_to_phy_smdkc110,将其中的20000000改为30000000即可。
#ifdef CONFIG_ENABLE_MMU
#ifdef CONFIG_MCP_SINGLE
ulong virt_to_phy_smdkc110(ulong addr)
{
if ((0xc0000000 <= addr) && (addr < 0xd0000000))
//return (addr - 0xc0000000 + 0x20000000);
return (addr - 0xc0000000 + 0x30000000);
else
printf("The input address don't need "\
"a virtual-to-physical translation : %08lx\n", addr);
return addr;
}
#else
ulong virt_to_phy_smdkc110(ulong addr)
{
if ((0xc0000000 <= addr) && (addr < 0xd0000000))
return (addr - 0xc0000000 + 0x30000000);
else if ((0x30000000 <= addr) && (addr < 0x50000000))
return addr;
else
printf("The input address don't need "\
"a virtual-to-physical translation : %08lx\n", addr);
return addr;
}
#endif
#endif
(2)同步代码,然后重新编译烧录运行。
6.3、总结:uboot代码牵一发而动全身
7、inand驱动问题的解决
7.1、先从现象出发定位问题
(1)解决问题的第一步,是定位问题。定位到出问题的代码处。通过搜索关键字EXT_CSD将问题定位在drivers/mmc/mmc.c的818行。
(2)找到出问题的代码之后,实际修改程序不一定改的是这一句代码。但是肯定和这一句代码有关联,我们要通过自己分析来找到这种关联。找到真正需要修改的地方。
7.2、网络搜索解决方案
/*
* Read and decode extended CSD.(读取和解码扩展CSD)
* CSD是SD内的一个寄存器,存着卡特有的信息
*/
//这个函数就是用来读取高版本SD卡里的扩展型CSD寄存器的
static int mmc_read_ext_csd(struct mmc *host){
int err;
u8 *ext_csd;
unsigned int ext_csd_struct;
//host->version表示SD卡控制器的版本,如果SD卡控制器的版本比较低,则就不用读了
if (host->version < (MMC_VERSION_4 | MMC_VERSION_MMC))
return 0;
/*
* As the ext_csd is so large and mostly unused, we don't store the
* raw block in mmc_card.
*/
ext_csd = malloc(512);
if (!ext_csd) {
printf("could not allocate a buffer to "
"receive the ext_csd.\n");
return -1;
}
err = mmc_switch(host, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, 0);
if (err)
return err;
err = mmc_switch(host, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_BUS_WIDTH,
EXT_CSD_BUS_WIDTH_1);
if (err)
return err;
//主控制器向SD卡发命令来读ext_csd寄存器
err = mmc_send_ext_csd(host, ext_csd);
if (err) {
/*
* High capacity cards should have this "magic" size
* stored in their CSD.
*/
if (host->capacity == (4096 * 512)) {
printf("unable to read EXT_CSD "
"on a possible high capacity card. "
"Card will be ignored.\n");
} else {
printf("unable to read "
"EXT_CSD, performance might suffer.\n");
err = 0;
}
goto out;
}
ext_csd_struct = ext_csd[EXT_CSD_REV];
//if (ext_csd_struct > 5) { // find_mmc_device(2);
if (ext_csd_struct > 8) { // find_mmc_device(0);
printf("unrecognised EXT_CSD structure "
"version %d\n", ext_csd_struct);
err = -1;
goto out;
}
if (ext_csd_struct >= 2) {
host->ext_csd.sectors =
ext_csd[EXT_CSD_SEC_CNT + 0] << 0 |
ext_csd[EXT_CSD_SEC_CNT + 1] << 8 |
ext_csd[EXT_CSD_SEC_CNT + 2] << 16 |
ext_csd[EXT_CSD_SEC_CNT + 3] << 24;
if (host->ext_csd.sectors) {
host->high_capacity = ((host->ocr & OCR_HCS) == OCR_HCS);
/* common host->read_bl_len=512 */
host->capacity = host->ext_csd.sectors;
}
}
switch (ext_csd[EXT_CSD_CARD_TYPE] & 0x3) {
case EXT_CSD_CARD_TYPE_52 | EXT_CSD_CARD_TYPE_26:
host->ext_csd.hs_max_dtr = 52000000;
host->clock = 52000000;
break;
case EXT_CSD_CARD_TYPE_26:
host->ext_csd.hs_max_dtr = 26000000;
host->clock = 26000000;
break;
default:
/* MMC v4 spec says this cannot happen */
printf("card is mmc v4 but doesn't "
"support any high-speed modes.\n");
goto out;
}
out:
free(ext_csd);
return err;
}
(1)初步的解决方案是自己先浏览一遍这个问题点周边代码上下文。通过浏览代码上下文,发现mmc_read_ext_csd这个函数是在读取SD/iNand的ext_csd寄存器的值。通过浏览代码结合出错地方,可以判断出:从卡端读取ext_csd寄存器是成功的,并且从读取结果中拿到了卡的版本号信息。然后代码对版本号进行了判断,发现版本号大于5所以报错。这就是问题所在。我们使用的iNand卡的版本号太高,大于5,而uboot代码本身不能处理版本号大于5的mmc卡,因此出错了。
(2)怎么解决?第一:更换iNand;第二:软件更改。
(3)网络搜索错误提示信息,然后逐个去查阅,看看哪个可以给我们提供解决问题的思路和方法。http://blog.csdn.net/wang_shuai_ww/article/details/22308853
7.3、尝试修改代码解决问题
(1)解决方法就是修改uboot中的代码,把判断的5改成更大的数字。譬如8,然后跳过这个错误。
7.4、推测和实验验证(SD卡和iNand的区别)
(1)当前板子上有一个iNand接在SD0上,有一个外置SD卡接在SD2上。那uboot中初始化的这个是iNand而不是SD卡。也就是说uboot中实际用的是SD0而不是SD2.
(2)通过实验发现,uboot使用外置SD卡时,这个版本号的问题不会出现。从这里可以推测出SD卡和iNand的区别,至少从一个角度可以看出:SD卡版本低,iNand的版本比较高。
board.c 496~500行改为,即可除掉Nand的初始化
#undef CONFIG_CMD_NAND
#if defined(CONFIG_CMD_NAND)
puts("NAND: ");
nand_init();
#endif
然后重新配置编译烧录即可进入命令行底下
8、一些小问题的修补
8.1、控制台串口更换为串口0
(1)三星的uboot中默认使用串口2来做控制台输入输出的。
(2)SOC中一共有4个串口(串口0、1、2、3),开发板X210上用DB9接口引出了2个串口,分别是串口2和串口0.(靠边的是串口2,靠里那个是串口0)。
(3)如果要更换串口0作为uboot控制台的输入输出,肯定是要更改硬件寄存器的;uboot中真正去硬件初始化串口控制器的代码在lowlevel_init.S中的uart_asm_init中,其中初始化串口的寄存器用ELFIN_UART_CONSOLE_BASE宏作为寄存器的基地址,然后结合偏移量对寄存器进行寻址初始化。所以uart_asm_init中到底初始化的是串口几(从0到3)?取决于ELFIN_UART_CONSOLE_BASE宏的值。这个宏的值又由CONFIG_SERIALn(n是从1到4)来决定,对应串口0~3;因此要更换串口0为串口2,只要在smdkv210single.h中定义CONFIG_SERIA3,然后屏蔽其它的宏就可以了。
(4)同步代码、配置编译烧录运行,发现串口线插在串口2上,console上只打印:SD checksum error.(这个是内部iROM中的代码打印出来的iNand校验失败的信息);然后将串口线改插到串口0上,启动,所有的信息出现。实验成功。
8.2、修改默认网络地址设置
(1)修改配置头文件smdkv210single.h中的CONFIG_IPADDR等宏,则可以修改uboot代码自带的跟网络相关的环境变量。
/*#define CONFIG_BOOTARGS "root=ramfs devfs=mount console=ttySA0,9600" */
#define CONFIG_ETHADDR 00:40:5c:26:0a:5b
#define CONFIG_NETMASK 255.255.255.0
#define CONFIG_IPADDR 192.168.1.20
#define CONFIG_SERVERIP 192.168.1.30
#define CONFIG_GATEWAYIP 192.168.1.1
(2)更改完成后如果环境变量还是原来的,是正常的。因为原来之前在uboot执行过saveenv,因此环境变量已经被保存到iNand中的ENV分区中去了。uboot启动后校验时iNand的ENV分区中的环境变量是正确的,因此会优先加载。我们在uboot源代码中修改的不过只是默认的环境变量,iNand中没有才会使用这份默认环境变量。
解决方案是擦除掉iNand中的那一份环境变量,然后迫使uboot启动时使用uboot代码中自带的这一份环境变量,就可以看到我们的更改值了。可以使用mmc write 0 30000000 11# 32(表示将DDR的0x30000000开头的一段内存中的内容写入iNand中的第17个扇区开始的32个扇区内,写入长度是32个扇区长度(16KB))来擦除iNand中的环境变量。
8.3、修改行提示符
#define CFG_PROMPT "CHM210 $ " /* Monitor Command Prompt */
8.4、总结
(1)uboot移植关键在于对uboot的整体结构和过程的了解,了解之后修改移植就非常简单。
9、网卡驱动移植1
9.1、网卡芯片与开发板的连接方式
(1)SoC的SROM bank CSn1和网卡芯片的CS#引脚相连(SROM就是SRAM/ROM)。SoC的SROMController其实就是SoC提供的对外总线式连接SRAM/ROM的接口。如果SoC要外部外接一些SRAM/ROM类的存储芯片(或者伪装成SROM接口的芯片,譬如网卡芯片)就要通过SROM Controller来连接。网卡接在SROM中好处就是网卡芯片好像一个存储芯片一样被扩展在SoC的一个地址空间中,主机SoC可以直接使用一个地址来访问网卡芯片内部寄存器。
(2)网卡芯片内部寄存器使用相对地址访问。网卡芯片内部很多寄存器都有一个地址,这些地址从00H开始,但是实际上我们SoC不能用0地址去访问这个网卡的芯片内部寄存器。SoC访问网卡芯片00H寄存器时要使用的地址应该是:起始地址+00H,这里的起始地址就是网卡芯片接在SROM bankn中的bankn对应的基地址。
(3)主机SoC上网,其实就是通过操控网卡芯片内部的寄存器、缓冲区等资源来上网的。也就是说其实SoC是不具有上网能力的,SoC是通过网卡芯片来间接上网的。
(4)总结:实际上也是一种总线式连接方式。优势是SoC内部不需要内置网卡控制器,所有的SFR全都在外部网卡芯片中,而且还可以通过地址直接访问(IO与内存统一编址),不用像Nand/SD接口一样使用时序来访问。
(5)从逻辑上来看,网卡更像是串口,而不像是SD/Nand,但跟串口还是有一些区别的。
9.2、原理图浏览
(1)210的SROM控制器允许8/16bit的接口,我们实际使用的是16位接口。
(2)网线有8根线,但是实际只有4根有效通信线,另外4根都是GND,用来抗干扰的。4根通信线中管发送的有2根(Tx-和Tx+),管接收的有2根(Rx+和Rx-)。因为网线上传输的是差分信号。
(3)网卡芯片有个CS#引脚,(CS就是chip select,片选信号,主机向CS发送有效信号则从机芯片工作,主机向CS发送无效信号则从机芯片不工作。),这个引脚要接主机SoC的片选信号引脚CSn1,主机S5PV210的每一个SROM bank中有一个片选信号CSn(n=0-5),从原理图可以看出,X210将DM9000的CS引脚接到了CSn1上,对应SROM bank1(推断出DM9000的总线地址基地址是0x88000000)。
(4)DM9000的CMD引脚接到了S5PV210的ADDR2引脚上。DM9000为了减少芯片引脚数,数据线和地址线是复用的(DATA0到DATA15这16根线是有时候做数据线传输数据,有时候做地址线传输地址的。什么时候做什么用就由CMD引脚决定。)通过查询数据手册知道:当CMD为高电平时对应传输是DATA,当CMD为低电平时对应传输为INDEX(offset,寄存器地址)。
注明:这些引脚上的电平变化都是控制器自动的,不需要程序员手工干预。程序员所需要做的就是在配置寄存器值时充分考虑到硬件电路的接法,然后将正确的数值配置到正确的寄存器。
10、网卡驱动移植2
10.1、网卡驱动文件介绍
(1)uboot中本来就提供了很多网卡芯片的驱动程序,dm9000对应的驱动是uboot/drivers/net/下的dm9000x.c和dm9000x.h。这个驱动来自于linux kernel源代码。所以我们uboot中是移植而不是编写。
(2)这个驱动是linux内核中做好的,根本不用改动,可以在uboot中直接使用的。而且因为linux驱动设计的很合理(数据和代码是分开的,这里驱动主要是代码,数据是硬件寄存器的值,取决于我们芯片的接法。),所以驱动本身具有可移植性。这个就决定了我们移植DM9000驱动时这个驱动文件dm9000x.c和h不用动,要动的是寄存器的值,将寄存器的值配置好,然后将来在驱动中会使用到这些寄存器。
10.2、网卡移植的关键:初始化
(1)uboot在第二阶段的init_sequences中进行了一系列的初始化,其中就有网卡芯片的初始化。这个初始化就是关键,在这里只要将网卡芯片正确的初始化了,则网卡芯片就能工作(意思是网卡驱动dm9000x.c和dm9000x.h依赖于这里的初始化而工作)。
(2)网卡初始化代码地方在:
start_armboot
init_sequence
board_init
dm9000_pre_init 这个函数就是驱动移植的关键
(3)dm9000_pre_init函数主要功能就是初始化DM9000网卡。这个初始化过程和我们开发板上DM9000网卡芯片的硬件连接方式有关。必须要结合开发板原理图来分析,从原理图分析信息,然后决定这个函数怎么编写。
(4)原来的代码是三星的工程师根据三星的开发板SMDKV210的硬件接法来写的程序,我们要根据自己的开发板的硬件接法去修改这个程序,让网卡在自己开发板上能工作。
(5)#define DM9000_16BIT_DATA这个宏用来表示DM9000工作在16位总线模式下。根据分析原理图,可以看到开发板上DM9000确实工作在16位模式下。
(6)从三星版本的代码中可以看出,它操作的是bit20-bit23,对照数据手册中寄存器定义,可以看出三星的开发板DM9000是接在Bank5上的。而我们接在bank1上的,因此我们需要操作的bit位是bit4-bit7
(7)总结:三个寄存器的修改。主要是三星的开发板DM9000接在bank5,我们接在了bank1上,因此要做一些修改。
10.3、基地址的配置等
(1)之前说过,驱动分为2部分:代码和数据。代码不用动,数据要修改。
(2)CONFIG_DM9000_BASE是DM9000网卡通过SROM bank映射到SoC中地址空间中的地址。这个地址的值取决于硬件接到了哪个bank,这个bank的基地址是SoC自己定义好的。譬如我们这里接到了bank1上,bank1的基地址是0x88000000,因此要将这个宏修改过来。
(3) 访问DM9000的寄存器的途径主要是由CMD和CS#这两个引脚的连接方式来确定的,CS#是DM9000的片选引脚,CMD引脚在芯片手册中描述如下:
CMD pin :
Command Type
When high, the access of this command cycle is DATA port
When low, the access of this command cycle is INDEX port
DM9000的CS#引脚接的是SoC的SROM Bank1(物理地址为0x88000000~0x8FFFFFFF)的片选引脚CSn1,CMD引脚则接的是SoC地址总线的ADDR2位。DM9000_IO表示DM9000的INDEX端口地址,直接就是CONFIG_DM9000_BASE;DM9000_DATA表示DM9000的DATA端口地址,因为DM9000芯片的CMD引脚接到了ADDR2,因此这里要+4(0b100,对应ADDR2)
确定了DM9000的INDEX和DATA端口地址,我们就有了访问网卡的途径。读网卡寄存器的方法是:先向INDEX端口写入寄存器的地址,再从DATA端口读出该寄存器的值。写寄存器的方法与之对应:先向INDEX端口写入寄存器的地址,再向DATA端口写入值。#通过I/O端口读写网卡寄存器的方法在驱动中由ior()、iow()这来两个函数实现。
(4)本来这样配置就完了,重新编译运行网卡就应该工作了。但是实际测试发现不工作,要怎么样修改呢?修改方式是将CONFIG_DM9000_BASE改成0x88000300就工作了。
问题:这个0x300从哪里来的?我得出的感觉最靠谱的解释是:跟DM9000网卡芯片型号版本有关,我认为这个0x300是DM9000网卡本身的问题,他本身的内部寄存器是以0x300开始,而非从0地址开始。
11、网卡驱动如何工作
11.1、网卡移植代码实践
(1)经过实践,网卡驱动移植成功。
11.2、linux系统中网卡驱动的典型工作方式
(1)在linux系统中,网卡算是一个设备,这个设备驱动工作后会生成一个设备名叫ethn(n是0、1、2、····)(无线网卡名字一般叫wlan0、wlan1····)。然后linux系统用一些专用命令来操作网卡,譬如ifconfig命令。
(2)linux下的应用程序如何使用网卡驱动来进行网络通信?最通用的方法就是socket接口。linux系统中有一系列的API和库函数,提供了一个socket编程接口,linux下的应用程序都是通过socket来实现上网的,socket内部就是间接调用的网卡驱动实现网络通信的。
(3)linux设计是非常完备的,应用层和驱动层是严格分离的。写网络编程应用层的人根本不用管驱动,只要会用socket接口即可;写底层驱动的人根本不用管应用层,只要面向linux的网络驱动框架模型即可。
11.3、uboot中网卡驱动的工作方式
(1)uboot本身是一个裸机程序,是一个整体,没有分为应用层和驱动层。
(2)按照逻辑来说,ping这样的命令实现的代码就是网络API实现的应用程序,像dm9000x.c和dm9000x.h这样的代码属于驱动程序。所以在uboot中这些东西是揉在一起的,应用是直接调用驱动实现的。也就是说ping命令内部是直接调用了dm9000的网卡驱动中的函数来实现自己的。
11.4、以ping命令为例查找代码验证分析
(1)ping命令是uboot的众多命令之一,ping命令实现的函数叫do_ping
(2)函数的调用关系:
do_ping
NetLoop
PingStart
PingSend
ArpRequest
eth_send(dm9000x.c中)
(3)验证了11.3中说的uboot中应用程序(ping)调用驱动程序(dm9000x.c)的方式。这就是一种直接调用的方式。
12、使用自己移植的uboot启动内核
12.1、解决问题:当前uboot不能启动内核
(1)用同样的方法(使用tftp 0x30008000 zImage-qt;将内核下载到0x30008000, 然后bootm 0x30008000启动内核),分别使用自己移植的uboot和使用九鼎移植版本的uboot去启动内核,发现九鼎移植版本的可以启动,但是自己移植的不可以启动。到此就可以断定内核是没有问题的,自己移植的uboot有问题,不能启动内核。
(2)做基本检查:首先怀疑是机器码不对。命令行输入bdinfo查看命令码,结果和九鼎移植版本的uboot对比发现machid都是0x998(2456),而且九鼎的uboot是能启动内核的,说明机器码就是2456没错。
(3)后来发现,之前实验时将串口2改为了串口0,而内核zImage-qt的串口输出在串口2,所以在串口0看不到内核的启动信息,所以只要将串口0改回串口2,在smdkv210single.h中修改串口编号就可以了。如果内核还不能启动,说明可能是传参的问题,需要到smdkv210single.h中查看有没有定义bootm传参需要的那几个宏。