当前位置: 首页 > ds >正文

海思网卡框架介绍

https://www.zhihu.com/tardis/zm/art/644640908

一、硬件和通信接口介绍

EXT_PORT0(GMAC1):RMII / RGMII UTP_PORT1:V(LAN1)	UTP_PORT3:V(LAN2) 

网络连接示意图

网卡工作在 OSI 网络体系的最后两层,物理层和数据链路层,物理层定义了数据传送与接收所需要的电与光信号、线路状态、时钟基准、数据编码和电路等,并向数据链路层设备提供标准接口。物理层的芯片称之为 PHY。数据链路层则提供寻址机构、数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能。以太网卡中数据链路层的芯片称之为 MAC 控制器。很多网卡的这两个部分是做到一起的。他们之间的关系是 PCI 总线接 MAC 总线,MAC 接 PHY,PHY 接网线(当然也不是直接接上的,还有一个变压装置)。

1 MAC(Media Access Control)

MAC:Media Access Control,即媒体访问控制子层协议。该协议位于 OSI 七层协议中数据链路层的下半部分,主要负责控制与连接物理层的物理介质。在发送数据的时候,MAC 协议可以事先判断是否可以发送数据,如果可以发送将给数据加上一些控制信息,最终将数据以及控制信息以规定的格式发送到物理层;在接收数据的时候,MAC 协议首先判断输入的信息并是否发生传输错误,如果没有错误,则去掉控制信息发送至 LLC 层。以太网 MAC 由 IEEE-802.3 以太网标准定义。

2 PHY

工作在OSI七层协议中的物理层, 嵌入式系统中“网卡”芯片一般都是PHY。它实现物理层。包括 MII/GMII(介质独立接口)子层、PCS(物理编码子层)、PMA(物理介质附加)子层、 PMD(物理介质相关)子层、MDI 子层。

3 通信接口

查看芯片手册可以知道,RGMII只支持MAC to MAC的方式

参考:

https://blog.csdn.net/s1_mple/article/details/144581093

RGMII (Reduced Gigabit Media Independent Interface) 是最常见接口,其支持 PHY 层的 10 Mbps,100 Mbps 和 1000 Mbps 连接速度。

RGMII 使用 4bit 位宽发送和接收数据路径,每个路径有其自己的源同步时钟。所有发送数据和控制信号都源同步于 TX_CLK,且所有接收数据和控制信号源同步于 RX_CLK。

对于所有速度模式,TX_CLK 由 MAC 提供时钟源,而 RX_CLK 由 PHY 提供时钟源。在 1000 Mbps 模式下,TX_CLK 和 RX_CLK 为 125 MHz,使用 Dual Data Rate (DDR) 传输信号。10 Mbps 和 100 Mbps 模式下,TX_CLK 和 RX_CLK 分别为 2.5 MHz和 25 MHz,并且使用上升沿 Single Data Rate (SDR) 传输信号。

画板

4 MDIO接口

MDIO 接口数据帧分为两种:

一种是clause22:百兆千兆以太网口

一种是clause45:千兆以上的以太网口

特性Clause 22Clause 45
起源标准IEEE 802.3 Clause 22 (RFC802.3)IEEE 802.3ae Clause 45
帧起始标志<font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">01</font>
[[5][34]]
<font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">00</font>
[[5][34][72]]
操作码(OP)2种:读(<font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">10</font>
)、写(<font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">01</font>
4种:地址帧(<font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">00</font>
)、写(<font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">01</font>
)、读(<font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">11</font>
)、读后自增(<font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">10</font>
) [[34][72]]
地址空间5位PHY地址 + 5位寄存器地址 → 仅支持32设备×32寄存器5位PHY地址 + 5位DEVAD + 16位寄存器地址 → 支持32设备×65536寄存器 [[15][72]]
电气标准仅支持5V电平支持低压设备(最低1.2V) [[15][72]]
访问方式单帧完成地址+数据操作双帧操作:地址帧 + 数据帧 [[34][72]]
![](https://cdn.nlark.com/yuque/0/2025/png/22342141/1749683354767-c1012487-e8a9-464b-934b-dbf4d3e6b336.png)
| 前导码(32比特) | ST=`01` | OP | PHYAD(5位) | REGAD(5位) | TA(2位) | DATA(16位) | 空闲 |

地址帧:| 前导码(32比特) | ST=`00` | OP=`00` | PHYAD(5位) | DEVAD(5位) | TA=`10` | ADDR(16位) | 空闲 |
数据帧(写操作示例):| 前导码(32比特) | ST=`00` | OP=`01` | PHYAD(5位) | DEVAD(5位) | TA=`10` | DATA(16位) | 空闲 |

5 RJ45接口

网络设备是通过网线连接起来的,插入网线的叫做RJ45座,如图所示

6 网络变压器

RJ45座要与PHY芯片连接在一起,但是中间需要一个网络变压器,网络变压器用来隔离和滤波等,网络变压器也是一个芯片,我们用的是G2401CE

二、软件驱动框架

1 总体软件框架

画板

2 结构体

struct mii_bus 结构体

struct mii_bus {struct module *owner;  // 指向拥有该总线的内核模块(如RTL8363驱动模块),用于模块引用计数管理const char *name;      // 总线名称(如 "rtl8363-mdio"),用于调试和日志标识char id[MII_BUS_ID_SIZE]; // 总线唯一标识符(如 "platform-0"),用于区分多个MDIO总线实例void *priv;           // 私有数据指针,通常指向驱动自定义的上下文(如RTL8363芯片的私有结构体)/* 关键操作函数 */int (*read)(struct mii_bus *bus, int addr, int regnum);  // 读PHY寄存器方法(底层硬件操作)int (*write)(struct mii_bus *bus, int addr, int regnum, u16 val); // 写PHY寄存器方法int (*reset)(struct mii_bus *bus);  // 可选的总线复位方法(如RTL8363硬件复位)struct mutex mdio_lock; // 互斥锁,确保同一时间只有一个线程访问MDIO总线(防止并发冲突)struct device *parent; // 父设备指针(通常是平台设备或网络控制器设备)/* 总线状态机 */enum {MDIOBUS_ALLOCATED = 1,  // 总线已分配但未注册MDIOBUS_REGISTERED,     // 总线已注册并可用(RTL8363驱动注册后进入此状态)MDIOBUS_UNREGISTERED,   // 总线已注销MDIOBUS_RELEASED,       // 总线资源已释放} state;struct device dev;     // 内嵌的Linux设备结构体,用于设备模型管理(如sysfs接口)/* PHY设备管理 */struct mdio_device *mdio_map[PHY_MAX_ADDR]; // PHY设备数组,索引为PHY地址(RTL8363可能占用地址0)u32 phy_mask;          // 屏蔽某些PHY地址(如不探测的地址)u32 phy_ignore_ta_mask; // 忽略特定PHY的读错误(TA: Turnaround,用于兼容异常PHY)/* 中断相关 */int irq[PHY_MAX_ADDR]; // 每个PHY地址对应的中断号(RTL8363可能使用一个全局中断)/* 复位控制 */int reset_delay_us;    // GPIO复位脉冲宽度(微秒)struct gpio_desc *reset_gpiod; // 复位GPIO描述符(用于硬件复位RTL8363)
};

struct phy_device

struct phy_device {struct mdio_device mdio;          /* MDIO总线设备基类,提供PHY寄存器读写能力 *//* PHY芯片驱动相关信息 */struct phy_driver *drv;           /* 指向PHY芯片驱动程序的函数集合 */u32 phy_id;                       /* PHY芯片的唯一硬件标识寄存器值 */struct phy_c45_device_ids c45_ids;/* 支持Clause 45标准的PHY芯片的扩展ID信息 *//* PHY类型和状态标志 (位域) */unsigned is_c45:1;                /* 1=支持IEEE 802.3 Clause 45标准 */unsigned is_internal:1;           /* 1=PHY内置于SoC内部 */unsigned is_pseudo_fixed_link:1;  /* 1=伪固定链路(无真实PHY) */unsigned has_fixups:1;            /* 1=需要特殊修正处理 */unsigned suspended:1;             /* 1=PHY处于电源管理挂起状态 */unsigned suspended_by_mdio_bus:1; /* 1=由MDIO总线触发的挂起状态 */unsigned sysfs_links:1;           /* 1=已创建sysfs符号链接 */unsigned loopback_enabled:1;      /* 1=环回模式已启用 */unsigned autoneg:1;               /* 1=自动协商已启用 */unsigned link:1;                  /* 1=物理链路连接状态正常 *//* PHY运行时状态 */enum phy_state state;             /* PHY状态机:DOWN/STARTING/READY等 */u32 dev_flags;                    /* 设备特性标志位(PHY_F_*系列) */phy_interface_t interface;        /* MAC-PHY接口类型:RMII/RGMII/SGMII等 *//** 强制速度&双工(无自动协商)* 对端速度&双工&暂停(自动协商)*/int speed;                        /* 当前链路速度:SPEED_10/100/1000/2500 */int duplex;                       /* 双工模式:DUPLEX_HALF/DUPLEX_FULL */int pause;                        /* 对称暂停流控支持状态 */int asym_pause;                   /* 非对称暂停流控支持状态 *//* 中断管理 */u32 interrupts;                   /* 启用的中断类型掩码 *//* PHY能力协商 */u32 supported;                    /* PHY芯片支持的能力(SUPPORTED_*标志) */u32 advertising;                  /* 向对端通告的能力(配置值) */u32 lp_advertising;               /* 对端通告的能力(自动协商结果) *//* 能源高效以太网配置 */u32 eee_broken_modes;             /* 禁用的节能以太网模式 */int link_timeout;                 /* 链路建立超时时间(毫秒) */#ifdef CONFIG_LED_TRIGGER_PHY/* PHY LED状态指示 */struct phy_led_trigger *phy_led_triggers; /* PHY状态LED触发器数组 */unsigned int phy_num_led_triggers;        /* LED触发器数量 */struct phy_led_trigger *last_triggered;   /* 最后触发的LED */struct phy_led_trigger *led_link_trigger; /* 链路状态专用LED触发器 */
#endif/* 中断配置 */int irq;                          /* PHY中断号(-1表示无中断) *//* 驱动私有数据 */void *priv;                       /* 驱动私有数据指针(存放芯片特定状态) *//* 中断和轮询基础设施 */struct work_struct phy_queue;     /* 工作队列(中断下半部处理) */struct delayed_work state_queue;  /* 延迟工作队列(状态轮询) */struct mutex lock;                /* 状态访问互斥锁 *//* 新一代MAC-PHY交互框架 */struct phylink *phylink;          /* Phylink抽象层指针 *//* 关联设备 */struct net_device *attached_dev;  /* 关联的网络设备(eth0等) *//* MDI/MDI-X控制 */u8 mdix;                          /* 当前MDI/MDI-X交叉状态 */u8 mdix_ctrl;                     /* MDI/MDI-X控制模式:AUTO/MDI/MDIX *//* 链路状态回调函数 */void (*phy_link_change)(struct phy_device *, bool up, bool do_carrier);void (*adjust_link)(struct net_device *dev); /* 链路变化核心回调函数 *//* Android内核兼容性保留字段 */ANDROID_KABI_RESERVE(1);ANDROID_KABI_RESERVE(2);ANDROID_KABI_RESERVE(3);ANDROID_KABI_RESERVE(4);
};

struct mdio_device

struct mdio_device {struct device dev;struct mii_bus *bus;char modalias[MDIO_NAME_SIZE];int (*bus_match)(struct device *dev, struct device_driver *drv);void (*device_free)(struct mdio_device *mdiodev);void (*device_remove)(struct mdio_device *mdiodev);/* Bus address of the MDIO device (0-31) */int addr;int flags;struct gpio_desc *reset;unsigned int reset_assert_delay;unsigned int reset_deassert_delay;
}; 

struct phy_driver

struct phy_driver {struct mdio_driver_common mdiodrv;  // MDIO驱动公共结构体,包含总线相关信息和操作u32 phy_id;                         // PHY设备的ID,用于识别特定PHY型号char *name;                         // PHY驱动名称,用于标识和调试u32 phy_id_mask;                    // PHY ID掩码,用于匹配PHY设备u32 features;                       // PHY支持的功能特性标志u32 flags;                          // 驱动标志位,控制驱动行为const void *driver_data;            // 驱动私有数据指针/* 软件复位PHY */int (*soft_reset)(struct phy_device *phydev);/* 初始化PHY配置,通常在复位后调用 */int (*config_init)(struct phy_device *phydev);/* 探测PHY设备时调用,用于设置设备特定的结构 */int (*probe)(struct phy_device *phydev);/* PHY电源管理 */int (*suspend)(struct phy_device *phydev);  // 挂起PHYint (*resume)(struct phy_device *phydev);   // 恢复PHY/* 配置自动协商参数,根据phydev->autoneg决定是否重置自动协商 */int (*config_aneg)(struct phy_device *phydev);/* 检查自动协商是否完成 */int (*aneg_done)(struct phy_device *phydev);/* 读取当前状态,确定协商的速度和双工模式 */int (*read_status)(struct phy_device *phydev);/* 清除挂起的中断 */int (*ack_interrupt)(struct phy_device *phydev);/* 启用或禁用中断 */int (*config_intr)(struct phy_device *phydev);/* 检查PHY是否产生了中断(用于共享中断引脚的多PHY设备) */int (*did_interrupt)(struct phy_device *phydev);/* 清理PHY驱动分配的资源 */void (*remove)(struct phy_device *phydev);/* 检查此驱动是否适合给定的phydev,如果为NULL则基于phy_id和phy_id_mask匹配 */int (*match_phy_device)(struct phy_device *phydev);/* 处理硬件时间戳的ethtool查询 */int (*ts_info)(struct phy_device *phydev, struct ethtool_ts_info *ti);/* 处理硬件时间戳的ioctl请求 */int (*hwtstamp)(struct phy_device *phydev, struct ifreq *ifr);/* 请求接收时间戳,如果skb被接受,驱动承诺在时间戳可用时通过netif_rx()交付 */bool (*rxtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);/* 请求发送时间戳,驱动承诺在时间戳可用时通过skb_complete_tx_timestamp()交付 */void (*txtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);/* 启用/禁用Wake on LAN功能 */int (*set_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);void (*get_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);/* 当核心即将改变链路状态时通知PHY驱动 */void (*link_change_notify)(struct phy_device *dev);/* 读取MMD寄存器的PHY特定实现 */int (*read_mmd)(struct phy_device *dev, int devnum, u16 regnum);/* 写入MMD寄存器的PHY特定实现 */int (*write_mmd)(struct phy_device *dev, int devnum, u16 regnum, u16 val);/* 读取PHY页寄存器 */int (*read_page)(struct phy_device *dev);/* 写入PHY页寄存器 */int (*write_page)(struct phy_device *dev, int page);/* 获取插件模块的EEPROM大小和类型信息 */int (*module_info)(struct phy_device *dev, struct ethtool_modinfo *modinfo);/* 从插件模块读取EEPROM数据 */int (*module_eeprom)(struct phy_device *dev, struct ethtool_eeprom *ee, u8 *data);/* 通过ethtool获取PHY统计信息 */int (*get_sset_count)(struct phy_device *dev);  // 获取统计项数量void (*get_strings)(struct phy_device *dev, u8 *data);  // 获取统计项名称void (*get_stats)(struct phy_device *dev, struct ethtool_stats *stats, u64 *data);  // 获取统计值/* 获取和设置PHY可调参数 */int (*get_tunable)(struct phy_device *dev, struct ethtool_tunable *tuna, void *data);int (*set_tunable)(struct phy_device *dev, struct ethtool_tunable *tuna, const void *data);/* 启用/禁用环回模式 */int (*set_loopback)(struct phy_device *dev, bool enable);
};
模块涉及代码描述
mdio模块mdio_hisi_gemac.c海思GMAC专用的MDIO总线驱动,注册MDIO控制器,支持PHY寄存器读写。
mdio_bus.c (内核通用)提供MDIO总线框架,管理PHY设备的注册、发现和通信协议(Clause 22/45)。
of_mdio.c (内核通用)解析设备树(DT)中的MDIO和PHY节点,自动绑定PHY设备到MDIO总线。
PHY模块phy_device.c通用PHY设备驱动框架,管理PHY状态机(如<font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">phy_state</font>)、自动协商和链路状态回调。
海思GMAC模快higmac.c海思千兆以太网控制器(GMAC)驱动,处理DMA描述符、数据收发和MAC层控制。

3 关键代码流程导读

1 注册流程
1.1)phy_device 注册

在mac控制器驱动中probe函数会在mdio bus上面注册phy_device驱动

一个PHY设备对应一个phy_device 示例,然后需要向linux内核注册这个示例

注册接口:phy_device_register

使用dump_stack打印如下:

 get_phy_device+0x12c/0x328of_mdiobus_register_phy+0x154/0x2a8of_mdiobus_register+0x118/0x360hisi_gemac_mdio_probe+0x1d0/0x278platform_drv_probe+0x50/0xa0really_probe+0x1c8/0x2a8driver_probe_device+0x58/0x108__driver_attach+0xec/0xf0bus_for_each_dev+0x74/0xc8driver_attach+0x20/0x28bus_add_driver+0x1b8/0x228driver_register+0x60/0x110__platform_driver_register+0x40/0x48hisi_gemac_mdio_driver_init+0x18/0x20do_one_initcall+0x5c/0x180kernel_init_freeable+0x148/0x1f0kernel_init+0x10/0x100ret_from_fork+0x10/0x1c
hisi_gemac_mdio_driver_inithisi_gemac_mdio_probe of_mdiobus_register //注册mdio_bus 并注册phy设备 of_mdio.cmdiobus_register  // 注册mdio busof_mdiobus_register_phy //循环查找phy设备 注册phy设备get_phy_device //获取phy设备phy_device_create //创建phy设备,设置phy地址、总先类型、PHY ID 、自协商、更新状态机phy_device_register //注册phy设备
1.2) mdio 注册

drivers\net\phy\mdio_hisi_gemac.c

static int hisi_gemac_mdio_probe(struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node;struct mii_bus *bus = NULL;struct hisi_gemac_mdio_data *data = NULL;struct resource *res = NULL;int ret;ret = hisi_gemac_pinctrl_config(pdev);if (ret) {pr_err("higmac pinctrl config error=%d.\n", ret);return ret;}bus = mdiobus_alloc_size(sizeof(*data));if (!bus)return -ENOMEM;bus->name = "hisi_gemac_mii_bus";bus->read = &hisi_gemac_mdio_read;bus->write = &hisi_gemac_mdio_write;if (snprintf_s(bus->id, MII_BUS_ID_SIZE, strlen(pdev->name), "%s", pdev->name) < 0)printk("snprintf_s failed!func:%s, line: %d\n", __func__, __LINE__);bus->parent = &pdev->dev;if(0 == strncmp(bus->id,"101c03c0",8)){rtl8761_bus=bus;printk("txr add debug rtl8761_bus id %s \n",bus->id);}data = bus->priv;res = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (res == NULL || data == NULL) {ret = -ENXIO;goto err_out_free_mdiobus;}data->membase = devm_ioremap(&pdev->dev, res->start,resource_size(res));if (!data->membase) {ret = -ENOMEM;goto err_out_free_mdiobus;}//时钟初始化data->clk = devm_clk_get(&pdev->dev, NULL);if (IS_ERR(data->clk)) {ret = PTR_ERR(data->clk);goto err_out_free_mdiobus;}ret = clk_prepare_enable(data->clk);if (ret)goto err_out_free_mdiobus;data->phy_rst = devm_reset_control_get(&pdev->dev, "phy_reset");if (IS_ERR(data->phy_rst))data->phy_rst = NULL;hisi_gemac_external_phy_reset(data);//mdio 注册ret = of_mdiobus_register(bus, np);if (ret)goto err_out_disable_clk;platform_set_drvdata(pdev, bus);return 0;err_out_disable_clk:clk_disable_unprepare(data->clk);err_out_free_mdiobus:mdiobus_free(bus);return ret;
}

设备扫描注册

经过
of_mdiobus_registermdiobus_registerint __mdiobus_register(struct mii_bus *bus, struct module *owner)
{struct mdio_device *mdiodev;int i, err;struct gpio_desc *gpiod;if (NULL == bus || NULL == bus->name ||NULL == bus->read || NULL == bus->write)return -EINVAL;BUG_ON(bus->state != MDIOBUS_ALLOCATED &&bus->state != MDIOBUS_UNREGISTERED);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);err = device_register(&bus->dev);if (err) {pr_err("mii_bus %s failed to register\n", bus->id);return -EINVAL;}mutex_init(&bus->mdio_lock);/* de-assert bus level PHY GPIO reset */gpiod = devm_gpiod_get_optional(&bus->dev, "reset", GPIOD_OUT_LOW);if (IS_ERR(gpiod)) {dev_err(&bus->dev, "mii_bus %s couldn't get reset GPIO\n",bus->id);device_del(&bus->dev);return PTR_ERR(gpiod);} else	if (gpiod) {bus->reset_gpiod = gpiod;gpiod_set_value_cansleep(gpiod, 1);udelay(bus->reset_delay_us);gpiod_set_value_cansleep(gpiod, 0);}if (bus->reset)bus->reset(bus);for (i = 0; i < PHY_MAX_ADDR; i++) {if ((bus->phy_mask & (1 << i)) == 0) {struct phy_device *phydev;//扫描所有的phy设备phydev = mdiobus_scan(bus, i);if (IS_ERR(phydev) && (PTR_ERR(phydev) != -ENODEV)) {err = PTR_ERR(phydev);goto error;}}}//mdiobus_create_device 将扫描到的设备挂在到mdio bus上mdiobus_setup_mdiodev_from_board_info(bus, mdiobus_create_device);bus->state = MDIOBUS_REGISTERED;pr_info("%s: probed\n", bus->name);return 0;error:while (--i >= 0) {mdiodev = bus->mdio_map[i];if (!mdiodev)continue;mdiodev->device_remove(mdiodev);mdiodev->device_free(mdiodev);}/* Put PHYs in RESET to save power */if (bus->reset_gpiod)gpiod_set_value_cansleep(bus->reset_gpiod, 1);device_del(&bus->dev);return err;
}
struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr)
{struct phy_device *phydev;int err;//创建phy_devicephydev = get_phy_device(bus, addr, false);if (IS_ERR(phydev))return phydev;/** For DT, see if the auto-probed phy has a correspoding child* in the bus node, and set the of_node pointer in this case.*/of_mdiobus_link_mdiodev(bus, &phydev->mdio);//注册到mdio bus上err = phy_device_register(phydev);if (err) {phy_device_free(phydev);return ERR_PTR(-ENODEV);}return phydev;
}
EXPORT_SYMBOL(mdiobus_scan);
mdiobus_scanget_phy_devicestruct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45)
{struct phy_c45_device_ids c45_ids = {0};u32 phy_id = 0;int r;#ifdef CONFIG_RTL8363_NBstatic int s_init = 0 ;if((addr != UTP_PORT1) && (addr != UTP_PORT3) && (addr != EXT_PORT0))		///only read 0//return NULL;return ERR_PTR(-ENODEV);// Compatable with fixed-phy.if(strncmp("fixed", bus->id, 5) == 0){r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);if (r)return ERR_PTR(r);/* If the phy_id is mostly Fs, there is no device there */if ((phy_id & 0x1fffffff) == 0x1fffffff)return ERR_PTR(-ENODEV);}else{//rtl8761_bus = bus;r = rtk8367rb_getphy_id(bus, addr, &phy_id, is_c45, &c45_ids);if (r)return ERR_PTR(r);//dump_stack();if(s_init == 0){rtk_switch_reg1b03();s_init = 1 ;}/* If the phy_id is mostly Fs, there is no device there */if ((phy_id & 0x1fffffff) == 0x1fffffff) {return ERR_PTR(-ENODEV);}if ((phy_id & 0xffff) == 0xffff) {return ERR_PTR(-ENODEV);}if (phy_id == 0x0) {return ERR_PTR(-ENODEV);}rtl8367rb_init();}
#else//拿到设备IDr = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);if (r)return ERR_PTR(r);/* If the phy_id is mostly Fs, there is no device there */if ((phy_id & 0x1fffffff) == 0x1fffffff)return ERR_PTR(-ENODEV);
#endifrtk_switch_reg1b03();dump_stack();return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);
}
EXPORT_SYMBOL(get_phy_device);

1 get_phy_id 或者rtk8367rb_getphy_id 来读取PHY芯片的ID

2 phy_device_create 填充phy_device 结构体

struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id,bool is_c45,struct phy_c45_device_ids *c45_ids)
{struct phy_device *dev;struct mdio_device *mdiodev;/* We allocate the device, and initialize the default values */dev = kzalloc(sizeof(*dev), GFP_KERNEL);if (!dev)return ERR_PTR(-ENOMEM);mdiodev = &dev->mdio;mdiodev->dev.parent = &bus->dev;mdiodev->dev.bus = &mdio_bus_type;mdiodev->dev.type = &mdio_bus_phy_type;mdiodev->bus = bus;mdiodev->bus_match = phy_bus_match;mdiodev->addr = addr;mdiodev->flags = MDIO_DEVICE_FLAG_PHY;mdiodev->device_free = phy_mdio_device_free;mdiodev->device_remove = phy_mdio_device_remove;dev->speed = 0;dev->duplex = -1;dev->pause = 0;dev->asym_pause = 0;dev->link = 0;dev->interface = PHY_INTERFACE_MODE_GMII;dev->autoneg = 0;//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;mutex_init(&dev->lock);//在这里创建了状态机,是另一个重点知识INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);INIT_WORK(&dev->phy_queue, phy_change_work);/* Request the appropriate module unconditionally; don't* bother trying to do so only if it isn't already loaded,* because that gets complicated. A hotplug event would have* done an unconditional modprobe anyway.* We don't do normal hotplug because it won't work for MDIO* -- because it relies on the device staying around for long* enough for the driver to get loaded. With MDIO, the NIC* driver will get bored and give up as soon as it finds that* there's no driver _already_ loaded.*/request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id));device_initialize(&mdiodev->dev);return dev;
}
int phy_device_register(struct phy_device *phydev)
{int err;//挂在到mdio bus上err = mdiobus_register_device(&phydev->mdio);if (err)return err;/* Deassert the reset signal */phy_device_reset(phydev, 0);/* Run all of the fixups for this PHY */err = phy_scan_fixups(phydev);if (err) {pr_err("PHY %d failed to initialize\n", phydev->mdio.addr);goto out;}err = device_add(&phydev->mdio.dev);if (err) {pr_err("PHY %d failed to add\n", phydev->mdio.addr);goto out;}return 0;out:/* Assert the reset signal */phy_device_reset(phydev, 1);mdiobus_unregister_device(&phydev->mdio);return err;
}
int mdiobus_register_device(struct mdio_device *mdiodev)
{int err;//对应图中红色线if (mdiodev->bus->mdio_map[mdiodev->addr])return -EBUSY;if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) {err = mdiobus_register_gpiod(mdiodev);if (err)return err;}mdiodev->bus->mdio_map[mdiodev->addr] = mdiodev;return 0;
}

