12.1. PCI 接口

2018-02-24 15:50 更新

12.1.1.?PCI 尋址

每個(gè) PCI 外設(shè)有一個(gè)總線(xiàn)號(hào), 一個(gè)設(shè)備號(hào), 一個(gè)功能號(hào)標(biāo)識(shí). PCI 規(guī)范允許單個(gè)系統(tǒng)占用多達(dá) 256 個(gè)總線(xiàn), 但是因?yàn)?256 個(gè)總線(xiàn)對(duì)許多大系統(tǒng)是不夠的, Linux 現(xiàn)在支持 PCI 域. 每個(gè) PCI 域可以占用多達(dá) 256 個(gè)總線(xiàn). 每個(gè)總線(xiàn)占用 32 個(gè)設(shè)備, 每個(gè)設(shè)備可以是一個(gè)多功能卡(例如一個(gè)聲音設(shè)備, 帶有一個(gè)附加的 CD-ROM 驅(qū)動(dòng))有最多 8 個(gè)功能. 因此, 每個(gè)功能可在硬件層次被一個(gè) 16-位地址或者 key , 標(biāo)識(shí). Linux 的設(shè)備驅(qū)動(dòng)編寫(xiě)者, 然而, 不需要處理這些二進(jìn)制地址, 因?yàn)樗鼈兪褂靡粋€(gè)特定的數(shù)據(jù)結(jié)構(gòu), 稱(chēng)為 pci_dev, 來(lái)在設(shè)備上操作.

大部分近期的工作站至少有 2 個(gè) PCI 總線(xiàn). 在單個(gè)系統(tǒng)插入多于 1 個(gè)總線(xiàn)要通過(guò)橋?qū)崿F(xiàn), 橋是特殊用途的 PCI 外設(shè), 它的工作是連接 2 個(gè)總線(xiàn). 一個(gè) PCI 系統(tǒng)的全部分布是一個(gè)樹(shù), 這里每個(gè)總線(xiàn)都連接到一個(gè)上層總線(xiàn), 直到在樹(shù)根的總線(xiàn) 0 . CardBus PC-card 系統(tǒng)也通過(guò)橋連接到 PCI 系統(tǒng). 圖一個(gè)典型 PCI 系統(tǒng)的布局表示了一個(gè)典型的 PCI 系統(tǒng), 其中各種橋被突出表示了.

圖?12.1.?一個(gè)典型 PCI 系統(tǒng)的布局

描述所有的配置項(xiàng)超出了本書(shū)的范圍. 常常地, 隨每個(gè)設(shè)備發(fā)布的技術(shù)文檔描述被支持的寄存器. 我們感興趣的是一個(gè)驅(qū)動(dòng)如何能知道它的設(shè)備以及它如何能存取設(shè)備的配置空間.

3 個(gè)或者 4 個(gè) PCI 寄存器標(biāo)識(shí)一個(gè)設(shè)備: verdorID, deviceID, 和 class 是 3 個(gè)常常用到的. 每個(gè) PCI 制造商分配正確的值給這些只讀寄存器, 并且驅(qū)動(dòng)可使用它們來(lái)查找設(shè)備. 另外, 字段 subsystem verdorID 和 subsystem deviceID 有時(shí)被供應(yīng)商設(shè)置來(lái)進(jìn)一步區(qū)分類(lèi)似的設(shè)備.

我們看這些寄存器的細(xì)節(jié):

vendorID
這個(gè) 16-位 寄存器標(biāo)識(shí)一個(gè)硬件制造商. 例如, 每個(gè) Intel 設(shè)備都標(biāo)有相同的供應(yīng)商號(hào), 0x8086. 這樣的號(hào)有一個(gè)全球的注冊(cè), 由 PCI 特別利益體所維護(hù), 并且供應(yīng)商必須申請(qǐng)有一個(gè)唯一的分配給它們的號(hào).

deviceID
這是另一個(gè) 16-位 寄存器, 由供應(yīng)商選擇; 對(duì)于這個(gè)設(shè)備 ID 沒(méi)有要求官方的注冊(cè). 這個(gè) ID 常常和 供應(yīng)商 ID 成對(duì)出現(xiàn)來(lái)組成一個(gè)唯一的 32-位 標(biāo)識(shí)符給一個(gè)硬件設(shè)備. 我們使用詞語(yǔ)"簽名"來(lái)指代供應(yīng)商和設(shè)備 ID 對(duì). 一個(gè)設(shè)備驅(qū)動(dòng)常常依靠簽名來(lái)標(biāo)識(shí)它的設(shè)備; 你可在硬件手冊(cè)中找到對(duì)于目標(biāo)設(shè)備要尋找的值.

