1. 前言

内核版本:linux 4.9.225,以freescale为例。

2. 概述

PHY芯片为OSI的最底层-物理层(Physical Layer),通过MII/GMII/RMII/SGMII/XGMII等多种媒体独立接口(介质无关接口)与数据链路层的MAC芯片相连,并通过MDIO接口实现对PHY状态的监控、配置和管理。

PHY与MAC整体的大致连接框架如下(图片来源于网络):

PHY的整个硬件系统组成比较复杂,PHY与MAC相连(也可以通过一个中间设备相连),MAC与CPU相连(有集成在内部的,也有外接的方式),PHY与MAC通过MII和MDIO/MDC相连,MII是走网络数据的,MDIO/MDC是用来与PHY的寄存器通讯的,对PHY进行配置。

PHY的驱动与I2C/SPI的驱动一样,分为控制器驱动设备器驱动。本节先讲控制器驱动。

3. PHY的控制器驱动总述

PHY的控制器驱动和SPI/I2C非常类似,控制器的核心功能是实现具体的读写功能。区别在于PHY的控制器读写功能的实现大致可以分为两种方式():

PHY的控制器一般被描述为mdio_bus平台设备(注意:这是一个设备,等同于SPI/I2C中的master设备;和总线、驱动、设备中的bus不是一个概念)。

既然是平台设备,那么设备树中必定要有可以被解析为平台设备的节点,也要有对应的平台设备驱动。与SPI驱动类似,PHY设备模型也是在控制器驱动的probe函数中注册的

本文两者都会涉及,主要讲后者。

4. 通过GPIO/外围soc模拟MDIO时序的方式

4.1 控制器平台设备在设备树中的大致描述方式(不完全准确,主要描述匹配的规则)

# linux-4.9.225\Documentation\devicetree\bindings\soc\fsl\cpm_qe\network.txt
* MDIO
Currently defined compatibles: fsl,pq1-fec-mdio (reg is same as first resource of FEC device) fsl,cpm2-mdio-bitbang (reg is port C registers)
Properties for fsl,cpm2-mdio-bitbang:
fsl,mdio-pin : pin of port C controlling mdio data
fsl,mdc-pin : pin of port C controlling mdio clock
Example: mdio@10d40 { compatible = "fsl,mpc8272ads-mdio-bitbang","fsl,mpc8272-mdio-bitbang","fsl,cpm2-mdio-bitbang";reg = <10d40 14>;#address-cells = <1>;#size-cells = <0>;fsl,mdio-pin = <12>;fsl,mdc-pin = <13>; # linux-4.9.225\Documentation\devicetree\bindings\phy    xxx_phy: xxx-phy@xxx {                       //描述控制器下挂PHY设备的节点reg = <0x0>;                             //PHY的地址};
};

4.2 控制器平台驱动代码走读

4.2.1 控制器平台驱动的注册

