UBOOT阶段实现网络命令通信

1、对ping命令分析

1.1、基本流程

uboot中存在命令ping,在uboot当中运行该命令,通过wireshark进行抓包,可以直观的看到以下内容。

image-20200728095902274

TI的IP为172.16.89.4

VM的IP为172.16.89.2

  • 1、TI发送广播,寻求IP为172.16.89.2的设备
  • 2、VM返回信息,告知TI自己的mac地址
  • 3、TI发送ICMP包,请求返回信息
  • 4、VM接收ICMP包,返回信息

以上完成了一次完整的ping命令,通过对以上步骤的分析,完成uboot对于网络包的解析流程分析。

static int do_ping(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	if (argc < 2)
		return -1;

	NetPingIP = string_to_ip(argv[1]);
	if (NetPingIP == 0)
		return CMD_RET_USAGE;

	if (NetLoop(PING) < 0) {
		printf("ping failed; host %s is not alive\n", argv[1]);
		return 1;
	}

	printf("host %s is alive\n", argv[1]);

	return 0;
}

do_ping完成了从命令深入net.c的任务,通过进入NetLoop,完成关于网络的一系列数据处理,一般关于网络传输的命令都是由NetLoop完成。

下面将分析NetLoop如何执行。

int NetLoop(enum proto_t protocol)
{
	bd_t *bd = gd->bd;
	int ret = -1;
	NetRestarted = 0;
	NetDevExists = 0;
	NetTryCount = 1;
	debug_cond(DEBUG_INT_STATE, "--- NetLoop Entry\n");
	bootstage_mark_name(BOOTSTAGE_ID_ETH_START, "eth_start");
	net_init();
	if (eth_is_on_demand_init() || protocol != NETCONS) {
		eth_halt();
		eth_set_current();
		if (eth_init(bd) < 0) {
			eth_halt();
			return -1;
		}
	} else
		eth_init_state_only(bd);

restart:
	net_set_state(NETLOOP_CONTINUE);

	/*
	 *	Start the ball rolling with the given start function.  From
	 *	here on, this code is a state machine driven by received
	 *	packets and timer events.
	 */
	debug_cond(DEBUG_INT_STATE, "--- NetLoop Init\n");
	NetInitLoop();
//前期进行了一系列的初始化和状态的设置
    switch (net_check_prereq(protocol)) {//此处检查当前网络是否准备好
	case 1:
		/* network not configured */
		eth_halt();
		return -1;
	case 2:
		/* network device not configured */
		break;
	case 0:
		NetDevExists = 1;
		NetBootFileXferSize = 0;
		switch (protocol) {//此处判断要实现的功能
		case TFTPGET:
		case TFTPPUT:
			/* always use ARP to get server ethernet address */
			TftpStart(protocol);
			break;
...
#if defined(CONFIG_CMD_PING)
		case PING:
			ping_start();//本次主要研究对象,后面展开讲述。
			break;
#endif
···
		}
···
	}
···
	for (;;) {
		WATCHDOG_RESET();
#ifdef CONFIG_SHOW_ACTIVITY
		show_activity(1);
#endif
···
		eth_rx();//读取新的packet
···
		if (ctrlc()) {
···
		}
		ArpTimeoutCheck();//检查是否超时,若未超时则对ARP进行响应
		if (timeHandler && ((get_timer(0) - timeStart) > timeDelta)) {
			thand_f *x;
···
			debug_cond(DEBUG_INT_STATE, "--- NetLoop timeout\n");
			x = timeHandler;//此处对arp响应超时进行处理
			timeHandler = (thand_f *)0;
			(*x)();
		}
		switch (net_state) {//判断当前状态
		case NETLOOP_RESTART:
···
		case NETLOOP_SUCCESS:
···
		case NETLOOP_FAIL:
···
		case NETLOOP_CONTINUE:
			continue;
		}
	}
done:
#ifdef CONFIG_CMD_TFTPPUT
	/* Clear out the handlers */
	net_set_udp_handler(NULL);//清空UDP协议处理函数
	net_set_icmp_handler(NULL);//清空ICMP协议处理函数
#endif
	return ret;
}

以上完成了NetLoop到ping_start的任务,完成ping_start之后,再进入到for循环,不断接受数据。

