U-Boot:以太网驱动简介

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 以太网设备的初始化

下面以 U-Boot 2016.05 代码下 ARMv7 架构代码为例,来说明 U-Boot 以太网设备的初始化过程。

/* arch/arm/lib/vector.S */

/* ARM 中断向量表 */
.globl _start /* U-Boot 入口 (由 arch/arm/cpu/u-boot.lds 链接脚本 指定) */

	.section ".vectors", "ax"

_start:

#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
	.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif

	b reset
	ldr pc, _undefined_instruction
	ldr pc, _software_interrupt
	ldr pc, _prefetch_abort
	ldr pc, _data_abort
	ldr pc, _not_used
	ldr pc, _irq
	ldr pc, _fiq

...

/* arch/arm/cpu/armv7/start.S */
reset: /* CPU 复位中断入口 */
	...

	bl _main

/* arch/arm/lib/crt0.S */
ENTRY(_main)
	...
	mov r0, #0
	bl board_init_f

	...

	ldr pc, =board_init_r
	
	...

ENDPROC(_main)

前面一部分,是 U-Boot 启动汇编部分,做了一些基础的初始化工作,如 C 运行时环境构建 等工作。下面从 board_init_r() 开始,分析 以太网驱动 初始化过程。首先是 网络接口(RMII, RGMII等)MDIO 总线PIN 脚配置

/* 以 AM335x 的 U-Boot 为例,假定配置的是 RGMII 千兆网口 */
board_init_f()
	initcall_run_list(init_sequence_f)
		board_early_init_f()
			set_mux_conf_regs()

/* board/xxx/xxx/mux.c */
static struct module_pin_mux rgmii1_pin_mux[] = {
	{OFFSET(mii1_crs),  MODE(7)},        /* RESET */
	
	//{OFFSET(rmii1_refclk), MODE(0) | RXACTIVE}, /* RGMII1_REFCLK */

	/*
	 * RGMII PIN 配置 (12 PINs): 
	 * RGMII1_TCTL, RGMII1_RCTL, RGMII1_TCLK, RGMII1_RCLK, RGMII1_TD0~3, RGMII1_RD0~3 
	 */
	{OFFSET(mii1_txen), MODE(2)},       /* RGMII1_TCTL */
	{OFFSET(mii1_rxdv), MODE(2) | RXACTIVE}, /* RGMII1_RCTL */
	{OFFSET(mii1_txd3), MODE(2)},   /* RGMII1_TD3 */
	{OFFSET(mii1_txd2), MODE(2)},   /* RGMII1_TD2 */
	{OFFSET(mii1_txd1), MODE(2)},   /* RGMII1_TD1 */
	{OFFSET(mii1_txd0), MODE(2)},   /* RGMII1_TD0 */
	{OFFSET(mii1_txclk), MODE(2)},   /* RGMII1_TCLK */
	{OFFSET(mii1_rxclk), MODE(2) | RXACTIVE}, /* RGMII1_RCLK */
	{OFFSET(mii1_rxd3), MODE(2) | RXACTIVE}, /* RGMII1_RD3 */
	{OFFSET(mii1_rxd2), MODE(2) | RXACTIVE}, /* RGMII1_RD2 */
	{OFFSET(mii1_rxd1), MODE(2) | RXACTIVE}, /* RGMII1_RD1 */
	{OFFSET(mii1_rxd0), MODE(2) | RXACTIVE}, /* RGMII1_RD0 */

	/* MDIO 总线 PIN 配置: MDIO DATA,CLK */
	{OFFSET(mdio_data), MODE(0) | RXACTIVE | PULLUP_EN},/* MDIO_DATA */
	{OFFSET(mdio_clk), MODE(0) | PULLUP_EN}, /* MDIO_CLK */
	
	{-1},
};

void set_mux_conf_regs(void)
{
	...
	configure_module_pin_mux(rgmii1_pin_mux);
	...
}

接着是 PHY以太网 MAC 芯片 的初始化:

/* common/board_r.c */
init_fnc_t init_sequence_r[] = {
	...
#ifdef CONFIG_CMD_NET
	...
	initr_net,
#endif
	...
};

board_init_r()
	...
	if (initcall_run_list(init_sequence_r))
  		hang();
	...

/* lib/initcall.c */
initcall_run_list()
	...
	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		...
		ret = (*init_fnc_ptr)(); /* initr_net() */
		...
	}

/* common/board_r.c */
initr_net()
	puts("Net:   ");
	eth_initialize(); /* 以 legacy 驱动模式 为例 */
	...

/* net/eth_legacy.c */
eth_initialize()
	...

	eth_devices = NULL;
	eth_current = NULL;
	/* 1. MII 和 PHY 芯片初始化 */
	eth_common_init();
		...
	#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) || defined(CONFIG_PHYLIB)
		miiphy_init();
			INIT_LIST_HEAD(&mii_devs);
			current_mii = NULL;
	#endif

	#ifdef CONFIG_PHYLIB
		phy_init(); /* 注册 PHY 芯片驱动 */
	#endif

	/* 2. 每个板型特定的 以太网 MAC 芯片 相关的初始化 */
	if (board_eth_init != __def_eth_init) {
		if (board_eth_init(gd->bd) < 0) /* 这里以 TI 的 AM335X 板型为例加以说明 */
			printf("Board Net Initialization Failed\n");
	} else if (cpu_eth_init != __def_eth_init) { /* CPU 架构类型 特定的 以太网初始化 */
		...
	}  else {
		printf("Net Initialization Skipped\n");
	}

	if (!eth_devices) { /* 没有找到 或 没有成功初始化 以太网芯片 */
  		puts("No ethernet found.\n");
  		...
  	}  else { /* 成功初始化 以太网芯片 */
  		...
  	}
  	...

下面分别看一下 PHY 芯片以太网 MAC 芯片 的 初始化过程。先看 PHY 芯片驱动 的注册过程:

/* drivers/net/phy/phy.c */
phy_init();
	...
#ifdef CONFIG_PHY_ATHEROS
	phy_atheros_init();
#endif
	...

/* drivers/net/phy/aetheros.c */
static struct phy_driver AR8021_driver =  {
	.name = "AR8021",
	.uid = 0x4dd040,
	.mask = 0x4ffff0,
	.features = PHY_GBIT_FEATURES,
	.config = ar8021_config,
	.startup = genphy_startup,
	.shutdown = genphy_shutdown,
};

static struct phy_driver AR8031_driver =  {
	.name = "AR8031/AR8033",
	.uid = 0x4dd074,
	.mask = 0xffffffef,
	.features = PHY_GBIT_FEATURES,
	.config = ar8031_config,
	.startup = genphy_startup,
	.shutdown = genphy_shutdown,
};

static struct phy_driver AR8035_driver =  {
	.name = "AR8035",
	.uid = 0x4dd072,
	.mask = 0xffffffef,
	.features = PHY_GBIT_FEATURES,
	.config = ar8035_config,
	.startup = genphy_startup,
	.shutdown = genphy_shutdown,
};

/* 向系统注册了3个类型 PHY 芯片的驱动 */
phy_atheros_init()
	phy_register(&AR8021_driver);
	phy_register(&AR8031_driver);
	phy_register(&AR8035_driver);
	...

/* drivers/net/phy/phy.c */
phy_register()
	INIT_LIST_HEAD(&drv->list);
	list_add_tail(&drv->list, &phy_drivers); /* 添加到 PHY 驱动列表 */

再看特定板型的 以太网芯片 初始化过程:

/* board/myirtech/myd_c335x/myd_c335x.c */
board_eth_init()
	...
	
	/* 设置为 RMII 接口模式 & 使能时钟 */
	writel(RMII_MODE_ENABLE | RMII_CHIPCKL_ENABLE, &cdev->miisel);

	/* 复位 PHY 芯片 */
	board_phy_init();

	/* 注册 以太网设备 到系统 */
	rv = cpsw_register(&cpsw_data);
		struct cpsw_priv *priv; /* 以太网设备 对象 私有数据 */
		struct eth_device *dev; /* 以太网设备 对象 */
		
		/* 创建 以太网设备 对象 */
		dev = calloc(sizeof(*dev), 1);
		...

		/* 创建 以太网设备 对象 私有数据 */
 		priv = calloc(sizeof(*priv), 1);

		priv->dev = dev;
 		priv->data = *data;

		/* 设置 以太网设备 对象 名称 和 接口 */
		strcpy(dev->name, "cpsw");
		dev->iobase = 0;
		dev->init = cpsw_init;
		dev->halt = cpsw_halt;
		dev->send = cpsw_send;
		dev->recv = cpsw_recv;
		dev->priv = priv;

		/* 注册 以太网设备 到系统 */
		eth_register(dev);
			...
			
			if (!eth_devices) {
				eth_devices = dev;
				eth_current = dev;
				...
			} else {
				...
			}

			dev->state = ETH_STATE_INIT;
			dev->next  = eth_devices;
			dev->index = index++;

			...
		
		/* 注册 MDIO 总线, 连接 以太网芯片 和 PHY 芯片 */
		ret = _cpsw_register(priv);
			...

			/* 以太网芯片 上连接的 slave (PHY 芯片) */
			priv->slaves = malloc(sizeof(struct cpsw_slave) * data->slaves);
			...

			/* 注册 MDIO 总线 到系统 */
			cpsw_mdio_init(priv->dev->name, data->mdio_base, data->mdio_div);
				struct mii_dev *bus = mdio_alloc();
				...
				bus->read = cpsw_mdio_read;
				bus->write = cpsw_mdio_write;
				strcpy(bus->name, name);
				
				mdio_register(bus);
					...
					/* add it to the list */
					list_add_tail(&bus->link, &mii_devs);
					
					if (!current_mii)
						current_mii = bus;
					...
			...
			/* 以太网芯片 上连接的 slave (PHY 芯片) 的 初始化 和 连接 */
			for_each_slave(slave, priv)
				cpsw_phy_init(priv, slave);
					...
					/* 关联 以太网芯片 和 PHY 芯片 */
					phydev = phy_connect(priv->bus, slave->data->phy_addr,
							priv->dev, slave->data->phy_if);
					...
					priv->phydev = phydev; /* 关联 PHY 和 以太网芯片 MAC */
					/* 初始配置 PHY 芯片 */
					phy_config(phydev);
					...
	...

来仔细看下 以太网芯片PHY 芯片 的连接过程 phy_connect()。期间扫描 MDIO 总线上的 PHY 设备,读取 PHY 芯片 ID,为扫描到的 PHY 创建设备对象,加载匹配的 PHY 驱动,并调用 PHY 驱动 .startup 回调接口进行 PHY 的初始化。

phy_connect() /* drivers/net/phy/phy.c */
	struct phy_device *phydev;

	phydev = phy_find_by_mask(bus, 1 << addr, interface); /* 创建 phy 设备对象(struct phy_device) */
	if (phydev)
		phy_connect_dev(phydev, dev); /* 关联 以太网芯片设备 和 PHY 设备 */
	else
		printf("Could not get PHY for %s: addr %d\n", bus->name, addr);
	return phydev;

phy_find_by_mask()
	/* Reset the bus */
	if (bus->reset) {
		bus->reset(bus);

		/* Wait 15ms to make sure the PHY has come out of hard reset */
		udelay(15000);
	}

	return get_phy_device_by_mask(bus, phy_mask, interface);

get_phy_device_by_mask()
	int i;
 	struct phy_device *phydev;

	...
	/* Try Standard (ie Clause 22) access */
	/* Otherwise we have to try Clause 45 */
	for (i = 0; i < 5; i++) {
		phydev = create_phy_by_mask(bus, phy_mask,
				i ? i : MDIO_DEVAD_NONE, interface);
		...
		if (phydev)
			return phydev;
	}
	
	...

create_phy_by_mask()
	u32 phy_id = 0xffffffff;
	while (phy_mask) {
		int addr = ffs(phy_mask) - 1;
		int r = get_phy_id(bus, addr, devad, &phy_id); /* 读取 PHY 芯片 ID, 用于匹配 PHY 驱动 */
		/* If the PHY ID is mostly f's, we didn't find anything */
		if (r == 0 && (phy_id & 0x1fffffff) != 0x1fffffff)
			return phy_device_create(bus, addr, phy_id, interface);
		phy_mask &= ~(1 << addr);
	}
	return NULL;

phy_device_create()
	struct phy_device *dev;

	dev = malloc(sizeof(*dev));
	...

	memset(dev, 0, sizeof(*dev));

	dev->duplex = -1;
	dev->link = 0;
	dev->interface = interface;

	dev->autoneg = AUTONEG_ENABLE;

	dev->addr = addr;
	dev->phy_id = phy_id;
	dev->bus = bus;

	dev->drv = get_phy_driver(dev, interface);

	phy_probe(dev); /* PHY 驱动 probe */
		phydev->advertising = phydev->supported = phydev->drv->features;
		phydev->mmds = phydev->drv->mmds;
		if (phydev->drv->probe)
			err = phydev->drv->probe(phydev);

	bus->phymap[addr] = dev;

	return dev;

get_phy_driver()
	...
	/* 找到匹配的 PHY 驱动 */
	list_for_each(entry, &phy_drivers) { /* 这里正是前面 eth_initialize() -> phy_init() 注册的 PHY 驱动 */
		drv = list_entry(entry, struct phy_driver, list);
		if ((drv->uid & drv->mask) == (phy_id & drv->mask))
			return drv;
	}

	...

phy_connect() 触发 PHY 驱动的 .startup 回调进行 PHY 的初始化,phy_config() 触发 PHY 驱动的 .config 回调对 PHY 进行进一步配置:

phy_config(phydev)
	board_phy_config(phydev);
		if (phydev->drv->config) {
			return phydev->drv->config(phydev);
		}

到此,以太网芯片PHY 芯片 初始配置工作已经完成,可以用来收发数据了。

3. 以太网设备的数据收发过程

以 U-Boot 的 ping 命令为例,来说明 以太网设备的数据收发过程。

/* cmd/net.c */
do_ping()
	...
	
	net_ping_ip = string_to_ip(argv[1]); /* ping 的 IP 地址 */
	...
	
	if (net_loop(PING) < 0) {
		/* ping 失败了 */
		printf("ping failed; host %s is not alive\n", argv[1]);
		return CMD_RET_FAILURE;
	}
 
	printf("host %s is alive\n", argv[1]);
	...

