14-Linux 网络设备驱动

14.1 Linux 网络设备驱动的结构

网络设备与字符/块设备最大的不同:没有设备文件,只能通过 socket 接口访问。四层结构:

复制代码
应用层 (socket)
   ↓
网络协议接口层 (net/core/skbuff.c, net/ipv4/)   ------ sk_buff、协议栈
   ↓
网络设备接口层 (net/core/dev.c)                  ------ net_device、netdev_ops
   ↓
设备驱动功能层 (你的驱动)                        ------ 收/发、NAPI、中断
   ↓
物理网卡 / QEMU 虚拟网卡

14.1.1 网络协议接口层

核心是 struct sk_buff (include/linux/skbuff.h),它承载一个网络包及其元数据。

cpp 复制代码
struct sk_buff {
	union {
		struct { struct sk_buff *next, *prev; };   /* 链表 */
		struct rb_node rbnode;
		struct list_head list;
	};
	struct sock             *sk;
	struct net_device       *dev;
	char                    cb[48] __aligned(8);      /* 驱动私有区 */
	unsigned long           _skb_refdst;
	struct sec_path         *sp;
	unsigned int            len, data_len;
	__u16                   mac_len, hdr_len;
	__u16                   queue_mapping;
	__u8                    ip_summed;
	__u8                    csum_level;
	__u8                    csum_not_inet;
	__u32                   priority;
	...
	sk_buff_data_t          tail;
	sk_buff_data_t          end;
	unsigned char           *head, *data;
	...
};

关键操作:

API 作用
dev_alloc_skb(len) 分配 skb(用于接收)
alloc_skb(len, gfp) 分配 skb
kfree_skb(skb) / dev_kfree_skb(skb) 释放
skb_put(skb, len) 尾部扩展(接收时)
skb_push(skb, len) 头部扩展(发送时)
skb_pull(skb, len) 头部收缩
skb_reserve(skb, len) 预留头部空间
skb_headroom() / skb_tailroom() 剩余空间
skb_clone() / skb_copy() 复制
skb_checksum_help() 计算校验和

14.1.2 网络设备接口层

核心是 struct net_device (include/linux/netdevice.h,数千行)和 struct net_device_ops。

cpp 复制代码
struct net_device_ops {
	int  (*ndo_init)(struct net_device *dev);
	void (*ndo_uninit)(struct net_device *dev);
	int  (*ndo_open)(struct net_device *dev);
	int  (*ndo_stop)(struct net_device *dev);
	netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb,
								  struct net_device *dev);
	netdev_features_t (*ndo_features_check)(struct sk_buff *skb,
											struct net_device *dev,
											netdev_features_t features);
	u16  (*ndo_select_queue)(struct net_device *dev, struct sk_buff *skb,
							 struct net_device *sb_dev);
	void (*ndo_set_rx_mode)(struct net_device *dev);
	int  (*ndo_set_mac_address)(struct net_device *dev, void *addr);
	int  (*ndo_validate_addr)(struct net_device *dev);
	int  (*ndo_eth_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
	int  (*ndo_change_mtu)(struct net_device *dev, int new_mtu);
	void (*ndo_tx_timeout)(struct net_device *dev, unsigned int txqueue);
	void (*ndo_get_stats64)(struct net_device *dev,
							struct rtnl_link_stats64 *storage);
	...
	int  (*ndo_bpf)(struct net_device *dev, struct netdev_bpf *bpf);
	int  (*ndo_xdp_xmit)(struct net_device *dev, int n,
						 struct xdp_frame **frames, u32 flags);
	...
};

7.2.5 的变化(对比 4.0):

项 4.0 7.2.5
ndo_start_xmit 存在 存在,但不再要求返回 NETDEV_TX_BUSY 后自己 stop_queue(见 14.5)
ndo_tx_timeout 单参数 新增 txqueue 参数
ndo_get_stats64 存在 首选(ndo_get_stats 已废弃)
ndo_do_ioctl 通用 拆分为 ndo_eth_ioctl/ndo_siocdevprivate 等
ndo_xdp_xmit/ndo_bpf 早期版本 成熟,XDP 成为重要特性
ndo_set_rx_mode_async 无 新增
init_etherdev 存在 已删除
netif_napi_add(dev, napi, poll, weight) 4 参数 weight 参数默认值用 netif_napi_add();显式指定用 netif_napi_add_weight()

14.1.3 设备驱动功能层

驱动实现的核心职责:

  1. 初始化 :分配 net_device、填 netdev_ops、注册。
  2. 打开/关闭 :ndo_open/ndo_stop 中申请中断、启动队列、使能 NAPI。
  3. 发送 :ndo_start_xmit 把 skb 交给硬件。
  4. 接收 :中断触发 → 关闭中断 → 调度 NAPI → napi_poll 收包 → napi_gro_receive()/netif_receive_skb()。
  5. 统计 :ndo_get_stats64 返回 rtnl_link_stats64。
  6. 参数设置 :ethtool_ops(include/linux/ethtool.h)。

14.2 网络设备驱动的注册与注销

cpp 复制代码
/* include/linux/netdevice.h */
struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
                                    unsigned char name_assign_type,
                                    void (*setup)(struct net_device *),
                                    unsigned int txqs, unsigned int rxqs);
struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs,
                                      unsigned int rxqs);
struct net_device *devm_alloc_etherdev_mqs(struct device *dev, int sizeof_priv,
                                           unsigned int txqs, unsigned int rxqs);

void free_netdev(struct net_device *dev);
int  register_netdev(struct net_device *dev);
void unregister_netdev(struct net_device *dev);
int  register_netdevice(struct net_device *dev);
void unregister_netdevice(struct net_device *dev);

register_netdev() vs register_netdevice():

API 说明
register_netdev() 上层封装,注册前做命名与合法性检查(驱动优先用这个)
register_netdevice() 底层接口,要求 dev->name 已设置,dev->reg_state 正确

7.2.5 推荐流程(devm 版本):

cpp 复制代码
static int my_net_probe(struct platform_device *pdev)
{
	struct net_device *ndev;
	struct my_priv *priv;

	ndev = devm_alloc_etherdev_mqs(&pdev->dev, sizeof(struct my_priv),
								   1, 1);
	if (!ndev)
			return -ENOMEM;

	priv = netdev_priv(ndev);
	priv->ndev = ndev;
	priv->base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(priv->base))
			return PTR_ERR(priv->base);

	ndev->netdev_ops = &my_netdev_ops;
	ndev->ethtool_ops = &my_ethtool_ops;
	ndev->watchdog_timeo = 5 * HZ;

	SET_NETDEV_DEV(ndev, &pdev->dev);

	return register_netdev(ndev);      /* devm 会在卸载时自动 free */
}

注销:

cpp 复制代码
static void my_net_remove(struct platform_device *pdev)
{
	struct net_device *ndev = dev_get_drvdata(&pdev->dev);
	unregister_netdev(ndev);
	/* devm_alloc_etherdev 会自动 free_netdev */
}

注意 :unregister_netdev() 会等待所有 in-flight 的发送/接收完成,因此必须在释放硬件资源之前调用。

14.3 网络设备的初始化

cpp 复制代码
static void my_setup(struct net_device *ndev)
{
	/* 二层类型 */
	ndev->type = ARPHRD_ETHER;

	/* 头部长度:以太网头 14 字节 */
	ndev->hard_header_len = ETH_HLEN;

	/* MTU 与最大帧 */
	ndev->mtu = ETH_DATA_LEN;               /* 1500 */
	ndev->min_mtu = ETH_MIN_MTU;
	ndev->max_mtu = ETH_DATA_LEN;

	/* 头部空间(用于 skb_reserve) */
	ndev->needed_headroom = 0;
	ndev->needed_tailroom = 0;

	/* 设备特性 */
	ndev->features = NETIF_F_HW_CSUM | NETIF_F_SG | NETIF_F_FRAGLIST;
	ndev->hw_features = ndev->features;

	/* 地址长度 */
	ndev->addr_len = ETH_ALEN;

	/* 广播地址 */
	eth_broadcast_addr(ndev->broadcast);

	/* 关闭 ARP 等,由协议栈决定 */
	ndev->flags |= IFF_NOARP;      /* 仅当设备不支持 ARP 时 */
}

常用字段表:

字段 说明
type ARPHRD_ETHER、ARPHRD_LOOPBACK、ARPHRD_IEEE80211
hard_header_len 链路层头长度
mtu 最大传输单元
min_mtu / max_mtu MTU 范围(4.0 之后引入,必须设置)
features / hw_features 硬件卸载能力
vlan_features VLAN 相关能力
addr_len MAC 地址长度
dev_addr MAC 地址
broadcast 广播地址
watchdog_timeo 发送超时时间
netdev_ops / ethtool_ops 操作集

分配私有数据:

cpp 复制代码
struct my_priv {
	struct net_device *ndev;
	void __iomem *base;
	struct napi_struct napi;
	struct net_device_stats stats;
	spinlock_t lock;
	...
};

priv = netdev_priv(ndev);      /* 从 net_device 反查私有数据 */

14.4 网络设备的打开与释放

cpp 复制代码
static int my_net_open(struct net_device *ndev)
{
	struct my_priv *priv = netdev_priv(ndev);
	int ret;

	/* 1. 申请中断 */
	ret = request_irq(ndev->irq, my_interrupt, IRQF_SHARED,
					  ndev->name, ndev);
	if (ret)
		return ret;

	/* 2. 使能 NAPI */
	napi_enable(&priv->napi);

	/* 3. 初始化硬件(使能收发) */
	my_hw_init(priv);

	/* 4. 启动发送队列 */
	netif_start_queue(ndev);

	/* 5. 启动 PHY/链路(真实网卡) */
	phy_start(priv->phydev);

	netif_info(priv, ifup, ndev, "device up\n");
	return 0;
}

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

	netif_stop_queue(ndev);        /* 先停队列 */
	my_hw_disable(priv);           /* 再停硬件 */
	napi_disable(&priv->napi);     /* 再停 NAPI */
	free_irq(ndev->irq, ndev);     /* 最后释放中断 */
	return 0;
}

顺序至关重要:

  • 打开:申请中断 → 使能 NAPI → 启动硬件 → 启动队列。
  • 关闭:停队列 → 停硬件 → 停 NAPI → 释放中断。

顺序错误会导致"中断处理函数访问已释放资源"或"队列启动后硬件未就绪"。

14.5 数据发送流程

cpp 复制代码
static netdev_tx_t my_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
	struct my_priv *priv = netdev_priv(ndev);
	unsigned long flags;

	spin_lock_irqsave(&priv->lock, flags);

	if (!my_tx_ready(priv)) {
		/* 7.2.5:直接返回 BUSY 并让核心层 stop 队列,
		 * 或者自己调用 netif_stop_queue() 后返回 NETDEV_TX_BUSY */
		netif_stop_queue(ndev);
		spin_unlock_irqrestore(&priv->lock, flags);
		return NETDEV_TX_BUSY;
	}

	/* 映射 DMA */
	priv->tx_dma = dma_map_single(&ndev->dev, skb->data, skb->len,
								  DMA_TO_DEVICE);
	if (dma_mapping_error(&ndev->dev, priv->tx_dma)) {
		spin_unlock_irqrestore(&priv->lock, flags);
		dev_kfree_skb(skb);
		return NETDEV_TX_OK;
	}

	/* 启动硬件传输 */
	my_hw_start_tx(priv, priv->tx_dma, skb->len);

	priv->tx_skb = skb;              /* 保存,完成时释放 */

	spin_unlock_irqrestore(&priv->lock, flags);
	return NETDEV_TX_OK;
}

netdev_tx_t 返回值:

值 含义
NETDEV_TX_OK 已接受(成功或已丢弃)
NETDEV_TX_BUSY 硬件忙,稍后重试(调用者必须 stop 队列)
NETDEV_TX_LOCKED 已被锁占用(已废弃,不要用)

7.2.5 的发送流程变化:

  • 不再需要驱动自己处理 NETDEV_TX_BUSY 的队列恢复 :核心层会保证在返回 NETDEV_TX_BUSY 前队列已被停止。
  • 多队列:ndev->real_num_tx_queues、netdev_get_tx_queue()、netif_tx_stop_queue() 支持每队列独立控制。
  • netif_wake_queue() 在发送完成中断中调用,恢复发送。
  • ndo_tx_timeout 新增 txqueue 参数,用于多队列设备定位超时队列。

发送完成中断:

cpp 复制代码
static irqreturn_t my_tx_irq(int irq, void *data)
{
	struct net_device *ndev = data;
	struct my_priv *priv = netdev_priv(ndev);

	if (my_tx_complete(priv)) {
		if (priv->tx_skb) {
			dma_unmap_single(&ndev->dev, priv->tx_dma,
							 priv->tx_skb->len, DMA_TO_DEVICE);
			dev_kfree_skb_irq(priv->tx_skb);   /* 中断上下文用 _irq 版本 */
			priv->tx_skb = NULL;
		}
		netif_wake_queue(ndev);
	}
	return IRQ_HANDLED;
}

