日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

PCI驱动编程

發布時間:2023/12/18 编程问答 100 豆豆
生活随笔 收集整理的這篇文章主要介紹了 PCI驱动编程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、字符設備和塊設備
Linux抽象了對硬件的處理,所有的硬件設備都可以像普通文件一樣來看待:它們可以使用和操作文件相同的、標準的系統調用接口來完成打開、關閉、讀寫和I/O控制操作,而驅動程序的主要任務也就是要實現這些系統調用函數。Linux系統中的所有硬件設備都使用一個特殊的設備文件來表示,例如,系統中的第一個IDE硬盤使用/dev/hda表示。每個設備文件對應有兩個設備號:一個是主設備號,標識該設備的種類,也標識了該設備所使用的驅動程序;另一個是次設備號,標識使用同一設備驅動程序的不同硬件設備。設備文件的主設備號必須與設備驅動程序在登錄該設備時申請的主設備號一致,否則用戶進程將無法訪問到設備驅動程序。

在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內核會根據該設備文件的設備類型和主設備號調用相應的驅動程序,并從用戶態進入到核心態,再由驅動程序判斷該設備的次設備號,最終完成對相應硬件的操作。

二、設備驅動程序接口
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 __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); };

當應用程序對設備文件進行諸如open、close、read、write等操作時,Linux內核將通過file_operations結構訪問驅動程 序提供的函數。例如,當應用程序對設備文件執行讀操作時,內核將調用file_operations結構中的read函數。

三、設備驅動程序模塊
Linux下的設備驅動程序可以按照兩種方式進行編譯,一種是直接靜態編譯成內核的一部分,另一種則是編譯成可以動態加載的模塊。如果編譯進內核的話,會增加內核的大小,還要改動內核的源文件,而且不能動態地卸載,不利于調試,所有推薦使用模塊方式。

從本質上來講,模塊也是內核的一部分,它不同于普通的應用程序,不能調用位于用戶態下的C或者C++庫函數,而只能調用Linux內核提供的函數,在/proc/ksyms中可以查看到內核提供的所有函數。

在以模塊方式編寫驅動程序時,要實現兩個必不可少的函數init_module( )和cleanup_module( ),而且至少要包含和兩 個頭文件。一般使用LDD3 例程中使用的makefile 作為基本的版本,稍作改變之后用來編譯驅動,編譯生成的模塊(一般為.ko文件)可以使用命令insmod載入Linux內核,從而成為內核的一個組成部分,此時內核會調用 模塊中的函數init_module( )。當不需要該模塊時,可以使用rmmod命令進行卸載,此進內核會調用模塊中的函數cleanup_module( )。任何時候都可以使用命令來lsmod查看目前已經加載的模塊以及正在使用該模塊的用戶數。

四、設備驅動程序結構
了解設備驅動程序的基本結構(或者稱為框架),對開發人員而言是非常重要的,Linux的設備驅動程序大致可以分為如下幾個部分:驅動程序的注冊與注銷、設備的打開與釋放、設備的讀寫操作、設備的控制操作、設備的中斷和輪詢處理。

1.驅動程序的注冊與注銷
向系統增加一個驅動程序意味著要賦予它一個主設備號,這可以通過在驅動程序的初始化過程中調用alloc_chrdev_region( )或者register_chrdev_region( )來完成。而在關閉字符設備時,則需要通過調用unregister_chrdev_region( )從內核中注銷設備,同時釋放占用的主設備號。

2.設備的打開與釋放
打開設備是通過調用file_operations結構中的函數open( )來完成的,它是驅動程序用來為今后的操作完成初始化準備工作的。在大部分驅動程序中,open( )通常需要完成下列工作:
a. 檢查設備相關錯誤,如設備尚未準備好等。
b. 如果是第一次打開,則初始化硬件設備。
c. 識別次設備號,如果有必要則更新讀寫操作的當前位置指針f_ops。
d. 分配和填寫要放在file->private_data里的數據結構。
e. 使用計數增1。
釋放設備是通過調用file_operations結構中的函數release( )來完成的,這個設備方法有時也被稱為close( ),它的作用正好與open( )相反,通常要完成下列工作:
a. 使用計數減1。
b. 釋放在file->private_data中分配的內存。
c. 如果使用計算為0,則關閉設備。

3.設備的讀寫操作
字符設備的讀寫操作相對比較簡單,直接使用函數read( )和write( )就可以了。但如果是塊設備的話,則需要調用函數block_read( )和block_write( )來進行數據讀寫,這兩個函數將向設備請求表中增加讀寫請求,以便Linux內核可以對請求順序進行優化。由于是對內存緩沖區而不是直接對設備進行操作的,因此能很大程度上加快讀寫速度。如果內存緩沖區中沒有所要讀入的數據,或者需要執行寫操作將數據寫入設備,那么就要執行真正的數據傳輸,這是通過調用數據結構blk_dev_struct中的函數request_fn( )來完成的。

4.設備的控制操作
除了讀寫操作外,應用程序有時還需要對設備進行控制,這可以通過設備驅動程序中的函數ioctl( )來完成,ioctl 系統調用有下面的原型: int ioctl(int fd, unsigned long cmd, …),第一個參數是文件描述符,第二個參數是具體的命令,一般使用宏定義來確定,第三個參數一般是傳遞給驅動中處理設備控制操作函數的參數。ioctl( )的用法與具體設備密切關聯,因此需要根據設備的實際情況進行具體分析。

5.設備的中斷和輪詢處理
對于不支持中斷的硬件設備,讀寫時需要輪流查詢設備狀態,以便決定是否繼續進行數據傳輸。如果設備支持中斷,則可以按中斷方式進行操作。

五、PCI驅動程序框架
1.關鍵數據結構
PCI設備上有三種地址空間:PCI的I/O空間、PCI的存儲空間和PCI的配置空間。CPU可以訪問PCI設備上的所有地址空間,其中I/O空間和存儲空間提供給設備驅動程序使用,而配置空間則由Linux內核中的PCI初始化代碼使用。內核在啟動時負責對所有PCI設備進行初始化,配置好所有的PCI設備,包括中斷號以及I/O基址,并在文件/proc/pci中列出所有找到的PCI設備,以及這些設備的參數和屬性。

Linux驅動程序通常使用結構(struct)來表示一種設備,而結構體中的變量則代表某一具體設備,該變量存放了與該設備相關的所有信息。好的驅動程序都應該能驅動多個同種設備,每個設備之間用次設備號進行區分,如果采用結構數據來代表所有能由該驅動程序驅動的設備,那么就可以簡單地使用數組下標來表示次設備號。

在PCI驅動程序中,下面幾個關鍵數據結構起著非常核心的作用:
a. pci_driver
這個數據結構在文件include/linux/pci.h里,其中最主要的是用于識別設備的id_table結構,以及用于檢測設備的函數probe( )和卸載設備的函數remove( ):

struct pci_driver {struct list_head node;const char *name;const struct pci_device_id *id_table; /* must be non-NULL for probe to be called */int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */int (*suspend) (struct pci_dev *dev, pm_message_t state); /* Device suspended */int (*suspend_late) (struct pci_dev *dev, pm_message_t state);int (*resume_early) (struct pci_dev *dev);int (*resume) (struct pci_dev *dev); /* Device woken up */void (*shutdown) (struct pci_dev *dev);int (*sriov_configure) (struct pci_dev *dev, int num_vfs); /* PF pdev */const struct pci_error_handlers *err_handler;struct device_driver driver;struct pci_dynids dynids; };

b. pci_dev
這個數據結構也在文件include/linux/pci.h里,它詳細描述了一個PCI設備幾乎所有的硬件信息,包括廠商ID、設備ID、各種資源等

struct pci_dev {struct list_head bus_list; /* node in per-bus list */struct pci_bus *bus; /* bus this device is on */struct pci_bus *subordinate; /* bus this device bridges to */void *sysdata; /* hook for sys-specific extension */struct proc_dir_entry *procent; /* device entry in /proc/bus/pci */struct pci_slot *slot; /* Physical slot this device is in */unsigned int devfn; /* encoded device & function index */unsigned short vendor;unsigned short device;unsigned short subsystem_vendor;unsigned short subsystem_device;unsigned int class; /* 3 bytes: (base,sub,prog-if) */u8 revision; /* PCI revision, low byte of class word */u8 hdr_type; /* PCI header type (`multi' flag masked out) */u8 pcie_type; /* PCI-E device/port type */u8 rom_base_reg; /* which config register controls the ROM */u8 pin; /* which interrupt pin this device uses */struct pci_driver *driver; /* which driver has allocated this device */u64 dma_mask; /* Mask of the bits of bus address this device implements. Normally this is 0xffffffff. You only need to change this if your device has broken DMA or supports 64-bit transfers. */struct device_dma_parameters dma_parms;pci_power_t current_state; /* Current operating state. In ACPI-speak, this is D0-D3, D0 being fully functional, and D3 being off. */int pm_cap; /* PM capability offset in the configuration space */unsigned int pme_support:5; /* Bitmask of states from which PME# can be generated */unsigned int d1_support:1; /* Low power state D1 is supported */unsigned int d2_support:1; /* Low power state D2 is supported */unsigned int no_d1d2:1; /* Only allow D0 and D3 */unsigned int wakeup_prepared:1; #ifdef CONFIG_PCIEASPMstruct pcie_link_state *link_state; /* ASPM link state. */ #endifpci_channel_state_t error_state; /* current connectivity state */struct device dev; /* Generic device interface */int cfg_size; /* Size of configuration space *//** Instead of touching interrupt line and base address registers* directly, use the values stored here. They might be different!*/unsigned int irq;struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs *//* These fields are used by common fixups */unsigned int transparent:1; /* Transparent PCI bridge */unsigned int multifunction:1;/* Part of multi-function device *//* keep track of device state */unsigned int is_added:1;unsigned int is_busmaster:1; /* device is busmaster */unsigned int no_msi:1; /* device may not use msi */unsigned int block_ucfg_access:1; /* userspace config space access is blocked */unsigned int broken_parity_status:1; /* Device generates false positive parity */unsigned int irq_reroute_variant:2; /* device needs IRQ rerouting variant */unsigned int msi_enabled:1;unsigned int msix_enabled:1;unsigned int ari_enabled:1; /* ARI forwarding */unsigned int is_managed:1;unsigned int is_pcie:1;unsigned int needs_freset:1; /* Dev requires fundamental reset */unsigned int state_saved:1;unsigned int is_physfn:1;unsigned int is_virtfn:1;unsigned int reset_fn:1;unsigned int is_hotplug_bridge:1;pci_dev_flags_t dev_flags;atomic_t enable_cnt; /* pci_enable_device has been called */u32 saved_config_space[16]; /* config space saved at suspend time */struct hlist_head saved_cap_space;struct bin_attribute *rom_attr; /* attribute descriptor for sysfs ROM entry */int rom_attr_enabled; /* has display of the rom attribute been enabled? */struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* sysfs file for WC mapping of resources */ #ifdef CONFIG_PCI_MSIstruct list_head msi_list; #endifstruct pci_vpd *vpd; #ifdef CONFIG_PCI_IOVunion {struct pci_sriov *sriov; /* SR-IOV capability related */struct pci_dev *physfn; /* the PF this VF is associated with */};struct pci_ats *ats; /* Address Translation Service */ #endif };

2.基本框架
在用模塊方式實現PCI設備驅動程序時,通常至少要實現以下幾個部分:初始化設備模塊、設備打開模塊、數據讀寫和控制模塊、中斷處理模塊、設備釋放模塊、設備卸載模塊。下面給出一個典型的PCI設備驅動程序的基本框架,從中不難體會到這幾個關鍵模塊是如何組織起來的。

/* 指明該驅動程序適用于哪一些PCI設備 */ static struct pci_device_id demo_pci_tbl[] ={{PCI_DEVICE(PCI_VENDOR_ID_DEMO,PCI_DEVICE_ID_DEMO),},{0,}, };/* 對特定PCI設備進行描述的數據結構 */ struct device_private { /* ... */};/* 中斷處理模塊 */ static irqreturn_t device_interrupt(int irq, void *dev_id) { /* ... */}static int demo_open(struct inode *inode,struct file *file) {/* ... */try_module_get(THIS_MODULE);return 0; }static int demo_read(struct file *file,char __user *buffer,size_t count,loff_t *offp) { /* ... */}static int demo_write(struct file *file,const char __user *buffer,size_t count,loff_t *offp) { /* ... */}static int demo_mmap(struct file *file, struct vm_area_struct *vma) { /* ... */}static int demo_ioctl(struct inode *inode,struct file *file, unsigned int cmd,unsigned long arg) { switch(cmd){ case CMD1: device_func(arg); break; ... default: } /* ... */}static int demo_release(struct inode *inode,struct file *file) {/* ... */ module_put(THIS_MODULE); return 0; }/* 設備文件操作接口 */ static struct file_operations demo_fops={.owner = THIS_MODULE, /* demo_fops所屬的設備模塊 */.read = demo_read, /* 讀設備操作*/.write = demo_write, /* 寫設備操作*/.open = demo_open, /* 打開設備操作*/.ioctl = demo_ioctl, /* 控制設備操作*/.mmap = demo_mmap, /* 內存重映射操作*/.release = demo_release, /* 釋放設備操作*/ /* ... */}; static int __init demo_probe(struct pci_dev *pci_dev,const struct pci_device_id *pci_id) { /* ... */}; static void __devexit demo_remove(struct pci_dev *pci_dev) { /* ... */}; /* 設備模塊信息 */ 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) { pci_register_driver(&demo_pci_driver); //注冊設備驅動 /* ... */}static void __exit demo_exit_module(void) {/* ... */ pci_unregister_driver(&demo_pci_driver); }/* 加載驅動程序模塊入口 */ module_init(demo_init_module);/* 卸載驅動程序模塊入口 module_exit(demo_exit_module); */

上面這段代碼給出了一個典型的PCI設備驅動程序的框架,是一種相對固定的模式。需要注意的是,同加載和卸載模塊相關的函數或數據結構都要在前面加上 __init、__exit等標志符,以使同普通函數區分開來。構造出這樣一個框架之后,接下去的工作就是如何完成框架內的各個功能模塊了。

六、框架的具體實現之模塊操作
1.struct pci_device_id
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_device_id被用在struct pci_device 中。在示例中,創建了一個結構體數組,每一個結構表明使用該結構體數組的驅動支持的設備,數組的最后一個值是全部設置為0的空結構體,也就是{0,}。這個結構體需要被導出到用戶空間,使熱插拔和模塊裝載系統知道什么模塊對應什么硬件設備,宏MODULE_DEVICE_TABLE完成這個工作。例如:

MODULE_DEVICE_TABLE(pci, demo_pci_tbl);

2.初始化設備模塊
在Linux系統下,想要完成對一個PCI設備的初始化,需要完成以下工作:
a. 檢查PCI總線是否被Linux內核支持;
b. 檢查設備是否插在總線插槽上,如果在的話則保存它所占用的插槽的位置等信息。
c. 讀出配置頭中的信息提供給驅動程序使用。

當Linux內核啟動并完成對所有PCI設備進行掃描、登錄和分配資源等初始化操作的同時,會建立起系統中所有PCI設備的拓撲結構。系統加載模塊是調用pci_init_module函數,在這個函數中我們通過pci_register_driver 把new_pci_driver注冊到系統中。在調用pci_register_driver時,需要提供一個pci_driver結構。這個函數首先檢測id_table中定義的PCI信息是否和系統中的PCI信息有匹配,如果有則返回0,匹配成功后調用probe函數對PCI設備進行進一步的操作。

static int __init demo_init_module (void) { /* allocate (several) major number */ret = alloc_chrdev_region(&devno, 0, MAX_DEVICE, "buffer"); ret= pci_register_driver(&demo_pci_driver); }

probe函數的作用就是啟動pci設備,讀取配置空間信息,進行相應的初始化。

static int __init demo_probe(struct pci_dev *pci_dev,const struct pci_device_id *pci_id) {int result;printk("probe function is running\n");struct device_privdata *privdata;privdata->pci_dev = pci_dev; //把設備指針地址放入PCI設備中的設備指針中,便于后面調用pci_get_drvdatapci_set_drvdata(pci_dev, privdata); /* 啟動PCI設備 */if(pci_enable_device(pci_dev)){printk(KERN_ERR "%s:cannot enable device\n", pci_name(pci_dev)); return -ENODEV;} /*動態申請設備號,把fops傳進去*/privdata->cdev = cdev_alloc();privdata->cdev->ops=&jlas_fops;privdata->cdev->owner = THIS_MODULE;cdev_add(privdata->cdev,devno,1);/*動態創建設備節點*/privdata->cdev_class = class_create(THIS_MODULE,DEV_NAME);device_create(privdata->cdev_class,NULL, devno, pci_dev, DEV_NAME);privdata->irq=pci_dev->irq;privdata->iobase=pci_resource_start(privdata->pci_dev, BAR_IO);/*判斷IO資源是否可用*/if((pci_resource_flags(pci_dev, BAR_IO) & IORESOURCE_IO) != IORESOURCE_IO)goto err_out;/* 對PCI區進行標記 ,標記該區域已經分配出去*/ ret= pci_request_regions(pci_dev, DEVICE_NAME);if(ret) goto err_out;/*初始化tasklet*/tasklet_init(&(privdata->my_tasklet),jlas_1780_do_tasklet,(unsigned long )&jlas_pci_cdev); /* 初始化自旋鎖 */ spin_lock_init(&private->lock); /*初始化等待隊列*/init_waitqueue_head(&(privdata->read_queue)); /* 設置成總線主DMA模式 */ pci_set_master(pci_dev); /*申請內存*/privdata->mem = (u32 *) __get_free_pages(GFP_KERNEL|__GFP_DMA | __GFP_ZERO, memorder);if (!privdata->mem) { goto err_out;} /*DMA映射*/privdata->dma_addrp = pci_map_single(pdev, privdata->mem,PAGE_SIZE * (1 << memorder), PCI_DMA_FROMDEVICE);if (pci_dma_mapping_error(pdev, privdata->dma_addrp)) {goto err_out;} /*對硬件進行初始化設置,往寄存器中寫一些值,復位硬件等*/device_init(xx_device);return 0;err_out:printk("error process\n");resource_cleanup_dev(FCswitch); //如果出現任何問題,釋放已經分配了的資源return ret; }

3.卸載設備模塊
卸載設備模塊與初始化設備模塊是相對應的,實現起來相對比較簡單,主要是調用函數pci_unregister_driver( )從Linux內核中注銷設備驅動程序:

static void __exit demo_cleanup_module (void) {pci_unregister_driver(&demo_pci_driver); }

在卸載模塊時調用pci_cleanup_module,這個函數中通過pci_unregister_driver對new_pci_driver進行注銷,這個會調用到remove函數。remove函數的職責就是釋放一切分配過的資源,根據自己代碼的需要進行具體的操作。

static void __devexit my_pci_remove(struct pci_dev *pci_dev) {struct device_private *private;private= (struct device_private*)pci_get_drvdata(pci_dev); /*對硬件進行操作,如硬件復位*/Device_close(xx_device);pci_unmap_single(pdev, privdata->dma_mem,PAGE_SIZE * (1 << memorder), PCI_DMA_FROMDEVICE); // 釋放分配的內存空間free_pages ((unsigned long) privdata->mem, memorder);pci_clear_master(pdev); /* Nobody seems to do this */tasklet_kill(&(privdata->my_tasklet));pci_release_regions(pci_dev); // 移除動態創建的設備號和設備device_destroy(device_class, device->my_dev);class_destroy(device_class);if(privdata->pci_dev!=NULL)cdev_del(privdata->cdev); privdata->pci_dev=NULL;pci_disable_device(pci_dev);pci_set_drvdata(pci_dev,NULL); }

4.中斷處理
中斷處理,主要就是讀取中斷寄存器,然后調用中斷處理函數來處理中斷的下半部分,一般通過tasklet或者workqueue來實現。
注意:由于使用request_irq 獲得的中斷是共享中斷,因此在中斷處理函數的上半部需要區分是不是該設備發出的中斷,這就需要讀取中斷狀態寄存器的值來判斷,如果不是該設備發起的中斷則 返回 IRQ_NONE

/* 中斷處理模塊 */ void jlas_do_tasklet(unsigned long data) {spin_lock(&(privdata->my_spin_lock));//具體操作spin_unlock(&(privdata->my_spin_lock));wake_up_interruptible(&(privdata->read_queue)); }static irqreturn_t device_interrupt(int irq, void *dev_id) {struct device_privdata *privdata = dev_id;tasklet_schedule(&(privdata->my_tasklet));return IRQ_HANDLED;/* ... */}

七、框架的具體實現之設備文件操作
1.設備文件操作接口
當應用程序對設備文件進行諸如open、close、read、write等操作時,Linux內核將通過file_operations結構訪問驅動程序提供的函數。例如,當應用程序對設備文件執行讀操作時,內核將調用file_operations結構中的read函數。

/* 設備文件操作接口 */ static struct file_operations demo_fops={.owner = THIS_MODULE, /* demo_fops所屬的設備模塊 */.read = demo_read, /* 讀設備操作*/.write = demo_write, /* 寫設備操作*/.open = demo_open, /* 打開設備操作*// .ioctl = demo_ioctl, /* 控制設備操作*/.mmap = demo_mmap, /* 內存重映射操作*/.release = demo_release, /* 釋放設備操作*//* ... */};

2.打開設備
open 方法提供給驅動來做任何的初始化來準備后續的操作.在這個模塊里主要實現申請中斷、檢查讀寫模式以及申請對設備的控制權等。在申請控制權的時候,非阻塞方式遇忙返回,否則進程主動接受調度,進入睡眠狀態,等待其它進程釋放對設備的控制權。 open 方法的原型是:

int (*open)(struct inode *inode, struct file *filp);

inode 參數有我們需要的信息,以它的 i_cdev 成員的形式, 里面包含我們之前建立的cdev 結構. 唯一的問題是通常我們不想要 cdev 結構本身, 我們需要的是包含 cdev 結構的 device_private 結構.

static int demo_open(struct inode *inode, struct file *filp) {struct device_private *private;private= container_of(inode->i_cdev, struct device_private, my_cdev);filp->private_data = private;private->open_flag++; /*申請中斷*/ret = request_irq(privdata->irq,interrupt_handler, IRQF_SHARED, DEV_NAME,privdata);if(ret)return -EINVAL; ...try_module_get(THIS_MODULE);return 0; }

3.釋放設備
release 方法的角色是 open 的反面,設備方法應當進行下面的任務:
a. 釋放 open 分配在 filp->private_data 中的任何東西
b. 在最后的 close 關閉設備

static int demo_release(struct inode *inode,struct file *filp) {struct device_private *private= filp->private_data;private->open_flag--;free_irq(pdev->irq, privdata);module_put(THIS_MODULE);printk("pci device close success\n");return 0; }

4.設備數據讀寫和ioctl
PCI設備驅動程序可以通過device_fops 結構中的函數device_ioctl( ),向應用程序提供對硬件進行控制的接口。例如,通過它可以從I/O寄存器里讀取一個數據,并傳送到用戶空間里。

static int device_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg) {int retval = 0;struct device_private *privdata= filp->private_data; switch (cmd){case CMD1:device_func(arg);break;...default:retval = -EINVAL;}return retval; }

5.內存映射

static int device_mmap(struct file *filp, struct vm_area_struct *vma) {int ret;struct device_private *private = filp->private_data;vma->vm_page_prot = PAGE_SHARED;//訪問權限vma->vm_pgoff = virt_to_phys(FCswitch->rx_buf_virts) >> PAGE_SHIFT;//偏移(頁幀號)ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, (unsigned long)(vma->vm_end-vma->vm_start), vma->vm_page_prot);if(ret!=0)return -EAGAIN;return 0; }

對 remap_pfn_range()函數的說明:
remap_pfn_range()函數的原型

int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);

該函數的功能是創建頁表。其中參數vma是內核根據用戶的請求自己填寫的,而參數addr表示內存映射開始處的虛擬地址,因此,該函數為addr~addr+size之間的虛擬地址構造頁表。

另外,pfn(Page Fram Number)是虛擬地址應該映射到的物理地址的頁面號,實際上就是物理地址右移PAGE_SHIFT位。如果PAGE_SHIFT為4kb,則 PAGE_SHIFT為12,因為PAGE_SHIFT等于1 << PAGE_SHIFT 。

最后一個參數prot是新頁所要求的保護屬性。

在驅動程序中,一般能使用remap_pfn_range()映射內存中的保留頁(如X86系統中的640KB~1MB區域)和設備I/O內存。因此,如 果想把kmalloc()申請的內存映射到用戶空間,則可以通過SetPageReserved把相應的內存設置為保留后就可以。

八、附錄
1.PCI設備私有數據結構

/* 對特定PCI設備進行描述的數據結構 */ struct device_private {/*次設備號*/unsigned int minor; /*注冊字符驅動和發現PCI設備的時候使用*/struct pci_dev *pci_dev;struct cdev *cdev;struct class *cdev_class;/*中斷號*/ unsigned int irq;/* 用于獲取PCI設備配置空間的基本信息 */ unsigned long iobase;/*用于保存分配給PCI設備的內存空間的信息*/ dma_addr_t dma_addrp;char *virts_addr;/*基本的同步手段*/spinlock_t lock;/*等待隊列*/wait_queue_head_t read_queue;/*tasklet*/struct tasklet_struct my_tasklet;/*異步*/struct fasync_struct *async_queue;/*設備打開標記*/int open_flag //* .....*/ };

2.PCI配置寄存器
所有的PCI設備都有至少256字節的地址空間,前64字節是標準化的,而其余的是設備相關的。圖1顯示了設備無關的配置空間的布局。

在Linux系統上,可以通過cat /proc/pci 等命令查看系統中所有PCI設備的類別、型號以及廠商等等信息,那就是從這些寄存器來的。下面是用lspci -x命令截取的部分信息(lspci命令也是使用/proc文件作為其信息來源)(PCI寄存器是小端字節序格式的):

00:00.0 Host bridge: Intel Corp. 440BX/ZX/DX - 82443BX/ZX/DX Host bridge (rev 01) 00: 86 80 90 71 06 00 00 02 01 00 00 06 00 00 00 00 10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20: 00 00 00 00 00 00 00 00 00 00 00 00 ad 15 76 19 30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

那么根據下面的PCI配置寄存器組的結構,這個Host bridge的Vendor ID就是0x8086。

圖1.標準化的PCI配置寄存器

參考資料:
1. LINUX設備驅動程序(第三版)
2. Linux下PCI設備驅動程序開發
http://www.ibm.com/developerworks/cn/linux/l-pci/index.html
3. Linux PCI 設備驅動基本框架(一)
http://www.cnblogs.com/zhuyp1015/archive/2012/06/30/2571400.html
4. Linux PCI 設備驅動基本框架(二)
http://www.cnblogs.com/zhuyp1015/archive/2012/06/30/2571408.html
5. 淺談Linux PCI設備驅動(一)
http://blog.csdn.net/linuxdrivers/article/details/5849698

總結

以上是生活随笔為你收集整理的PCI驱动编程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。