linux 以太网卡驱动

一、网络设备基本结构

  网络设备的系统框图如下所示:

  

 

  mac:工作在网络模型的数据链路层,通过rgmii或rmii接口连接phy,mac控制器中的mdio控制器提供mdio接口,用于访问phy寄存器。

  phy:工作在网络模型的物理层,是IEEE802.3规定的一个标准模块。IEEE802.3规定了 地址0~15共16个通用寄存器,只要配置好这些通用寄存器就能保证phy芯片正常工作。16~31地址的寄存器有厂家自行定义。

 

二、linux中关于网络设备概述

  总要结构体

  struct phy_device:phy

  struct phy_driver:phy驱动

  struct mdio_device:mdio从设备,指的就是phy设备

  struct bus_type mdio_bus_type:mdio总线

  struct mii_bus:mdio总线,定义了mdio接口读写方法

 

  linux通过设备、总线、驱动模型来管理设备和驱动,mac控制器通过mdio总线来管理phy设备,mdio总线与i2c总线类似,可以一个主机对应多个从设备,每个从设备都有地址。mdio最多接32个phy设备。内核中用mdio_bus_type代表mdio总线。是struct bus_type结构体,对应的目录是/sys/mdio,在/sys/mdio/devices目录中会有挂载在mdio的phy设备,在/sys/mdio/drivers中会有phy设备的驱动。hi3559对应的/sys/bus/mdio 目录如下所示:

  

  

 

 

mii_bus 结构体代表mdio总线,定义了mdio总线的读写方法,这个总线和mdio_bus_type不一样,mdio_bus_type是bus_type,只是为了管理mdio设备和驱动。
struct mii_bus {
    struct module *owner;
    const char *name;
    char id[MII_BUS_ID_SIZE];
    void *priv;
    int (*read)(struct mii_bus *bus, int addr, int regnum);          //mdio总线的读方法
    int (*write)(struct mii_bus *bus, int addr, int regnum, u16 val);    //mdio总线写方法
    int (*reset)(struct mii_bus *bus);

    /*
     * A lock to ensure that only one thing can read/write
     * the MDIO bus at a time
     */
    struct mutex mdio_lock;

    struct device *parent;
    enum {
        MDIOBUS_ALLOCATED = 1,
        MDIOBUS_REGISTERED,
        MDIOBUS_UNREGISTERED,
        MDIOBUS_RELEASED,
    } state;
    struct device dev;                                //mdio总线实体

    /* list of all PHYs on bus */
    struct mdio_device *mdio_map[PHY_MAX_ADDR];                //挂载在mdio总线下的phy设备,最多32个,PHY_MAX_ADDR值为32

    /* PHY addresses to be ignored when probing */
    u32 phy_mask;

    /* PHY addresses to ignore the TA/read failure */
    u32 phy_ignore_ta_mask;

    /*
     * An array of interrupts, each PHY's interrupt at the index
     * matching its address
     */
    int irq[PHY_MAX_ADDR];                            //pyh地址
};

 

三、Hi3559以太网卡驱动加载过程

3.1 hi3559以太网设备树配置

  Hi3559以太网控制器设备树节点:Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\arch\arm64\boot\dts\hisilicon\hi3559av100.dtsi:

  

  mac中mdio设备树节点:

  

  添加phy设备树节点:

  Hi3559AV100_SDK_V2.0.3.0\package\osdrv\opensource\kernel\linux-4.9.y\arch\arm64\boot\dts\hisilicon\hi3559av100-demb.dtsi:

  

3.2 mac控制器驱动加载

mac控制器入口函数为:linux-4.9.y\drivers\net\ethernet\hisilicon\higmac\higmac.c

  

  

  可以看到compatible与higmac设备树中的一致,所以higmac_dev_probe就是gmac控制器的驱动入口函数

3.3 phy设备创建

  从设备树中可以看出,phy在mdio下面,所以先要加载mdio驱动,再加载phy驱动。mdio驱动入口函数为:linux-4.9.y\drivers\net\phy\mdio-hisi-gemac.c

  

  hisi_gemac_mdio_probe

    -->of_mdiobus_register

      -->of_mdiobus_register_phy

        -->get_phy_device

        -->phy_device_register

of_mdiobus_register函数主要功能是注册mdio总线mii_bus,以及创建phy
int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
{
    struct device_node *child;
    bool scanphys = false;
    int addr, rc;

    /* Do not continue if the node is disabled */
    if (!of_device_is_available(np))
        return -ENODEV;

    /* Mask out all PHYs from auto probing.  Instead the PHYs listed in
     * the device tree are populated after the bus has been registered */
    mdio->phy_mask = ~0;

    mdio->dev.of_node = np;

    /* Register the MDIO bus */
    rc = mdiobus_register(mdio);    //注册mdio总线
    if (rc)
        return rc;

    /* Loop over the child nodes and register a phy_device for each phy */
    for_each_available_child_of_node(np, child) {    //扫描mdio设备树节点下的所有phy芯片,一条mdio总线最多可以接32个phy芯片
        addr = of_mdio_parse_addr(&mdio->dev, child);  //读取设备树中phy的地址,即reg的值
        if (addr < 0) {
            scanphys = true;
            continue;
        }

        if (of_mdiobus_child_is_phy(child))        //判断mdio下面如果是phy芯片,则注册phy
            of_mdiobus_register_phy(mdio, child, addr);
        else
            of_mdiobus_register_device(mdio, child, addr);
    }

    if (!scanphys)
        return 0;

    /* auto scan for PHYs with empty reg property */   //如果设备树中没有定义phy地址,则自动扫描
    for_each_available_child_of_node(np, child) {
        /* Skip PHYs with reg property set */
        if (of_find_property(child, "reg", NULL))
            continue;

        for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
            /* skip already registered PHYs */
            if (mdiobus_is_registered_device(mdio, addr))
                continue;

            /* be noisy to encourage people to set reg property */
            dev_info(&mdio->dev, "scan phy %s at address %i\n",
                 child->name, addr);

            if (of_mdiobus_child_is_phy(child))
                of_mdiobus_register_phy(mdio, child, addr);
        }
    }

    return 0;
}

 

of_mdiobus_register_phy函数功能是获取phy的id并创建phy,phy id号定义在地址为02和03的寄存器中,由厂家设置。
static void of_mdiobus_register_phy(struct mii_bus *mdio,
                    struct device_node *child, u32 addr)
{
    struct phy_device *phy;
    bool is_c45;
    int rc;
    u32 phy_id;

    printk ("yy of_mdiobus_register_phy is run\n");
    is_c45 = of_device_is_compatible(child,
                     "ethernet-phy-ieee802.3-c45");

    if (!is_c45 && !of_get_phy_id(child, &phy_id))        //如果设备上定义了phy的id就用设备树中的id,这里设备树没有定义
        phy = phy_device_create(mdio, addr, phy_id, 0, NULL);
    else
        phy = get_phy_device(mdio, addr, is_c45);        //从02和03寄存器中读取phy id号,并创建phy
    if (IS_ERR(phy))
        return;

    rc = irq_of_parse_and_map(child, 0);
    if (rc > 0) {
        phy->irq = rc;
        mdio->irq[addr] = rc;
    } else {
        phy->irq = mdio->irq[addr];
    }

    if (of_property_read_bool(child, "broken-turn-around"))
        mdio->phy_ignore_ta_mask |= 1 << addr;

    /* Associate the OF node with the device structure so it
     * can be looked up later */
    of_node_get(child);
    phy->mdio.dev.of_node = child;

    /* All data is now stored in the phy struct;
     * register it */  
    rc = phy_device_register(phy);      //会在/sys/bus/mdio/devices/ 目录下创建phy设备的软链接,真正的phy设备文件会创建在/sys/devices/platform 目录下
    if (rc) {
        phy_device_free(phy);
        of_node_put(child);
        return;
    }

    dev_dbg(&mdio->dev, "registered phy %s at address %i\n",
        child->name, addr);
}

 3.4 phy驱动加载

phy驱动入口函数:linux-4.9.y\drivers\net\phy\phy_device.c

  

  这里加载了两个phy驱动,一个是百兆的一个是千兆的。驱动加载成功后,会在/sys/bus/mdio/drivers 目录下生成这个两个驱动目录

  

 

 phy_drivers_register

  -->phy_driver_register

    -->driver_register

      -->phy_probe

phy_driver_register函数主要是填充new_driver这个结构体,然后调用driver_register注册new_driver,
int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
{
    int retval;

    new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY;
    new_driver->mdiodrv.driver.name = new_driver->name;
    new_driver->mdiodrv.driver.bus = &mdio_bus_type;    //对应的总线是/sys/bus/mdio
    new_driver->mdiodrv.driver.probe = phy_probe;
    new_driver->mdiodrv.driver.remove = phy_remove;
    new_driver->mdiodrv.driver.owner = owner;

    retval = driver_register(&new_driver->mdiodrv.driver);
    if (retval) {
        pr_err("%s: Error %d in registering driver\n",
               new_driver->name, retval);

        return retval;
    }

    pr_debug("%s: Registered new driver\n", new_driver->name);

    return 0;
}

 3.5 mdio总线创建

  linux-4.9.y\drivers\net\phy\mdio_bus.c

  

  这个函数执行完后会在/sys/bus 目录下创建mdio_bus目录,代码mdio总线。

  

posted @ 2022-05-19 18:01  YYFaGe  阅读(3359)  评论(0编辑  收藏  举报