本文主要是将如何将tftp嵌入到uboot中,开机启动。
使用规格如下:
board |
hi3519v101 C020 |
arch |
hi3519 |
uboot version |
2010.06 |
1 添加tftp步骤
上一章更新了uboot的启动流程。而在uboot下做tftp升级,因为海思本身支持ftfp升级,(可以查看main.c 372 行CONFIG_UPDATE_TFTP)。
故需要做的工作如下:
1 2 3 4 5 6
| 1. 在/include/configs/hi3519v101.h中增加 #define UPDATE_TFTP,则在common/main.c中可寻找到关于tftp的升级代码。 2. 编写common/下update.c代码,当tftp收到代码以后进行升级。 3. 由于update.c使用fit文件,需要学习解析FIT文件。 4. 用于update.c使用flash擦写,而3519使用EMMC,需学习擦写EMMC文件。 5. 重新编写update.c 6. tftp等待时间有点长,需要修改net/tftp.c中关于tftp的等待时间。
|
其中有几个注意点:
- *common下的cmd_XXX.c都是在uboot下使用命令实现的。tftp就是在cmd_net.c中实现,如果不会,可以参考cmd下的代码*
- 前章节已经说明,但是重新说明一下,3519的存储初始化采用广撒网操作,即定义了nor,nand,emmc的三种存储器初始化,哪个操作成功就算是成功了。
- 针对于存储的操作,都采用hisi自己写的操作。具体查看drivers/mtd/中hi开头的di驱动。
- 关于EMMC的驱动,都在drivers/mmc/himciv200.c中。可以查看common/cmd_mmc.c参考mmc工作方式
1 2 3
| #define CONFIG_CMD_SF #define CONFIG_CMD_NAND #define CONFIG_GENERIC_MMC
|
2 实施步骤
查看common/main.c中372行已经有TFTP升级了,所以只需要定义CONFIG_UPDATE_TFTP即可。
1. 在hi3519v101.所以只需要定义CONFIG_UPDATE_TFTP即可
1 2 3 4 5 6
| /*---->include/configs/hi3519v101.h<----------------*/ #define CONFIG_FIT 1 #define CONFIG_UPDATE_TFTP 1 // #define CONFIG_OF_LIBFDT 1 // #define CONFIG_UPDATE_TFTP_CNT_MAX 1 // #define CONFIG_UPDATE_TFTP_MSEC_MAX 1
|
其中,除了要定义CONFIG_OF_LIBFDT,还要定义CONFIG_FIT。。毕竟我们需要使用FIT文件结构进行升级。
在实施中,一定要阅读以下几个文档:
- README 2097行有关于tftp升级的详细介绍
- doc/README.update 有详细的关于auto tftp update的介绍
- CONFIG_OF_LIBFDT是关于描述FDT的文件,在README.update中建议打开,但是在实际使用中,我们并没有用到FDT相关功能,打开以后问题多多,所以关闭了
- CONFIG_UPDATE_TFTP_CNT_MAX和CONFIG_UPDATE_TFTP_MSEC_MAX是关于tftp等待时间,这里定义的只是初始值,在update.c中可以直接定义,而且在tftp.c中关于tftp的等待时间有严格限制,需要修改
2. 查看update.c代码
update.c中update_tftp代码非常简单,首先获取要升级的文件,获取内存地址,使用tftp获取文件,使用FIT进行解析。解析完毕后存储到文件中。步骤如下:
1 2 3 4 5
| 1. 获取要升级的文件名称 2. 获取要升级存放的内存地址 3. 调用tftp协议进行文件传输 4. 解析FIT文件,获取单个文件 5. 使用擦写文件
|
需要做的工作如下:
1 2 3 4
| 1. 设置升级文件名称 2. 设置内存存放地址,默认为0x82000000 3. 学习FIT格式 4. 学习EMMC擦写
|
3. FIT文件解析
关于FIT文件,详细的操作文档在:
1 2
| doc/uImage.FIT //请详细仔细阅读
|
FIT举例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| //FIT是作为内核的一种文件描述方式,其实和xml差不多 / { // 根文件“/”是必要的,如同文件系统的根目录,以下都是根节点下子节点,例子中,/下只有images一个子节点 description = "IPNC auto-tftp firmware";//根节点下关于子节点描述 #address-cells = <1>; //子节点地址描述,<1>表示只有1个地址
images { //子节点image update@1 { //子节点,名称@地址 description = "u-boot"; //子节点描述符 data = /incbin/("./uboot.bin"); //子节点存在地址,mkimage会使用 type = "standalone"; //类型,类型定义很多,查看README compression = "none";//是否压缩 arch = "arm"; //架构 load = <0x00000000>; //启动地址 entry = <0x100000>; //地址大小 hash@1 { algo = "md5"; //校验规则 }; }; }
|
生产FIT,在PC下操作如下:
1 2
| mkimage -fl update_firmware.its ./firmware.bin //使用mkimage将its的文件描述符打包成firmware.bin升级文件
|
4. EMMC擦写
查看cmd_env代码,为了区分使用nand,nor,emmc去保存,uboot写了三套saveenv的结构体,通过查看当前的存储,调用不同的存储擦写操作,简单粗暴,大写的佩服。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 截取的cmd_mmc.c中擦写mmc的代码 { if (strncmp(argv[1], "write", sizeof("write")) == 0) { int dev = simple_strtoul(argv[2], NULL, 10); void *addr = (void *)simple_strtoul(argv[3], NULL, 16); u32 cnt = simple_strtoul(argv[5], NULL, 16); u32 n; struct mmc *mmc = find_mmc_device(dev) int blk = simple_strtoul(argv[4], NULL, 16) if (!mmc) return 1 printf("\nMMC write: dev # %d, block # %d, count %d ... ", dev, blk, cnt) mmc_init(mmc) n = mmc->block_dev.block_write(dev, blk, cnt, addr) printf("%d blocks written: %s\n", n, (n == cnt) ? "OK" : "ERROR"); return (n == cnt) ? 0 : 1; } else if (strncmp(argv[1], "write.ext4sp", sizeof("write.ext4sp")) == 0) { #ifdef CONFIG_EXT4_SPARSE int dev = simple_strtoul(argv[2], NULL, 10); void *addr = (void *)simple_strtoul(argv[3], NULL, 16); u32 blk = simple_strtoul(argv[4], NULL, 16); u32 cnt = simple_strtoul(argv[5], NULL, 16); struct mmc *mmc = find_mmc_device(dev); if (!mmc) return 1; mmc_init(mmc); printf("\nMMC write ext4 sparse: dev # %d, " "block # %d, count %d ... \n", dev, blk, cnt); return ext4_unsparse(mmc, dev, addr, blk, cnt); #else printf("Not support.\n"); return 0; #endif }
|
可以查看EMMC有两种写法,一种是write,一种是write.ext4sp,两种写法一定要注意。
5. update.c编写
参考代码
6. tftp等待时间过长
在使用tftp中,如果网线没有插好,大约有10s等待时间,如果tftp服务器没有开启,大约有20s等待时间。考虑到如果网线没有插,优化10s等待时间意义不大,所以着重优化tftp等待时间。
通过代码追踪可以看到,阻塞主要发生在net/net.c中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| //----->net/net.c 307行 NetLoop(proto_t protocol) { …… …… // 347行,如果网线没有插上,主要阻塞在eth初始化上,可以优化net/net-drc.c上代码,但是意义不大 if (eth_init(bd) < 0) { eth_halt(); return(-1); } …… …… //384行,在TFTP中有一个数值TftpTimeoutCountMax和TftpTimeout,这两个函数有什么用呢,后续会用到 //TftpTimeoutCountMax,tftp失败尝试次数 //TftpTimeout,TFTP timeout处理函数,根据count值重新尝试TFTP switch (protocol) { case TFTP: /* always use ARP to get server ethernet address */ TftpStart(); break; …… } …… …… //487行,此处ARP发包用到了configs/hi3519v101.h中的 //#define CONFIG_ARP_TIMEOUT 50000UL //#define CONFIG_NET_RETRY_COUNT 50 //进行了50次,50000次电平的发包,当然会慢,注释了相关函数 ArpTimeoutCheck(); ……… ……… //493行,此处为TFTP time out后的处理 if (timeHandler && ((get_timer(0) - timeStart) > timeDelta)) { thand_f *x; ………… } …… …… }
|
所以,想要将时间变短,改变ArpTimeoutCheck()的等待时间即可。
修改在configs/hi3519v101.h中
7 Q&A
1. 每次重新烧写uboot,环境变量不变
env初始化有两个地方:
1 2
| 1. arm/lib/board.c中, init_sequence初始化序列中会初始化,但是此时存储类型还没有确定,初始化无用 2. arm/lib/board.c中433行env_relocate (),初始化存储以后,会根据存储设备,更新关于存储设备的初始化,擦写,读取等操作的动作。此刻再从存储设备中读取环境变量
|
在第二次调用的时候,会调用common/env_emmc.c中代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| void emmc_env_relocate_spec(void) { int ret; struct mmc *mmc = find_mmc_device(0);
if (!mmc) goto error;
mmc_init(mmc);
printf("offset :%x, size %x\n", CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE); ret = mmc->block_dev.block_read(0, CONFIG_ENV_OFFSET >> 9, CONFIG_ENV_SIZE >> 9, env_ptr);
flush_cache((ulong)env_ptr, CONFIG_ENV_SIZE);
if (ret != (CONFIG_ENV_SIZE >> 9)) goto error;
if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc) goto error;
gd->env_valid = 1;
return;
error: puts("*** Warning - bad CRC, using default environment\n\n");
set_default_env();
|
得到重要信息,存储在configs/hi3519v101.h中的定义:
1 2
| #define CONFIG_ENV_OFFSET 0x80000 #define CONFIG_ENV_SIZE 0x40000
|
可以知道,在uboot 1M的存储空间中,前512K用于存储uboot,后续512K用于存储环境变量。根据需求进行擦写
问题来了,如果没有tftp升级,那我们应该怎么做呢?哈哈。。有时间做到再说吧。大致在net中增加新的网络驱动,然后在main.c中增加对应代码即可。
8 测试
项目 |
uboot启动时间 |
网线不在 |
11+3+1+1 = 16s |
server IP不存在,TFTP服务未开启 |
2+3+1+1s =7s |
server IP存在,TFTP服务未开启 |
2+3+2+1s = 8s |
Server IP存在,TFTP服务开启 |
根据服务器启动时间 |
时间: A+B+C+D
- A —初始化与网络初始化
- B —arp寻找server
- C —tftp尝试
- D —bootdelay
9 patch包
源文件打包diff
1 2 3 4 5 6 7
| diff -Naur u-boot-2010.06 u-boot-2010.06-mh > update.patch
diff -Naur 源文件 更改文件 > patch名称 -N –new-file 在比较目录时,若文件A仅出现在某个目录中,会显示:Onlyin目录;文件A若使用-N参数,则diff会将文件A与一个空白的文件比较。 -a 将所有文件当作文本文件来处理 -u 以合并的方式来显示文件内容的不同。 -r 递归
|
atch添加
在源文件中,执行
1 2 3 4
| tar -xzvf u-boot-2010.06.tgz patch -p0 < update.patch
-pX 表示忽略多少文件夹
|