pci驱动框架
1.PCI ?設(shè)備。符合 PCI 總線標(biāo)準(zhǔn)的設(shè)備就被稱為 PCI 設(shè)備,PCI ?總線架構(gòu)中可以包含多個(gè) PCI 設(shè)備。圖中的 Audio 、LAN 都是一個(gè) PCI 設(shè)備。PCI ?設(shè)備同時(shí)也分為主設(shè)備和目標(biāo)設(shè)備兩種,主設(shè)備是一次訪問(wèn)操作的發(fā)起者,而目標(biāo)設(shè)備則是被訪問(wèn)者。
2.PCI ?總線。PCI ?總線在系統(tǒng)中可以有多條,類似于樹(shù)狀結(jié)構(gòu)進(jìn)行擴(kuò)展,每條 PCI 總線都可以連接多個(gè) PCI ?設(shè)備/ 橋。上圖中有兩條 PCI 總線。
3.PCI ?橋。當(dāng)一條 PCI 總線的承載量不夠時(shí),可以用新的 PCI 總線進(jìn)行擴(kuò)展,而 PCI 橋則是連接 PCI 總線之間的紐帶。圖中的 PCI 橋有兩個(gè),一個(gè)橋用來(lái)連接處理器、內(nèi)存以及 PCI 總線,而另外一條則用來(lái)連接另一條 PCI 總線。
PCI設(shè)備與驅(qū)動(dòng)?
1. PCI相關(guān)結(jié)構(gòu)體
Linux ?提供了三類數(shù)據(jù)結(jié)構(gòu)用以描述 PCI 控制器、PCI ?設(shè)備以及 PCI 總線。
PCI ?控制器:
<span style="font-size:18px;">struct pci_controller {struct pci_controller *next; //該屬性指向下一個(gè) PCI 控制器struct pci_bus *bus; // 該屬性標(biāo)志了當(dāng)前 PCI 控制器所連接的 PCI 總線struct pci_ops *pci_ops; //該屬性標(biāo)志了當(dāng)前 PCI 控制器所對(duì)應(yīng)的 PCI 配制空間讀寫(xiě)操作函數(shù)struct resource *mem_resource; //該屬性標(biāo)志了當(dāng)前 PCI 控制器所支持的 Memory 地址區(qū)間unsigned long mem_offset; //地址偏移量struct resource *io_resource; //該屬性標(biāo)志了當(dāng)前 PCI 控制器所支持的 IO 地址空間。unsigned long io_offset; //偏移量unsigned long io_map_base; //PCI 設(shè)備的 IO map 基地址unsigned int index; //該屬性標(biāo)志 PCI 控制器的編號(hào)。/* For compatibility with current (as of July 2003) pciutilsand XFree86. Eventually will be removed. */unsigned int need_domain_info; //域信息int iommu; //MMU/* Optional access methods for reading/writing the bus numberof the PCI controller */int (*get_busno)(void);void (*set_busno)(int busno); }; </span>PCI ?總線:
<span style="font-size:18px;">struct pci_bus {struct list_head node; /* node in list of buses */struct pci_bus *parent; /* parent bus this bridge is on */struct list_head children; //總線連接的所有 PCI 子總線鏈表。struct list_head devices; //總線連接的所有 PCI 設(shè)備鏈表struct pci_dev *self; //該屬性標(biāo)志了連接的上行 PCI 橋struct list_head slots; /* list of slots on this bus */struct resource *resource[PCI_BUS_NUM_RESOURCES];//該屬性標(biāo)志了 Memory/IO 地址空間。struct pci_ops *ops; //該屬性標(biāo)志了總線上所有 PCI 設(shè)備的配制空間讀寫(xiě)操作函數(shù)。void *sysdata; //指向系統(tǒng)特定的擴(kuò)展數(shù)據(jù) struct proc_dir_entry *procdir; /* directory entry in /proc/bus/pci */unsigned char number; /* bus number */unsigned char primary; /* number of primary bridge */unsigned char secondary; /* number of secondary bridge */unsigned char subordinate; /* max number of subordinate buses */char name[48];unsigned short bridge_ctl; /* manage NO_ISA/FBB/et al behaviors */pci_bus_flags_t bus_flags; /* Inherited by child busses */struct device *bridge;struct device dev;struct bin_attribute *legacy_io; /* legacy I/O for this bus */struct bin_attribute *legacy_mem; /* legacy mem */unsigned int is_added:1; }; </span>
PCI ?設(shè)備:
每種類的PCI設(shè)備都可以用結(jié)構(gòu)類型pci_dev來(lái)描述。更為準(zhǔn)確地說(shuō),應(yīng)該是每一個(gè)PCI功能,即PCI邏輯設(shè)備都唯一地對(duì)應(yīng)有一個(gè)pci_dev設(shè)備描述符。該數(shù)據(jù)結(jié)構(gòu)的定義部分屬性如下(include/linux/pci.h):
總線設(shè)備鏈表元素bus_list:每一個(gè)pci_dev結(jié)構(gòu)除了鏈接到全局設(shè)備鏈表中外,還會(huì)通過(guò)這個(gè)成員連接到其所屬PCI總線的設(shè)備鏈表中。每一條PCI總線都維護(hù)一條它自己的設(shè)備鏈表視圖,以便描述所有連接在該P(yáng)CI總線上的設(shè)備,其表頭由PCI總線的pci_bus結(jié)構(gòu)中的 devices成員所描述。
hdr_type:8位符號(hào)整數(shù),表示PCI配置空間頭部的類型。其中,bit[7]=1表示這是一個(gè)多功能設(shè)備,bit[7]=0表示這是一個(gè)單功能設(shè)備。Bit[6:0]則表示PCI配置空間頭部的布局類型,值00h表示這是一個(gè)一般PCI設(shè)備的配置空間頭部,值01h表示這是一個(gè)PCI-to-PCI橋的配置空間頭部,值02h表示CardBus橋的配置空間頭部
rom_base_reg:8位無(wú)符號(hào)整數(shù),表示PCI配置空間中的ROM基地址寄存器在PCI配置空間中的位置。ROM基地址寄存器在不同類型的PCI配置空間頭部的位置是不一樣的,對(duì)于type 0的配置空間布局,ROM基地址寄存器的起始位置是30h,而對(duì)于PCI-to-PCI橋所用的type 1配置空間布局,ROM基地址寄存器的起始位置是38h
2.PCI設(shè)備與驅(qū)動(dòng)關(guān)系
PCI設(shè)備通常由一組參數(shù)唯一地標(biāo)識(shí),它們被vendorID,deviceID和class nodes所標(biāo)識(shí),即設(shè)備廠商,型號(hào)等,這些參數(shù)保存在 pci_device_id結(jié)構(gòu)中。每個(gè)PCI設(shè)備都會(huì)被分配一個(gè)pci_dev變量,內(nèi)核就用這個(gè)數(shù)據(jù)結(jié)構(gòu)來(lái)表示一個(gè)PCI設(shè)備。?
所有的PCI驅(qū)動(dòng)程序都必須定義一個(gè)pci_driver結(jié)構(gòu)變量,在該變量中包含了這個(gè)PCI驅(qū)動(dòng)程序所提供的不同功能的函數(shù),同時(shí),在這個(gè)結(jié)構(gòu)中也包含了一個(gè)device_driver結(jié)構(gòu),這個(gè)結(jié)構(gòu)定義了PCI子系統(tǒng)與PCI設(shè)備之間的接口。在注冊(cè)PCI驅(qū)動(dòng)程序時(shí),這個(gè)結(jié)構(gòu)將被初始化,同時(shí)這個(gè) pci_driver變量會(huì)被鏈接到pci_bus_type中的驅(qū)動(dòng)鏈上去。?
在pci_driver中有一個(gè)成員struct pci_device_id *id_table,它列出了這個(gè)設(shè)備驅(qū)動(dòng)程序所能夠處理的所有PCI設(shè)備的ID值。
3.PCI設(shè)備與驅(qū)動(dòng)的綁定過(guò)程
? ? ? ? 下面描述一下對(duì)于PCI設(shè)備與驅(qū)動(dòng)綁定的過(guò)程。首先在系統(tǒng)啟動(dòng)的時(shí)候,PCI總線會(huì)去掃描連接到這個(gè)總線上的設(shè)備,同時(shí)為每一個(gè)設(shè)備建立一個(gè)pci_dev結(jié)構(gòu),在這個(gè)結(jié)構(gòu)中有一個(gè)device成員,并將這些pci_dev結(jié)構(gòu)鏈接到PCI總線描述符上的devices鏈。如下圖所示:
???????????????????????????????
??????? 第二步是當(dāng)PCI驅(qū)動(dòng)被加載時(shí),pci_driver結(jié)構(gòu)體將被初始化,這一過(guò)程在函數(shù)pci_register_driver中:
? ? ? ? drv->driver.bus = &pci_bus_type;
? ? ? ? drv->driver.probe = pci_device_probe;
? ? ? ? 最后會(huì)調(diào)用driver_register(&drv->driver)將這個(gè)PCI驅(qū)動(dòng)掛載到總線描述符的驅(qū)動(dòng)鏈上。同時(shí)在注冊(cè)的過(guò)程中,會(huì)根據(jù)pci_driver中的id_table中的ID值去查看該驅(qū)動(dòng)支持哪些設(shè)備,將這些設(shè)備掛載到pci_driver中的devices鏈中來(lái)。如下圖所示:
????? ? ? ?? ???????
????????????????
?????????????? 對(duì)于不同的設(shè)備,可能驅(qū)動(dòng)程序也不一樣,因此,對(duì)于上圖中的Dev3,可能就需要另外一個(gè)驅(qū)動(dòng)程序來(lái)對(duì)其進(jìn)行驅(qū)動(dòng)。所以當(dāng)加載了Dev3的驅(qū)動(dòng)程序時(shí),其示意圖如下圖所示:
????????????????????????
PCI設(shè)備驅(qū)動(dòng)程序
????? 1.基本框架
????????在用模塊方式實(shí)現(xiàn)PCI設(shè)備驅(qū)動(dòng)程序時(shí),通常至少要實(shí)現(xiàn)以下幾個(gè)部分:初始化設(shè)備模塊、設(shè)備打開(kāi)模塊、數(shù)據(jù)讀寫(xiě)和控制模塊、中斷處理模塊、設(shè)備釋放模塊、設(shè)備卸載模塊。下面給出一個(gè)典型的PCI設(shè)備驅(qū)動(dòng)程序的基本框架,從中不難體會(huì)到這幾個(gè)關(guān)鍵模塊是如何組織起來(lái)的。
上面這段代碼給出了一個(gè)典型的PCI設(shè)備驅(qū)動(dòng)程序的框架,是一種相對(duì)固定的模式。需要注意的是,同加載和卸載模塊相關(guān)的函數(shù)或數(shù)據(jù)結(jié)構(gòu)都要在前面加上__init、__exit等標(biāo)志符,以使同普通函數(shù)區(qū)分開(kāi)來(lái)。構(gòu)造出這樣一個(gè)框架之后,接下去的工作就是如何完成框架內(nèi)的各個(gè)功能模塊了。
/* 指明該驅(qū)動(dòng)程序適用于哪一些PCI設(shè)備 */ static struct pci_device_id xxx_pci_tbl [] __initdata = {{PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO},{0,} }; /* 對(duì)特定PCI設(shè)備進(jìn)行描述的數(shù)據(jù)結(jié)構(gòu) */ struct xxx_card {unsigned int magic;/* 使用鏈表保存所有同類的PCI設(shè)備 */struct xxx_card *next;/* ... */ } /* 中斷處理模塊 */ static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) {/* ... */ } /* 設(shè)備文件操作接口 */ static struct file_operations demo_fops = {owner: THIS_MODULE, /* demo_fops所屬的設(shè)備模塊 */read: xxx_read, /* 讀設(shè)備操作*/write: xxx_write, /* 寫(xiě)設(shè)備操作*/ioctl: xxx_ioctl, /* 控制設(shè)備操作*/mmap: xxx_mmap, /* 內(nèi)存重映射操作*/open: xxx_open, /* 打開(kāi)設(shè)備操作*/release: xxx_release /* 釋放設(shè)備操作*//* ... */ }; /* 設(shè)備模塊信息 */ static struct pci_driver demo_pci_driver = {name: xxx_MODULE_NAME, /* 設(shè)備模塊名稱 */id_table: xxx_pci_tbl, /* 能夠驅(qū)動(dòng)的設(shè)備列表 */probe: xxx_probe, /* 查找并初始化設(shè)備 */remove: xxx_remove /* 卸載設(shè)備模塊 *//* ... */ }; static int __init xxx_init_module (void) {/* ... */ } static void __exit xxx_cleanup_module (void) {pci_unregister_driver(&xxx_pci_driver); } /* 加載驅(qū)動(dòng)程序模塊入口 */ module_init(xxx_init_module); /* 卸載驅(qū)動(dòng)程序模塊入口 */ module_exit(xxx_cleanup_module);
2.初始化
在Linux系統(tǒng)下,想要完成對(duì)一個(gè)PCI設(shè)備的初始化,需要完成以下工作:
檢查PCI總線是否被Linux內(nèi)核支持;
檢查設(shè)備是否插在總線插槽上,如果在的話則保存它所占用的插槽的位置等信息。
讀出配置頭中的信息提供給驅(qū)動(dòng)程序使用。
當(dāng)Linux內(nèi)核啟動(dòng)并完成對(duì)所有PCI設(shè)備進(jìn)行掃描、登錄和分配資源等初始化操作的同時(shí),會(huì)建立起系統(tǒng)中所有PCI設(shè)備的拓?fù)浣Y(jié)構(gòu),此后當(dāng)PCI驅(qū)動(dòng)程序需要對(duì)設(shè)備進(jìn)行初始化時(shí),一般都會(huì)調(diào)用如下的代碼:
static int __init xxx_init_module (void) {/* 檢查系統(tǒng)是否支持PCI總線 */if (!pci_present())return -ENODEV;/* 注冊(cè)硬件驅(qū)動(dòng)程序 */if (!pci_register_driver(&demo_pci_driver)) {pci_unregister_driver(&demo_pci_driver);return -ENODEV;}/* ... */return 0; }
驅(qū)動(dòng)程序首先調(diào)用函數(shù)pci_present( )檢查PCI總線是否已經(jīng)被Linux內(nèi)核支持,如果系統(tǒng)支持PCI總線結(jié)構(gòu),這個(gè)函數(shù)的返回值為0,如果驅(qū)動(dòng)程序在調(diào)用這個(gè)函數(shù)時(shí)得到了一個(gè)非0的返回值,那么驅(qū)動(dòng)程序就必須得中止自己的任務(wù)了。在2.4以前的內(nèi)核中,需要手工調(diào)用pci_find_device( )函數(shù)來(lái)查找PCI設(shè)備,但在2.4以后更好的辦法是調(diào)用pci_register_driver( )函數(shù)來(lái)注冊(cè)PCI設(shè)備的驅(qū)動(dòng)程序,此時(shí)需要提供一個(gè)pci_driver結(jié)構(gòu),在該結(jié)構(gòu)中給出的probe探測(cè)例程將負(fù)責(zé)完成對(duì)硬件的檢測(cè)工作。
static int __init xxx_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id) {struct xxx_card *card;/* 啟動(dòng)PCI設(shè)備 */if (pci_enable_device(pci_dev))return -EIO;/* 設(shè)備DMA標(biāo)識(shí) */if (pci_set_dma_mask(pci_dev, DEMO_DMA_MASK)) {return -ENODEV;}/* 在內(nèi)核空間中動(dòng)態(tài)申請(qǐng)內(nèi)存 */if ((card = kmalloc(sizeof(struct demo_card), GFP_KERNEL)) == NULL) {printk(KERN_ERR "pci_demo: out of memory\n");return -ENOMEM;}memset(card, 0, sizeof(*card));/* 讀取PCI配置信息 */card->iobase = pci_resource_start (pci_dev, 1);card->pci_dev = pci_dev;card->pci_id = pci_id->device;card->irq = pci_dev->irq;card->next = devs;card->magic = DEMO_CARD_MAGIC;/* 設(shè)置成總線主DMA模式 */ pci_set_master(pci_dev);/* 申請(qǐng)I/O資源 */request_region(card->iobase, 64, card_names[pci_id->driver_data]);return 0; }
3.OPEN
在這個(gè)模塊里主要實(shí)現(xiàn)申請(qǐng)中斷、檢查讀寫(xiě)模式以及申請(qǐng)對(duì)設(shè)備的控制權(quán)等。在申請(qǐng)控制權(quán)的時(shí)候,非阻塞方式遇忙返回,否則進(jìn)程主動(dòng)接受調(diào)度,進(jìn)入睡眠狀態(tài),等待其它進(jìn)程釋放對(duì)設(shè)備的控制權(quán)。
static int xxx_open(struct inode *inode, struct file *file) {/* 申請(qǐng)中斷,注冊(cè)中斷處理程序 */request_irq(card->irq, &xxx_interrupt, SA_SHIRQ,card_names[pci_id->driver_data], card)) {/* 檢查讀寫(xiě)模式 */if(file->f_mode & FMODE_READ) {/* ... */}if(file->f_mode & FMODE_WRITE) {/* ... */}/* 申請(qǐng)對(duì)設(shè)備的控制權(quán) */down(&card->open_sem);while(card->open_mode & file->f_mode) {if (file->f_flags & O_NONBLOCK) {/* NONBLOCK模式,返回-EBUSY */up(&card->open_sem);return -EBUSY;} else {/* 等待調(diào)度,獲得控制權(quán) */card->open_mode |= f_mode & (FMODE_READ | FMODE_WRITE);up(&card->open_sem);/* 設(shè)備打開(kāi)計(jì)數(shù)增1 */MOD_INC_USE_COUNT;/* ... */}} }
4.數(shù)據(jù)的讀寫(xiě)和控制
PCI設(shè)備驅(qū)動(dòng)程序可以通過(guò)demo_fops 結(jié)構(gòu)中的函數(shù)demo_ioctl( ),向應(yīng)用程序提供對(duì)硬件進(jìn)行控制的接口。例如,通過(guò)它可以從I/O寄存器里讀取一個(gè)數(shù)據(jù),并傳送到用戶空間里:
static int xxx_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg) {/* ... */switch(cmd) {case DEMO_RDATA:/* 從I/O端口讀取4字節(jié)的數(shù)據(jù) */val = inl(card->iobae + 0x10);/* 將讀取的數(shù)據(jù)傳輸?shù)接脩艨臻g */return 0;}/* ... */ }
事實(shí)上,在demo_fops里還可以實(shí)現(xiàn)諸如demo_read( )、demo_mmap( )等操作,Linux內(nèi)核源碼中的driver目錄里提供了許多設(shè)備驅(qū)動(dòng)程序的源代碼,找那里可以找到類似的例子。在對(duì)資源的訪問(wèn)方式上,除了有I/O指令以外,還有對(duì)外設(shè)I/O內(nèi)存的訪問(wèn)。對(duì)這些內(nèi)存的操作一方面可以通過(guò)把I/O內(nèi)存重新映射后作為普通內(nèi)存進(jìn)行操作,另一方面也可以通過(guò)總線主DMA(Bus Master DMA)的方式讓設(shè)備把數(shù)據(jù)通過(guò)DMA傳送到系統(tǒng)內(nèi)存中。
5.中斷處理
PC的中斷資源比較有限,只有0~15的中斷號(hào),因此大部分外部設(shè)備都是以共享的形式申請(qǐng)中斷號(hào)的。當(dāng)中斷發(fā)生的時(shí)候,中斷處理程序首先負(fù)責(zé)對(duì)中斷進(jìn)行識(shí)別,然后再做進(jìn)一步的處理。
static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) {struct xxx_card *card = (struct xxx_card *)dev_id;u32 status;spin_lock(&card->lock);/* 識(shí)別中斷 */status = inl(card->iobase + GLOB_STA);if(!(status & INT_MASK)) {spin_unlock(&card->lock);return; /* not for us */}/* 告訴設(shè)備已經(jīng)收到中斷 */outl(status & INT_MASK, card->iobase + GLOB_STA);spin_unlock(&card->lock);/* 其它進(jìn)一步的處理,如更新DMA緩沖區(qū)指針等 */ }
6.釋放和卸載
釋放設(shè)備模塊主要負(fù)責(zé)釋放對(duì)設(shè)備的控制權(quán),釋放占用的內(nèi)存和中斷等,所做的事情正好與打開(kāi)設(shè)備模塊相反:
static int xxx_release(struct inode *inode, struct file *file) {/* ... *//* 釋放對(duì)設(shè)備的控制權(quán) */card->open_mode &= (FMODE_READ | FMODE_WRITE);/* 喚醒其它等待獲取控制權(quán)的進(jìn)程 */wake_up(&card->open_wait);up(&card->open_sem);/* 釋放中斷 */free_irq(card->irq, card);/* 設(shè)備打開(kāi)計(jì)數(shù)增1 */MOD_DEC_USE_COUNT;/* ... */ }
卸載設(shè)備模塊與初始化設(shè)備模塊是相對(duì)應(yīng)的,實(shí)現(xiàn)起來(lái)相對(duì)比較簡(jiǎn)單,主要是調(diào)用函數(shù)pci_unregister_driver( )從Linux內(nèi)核中注銷設(shè)備驅(qū)動(dòng)程序:
static void __exit xxx_cleanup_module (void) {pci_unregister_driver(&xxx_pci_driver); }
總結(jié)
- 上一篇: 牛文文点评长租公寓行业:过度金融化会毁灭
- 下一篇: 腾讯轻量云FREEBSD11.1安装pa