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 实现应遵循以下步骤:
- 硬件复位与初始化:重置 MAC 控制器、PHY 状态机,清空 FIFO 和描述符环。
- 分配与初始化描述符环:为 TX/RX 队列分配 DMA 缓冲区,建立描述符链表。
- 注册中断处理函数:调用
request_irq或request_threaded_irq,绑定中断号与处理函数。 - 启用 NAPI:调用
napi_enable,使 NAPI 调度机制就绪。 - 启动硬件发送/接收:写寄存器使能 MAC 的 TX 和 RX 路径。
- 启动 PHY:调用
phy_start或直接操作 PHY 寄存器建立链路。 - 更新链路状态:通过
netif_carrier_on通知内核链路已就绪。
关键原则:ndo_open 必须是幂等的——多次调用不应导致资源泄漏或状态混乱。建议在函数入口处增加 netif_running 检查,避免重复启动。
4.2 ndo_stop:设备停止的逆向流程
ndo_stop 是 ndo_open 的逆操作,执行顺序必须严格反向,否则可能导致 DMA 正在访问已释放的内存:
- 停止 PHY 和链路通告:调用
phy_stop,关闭自动协商和链路检测。 - 停止硬件收发:写寄存器禁用 MAC 的 TX 和 RX 路径,等待正在进行的 DMA 传输完成。
- 关闭 NAPI:调用
napi_disable,确保所有 NAPI 实例不再被调度。 - 释放中断:调用
free_irq注销中断处理函数。 - 释放描述符环与 DMA 缓冲区:遍历所有描述符,调用
dma_unmap_single或dma_free_coherent释放内存。 - 硬件复位(可选):将 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 之间的完整状态转换,以及各阶段对应的硬件和软件状态:
4.6 中断与 NAPI 交互流程
下图展示了从硬件中断触发到 NAPI 轮询收包的完整交互链路,帮助理解 ndo_open 中注册中断与启用 NAPI 的时序意义:
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 的时序关系,快速定位启动/停止过程中的异常。