Amlogic网卡是如何获取MAC地址的

首先搜到的,是
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写进去的。

你可能感兴趣的:(Amlogic网卡是如何获取MAC地址的)