/* net/net.c */
net_loop()
	...
	/* 网络系统初始化 */
	net_init();
		static int first_call = 1;
		
		if (first_call) {
			net_tx_packet = &net_pkt_buf[0] + (PKTALIGN - 1);
			net_tx_packet -= (ulong)net_tx_packet % PKTALIGN;
			for (i = 0; i < PKTBUFSRX; i++) {
				net_rx_packets[i] = net_tx_packet + (i + 1) * PKTSIZE_ALIGN;
			}
			arp_init();
				arp_wait_packet_ethaddr = NULL;
				net_arp_wait_packet_ip.s_addr = 0;
				net_arp_wait_reply_ip.s_addr = 0;
				arp_wait_tx_packet_size = 0;
				arp_tx_packet = &arp_tx_packet_buf[0] + (PKTALIGN - 1);
				arp_tx_packet -= (ulong)arp_tx_packet % PKTALIGN;
			net_clear_handlers();
				net_set_udp_handler(NULL);
				net_set_arp_handler(NULL);
				net_set_timeout_handler(0, NULL);

			/* Only need to setup buffer pointers once. */
			first_call = 0;
		}

		/* 读取 以太网 MAC 到 @net_ethaddr */
		net_init_loop();
			if (eth_get_dev())  // @eth_current
				memcpy(net_ethaddr, eth_get_ethaddr(), 6);
			...
	...
	/*
	 * eth_is_on_demand_init() 返回非0值, 表示 以太网芯片 
	 * 在需要真正进行通信的时候, 才进行具体芯片硬件相关的
	 * 初始化, 也就是推迟芯片硬件相关的初始化工作.
	 */
	if (eth_is_on_demand_init() || protocol != NETCONS) {
		eth_halt();
		eth_set_current();
		ret = eth_init(); /* 以太网芯片初始化 */
			...
			eth_current->init(eth_current, gd->bd) = cpsw_init()
				_cpsw_init(priv, dev->enetaddr);
					/* 各种 以太网芯片的 寄存器配置 */
					...
					/* 连接的各个 PHY 相关初始化 */
					for_active_slave(slave, priv)
						cpsw_slave_init(slave, priv);
					/* 连接的各个 PHY link 状态相关初始化:启动 PHY 等工作 */
					cpsw_update_link(priv);
						for_active_slave(slave, priv)
							cpsw_slave_update_link(slave, priv, &link);
								...
								phy_startup(phy); /* 启动 PHY */
									/* 调用 PHY 驱动的 .startup 回调 */
									if (phydev->drv->startup)
										return phydev->drv->startup(phydev);
								*link = phy->link;
								/* 接口模式: RGMII, RMII, MII */
								if (*link) { /* link up */
									mac_control = priv->data.mac_control;
									if (phy->speed == 1000)
										mac_control |= GIGABITEN;
									if (phy->duplex == DUPLEX_FULL)
										mac_control |= FULLDUPLEXEN;
									if (phy->speed == 100)
										mac_control |= MIIEN;
								}
								...
								/*
								 * 我们熟悉的日志来了,如:
								 * link up on port 0, speed 100, full duplex
								 */
								if (mac_control) {
									printf("link up on port %d, speed %d, %s duplex\n",
										slave->slave_num, phy->speed,
										(phy->duplex == DUPLEX_FULL) ? "full" : "half");
								}  else {
									printf("link down on port %d\n", slave->slave_num);
								}
								...
					...
			...
		...
	} 
	...
	switch (net_check_prereq(protocol)) { /* 做一些合法性检查 */
	...
	case 0: /* 检查结果 ok, 执行协议相关的 初始化(如 ICMP ping) */
		net_dev_exists = 1;
		net_boot_file_size = 0;
		switch (protocol) {
		...
	#if defined(CONFIG_CMD_PING)
		case PING:
   			ping_start();
   				printf("Using %s device\n", eth_get_name()); /* 熟悉吧: Using cpsw device */
   				net_set_timeout_handler(10000UL, ping_timeout_handler); /* 设置 ping 超时处理接口 */
   					time_handler = f;
   					time_start = get_timer(0);
   					time_delta = iv * CONFIG_SYS_HZ / 1000;
   				ping_send(); /* 构建 ICMP 包 + 发送 ARP 请求 */
   					uchar *pkt;
   					int eth_hdr_size;
   					
   					...
   					net_arp_wait_packet_ip = net_ping_ip;
   					
   					eth_hdr_size = net_set_ether(net_tx_packet, net_null_ethaddr, PROT_IP);
					pkt = (uchar *)net_tx_packet + eth_hdr_size;
					
					set_icmp_header(pkt, net_ping_ip); /* 构建 ICMP 包头部 */

					/* size of the waiting packet */
					arp_wait_tx_packet_size = eth_hdr_size + IP_ICMP_HDR_SIZE;

					/* and do the ARP request */
					arp_wait_try = 1;
					arp_wait_timer_start = get_timer(0);
					arp_request(); /* 发送 ARP 请求把对方的 MAC 地址要过来 */
						...
						arp_raw_request(net_ip, net_null_ethaddr, net_arp_wait_reply_ip);
							uchar *pkt;
							struct arp_hdr *arp;
							int eth_hdr_size;

							...
							pkt = arp_tx_packet;

							eth_hdr_size = net_set_ether(pkt, net_bcast_ethaddr, PROT_ARP);
							pkt += eth_hdr_size;

							arp = (struct arp_hdr *)pkt;

							/* 构建 ARP 请求头部 */
							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);

							/* 构建 ARP 请求 数据报 内容 */
							memcpy(&arp->ar_sha, net_ethaddr, ARP_HLEN); /* source ET addr */
							net_write_ip(&arp->ar_spa, source_ip);  /* source IP addr */
							memcpy(&arp->ar_tha, target_ethaddr, ARP_HLEN); /* target ET addr */
							net_write_ip(&arp->ar_tpa, target_ip);  /* target IP addr */

							/* 通过 以太网芯片 将 ARP 请求包发送出去 */
							net_send_packet(arp_tx_packet, eth_hdr_size + ARP_HDR_SIZE);
								eth_send(pkt, len);
									eth_current->send(eth_current, packet, length);
										cpsw_send()
					return 1; /* waiting */
   			break;
	#endif	
		...
		}
	...
	}

	...

	/* 网络包 收发循环 */
	for (;;) {
		...
		if (arp_timeout_check() > 0) /* ARP 请求 响应 超时检测 */
			time_start = get_timer(0);

		/*
		 * 收取网络包。
		 * 如发 ping 命令时,
		 * 第一次收取 ARP 请求的响应数据;
		 * 第二次收取 ICMP 包的 echo 包。
		 */
		eth_rx();
			eth_current->recv(eth_current);
				cpsw_recv() /* 以太网芯片的接收数据 */
					...
					len = _cpsw_recv(priv, &pkt); /* 从以太网芯片读取接收的数据 */

					/* 网络协议栈 处理 进入的数据报 */
					if (len > 0) {
						net_process_received_packet(pkt, len); /* net/net.c */
							...
							net_rx_packet = in_packet; /* 接收的数据缓冲 */
							net_rx_packet_len = len;
							et = (struct ethernet_hdr *)in_packet;
							
							...
							
							eth_proto = ntohs(et->et_protlen); /* 提取 数据报 的 协议类型 */
							...
							switch (eth_proto) {
							case PROT_ARP: /* 处理 ARP 请求 的 回应数据报 */
								arp_receive(et, ip, len);
									struct arp_hdr *arp;
									struct in_addr reply_ip_addr;
									...
									arp = (struct arp_hdr *)ip; /* ARP 请求响应头部 */
									...
									switch (ntohs(arp->ar_op)) {
									...
									case ARPOP_REPLY:		/* arp reply */
										...
										reply_ip_addr = net_read_ip(&arp->ar_spa); /* ARP 请求的目标 IP (这里是 ping 的 IP) */
										
										/* matched waiting packet's address */
										if (reply_ip_addr.s_addr == net_arp_wait_reply_ip.s_addr) { /* 匹配目标地址 */
											/* set the mac address in the waiting packet's header
											   and transmit it */
											   /* 设置 ping 目标 IP 的 MAC 地址 */
											   memcpy(((struct ethernet_hdr *)net_tx_packet)->et_dest,
														&arp->ar_sha, ARP_HLEN);
												/* 好了,现在有了目标 IP 和 其 MAC,可以向其发送 ICMP 包了 */
												net_send_packet(net_tx_packet, arp_wait_tx_packet_size);
												/* no arp request pending now */
												net_arp_wait_packet_ip.s_addr = 0;
												arp_wait_tx_packet_size = 0;
												arp_wait_packet_ethaddr = NULL;
										}
										return;
									...
									}
								break;
							...
							case PROT_IP:
								...
								if (ip->ip_p == IPPROTO_ICMP) { /* 处理 ICMP 包的 echo */
									receive_icmp(ip, len, src_ip, et);
										...
										switch (icmph->type) {
										...
										default:
									#if defined(CONFIG_CMD_PING)
										ping_receive(et, ip, len);
											...
											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)
													/* 好的,成功收到对端对 ICMP 请求的 echo */
													net_set_state(NETLOOP_SUCCESS);
												return;
											...
											}
									#endif
										}
									return;
								}  else if (ip->ip_p != IPPROTO_UDP) {	/* Only UDP packets */
									...
								}
								...
							...
							}
						...
					}

					return len;
		...

		/* 协议包超时处理(如 ICMP 回应超时) */
		if (time_handler &&
		    ((get_timer(0) - time_start) > time_delta)) {
		    thand_f *x;

			...
			x = time_handler;
			time_handler = (thand_f *)0;
			(*x)(); /* ping_timeout_handler() */
		}

		/* 本次失败之后,可能再来一次之前(是否重来,取决于配置),重新进行以太网初始化 */
		if (net_state == NETLOOP_FAIL)
			ret = net_start_again(); /* 如果配置了重试(如 "netretry"="yes"),返回 NETLOOP_RESTART */

		switch (net_state) {
		case NETLOOP_RESTART: /* 重试 */
			net_restarted = 1;
			goto restart;
		
		case NETLOOP_SUCCESS: /* 成功,做网络设置清理工作,然后退出循环 */
			net_cleanup_loop();
			...
			goto done;

		case NETLOOP_FAIL: /* 失败,并且没有配置重试,做网络设置清理工作,然后退出循环 */
			net_cleanup_loop();
			...
			goto done;
		}
	}

