u-boot與linux下網卡MAC地址的更改

前言
①假設有許多開發板,連接到了同一個路由器中,而路由器的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地址,這有兩種情況:

在初始化的過程中會去讀取MAC地址相關的環境變量
也有可能是從網卡芯片中的內置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\n", __func__);
ret = -ENOMEM;
goto err;
}

dev = (struct eth_device *)malloc(sizeof(struct eth_device));
if (!dev) {
printf(SHETHER_NAME ": %s: malloc failed\n", __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\n");

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。需要注意的是這個寄存器是可讀可寫的:

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\n", 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個:

u-boot中
Linux啓動後
u-boot中更改MAC地址
共有兩種方法:

destroy所有的環境變量:不需要重新編譯u-boot,但是如果env寫死在代碼中,那麼就無能爲力了
在板極配置頭文件(include/configs/xxx.h)中定義CONFIG_ENV_OVERWRITE 後重新編譯u-boot:需要重新編譯u-boot
銷燬env區域來更改
對於第一種方法,因爲不同的板子,存放u-boot環境變量的位置也不一樣,有可能是nand flash、eMMC、SD Card也有可能是SPI Norflash,因此命令各不相同,但是顯然步驟幾乎都是一樣的:

保存現在的所有env,以免萬一哪些項需要的是無法恢復:可以使用pri來查看所有的env

擦除u-boot env區域:使用不同flash的擦除命令來完成
設置default env值:可以使用env default -a
設置MAC地址:setenv ethaddr XX:YY:...
將env存儲到env layout區域::saveenv
重新編譯u-boot來更改
如果是代碼中直接寫死了ethaddr環境變量的值,那麼就只能重新編譯了:

#define CONFIG_EXTRA_ENV_SETTINGS \
"baudrate=460800\0" \
"bootm_low=0x40e00000\0" \
"bootm_size=0x100000\0" \
"ethact=smc911x-2\0" \
"ethaddr=2e:09:0a:00:6e:1e\0"
如果代碼中沒有寫死,那麼可以在配置文件中添加下面一行來讓ethaddr可以被更改:

#define CONFIG_ENV_OVERWRITE 1
linux下更改MAC地址

當系統啓動到OS後,要更改MAC地址既可以直接使用命令來更改,也可以在sysV-init腳本中配置。

①對於命令行,可以直接使用busybox中的ifconfig來完成:

busybox ifconfig eth0 hw ether 12:34:56:78:90:af
但是這個更改無法永久性的更改,重啓後就又恢復回到原值了,因爲ifconfig命令中根本就不會去更改硬件寄存器,或者環境變量,ifconfig中設置hw部分的代碼如下:

#if ENABLE_FEATURE_IFCONFIG_HW
} else { /* A_CAST_HOST_COPY_IN_ETHER */
/* This is the "hw" arg case. */
smalluint hw_class = index_in_substrings("ether\0"
IF_FEATURE_HWIB("infiniband\0"), *argv) + 1;
if (!hw_class || !*++argv)
bb_show_usage();
host = *argv;
if (hw_class == 1 ? in_ether(host, &sa) : in_ib(host, &sa))
bb_error_msg_and_die("invalid hw-addr %s", host);
p = (char *) &sa;
}
#endif
②對於使用更改SysV-Init或者systemd等的更改,google中可以找到許多的答案。但是這種方法也無法永久性的更改,因爲同樣也沒有操作硬件。

參考
1. 14.2.14. Where Can I Get a Valid MAC Address from?

如果文章有格式問題,請移步:http://www.hexiongjun.com/?p=228

轉載請註明出處。作者:TonyHo hexiongjun.com 


————————————————
版權聲明:本文爲CSDN博主「TonyHo」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/sy373466062/article/details/49021485

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章