static const struct of_device_id fs_enet_mdio_bb_match[] = {{.compatible = "fsl,cpm2-mdio-bitbang",           //匹配平台设备的名称},{},
};
MODULE_DEVICE_TABLE(of, fs_enet_mdio_bb_match);static struct platform_driver fs_enet_bb_mdio_driver = {.driver = {.name = "fsl-bb-mdio",.of_match_table = fs_enet_mdio_bb_match,},.probe = fs_enet_mdio_probe,.remove = fs_enet_mdio_remove,
};module_platform_driver(fs_enet_bb_mdio_driver);     //注册控制器平台设备驱动

4.2.2 控制器平台驱动的probe函数走读

/**********************************************************************************************通过GPIO/外围soc模拟MDIO时序方式的MDIO驱动(probe函数中完成PHY设备的创建和注册)
***********************************************************************************************/
# linux-4.9.225\drivers\net\ethernet\freescale\fs_enet\mii-bitbang.cfs_enet_mdio_probe(struct platform_device *ofdev)
|--- bitbang = kzalloc(sizeof(struct bb_info), GFP_KERNEL)
|
|--- bitbang->ctrl.ops = &bb_ops  ----------------------------------------------->| static struct mdiobb_ops bb_ops = {
|                                                                                 |  .owner = THIS_MODULE,
|                                                                                 |  .set_mdc = mdc,
|                                                                                 |  .set_mdio_dir = mdio_dir,
|                                                                                 |  .set_mdio_data = mdio,               |-->实现为GPIO的读写
|                                                                                 |  .get_mdio_data = mdio_read,
|                                                                                 | };
|                                                                                  \<---------------------------------------------------------|
|--- new_bus = alloc_mdio_bitbang(&bitbang->ctrl)                                                                                             |
|    |--- bus = mdiobus_alloc()        -----------|                                                     | struct mdiobb_ctrl *ctrl = bus->priv|  |
|    |--- bus->read = mdiobb_read      -----------|                                                  | ctrl->ops->set_mdc                  |  |
|    |--- bus->write = mdiobb_write    -----------|--mdiobb_read/mdiobb_write/mdiobb_reset函数的实现-| ctrl->ops->set_mdio_dir            |--|
|    |--- bus->reset = mdiobb_reset    -----------|       /                                          | ctrl->ops->set_mdio_data            |
|    |--- bus->priv = ctrl  <----------------------------                                            | ctrl->ops->get_mdio_data            |
|
|--- fs_mii_bitbang_init                                      //设置用来模拟mdc和mdio的管脚资源
|    |--- of_address_to_resource(np, 0, &res)                 //转换设备树地址并作为资源返回,设备树中指定
|    |
|    |--- snprintf(bus->id, MII_BUS_ID_SIZE, "%x", res.start) //把资源的起始地址设置为bus->id
|    |
|    |--- data = of_get_property(np, "fsl,mdio-pin", &len)
|    |--- mdio_pin = *data                                    //决定控制mdio数据的端口的引脚
|    |
|    |--- data = of_get_property(np, "fsl,mdc-pin", &len)
|    |--- mdc_pin = *data                                     //控制mdio时钟的端口引脚
|    |
|    |--- bitbang->dir = ioremap(res.start, resource_size(&res))
|    |
|    |--- bitbang->dat = bitbang->dir + 4
|    |--- bitbang->mdio_msk = 1 << (31 - mdio_pin)
|    |--- bitbang->mdc_msk = 1 << (31 - mdc_pin)
|
|--- of_mdiobus_register(new_bus, ofdev->dev.of_node)       //注册mii_bus设备,并通过设备树子节点创建PHY设备 <===of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
|    |--- mdio->phy_mask = ~0                               //屏蔽所有PHY,防止自动探测。相反,设备树中列出的phy将在总线注册后填充
|    |--- mdio->dev.of_node = np
|    |--- mdiobus_register(mdio)                            //@注意@ 注册MDIO总线设备(注意是总线设备不是总线,因为总线也是一种设备。mdio_bus是在其他地方注册的,后面会讲到)
|    |    |--- __mdiobus_register(bus, THIS_MODULE)
|    |    |    |--- bus->owner = owner
|    |    |    |--- bus->dev.parent = bus->parent
|    |    |    |--- bus->dev.class = &mdio_bus_class
|    |    |    |--- bus->dev.groups = NULL
|    |    |    |--- dev_set_name(&bus->dev, "%s", bus->id)  //设置总线设备的名称
|    |    |    |--- device_register(&bus->dev)              //注册总线设备
|    |
|    |--- for_each_available_child_of_node(np, child)       //遍历这个平台设备的子节点并为每个phy注册一个phy_device
|         |--- addr = of_mdio_parse_addr(&mdio->dev, child) //从子节点的"reg"属性中获得PHY设备的地址
|         |    |--- of_property_read_u32(np, "reg", &addr)
|         |--- if (addr < 0)                                //如果未获得子节点的"reg"属性,则在后面再启用扫描可能存在的PHY的,然后注册
|         |    |--- scanphys = true
|         |    |--- continue
|         |
|         |--- of_mdiobus_register_phy(mdio, child, addr)   //创建并注册PHY设备
|         |    |--- is_c45 = of_device_is_compatible(child,"ethernet-phy-ieee802.3-c45") //判断设备树中的PHY的属性是否指定45号条款
|         |    |
|         |    |--- if (!is_c45 && !of_get_phy_id(child, &phy_id))      //如果设备树中的PHY的属性未指定45号条款 且未通过"ethernet-phy-id%4x.%4x"属性指定PHY的ID
|         |    |    |---phy_device_create(mdio, addr, phy_id, 0, NULL)
|         |    |---else //我这里采用的是else分支
|         |    |    |---phy = get_phy_device(mdio, addr, is_c45)        //在@bus上的@addr处读取PHY的ID寄存器,然后分配并返回表示它的phy_device
|         |    |        |--- get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids)        //通过mdio得到PHY的ID
|         |    |        |--- phy_device_create(bus, addr, phy_id, is_c45, &c45_ids)  //创建PHY设备
|         |    |             |--- struct phy_device *dev
|         |    |             |--- struct mdio_device *mdiodev
|         |    |             |--- dev = kzalloc(sizeof(*dev), GFP_KERNEL)
|         |    |             |--- mdiodev = &dev->mdio                     //mdiodev是最新的内核引入,较老的版本没有这个结构
|         |    |             |--- mdiodev->dev.release = phy_device_release
|         |    |             |--- mdiodev->dev.parent = &bus->dev
|         |    |             |--- mdiodev->dev.bus = &mdio_bus_type        //PHY设备和驱动都会挂在mdio_bus下,匹配时会调用对应的match函数  ---|
|         |    |             |--- mdiodev->bus = bus                                                                                         |
|         |    |             |--- mdiodev->pm_ops = MDIO_BUS_PHY_PM_OPS                                                                      |
|         |    |             |--- mdiodev->bus_match = phy_bus_match       //真正实现PHY设备和驱动匹配的函数<--------------------------------|
|         |    |             |--- mdiodev->addr = addr
|         |    |             |--- mdiodev->flags = MDIO_DEVICE_FLAG_PHY
|         |    |             |--- mdiodev->device_free = phy_mdio_device_free
|         |    |             |--- diodev->device_remove = phy_mdio_device_remove
|         |    |             |--- dev->speed = SPEED_UNKNOWN
|         |    |             |--- dev->duplex = DUPLEX_UNKNOWN
|         |    |             |--- dev->pause = 0
|         |    |             |--- dev->asym_pause = 0
|         |    |             |--- dev->link = 1
|         |    |             |--- dev->interface = PHY_INTERFACE_MODE_GMII
|         |    |             |--- dev->autoneg = AUTONEG_ENABLE                            //默认支持自协商
|         |    |             |--- dev->is_c45 = is_c45
|         |    |             |--- dev->phy_id = phy_id
|         |    |             |--- if (c45_ids)
|         |    |             |    |--- dev->c45_ids = *c45_ids
|         |    |             |--- dev->irq = bus->irq[addr]
|         |    |             |--- dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr)
|         |    |             |--- dev->state = PHY_DOWN                                   //指示PHY设备和驱动程序尚未准备就绪,在PHY驱动的probe函数中会更改为READY
|         |    |             |--- INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine) //PHY的状态机(核心WORK)
|         |    |             |--- INIT_WORK(&dev->phy_queue, phy_change)                  //由phy_interrupt / timer调度以处理PHY状态的更改
|         |    |             |--- request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id))//加载内核模块(这里没有细致研究过)
|         |    |             |--- device_initialize(&mdiodev->dev) //设备模型中的一些设备,主要是kset、kobject、ktype的设置
|         |    |
|         |    |--- irq_of_parse_and_map(child, 0) //将中断解析并映射到linux virq空间(未深入研究)
|         |    |--- if (of_property_read_bool(child, "broken-turn-around"))//MDIO总线中的TA(Turnaround time)
|         |    |    |--- mdio->phy_ignore_ta_mask |= 1 << addr
|         |    |
|         |    |--- of_node_get(child)//将OF节点与设备结构相关联,以便以后查找
|         |    |--- phy->mdio.dev.of_node = child
|         |    |
|         |    |--- phy_device_register(phy)//注册PHY设备
|         |    |    |--- mdiobus_register_device(&phydev->mdio) //注册到mdiodev->bus,其实笔者认为这是一个虚拟的注册,仅仅是根据PHY的地址在mdiodev->bus->mdio_map数组对应位置填充这个mdiodev
|         |    |    |    |--- mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev  // 方便通过mdiodev->bus统一管理和查找,以及关联bus的读写函数,方便PHY的功能配置
|         |    |    |
|         |    |    |--- device_add(&phydev->mdio.dev)//注册到linux设备模型框架中
|         |
|         |--- if (!scanphys)  //如果从子节点的"reg"属性中获得PHY设备的地址,scanphys=false,这里就直接返回了,因为不需要再扫描了
|         |    |--- return 0
|         |
/******************************************************************************************************************一般来说只要设备树种指定了PHY设备的"reg"属性,后面的流程可以自动忽略
******************************************************************************************************************
|         |--- for_each_available_child_of_node(np, child)       //自动扫描具有空"reg"属性的PHY
|              |--- if (of_find_property(child, "reg", NULL))    //跳过具有reg属性集的PHY
|              |    |--- continue
|              |
|              |--- for (addr = 0; addr < PHY_MAX_ADDR; addr++)         //循环遍历扫描
|                   |--- if (mdiobus_is_registered_device(mdio, addr))  //跳过已注册的PHY
|                   |    |--- continue
|                   |
|                   |--- dev_info(&mdio->dev, "scan phy %s at address %i\n", child->name, addr) //打印扫描的PHY,建议开发人员设置"reg"属性
|                   |
|                   |--- if (of_mdiobus_child_is_phy(child))
|                        |--- of_mdiobus_register_phy(mdio, child, addr) //注册PHY设备
|
******************************************************************************************************************/

