Linux内核中DM8168的网口驱动移植

背景

在DM8168的EVM板中,两个EMAC模块分别连接到两个PHY上,用同一个MDIO模块管理,两个PHY配置为不同地址。内核启动后可以注册为两个eth设备。
然而在新研FXX板中,DM8168改变了网口连接方式,其EMAC0未引连接PHY,而是直接用GMII接口连接到对端某芯片上。因此需要修改内核驱动代码来完成适配。

分析

分析内核与网口有关的驱动可知,EMAC和MDIO都作为内核platform设备由platform虚拟总线来管理。因此对驱动的分析分为两部分,一是一是device部分,一是device_driver部分。在内核启动过程中,两者先后注册到总线上,然后适配在一起共同工作。

网口设备

两个网口和一个MDIO都是platform_device的设备。设备注册和初始化的调用关系为:
omap2_init_devices()–>ti81xx_ethernet_init()–>ti816x_ethernet_init()
先看omap2_init_devices(),它被声明为:

======== arch/arm/mach-omap2/devices.c 2208 2208 ======== arch_initcall(omap2_init_devices);

因此是在系统初始化的某一个阶段调用的。

再看ti816x_ethernet_init()函数:

  1. 把MAC地址等存入ti816x_emac1_pdata私有数据变量中
  2. 把ti816x_emac1_device设备的私有信息指针指向ti816x_emac1_pdata
  3. 调用platform_device_register()注册ti816x_emac1_device设备
  4. 调用platform_device_register()注册ti816x_mdio_device设备
  5. 与1~3步同样的方式注册ti816x_emac2_device设备
  6. 调用ti816x_emac_mux()把EMAC1的引脚配置为EMAC功能。

我们来看一看静态的网口私有变量结构体:

======== arch/arm/mach-omap2/devices.c 1125 1133 ========
static struct emac_platform_data ti816x_emac1_pdata = {
    .rmii_en    =   0,
    .phy_id     =   "0:01",
};

static struct emac_platform_data ti816x_emac2_pdata = {
    .rmii_en    =   0,
    .phy_id     =   "0:02",
};

结构体中这两个字段尤其是phy_id字段特别有用,下面会分析到。

网口驱动

驱动分为MDIO驱动和EMAC驱动两部分。

MDIO驱动

============ drivers/net/davince_md.c 460 464 ========
static int __init davinci_mdio_init(void)
{
    return platform_driver_register(&davinci_mdio_driver);
}
device_initcall(davinci_mdio_init);

这个函数也是在设备初始化的某一个阶段调用的。这里将davinci_mdio_driver这个驱动注册到了platform总线中。在注册过程中,会调用其probe方法davinci_mdio_probe(),它申请一条MDIO总线,然后调用mdiobus_register()注册之,这条总线上将来会挂接PHY设备。

mdiobus_register()–>davinci_mdio_reset()&mdiobus_scan()–>get_phy_device()–>get_phy_id()

mdiobus_register()主要流程是:

  1. 调用davinci_mdio_reset()使能MDIO模块,再等待一段足够长的时间让总线遍历所有PHY,然后把活动的PHY地址记录入phy_mask中。
  2. 按照活动PHY地址,依次调用mdiobus_scan()

mdiobus_scan()–>get_phy_device()–>get_phy_id()
调用davinci_mdio_read()读取某个PHY的ID,读取到正确值后就调用phy_device_create()创建这样一个设备。然后把设备注册到MDIO总线上,注册时按照0:01这样的规则为设备命名,其中0代表总线ID,01代表PHY的地址。
这样MDIO总线和总线上的设备就依次注册完成。再打印出这些设备后davinci_mdio_probe()结束。

EMAC驱动

============ drivers/net/davince_emac.c 2042 2046 ========
static int __init davinci_emac_init(void)
{
    return platform_driver_register(&davinci_emac_driver);
}
late_initcall(davinci_emac_init);

这个函数也是在设备初始化的某一个阶段调用的。

这里将davinci_emac_driver这个驱动注册到了platform总线中。在注册过程中,会调用其probe方法davinci_emac_probe()。主要流程是:

  1. 申请一个net_device结构体和私有数据结构体
    申请结构体调用alloc_etherdev()实现,它是alloc_netdev_mq()的一个包裹函数,会调用kzalloc()申请一个net_device结构体加上私有数据再加对齐数据的大小,然后调用ether_setup()初始化net_device中的一些函数指针和一些变量字段。这个过程中会把设备名称初始化为ethx这样的结构,其中x由注册顺序决定。对于以太网设备申请的net_device和私有数据是在内存中连续的。
  2. 对两个结构体的各字段进行初始化
    然后probe会初始化私有数据结构体中的各字段,以及net_device结构体中其余一些函数指针和变量字段。
  3. 调用register_netdev()注册。
    最后register_netdev()函数是register_netdevice()的包裹函数,这个函数在《Understanding Linux Network Intervals》中有详细描述,这里略过。

设备私有数据结构中有一个phy_id指针字段,它被指向EMAC设备私有数据中的phy_id。这个字段决定了对外的物理连接。

emac_dev_open()函数中,有这样一段对phy的配置:

=========== drivers/net/davince_emac.c 1593 1631 ==============
    priv->phydev = NULL;
    /* use the first phy on the bus if pdata did not give us a phy id */
    if (!priv->phy_id) {
        struct device *phy;

        phy = bus_find_device(&mdio_bus_type, NULL, NULL,
                      match_first_device);
        if (phy)
            priv->phy_id = dev_name(phy);
    }

    if (priv->phy_id && *priv->phy_id) {
        priv->phydev = phy_connect(ndev, priv->phy_id,
                       &emac_adjust_link, 0,
                       PHY_INTERFACE_MODE_MII);

        if (IS_ERR(priv->phydev)) {
            dev_err(emac_dev, "could not connect to phy %s\n",
                priv->phy_id);
            priv->phydev = NULL;
            return PTR_ERR(priv->phydev);
        }

        priv->link = 0;
        priv->speed = 0;
        priv->duplex = ~0;

        dev_info(emac_dev, "attached PHY driver [%s] "
            "(mii_bus:phy_addr=%s, id=%x)\n",
            priv->phydev->drv->name, dev_name(&priv->phydev->dev),
            priv->phydev->phy_id);
    } else {
        /* No PHY , fix the link, speed and duplex settings */
        dev_notice(emac_dev, "no phy, defaulting to 100/full\n");
        priv->link = 1;
        priv->speed = SPEED_100;
        priv->duplex = DUPLEX_FULL;
        emac_update_phystatus(priv);
    }

当phy_id这个指针为空时,则从MDIO总线上找到第一个设备成为phy_id。
如果phy_id指针不为空,且指向内容不为空,则调用phy_connect()来连接指定PHY,将连接状态置为断开。
否则表示没有指定默认PHY且没有找到可用PHY,将连接配置为100M全双工。
这样私有数据的phydev字段为空,因此之后所有对PHY的操作都不真正实现。

实际上在phy_id的定义里,有这样一段注释:

============== include/linux/davince_emac.h 29 35 =================
    /*
     * phy_id can be one of the following:
     *   - NULL     : use the first phy on the bus,
     *   - ""       : force to 100/full, no mdio control
     *   - "<bus>:<addr>"   : use the specified bus and phy
     */
    const char *phy_id;

因此对于FXX板来说,要配置为千兆,则需要将设备结构体的该字段设置为”“,然后在上述emac_dev_open()中的else分支中将priv->speed赋值为SPEED_1000即可。

总结

虽然最终代码上的修改可能只有两三行,但是背后可以挖掘的内容还有很多,例如是设备先注册还是驱动先注册,协议栈何时启动等等,以后慢慢分析。

你可能感兴趣的:(移植,网口驱动)