美文网首页
Amlogic网卡是如何获取MAC地址的

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

作者: 叶迎宪 | 来源:发表于2022-03-12 18:35 被阅读0次

    首先搜到的,是
    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地址的

          本文链接:https://www.haomeiwen.com/subject/celarrtx.html