linux中PCI匯流排驅動
歡迎轉載!
一.理論知識
1. PCI匯流排的特點:
(1)速度快,時鐘頻率提高到33M,而且還為進一步把時鐘頻率提高到66MHZ、匯流排頻寬提高到64位留下了餘地。(2)對於地址的分配和設定,系統軟體自動設定,每塊外設通過某種途徑告訴系統該外設有幾個儲存區間和I/O地址區間,每個區間的大小以及本地地址。系統軟體知道了總共有多少外設以及各種的儲存空間後就會統一為外設分配實體地址。(3)對於匯流排的競爭,PCI總線上配備了一個仲裁器,遇到衝突,仲裁器會選擇其中之一暫時成為當前的主裝置,而其他只能等待。同時考慮到這樣的效率問題,PCI匯流排為寫提綱了緩衝,故寫比讀會快。(4
2. PCI裝置概述
每個PCI裝置有許多地址配置的暫存器,初始化時要通過這些暫存器來配置該裝置的匯流排地址,一旦完成配置以後,CPU就可以訪問該裝置的各項資源了。PCI標準規定每個裝置的配置暫存器組最多可以有256個連續的位元組空間,開頭64個位元組叫頭部,分為0型(PCI裝置)和1型(PCI橋)頭部,頭部開頭16
裝置的配置暫存器組採用相同的地址,由所在匯流排的PCI橋在訪問時附加上其他條件區分,對於I386處理器,有兩個32位暫存器,0XCF8為地址暫存器,0XCFC為資料暫存器。地址暫存器寫入的內容包括匯流排號,裝置號,功能號。邏輯地址(XX:YY.Z),XX表示PCI匯流排號,最多256個匯流排。YY表示PCI裝置號,最多32個裝置。Z表示PCI裝置功能號,最多8個功能。
3. 查詢PCI匯流排和裝置的命令
檢視PCI匯流排和PCI裝置組成的樹狀圖 lspci –t
檢視配置區的情況 lspci –x,注意PCI暫存器是小端格式
4. PCI匯流排架構
所有的根匯流排都連結在pci_root_buses連結串列中。Pci_bus ->device連結串列連結著該匯流排下的所有裝置。而pci_bus->children連結串列連結著它的下層匯流排,對於pci_dev來說,pci_dev->bus指向它所屬的pci_bus. Pci_dev->bus_list連結在它所屬bus的device連結串列上。此外,所有pci裝置都連結在pci_device連結串列中。
二.PCI驅動
1. PCI定址空間
PCI裝置包括殺個定址空間:配置空間,I/O埠空間,記憶體空間。
1.1 PCI配置空間:
核心為驅動提供的函式:
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)
配置空間的偏移定義在include/linux/pci_regs.h
1.2 PCI的I/O和記憶體空間:
從配置區相應暫存器得到I/O區域的基址:
pci_resource_start(struct pci_dev *dev, int bar) Bar值的範圍為0-5。
從配置區相應暫存器得到I/O區域的記憶體區域長度:
pci_resource_length(struct pci_dev *dev, int bar) Bar值的範圍為0-5。
從配置區相應暫存器得到I/O區域的記憶體的相關標誌:
pci_resource_flags(struct pci_dev *dev, int bar) Bar值的範圍為0-5。
申請I/O埠:
request_mem_region(io_base, length, name)
讀寫:
inb() inw() inl() outb() outw() outl()
2. PCI匯流排支援的裝置
PCI驅動程式向PCI子系統註冊其支援的廠家ID,裝置ID和裝置類編碼。使用這個資料庫,插入的卡通過配置空間被識別後,PCI子系統把插入的卡和對應的驅動程式繫結。
PCI裝置列表
struct pci_device_id {
__u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/
__u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
__u32 class, class_mask; /* (class,subclass,prog-if) triplet */
kernel_ulong_t driver_data; /* Data private to the driver */
};
注意:如果可以處理任何情況,可將相應的暫存器設定為PCI_ANY_ID。
3. PCI驅動其他API
獲取驅動私有資料:pci_get_drvdata();
使能PCI裝置:pci_enable_device()
匯流排主DMA模式設定:pci_set_master()
4. *******
三.PCI驅動模型
一個通過PCI匯流排與系統連線的裝置的驅動主要包括兩部分:第一PCI驅動,第二,裝置本身的驅動,包括字元裝置,網路裝置,tty裝置,音訊裝置等。PCI驅動的核心是pci_driver,在探測函式中完成資源的申請,並註冊相應的字元裝置,網路裝置,tty裝置,音訊裝置等。
static struct pci_device_id buttons_pci_tbl[] __initdata={
{PCI_ANY_ID,PCI_ANY_ID,PCI_ANY_ID,PCI_ANY_ID,0,0,0},
{0,}
}; //PCI裝置支援項
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
//中斷處理程式
}
static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
}
static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
}
static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = s3c24xx_buttons_open,
.release = s3c24xx_buttons_close,
.read = s3c24xx_buttons_read,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
static int pci_key__probe (struct pci_dev *pdev, const struct pci_device_id *ent)
{
int ret;
pci_enable_device(pdev); //使能PCI裝置
pci_set_master(pdev);
ret = misc_register(&misc); //註冊雜項裝置
printk (DEVICE_NAME"\tinitialized\n");
return ret;
}
static int pci_key__remove (struct pci_dev *pdev, const struct pci_device_id *ent)
{
pci_disable_device(pdev);
misc_deregister(&misc);
return 0;
}
static struct pci_driver pci_key_driver = {
.name = "pci_key",
.id_table =buttons_pci_tbl,
.probe = pci_key__probe,
.remove = pci_key__remove,
};
static int __init dev_init(void)
{
return pci_register_driver(&pci_key_driver);
}
static void __exit dev_exit(void)
{
pci_unregister_driver(&pci_key_driver);
}
module_init(dev_init);
module_exit(dev_exit);
四.PCI裝置的列舉過程
基於Mini2440開發板,在此,我只分析linux2.6.32.2核心的PCI裝置列舉過程
pci的程式碼分為兩個部份.一個部份是與平臺相關的部份.存放在linux2.6.32.2\arch\x86\pci\ ,另一個部份是平臺無關的程式碼,存放在linux2.6.32.2\driver\pci\下面.
我們從與平臺相關的部份.存放在linux2.6.32.2\arch\x86\pci\ Makefile看起,
obj-y := i386.o init.o
obj-$(CONFIG_PCI_BIOS) += pcbios.o
obj-$(CONFIG_PCI_MMCONFIG) += mmconfig_$(BITS).o direct.o mmconfig-shared.o
obj-$(CONFIG_PCI_DIRECT) += direct.o
obj-$(CONFIG_PCI_OLPC) += olpc.o
obj-y += fixup.o
obj-$(CONFIG_ACPI) += acpi.o
obj-y += legacy.o irq.o
obj-$(CONFIG_X86_VISWS) += visws.o
obj-$(CONFIG_X86_NUMAQ) += numaq_32.o
obj-y += common.o early.o
obj-y += amd_bus.o
由這個Malefile我們可以知道init.c是一定被編譯的,那麼我們就看看這個init.c,在這個檔案裡面只有pci_arch_init函式,那麼我們就看看。
static __init int pci_arch_init(void)
{
#ifdef CONFIG_PCI_DIRECT //直接進行PCI裝置的探測和列舉
int type = 0;
type = pci_direct_probe();
#endif
if (!(pci_probe & PCI_PROBE_NOEARLY))
pci_mmcfg_early_init();
#ifdef CONFIG_PCI_OLPC
if (!pci_olpc_init())
return 0; /* skip additional checks if it's an XO */
#endif
#ifdef CONFIG_PCI_BIOS //通過BIOS進行PCI裝置的探測和列舉
pci_pcbios_init();
#endif
#ifdef CONFIG_PCI_DIRECT //直接進行PCI裝置的探測和列舉
pci_direct_init(type);
#endif
if (!raw_pci_ops && !raw_pci_ext_ops)
printk(KERN_ERR
"PCI: Fatal: No config space access function found\n");
dmi_check_pciprobe();
dmi_check_skip_isa_align();
return 0;
}
arch_initcall(pci_arch_init);
首先要說明下,這個函式由arch_initcall(pci_arch_init)呼叫,而arch_initcall引用的函式都會放在init區域,這裡面的函式是kernel啟動的時候會自己執行的函式。
好了,我們現在看看這個pci_arch_init函式吧,咋一眼裡面內容好多,CONFIG_PCI_BIOS表示通過BIOS進行PCI裝置的探測和列舉; CONFIG_PCI_DIRECT表示直接進行PCI裝置的探測和列舉,為了突出重點,我們分析CONFIG_PCI_DIRECT的過程.把其它不相關的程式碼略掉.剩餘的就簡單了。
首先看pci_direct_probe()函式,在pci規範中定義了兩種操作配置空間的方法,即“1型”和“2型”,通常會使用“1型”。因此,在程式碼中pci_direct_probe()一般會返回1,即使用“1型”。
那剩下來只有pci_direct_init函式需要分析的了,繼續看
void __init pci_direct_init(int type)
{
if (type == 0)
return;
printk(KERN_INFO "PCI: Using configuration type %d for base access\n",
type);
if (type == 1) {
raw_pci_ops = &pci_direct_conf1;
if (raw_pci_ext_ops)
return;
if (!(pci_probe & PCI_HAS_IO_ECS))
return;
printk(KERN_INFO "PCI: Using configuration type 1 "
"for extended access\n");
raw_pci_ext_ops = &pci_direct_conf1;
return;
}
raw_pci_ops = &pci_direct_conf2;
}
在這個函式中,我們只關注“1型”情況,我們發現只是把raw_pci_ops = &pci_direct_conf1,那麼我們就要看看pci_direct_conf1是什麼了,繼續跟蹤
struct pci_raw_ops pci_direct_conf1 = {
.read = pci_conf1_read,
.write = pci_conf1_write,
};
這個結構其實就是pci裝置配置空間操作的介面。好了,分析到這裡,我們知道給raw_pci_ops賦了新值,但僅此而已,這個新值怎麼用,我們不知道,路戛然而止。那麼我們就繼續看這個linux2.6.32.2\arch\x86\pci\ Makefile內容,我們發現除了init.c,還有其他幾個檔案是預設必須被編譯的,包括i386.o, init.o,fixup.o,legacy.o, irq.o
我們開啟這幾個檔案發現只有legacy.c裡面有個系統呼叫subsys_initcall(pci_subsys_init),我們知道這個系統呼叫也會在核心啟動時被自動呼叫,同時因為subsys_initcall比上面我們講的arch_initcal優先順序低,所以,我們應該會在這個檔案legacy.c裡找到上面我們提出的疑問,即raw_pci_ops怎麼用?
好了,我們在legacy.c中搜索raw_pci_ops便發現了pci_legacy_init函式,而這個函式由於被pci_subsys_init呼叫而會在核心啟動時候被呼叫。那麼我們下面的任務就是看看了pci_legacy_init函數了。
static int __init pci_legacy_init(void)
{
if (!raw_pci_ops) {
printk("PCI: System does not support PCI\n");
return 0;
}
if (pcibios_scanned++)
return 0;
printk("PCI: Probing PCI hardware\n");
pci_root_bus = pcibios_scan_root(0);
if (pci_root_bus)
pci_bus_add_devices(pci_root_bus);
return 0;
}
很明顯根據printk的提示,我們知道pcibios_scan_root(0)是探測PCI裝置的,那麼0的含義自然就是PCI根匯流排了。
好,下面我們就好好研究研究這個PCI自動探測過程了。
struct pci_bus * __devinit pcibios_scan_root(int busnum)
{
struct pci_bus *bus = NULL;
struct pci_sysdata *sd;
while ((bus = pci_find_next_bus(bus)) != NULL) {
if (bus->number == busnum) {
/* Already scanned */
return bus;
}
}
/* Allocate per-root-bus (not per bus) arch-specific data.
* TODO: leak; this memory is never freed.
* It's arguable whether it's worth the trouble to care.
*/
sd = kzalloc(sizeof(*sd), GFP_KERNEL);
if (!sd) {
printk(KERN_ERR "PCI: OOM, not probing PCI bus %02x\n", busnum);
return NULL;
}
sd->node = get_mp_bus_to_node(busnum);
printk(KERN_DEBUG "PCI: Probing PCI hardware (bus %02x)\n", busnum);
bus = pci_scan_bus_parented(NULL, busnum, &pci_root_ops, sd);
if (!bus)
kfree(sd);
return bus;
}
我們可以發現pcibios_scan_root函式剛開始尋找這個匯流排號是否被遍歷過,如果是,直接退出,接下來分配一個sd,然後呼叫pci_scan_bus_parented完成真正的PCI裝置探測,繼續看。
struct pci_bus * __devinit pci_scan_bus_parented(struct device *parent,
int bus, struct pci_ops *ops, void *sysdata)
{
struct pci_bus *b;
b = pci_create_bus(parent, bus, ops, sysdata);
if (b)
b->subordinate = pci_scan_child_bus(b);
return b;
}
在pci_scan_bus_parented裡,在這個函式裡我們完成兩件事,第一件事是根據匯流排號建立pci_bus結構體,然後將這個結構體加入匯流排連結串列pci_root_buses中,這由pci_create_bus完成的。第二件事是遍歷這個匯流排下的所有PCI裝置,返回PCI匯流排的下屬PCI匯流排的匯流排編號的最大值,這由pci_scan_child_bus完成的。
我們先看第一件事
struct pci_bus * pci_create_bus(struct device *parent,
int bus, struct pci_ops *ops, void *sysdata)
{
int error;
struct pci_bus *b;
struct device *dev;
b = pci_alloc_bus(); //分配pci_bus結構體
if (!b)
return NULL;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev){
kfree(b);
return NULL;
}
b->sysdata = sysdata;
b->ops = ops;
if (pci_find_bus(pci_domain_nr(b), bus)) {
/* If we already got to this bus through a different bridge, ignore it */
pr_debug("PCI: Bus %04x:%02x already known\n", pci_domain_nr(b), bus);
goto err_out;
}
down_write(&pci_bus_sem);
list_add_tail(&b->node, &pci_root_buses); //將pci_bus加入匯流排pci_root_buses連結串列
up_write(&pci_bus_sem);
dev->parent = parent;
dev->release = pci_release_bus_bridge_dev;
dev_set_name(dev, "pci%04x:%02x", pci_domain_nr(b), bus);
error = device_register(dev);
if (error)
goto dev_reg_err;
b->bridge = get_device(dev);
if (!parent)
set_dev_node(b->bridge, pcibus_to_node(b));
b->dev.class = &pcibus_class;
b->dev.parent = b->bridge;
dev_set_name(&b->dev, "%04x:%02x", pci_domain_nr(b), bus);
error = device_register(&b->dev);
if (error)
goto class_dev_reg_err;
error = device_create_file(&b->dev, &dev_attr_cpuaffinity);
if (error)
goto dev_create_file_err;
/* Create legacy_io and legacy_mem files for this bus */
pci_create_legacy_files(