<背景>
PCI設備有許多地址配置的寄存器,初始化時這寄存器來配置設備的總線地址,配置好后CPU就可以訪問該設備的各項資源了。(提煉:配置總線地址)
<配置寄存器>
(1)256字節的PCI配置空間分為64字節的頭標區和192字節的設備相關區兩部分。頭標區的各個寄存器用來唯一地識別設備;設備相關區則保存一些與設備相關的數據。
(2)配置空間的頭標區又分為兩部分:前16個字節的定義在各種類型的PCI設備中都是一樣的;剩余的字節隨設備類型不同而有所不同。位於偏移地址0EH處的頭標類型字段規定了頭標區的布局結構。目前,規范定義了三種頭標類型。
a:設備的識別
(1) 供應商代碼:該寄存器用於識別PCI設備的制造商,具體代碼由PCI SIG(http://www.pcisig.com)分配。0FFFFH是無效的供應商代碼。
(2) 設備代碼。該寄存器用來標識某供應商生產的具體設備,代碼由各供應商定義。供應商代碼和設備代碼,讀者可以到網站
http://www.pcidatabase.com/查閱。
(3) 版本號。該寄存器用來定義指定設備的版本信息。
(4) 頭標類型。該字段的第7位為“1”標識該設備是多功能設備,為“0”標識為單功能設備;該字段的0~6位就是上文表中所述的頭標類型。
(5) 設備分類代碼。用來標識設備的總體功能和特定的寄存器級編程接口。
上面5個字段均為只讀類型,所有的PCI設備都必須實現其功能。
b:設備控制和設備狀態
(1) 命令寄存器為一個設備發出和響應PCI總線命令提供粗略的控制。圖4就是命令寄存器格式。
位0(I/O空間控制):控制對I/O空間訪問的響應。該位為0時,禁止設備響應對I/O空間的訪問;該位為1時,允許設備響應I/O空間的訪問。缺省設置為0。
位1(存儲器空間控制):控制一個設備對存儲器空間訪問的響應。該位為0時,禁止響應;該位為1時,允許設備響應對存儲器空間的訪問。缺省設置為0。
狀態寄存器用來記錄PCI總線有關的狀態信息。
c:基址寄存器
PCI設備中,除了配置空間外,還有兩個物理空間:內存空間和I/O空間。為了訪問這兩個地址空間,就必須使用基址寄存器。頭標類型0中涉及3種基址寄存器:內存空間基址寄存器、I/O空間基址寄存器和擴展ROM基址寄存器。
d:其他寄存器
其他寄存器包括一些本文不涉及到的寄存器,如中斷引腳、中斷線等等。
<PCI配置空間的訪問>
a:PCI規范使用從0CF8H~0CFFH 這8個I/O地址來訪問所有設備的PCI配置空間。這8個字節實際上構成了兩個32位寄存器:0CF8H寄存器叫做“配置地址寄存器”;0CFCH叫做“配置數據寄存器”。當要訪問配置空間的寄存器時,先向地址寄存器寫上目標地址,然后就可以從數據寄存器中讀寫數據了。
PCI配置空間對應於一個PCI邏輯設備,所以要訪問一個配置空間的某個寄存器,必須要指定:PCI總線號、PCI設備號、PCI設備功能號和寄存器號。配置地址寄存器的格式如下:
第0、1位上的“0”是用來要求你只能按雙字(4字節)來讀寫配置空間寄存器。第31位“使能位”用來決定是否允許訪問配置空間:為“1”時表示可以訪問;為“0”時表示不可以訪問。
從上面的配置地址寄存器的格式我們可以看出:總線號從0~255、設備號從0~31、功能號從0~7。根據配置空間的第0個寄存器是否返回0FFFFH值來判斷是否存在該PCI設備(這里可以看出PCI為什支持32個設備)
<PCI驅動開發概總>
對於驅動開發人員來說,pci具有如下吸引人的優勢:
a:設備自動配置系統,與舊的ISA驅動程序不一樣,pci驅動不需要實現復雜的檢測邏輯。
b:系統啟動時,BIOS(如果是嵌入式系統內核本身會完成該任務)會遍歷pci總線並分配資源(比如中斷優先級,I/O基地址)
c: 設備去驅動程序會查詢叫做"PCI配置空間"的內存來找到資源分配情況
d:PCI設備總共具有256B的配置空間內存。配置空間 頂部64B空間的含義是標准的,所有設備的配置在這段區域都是相似的。該空間被分成狀態,I/O基地址,中斷線。
<訪問PCI>
a:內核函數
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)
形參分析:
pdev:指向PCI設備的結構體
offset:配置空間的偏移地址
value:需要寫入或讀出數據的存放位置
舉例:
unsigned char irq;
pci_read_config_byte(pdev,PCI_INTERRUPT_LINE,&irq);
注意:在配置空間中的中斷號的偏移是60,這為什么不使用60,是因為在Linux內核中/include/linux/pci_regs.h進行了定義。
<I/O和內存>
I/O訪問
a:要訪問一個PCI設備的I/O空間或內存空間在內存或I/O區域的映射。需要讀取配置空間的相應基地址寄存器里得到I/O區域的基地址。
(1)從配置區域相應基址寄存器得到I/O區域的基地址
unsigned long io_base = pci_resource_start(pdev,bar)
注意:該函數還有相應的變形
unsigned long pci_resource_[start|lenght|flags](struct pci_dev*pdev, int bar)
(2)調用內核函數request_region()獲得這個IO區域,標明這片區域對應的設備
request_region(io_base,length,"mydriver")
(3)這樣就可以使用I/O操作函數訪問這些寄存器了,
inl();
outl();
內存訪問
(1)調用該函數獲得內存基地址
unsigned long pci_resource_[start|lenght|flags](struct pci_dev*pdev, int bar)
(2)調用內核函數request_mem_region()獲得這個內存區域,標明這片區域對應的設備
request_region(mmio_base,mmio_length,"mydriver")
(3)將獲得內存地址轉換成虛擬地址
buffer = pci_iomap(pdev, bar,mmio_length)
<驅動實例>
a當PCI熱插拔檢測到新插入的設備的ID屬性和驅動程序中的pci_device_id表里的ID信息一致的時候。該層將激發該驅動程序的probe()函數被調用。進一步注冊相應的設備驅動。可以看出首先得要注冊pci_driver程序:
調用函數pci_register_drivet()
b: