2. PCI 總線族
下面的總線都屬于 PCI 族:
- PCI:32 bit 總線,33 或 66 MHz。
- MiniPCI:插槽更小,用于筆記本電腦。
- CardBus:外部卡槽,用于筆記本電腦。
- PIX Extended(PCI-X):比 PCI 插槽要寬,64 bit,但支持插入一個(gè)標(biāo)準(zhǔn) PCI 卡。
- PCI Express(PCIe or PCI-E):PCI 的當(dāng)前代,用串行接口取代并行接口。
- PCI Express Mini Card:應(yīng)用于較新的筆記本,取代 MiniPCI。
- Express Card:應(yīng)用于較新的筆記本,取代 CardBus。
這些技術(shù)都是互相兼容的,內(nèi)核驅(qū)動(dòng)可以使用同一套。內(nèi)核不需要感知硬件到底用的是什么插槽和總線變種。
3. PCI 設(shè)備類型
PCI 總線上的設(shè)備類型主要有:
4. PCI 特性
主要是針對(duì)設(shè)備驅(qū)動(dòng)開(kāi)發(fā)者。
- boot 階段 BIOS 或 linux(如果配置了的話)會(huì)自動(dòng)分配設(shè)備的資源(I/O 地址,IRQ 中斷線)。PCI/PCIe 的地址分配,參考本號(hào)《系統(tǒng)地址映射初始化:基于 PCI 的系統(tǒng)》、《系統(tǒng)地址映射初始化:基于 PCIe 的系統(tǒng)》。
- 設(shè)備驅(qū)動(dòng)只需要讀取系統(tǒng)地址空間中的相應(yīng)配置即可。
- 大小端:PCI 設(shè)備的配置信息是小端的。寫(xiě)驅(qū)動(dòng)的時(shí)候要注意(有些內(nèi)核函數(shù)可以提供大小端轉(zhuǎn)化)。
5. 列舉 PCI 設(shè)備
- lspci:列舉所有 PCI 設(shè)備。
- lspci -tv:列舉 PCI 總線設(shè)備樹(shù)。
- PCI 設(shè)備樹(shù)與 /sys 中的數(shù)據(jù)結(jié)構(gòu)對(duì)應(yīng)
6. PCI 設(shè)備配置
- 每個(gè) PCI 設(shè)備有一個(gè) 256 byte 地址空間長(zhǎng)度的配置寄存器。
- 可以通過(guò) lspci -x 查看設(shè)備的配置:
- 標(biāo)準(zhǔn)的 PCI 配置信息:
- 偏移 0:Vendor ID。
- 偏移 2:Device ID。
- 偏移 10:Class ID(網(wǎng)卡、顯卡、橋 ...)。
- 偏移 16 - 39:Base Address Registers(BAR)0 到 5。
- 偏移 44:SubVendor ID。
- 偏移 46:SubDevice ID。
- 偏移 64 及以上:設(shè)備制造商。
這些偏移的定義,在內(nèi)核的 include/linux/pci_regs.h。
7. linux 驅(qū)動(dòng)
7.1 注冊(cè)所支持的設(shè)備
drivers/net/ne2k-pci.c:
static struct pci_device_id ne2k_pci_tbl[] = {
{ 0x10ec, 0x8029, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_RealTek_RTL_8029 },
{ 0x1050, 0x0940, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Winbond_89C940 },
{ 0x11f6, 0x1401, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Compex_RL2000 },
{ 0x8e2e, 0x3000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_KTI_ET32P2 },
{ 0x4a14, 0x5000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_NetVin_NV5000SC },
{ 0x1106, 0x0926, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Via_86C926 },
{ 0x10bd, 0x0e34, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_SureCom_NE34 },
{ 0x1050, 0x5a5a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Winbond_W89C940F },
{ 0x12c3, 0x0058, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Holtek_HT80232 },
{ 0x12c3, 0x5598, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Holtek_HT80229 },
{ 0x8c4a, 0x1980, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Winbond_89C940_8c4a },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, ne2k_pci_tbl);
7.2 注冊(cè)驅(qū)動(dòng)
注冊(cè)驅(qū)動(dòng)的操作函數(shù),以及驅(qū)動(dòng)所支持的設(shè)備表:
static struct pci_driver ne2k_driver = {
.name = DRV_NAME,
.probe = ne2k_pci_init_one,
.remove = __devexit_p(ne2k_pci_remove_one),
.id_table = ne2k_pci_tbl,
#ifdef CONFIG_PM
.suspend = ne2k_pci_suspend,
.resume = ne2k_pci_resume,
#endif /* CONFIG_PM */
};
static int __init ne2k_pci_init(void)
{
return pci_register_driver(&ne2k_driver);
}
static void __exit ne2k_pci_cleanup(void)
{
pci_unregister_driver (&ne2k_driver);
}
- 驅(qū)動(dòng)的操作函數(shù)以及所支持的設(shè)備,會(huì)在模塊加載時(shí)被載入。
- 如果找到一個(gè)匹配的設(shè)備,PCI 框架代碼會(huì)調(diào)用驅(qū)動(dòng)的 probe() 函數(shù)。
- 非常類似 USB 設(shè)備驅(qū)動(dòng)!
7.3 驅(qū)動(dòng)操作函數(shù)修飾
- __init:模塊初始化函數(shù)。這些代碼會(huì)在驅(qū)動(dòng)初始化完之后被丟棄。
- __exit:模塊退出函數(shù)。如果是靜態(tài)編譯(編到內(nèi)核中)的驅(qū)動(dòng),會(huì)忽略之。
- __devinit:probe 函數(shù)以及所有初始化函數(shù)。如果使能了 CONFIG_HOTPLUG 內(nèi)核配置,則此類函數(shù)就是個(gè)正常的函數(shù),否則等同 __init。
- __devinitconst:用于設(shè)備 ID 表。
- __devexit:移除時(shí)會(huì)調(diào)用的函數(shù)。同 __devinit。
- 所有引用 __devinit 函數(shù)地址的地方,都應(yīng)該使用 __devexit_p(fun) 進(jìn)行聲明修飾。如果代碼被丟棄的話,此修飾會(huì)將函數(shù)地址替換為 NULL。
示例:
static struct pci_driver ne2k_driver = {
.name = DRV_NAME,
.probe = ne2k_pci_init_one,
.remove = __devexit_p(ne2k_pci_remove_one),
.id_table = ne2k_pci_tbl,
...
};
7.4 設(shè)備初始化步驟
- 使能設(shè)備。
- 請(qǐng)求 I/O 端口以及 I/O 內(nèi)存資源。
- 設(shè)置 DMA mask size(coherent 及 streaming DMA 皆需要)。
- 分配并初始化共享控制數(shù)據(jù)(pci_allocate_coherent())。
- 初始化設(shè)備寄存器(如果需要的話)。
- 注冊(cè) IRQ 處理函數(shù)(request_irq())。
- 注冊(cè)進(jìn)其他子系統(tǒng)(網(wǎng)絡(luò)、顯示、存儲(chǔ),等等)。
- 使能 DMA 處理引擎。
7.5 設(shè)備使能
- 在訪問(wèn)設(shè)備寄存器之前,驅(qū)動(dòng)需要先執(zhí)行 pci_enable_device(),這會(huì)導(dǎo)致:
- 如果設(shè)備在 suspend 狀態(tài),則喚醒之。
- 分配設(shè)備的 I/O 和內(nèi)存區(qū)域(如果 BIOS 沒(méi)有搞定的話)。
- 為設(shè)備分配一個(gè) IRQ(如果 BIOS 沒(méi)有搞定的話)。
pci_enable_device() 可能會(huì)失敗,所以需要檢查其返回值!
pci_enable_device(drivers/net/ne2k-pci.c)示例:
static int __devinit ne2k_pci_init_one
(struct pci_dev *pdev, const struct pci_device_id *ent)
{
...
i = pci_enable_device (pdev);
if (i)
return i;
...
}
- 調(diào)用 pci_set_master() 使能 DMA,這會(huì)導(dǎo)致:
- 通過(guò)設(shè)置 PCI_COMMAND 寄存器中的 bus master bit 來(lái)使能 DMA。完成后設(shè)備便可在地址總線上扮演一個(gè) master 的角色。
- 如果 BIOS 設(shè)置了偽造的值,則修復(fù)延遲定時(shí)器的值(Fix the latency timer value if it's set to something bogus by the BIOS. 這句話沒(méi)看明白)。
- 如果設(shè)備可以使用 PCI Memory-Write-Invalidate transaction(寫(xiě)整個(gè) cache lines),你還可以調(diào)用 pci_set_mwi():
- 此函數(shù)使能 Memory-Write-Invalidate 的 PCI_COMMAND bit。
- 此函數(shù)還確保對(duì) cache line size 寄存器進(jìn)行正確的設(shè)置。
7.6 訪問(wèn)配置寄存器
訪問(wèn) I/O 內(nèi)存和端口信息。
#include < linux/pci.h >
/* 讀接口 */
int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
/* 讀示例:drivers/net/cassini.c */
pci_read_config_word(cp- >pdev, PCI_STATUS, &cfg);
/* 寫(xiě)接口 */
int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
/* 寫(xiě)示例:drivers/net/s2io.c */
/* Clear "detected parity error" bit
pci_write_config_word(sp- >pdev, PCI_STATUS, 0x8000);
- 每個(gè) PCI 設(shè)備最多有 6 個(gè) I/O 或內(nèi)存區(qū)域,通過(guò) BAR0 到 BAR5 來(lái)描述。
- 訪問(wèn) I/O 區(qū)域的基地址:
#include < linux/pci.h >
long iobase = pci_resource_start(pdev, bar);
- 訪問(wèn) I/O 區(qū)域的大?。?/li>
long iosize = pci_resource_len(pdev, bar);
- 預(yù)留 I/O 區(qū)域:
request_region(iobase, iosize, “my driver”);
或者簡(jiǎn)單點(diǎn):
pci_request_region(pdev, bar, “my driver”);
或者更簡(jiǎn)單點(diǎn):
pci_request_regions(pdev, “my driver”);
示例代碼(drivers/net/ne2k-pci.c):
ioaddr = pci_resource_start (pdev, 0);
irq = pdev- >irq;
if (!ioaddr || ((pci_resource_flags (pdev, 0) & IORESOURCE_IO) == 0))
{
dev_err(&pdev- >dev, "no I/O resource at PCI BAR #0\\n");
return -ENODEV;
}
if (request_region (ioaddr, NE_IO_EXTENT, DRV_NAME) == NULL) {
dev_err(&pdev- >dev, "I/O resource 0x%x @ 0x%lx busy\\n", NE_IO_EXTENT, ioaddr);
return -EBUSY;
}
7.7 設(shè)置 DMA mask size
- 對(duì)于擁有超過(guò)(或少于)(那不就是 whatever 么?) 32 bit 總線 master capability 的設(shè)備,對(duì)此設(shè)備調(diào)用 pci_dma_set_mask() 聲明之。
- 特別是對(duì)于使用 64 bit DMA 的 PCI-X 和 PCIe 兼容設(shè)備來(lái)說(shuō),驅(qū)動(dòng)必須調(diào)用此函數(shù)
- 如果設(shè)備可以直接尋址系統(tǒng) RAM 中高于 4G 物理地址的“緩存一致性內(nèi)存”,則通過(guò) pci_set_consistent_dma_mask() 注冊(cè)之。
示例(drivers/net/wireless/ipw2200.c):
err = pci_set_dma_mask(pdev, DMA_32BIT_MASK);
if (!err)
err = pci_set_consistent_dma_mask(pdev, DMA_32BIT_MASK);
if (err) {
printk(KERN_WARNING DRV_NAME ": No suitable DMA available.\\n");
goto out_pci_disable_device;
}
7.8 分配緩存一致性的 DMA buffers
至此,已完成 DMA mask size 的分配。
- 如果你準(zhǔn)備使用緩存一致性的 buffers,則分配之。
- 詳情參考內(nèi)核 Documentation/DMA-API.txt。
7.9 初始化設(shè)備寄存器
如果設(shè)備需要的話:
- 設(shè)置一些 "capability" 域。
- 進(jìn)行一些 vendor specific 的初始化工作或復(fù)位。
示例:清除 pending 的中斷。
7.10 注冊(cè)中斷處理函數(shù)
- 調(diào)用 request_irq() 時(shí)需要傳入 IRQF_SHARED flag,因?yàn)?PCI 中斷線是可共享的。
- 中斷注冊(cè)同時(shí)會(huì)使能中斷,故在中斷注冊(cè)的時(shí)間點(diǎn)上,需要:
- 確保設(shè)備已完成全部初始化工作并準(zhǔn)備好處理中斷。
- 確保設(shè)備在調(diào)用 request_irq() 之前沒(méi)有 pending 的中斷。
- 實(shí)際調(diào)用 request_irq() 的地方取決于設(shè)備類型,以及其所屬的子系統(tǒng)(比如網(wǎng)絡(luò)、顯示、存儲(chǔ) ...)。
- 驅(qū)動(dòng)隨后被注冊(cè)進(jìn)其所屬的子系統(tǒng)。
7.11 PCI 設(shè)備關(guān)停
在 remove() 函數(shù)中,你通常需要對(duì)在設(shè)備初始化(probe() 函數(shù))中所做工作進(jìn)行逆操作:
- 禁能設(shè)備產(chǎn)生中斷:如果你不禁能設(shè)備中斷的話,系統(tǒng)可能會(huì)收到 spurious 中斷,并最終禁用掉整個(gè)中斷線。該中斷線上的其他設(shè)備就遭殃了!
- 釋放 IRQ。
- 停止所有 DMA 活動(dòng):需要在關(guān)閉 IRQs 之后再做(原 slides 這句話后面還有一個(gè)注:could start new DMAs,這句話沒(méi)看明白)。
- 釋放 DMA buffers:先釋放 streaming buffers,然后再釋放 consistent buffers。
- 從其他子系統(tǒng)中取消注冊(cè)。
- 通過(guò) io_unmap() 取消映射 I/O 內(nèi)存及端口。
- 通過(guò) pci_disable_device() 關(guān)閉設(shè)備。
- 取消注冊(cè) I/O 內(nèi)存和端口:如果這一步不做的話,會(huì)無(wú)法重新加載驅(qū)動(dòng)。
評(píng)論
查看更多