在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()
函数:
platform_device_register()
注册ti816x_emac1_device设备platform_device_register()
注册ti816x_mdio_device设备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驱动两部分。
============ 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()
主要流程是:
davinci_mdio_reset()
使能MDIO模块,再等待一段足够长的时间让总线遍历所有PHY,然后把活动的PHY地址记录入phy_mask中。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()
结束。
============ 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()
。主要流程是:
alloc_etherdev()
实现,它是alloc_netdev_mq()
的一个包裹函数,会调用kzalloc()
申请一个net_device结构体加上私有数据再加对齐数据的大小,然后调用ether_setup()
初始化net_device中的一些函数指针和一些变量字段。这个过程中会把设备名称初始化为ethx这样的结构,其中x由注册顺序决定。对于以太网设备申请的net_device和私有数据是在内存中连续的。设备私有数据结构中有一个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即可。
虽然最终代码上的修改可能只有两三行,但是背后可以挖掘的内容还有很多,例如是设备先注册还是驱动先注册,协议栈何时启动等等,以后慢慢分析。