Linux PCI驱动编写
??這幾天將以前為內核2.6寫的驅動移植到了4.1下,在這里記錄一下過程,以及從頭整理一下linux下pci驅動的編寫方法。
??以前的驅動沒有使用到linux下的probe方法,在4.1內核下成功編譯后,一直無法進入中斷,因此參考ch36x的驅動,重新寫了驅動初始化部分,當應用層的程序可以調用驅動正確讀回采集卡數據的時候,那份欣喜與滿足感是難以言表的。
驅動模塊初始化相關函數定義
??PCI驅動和其它linux驅動一樣,需要定義init和exit兩個函數作為加載模塊的入口點和卸載模塊的出口點,可以使用module_pci_driver宏,只要將pci_driver結構體變量作為參數調用這個宏,就會自動定義init和exit這兩個函數,好處是代碼整潔,缺點就是不夠靈活,我這里仍然用傳統的方法使用module_init和module_exit兩個宏:
module_init( init_m ); module_exit( cleanup_m );??init_m是使用insmod命令加載驅動模塊時調用的函數,cleanup_m是用rmmod卸載模塊時調用的函數。
??在init_m函數中先調用alloc_chrdev_region來給設備分配編號,然后調用class_create函數創建一個設備類,只有調用這個函數后才能夠使用device_create函數在/dev目錄下動態創建設備節點(在probe函數中調用),最后調用pci_register_driver宏把pci專用的結構體pci_driver變量放到pci總線設備組中,讓內核知道有我們這個驅動模塊的存在。
alloc_chrdev_region函數說明如下:
函數原型:
參數說明:
dev:向內核申請下來的設備號
baseminor :次設備號的起始
count: 申請次設備號的個數
name :執行 cat /proc/devices顯示的名稱,由驅動編寫者定義
返回值:執行成功返回0
class_create函數說明如下:
函數原型:
參數說明:
owner:指定類的所有者是哪個模塊,可以用THIS_MODULE獲取本驅動模塊;
name:類名,由驅動編寫者定義。
返回值:創建的類指針。
可以用IS_ERR判斷返回的類指針是否有錯誤,如果有錯誤,則使用PTR_ERR來返回錯誤代碼。
在cleanup_m函數中注銷掉pci驅動結構體以及之前申請到的設備號。
pci_driver結構體:
這個結構體的聲明在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); };我的驅動中定義的有如下幾項:
name:驅動模塊名稱
id_table:PCI設備的廠商ID,設備ID等
probe:總線發現我們設備的時候調用的函數,對于已經插好的PCI板卡,加載驅動模塊的時候就會調用此函數。
remove:移除設備的時候調用,在卸載驅動模塊的時候也會調用。
probe函數
??在這個函數中要為設備的私有數據結構變量分配空間,使能設備,獲取設備所需的資源,注冊中斷等,其中涉及到的相關函數介紹如下:
pci_enable_device:使能設備的IO和內存空間,如果設備處于掛起狀態則喚醒之。
??pci_request_regions:通知內核設備IO和內存空間已經被指定名稱的設備占用,其它就不要再占用了。
??pci_set_drvdata:設置PCI驅動私有數據,本來這個函數將私有數據結構變量和pci_dev關聯起來了,但是在file_operations相關操作中沒有pci_dev獲取方法,所以我最后極不優雅的使用全局變量來記錄設備私有數據。
??request_irq:注冊中斷
??cdev_init:初始化字符設備的cdev變量
??cdev_add:添加一個字符設備到系統中
??device_create:創建字符設備,這個函數調用完成后,在系統的/dev目錄下將會有函數設定的設備文件出現。
remove函數
??此函數清理了probe函數中所申請的資源,注意清理資源時要和申請資源的順序相反。
驅動模塊文件操作相關函數
??之前的工作是將驅動模塊加載到內核中,并且為相關設備分配資源。接下來要想應用層訪問到設備,操控設備,則需要進行一些文件操作。對于應用程序來講,一個設備也就是一個文件。
file_operations結構體
??這個結構體的聲明在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); };??結構體成員函數很多,但是我的驅動只實現了open,release,unlocked_ioctl,read。下面一一介紹。
open函數
??應用層調用open函數時,驅動模塊的open函數就會被調用(有點廢話的感覺),驅動模塊的open函數會傳遞進兩個參數:
??inode結構和file結構,inode結構對于我們比較有用的就是獲取設備索引,獲得設備索引后就可以填充file結構中的private_data成員變量,這個成員變量實際上就是前面probe函數中定義的私有數據成員變量,在我的程序里,我用一個全局指針數組來存放所有設備的私有數據成員指針。偽代碼如下:
release函數在應用層調用close函數時會被調用,它是open的反向操作,就不多說了。
read函數將內核數據復制到應用層,我這里就是將采集卡讀出的數據交給應用程序。
unlocked_ioctl函數
??這個函數在低版本內核中就是ioctl,對應應用層的ioctl調用,在kernel 2.6.36后使用unlocked_ioctl和compat_ioctl代替,其中unlocked_ioctl在應用層和內核層相同位數的時候調用,compat_ioctl在32位應用,64位內核驅動的時候調用。
??在我的驅動中,這個函數是應用程序與設備通訊的主要函數,設置設備參數,讀回設備狀態等操作均在這個函數中完成。偽代碼如下:
??最后在說一點,驅動完成后,我安裝的時候遇到了“-1 Unknown symbol in module”錯誤,需要在文件添加:MODULE_LICENSE(“GPL”);問題解決。
修改設備節點權限,讓非root用戶可以使用
首先看你節點信息:
udevadm info --attribute-walk --name=/dev/pl21080
/dev/pl21080是節點名稱
我的信息:
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就可以讓普通用戶有權限了
總結
以上是生活随笔為你收集整理的Linux PCI驱动编写的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 流程是什么
- 下一篇: linux 其他常用命令