5. 直接调用CPU的MDIO控制器的方式

5.1 控制器平台设备在设备树中的大致描述方式(不完全准确,主要描述匹配的规则)

# linux4.9.225\Documentation\devicetree\bindings\powerpc\fsl\fman.txt
Example for FMan v3 internal MDIO:
mdio@e3120 {                                 //描述MDIO控制器驱动节点compatible = "fsl,fman-mdio";reg = <0xe3120 0xee0>;fsl,fman-internal-mdio;tbi1: tbi-phy@8 {                       //描述控制器下挂PHY设备的节点reg = <0x8>;device_type = "tbi-phy"; };
};

5.2 控制器平台驱动代码走读

5.2.1 控制器平台驱动的注册

# linux-4.9.225\drivers\net\ethernet\freescale\fsl_pq_mdio.c
static const struct of_device_id fsl_pq_mdio_match[] = {....../* No Kconfig option for Fman support yet */{.compatible = "fsl,fman-mdio",                  //匹配平台设备的名称.data = &(struct fsl_pq_mdio_data) {.mii_offset = 0,/* Fman TBI operations are handled elsewhere */},},......{},
};static struct platform_driver fsl_pq_mdio_driver = {.driver = {.name = "fsl-pq_mdio",.of_match_table = fsl_pq_mdio_match,},.probe = fsl_pq_mdio_probe,.remove = fsl_pq_mdio_remove,
};module_platform_driver(fsl_pq_mdio_driver);          //注册控制器平台设备驱动

5.2.2 控制器平台驱动的probe函数走读

/****************************************************************************************直接调用CPU的MDIO控制器的方式的MDIO控制器驱动(probe函数中涉及PHY设备的创建和注册)
****************************************************************************************/
# linux-4.9.225\drivers\net\ethernet\freescale\fsl_pq_mdio.cfsl_pq_mdio_probe(struct platform_device *pdev
|--- struct fsl_pq_mdio_priv *priv
|--- struct mii_bus *new_bus
|
|--- new_bus = mdiobus_alloc_size(sizeof(*priv))  //分配结构体
|--- priv = new_bus->priv
|--- new_bus->name = "Freescale PowerQUICC MII Bus"
|--- new_bus->read = &fsl_pq_mdio_read           //总线的读接口
|--- new_bus->write = &fsl_pq_mdio_write         //总线的写接口
|--- new_bus->reset = &fsl_pq_mdio_reset         //总线的复位接口
|
|--- of_address_to_resource(np, 0, &res)        //获取控制器地址资源
|--- snprintf(bus->id, MII_BUS_ID_SIZE, "%x", res.start)    //把资源的起始地址设置为bus->id
|
|--- of_mdiobus_register(new_bus, np)//注册mii_bus设备,并通过设备树中控制器的子节点创建PHY设备,这一点与模拟方式流程相同  

of_mdiobus_register的流程与第四小节一致,这里就不再列出。

6. 控制器的读写会在哪里得到调用?

在PHY设备的注册中(读PHY ID)、PHY的初始化、自协商、中断、状态、能力获取等流程中经常可以看到phy_read和phy_write两个函数(下一节要讲的PHY驱动),这两个函数的实现就依赖于控制器设备mii_bus的读写。

phy_read和phy_write定义在linux-4.9.225\include\linux\phy.h中,如下:

static inline int phy_read(struct phy_device *phydev, u32 regnum)
{return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, regnum);
}static inline int phy_write(struct phy_device *phydev, u32 regnum, u16 val)
{return mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, regnum, val);
}