device_add的最终结果为:

1.3)phy_driver 注册
phy_initphy_driver_registerint phy_driver_register(struct phy_driver *new_driver, struct module *owner)
{int retval;new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY;new_driver->mdiodrv.driver.name = new_driver->name;new_driver->mdiodrv.driver.bus = &mdio_bus_type;//探测函数new_driver->mdiodrv.driver.probe = phy_probe;new_driver->mdiodrv.driver.remove = phy_remove;new_driver->mdiodrv.driver.owner = owner;/* The following works around an issue where the PHY driver doesn't bind* to the device, resulting in the genphy driver being used instead of* the dedicated driver. The root cause of the issue isn't known yet* and seems to be in the base driver core. Once this is fixed we may* remove this workaround.*/new_driver->mdiodrv.driver.probe_type = PROBE_FORCE_SYNCHRONOUS;// 注册函数retval = driver_register(&new_driver->mdiodrv.driver);if (retval) {pr_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);
1.4)phy_device和phy_driver 的连接
 phy_bus_match+0x18/0xc8mdio_bus_match+0x4c/0x60__device_attach_driver+0x34/0xd8bus_for_each_drv+0x68/0xd0__device_attach+0xd8/0x138device_initial_probe+0x10/0x18bus_probe_device+0x94/0xa0device_add+0x3f0/0x618phy_device_register+0x54/0x98of_mdiobus_register_phy+0x210/0x2a8of_mdiobus_register+0x118/0x360hisi_gemac_mdio_probe+0x1d0/0x278platform_drv_probe+0x50/0xa0really_probe+0x1c8/0x2a8driver_probe_device+0x58/0x108__driver_attach+0xec/0xf0bus_for_each_dev+0x74/0xc8driver_attach+0x20/0x28bus_add_driver+0x1b8/0x228driver_register+0x60/0x110__platform_driver_register+0x40/0x48hisi_gemac_mdio_driver_init+0x18/0x20
