《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》 第 14 章 Linux 网络设备驱动

《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      = &eth_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 upip 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_etherdevnetdev_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_opsip -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年