class
每個(gè)外設(shè)都屬于一個(gè)類(lèi). 類(lèi)寄存器是一個(gè) 16-位 值, 它的高 8 位標(biāo)識(shí)"基類(lèi)"(或者群). 例如, "ethernet"和"token ring"是 2 個(gè)類(lèi)都屬于"network"群, 而"serial"和"parallel"屬于"communication"群. 一些驅(qū)動(dòng)可支持幾個(gè)類(lèi)似的設(shè)備, 每個(gè)都有一個(gè)不同的簽名但是都屬于同樣的類(lèi); 這些驅(qū)動(dòng)可依賴(lài)類(lèi)寄存器標(biāo)識(shí)它們的外設(shè), 就象后面所示.

subsystem vendorIDsubsystem deviceID
這些字段可用來(lái)進(jìn)一步標(biāo)識(shí)一個(gè)設(shè)備. 如果芯片對(duì)于本地總線(xiàn)是一個(gè)通用接口芯片, 它常常被用在幾個(gè)完全不同的地方, 并且驅(qū)動(dòng)必須標(biāo)識(shí)出它在與之通話(huà)的實(shí)際設(shè)備. 子系統(tǒng)標(biāo)志用作此目的.

使用這些不同的標(biāo)識(shí)符, 一個(gè) PCI 驅(qū)動(dòng)可告知內(nèi)核它支持什么類(lèi)型的設(shè)備. struct pci_device_id 結(jié)構(gòu)被用來(lái)定義一個(gè)驅(qū)動(dòng)支持的不同類(lèi)型 PCI 設(shè)備的列表. 這個(gè)結(jié)構(gòu)包含不同的成員:

u32 vendor;u32 device;
這些指定一個(gè)設(shè)備的 PCI 供應(yīng)商和設(shè)備 ID. 如果驅(qū)動(dòng)可處理任何供應(yīng)商或者設(shè)備 ID, 值 PCI_ANY_ID 應(yīng)當(dāng)用作這些成員上.

u32 subvendor;u32 subdevice;
這些指定一個(gè)設(shè)備的 PCI 子系統(tǒng)供應(yīng)商和子系統(tǒng)設(shè)備 ID. 如果驅(qū)動(dòng)可處理任何類(lèi)型的子系統(tǒng) ID, 值 PCI_ANY_ID 應(yīng)當(dāng)用作這些成員上.

u32 class;u32 class_mask;
這 2 個(gè)值允許驅(qū)動(dòng)來(lái)指定它支持一類(lèi) PCI 類(lèi)設(shè)備. 不同的 PCI 設(shè)備類(lèi)( 一個(gè) VAG 控制器是一個(gè)例子 )在 PCI 規(guī)范里被描述. 如果一個(gè)驅(qū)動(dòng)可處理任何子系統(tǒng) ID, 值 PCI_ANY_ID 應(yīng)當(dāng)用作這些字段.

kernel_ulong_t driver_data;
這個(gè)值不用來(lái)匹配一個(gè)設(shè)備, 但是用來(lái)持有信息, PCI 驅(qū)動(dòng)可用來(lái)區(qū)分不同的設(shè)備, 如果它想這樣.

有 2 個(gè)幫助宏定義應(yīng)當(dāng)被用來(lái)初始化一個(gè) struct pci_device_id 結(jié)構(gòu):

PCI_DEVICE(vendor, device)
這個(gè)創(chuàng)建一個(gè) struct pci_device_id , 它只匹配特定的供應(yīng)商和設(shè)備 ID. 這個(gè)宏設(shè)置這個(gè)結(jié)構(gòu)的子供應(yīng)商和子設(shè)備成員為 PCI_ANY_ID.

PCI_DEVICE_CLASS(device_class, device_class_mask)
這個(gè)創(chuàng)建一個(gè) struct pci_device_id, 它匹配一個(gè)特定的 PCI 類(lèi).

一個(gè)使用這些宏來(lái)定義一個(gè)驅(qū)動(dòng)支持的設(shè)備類(lèi)型的例子, 在下面的內(nèi)核文件中可找到:


drivers/usb/host/ehci-hcd.c: 
static const struct pci_device_id pci_ids[] = { {
 /* handle any USB 2.0 EHCI controller */
 PCI_DEVICE_CLASS(((PCI_CLASS_SERIAL_USB << 8) | 0x20), ~0),
 .driver_data = (unsigned long) &ehci_driver,
 },
 { /* end: all zeroes */ }

}; 
drivers/i2c/busses/i2c-i810.c: 

static struct pci_device_id i810_ids[] = {
 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG1) },
 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG3) },
 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810E_IG) },
 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82815_CGC) },
 { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82845G_IG) },
 { 0, }, 
}; 

這些例子創(chuàng)建一個(gè) struct pci_device_id 結(jié)構(gòu)的列表, 列表中最后一個(gè)是被設(shè)置為全零的的空結(jié)構(gòu). 這個(gè) ID 的數(shù)組用在 struct pci_driver (下面講述), 并且它還用來(lái)告訴用戶(hù)空間這個(gè)特定的驅(qū)動(dòng)支持哪個(gè)設(shè)備.

