前言
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 載入時,第一個初呼叫的函數是 init_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。只有符合這些 filter 的位址芯片才會用中斷通知 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_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 處理這