static int phy_bus_match(struct device *dev, struct device_driver *drv)
{struct phy_device *phydev = to_phy_device(dev);struct phy_driver *phydrv = to_phy_driver(drv);const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids);int i;if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY))return 0;if (phydrv->match_phy_device)return phydrv->match_phy_device(phydev);if (phydev->is_c45) {for (i = 1; i < num_ids; i++) {if (!(phydev->c45_ids.devices_in_package & (1 << i)))continue;if ((phydrv->phy_id & phydrv->phy_id_mask) ==(phydev->c45_ids.device_ids[i] &phydrv->phy_id_mask))return 1;}return 0;} else {//当phy_driver的id和phy_device的id相同时,表示匹配上return (phydrv->phy_id & phydrv->phy_id_mask) ==(phydev->phy_id & phydrv->phy_id_mask);}
}
1.5)phy_device和 net_device的连接
  • <font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">net_device</font> 需要发送数据时,会通过 PHY 的 <font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">phy_start_aneg()</font> 进行自动协商。
  • <font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">net_device</font> 需要检测链路状态时,会调用 <font style="color:rgba(0, 0, 0, 0.88);background-color:rgba(175, 184, 193, 0.2);">phy_read_status()</font>
Call trace:phy_connect_direct+0x54/0x80of_phy_connect+0x4c/0x98higmac_dev_probe+0x408/0xda0platform_drv_probe+0x50/0xa0really_probe+0x1d0/0x2b0driver_probe_device+0x58/0x108__driver_attach+0xec/0xf0bus_for_each_dev+0x74/0xc8driver_attach+0x20/0x28bus_add_driver+0x1b8/0x228driver_register+0x60/0x110__platform_driver_register+0x40/0x48higmac_init+0x20/0x3c
struct phy_device *phy_connect(struct net_device *dev, const char *bus_id,void (*handler)(struct net_device *),phy_interface_t interface)
{struct phy_device *phydev;struct device *d;int rc;/* Search the list of PHY devices on the mdio bus for the* PHY with the requested name*/d = bus_find_device_by_name(&mdio_bus_type, NULL, bus_id);if (!d) {pr_err("PHY %s not found\n", bus_id);return ERR_PTR(-ENODEV);}phydev = to_phy_device(d);rc = phy_connect_direct(dev, phydev, handler, interface);put_device(d);if (rc)return ERR_PTR(rc);return phydev;
}
EXPORT_SYMBOL(phy_connect);

1 首先调用bus_find_device_by_name 去根据设备名称查找mdio总线上的设备,一旦有匹配立即返回对应的struct device的指针

2 接着调用to_phy_device,获得struct device d 对应的stuct phy_device

3 最后调用phy_connect_direct,将对应的driver作为phy_device->dev的驱动,接着调用phydev->attached_dev = dev; 将网络设备net_device 与phy_device完成连接,将函数带的参数赋值给phy_device对应的成员变量

4 phy_start_machine 启动了状态机

int phy_connect_direct(struct net_device *dev, struct phy_device *phydev,void (*handler)(struct net_device *),phy_interface_t interface)
{int rc;if (!dev)return -EINVAL;rc = phy_attach_direct(dev, phydev, phydev->dev_flags, interface);if (rc)return rc;phy_prepare_link(phydev, handler);phy_start_machine(phydev);if (phydev->irq > 0)phy_start_interrupts(phydev);return 0;
}
EXPORT_SYMBOL(phy_connect_direct);

4 状态机

头文件在include/linux/phy.h

enum phy_state {PHY_DOWN = 0,//0 dowm 如关闭网卡 ifconfig eth0 downPHY_STARTING,//1 PHY芯片OK了,但是驱动还没有准备好PHY_READY,   //2 PHY设备注册成功PHY_PENDING, //3 PHY 芯片挂起PHY_UP,      //4 开启网卡 ifconfig eth0 upPHY_AN,      //5 网卡自动协商PHY_RUNNING, // 6 网卡已经插入网线,并建立物理连接PHY_NOLINK,  //7 断网,如拔掉网线PHY_FORCING, //8 自动协商标识未被使能,就强制执行自动协商(读取phy寄存器、并设置通讯速率、半双工或全双工模式等)PHY_CHANGELINK,//9 当连接时,会换到PHY_RUNNING,当断网是,会切到PHY_NOLINKPHY_HALTED,    //10 phy挂起PHY_RESUMING   //11 phy恢复
};
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);bool needs_aneg = false, do_suspend = false;enum phy_state old_state;int err = 0;int old_link;mutex_lock(&phydev->lock);old_state = phydev->state;if (phydev->drv && phydev->drv->link_change_notify)phydev->drv->link_change_notify(phydev);switch (phydev->state) {case PHY_DOWN:case PHY_STARTING:case PHY_READY:case PHY_PENDING:break;case PHY_UP:needs_aneg = true;//网卡协商phydev->link_timeout = PHY_AN_TIMEOUT;break;case PHY_AN:err = phy_read_status(phydev);if (err < 0)break;/* If the link is down, give up on negotiation for now */if (!phydev->link) {phydev->state = PHY_NOLINK;phy_link_down(phydev, true);break;}/* Check if negotiation is done.  Break if there's an error */err = phy_aneg_done(phydev);if (err < 0)break;/* If AN is done, we're running */if (err > 0) {phydev->state = PHY_RUNNING;phy_link_up(phydev);} else if (0 == phydev->link_timeout--)needs_aneg = true;break;case PHY_NOLINK:if (!phy_polling_mode(phydev))break;err = phy_read_status(phydev);if (err)break;if (phydev->link) {if (AUTONEG_ENABLE == phydev->autoneg) {err = phy_aneg_done(phydev);if (err < 0)break;if (!err) {phydev->state = PHY_AN;phydev->link_timeout = PHY_AN_TIMEOUT;break;}}phydev->state = PHY_RUNNING;phy_link_up(phydev);}break;case PHY_FORCING:err = genphy_update_link(phydev);if (err)break;if (phydev->link) {phydev->state = PHY_RUNNING;phy_link_up(phydev);} else {if (0 == phydev->link_timeout--)needs_aneg = true;phy_link_down(phydev, false);}break;case PHY_RUNNING:/* Only register a CHANGE if we are polling and link changed* since latest checking.*/if (phy_polling_mode(phydev)) {old_link = phydev->link;err = phy_read_status(phydev);if (err)break;if (old_link != phydev->link)phydev->state = PHY_CHANGELINK;}/** Failsafe: check that nobody set phydev->link=0 between two* poll cycles, otherwise we won't leave RUNNING state as long* as link remains down.*/if (!phydev->link && phydev->state == PHY_RUNNING) {phydev->state = PHY_CHANGELINK;phydev_err(phydev, "no link in PHY_RUNNING\n");}break;case PHY_CHANGELINK:err = phy_read_status(phydev);if (err)break;if (phydev->link) {phydev->state = PHY_RUNNING;phy_link_up(phydev);} else {phydev->state = PHY_NOLINK;phy_link_down(phydev, true);}break;case PHY_HALTED:if (phydev->link) {phydev->link = 0;phy_link_down(phydev, true);do_suspend = true;}break;case PHY_RESUMING:if (AUTONEG_ENABLE == phydev->autoneg) {err = phy_aneg_done(phydev);if (err < 0)break;/* err > 0 if AN is done.* Otherwise, it's 0, and we're  still waiting for AN*/if (err > 0) {err = phy_read_status(phydev);if (err)break;if (phydev->link) {phydev->state = PHY_RUNNING;phy_link_up(phydev);} else	{phydev->state = PHY_NOLINK;phy_link_down(phydev, false);}} 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;phy_link_up(phydev);} else	{phydev->state = PHY_NOLINK;phy_link_down(phydev, false);}}break;}mutex_unlock(&phydev->lock);if (needs_aneg)err = phy_start_aneg_priv(phydev, false);else if (do_suspend)phy_suspend(phydev);if (err < 0)phy_error(phydev);if (old_state != phydev->state)phydev_dbg(phydev, "PHY state change %s -> %s\n",phy_state_to_str(old_state),phy_state_to_str(phydev->state));/* Only re-schedule a PHY state machine change if we are polling the* PHY, if PHY_IGNORE_INTERRUPT is set, then we will be moving* between states from phy_mac_interrupt()*/if (phy_polling_mode(phydev))queue_delayed_work(system_power_efficient_wq, &phydev->state_queue,PHY_STATE_TIME * HZ);
}