12.1.4.?MODULEDEVICETABLE 宏

這個(gè) pci_device_id 結(jié)構(gòu)需要被輸出到用戶(hù)空間, 來(lái)允許熱插拔和模塊加載系統(tǒng)知道什么模塊使用什么硬件設(shè)備. 宏 MODULE_DEVICE_TABLE 完成這個(gè). 例如:


MODULE_DEVICE_TABLE(pci, i810_ids); 

這個(gè)語(yǔ)句創(chuàng)建一個(gè)局部變量稱(chēng)為 __mod_pci_device_table, 它指向 struct pci_device_id 的列表. 稍后在內(nèi)核建立過(guò)程中, depmod 程序在所有的模塊中尋找 __mod_pci_device_table. 如果找到這個(gè)符號(hào), 它將數(shù)據(jù)拉出模塊并且添加到文件 /lib/modules/KERNEL_VERSION/modules.pcimap. 在 depmod 完成后, 所有的被內(nèi)核中的模塊支持的 PCI 設(shè)備被列出, 帶有它們的模塊名子, 在那個(gè)文件中. 當(dāng)內(nèi)核告知熱插拔系統(tǒng)有新的 PCI 設(shè)備已找到, 熱插拔系統(tǒng)使用 moudles.pcimap 文件來(lái)找到正確的驅(qū)動(dòng)來(lái)加載.

12.1.5.?注冊(cè)一個(gè) PCI 驅(qū)動(dòng)

為了被正確注冊(cè)到內(nèi)核, 所有的 PCI 驅(qū)動(dòng)必須創(chuàng)建的主結(jié)構(gòu)是 struct pci_driver 結(jié)構(gòu). 這個(gè)結(jié)構(gòu)包含許多函數(shù)回調(diào)和變量, 來(lái)描述 PCI 驅(qū)動(dòng)給 PCI 核心. 這里是這個(gè)結(jié)構(gòu)的一個(gè) PCI 驅(qū)動(dòng)需要知道的成員:

const char *name;
驅(qū)動(dòng)的名子. 它必須是唯一的, 在內(nèi)核中所有 PCI 驅(qū)動(dòng)里面. 通常被設(shè)置為和驅(qū)動(dòng)模塊名子相同的名子. 它顯示在 sysfs 中在 /sys/bus/pci/drivers/ 下, 當(dāng)驅(qū)動(dòng)在內(nèi)核時(shí).

const struct pci_device_id *id_table;
指向 struct pci_device_id 表的指針, 在本章后面描述它.

int (probe) (struct pci_dev dev, const struct pci_device_id *id);
指向 PCI 驅(qū)動(dòng)中 probe 函數(shù)的指針. 這個(gè)函數(shù)被 PCI 核心調(diào)用, 當(dāng)它有一個(gè)它認(rèn)為這個(gè)驅(qū)動(dòng)想控制的 struct pci_dev 時(shí). 一個(gè)指向 struct pci_device_id 的指針, PCI 核心用來(lái)做這個(gè)決定的, 也被傳遞給這個(gè)函數(shù). 如果這個(gè) PCI 驅(qū)動(dòng)需要這個(gè)傳遞給它的 struct pci_dev, 它應(yīng)當(dāng)正確初始化這個(gè)設(shè)備并且返回 0. 如果這個(gè)驅(qū)動(dòng)不想擁有這個(gè)設(shè)備, 或者產(chǎn)生一個(gè)錯(cuò)誤, 它應(yīng)當(dāng)返回一個(gè)負(fù)的錯(cuò)誤值. 關(guān)于這個(gè)函數(shù)的更多的細(xì)節(jié)在本章后面.

void (remove) (struct pci_dev dev);
指向 PCI 核心在 struct pci_dev 被從系統(tǒng)中去除時(shí)調(diào)用的函數(shù)的指針, 或者當(dāng) PCI 驅(qū)動(dòng)被從內(nèi)核中卸載時(shí). 關(guān)于這個(gè)函數(shù)的更多的細(xì)節(jié)在本章后面.

int (suspend) (struct pci_dev dev, u32 state);
當(dāng) struct pci_dev 被掛起時(shí) PCI 核心調(diào)用的函數(shù)的指針. 掛起狀態(tài)在 state 變量里傳遞. 這個(gè)函數(shù)是可選的; 一個(gè)驅(qū)動(dòng)不必提供它.

int (resume) (struct pci_dev dev);
當(dāng) pci_dev 被恢復(fù)時(shí) PCI 核心調(diào)用的函數(shù)的指針. 它一直被調(diào)用在調(diào)用掛起之后. 這個(gè)函數(shù)時(shí)可選的; 一個(gè)驅(qū)動(dòng)不必提供它.

總之, 為創(chuàng)建一個(gè)正確的 struct pci_driver 結(jié)構(gòu), 只有 4 個(gè)字段需要被初始化:


static struct pci_driver pci_driver = {
 .name = "pci_skel",
 .id_table = ids,
 .probe = probe,
 .remove = remove,
}; 

為注冊(cè) struct pci_driver 到 PCI 核心, 用一個(gè)帶有指向 struct pci_driver 的指針調(diào)用 pci_register_driver. 傳統(tǒng)上這在 PCI 驅(qū)動(dòng)的模塊初始化代碼中完成:


static int __init pci_skel_init(void)
{
 return pci_register_driver(&pci_driver);
}

注意, pci_register_driver 函數(shù)要么返回一個(gè)負(fù)的錯(cuò)誤碼, 要么是 0 當(dāng)所有都成功注冊(cè). 它不返回綁定到驅(qū)動(dòng)上的設(shè)備號(hào),或者一個(gè)錯(cuò)誤碼如果沒(méi)有設(shè)備被綁定到驅(qū)動(dòng)上. 這是自 2.6 發(fā)布之前的內(nèi)核的一個(gè)改變, 并且是因?yàn)橄铝械那闆r而來(lái)的:

  • 在支持 PCI 熱插拔的系統(tǒng)上, 或者 CardBus 系統(tǒng), 一個(gè) PCI 設(shè)備可在任何時(shí)間點(diǎn)出現(xiàn)或消失. 如果驅(qū)動(dòng)可在設(shè)備出現(xiàn)前被加載是有幫助的, 可以減少用來(lái)初始化一個(gè)設(shè)備的時(shí)間.

  • 2.6 內(nèi)核允許新 PCI ID 被動(dòng)態(tài)地分配給一個(gè)驅(qū)動(dòng), 在它被加載之后. 這是通過(guò)被創(chuàng)建在 sysfs 中的所有 PCI 驅(qū)動(dòng)目錄的文件 new_id 來(lái)完成的. 如果一個(gè)新設(shè)備在被使用而內(nèi)核對(duì)它還不知道時(shí), 這是非常有用的. 一個(gè)用戶(hù)可寫(xiě) PCI ID 值到 new_id 文件, 并且接著驅(qū)動(dòng)綁定到新設(shè)備. 如果一個(gè)驅(qū)動(dòng)不被允許加載直到一個(gè)設(shè)備在系統(tǒng)中出現(xiàn), 這個(gè)接口將不能工作.

當(dāng) PCI 驅(qū)動(dòng)被卸載, struct pci_drive 需要從內(nèi)核中注銷(xiāo). 這通過(guò)調(diào)用 pci_unregister_driver 完成. 當(dāng)發(fā)生這個(gè)調(diào)用, 任何當(dāng)前綁定到這個(gè)驅(qū)動(dòng)的 PCI 設(shè)備都被去除, 并且這個(gè) PCI 驅(qū)動(dòng)的 remove 函數(shù)在 pci_unregister_driver 函數(shù)返回之前被調(diào)用.


static void __exit pci_skel_exit(void)
{

 pci_unregister_driver(&pci_driver);
}

12.1.6.?老式 PCI 探測(cè)

在老的內(nèi)核版本中, 函數(shù) pci_register_driver, 不是一直被 PCI 驅(qū)動(dòng)使用. 相反, 它們要么手工瀏覽系統(tǒng)中的 PCI 設(shè)備列表, 要么它們將調(diào)用一個(gè)能夠搜索一個(gè)特定 PCI 設(shè)備的函數(shù). 驅(qū)動(dòng)的瀏覽系統(tǒng)中 PCI 設(shè)備列表的能力已被從 2.6 內(nèi)核中去除, 為了阻止驅(qū)動(dòng)破壞內(nèi)核, 如果它們偶爾修改 PCI 設(shè)備列表當(dāng)一個(gè)設(shè)備同時(shí)被去除時(shí).

如果發(fā)現(xiàn)一個(gè)特定 PCI 設(shè)備的能力真正被需要, 下列的函數(shù)可用:

struct pci_dev pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev from);
這個(gè)函數(shù)掃描當(dāng)前系統(tǒng)中 PCI 設(shè)備的列表, 并且如果輸入?yún)?shù)匹配指定的供應(yīng)商和設(shè)備 ID, 它遞增在 struct pci_dev 變量 found 中的引用計(jì)數(shù), 并且返回它給調(diào)用者. 這阻止了這個(gè)結(jié)構(gòu)沒(méi)有任何通知地消失, 并且確保內(nèi)核不會(huì) oops. 在驅(qū)動(dòng)用完由這個(gè)函數(shù)返回的 struct pci_dev, 它必須調(diào)用函數(shù) pci_dev_put 來(lái)正確地遞減回使用計(jì)數(shù), 以允許內(nèi)核清理設(shè)備如果它被去除.參數(shù) from 用同一個(gè)簽名來(lái)得到多個(gè)設(shè)備; 這個(gè)參數(shù)應(yīng)答指向已被找到的最后一個(gè)設(shè)備, 以便搜索能夠繼續(xù), 而不必從列表頭開(kāi)始. 為找到第一個(gè)設(shè)備, from 被指定為 NULL. 如果沒(méi)有找到(進(jìn)一步)設(shè)備, 返回 NULL.

一個(gè)如何正確使用這個(gè)函數(shù)的例子是:


struct pci_dev *dev;
dev = pci_get_device(PCI_VENDOR_FOO, PCI_DEVICE_FOO, NULL);
if (dev)
{
        /* Use the PCI device */
        ...
        pci_dev_put(dev);
}

這個(gè)函數(shù)不能從中斷上下文中被調(diào)用. 如果這樣做了, 一個(gè)警告被打印到系統(tǒng)日志.

struct pci_dev pci_get_subsys(unsigned int vendor, unsigned int device, unsigned int ss_vendor, unsigned int ss_device, struct pci_dev from);
這個(gè)函數(shù)就象 pci_get_device 一樣, 但是它允許當(dāng)尋找設(shè)備時(shí)指定子系統(tǒng)供應(yīng)商和子系統(tǒng)設(shè)備 ID. 這個(gè)函數(shù)不能從中斷上下文調(diào)用. 如果是, 一個(gè)警告被打印到系統(tǒng)日志.

struct pci_dev pci_get_slot(struct pci_bus bus, unsigned int devfn);
這個(gè)函數(shù)查找系統(tǒng)中的 PCI 設(shè)備的列表, 在指定的 struct pci_bus 上, 一個(gè)指定的 PCI 設(shè)備的設(shè)備和功能號(hào). 如果找到一個(gè)匹配的設(shè)備, 它的引用計(jì)數(shù)被遞增并且返回指向它的一個(gè)指針. 當(dāng)調(diào)用者完成存取 struct pci_dev, 它必須調(diào)用 pci_dev_put.

所有指向函數(shù)都不能從中斷上下文調(diào)用. 如果是, 一個(gè)警告被打印到系統(tǒng)日志中.

12.1.7.?使能 PCI 設(shè)備

在 PCI 驅(qū)動(dòng)的探測(cè)函數(shù)中, 在驅(qū)動(dòng)可存取 PCI 設(shè)備的任何設(shè)備資源(I/O 區(qū)或者中斷)之前, 驅(qū)動(dòng)必須調(diào)用 pci_enable_device 函數(shù):

int pci_enable_device(struct pci_dev *dev);
這個(gè)函數(shù)實(shí)際上使能設(shè)備. 它喚醒設(shè)備以及在某些情況下也分配它的中斷線(xiàn)和 I/O 區(qū). 例如, 這發(fā)生在 CardBus 設(shè)備上(它在驅(qū)動(dòng)層次上已經(jīng)完全和 PCI 等同了).

12.1.8.?存取配置空間

在驅(qū)動(dòng)已探測(cè)到設(shè)備后, 它常常需要讀或?qū)?3 個(gè)地址空間: 內(nèi)存, 端口, 和配置. 特別地, 存取配置空間對(duì)驅(qū)動(dòng)是至關(guān)重要的, 因?yàn)檫@是唯一的找到設(shè)備被映射到內(nèi)存和 I/O 空間的位置的方法.

因?yàn)槲⑻幚砥鳠o(wú)法直接存取配置空間, 計(jì)算機(jī)供應(yīng)商不得不提供一個(gè)方法來(lái)完成它. 為存取配置空間, CPU 必須寫(xiě)和讀 PCI 控制器中的寄存器, 但是確切的實(shí)現(xiàn)是依賴(lài)于供應(yīng)商的, 并且和這個(gè)討論無(wú)關(guān), 因?yàn)?Linux提供了一個(gè)標(biāo)準(zhǔn)接口來(lái)存取配置空間.

對(duì)于驅(qū)動(dòng), 配置空間可通過(guò)8-位, 16-位, 或者 32-位數(shù)據(jù)傳輸來(lái)存取. 相關(guān)的函數(shù)原型定義于 <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);
從由 dev 所標(biāo)識(shí)出的設(shè)備的配置空間讀 1 個(gè), 2 個(gè)或者 4 個(gè)字節(jié). where 參數(shù)是從配置空間開(kāi)始的字節(jié)偏移. 從配置空間取得的值通過(guò) val 指針?lè)祷? 并且這個(gè)函數(shù)的返回值是一個(gè)錯(cuò)誤碼. word 和 dword 函數(shù)轉(zhuǎn)換剛剛讀的值從小端到處理器的本地字節(jié)序, 因此你不必處理字節(jié)序.

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ě) 1 個(gè), 2 個(gè)或者 4 個(gè)字節(jié)到配置空間. 象通常一樣, 設(shè)備由 dev 所標(biāo)識(shí), 并且象通常一樣被寫(xiě)的值被傳遞. word 和 dword 函數(shù)轉(zhuǎn)換這個(gè)值到小端, 在寫(xiě)到外設(shè)之前.