注意 :中断上下文中释放 skb 必须用 dev_kfree_skb_irq() 或 dev_kfree_skb_any() ,直接用 kfree_skb() 可能睡眠。

14.6 数据接收流程

7.2.5 的标准 NAPI 流程:

cpp 复制代码
/* 1. 中断处理函数:只调度 NAPI,不真正收包 */
static irqreturn_t my_rx_irq(int irq, void *data)
{
	struct net_device *ndev = data;
	struct my_priv *priv = netdev_priv(ndev);

	if (!my_is_rx_irq(priv))
		return IRQ_NONE;

	/* 关闭接收中断,避免中断风暴 */
	my_disable_rx_irq(priv);

	if (napi_schedule_prep(&priv->napi)) {
		my_ack_rx_irq(priv);
		__napi_schedule(&priv->napi);
	}
	return IRQ_HANDLED;
}

/* 2. 轮询函数:在软中断上下文批量收包 */
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 *ndev = priv->ndev;
	int work_done = 0;

	while (work_done < budget) {
		struct sk_buff *skb;
		unsigned int len;

		skb = my_rx_one(priv, &len);
		if (!skb)
			break;

		/* 填充协议信息 */
		skb->protocol = eth_type_trans(skb, ndev);
		skb->ip_summed = CHECKSUM_NONE;   /* 或 CHECKSUM_UNNECESSARY */

		/* 交给协议栈(支持 GRO 合并) */
		napi_gro_receive(napi, skb);

		ndev->stats.rx_packets++;
		ndev->stats.rx_bytes += len;
		work_done++;
	}

	if (work_done < budget) {
		napi_complete_done(napi, work_done);
		my_enable_rx_irq(priv);        /* 重新使能中断 */
	}

	return work_done;
}

关键 API 对比:

API 上下文 用途
netif_rx(skb) 任何(含中断) 旧式,把 skb 放入 backlog 队列,稍后处理
netif_receive_skb(skb) 进程/软中断 直接上送协议栈
napi_gro_receive(napi, skb) NAPI 轮询中 支持 GRO 合并(现代首选)
netif_receive_skb_list() 软中断 批量上送

NAPI 注册(7.2.5):

cpp 复制代码
/* 默认 weight(64) */
netif_napi_add(ndev, &priv->napi, my_napi_poll);

/* 显式 weight */
netif_napi_add_weight(ndev, &priv->napi, my_napi_poll, 64);

/* 需要时在 ndo_open 中 enable,ndo_stop 中 disable */
napi_enable(&priv->napi);
napi_disable(&priv->napi);

为什么用 NAPI:

  • 高流量下中断风暴会耗尽 CPU;NAPI 在中断后关闭中断,用轮询批量收包。
  • budget 限制单次轮询的包数,保证公平性。
  • GRO 把同一流的多个小包合并成一个大包,减少协议栈开销。

接收路径的必须操作:

  1. skb->protocol = eth_type_trans(skb, ndev)------以太网驱动必须调用。
  2. 设置 skb->ip_summed(CHECKSUM_UNNECESSARY 表示硬件已校验)。
  3. 更新 ndev->stats(或实现 ndo_get_stats64)。
  4. 用 napi_gro_receive() 上送。

14.7 网络连接状态

cpp 复制代码
/* 链路状态 */
netif_carrier_on(ndev);        /* 链路建立 */
netif_carrier_off(ndev);       /* 链路断开 */
netif_carrier_ok(ndev);        /* 查询 */

/* 队列状态 */
netif_start_queue(ndev);
netif_stop_queue(ndev);
netif_wake_queue(ndev);
netif_queue_stopped(ndev);

/* 运行状态 */
netif_running(ndev);           /* ndo_open 是否成功 */
netif_device_present(ndev);    /* 设备是否存在(热插拔) */
netif_device_attach(ndev) / netif_device_detach(ndev);

carrier 语义 :netif_carrier_on() 会触发 netdev_carrier_changed 通知,用户空间通过 netlink 收到 RTM_NEWLINK 事件。

PHY 层(真实网卡):

cpp 复制代码
/* include/linux/phy.h */
struct phy_device *phydev;

