平台信息:
內核:linux3.1.0
系統:android/android6.0
平台:RK3288
作者:庄澤彬(歡迎轉載,請注明作者)
郵箱:2760715357@qq.com
說明:提供以太網mac地址燒錄以及讀寫的方式
一、功能演示以及說明:
1.1在安卓的文件系統生成如下的設備節點:/sys/kernel/pax_ethernet/mac用於燒錄以及讀取以太網的mac地址。使用adb命令進行以太網mac地址的燒寫以及讀寫。本質上在使用echo "aa:aa:aa:aa:aa:aa" > /sys/kernel/pax_ethernet/mac這個命令的時候會調用kernel的底層驅動往我們存放的mac地址的分區寫入以太網的mac地址。我們的需求是在燒錄了以太網的mac地址之后,設備就一直使用我們燒錄的mac給網卡設備,不在使用隨機數生成mac地址。實現的思路大致如下:在使用adb命令往/sys/kernel/pax_ethernet/mac這個設備節點寫入合法的mac地址之后,在重啟之后uboot啟動的時候會從這燒錄mac地址的分區讀取燒錄mac地址,如果燒錄的mac地址合法,就會通過cmdline的機制傳遞給kernel,kernel的以太網往驅動會解析uboot發送的cmdline,將傳遞的字符串解析之后,如果合法在賦值給網卡,我在kernel的驅動還做了判斷,如果uboot傳遞的mac地址出錯或者讀取有異常,kernel會在一次從分區中獲取mac地址。好吧,講了這么多,我們還是看代碼是如何實現的吧。

二、uboot讀取以太網的mac地址以及傳遞mac地址給kernel的相關代碼片段如下:
2.1這部分代碼是我封裝的用於讀取分區中以太網mac地址的讀和寫的接口
1 int sp_get_mac(char *value, int len){ 2 3 unsigned blocks,offset_blocks; 4 const disk_partition_t* ptn = get_disk_partition("sp"); 5 6 /* strcpy(value,"0123456789"); */ 7 /* return 0; */ 8 9 offset_blocks = DIV_ROUND_UP(SP_MAC_OFFSET, RK_BLK_SIZE); 10 /* blocks = DIV_ROUND_UP(len, RK_BLK_SIZE); */ 11 12 if (ptn) { 13 return rkloader_CopyFlash2Memory(value,ptn->start+offset_blocks,1); 14 } 15 16 return -1; 17 } 18 19 int sp_set_mac(char *value, int len){ 20 21 unsigned blocks,offset_blocks; 22 const disk_partition_t* ptn = get_disk_partition("sp"); 23 24 offset_blocks = DIV_ROUND_UP(SP_MAC_OFFSET, RK_BLK_SIZE); 25 blocks = DIV_ROUND_UP(len, RK_BLK_SIZE); 26 27 if (ptn) { 28 StorageEraseBlock(ptn->start+offset_blocks, blocks, 1); 29 return rkloader_CopyMemory2Flash(value,ptn->start+offset_blocks,blocks); 30 } 31 32 return -1; 33 }
2.2uboot傳遞給kernel的相關代碼片段:
1 //讀取sp分區的mac地址 2 memset(tbuf,0,sizeof(tbuf)); 3 ret = sp_get_mac(tbuf,64); 4 if(ret!=0){ 5 tbuf[0]=0; 6 }else{ 7 if((tbuf[0]==0xff)&&(tbuf[1]==0xff)&&(tbuf[2]==0xff)&&\ 8 (tbuf[3]==0xff)&&(tbuf[4]==0xff)&&(tbuf[5]==0xff)){ 9 tbuf[0]=0; 10 }else if((tbuf[0]==0x00)&&(tbuf[1]==0x00)&&(tbuf[2]==0x00)&&\ 11 (tbuf[3]==0x00)&&(tbuf[4]==0x00)&&(tbuf[5]==0x00)){ 12 tbuf[0]=0; 13 }else{ 14 unsigned char tmp[32]; 15 memset(tmp,0,32); 16 17 sprintf(tmp,"%02x:%02x:%02x:%02x:%02x:%02x",tbuf[0],tbuf[1],tbuf[2],tbuf[3],tbuf[4],tbuf[5]); 18 printf("[%s:%d]mac:%s\r\n",__func__,__LINE__,tmp); 19 snprintf(command_line, len, 20 "%s eth_mac=%s", command_line, tmp); 21 } 22 } 23 tbuf[63]=0;
2.3實驗結果如下,具體的代碼大家就自己看吧。uboot階段以及成果讀取並且通過cmdline發送mac的地址.



三、kernel的以太網驅動解析cmdline並賦值給以太網的網卡設備。
3.1kernel解析cmdline的相關代碼片段如下:查看下面的圖片kernel已經成果的獲取uboot傳遞的mac地址
u_char mac_addr_str[18] = {0}; u_char mac_addr[7] = {0}; static int __init get_mac_addr(char *str) { strncpy(mac_addr_str,str,17); printk(KERN_ERR"[%s:%d] mac_addr_str = %s",__func__,__LINE__,mac_addr_str); return 0; } //解析cmdline __setup("eth_mac=",get_mac_addr); module_init(stmmac_init); module_exit(stmmac_exit);

3.2kernel層將傳遞的mac地址賦值給設備.
u_char char2num(u_char ch) { switch(ch){ case 'a': case 'A': return 10; break; case 'b': case 'B': return 11; break; case 'c': case 'C': return 12; break; case 'd': case 'D': return 13; break; case 'e': case 'E': return 14; break; case 'f': case 'F': return 15; break; default: return 0; } } void str2byte(u_char *str, u_char *byte) { int i=0, j=0; u_char num, n; u_char temp[20] = {0}; for(i=0; i<17; i++){ if(str[i] == ':'){ continue; }else{ temp[j] = str[i]; j++; } } temp[j]='\0'; i=0; while(*(temp+i)!='\0') { if(*(temp+i)>='0' && *(temp+i) <= '9'){ if(i%2 == 0){ //żÊýΪʮλ num = (*(temp+i)-'0') * 16; }else{ num = num + (*(temp+i)-'0'); } i++; }else if((*(temp+i)>='a' && *(temp+i) <= 'f') || (*(temp+i)>='A' && *(temp+i) <= 'F')){ n = char2num(*(temp+i)); if(n == 0){ memset(byte, 0, 6); break; } if(i%2 == 0){ //żÊýΪʮλ num = n * 16; }else{ num = num + n; } i++; }else{ memset(byte, 0, 6); break; } if(i%2 == 0){ *byte++ = num; } } } static ssize_t block_mac_store(const char *buf, size_t count) { if (buf != NULL && strlen(buf)) { write_block_info(BLOCK_NAME, buf, strlen(buf), MAC_ADDR_OFFSET); } return 0; } static ssize_t block_mac_show( char *buf) { char mac_buf[18] = {0}; read_block_info(BLOCK_NAME, mac_buf, 17, MAC_ADDR_OFFSET); printk(KERN_ERR"[%s:%d] mac: %pM\r\n",__func__,__LINE__,mac_buf); return sprintf(buf, "%s", mac_buf); }
//這部分的代碼就是賦值將獲取的mac地址賦值給網卡設備的主要地方. //cmdline´«µÝµÄmacµØÖ· str2byte(mac_addr_str, mac_addr); printk(KERN_ERR"[%s:%d]%02x:%02x:%02x:%02x:%02x:%02x\r\n",__func__,__LINE__,mac_addr[0],mac_addr[1],mac_addr[2],mac_addr[3],mac_addr[4],mac_addr[5]); if(is_valid_ether_addr(mac_addr)){ priv->dev->dev_addr = mac_addr; printk(KERN_ERR"[%s:%d]\r\n",__func__,__LINE__); } if(!is_valid_ether_addr(priv->dev->dev_addr)){ memset(block_mac_buf,0,sizeof(block_mac_buf)); block_mac_show(block_mac_buf); printk(KERN_ERR"[%s:%d]%02x:%02x:%02x:%02x:%02x:%02x\r\n",__func__,__LINE__,block_mac_buf[0],block_mac_buf[1],block_mac_buf[2],block_mac_buf[3],block_mac_buf[4],block_mac_buf[5]); if(is_valid_ether_addr(block_mac_buf)){ priv->dev->dev_addr = block_mac_buf; printk(KERN_ERR"[%s:%d]\r\n",__func__,__LINE__); } }

四、生成設備/sys/kernel/pax_ethernet/mac的方法如下。
1 + 2 +static ssize_t sys_mac_show(struct kobject *kobj, struct kobj_attribute *attr, 3 + char *buf) 4 +{ 5 + char temp_mac_buf[18]; 6 + 7 + memset(temp_mac_buf,0,sizeof(temp_mac_buf)); 8 + read_block_info(BLOCK_NAME, temp_mac_buf, 18, MAC_ADDR_OFFSET); 9 + printk(KERN_ERR"[%s:%d]%02x:%02x:%02x:%02x:%02x:%02x\r\n",__func__,__LINE__,temp_mac_buf[0],temp_mac_buf[1],temp_mac_buf[2],temp_mac_buf[3],temp_mac_buf[4],temp_mac_buf[5]); 10 + 11 + return sprintf(buf, "%pM\n", temp_mac_buf); 12 +} 13 + 14 +static ssize_t sys_mac_store(struct kobject *kobj, struct kobj_attribute *attr, 15 + const char *buf, size_t count) 16 +{ 17 + u_char mac_addr[7] = {0}; 18 + 19 + printk(KERN_ERR"[%s:%d]%02x:%02x:%02x:%02x:%02x:%02x\r\n",__func__,__LINE__,buf[0],buf[1],buf[2],buf[3],buf[4],buf[5]); 20 + if (buf != NULL && strlen(buf)){ 21 + //memcpy(mac_str, buf, strlen(buf)); 22 + //<D7>ַ<FB><B4><AE><B5><C4>ת<BB><BB> 23 + str2byte(buf, mac_addr); 24 + printk(KERN_ERR"[%s:%d]%02x:%02x:%02x:%02x:%02x:%02x\r\n",__func__,__LINE__,mac_addr[0],mac_addr[1],mac_addr[2],mac_addr[3],mac_addr[4],mac_addr[5]); 25 + write_block_info(BLOCK_NAME, mac_addr, 6, MAC_ADDR_OFFSET); 26 + } 27 + 28 + return count; 29 +} 30 + 31 +static struct kobj_attribute mac_attribute = 32 + __ATTR(mac, 0666, sys_mac_show, sys_mac_store); 33 + 34 + 35 +static struct attribute *attrs[] = { 36 + &mac_attribute.attr, 37 + NULL, /* need to NULL terminate the list of attributes */ 38 +}; 39 +static struct attribute_group attr_group = { 40 + .attrs = attrs, 41 +}; 42 + 43 +static struct kobject *ethernet_kobj; 44 + 45 46 47 + ethernet_kobj = kobject_create_and_add("pax_ethernet", kernel_kobj); 48 + if (!ethernet_kobj) 49 + return -ENOMEM; 50 + 51 + /* Create the files associated with this kobject */ 52 + ret = sysfs_create_group(ethernet_kobj, &attr_group); 53 + if (ret) 54 + kobject_put(ethernet_kobj); 55 +
五、kernel層對分區操作的函數如下:
1 int write_block_info(const char *name, char *data, int length, loff_t offset) 2 { 3 struct file *fp; 4 mm_segment_t fs; 5 6 7 AUTHINFO_DEBUG("%s start, data: %s, length: %d \n",__func__, data, length); 8 9 fp = filp_open(name, O_RDWR | O_CREAT, 0644); 10 if (IS_ERR(fp)) { 11 AUTHINFO_ERROR("create file error"); 12 return -1; 13 } 14 15 fs = get_fs(); 16 set_fs(KERNEL_DS); 17 18 vfs_write(fp, data, length, &offset); 19 20 filp_close(fp, NULL); 21 set_fs(fs); 22 23 AUTHINFO_DEBUG("%s end",__func__); 24 25 return 0; 26 } 27 EXPORT_SYMBOL(write_block_info); 28 29 int read_block_info(const char *name, char *buf, int length, loff_t offset) 30 { 31 struct file *fp; 32 mm_segment_t fs; 33 34 AUTHINFO_DEBUG("%s start",__func__); 35 fp = filp_open(name, O_RDWR | O_CREAT, 0644); 36 if (IS_ERR(fp)) { 37 AUTHINFO_ERROR("create file error"); 38 return -1; 39 } 40 41 fs = get_fs(); 42 set_fs(KERNEL_DS); 43 44 vfs_read(fp, buf, length, &offset); 45 46 47 filp_close(fp, NULL); 48 set_fs(fs); 49 50 AUTHINFO_DEBUG("%s end %d %s",__func__,length,buf); 51 52 return 0; 53 } 54 EXPORT_SYMBOL(read_block_info);
六、最終的結果.查看一下設置以太網的mac地址成功,可以下班了啊。

