☰
Linux 驱动开发:如何添加以太网驱动
2026/10/1 15:59:52 网站建设 项目流程

1. 引言

在 Linux 系统中,以太网驱动是网络功能正常工作的基础。无论是开发板、嵌入式设备还是服务器网卡,都需要通过驱动与内核网络协议栈交互。本文将从驱动框架、核心数据结构、注册流程到实际代码示例,系统讲解如何在 Linux 中编写并添加一个以太网驱动。

2. 以太网驱动框架概述

Linux 内核为网络设备驱动提供了统一的抽象层,核心是struct net_device结构体。驱动开发者需要完成设备探测、资源初始化、打开/关闭接口、数据收发等操作,并把这些操作注册到内核网络子系统。

一个完整的以太网驱动通常包含以下组成部分:

  • 设备探测与初始化:通过平台总线、PCI 或设备树匹配硬件。
  • 网络设备注册:分配并初始化net_device,设置操作函数集。
  • 数据发送:实现ndo_start_xmit回调,把 skb 交给硬件。
  • 数据接收:通过中断或轮询方式接收数据,并上交给协议栈。
  • 中断处理:处理收发完成、链路状态变化等事件。

3. 核心数据结构

编写以太网驱动前,需要先理解几个关键的内核数据结构。

3.1 struct net_device

这是网络设备的核心结构体,描述一个网络接口的全部属性,包括设备名、MAC 地址、MTU、操作函数集等。驱动通过alloc_etherdev或alloc_netdev分配。

3.2 struct net_device_ops

该结构体定义了设备的各种操作回调,例如打开设备、停止设备、发送数据、修改 MAC 地址等。驱动必须实现其中必要的回调并赋值给net_device的netdev_ops字段。

3.3 struct sk_buff

内核网络协议栈中用于承载数据包的核心结构体。发送时驱动从sk_buff中取出数据交给硬件;接收时驱动把硬件数据封装成sk_buff并交给协议栈。

4. 驱动注册流程

以太网驱动的注册流程通常分为以下几个步骤:

  1. 定义平台驱动或 PCI 驱动结构体,并注册到内核总线。
  2. 在探测函数中读取硬件资源(寄存器地址、中断号等)。
  3. 调用alloc_etherdev分配网络设备结构体。
  4. 初始化net_device的字段和操作函数集。
  5. 调用register_netdev把设备注册到内核网络子系统。
  6. 在移除函数中调用unregister_netdev和free_netdev释放资源。

5. 代码示例:一个简单的平台以太网驱动

下面以平台设备为例,演示一个最小可用的以太网驱动骨架。该示例使用虚拟寄存器模拟硬件,重点展示驱动框架的搭建过程。

#include <linux/module.h> #include <linux/platform_device.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/skbuff.h> #include <linux/interrupt.h> #define DRV_NAME "demo_eth" struct demo_eth_priv { struct net_device *netdev; void __iomem *base; int irq; }; static int demo_eth_open(struct net_device *netdev) { netif_start_queue(netdev); return 0; } static int demo_eth_stop(struct net_device *netdev) { netif_stop_queue(netdev); return 0; } static netdev_tx_t demo_eth_start_xmit(struct sk_buff *skb, struct net_device *netdev) { /* 实际驱动在此处把 skb 数据写入硬件发送 FIFO */ dev_kfree_skb(skb); return NETDEV_TX_OK; } static const struct net_device_ops demo_eth_netdev_ops = { .ndo_open = demo_eth_open, .ndo_stop = demo_eth_stop, .ndo_start_xmit = demo_eth_start_xmit, }; static int demo_eth_probe(struct platform_device *pdev) { struct demo_eth_priv *priv; struct net_device *netdev; struct resource *res; int ret; netdev = alloc_etherdev(sizeof(struct demo_eth_priv)); if (!netdev) return -ENOMEM; priv = netdev_priv(netdev); priv->netdev = netdev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(priv->base)) { ret = PTR_ERR(priv->base); goto err_free; } priv->irq = platform_get_irq(pdev, 0); if (priv->irq < 0) { ret = priv->irq; goto err_free; } netdev->netdev_ops = &demo_eth_netdev_ops; eth_hw_addr_set(netdev, (u8[]){0x00, 0x11, 0x22, 0x33, 0x44, 0x55}); ret = register_netdev(netdev); if (ret) goto err_free; platform_set_drvdata(pdev, priv); return 0; err_free: free_netdev(netdev); return ret; } static void demo_eth_remove(struct platform_device *pdev) { struct demo_eth_priv *priv = platform_get_drvdata(pdev); unregister_netdev(priv->netdev); free_netdev(priv->netdev); } static struct platform_driver demo_eth_driver = { .probe = demo_eth_probe, .remove = demo_eth_remove, .driver = { .name = DRV_NAME, .of_match_table = demo_eth_of_match, }, }; module_platform_driver(demo_eth_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Demo Ethernet Driver");

6. 设备树匹配

在设备树中,需要为硬件节点指定 compatible 属性,与驱动中的of_match_table对应。示例设备树节点如下:

demo_eth: ethernet@10000000 { compatible = "vendor,demo-eth"; reg = <0x10000000 0x1000>; interrupts = <31>; };

驱动中需要补充对应的匹配表:

static const struct of_device_id demo_eth_of_match[] = { { .compatible = "vendor,demo-eth" }, { } }; MODULE_DEVICE_TABLE(of, demo_eth_of_match);

7. 编译与加载

将驱动源码放入内核源码树的drivers/net/ethernet/目录,并修改对应的 Kconfig 和 Makefile,然后编译内核或编译为模块。

如果编译为模块,加载命令如下:

insmod demo_eth.ko

加载成功后,可以通过以下命令查看设备是否注册成功:

ip link show

如果看到类似eth0的接口,说明驱动注册成功。

8. 总结

添加以太网驱动的核心在于理解net_device框架、实现必要的操作回调,并正确完成设备注册流程。实际项目中还需要处理 DMA、中断、链路状态检测、NAPI 等复杂逻辑,但整体框架与本文示例一致。建议读者在真实硬件上结合内核文档逐步调试,加深对网络驱动子系统的理解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询