首先搜到的,是
https://stackoverflow.com/questions/15522948/how-to-extract-the-mac-address-of-an-interface-from-witthin-a-driver-code
设置mac地址,需要操作 net_device 结构体的 dev_addr 成员。而 amlogic 网卡驱动名称为 meson6-dwmac,驱动代码位于 drivers/net/ethernet/stmicro/stmmac/dwmac-meson.c。在这一层代码搜索关键字 dev_addr ,发现所有的操作都位于 stmmac/stmmac_main.c:
int stmmac_dvr_probe(struct device *device,
struct plat_stmmacenet_data *plat_dat,
struct stmmac_resources *res)
{
...
if (res->mac)
memcpy(priv->dev->dev_addr, res->mac, ETH_ALEN);
...
}
需要往上一层跟踪,看res是怎么传过来的 dwmac-meson.c
static int meson6_dwmac_probe(struct platform_device *pdev)
{
...
plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac);
if (IS_ERR(plat_dat))
return PTR_ERR(plat_dat);
...
ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
if (ret)
goto err_remove_config_dt;
...
}
接着看stmmac_probe_config_dt,位于 stmmac_platform.c
#ifdef CONFIG_DWMAC_MESON
static u8 DEFMAC[] = {0, 0, 0, 0, 0, 0};
static unsigned int g_mac_addr_setup;
static unsigned char chartonum(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return (c - 'A') + 10;
if (c >= 'a' && c <= 'f')
return (c - 'a') + 10;
return 0;
}
static int __init mac_addr_set(char *line)
{
unsigned char mac[6];
int i = 0;
for (i = 0; i < 6 && line[0] != '\0' && line[1] != '\0'; i++) {
mac[i] = chartonum(line[0]) << 4 | chartonum(line[1]);
line += 3;
}
memcpy(DEFMAC, mac, 6);
pr_debug("uboot setup mac-addr: %x:%x:%x:%x:%x:%x\n",
DEFMAC[0], DEFMAC[1], DEFMAC[2], DEFMAC[3], DEFMAC[4], DEFMAC[5]);
g_mac_addr_setup++;
return 0;
}
__setup("mac=", mac_addr_set);
#endif
static int setup_mac_addr(struct platform_device *pdev, const char **mac)
{
struct device_node *np = pdev->dev.of_node;
#ifdef CONFIG_DWMAC_MESON
if (g_mac_addr_setup) /*so uboot mac= is first priority.*/
*mac = DEFMAC;
else
*mac = of_get_mac_address(np);
#else
*mac = of_get_mac_address(np);
#endif
return 0;
}
struct plat_stmmacenet_data *
stmmac_probe_config_dt(struct platform_device *pdev, const char **mac)
{
...
setup_mac_addr(pdev, mac);
plat->interface = of_get_phy_mode(np);
...
}
从上面的代码来看,通过__setup宏,注册了mac_addr_set。当uboot传过来的cmdline中包含mac=参数时,会被记录到DEFMAC数组中。但如果uboot没有传mac地址过来,则通过调用of_get_mac_address获取mac地址。
搜了一下,这个of_get_mac_address位于drivers/of/of_net.c
const void *of_get_mac_address(struct device_node *np)
{
const void *addr;
addr = of_get_mac_addr(np, "mac-address");
if (addr)
return addr;
addr = of_get_mac_addr(np, "local-mac-address");
if (addr)
return addr;
return of_get_mac_addr(np, "address");
}
无非就是从dtb中搜索。但是dtb中没有定义会发送什么情况呢?翻了一下dtb,确实没有定义。如果在dtb中定义了,岂不是每个烧录的硬件mac地址都一样了?
看到这里,有点懵了。uboot又没有传过来,dtb中又没有定义,mac地址是怎么来的?还是加些日志,跟踪一下代码怎么执行吧。结果好吧,setup_mac_addr执行完成后,*mac 还是空指针,说明这时候还没有mac地址
又回去看了一遍代码。好吧,是我走漏眼了。在stmmac_dvr_probe后面一段,调用了stmmac_check_ether_addr,这里也可能设置了mac地址
static void stmmac_check_ether_addr(struct stmmac_priv *priv)
{
if (!is_valid_ether_addr(priv->dev->dev_addr)) {
priv->hw->mac->get_umac_addr(priv->hw,
priv->dev->dev_addr, 0);
if (!is_valid_ether_addr(priv->dev->dev_addr))
eth_hw_addr_random(priv->dev);
pr_info("%s: device MAC address %pM\n", priv->dev->name,
priv->dev->dev_addr);
}
}
真的是眼花了,居然没发现串口中打印了这么一行
[ 2.164538@4] eth%d: device MAC address 02:00:00:08:06:01
看来就是priv->hw->mac->get_umac_addr设置的mac地址。继续找这个prive->hw到底指向了什么东西。还是在 stmmac_main.c 里面
static int stmmac_hw_init(struct stmmac_priv *priv)
{
struct mac_device_info *mac;
/* Identify the MAC HW device */
if (priv->plat->has_gmac) {
priv->dev->priv_flags |= IFF_UNICAST_FLT;
mac = dwmac1000_setup(priv->ioaddr,
priv->plat->multicast_filter_bins,
priv->plat->unicast_filter_entries,
&priv->synopsys_id);
} else if (priv->plat->has_gmac4) {
priv->dev->priv_flags |= IFF_UNICAST_FLT;
mac = dwmac4_setup(priv->ioaddr,
priv->plat->multicast_filter_bins,
priv->plat->unicast_filter_entries,
&priv->synopsys_id);
} else {
mac = dwmac100_setup(priv->ioaddr, &priv->synopsys_id);
}
if (!mac)
return -ENOMEM;
priv->hw = mac;
通过打日志,可以发现初始化走的是第一个分支,dwmac1000那里。而设置has_gmac的地方位于stmmac_platform.c
struct plat_stmmacenet_data *
stmmac_probe_config_dt(struct platform_device *pdev, const char **mac)
{
...
if (of_device_is_compatible(np, "st,spear600-gmac") ||
of_device_is_compatible(np, "snps,dwmac-3.50a") ||
of_device_is_compatible(np, "snps,dwmac-3.70a") ||
of_device_is_compatible(np, "snps,dwmac")
#ifdef CONFIG_AMLOGIC_ETH_PRIVE
|| of_device_is_compatible(np, "amlogic, gxbb-eth-dwmac")
#endif
) {
/* Note that the max-frame-size parameter as defined in the
* ePAPR v1.1 spec is defined as max-frame-size, it's
* actually used as the IEEE definition of MAC Client
* data, or MTU. The ePAPR specification is confusing as
* the definition is max-frame-size, but usage examples
* are clearly MTUs
*/
of_property_read_u32(np, "max-frame-size", &plat->maxmtu);
of_property_read_u32(np, "snps,multicast-filter-bins",
&plat->multicast_filter_bins);
of_property_read_u32(np, "snps,perfect-filter-entries",
&plat->unicast_filter_entries);
plat->unicast_filter_entries = dwmac1000_validate_ucast_entries(
plat->unicast_filter_entries);
plat->multicast_filter_bins = dwmac1000_validate_mcast_bins(
plat->multicast_filter_bins);
plat->has_gmac = 1;
plat->pmt = 1;
}
}
而dts中是这样定义的
ethmac: ethernet@ff3f0000 {
compatible = "amlogic, g12a-eth-dwmac","snps,dwmac";
reg = <0x0 0xff3f0000 0x0 0x10000
0x0 0xff634540 0x0 0x8
0x0 0xff64c000 0x0 0xa0>;
reg-names = "eth_base", "eth_cfg", "eth_pll";
interrupts = <0 8 1>;
interrupt-names = "macirq";
status = "disabled";
clocks = <&clkc CLKID_ETH_CORE>;
clock-names = "ethclk81";
pll_val = <0x9c0040a 0x927e0000 0xac5f49e5>;
analog_val = <0x20200000 0x0000c000 0x00000023>;
};
有点扯远了,还是关注dwmac1000_setup做了些什么。代码位于drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
static void dwmac1000_get_umac_addr(struct mac_device_info *hw,
unsigned char *addr,
unsigned int reg_n)
{
void __iomem *ioaddr = hw->pcsr;
stmmac_get_mac_addr(ioaddr, addr, GMAC_ADDR_HIGH(reg_n),
GMAC_ADDR_LOW(reg_n));
}
static const struct stmmac_ops dwmac1000_ops = {
.core_init = dwmac1000_core_init,
.rx_ipc = dwmac1000_rx_ipc_enable,
.dump_regs = dwmac1000_dump_regs,
.host_irq_status = dwmac1000_irq_status,
.set_filter = dwmac1000_set_filter,
.flow_ctrl = dwmac1000_flow_ctrl,
.pmt = dwmac1000_pmt,
.set_umac_addr = dwmac1000_set_umac_addr,
.get_umac_addr = dwmac1000_get_umac_addr,
.set_eee_mode = dwmac1000_set_eee_mode,
.reset_eee_mode = dwmac1000_reset_eee_mode,
.set_eee_timer = dwmac1000_set_eee_timer,
.set_eee_pls = dwmac1000_set_eee_pls,
.debug = dwmac1000_debug,
.pcs_ctrl_ane = dwmac1000_ctrl_ane,
.pcs_rane = dwmac1000_rane,
.pcs_get_adv_lp = dwmac1000_get_adv_lp,
};
struct mac_device_info *dwmac1000_setup(void __iomem *ioaddr, int mcbins,
int perfect_uc_entries,
int *synopsys_id)
{
...
mac->pcsr = ioaddr;
mac->mac = &dwmac100_ops;
}
drivers/net/ethernet/stmicro/stmmac/dwmac1000.h
/* GMAC HW ADDR regs */
#define GMAC_ADDR_HIGH(reg) (((reg > 15) ? 0x00000800 : 0x00000040) + \
(reg * 8))
#define GMAC_ADDR_LOW(reg) (((reg > 15) ? 0x00000804 : 0x00000044) + \
(reg * 8))
stmmac_get_mac_addr 位于 drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c
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;
}
又得翻代码,看ioaddr是怎么来的
int stmmac_dvr_probe(struct device *device,
struct plat_stmmacenet_data *plat_dat,
struct stmmac_resources *res)
{
priv->ioaddr = res->addr;
}
static int meson6_dwmac_probe(struct platform_device *pdev)
{
ret = stmmac_get_platform_resources(pdev, &stmmac_res);
if (ret)
return ret;
ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
}
int stmmac_get_platform_resources(struct platform_device *pdev,
struct stmmac_resources *stmmac_res)
{
...
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
stmmac_res->addr = devm_ioremap_resource(&pdev->dev, res);
return PTR_ERR_OR_ZERO(stmmac_res->addr);
}
一下没看得太明白,不过貌似ioaddr也就是一个内存的地址而已,没有看到有其他代码设置或者初始化这块内存的值。网上搜了一下,amlogic要改mac地址,要么改uboot传递到kernel的参数,要么利用efuse,就是没讲清楚如果都没有设置的话mac地址是怎么来的。从kernel没有突破,就尝试从uboot看看吧。uboot日志中会有这么一行
MACADDR:02:00:00:08:06:01(from chipid)
翻到对应的代码,位于uboot的 net/eth.c
int eth_write_hwaddr(struct eth_device *dev, const char *base_name,
int eth_number)
{
...
if (is_valid_ether_addr(dev->enetaddr)) {
eth_setenv_enetaddr_by_index(base_name, eth_number,
dev->enetaddr);
} else {
eth_getenv_enetaddr_by_index(base_name, eth_number, env_enetaddr);
if(env_enetaddr[0] == 0x0 && env_enetaddr[1] == 0x15 && env_enetaddr[2] == 0x18
&& env_enetaddr[3] == 0x01 && env_enetaddr[4] == 0x81 && env_enetaddr[5] == 0x31)
{
#ifdef CONFIG_RANDOM_ETHADDR
eth_hw_addr_random(dev);
eth_setenv_enetaddr_by_index(base_name, eth_number,
dev->enetaddr);
#endif
}
uint8_t buff[16];
if (get_chip_id(&buff[0], sizeof(buff)) == 0) {
sprintf((char *)env_enetaddr,"02:%02x:%02x:%02x:%02x:%02x",buff[8],
buff[7],buff[6],buff[5],buff[4]);
printf("MACADDR:%s(from chipid)\n",env_enetaddr);
setenv("ethaddr",(const char *)env_enetaddr);
}
eth_getenv_enetaddr_by_index(base_name, eth_number, env_enetaddr);
if (eth_address_set(env_enetaddr)) {
if (eth_address_set(dev->enetaddr) &&
memcmp(dev->enetaddr, env_enetaddr, 6)) {
printf("\nWarning: %s MAC addresses don't match:\n",
dev->name);
printf("Address in SROM is %pM\n",
dev->enetaddr);
printf("Address in environment is %pM\n",
env_enetaddr);
}
memcpy(dev->enetaddr, env_enetaddr, 6);
} else if (is_valid_ether_addr(dev->enetaddr)) {
eth_setenv_enetaddr_by_index(base_name, eth_number,
dev->enetaddr);
printf("\nWarning: %s using MAC address from net device\n",
dev->name);
} else if (!(eth_address_set(dev->enetaddr))) {
printf("\nError: %s address not set.\n",
dev->name);
return -EINVAL;
}
}
if (dev->write_hwaddr && !eth_mac_skip(eth_number)) {
if (!is_valid_ether_addr(dev->enetaddr)) {
printf("\nError: %s address %pM illegal value\n",
dev->name, dev->enetaddr);
return -EINVAL;
}
ret = dev->write_hwaddr(dev);
if (ret)
printf("\nWarning: %s failed to set MAC address\n", dev->name);
}
而一些预定义宏,位于 board/amlogic/configs/g12b_w400_v1.h
// #define CONFIG_RANDOM_ETHADDR 1
说实在话,这段代码写得有点乱。大致意思是先读出chipid,赋给env_enetaddr。如果环境变量中有设置这个网口的mac地址(例如开启了随机MAC CONFIG_RANDOM_ETHADDR),则 env_enetaddr被环境变量覆盖。如果dev设备中有write_hwaddr函数,则调用这个函数对mac地址进行写入。至于有没有write_hwaddr,光读代码一下子不容易看出来,还是实际运行看一下。通过printf把 dev->name, dev->write_hwaddr都打印出来
MACADDR:02:00:00:08:06:01(from chipid)
dwmac.ff3f0000 00000000d7eb77a8
说明是有write_hwaddr这个函数,把mac地址写入硬件的某个寄存器之类的地方的。具体再找找write_hwaddr的实现在哪里。看到uboot编译的时候,driver/net目录下面只编译了一个designware.o,因此进去 drivers/net/designware.c 看看
static int dw_write_hwaddr(struct eth_device *dev)
{
struct dw_eth_dev *priv = dev->priv;
struct eth_mac_regs *mac_p = priv->mac_regs_p;
u32 macid_lo, macid_hi;
u8 *mac_id = &dev->enetaddr[0];
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;
}
struct eth_mac_regs {
u32 conf; /* 0x00 */
u32 framefilt; /* 0x04 */
u32 hashtablehigh; /* 0x08 */
u32 hashtablelow; /* 0x0c */
u32 miiaddr; /* 0x10 */
u32 miidata; /* 0x14 */
u32 flowcontrol; /* 0x18 */
u32 vlantag; /* 0x1c */
u32 version; /* 0x20 */
u8 reserved_1[20];
u32 intreg; /* 0x38 */
u32 intmask; /* 0x3c */
u32 macaddr0hi; /* 0x40 */
u32 macaddr0lo; /* 0x44 */
};
直接往某块内存把MAC地址写了进去。这个跟linux驱动从某块内存读出MAC地址是对应的,而且偏移量也正好是0x40和0x44。说明默认的根据cpu id获取到的mac地址就是uboot写进去的。