RTL8139的linux原始碼分析

來源:互聯網
上載者:User
前言
RTL8139 可能是目前最受歡迎的網路卡,它的價格便宜,功能上也還能接受。雖然在效能
上有時會略不及Intel 的 eepro100,但因為價格實在太便宜了,所以晶片上的一點小問題
通常也接忽略不計。

廢話少話,馬上來說明 8139too 這個驅動程式。8139 雖然價格不高,但該有的功能一點
也不缺。它內建了符合 MII 規格的 tranceiver,可以自動判斷串連的網路是那一種型態
。它也可以使用 DMA 直接使用位於主記憶體的緩區來存網路上接收的封包,同樣的,待傳
送的封包也可利用 DMA 傳送到網路卡上。所以雖然在 8139 晶片上只有 2K 的接收緩衝區
和 2K 的傳送緩衝區,其效能仍十分不錯。

除了 realtek 本身外,有不少的廠商也使用相同的核心生產了和 8139 相容的網路晶片,
包括了

SMC 1211
MPX 5030
DELTA 8139
ADDTRON 8139
DFE 538
可能還有更多。
驅動程式初始化
就像其它的驅動程式一樣,驅動程式在使用 insmod 載入時,第一個初呼叫的函數是 ini
t_module,在使用 rmmod 移除時,cleanuo_module 會被呼叫。在 init_module 中,我們
註冊了一個 PCI 驅動程式
static struct pci_driver rtl8139_pci_driver = {
name: MODNAME,
id_table: rtl8139_pci_tbl,
probe: rtl8139_init_one,
remove: rtl8139_remove_one,
suspend: rtl8139_suspend,
resume: rtl8139_resume,
};

static int __init rtl8139_init_module (void)
{
return pci_module_init (&rtl8139_pci_driver);
}

這個結構和上次介紹的 sis900 其實差別不大。rtl8139_init_one 用來初始化一個 8139
晶片。PCI 驅動程式最大的好處是 PCI BUS 提供了組態空間 (configuration space) 來
存放驅動程式所需的 IO 位址及中斷號碼等資料,我們不必再像 ISA 驅動程式一樣需要指
定這些資源。

rtl8139_init_one 會呼叫 rtl8139_init_board 來初始化晶片,基本上 8139 這個晶片算
是一個很容易使用的晶片,基本的 PCI 初始化後就可以直接使用了。所以 rtl8139_init
_one 和 rtl8139_init_board 其實多半是在做一些錯誤檢查的動作,並由 PCI 表格中所
得稍後會用的到的資源。

我從 rtl8139_init_board 中取出一些比較重要的片斷加以說明,其它的部份請自行參考
原始碼。
......
// 由 PCI 子系統中讀出所需的資源
mmio_start = pci_resource_start (pdev, 1);
mmio_end = pci_resource_end (pdev, 1);
mmio_flags = pci_resource_flags (pdev, 1);
mmio_len = pci_resource_len (pdev, 1);
......
......
// 將這些資源保留下來
rc = pci_request_regions (pdev, "8139too");
pci_set_master (pdev);
......
// 將 IO 位址對映到記憶體
ioaddr = ioremap (mmio_start, mmio_len);
dev->base_addr = (long) ioaddr;
tp->mmio_addr = ioaddr;
......
// 重設晶片
RTL_W8 (ChipCmd, (RTL_R8 (ChipCmd) & ChipCmdClear) | CmdReset);

/* Check that the chip has finished the reset. */
for (i = 1000; i > 0; i--) {
barrier();
udelay (10);
if ((RTL_R8 (ChipCmd) & CmdReset) == 0)
break;
}
// 判斷晶片確的版本
......

在 rtl8139_init_one 中最重要的是下面這一段
dev->open = rtl8139_open;
dev->hard_start_xmit = rtl8139_start_xmit;
dev->stop = rtl8139_close;
dev->get_stats = rtl8139_get_stats;
dev->set_multicast_list = rtl8139_set_rx_mode;
dev->do_ioctl = mii_ioctl;
dev->tx_timeout = rtl8139_tx_timeout;
dev->watchdog_timeo = TX_TIMEOUT;

dev->irq = pdev->irq;

基本上和上次介紹的函數基本上相同,在此不再重複。上面比較特別的可能只有 ioremap
這個函數,它的用途是將 mmio_start 開始 mmio_len 長度的 IO 映射到記憶體中,之後
我們就可以直接使用函數的傳回值來做 IO 的動作了。

一般而言,mmio_start 的值是一個位於 CPU 定址空間中的實體位址,在一般的架構下,
硬體的設計者會保留一塊記憶體位置給記憶體映射裝置 (memory-mapped device) 使用。
這些裝置允許 CPU 用記憶體調用的方式取用其上的暫存器,在有些不支援 IO 調用的架構
中,這些唯一取得裝置暫存器的方法。

舉個例說,如果你要調用第 100 號暫存器,你可以使用
unsigned int *ap = (unsigned int *) mmio_start + 0x100;
printf("register 0x100 = %x/n", *ap);

接下來我們一一解釋這些函數。

開始裝置-- rtl8139_open
這個函數會在你使用 ifconfig 時初呼叫,在這個函數中,你必須做下列的事

註冊中斷函數 rtl8139_interrupt
分配並初始化 8139 所需的接收與傳送緩衝區。
產生一個 kernel thread 負責查看網路連線的狀態
比較特別的是第三個動作,

rtl8139_start_xmit
這個函數會在傳送一個封包時初呼叫,
static int rtl8139_start_xmit (struct sk_buff *skb, struct net_device *dev)
{
entry = tp->cur_tx % NUM_TX_DESC;

8139 支援四個傳送緩衝區,你必須挑出下一個要用的緩衝區。接下來,你必須把緩衝區記
憶體的實體表址 (physical address) 設定到 8139 的暫存器中。
tp->tx_info[entry].skb = skb;
if ((long) skb->data & 3) { /* Must use alignment buffer. */
/* tp->tx_info[entry].mapping = 0; */
memcpy (tp->tx_buf[entry], skb->data, skb->len);
RTL_W32 (TxAddr0 + (entry * 4),
tp->tx_bufs_dma + (tp->tx_buf[entry] - tp->tx_bufs));
} else {
tp->tx_info[entry].mapping =
pci_map_single (tp->pci_dev, skb->data, skb->len,
PCI_DMA_TODEVICE);
RTL_W32 (TxAddr0 + (entry * 4), tp->tx_info[entry].mapping);
}

上面的程式碼小心的處理的 alignment 的問題, 8139 要求緩衝區的表址必須對齊 32 位
元。也就是說位址必須能被 4 除盡。如果不行的話,我們必須另外安排一個表址對齊 32
位元的緩衝區,把資料拷貝到那裡去,然後將這個新緩衝區的實體表址存放到暫存器中去

RTL_W32 (TxStatus0 + (entry * sizeof (u32)),
tp->tx_flag | (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN));

這一段程式用來設定封包的長度,一個正確的 ethernet 封包必須至少有 64 位元組長。
不幸的,8139 不管這件事,你設定多長它就送多少。上面這一行程式就在確定封包的長度
至少有 ETH_ZLEN。

dev->trans_start = jiffies;
spin_lock_irq (&tp->lock);

tp->cur_tx++;
if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx)
netif_stop_queue (dev);

spin_unlock_irq (&tp->lock);
return 0;
}

這以前解釋過到,當緩衝區用完時必須通知上層不要再送封包下來了。

rtl8139_set_rx_mode
這個函數用來設定接收的模式,8139 提供了 64 組 MAC 位址 filter。只有符合這些 fi
lter 的位址晶片才會用中斷通知 CPU 前來處理,一般狀態下,我們只接收和 8139 本身
MAC 相符的封包。只有在像 tcpdump 之類的程式中才會想要接收其它的封包。

rtl8139_interrupt
在中斷函數中,我們必須將狀態代碼讀入,然後根據狀態代碼的指示做不同的事。我們要處理
的狀況有

發生錯誤,可能是接收緩衝區滿了,傳送發生錯誤,bus 發生錯誤,接收發生錯誤。根據
不同的狀況,必須做不同的處理。如果傳送錯誤,則再送一次。如果接收錯誤,那可能只
好等待上層協定發現並重送封包。如果是 PVCI BUS 錯誤,則可能要重設 BUS。
接收到封包,呼叫 rtl8139_rx_interrupt
傳送完一個封包,呼叫 rtl8139_tx_interrupt
當接收到一個封包時,我們必須通知上層協定前檢處理
skb = dev_alloc_skb (pkt_size + 2);
if (skb) {
skb->dev = dev;
skb_reserve (skb, 2); /* 16 byte align the IP fields. */

eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0);
skb_put (skb, pkt_size);

skb->protocol = eth_type_trans (skb, dev);
netif_rx (skb);
dev->last_rx = jiffies;
tp->stats.rx_bytes += pkt_size;
tp->stats.rx_packets++;
} else {

這段程式是非常典型的接收封包程式,先使用 dev_alloc_skb 分配一段足夠大小的緩衝區
。skb_put會調整緩衝區的大小,關鑒在使用 netif_rx 通知上層協定有新的封包傳入。在
稍後會由 BH_NET 這個 bottom half 處理這個封包。
當一個封包傳送完成後,我們必須將緩衝區釋放。這件工作在 rtl8139_tx_interrupt 中
被完成,此時我們必須呼叫上層協定表示可以傳送新的封包了。這件事由下列在 rtl8139
_tx_interrupt 最後面的程式完成
if (tp->dirty_tx != dirty_tx) {
tp->dirty_tx = dirty_tx;
if (netif_queue_stopped (dev))
netif_wake_queue (dev);
}

我們小心的避免呼叫太多次 netif_wake_queue,只有在裝置己經因為緩衝區滿了且有新的
封包要傳送時才去呼叫 netif_wake_queue。

結語
其實我還有很多細節還沒有解釋,如 MII 的處理,錯誤的處理和 eeprom 的處理,這些就
留給大家自行研究了。如果有人有興趣將這些細節補上,我很樂意將它們加入文章之中。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.