pcie驱动介绍
轉(zhuǎn)載:? ? ?pcie驅(qū)動(dòng)介紹_fight_onlyfor_you的博客-CSDN博客_pcie驅(qū)動(dòng)
PCIE設(shè)備的地址由總線號(hào)、設(shè)備號(hào)和功能號(hào)組成,分別稱為廠家ID、設(shè)備ID和設(shè)備類代碼
我們可以利用lspci工具了解這些概念。PCI工具集的一部分,下載地址為http://mj.ucw.cz/sw/pciutils/
下面這個(gè)照片是在xx.xx.xx.xx下面的shell終端下運(yùn)行l(wèi)spci,運(yùn)行l(wèi)spci
上面輸出代碼每行開(kāi)頭的邏輯地址(xx:yy.z).XX代表PCI的總線號(hào)。一個(gè)PCI域能夠容納256個(gè)總線。上圖中有3個(gè)PCI橋,引出來(lái)4條pci總線
YY是PCI設(shè)備編號(hào)。每個(gè)PCI總線可以支持32個(gè)PCI設(shè)備。Z表示PCI功能,每個(gè)PCI設(shè)備可以容納8個(gè)PCI功能。向上圖中的Advanced Micro devices設(shè)備就有兩個(gè)功能
在我們T1040插上Intel? Ethernet Controller I350 T4之后
輸入lspci可以看到如下輸出信息
由于是4個(gè)網(wǎng)口,所以這里對(duì)應(yīng)的功能號(hào)也是4個(gè)。回到我們的服務(wù)器上去,在服務(wù)器上面輸入lspci -t可以看到如下信息
對(duì)這個(gè)信息的理解你可以參考下面的體系架構(gòu)來(lái)畫(huà)出相應(yīng)的框圖,便于理解
從上面的輸出結(jié)果來(lái)看,我們的Advanced Micro devices,先從PCI總線開(kāi)始(標(biāo)號(hào)是0000),經(jīng)過(guò)PCI-PCI橋(00:01.0)。
PCI設(shè)備擁有256B的空間,用于存放配置寄存器。改空間是辨別PCI卡型號(hào)和性能的關(guān)鍵。我們可以查看配置去的詳細(xì)情況
在shell下面輸入lspci -x
如下圖
PCI寄存器是小端字節(jié)序格式,可以通過(guò)sysfs來(lái)查看PCI的配置,如下命令,查看命令和結(jié)果如下
對(duì)上圖進(jìn)行解釋,前面兩字節(jié)是廠家ID,表示生產(chǎn)廠家。PCI廠家ID在全球都是統(tǒng)一分配和管理的,在www.pcidatabase.com可以查看。PCI配置空間含義如下
對(duì)應(yīng)上上圖的輸出信息,翻譯起來(lái)就是
廠家ID是0x1002,設(shè)備ID是6778,設(shè)備類型代碼是0300,基址寄存器是0x000c-0000
子廠家ID是174b,子設(shè)備ID是d145,
配置空間中包含10字節(jié)用于表示設(shè)備類型的編碼。表示PCI橋設(shè)備類編碼的第一個(gè)字節(jié)是0x06,網(wǎng)絡(luò)設(shè)備類編碼的第一個(gè)字節(jié)是0x02,類編碼類型的定義見(jiàn)include/linux/pci_ids.h
PCI驅(qū)動(dòng)程序向PCI子系統(tǒng)注冊(cè)其支持的廠家ID、設(shè)備ID和設(shè)備類編碼。使用插入的卡通過(guò)配置空間被識(shí)別后,PCI子系統(tǒng)把插入的卡和對(duì)應(yīng)的驅(qū)動(dòng)綁定
訪問(wèn)PCI
PCI設(shè)備包括3個(gè)尋址空間:配置空間、IO端口和設(shè)備內(nèi)存。
配置區(qū)
內(nèi)核為驅(qū)動(dòng)程序提供6個(gè)可以調(diào)用的函數(shù)來(lái)訪問(wèn)PCI配置區(qū):
pci_read_config_[byte|word|dword](struct pci_dev *pdev,int offset,int *value);
pci_write_config_[byte|word|dword](struct pci_dev *pdev,int offset,int *value);
在參數(shù)列表中,strutc pci_dev是PCI設(shè)備結(jié)構(gòu)體,offset是想訪問(wèn)的配置空間中的字節(jié)位置。對(duì)讀函數(shù)來(lái)講,value是數(shù)據(jù)緩沖區(qū)的指針;對(duì)寫函數(shù)來(lái)講,value放的是準(zhǔn)備寫的數(shù)據(jù)。
看看下面的例子。
要得到分配給某卡功能的中斷號(hào),進(jìn)行如下操作
unsigend char_irq;
pci_read_config_byte(pdev,PCI_INTERRUPT_LINE,&irq);
按照PCI規(guī)定說(shuō)明,PCI配置空間偏移量地址60處存放卡的IRQ號(hào),配置空間的偏移地址都在文件include/linux/pci_regs.h中有詳細(xì)定義,所以一般用到PCI_INTERRUPT_LINE 表示偏移地址。于此類似,要想讀PCI狀態(tài)寄存器(配置空間偏移6處),進(jìn)行如下操作:
unsigned short status; ?pci_read_config_word(pdev,PCI_STATUS,&status);
配置去開(kāi)頭的64B是規(guī)范了的。其他地方由設(shè)備生產(chǎn)廠家按照自己的意思定義。
比如說(shuō)我們使用的Xircom卡,在64B偏移處的4個(gè)字節(jié)用于電源管理的目的,為了禁止電灌管理功能,Xircom CardBus驅(qū)動(dòng)程序在實(shí)現(xiàn)文件drivers/net/tulip/xircom_cb.c里進(jìn)行如下操作:
#define PCI_POWERMGMT 0x40
pci_write_config_dword(pdev,PCI_POWERMGMT,0x0000);
IO和內(nèi)存
PCI卡有6個(gè)IO或內(nèi)存區(qū)域,I/O區(qū)域包括寄存器,內(nèi)存區(qū)域存放數(shù)據(jù)。例如:視頻卡的I/0區(qū)域包含控制寄存器,內(nèi)存區(qū)域映射緩沖。但并不是所有的卡都有可尋址的內(nèi)存區(qū)域。
I/O和內(nèi)存區(qū)域的功能和硬件有關(guān)系,在設(shè)備手冊(cè)中可以查到。
像配置區(qū)一樣,內(nèi)核提供一系列的輔助函數(shù),可以用來(lái)操作PCI設(shè)備的/O和內(nèi)存區(qū)域
Unsigned long pci_resource_[start|len|end|flag](struct pci_dev *pdev,int bar);為操作I/O區(qū)域。如PCI視頻卡設(shè)備控制寄存器,驅(qū)動(dòng)程序要完成以下事件
(1)從配置區(qū)相應(yīng)基址寄存器里得到I/O區(qū)域的基址:
unsigned long io_base = pci_resource_start(pdev,bar);
這里假定設(shè)備控制寄存器被映射到由變量bar關(guān)聯(lián)的I/O區(qū)域,bar值的變化范圍是0-5。
(2)用內(nèi)核的request_region()常規(guī)機(jī)制獲得這個(gè)I/O區(qū)域,并標(biāo)明它對(duì)應(yīng)的設(shè)備:
request_region(io_base,length,”my_driver”);
通過(guò)調(diào)用request_region()申請(qǐng)I/O地址后,設(shè)備驅(qū)動(dòng)程序在申請(qǐng)的I/O地址中操作運(yùn)行。此機(jī)制確保其他程序不能申請(qǐng)此區(qū)域,直至用過(guò)調(diào)用release_region()釋放占用的區(qū)域。
lenth用于控制寄存器空間的大小,my_driver表示這個(gè)區(qū)域的使用者。在文件/proc/ioports中的my_driver對(duì)應(yīng)的文本行里可以看到這片I/O區(qū)域。也可以使用drivers/pci/pci.c文件中定義的封裝函數(shù)pci_request_region()申請(qǐng)I/O端口
(3)用寄存器手冊(cè)上的偏移地址加上(1)得到的基址,然后用inb()和outb()函數(shù)來(lái)訪問(wèn)這些寄存器
Read函數(shù)
Register_data = inl(io_base+REGISTER_OFFSET);
write函數(shù)
Outl(register_data,io_base+REGISTER_OFFSET)
為了能夠操作PCI設(shè)備的內(nèi)存區(qū)域,按照下面步驟進(jìn)行
(1)獲得基址、內(nèi)存區(qū)域長(zhǎng)度以及內(nèi)存相關(guān)標(biāo)志
Unsigned long mmio_base=pci_resource_start(pdev,bar)
Unsigned long mmio_length=pci_resource_length(pdev,bar)
Unsigned long mmio_flags=pci_resource_flags(pdev,bar)
這里假定設(shè)備內(nèi)存區(qū)域被映射到基址寄存器bar
(2)用內(nèi)核的request_men_region()常規(guī)機(jī)制標(biāo)記這片內(nèi)存區(qū)的擁有者
Request_men_region(mmio_base,mmio_length,”my_driver”);
用前面提到的封裝函數(shù)pci_request_region()也可以
(3)讓CPU訪問(wèn)第(1)步獲得的設(shè)備內(nèi)存。為防止發(fā)生意外,某些內(nèi)存空間(如包含寄存器的地方)設(shè)置成不讓CPU可預(yù)取或不可啟用高速緩存。對(duì)于其他設(shè)備(如本例中提到的區(qū)域),CPU 可以啟用高速緩存。根據(jù)內(nèi)存區(qū)域的訪問(wèn)標(biāo)志,使用合適的函數(shù)可以得到與映射區(qū)域相對(duì)應(yīng)的內(nèi)核虛擬地址
Viod __iomen *buffer;
If(flages & IORESOURCE_CACHEABLE)
{
Buffer=ioremap(mmio_base,mmio_length);
}
Else
{
Buffer = ioremap_nocache(mmio_base,mmio_length);
}
為了安全起見(jiàn)并避免執(zhí)行前述的檢查,使用lib/iomap.c定義的函數(shù)pci_iomap()代替
? Buffer=pci_iomap(pdev,bar,mmio_length)
DMA緩沖區(qū)是DMA傳送時(shí)用做源端地址或者目的地址的內(nèi)存區(qū)。如果總線接口能訪問(wèn)的地址受限,將會(huì)影響DMA緩沖區(qū)的大小。因此,用于24位總線(如ISA)的DMA緩沖區(qū)只占系統(tǒng)內(nèi)存底部的16MB。稱為ZONE_DMA。PCI總線默認(rèn)為32位,所以在32的平臺(tái)下一般不會(huì)碰到這種限制。可以用下面的函數(shù)告訴內(nèi)核系統(tǒng)中可用作DMA緩沖區(qū)的地址的特殊要求:
dma_set_mask(struct device *dev,u64 mask);
如果這個(gè)函數(shù)返回成功,就可以在mask指定的地址范圍內(nèi)進(jìn)行DMA操作。如e1000PCI-X吉位以太網(wǎng)驅(qū)動(dòng)程序(drivers/net/e1000/e1000-main.c)里:
If(!(err = pci_set_dma_mask(pdev,DMA_64)))
{
/*system supports 64-bit DMA*/
Pci_using_dac =1
}
Else
{
/*see if 32-bit DMA is supported */
If((err =pci_set_dma_mask(pdev,DMA_32BIT_MASK)))
{
/*no,let’s abort*/
E1000_ERR(“No usable DMA configuration,aborting\n”);
Return err;
}
/*32-bit DMA*/
Pci_using_dac=0;
}
I/O設(shè)備從總線控制器或IOMMU(I/O Memory Unit,I/O內(nèi)存管理單元)的角度看待DMA緩沖區(qū)。所以,I/O設(shè)備需要的是DMA緩沖區(qū)的總線地址,而不是物理地址或者內(nèi)核虛擬地址。如果你想告訴PCI卡DMA緩沖區(qū)的地址信息,必須讓PCI卡知道DMA緩沖區(qū)的總線地址。總線地址的類型是dma_addr_t,在文件include/asm-your-arch/types.h里有定義
DMA還有幾個(gè)概念要了解,一個(gè)是回彈緩沖區(qū)。回彈緩沖區(qū)駐留在可作為DMA緩沖區(qū)的內(nèi)存區(qū)域里,在DMA請(qǐng)求的源或目的地址沒(méi)有DMA功能的內(nèi)存區(qū)域時(shí),它可以作為臨時(shí)內(nèi)存區(qū)存放數(shù)據(jù)。例如,用DMA方式從32位PCI外圍設(shè)備向地址高于4GB的目標(biāo)傳送數(shù)據(jù)時(shí),如果沒(méi)有IOMMU單元,就需要用到回彈緩沖區(qū)了。數(shù)據(jù)先暫時(shí)存放在回彈緩沖區(qū),再?gòu)?fù)制到目標(biāo)地址。第二個(gè)概念頗具DMA的特色:分散、聚集。當(dāng)要傳輸?shù)臄?shù)據(jù)分布在不連續(xù)的內(nèi)存上時(shí),分散/聚集能對(duì)這些不連續(xù)緩沖區(qū)的數(shù)據(jù)進(jìn)行一次性發(fā)送,反過(guò)來(lái),DMA也能把數(shù)據(jù)從卡正確地傳送到地址不連續(xù)的緩沖區(qū)中。分散/聚集通過(guò)減少重復(fù)的DMA傳送請(qǐng)求來(lái)提高效率。
內(nèi)核提供有用的API函數(shù)來(lái)屏蔽配置DMA的細(xì)節(jié)。如果你是在給支持總線管理的PCI卡(大多數(shù)PCI卡都支持)寫驅(qū)動(dòng)程序,這些API會(huì)更簡(jiǎn)單。PCI DMA函數(shù)基本上就是封裝DMA的通用服務(wù)函數(shù),在include/asm/-genetic/pci-dma-compat.h文件中有定義。本章中我們只用PCI的DMA API。
內(nèi)核為PCI驅(qū)動(dòng)程序提供了兩類DMA服務(wù)
(1)一致性DMA訪問(wèn)方法。這些程序保證DMA傳送數(shù)據(jù)的一致性。如果PCI設(shè)備和CPU都有可能干預(yù)DMA緩沖區(qū),保證數(shù)據(jù)的一致性是很重要的,這時(shí)要用一致性API函數(shù)。它是性能上的一個(gè)折中。要得到能保證數(shù)據(jù)一致性的DMA緩沖區(qū),用下面的API函數(shù)
?Void *pci_alloc_consistent(strcuct pci_dev *pdev,size_t ?size,dma_addr_t *dma_handle);
?這個(gè)函數(shù)分配一個(gè)DMA緩沖區(qū),生成它的總線地址,并返回相關(guān)的內(nèi)核虛擬地址。前面兩個(gè)參數(shù)是PCI設(shè)備結(jié)構(gòu)體和DMA緩沖區(qū)的字節(jié)大小,第三個(gè)參數(shù)(dma-handle)是指向總線地址的指針,由函數(shù)調(diào)用產(chǎn)生。下面的代碼片段分配和釋放一個(gè)一致性DMA緩沖區(qū)
/*ALLOCATE*/
Void *vaddr = pci_alloc_consistent(pdev,size,&dma_handle)
/*free*/
Pci_free_consistent(pdev,size,vaddr,dma_handle);
(2)流式DMA訪問(wèn)函數(shù)。這些API不保證數(shù)據(jù)的一致性,所以速度更快。它們?cè)诓惶枰狢PU和I/O設(shè)備共享DMA緩沖區(qū)的時(shí)候比較有用。當(dāng)設(shè)備存儲(chǔ)的流式緩沖區(qū)被映射以便被設(shè)備訪問(wèn)后,驅(qū)動(dòng)程序要明確得去映射(或同步)流式緩沖區(qū),之后CPU才可以可靠地操作該緩沖區(qū)。流式訪問(wèn)有兩類函數(shù):pci_[map|unmap|dma_sync]_single()和pci_[map |unmap|dma_sync]_sg().
第一組函數(shù)可以映射、去映射以及同步一個(gè)預(yù)先分配的單一DMA緩沖區(qū)。Pci_map_single()的原型如下
Dma_addr_t pci_map_single(struct pci_dev *pdev,void *ptr,size_t size,int direction);
前三個(gè)參數(shù)分別表示PCI設(shè)備結(jié)構(gòu)體,預(yù)先分配的DMA緩沖區(qū)虛擬內(nèi)核地址和緩沖區(qū)的字節(jié)數(shù)。第四個(gè)參數(shù)取下面幾個(gè)值:PCI_DMA_BIDIRECTION、PCI_DMA_TODEVICE、PCI_DMA_FROMDEVICE和PCI_DMA_NONE,從名字上就很容易看出這些變量的含義。但是使用第一個(gè)選項(xiàng)(PCI_DMA_BIDIRECTION)的代價(jià)比較大,最后一個(gè)選項(xiàng)是在調(diào)試的時(shí)候使用。
第二組函數(shù)映射、去映射并且同步一個(gè)分散/聚集的DMA緩沖區(qū)鏈。Pci_map_sg()的原型如下:
Int pci_map_sg(struct pci_dev *pdev,struct scatterlist *sgl,int num_entries,int direction);
第二個(gè)參數(shù)是sruct scatterlist表示分散內(nèi)存的鏈表,在include/asm-your-arch/scatterlist.h
里面定義。Num_entries變量表示分散內(nèi)存的鏈表入口函數(shù)。第一和最后一個(gè)參數(shù)的意思跟pci_map_single()函數(shù)里的參數(shù)是一樣的。
————————————————
版權(quán)聲明:本文為CSDN博主「fight_onlyfor_you」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/fight_onlyfor_you/article/details/76718741
總結(jié)
- 上一篇: 【安卓病毒分析报告】FakeBank-盗
- 下一篇: 勤哲服务器用户名在哪查看,勤哲服务器入门