到此,以太网芯片的整个数据的收发过程已经分析完毕。
值得一提的是,其中有1个错误报告信息是开发中常见的:在向目标机器发送 ARP 请求获取其 MAC 地址,可能出现的 ARP 请求超时:

arp_timeout_check()
	ulong t;

	...
	
	t = get_timer(0);

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

		if (arp_wait_try >= ARP_TIMEOUT_COUNT) { /* 重发了 ARP_TIMEOUT_COUNT 次,仍然没收到回应 */
			puts("\nARP Retry count exceeded; starting again\n");
			arp_wait_try = 0;
			net_set_state(NETLOOP_FAIL);
		}  else {
			arp_wait_timer_start = t;
			arp_request(); /* 再次发送 ARP 请求 */
		}
	}

这时的错误报告信息内容如下:

ARP Retry count exceeded; starting again

遇到这种情况,可以尝试调大 ARP 请求超时时间 ARP_TIMEOUT 和 次数 ARP_TIMEOUT_COUNT,如果仍不成功,就可能硬件引发的问题,譬如 PHY 没有工作等等。
笔者本人遇到的一个实际案例,是硬件的 RMII 接口 和 PHY 复位脚 改变了,软件没有做相应修改,导致 ARP 广播没有办法从 PHY 发送出去,实际抓包,目标机器也没有收到对应的 ARP 广播包。

posted @ 2025-04-07 13:46  JiMoKuangXiangQu  阅读(81)  评论(0)    收藏  举报