12.1. PCI 接口

2018-02-24 15:50 更新

12.1.1.?PCI 尋址

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

一個使用這些宏來定義一個驅(qū)動支持的設(shè)備類型的例子, 在下面的內(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)建一個 struct pci_device_id 結(jié)構(gòu)的列表, 列表中最后一個是被設(shè)置為全零的的空結(jié)構(gòu). 這個 ID 的數(shù)組用在 struct pci_driver (下面講述), 并且它還用來告訴用戶空間這個特定的驅(qū)動支持哪個設(shè)備.

12.1.4.?MODULEDEVICETABLE 宏

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


MODULE_DEVICE_TABLE(pci, i810_ids); 

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

12.1.5.?注冊一個 PCI 驅(qū)動

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

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

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ū)動中 probe 函數(shù)的指針. 這個函數(shù)被 PCI 核心調(diào)用, 當它有一個它認為這個驅(qū)動想控制的 struct pci_dev 時. 一個指向 struct pci_device_id 的指針, PCI 核心用來做這個決定的, 也被傳遞給這個函數(shù). 如果這個 PCI 驅(qū)動需要這個傳遞給它的 struct pci_dev, 它應(yīng)當正確初始化這個設(shè)備并且返回 0. 如果這個驅(qū)動不想擁有這個設(shè)備, 或者產(chǎn)生一個錯誤, 它應(yīng)當返回一個負的錯誤值. 關(guān)于這個函數(shù)的更多的細節(jié)在本章后面.

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

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

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

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


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

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


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

注意, pci_register_driver 函數(shù)要么返回一個負的錯誤碼, 要么是 0 當所有都成功注冊. 它不返回綁定到驅(qū)動上的設(shè)備號,或者一個錯誤碼如果沒有設(shè)備被綁定到驅(qū)動上. 這是自 2.6 發(fā)布之前的內(nèi)核的一個改變, 并且是因為下列的情況而來的:

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

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

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


static void __exit pci_skel_exit(void)
{

 pci_unregister_driver(&pci_driver);
}

12.1.6.?老式 PCI 探測

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

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

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

一個如何正確使用這個函數(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);
}

這個函數(shù)不能從中斷上下文中被調(diào)用. 如果這樣做了, 一個警告被打印到系統(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);
這個函數(shù)就象 pci_get_device 一樣, 但是它允許當尋找設(shè)備時指定子系統(tǒng)供應(yīng)商和子系統(tǒng)設(shè)備 ID. 這個函數(shù)不能從中斷上下文調(diào)用. 如果是, 一個警告被打印到系統(tǒng)日志.

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

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

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

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

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

12.1.8.?存取配置空間

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

因為微處理器無法直接存取配置空間, 計算機供應(yīng)商不得不提供一個方法來完成它. 為存取配置空間, CPU 必須寫和讀 PCI 控制器中的寄存器, 但是確切的實現(xiàn)是依賴于供應(yīng)商的, 并且和這個討論無關(guān), 因為 Linux提供了一個標準接口來存取配置空間.

對于驅(qū)動, 配置空間可通過8-位, 16-位, 或者 32-位數(shù)據(jù)傳輸來存取. 相關(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 所標識出的設(shè)備的配置空間讀 1 個, 2 個或者 4 個字節(jié). where 參數(shù)是從配置空間開始的字節(jié)偏移. 從配置空間取得的值通過 val 指針返回, 并且這個函數(shù)的返回值是一個錯誤碼. 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);
寫 1 個, 2 個或者 4 個字節(jié)到配置空間. 象通常一樣, 設(shè)備由 dev 所標識, 并且象通常一樣被寫的值被傳遞. word 和 dword 函數(shù)轉(zhuǎn)換這個值到小端, 在寫到外設(shè)之前.

所有的之前的函數(shù)被實現(xiàn)為真正調(diào)用下列函數(shù)的內(nèi)聯(lián)函數(shù). 可自由使用這些函數(shù)代替上面這些, 如果這個驅(qū)動在任何特別時刻不能及時存取 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 變量需要來代替 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 變量需要來替代 struct pci_dev *.

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


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)存空間

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

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

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

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

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

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

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

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

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

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

通過使用 pciresource 函數(shù), 一個設(shè)備驅(qū)動可完全忽略底層的 PCI 寄存器, 因為系統(tǒng)已經(jīng)使用它們來構(gòu)造資源信息.

12.1.10.?PCI 中斷

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

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

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


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

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

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

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

PCI_INTERRUPT_LINE 寄存器, 另一方面, 是讀/寫的. 當啟動計算機, 固件掃描它的 PCI 設(shè)備并為每個設(shè)備設(shè)置寄存器固件中斷腳是如何連接給它的 PCI 槽位. 這個值由固件分配, 因為只有固件知道主板如何連接不同的中斷腳到處理器. 對于設(shè)備驅(qū)動, 但是, PCI_INTERRUPT_LINE 寄存器是只讀的. 有趣的是, 近期的 Linux 內(nèi)核版本在某些情況下可分配中斷線, 不用依靠 BIOS.

12.1.11.?硬件抽象

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

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

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


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);
};

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

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


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

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

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

SBus

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

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

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

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號