void ping_start(void)
{
	printf("Using %s device\n", eth_get_name());//当前使用的设备
	NetSetTimeout(10000UL, ping_timeout);//设置超时时间
	ping_send();//发送数据
}

以上完成了一些简单的任务,主要任务还是由ping_send来执行。

static int ping_send(void)
{
	uchar *pkt;
	int eth_hdr_size;
	/* XXX always send arp request */
	debug_cond(DEBUG_DEV_PKT, "sending ARP for %pI4\n", &NetPingIP);
	NetArpWaitPacketIP = NetPingIP;//此处为全局变量,在执行ping命令阶段即已赋值。
	eth_hdr_size = NetSetEther(NetTxPacket, NetEtherNullAddr, PROT_IP);//获取大小
	pkt = (uchar *)NetTxPacket + eth_hdr_size;//设置pkt

	set_icmp_header(pkt, NetPingIP);//为pkt添加头

	/* size of the waiting packet */
	NetArpWaitTxPacketSize = eth_hdr_size + IP_ICMP_HDR_SIZE;//等待的packet大小

	/* and do the ARP request */
	NetArpWaitTry = 1;
	NetArpWaitTimerStart = get_timer(0);
	ArpRequest();//arp响应
	return 1;	/* waiting 如果没有及时响应,会交由NetLoop执行响应*/
}

1.2、arp响应

​ 关于NetLoop响应问题,当执行完ping_send之后,如果arp没有相应,那么包是没有发出去的,就必须依赖于NetLoop进行循环等待arp响应,这样我们才能够得到IP和mac地址,才能够发送数据,下面我们详细分析,arp响应是如何被捕捉到,并且将包发出去的。

主要在以下三个函数当中,在NetLoop中,有一个ArpTimeoutCheck函数,他在不断地检测是否arp响应超时,在规定时间内还未收到arp回应,则执行ArpRequest,然后执行arp_raw_request,再次将arp数据包发出,即NetSendPacket,等待响应。
以下就是关于超时的检测,其中一个比较重要的变量NetArpWaitPacketIP,这个变量是从何处确定的呢?可以看到当NetArpWaitPacketIP=0时,是会直接返回的。该变量在ping_start时就已经设置,如果未设置,是不会等待arp响应的。

void ArpTimeoutCheck(void)
{
	ulong t;

	if (!NetArpWaitPacketIP)//是否在等待arp
		return;

	t = get_timer(0);

	/* check for arp timeout */
	if ((t - NetArpWaitTimerStart) > ARP_TIMEOUT) {
		NetArpWaitTry++;

		if (NetArpWaitTry >= ARP_TIMEOUT_COUNT) {
			puts("\nARP Retry count exceeded; starting again\n");
			NetArpWaitTry = 0;
			NetStartAgain();
		} else {
			NetArpWaitTimerStart = t;
			ArpRequest();
		}
	}
}

以下两个函数暂时不做详细分析。

void arp_raw_request(IPaddr_t sourceIP, const uchar *targetEther,
	IPaddr_t targetIP)
{
	uchar *pkt;
	struct arp_hdr *arp;
	int eth_hdr_size;

	debug_cond(DEBUG_DEV_PKT, "ARP broadcast %d\n", NetArpWaitTry);

	pkt = NetArpTxPacket;

	eth_hdr_size = NetSetEther(pkt, NetBcastAddr, PROT_ARP);
	pkt += eth_hdr_size;

	arp = (struct arp_hdr *) pkt;

	arp->ar_hrd = htons(ARP_ETHER);
	arp->ar_pro = htons(PROT_IP);
	arp->ar_hln = ARP_HLEN;
	arp->ar_pln = ARP_PLEN;
	arp->ar_op = htons(ARPOP_REQUEST);

	memcpy(&arp->ar_sha, NetOurEther, ARP_HLEN);	/* source ET addr */
	NetWriteIP(&arp->ar_spa, sourceIP);		/* source IP addr */
	memcpy(&arp->ar_tha, targetEther, ARP_HLEN);	/* target ET addr */
	NetWriteIP(&arp->ar_tpa, targetIP);		/* target IP addr */

	NetSendPacket(NetArpTxPacket, eth_hdr_size + ARP_HDR_SIZE);//发送arp包
}

