做一个“好”人... 思想上会思考; 生活上有追求; 技术上不停步; 工作上有担当;
分类: LINUX
2015-07-25 11:20:33
原文地址:U-boot中TFTP解释及nfs命令的小bug 作者:mutes
一、 概述
U-boot中的TFTP用于发送较小的文件。下层使用UDP协议,发送使用UDP 69端口,每次发送的最大分组为512 Bytes。发送双方采用超时重传机制。数据传输模式为octet模式( 二进制模式 )。
在U-boot中使用tftp功能可以使用如下命令:
tftp MemoryAddress FileName
其中MemoryAddress为放入文件的内存首地址,FileName为传送文件的文件名(文件需放在tftp默认目录下)。
二、 使用TFTP
使用TFTP传送文件的步骤:
1 使用时已经初始化以下变量:
NetOurIP (本机IP地址,定义在Net.c文件)
NetServerIP(TFTP服务器的IP地址,定义在Net.c文件)
BootFile (需要传送文件的文件名,定于在Tftp.c)
NetOurGatewayIP (本机的网关地址)
NetOurSubnetMask (本机子网掩码)
2 调用TftpStart 函数开始文件传送。
三、 TFTP实现
(1)客户端状态
TFTP使用一个变量TftpState来描述TFTP客户端可能的5种状态:
状态 |
说明 |
STATE_RRQ |
发出读取请求,正等待批准 |
STATE_DATA |
正在接收数据 |
STATE_TOO_LARGE |
文件太大 |
STATE_BAD_MAGIC |
Bad magic number |
STATE_OACK |
收到OACK帧 |
(2)包类型
使用TFTP传送的包分为6种类型:
类型 |
说明 |
TFTP_RRQ |
请求读取文件 |
TFTP_WRQ |
请求写文件 |
TFTP_DATA |
数据包 |
TFTP_ACK |
确认包 |
TFTP_ERROR |
包含出错信息的包 |
TFTP_OACK |
option acknowledge,当服务器超时未收到客户端的ACK帧时,主动发出一个OACK帧 |
(3)各种包结构
TFTP协议中各种包的结构:
图1.TFTP协议中包结构
具体到U-boot中客户端发送的各种包的结构:
RRQ(请求读的包的结构):
TFTP_RRQ(包的类型) |
请求文件名 |
0 |
“octet”(传输模式) |
0 |
“timeout”(超时重传机制) |
0 |
TIMEOUT(超时重传等待时间) |
ACK(确认帧)结构:
TFTP_ACK |
确认的包的编号 |
(4)主要函数及作用
U-boot中与tftp协议有关的文件有tftp.c与tftp.h文件,主要函数包括:
static __inline__ void
store_block (unsigned block, uchar * src, unsigned len);
/* 根据预先设定的地址load_addr决定将首地址为src,长度为len的block个数据块写入flash 或写入内存*/
static void
TftpSend (void) ;
/* 根据TftpState变量的不同值发送不同的 Tftp包 (使用UDP协议发送) */
static void
TftpHandler (uchar * pkt, unsigned dest, unsigned src, unsigned len) ;
/* 处理收到的Tftp包 */
static void
TftpTimeout (void);
/* 超时处理函数 */
void
TftpStart (void);
/* 初始化各个需要的数据 然后开始发送TFTP读请求 */
(5) 流程
TFTP总体流程:
首先客户端发出读写请求。如果服务器批准此请求,则打开连接,并第一个数据包。客户端收到数据包后发回确认,而服务器发出下一个数据包以前必须得到客户对上一个数据包的确认。如果数据包在传输过程中丢失,服务器方会在超时后重新传输最后一个未被确认的数据包。
发送的数据包一般为512字节,如果一个数据包小于 512 字节,则表示这个包是最后一个包,如果发送的数据正好是 512 的整数倍,发送完后再发一个空包。
客户端程序流程:
<1>客户端设定NetOurIP 、NetServerIP、BootFile 变量的值。
<2>调用TftpStart函数,TftpStart函数完成如下的6步工作:
1若BootFile为空,则使用默认文件名;否则使用BootFile为传送的文件名。
2比较客户端和服务器的子网地址,判断两者是否在同一个子网内。
3设定超时处理函数为TftpTimeout 。
4设置处理接收到的TFTP包的函数为TftpHandler。
5初始化服务器端口、本机端口、超时次数为0、初始状态为RRQ
6调用TftpSend函数发送TFTP读取请求。
<3> TftpSend函数根据TftpState 发送不同的包,同时TftpHandler函数根据包的类型和当前客户端的状态(TftpState) 处理接收到的包。
图2.TFTP流程
转自http://www.cnblogs.com/heaad/archive/2009/08/10/1542538.html
uboot1.1.6中nfs命令的小bug uboot1.1.6中的nfs命令不好使,里面有个小bug 表现为下载大文件时总是失败,经过和tftp命令的比较,这个小bug终于水落石出。 先跟踪下nfs数据包的接收情况 root@lzd-laptop:/home/lzd# tcpdump -r aaa reading from file aaa, link-type EN10MB (Ethernet) 15:16:44.290988 arp who-has lzd-laptop.local tell 10.107.4.146 15:16:45.100523 arp who-has 10.107.4.254 tell lzd-laptop.local 15:16:46.100539 arp who-has 10.107.4.254 tell lzd-laptop.local 15:16:47.100544 arp who-has 10.107.4.254 tell lzd-laptop.local 15:16:49.290554 arp who-has lzd-laptop.local tell 10.107.4.146 15:16:49.290609 arp reply lzd-laptop.local is-at 00:0b:db:98:a0:5d (oui Unknown) 15:16:49.290724 IP 10.107.4.146.1000 > lzd-laptop.local.sunrpc: UDP, length 56 15:16:49.291018 IP lzd-laptop.local.sunrpc > 10.107.4.146.1000: UDP, length 28 15:16:49.294441 IP 10.107.4.146.1000 > lzd-laptop.local.sunrpc: UDP, length 56 15:16:49.294657 IP lzd-laptop.local.sunrpc > 10.107.4.146.1000: UDP, length 28 15:16:49.298118 IP 10.107.4.146.1000 > lzd-laptop.local.54597: UDP, length 80 15:16:49.299982 IP lzd-laptop.local.54597 > 10.107.4.146.1000: UDP, length 60 15:16:49.303403 IP 10.107.4.146.4905 > lzd-laptop.local.nfs: 104 lookup [|nfs] 15:16:49.303528 IP lzd-laptop.local.nfs > 10.107.4.146.4905: reply ok 128 lookup [|nfs] 15:16:49.306989 IP 10.107.4.146.4906 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.307150 IP lzd-laptop.local.nfs > 10.107.4.146.4906: reply ok 1124 read 15:16:49.309664 IP 10.107.4.146.4907 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.309846 IP lzd-laptop.local.nfs > 10.107.4.146.4907: reply ok 1124 read 15:16:49.312293 IP 10.107.4.146.4908 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.312515 IP lzd-laptop.local.nfs > 10.107.4.146.4908: reply ok 1124 read 15:16:49.314964 IP 10.107.4.146.4909 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.315129 IP lzd-laptop.local.nfs > 10.107.4.146.4909: reply ok 1124 read 15:16:49.317634 IP 10.107.4.146.4910 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.317763 IP lzd-laptop.local.nfs > 10.107.4.146.4910: reply ok 1124 read 15:16:49.320265 IP 10.107.4.146.4911 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.320330 IP lzd-laptop.local.nfs > 10.107.4.146.4911: reply ok 1124 read 15:16:49.322834 IP 10.107.4.146.4912 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.323146 IP lzd-laptop.local.nfs > 10.107.4.146.4912: reply ok 1124 read 15:16:49.325599 IP 10.107.4.146.4913 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.325757 IP lzd-laptop.local.nfs > 10.107.4.146.4913: reply ok 1124 read 15:16:49.328209 IP 10.107.4.146.4914 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.328372 IP lzd-laptop.local.nfs > 10.107.4.146.4914: reply ok 1124 read 15:16:49.330837 IP 10.107.4.146.4915 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.331053 IP lzd-laptop.local.nfs > 10.107.4.146.4915: reply ok 1124 read 15:16:49.333506 IP 10.107.4.146.4916 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.333696 IP lzd-laptop.local.nfs > 10.107.4.146.4916: reply ok 1124 read 15:16:49.336198 IP 10.107.4.146.4917 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.336364 IP lzd-laptop.local.nfs > 10.107.4.146.4917: reply ok 1124 read 15:16:49.338810 IP 10.107.4.146.4918 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.338934 IP lzd-laptop.local.nfs > 10.107.4.146.4918: reply ok 1124 read 15:16:49.341381 IP 10.107.4.146.4919 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.341540 IP lzd-laptop.local.nfs > 10.107.4.146.4919: reply ok 1124 read 15:16:49.343983 IP 10.107.4.146.4920 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.344074 IP lzd-laptop.local.nfs > 10.107.4.146.4920: reply ok 1124 read 15:16:49.346526 IP 10.107.4.146.4921 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.346658 IP lzd-laptop.local.nfs > 10.107.4.146.4921: reply ok 1124 read 15:16:49.349168 IP 10.107.4.146.4922 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.349697 IP lzd-laptop.local.nfs > 10.107.4.146.4922: reply ok 1124 read 15:16:49.352145 IP 10.107.4.146.4923 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.352310 IP lzd-laptop.local.nfs > 10.107.4.146.4923: reply ok 1124 read 15:16:49.354758 IP 10.107.4.146.4924 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.354917 IP lzd-laptop.local.nfs > 10.107.4.146.4924: reply ok 1124 read 15:16:49.357364 IP 10.107.4.146.4925 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.357509 IP lzd-laptop.local.nfs > 10.107.4.146.4925: reply ok 1124 read 15:16:49.359962 IP 10.107.4.146.4926 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.360090 IP lzd-laptop.local.nfs > 10.107.4.146.4926: reply ok 1124 read 15:16:49.362596 IP 10.107.4.146.4927 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.362740 IP lzd-laptop.local.nfs > 10.107.4.146.4927: reply ok 1124 read 15:16:49.365238 IP 10.107.4.146.4928 > lzd-laptop.local.nfs: 104 read [|nfs] 15:16:49.365423 IP lzd-laptop.local.nfs > 10.107.4.146.4928: reply ok 1124 read 这里表示我的主机已经给dm9000一个1124大小的包 15:16:50.104534 arp who-has 10.107.4.254 tell lzd-laptop.local 15:16:51.104547 arp who-has 10.107.4.254 tell lzd-laptop.local 15:16:52.104530 arp who-has 10.107.4.254 tell lzd-laptop.local 15:16:54.288529 arp who-has 10.107.4.146 tell lzd-laptop.local 15:16:55.108525 arp who-has 10.107.4.254 tell lzd-laptop.local 15:16:55.288543 arp who-has 10.107.4.146 tell lzd-laptop.local 15:16:56.108530 arp who-has 10.107.4.254 tell lzd-laptop.local 15:16:56.288526 arp who-has 10.107.4.146 tell lzd-laptop.local 15:16:57.108530 arp who-has 10.107.4.254 tell lzd-laptop.local root@lzd-laptop:/home/lzd# 我相信自己的笔记本确实发送了这个包,但是打开dm9000的调试,发现 dm9000发送了请求后,就一直没有得到这个包,所以可以肯定是dm9000丢失了这个包, 起初以为是nfs传输的文件块比较大的原因,看了dm9000a的手册,发现不可能,因为他的 环形接收缓冲区有13k,什么包都能接收了。在跟tftp命令作个比较,发现了答案。 tftp命令也会丢失数据包,但是在等待一段 时间后,它就再次发送请求,要求得到刚才的那个 数据包,也就是重传机制。但是nfs却没有这样作。下面的函数说明了这点区别。 这个是tftp的超时处理函数 static void TftpTimeout (void) { if (++TftpTimeoutCount > TIMEOUT_COUNT) { puts ("\nRetry count exceeded; starting again\n"); NetStartAgain (); } else { puts ("T "); NetSetTimeout (TIMEOUT * CFG_HZ, TftpTimeout); 重设超时时间 TftpSend (); 这里重新请求发送了 } } 这个是nfs的超时处理函数 static void NfsTimeout (void) { puts ("Timeout\n"); NetState = NETLOOP_FAIL; 只要超时,得不到包,就直接死掉了 return; } 如下更改后就ok了。 #if 0 static void NfsTimeout (void) { puts ("Timeout\n"); NetState = NETLOOP_FAIL; return; } #endif NfsTimeout (void) { puts ("Timeout,try again"); NfsSend (); } 下面是验证: lzd> tftp 0x30000000 image dm9000 i/o: 0x20000300, id: 0x90000a46 MAC: 00:80:00:80:00:80 TFTP from server 10.107.4.145; our IP address is 10.107.4.146 Filename 'image'. Load address: 0x30000000 Loading: T ################################################################# ################T ########T ######################################### ################################################################# ################################################################# ########T T ################################### done Bytes transferred = 1546996 (179af4 hex) lzd> nfs 0x32000000 /home/lzd/nfs/image dm9000 i/o: 0x20000300, id: 0x90000a46 MAC: 00:80:00:80:00:80 File transfer via NFS from server 10.107.4.145; our IP address is 10.107.4.146 Filename '/home/lzd/nfs/image'. Load address: 0x32000000 Loading: ################################################################# ####################Timeout,try again############################################# #####################################Timeout,try again############################ ################################################################# ###########################################Timeout,try again*** ERROR: Cannot umount lzd> cmp 0x30000000 0x32000000 0x179af0 word at 0x30179af4 (0x33f4fca8) != word at 0x32179af4 (0x00000000) Total of 386749 words were the same lzd> 最后面的ERROR: 提示是因为我的主机一直不停的在向网关发送arp包导致的,当我把网关的ip改成空后,就没有问题了。 lzd> nfs 0x32000000 /home/lzd/nfs/image dm9000 i/o: 0x20000300, id: 0x90000a46 MAC: 00:80:00:80:00:80 File transfer via NFS from server 10.107.4.145; our IP address is 10.107.4.146 Filename '/home/lzd/nfs/image'. Load address: 0x32000000 Loading: ############Timeout,try again##Timeout,try again#########################################Timeout,try again####### ################################################################# ################################################################# ################################################################# ############Timeout,try again###Timeout,try again############################ done Bytes transferred = 1546996 (179af4 hex) lzd> 下面是关于nfs的基础知识: NFS请求和响应 SunNFS(网络文件系统)的请求和响应显示格式是: src.xid>dst.nfs:lenopargs src.nfs>dst.xid:replystatlenopresults sushi.6709>wrl.nfs:112readlinkfh21,24/10.73165 wrl.nfs>sushi.6709:replyok40readlink"../var" sushi.201b>wrl.nfs: 144lookupfh9,74/4096.6878"xcolors" wrl.nfs>sushi.201b: replyok128lookupfh9,74/4134.3150 在第一行,主机sushi向wrl发送号码为6709的交易会话(注意源主机后面的数字是交易号,不是端口).这项请求长112字节,不包括UDP和IP报 头.在文件句柄(fh)21,24/10.731657119上执行readlink(读取符号连接)操作.(如果运气不错,就象这种情况,文件句柄可以依次翻译成 主次设备号,i节点号,和事件号(generationnumber).)Wrl回答`ok'和连接的内容. 在第三行,sushi请求wrl在目录文件9,74/4096.6878中查找`xcolors'.注意数据的打印格式取决于操作类型.格式应该是可以自我说明的. 给出-v(verbose)选项可以显示附加信息.例如: sushi.1372a>wrl.nfs: 148readfh21,11/12.1958192bytes@24576 wrl.nfs>sushi.1372a: replyok1472readREG100664ids417/0sz29388 (- v同时使它显示IP报头的TTL,ID,和分片域,在这个例子里把它们省略了.)在第一行,sushi请求wrl从文件21,11/12.195的偏移位置24576 开始,读取8192字节.Wrl回答`ok';第二行显示的报文是应答的第一个分片,因此只有1472字节(其余数据在后续的分片中传过来, 但由于这些分片 里没有NFS甚至UDP报头,因此根据所使用的过滤器表达式,有可能不显示).-v选项还会显示一些文件属性(它们作为文件数据的附带部分传回来):文 件类型(普通文件``REG''),存取模式(八进制数),uid和gid,以及文件大小. 如果再给一个-v选项(-vv),还能显示更多的细节. 注意NFS请求的数据量非常大,除非增加snaplen,否则很多细节无法显示.试一试`-s192'选项. NFS应答报文没有明确标明RPC操作.因此tcpdump保留有``近来的''请求记录,根据交易号匹配应答报文.如果应答报文没有相应的请求报文,它就 无法分析. -s 从每个报文中截取snaplen字节的数据,而不是缺省的68(如果是SunOS的NIT,最小值是96).68个字节适用于 IP,ICMP,TCP和 UDP,但是有可能截掉名字服务器和NFS报文的协议信息(见下面).输出时如果指定``[|proto]'',tcpdump可以指出那些捕捉量过小的 数据报,这里的proto是截断发生处的协议层名称.注意,采用更大的捕捉范围既增加了处理报文的时间,又相应的减少了报文的缓冲数量,可能导致报文的丢 失.你应该把snaplen设的尽量小,只要能够容纳你需要的协议信息就可以了. 第三阶段:启动完全,开始运行 1. main_loop()在common/main.c中 1.1 读命令readline() 由于getc最终会调用设备读接口[stdio_devices[file]->getc ()],因此此前将控制台适配为哪种设备,则现在就是和哪种设备通信; 1.2 执行命令run_command() 各 种命令通过U_BOOT_CMD宏被链接器搜集到.u_boot_cmd所在的__u_boot_cmd_start和 __u_boot_cmd_end区间中;具体参考include/comman.h和board/davinci/dm365/u-boot.lds; Find_cmd中,从__u_boot_cmd_start开始查找对应命令结构体变量;如果是tftp等网络命令,则执行 do_tftpb()->netboot_common()->NetLoop(); 2. netloop() 在for (;;)循环中使用eth_rx()不断接收数据,同时通过一状态机决定是否处理完毕; 3. eth_rx() 这个函数实际上实现了数据由网络到上层协议栈再到应用层解析的整个过程,应用解析后会更改状态机状态; 对 于tftp则是在tftp_start时由于利用NetSetHandler()指定了全局量packetHandler为TftpHandler;因此 根据如下调用关系eth_rx()->net_receive()->packetHandler(),可知数据被tftphandler 处理并修改Netstate状态值; 4. nand 对于nand的write操作稍需注意,由于nand是按“与”操作才能 写 入,也就是擦除后,写入前nand中的bit值必须是1。这就意味着所写如果是 1,将不使nand的bit改变。在uboot nand的驱动中对于oob的fill也正是利用了这一点。看似将memory中的oob数据写到了nand oob中,但由于memory中的oob各bit是1,因此对nand oob值不产生影响。 第四阶段:U-boot引导内核 1. go命令的实现 go命令调用 common/cmd_boot.c下do_go()函数,跳转到某个地址执行。如果在这个地址准备好了自引导的内核映像,就可以启动了。尽管go命令可以带变参,实际使用时一般不用来传递参数。 2. bootm命令的实现 bootm命令调用 common/cmd_bootm.c下的do_bootm函数,这个函数专门用来引导各种操作系统,可以支持引导Linux、vxworks等操作系统。引导Linux时,调用do_boom_linux()函数。 3. do_bootm_linux函数的实现 do_bootm_linux函数在lib_arm/bootm.c下,是专门引导Linux映像的函数,它还可以处理ramdisk文件系统的映像。 |