4. ndo_open 与 ndo_stop 实现:设备启动与停止的典型流程

网络设备的启动与停止是驱动生命周期中最核心的两个操作,分别对应 ndo_open 和 ndo_stop 回调。这两个回调不仅负责硬件状态切换,还承担着中断注册、NAPI 启用/禁用、资源分配与释放等关键任务。本章将深入剖析这两个接口的典型实现流程,并给出可直接参考的代码模板。

4.1 ndo_open:设备启动的标准化流程

当用户执行 ip link set eth0 up 或 ifconfig eth0 up 时,内核网络子系统会调用驱动注册的 ndo_open 回调。一个健壮的 ndo_open 实现应遵循以下步骤:

  1. 硬件复位与初始化:重置 MAC 控制器、PHY 状态机,清空 FIFO 和描述符环。
  2. 分配与初始化描述符环:为 TX/RX 队列分配 DMA 缓冲区,建立描述符链表。
  3. 注册中断处理函数:调用 request_irq 或 request_threaded_irq,绑定中断号与处理函数。
  4. 启用 NAPI:调用 napi_enable,使 NAPI 调度机制就绪。
  5. 启动硬件发送/接收:写寄存器使能 MAC 的 TX 和 RX 路径。
  6. 启动 PHY:调用 phy_start 或直接操作 PHY 寄存器建立链路。
  7. 更新链路状态:通过 netif_carrier_on 通知内核链路已就绪。

关键原则:ndo_open 必须是幂等的——多次调用不应导致资源泄漏或状态混乱。建议在函数入口处增加 netif_running 检查,避免重复启动。

4.2 ndo_stop:设备停止的逆向流程

ndo_stop 是 ndo_open 的逆操作,执行顺序必须严格反向,否则可能导致 DMA 正在访问已释放的内存:

  1. 停止 PHY 和链路通告:调用 phy_stop,关闭自动协商和链路检测。
  2. 停止硬件收发:写寄存器禁用 MAC 的 TX 和 RX 路径,等待正在进行的 DMA 传输完成。
  3. 关闭 NAPI:调用 napi_disable,确保所有 NAPI 实例不再被调度。
  4. 释放中断:调用 free_irq 注销中断处理函数。
  5. 释放描述符环与 DMA 缓冲区:遍历所有描述符,调用 dma_unmap_single 或 dma_free_coherent 释放内存。
  6. 硬件复位(可选):将 MAC 控制器置于低功耗或复位状态。

常见陷阱:在 ndo_stop 中必须先停止硬件,再关闭 NAPI 和释放中断。如果顺序颠倒,硬件可能在中断处理函数访问已释放的描述符时触发野指针访问,导致内核崩溃。

4.3 中断注册与 NAPI 启用的协同

中断注册和 NAPI 启用是 ndo_open 中最关键的两个步骤,它们之间存在严格的时序依赖:

步骤 操作 说明
1 分配描述符环 确保中断处理函数可以安全访问 DMA 内存
2 注册中断 使用 request_irq(irq, handler, flags, name, dev)
3 启用 NAPI 调用 napi_enable(&priv->napi)
4 使能硬件中断 写 MAC 寄存器允许中断信号到达 CPU

中断处理函数(ISR)的典型设计是:在 ISR 中屏蔽硬件中断,然后通过 napi_schedule 触发 NAPI 轮询。因此,napi_enable 必须在硬件中断使能之前调用,否则 ISR 中调用 napi_schedule 时 NAPI 尚未就绪,会导致调度失败。

4.4 典型代码模板

以下是一个简化的 ndo_open 和 ndo_stop 实现,展示了中断注册与 NAPI 启用的标准模式:

static int my_ndo_open(struct net_device *ndev)
{
    struct my_priv *priv = netdev_priv(ndev);
    int ret;

    /* 1. 硬件复位 */
    my_mac_reset(priv);

    /* 2. 分配描述符环 */
    ret = my_alloc_rings(priv);
    if (ret)
        return ret;

    /* 3. 注册中断 */
    ret = request_irq(priv->irq, my_isr, IRQF_SHARED,
                      ndev->name, ndev);
    if (ret) {
        my_free_rings(priv);
        return ret;
    }

    /* 4. 启用 NAPI */
    napi_enable(&priv->napi);

    /* 5. 使能硬件收发 */
    my_mac_enable_txrx(priv);

    /* 6. 启动 PHY */
    phy_start(priv->phydev);

    /* 7. 链路就绪 */
    netif_carrier_on(ndev);

    return 0;
}

static int my_ndo_stop(struct net_device *ndev)
{
    struct my_priv *priv = netdev_priv(ndev);

    /* 1. 停止 PHY */
    phy_stop(priv->phydev);

    /* 2. 停止硬件收发 */
    my_mac_disable_txrx(priv);

    /* 3. 关闭 NAPI */
    napi_disable(&priv->napi);

    /* 4. 释放中断 */
    free_irq(priv->irq, ndev);

    /* 5. 释放描述符环 */
    my_free_rings(priv);

    /* 6. 链路断开 */
    netif_carrier_off(ndev);

    return 0;
}

4.5 设备启动与停止状态机

下图展示了网络设备在 ndo_open 和 ndo_stop 之间的完整状态转换,以及各阶段对应的硬件和软件状态:

设备启动与停止状态机 DOWN OPENING UP STOPPING ndo_open 成功 ndo_stop 资源释放完成 失败回滚 说明:OPENING 和 STOPPING 是瞬时状态。在 OPENING 阶段任何步骤失败都必须回滚到 DOWN 状态, 确保设备状态一致。STOPPING 阶段不可中断,必须完整执行所有清理步骤。

4.6 中断与 NAPI 交互流程

下图展示了从硬件中断触发到 NAPI 轮询收包的完整交互链路,帮助理解 ndo_open 中注册中断与启用 NAPI 的时序意义:

中断与 NAPI 交互流程 硬件 MAC 中断控制器 ISR NAPI 轮询 网络协议栈 中断信号 分发 napi_schedule netif_receive_skb ndo_open 中: 1. 注册 ISR 2. napi_enable 3. 使能硬件中断

4.7 常见问题与避坑指南

问题 原因 解决方案
中断风暴导致系统卡死 ISR 中未屏蔽硬件中断,或 NAPI 未正确启用 ISR 中立即屏蔽中断,通过 napi_schedule 切换到轮询模式
二次 open 导致资源泄漏 未检查 netif_running,重复分配描述符环 在 ndo_open 入口增加 if (netif_running(ndev)) return 0;
DMA 访问已释放内存 ndo_stop 中先释放描述符再停止硬件 严格遵循:停止硬件 → 等待 DMA 完成 → 释放资源
NAPI 调度失败 napi_enable 在中断使能之后调用 确保 napi_enable 在 request_irq 之后、硬件中断使能之前

调试建议:在 ndo_open 和 ndo_stop 中增加 netdev_info 打印,记录每个步骤的进入和退出。配合 trace_printk 可以精确追踪中断与 NAPI 的时序关系,快速定位启动/停止过程中的异常。