Linux PCI驱动编写
??這幾天將以前為內(nèi)核2.6寫的驅(qū)動移植到了4.1下,在這里記錄一下過程,以及從頭整理一下linux下pci驅(qū)動的編寫方法。
??以前的驅(qū)動沒有使用到linux下的probe方法,在4.1內(nèi)核下成功編譯后,一直無法進(jìn)入中斷,因此參考ch36x的驅(qū)動,重新寫了驅(qū)動初始化部分,當(dāng)應(yīng)用層的程序可以調(diào)用驅(qū)動正確讀回采集卡數(shù)據(jù)的時候,那份欣喜與滿足感是難以言表的。
驅(qū)動模塊初始化相關(guān)函數(shù)定義
??PCI驅(qū)動和其它linux驅(qū)動一樣,需要定義init和exit兩個函數(shù)作為加載模塊的入口點(diǎn)和卸載模塊的出口點(diǎn),可以使用module_pci_driver宏,只要將pci_driver結(jié)構(gòu)體變量作為參數(shù)調(diào)用這個宏,就會自動定義init和exit這兩個函數(shù),好處是代碼整潔,缺點(diǎn)就是不夠靈活,我這里仍然用傳統(tǒng)的方法使用module_init和module_exit兩個宏:
module_init( init_m ); module_exit( cleanup_m );??init_m是使用insmod命令加載驅(qū)動模塊時調(diào)用的函數(shù),cleanup_m是用rmmod卸載模塊時調(diào)用的函數(shù)。
??在init_m函數(shù)中先調(diào)用alloc_chrdev_region來給設(shè)備分配編號,然后調(diào)用class_create函數(shù)創(chuàng)建一個設(shè)備類,只有調(diào)用這個函數(shù)后才能夠使用device_create函數(shù)在/dev目錄下動態(tài)創(chuàng)建設(shè)備節(jié)點(diǎn)(在probe函數(shù)中調(diào)用),最后調(diào)用pci_register_driver宏把pci專用的結(jié)構(gòu)體pci_driver變量放到pci總線設(shè)備組中,讓內(nèi)核知道有我們這個驅(qū)動模塊的存在。
alloc_chrdev_region函數(shù)說明如下:
函數(shù)原型:
參數(shù)說明:
dev:向內(nèi)核申請下來的設(shè)備號
baseminor :次設(shè)備號的起始
count: 申請次設(shè)備號的個數(shù)
name :執(zhí)行 cat /proc/devices顯示的名稱,由驅(qū)動編寫者定義
返回值:執(zhí)行成功返回0
class_create函數(shù)說明如下:
函數(shù)原型:
參數(shù)說明:
owner:指定類的所有者是哪個模塊,可以用THIS_MODULE獲取本驅(qū)動模塊;
name:類名,由驅(qū)動編寫者定義。
返回值:創(chuàng)建的類指針。
可以用IS_ERR判斷返回的類指針是否有錯誤,如果有錯誤,則使用PTR_ERR來返回錯誤代碼。
在cleanup_m函數(shù)中注銷掉pci驅(qū)動結(jié)構(gòu)體以及之前申請到的設(shè)備號。
pci_driver結(jié)構(gòu)體:
這個結(jié)構(gòu)體的聲明在include/linux/pci.h里面:
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); };我的驅(qū)動中定義的有如下幾項(xiàng):
name:驅(qū)動模塊名稱
id_table:PCI設(shè)備的廠商ID,設(shè)備ID等
probe:總線發(fā)現(xiàn)我們設(shè)備的時候調(diào)用的函數(shù),對于已經(jīng)插好的PCI板卡,加載驅(qū)動模塊的時候就會調(diào)用此函數(shù)。
remove:移除設(shè)備的時候調(diào)用,在卸載驅(qū)動模塊的時候也會調(diào)用。
probe函數(shù)
??在這個函數(shù)中要為設(shè)備的私有數(shù)據(jù)結(jié)構(gòu)變量分配空間,使能設(shè)備,獲取設(shè)備所需的資源,注冊中斷等,其中涉及到的相關(guān)函數(shù)介紹如下:
pci_enable_device:使能設(shè)備的IO和內(nèi)存空間,如果設(shè)備處于掛起狀態(tài)則喚醒之。
??pci_request_regions:通知內(nèi)核設(shè)備IO和內(nèi)存空間已經(jīng)被指定名稱的設(shè)備占用,其它就不要再占用了。
??pci_set_drvdata:設(shè)置PCI驅(qū)動私有數(shù)據(jù),本來這個函數(shù)將私有數(shù)據(jù)結(jié)構(gòu)變量和pci_dev關(guān)聯(lián)起來了,但是在file_operations相關(guān)操作中沒有pci_dev獲取方法,所以我最后極不優(yōu)雅的使用全局變量來記錄設(shè)備私有數(shù)據(jù)。
??request_irq:注冊中斷
??cdev_init:初始化字符設(shè)備的cdev變量
??cdev_add:添加一個字符設(shè)備到系統(tǒng)中
??device_create:創(chuàng)建字符設(shè)備,這個函數(shù)調(diào)用完成后,在系統(tǒng)的/dev目錄下將會有函數(shù)設(shè)定的設(shè)備文件出現(xiàn)。
remove函數(shù)
??此函數(shù)清理了probe函數(shù)中所申請的資源,注意清理資源時要和申請資源的順序相反。
驅(qū)動模塊文件操作相關(guān)函數(shù)
??之前的工作是將驅(qū)動模塊加載到內(nèi)核中,并且為相關(guān)設(shè)備分配資源。接下來要想應(yīng)用層訪問到設(shè)備,操控設(shè)備,則需要進(jìn)行一些文件操作。對于應(yīng)用程序來講,一個設(shè)備也就是一個文件。
file_operations結(jié)構(gòu)體
??這個結(jié)構(gòu)體的聲明在include/linux/fs.h文件中
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);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);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);int (*show_fdinfo)(struct seq_file *m, struct file *f); };??結(jié)構(gòu)體成員函數(shù)很多,但是我的驅(qū)動只實(shí)現(xiàn)了open,release,unlocked_ioctl,read。下面一一介紹。
open函數(shù)
??應(yīng)用層調(diào)用open函數(shù)時,驅(qū)動模塊的open函數(shù)就會被調(diào)用(有點(diǎn)廢話的感覺),驅(qū)動模塊的open函數(shù)會傳遞進(jìn)兩個參數(shù):
??inode結(jié)構(gòu)和file結(jié)構(gòu),inode結(jié)構(gòu)對于我們比較有用的就是獲取設(shè)備索引,獲得設(shè)備索引后就可以填充file結(jié)構(gòu)中的private_data成員變量,這個成員變量實(shí)際上就是前面probe函數(shù)中定義的私有數(shù)據(jù)成員變量,在我的程序里,我用一個全局指針數(shù)組來存放所有設(shè)備的私有數(shù)據(jù)成員指針。偽代碼如下:
release函數(shù)在應(yīng)用層調(diào)用close函數(shù)時會被調(diào)用,它是open的反向操作,就不多說了。
read函數(shù)將內(nèi)核數(shù)據(jù)復(fù)制到應(yīng)用層,我這里就是將采集卡讀出的數(shù)據(jù)交給應(yīng)用程序。
unlocked_ioctl函數(shù)
??這個函數(shù)在低版本內(nèi)核中就是ioctl,對應(yīng)應(yīng)用層的ioctl調(diào)用,在kernel 2.6.36后使用unlocked_ioctl和compat_ioctl代替,其中unlocked_ioctl在應(yīng)用層和內(nèi)核層相同位數(shù)的時候調(diào)用,compat_ioctl在32位應(yīng)用,64位內(nèi)核驅(qū)動的時候調(diào)用。
??在我的驅(qū)動中,這個函數(shù)是應(yīng)用程序與設(shè)備通訊的主要函數(shù),設(shè)置設(shè)備參數(shù),讀回設(shè)備狀態(tài)等操作均在這個函數(shù)中完成。偽代碼如下:
??最后在說一點(diǎn),驅(qū)動完成后,我安裝的時候遇到了“-1 Unknown symbol in module”錯誤,需要在文件添加:MODULE_LICENSE(“GPL”);問題解決。
修改設(shè)備節(jié)點(diǎn)權(quán)限,讓非root用戶可以使用
首先看你節(jié)點(diǎn)信息:
udevadm info --attribute-walk --name=/dev/pl21080
/dev/pl21080是節(jié)點(diǎn)名稱
我的信息:
looking at device ‘/devices/pci0000:00/0000:00:1c.1/0000:02:00.0/0000:03:0c.0/pl2108_class/pl21080’:
KERNEL==“pl21080”
SUBSYSTEM==“pl2108_class”
DRIVER==“”
然后在/etc/udev/rules.d目錄下新建文件
vi 10-myrule.rules
添加
SUBSYSTEM==“pl2108_class”, MODE=“0666”
保存退出
此時再insmod就可以讓普通用戶有權(quán)限了
總結(jié)
以上是生活随笔為你收集整理的Linux PCI驱动编写的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 流程是什么
- 下一篇: docker安装配置 阿里云加速器