void ArpRequest(void)
{
	if ((NetArpWaitPacketIP & NetOurSubnetMask) !=
	    (NetOurIP & NetOurSubnetMask)) {
		if (NetOurGatewayIP == 0) {
			puts("## Warning: gatewayip needed but not set\n");
			NetArpWaitReplyIP = NetArpWaitPacketIP;
		} else {
			NetArpWaitReplyIP = NetOurGatewayIP;
		}
	} else {
		NetArpWaitReplyIP = NetArpWaitPacketIP;
	}

	arp_raw_request(NetOurIP, NetEtherNullAddr, NetArpWaitReplyIP);
}

1.3、arp接收以及发送ICMP

arp的接收在NetLoop当中进行,主要是调用驱动函数的接口来接收,然后驱动当中的recv函数调用了NetReceive。在NetReceive当中,会对当前接收到的类型进行判断,如果是arp,则执行相应的函数,对arp进行处理。

NetReceive(uchar *inpkt, int len)
{
···
	switch (eth_proto) {
	case PROT_ARP:
		ArpReceive(et, ip, len);
		break;  
    }
}

在ArpReceive处理函数中会对发过来的包进行处理,然后将之前的需要发的数据发送出去。

void ArpReceive(struct ethernet_hdr *et, struct ip_udp_hdr *ip, int len)
{
···
	switch (ntohs(arp->ar_op)) {//对收到的数据进行处理
	case ARPOP_REQUEST://如果是获得的请求,则返回我们的IP以及mac
···
		NetSendPacket((uchar *)et, eth_hdr_size + ARP_HDR_SIZE);
		return;

	case ARPOP_REPLY:		/* arp reply */
···//如果收到的是响应,则发送我们之前计划发送的packet
			memcpy(((struct ethernet_hdr *)NetTxPacket)->et_dest,
				&arp->ar_sha, ARP_HLEN);
			NetSendPacket(NetTxPacket, NetArpWaitTxPacketSize);
		···
		return;
	default:
···
		return;
	}
}

上面提到收到arp响应之后,将我们之前做好的packet发送出去,这个packet在ping_start()->ping_send()时就已经准备好了。以上完成arp接收,并且响应,发出ICMP的包,下一步是接收PC返回的ICMP

1.4、接收ICMP

还是之前提到的NetReceive函数,在NetLoop函数中不断的循环接收,也就不断地执行NetReceive函数,直到NetLoop当中状态变量改变,跳出该循环。

NetReceive(uchar *inpkt, int len)
{
···
	switch (eth_proto) {
case PROT_IP:
		···
		···
		···
		if (ip->ip_p == IPPROTO_ICMP) {
			receive_icmp(ip, len, src_ip, et);
			return;
		}···
		break;
    }
}

这里只取该部分代码进行分析。

static void receive_icmp(struct ip_udp_hdr *ip, int len,
			IPaddr_t src_ip, struct ethernet_hdr *et)
{
	struct icmp_hdr *icmph = (struct icmp_hdr *)&ip->udp_src;

	switch (icmph->type) {//对icmp包进行分析
	case ICMP_REDIRECT:
		if (icmph->code != ICMP_REDIR_HOST)
			return;
		printf(" ICMP Host Redirect to %pI4 ",
			&icmph->un.gateway);
		break;
	default:
#if defined(CONFIG_CMD_PING)
		ping_receive(et, ip, len);//此处对ping命令进行处理,是结束循环的关键。
#endif
···
		break;
	}
}
void ping_receive(struct ethernet_hdr *et, struct ip_udp_hdr *ip, int len)
{
	struct icmp_hdr *icmph = (struct icmp_hdr *)&ip->udp_src;
	IPaddr_t src_ip;
	int eth_hdr_size;

	switch (icmph->type) {
	case ICMP_ECHO_REPLY://此处是接收到PC的reply,
		src_ip = NetReadIP((void *)&ip->ip_src);
		if (src_ip == NetPingIP)
			net_set_state(NETLOOP_SUCCESS);//设置状态为成功,结束循环
		return;
	case ICMP_ECHO_REQUEST://此处为接收到PC的请求ICMP,则发送ICMP,等待返回
//此处是PC主动ping通开发板的关键
		eth_hdr_size = net_update_ether(et, et->et_src, PROT_IP);
		debug_cond(DEBUG_DEV_PKT, "Got ICMP ECHO REQUEST, return "
			"%d bytes\n", eth_hdr_size + len);
···
		NetSendPacket((uchar *)et, eth_hdr_size + len);
		return;
/*	default:
		return;*/
	}
}