其中mdiobus_read和mdiobus_write定义在linux-4.9.225\drivers\net\phy\mdio_bus.c中,如下:

/*** mdiobus_read - Convenience function for reading a given MII mgmt register* @bus: the mii_bus struct* @addr: the phy address* @regnum: register number to read** NOTE: MUST NOT be called from interrupt context,* because the bus read/write functions may wait for an interrupt* to conclude the operation.*/
int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
{int retval;BUG_ON(in_interrupt());mutex_lock(&bus->mdio_lock);retval = bus->read(bus, addr, regnum);mutex_unlock(&bus->mdio_lock);return retval;
}/*** mdiobus_write - Convenience function for writing a given MII mgmt register* @bus: the mii_bus struct* @addr: the phy address* @regnum: register number to write* @val: value to write to @regnum** NOTE: MUST NOT be called from interrupt context,* because the bus read/write functions may wait for an interrupt* to conclude the operation.*/
int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
{int err;BUG_ON(in_interrupt());mutex_lock(&bus->mdio_lock);err = bus->write(bus, addr, regnum, val);mutex_unlock(&bus->mdio_lock);return err;
}

可以清楚的看到bus->read和bus->write读写接口在这里得到调用。

PHY驱动调试之 --- PHY控制器驱动(二)相关推荐

  1. 全志A64平台 TP9950 BT656输入驱动调试(2)驱动调试

    1. 前言 承接上篇文章, 这里主要是驱动调试部分 2. 调试记录 (1)硬件引脚梳理 9950没有上电时序要求,直接在dts中将PMU电配好即可 (2) 调试I2C tp9950地址为0x44: 使 ...

  2. Linux内核驱动调试,Linux内核设备驱动之内核的调试技术笔记整理

    /****************** * 内核的调试技术 ******************/ (1)内核源代码中的一些与调试相关的配置选项 内核的配置选项中包含了一些与内核调试相关的选项,都集中 ...

  3. linux 内核驱动 调试,QDMA Linux 内核驱动使用和调试指南

    BY Deepesh Man Shakya 注意:本论坛博客所有内容皆来源于Xilinx工程师,如需转载,请写明出处作者及赛灵思论坛链接并发邮件至cncrc@xilinx.com,未经Xilinx及著 ...

  4. 【驱动】以太网扫盲(三)PHY的控制器驱动框架分析

    1. 概述 PHY芯片为OSI的最底层-物理层(Physical Layer),通过MII/GMII/RMII/SGMII/XGMII等多种媒体独立接口(介质无关接口)与数据链路层的MAC芯片相连,并 ...

  5. Linux旋钮驱动调试(安卓)

    Linux旋钮驱动调试(安卓) Linux旋钮驱动调试(安卓) 一.旋钮特性 二.驱动编写 三.GPIO配置(设备树) 1.dtsi配置节点属性 2.dts配置gpio属性 四.input事件调试 五 ...

  6. spi总线 上层调用_spi总线的mmc卡驱动调试总结 | 学步园

    这周调试挂载在spi总线sd卡驱动,总结一下 因为这个涉及到2个驱动,spi总线驱动和sd卡驱动,sd卡设备挂载在spi总线上并不像设备挂载在i2c总线上, i2c总线提供设备挂载需要的借口函数att ...

  7. i2c--ioctl--主机控制器驱动(i2c_adapter)--外设驱动(i2c_driver)

    updating... i2c驱动目录 [root@localhost i2c]# pwd /opt/FriendlyArm/mini2440/linux-2.6.32.2/drivers/i2c [ ...

  8. WinCE驱动调试助手V2.5

    http://www.cnblogs.com/we-hjb/archive/2008/12/15/1280822.html http://blog.chinaunix.net/u1/49088/sho ...

  9. Linux SPI子系统(3):SPI控制器驱动层

    目录 1. 前言 2. SPI控制器驱动 2.1 dts 2.2 匹配注册 2.3 probe函数 2.4 数据收发函数:spi_st_transfer_one 2.5 中断处理程序:spi_st_i ...

最新文章

  1. 深入分析Spark任务调度的原理--Java后端同学入门Spark编程系列
  2. Spring AOP底层实现原理
  3. boost::mpl::string相关的测试程序
  4. pass样本量_年度质量回顾-样本量
  5. Day4 dict和set
  6. c语言如果x小于,请达人帮忙做份C语言试卷,谢谢了
  7. 蓝桥杯集训之开训第一课
  8. java_home not found in your enviroment 问题解决方法
  9. 数学知识——博弈论(巴什博奕、尼姆博奕、威佐夫博奕)思路及例题
  10. 使用Python模块webbrowser实现自动打开网页/刷新网页
  11. onenote 导出为 html,Exporting or convert OneNote 2010 to HTML, not MHTML
  12. Router路由守卫
  13. 跟着团子学SAP PS—项目结算规则的自动生成 CJB2/CJB1 (ETO模式下正确结算规则设定案例)
  14. 路由器管理android,ASUS Router app-华硕路由器管理软件(ASUS Router)下载v2.0.0.6.16 官方安卓版-西西软件下载...
  15. Linux使用Aria2命令下载BT种子/磁力/直链文件
  16. 系统——Linux服务器
  17. 智联招聘自动刷新简历软件
  18. qq农场私服php源代码,仿QQ农场源码、QQ牧场源代码(整合UCHOME)
  19. C语言物流信息管理系统实践,[源码和文档分享]基于C语言的物流配送管理信息系统...
  20. 三国志9如何培养新武将

热门文章

  1. networkx网络拓扑节点图和树,python
  2. 疫情下的情人节 餐饮业再亏700亿!
  3. Box2D 源码编译
  4. 可以美化图片的方法每日分享
  5. 2020写给未来 100w 粉丝的年终总结
  6. 萌新做点小玩意儿DAY-15 线性表(顺序表)及其应用
  7. PX4模块设计之二十六:BatteryStatus模块
  8. java石头人_宋玉成
  9. 手把手教你编译RS GIS开源库(一)编译ECW3.3库
  10. 国考特殊专业职位证监计算机类,报考2018年国考证监会,这些事情你要知道!...