龙芯2K1000平台重启后之前设置IP失效问题解决

  最近遇到了一个比较烦人的问题,就是我的这个loongnix系统上在接网口0之后,设置了固定的IP地址,重启之后,之前创建的有线连接失效,反而新增了一个新的有线连接。捯饬了两三天,终于将其优化成固定的MAC。其间,有一个跑精简系统的2k1000的板子的网口MAC地址每次开机不会改变,误导了我,走了点弯路。这里记录一下。
  首先明确的是,我不能通过set ethaddr来设置这个地址,虽然能快速解决,但是比较麻烦,如果板卡较多,为了防止MAC冲突,还得每个都得记录一下。
  板子使用的PHY芯片都是RTL8211F。(可以忽略中间调试过程,直接看最后几段的解决方法)
  首先,我肯定的认为是我的内核有问题,在排查过程中,使用dmesg看,驱动每次执行完会打印当前的MAC地址。4.19版本内核的打印值都是相同的。

[    4.023834] stmmac - user ID: 0xd1, Synopsys ID: 0x37
[    4.023841]  Enhanced/Alternate descriptors
[    4.023847]  Ring mode enabled
[    4.023855]  DMA HW capability register supported
[    4.023863]  RX Checksum Offload Engine supported (type 2)
[    4.023897]  TX Checksum insertion supported
[    4.023902]  Wake-Up On Lan supported
[    4.023909]  Enable GMAC 64bit DMA
[    4.023914]  Enable RX Mitigation via HW Watchdog Timer
[    4.023925] eth%d: device MAC address d6:40:79:32:5d:51
[    4.032971] ata1: SATA link up 3.0 Gbps (SStatus 123 SControl 300)
[    4.033190] ata1.00: ATA-10: KINGSTON SNS4151S316G, S9FM01.1, max UDMA/100
[    4.033201] ata1.00: 31277232 sectors, multi 16: LBA48 NCQ (depth 31/32)
[    4.033493] ata1.00: configured for UDMA/100
[    4.034012] scsi 0:0:0:0: Direct-Access     ATA      KINGSTON SNS4151 01.1 PQ: 0 ANSI: 5
[    4.035508] libphy: stmmac: probed
[    4.035526] eth0: PHY ID 001cc916 at 0 IRQ 0 (stmmac-0:00) active
[    4.035535] eth0: PHY ID 001cc916 at 1 IRQ 0 (stmmac-0:01)

  这里在初步怀疑是内核的问题的时候,而且错误的以为是内核从PHY芯片里读出来的MAC值。看到这个内核调用的是drivers/net/ethernet/stmicro/stmmac驱动之后,二话没说,就来了一顿移植。两个版本的内核框架性的东西改动挺多的,好多文件都要改,移了半天没编过,果断放弃。
  接下来,对比两个版本的内核,发现都是通过读去2K1000的寄存器获取的MAC值。

static void dwmac1000_get_umac_addr(void __iomem *ioaddr, unsigned char *addr,
				    unsigned int reg_n)
{
	stmmac_get_mac_addr(ioaddr, addr, GMAC_ADDR_HIGH(reg_n),
			    GMAC_ADDR_LOW(reg_n));
}
void stmmac_get_mac_addr(void __iomem *ioaddr, unsigned char *addr,
			 unsigned int high, unsigned int low)
{
	unsigned int hi_addr, lo_addr;

	/* Read the MAC address from the hardware */
	hi_addr = readl(ioaddr + high);
	lo_addr = readl(ioaddr + low);
	
	/* Extract the MAC address from the high and low words */
	addr[0] = lo_addr & 0xff;
	addr[1] = (lo_addr >> 8) & 0xff;
	addr[2] = (lo_addr >> 16) & 0xff;
	addr[3] = (lo_addr >> 24) & 0xff;
	addr[4] = hi_addr & 0xff;
	addr[5] = (hi_addr >> 8) & 0xff;
}

  如果正常的话,对应的寄存器地址和值应该是以下情况:

[    1.576098]  hi_addr 9000000040040040: 0x80002010
[    1.582132]  lo_addr 9000000040040044: 0xd9cb1a7e

  但是实际我这个出问题的读到的值是:

[    4.028808]     hi_addr 9000000040040040: 0x80000000
[    4.028815]     lo_addr 9000000040040044: 0x00000000

  这里就很奇怪了,然后看了下手册,发现RTL8211F手册上明确写着是TRANSEIVER,也就是没有控制器,单纯是个传输器,结合最开始的log,也就不难发现,MDIO总线初始化之前就读到了MAC值,说明也不是从RTL8211F获取到的:
在这里插入图片描述  包含MAC的芯片也是有的,这种一般将MAC和PHY集成在一起,对外暴露的是另一种并行总线,例如SPI或者PCIe等等。例如RTL8111E,就写着是CONTROLLER。
在这里插入图片描述  到这里就可以明确是uboot的一些操作,对内核起了作用。这里就直接说结果了,其实就是uboot第一次下载之后, 先设备树中获取MAC值,不好意思,设备树中没有设置(最好也不要设置,否则MAC都一样了)。 然后从环境变量中获取MAC,但是呢,获取到的是0。然后判断不可用,随机生成了MAC值,然后写到CPU的寄存器中。大致流程见以下代码:

static int eth_post_probe(struct udevice *dev)
{
	struct eth_device_priv *priv = dev->uclass_priv;
	struct eth_pdata *pdata = dev->platdata;
	unsigned char env_enetaddr[ARP_HLEN];
	
...

	/* Check if the device has a valid MAC address in device tree */
	if (!eth_dev_get_mac_address(dev, pdata->enetaddr) ||
	    !is_valid_ethaddr(pdata->enetaddr)) {
		/* Check if the device has a MAC address in ROM */
		if (eth_get_ops(dev)->read_rom_hwaddr)
			eth_get_ops(dev)->read_rom_hwaddr(dev);
	}

	eth_env_get_enetaddr_by_index("eth", dev->seq, env_enetaddr);
...
	} else if (is_zero_ethaddr(pdata->enetaddr) ||
		   !is_valid_ethaddr(pdata->enetaddr)) {
#ifdef CONFIG_NET_RANDOM_ETHADDR
		net_random_ethaddr(pdata->enetaddr);
		printf("\nWarning: %s (eth%d) using random MAC address - %pM\n",
		       dev->name, dev->seq, pdata->enetaddr);
		eth_env_set_enetaddr_by_index("eth", dev->seq, pdata->enetaddr);
		env_save();
...
	}

	eth_write_hwaddr(dev);

	return 0;
}

最后的eth_write_hwaddr实际上调用了_dw_write_hwaddr函数进行相应的寄存器写值来实现传递,这个寄存器就是上面读出来的那个绝对地址9000000040040040的对应的寄存器。

static int _dw_write_hwaddr(struct dw_eth_dev *priv, u8 *mac_id)
{
	struct eth_mac_regs *mac_p = priv->mac_regs_p;
	u32 macid_lo, macid_hi;

	macid_lo = mac_id[0] + (mac_id[1] << 8) + (mac_id[2] << 16) +
		   (mac_id[3] << 24);
	macid_hi = mac_id[4] + (mac_id[5] << 8);

	writel(macid_hi, &mac_p->macaddr0hi);
	writel(macid_lo, &mac_p->macaddr0lo);

	return 0;
}

  那么,接下来我只要实现PMON传递MAC值就好了。PMON是通过以环境变量中的MAC值来更新设备树中的网卡节点的MAC值,然后来实现MAC值传递的。具体可参考本人的另一篇文章:龙芯PMON(2K1000)启动流程(三、C语言部分③)中的3.6.1 校验 和 3.7.1中有写到。这里就不再赘述。
  那么,我只需要实现PMON第一次烧录之后,随机生成一个MAC并固化(代码目前是固化到FLASH中)。具体的代码如下:

  MAC校验函数如下,没什么好说的,发现MAC不一样就使,判断条件生效,接下来执行更新。