以上的分析是完成添加新命令get_netcmd的关键。

2、添加新命令get_netcmd

2.1、为什么添加该命令

当uboot进入命令行之后,我们会发现,PC对uboot发送ping命令是没有响应的,经过第1节的分析,我们也看到了uboot是有相应的响应机制的,只是没有运行起来,所以就需要我们添加一条命令,跳入的NetLoop当中,让uboot循环的去接收数据,然后根据相应的机制返回数据。

添加完该命令后效果如下:

image-20200728100209060

  • PC广播寻找172.16.89.4
  • TI返回arp
  • PC发送ICMP
  • TI响应ICMP

另外的,添加该命令可以实现陷入循环接收网络数据的状态,可以接收来自网络的命令,以此来实现PC与UBOOT的通信,发送特定命令,执行我们想要的任务。

2.2、如何开始命令、如何结束命令

如何开始命令比较简单,开始命令有bootcmd进行处理,我们需要提供相应的命令接口即可。

static int get_netcmd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
		if (NetLoop(ARP) < 0) {
					return 1;
						}
			return 0;
}
U_BOOT_CMD(
			get_netcmd,	1,	1,	get_netcmd,
				"get UDP message from network host",
					"get tftp ok cmd"
		);

可以看到get_netcmd命令非常简单,就是利用NetLoop接口进入到接收模式当中,其中传入的参数为ARP,也就是进入接收ARP的模式,虽然是接收ARP的模式,同时也是可以接收其他类型的数据的,主要依赖于handler函数。

如何结束命令,结束命令有两种方式,一种是接收到我们想要的数据,结束命令,另一种是接收时长到了,结束命令。

关于接收到想要的数据结束命令,从下一小节中进行分析,本小节先分析接收时长到了的情况。

在NetLoop当中添加判断,如果未接到我们想要的数据,并且超过时长,直接设置状态为失败,跳出该循环。这就完成了该命令的超时结束。

if(protocol==ARP&&strcmp(s, "ok")){
    timeb=get_timer(0);
    if(timeb-timea>GET_NETCMD_TIME){
        printf("\n not get net cmd \n");
        net_set_state(NETLOOP_FAIL);
    }
}

2.3、如何解析收到的数据

关于解析收到的数据,主要用于处理数据,判断是否是我们设定的特殊的命令,如果是特殊命令,则设置相应的环境变量,供bootcmd使用。以下就是解析pkt包的函数,经过一系列的协议处理之后,在该函数中pkt仅剩下了需要的数据,这些数据就可以用来设置环境变量,环境变量用于启动。

static void dummy_handler(uchar *pkt, unsigned dport,
			IPaddr_t sip, unsigned sport,
			unsigned len)
{
//此处对接受的UDP数据处理,设置环境变量供启动脚本使用
//	printf("received UDP (from=%pI4, len=%d)\n",&sip, len);
//	printf("pkt=%s",pkt);
	if(!strcmp(pkt, "hello\n")){
		setenv("tftp_status", "ok");
		printf("now run netboot \n");
		net_set_state(NETLOOP_SUCCESS);
	}else if(!strcmp(pkt, "PRELOADER\n")){
		setenv("tftp_preloader","ok");
		printf("now load preloader \n");
		net_set_state(NETLOOP_SUCCESS);
	}else if(!strcmp(pkt, "UBOOT\n")){
		setenv("tftp_uboot","ok");
		printf("now load uboot \n");
		net_set_state(NETLOOP_SUCCESS);
	}else if(!strcmp(pkt, "CPU0\n")){
		setenv("tftp_cpu0","ok");
		printf("now load cpu0 \n");
		net_set_state(NETLOOP_SUCCESS);
	}else if(!strcmp(pkt, "CPU1\n")){
		setenv("tftp_cpu1","ok");
		printf("now load cpu1 \n");
		net_set_state(NETLOOP_SUCCESS);
	}else if(!strcmp(pkt, "FPGA\n")){
		setenv("tftp_fpga","ok");
		printf("now load fpga \n");
		net_set_state(NETLOOP_SUCCESS);
	}else{
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔通天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值