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即可。
总结
虽然最终代码上的修改可能只有两三行,但是背后可以挖掘的内容还有很多,例如是设备先注册还是驱动先注册,协议栈何时启动等等,以后慢慢分析。相关文章推荐
- Epoll模型详解
- 【Linux】浅谈I/O模型
- Linux /proc/buddyinfo理解
- linux network command
- centos 5.6 简单搭建samba服务器
- Linux汇编代码学习,反汇编简单的c及分析汇编代码工作过程
- Ubuntu linux 关机、重启、注销 命令
- [linux]linux下开启wifi热点
- 学习Linux计划书
- 双系统(linux+window) 修改启动首选项
- 安装完CentOS系统后发现时间与现在时间相差8小时
- linux 常见测试题
- 如何将CentOS中的中文语言改成英文的
- Linux 学习笔记
- Centos7安装Scrapy过程
- Centos下安装Scrapy
- Linux 的多线程编程的高效开发经验
- Linux时间函数之gettimeofday()函数之使用方法
- 一张图看清Linux 内核运行原理
- linux 安装svn