phydev = phy_connect(ndev, phy_id, my_adjust_link, PHY_INTERFACE_MODE_RGMII);
phy_start(phydev);
phy_stop(phydev);
phy_disconnect(phydev);

/* 在 adjust_link 中更新链路状态 */
static void my_adjust_link(struct net_device *ndev)
{
	struct my_priv *priv = netdev_priv(ndev);
	if (priv->phydev->link)
		netif_carrier_on(ndev);
	else
		netif_carrier_off(ndev);
}

14.8 参数设置和统计数据

14.8.1 ethtool

cpp 复制代码
/* include/linux/ethtool.h */
struct ethtool_ops {
	u32 (*get_link)(struct net_device *);
	int (*get_link_ksettings)(struct net_device *,
							  struct ethtool_link_ksettings *);
	int (*set_link_ksettings)(struct net_device *,
							  const struct ethtool_link_ksettings *);
	void (*get_drvinfo)(struct net_device *, struct ethtool_drvinfo *);
	int (*get_sset_count)(struct net_device *, int);
	void (*get_strings)(struct net_device *, u32, u8 *);
	void (*get_ethtool_stats)(struct net_device *, struct ethtool_stats *, u64 *);
	void (*get_regs)(struct net_device *, struct ethtool_regs *, void *);
	int (*get_coalesce)(struct net_device *, struct ethtool_coalesce *);
	int (*set_coalesce)(struct net_device *, struct ethtool_coalesce *);
	...
};

7.2.5 变化:

  • get_settings/set_settings 已删除 ,必须用 get_link_ksettings/set_link_ksettings。
  • 新增 get_module_info/get_module_eeprom_by_page、get_link_ext_state 等。
  • 统计用 get_ethtool_stats + get_sset_count + get_strings 三件套。

14.8.2 统计计数

cpp 复制代码
/* 方式一:直接维护 net_device 的 stats(简单) */
ndev->stats.rx_packets++;
ndev->stats.rx_bytes += len;
ndev->stats.tx_packets++;
ndev->stats.tx_bytes += len;
ndev->stats.rx_errors++;
ndev->stats.rx_dropped++;

/* 方式二:实现 ndo_get_stats64(推荐,支持 64 位原子计数) */
static void my_get_stats64(struct net_device *ndev,
                           struct rtnl_link_stats64 *stats)
{
	struct my_priv *priv = netdev_priv(ndev);

	stats->rx_packets = atomic64_read(&priv->rx_packets);
	stats->tx_packets = atomic64_read(&priv->tx_packets);
	stats->rx_bytes   = atomic64_read(&priv->rx_bytes);
	stats->tx_bytes   = atomic64_read(&priv->tx_bytes);
	stats->rx_errors  = priv->rx_errors;
	stats->tx_errors  = priv->tx_errors;
}

用户空间查看:

bash 复制代码
ip -s link show eth0
ethtool eth0
ethtool -S eth0
cat /proc/net/dev

14.9 DM9000 网卡设备驱动实例

14.9.1 DM9000 网卡硬件描述

DM9000 是原书使用的经典以太网控制器:

特性 说明
接口 8/16 位本地总线(也支持 SRAM 接口)
地址线 CMD 引脚区分索引寄存器与数据寄存器
中断 单一 IRQ,状态寄存器区分收发/错误
DMA 无,CPU 逐字节/字搬运
收发缓冲 内部 16KB SRAM,分区为 TX/RX FIFO

寄存器访问模式(原书重点):

cpp 复制代码
/* CMD = 0:访问索引寄存器(INDEX) */
/* CMD = 1:访问数据寄存器(DATA) */

#define DM9000_INDEX    (priv->base)             /* 低地址 */
#define DM9000_DATA     (priv->base + priv->io_addr)

static u8 dm9000_readb(struct dm9000 *db, u16 reg)
{
	writeb(reg, db->addr_lock);
	return readb(db->io_data);
}

static void dm9000_writeb(struct dm9000 *db, u16 reg, u8 value)
{
	writeb(reg, db->addr_lock);
	writeb(value, db->io_data);
}

关键寄存器:

寄存器 地址 作用
NCR 0x00 网络控制(复位、回环)
NSR 0x01 网络状态(链路、速度)
TCR 0x02 发送控制
TSR1/TSR2 0x03/0x04 发送状态
RCR 0x05 接收控制
RSR 0x06 接收状态
ISR 0xFE 中断状态
IMR 0xFF 中断屏蔽
MWCMD 0xF8 内存写命令
MRCMD 0xF2 内存读命令