所有的之前的函數(shù)被實(shí)現(xiàn)為真正調(diào)用下列函數(shù)的內(nèi)聯(lián)函數(shù). 可自由使用這些函數(shù)代替上面這些, 如果這個(gè)驅(qū)動(dòng)在任何特別時(shí)刻不能及時(shí)存取 struct pci_dev :

int pci_bus_read_config_byte (struct pci_bus bus, unsigned int devfn, int where, u8 val);int pci_bus_read_config_word (struct pci_bus bus, unsigned int devfn, int where, u16 val);int pci_bus_read_config_dword (struct pci_bus bus, unsigned int devfn, int where, u32 val);
就象 pci_read_function 一樣, 但是 struct pci_bus 和 devfn 變量需要來(lái)代替 struct pci_dev .

int pci_bus_write_config_byte (struct pci_bus bus, unsigned int devfn, int where, u8 val);int pci_bus_write_config_word (struct pci_bus bus, unsigned int devfn, int where, u16 val);int pci_bus_write_config_dword (struct pci_bus bus, unsigned int devfn, int where, u32 val);
如同 pciwrite 函數(shù), 但是 struct pci_bus
和 devfn 變量需要來(lái)替代 struct pci_dev *.

使用 pciread 函數(shù)尋址配置變量的最好方法是通過(guò)定義在 <linux/pci.h> 中的符號(hào)名. 例如, 下面的小函數(shù)獲取一個(gè)設(shè)備的版本 ID , 通過(guò)在使用 pci_read_config_bye 時(shí)傳遞一個(gè)符號(hào)名.


static unsigned char skel_get_revision(struct pci_dev *dev)
{
 u8 revision;
 pci_read_config_byte(dev, PCI_REVISION_ID, &revision);
 return revision;
}

12.1.9.?存取 I/O 和內(nèi)存空間

一個(gè) PCI 設(shè)備實(shí)現(xiàn)直至 6 個(gè) I/O 地址區(qū). 每個(gè)區(qū)由要么內(nèi)存要么 I/O 區(qū)組成. 大部分設(shè)備實(shí)現(xiàn)它們的 I/O 寄存器在內(nèi)存區(qū)中, 因?yàn)橥ǔK且粋€(gè)完善的方法(如同在" I/O 端口和 I/O 內(nèi)存"一節(jié)中解釋的, 在第 9 章). 但是, 不像正常的內(nèi)存, I/O 寄存器不應(yīng)當(dāng)被 CPU 緩存, 因?yàn)槊看未嫒《伎赡苡羞呺H效果. 作為內(nèi)存區(qū)來(lái)實(shí)現(xiàn) I/O 寄存器的 PCI 設(shè)備, 通過(guò)設(shè)置一個(gè)在它的配置寄存器的"內(nèi)存可預(yù)取"位來(lái)標(biāo)志出這個(gè)不同.[43] 如果這個(gè)內(nèi)存區(qū)被標(biāo)識(shí)為可預(yù)取的, CPU 可緩存它的內(nèi)容并且對(duì)它做所有類(lèi)型的優(yōu)化. 非可預(yù)取的內(nèi)存存取, 另一方面, 不能被優(yōu)化因?yàn)槊看未嫒】赡苡羞呺H效果, 就象 I/O 端口. 映射它們的寄存器到一個(gè)內(nèi)存地址范圍的外設(shè)聲明這個(gè)范圍是非可預(yù)取的, 而象在 PCI 板的視頻內(nèi)存的一些是可預(yù)取的. 在本節(jié), 我們使用詞語(yǔ)"區(qū)"來(lái)指代一個(gè)通用的 I/O 地址空間, 這個(gè)空間要么是內(nèi)存映射的, 要么是端口映射的.

一個(gè)接口板報(bào)告它的區(qū)的大小和當(dāng)前位置, 使用配置寄存器- 6 個(gè) 32 位寄存器, 在圖12-2中顯示的, 它們的符號(hào)名是 PCI_ADDRESS_0 到 PCI_BASE_ADDRESS_5. 因?yàn)?PCI 定義的 I/O 空間是 32-位空間, 使用同樣的配置接口給內(nèi)存和 I/O是有意義的. 如果設(shè)備使用 64-位地址總線(xiàn), 它可以在 64-位內(nèi)存空間聲明各個(gè)區(qū), 使用 2 個(gè)連續(xù)的 PCI_BASE_ADDRESS 寄存器給每個(gè)區(qū), 低位在前. 對(duì)一個(gè)設(shè)備可能提供 32-位 和 64-位區(qū).

