1 引言
SAMA5D27处理器有一个Ethernet MAC(GMAC)控制器,支持10/100 Mbps。Linux下GMAC控制器的驱动源码路径为: \linux-at91\drivers\net\ethernet\cadence\macb_main.c。
查看SAMA5D27处理器内核启动日志,找到网卡初始化部分,查看硬件MAC地址的设置。
进入系统后,执行ifconfig查看网卡信息。下图中eth0 对应的是SAMA5D27 GMAC,连接的PHY KSZ8081。
在SAMA5D27的Linux系统下,每次启动Linux系统,MAC都会发生变化,这个主要是驱动里关于MAC的处理机制导致的。下面的章节阐述了驱动中MAC的处理机制。
2 Linux内核驱动中MAC地址设置流程
找到并进入probe函数:macb_probe
路径:\linux-at91\drivers\net\ethernet\cadence\macb_main.c
找到mac 地址获取部分的函数。
首先来看函数:of_get_mac_address,路径:/drivers/of/of_net.c。该函数用于搜索设备树节点下是否配置MAC地址相关的属性。
我们的设备树下并未配置MAC address相关的属性,接着进入of_get_nvmem_mac_address函数。
函数:of_get_nvmem_mac_address,路径:/drivers/of/of_net.c。该函数用于搜索设备树节点下是否有nvmem(Non Volatile Memory,不易丢失的存储)配置MAC地址相关的属性。
我们的设备树下并未配置非易失存储MAC address相关的属性,接着进入macb_get_hwaddr函数。
函数:macb_get_hwaddr,路径:\drivers\net\ethernet\cadence\macb_main.c
dev_get_platdata获取device结构体成员变量的void * platform_data。接下来读取GMAC Specific Address Bottom/Top Register[1:4],获取可用的 hw address,由于这个4组寄存器并未进行配置,值全部为零,并不是有效的MAC地址。
在没有可用的 hw address的时候,会调用函数eth_hw_addr_random,随机生成一组MAC地址。
3 Linux用户态修改MAC地址
3.1 配置命令ifconfig
可以通过命令行设置MAC物理地址,命令如下:
ifconfig eth0 down
ifconfig eth0 hw ether 1234567890ab
ifconfig eth0 up
3.2 配置命令ifconfig与内核的交互
下面介绍一下命令行ifconfig是如何与内核交互的。
ifconfig的源代码位于busybox,也可自行下载,分析如下:
对应ifconfig eth0 down和ifconfig eth0 up的函数如下:
比如,当我们敲ifconfig eth0 down时,实则就是调用:
set_if_down(“eth0”, master_flags.ifr_flags);
除了上面的两个函数外,还有以下常用函数:
set_if_addr(); //设置地址(包括IP,掩码,广播,目的地)
set_master_hwaddr(); //设置mac物理地址
接下来我们以eth0为例,来跟踪ifconfig up/down和ifconfig eth0 hw ether如何调用内核的。
1)分析set_if_up()
set_if_up()函数将会调用set_if_flags(“eth0”, flags | IFF_UP), 添加ifname(eth0) 开启标志位。
2)分析set_if_up()->set_if_flags(“eth0”, flags | IFF_UP)
该函数如下所示:
3)寻找SIOCSIFFLAGS宏,看看内核那里在实现它。
找到位于net\core\dev_ioctl.c的dev_ioctl()函数,该函数重要部分代码如下:
从上面可以看出,我们设置mac物理地址时的流程也会运行到这里,最终他们都会调用dev_ifsioc(net, &ifr, cmd)函数。
4)最终ifconfig eth0 up调用内核过程
然后在__dev_change_flags(dev, flags)函数中,通过判断flag的IFF_UP位上的值是否相反,来实现是调用__dev_close()还是__dev_open()来开关eth0。
如下图所示:
然后__dev_open()则将会调用网卡驱动的net_device_ops结构体下的成员函数实现打开。
__dev_open(dev):
dev->netdev_ops->ndo_validate_addr(dev); //测试dev->dev_addr(hw addr)是否有效,一般都是调用eth_validate_addr()函数,需要注意hw_addr[0]的最低位不能为1
dev->netdev_ops->ndo_open(dev); //调用open()函数实现ifconfig up
同样__dev_close()会调用下面的成员函数实现关闭:
dev->netdev_ops->ndo_stop(dev); //调用stop ()函数实现ifconfig down
寻找net_device_ops结构体的成员函数位于哪里?
上面讲的dev 变量是struct net_device类型,而struct net_device在内核中表示我们的一个网卡驱动设备,注册该变量的文件都处于内核drivers/net目录下,通过register_netdev() 内核函数来注册。
以SAMA5D27处理的GMAC网卡为例,路径:
\linux-at91\drivers\net\ethernet\cadence\macb_main.c
而对于ifconfig eth0 hw ether 设置网卡的MAC地址流程如下所示:
最终调用 eth_mac_addr 函数:
路径:/net/ethernet/eth.c
4 总结
使用ifconfig命令可以临时修改MAC地址,但开发板重启后,由于内核重新加载,MAC地址还是会随机生成。如果想固定MAC地址,可以对内核进行修改,在MAC控制器驱动的probe函数中,macb_get_hwaddr函数下,直接对存储MAC地址的结构体进行赋值。