网口调试基础之一网口phy驱动
本节主要梳理linux网口驱动流程。
设备间的逻辑关联图及抽象
如下图为cpu与mac、phy之间的逻辑关联图,针对mac与phy之间的连接,包括两部分:
- 通过mii/rgmii/sgmii/…与phy连接,进行数据的通信;
- 通过mdc/mdio与phy连接,实现对phy设备的控制命令的下发等功能
MII控制总线
MII Management interface用于MAC层芯片控制和配置phy设备,而ieee 802.3中规定了phy device寄存器的额地址空间为5位,最多可定义32各寄存器,而ieee 802.3定义了地址为0-15这16个寄存器的功能,主要包括control、status、phy identifer、auto-neg相关等,16-31由厂家自行定义。
phy驱动中重要的有两个结构体
PHY设备
struct phy_device {struct phy_driver *drv; //PHY设备驱动struct mii_bus *bus; //对应的MII总线struct device dev; //设备文件u32 phy_id; //PHY IDenum phy_state state; //PHY状态u32 dev_flags;phy_interface_t interface; //PHY接口int addr; //PHY 总线地址(0~31)int speed; //速度int duplex; //双工模式int pause; //停止int asym_pause; //int link; u32 interrupts; //中断使能标志u32 supported; u32 advertising;int autoneg;int link_timeout; //026int irq; //中断号void *priv; //私有数据struct work_struct phy_queue; //PHY工作队列struct delayed_work state_queue; //PHY延时工作队列atomic_t irq_disable; struct mutex lock;struct net_device *attached_dev; //网络设备void (*adjust_link)(struct net_device *dev);void (*adjust_state)(struct net_device *dev);
};
PHY驱动
struct phy_driver {u32 phy_id; //PHY IDchar *name; //PHY名unsigned int phy_id_mask;u32 features; //特性u32 flags; //标记int (*config_init)(struct phy_device *phydev); //配置初始化int (*probe)(struct phy_device *phydev); //探测到 probe方法int (*suspend)(struct phy_device *phydev); //唤醒int (*resume)(struct phy_device *phydev); //挂起int (*config_aneg)(struct phy_device *phydev); //支援(Auto-negotiation)配置int (*read_status)(struct phy_device *phydev); //读支援(Auto-negotiation)状态int (*ack_interrupt)(struct phy_device *phydev); //清中断int (*config_intr)(struct phy_device *phydev); //使能/禁用 中断int (*did_interrupt)(struct phy_device *phydev); //判断是否由中断void (*remove)(struct phy_device *phydev); //移除int (*hwtstamp)(struct phy_device *phydev, struct ifreq *ifr); //时间戳处理bool (*rxtstamp)(struct phy_device *dev, struct sk_buff *skb, int type); //接收时间戳void (*txtstamp)(struct phy_device *dev, struct sk_buff *skb, int type); //发送时间戳struct device_driver driver; //设备驱动文件
};
设备和驱动的注册函数
1 注册PHY设备
int phy_device_register(struct phy_device *phydev)
{int err;if (phydev->bus->phy_map[phydev->addr]) //判断PHY是否已经给注册了return -EINVAL;phydev->bus->phy_map[phydev->addr] = phydev; //添加PHY到总线的phy_map里phy_scan_fixups(phydev); //执行匹配的fixupserr = device_register(&phydev->dev); //注册设备if (err) {pr_err("phy %d failed to register\n", phydev->addr);goto out;}return 0;out:phydev->bus->phy_map[phydev->addr] = NULL;return err;
}
EXPORT_SYMBOL(phy_device_register);
PHY设备一般是动态注册的,在注册之前一般会调用get_phy_device函数
struct phy_device * get_phy_device(struct mii_bus *bus, int addr)
{struct phy_device *dev = NULL;u32 phy_id;int r;r = get_phy_id(bus, addr, &phy_id); //1 获取PHY IDif (r)return ERR_PTR(r);if ((phy_id & 0x1fffffff) == 0x1fffffff)return NULL;if ((phy_id & 0x1fffffff) == 0x0000ffff)//add by cyj, support marvel phyreturn NULL;dev = phy_device_create(bus, addr, phy_id); //2 创建PHY设备return dev;
}
EXPORT_SYMBOL(get_phy_device);
获取PHY ID
int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id)
{int phy_reg;//调用PHY的总线也就是mii总线的读方法获取PHY IDphy_reg = bus->read(bus, addr, MII_PHYSID1); //获取PHYS ID1命令if (phy_reg < 0)return -EIO;*phy_id = (phy_reg & 0xffff) << 16;phy_reg = bus->read(bus, addr, MII_PHYSID2); //获取PHYS ID1命令if (phy_reg < 0)return -EIO;*phy_id |= (phy_reg & 0xffff);return 0;
}
EXPORT_SYMBOL(get_phy_id);
创建PHY设备
static struct phy_device* phy_device_create(struct mii_bus *bus,int addr, int phy_id)
{struct phy_device *dev;dev = kzalloc(sizeof(*dev), GFP_KERNEL); //分配phy设备内存if (NULL == dev)return (struct phy_device*) PTR_ERR((void*)-ENOMEM);dev->dev.release = phy_device_release;dev->speed = 0; //速度dev->duplex = -1; //双工模式dev->pause = dev->asym_pause = 0;dev->link = 1; dev->interface = PHY_INTERFACE_MODE_GMII; //接口模式GMIIdev->autoneg = AUTONEG_ENABLE; //自动使能dev->addr = addr; //地址dev->phy_id = phy_id; //PHY IDdev->bus = bus; //mii总线dev->dev.parent = bus->parent; //父设备dev->dev.bus = &mdio_bus_type; //总线类型dev->irq = bus->irq != NULL ? bus->irq[addr] : PHY_POLL; //中断/轮询dev_set_name(&dev->dev, PHY_ID_FMT, bus->id, addr); //PHY 设备文件名dev->state = PHY_DOWN; //状态DOWNmutex_init(&dev->lock);INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); //初始化PHY状态机request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id));return dev;
}
2注册phy驱动
int phy_driver_register(struct phy_driver *new_driver)
{int retval;new_driver->driver.name = new_driver->name; //驱动名new_driver->driver.bus = &mdio_bus_type; //总线类型new_driver->driver.probe = phy_probe; //探测函数new_driver->driver.remove = phy_remove; //移除函数retval = driver_register(&new_driver->driver); //注册设备驱动if (retval) {printk(KERN_ERR "%s: Error %d in registering driver\n",new_driver->name, retval);return retval;}pr_debug("%s: Registered new driver\n", new_driver->name);return 0;
}
EXPORT_SYMBOL(phy_driver_register);
3 通过MDIO总线匹配
phy设备和驱动的总线类型都是mdio_bus_type
struct bus_type mdio_bus_type = {.name = "mdio_bus",.match = mdio_bus_match, //匹配方法.pm = MDIO_BUS_PM_OPS,
};
EXPORT_SYMBOL(mdio_bus_type);static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{struct phy_device *phydev = to_phy_device(dev); //获取PHY设备struct phy_driver *phydrv = to_phy_driver(drv); //获取PHY驱动return ((phydrv->phy_id & phydrv->phy_id_mask) ==(phydev->phy_id & phydrv->phy_id_mask)); //比较phy_id
}
匹配成功就会调用phy驱动的probe方法,也即是phy_probe
static int phy_probe(struct device *dev)
{struct phy_device *phydev;struct phy_driver *phydrv;struct device_driver *drv;int err = 0;phydev = to_phy_device(dev); //获取PHY设备drv = get_driver(phydev->dev.driver);phydrv = to_phy_driver(drv); //获取PHY驱动phydev->drv = phydrv; //捆绑一下if (!(phydrv->flags & PHY_HAS_INTERRUPT)) //设置中断方式phydev->irq = PHY_POLL;mutex_lock(&phydev->lock);phydev->supported = phydrv->features; //设置PHY设备特性phydev->advertising = phydrv->features; //设置PHY设备特性phydev->state = PHY_READY; //状态设置为"准备好"if (phydev->drv->probe) //如果驱动有probe方法err = phydev->drv->probe(phydev); //则调用mutex_unlock(&phydev->lock);return err;
}
三 初始化流程
主函数主要初始化mdio总线、注册通用的phy设备驱动
static int __init phy_init(void)
{int rc;rc = mdio_bus_init(); //初始化mdio总线if (rc)return rc;rc = phy_driver_register(&genphy_driver); //注册通用的PHY设备驱动if (rc)mdio_bus_exit();return rc;
}static struct phy_driver genphy_driver = {.phy_id = 0xffffffff,.phy_id_mask = 0xffffffff,.name = "Generic PHY",.config_init = genphy_config_init, //初始化函数.features = 0,.config_aneg = genphy_config_aneg, //配置自协商(Auto-negotiation).read_status = genphy_read_status, //读状态.suspend = genphy_suspend,.resume = genphy_resume,.driver = {.owner= THIS_MODULE, },
};
genphy_driver驱动中有初始化配置方法,配置速度、半双工/全双工、自协商模式等。
static int genphy_config_init(struct phy_device *phydev)
{int val;u32 features;//默认支持特性features = (SUPPORTED_TP | SUPPORTED_MII| SUPPORTED_AUI | SUPPORTED_FIBRE |SUPPORTED_BNC);val = phy_read(phydev, MII_BMSR); //读基础状态if (val < 0)return val;if (val & BMSR_ANEGCAPABLE) //支持(auto-negotiation)features |= SUPPORTED_Autoneg;if (val & BMSR_100FULL) //100兆全双工features |= SUPPORTED_100baseT_Full;if (val & BMSR_100HALF) //100兆半双工features |= SUPPORTED_100baseT_Half;if (val & BMSR_10FULL) //10兆全双工features |= SUPPORTED_10baseT_Full;if (val & BMSR_10HALF) //10兆半双工features |= SUPPORTED_10baseT_Half;if (val & BMSR_ESTATEN) {val = phy_read(phydev, MII_ESTATUS); //读扩展状态if (val < 0)return val;if (val & ESTATUS_1000_TFULL) //1000兆全双工features |= SUPPORTED_1000baseT_Full;if (val & ESTATUS_1000_THALF) //1000兆半双工features |= SUPPORTED_1000baseT_Half;}phydev->supported = features; //PHY特性phydev->advertising = features;return 0;
}
四 PHY状态机
状态类型
enum phy_state {PHY_DOWN=0,PHY_STARTING, //开始PHY_READY, //准备好PHY_PENDING, //挂起PHY_UP, //开启PHY_AN, //判断连接状态中 negotiatingPHY_RUNNING, //运行PHY_NOLINK, //开启 未连接PHY_FORCING, //设置中PHY_CHANGELINK, //连接状态改变PHY_HALTED, //停止PHY_RESUMING //唤醒
};
状态机phy_state_machine
在phy_device_create函数中,开启了状态机
void phy_state_machine(struct work_struct *work)
{struct delayed_work *dwork = to_delayed_work(work);struct phy_device *phydev = container_of(dwork, struct phy_device, state_queue);int needs_aneg = 0;int err = 0;mutex_lock(&phydev->lock);if (phydev->adjust_state)phydev->adjust_state(phydev->attached_dev);switch(phydev->state) {case PHY_DOWN: //关闭((ifconfig eth0 down)case PHY_STARTING: //开始case PHY_READY: //准备好case PHY_PENDING: //挂起break;case PHY_UP: //开启(ifconfig eth0 up)needs_aneg = 1;phydev->link_timeout = PHY_AN_TIMEOUT;break;case PHY_AN: //判断连接状态中 negotiatingerr = phy_read_status(phydev);if (err < 0)break;if (!phydev->link) {phydev->state = PHY_NOLINK;netif_carrier_off(phydev->attached_dev);phydev->adjust_link(phydev->attached_dev);break;}err = phy_aneg_done(phydev);if (err < 0)break;if (err > 0) {phydev->state = PHY_RUNNING;netif_carrier_on(phydev->attached_dev);phydev->adjust_link(phydev->attached_dev);} else if (0 == phydev->link_timeout--) {int idx;needs_aneg = 1;if (phydev->drv->flags & PHY_HAS_MAGICANEG)break;idx = phy_find_valid(0, phydev->supported);phydev->speed = settings[idx].speed;phydev->duplex = settings[idx].duplex;phydev->autoneg = AUTONEG_DISABLE;pr_info("Trying %d/%s\n", phydev->speed,DUPLEX_FULL ==phydev->duplex ?"FULL" : "HALF");}break;case PHY_NOLINK: //开启 未连接err = phy_read_status(phydev);if (err)break;if (phydev->link) {phydev->state = PHY_RUNNING;netif_carrier_on(phydev->attached_dev);phydev->adjust_link(phydev->attached_dev);}break;case PHY_FORCING: //设置中err = genphy_update_link(phydev);if (err)break;if (phydev->link) {phydev->state = PHY_RUNNING;netif_carrier_on(phydev->attached_dev);} else {if (0 == phydev->link_timeout--) {phy_force_reduction(phydev);needs_aneg = 1;}}phydev->adjust_link(phydev->attached_dev);break;case PHY_RUNNING: //运行if (PHY_POLL == phydev->irq)phydev->state = PHY_CHANGELINK;break;case PHY_CHANGELINK: //连接状态改变err = phy_read_status(phydev);if (err)break;if (phydev->link) {phydev->state = PHY_RUNNING;netif_carrier_on(phydev->attached_dev);} else {phydev->state = PHY_NOLINK;netif_carrier_off(phydev->attached_dev);}phydev->adjust_link(phydev->attached_dev);if (PHY_POLL != phydev->irq)err = phy_config_interrupt(phydev,PHY_INTERRUPT_ENABLED);break;case PHY_HALTED: //停止if (phydev->link) {phydev->link = 0;netif_carrier_off(phydev->attached_dev);phydev->adjust_link(phydev->attached_dev);}break;case PHY_RESUMING: //唤醒err = phy_clear_interrupt(phydev);if (err)break;err = phy_config_interrupt(phydev,PHY_INTERRUPT_ENABLED);if (err)break;if (AUTONEG_ENABLE == phydev->autoneg) {err = phy_aneg_done(phydev);if (err < 0)break;if (err > 0) {err = phy_read_status(phydev);if (err)break;if (phydev->link) {phydev->state = PHY_RUNNING;netif_carrier_on(phydev->attached_dev);} elsephydev->state = PHY_NOLINK;phydev->adjust_link(phydev->attached_dev);} else {phydev->state = PHY_AN;phydev->link_timeout = PHY_AN_TIMEOUT;}}else {err = phy_read_status(phydev);if (err)break;if (phydev->link) {phydev->state = PHY_RUNNING;netif_carrier_on(phydev->attached_dev);} elsephydev->state = PHY_NOLINK;phydev->adjust_link(phydev->attached_dev);}break;}mutex_unlock(&phydev->lock);if (needs_aneg) //需要自动配置(例如ifconfig eth0 up就会调用)err = phy_start_aneg(phydev); //开始自动配置if (err < 0)phy_error(phydev);schedule_delayed_work(&phydev->state_queue, PHY_STATE_TIME * HZ);
}
运行ifconfig eth0 up
case PHY_UP: //开启(ifconfig eth0 up)needs_aneg = 1;phydev->link_timeout = PHY_AN_TIMEOUT;break;
相应处理
if (needs_aneg) //需要自动协商机制(例如ifconfig eth0 up就会调用)err = phy_start_aneg(phydev); //开始自动配置
调用phy_start_aneg自动配置函数
int phy_start_aneg(struct phy_device *phydev)
{int err;mutex_lock(&phydev->lock);if (AUTONEG_DISABLE == phydev->autoneg)phy_sanitize_settings(phydev);err = phydev->drv->config_aneg(phydev); //调用驱动的config_aneg方法,默认是genphy_config_anegif (err < 0)goto out_unlock;if (phydev->state != PHY_HALTED) { //调整修改PHY设备状态if (AUTONEG_ENABLE == phydev->autoneg) {phydev->state = PHY_AN;phydev->link_timeout = PHY_AN_TIMEOUT;} else {phydev->state = PHY_FORCING;phydev->link_timeout = PHY_FORCE_TIMEOUT;}}
out_unlock:mutex_unlock(&phydev->lock);return err;
}
EXPORT_SYMBOL(phy_start_aneg);
调用默认的自动协商方法genphy_config_aneg
int genphy_config_aneg(struct phy_device *phydev)
{int result;if (AUTONEG_ENABLE != phydev->autoneg)return genphy_setup_forced(phydev);result = genphy_config_advert(phydev);if (result < 0) /* error */return result;if (result == 0) {int ctl = phy_read(phydev, MII_BMCR); //获取状态if (ctl < 0)return ctl;if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE))result = 1; /* do restart aneg */}if (result > 0)result = genphy_restart_aneg(phydev); //重新开启自动协商机制return result;
}
EXPORT_SYMBOL(genphy_config_aneg);
接着调用genphy_restart_aneg,重新开启自动协商机制
int genphy_restart_aneg(struct phy_device *phydev)
{int ctl;ctl = phy_read(phydev, MII_BMCR); //获取基本状态if (ctl < 0)return ctl;ctl |= (BMCR_ANENABLE | BMCR_ANRESTART); //使能自动协商机制使能 重启/* Don't isolate the PHY if we're negotiating */ctl &= ~(BMCR_ISOLATE);ctl = phy_write(phydev, MII_BMCR, ctl); //写命令return ctl;
}
EXPORT_SYMBOL(genphy_restart_aneg);
其他常用的API
static inline int phy_read(struct phy_device *phydev, u32 regnum); //PHY读
static inline int phy_write(struct phy_device *phydev, u32 regnum, u16 val); //PHY写
void phy_start(struct phy_device *phydev); //PHY开始
void phy_stop(struct phy_device *phydev); //PHY停止
int phy_init_hw(struct phy_device *phydev); //PHY初始化硬件
struct phy_device * phy_attach(struct net_device *dev,const char *bus_id, u32 flags, phy_interface_t interface); //PHY接上
void phy_detach(struct phy_device *phydev); //PHY分离
struct phy_device *phy_find_first(struct mii_bus *bus); //查找mii_bus总线上第一个PHY
int phy_connect_direct(struct net_device *dev, struct phy_device *phydev,void (*handler)(struct net_device *), u32 flags,phy_interface_t interface); //PHY直接连接网络设备
struct phy_device * phy_connect(struct net_device *dev, const char *bus_id,void (*handler)(struct net_device *), u32 flags,phy_interface_t interface); //PHY连接网络设备
void phy_disconnect(struct phy_device *phydev); //PHY断开与网络设备的连接
int phy_start_interrupts(struct phy_device *phydev);//PHY开始中断
int phy_stop_interrupts(struct phy_device *phydev); //PHY停止中断
int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd); //ethtool工具sset功能
int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd); //ethtool工具gset功能
int phy_mii_ioctl(struct phy_device *phydev,struct ifreq *ifr, int cmd); //通用PHY/mii接口
网口调试基础之一网口phy驱动相关推荐
- Linux phy驱动开发总结
文章目录 基础 须知 数据结构 数据结构UML类图 MAC驱动 初始化简述 驱动实现 关于MAC连接PHY PHY驱动 动态注册 静态注册 通用PHY 参考模板 IP18xx驱动调试 须知: 问题 方 ...
- 网口调试方式以及性能测试iperf |CSDN创作打卡
文章目录 调试步骤 网口phy/mac调试方法 PHY 寄存器读写调试 查看PHY是否识别 网口性能测试iperf 调试步骤 阅读对应PHY的技术手册,弄清楚MDIO使用条款 测试PHY的外部供电是否 ...
- 转载:ZYNQ+linux网口调试笔记(1)PS-GEM0
ZYNQ+linux网口调试笔记(1)PS-GEM0 转载原文:https://www.jianshu.com/p/a4e25e8b2f5e 开发环境 Windows SDK 2017.4 Ubunt ...
- zynq linux ip配置,ZYNQ+linux网口调试笔记(2)PS-GEM1
1. 开发环境 Windows SDK 2017.4 Ubuntu Petalinux 2017.4 硬件平台:米联客ZYNQ开发板MIZ7035 2. 开发目标 在ZYNQ上使用gigE Visio ...
- zynq linux如何使用pl ip,ZYNQ+linux网口调试笔记(3)PL-ETH
1. 开发环境 Windows SDK 2017.4 Ubuntu Petalinux 2017.4 硬件平台:米联客ZYNQ开发板MIZ7035 2. 开发目标 在ZYNQ上使用gigE Visio ...
- 网口扫盲二:Mac与Phy组成原理的简单分析
1. general 下图是网口结构简图.网口由CPU.MAC和PHY三部分组成.DMA控制器通常属于CPU的一部分,用虚线放在这里是为了表示DMA控制器可能会参与到网口数据传输中. MAC(Medi ...
- 使用SSCOM V5.13进行串口网口调试
一.网口调试: 网口无需连接,即可收发数据,客户端软件正常运行,客户端电脑无需运行SSCOM V5.13.只有客户端软件运行之后端口才能正常工作. 二.串口调试:
- 有人物联网485转网口模块网口调试助手1035未知错误
有人物联网485转网口模块网口调试助手1035未知错误 问题 解决 问题 项目使用有人物联网485转网口模块USR-TCP232-304,将模块接入实验室路由器,IP地址设置为动态IP,路由器上查得I ...
- 网络驱动->PHY驱动调试
1. Linux 系统网络协议层架构 网络协议框架图: 网络子系统是 linux 操作系统里很重要的一部分.关于这部分有很多的参考资料.这里主要说明一下 phy 芯片在整个子系统里的位置.从这个结构里 ...
最新文章
- 如何建立图像数据矩阵和图像显示灰度之间的关系!_放射技术考试第四章第一节 数字图像的特征...
- java 中while编译之后_从APK反编译的Java-while循环什么也不做
- wireshark 常用命令
- 【python数据挖掘课程】十二.Pandas、Matplotlib结合SQL语句对比图分析
- RabbitMQ死信队列代码架构图
- 【今晚七点半】:白板与开源
- 反转二叉树 java_leetcode刷题笔记-226. 翻转二叉树(java实现)
- python足球投注_/usr/lib目录属性更改引发的蝴蝶效应
- mysql怎么让自增id不连续_MySQL中自增主键不连续之解决方案。(20131109)
- XINS 3.1.0 Alpha2 发布,远程 API 调用规范
- P5057 [CQOI2006]简单题
- mybatis开发中遇到的小问题
- 针对英特尔xtu超频软件安装失败以及英伟达GeForce Experience安装程序无法继续的解决方法
- 计算机专用英语词汇1695个词汇表(传)
- 由三点画圆到未来日记:失控中的位置隐私
- 如何下载网页中的360全景图片(720全景图片)到本地?
- STM32H743内部所有SRAM的使用
- 【仙女踩坑实录】Macbook修改文件创建时间
- (二)UPF之电压域、低功耗模式编码(Primary Supply Set、Power State)
- Deep Learning for Single Image Super-Resolution: A Brief Review SISR综述分析