14.9.2 DM9000 网卡驱动设计分析

QEMU 替代方案:QEMU 没有 DM9000 模型,但有三种等效路径:

  1. 读源码 :drivers/net/ethernet/davicom/dm9000.c 在 7.2.5 中依然存在,直接阅读。
  2. 用 virtio-net 学真实驱动 :drivers/net/virtio_net.c 是 7.2.5 中最现代、最值得读的网络驱动。
  3. 写一个"内存模拟网卡" :用 ndo_start_xmit 直接回环(loopback),完整走通 netdev_ops 流程。

DM9000 驱动的核心结构(7.2.5 源码要点):

cpp 复制代码
struct board_info {
	void __iomem *io_addr;          /* 索引寄存器基址 */
	void __iomem *io_data;          /* 数据寄存器基址 */
	struct net_device *ndev;
	struct resource *addr_res;
	struct resource *data_res;
	struct resource *irq_res;
	struct timer_list timer;
	struct mii_if_info mii;
	unsigned int flags;
	...
};

驱动注册(drivers/net/ethernet/davicom/dm9000.c):

cpp 复制代码
static struct platform_driver dm9000_driver = {
	.driver = {
		.name           = "dm9000",
		.pm             = &dm9000_drv_pm_ops,
		.of_match_table = of_match_ptr(dm9000_of_matches),
	},
	.probe   = dm9000_probe,
	.remove  = dm9000_drv_remove,       /* 7.2.5:返回 void */
};

dm9000_probe() 关键步骤:

  1. platform_get_resource() 取地址与中断资源。
  2. devm_ioremap() 映射索引/数据寄存器。
  3. alloc_etherdev() 分配 net_device。
  4. 读取芯片 ID(DM9000_VIDL/DM9000_PIDL)验证硬件。
  5. 读取 EEPROM/设备树中的 MAC 地址。
  6. 设置 netdev_ops = &dm9000_netdev_ops。
  7. 初始化 NAPI、定时器(PHY 轮询)。
  8. register_netdev()。

dm9000_netdev_ops:

cpp 复制代码
static const struct net_device_ops dm9000_netdev_ops = {
	.ndo_open               = dm9000_open,
	.ndo_stop               = dm9000_stop,
	.ndo_start_xmit         = dm9000_start_xmit,
	.ndo_tx_timeout         = dm9000_timeout,
	.ndo_set_multicast_list = dm9000_hash_table,
	.ndo_do_ioctl           = dm9000_ioctl,
	.ndo_change_mtu         = eth_change_mtu,        /* 7.2.5 中已被移除,需自己实现或忽略 */
	.ndo_validate_addr      = eth_validate_addr,
	.ndo_set_mac_address    = eth_mac_addr,
	.ndo_get_stats64        = dm9000_get_stats64,
};

7.2.5 移植提醒:

  • ndo_set_multicast_list 已删除 ,改用 ndo_set_rx_mode。
  • ndo_change_mtu 的默认实现 eth_change_mtu 已删除,应使用 ndo_change_mtu 自定义或依赖核心层检查。
  • ndo_do_ioctl 被拆分为 ndo_eth_ioctl 等。
  • .remove 返回 void。

发送流程(DM9000):

cpp 复制代码
static netdev_tx_t dm9000_start_xmit(struct sk_buff *skb,
                                     struct net_device *dev)
{
	struct board_info *db = netdev_priv(dev);

	if (db->tx_pkt_cnt > 1)
		return NETDEV_TX_BUSY;

	netif_stop_queue(dev);

	/* 写入长度 */
	dm9000_writew(db, DM9000_TXPLL, skb->len);
	dm9000_writew(db, DM9000_TXPLH, skb->len >> 8);

	/* 写命令,逐字节搬运数据 */
	dm9000_writeb(db, DM9000_MWCMD, 0);
	writesb(db->io_data, skb->data, skb->len);

	/* 启动发送 */
	dm9000_writeb(db, DM9000_TCR, TCR_TXREQ);

	dev->stats.tx_bytes += skb->len;
	dev_kfree_skb(skb);
	db->tx_pkt_cnt++;
	return NETDEV_TX_OK;
}

接收流程(DM9000):

cpp 复制代码
static int dm9000_rx(struct net_device *dev)
{
	struct board_info *db = netdev_priv(dev);
	u8 rxbyte, *rdptr;
	u16 RxLen, RxStatus;
	struct sk_buff *skb;
	int good_packet;

	/* 读接收状态 */
	dm9000_readb(db, DM9000_MRCMD, &rxbyte, 1);
	RxStatus = ...;
	RxLen = ...;

	if (RxLen > DM9000_PKT_MAX) { /* 异常 */ }

	/* 分配 skb 并搬运数据 */
	skb = dev_alloc_skb(RxLen + 4);
	rdptr = skb_put(skb, RxLen - 4);
	dm9000_readb(db, DM9000_MRCMD, rdptr, RxLen - 4);

	skb->protocol = eth_type_trans(skb, dev);
	netif_rx(skb);                 /* 或 napi_gro_receive() */
	dev->stats.rx_packets++;
	dev->stats.rx_bytes += RxLen - 4;
	return 0;
}

QEMU 学习建议 :用 drivers/net/virtio_net.c 对照阅读,它展示了 7.2.5 网络驱动的所有现代特性(多队列、NAPI、XDP、ethtool、DMA、TSO)。

14.10 常见错误

错误 现象 修正
忘记 eth_type_trans() 协议栈收不到包 接收时必须调用
中断上下文用 kfree_skb() 睡眠警告 用 dev_kfree_skb_irq()
ndo_start_xmit 返回 BUSY 但未停队列 队列状态不一致 核心层会处理,但显式 stop 更清晰
ndo_open 中先启动队列后申请中断 中断到达时未就绪 顺序:中断→NAPI→硬件→队列
ndo_stop 中先释放中断后停硬件 中断丢失/竞态 顺序:队列→硬件→NAPI→中断
未实现 ndo_get_stats64 ip -s link 无数据 实现或直接维护 stats
ndo_tx_timeout 少 txqueue 参数 编译错误 加参数
ndo_set_multicast_list 已删除 用 ndo_set_rx_mode
ethtool get_settings 已删除 用 get_link_ksettings
NAPI 中 budget 用尽仍 napi_complete 包积压 只在 work_done < budget 时完成
忘记 netif_wake_queue() 发送永久停止 在发送完成中唤醒
alloc_etherdev 后手动 kfree 双重释放 用 free_netdev()

14.11 本章实验清单

实验 目的 关键观察
virtio-net 收发 学习现代网络驱动 ip link、ping
内存回环网卡 学习 netdev_ops 全流程 ip addr add + ping 自己
NAPI 观察 学习收包机制 /proc/interrupts 中断数不再线性增长
ethtool 实现 学习参数设置 ethtool -i/-S/--show-ring
统计计数 学习 rtnl_link_stats64 ip -s link 数据正确
XDP 程序加载 学习 7.2.5 新特性 bpftool prog load 成功
多队列(-smp 4) 学习多队列发送 /sys/class/net/eth0/queues/

14.12 总结

  1. 网络设备没有设备文件,走 socket 路径;核心结构是 net_device + netdev_ops + sk_buff。
  2. 7.2.5 用 netif_napi_add()/netif_napi_add_weight() 注册 NAPI,中断只调度、轮询才收包。
  3. 接收路径必须调用 eth_type_trans(),并用 napi_gro_receive() 支持 GRO。
  4. 发送路径中 NETDEV_TX_BUSY 的队列管理已由核心层保证;ndo_tx_timeout 新增 txqueue 参数。
  5. init_etherdev、ndo_set_multicast_list、eth_change_mtu、ethtool get_settings 等旧接口已删除。
  6. DM9000 在 7.2.5 中仍可读,但更推荐对照 virtio_net.c 学习现代写法。
  7. QEMU 的 virtio-net 加上自写的回环网卡,即可完整复现原书的全部网络驱动知识点。
相关推荐
Snasph3 小时前
18-ARM Linux 设备树
linux 驱动学习
Snasph4 小时前
20-Linux 芯片级移植及底层驱动
linux 驱动学习
Snasph4 小时前
12- Linux 设备驱动的软件架构思想
linux 驱动学习
Snasph5 小时前
23-从 Linux 4.0 到 7.2.5 的迁移
linux 驱动学习
Snasph11 天前
01-设备驱动概述与开发环境构建
linux 驱动学习
Snasph11 天前
05-Linux 文件系统与设备文件
linux 驱动学习
Snasph11 天前
04-Linux 内核模块
linux 驱动学习