精彩---rtl8139網絡卡驅動程式分析
阿新 • • 發佈:2019-01-23
而另一類是普通的控制暫存器空間,這一部分對映完後CPU可以訪問來控制裝置工作。
現在我們回到上面pci_register_driver的第二步,如果找到相關裝置和我們的pci_device_id結構陣列對上號了,說明我們找到服務物件了,則呼叫rtl8139_init_one,它主要做了七件事:
①建立net_device結構,讓它在核心中代表這個網路裝置。但是讀者可能會問,pci_dev也是代表著這個裝置,那麼兩者有什麼區別 呢,正如我們上面討論的,網絡卡裝置既要遵循PCI規範,也要擔負起其作為網絡卡裝置的職責,於是就分了兩塊,pci_dev用來負責網絡卡的PCI規範,而這裡要說的net_device則是負責網絡卡的網路裝置這個職責。
dev = init_etherdev (NULL, sizeof (*tp));
if (dev == NULL) {
printk ("unable to alloc new ethernet\n");
return -ENOMEM;
}
tp = dev->priv;
init_etherdev函式在Linux/drivers/net/net_init.c中,在這個函式中分配了net_device的記憶體並進行了初步的初始化。這裡值得注意的是net_device中的一個成員priv,它代表著不同網絡卡的私有資料,比如Intel的網絡卡和Realtek 的網絡卡在核心中都是以net_device來代表。但是他們是有區別的,比如Intel和Realtek實現同一功能的方法不一樣,這些都是靠著priv 來體現。所以這裡把拿出來同net_device相提並論。分配記憶體時,net_device中除了priv以外的成員都是固定的,而priv的大小是可 以任意的,所以分配時要把priv的大小傳過去。
② 開啟這個裝置(其實是開啟了裝置的暫存器對映到記憶體的功能)
rc = pci_enable_device (pdev);
if (rc)
goto err_out;
pci_enable_device也是一個核心開發出來的介面,程式碼在drivers/pci/pci.c中,筆者跟蹤發現這個函式主要就是把 PCI配置空間的Command域的0位和1位置成了1,從而達到了開啟裝置的目的,因為rtl8139的官方datasheet中,說明了這兩位的作用 就是開啟記憶體對映和I/O對映,如果不開的話,那我們以上討論的把控制暫存器空間對映到記憶體空間的這一功能就被遮蔽了,這對我們是非常不利的,除此之外,pci_enable_device 還做了些中斷開啟工作。
③獲得各項資源
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);
讀者也許疑問我們的暫存器被對映到記憶體中的什麼地方是什麼時候有誰決定的呢。是這樣的,在硬體加電初始化時,BIOS韌體同一檢查了所有的PCI 裝置,並統一為他們分配了一個和其他互不衝突的地址,讓他們的驅動程式可以向這些地址對映他們的暫存器,這些地址被BIOS寫進了各個裝置的配置空間,因為這個活動是一個PCI的標準的活動,所以自然寫到各個裝置的配置空間裡而不是他們風格各異的控制暫存器空間裡。當然只有BIOS可以訪問配置空間。當作業系統初始化時,他為每個PCI裝置分配了pci_dev結構,並且把BIOS獲得的並寫到了配置空間中的地址讀出來寫到了pci_dev中的 resource欄位中。這樣以後我們在讀這些地址就不需要在訪問配置空間了,直接跟pci_dev要就可以了,我們這裡的四個函式就是直接從 pci_dev讀出了相關資料,程式碼在include/linux/pci.h中。定義如下:
#define pci_resource_start(dev,bar) ((dev)->resource[(bar)].start)
#define pci_resource_end(dev,bar) ((dev)->resource[(bar)].end)
這裡需要說明一下,每個PCI裝置有0-5一共6個地址空間,我們通常只使用前兩個,這裡我們把引數1傳給了bar就是使用記憶體對映的地址空間。
④把得到的地址進行對映
ioaddr = ioremap (mmio_start, mmio_len);
if (ioaddr == NULL) {
printk ("cannot remap MMIO, aborting\n");
rc = -EIO;
goto err_out_free_res;
}
ioremap是核心提供的用來對映外設暫存器到主存的函式,我們要對映的地址已經從pci_dev中讀了出來(上一步),這樣就水到渠成的成功 映射了而不會和其他地址有衝突。對映完了有什麼效果呢,我舉個例子,比如某個網絡卡有100個暫存器,他們都是連在一塊的,位置是固定的,加入每個暫存器佔 4個位元組,那麼一共400個位元組的空間被對映到記憶體成功後,ioaddr就是這段地址的開頭(注意ioaddr是虛擬地址,而mmio_start是實體地址,它是BIOS得到的,肯定是實體地址,而保護模式下CPU不認實體地址,只認虛擬地址),ioaddr+0就是第一個暫存器的地址,ioaddr+4就是第二個暫存器地址(每個暫存器佔4個位元組),以此類推,我們就能夠在記憶體中訪問到所有的暫存器進而操控他們了。
現在我們回到上面pci_register_driver的第二步,如果找到相關裝置和我們的pci_device_id結構陣列對上號了,說明我們找到服務物件了,則呼叫rtl8139_init_one,它主要做了七件事:
①建立net_device結構,讓它在核心中代表這個網路裝置。但是讀者可能會問,pci_dev也是代表著這個裝置,那麼兩者有什麼區別 呢,正如我們上面討論的,網絡卡裝置既要遵循PCI規範,也要擔負起其作為網絡卡裝置的職責,於是就分了兩塊,pci_dev用來負責網絡卡的PCI規範,而這裡要說的net_device則是負責網絡卡的網路裝置這個職責。
dev = init_etherdev (NULL, sizeof (*tp));
printk ("unable to alloc new ethernet\n");
return -ENOMEM;
}
tp = dev->priv;
init_etherdev函式在Linux/drivers/net/net_init.c中,在這個函式中分配了net_device的記憶體並進行了初步的初始化。這裡值得注意的是net_device中的一個成員priv,它代表著不同網絡卡的私有資料,比如Intel的網絡卡和Realtek 的網絡卡在核心中都是以net_device來代表。但是他們是有區別的,比如Intel和Realtek實現同一功能的方法不一樣,這些都是靠著priv 來體現。所以這裡把拿出來同net_device相提並論。分配記憶體時,net_device中除了priv以外的成員都是固定的,而priv的大小是可 以任意的,所以分配時要把priv的大小傳過去。
②
rc = pci_enable_device (pdev);
if (rc)
goto err_out;
pci_enable_device也是一個核心開發出來的介面,程式碼在drivers/pci/pci.c中,筆者跟蹤發現這個函式主要就是把 PCI配置空間的Command域的0位和1位置成了1,從而達到了開啟裝置的目的,因為rtl8139的官方datasheet中,說明了這兩位的作用 就是開啟記憶體對映和I/O對映,如果不開的話,那我們以上討論的把控制暫存器空間對映到記憶體空間的這一功能就被遮蔽了,這對我們是非常不利的,除此之外,pci_enable_device
③獲得各項資源
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);
讀者也許疑問我們的暫存器被對映到記憶體中的什麼地方是什麼時候有誰決定的呢。是這樣的,在硬體加電初始化時,BIOS韌體同一檢查了所有的PCI 裝置,並統一為他們分配了一個和其他互不衝突的地址,讓他們的驅動程式可以向這些地址對映他們的暫存器,這些地址被BIOS寫進了各個裝置的配置空間,因為這個活動是一個PCI的標準的活動,所以自然寫到各個裝置的配置空間裡而不是他們風格各異的控制暫存器空間裡。當然只有BIOS可以訪問配置空間。當作業系統初始化時,他為每個PCI裝置分配了pci_dev結構,並且把BIOS獲得的並寫到了配置空間中的地址讀出來寫到了pci_dev中的 resource欄位中。這樣以後我們在讀這些地址就不需要在訪問配置空間了,直接跟pci_dev要就可以了,我們這裡的四個函式就是直接從 pci_dev讀出了相關資料,程式碼在include/linux/pci.h中。定義如下:
#define pci_resource_start(dev,bar) ((dev)->resource[(bar)].start)
#define pci_resource_end(dev,bar) ((dev)->resource[(bar)].end)
這裡需要說明一下,每個PCI裝置有0-5一共6個地址空間,我們通常只使用前兩個,這裡我們把引數1傳給了bar就是使用記憶體對映的地址空間。
④把得到的地址進行對映
ioaddr = ioremap (mmio_start, mmio_len);
if (ioaddr == NULL) {
printk ("cannot remap MMIO, aborting\n");
rc = -EIO;
goto err_out_free_res;
}
ioremap是核心提供的用來對映外設暫存器到主存的函式,我們要對映的地址已經從pci_dev中讀了出來(上一步),這樣就水到渠成的成功 映射了而不會和其他地址有衝突。對映完了有什麼效果呢,我舉個例子,比如某個網絡卡有100個暫存器,他們都是連在一塊的,位置是固定的,加入每個暫存器佔 4個位元組,那麼一共400個位元組的空間被對映到記憶體成功後,ioaddr就是這段地址的開頭(注意ioaddr是虛擬地址,而mmio_start是實體地址,它是BIOS得到的,肯定是實體地址,而保護模式下CPU不認實體地址,只認虛擬地址),ioaddr+0就是第一個暫存器的地址,ioaddr+4就是第二個暫存器地址(每個暫存器佔4個位元組),以此類推,我們就能夠在記憶體中訪問到所有的暫存器進而操控他們了。