PHY驱动调试之 --- PHY控制器驱动(二)
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的控制器读写功能的实现大致可以分为两种方式():
- 直接调用CPU的MDIO控制器(直接调用cpu对应的寄存器)的方式;
- 通过GPIO/外围soc模拟MDIO时序的方式;
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.c
fs_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.c
fsl_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控制器驱动(二)的更多相关文章
- android 电容屏(二):驱动调试之基本概念篇
平台信息: 内核:linux3.4.39系统:android4.4 平台:S5P4418(cortex a9) 作者:瘋耔(欢迎转载,请注明作者) 欢迎指正错误,共同学习.共同进步!! 关注博主新浪博 ...
- 【转】android 电容屏(二):驱动调试之基本概念篇
关键词:android 电容屏 tp 工作队列 中断 多点触摸协议平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台:S5PV310(samsung ...
- 【转】android 电容屏(三):驱动调试之驱动程序分析篇
关键词:android 电容屏 tp 工作队列 中断 坐点计算 电容屏主要参数平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台:S5PV310( ...
- 【转】Android LCD(四):LCD驱动调试篇
关键词:android LCD TFTSN75LVDS83B TTL-LVDS LCD电压背光电压 平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台 ...
- linux驱动调试--段错误之oops信息分析
linux驱动调试--段错误之oops信息分析 http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29401328&id= ...
- 基于input子系统的sensor驱动调试(一)
要想弄明白世界的本质,就要追根溯源:代码也是一样的道理: 最近调试几个sensor驱动,alps sensor驱动.compass sensor驱动.G-sensor驱动都是一样的架构: 一.基于in ...
- Linux中断子系统:级联中断控制器驱动
Linux中断子系统 Linux中断子系统是个很大的话题,如下面的思维导图所示,包含硬件.驱动.中断上半部.中断下半部等等.本文着眼于中断控制器(PIC),特别是级联中断控制器驱动部分,对驱动的设计和 ...
- 驱动调试(一)-printk
目录 驱动调试(一)-printk 引入 框架 入口console_setup add_preferred_console register_console s3c24xx_serial_initco ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十二:串口模块① — 发送
实验十二:串口模块① — 发送 串口固然是典型的实验,想必许多同学已经作烂,不过笔者还要循例介绍一下.我们知道串口有发送与接收之分,实验十二的实验目的就是实现串口发送,然而不同的是 ... 笔者会用另 ...
- Linux SPI总线和设备驱动架构之三:SPI控制器驱动
通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动.通用接口层和控制器驱动三大部分.其中,控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能:1. ...
随机推荐
- KingbaseES V8R3集群运维案例之---主库系统down failover切换过程分析
案例说明: KingbaseES V8R3集群failover时两个cluster都会触发,但只有一个cluster会调用脚本去执行真正的切换流程,另一个有对应的打印,但不会调用脚本,只是走相关的 ...
- systemctl_用法总结
查看开机启动项 //查询开机启动项 systemctl list-unit-files // 输出 UNIT FILE 对应服务名:STATE 是状态:enable 是开机启动,disable是开机不 ...
- Windows LDAP加固之替换LDAP加密证书
之前两篇文章介绍了LDAP的安全加固,其中提到了TLS加密LDAP通信.对于通常的网页加密,RDP加密都可以在对应的管理界面中选择使用哪个证书来加密.那么对于LDAP服务,怎么确定当前使用的是哪张证书 ...
- SQL语句中过滤条件放在on和where子句中的区别和联系
摘要: 介绍在多表关联SQL语句中,过滤条件放在on和where子句中的区别--inner join中没区别,外连接就不一样. 综述 蚂蚁金服的一道SQL面试题如下:SQL语句中,过滤条件放在on ...
- 在kibana中查看nginx日志的Discover,Dashboards
官方的操作: 1.安装filebeat,配置filebeat获取nginx日志,来源有两种: 第一种是使用自带的模块进行收集,在modules.d目录中启用模块配置,运行Filebeat时启用模块,在 ...
- CentOS 7.x 升级OpenSSH
升级SSH 存在中断风险,如果SSH 升级失败将会导致终端无法登录,建议在使用本地虚拟机进行测试后对线上生产环境进行升级操作!!! 三级等保评测中对主机进行漏洞扫描发现linux主机存在高危漏洞,查看 ...
- td-agent的v2,v3,v4版本区别
官方地址:https://docs.fluentd.org/quickstart/td-agent-v2-vs-v3-vs-v4
- PAT (Advanced Level) Practice 1001 A+B Format 分数 20
Calculate a+b and output the sum in standard format -- that is, the digits must be separated into gr ...
- Vue3 Vite3 多环境配置 - 基于 vite 创建 vue3 全家桶项目(续篇)
在项目或产品的迭代过程中,通常会有多套环境,常见的有: dev:开发环境 sit:集成测试环境 uat:用户接收测试环境 pre:预生产环境 prod:生产环境 环境之间配置可能存在差异,如接口地址. ...
- pgsql 的问题
pgsql 怎么插入inet类型的数据?insert into table (remote_addr) values ( ?::INET); pgsql如何截取时间的精度 select create ...