?? rtl8139 驅(qū)動程序解析.txt
字號:
RTL8139 驅(qū)動程序解析
前言
RTL8139 可能是目前最受歡迎的網(wǎng)絡(luò)卡,它的價(jià)格便宜,功能上也還能接受。雖然在效能上有時(shí)會略不及Intel 的 eepro100,但因?yàn)閮r(jià)格實(shí)在太便宜了,所以芯片上的一點(diǎn)小問題通常也接忽略不計(jì)。
廢話少話,馬上來說明 8139too 這個(gè)驅(qū)動程序。8139 雖然價(jià)格不高,但該有的功能一點(diǎn)也不缺。它內(nèi)建了符合 MII 規(guī)格的 tranceiver,可以自動判斷連接的網(wǎng)絡(luò)是那一種型態(tài)。它也可以使用 DMA 直接使用位于主記憶體的緩區(qū)來存網(wǎng)絡(luò)上接收的封包,同樣的,待傳送的封包也可利用 DMA 傳送到網(wǎng)絡(luò)卡上。所以雖然在 8139 芯片上只有 2K 的接收緩沖區(qū)和 2K 的傳送緩沖區(qū),其效能仍十分不錯。
除了 realtek 本身外,有不少的廠商也使用相同的內(nèi)核生產(chǎn)了和 8139 相容的網(wǎng)絡(luò)芯片,包括了
SMC 1211
MPX 5030
DELTA 8139
ADDTRON 8139
DFE 538
可能還有更多。
驅(qū)動程序初始化
就像其它的驅(qū)動程序一樣,驅(qū)動程序在使用 insmod 載入時(shí),第一個(gè)初呼叫的函數(shù)是 init_module,在使用 rmmod 移除時(shí),cleanuo_module 會被呼叫。在 init_module 中,我們注冊了一個(gè) PCI 驅(qū)動程序
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);
}
這個(gè)結(jié)構(gòu)和上次介紹的 sis900 其實(shí)差別不大。rtl8139_init_one 用來初始化一個(gè) 8139 芯片。PCI 驅(qū)動程序最大的好處是 PCI BUS 提供了組態(tài)空間 (configuration space) 來存放驅(qū)動程序所需的 IO 位址及中斷號碼等資料,我們不必再像 ISA 驅(qū)動程序一樣需要指定這些資源。
rtl8139_init_one 會呼叫 rtl8139_init_board 來初始化芯片,基本上 8139 這個(gè)芯片算是一個(gè)很容易使用的芯片,基本的 PCI 初始化后就可以直接使用了。所以 rtl8139_init_one 和 rtl8139_init_board 其實(shí)多半是在做一些錯誤檢查的動作,并由 PCI 表格中所得稍后會用的到的資源。
我從 rtl8139_init_board 中取出一些比較重要的片斷加以說明,其它的部份請自行參考源代碼。
......
// 由 PCI 子系統(tǒng)中讀出所需的資源
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;
......
// 重設(shè)芯片
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;
基本上和上次介紹的函數(shù)基本上相同,在此不再重復(fù)。上面比較特別的可能只有 ioremap 這個(gè)函數(shù),它的用途是將 mmio_start 開始 mmio_len 長度的 IO 映射到記憶體中,之后我們就可以直接使用函數(shù)的傳回值來做 IO 的動作了。
一般而言,mmio_start 的值是一個(gè)位于 CPU 定址空間中的實(shí)體位址,在一般的架構(gòu)下,硬件的設(shè)計(jì)者會保留一塊記憶體位置給記憶體映射裝置 (memory-mapped device) 使用。這些裝置允許 CPU 用記憶體調(diào)用的方式取用其上的暫存器,在有些不支援 IO 調(diào)用的架構(gòu)中,這些唯一取得裝置暫存器的方法。
舉個(gè)例說,如果你要調(diào)用第 100 號暫存器,你可以使用
unsigned int *ap = (unsigned int *) mmio_start + 0x100;
printf("register 0x100 = %x\n", *ap);
接下來我們一一解釋這些函數(shù)。
開始裝置-- rtl8139_open
這個(gè)函數(shù)會在你使用 ifconfig 時(shí)初呼叫,在這個(gè)函數(shù)中,你必須做下列的事
注冊中斷函數(shù) rtl8139_interrupt
分配并初始化 8139 所需的接收與傳送緩沖區(qū)。
產(chǎn)生一個(gè) kernel thread 負(fù)責(zé)查看網(wǎng)絡(luò)連線的狀態(tài)
比較特別的是第三個(gè)動作,
rtl8139_start_xmit
這個(gè)函數(shù)會在傳送一個(gè)封包時(shí)初呼叫,
static int rtl8139_start_xmit (struct sk_buff *skb, struct net_device *dev)
{
entry = tp->cur_tx % NUM_TX_DESC;
8139 支援四個(gè)傳送緩沖區(qū),你必須挑出下一個(gè)要用的緩沖區(qū)。接下來,你必須把緩沖區(qū)記憶體的實(shí)體表址 (physical address) 設(shè)定到 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 要求緩沖區(qū)的表址必須對齊 32 位元。也就是說位址必須能被 4 除盡。如果不行的話,我們必須另外安排一個(gè)表址對齊 32 位元的緩沖區(qū),把資料拷貝到那里去,然后將這個(gè)新緩沖區(qū)的實(shí)體表址存放到暫存器中去。
RTL_W32 (TxStatus0 + (entry * sizeof (u32)),
tp->tx_flag | (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN));
這一段程序用來設(shè)定封包的長度,一個(gè)正確的 ethernet 封包必須至少有 64 位元組長。不幸的,8139 不管這件事,你設(shè)定多長它就送多少。上面這一行程序就在確定封包的長度至少有 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;
}
這以前解釋過到,當(dāng)緩沖區(qū)用完時(shí)必須通知上層不要再送封包下來了。
rtl8139_set_rx_mode
這個(gè)函數(shù)用來設(shè)定接收的模式,8139 提供了 64 組 MAC 位址 filter。只有符合這些 filter 的位址芯片才會用中斷通知 CPU 前來處理,一般狀態(tài)下,我們只接收和 8139 本身 MAC 相符的封包。只有在像 tcpdump 之類的程序中才會想要接收其它的封包。
rtl8139_interrupt
在中斷函數(shù)中,我們必須將狀態(tài)碼讀入,然后根據(jù)狀態(tài)碼的指示做不同的事。我們要處理的狀況有
發(fā)生錯誤,可能是接收緩沖區(qū)滿了,傳送發(fā)生錯誤,bus 發(fā)生錯誤,接收發(fā)生錯誤。根據(jù)不同的狀況,必須做不同的處理。如果傳送錯誤,則再送一次。如果接收錯誤,那可能只好等待上層協(xié)定發(fā)現(xiàn)并重送封包。如果是 PVCI BUS 錯誤,則可能要重置 BUS。
接收到封包,呼叫 rtl8139_rx_interrupt
傳送完一個(gè)封包,呼叫 rtl8139_tx_interrupt
當(dāng)接收到一個(gè)封包時(shí),我們必須通知上層協(xié)定前檢處理
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 分配一段足夠大小的緩沖區(qū)。skb_put會調(diào)整緩沖區(qū)的大小,關(guān)鑒在使用 netif_rx 通知上層協(xié)定有新的封包傳入。在稍后會由 BH_NET 這個(gè) bottom half 處理這個(gè)封包。
當(dāng)一個(gè)封包傳送完成后,我們必須將緩沖區(qū)釋放。這件工作在 rtl8139_tx_interrupt 中被完成,此時(shí)我們必須呼叫上層協(xié)定表示可以傳送新的封包了。這件事由下列在 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,只有在裝置己經(jīng)因?yàn)榫彌_區(qū)滿了且有新的封包要傳送時(shí)才去呼叫 netif_wake_queue。
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -