您的位置:首页 > 运维架构 > Linux

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

2016-02-23 23:55 549 查看

背景

在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()
函数:

把MAC地址等存入ti816x_emac1_pdata私有数据变量中

把ti816x_emac1_device设备的私有信息指针指向ti816x_emac1_pdata

调用
platform_device_register()
注册ti816x_emac1_device设备

调用
platform_device_register()
注册ti816x_mdio_device设备

与1~3步同样的方式注册ti816x_emac2_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驱动两部分。

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()
主要流程是:

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

按照活动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()
。主要流程是:

申请一个net_device结构体和私有数据结构体

申请结构体调用
alloc_etherdev()
实现,它是
alloc_netdev_mq()
的一个包裹函数,会调用
kzalloc()
申请一个net_device结构体加上私有数据再加对齐数据的大小,然后调用
ether_setup()
初始化net_device中的一些函数指针和一些变量字段。这个过程中会把设备名称初始化为ethx这样的结构,其中x由注册顺序决定。对于以太网设备申请的net_device和私有数据是在内存中连续的。

对两个结构体的各字段进行初始化

然后probe会初始化私有数据结构体中的各字段,以及net_device结构体中其余一些函数指针和变量字段。

调用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即可。

总结

虽然最终代码上的修改可能只有两三行,但是背后可以挖掘的内容还有很多,例如是设备先注册还是驱动先注册,协议栈何时启动等等,以后慢慢分析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: