linux 内核PCI驱动总结记录
1.??介紹
Peripheral ComponentInterconnect (PCI,外圍設備互聯)。總線由電氣接口、編程接口組成。主要討論編程接口。最常用的總線,內核支持最好的總線。ISA裸金屬總線,電子愛好者偏愛。
2.??PCI的特點
是一種完整的規范,定義計算機計算機不同部分之間的通信。
?
獲取、訪問PCI設備。
?
對比ISA總線三個目標:
比ISA有更好的性能。
盡可能平臺無關的。
簡化系統添加、刪除外設。
?
支持32位、64位數據總線。
對驅動編寫者最相關的是接口板的自動發現。PCI設備是無跳線設備,在系統引導階段自動配置。包括設備的配置信息等一些工作都是自動完成的,不需要任何的探測。之后,驅動編寫者就能夠訪問設備的配置信息,以便初始化設備。
?
3.??PCI尋址
每個PCI外設由bus:device.function一個16位地址標識。bus(8位)、device(5位)、function(3位)。單個總共256個總線、每個總線最多32個設備、每個設備最多8個功能(比如聲音功能)。linux為了擴展總線數量,提供domain(16位)。系統把PCI設備抽象為pci_dev結構,因此不需要訪問這些二進制地址。
?
當前的工作站一般都配有2個以上的pci總線。不同PCI總線之間通過PCI橋連接(一個特殊的PCI設備)。PCI系統的整體布局是一個樹狀的結構。每個總線都連接上一級總線,一直到根總線0.
可以使用lspci命令查看當前系統的pci。或者在文件系統/proc/pci和/proc/bus/pci中。
外設板電路響應三種地址空間:內存、IO、配置空間。前兩種地址空間在同一個PCI總線上是共享的。配置空間是物理尋址的,每次只對一個槽尋址。
內存和IO空間通常通過inb、readb等方式訪問。配置空間需要通過特殊的內核函數訪問配置寄存器。每個PCI槽有4個中斷引腳,每個設備功能使用其中的一個。
???????? 1個PCI總線使用32位的地址總線用于IO尋址(4G),32位的地址總線(現在設備有的支持64位)用于內存尋址。在系統啟動階段,固件初始化PCI硬件的時候,把每個區域映射到不同的地址。驅動程序不需要探測,而從配置空間讀取映射的地址。
???????? 對于每個設備功能,PCI配置空間由256字節組成(PCIE的是64KB),并且配置空間的布局是標準的。配置空間的4個字節(哪4個字節?)標識唯一的功能ID。
?
4.??引導階段
主板上的固件(比如BIOS),讀寫PCI設備中的寄存器,訪問配置空間。
系統引導階段,linux內核為每個PCI地址區域申請安全的處理器地址。后續驅動可以從/sys/bus/pci/devices/*目錄中讀取映射的地址。
$ tree /sys/bus/pci/devices/0000:00:10.0
/sys/bus/pci/devices/0000:00:10.0
|-- class
|-- config
|-- detach_state
|-- device
|-- irq
|-- power
| ?`-- state
|-- resource
|-- subsystem_device
|-- subsystem_vendor
`-- vendor
5.??配置寄存器和初始化
所有的PCI設備至少包含256字節的配置地址空間(PCIE是64KB)。其中前64字節是標志的。
PCI配置寄存器包括可選和必需兩部分,必需的部分聲明功能和其他字段是否可用。
PCI寄存器是小端字節序。
- vendorID
全局性、全球性。16位標識。比如intel的0x8086.
- deviceID
廠商定義的16位標識。通常使用vendorID+deviceID 32位標識一個設備。驅動根據該32位標識,定位到一個設備。
- class
16位的標識,高8位標識基本類(group)。比如,以太網、令牌環網屬于網絡group,串行、并行屬于通信group。一些驅動支持多種相同類型的設備,驅動可以根據類型區分支持的設備。
- subsystem vendorID
- subsystem deviceID
subsystem類型的標識,用于進一步識別設備。當一個芯片是連接到本地板載上的通用芯片時,它可能有多用用途。驅動使用subsystem標識,識別具體連接的設備。
內核標識設備ID的結構是:
struct pci_device_id {
???????? __u32vendor, device;?????????????? /* Vendorand device ID or PCI_ANY_ID*/
???????? __u32subvendor, subdevice;? /* Subsystem ID'sor PCI_ANY_ID */
???????? __u32class, class_mask; /*(class,subclass,prog-if) triplet */
???????? kernel_ulong_tdriver_data;?? /* Data private to thedriver */
};6.??MODULE_DEVICE_TABLE
通過把pci_device_id結構導出到用戶空間中,使熱插拔和模塊加載系統知道什么模塊對應什么設備。
例如:
MODULE_DEVICE_TABLE(pci, i810_ids);
具體的實現是:
extern const typeof(name)__mod_##type##__##name##_device_table????????????? \
?__attribute__ ((unused, alias(__stringify(name))))
?
其中pci是模塊名,i810_ids是pci_device_id變量名。MODULE_DEVICE_TABLE宏把例如i810_ids的變量名,起一個__mod_pci_device_table結構的別名。模塊編譯之后,在對應的模塊ELF文件中會有相應的__mod_pci_device_table結構符號。在內核構建時,depmod搜索所有模塊的類似__mod_pci_device_table結構的符號,從中解析出type和name,并取出pci_device_id數據導出到/lib/modules/KERNEL_VERSION/modules.pcimap文件中。之后內核所有模塊支持的設備和模塊的名字可在該文件中找到。當內核告知熱插拔系統,發現一個新的設備時,熱插拔系統根據modules.pcimap文件找到對應的驅動。
?
注:模塊的概念。PCI是一個模塊。
?
7.??PCI驅動注冊
為了正確的注冊到內核,PCI驅動必須創建一個結構:
struct pci_driver {
??? struct list_head node;
??? const char *name;
??? const struct pci_device_id *id_table;??/* must be non-NULL for probe to be called*/
??? int? (*probe)?(struct pci_dev*dev,const struct pci_device_id*id);??/* New device inserted */
??? void (*remove)(struct pci_dev*dev);??/* Device removed (NULL if not a hot-plugcapable driver) */
??? int? (*suspend)(struct pci_dev*dev, pm_message_tstate);?/* Device suspended */
??? int? (*suspend_late)(struct pci_dev*dev, pm_message_tstate);
??? int? (*resume_early)(struct pci_dev*dev);
??? int? (*resume)(struct pci_dev*dev);??????????????????/* Device woken up */
??? void (*shutdown)(struct pci_dev*dev);
??? int (*sriov_configure)(struct pci_dev*dev,int num_vfs);/* PF pdev */
??? const struct pci_error_handlers *err_handler;
??? struct device_driver??? driver;
??? struct pci_dynids dynids;
};
包括一些回調函數和描述PCI驅動與PCI核心對應的變量。
其中一些區域需要PCI驅動注意。
const char*name;
const struct pci_device_id*id_table;
int? (*probe)?(struct pci_dev*dev,const struct pci_device_id*id);
void (*remove)(struct pci_dev*dev);
int? (*suspend)(struct pci_dev*dev, pm_message_tstate);
int? (*resume)(struct pci_dev*dev);
總的來說,一個PCI驅動結構只需要4個區域被初始化。
static struct pci_driver pci_driver = {
.name ="pci_skel",
.id_table = ids,
.probe = probe,
.remove =remove,
};
通常在模塊初始化代碼中,注冊pci驅動。比如:
static int __init pci_skel_init(void)
{
return pci_register_driver(&pci_driver);
}
2.6更新后,在支持PCI熱插拔、或CardBus系統上,PCI設備可以出現在任何時刻。
在系統運行時刻,通過寫值到驅動的new_id中,指定驅動支持的新設備(原內核未認知的設備)。
當PCI驅動被卸載時,需要調用pci_unregister_driver。例如:
static void __exit pci_skel_exit(void)
{
pci_unregister_driver(&pci_driver);
}
8.??使能PCI設備
在PCI的探測函數中,在驅動訪問PCI設備的任何資源之前(IO區域或資源),驅動程序必須調用函數:
int pci_enable_device(struct pci_dev *dev);
用來激活設備。
9.??訪問配置空間
在驅動監測到設備之后,通常需要訪問三個區域:內存、IO區域、配置空間。
訪問配置空間尤其重要,因為需要通過配置空間找到內存區域映射和IO區域映射。
linux提供了一套訪問配置空間的標準接口。
對驅動而言,可通過8、16、32位數據傳輸訪問配置空間。相關的函數定義在<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:訪問設備的邏輯表示
where:要讀取位置在配置空間中的位移
*val:讀取的值
不需要考慮字節序,會自動轉換。
?
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);
dev:寫入設備的邏輯表示
where:要寫入位置在配置空間中的位移
*val:寫入的值
不需要考慮字節序,會自動轉換。
?
在驅動未獲得pci_dev時,可使用如上函數讀寫配置空間
int pci_bus_read_config_byte (structpci_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);
?
int pci_bus_write_config_byte (structpci_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);
?
訪問配置空間的最好方式是通過pci_read_系列函數,例如:
static unsigned charskel_get_revision(struct pci_dev *dev)
{
u8 revision;
pci_read_config_byte(dev,PCI_REVISION_ID, &revision);
return revision;
}
10.??訪問IO和內存空間
一個PCI設備最多可實現6個IO地址區域。每個區域可以是內存或者IO地址。大多數設備在內存區域實現IO寄存器,這也是一個明智的方法。需要注意的是,和常規內存不同,IO寄存器不應該由CPU緩存,因為每次訪問都可能邊緣效應。為了取消這個默認設置,內存區域實現的IO寄存器,可以通過在其配置寄存器中設置“memory-is-prefetchable”。若是可預取的,CPU可緩存其內容并進行各種優化。若不是可預取的,則不能優化,因為每次訪問都有邊際效應,就行IO端口一樣。
?
接口板(PCI設備)通過6個32位的寄存器(PCI_BASE_ADDRESS_0到PCI_BASE_ADDRESS_5)聲明區域的大小和位置。所以最多實現6個IO地址區域。因為PCI的IO地址空間是32位的,所以不管是內存或IO區域使用相同的配置接口是有道理的。如果設備的數據總線是64位的,那么每個區域使用兩個連續的32位寄存器實現。一個PCI設備既提供32位區域又提供64位區域是有可能的。
?
內核已經把PCI設備的IO區域信息映射進了通用資源管理中。所以,不需要通過訪問配置寄存器來獲取IO區域信息。可以通過訪問/sys/bus/pci/devices/*/resource的內容獲取。但首選的方法是通過下列函數獲取。
unsigned long pci_resource_start(structpci_dev *dev, int bar);
unsigned long pci_resource_end(structpci_dev *dev, int bar);
bar:指定要獲取的區域(0到5)
?
unsigned long pci_resource_flags(structpci_dev *dev, int bar);
資源flag用來定義某個區域的特性。其中幾個重要的標志如下:
IORESOURCE_IO
IORESOURCE_MEM
IORESOURCE_PREFETCH
IORESOURCE_READONLY(PCI資源從不設置該標志)
?
驅動程序不需要訪問配置寄存器去獲得這些資源信息,因為系統已經構建了這些資源信息,驅動直接使用pci_resource_系列函數獲取即可。
11.??PCI中斷
在linux系統啟動時,已經為PCI設備分配了一個唯一的中斷號,位于配置空間的第60寄存器(PCI_INTERRUPT_LINE),一個字節長度,最多256個中斷號。第61個寄存器(PCI_INTERRUPT_PIN)說明PCI設備是否支持中斷,如果不支持,則為0,如果支持,非0.
如果是非0的,PCI_INTERRUPT_PIN的值是中斷引腳的編號()。
驅動通過下面代碼讀取中斷號,以便使用:
result = pci_read_config_byte(dev,PCI_INTERRUPT_LINE, &myirq);
if (result) {
/* deal witherror */
}
?
12.??總結歸納
linux中:
先module初始化,內含pci初始化(對于pci設備而言)總結
以上是生活随笔為你收集整理的linux 内核PCI驱动总结记录的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Oracle中rank函数详解
- 下一篇: Docker 安装及镜像加速器配置