內(nèi)核中, PCI 設(shè)備的 I/O 區(qū)已被集成到通用的資源管理中. 由于這個(gè)原因, 你不必存取配置變量來(lái)知道你的設(shè)備映射到內(nèi)存或者 I/O 空間什么地方. 首選的用來(lái)獲得區(qū)信息的接口包括下列函數(shù):

unsigned long pci_resource_start(struct pci_dev *dev, int bar);
這個(gè)函數(shù)返回第一個(gè)地址(內(nèi)存地址或者 I/O 端口號(hào)), 和 6 個(gè) PCI I/O 區(qū)中的一個(gè)相關(guān)聯(lián)的. 這個(gè)區(qū)通過(guò)整數(shù) bar (the base address register), 范圍從 0-5 (包含).

unsigned long pci_resource_end(struct pci_dev *dev, int bar);
這個(gè)函數(shù)返回最后一個(gè)地址, I/O 區(qū)號(hào) bar 的一部分. 注意這是最后一個(gè)可用地址, 不是這個(gè)區(qū)后的第一個(gè)地址.

unsigned long pci_resource_flags(struct pci_dev *dev, int bar);
這個(gè)函數(shù)返回和這個(gè)資源相關(guān)聯(lián)的標(biāo)識(shí).

資源標(biāo)識(shí)用來(lái)定義單個(gè)資源的一些特性. 對(duì)于和 PCI I/O 區(qū)相關(guān)聯(lián)的 PCI資源, 這個(gè)信息從基地址寄存器中抽取出來(lái), 但是可來(lái)自其他地方, 對(duì)于沒(méi)有和 PCI 設(shè)備關(guān)聯(lián)的資源.

所有的資源標(biāo)志都定義在 <linux/ioport.h>; 最重要的是:

IORESOURCE_IOIORESOURCE_MEM
如果被關(guān)聯(lián)的 I/O 區(qū)存在, 一個(gè)并且只有一個(gè)這樣的標(biāo)志被設(shè)置.

IORESOURCE_PREFETCHIORESOURCE_READONLY
這些標(biāo)志告訴是否一個(gè)內(nèi)存區(qū)是可預(yù)取的并且/或者寫(xiě)保護(hù)的. 后一個(gè)標(biāo)志對(duì) PCI 資源從不設(shè)置.

通過(guò)使用 pciresource 函數(shù), 一個(gè)設(shè)備驅(qū)動(dòng)可完全忽略底層的 PCI 寄存器, 因?yàn)橄到y(tǒng)已經(jīng)使用它們來(lái)構(gòu)造資源信息.

12.1.10.?PCI 中斷

對(duì)于中斷, PCI 是容易處理的. 在 Linux 啟動(dòng)時(shí), 計(jì)算機(jī)的固件已經(jīng)分配一個(gè)唯一的中斷號(hào)給設(shè)備, 并且驅(qū)動(dòng)只需要使用它. 中斷號(hào)被存儲(chǔ)于配置寄存器 60 (PCI_INTERRUPT_LINE), 它是一個(gè)字節(jié)寬. 這允許最多 256 個(gè)中斷線(xiàn), 但是實(shí)際的限制依賴(lài)于使用CPU. 驅(qū)動(dòng)不必費(fèi)心去檢查中斷號(hào), 因?yàn)樵?PCI_INTERRUPT_LINE 中找到的值保證是正確的一個(gè).

如果設(shè)備不支持中斷, 寄存器 61 (PCI_INTERRUPT_PIN) 是 0; 否則, 它是非零的值. 但是, 因?yàn)轵?qū)動(dòng)知道設(shè)備是否是被中斷驅(qū)動(dòng)的, 它常常不需要讀 PCI_INTERRUPT_PIN.

因此, 用來(lái)處理中斷的 PCI 特定的代碼需要讀配置字節(jié)來(lái)獲得保存在一個(gè)局部變量中的中斷號(hào), 如同在下面代碼中顯示的. 除此之外, 在第 10 章的信息適用.


result = pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &myirq);
if (result)
{
        /* deal with error */
}

本節(jié)剩下的提供了額外的信息給好奇的讀者, 但是對(duì)編寫(xiě)程序不必要.

一個(gè) PCI 連接器有 4 個(gè)中斷線(xiàn), 并且外設(shè)板可使用任何一個(gè)或者多個(gè). 每個(gè)管腳被獨(dú)立連接到主板的中斷控制器中, 因此中斷可被共享而沒(méi)有任何電路上的問(wèn)題. 中斷控制器接著負(fù)責(zé)映射中斷線(xiàn)(引腳)到處理器的硬件; 這種依賴(lài)平臺(tái)的操作留給控制器以便在總線(xiàn)自身上獲得平臺(tái)獨(dú)立性.

位于 PCI_INTERRUPT_PIN 的只讀的配置寄存器用來(lái)告知計(jì)算機(jī)實(shí)際上使用哪個(gè)管腳. 值得記住每個(gè)設(shè)備板可有多到 8 個(gè)設(shè)備; 每個(gè)設(shè)備使用一個(gè)單個(gè)中斷腳并且在它的配置寄存器中報(bào)告它. 在同一個(gè)設(shè)備板上的不同設(shè)備可使用不同的中斷腳或者共享同一個(gè).

PCI_INTERRUPT_LINE 寄存器, 另一方面, 是讀/寫(xiě)的. 當(dāng)啟動(dòng)計(jì)算機(jī), 固件掃描它的 PCI 設(shè)備并為每個(gè)設(shè)備設(shè)置寄存器固件中斷腳是如何連接給它的 PCI 槽位. 這個(gè)值由固件分配, 因?yàn)橹挥泄碳乐靼迦绾芜B接不同的中斷腳到處理器. 對(duì)于設(shè)備驅(qū)動(dòng), 但是, PCI_INTERRUPT_LINE 寄存器是只讀的. 有趣的是, 近期的 Linux 內(nèi)核版本在某些情況下可分配中斷線(xiàn), 不用依靠 BIOS.

12.1.11.?硬件抽象

我們結(jié)束 PCI 的討論, 通過(guò)快速看一下系統(tǒng)如何處理在市場(chǎng)上的多種 PCI 控制器. 這只是一個(gè)信息性的小節(jié), 打算來(lái)展示給好奇的讀者, 內(nèi)核的面向?qū)ο蠓植既绾蜗蛳聰U(kuò)展到最低層.

用來(lái)實(shí)現(xiàn)硬件抽象的機(jī)制是通常的包含方法的結(jié)構(gòu). 它是一個(gè)很強(qiáng)功能的技術(shù), 只添加最小的解引用一個(gè)指針的開(kāi)銷(xiāo)到正常的函數(shù)調(diào)用開(kāi)銷(xiāo)當(dāng)中. 在 PCI 管理的情況下, 唯一的硬件相關(guān)的操作是讀和寫(xiě)配置寄存器的那些, 因?yàn)樵?PCI 世界中所有其他的都通過(guò)直接讀和寫(xiě) I/O 和內(nèi)存地址空間來(lái)完成, 并且那些是在 CPU 的直接控制之下.

因此, 配置寄存器存取的相關(guān)的結(jié)構(gòu)只包含 2 個(gè)成員:


struct pci_ops
{
        int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);
        int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val);
};

這個(gè)結(jié)構(gòu)定義在 <linux/pci.h> 并且被 drivers/pci/pci.c 使用, 這里定義了實(shí)際的公共函數(shù).

作用于 PCI 配置空間的這 2 個(gè)函數(shù)有更大的開(kāi)銷(xiāo), 比解引用一個(gè)指針; 由于代碼的面向?qū)ο筇匦? 它們使用層疊指針, 但是操作中開(kāi)銷(xiāo)不是一個(gè)問(wèn)題, 這些操作很少被進(jìn)行并且從不處于速度-關(guān)鍵的路徑中. pci_read_config_byte(dev, where, val)的實(shí)際實(shí)現(xiàn), 例如, 擴(kuò)展為:


dev->bus->ops->read(bus, devfn, where, 8, val); 

系統(tǒng)中各種 PCI 總線(xiàn)在系統(tǒng)啟動(dòng)時(shí)被探測(cè), 并且此時(shí) struct pci_bus 項(xiàng)被創(chuàng)建并且和它們的特性所關(guān)聯(lián), 包括 ops 字節(jié).

通過(guò)"硬件操作"數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)硬件抽象在 Linux 內(nèi)核中是典型的. 一個(gè)重要的例子是 struct alpha_machine_vector 數(shù)據(jù)結(jié)構(gòu). 它定義于 <asm-alpha/machvec.h> 和負(fù)責(zé)任何可能的跨不同基于 Alpha 的計(jì)算機(jī)的改變.

SBus

[40] 一些體系也顯示 PCI 域信息在 /proc/pci 和 /proc/bus/pci 文件.

[41] 實(shí)際上, 那個(gè)配置不限定在系統(tǒng)啟動(dòng)時(shí); 可熱插拔的設(shè)備, 例如, 在啟動(dòng)時(shí)不可用并且相反在之后出現(xiàn). 這里的要點(diǎn)是設(shè)備啟動(dòng)必須不改變 I/O 或者內(nèi)存區(qū)的地址.

[42] 你將在設(shè)備自己的硬件手冊(cè)里發(fā)現(xiàn)它的 ID. 在文件 pci.ids 中包含一個(gè)列表, 這個(gè)文件是 pciutils 軟件包和內(nèi)核代碼的一部分; 它不假裝是完整的, 只是列出最知名的供應(yīng)商和設(shè)備. 這個(gè)文件的內(nèi)核版本將來(lái)不會(huì)被包含在內(nèi)核系列中.

[43] 信息位于一個(gè)基地址 PCI 寄存器的低位. 這些位定義在 <linux/pci.h>.

以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)