找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1009

积分

0

好友

131

主题
发表于 前天 02:34 | 查看: 7| 回复: 0

在Linux网络驱动开发中,除了net_device结构体和NAPI等软件架构,理解数据包的实际发送硬件链路同样关键。这通常涉及MAC与PHY两个核心硬件模块的协同工作。

以太网硬件通常由两个独立的部分组成:

  • MAC (Media Access Control):位于数据链路层,负责以太网帧的格式处理、CRC校验、MAC地址识别以及与CPU之间的数据搬移(通常通过DMA)。MAC通常是集成在SoC内部的网络控制器,其驱动是网络功能的核心。
  • PHY (Physical Layer):位于物理层,负责将数字信号调制为能在网线上传输的模拟信号,包括编码、解码、时钟恢复和自适应等。PHY通常是一颗独立的外部芯片(如RTL8211E, AR8031)。

简单来说,MAC负责组帧和逻辑控制,PHY则负责物理信号的转换。二者通过MII/RMII/RGMII等接口连接。

为了配置和监控PHY芯片,需要一个管理接口,这就是MDIO (Management Data Input/Output) 总线。它是一个简单的两线串行协议:

  • MDC:管理数据时钟线。
  • MDIO:管理数据输入/输出线。

MAC驱动作为主设备,通过MDIO总线读写PHY的内部寄存器,从而配置其速度(10/100/1000 Mbps)、双工模式(半双工/全双工)以及监控链路状态。

Linux内核驱动开发中,PHY驱动被抽象为一个独立的子系统,架构清晰:

  1. MDIO总线驱动:通常集成在SoC的MAC驱动中,负责实现MDIO协议的底层时序,将读写请求转换为具体的MDC/MDIO信号。
  2. PHY核心:负责PHY设备的发现、注册、状态机管理和提供通用API。
  3. PHY驱动:针对特定型号的PHY芯片(如RTL8211F),实现其私有寄存器的配置逻辑。这正是驱动开发者需要编写的主要部分。

PHY驱动的编写围绕 struct phy_driver 结构体展开,开发者无需关心MDIO总线的传输细节,只需专注于芯片寄存器的操作。

PHY驱动匹配与核心结构
驱动通过一个由OUI(组织唯一标识符)和型号组成的PHY ID进行匹配。驱动核心结构体如下:

#include <linux/phy.h>
#include <linux/module.h>

/* PHY 驱动的核心结构体,封装了驱动的能力和匹配信息 */
struct phy_driver {
    u32 phy_id;        // 芯片的唯一 ID,通常通过读取 PHY_IDR1/PHY_IDR2 寄存器获得
    u32 phy_id_mask;   // 匹配掩码
    char *name;        // 驱动名称
    // 关键回调函数
    int (*probe)(struct phy_device *phydev);
    int (*config_init)(struct phy_device *phydev); // 芯片初始化配置
    int (*read_status)(struct phy_device *phydev); // 读取链路状态
    void (*remove)(struct phy_device *phydev);
    // ...
    struct device_driver driver;
};

寄存器读写API
PHY核心通过MDIO总线驱动封装好了寄存器读写函数,开发者直接调用即可:

int phy_read(struct phy_device *phydev, u32 regnum);
int phy_write(struct phy_device *phydev, u32 regnum, u16 val);

PHY驱动实例:Realtek PHY配置
以下是一个简化的Realtek PHY驱动模板,展示了 config_init 函数的典型实现:

#define REALTEK_PHY_ID    0x001cc912 // 假设的 Realtek 芯片ID

// 寄存器地址定义 (部分为标准,部分为厂商特定)
#define MII_BMCR      0x00 // 基本模式控制寄存器
#define MII_BMSR      0x01 // 基本模式状态寄存器
#define RTL_SPEC_REG  0x10 // 厂商特定的寄存器

/* PHY 芯片的初始化配置 */
static int rtl_config_init(struct phy_device *phydev)
{
    int ret;
    // 1. 读取标准状态寄存器,了解芯片基本情况
    ret = phy_read(phydev, MII_BMSR);
    if (ret < 0) return ret;
    dev_info(&phydev->dev, "PHY 芯片基本状态: 0x%x\n", ret);

    // 2. 配置芯片特定的功能寄存器
    // 例如:写入 0x0100 启用 Realtek 的某种节能模式
    ret = phy_write(phydev, RTL_SPEC_REG, 0x0100);
    if (ret < 0) return ret;

    // 3. 调用内核通用函数,配置标准的速度/双工自动协商
    ret = genphy_config_aneg(phydev);
    return ret;
}

static int rtl_probe(struct phy_device *phydev)
{
    // probe函数通常很简单,进行必要的初始化或信息打印
    dev_info(&phydev->dev, "Realtek PHY 芯片已匹配并加载\n");
    return 0;
}

static struct phy_driver rtl_phy_driver = {
    .phy_id         = REALTEK_PHY_ID,
    .phy_id_mask    = 0xfffffff0, // 掩码用于匹配同一系列芯片
    .name           = "Realtek-Eth-PHY",
    .probe          = rtl_probe,
    .config_init    = rtl_config_init,
    .read_status    = genphy_read_status, // 使用内核提供的通用状态读取函数
    .driver = {
        .owner = THIS_MODULE,
    },
};

// 注册 PHY 驱动
module_phy_driver(rtl_phy_driver);
MODULE_LICENSE("GPL");

工作流程总结
整个Linux网络子系统中MDIO/MAC/PHY的协同工作流程如下:

  1. MAC驱动:初始化SoC内部网络控制器,实现net_device的收发逻辑(DMA/NAPI),并注册其MDIO总线控制器驱动。
  2. PHY核心:扫描MDIO总线,读取每个PHY设备的ID寄存器(0x02和0x03),获取phy_id
  3. PHY驱动匹配:PHY核心根据获取的phy_id,匹配已注册的struct phy_driver
  4. PHY驱动工作:调用匹配成功的PHY驱动的config_init等回调函数,通过phy_read/phy_write完成芯片特定配置。
  5. 数据流:网络数据包经MAC组帧后,通过MII接口发送给PHY,PHY根据配置将其转换为物理电信号发送至网络。



上一篇:PE文件TLS表深度剖析:Windows多线程编程中的数据结构与内存模型详解
下一篇:Rust 进阶(二):当你开始写复杂系统,Rust 会逼你思考什么
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 18:06 , Processed in 0.109302 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表