http://www.xdnf.cn/news/13870.html

相关文章:

  • Application with id application_xxx doesn‘t exist in RM解决方法
  • 基于mapreduce的气候分析系统设计与实现
  • 创客匠人:为知识变现与 IP 打造赋能
  • 纯血HarmonyOS ArKTS NETX 5 打造小游戏实践:狼人杀(介绍版(附源文件)
  • docker 02网络
  • Rollup vs Webpack 深度对比:前端构建工具终极指南
  • (二十六)深度解析领域特定语言(DSL)第四章——词法分析:基于正则表达式的词法分析器
  • 完全渲染后的页面内容
  • Matlab 实现基于深度学习的高压开关柜多故障实时检测方法研究
  • 《TCP/IP协议卷1》第1章 概述
  • Panthor 开源方案与 Mesa 图形库的技术解析
  • 【地图服务限制范围】
  • Odoo 18 库存中管理最低安全库存规则(再订货规则)
  • Python Day49 学习(日志Day19复习)
  • 【Java多线程从青铜到王者】阻塞队列(十)
  • 欧拉系统openEuler-24.03忘记密码,如何改密码
  • Python训练营-Day29-复习日
  • 修改FFMpeg的日志函数av_log,使其在记录日志时能显示调用该函数的位置(文件名和行号)
  • Metastore 架构示意图和常用 SQL
  • 前端加密当日
  • 力扣前缀和
  • 河南农担携手Gitee企业版:构建农业金融数字化研发新基建
  • 网络层协议:IP
  • qt初识--02
  • 移动电储能工作原理及SOC约束解析
  • 光谱相机叶绿素荧光成像技术的原理
  • Vue 组件通信
  • Jenkins 配置信息导出 的详细说明(中英对照)
  • 如何用AI赋能学习
  • OpenCV CUDA模块图像变形------对图像进行 尺寸缩放(Resize)操作函数resize()