u-boot与linux下网卡MAC地址的更改

2019-07-13 08:13发布

前言

①假设有许多开发板,连接到了同一个路由器中,而路由器的dhcpd是根据MAC地址来分配一个固定的IP地址,那么就需要为每一个开发板设定一个不同的MAC地址从而获取不同的IP地址。 ②MAC地址是需要购买的(参考点1),在产品出厂之前,都需要给板子一个MAC地址(如果有对应的设备,例如网卡,Wifi)。且这个MAC地址在出厂后,我们并不期望被更改。 下面简述了嵌入式产品中如何保证IP地址不被更改的、以及开发过程或者生产过程如何更改MAC地址。

不被更改的方法

嵌入式中对于期望不被更改的数据,一般都是存放在无法被直接擦除或者修改的存储设备中,例如nand、eMMC、EEPROM、带保护的Nor Flash、甚至是直接写在程序中。这里说的无法被直接修改是相对内存而言的,并不是无法被修改。例如nand在修改之前,一般是需要去保护、擦除。 具体的,对于嵌入式产品而言,一般都是使用u-boot来作为bootloader,u-boot将需要的一些参数放在环境变量中,例如MAC地址就是从环境变量ethaddr中获取的。因此要保证MAC地址能够不被直接修改,那么可以将u-boot中的环境变量放在一个带有保护措施的存储器中,在需要改写的时候去除保护,改写完成后再进行保护。 u-boot启动后,在板极硬件初始化的过程中,会去初始化网卡,并获取MAC地址,这有两种情况:
  1. 在初始化的过程中会去读取MAC地址相关的环境变量
  2. 也有可能是从网卡芯片中的内置EEPROM获取MAC地址(例如SMC911x,就会有一个EEPROM存储MAC地址,上电后,会将此IP地址加载到用户可以访问的寄存器中)
调用的路径如下,具体详细的关于u-boot中eth driver的说明参考u-boot中的文档doc/drivers.net.eth: board_init() eth_initialize() board_eth_init() / cpu_eth_init() driver_register() initialize eth_device eth_register()

从环境变量ethaddr获取MAC地址的实例

具体到瑞萨的Cortex-A15芯片而言,如果使用的是SoC自带的ether,那么在board_eth_init中会从环境变量中读取ethaddr环境变量,来获取MAC地址。代码文件sh-net.c。 int board_eth_init(bd_t *bis) { int ret = -ENODEV; u32 val; unsigned char enetaddr[6]; #ifdef CONFIG_SH_ETHER ret = sh_eth_initialize(bis); if (!eth_getenv_enetaddr("ethaddr", enetaddr)) return ret; /* Set Mac address */ val = enetaddr[0] << 24 | enetaddr[1] << 16 | enetaddr[2] << 8 | enetaddr[3]; writel(val, 0xEE7003C0); val = enetaddr[4] << 8 | enetaddr[5]; writel(val, 0xEE7003C8); #endif return ret; } 在sh_eth_initialize(瑞萨的一款Cortex-A15 SoC自带的eth)中,会去读取ethaddr环境变量: int sh_eth_initialize(bd_t *bd) { int ret = 0; struct sh_eth_dev *eth = NULL; struct eth_device *dev = NULL; eth = (struct sh_eth_dev *)malloc(sizeof(struct sh_eth_dev)); if (!eth) { printf(SHETHER_NAME ": %s: malloc failed ", __func__); ret = -ENOMEM; goto err; } dev = (struct eth_device *)malloc(sizeof(struct eth_device)); if (!dev) { printf(SHETHER_NAME ": %s: malloc failed ", __func__); ret = -ENOMEM; goto err; } memset(dev, 0, sizeof(struct eth_device)); memset(eth, 0, sizeof(struct sh_eth_dev)); eth->port = CONFIG_SH_ETHER_USE_PORT; eth->port_info[eth->port].phy_addr = CONFIG_SH_ETHER_PHY_ADDR; dev->priv = (void *)eth; dev->iobase = 0; dev->init = sh_eth_init; ...... sprintf(dev->name, SHETHER_NAME); /* Register Device to EtherNet subsystem */ eth_register(dev); bb_miiphy_buses[0].priv = eth; miiphy_register(dev->name, bb_miiphy_read, bb_miiphy_write); if (!eth_getenv_enetaddr("ethaddr", dev->enetaddr)) puts("Please set MAC address "); return ret; ..... } 在倒数几行中,使用eth_getenv_ethaddr函数从环境变量ethaddr中获取并设置MAC地址,如果环境变量中不存在此环境变量,那么就会要求你先设置一个。如果设置成功了,那么以后都会从这个环境变量中获取。

从网卡芯片中的EEPROM中获取MAC地址

对于SMC911x(这里使用smc911x与smsc911x,不作区分,一个是实际的名字,一个是代码中使用的名字)芯片而言,在其上电后,会从EEPROM中将MAC地址加载到用户可以访问的MAC地址寄存器中,参考下面的图Figure1:MAC Address Reg中的框图中的Description描述。如果SMC911x没有设置过MAC地址,那么出厂的默认MAC都是FF。 对于SMC911x(smc911x.c代码文件点击此处, smsc911x的datasheet下载点击此处),u-boot在启动后,smc911x的initialze中会到硬件中获取MAC地址,如果MAC地址全部为FF,那么就会从环境变量中获取,然后在smc911x_init中将此MAC值设置到芯片的MAC地址存储寄存器中,芯片会自动将这个MAC地址写入到EEPROM中,用于下一次的使用。 详细而言,网络控制器芯片SMC911X,使用两个寄存器来保存MAC地址值,这两个寄存器32Bit的,每一个寄存器保存6字节MAC地址的3Byte。需要注意的是这个寄存器是可读可写的: SMSC_MAC_ADD_reg
Figure1:MAC Address Reg 对应的代码实现在drivers/net/smc911x.c中,设置MAC地址的函数为: static void smc911x_handle_mac_address(struct eth_device *dev) { unsigned long addrh, addrl; uchar *m = dev->enetaddr; addrl = m[0] | (m[1] << 8) | (m[2] << 16) | (m[3] << 24); addrh = m[4] | (m[5] << 8); smc911x_set_mac_csr(dev, ADDRL, addrl); smc911x_set_mac_csr(dev, ADDRH, addrh); printf(DRIVERNAME ": MAC %pM ", m); } 其中第8-9行,就是设置数据书册中的提到的MAC地址高低寄存器。

不可被覆盖

如果在设置了ethaddr之后,又去设置ethaddr环境变量,那么会出现错误,提示不能重写ethaddr,这样就完成了基本的不可重写的保护: set ethaddr 2e:09:0a:00:6e:1f [ 28.372] ## Error: Can't overwrite "ethaddr" [ 28.373] ## Error inserting "ethaddr" variable, errno=1

更改的方法

MAC地址可以配置的地方有以下2个:
  1. u-boot中
  2. Linux启动后

u-boot中更改MAC地址

共有两种方法:
  1. destroy所有的环境变量:不需要重新编译u-boot,但是如果env写死在代码中,那么就无能为力了
  2. 在板极配置头文件(include/configs/xxx.h)中定义CONFIG_ENV_OVERWRITE 后重新编译u-boot:需要重新编译u-boot

销毁env区域来更改

对于第一种方法,因为不同的板子,存放u-boot环境变量的位置也不一样,有可能是nand flash、eMMC、SD Card也有可能是SPI Norflash,因此命令各不相同,但是显然步骤几乎都是一样的: 保存现在的所有env,以免万一哪些项需要的是无法恢复:可以使用pri来查看所有的env
  1. 擦除u-boot env区域:使用不同flash的擦除命令来完成
  2. 设置default env值:可以使用env default -a
  3. 设置MAC地址:setenv ethaddr XX:YY:...
  4. 将env存储到env layout区域::saveenv

重新编译u-boot来更改

如果是代码中直接写死了ethaddr环境变量的值,那么就只能重新编译了: #define CONFIG_EXTRA_ENV_SETTINGS "baudrate=460800