static int check_mac_ok(void)
{
//读取设备树中的MAC地址,代码简单就略
...
			tgt_ethaddr(mac_addr);   //获取环境变量中的地址
			mac_addr[5] += id;
...
		for(i = 0;i < 6;i++) {    //比对不通就返回0即将执行将环境变量中的MAC更新到设备树中
			if(mac_addr[i] != (*((char *)nodep + i) & 0xff)) {
				printf("mac_eeprom[%d]=0x%x; mac_dtb[%d]=0x%x; reset mac addr in dtb\n", \
						i, mac_addr[i], i, *((char *)nodep + i) & 0xff);
				return 0;
			}
		}
	}
	return 1;
}

  更新MAC,问题就出在这里了,没有对MAC进行一个可用性校验,第一次烧写之后,从FLASH里读到的MAC值都是0,到这里就直接更新到设备树中了,其实这是一个不可用的MAC。而且在set ethaddr命令执行的时候,也会执行到这里,如果设置的MAC地址不可用例如广播地址等,这里还是会直接更新到设备树。

static int update_mac(void * ssp, int id)
{
...
		tgt_ethaddr(mac_addr);
		mac_addr[5] += id;    
...
	len = 6;
	if(fdt_setprop(ssp, nodeoffset, "mac-address", mac_addr, len)) {
		printf("Update mac-address%d error\n", id);
		return -1;
	}
	return 0;
}

解决方法在这里:

  那么我们只需在这里加一个MAC地址不可用校验,如果地址不可用,我们就随机生成一个MAC,更新反过来更新到环境变量,再更新到设备树中,即可。

static int update_mac(void * ssp, int id)
{
...
		tgt_ethaddr(mac_addr);
		if (!is_valid_ether_addr_linux(mac_addr)) {      //add by dingling
			eth_random_addr(mac_addr);
			printf("Warning:  ethaddr using random MAC address - %02x:%02x:%02x:%02x:%02x:%02x\n", \
							mac_addr[0],mac_addr[1],mac_addr[2],mac_addr[3],mac_addr[4],mac_addr[5]);

			sprintf(env, "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1],
						mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
			setenv("ethaddr",env);            //更新环境变量中不可用的MAC地址
		}
		else
			mac_addr[5] += id;    
...
	len = 6;
	if(fdt_setprop(ssp, nodeoffset, "mac-address", mac_addr, len)) {
		printf("Update mac-address%d error\n", id);
		return -1;
	}
	return 0;
}

烧写启动后:

Warning:  ethaddr 00:00:00:00:00:00 is invalid
Warning:  ethaddr using random MAC address - 3e:d0:62:f5:46:94

下面贴出一个测试项,来说明什么是不可用地址:

PMON> set ethaddr 4b:73:3C:a9:10:83

Warning:  ethaddr 4b:73:3c:a9:10:83 is invalid
Warning:  ethaddr using random MAC address - 3e:d0:62:f5:46:94

很显然,重启之后判断为不可用了。

int is_zero_ether_addr(const u8 *addr)
{
        return !(addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]);
}

int is_multicast_ether_addr(const u8 *addr)
{
        return (0x01 & addr[0]);
}

int is_valid_ether_addr_linux(const u8 *addr)
{
        /* FF:FF:FF:FF:FF:FF is a multicast address so we don't need to
         * explicitly check for it here. */
        return !is_multicast_ether_addr(addr) && !is_zero_ether_addr(addr);
}

其中第1字节的最低位标识这个地址是组播地址还是单播地址。
这是因为以太网的传输协议高字节先传,但每一字节内低位先传的特性所决定的。
IEEE 802.3 3.2.3 Address fields: “The first bit (LSB) shall be used in the Destination Address field as an address type designation bit to identify the Destination Address either as an individual or as a group address. If this bit is 0, it shall indicate that the address field contains an individual address. If this bit is 1, it shall indicate that the address field contains a group address that identifies none, one or more, or all of the stations connected to the LAN. In the Source Address field, the first bit is reserved and set to 0.”。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值