《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》
第 14 章 Linux 网络设备驱动
参考:宋宝华 著,机械工业出版社,2015年版
14.1 Linux 网络设备驱动的结构
14.1.1 网络设备驱动的特殊性
网络设备驱动是 Linux 三大设备驱动类型中最特殊的一类,与字符设备和块设备有本质区别:
网络设备 vs 字符/块设备:
字符/块设备:
- 对应 /dev 目录下的设备文件(/dev/ttyS0、/dev/sda)
- 通过 open/read/write/ioctl 访问
- 用户空间直接操作
网络设备:
- 不对应 /dev 目录下的设备文件
- 通过套接字(Socket)接口访问
- 用户空间通过 socket()/send()/recv() 操作
- 设备名称:eth0、wlan0、lo 等
- 通过 ifconfig/ip 命令管理
网络设备驱动的独特之处:
✓ 数据以数据包(Packet)为单位传输
✓ 使用 sk_buff(套接字缓冲区)管理数据包
✓ 通过 net_device 结构体注册到内核
✓ 与内核网络协议栈紧密集成
✓ 支持统计信息(发送/接收字节数、错误数等)
14.1.2 Linux 网络设备驱动的层次结构
Linux 网络子系统层次结构:
┌─────────────────────────────────────────────────────────┐
│ 用户空间 │
│ 应用程序:socket() / send() / recv() / ioctl() │
└──────────────────────────┬──────────────────────────────┘
│ 系统调用
┌──────────────────────────▼──────────────────────────────┐
│ BSD Socket 接口层 │
│ AF_INET / AF_UNIX / AF_NETLINK │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ 传输层(Transport Layer) │
│ TCP / UDP / SCTP / DCCP │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ 网络层(Network Layer) │
│ IPv4 / IPv6 / ICMP / ARP │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ 链路层(Link Layer) │
│ 以太网帧处理 / Netfilter / Traffic Control │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ 网络设备驱动层(Device Driver) │
│ net_device + net_device_ops │
│ sk_buff(数据包缓冲区) │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ 物理网卡硬件 │
│ 以太网控制器 / Wi-Fi 芯片 / 蓝牙芯片 │
└─────────────────────────────────────────────────────────┘
14.1.3 sk_buff ------ 套接字缓冲区
sk_buff(Socket Buffer)是 Linux 网络子系统中最核心的数据结构,用于在各层之间传递网络数据包:
c
#include <linux/skbuff.h>
/*
* sk_buff:描述一个网络数据包
* 在各网络层之间传递,每层添加/删除自己的头部
*/
struct sk_buff {
/* 链表指针 */
struct sk_buff *next;
struct sk_buff *prev;
/* 关联的网络设备 */
struct net_device *dev;
/* 数据指针(关键)*/
unsigned char *head; /* 缓冲区起始地址 */
unsigned char *data; /* 有效数据起始地址 */
unsigned char *tail; /* 有效数据结束地址 */
unsigned char *end; /* 缓冲区结束地址 */
/* 数据长度 */
unsigned int len; /* 有效数据长度 */
unsigned int data_len; /* 分片数据长度 */
/* 协议信息 */
__be16 protocol; /* 上层协议类型 */
unsigned char pkt_type; /* 数据包类型 */
/* 时间戳 */
ktime_t tstamp;
/* ... 还有很多字段 ... */
};
/*
* sk_buff 数据区域示意:
*
* head end
* ↓ ↓
* ┌──────────┬──────────────────────────┬──────────┐
* │ headroom │ 有效数据 │ tailroom │
* └──────────┴──────────────────────────┴──────────┘
* ↑ ↑
* data tail
*
* 各层协议头部依次添加到 headroom(skb_push)
* 数据从 tailroom 追加(skb_put)
* 各层协议头部依次从 data 移除(skb_pull)
*/
sk_buff 的常用操作函数:
c
/* ── 分配和释放 ──────────────────────────────────────────── */
/* 分配 sk_buff(size:数据区大小,priority:GFP 标志)*/
struct sk_buff *skb = alloc_skb(size, GFP_ATOMIC);
struct sk_buff *skb = dev_alloc_skb(size); /* 网络驱动专用,自动预留 headroom */
/* 释放 sk_buff */
kfree_skb(skb); /* 减少引用计数,为0时释放 */
dev_kfree_skb(skb); /* 驱动中使用(非中断上下文)*/
dev_kfree_skb_irq(skb); /* 中断上下文中使用 */
/* ── 数据操作 ──────────────────────────────────────────── */
/* skb_put:在尾部添加数据(返回添加前的 tail 指针)*/
unsigned char *data = skb_put(skb, len);
memcpy(data, src, len);
/* skb_push:在头部添加数据(返回新的 data 指针)*/
/* 用于添加协议头部 */
struct ethhdr *eth = (struct ethhdr *)skb_push(skb, ETH_HLEN);
/* skb_pull:从头部移除数据(返回新的 data 指针)*/
/* 用于剥离协议头部 */
skb_pull(skb, ETH_HLEN);
/* skb_reserve:在头部预留空间(必须在添加数据前调用)*/
skb_reserve(skb, NET_IP_ALIGN); /* 预留 IP 对齐空间 */
/* ── 信息获取 ──────────────────────────────────────────── */
unsigned int len = skb->len; /* 数据长度 */
unsigned char *data = skb->data; /* 数据起始地址 */
struct ethhdr *eth = eth_hdr(skb); /* 以太网头部 */
struct iphdr *ip = ip_hdr(skb); /* IP 头部 */
struct tcphdr *tcp = tcp_hdr(skb); /* TCP 头部 */
14.1.4 net_device ------ 网络设备结构体
c
#include <linux/netdevice.h>
/*
* net_device:描述一个网络设备
* 每个网络接口(eth0、wlan0 等)对应一个 net_device
*/
struct net_device {
char name[IFNAMSIZ]; /* 接口名称(如 "eth0")*/
unsigned long state; /* 设备状态标志 */
unsigned int flags; /* 接口标志(IFF_UP 等)*/
unsigned int mtu; /* 最大传输单元(默认 1500)*/
unsigned char dev_addr[MAX_ADDR_LEN]; /* MAC 地址 */
const struct net_device_ops *netdev_ops; /* 设备操作函数集 */
const struct ethtool_ops *ethtool_ops; /* ethtool 操作 */
struct net_device_stats stats; /* 统计信息 */
void *ml_priv; /* 驱动私有数据(旧方式)*/
/* 新方式:使用 netdev_priv(dev) 获取私有数据 */
/* ... 还有很多字段 ... */
};
/* 分配 net_device(同时分配驱动私有数据)*/
struct net_device *dev = alloc_netdev(sizeof(struct my_priv),
"eth%d",
NET_NAME_UNKNOWN,
ether_setup);
/* 或使用以太网专用分配函数 */
struct net_device *dev = alloc_etherdev(sizeof(struct my_priv));
/* 获取驱动私有数据 */
struct my_priv *priv = netdev_priv(dev);
/* 释放 net_device */
free_netdev(dev);
14.2 网络设备驱动的注册与注销
14.2.1 注册网络设备
c
#include <linux/netdevice.h>
/*
* register_netdev:注册网络设备到内核
* 注册后,设备在 /sys/class/net/ 下可见
* 用户可以通过 ifconfig/ip 命令管理
*
* 返回:0(成功),负值(失败)
*/
int ret = register_netdev(dev);
if (ret) {
pr_err("注册网络设备失败:%d\n", ret);
free_netdev(dev);
return ret;
}
/* 注销网络设备 */
unregister_netdev(dev);
free_netdev(dev);
14.2.2 网络设备驱动的完整注册流程
网络设备驱动注册流程:
module_init()
↓
1. alloc_etherdev(sizeof(struct my_priv))
← 分配 net_device + 驱动私有数据
↓
2. 获取私有数据:priv = netdev_priv(dev)
↓
3. 初始化私有数据(硬件资源、锁等)
↓
4. 设置 net_device 参数:
dev->netdev_ops = &my_netdev_ops
dev->ethtool_ops = &my_ethtool_ops
memcpy(dev->dev_addr, mac_addr, ETH_ALEN)
↓
5. register_netdev(dev)
← 注册到内核,设备名称确定(eth0/eth1 等)
↓
6. 用户执行 ifconfig eth0 up
← 调用 ndo_open
↓
7. 设备就绪,可以收发数据包
module_exit()
↓
1. unregister_netdev(dev) ← 注销设备
2. free_netdev(dev) ← 释放内存
14.3 网络设备的初始化
14.3.1 net_device_ops ------ 网络设备操作函数集
c
/*
* net_device_ops:网络设备操作函数集
* 驱动通过实现这些函数来响应内核的操作请求
*/
static const struct net_device_ops my_netdev_ops = {
/* 打开设备(ifconfig eth0 up)*/
.ndo_open = my_net_open,
/* 关闭设备(ifconfig eth0 down)*/
.ndo_stop = my_net_stop,
/* 发送数据包 */
.ndo_start_xmit = my_net_xmit,
/* 获取统计信息 */
.ndo_get_stats = my_net_get_stats,
/* 设置 MAC 地址 */
.ndo_set_mac_address = eth_mac_addr,
/* 修改 MTU */
.ndo_change_mtu = eth_change_mtu,
/* 验证地址 */
.ndo_validate_addr = eth_validate_addr,
/* 设置多播地址列表 */
.ndo_set_rx_mode = my_set_rx_mode,
/* 处理 ioctl */
.ndo_do_ioctl = my_net_ioctl,
/* 发送超时处理 */
.ndo_tx_timeout = my_tx_timeout,
/* VLAN 相关(可选)*/
/* .ndo_vlan_rx_add_vid = my_vlan_rx_add_vid, */
};
14.3.2 以太网设备初始化
c
/*
* ether_setup:设置以太网设备的默认参数
* alloc_etherdev 内部会调用此函数
*/
void ether_setup(struct net_device *dev)
{
dev->header_ops = ð_header_ops;
dev->type = ARPHRD_ETHER; /* 以太网类型 */
dev->hard_header_len = ETH_HLEN; /* 以太网头部长度(14字节)*/
dev->mtu = ETH_DATA_LEN; /* MTU = 1500 */
dev->addr_len = ETH_ALEN; /* MAC 地址长度(6字节)*/
dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; /* 发送队列长度 */
dev->flags = IFF_BROADCAST | IFF_MULTICAST;
/* ... */
}
/* 驱动初始化示例 */
static int my_net_probe(struct platform_device *pdev)
{
struct net_device *dev;
struct my_priv *priv;
int ret;
/* 1. 分配 net_device(以太网设备)*/
dev = alloc_etherdev(sizeof(struct my_priv));
if (!dev) {
dev_err(&pdev->dev, "分配 net_device 失败\n");
return -ENOMEM;
}
/* 2. 获取驱动私有数据 */
priv = netdev_priv(dev);
priv->dev = dev;
priv->pdev = pdev;
/* 3. 初始化硬件资源 */
priv->base = devm_platform_ioremap_resource(pdev, 0);
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;
}
/* 4. 初始化锁和工作队列 */
spin_lock_init(&priv->lock);
INIT_WORK(&priv->tx_work, my_tx_work);
/* 5. 读取 MAC 地址(从硬件或设备树)*/
my_read_mac_address(priv, dev->dev_addr);
/* 6. 设置操作函数集 */
dev->netdev_ops = &my_netdev_ops;
dev->ethtool_ops = &my_ethtool_ops;
/* 7. 设置设备特性 */
dev->features |= NETIF_F_IP_CSUM; /* 硬件 IP 校验和 */
dev->features |= NETIF_F_SG; /* 分散/聚集 I/O */
/* 8. 注册网络设备 */
ret = register_netdev(dev);
if (ret) {
dev_err(&pdev->dev, "注册网络设备失败\n");
goto err_free;
}
platform_set_drvdata(pdev, dev);
dev_info(&pdev->dev, "网络设备 %s 注册成功\n", dev->name);
return 0;
err_free:
free_netdev(dev);
return ret;
}
14.4 网络设备的打开与释放
14.4.1 ndo_open ------ 打开网络设备
当用户执行 ifconfig eth0 up 或 ip link set eth0 up 时,内核调用驱动的 ndo_open 函数:
c
/*
* ndo_open:打开网络设备
* 在此函数中完成:
* 1. 申请中断
* 2. 初始化硬件(复位、配置寄存器)
* 3. 分配 DMA 缓冲区
* 4. 启动发送队列
* 5. 启动接收
*/
static int my_net_open(struct net_device *dev)
{
struct my_priv *priv = netdev_priv(dev);
int ret;
/* 1. 申请中断 */
ret = request_irq(priv->irq, my_net_irq_handler,
IRQF_SHARED, dev->name, dev);
if (ret) {
netdev_err(dev, "申请中断 %d 失败\n", priv->irq);
return ret;
}
/* 2. 初始化硬件 */
ret = my_hw_init(priv);
if (ret) {
netdev_err(dev, "硬件初始化失败\n");
goto err_irq;
}
/* 3. 分配接收缓冲区 */
ret = my_alloc_rx_buffers(priv);
if (ret) {
netdev_err(dev, "分配接收缓冲区失败\n");
goto err_hw;
}
/* 4. 启动硬件接收 */
my_hw_start_rx(priv);
/* 5. 启动发送队列(允许内核向驱动提交发送请求)*/
netif_start_queue(dev);
netdev_info(dev, "网络设备已打开\n");
return 0;
err_hw:
my_hw_deinit(priv);
err_irq:
free_irq(priv->irq, dev);
return ret;
}
14.4.2 ndo_stop ------ 关闭网络设备
当用户执行 ifconfig eth0 down 时,内核调用驱动的 ndo_stop 函数:
c
/*
* ndo_stop:关闭网络设备
* 与 ndo_open 相反,逆序释放资源
*/
static int my_net_stop(struct net_device *dev)
{
struct my_priv *priv = netdev_priv(dev);
/* 1. 停止发送队列(不再接受新的发送请求)*/
netif_stop_queue(dev);
/* 2. 停止硬件接收 */
my_hw_stop_rx(priv);
/* 3. 等待所有正在进行的传输完成 */
my_hw_wait_tx_done(priv);
/* 4. 停止硬件 */
my_hw_deinit(priv);
/* 5. 释放接收缓冲区 */
my_free_rx_buffers(priv);
/* 6. 释放中断 */
free_irq(priv->irq, dev);
netdev_info(dev, "网络设备已关闭\n");
return 0;
}
14.5 数据包的发送
14.5.1 ndo_start_xmit ------ 发送数据包
ndo_start_xmit 是网络驱动最核心的函数,当内核需要发送数据包时调用:
c
/*
* ndo_start_xmit:发送数据包
*
* skb:要发送的数据包(sk_buff)
* dev:网络设备
*
* 返回值:
* NETDEV_TX_OK:数据包已接受(驱动负责发送)
* NETDEV_TX_BUSY:驱动忙,无法接受(内核会重试)
*/
static netdev_tx_t my_net_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct my_priv *priv = netdev_priv(dev);
unsigned long flags;
int ret = NETDEV_TX_OK;
/* 1. 检查数据包长度 */
if (skb->len > ETH_FRAME_LEN) {
netdev_err(dev, "数据包过大:%d 字节\n", skb->len);
dev_kfree_skb(skb);
priv->stats.tx_errors++;
return NETDEV_TX_OK;
}
spin_lock_irqsave(&priv->lock, flags);
/* 2. 检查发送缓冲区是否可用 */
if (my_hw_tx_busy(priv)) {
/* 发送缓冲区满,停止队列 */
netif_stop_queue(dev);
spin_unlock_irqrestore(&priv->lock, flags);
netdev_warn(dev, "发送缓冲区满,停止队列\n");
return NETDEV_TX_BUSY;
}
/* 3. 将数据包写入硬件发送缓冲区 */
my_hw_write_tx_data(priv, skb->data, skb->len);
/* 4. 触发硬件发送 */
my_hw_trigger_tx(priv);
/* 5. 更新统计信息 */
priv->stats.tx_packets++;
priv->stats.tx_bytes += skb->len;
/* 6. 记录发送时间(用于超时检测)*/
dev->trans_start = jiffies;
spin_unlock_irqrestore(&priv->lock, flags);
/* 7. 释放 sk_buff(驱动已接管数据,不再需要 skb)*/
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
14.5.2 发送队列控制
c
/* 发送队列控制函数 */
/* 停止发送队列(驱动忙时调用,内核不再提交新的发送请求)*/
netif_stop_queue(dev);
/* 启动发送队列(驱动就绪时调用)*/
netif_start_queue(dev);
/* 唤醒发送队列(从停止状态恢复,通常在发送完成中断中调用)*/
netif_wake_queue(dev);
/* 检查发送队列是否停止 */
if (netif_queue_stopped(dev)) {
/* 队列已停止 */
}
/* 多队列设备 */
netif_tx_stop_all_queues(dev); /* 停止所有发送队列 */
netif_tx_wake_all_queues(dev); /* 唤醒所有发送队列 */
14.5.3 发送超时处理
c
/*
* ndo_tx_timeout:发送超时处理
* 当发送队列停止超过 watchdog_timeo 时间后,内核调用此函数
* 通常用于检测硬件死锁并复位
*/
static void my_tx_timeout(struct net_device *dev)
{
struct my_priv *priv = netdev_priv(dev);
netdev_warn(dev, "发送超时!复位硬件...\n");
/* 更新统计 */
priv->stats.tx_errors++;
/* 复位硬件 */
my_hw_reset(priv);
/* 重新初始化 */
my_hw_init(priv);
/* 重新启动发送队列 */
netif_wake_queue(dev);
}
/* 设置发送超时时间(在 probe 中设置)*/
dev->watchdog_timeo = 5 * HZ; /* 5 秒超时 */
14.6 数据包的接收
14.6.1 中断驱动的数据包接收
网络数据包的接收通常由硬件中断触发:
c
/*
* 网络设备中断处理函数
* 当网卡收到数据包时,产生中断
*/
static irqreturn_t my_net_irq_handler(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
struct my_priv *priv = netdev_priv(dev);
u32 status;
/* 读取中断状态寄存器 */
status = readl(priv->base + INT_STATUS_REG);
/* 清除中断 */
writel(status, priv->base + INT_STATUS_REG);
/* 处理接收中断 */
if (status & RX_INT_FLAG) {
my_net_rx(dev);
}
/* 处理发送完成中断 */
if (status & TX_DONE_FLAG) {
my_net_tx_done(dev);
}
return IRQ_HANDLED;
}
/*
* my_net_rx:接收数据包
* 从硬件接收缓冲区读取数据,封装成 sk_buff,提交给协议栈
*/
static void my_net_rx(struct net_device *dev)
{
struct my_priv *priv = netdev_priv(dev);
struct sk_buff *skb;
unsigned int pkt_len;
unsigned char *data;
/* 循环处理所有待接收的数据包 */
while (my_hw_rx_ready(priv)) {
/* 1. 获取数据包长度 */
pkt_len = my_hw_get_rx_len(priv);
if (pkt_len < ETH_ZLEN || pkt_len > ETH_FRAME_LEN) {
netdev_err(dev, "无效的数据包长度:%u\n", pkt_len);
priv->stats.rx_errors++;
my_hw_skip_rx_packet(priv);
continue;
}
/* 2. 分配 sk_buff */
skb = dev_alloc_skb(pkt_len + NET_IP_ALIGN);
if (!skb) {
netdev_err(dev, "分配 sk_buff 失败,丢弃数据包\n");
priv->stats.rx_dropped++;
my_hw_skip_rx_packet(priv);
continue;
}
/* 3. 预留 IP 对齐空间(提高 IP 头部访问效率)*/
skb_reserve(skb, NET_IP_ALIGN);
/* 4. 从硬件读取数据到 sk_buff */
data = skb_put(skb, pkt_len);
my_hw_read_rx_data(priv, data, pkt_len);
/* 5. 设置 sk_buff 的协议类型 */
skb->protocol = eth_type_trans(skb, dev);
/* 6. 设置接收时间戳 */
skb->ip_summed = CHECKSUM_NONE; /* 软件校验和 */
/* 7. 更新统计信息 */
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt_len;
/* 8. 将数据包提交给内核网络协议栈 */
netif_rx(skb);
/* 或使用 netif_receive_skb(在软中断上下文中)*/
/* netif_receive_skb(skb); */
}
}
14.6.2 NAPI ------ 新型 API(高性能接收)
NAPI(New API)是 Linux 网络驱动的高性能接收机制,结合了中断和轮询的优点:
NAPI 工作原理:
传统中断方式(低负载时效率高):
每个数据包 → 一次中断 → 处理一个包
高负载时:大量中断 → CPU 忙于处理中断 → 性能下降
NAPI 方式(高负载时效率高):
第一个数据包 → 中断 → 禁止中断 → 轮询处理多个包
处理完所有包 → 重新使能中断
优点:
✓ 高负载时减少中断次数,提高吞吐量
✓ 低负载时仍使用中断,保证延迟
✓ 支持流量控制(budget 机制)
c
#include <linux/netdevice.h>
/* NAPI 结构体(通常内嵌在驱动私有数据中)*/
struct my_priv {
struct net_device *dev;
struct napi_struct napi; /* NAPI 结构体 */
/* ... */
};
/* NAPI 轮询函数 */
static int my_napi_poll(struct napi_struct *napi, int budget)
{
struct my_priv *priv = container_of(napi, struct my_priv, napi);
struct net_device *dev = priv->dev;
int work_done = 0;
/* 处理最多 budget 个数据包 */
while (work_done < budget && my_hw_rx_ready(priv)) {
struct sk_buff *skb;
unsigned int pkt_len = my_hw_get_rx_len(priv);
skb = dev_alloc_skb(pkt_len + NET_IP_ALIGN);
if (!skb) {
priv->stats.rx_dropped++;
my_hw_skip_rx_packet(priv);
continue;
}
skb_reserve(skb, NET_IP_ALIGN);
skb_put(skb, pkt_len);
my_hw_read_rx_data(priv, skb->data, pkt_len);
skb->protocol = eth_type_trans(skb, dev);
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt_len;
/* NAPI 使用 netif_receive_skb 而非 netif_rx */
netif_receive_skb(skb);
work_done++;
}
/* 如果处理的包数少于 budget,说明所有包都处理完了 */
if (work_done < budget) {
/* 完成 NAPI 轮询,重新使能中断 */
napi_complete(napi);
my_hw_enable_rx_irq(priv);
}
return work_done;
}
/* 中断处理函数(NAPI 方式)*/
static irqreturn_t my_napi_irq_handler(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
struct my_priv *priv = netdev_priv(dev);
/* 禁止接收中断(防止中断风暴)*/
my_hw_disable_rx_irq(priv);
/* 调度 NAPI 轮询(在软中断上下文中执行 poll 函数)*/
napi_schedule(&priv->napi);
return IRQ_HANDLED;
}
/* 在 probe 函数中初始化 NAPI */
static int my_net_probe(struct platform_device *pdev)
{
struct net_device *dev;
struct my_priv *priv;
dev = alloc_etherdev(sizeof(struct my_priv));
priv = netdev_priv(dev);
/* 初始化 NAPI(64 = 每次轮询最多处理的包数)*/
netif_napi_add(dev, &priv->napi, my_napi_poll, 64);
/* ... 其他初始化 ... */
register_netdev(dev);
return 0;
}
/* 在 ndo_open 中使能 NAPI */
static int my_net_open(struct net_device *dev)
{
struct my_priv *priv = netdev_priv(dev);
/* 使能 NAPI */
napi_enable(&priv->napi);
/* ... 其他初始化 ... */
netif_start_queue(dev);
return 0;
}
/* 在 ndo_stop 中禁用 NAPI */
static int my_net_stop(struct net_device *dev)
{
struct my_priv *priv = netdev_priv(dev);
netif_stop_queue(dev);
/* 禁用 NAPI */
napi_disable(&priv->napi);
/* ... 其他清理 ... */
return 0;
}
14.7 网络连接状态
14.7.1 链路状态检测
网络驱动需要向内核报告网络连接状态(Link Up/Down):
c
/* 报告链路状态 */
/* 链路连接(如网线插入)*/
netif_carrier_on(dev);
/* 链路断开(如网线拔出)*/
netif_carrier_off(dev);
/* 检查链路状态 */
if (netif_carrier_ok(dev)) {
pr_info("链路已连接\n");
} else {
pr_info("链路已断开\n");
}
/* 典型应用:在中断处理函数中检测链路状态变化 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
struct my_priv *priv = netdev_priv(dev);
u32 status = readl(priv->base + INT_STATUS_REG);
/* 链路状态变化中断 */
if (status & LINK_CHANGE_INT) {
u32 link_status = readl(priv->base + PHY_STATUS_REG);
if (link_status & LINK_UP) {
netdev_info(dev, "链路已连接,速率:%s\n",
(link_status & SPEED_100M) ? "100Mbps" : "10Mbps");
netif_carrier_on(dev);
netif_wake_queue(dev);
} else {
netdev_info(dev, "链路已断开\n");
netif_carrier_off(dev);
netif_stop_queue(dev);
}
}
return IRQ_HANDLED;
}
14.7.2 PHY 管理
现代以太网驱动通常通过 PHY 子系统管理物理层芯片:
c
#include <linux/phy.h>
/* 连接 PHY 设备 */
static int my_net_open(struct net_device *dev)
{
struct my_priv *priv = netdev_priv(dev);
struct phy_device *phydev;
/* 连接 PHY(通过 MDIO 总线)*/
phydev = of_phy_connect(dev,
priv->phy_node,
my_phy_adjust_link, /* 链路状态变化回调 */
0,
PHY_INTERFACE_MODE_RMII);
if (!phydev) {
netdev_err(dev, "连接 PHY 失败\n");
return -ENODEV;
}
priv->phydev = phydev;
/* 启动 PHY 自动协商 */
phy_start(phydev);
netif_start_queue(dev);
return 0;
}
/* PHY 链路状态变化回调 */
static void my_phy_adjust_link(struct net_device *dev)
{
struct my_priv *priv = netdev_priv(dev);
struct phy_device *phydev = priv->phydev;
if (phydev->link) {
/* 链路连接,配置 MAC 速率和双工模式 */
my_hw_set_speed(priv, phydev->speed);
my_hw_set_duplex(priv, phydev->duplex);
netif_carrier_on(dev);
netdev_info(dev, "链路连接:%dMbps %s\n",
phydev->speed,
phydev->duplex == DUPLEX_FULL ? "全双工" : "半双工");
} else {
netif_carrier_off(dev);
netdev_info(dev, "链路断开\n");
}
}
14.8 参数设置和统计数据
14.8.1 网络设备统计信息
c
/*
* net_device_stats:网络设备统计信息
* 通过 ndo_get_stats 返回给用户空间
* 用户可以通过 ifconfig 或 ip -s link 查看
*/
struct net_device_stats {
unsigned long rx_packets; /* 接收的数据包总数 */
unsigned long tx_packets; /* 发送的数据包总数 */
unsigned long rx_bytes; /* 接收的字节总数 */
unsigned long tx_bytes; /* 发送的字节总数 */
unsigned long rx_errors; /* 接收错误总数 */
unsigned long tx_errors; /* 发送错误总数 */
unsigned long rx_dropped; /* 接收丢弃的数据包数 */
unsigned long tx_dropped; /* 发送丢弃的数据包数 */
unsigned long multicast; /* 接收的多播数据包数 */
unsigned long collisions; /* 碰撞次数 */
unsigned long rx_length_errors; /* 长度错误 */
unsigned long rx_over_errors; /* 接收溢出错误 */
unsigned long rx_crc_errors; /* CRC 错误 */
unsigned long rx_frame_errors; /* 帧错误 */
unsigned long rx_fifo_errors; /* FIFO 溢出错误 */
unsigned long rx_missed_errors; /* 丢失的数据包 */
unsigned long tx_aborted_errors; /* 发送中止错误 */
unsigned long tx_carrier_errors; /* 载波错误 */
unsigned long tx_fifo_errors; /* FIFO 错误 */
unsigned long tx_heartbeat_errors; /* 心跳错误 */
unsigned long tx_window_errors; /* 窗口错误 */
};
/* 实现 ndo_get_stats */
static struct net_device_stats *my_net_get_stats(struct net_device *dev)
{
struct my_priv *priv = netdev_priv(dev);
/* 从硬件读取最新统计数据(可选)*/
my_hw_update_stats(priv);
return &priv->stats;
}
/* 查看统计信息 */
/* ifconfig eth0 */
/* ip -s link show eth0 */
/* cat /proc/net/dev */
14.8.2 ethtool 接口
ethtool 是 Linux 网络设备的标准配置工具,驱动通过实现 ethtool_ops 支持 ethtool:
c
#include <linux/ethtool.h>
/* 获取驱动信息 */
static void my_get_drvinfo(struct net_device *dev,
struct ethtool_drvinfo *info)
{
strlcpy(info->driver, "my_net_driver", sizeof(info->driver));
strlcpy(info->version, "1.0.0", sizeof(info->version));
strlcpy(info->bus_info, "platform", sizeof(info->bus_info));
}
/* 获取链路设置 */
static int my_get_link_ksettings(struct net_device *dev,
struct ethtool_link_ksettings *cmd)
{
struct my_priv *priv = netdev_priv(dev);
struct phy_device *phydev = priv->phydev;
if (!phydev)
return -ENODEV;
return phy_ethtool_ksettings_get(phydev, cmd);
}
/* 设置链路参数 */
static int my_set_link_ksettings(struct net_device *dev,
const struct ethtool_link_ksettings *cmd)
{
struct my_priv *priv = netdev_priv(dev);
struct phy_device *phydev = priv->phydev;
if (!phydev)
return -ENODEV;
return phy_ethtool_ksettings_set(phydev, cmd);
}
/* 获取 EEPROM 数据 */
static int my_get_eeprom(struct net_device *dev,
struct ethtool_eeprom *eeprom,
u8 *data)
{
/* 读取网卡 EEPROM(如 MAC 地址存储区)*/
return my_hw_read_eeprom(netdev_priv(dev), eeprom->offset,
eeprom->len, data);
}
static const struct ethtool_ops my_ethtool_ops = {
.get_drvinfo = my_get_drvinfo,
.get_link = ethtool_op_get_link,
.get_link_ksettings = my_get_link_ksettings,
.set_link_ksettings = my_set_link_ksettings,
.get_eeprom = my_get_eeprom,
};
/* 用户空间使用 ethtool */
/* ethtool eth0 ← 查看基本信息 */
/* ethtool -i eth0 ← 查看驱动信息 */
/* ethtool -s eth0 speed 100 duplex full ← 设置速率 */
/* ethtool -e eth0 ← 读取 EEPROM */
14.9 DM9000 网卡驱动实例
14.9.1 DM9000 简介
DM9000 是大信(DAVICOM)公司生产的一款单芯片以太网控制器,广泛用于嵌入式系统(如 mini2440、JZ2440 等开发板):
DM9000 特性:
- 10/100Mbps 以太网控制器
- 内置 16KB SRAM(发送缓冲区 + 接收缓冲区)
- 支持 8/16/32 位数据总线接口
- 通过 CPU 地址总线直接访问(MMIO 方式)
- 内置 PHY(物理层芯片)
- 支持全双工/半双工自动协商
DM9000 与 CPU 的连接:
CPU 地址总线 → DM9000 CMD 引脚(区分地址/数据访问)
CPU 数据总线 → DM9000 数据引脚
CPU 片选信号 → DM9000 CS 引脚
DM9000 INT → CPU 中断引脚
访问方式:
写地址:向 CMD=0 的地址写入寄存器地址
读/写数据:向 CMD=1 的地址读/写数据
14.9.2 DM9000 驱动的关键实现
c
/*
* dm9000.c ------ DM9000 网卡驱动关键部分
* 参考 Linux 内核 drivers/net/ethernet/davicom/dm9000.c
*/
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/interrupt.h>
/* DM9000 寄存器定义 */
#define DM9000_NCR 0x00 /* 网络控制寄存器 */
#define DM9000_NSR 0x01 /* 网络状态寄存器 */
#define DM9000_TCR 0x02 /* 发送控制寄存器 */
#define DM9000_TSR1 0x03 /* 发送状态寄存器1 */
#define DM9000_TSR2 0x04 /* 发送状态寄存器2 */
#define DM9000_RCR 0x05 /* 接收控制寄存器 */
#define DM9000_RSR 0x06 /* 接收状态寄存器 */
#define DM9000_ROCR 0x07 /* 接收溢出计数寄存器 */
#define DM9000_BPTR 0x08 /* 背压阈值寄存器 */
#define DM9000_FCTR 0x09 /* 流控阈值寄存器 */
#define DM9000_FCR 0x0A /* 流控寄存器 */
#define DM9000_EPCR 0x0B /* EEPROM/PHY 控制寄存器 */
#define DM9000_EPAR 0x0C /* EEPROM/PHY 地址寄存器 */
#define DM9000_EPDRL 0x0D /* EEPROM/PHY 数据寄存器低字节 */
#define DM9000_EPDRH 0x0E /* EEPROM/PHY 数据寄存器高字节 */
#define DM9000_WCR 0x0F /* 唤醒控制寄存器 */
#define DM9000_PAR 0x10 /* 物理地址寄存器(MAC 地址,6字节)*/
#define DM9000_MAR 0x16 /* 多播地址寄存器(8字节)*/
#define DM9000_GPCR 0x1E /* 通用目的控制寄存器 */
#define DM9000_GPR 0x1F /* 通用目的寄存器 */
#define DM9000_TRPAL 0x22 /* 发送读指针地址低字节 */
#define DM9000_TRPAH 0x23 /* 发送读指针地址高字节 */
#define DM9000_RWPAL 0x24 /* 接收写指针地址低字节 */
#define DM9000_RWPAH 0x25 /* 接收写指针地址高字节 */
#define DM9000_VID_L 0x28 /* 厂商 ID 低字节 */
#define DM9000_VID_H 0x29 /* 厂商 ID 高字节 */
#define DM9000_PID_L 0x2A /* 产品 ID 低字节 */
#define DM9000_PID_H 0x2B /* 产品 ID 高字节 */
#define DM9000_CHIPR 0x2C /* 芯片版本寄存器 */
#define DM9000_MRCMDX 0xF0 /* 内存数据预读命令(不增加地址)*/
#define DM9000_MRCMD 0xF2 /* 内存数据读命令(增加地址)*/
#define DM9000_MRRL 0xF4 /* 内存数据读地址低字节 */
#define DM9000_MRRH 0xF5 /* 内存数据读地址高字节 */
#define DM9000_MWCMDX 0xF6 /* 内存数据写命令(不增加地址)*/
#define DM9000_MWCMD 0xF8 /* 内存数据写命令(增加地址)*/
#define DM9000_MWRL 0xFA /* 内存数据写地址低字节 */
#define DM9000_MWRH 0xFB /* 内存数据写地址高字节 */
#define DM9000_TXPLL 0xFC /* 发送数据包长度低字节 */
#define DM9000_TXPLH 0xFD /* 发送数据包长度高字节 */
#define DM9000_ISR 0xFE /* 中断状态寄存器 */
#define DM9000_IMR 0xFF /* 中断屏蔽寄存器 */
/* 中断标志 */
#define DM9000_IMR_PAR 0x80 /* 使能所有中断 */
#define DM9000_IMR_ROOM 0x40 /* 接收溢出中断 */
#define DM9000_IMR_ROM 0x20 /* 接收溢出计数器溢出中断 */
#define DM9000_IMR_PTM 0x10 /* 发送完成中断 */
#define DM9000_IMR_PRM 0x08 /* 接收完成中断 */
/* DM9000 驱动私有数据 */
struct dm9000_priv {
struct net_device *dev;
void __iomem *io_addr; /* 地址寄存器(CMD=0)*/
void __iomem *io_data; /* 数据寄存器(CMD=1)*/
int irq;
spinlock_t lock;
struct net_device_stats stats;
u8 io_mode; /* 8/16/32 位模式 */
};
/* ── 寄存器访问函数 ──────────────────────────────────────── */
/* 写寄存器地址 */
static void dm9000_outb(u8 reg, void __iomem *addr)
{
writeb(reg, addr);
}
/* 读寄存器数据 */
static u8 dm9000_inb(void __iomem *addr)
{
return readb(addr);
}
/* 读 DM9000 寄存器 */
static u8 dm9000_read_reg(struct dm9000_priv *priv, u8 reg)
{
writeb(reg, priv->io_addr); /* 写地址 */
return readb(priv->io_data); /* 读数据 */
}
/* 写 DM9000 寄存器 */
static void dm9000_write_reg(struct dm9000_priv *priv, u8 reg, u8 val)
{
writeb(reg, priv->io_addr); /* 写地址 */
writeb(val, priv->io_data); /* 写数据 */
}
/* ── 硬件初始化 ──────────────────────────────────────────── */
static int dm9000_hw_init(struct dm9000_priv *priv)
{
u32 id;
/* 软件复位 */
dm9000_write_reg(priv, DM9000_NCR, 0x03);
udelay(100);
dm9000_write_reg(priv, DM9000_NCR, 0x00);
/* 验证芯片 ID */
id = dm9000_read_reg(priv, DM9000_VID_L) |
(dm9000_read_reg(priv, DM9000_VID_H) << 8) |
(dm9000_read_reg(priv, DM9000_PID_L) << 16) |
(dm9000_read_reg(priv, DM9000_PID_H) << 24);
if (id != 0x90000A46) { /* DM9000 的 VID=0x0A46, PID=0x9000 */
pr_err("dm9000: 无效的芯片 ID:0x%08x\n", id);
return -ENODEV;
}
pr_info("dm9000: 检测到 DM9000 芯片\n");
/* 配置接收控制寄存器 */
dm9000_write_reg(priv, DM9000_RCR,
0x31); /* 使能接收,过滤错误帧,接收广播 */
/* 配置中断屏蔽寄存器 */
dm9000_write_reg(priv, DM9000_IMR,
DM9000_IMR_PAR | DM9000_IMR_PTM | DM9000_IMR_PRM);
return 0;
}
/* ── 发送数据包 ──────────────────────────────────────────── */
static netdev_tx_t dm9000_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct dm9000_priv *priv = netdev_priv(dev);
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
/* 停止发送队列(等待发送完成中断后再启动)*/
netif_stop_queue(dev);
/* 写发送数据包长度 */
dm9000_write_reg(priv, DM9000_TXPLL, skb->len & 0xFF);
dm9000_write_reg(priv, DM9000_TXPLH, (skb->len >> 8) & 0xFF);
/* 写数据到 DM9000 内部 SRAM */
writeb(DM9000_MWCMD, priv->io_addr); /* 选择内存写命令 */
/* 根据总线宽度选择写入方式 */
if (priv->io_mode == 2) {
/* 16 位模式 */
u16 *data = (u16 *)skb->data;
int i;
for (i = 0; i < (skb->len + 1) / 2; i++)
writew(data[i], priv->io_data);
} else {
/* 8 位模式 */
int i;
for (i = 0; i < skb->len; i++)
writeb(skb->data[i], priv->io_data);
}
/* 触发发送 */
dm9000_write_reg(priv, DM9000_TCR, 0x01);
/* 更新统计 */
priv->stats.tx_packets++;
priv->stats.tx_bytes += skb->len;
dev->trans_start = jiffies;
spin_unlock_irqrestore(&priv->lock, flags);
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
/* ── 接收数据包 ──────────────────────────────────────────── */
static void dm9000_rx(struct net_device *dev)
{
struct dm9000_priv *priv = netdev_priv(dev);
struct sk_buff *skb;
u8 rxbyte, status;
u16 rx_len;
/* 循环接收所有待处理的数据包 */
do {
/* 预读接收状态(不增加地址)*/
writeb(DM9000_MRCMDX, priv->io_addr);
rxbyte = readb(priv->io_data);
/* 检查是否有数据包 */
if (rxbyte == 0)
break; /* 没有数据包 */
if (rxbyte != 0x01) {
/* 接收状态异常,复位 */
dm9000_write_reg(priv, DM9000_RCR, 0x00);
dm9000_write_reg(priv, DM9000_ISR, 0x80);
dm9000_write_reg(priv, DM9000_RCR, 0x31);
break;
}
/* 读接收状态和长度 */
writeb(DM9000_MRCMD, priv->io_addr);
status = readb(priv->io_data);
rx_len = readb(priv->io_data) | (readb(priv->io_data) << 8);
/* 检查接收状态 */
if (status & 0xBF) {
priv->stats.rx_errors++;
/* 跳过此数据包 */
continue;
}
/* 分配 sk_buff */
skb = dev_alloc_skb(rx_len + 4);
if (!skb) {
priv->stats.rx_dropped++;
continue;
}
skb_reserve(skb, 2); /* 对齐 */
/* 从 DM9000 SRAM 读取数据 */
if (priv->io_mode == 2) {
/* 16 位模式 */
u16 *data = (u16 *)skb_put(skb, rx_len);
int i;
for (i = 0; i < (rx_len + 1) / 2; i++)
data[i] = readw(priv->io_data);
} else {
/* 8 位模式 */
u8 *data = skb_put(skb, rx_len);
int i;
for (i = 0; i < rx_len; i++)
data[i] = readb(priv->io_data);
}
/* 设置协议类型 */
skb->protocol = eth_type_trans(skb, dev);
/* 更新统计 */
priv->stats.rx_packets++;
priv->stats.rx_bytes += rx_len;
/* 提交给协议栈 */
netif_rx(skb);
} while (1);
}
/* ── 中断处理函数 ────────────────────────────────────────── */
static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
struct dm9000_priv *priv = netdev_priv(dev);
u8 int_status;
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
/* 禁止 DM9000 中断 */
dm9000_write_reg(priv, DM9000_IMR, DM9000_IMR_PAR);
/* 读取中断状态 */
int_status = dm9000_read_reg(priv, DM9000_ISR);
/* 清除中断 */
dm9000_write_reg(priv, DM9000_ISR, int_status);
/* 处理接收中断 */
if (int_status & DM9000_IMR_PRM) {
dm9000_rx(dev);
}
/* 处理发送完成中断 */
if (int_status & DM9000_IMR_PTM) {
u8 tx_status = dm9000_read_reg(priv, DM9000_TSR1);
if (tx_status & 0xC8) {
priv->stats.tx_errors++;
}
/* 重新启动发送队列 */
netif_wake_queue(dev);
}
/* 重新使能 DM9000 中断 */
dm9000_write_reg(priv, DM9000_IMR,
DM9000_IMR_PAR | DM9000_IMR_PTM | DM9000_IMR_PRM);
spin_unlock_irqrestore(&priv->lock, flags);
return IRQ_HANDLED;
}
/* ── 网络设备操作函数集 ──────────────────────────────────── */
static int dm9000_open(struct net_device *dev)
{
struct dm9000_priv *priv = netdev_priv(dev);
int ret;
ret = request_irq(priv->irq, dm9000_interrupt,
IRQF_TRIGGER_RISING, dev->name, dev);
if (ret)
return ret;
dm9000_hw_init(priv);
netif_start_queue(dev);
return 0;
}
static int dm9000_stop(struct net_device *dev)
{
struct dm9000_priv *priv = netdev_priv(dev);
netif_stop_queue(dev);
/* 禁止所有中断 */
dm9000_write_reg(priv, DM9000_IMR, DM9000_IMR_PAR);
free_irq(priv->irq, dev);
return 0;
}
static struct net_device_stats *dm9000_get_stats(struct net_device *dev)
{
struct dm9000_priv *priv = netdev_priv(dev);
return &priv->stats;
}
static const struct net_device_ops dm9000_netdev_ops = {
.ndo_open = dm9000_open,
.ndo_stop = dm9000_stop,
.ndo_start_xmit = dm9000_start_xmit,
.ndo_get_stats = dm9000_get_stats,
.ndo_set_mac_address = eth_mac_addr,
.ndo_validate_addr = eth_validate_addr,
.ndo_tx_timeout = dm9000_tx_timeout,
};
/* ── probe 函数 ──────────────────────────────────────────── */
static int dm9000_probe(struct platform_device *pdev)
{
struct net_device *dev;
struct dm9000_priv *priv;
struct resource *res_addr, *res_data;
int ret;
/* 分配 net_device */
dev = alloc_etherdev(sizeof(struct dm9000_priv));
if (!dev)
return -ENOMEM;
priv = netdev_priv(dev);
priv->dev = dev;
/* 获取地址寄存器资源 */
res_addr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->io_addr = devm_ioremap_resource(&pdev->dev, res_addr);
if (IS_ERR(priv->io_addr)) {
ret = PTR_ERR(priv->io_addr);
goto err_free;
}
/* 获取数据寄存器资源 */
res_data = platform_get_resource(pdev, IORESOURCE_MEM, 1);
priv->io_data = devm_ioremap_resource(&pdev->dev, res_data);
if (IS_ERR(priv->io_data)) {
ret = PTR_ERR(priv->io_data);
goto err_free;
}
/* 获取中断号 */
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0) {
ret = priv->irq;
goto err_free;
}
spin_lock_init(&priv->lock);
/* 读取 MAC 地址(从 EEPROM 或设备树)*/
eth_hw_addr_random(dev); /* 随机 MAC 地址(测试用)*/
/* 设置操作函数集 */
dev->netdev_ops = &dm9000_netdev_ops;
dev->watchdog_timeo = 5 * HZ;
/* 注册网络设备 */
ret = register_netdev(dev);
if (ret) {
dev_err(&pdev->dev, "注册网络设备失败\n");
goto err_free;
}
platform_set_drvdata(pdev, dev);
dev_info(&pdev->dev, "DM9000 网卡驱动加载成功,设备名:%s\n", dev->name);
return 0;
err_free:
free_netdev(dev);
return ret;
}
static int dm9000_remove(struct platform_device *pdev)
{
struct net_device *dev = platform_get_drvdata(pdev);
unregister_netdev(dev);
free_netdev(dev);
return 0;
}
/* 设备树匹配表 */
static const struct of_device_id dm9000_of_match[] = {
{ .compatible = "davicom,dm9000" },
{}
};
MODULE_DEVICE_TABLE(of, dm9000_of_match);
static struct platform_driver dm9000_driver = {
.probe = dm9000_probe,
.remove = dm9000_remove,
.driver = {
.name = "dm9000",
.of_match_table = dm9000_of_match,
},
};
module_platform_driver(dm9000_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DM9000 以太网控制器驱动");
14.9.3 DM9000 设备树配置
dts
/* 设备树中的 DM9000 配置 */
/ {
ethernet@20000000 {
compatible = "davicom,dm9000";
/* 地址寄存器(CMD=0)和数据寄存器(CMD=1)*/
reg = <0x20000000 0x2>, /* 地址寄存器 */
<0x20000004 0x2>; /* 数据寄存器(偏移4字节)*/
/* 中断配置 */
interrupt-parent = <&gpio>;
interrupts = <7 IRQ_TYPE_EDGE_RISING>;
/* 可选:MAC 地址 */
local-mac-address = [00 0A 2D 00 00 01];
/* 可选:总线宽度 */
davicom,no-eeprom;
};
};
14.9.4 DM9000 驱动测试
bash
# 加载驱动
sudo insmod dm9000.ko
# 查看网络设备
ip link show
# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 ...
# 2: eth0: <BROADCAST,MULTICAST> mtu 1500 ...
# link/ether 00:0a:2d:00:00:01 brd ff:ff:ff:ff:ff:ff
# 配置 IP 地址并启动
sudo ip addr add 192.168.1.100/24 dev eth0
sudo ip link set eth0 up
# 查看链路状态
ip link show eth0
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
# link/ether 00:0a:2d:00:00:01 brd ff:ff:ff:ff:ff:ff
# 测试连通性
ping 192.168.1.1
# 查看统计信息
ip -s link show eth0
# 2: eth0: ...
# RX: bytes packets errors dropped missed mcast
# 12345 100 0 0 0 5
# TX: bytes packets errors dropped carrier collsns
# 6789 50 0 0 0 0
# 使用 ethtool 查看驱动信息
ethtool -i eth0
# driver: dm9000
# version: 1.0.0
# bus-info: platform
# 查看内核日志
dmesg | grep dm9000
# dm9000: 检测到 DM9000 芯片
# dm9000: DM9000 网卡驱动加载成功,设备名:eth0
本章小结
| 章节 | 核心知识点 | 关键 API |
|---|---|---|
| 14.1 网络设备驱动结构 | 网络设备的特殊性;7层网络协议栈;sk_buff结构体与操作(alloc/put/push/pull/reserve);net_device结构体 | alloc_skb()、skb_put()、skb_push()、skb_pull() |
| 14.2 注册与注销 | register_netdev/unregister_netdev;完整注册流程图(7步) |
register_netdev()、unregister_netdev() |
| 14.3 网络设备初始化 | net_device_ops完整函数集;alloc_etherdev;netdev_priv;probe函数完整实现 |
alloc_etherdev()、netdev_priv()、ether_setup() |
| 14.4 打开与释放 | ndo_open(申请中断/初始化硬件/启动队列);ndo_stop(逆序释放);netif_start_queue |
netif_start_queue()、netif_stop_queue() |
| 14.5 数据包发送 | ndo_start_xmit完整实现;发送队列控制;发送超时处理(ndo_tx_timeout) | netif_stop_queue()、netif_wake_queue()、dev_kfree_skb() |
| 14.6 数据包接收 | 中断驱动接收;dev_alloc_skb+skb_put+eth_type_trans+netif_rx;NAPI高性能接收机制 |
dev_alloc_skb()、netif_rx()、napi_schedule()、napi_complete() |
| 14.7 网络连接状态 | netif_carrier_on/off;PHY子系统;of_phy_connect;链路状态变化回调 |
netif_carrier_on()、netif_carrier_off()、phy_start() |
| 14.8 参数设置和统计 | net_device_stats完整字段;ndo_get_stats;ethtool_ops(drvinfo/link_ksettings/eeprom) | ethtool_ops、ip -s link show |
| 14.9 DM9000驱动实例 | DM9000寄存器定义;寄存器读写函数;发送/接收/中断处理完整实现;设备树配置;测试流程 | 完整驱动代码 |
网络设备驱动开发要点
1. sk_buff 的正确使用
接收:dev_alloc_skb → skb_reserve → skb_put → eth_type_trans → netif_rx
发送:使用 skb->data 和 skb->len → dev_kfree_skb
2. 发送队列管理
缓冲区满时:netif_stop_queue
发送完成后:netif_wake_queue
设备关闭时:netif_stop_queue
3. 中断处理
顶半部:读状态、清中断、调度底半部
底半部:处理接收数据包(NAPI poll)
4. 统计信息维护
每次成功接收:rx_packets++, rx_bytes += len
每次成功发送:tx_packets++, tx_bytes += len
错误时:rx_errors++ 或 tx_errors++
5. 链路状态管理
链路连接:netif_carrier_on + netif_wake_queue
链路断开:netif_carrier_off + netif_stop_queue
6. 高性能场景使用 NAPI
中断中:napi_schedule(调度轮询)
poll中:netif_receive_skb + napi_complete
参考文献:宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,机械工业出版社,2015年