Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux 驅動層數據發送全流程:以 e100 為例解析環形緩衝區與 DMA 映射

編輯:Linux 教學

網卡驅動的發送路徑是 Linux 網路子系統性能的關鍵環節。本文以 Intel e100 驅動為例,深度剖析從 hard_start_xmit 入口到硬件命令提交的完整流程,重點解析環形發送緩衝區構建、DMA 映射與命令提交機制。

發送環形緩衝區初始化:e100_alloc_cbs

驅動啟動階段 e100_up() 調用 e100_alloc_cbs 構建發送環:

static int e100_alloc_cbs(struct nic *nic)
{
    struct cb *cb;
    unsigned int i, count = nic->params.cbs.count;

    nic->cuc_cmd = cuc_start;
    nic->cb_to_use = nic->cb_to_send = nic->cb_to_clean = NULL;
    nic->cbs_avail = 0;

    // 1. 分配一致性 DMA 內存(CPU 可訪問虛擬地址 + 總線物理地址)
    nic->cbs = pci_alloc_consistent(nic->pdev,
        sizeof(struct cb) * count, &nic->cbs_dma_addr);
    if (!nic->cbs) return -ENOMEM;

    // 2. 構建環形鏈表,初始化 DMA 地址與鏈接指針
    for (cb = nic->cbs, i = 0; i < count; cb++, i++) {
        cb->next = (i + 1 < count) ? cb + 1 : nic->cbs;
        cb->prev = (i == 0) ? nic->cbs + count - 1 : cb - 1;
        cb->dma_addr = nic->cbs_dma_addr + i * sizeof(struct cb);
        cb->link = cpu_to_le32(nic->cbs_dma_addr + ((i+1)%count) * sizeof(struct cb));
        cb->skb = NULL;
    }

    // 3. 初始化工作指針
    nic->cb_to_use = nic->cb_to_send = nic->cb_to_clean = nic->cbs;
    nic->cbs_avail = count;
    return 0;
}

關鍵數據結構:cb(Command Block)

  • next/prev:CPU 端環形鏈表指針
  • dma_addr:該 CB 的總線物理地址
  • link:硬件端鏈接指針(指向下一個 CB 的物理地址)
  • skb:關聯的 socket buffer 指針
  • command:命令控制字(含 S-bit:暫停位)

發送入口:e100_xmit_frame

核心調用 dev->hard_start_xmit 進入驅動發送邏輯:

static int e100_xmit_frame(struct sk_buff *skb, struct net_device *netdev)
{
    struct nic *nic = netdev_priv(netdev);
    int err;

    // 某些晶片需 workaround
    if (nic->flags & ich_10h_workaround) {
        e100_exec_cmd(nic, cuc_nop, 0);
        udelay(1);
    }

    // 核心提交邏輯
    err = e100_exec_cb(nic, skb, e100_xmit_prepare);
    switch (err) {
    case -ENOSPC: netif_stop_queue(netdev); break;  // 佇列滿,停止上層佇列
    case -ENOMEM: netif_stop_queue(netdev); return 1;  // 硬錯誤
    }
    netdev->trans_start = jiffies;
    return 0;
}

核心提交邏輯:e100_exec_cb

此函數完成 SKB 入環、DMA 映射、硬件命令提交:

static inline int e100_exec_cb(struct nic *nic, struct sk_buff *skb,
                               void (*cb_prepare)(struct nic *, struct cb *, struct sk_buff *))
{
    struct cb *cb;
    unsigned long flags;
    int err = 0;

    spin_lock_irqsave(&nic->cb_lock, flags);

    if (unlikely(!nic->cbs_avail)) { err = -ENOMEM; goto err_unlock; }

    // 1. 獲取可用 CB,推進使用指針
    cb = nic->cb_to_use;
    nic->cb_to_use = cb->next;
    nic->cbs_avail--;
    cb->skb = skb;

    if (unlikely(!nic->cbs_avail)) err = -ENOSPC;

    // 2. 調用準備函數:填充 CB、構建 DMA 映射
    cb_prepare(nic, cb, skb);

    // 3. 硬件命令提交協議:先置當前 CB S-bit,再清前一個 CB S-bit
    cb->command |= cpu_to_le16(cb_s);
    wmb();  // 內存屏障,確保寫入順序
    cb->prev->command &= cpu_to_le16(~cb_s);

    // 4. 處理待發送鏈:將 cb_to_send 推進至 cb_to_use
    while (nic->cb_to_send != nic->cb_to_use) {
        if (unlikely(e100_exec_cmd(nic, nic->cuc_cmd, nic->cb_to_send->dma_addr))) {
            break;  // 硬件忙,稍後重試
        } else {
            nic->cuc_cmd = cuc_resume;
            nic->cb_to_send = nic->cb_to_send->next;
        }
    }

err_unlock:
    spin_unlock_irqrestore(&nic->cb_lock, flags);
    return err;
}

關鍵機制解析

  1. 雙指針設計:cb_to_use(生產者)、cb_to_send(消費者)、cb_to_clean(清理者)分離關注點,支援無鎖並發優化。
  2. S-bit 協議:硬件讀取 CB 時檢查 S-bit,為 1 表示暫停。驅動「先置新 CB S-bit,再清舊 CB S-bit」確保硬件無縫銜接。
  3. 內存屏障:wmb() 保證命令字寫入對硬件可見的順序性。
  4. 批量提交:while 循環盡可能一次性提交多個 CB,減少 MMIO 寫入開銷。

硬件命令執行:e100_exec_cmd

static inline int e100_exec_cmd(struct nic *nic, u8 cmd, dma_addr_t dma_addr)
{
    unsigned long flags;
    unsigned int i;
    int err = 0;
    // ... 向硬件 SCB 寫入命令與 CB 物理地址,輪詢完成狀態 ...
}

此函數直接操作硬件控制寄存器(SCB),提交 CU(命令單元)命令,等待硬件響應。

總結

e100 發送路徑展示了經典驅動設計模式:一致性 DMA 內存分配、環形緩衝區管理、雙指針生產消費模型、硬件協議位操作。理解這一流程,有助於掌握高性能網卡驅動的核心架構與優化思路。

Copyright © Windows教程網 All Rights Reserved