#include <linux/fs.h> #include <linux/module.h> #include <linux/init.h> #include <linux/pci.h> #include <linux/interrupt.h> #include <asm-generic/signal.h> #undef debug // ATTENTION copied from /uboot_for_mpc/arch/powerpc/include/asm/signal.h // Maybe it don't work with that //____________________________________________________________ #define SA_INTERRUPT 0x20000000 /* dummy -- ignored */ #define SA_SHIRQ 0x04000000 //____________________________________________________________ #define pci_module_init pci_register_driver // function is obsoleted // Hardware specific part #define MY_VENDOR_ID 0x5333 #define MY_DEVICE_ID 0x8e40 #define MAJOR_NR 240 #define DRIVER_NAME "PCI-Driver" static unsigned long ioport=0L, iolen=0L, memstart=0L, memlen=0L,flag0,flag1,flag2,temp=0L; // private_data struct _instance_data { int counter; // just as a example (5-27) // other instance specific data }; // Interrupt Service Routine static irqreturn_t pci_isr( int irq, void *dev_id, struct pt_regs *regs ) { return IRQ_HANDLED; } // Check if this driver is for the new device static int device_init(struct pci_dev *dev, const struct pci_device_id *id) { int err=0; // temp variable #ifdef debug flag0=pci_resource_flags(dev, 0 ); flag1=pci_resource_flags(dev, 1 ); flag2=pci_resource_flags(dev, 2 ); printk("DEBUG: FLAGS0 = %u\n",flag0); printk("DEBUG: FLAGS1 = %u\n",flag1); printk("DEBUG: FLAGS2 = %u\n",flag2); /* * The following sequence checks if the resource is in the * IO / Storage / Interrupt / DMA address space * and prints the result in the dmesg log */ if(pci_resource_flags(dev,0) & IORESOURCE_IO) { // Ressource is in the IO address space printk("DEBUG: IORESOURCE_IO\n"); } else if (pci_resource_flags(dev,0) & IORESOURCE_MEM) { // Resource is in the Storage address space printk("DEBUG: IORESOURCE_MEM\n"); } else if (pci_resource_flags(dev,0) & IORESOURCE_IRQ) { // Resource is in the IRQ address space printk("DEBUG: IORESOURCE_IRQ\n"); } else if (pci_resource_flags(dev,0) & IORESOURCE_DMA) { // Resource is in the DMA address space printk("DEBUG: IORESOURCE_DMA\n"); } else { printk("DEBUG: NOTHING\n"); } #endif /* debug */ // allocate memory_region memstart = pci_resource_start( dev, 0 ); memlen = pci_resource_len( dev, 0 ); if( request_mem_region( memstart, memlen, dev->dev.kobj.name )==NULL ) { printk(KERN_ERR "Memory address conflict for device \"%s\"\n", dev->dev.kobj.name); return -EIO; } // allocate a interrupt if(request_irq(dev->irq,pci_isr,SA_INTERRUPT|SA_SHIRQ, "pci_drv",dev)) { printk( KERN_ERR "pci_drv: IRQ %d not free.\n", dev->irq ); } else { err=pci_enable_device( dev ); if(err==0) // enable device successful { return 0; } else // enable device not successful { return err; } } // cleanup_mem release_mem_region( memstart, memlen ); return -EIO; } // Function for deinitialization of the device static void device_deinit( struct pci_dev *pdev ) { free_irq( pdev->irq, pdev ); if( memstart ) release_mem_region( memstart, memlen ); } static struct file_operations pci_fops; static struct pci_device_id pci_drv_tbl[] __devinitdata = { { MY_VENDOR_ID, // manufacturer identifier MY_DEVICE_ID, // device identifier PCI_ANY_ID, // subsystem manufacturer identifier PCI_ANY_ID, // subsystem device identifier 0, // device class 0, // mask for device class 0 }, // driver specific data { 0, } }; static int driver_open( struct inode *geraetedatei, struct file *instance ) { struct _instance_data *iptr; iptr = (struct _instance_data *)kmalloc(sizeof(struct _instance_data), GFP_KERNEL); if( iptr==0 ) { printk("not enough kernel mem\n"); return -ENOMEM; } /* replace the following line with your instructions */ iptr->counter= strlen("Hello World\n")+1; // just as a example (5-27) instance->private_data = (void *)iptr; return 0; } static void driver_close( struct file *instance ) { if( instance->private_data ) kfree( instance->private_data ); } static struct pci_driver pci_drv = { .name= "pci_drv", .id_table= pci_drv_tbl, .probe= device_init, .remove= device_deinit, }; static int __init pci_drv_init(void) { // register the driver by the OS if(register_chrdev(MAJOR_NR, DRIVER_NAME, &pci_fops)==0) { if(pci_module_init(&pci_drv) == 0 ) // register by the subsystem return 0; unregister_chrdev(MAJOR_NR,DRIVER_NAME); // unregister if no subsystem support } return -EIO; } static void __exit pci_drv_exit(void) { pci_unregister_driver( &pci_drv ); unregister_chrdev(MAJOR_NR,DRIVER_NAME); } module_init(pci_drv_init); module_exit(pci_drv_exit); MODULE_LICENSE("GPL");
From a software standpoint, PCI and PCI Express devices are essentially the same. PCIe devices had the same configuration space, BARs, and (usually) support the same PCI INTx interrupts.通常狀況下,二者基本保持一致node
Example #1: Windows XP has no special knowledge of PCIe, but runs fine on PCIe systems.linux
Example #2: My company offers both PCI and PCIe versions of a peripheral board, and they use the same Windows/Linux driver package. The driver does not "know" the difference between the two boards.shell
However: PCIe devices frequently take advantage of "advanced" features, like MSI, Hotplugging, extended configuration space, etc. Many of these feature existed on legacy PCI, but were unused. If this is a device you are designing, it is up to you whether or not you implement these advanced features.可是pcie在一些高級特性上有優點,好比MSI(Message Signaled Interrupts)、Hotplugging(熱插拔)、配置空間擴展等。數組
Linux將全部外部設備當作是一類特殊文件,稱之爲「設備文件」,若是說系統調用是Linux內核和應用程序之間的接口,那麼設備驅動程序則能夠當作是Linux內核與外部設備之間的接口。設備驅動程序嚮應用程序屏蔽了硬件在實現上的細節,使得應用程序能夠像操做普通文件同樣來操做外部設備。緩存
1. 字符設備和塊設備數據結構
Linux抽象了對硬件的處理,全部的硬件設備均可以像普通文件同樣來看待:它們可使用和操做文件相同的、標準的系統調用接口來完成打開、關閉、讀寫和I/O控制操做,而驅動程序的主要任務也就是要實現這些系統調用函數。Linux系統中的全部硬件設備都使用一個特殊的設備文件來表示,例如,系統中的第一個IDE硬盤使用/dev/hda表示。每一個設備文件對應有兩個設備號:一個是主設備號,標識該設備的種類,也標識了該設備所使用的驅動程序;另外一個是次設備號,標識使用同一設備驅動程序的不一樣硬件設備。設備文件的主設備號必須與設備驅動程序在登陸該設備時申請的主設備號一致,不然用戶進程將沒法訪問到設備驅動程序。app
在Linux操做系統下有兩類主要的設備文件:一類是字符設備,另外一類則是塊設備。字符設備是以字節爲單位逐個進行I/O操做的設備,在對字符設備發出讀寫請求時,實際的硬件I/O緊接着就發生了,通常來講字符設備中的緩存是無關緊要的,並且也不支持隨機訪問。塊設備則是利用一塊系統內存做爲緩衝區,當用戶進程對設備進行讀寫請求時,驅動程序先查看緩衝區中的內容,若是緩衝區中的數據能知足用戶的要求就返回相應的數據,不然就調用相應的請求函數來進行實際的I/O操做。塊設備主要是針對磁盤等慢速設備設計的,其目的是避免耗費過多的CPU時間來等待操做的完成。通常說來,PCI卡一般都屬於字符設備。框架
全部已經註冊(即已經加載了驅動程序)的硬件設備的主設備號能夠從/proc/devices文件中獲得。使用mknod命令能夠建立指定類型的設備文件,同時爲其分配相應的主設備號和次設備號。例如,下面的命令:異步
[root@gary root]# mknod /dev/lp0 c 6 0 |
將創建一個主設備號爲6,次設備號爲0的字符設備文件/dev/lp0。當應用程序對某個設備文件進行系統調用時,Linux內核會根據該設備文件的設備類型和主設備號調用相應的驅動程序,並從用戶態進入到核心態,再由驅動程序判斷該設備的次設備號,最終完成對相應硬件的操做。async
2. 設備驅動程序接口
Linux中的I/O子系統向內核中的其餘部分提供了一個統一的標準設備接口,這是經過include/linux/fs.h中的數據結構file_operations來完成的:
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); }; |
當應用程序對設備文件進行諸如open、close、read、write等操做時,Linux內核將經過file_operations結構訪問驅動程序提供的函數。例如,當應用程序對設備文件執行讀操做時,內核將調用file_operations結構中的read函數。
3. 設備驅動程序模塊
Linux下的設備驅動程序能夠按照兩種方式進行編譯,一種是直接靜態編譯成內核的一部分,另外一種則是編譯成能夠動態加載的模塊。若是編譯進內核的話,會增長內核的大小,還要改動內核的源文件,並且不能動態地卸載,不利於調試,全部推薦使用模塊方式。
從本質上來說,模塊也是內核的一部分,它不一樣於普通的應用程序,不能調用位於用戶態下的C或者C++庫函數,而只能調用Linux內核提供的函數,在/proc/ksyms中能夠查看到內核提供的全部函數。
在以模塊方式編寫驅動程序時,要實現兩個必不可少的函數init_module( )和cleanup_module( ),並且至少要包含<linux/krernel.h>和<linux/module.h>兩個頭文件。在用gcc編譯內核模塊時,須要加上-DMODULE -D__KERNEL__ -DLINUX這幾個參數,編譯生成的模塊(通常爲.o文件)可使用命令insmod載入Linux內核,從而成爲內核的一個組成部分,此時內核會調用模塊中的函數init_module( )。當不須要該模塊時,可使用rmmod命令進行卸載,此進內核會調用模塊中的函數cleanup_module( )。任什麼時候候均可以使用命令來lsmod查看目前已經加載的模塊以及正在使用該模塊的用戶數。
4. 設備驅動程序結構
瞭解設備驅動程序的基本結構(或者稱爲框架),對開發人員而言是很是重要的,Linux的設備驅動程序大體能夠分爲以下幾個部分:驅動程序的註冊與註銷、設備的打開與釋放、設備的讀寫操做、設備的控制操做、設備的中斷和輪詢處理。
向系統增長一個驅動程序意味着要賦予它一個主設備號,這能夠經過在驅動程序的初始化過程當中調用register_chrdev( )或者register_blkdev( )來完成。而在關閉字符設備或者塊設備時,則須要經過調用unregister_chrdev( )或unregister_blkdev( )從內核中註銷設備,同時釋放佔用的主設備號。
打開設備是經過調用file_operations結構中的函數open( )來完成的,它是驅動程序用來爲從此的操做完成初始化準備工做的。在大部分驅動程序中,open( )一般須要完成下列工做:
釋放設備是經過調用file_operations結構中的函數release( )來完成的,這個設備方法有時也被稱爲close( ),它的做用正好與open( )相反,一般要完成下列工做:
字符設備的讀寫操做相對比較簡單,直接使用函數read( )和write( )就能夠了。但若是是塊設備的話,則須要調用函數block_read( )和block_write( )來進行數據讀寫,這兩個函數將向設備請求表中增長讀寫請求,以便Linux內核能夠對請求順序進行優化。因爲是對內存緩衝區而不是直接對設備進行操做的,所以能很大程度上加快讀寫速度。若是內存緩衝區中沒有所要讀入的數據,或者須要執行寫操做將數據寫入設備,那麼就要執行真正的數據傳輸,這是經過調用數據結構blk_dev_struct中的函數request_fn( )來完成的。
除了讀寫操做外,應用程序有時還須要對設備進行控制,這能夠經過設備驅動程序中的函數ioctl( )來完成。ioctl( )的用法與具體設備密切關聯,所以須要根據設備的實際狀況進行具體分析。
對於不支持中斷的硬件設備,讀寫時須要輪流查詢設備狀態,以便決定是否繼續進行數據傳輸。若是設備支持中斷,則能夠按中斷方式進行操做。
3、PCI驅動程序實現
1. 關鍵數據結構
PCI設備上有三種地址空間:PCI的I/O空間、PCI的存儲空間和PCI的配置空間。CPU能夠訪問PCI設備上的全部地址空間,其中I/O空間和存儲空間提供給設備驅動程序使用,而配置空間則由Linux內核中的PCI初始化代碼使用。內核在啓動時負責對全部PCI設備進行初始化,配置好全部的PCI設備,包括中斷號以及I/O基址,並在文件/proc/pci中列出全部找到的PCI設備,以及這些設備的參數和屬性。
Linux驅動程序一般使用結構(struct)來表示一種設備,而結構體中的變量則表明某一具體設備,該變量存放了與該設備相關的全部信息。好的驅動程序都應該能驅動多個同種設備,每一個設備之間用次設備號進行區分,若是採用結構數據來表明全部能由該驅動程序驅動的設備,那麼就能夠簡單地使用數組下標來表示次設備號。
在PCI驅動程序中,下面幾個關鍵數據結構起着很是核心的做用:
這個數據結構在文件include/linux/pci.h裏,這是Linux內核版本2.4以後爲新型的PCI設備驅動程序所添加的,其中最主要的是用於識別設備的id_table結構,以及用於檢測設備的函數probe( )和卸載設備的函數remove( ):
struct pci_driver { struct list_head node; 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 (*save_state) (struct pci_dev *dev, u32 state); int (*suspend)(struct pci_dev *dev, u32 state); int (*resume) (struct pci_dev *dev); int (*enable_wake) (struct pci_dev *dev, u32 state, int enable); }; |
這個數據結構也在文件include/linux/pci.h裏,它詳細描述了一個PCI設備幾乎全部的硬件信息,包括廠商ID、設備ID、各類資源等:
struct pci_dev { struct list_head global_list; struct list_head bus_list; struct pci_bus *bus; struct pci_bus *subordinate; void *sysdata; struct proc_dir_entry *procent; unsigned int devfn; unsigned short vendor; unsigned short device; unsigned short subsystem_vendor; unsigned short subsystem_device; unsigned int class; u8 hdr_type; u8 rom_base_reg; struct pci_driver *driver; void *driver_data; u64 dma_mask; u32 current_state; unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE]; unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE]; unsigned int irq; struct resource resource[DEVICE_COUNT_RESOURCE]; struct resource dma_resource[DEVICE_COUNT_DMA]; struct resource irq_resource[DEVICE_COUNT_IRQ]; char name[80]; char slot_name[8]; int active; int ro; unsigned short regs; int (*prepare)(struct pci_dev *dev); int (*activate)(struct pci_dev *dev); int (*deactivate)(struct pci_dev *dev); }; |
2. 基本框架
在用模塊方式實現PCI設備驅動程序時,一般至少要實現如下幾個部分:初始化設備模塊、設備打開模塊、數據讀寫和控制模塊、中斷處理模塊、設備釋放模塊、設備卸載模塊。下面給出一個典型的PCI設備驅動程序的基本框架,從中不難體會到這幾個關鍵模塊是如何組織起來的。
/* 指明該驅動程序適用於哪一些PCI設備 */ static struct pci_device_id demo_pci_tbl [] __initdata = { {PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO}, {0,} }; /* 對特定PCI設備進行描述的數據結構 */ struct demo_card { unsigned int magic; /* 使用鏈表保存全部同類的PCI設備 */ struct demo_card *next; /* ... */ } /* 中斷處理模塊 */ static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs) { /* ... */ } /* 設備文件操做接口 */ static struct file_operations demo_fops = { owner: THIS_MODULE, /* demo_fops所屬的設備模塊 */ read: demo_read, /* 讀設備操做*/ write: demo_write, /* 寫設備操做*/ ioctl: demo_ioctl, /* 控制設備操做*/ mmap: demo_mmap, /* 內存重映射操做*/ open: demo_open, /* 打開設備操做*/ release: demo_release /* 釋放設備操做*/ /* ... */ }; /* 設備模塊信息 */ static struct pci_driver demo_pci_driver = { name: demo_MODULE_NAME, /* 設備模塊名稱 */ id_table: demo_pci_tbl, /* 可以驅動的設備列表 */ probe: demo_probe, /* 查找並初始化設備 */ remove: demo_remove /* 卸載設備模塊 */ /* ... */ }; static int __init demo_init_module (void) { /* ... */ } static void __exit demo_cleanup_module (void) { pci_unregister_driver(&demo_pci_driver); } /* 加載驅動程序模塊入口 */ module_init(demo_init_module); /* 卸載驅動程序模塊入口 */ module_exit(demo_cleanup_module); |
上面這段代碼給出了一個典型的PCI設備驅動程序的框架,是一種相對固定的模式。須要注意的是,同加載和卸載模塊相關的函數或數據結構都要在前面加上__init、__exit等標誌符,以使同普通函數區分開來。構造出這樣一個框架以後,接下去的工做就是如何完成框架內的各個功能模塊了。
3. 初始化設備模塊
在Linux系統下,想要完成對一個PCI設備的初始化,須要完成如下工做:
當Linux內核啓動並完成對全部PCI設備進行掃描、登陸和分配資源等初始化操做的同時,會創建起系統中全部PCI設備的拓撲結構,此後當PCI驅動程序須要對設備進行初始化時,通常都會調用以下的代碼:
static int __init demo_init_module (void) { /* 檢查系統是否支持PCI總線 */ if (!pci_present()) return -ENODEV; /* 註冊硬件驅動程序 */ if (!pci_register_driver(&demo_pci_driver)) { pci_unregister_driver(&demo_pci_driver); return -ENODEV; } /* ... */ return 0; } |
驅動程序首先調用函數pci_present( )檢查PCI總線是否已經被Linux內核支持,若是系統支持PCI總線結構,這個函數的返回值爲0,若是驅動程序在調用這個函數時獲得了一個非0的返回值,那麼驅動程序就必須得停止本身的任務了。在2.4之前的內核中,須要手工調用pci_find_device( )函數來查找PCI設備,但在2.4之後更好的辦法是調用pci_register_driver( )函數來註冊PCI設備的驅動程序,此時須要提供一個pci_driver結構,在該結構中給出的probe探測例程將負責完成對硬件的檢測工做。
static int __init demo_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id) { struct demo_card *card; /* 啓動PCI設備 */ if (pci_enable_device(pci_dev)) return -EIO; /* 設備DMA標識 */ if (pci_set_dma_mask(pci_dev, DEMO_DMA_MASK)) { return -ENODEV; } /* 在內核空間中動態申請內存 */ 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; /* 設置成總線主DMA模式 */ pci_set_master(pci_dev); /* 申請I/O資源 */ request_region(card->iobase, 64, card_names[pci_id->driver_data]); return 0; } |
4. 打開設備模塊
在這個模塊裏主要實現申請中斷、檢查讀寫模式以及申請對設備的控制權等。在申請控制權的時候,非阻塞方式遇忙返回,不然進程主動接受調度,進入睡眠狀態,等待其它進程釋放對設備的控制權。
static int demo_open(struct inode *inode, struct file *file) { /* 申請中斷,註冊中斷處理程序 */ request_irq(card->irq, &demo_interrupt, SA_SHIRQ, card_names[pci_id->driver_data], card)) { /* 檢查讀寫模式 */ if(file->f_mode & FMODE_READ) { /* ... */ } if(file->f_mode & FMODE_WRITE) { /* ... */ } /* 申請對設備的控制權 */ 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 { /* 等待調度,得到控制權 */ card->open_mode |= f_mode & (FMODE_READ | FMODE_WRITE); up(&card->open_sem); /* 設備打開計數增1 */ MOD_INC_USE_COUNT; /* ... */ } } } |
5. 數據讀寫和控制信息模塊
PCI設備驅動程序能夠經過demo_fops 結構中的函數demo_ioctl( ),嚮應用程序提供對硬件進行控制的接口。例如,經過它能夠從I/O寄存器裏讀取一個數據,並傳送到用戶空間裏:
static int demo_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { /* ... */ switch(cmd) { case DEMO_RDATA: /* 從I/O端口讀取4字節的數據 */ val = inl(card->iobae + 0x10); /* 將讀取的數據傳輸到用戶空間 */ return 0; } /* ... */ } |
事實上,在demo_fops裏還能夠實現諸如demo_read( )、demo_mmap( )等操做,Linux內核源碼中的driver目錄裏提供了許多設備驅動程序的源代碼,找那裏能夠找到相似的例子。在對資源的訪問方式上,除了有I/O指令之外,還有對外設I/O內存的訪問。對這些內存的操做一方面能夠經過把I/O內存從新映射後做爲普通內存進行操做,另外一方面也能夠經過總線主DMA(Bus Master DMA)的方式讓設備把數據經過DMA傳送到系統內存中。
6. 中斷處理模塊
PC的中斷資源比較有限,只有0~15的中斷號,所以大部分外部設備都是以共享的形式申請中斷號的。當中斷髮生的時候,中斷處理程序首先負責對中斷進行識別,而後再作進一步的處理。
static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct demo_card *card = (struct demo_card *)dev_id; u32 status; spin_lock(&card->lock); /* 識別中斷 */ status = inl(card->iobase + GLOB_STA); if(!(status & INT_MASK)) { spin_unlock(&card->lock); return; /* not for us */ } /* 告訴設備已經收到中斷 */ outl(status & INT_MASK, card->iobase + GLOB_STA); spin_unlock(&card->lock); /* 其它進一步的處理,如更新DMA緩衝區指針等 */ } |
7. 釋放設備模塊
釋放設備模塊主要負責釋放對設備的控制權,釋放佔用的內存和中斷等,所作的事情正好與打開設備模塊相反:
static int demo_release(struct inode *inode, struct file *file) { /* ... */ /* 釋放對設備的控制權 */ card->open_mode &= (FMODE_READ | FMODE_WRITE); /* 喚醒其它等待獲取控制權的進程 */ wake_up(&card->open_wait); up(&card->open_sem); /* 釋放中斷 */ free_irq(card->irq, card); /* 設備打開計數增1 */ MOD_DEC_USE_COUNT; /* ... */ } |
8. 卸載設備模塊
卸載設備模塊與初始化設備模塊是相對應的,實現起來相對比較簡單,主要是調用函數pci_unregister_driver( )從Linux內核中註銷設備驅動程序:
static void __exit demo_cleanup_module (void) { pci_unregister_driver(&demo_pci_driver); } |
(1)編寫Makefile文件
makefile文件實例
ifneq ($(KERNELRELEASE),) obj-m:=hello.o else #generate the path CURRENT_PATH:=$(shell pwd) #the absolute path LINUX_KERNEL_PATH:=/lib/modules/$(shell uname -r)/build #complie object default: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules clean: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean endif
obj-m 表示該文件要做爲模塊編譯 obj-y則表示該文件要編譯進內核
正常狀況下只需修改hello.o便可
(2)執行make命令生成 *.ko 文件
(3)sudo insmod *.ko加載驅動模塊
(4)sudo rmmod *.ko卸載驅動模塊
(5)使用dmesg | tail -10來查看內核輸出的最後十條信息
(6)使用modinfo *.ko來查看模塊信息
一個PCI設備可實現多達6個I/O地址區域,每一個區域既可使內存也能夠是I/O地址。在內核中PCI設備的I/O區域已經被集成到通用資源管理器。所以,咱們無需訪問配置變量來了解設備被映射到內存或者I/O空間的何處。得到區域信息的首選接口是下面的宏定義:
#define pci_resource_start(dev, bar)((dev)->resource[(bar)].start)
該宏返回六個PCI I/O區域之一的首地址(內存地址或者I/O端口號).該區域由整數的bar(base address register,基地址寄存器)指定,bar取值爲0到5。
#define pci_resource_end(dev, bar)((dev)->resource[(bar)].end)
該宏返回第bar個I/O區域的首地址。注意這是最後一個可用的地址,而不是該區域以後的第一個地址。
#define pci_resource_flags(dev, bar)((dev)->resource[(bar)].flags)
該宏返回和該資源相關聯的標誌。資源標誌用來定義單個資源的特性,對與PCI I/O區域相關的PCI資源,該信息從基地址寄存器中得到,但對於和PCI無關的資源,它可能來自其餘地方。全部資源標誌定義在<linux/ioport.h>。
(1)DMA循環緩衝區的分配與實現:
對於高速數據信號的採集處理,須要在驅動程序的初始化模塊(probe)中申請大量的DMA循環緩衝區,申請的大小直接關係着可否實時對高速數據處理的成敗。直接內存訪問(DMA)是一種硬件機制,容許外圍設備和主內存直接直接傳輸I/O數據,避免了大量的計算開銷。
(2) Linux內核的內存分區段:
三個區段,可用於DMA的內存,常規內存以及高端內存。
· 一般的內存分配發生在常規內存區,可是經過設置內存標識也能夠請求在其餘區段中分配。可用於DMA的內存指存在於特別地址範圍內的內存,外設能夠利用這些內存執行DMA訪問,進行數據通訊傳輸。
· DMA循環緩衝區的分配要求:物理連續,DMA能夠訪問,足夠大。
(3)Linux內存分配函數:
· Linux系統使用虛擬地址,內存分配函數提供的都是虛擬地址,經過virt_to_bus轉換才能獲得物理地址。
· 分配內核內存空間的函數:kmalloc實現小於128KB的內核內存申請,申請空間物理連續;__get_free_pages實現最大4MB的內存申請,以頁爲單位,所申請空間物理連續;vmalloc分配的虛擬地址空間連續,但在物理上可能不連續。
· Linux內核中專門提供了用於PCI設備申請內核內存的函數pci_alloc_consistent,支持按字節長度申請,該函數調用__get_free_pages,故一次最大爲4MB。
(4) DMA數據傳輸的方式:
· 一種是軟件發起的數據請求(例如經過read函數調用),另外一種是硬件異步將數據傳給系統。對於數據採集設備,即使沒有進程去讀取數據,也要不斷寫入,隨時等待進程調用,所以驅動程序應該維護一個環形緩衝區,當read調用時能夠隨時返回給用戶空間須要的數據。
(5)PCIe中向CPU發起中斷請求的方式:
· 消息信號中斷(MSI),INTx中斷。
· 在MSI中斷方式下,設備經過向OS預先分配的主存空間寫入特定數據的方式請求CPU的中斷服務,爲PCIe系統首選的中斷信號機制,對於PCIe到PCI/PCI-X的橋接設備和不能使用MSI機制的傳統端點設備,採用INTx虛擬中斷機制。
· PCIe設備註冊中斷時使用共享中斷方式,Linux系統經過request_irq實現中斷處理程序的註冊,調用位置在設備第一次打開,硬件產生中斷以前;一樣,free_irq時機在最後一次關閉設備,硬件不用中斷處理器以後。
· 中斷處理函數的功能是將有關中斷接收的信息反饋給設備,並對數據進行相應讀寫。中斷信號到來,系統調用相應的中斷處理函數,函數判斷中斷號是否匹配,如果,則清除中斷寄存器相應的位,即在驅動程序發起新的DMA以前設備不會產生其餘中斷,而後進行相應處理。
(6)數據讀寫和ioctl控制:
· 數據讀寫:應用進程不須要數據時,驅動程序動態維護DMA環形緩衝區,當應用進程請求數據,驅動程序經過Linux內核提供copy_from_user()/copy_to_user()實現內核態和用戶態之間的數據拷貝。
· 硬件控制:用戶空間常常回去請求設備鎖門,報告錯誤信息,設置寄存器等,這些操做都經過ioctl支持,能夠對PCIe卡給定的寄存器空間進行配置。
(7)中斷處理程序的註冊:
· 中斷號在BIOS初始化階段分配並寫入設備配置空間,而後Linux在創建pci_dev時從配置空間中讀出該中斷號並寫入pci_dev的irq成員中,因此註冊中斷程序時直接從pci_dev中讀取就行。
· 當設備發生中斷,8259A將中斷號發給CPU,CPU根據中斷號找到中斷處理程序,執行。
(8)DMA數據傳輸機制的產生:
· 傳統經典過程:數據到達網卡 -> 網卡產生一箇中斷給內核 -> 內核使用 I/O 指令,從網卡I/O區域中去讀取數據。這種方式,當大流量數據到來時,網卡會產生大量中斷,內核在中斷上下文中,會浪費大量資源處理中斷自己。
· 改進:NAPI,即輪詢,即內核屏蔽中斷,隔必定時間去問網卡,是否有數據。則在數據量小的狀況下,這種方式會浪費大量資源。
· 另外一個問題,CPU到網卡的I/O區域,包括I/O寄存器和I/O內存中讀取,再放到系統物理內存,都佔用大量CPU資源,作改進,即有了DMA,讓網卡直接從主內存之間讀寫本身的I/O數據。
· 首先,內核在主內存中爲收發數據創建一個環形的緩衝隊列(DMA環形緩衝區),內核將這個緩衝區經過DMA映射,將這個隊列交給網卡;網卡收到數據,直接放進環形緩衝區,即直接放到主內存,而後向系統產生中斷;
· 內核收到中斷,取消DMA映射,能夠直接從主內存中讀取數據。