Uboot的网络
7. net
uboot 支持 tcp/ip 网络协议,但是作为一个 bootloader 它并没有把协议栈作为一个后台线程长时间运行,而是在使用到网络功能时才会初始化协议栈、使用网络功能。
7.1. 协议栈主循环
net_loop
(net/net.c
) 是网络协议栈的主循环,所有的网络操作最终都会进入这里。
int net_loop(enum proto_t protocol)
{
...
if (eth_is_on_demand_init() || protocol != NETCONS) {
eth_halt();
eth_set_current();
ret = eth_init();
if (ret < 0) {
eth_halt();
return ret;
}
} else {
eth_init_state_only();
}
restart:
...
switch (net_check_prereq(protocol)) {
case 1:
/* network not configured */
eth_halt();
return -ENODEV;
case 2:
/* network device not configured */
break;
case 0:
net_dev_exists = 1;
net_boot_file_size = 0;
switch (protocol) {
case TFTPGET:
#ifdef CONFIG_CMD_TFTPPUT
case TFTPPUT:
#endif
/* always use ARP to get server ethernet address */
tftp_start(protocol);
break;
#ifdef CONFIG_CMD_TFTPSRV
case TFTPSRV:
tftp_start_server();
break;
#endif
#if defined(CONFIG_CMD_DHCP)
case DHCP:
bootp_reset();
net_ip.s_addr = 0;
dhcp_request(); /* Basically same as BOOTP */
break;
#endif
case BOOTP:
bootp_reset();
net_ip.s_addr = 0;
bootp_request();
break;
#if defined(CONFIG_CMD_RARP)
case RARP:
rarp_try = 0;
net_ip.s_addr = 0;
rarp_request();
break;
#endif
#if defined(CONFIG_CMD_PING)
case PING:
ping_start();
break;
#endif
#if defined(CONFIG_CMD_NFS)
case NFS:
nfs_start();
break;
#endif
#if defined(CONFIG_CMD_CDP)
case CDP:
cdp_start();
break;
#endif
#if defined(CONFIG_NETCONSOLE) && !(CONFIG_SPL_BUILD)
case NETCONS:
nc_start();
break;
#endif
#if defined(CONFIG_CMD_SNTP)
case SNTP:
sntp_start();
break;
#endif
#if defined(CONFIG_CMD_DNS)
case DNS:
dns_start();
break;
#endif
#if defined(CONFIG_CMD_LINK_LOCAL)
case LINKLOCAL:
link_local_start();
break;
#endif
default:
break;
}
break;
}
#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII)
#if defined(CONFIG_SYS_FAULT_ECHO_LINK_DOWN) && \
defined(CONFIG_STATUS_LED) && \
defined(STATUS_LED_RED)
/*
* Echo the inverted link state to the fault LED.
*/
if (miiphy_link(eth_get_dev()->name, CONFIG_SYS_FAULT_MII_ADDR))
status_led_set(STATUS_LED_RED, STATUS_LED_OFF);
else
status_led_set(STATUS_LED_RED, STATUS_LED_ON);
#endif /* CONFIG_SYS_FAULT_ECHO_LINK_DOWN, ... */
#endif /* CONFIG_MII, ... */
#ifdef CONFIG_USB_KEYBOARD
net_busy_flag = 1;
#endif
/*
* Main packet reception loop. Loop receiving packets until
* someone sets `net_state' to a state that terminates.
*/
for (;;) {
WATCHDOG_RESET();
#ifdef CONFIG_SHOW_ACTIVITY
show_activity(1);
#endif
if (arp_timeout_check() > 0)
time_start = get_timer(0);
/*
* Check the ethernet for a new packet. The ethernet
* receive routine will process it.
* Most drivers return the most recent packet size, but not
* errors that may have happened.
*/
eth_rx();
/*
* Abort if ctrl-c was pressed.
*/
if (ctrlc()) {
/* cancel any ARP that may not have completed */
net_arp_wait_packet_ip.s_addr = 0;
net_cleanup_loop();
eth_halt();
/* Invalidate the last protocol */
eth_set_last_protocol(BOOTP);
puts("\nAbort\n");
/* include a debug print as well incase the debug
messages are directed to stderr */
debug_cond(DEBUG_INT_STATE, "--- net_loop Abort!\n");
ret = -EINTR;
goto done;
}
/*
* Check for a timeout, and run the timeout handler
* if we have one.
*/
if (time_handler &&
((get_timer(0) - time_start) > time_delta)) {
thand_f *x;
#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII)
#if defined(CONFIG_SYS_FAULT_ECHO_LINK_DOWN) && \
defined(CONFIG_STATUS_LED) && \
defined(STATUS_LED_RED)
/*
* Echo the inverted link state to the fault LED.
*/
if (miiphy_link(eth_get_dev()->name,
CONFIG_SYS_FAULT_MII_ADDR))
status_led_set(STATUS_LED_RED, STATUS_LED_OFF);
else
status_led_set(STATUS_LED_RED, STATUS_LED_ON);
#endif /* CONFIG_SYS_FAULT_ECHO_LINK_DOWN, ... */
#endif /* CONFIG_MII, ... */
debug_cond(DEBUG_INT_STATE, "--- net_loop timeout\n");
x = time_handler;
time_handler = (thand_f *)0;
(*x)();
}
if (net_state == NETLOOP_FAIL)
ret = net_start_again();
switch (net_state) {
case NETLOOP_RESTART:
net_restarted = 1;
goto restart;
case NETLOOP_SUCCESS:
net_cleanup_loop();
if (net_boot_file_size > 0) {
printf("Bytes transferred = %d (%x hex)\n",
net_boot_file_size, net_boot_file_size);
setenv_hex("filesize", net_boot_file_size);
setenv_hex("fileaddr", load_addr);
}
if (protocol != NETCONS)
eth_halt();
else
eth_halt_state_only();
eth_set_last_protocol(protocol);
ret = net_boot_file_size;
debug_cond(DEBUG_INT_STATE, "--- net_loop Success!\n");
goto done;
case NETLOOP_FAIL:
net_cleanup_loop();
/* Invalidate the last protocol */
eth_set_last_protocol(BOOTP);
debug_cond(DEBUG_INT_STATE, "--- net_loop Fail!\n");
goto done;
case NETLOOP_CONTINUE:
continue;
}
}
done:
#ifdef CONFIG_USB_KEYBOARD
net_busy_flag = 0;
#endif
#ifdef CONFIG_CMD_TFTPPUT
/* Clear out the handlers */
net_set_udp_handler(NULL);
net_set_icmp_handler(NULL);
#endif
return ret;
}
从上面的代码可以看出 uboot 网络处里流程和一般的协议栈类似(如 lwip 所有的操作都是放在一个循环,在一个线程中运行)。
流程可以分解为 3 步:
- 初始化协议栈
net_init(); net_init_loop();
- 区分不同的子协议(如 ping 、 ICMP 、 arp 等)
case DHCP: ... case PING: ... case DNS: ... ...
- 关闭协议栈和网络设备
... eth_halt(); ... net_cleanup_loop(); ...
7.2. 协议栈和驱动
协议栈收发数据都是通过函数 eth_send()
和 eth_rx()
完成的。
发包:
int eth_send(void *packet, int length)
{
...
current = eth_get_dev();
...
ret = eth_get_ops(current)->send(current, packet, length);
if (ret < 0) {
/* We cannot completely return the error at present */
debug("%s: send() returned error %d\n", __func__, ret);
}
return ret;
}
收包:
int eth_rx(void)
{
...
current = eth_get_dev();
...
for (i = 0; i < 32; i++) {
ret = eth_get_ops(current)->recv(current, flags, &packet);
flags = 0;
if (ret > 0)
net_process_received_packet(packet, ret);
if (ret >= 0 && eth_get_ops(current)->free_pkt)
eth_get_ops(current)->free_pkt(current, packet, ret);
if (ret <= 0)
break;
}
...
return ret;
}
两者最终都是调用实际的驱动函数 cpsw_send()
和 cpsw_recv()
进行收发包
7.3. ping
uboot 网络支持多种操作,但是都有一个特点就是必须 uboot 首先发起操作,然后相应对端的操作,并不能像一般的系统那样时时刻刻接受对端的网络请求。
ping 的实现也只是向外发送 ping 包,然后接收响应,并不会响应对端发起的 ping 操作。
ping 是作为一个命令(ping
)存在于 uboot 的,它的函数实现是 do_ping()
(cmd/net.c
) :
static int do_ping(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
...
if (net_loop(PING) < 0) {
printf("ping failed; host %s is not alive\n", argv[1]);
return CMD_RET_FAILURE;
}
...
}
然后进入 net_loop()
,首先向外发 ping 包 :
...
switch (protocol) {
...
case PING:
ping_start();
break;
...
}
调用 ping 的发包函数
void ping_start(void)
{
printf("Using %s device\n", eth_get_name());
net_set_timeout_handler(10000UL, ping_timeout_handler);
ping_send();
}
static int ping_send(void)
{
...
arp_request();
return 1; /* waiting */
}
arp_request()
最终会执行 eth_send()
调用网卡驱动发送数据包。
接下来,net_loop()
会等待对端发送的响应包,并进行处理:
for (;;) {
...
eth_rx();
...
}
eth_rx()
调用网卡驱动,而网卡驱动会调用函数 net_process_received_packet()
处理协议栈相关的操作,此处就是执行 ping_receive()
完成 ping 操作的收阶段:
void ping_receive(struct ethernet_hdr *et, struct ip_udp_hdr *ip, int len)
{
struct icmp_hdr *icmph = (struct icmp_hdr *)&ip->udp_src;
struct in_addr src_ip;
int eth_hdr_size;
switch (icmph->type) {
case ICMP_ECHO_REPLY:
src_ip = net_read_ip((void *)&ip->ip_src);
if (src_ip.s_addr == net_ping_ip.s_addr)
net_set_state(NETLOOP_SUCCESS);
return;
...
/* default:
return;*/
}
}
通过函数 net_set_state()
告知 net_loop()
ping 操作成功,否则 ping 失败,即网络有问题。
7.4. tftp
tftp 分两部分:向服务器发送请求和从服务器接收数据,所以在 net_loop()
中 tftp 协议有两组判断条件:
int net_loop(enum proto_t protocol)
{
...
case TFTPGET:
#ifdef CONFIG_CMD_TFTPPUT
case TFTPPUT:
#endif
/* always use ARP to get server ethernet address */
tftp_start(protocol);
break;
...
}
发送请求(TFTPGET
和 TFTPPUT
),向对端发送 tftp 请求,比如 GET 和 PUT ,调用 tftp_start()
发送请求报文:
void tftp_start(enum proto_t protocol)
{
...
net_set_udp_handler(tftp_handler);
...
tftp_send();
...
}
其中 net_set_udp_handler()
会注册 tftp_handler()
到钩子函数 static rxhand_f *udp_packet_handler;
,而 tftp_handler()
会调用 tftp_complete()
检查接收文件是否结束。
接下来 net_loop()
会循环调用 eth_rx()
接收数据包,直到所有数据都收完(钩子函数 udp_packet_handler
会不断的检查已收到的文件长度和实际文件长度是否一直)。
7.5. 小结
uboot 的网络协议栈可以说是麻雀虽小肝胆俱全,网络协议栈所需要的功能它基本都实现了,比如 ping 、 tftp 、tftp server 、DHCP 、 bootp 、 arp/rarp 、DNS 等,它主要侧重于功能的实现,对实时性、并发性等要求不高,所以都是由用户输入命令主动发起操作,并且是单线程操作。